diff --git a/Core/Core.csproj b/Core/Core.csproj index dd8c193..715605d 100644 --- a/Core/Core.csproj +++ b/Core/Core.csproj @@ -52,9 +52,8 @@ ..\packages\fasterflect.2.1.3\lib\net40\Fasterflect.dll True - - False - ..\References\log4net.dll + + ..\packages\log4net.2.0.8\lib\net45-full\log4net.dll ..\packages\MathNet.Numerics.4.5.1\lib\net40\MathNet.Numerics.dll diff --git a/Core/packages.config b/Core/packages.config index 6b1c8c6..b638e53 100644 --- a/Core/packages.config +++ b/Core/packages.config @@ -4,7 +4,7 @@ - + diff --git a/WebCms.Tests/app.config b/WebCms.Tests/app.config index c51dfea..397b743 100644 --- a/WebCms.Tests/app.config +++ b/WebCms.Tests/app.config @@ -8,23 +8,23 @@ - + - + - + - + - + @@ -50,6 +50,10 @@ + + + + \ No newline at end of file diff --git a/WebCms/App_Data/Models/all.dll.path b/WebCms/App_Data/Models/all.dll.path index f73bf4b..bb7f9d9 100644 --- a/WebCms/App_Data/Models/all.dll.path +++ b/WebCms/App_Data/Models/all.dll.path @@ -1 +1 @@ -C:\Users\poprhythm\AppData\Local\Temp\Temporary ASP.NET Files\vs\f80e29bb\faae20bf\App_Web_all.generated.cs.8f9494c4.kym3i-lp.dll \ No newline at end of file +C:\Users\poprhythm\AppData\Local\Temp\Temporary ASP.NET Files\vs\f80e29bb\faae20bf\App_Web_all.generated.cs.8f9494c4.z8ixywcx.dll \ No newline at end of file diff --git a/WebCms/App_Data/Models/all.generated.cs b/WebCms/App_Data/Models/all.generated.cs index 785594d..3f98e66 100644 --- a/WebCms/App_Data/Models/all.generated.cs +++ b/WebCms/App_Data/Models/all.generated.cs @@ -8,7 +8,7 @@ using Umbraco.Web; using Umbraco.ModelsBuilder; using Umbraco.ModelsBuilder.Umbraco; [assembly: PureLiveAssembly] -[assembly:ModelsBuilderAssembly(PureLive = true, SourceHash = "c5118162be4a0dc0")] +[assembly:ModelsBuilderAssembly(PureLive = true, SourceHash = "79624ab1619e0f80")] [assembly:System.Reflection.AssemblyVersion("0.0.0.1")] @@ -18,7 +18,7 @@ using Umbraco.ModelsBuilder.Umbraco; // // This code was generated by a tool. // -// Umbraco.ModelsBuilder v3.0.5.96 +// Umbraco.ModelsBuilder v3.0.10.102 // // Changes to this file will be lost if the code is regenerated. // @@ -166,9 +166,9 @@ namespace Umbraco.Web.PublishedContentModels /// Site Logo /// [ImplementPropertyType("siteLogo")] - public object SiteLogo + public string SiteLogo { - get { return this.GetPropertyValue("siteLogo"); } + get { return this.GetPropertyValue("siteLogo"); } } /// @@ -475,9 +475,9 @@ namespace Umbraco.Web.PublishedContentModels /// Upload file /// [ImplementPropertyType("umbracoFile")] - public object UmbracoFile + public string UmbracoFile { - get { return this.GetPropertyValue("umbracoFile"); } + get { return this.GetPropertyValue("umbracoFile"); } } } diff --git a/WebCms/App_Data/Models/models.generated.cs b/WebCms/App_Data/Models/models.generated.cs index 3853653..b330ca2 100644 --- a/WebCms/App_Data/Models/models.generated.cs +++ b/WebCms/App_Data/Models/models.generated.cs @@ -2,7 +2,7 @@ // // This code was generated by a tool. // -// Umbraco.ModelsBuilder v3.0.5.96 +// Umbraco.ModelsBuilder v3.0.10.102 // // Changes to this file will be lost if the code is regenerated. // @@ -19,7 +19,7 @@ using Umbraco.ModelsBuilder; using Umbraco.ModelsBuilder.Umbraco; [assembly: PureLiveAssembly] -[assembly:ModelsBuilderAssembly(PureLive = true, SourceHash = "c5118162be4a0dc0")] +[assembly:ModelsBuilderAssembly(PureLive = true, SourceHash = "79624ab1619e0f80")] [assembly:System.Reflection.AssemblyVersion("0.0.0.1")] namespace Umbraco.Web.PublishedContentModels @@ -150,9 +150,9 @@ namespace Umbraco.Web.PublishedContentModels /// Site Logo /// [ImplementPropertyType("siteLogo")] - public object SiteLogo + public string SiteLogo { - get { return this.GetPropertyValue("siteLogo"); } + get { return this.GetPropertyValue("siteLogo"); } } /// @@ -459,9 +459,9 @@ namespace Umbraco.Web.PublishedContentModels /// Upload file /// [ImplementPropertyType("umbracoFile")] - public object UmbracoFile + public string UmbracoFile { - get { return this.GetPropertyValue("umbracoFile"); } + get { return this.GetPropertyValue("umbracoFile"); } } } diff --git a/WebCms/App_Data/Models/models.hash b/WebCms/App_Data/Models/models.hash index 2261b5f..e9c0a40 100644 --- a/WebCms/App_Data/Models/models.hash +++ b/WebCms/App_Data/Models/models.hash @@ -1 +1 @@ -c5118162be4a0dc0 \ No newline at end of file +79624ab1619e0f80 \ No newline at end of file diff --git a/WebCms/Config/BackOfficeTours/getting-started.json b/WebCms/Config/BackOfficeTours/getting-started.json new file mode 100644 index 0000000..0508676 --- /dev/null +++ b/WebCms/Config/BackOfficeTours/getting-started.json @@ -0,0 +1,493 @@ +[ + { + "name": "Introduction", + "alias": "umbIntroIntroduction", + "group": "Getting Started", + "groupOrder": 100, + "allowDisable": true, + "requiredSections": [ + "content", + "media", + "settings", + "developer", + "users", + "member", + "forms" + ], + "steps": [ + { + "title": "Welcome to Umbraco - The Friendly CMS", + "content": "

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.

In this quick tour we will introduce you to the main areas of Umbraco and show you how to best get started.

If you don't want to take the tour now you can always start it by opening the Help drawer in the bottom left corner.

", + "type": "intro" + }, + { + "element": "#applications", + "elementPreventClick": true, + "title": "Main Menu", + "content": "This is the main menu in Umbraco backoffice. Here you can navigate between the different sections, see your user profile and open the help drawer.", + "backdropOpacity": 0.6 + }, + { + "element": "[data-element='section-content']", + "elementPreventClick": true, + "title": "Sections", + "content": "Each area in Umbraco is called a Section. Right now you are in the Content section, when you want to go to another section simply click on the appropriate icon in the main menu and you'll be there in no time.", + "backdropOpacity": 0.6 + }, + { + "element": "#tree", + "elementPreventClick": true, + "title": "The Tree", + "content": "

This is the Tree and it is the main navigation inside a section.

In the Content section the tree is called the Content tree and here you can navigate the content of your website.

" + }, + { + "element": "[data-element='editor-container']", + "elementPreventClick": true, + "title": "Dashboards", + "content": "

A dashboard is the main view you are presented with when entering a section within the backoffice, and can be used to show valuable information to the users of the system.

Notice that some sections have multiple dashboards.

" + }, + { + "element": "[data-element='global-search-field']", + "title": "Search", + "content": "The search allows you to quickly find whatever you're looking for across sections within Umbraco." + }, + { + "element": "#applications [data-element='section-user']", + "title": "User profile", + "content": "Now click on your user avatar to open the user profile dialog.", + "event": "click", + "backdropOpacity": 0.6 + }, + { + "element": "[data-element~='overlay-user']", + "elementPreventClick": true, + "title": "User profile", + "content": "

Here you can see details about your user, what Umbraco version the site is running, change your password and log out of Umbraco.

In the User section you will be able to do more advanced user management.

" + }, + { + "element": "[data-element~='overlay-user'] [data-element='button-overlayClose']", + "title": "User profile", + "content": "Let's close the user profile again.", + "event": "click" + }, + { + "element": "#applications [data-element='section-help']", + "title": "Help", + "content": "If you ever find yourself in trouble click here to open the Help drawer.", + "event": "click", + "backdropOpacity": 0.6 + }, + { + "element": "[data-element='drawer']", + "elementPreventClick": true, + "title": "Help", + "content": "

In the help drawer you will find articles and videos related to the section you are using.

This is also where you will find the next tour on how to get started with Umbraco.

", + "backdropOpacity": 0.6 + }, + { + "element": "[data-element='drawer'] [data-element='help-tours']", + "title": "Tours", + "content": "To continue your journey on getting started with Umbraco, you can find more tours right here." + } + ] + }, + { + "name": "Create document type", + "alias": "umbIntroCreateDocType", + "group": "Getting Started", + "groupOrder": 100, + "requiredSections": [ + "content", + "media", + "settings", + "developer", + "users", + "member", + "forms" + ], + "steps": [ + { + "title": "Create your first Document Type", + "content": "

Step 1 of any site is to create a Document Type.
A Document Type is a template for content. For each type of content you want to create you'll create a Document Type. This will define where content based on this Document Type can be created, how many properties it holds and what the input method should be for these properties.

When you have at least one Document Type in place you can start creating content and this content can then be used in a template.

In this tour you will learn how to set up a basic Document Type with a property to enter a short text.

", + "type": "intro" + }, + { + "element": "#applications [data-element='section-settings']", + "title": "Navigate to the Settings sections", + "content": "In the Settings section you can create and manage Document types.", + "event": "click", + "backdropOpacity": 0.6 + }, + { + "element": "#tree [data-element='tree-item-documentTypes']", + "title": "Create Document Type", + "content": "

Hover over the Document Type tree and click the three small dots to open the context menu.

", + "event": "click", + "eventElement": "#tree [data-element='tree-item-documentTypes'] [data-element='tree-item-options']" + }, + { + "element": "#dialog [data-element='action-documentType']", + "title": "Create Document Type", + "content": "

Click Document Type to create a new document type with a template. The template will be automatically created and set as the default template for this Document Type.

You will use the template in a later tour to render content.

", + "event": "click" + }, + { + "element": "[data-element='editor-name-field']", + "title": "Enter a name", + "content": "

Your Document Type needs a name. Enter Home Page in the field and click Next.", + "view": "doctypename" + }, + { + "element": "[data-element='editor-description']", + "title": "Enter a description", + "content": "

A description helps to pick the right document type when creating content.

Write a description for our Home page. It could be:

The home page of the website

" + }, + { + "element": "[data-element='group-add']", + "title": "Add tab", + "content": "Tabs are used to organize properties on content in the Content section. Click Add new tab to add a tab.", + "event": "click" + }, + { + "element": "[data-element='group-name-field']", + "title": "Name the tab", + "content": "

Enter Home in the tab name.

You can name a tab anything you want and if you have a lot of properties it can be useful to add multiple tabs.

", + "view": "tabName" + }, + { + "element": "[data-element='property-add']", + "title": "Add a property", + "content": "

Properties are the different input fields on a content page.

On our Home Page we want to add a welcome text.

Click Add property to open the property dialog.

", + "event": "click" + }, + { + "element": "[data-element~='overlay-property-settings'] [data-element='property-name']", + "title": "Name the property", + "content": "Enter Welcome Text as the name for the property.", + "view": "propertyname" + }, + { + "element": "[data-element~='overlay-property-settings'] [data-element='property-description']", + "title": "Enter a description", + "content": "

A description will help your editor fill in the right content.

Enter a description for the property editor. It could be:

Write a nice introduction text so the visitors feel welcome

" + }, + { + "element": "[data-element~='overlay-property-settings'] [data-element='editor-add']", + "title": "Add editor", + "content": "When you add an editor you choose what the input method for this property will be. Click Add editor to open the editor picker dialog.", + "event": "click" + }, + { + "element": "[data-element~='overlay-editor-picker']", + "elementPreventClick": true, + "title": "Editor picker", + "content": "

In the editor picker dialog we can pick one of the many built-in editors.

You can choose from preconfigured data types (Reuse) or create a new configuration (Available editors).

" + }, + { + "element": "[data-element~='overlay-editor-picker'] [data-element='editor-Textarea']", + "title": "Select editor", + "content": "Select the Textarea editor. This will add a textarea to the Welcome Text property.", + "event": "click" + }, + { + "element": "[data-element~='overlay-editor-settings']", + "elementPreventClick": true, + "title": "Editor settings", + "content": "Each property editor can have individual settings. For the textarea editor you can set a character limit but in this case it is not needed." + }, + { + "element": "[data-element~='overlay-editor-settings'] [data-element='button-overlaySubmit']", + "title": "Save editor", + "content": "Click Submit to save the changes.", + "event": "click" + }, + { + "element": "[data-element~='overlay-property-settings'] [data-element='button-overlaySubmit']", + "title": "Add property to document type", + "content": "Click Submit to add the property to the document type.", + "event": "click" + }, + { + "element": "[data-element~='sub-view-permissions']", + "title": "Check the document type permissions", + "content": "Click Permissions to view the permissions page.", + "event": "click" + }, + { + "element": "[data-element~='permissions-allow-as-root']", + "title": "Allow this document type to work at the root of your site", + "content": "Toggle the switch Allow as root to allow new content pages based on this document type to be created at the root of your site", + "event": "click" + }, + { + "element": "[data-element='button-save']", + "title": "Save the document type", + "content": "All we need now is to save the document type. Click Save to create and save your new document type.", + "event": "click" + } + ] + }, + { + "name": "Create Content", + "alias": "umbIntroCreateContent", + "group": "Getting Started", + "groupOrder": 100, + "requiredSections": [ + "content", + "media", + "settings", + "developer", + "users", + "member", + "forms" + ], + "steps": [ + { + "title": "Creating your first content node", + "content": "

In this tour you will learn how to create the home page for your website. It will use the Home Page Document type you created in the previous tour.

", + "type": "intro" + }, + { + "element": "#applications [data-element='section-content']", + "title": "Navigate to the Content section", + "content": "

In the Content section you can create and manage the content of the website.

The Content section contains the content of your website. Content is displayed as nodes in the content tree.

", + "event": "click", + "backdropOpacity": 0.6 + }, + { + "element": "[data-element='tree-root']", + "title": "Open context menu", + "content": "

Open the context menu by hovering over the root of the content section.

Now click the three small dots to the right.

", + "event": "click", + "eventElement": "#tree [data-element='tree-root'] [data-element='tree-item-options']" + }, + { + "element": "[data-element='action-create-homePage']", + "title": "Create Home page", + "content": "

The context menu shows you all the actions that are available on a node

Click on Home Page to create a new page of type Home Page.

", + "event": "click" + }, + { + "element": "[data-element='editor-content'] [data-element='editor-name-field']", + "title": "Give your new page a name", + "content": "

Our new page needs a name. Enter Home in the field and click Next.

", + "view": "nodename" + }, + { + "element": "[data-element='editor-content'] [data-element='property-welcomeText']", + "title": "Add a welcome text", + "content": "

Add content to the Welcome Text field.

If you don't have any ideas here is a start:

I am learning Umbraco. High Five I Rock #H5IR
.

" + }, + { + "element": "[data-element='editor-content'] [data-element='button-saveAndPublish']", + "title": "Save and Publish", + "content": "

Now click the Save and publish button to save and publish your changes.

", + "event": "click" + } + ] + }, + { + "name": "Render in template", + "alias": "umbIntroRenderInTemplate", + "group": "Getting Started", + "groupOrder": 100, + "requiredSections": [ + "content", + "media", + "settings", + "developer", + "users", + "member", + "forms" + ], + "steps": [ + { + "title": "Render your content in a template", + "content": "

Templating in Umbraco builds on the concept of Razor Views from ASP.NET MVC. This tour is a sneak peak on how to write templates in Umbraco.

In this tour you will learn how to render content from the Home Page document type so you can see the content added to our Home content page.

", + "type": "intro" + }, + { + "element": "#applications [data-element='section-settings']", + "title": "Navigate to the Settings section", + "content": "

In the Settings section you will find all the templates.

It is of course also possible to edit all your code files in your favorite code editor.

", + "event": "click", + "backdropOpacity": 0.6 + }, + { + "element": "#tree [data-element='tree-item-templates']", + "title": "Expand the Templates node", + "content": "

To see all our templates click the small triangle to the left of the templates node.

", + "event": "click", + "eventElement": "#tree [data-element='tree-item-templates'] [data-element='tree-item-expand']", + "view": "templatetree" + }, + { + "element": "#tree [data-element='tree-item-templates'] [data-element='tree-item-Home Page']", + "title": "Open Home template", + "content": "

Click the Home Page template to open and edit it.

", + "eventElement": "#tree [data-element='tree-item-templates'] [data-element='tree-item-Home Page'] a.umb-tree-item__label", + "event": "click" + }, + { + "element": "[data-element='editor-templates'] [data-element='code-editor']", + "title": "Edit template", + "content": "

The template can be edited here or in your favorite code editor.

To render the field from the document type add the following to the template:

<h1>@Model.Content.Name</h1>
<p>@Model.Content.WelcomeText</p>

" + }, + { + "element": "[data-element='editor-templates'] [data-element='button-save']", + "title": "Save the template", + "content": "Click the Save button and your template will be saved.", + "event": "click" + } + ] + }, + { + "name": "View Home page", + "alias": "umbIntroViewHomePage", + "group": "Getting Started", + "groupOrder": 100, + "requiredSections": [ + "content", + "media", + "settings", + "developer", + "users", + "member", + "forms" + ], + "steps": [ + { + "title": "View your Umbraco site", + "content": "

Our three main components for a page are done: Document type, Template, and Content. It is now time to see the result.

In this tour you will learn how to see your published website.

", + "type": "intro" + }, + { + "element": "#applications [data-element='section-content']", + "title": "Navigate to the content sections", + "content": "In the Content section you will find the content of our website.", + "event": "click", + "backdropOpacity": 0.6 + }, + { + "element": "#tree [data-element='tree-item-Home']", + "title": "Open the Home page", + "content": "

Click the Home page to open it.

", + "event": "click", + "eventElement": "#tree [data-element='tree-item-Home'] a.umb-tree-item__label" + }, + { + "element": "[data-element='editor-content'] [data-element='tab-_umb_infoTab']", + "title": "Info", + "content": "

Under the info tab you will find the default information about a content item.

", + "event": "click" + }, + { + "element": "[data-element='editor-content'] [data-element='node-info-urls']", + "title": "Open page", + "content": "

Click the Link to document to view your page.

Tip: Click the preview button in the bottom right corner to preview changes without publishing them.

", + "event": "click", + "eventElement": "[data-element='editor-content'] [data-element='node-info-urls'] a[target='_blank']" + } + ] + }, + { + "name": "The Media library", + "alias": "umbIntroMediaSection", + "group": "Getting Started", + "groupOrder": 100, + "requiredSections": [ + "content", + "media", + "settings", + "developer", + "users", + "member", + "forms" + ], + "steps": [ + { + "title": "How to use the media library", + "content": "

A website would be boring without media content. In Umbraco you can manage all your images, documents, videos etc. in the Media section. Here you can upload and organise your media items and see details about each item.

In this tour you will learn how to upload and organise your Media library in Umbraco. It will also show you how to view details about a specific media item.

", + "type": "intro" + }, + { + "element": "#applications [data-element='section-media']", + "title": "Navigate to the Media section", + "content": "The media section is where you manage all your media items.", + "event": "click", + "backdropOpacity": 0.6 + }, + { + "element": "#tree [data-element='tree-root']", + "title": "Create a new folder", + "content": "

First create a folder for your images. Hover over the media root node and click the three small dots on the right side of the item.

", + "event": "click", + "eventElement": "#tree [data-element='tree-root'] [data-element='tree-item-options']" + }, + { + "element": "#dialog [data-element='action-Folder']", + "title": "Create a new folder", + "content": "

Select the Folder option to select the type folder.

", + "event": "click" + }, + { + "element": "[data-element='editor-media'] [data-element='editor-name-field']", + "title": "Enter a name", + "content": "

Enter My Images in the field.

", + "view": "foldername" + }, + { + "element": "[data-element='editor-media'] [data-element='button-save']", + "title": "Save the folder", + "content": "

Click the Save button to create the new folder.

", + "event": "click" + }, + { + "element": "[data-element='editor-media'] [data-element='dropzone']", + "title": "Upload images", + "content": "

In the upload area you can upload your media items.

Click the Click here to choose files button and select a couple of images on your computer and upload them.

", + "view": "uploadimages" + }, + { + "element": "[data-element='editor-media'] [data-element='media-grid-item-0']", + "title": "View media item details", + "content": "Hover over the media item and Click the purple bar to view details about the media item.", + "event": "click", + "eventElement": "[data-element='editor-media'] [data-element='media-grid-item-0'] [data-element='media-grid-item-edit']" + }, + { + "element": "[data-element='editor-media'] [data-element='property-umbracoFile']", + "elementPreventClick": true, + "title": "The uploaded image", + "content": "

Here you can see the image you have uploaded.

" + }, + { + "element": "[data-element='editor-media'] [data-element='property-umbracoBytes']", + "title": "Image size", + "content": "

You will also find other details about the image, like the size.

Media items work in much the same way as content. So you can add extra properties to an image by creating or editing the Media types in the Settings section.

" + }, + { + "element": "[data-element='editor-media'] [data-element='tab-_umb_infoTab']", + "title": "Info", + "content": "Like the content section you can also find default information about the media item. You will find these under the info tab.", + "event": "click" + }, + { + "element": "[data-element='editor-media'] [data-element='node-info-urls']", + "title": "Link to media", + "content": "The path to the media item..." + }, + { + "element": "[data-element='editor-media'] [data-element='node-info-update-date']", + "title": "Last edited", + "content": "...and information about when the media item has been created and edited." + }, + { + "element": "[data-element='editor-container']", + "elementPreventClick": true, + "title": "Using media items", + "content": "You can reference a media item directly in a template by using the path or try adding a Media Picker to a document type property so you can select media items from the content section." + } + ] + } +] diff --git a/WebCms/Config/ClientDependency.config b/WebCms/Config/ClientDependency.config index 9239d4c..e3d17db 100644 --- a/WebCms/Config/ClientDependency.config +++ b/WebCms/Config/ClientDependency.config @@ -10,7 +10,7 @@ NOTES: * Compression/Combination/Minification is not enabled unless debug="false" is specified on the 'compiliation' element in the web.config * A new version will invalidate both client and server cache and create new persisted files --> - + + - + - + - + - + + + + + + json + + + - + @@ -116,4 +124,11 @@ + + + + + + + diff --git a/WebCms/Config/ExamineIndex.config b/WebCms/Config/ExamineIndex.config index 67cf3ad..300f75b 100644 --- a/WebCms/Config/ExamineIndex.config +++ b/WebCms/Config/ExamineIndex.config @@ -8,10 +8,10 @@ More information and documentation can be found on GitHub: https://github.com/Sh --> - + - + @@ -24,5 +24,5 @@ More information and documentation can be found on GitHub: https://github.com/Sh - + \ No newline at end of file diff --git a/WebCms/Config/HealthChecks.config b/WebCms/Config/HealthChecks.config new file mode 100644 index 0000000..1eb7b0e --- /dev/null +++ b/WebCms/Config/HealthChecks.config @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + diff --git a/WebCms/Config/UrlRewriting.config b/WebCms/Config/UrlRewriting.config deleted file mode 100644 index 9f32219..0000000 --- a/WebCms/Config/UrlRewriting.config +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - diff --git a/WebCms/Config/feedProxy.config b/WebCms/Config/feedProxy.config index bc9a916..38e7d27 100644 --- a/WebCms/Config/feedProxy.config +++ b/WebCms/Config/feedProxy.config @@ -8,4 +8,5 @@ + diff --git a/WebCms/Config/grid.editors.config.js b/WebCms/Config/grid.editors.config.js index 1adb6da..12fa726 100644 --- a/WebCms/Config/grid.editors.config.js +++ b/WebCms/Config/grid.editors.config.js @@ -1,82 +1,27 @@ [ - { - "name": "Rich text editor", - "alias": "rte", - "view": "rte", - "icon": "icon-article" - }, - { - "name": "Image", - "alias": "media", - "view": "media", - "icon": "icon-picture" - }, - { - "name": "Image wide", - "alias": "media_wide", - "view": "media", - "render": "/App_Plugins/Grid/Editors/Render/media_wide.cshtml", - "icon": "icon-picture" - }, - { - "name": "Image wide cropped", - "alias": "media_wide_cropped", - "view": "media", - "render": "media", - "icon": "icon-picture", - "config": { - "size": { - "width": 1920, - "height": 700 - } - } - }, - { - "name": "Image rounded", - "alias": "media_round", - "view": "media", - "render": "/App_Plugins/Grid/Editors/Render/media_round.cshtml", - "icon": "icon-picture" - }, - { - "name": "Image w/ text right", - "alias": "media_text_right", - "view": "/App_Plugins/Grid/Editors/Views/media_with_description.html", - "render": "/App_Plugins/Grid/Editors/Render/media_text_right.cshtml", - "icon": "icon-picture" - }, - { - "name": "Macro", - "alias": "macro", - "view": "macro", - "icon": "icon-settings-alt" - }, - { - "name": "Embed", - "alias": "embed", - "view": "embed", - "render": "/App_Plugins/Grid/Editors/Render/embed_videowrapper.cshtml", - "icon": "icon-movie-alt" - }, - { - "name": "Banner Headline", - "alias": "banner_headline", - "view": "textstring", - "icon": "icon-coin", - "config": { - "style": "font-size: 36px; line-height: 45px; font-weight: bold; text-align:center", - "markup": "

#value#

" - } + { + "name": "Rich text editor", + "alias": "rte", + "view": "rte", + "icon": "icon-article" }, { - "name": "Banner Tagline", - "alias": "banner_tagline", - "view": "textstring", - "icon": "icon-coin", - "config": { - "style": "font-size: 25px; line-height: 35px; font-weight: normal; text-align:center", - "markup": "

#value#

" - } + "name": "Image", + "alias": "media", + "view": "media", + "icon": "icon-picture" + }, + { + "name": "Macro", + "alias": "macro", + "view": "macro", + "icon": "icon-settings-alt" + }, + { + "name": "Embed", + "alias": "embed", + "view": "embed", + "icon": "icon-movie-alt" }, { "name": "Headline", @@ -89,63 +34,13 @@ } }, { - "name": "Headline centered", - "alias": "headline_centered", + "name": "Quote", + "alias": "quote", "view": "textstring", - "icon": "icon-coin", + "icon": "icon-quote", "config": { - "style": "font-size: 30px; line-height: 45px; font-weight: bold; text-align:center;", - "markup": "

#value#

" + "style": "border-left: 3px solid #ccc; padding: 10px; color: #ccc; font-family: serif; font-style: italic; font-size: 18px", + "markup": "
#value#
" } - }, - { - "name": "Abstract", - "alias": "abstract", - "view": "textstring", - "icon": "icon-coin", - "config": { - "style": "font-size: 16px; line-height: 20px; font-weight: bold;", - "markup": "

#value#

" - } - }, - { - "name": "Paragraph", - "alias": "paragraph", - "view": "textstring", - "icon": "icon-font", - "config": { - "style": "font-size: 16px; line-height: 20px; font-weight: light;", - "markup": "

#value#

" - } - }, - { - "name": "Quote", - "alias": "quote", - "view": "textstring", - "icon": "icon-quote", - "config": { - "style": "border-left: 3px solid #ccc; padding: 10px; color: #ccc; font-family: serif; font-variant: italic; font-size: 18px", - "markup": "
#value#
" - } - }, - { - "name": "Quote with description", - "alias": "quote_D", - "view": "/App_Plugins/Grid/Editors/Views/quote_with_description.html", - "render": "/App_Plugins/Grid/Editors/Render/quote_with_description.cshtml", - "icon": "icon-quote", - "config": { - "style": "border-left: 3px solid #ccc; padding: 10px; color: #ccc; font-family: serif; font-variant: italic; font-size: 18px" - } - }, - { - "name": "Code", - "alias": "code", - "view": "textstring", - "icon": "icon-code", - "config": { - "style": "overflow: auto;padding: 6px 10px;border: 1px solid #ddd;border-radius: 3px;background-color: #f8f8f8;font-size: .9rem;font-family: 'Courier 10 Pitch', Courier, monospace;line-height: 19px;", - "markup": "
#value#
" - } - } + } ] \ No newline at end of file diff --git a/WebCms/Config/imageprocessor/cache.config b/WebCms/Config/imageprocessor/cache.config new file mode 100644 index 0000000..841ac21 --- /dev/null +++ b/WebCms/Config/imageprocessor/cache.config @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/WebCms/Config/imageprocessor/processing.config b/WebCms/Config/imageprocessor/processing.config new file mode 100644 index 0000000..363d0e5 --- /dev/null +++ b/WebCms/Config/imageprocessor/processing.config @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebCms/Config/imageprocessor/security.config b/WebCms/Config/imageprocessor/security.config new file mode 100644 index 0000000..7bce8ee --- /dev/null +++ b/WebCms/Config/imageprocessor/security.config @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebCms/Config/tinyMceConfig.config b/WebCms/Config/tinyMceConfig.config index 09cdffb..f338305 100644 --- a/WebCms/Config/tinyMceConfig.config +++ b/WebCms/Config/tinyMceConfig.config @@ -27,7 +27,6 @@ undo Undo - Remove Format images/editor/undo.gif undo 11 @@ -233,7 +232,6 @@ code codemirror paste - umbracolink anchor charmap table @@ -241,7 +239,7 @@ hr - diff --git a/WebCms/Config/trees.config b/WebCms/Config/trees.config index c2df969..8c1e162 100644 --- a/WebCms/Config/trees.config +++ b/WebCms/Config/trees.config @@ -11,27 +11,27 @@ - + - + - + + - + - - + + - - - + + @@ -45,4 +45,4 @@ - \ No newline at end of file + diff --git a/WebCms/Config/umbracoSettings.config b/WebCms/Config/umbracoSettings.config index c700307..d19e505 100644 --- a/WebCms/Config/umbracoSettings.config +++ b/WebCms/Config/umbracoSettings.config @@ -1,13 +1,17 @@ - + - + + + + + @@ -36,11 +40,7 @@ - In Preview Mode - click to end]]> - - - - 1800 + In Preview Mode - click to end]]> - ashx,aspx,ascx,config,cshtml,vbhtml,asmx,air,axd,swf,xml,html,htm,svg,php,htaccess + ashx,aspx,ascx,config,cshtml,vbhtml,asmx,air,axd,swf,xml,xhtml,html,htm,svg,php,htaccess Textstring + + + true + + + assets/img/installer.jpg + false + + true false @@ -152,7 +161,10 @@ Configure it here if you need anything specific. Needs to be a complete url with scheme and umbraco path, eg http://mysite.com/umbraco. NOT just "mysite.com" or "mysite.com/umbraco" or "http://mysite.com". --> - + diff --git a/WebCms/Media/Web.config b/WebCms/Media/Web.config index 6cbb733..cd48da3 100644 --- a/WebCms/Media/Web.config +++ b/WebCms/Media/Web.config @@ -6,4 +6,4 @@ - \ No newline at end of file + diff --git a/WebCms/Umbraco/Config/Create/UI.xml b/WebCms/Umbraco/Config/Create/UI.xml index 3e2c60a..e93a08f 100644 --- a/WebCms/Umbraco/Config/Create/UI.xml +++ b/WebCms/Umbraco/Config/Create/UI.xml @@ -1,4 +1,4 @@ - +
Template
@@ -27,7 +27,7 @@ /create/simple.ascx - +
Macro
diff --git a/WebCms/Umbraco/Config/Lang/cs.xml b/WebCms/Umbraco/Config/Lang/cs.xml index 50d5c0d..939b4ff 100644 --- a/WebCms/Umbraco/Config/Lang/cs.xml +++ b/WebCms/Umbraco/Config/Lang/cs.xml @@ -32,10 +32,8 @@ Odeslat k publikování Odeslat k překladu Seřadit - Odeslat k publikování Přeložit Aktualizovat - Výchozí hodnota Přístup zakázán. @@ -469,14 +467,6 @@ Výchozí uživatel byl deaktivován, nebo nemá přístup k umbracu!

Netřeba nic dalšího dělat. Klikněte na Následující pro pokračování.]]> Heslo výchozího uživatele bylo úspěšně změněno od doby instalace!

Netřeba nic dalšího dělat. Klikněte na Následující pro pokračování.]]> Heslo je změněno! - - umbraco vytváří výchozího uživatele s uživatelským jménem ('admin') a heslem ('default'). Je důležité změnit toto heslo na jiné. -

-

- Tento krok zkontroluje heslo výchozího uživatele a doporučí, má-li být změněno. -

- ]]>
Mějte skvělý start, sledujte naše uváděcí videa Kliknutím na tlačítko následující (nebo modifikováním umbracoConfigurationStatus v souboru web.config) přijímáte licenci tohoto software tak, jak je uvedena v poli níže. Upozorňujeme, že tato distribuce umbraca se skládá ze dvou různých licencí, open source MIT licence pro framework a umbraco freeware licence, která pokrývá UI. Není nainstalováno. @@ -676,7 +666,7 @@ Ochrana prostřednictvím rolí použijte členské skupiny umbraca.]]> - autentizaci prostřednictvím rolí]]> + Musíte vytvořit členskou skupinu před tím, než můžete použít autentizaci prostřednictvím rolí Chybová stránka Použita, když jsou lidé přihlášení, ale nemají přístup Vyberte, jak omezit přístup k této stránce @@ -781,7 +771,7 @@ Creation date Třídění bylo ukončeno. Abyste nastavili, jak mají být položky seřazeny, přetáhněte jednotlivé z nich nahoru či dolů. Anebo klikněte na hlavičku sloupce pro setřídění celé kolekce -
Během třídění nezavírejte toto okno]]>
+ Publikování bylo zrušeno doplňkem třetí strany @@ -858,6 +848,12 @@ Šablona + Rich Text Editor + Image + Macro + Embed + Headline + Quote Choose type of content Choose a layout Add a row @@ -911,8 +907,6 @@ Vložit za polem Vložit před polem Rekurzivní - Odstranit tagy odstavce - Odstraní jakékoliv &lt;P&gt; na začátku a na konci textu Standardní pole Velká písmena Kódování URL @@ -1039,8 +1033,6 @@ Úvodní uzel v obsahu Uživatelské jméno Oprávnění uživatele - Typ uživatele - Typy uživatelů Spisovatel Váš profil Vaše nedávná historie diff --git a/WebCms/Umbraco/Config/Lang/da.xml b/WebCms/Umbraco/Config/Lang/da.xml index 058f0f6..47c0164 100644 --- a/WebCms/Umbraco/Config/Lang/da.xml +++ b/WebCms/Umbraco/Config/Lang/da.xml @@ -2,7 +2,7 @@ The Umbraco community - http://our.umbraco.org/documentation/Extending-Umbraco/Language-Files + https://our.umbraco.com/documentation/Extending-Umbraco/Language-Files Tilføj domæne @@ -12,12 +12,12 @@ Kopier Opret Opret pakke + Opret gruppe Slet Deaktivér Tøm papirkurv + Aktivér Eksportér dokumenttype - Eksportér til .NET - Eksportér til .NET Importér dokumenttype Importér pakke Redigér i Canvas @@ -26,22 +26,51 @@ Notificeringer Offentlig adgang Udgiv - Fortryd udgivelse + Afpublicér Genindlæs elementer Genudgiv hele sitet + Omdøb Gendan Sæt rettigheder for siden %0% - Hvor vil du flytte - hen til i træstrukturen? + Vælg hvortil du vil flytte + I træstrukturen nedenfor Rettigheder Fortryd ændringer Send til udgivelse Send til oversættelse + Sæt gruppe Sortér - Send til udgivelse Oversæt Opdatér - Standard værdi + Sæt rettigheder + Lås op + Opret indholdsskabelon + + + Indhold + Administration + Struktur + Andet + + + Tillad adgang til at tildele sprog og domæner + Tillad adgang for at få vist en nodes historik + Tillad adgang for at få vist en node + Tillad adgang til at ændre dokumenttype for en node + Tillad adgang til at kopiere en node + Tillad adgang til at oprette noder + Tillad adgang til at slette noder + Tillad adgang til at flytte en node + Tillad adgang til at indstille og ændre offentlig adgang til en node + Tillad adgang til at udgive en node + Tillad adgang til at ændre rettigheder for en node + Tillad adgang til at returnere en node til en tidligere tilstand + Tillad adgang til at sende en node til godkendelse før den udgives + Tillad adgang til at sende en node til oversættelse + Tillad adgang til at ændre sorteringsrækkefølge for noder + Tillad adgang til at oversætte en node + Tillad adgang til at gemme en node + Tillad adgang til at oprette en indholdsskabelon Tilladelse nægtet. @@ -58,9 +87,13 @@ Domænet '%0%' er nu opdateret eller rediger nuværende domæner f.eks. ditdomaene.com, www.ditdomaene.com - - - For + Nedarv + Sprog + + eller nedarv sprog fra forældre noder. Gælder også
+ for den aktuelle node, medmindre et domæne nedenfor også indstiller et sprog.]]> +
+ Domæner Ryd valg @@ -87,14 +120,42 @@ Tilbage til listen Gem Gem og udgiv + Gem og planlæg Gem og send til udgivelse Gem listevisning - Se siden - Preview er deaktiveret fordi der ikke er nogen skabelon tildelt + Forhåndsvisning + Forhåndsvisning er deaktiveret fordi der ikke er nogen skabelon tildelt Vælg formattering Vis koder Indsæt tabel Generer modeller + Gem og generer modeller + Fortryd + Genskab + Slet tag + Fortryd + Bekræft + + + For + Brugeren har slettet indholdet + Brugeren har afpubliceret indholdet + Brugeren har gemt og udgivet indholdet + Brugeren har gemt indholdet + Brugeren har flyttet indholdet + Brugeren har kopieret indholdet + Brugeren har tilbagerullet indholdet til en tidligere tilstand + Brugeren har sendt indholdet til udgivelse + Brugeren har sendt indholdet til oversættelse + Kopieret + Udgivet + Flyttet + Gemt + Slettet + Afpubliceret + Indhold tilbagerullet + Sendt til udgivelse + Sendt til oversættelse For at skifte det valgte indholds dokumenttype, skal du først vælge en ny dokumenttype, som er gyldig på denne placering. @@ -134,29 +195,40 @@ Dette punkt er ændret siden udgivelsen Dette punkt er endnu ikke udgivet Sidst udgivet + Der er ingen elementer at vise Der er ingen elementer at vise på listen. + Intet indhold er blevet tilføjet + Ingen medlemmer er blevet tilføjet Medietype Link til medie(r) Medlemsgruppe Rolle Medlemstype + Der er endnu ikke lavet nogle ændringer. Ingen dato valgt Sidetitel Egenskaber Dette dokument er udgivet, men ikke synligt da den overliggende side '%0%' ikke er udgivet! Upd: dette dokument er udgiver, men er ikke i cachen (intern fejl) + Kunne ikke hente url'en + Dette dokument er udgivet, men dets url ville kollidere med indholdet %0% + Udgiv + Udgivet + Udgivet (Ventede ændringer) Udgivet Udgivelsesstatus Udgivelsesdato - Dato for Fortryd udgivelse + Afpubliceringsdato Fjern dato - Sorteringrækkefølgen er opdateret + Vælg dato + Sorteringsrækkefølgen er opdateret For at sortere, træk siderne eller klik på en af kolonnehovederne. Du kan vælge flere sider ved at holde "shift" eller "control" nede mens du vælger. Statistik Titel (valgfri) Alternativ tekst (valgfri) Type - Fortryd udgivelse + Afpublicér + Afpubliceret Sidst redigeret Tidspunkt for seneste redigering Fjern fil @@ -165,14 +237,33 @@ Ikke medlem af grupper(ne) Undersider Åben i vindue + Dette oversætter til den følgende tid på serveren: + Hvad betyder det?]]> + Er du sikker på, at du vil slette dette element? + Egenskaben %0% anvender editoren %1% som ikke er understøttet af Nested Content. + Tilføj en ny tekstboks + Fjern denne tekstboks + Indholdsrod + + + Opret en ny indholdsskabelon fra '%0%' + Blank + Vælg en indholdsskabelon + Indholdsskabelon oprettet + En indholdsskabelon blev oprettet fra '%0%' + En anden indholdsskabelon med samme navn eksisterer allerede + En indholdskabelon er foruddefineret indhold, som en redaktør kan vælge at bruge som grundlag for at oprette nyt indhold Klik for at uploade Slip filerne her... Link til medie eller klik her for at vælge filer + Du kan trække filer herind for at uploade Tilladte filtyper er kun + Kan ikke uploade denne fil, den har ikke en godkendt filtype Maks filstørrelse er + Medie rod Opret et nyt medlem @@ -181,12 +272,21 @@ Hvor ønsker du at oprette den nye %0% Opret under + Vælg den dokumenttype, du vil oprette en indholdsskabelon til Vælg en type og skriv en titel "dokument typer".]]> "media typer".]]> - Dokument type uden skabelon + Dokumenttype uden skabelon Ny mappe Ny datatype + Ny JavaScript-fil + Ny tom partial view + Ny mappe + Ny partial view makro + Ny partial view fra snippet + Ny tom partial view makro + Ny partial view makro fra snippet + Ny partial view makro (uden makro) Til dit website @@ -232,6 +332,8 @@ Kopierede %0% ud af %1% elementer + Link titel + Link Navn på lokalt link Rediger domæner Luk denne dialog @@ -259,11 +361,14 @@ Denne makro har ingen egenskaber du kan redigere Indsæt tekst Rediger rettigheder for + Sæt rettigheder for + Sæt rettigheder for %0% for brugergruppe %1% + Vælg de brugergrupper, du vil angive tilladelser til Elementerne i papirkurven slettes. Luk venligst ikke dette vindue mens sletningen foregår Papirkurven er nu tom Når elementer slettes fra papirkurven, slettes de for altid regexlib.com's webservice oplever i øjeblikket problemer, vi ikke har kontrol over. Beklager ulejligheden. ]]> - Søg efter et regulært udtryk for at tilføje validering til et formularfelt. Eksempel: 'email', 'zip-code', 'url' + Søg efter et regulært udtryk for at tilføje validering til et formularfelt. Eksempel: 'e-mail', 'postnr.', 'url' Fjern makro Obligatorisk Sitet er genindekseret @@ -282,19 +387,28 @@ Link til side Åben linket i et nyt vindue eller fane Link til medie + Link til fil + Vælg indhold startnode Vælg medie Vælg ikon Vælg item Vælg link Vælg makro Vælg indhold + Vælg medie startnode Vælg medlem Vælg medlemsgruppe + Vælg node + Vælg sektioner + Vælg brugere + Ingen ikoner blev fundet Der er ingen parametre for denne makro + Der er ikke tilføjet nogen makroer Link dit Fjern link fra dit konto Vælg editor + Vælg snippet @@ -309,17 +423,24 @@ Navnet '%0%' eksisterer allerede. ]]> + Ordbog Indtast dit brugernavn Indtast dit kodeord + Bekræft dit kodeord Navngiv %0%... Indtast navn... + Indtast en e-mail... + Indtast et brugernavn... Label... Indtast beskrivelse Søg... Filtrer... Indtast nøgleord (tryk på Enter efter hvert nøgleord)... + Indtast din e-mail + Indtast en besked... + Dit brugernavn er typisk din e-mailadresse Tillad på rodniveau @@ -339,6 +460,11 @@ Opret brugerdefineret listevisning Fjern brugerdefineret listevisning + + Omdøbt + Indtast et ny mappenavn her + %0% was renamed to %1% + Tilføj førværdi Database-datatype @@ -351,6 +477,13 @@ Relaterede stylesheets Vis label Bredde og højde + All property types & property data + using this data type will be deleted permanently, please confirm you want to delete these as well + Ja, slet + and all property types & property data using this data type + Vælg den mappe, der skal flyttes + til i træstrukturen nedenfor + blev flyttet under Dine data er blevet gemt, men før du kan udgive denne side er der nogle fejl der skal rettes: @@ -419,18 +552,24 @@ Rediger Redigeret Elementer - Email + E-mail Fejl Find + Første + Grupper Højde Hjælp + Skjul + Historik Ikon Importer Indre margen Indsæt Installér + Ugyldig Justering Sprog + Sidste Layout Henter Låst @@ -438,6 +577,8 @@ Log af Log ud Makro + Påkrævet + Besked Flyt Mere Navn @@ -448,29 +589,35 @@ OK Åben eller + Sortér efter Kodeord Sti Placeholder ID Et øjeblik... Forrige Egenskaber - Email der skal modtage indhold af formular + E-mail der skal modtage indhold af formular Papirkurv Din papirkurv er tom Mangler + Fjern Omdøb Forny Påkrævet + Hent Prøv igen Rettigheder + Planlagt publicering Søg + Beklager, vi kan ikke finde det, du leder efter. + Ingen elementer er blevet tilføjet Server Vis Hvilken side skal vises efter at formularen er sendt Størrelse Sortér + Status Indsend - Type Skriv for at søge... Op @@ -496,6 +643,7 @@ Gemmer... nuværende Indlejring + Hent valgt @@ -525,12 +673,22 @@ Brug listevisning Tillad på rodniveau + + Comment/Uncomment lines + Remove line + Copy Lines Up + Copy Lines Down + Move Lines Up + Move Lines Down + + General + Editor Baggrundsfarve Fed - Tekst farve + Tekstfarve Skrifttype Tekst @@ -551,7 +709,7 @@ Næste for at fortsætte.]]> Databasen er ikke fundet. Kontrollér venligst at informationen i database forbindelsesstrengen i "web.config" filen er korrekt.

-

For at fortsætte bedes du venligst rette "web.config" filen (ved at bruge Visual Studio eller dit favoritprogram), scroll til bunden, tilføj forbindelsesstrengen til din database i feltet som hedder "umbracoDbDSN" og gem filen.

Klik på Forsøg igen knappen når du er færdig.
Mere information om at redigere web.config her.

]]> +

For at fortsætte bedes du venligst rette "web.config" filen (ved at bruge Visual Studio eller dit favoritprogram), scroll til bunden, tilføj forbindelsesstrengen til din database i feltet som hedder "umbracoDbDSN" og gem filen.

Klik på Forsøg igen knappen når du er færdig.
Mere information om at redigere web.config her.

]]>
Kontakt venligst din ISP hvis det er nødvendigt. Hvis du installerer på en lokal maskine eller server kan du muligvis få informationerne fra din systemadministrator.]]> Tryk på Opgradér knappen for at opgradere din database til Umbraco %0%

Bare rolig - intet indhold vil blive slettet og alt vil stadig fungere bagefter!

]]>
@@ -561,20 +719,19 @@ Normalbrugeren er blevet gjort utjenstdygtig eller har ikke adgang til Umbraco!

Du behøver ikke foretage yderligere handlinger. Tryk på Næste for at fortsætte.

]]>
Normalbrugerens adgangskode er på succesfuld vis blevet ændret siden installationen!

Du behøver ikke foretage yderligere handlinger. Tryk på Næste for at fortsætte.

]]>
Adgangskoden er blevet ændret! - Umbraco opretter en normalbruger med et login ('admin') og en adgangskode ('default').

Det er vigtigt at adgangskoden bliver ændret til noget unikt.

Dette skridt vil kontrollere normalbrugerens adgangskode og foreslå om det er nødt til at blive ændret.

]]>
Få en fremragende start, se vores videoer Ved at klikke på næste knappen (eller ved at ændre UmbracoConfigurationStatus i web.config filen), accepterer du licensaftalen for denne software, som specificeret i boksen nedenfor. Bemærk venligst at denne Umbraco distribution består af to forskellige licenser, MIT's Open Source Licens for frameworket og Umbraco Freeware Licensen som dækker UI'en. Endnu ikke installeret Berørte filer og foldere Flere informationer om at opsætte rettigheder for Umbraco her Du er nødt til at give ASP.NET 'modify' rettigheder på følgende filer/foldere - Dine rettighedsinstillinger er næsten perfekte!

Du kan køre Umbraco uden problemer, men du vil ikke være i stand til at installere pakker, som er anbefalet for at få fuldt udbytte af Umbraco.]]>
+ Dine rettighedsindstillinger er næsten perfekte!

Du kan køre Umbraco uden problemer, men du vil ikke være i stand til at installere pakker, som er anbefalet for at få fuldt udbytte af Umbraco.]]>
Hvorledes besluttes Klik her for at læse tekstversionen video tutorials om at opsætte folderrettigheder for Umbraco eller læs tekstversionen.]]> - Dine rettighedsinstillinger kan være et problem!

Du kan afvikle Umbraco uden problemer, men du vil ikke være i stand til at oprette foldere eller installere pakker, hvilket er anbefalet for at få fuldt udbytte af Umbraco.]]>
- Dine rettighedsinstillinger er ikke klar til Umbraco!

For at afvikle Umbraco er du nødt til at opdatere dine rettighedsinstillinger.]]>
- Dine rettighedsinstillinger er perfekte!

Du er nu parat til at afvikle Umbraco og installere pakker!]]>
+ Dine rettighedsindstillinger kan være et problem!

Du kan afvikle Umbraco uden problemer, men du vil ikke være i stand til at oprette foldere eller installere pakker, hvilket er anbefalet for at få fuldt udbytte af Umbraco.]]>
+ Dine rettighedsindstillinger er ikke klar til Umbraco!

For at afvikle Umbraco er du nødt til at opdatere dine rettighedsindstillinger.]]>
+ Dine rettighedsindstillinger er perfekte!

Du er nu parat til at afvikle Umbraco og installere pakker!]]>
Løser folder problem Følg dette link for mere information om udfordringer med ASP.NET og oprettelse af foldere Sætter folderrettigheder op @@ -586,7 +743,7 @@ Dette er vores liste over anbefalede moduler. Kryds dem af du ønsker at installere eller se den fulde liste af moduler ]]> Kun anbefalet for erfarne brugere Jeg ønsker at begynder med et simpelt website - "Runway" er et simpelt website som stiller nogle basale dokumenttyper og skabeloner til rådighed. Instaleringsprogrammet kan automatisk opsætte Runway for dig, men du kan nemt redigere, udvide eller fjerne det. Det er ikke nødvendigt og du kan sagtens bruge Umbraco uden. Men Runway tilbyder et fundament, som er baseret på 'Best Practices', som får dig igang hurtigere end nogensinde før. Hvis du vælger at installere Runway, kan du efter eget valg vælge de grundlæggende byggesten kaldet 'Runway Modules' til at forbedre dine Runway-sider.

Inkluderet med Runway:Home Page, Getting Started page, Installing Modules page.
Valgfri Moduler: Top Navigation, Sitemap, Contact, Gallery.

]]>
+ "Runway" er et simpelt website som stiller nogle basale dokumenttyper og skabeloner til rådighed. Installeringsprogrammet kan automatisk opsætte Runway for dig, men du kan nemt redigere, udvide eller fjerne det. Det er ikke nødvendigt og du kan sagtens bruge Umbraco uden. Men Runway tilbyder et fundament, som er baseret på 'Best Practices', som får dig igang hurtigere end nogensinde før. Hvis du vælger at installere Runway, kan du efter eget valg vælge de grundlæggende byggesten kaldet 'Runway Modules' til at forbedre dine Runway-sider.

Inkluderet med Runway:Home Page, Getting Started page, Installing Modules page.
Valgfri Moduler: Top Navigation, Sitemap, Contact, Gallery.

]]>
Hvad er Runway Skridt 1/5: Acceptér licens Skridt 2/5: Database-konfiguration @@ -633,6 +790,8 @@ Glemt adgangskode? En e-mail vil blive sendt til den angivne adresse med et link til at nulstille din adgangskode En e-mail med instruktioner for nulstilling af adgangskoden vil blive sendt til den angivne adresse, hvis det matcher vores optegnelser + Vis adgangskode + Skjul adgangskode Tilbage til login formular Angiv en ny adgangskode Din adgangskode er blevet opdateret @@ -690,7 +849,40 @@ Mange hilsner fra Umbraco robotten Vælg pakken fra din computer. Umbraco pakker er oftest en ".zip" fil - Udvikler + Slip her for at uploade + eller klik her for at vælge pakkefil + Upload pakke + Installer en lokal pakke ved at vælge den fra din computer. Installer kun pakker fra kilder, du kender og stoler på + Upload en anden pakke + Annuller og upload en anden pakke + Licens + Jeg accepterer + betingelser for anvendelse + Installér pakke + Afslut + Installeret pakker + Du har ingen pakker installeret + 'Pakker' øverst til højre på din skærm]]> + Søg efter pakker + Resultater for + Vi kunne ikke finde resultater for + Prøv venligst at søge efter en anden pakke eller gennemse kategorierne + Populære + Nye udgivelser + har + karma points + Information + Ejer + Bidragsydere + Oprettet + Nuværende version + .NET version + Downloads + Likes + Kompatibilitet + Denne pakke er kompatibel med de følgende versioner af Umbraco, som rapporteret af community-medlemmer. Fuld kompatibilitet kan ikke garanteres for versioner rapporteret nedenfor 100% + Eksterne kilder + Forfatter Demonstration Dokumentation Pakke meta data @@ -703,7 +895,7 @@ Mange hilsner fra Umbraco robotten Pakke opbevaringsbase Bekræft af-installering Pakken blev fjernet - Pakken er på succefuld vis blevet fjernet + Pakken er på succesfuld vis blevet fjernet Afinstallér pakke @@ -714,7 +906,18 @@ Mange hilsner fra Umbraco robotten Opdateringsinstrukser Der er en tilgængelig opdatering til denne pakke. Du kan downloade den direkte fra Umbracos pakke opbevaringsbase. Pakke version + Pakke versionshistorik Se pakkeudviklerens website + Pakke allerede installeret + Denne pakke kan ikke installeres, den kræver en minimum Umbraco version af + Afinstallerer... + Downloader... + Importeret... + Installerer... + Genstarter, vent venligst... + Færdig, din browser opdateres nu, vent venligst... + Klik venligst på 'Afslut' for at gennemføre installation og opdatere siden. + Uploader pakke... Indsæt med fuld formattering (Anbefales ikke) @@ -725,7 +928,7 @@ Mange hilsner fra Umbraco robotten Rollebaseret beskyttelse Hvis du ønsker at kontrollere adgang til siden ved hjælp af rollebaseret godkendelse via Umbracos medlemsgrupper. - rollebaseret godkendelse]]> + Du skal oprette en medlemsgruppe før du kan bruge rollebaseret godkendelse Fejlside Brugt når folk er logget ind, men ingen adgang Vælg hvordan siden skal beskyttes @@ -766,6 +969,15 @@ Mange hilsner fra Umbraco robotten Du har ikke konfigureret nogen godkendte farver + + Du har valgt et dokument som er slettet eller lagt i papirkurven + Du har valgt dokumenter som er slettede eller lagt i papirkurven + + + Du har valgt et medie som er slettet eller lagt i papirkurven + Du har valgt medier som er slettede eller lagt i papirkurven + Slettet medie + indtast eksternt link vælg en intern side @@ -778,108 +990,7 @@ Mange hilsner fra Umbraco robotten Nulstil - - Vælg indholdstype - Vælg layout - Tilføj række - Tilføj indhold - Slip indhold - Instillinger tilføjet - Indholdet er ikke tilladt her - Indholdet er tilladt her - - Klik for at indlejre - Klik for at indsætte et billede - Billedtekst... - Skriv her... - - Gridlayout - Et layout er det overordnede arbejdsområde til dit gitter - du vil typisk kun behøve ét eller to - Tilføj gitterlayout - Juster dit layout ved at justere kolonnebredder og tilføj yderligere sektioner - - Rækkekonfigurationer - Rækker er foruddefinerede celler, der arrangeres vandret - Tilføj rækkekonfiguration - Juster rækken ved at indstille cellebredder og tilføje yderligere celler - - Kolonner - Det totale antaller kolonner i gitteret - - Indstillinger - Konfigurer, hvilket indstillinger, brugeren kan ændre - - - Typografi - Vælg, hvilke typografiværdier, en redaktør kan ændre - - Indstillinger gemmes kun, hvis den indtaste json-konfiguration er gyldig - - Tillad alle editorer - Tillad alle rækkekonfigurationer - - Sæt som standard - Vælg ekstra - Vælg standard - er tilføjet - - - - - Kompositioner - Du har ikke tilføjet nogle faner - Tilføj ny fane - Tilføj endnu en fane - Nedarvet fra - Tilføj property - Påkrævet label - - Aktiver listevisning - Konfigurer indholdet til at blive vist i en sorterbar og søgbar liste, dens børn vil ikke blive vist i træet - - Tilladte skabeloner - Vælg hvilke skabeloner der er tilladt at bruge på dette indhold - - Tillad på rodniveau - Kun dokumenttyper med denne indstilling aktiveret oprettes i rodniveau under inhold og mediearkiv - Ja – indhold af denne type er tilladt i roden - - Tilladte typer - Tillad at oprette indhold af en specifik type under denne - - Vælg child node - - Nedarv faner og egenskaber fra en anden dokumenttype. Nye faner vil blive tilføjet den nuværende dokumenttype eller sammenflettet hvis fanenavnene er ens. - Indholdstypen bliver brugt i en komposition og kan derfor ikke blive anvendt som komposition - Der er ingen indholdstyper tilgængelige at bruge som komposition - - Tilgængelige editors - Genbrug - Editor indstillinger - - Konfiguration - - Ja, slet - - blev flyttet til - Vælg hvor - skal flyttes til - - Alle dokumenttyper - Alle dokumenter - Alle medier - - som benytter denne dokumenttype vil blive slettet permanent. Bekræft at du også vil slette dem. - som benytter denne medietype vil blive slettet permanent. Bekræft at du også vil slette dem. - som benytter denne medlemstype vil blive slettet permanent. Bekræft at du også vil slette dem. - - og alle dokumenter, som benytter denne type - og alle medier, som benytter denne type - og alle medlemmer, som benytter denne type - - der bruger denne editor vil blive opdateret med de nye indstillinger - Nuværende version @@ -929,7 +1040,7 @@ Mange hilsner fra Umbraco robotten Faneblad Titel på faneblad Faneblade - Master Dokument Type + Master Dokumenttype Opret matchende skabelon @@ -937,7 +1048,7 @@ Mange hilsner fra Umbraco robotten Oprettelsesdato Sortering udført Træk de forskellige sider op eller ned for at indstille hvordan de skal arrangeres, eller klik på kolonnehovederne for at sortere hele rækken af sider -
Luk ikke dette vindue imens]]>
+ Annulleret @@ -995,26 +1106,262 @@ Mange hilsner fra Umbraco robotten Partial view gemt uden fejl! Partial view ikke gemt Der opstod en fejl ved at gemme filen. + Rettigheder gemt for + Script view gemt + Script view gemt uden fejl! + Script view ikke gemt + An error occurred saving the file. + An error occurred saving the file. + Slettede %0% brugergrupper + %0% blev slettet + Aktiverede %0% brugere + Der opstod en fejl under aktivering af brugerne + Deaktiverede %0% brugere + Der opstod en fejl under deaktivering af brugerne + %0% er nu aktiveret + Der opstod en fejl under aktivering af brugeren + %0% er nu deaktiveret + Der opstod en fejl under deaktivering af brugeren + Brugergrupper er blevet indstillet + Slettede %0% brugergrupper + %0% blev slettet + Låste %0% brugere op + Der opstod en fejl under oplåsning af brugerne + %0% er nu låst op + Der opstod en fejl under oplåsning af brugeren - Bruger CSS-syntax f.eks. h1, .redheader, .blueTex + Bruger CSS-syntaks f.eks. h1, .redheader, .blueTex Rediger stylesheet Rediger CSS-egenskab Navn der identificerer CSS-egenskaben i tekstredigeringsværktøjet - Vis prøve + Forhåndsvisning Styles + Rediger skabelon + + Sektioner Indsæt indholdsområde - Indsæt indholdsområdemarkering - Indsæt ordbogselement - Indsæt makro - Indsæt Umbraco sidefelt + Indsæt pladsholder for indholdsområde + + Indsæt + Hvad vil du indsætte? + + Oversættelse + Indsætter en oversætbar tekst, som skifter efter det sprog, som websitet vises i. + + Makro + + En makro er et element, som kan have forskellige indstillinger, når det indsættes. + Brug det som en genbrugelig del af dit design såsom gallerier, formularer og lister. + + + Sideværdi + + Viser værdien af et felt fra den nuværende side. Kan indstilles til at bruge rekursive værdier eller + vise en standardværdi i tilfælde af, at feltet er tomt. + + + Partial view + + Et Partial View er et skabelonelement, som kan indsættes i andre skabeloner og derved + genbruges og deles på tværs af sideskabelonerne. + + Master skabelon - Lynguide til Umbracos skabelontags + Ingen masterskabelon + Ingen master + + Indsæt en underliggende skabelon + + @RenderBody() element. + ]]> + + + + Definer en sektion + + @section { ... }. Herefter kan denne sektion flettes ind i + overliggende skabelon ved at indsætte et @RenderSection element. + ]]> + + + Indsæt en sektion + + @RenderSection(name) element. Den underliggende skabelon skal have + defineret en sektion via et @section [name]{ ... } element. + ]]> + + + Sektionsnavn + Sektionen er obligatorisk + + + Hvis obligatorisk, skal underskabelonen indeholde en @section -definition. + + + + Query builder + sider returneret, på + + Returner + alt indhold + indhold af typen "%0%" + + fra + mit website + hvor + og + + er + ikke er + er før + er før (inkl. valgte dato) + er efter + er efter (inkl. valgte dato) + er + ikke er + indeholder + ikke indeholder + er større end + er større end eller det samme som + er mindre end + er mindre end eller det samme som + + Id + Navn + Oprettelsesdato + Sidste opdatering + + Sortér efter + stigende rækkefølge + faldende rækkefølge + Skabelon + + + Rich Text Editor + Billede + Macro + Embed + Overskrift + Citat + Vælg indholdstype + Vælg layout + Tilføj række + Tilføj indhold + Slip indhold + Indstillinger tilføjet + + Indholdet er ikke tilladt her + Indholdet er tilladt her + + Klik for at indlejre + Klik for at indsætte et billede + Billedtekst... + Skriv her... + + Grid layout + Et layout er det overordnede arbejdsområde til dit grid - du vil typisk kun behøve ét eller to + Tilføj grid layout + Juster dit layout ved at justere kolonnebredder og tilføj yderligere sektioner + + Rækkekonfigurationer + Rækker er foruddefinerede celler, der arrangeres vandret + Tilføj rækkekonfiguration + Juster rækken ved at indstille cellebredder og tilføje yderligere celler + + Kolonner + Det totale antal kolonner i dit grid + + Indstillinger + Konfigurer, hvilket indstillinger, brugeren kan ændre + + + Typografi + Vælg hvilke typografiværdier en redaktør kan ændre + + Indstillinger gemmes kun, hvis den indtastede json-konfiguration er gyldig + + Tillad alle editorer + Tillad alle rækkekonfigurationer + + Sæt som standard + Vælg ekstra + Vælg standard + er tilføjet + + Maksimalt emner + Efterlad blank eller sæt til 0 for ubegrænset + + + + + Kompositioner + Du har ikke tilføjet nogle faner + Tilføj ny fane + Tilføj endnu en fane + Nedarvet fra + Tilføj egenskab + Påkrævet label + + Aktiver listevisning + Konfigurer indholdet til at blive vist i en sorterbar og søgbar liste, dens børn vil ikke blive vist i træet + + Tilladte skabeloner + Vælg hvilke skabeloner der er tilladt at bruge på dette indhold + + Tillad på rodniveau + Kun dokumenttyper med denne indstilling aktiveret oprettes i rodniveau under inhold og mediearkiv + Ja – indhold af denne type er tilladt i roden + + Tilladte typer + Tillad at oprette indhold af en specifik type under denne + + Vælg child node + + Nedarv faner og egenskaber fra en anden dokumenttype. Nye faner vil blive tilføjet den nuværende dokumenttype eller sammenflettet hvis fanenavnene er ens. + Indholdstypen bliver brugt i en komposition og kan derfor ikke blive anvendt som komposition + Der er ingen indholdstyper tilgængelige at bruge som komposition + + Tilgængelige editors + Genbrug + Editor indstillinger + + Konfiguration + + Ja, slet + + blev flyttet til + Vælg hvor + skal flyttes til + + Alle dokumenttyper + Alle dokumenter + Alle medier + + som benytter denne dokumenttype vil blive slettet permanent. Bekræft at du også vil slette dem. + som benytter denne medietype vil blive slettet permanent. Bekræft at du også vil slette dem. + som benytter denne medlemstype vil blive slettet permanent. Bekræft at du også vil slette dem. + + og alle dokumenter, som benytter denne type + og alle medier, som benytter denne type + og alle medlemmer, som benytter denne type + + der bruger denne editor vil blive opdateret med de nye indstillinger + Medlem kan redigere + Vis på medlemsprofil + fane har ingen sorteringsrækkefølge + Alternativt felt Alternativ tekst @@ -1033,13 +1380,11 @@ Mange hilsner fra Umbraco robotten Indsæt efter felt Indsæt før felt Rekursivt - Fjern paragraf-tags - Fjerner eventuelle &lt;P&gt; omkring teksten Standard felter - Uppercase + Store bogstaver URL encode Hvis indholdet af felterne skal sendes til en url, skal denne slåes til så specialtegn formateres - Denne tekst vil blive brugt hvis ovenstående felter er tomme + Denne tekst bruges hvis ovenstående felter er tomme Dette felt vil blive brugt hvis ovenstående felt er tomt Ja, med klokkeslæt. Dato/tid separator: @@ -1072,6 +1417,9 @@ Mange hilsner fra Umbraco robotten Upload oversættelse (xml) + Indhold + Indholdsskabeloner + Mediearkiv Cacheviser Papirkurv Oprettede pakker @@ -1090,9 +1438,10 @@ Mange hilsner fra Umbraco robotten Medlemstype Dokumenttyper Relationstyper - Pakker Pakker + Partial Views + Partial View Makro Filer Python Installer fra "repository" Installer Runway @@ -1103,6 +1452,7 @@ Mange hilsner fra Umbraco robotten Skabeloner XSLT-filer Analytics + Brugere Ny opdatering er klar @@ -1111,23 +1461,45 @@ Mange hilsner fra Umbraco robotten Der kunne ikke tjekkes for ny opdatering. Se trace for mere info. + Adgang + Baseret på de tildelte grupper og startnoder har brugeren adgang til følgende noder + Tildel adgang Administrator Kategorifelt + Bruger oprettet Skift dit kodeord + Skift billede Nyt kodeord + er ikke blevet låst ude + Kodeordet er ikke blevet ændret Gentag dit nye kodeord Du kan ændre dit kodeord, som giver dig adgang til Umbraco Back Office ved at udfylde formularen og klikke på knappen 'Skift dit kodeord' Indholdskanal + Skift billede + Opret nye brugere for at give dem adgang til Umbraco. Når en ny bruger oprettes, genereres der en adgangskode, som du kan dele med brugeren. Beskrivelsesfelt Deaktivér bruger Dokumenttype Redaktør Uddragsfelt + Fejlede loginforsøg + Gå til brugerprofil + Tilføj grupper for at tildele adgang og tilladelser + Invitér anden bruger + Invitér nye brugere til at give dem adgang til Umbraco. En invitation vil blive sendt via e-mail til brugeren med oplysninger om, hvordan man logger ind i Umbraco. Sprog + Indstil det sprog, du vil se i menuer og dialoger + Seneste låst ude dato + Seneste login + Kodeord sidst ændret Brugernavn Startnode i mediearkivet + Begræns mediebiblioteket til en bestemt startnode + Medie startnoder + Begræns mediebiblioteket til bestemte startnoder Moduler Deaktivér adgang til Umbraco + har endnu ikke logget ind Gammelt kodeord Adgangskode Nulstil kodeord @@ -1142,26 +1514,74 @@ Mange hilsner fra Umbraco robotten Erstat underelement-rettigheder Du ændrer i øjeblikket rettigheder for siderne: Vælg sider for at ændre deres rettigheder + Fjern billede + Standard rettigheder + Granulære rettigheder + Sæt rettigheder for specifikke noder + Profil Søg alle 'børn' - Start node + Startnode + Aktiv + Alle + Deaktiveret + Låst ude + Inviteret + Navn (A-Å) + Navn (Å-A) + Nyeste + Ældste + Senest login + Tilføj sektioner for at give brugerne adgang + Vælg brugergrupper + Ingen startnode valgt + Ingen startnoder valgt + Startnode + Begræns indholdstræet til en bestemt startnode + Indhold startnoder + Begræns indholdstræet til bestemte startnoder + Bruger sidst opdateret + er blevet oprettet + Den nye bruger er blevet oprettet. For at logge ind i Umbraco skal du bruge adgangskoden nedenfor. + Brugeradministration Navn Brugertilladelser - Brugertype - Brugertyper + Brugergruppe tilladelser + Brugergruppe + Brugergrupper + er blevet inviteret + En invitation er blevet sendt til den nye bruger med oplysninger om, hvordan man logger ind i Umbraco. + Hej og velkommen til Umbraco! På bare 1 minut vil du være klar til at komme i gang, vi skal bare have dig til at oprette en adgangskode og tilføje et billede til din avatar. + Upload et billede for at gøre det nemt for andre brugere at genkende dig. Forfatter Oversætter Skift Din profil Din historik Session udløber + Invitér bruger + Opret bruger + Send invitation + Tilbage til brugere + Umbraco: Invitation + +

Hej %0%, du er blevet inviteret af %1% til Umbraco backoffice.

Besked fra %1%: %2%

Klik på dette link for acceptere invitationen

Hvis du ikke kan klikke på linket, så kopier og indsæt denne URL i dit browservindue

%3%

]]> +
- Validation - Valider som email + Validering + Valider som e-mail Valider som tal Valider som Url ...eller indtast din egen validering Feltet er påkrævet + Indtast et regulært udtryk + Du skal tilføje mindst + Du kan kun have + elementer + elementer valgt + Ugyldig dato + Ikke et tal + Ugyldig e-mail Slå URL tracker fra @@ -1180,4 +1600,17 @@ Mange hilsner fra Umbraco robotten URL tracker er nu slået fra. Der opstod en fejl under forsøget på at slå URL trackeren til, der findes mere information i logfilen. + + Ingen ordbog elementer at vælge imellem + + + Karakterer tilbage + + + Slettet indhold med Id: {0} Relateret til original "parent" med id: {1} + Slettet medie med Id: {0} relateret til original "parent" / mappe med id: {1} + Kan ikke automatisk genoprette dette dokument/medie + Der findes ikke nogen "Genopret" relation for dette dokument/medie. Brug "Flyt" muligheden fra menuen for at flytte det manuelt. + Det dokument/medie du ønsker at genoprette under ('%0%') er i skraldespanden. Brug "Flyt" muligheden fra menuen for at flytte det manuelt. +
diff --git a/WebCms/Umbraco/Config/Lang/de.xml b/WebCms/Umbraco/Config/Lang/de.xml index ac39589..8d8a755 100644 --- a/WebCms/Umbraco/Config/Lang/de.xml +++ b/WebCms/Umbraco/Config/Lang/de.xml @@ -2,7 +2,7 @@ The Umbraco community - http://our.umbraco.org/documentation/Extending-Umbraco/Language-Files + https://our.umbraco.com/documentation/Extending-Umbraco/Language-Files Hostnamen verwalten @@ -33,10 +33,8 @@ Zur Veröffentlichung einreichen Zur Übersetzung senden Sortieren - Zur Veröffentlichung einreichen Übersetzen Aktualisieren - Standardwert Erlaubnis verweigert. @@ -85,6 +83,7 @@ Zurück zur Liste Speichern Speichern und veröffentlichen + Speichern und planen Speichern und zur Abnahme übergeben Vorschau Die Vorschaufunktion ist deaktiviert, da keine Vorlage zugewiesen ist @@ -221,6 +220,8 @@ Copied %0% out of %1% items + Name des Link + Link Name Hostnamen verwalten Fenster schließen @@ -278,6 +279,7 @@ Durchsuchen ... Filtern ... Tippen, um Tags hinzuzufügen (nach jedem Tag die Eingabetaste drücken) ... + Der Benutzername ist normalerweise Ihre E-Mail-Adresse Auf oberster Ebene erlauben @@ -463,7 +465,7 @@ Die Datenbank wurde für Umbraco %0% konfiguriert. Klicken Sie auf <strong>weiter</strong>, um fortzufahren. <p>Die angegebene Datenbank ist leider nicht erreichbar. Bitte prüfen Sie die Verbindungszeichenfolge ("Connection String") in der "web.config"-Datei.</p> <p>Um fortzufahren, passen Sie bitte die "web.config"-Datei mit einem beliebigen Text-Editor an. Scrollen Sie dazu nach unten, fügen Sie die Verbindungszeichenfolge für die zuverbindende Datenbank als Eintrag "UmbracoDbDSN" hinzu und speichern Sie die Datei.</p> -<p>Klicken Sie nach erfolgter Anpassung auf <strong>Wiederholen</strong>.<br />Wenn Sie weitere technische Informationen benötigen, besuchen Sie <a href="http://our.Umbraco.org/wiki" target="_blank">The Umbraco documentation wiki</a>.</p> +<p>Klicken Sie nach erfolgter Anpassung auf <strong>Wiederholen</strong>.<br />Wenn Sie weitere technische Informationen benötigen, besuchen Sie <a href="https://our.umbraco.com/wiki" target="_blank">The Umbraco documentation wiki</a>.</p> Um diesen Schritt abzuschließen, müssen Sie die notwendigen Informationen zur Datenbankverbindung angeben.<br />Bitte kontaktieren Sie Ihren Provider bzw. Server-Administrator für weitere Informationen. @@ -479,13 +481,6 @@ <strong>Der Standard-Benutzer wurde deaktiviert oder hat keinen Zugriff auf Umbraco.</strong></p><p>Es sind keine weiteren Aktionen notwendig. Klicken Sie auf <b>Weiter</b> um fortzufahren. <strong>Das Kennwort des Standard-Benutzers wurde seit der Installation verändert.</strong></p><p>Es sind keine weiteren Aktionen notwendig. Klicken Sie auf <b>Weiter</b> um fortzufahren. Das Kennwort wurde geändert! - <p> - Bei der Installation von Umbraco wurde ein Standard-Benutzer mit dem Login-Namen <strong>'admin'</strong> und dem Kennwort <strong>'default'</strong> erstellt. - <strong>WICHTIG:</strong> Das Kennwort sollte auf ein sicheres, eigenes Kennwort geändert werden. - </p> - <p> - Das Kennwort des Standard-Benutzers wird jetzt geprüft und im Anschluss werden eventuell notwendige Änderungen vorschlagen. - </p> Schauen Sie sich die Einführungsvideos für einen schnellen und einfachen Start an. Mit der Installation stimmen Sie der angezeigten Lizenz für diese Software zu. Bitte beachten Sie, dass diese Umbraco-Distribution aus zwei Lizenzen besteht. Einer freien Open-Source MIT-Lizenz für das Framework und der Umbraco-Freeware-Lizenz für die Verwaltungsoberfläche. Noch nicht installiert. @@ -770,7 +765,7 @@ Wenn Sie sich für Runway entscheiden, können Sie optional Blöcke nutzen, die Erstellungsdatum Sortierung abgeschlossen. Ziehen Sie die Elemente an ihre gewünschte neue Position. - Bitte warten, die Seiten werden sortiert. Das kann einen Moment dauern. Bitte schließen Sie dieses Fenster nicht, bis der Sortiervorgang abgeschlossen ist. + Bitte warten, die Seiten werden sortiert. Das kann einen Moment dauern. Fehlgeschlagen @@ -856,9 +851,15 @@ Wenn Sie sich für Runway entscheiden, können Sie optional Blöcke nutzen, die Vorlage + Rich Text Editor + Image + Macro + Embed + Headline + Quote Element hinzufügen Zeilenlayout auswählen - Einfach auf <i class="icon icon-add blue"></i> klicken, um das erste Element anzulegen + Element mit <i class="icon icon-add blue"></i> hinzufügen Drop content Klicken, um Inhalt einzubetten Klicken, um Abbildung einzufügen @@ -902,8 +903,6 @@ Wenn Sie sich für Runway entscheiden, können Sie optional Blöcke nutzen, die An den Feldinhalt anhängen Dem Feldinhalt voranstellen Rekursiv - Textabsatz entfernen - Alle <p> am Anfang und am Ende des Feldinhalts werden entfernt Standardfelder Großbuchstaben URL kodieren @@ -1023,8 +1022,6 @@ Ihr freundlicher Umbraco-Robot Startelement in den Inhalten Benutzername Berechtigungen - Rolle - Rollen Autor Übersetzer Ihr Profil diff --git a/WebCms/Umbraco/Config/Lang/en.xml b/WebCms/Umbraco/Config/Lang/en.xml index be62c5d..2cab60a 100644 --- a/WebCms/Umbraco/Config/Lang/en.xml +++ b/WebCms/Umbraco/Config/Lang/en.xml @@ -1,650 +1,849 @@ - - The Umbraco community - http://our.umbraco.org/documentation/Extending-Umbraco/Language-Files - - - Culture and Hostnames - Audit Trail - Browse Node - Change Document Type - Copy - Create - Create Package - Delete - Disable - Empty recycle bin - Export Document Type - Import Document Type - Import Package - Edit in Canvas - Exit - Move - Notifications - Public access - Publish - Unpublish - Reload - Republish entire site - Restore - Set permissions for the page %0% - Choose where to move - to in the tree structure below - Permissions - Rollback - Send To Publish - Send To Translation - Sort - Send to publication - Translate - Update - Default value - - - Permission denied. - Add new Domain - remove - Invalid node. - Invalid domain format. - Domain has already been assigned. - Language - Domain - New domain '%0%' has been created - Domain '%0%' is deleted - Domain '%0%' has already been assigned - Domain '%0%' has been updated - Edit Current Domains - + The Umbraco community + https://our.umbraco.com/documentation/Extending-Umbraco/Language-Files + + + Culture and Hostnames + Audit Trail + Browse Node + Change Document Type + Copy + Create + Export + Create Package + Create group + Delete + Disable + Empty recycle bin + Enable + Export Document Type + Import Document Type + Import Package + Edit in Canvas + Exit + Move + Notifications + Public access + Publish + Unpublish + Reload + Republish entire site + Rename + Restore + Set permissions for the page %0% + Choose where to move + In the tree structure below + Permissions + Rollback + Send To Publish + Send To Translation + Set group + Sort + Translate + Update + Set permissions + Unlock + Create Content Template + Resend Invitation + + + Content + Administration + Structure + Other + + + Allow access to assign culture and hostnames + Allow access to view a node's history log + Allow access to view a node + Allow access to change document type for a node + Allow access to copy a node + Allow access to create nodes + Allow access to delete nodes + Allow access to move a node + Allow access to set and change public access for a node + Allow access to publish a node + Allow access to change permissions for a node + Allow access to roll back a node to a previous state + Allow access to send a node for approval before publishing + Allow access to send a node for translation + Allow access to change the sort order for nodes + Allow access to translate a node + Allow access to save a node + Allow access to create a Content Template + + + Permission denied. + Add new Domain + remove + Invalid node. + Invalid domain format. + Domain has already been assigned. + Language + Domain + New domain '%0%' has been created + Domain '%0%' is deleted + Domain '%0%' has already been assigned + Domain '%0%' has been updated + Edit Current Domains + + - Inherit - Culture - or inherit culture from parent nodes. Will also apply
- to the current node, unless a domain below applies too.]]>
- Domains - - - Viewing for - - - Clear selection - Select - Select current folder - Do something else - Bold - Cancel Paragraph Indent - Insert form field - Insert graphic headline - Edit Html - Indent Paragraph - Italic - Center - Justify Left - Justify Right - Insert Link - Insert local link (anchor) - Bullet List - Numeric List - Insert macro - Insert picture - Edit relations - Return to list - Save - Save and publish - Save and send for approval - Save list view - Preview - Preview is disabled because there's no template assigned - Choose style - Show styles - Insert table - Generate models - - - To change the document type for the selected content, first select from the list of valid types for this location. - Then confirm and/or amend the mapping of properties from the current type to the new, and click Save. - The content has been re-published. - Current Property - Current type - The document type cannot be changed, as there are no alternatives valid for this location. An alternative will be valid if it is allowed under the parent of the selected content item and that all existing child content items are allowed to be created under it. - Document Type Changed - Map Properties - Map to Property - New Template - New Type - none - Content - Select New Document Type - The document type of the selected content has been successfully changed to [new type] and the following properties mapped: - to - Could not complete property mapping as one or more properties have more than one mapping defined. - Only alternate types valid for the current location are displayed. - - - Is Published - About this page - Alias - (how would you describe the picture over the phone) - Alternative Links - Click to edit this item - Created by - Original author - Updated by - Created - Date/time this document was created - Document Type - Editing - Remove at - This item has been changed after publication - This item is not published - Last published - There are no items to show - There are no items to show in the list. - Media Type - Link to media item(s) - Member Group - Role - Member Type - No date chosen - Link title - Properties - This document is published but is not visible because the parent '%0%' is unpublished - This document is published but is not in the cache - Could not get the url - This document is published but its url would collide with content %0% - Publish - Publication Status - Publish at - Unpublish at - Clear Date - Sortorder is updated - To sort the nodes, simply drag the nodes or click one of the column headers. You can select multiple nodes by holding the "shift" or "control" key while selecting - Statistics - Title (optional) - Alternative text (optional) - Type - Unpublish - Last edited - Date/time this document was edited - Remove file(s) - Link to document - Member of group(s) - Not a member of group(s) - Child items - Target - This translates to the following time on the server: - What does this mean?]]> - - - Click to upload - Drop your files here... - Link to media - or click here to choose files - Only allowed file types are - Max file size is - - - Create a new member - All Members - - - Where do you want to create the new %0% - Create an item under - Choose a type and a title - "document types".]]> - "media types".]]> - Document Type without a template - New folder - New data type - - - Browse your website - - Hide - If Umbraco isn't opening, you might need to allow popups from this site - has opened in a new window - Restart - Visit - Welcome - - - Stay - Discard changes - You have unsaved changes - Are you sure you want to navigate away from this page? - you have unsaved changes - - - Done + should be avoided. Better use the culture setting above.]]> +
+ Inherit + Culture + + or inherit culture from parent nodes. Will also apply
+ to the current node, unless a domain below applies too.]]> +
+ Domains + + + Clear selection + Select + Select current folder + Do something else + Bold + Cancel Paragraph Indent + Insert form field + Insert graphic headline + Edit Html + Indent Paragraph + Italic + Center + Justify Left + Justify Right + Insert Link + Insert local link (anchor) + Bullet List + Numeric List + Insert macro + Insert picture + Edit relations + Return to list + Save + Save and publish + Save and schedule + Save and send for approval + Save list view + Preview + Preview is disabled because there's no template assigned + Choose style + Show styles + Insert table + Generate models + Save and generate models + Undo + Redo + Delete tag + Cancel + Confirm + + + Viewing for + Delete Content performed by user + UnPublish performed by user + Save and Publish performed by user + Save Content performed by user + Move Content performed by user + Copy Content performed by user + Content rollback performed by user + Content Send To Publish performed by user + Content Send To Translation performed by user + Copy + Publish + Move + Save + Delete + Unpublish + Rollback + Send To Publish + Send To Translation + + + To change the document type for the selected content, first select from the list of valid types for this location. + Then confirm and/or amend the mapping of properties from the current type to the new, and click Save. + The content has been re-published. + Current Property + Current type + The document type cannot be changed, as there are no alternatives valid for this location. An alternative will be valid if it is allowed under the parent of the selected content item and that all existing child content items are allowed to be created under it. + Document Type Changed + Map Properties + Map to Property + New Template + New Type + none + Content + Select New Document Type + The document type of the selected content has been successfully changed to [new type] and the following properties mapped: + to + Could not complete property mapping as one or more properties have more than one mapping defined. + Only alternate types valid for the current location are displayed. + + + Is Published + About this page + Alias + (how would you describe the picture over the phone) + Alternative Links + Click to edit this item + Created by + Original author + Updated by + Created + Date/time this document was created + Document Type + Editing + Remove at + This item has been changed after publication + This item is not published + Last published + There are no items to show + There are no items to show in the list. + No content has been added + No members have been added + Media Type + Link to media item(s) + Member Group + Role + Member Type + No changes have been made + No date chosen + Page title + This media item has no link + Properties + This document is published but is not visible because the parent '%0%' is unpublished + This document is published but is not in the cache + Could not get the url + This document is published but its url would collide with content %0% + Publish + Published + Published (pending changes) + Publication Status + Publish at + Unpublish at + Clear Date + Set date + Sortorder is updated + To sort the nodes, simply drag the nodes or click one of the column headers. You can select multiple nodes by holding the "shift" or "control" key while selecting + Statistics + Title (optional) + Alternative text (optional) + Type + Unpublish + Unpublished + Last edited + Date/time this document was edited + Remove file(s) + Link to document + Member of group(s) + Not a member of group(s) + Child items + Target + This translates to the following time on the server: + What does this mean?]]> + Are you sure you want to delete this item? + Property %0% uses editor %1% which is not supported by Nested Content. + Add another text box + Remove this text box + Content root + This value is hidden. If you need access to view this value please contact your website administrator. + This value is hidden. + + + Create a new Content Template from '%0%' + Blank + Select a Content Template + Content Template created + A Content Template was created from '%0%' + Another Content Template with the same name already exists + A Content Template is pre-defined content that an editor can select to use as the basis for creating new content + + + Click to upload + Drop your files here... + Link to media + or click here to choose files + You can drag files here to upload + Only allowed file types are + Cannot upload this file, it does not have an approved file type + Max file size is + Media root + + + Create a new member + All Members + + + Where do you want to create the new %0% + Create an item under + Select the document type you want to make a content template for + Choose a type and a title + "document types".]]> + "media types".]]> + Document Type without a template + New folder + New data type + New javascript file + New empty partial view + New partial view macro + New partial view from snippet + New empty partial view macro + New partial view macro from snippet + New partial view macro (without macro) + + + Browse your website + - Hide + If Umbraco isn't opening, you might need to allow popups from this site + has opened in a new window + Restart + Visit + Welcome + + + Stay + Discard changes + You have unsaved changes + Are you sure you want to navigate away from this page? - you have unsaved changes + Unpublishing will remove this page and all its descendants from the site. + + + Done - Deleted %0% item - Deleted %0% items - Deleted %0% out of %1% item - Deleted %0% out of %1% items + Deleted %0% item + Deleted %0% items + Deleted %0% out of %1% item + Deleted %0% out of %1% items - Published %0% item - Published %0% items - Published %0% out of %1% item - Published %0% out of %1% items + Published %0% item + Published %0% items + Published %0% out of %1% item + Published %0% out of %1% items - Unpublished %0% item - Unpublished %0% items - Unpublished %0% out of %1% item - Unpublished %0% out of %1% items + Unpublished %0% item + Unpublished %0% items + Unpublished %0% out of %1% item + Unpublished %0% out of %1% items - Moved %0% item - Moved %0% items - Moved %0% out of %1% item - Moved %0% out of %1% items + Moved %0% item + Moved %0% items + Moved %0% out of %1% item + Moved %0% out of %1% items - Copied %0% item - Copied %0% items - Copied %0% out of %1% item - Copied %0% out of %1% items - - - Name - Manage hostnames - Close this window - Are you sure you want to delete - Are you sure you want to disable - Please check this box to confirm deletion of %0% item(s) - Are you sure? - Are you sure? - Cut - Edit Dictionary Item - Edit Language - Insert local link - Insert character - Insert graphic headline - Insert picture - Insert link - Click to add a Macro - Insert table - Last Edited - Link - Internal link: - When using local links, insert "#" in front of link - Open in new window? - Macro Settings - This macro does not contain any properties you can edit - Paste - Edit Permissions for - The items in the recycle bin are now being deleted. Please do not close this window while this operation takes place - The recycle bin is now empty - When items are deleted from the recycle bin, they will be gone forever - regexlib.com's webservice is currently experiencing some problems, which we have no control over. We are very sorry for this inconvenience.]]> - Search for a regular expression to add validation to a form field. Example: 'email, 'zip-code' 'url' - Remove Macro - Required Field - Site is reindexed - The website cache has been refreshed. All publish content is now up to date. While all unpublished content is still unpublished - The website cache will be refreshed. All published content will be updated, while unpublished content will stay unpublished. - Number of columns - Number of rows - Set a placeholder id by setting an ID on your placeholder you can inject content into this template from child templates, - by referring this ID using a <asp:content /> element.]]> - Select a placeholder id from the list below. You can only - choose Id's from the current template's master.]]> - Click on the image to see full size - Pick item - View Cache Item - Create folder... - Relate to original - Include descendants - The friendliest community - Link to page - Opens the linked document in a new window or tab - Link to media - Select media - Select icon - Select item - Select link - Select macro - Select content - Select member - Select member group - No icons were found - There are no parameters for this macro - External login providers - Exception Details - Stacktrace - Inner Exception - Link your - Un-Link your - account - Select editor - - - Copied %0% item + Copied %0% items + Copied %0% out of %1% item + Copied %0% out of %1% items + + + Link title + Link + Anchor / querystring + Name + Manage hostnames + Close this window + Are you sure you want to delete + Are you sure you want to disable + Please check this box to confirm deletion of %0% item(s) + Are you sure? + Are you sure? + Cut + Edit Dictionary Item + Edit Language + Insert local link + Insert character + Insert graphic headline + Insert picture + Insert link + Click to add a Macro + Insert table + Last Edited + Link + Internal link: + When using local links, insert "#" in front of link + Open in new window? + Macro Settings + This macro does not contain any properties you can edit + Paste + Edit permissions for + Set permissions for + Set permissions for %0% for user group %1% + Select the users groups you want to set permissions for + The items in the recycle bin are now being deleted. Please do not close this window while this operation takes place + The recycle bin is now empty + When items are deleted from the recycle bin, they will be gone forever + regexlib.com's webservice is currently experiencing some problems, which we have no control over. We are very sorry for this inconvenience.]]> + Search for a regular expression to add validation to a form field. Example: 'email, 'zip-code' 'url' + Remove Macro + Required Field + Site is reindexed + The website cache has been refreshed. All publish content is now up to date. While all unpublished content is still unpublished + The website cache will be refreshed. All published content will be updated, while unpublished content will stay unpublished. + Number of columns + Number of rows + + Set a placeholder id by setting an ID on your placeholder you can inject content into this template from child templates, + by referring this ID using a <asp:content /> element.]]> + + + Select a placeholder id from the list below. You can only + choose Id's from the current template's master.]]> + + Click on the image to see full size + Pick item + View Cache Item + Create folder... + Relate to original + Include descendants + The friendliest community + Link to page + Opens the linked document in a new window or tab + Link to media + Link to file + Select content start node + Select media + Select icon + Select item + Select link + Select macro + Select content + Select media start node + Select member + Select member group + Select node + Select sections + Select users + No icons were found + There are no parameters for this macro + There are no macros available to insert + External login providers + Exception Details + Stacktrace + Inner Exception + Link your + Un-link your + account + Select editor + Select snippet + + + + %0%
' below
You can add additional languages under the 'languages' in the menu on the left - ]]> - Culture Name - Edit the key of the dictionary item. - - + + Culture Name + Edit the key of the dictionary item. + + - - - - Enter your username - Enter your password - Confirm your password - Name the %0%... - Enter a name... - Label... - Enter a description... - Type to search... - Type to filter... - Type to add tags (press enter after each tag)... - Enter your email - - - Allow at root - Only Content Types with this checked can be created at the root level of Content and Media trees - Allowed child node types - Document Type Compositions - Create - Delete tab - Description - New tab - Tab - Thumbnail - Enable list view - Configures the content item to show a sortable & searchable list of its children, the children will not be shown in the tree - Current list view - The active list view data type - Create custom list view - Remove custom list view - - - Add prevalue - Database datatype - Property editor GUID - Property editor - Buttons - Enable advanced settings for - Enable context menu - Maximum default size of inserted images - Related stylesheets - Show label - Width and height - - - Your data has been saved, but before you can publish this page there are some errors you need to fix first: - The current membership provider does not support changing password (EnablePasswordRetrieval need to be true) - %0% already exists - There were errors: - There were errors: - The password should be a minimum of %0% characters long and contain at least %1% non-alpha numeric character(s) - %0% must be an integer - The %0% field in the %1% tab is mandatory - %0% is a mandatory field - %0% at %1% is not in a correct format - %0% is not in a correct format - - - Received an error from the server - The specified file type has been disallowed by the administrator - NOTE! Even though CodeMirror is enabled by configuration, it is disabled in Internet Explorer because it's not stable enough. - Please fill both alias and name on the new property type! - There is a problem with read/write access to a specific file or folder - Error loading Partial View script (file: %0%) - Error loading userControl '%0%' - Error loading customControl (Assembly: %0%, Type: '%1%') - Error loading MacroEngine script (file: %0%) - "Error parsing XSLT file: %0% - "Error reading XSLT file: %0% - Please enter a title - Please choose a type - You're about to make the picture larger than the original size. Are you sure that you want to proceed? - Error in python script - The python script has not been saved, because it contained error(s) - Startnode deleted, please contact your administrator - Please mark content before changing style - No active styles available - Please place cursor at the left of the two cells you wish to merge - You cannot split a cell that hasn't been merged. - Error in XSLT source - The XSLT has not been saved, because it contained error(s) - There is a configuration error with the data type used for this property, please check the data type - - - About - Action - Actions - Add - Alias - All - Are you sure? - Back - Border - by - Cancel - Cell margin - Choose - Close - Close Window - Comment - Confirm - Constrain proportions - Continue - Copy - Create - Database - Date - Default - Delete - Deleted - Deleting... - Design - Dimensions - Down - Download - Edit - Edited - Elements - Email - Error - Find - Height - Help - Icon - Import - Inner margin - Insert - Install - Invalid - Justify - Language - Layout - Loading - Locked - Login - Log off - Logout - Macro - Mandatory - Move - More - Name - New - Next - No - of - OK - Open - or - Password - Path - Placeholder ID - One moment please... - Previous - Properties - Email to receive form data - Recycle Bin - Remaining - Rename - Renew - Required - Retry - Permissions - Search - Server - Show - Show page on Send - Size - Sort - Submit - Type - Type to search... - Up - Update - Upgrade - Upload - Url - User - Username - Value - View - Welcome... - Width - Yes - Folder - Search results - Reorder - I am done reordering - Preview - Change password - to - List view - Saving... - current - Embed - selected - + + Dictionary overview + + + Enter your username + Enter your password + Confirm your password + Name the %0%... + Enter a name... + Enter an email... + Enter a username... + Label... + Enter a description... + Type to search... + Type to filter... + Type to add tags (press enter after each tag)... + Enter your email... + Enter a message... + Your username is usually your email + #value or ?key=value + + + Allow at root + Only Content Types with this checked can be created at the root level of Content and Media trees + Allowed child node types + Document Type Compositions + Create + Delete tab + Description + New tab + Tab + Thumbnail + Enable list view + Configures the content item to show a sortable & searchable list of its children, the children will not be shown in the tree + Current list view + The active list view data type + Create custom list view + Remove custom list view + A content type, media type or member type with this alias already exists + + + Renamed + Enter a new folder name here + %0% was renamed to %1% + + + Add prevalue + Database datatype + Property editor GUID + Property editor + Buttons + Enable advanced settings for + Enable context menu + Maximum default size of inserted images + Related stylesheets + Show label + Width and height + All property types & property data + using this data type will be deleted permanently, please confirm you want to delete these as well + Yes, delete + and all property types & property data using this data type + Select the folder to move + to in the tree structure below + was moved underneath + + + Your data has been saved, but before you can publish this page there are some errors you need to fix first: + The current membership provider does not support changing password (EnablePasswordRetrieval need to be true) + %0% already exists + There were errors: + There were errors: + The password should be a minimum of %0% characters long and contain at least %1% non-alpha numeric character(s) + %0% must be an integer + The %0% field in the %1% tab is mandatory + %0% is a mandatory field + %0% at %1% is not in a correct format + %0% is not in a correct format + + + Received an error from the server + The specified file type has been disallowed by the administrator + NOTE! Even though CodeMirror is enabled by configuration, it is disabled in Internet Explorer because it's not stable enough. + Please fill both alias and name on the new property type! + There is a problem with read/write access to a specific file or folder + Error loading Partial View script (file: %0%) + Error loading userControl '%0%' + Error loading customControl (Assembly: %0%, Type: '%1%') + Error loading MacroEngine script (file: %0%) + "Error parsing XSLT file: %0% + "Error reading XSLT file: %0% + Please enter a title + Please choose a type + You're about to make the picture larger than the original size. Are you sure that you want to proceed? + Error in python script + The python script has not been saved, because it contained error(s) + Startnode deleted, please contact your administrator + Please mark content before changing style + No active styles available + Please place cursor at the left of the two cells you wish to merge + You cannot split a cell that hasn't been merged. + Error in XSLT source + The XSLT has not been saved, because it contained error(s) + There is a configuration error with the data type used for this property, please check the data type + + + Options + About + Action + Actions + Add + Alias + All + Are you sure? + Back + Border + by + Cancel + Cell margin + Choose + Close + Close Window + Comment + Confirm + Constrain + Constrain proportions + Continue + Copy + Create + Database + Date + Default + Delete + Deleted + Deleting... + Design + Dictionary + Dimensions + Down + Download + Edit + Edited + Elements + Email + Error + Find + First + General + Groups + Height + Help + Hide + History + Icon + Import + Info + Inner margin + Insert + Install + Invalid + Justify + Label + Language + Last + Layout + Links + Loading + Locked + Login + Log off + Logout + Macro + Mandatory + Message + Move + More + Name + New + Next + No + of + Off + OK + Open + On + or + Order by + Password + Path + Placeholder ID + One moment please... + Previous + Properties + Email to receive form data + Recycle Bin + Your recycle bin is empty + Remaining + Remove + Rename + Renew + Required + Retrieve + Retry + Permissions + Scheduled Publishing + Search + Sorry, we can not find what you are looking for + No items have been added + Server + Show + Show page on Send + Size + Sort + Status + Submit + Type + Type to search... + under + Up + Update + Upgrade + Upload + Url + User + Username + Value + View + Welcome... + Width + Yes + Folder + Search results + Reorder + I am done reordering + Preview + Change password + to + List view + Saving... + current + Embed + Retrieve + selected + - - Black - Green - Yellow - Orange - Blue - Red - + + Black + Green + Yellow + Orange + Blue + Blue Grey + Grey + Brown + Light Blue + Cyan + Light Green + Lime + Amber + Deep Orange + Red + Pink + Purple + Deep Purple + Indigo + - - Add tab - Add property - Add editor - Add template - Add child node - Add child + + Add tab + Add property + Add editor + Add template + Add child node + Add child - Edit data type + Edit data type - Navigate sections + Navigate sections - Shortcuts - show shortcuts + Shortcuts + show shortcuts - Toggle list view - Toggle allow as root - + Toggle list view + Toggle allow as root - - Background colour - Bold - Text colour - Font - Text - + Comment/Uncomment lines + Remove line + Copy Lines Up + Copy Lines Down + Move Lines Up + Move Lines Down - - Page - - - The installer cannot connect to the database. - Could not save the web.config file. Please modify the connection string manually. - Your database has been found and is identified as - Database configuration - General + Editor + + + Background colour + Bold + Text colour + Font + Text + + + Page + + + The installer cannot connect to the database. + Could not save the web.config file. Please modify the connection string manually. + Your database has been found and is identified as + Database configuration + + install button to install the Umbraco %0% database - ]]> - Next to proceed.]]> - Database not found! Please check that the information in the "connection string" of the "web.config" file is correct.

+ ]]> +
+ Next to proceed.]]> + + Database not found! Please check that the information in the "connection string" of the "web.config" file is correct.

To proceed, please edit the "web.config" file (using Visual Studio or your favourite text editor), scroll to the bottom, add the connection string for your database in the key named "UmbracoDbDSN" and save the file.

Click the retry button when - done.
- More information on editing web.config here.

]]>
- + done.
+ More information on editing web.config here.

]]> +
+ + Please contact your ISP if necessary. - If you're installing on a local machine or server you might need information from your system administrator.]]> - + + + Press the upgrade button to upgrade your database to Umbraco %0%

Don't worry - no content will be deleted and everything will continue working afterwards!

- ]]>
- Press Next to - proceed. ]]> - next to continue the configuration wizard]]> - The Default users' password needs to be changed!]]> - The Default user has been disabled or has no access to Umbraco!

No further actions needs to be taken. Click Next to proceed.]]> - The Default user's password has been successfully changed since the installation!

No further actions needs to be taken. Click Next to proceed.]]> - The password is changed! - - Umbraco creates a default user with a login ('admin') and password ('default'). It's important that the password is - changed to something unique. -

-

- This step will check the default user's password and suggest if it needs to be changed. -

- ]]>
- Get a great start, watch our introduction videos - By clicking the next button (or modifying the umbracoConfigurationStatus in web.config), you accept the license for this software as specified in the box below. Notice that this Umbraco distribution consists of two different licenses, the open source MIT license for the framework and the Umbraco freeware license that covers the UI. - Not installed yet. - Affected files and folders - More information on setting up permissions for Umbraco here - You need to grant ASP.NET modify permissions to the following files/folders - Your permission settings are almost perfect!

- You can run Umbraco without problems, but you will not be able to install packages which are recommended to take full advantage of Umbraco.]]>
- How to Resolve - Click here to read the text version - video tutorial on setting up folder permissions for Umbraco or read the text version.]]> - Your permission settings might be an issue! + ]]> + + + Press Next to + proceed. ]]> + + next to continue the configuration wizard]]> + The Default users' password needs to be changed!]]> + The Default user has been disabled or has no access to Umbraco!

No further actions needs to be taken. Click Next to proceed.]]> + The Default user's password has been successfully changed since the installation!

No further actions needs to be taken. Click Next to proceed.]]> + The password is changed! + Get a great start, watch our introduction videos + By clicking the next button (or modifying the umbracoConfigurationStatus in web.config), you accept the license for this software as specified in the box below. Notice that this Umbraco distribution consists of two different licenses, the open source MIT license for the framework and the Umbraco freeware license that covers the UI. + Not installed yet. + Affected files and folders + More information on setting up permissions for Umbraco here + You need to grant ASP.NET modify permissions to the following files/folders + + Your permission settings are almost perfect!

+ You can run Umbraco without problems, but you will not be able to install packages which are recommended to take full advantage of Umbraco.]]> +
+ How to Resolve + Click here to read the text version + video tutorial on setting up folder permissions for Umbraco or read the text version.]]> + + Your permission settings might be an issue!

- You can run Umbraco without problems, but you will not be able to create folders or install packages which are recommended to take full advantage of Umbraco.]]>
- Your permission settings are not ready for Umbraco! + You can run Umbraco without problems, but you will not be able to create folders or install packages which are recommended to take full advantage of Umbraco.]]> + + + Your permission settings are not ready for Umbraco!

- In order to run Umbraco, you'll need to update your permission settings.]]>
- Your permission settings are perfect!

- You are ready to run Umbraco and install packages!]]>
- Resolving folder issue - Follow this link for more information on problems with ASP.NET and creating folders - Setting up folder permissions - + + + Your permission settings are perfect!

+ You are ready to run Umbraco and install packages!]]> +
+ Resolving folder issue + Follow this link for more information on problems with ASP.NET and creating folders + Setting up folder permissions + + - I want to start from scratch - + + I want to start from scratch + + learn how) You can still choose to install Runway later on. Please go to the Developer section and choose Packages. - ]]> - You've just set up a clean Umbraco platform. What do you want to do next? - Runway is installed - + + You've just set up a clean Umbraco platform. What do you want to do next? + Runway is installed + + This is our list of recommended modules, check off the ones you would like to install, or view the full list of modules - ]]> - Only recommended for experienced users - I want to start with a simple website - + + Only recommended for experienced users + I want to start with a simple website + + "Runway" is a simple website providing some basic document types and templates. The installer can set up Runway for you automatically, but you can easily edit, extend or remove it. It's not necessary and you can perfectly use Umbraco without it. However, @@ -655,87 +854,183 @@ Included with Runway: Home page, Getting Started page, Installing Modules page.
Optional Modules: Top Navigation, Sitemap, Contact, Gallery. - ]]>
- What is Runway - Step 1/5 Accept license - Step 2/5: Database configuration - Step 3/5: Validating File Permissions - Step 4/5: Check Umbraco security - Step 5/5: Umbraco is ready to get you started - Thank you for choosing Umbraco - Browse your new site -You installed Runway, so why not see how your new website looks.]]> - Further help and information -Get help from our award winning community, browse the documentation or watch some free videos on how to build a simple site, how to use packages and a quick guide to the Umbraco terminology]]> - Umbraco %0% is installed and ready for use - /web.config file and update the AppSetting key UmbracoConfigurationStatus in the bottom to the value of '%0%'.]]> - started instantly by clicking the "Launch Umbraco" button below.
If you are new to Umbraco, -you can find plenty of resources on our getting started pages.]]>
- Launch Umbraco -To manage your website, simply open the Umbraco back office and start adding content, updating the templates and stylesheets or add new functionality]]> - Connection to database failed. - Umbraco Version 3 - Umbraco Version 4 - Watch - Umbraco %0% for a fresh install or upgrading from version 3.0. + ]]> + + What is Runway + Step 1/5 Accept license + Step 2/5: Database configuration + Step 3/5: Validating File Permissions + Step 4/5: Check Umbraco security + Step 5/5: Umbraco is ready to get you started + Thank you for choosing Umbraco + + Browse your new site +You installed Runway, so why not see how your new website looks.]]> + + + Further help and information +Get help from our award winning community, browse the documentation or watch some free videos on how to build a simple site, how to use packages and a quick guide to the Umbraco terminology]]> + + Umbraco %0% is installed and ready for use + + /web.config file and update the AppSetting key UmbracoConfigurationStatus in the bottom to the value of '%0%'.]]> + + + started instantly by clicking the "Launch Umbraco" button below.
If you are new to Umbraco, +you can find plenty of resources on our getting started pages.]]> +
+ + Launch Umbraco +To manage your website, simply open the Umbraco back office and start adding content, updating the templates and stylesheets or add new functionality]]> + + Connection to database failed. + Umbraco Version 3 + Umbraco Version 4 + Watch + + Umbraco %0% for a fresh install or upgrading from version 3.0.

- Press "next" to start the wizard.]]>
- - - Culture Code - Culture Name - - - You've been idle and logout will automatically occur in - Renew now to save your work - - - Happy super Sunday - Happy manic Monday - Happy tubular Tuesday - Happy wonderful Wednesday - Happy thunderous Thursday - Happy funky Friday - Happy Caturday - Log in below - Sign in with - Session timed out - © 2001 - %0%
Umbraco.com

]]>
- Forgotten password? - An email will be sent to the address specified with a link to reset your password - An email with password reset instructions will be sent to the specified address if it matched our records - Return to login form - Please provide a new password - Your Password has been updated - The link you have clicked on is invalid or has expired - Umbraco: Reset Password - - Your username to login to the Umbraco back-office is: %0%

Click here to reset your password or copy/paste this URL into your browser:

%1%

]]> -
- - - Dashboard - Sections - Content - - - Choose page above... - %0% has been copied to %1% - Select where the document %0% should be copied to below - %0% has been moved to %1% - Select where the document %0% should be moved to below - has been selected as the root of your new content, click 'ok' below. - No node selected yet, please select a node in the list above before clicking 'ok' - The current node is not allowed under the chosen node because of its type - The current node cannot be moved to one of its subpages - The current node cannot exist at the root - The action isn't allowed since you have insufficient permissions on 1 or more child documents. - Relate copied items to original - - - Edit your notification for %0% - "next" to start the wizard.]]> + + + + Culture Code + Culture Name + + + You've been idle and logout will automatically occur in + Renew now to save your work + + + Happy super Sunday + Happy manic Monday + Happy tubular Tuesday + Happy wonderful Wednesday + Happy thunderous Thursday + Happy funky Friday + Happy Caturday + Log in below + Sign in with + Session timed out + © 2001 - %0%
Umbraco.com

]]>
+ Forgotten password? + An email will be sent to the address specified with a link to reset your password + An email with password reset instructions will be sent to the specified address if it matched our records + Show password + Hide password + Return to login form + Please provide a new password + Your Password has been updated + The link you have clicked on is invalid or has expired + Umbraco: Reset Password + + + + + + + + + + + + +
+ + + + + +
+ +
+ +
+
+ + + + + + +
+
+
+ + + + +
+ + + + +
+

+ Password reset requested +

+

+ Your username to login to the Umbraco back-office is: %0% +

+

+ + + + + + +
+ + Click this link to reset your password + +
+

+

If you cannot click on the link, copy and paste this URL into your browser window:

+ + + + +
+ + %1% + +
+

+
+
+


+
+
+ + + ]]> +
+ + + Dashboard + Sections + Content + + + Choose page above... + %0% has been copied to %1% + Select where the document %0% should be copied to below + %0% has been moved to %1% + Select where the document %0% should be moved to below + has been selected as the root of your new content, click 'ok' below. + No node selected yet, please select a node in the list above before clicking 'ok' + The current node is not allowed under the chosen node because of its type + The current node cannot be moved to one of its subpages + The current node cannot exist at the root + The action isn't allowed since you have insufficient permissions on 1 or more child documents. + Relate copied items to original + + + Edit your notification for %0% + + - Hi %0%

- -

This is an automated mail to inform you that the task '%1%' - has been performed on the page '%2%' - by the user '%3%' -

- -

-

Update summary:

- - %6% + ]]> + + + + + + + + + +
+ + +
+ + + + + +
+ +
+ +
+
-

- - - -

Have a nice day!

- Cheers from the Umbraco robot -

]]>
- [%0%] Notification about %1% performed on %2% - Notifications - - - + + + +
+
+ + + + +
+ + + + +
+

+ Hi %0%, +

+

+ This is an automated mail to inform you that the task '%1%' has been performed on the page '%2%' by the user '%3%' +

+ + + + + + +
+ +
+ EDIT
+
+

+

Update summary:

+ + %6% +
+

+

+ Have a nice day!

+ Cheers from the Umbraco robot +

+
+
+


+
+ + + + + + + ]]> +
+ [%0%] Notification about %1% performed on %2% + Notifications + + + + - button and locating the package. Umbraco packages usually have a ".zip" extension. - ]]> - Author - Demonstration - Documentation - Package meta data - Package name - Package doesn't contain any items -
- You can safely remove this from the system by clicking "uninstall package" below.]]>
- No upgrades available - Package options - Package readme - Package repository - Confirm uninstall - Package was uninstalled - The package was successfully uninstalled - Uninstall package - + button and locating the package. Umbraco packages usually have a ".umb" or ".zip" extension. + ]]> + + Drop to upload + or click here to choose package file + Upload package + Install a local package by selecting it from your machine. Only install packages from sources you know and trust + Upload another package + Cancel and upload another package + License + I accept + terms of use + Install package + Finish + Installed packages + You don’t have any packages installed + 'Packages' icon in the top right of your screen]]> + Search for packages + Results for + We couldn’t find anything for + Please try searching for another package or browse through the categories + Popular + New releases + has + karma points + Information + Owner + Contributors + Created + Current version + .NET version + Downloads + Likes + Compatibility + This package is compatible with the following versions of Umbraco, as reported by community members. Full compatability cannot be gauranteed for versions reported below 100% + External sources + Author + Demonstration + Documentation + Package meta data + Package name + Package doesn't contain any items + +
+ You can safely remove this from the system by clicking "uninstall package" below.]]> +
+ No upgrades available + Package options + Package readme + Package repository + Confirm package uninstall + Package was uninstalled + The package was successfully uninstalled + Uninstall package + + Notice: any documents, media etc depending on the items you remove, will stop working, and could lead to system instability, - so uninstall with caution. If in doubt, contact the package author.]]> - Download update from the repository - Upgrade package - Upgrade instructions - There's an upgrade available for this package. You can download it directly from the Umbraco package repository. - Package version - Package version history - View package website - Package already installed - This package cannot be installed, it requires a minimum Umbraco version of %0% - Uninstalling... - Downloading... - Importing... - Installing... - Restarting, please wait... - All done, your browser will now refresh, please wait... - Please click finish to complete installation and reload page. - - - Paste with full formatting (Not recommended) - The text you're trying to paste contains special characters or formatting. This could be caused by copying text from Microsoft Word. Umbraco can remove special characters or formatting automatically, so the pasted content will be more suitable for the web. - Paste as raw text without any formatting at all - Paste, but remove formatting (Recommended) - - - Role based protection - using Umbraco's member groups.]]> - role-based authentication.]]> - Error Page - Used when people are logged on, but do not have access - Choose how to restrict access to this page - %0% is now protected - Protection removed from %0% - Login Page - Choose the page that contains the login form - Remove Protection - Select the pages that contain login form and error messages - Pick the roles who have access to this page - Set the login and password for this page - Single user protection - If you just want to setup simple protection using a single login and password - - - - + + Download update from the repository + Upgrade package + Upgrade instructions + There's an upgrade available for this package. You can download it directly from the Umbraco package repository. + Package version + Package version history + View package website + Package already installed + This package cannot be installed, it requires a minimum Umbraco version of + Uninstalling... + Downloading... + Importing... + Installing... + Restarting, please wait... + All done, your browser will now refresh, please wait... + Please click 'Finish' to complete installation and reload the page. + Uploading package... + + + Paste with full formatting (Not recommended) + The text you're trying to paste contains special characters or formatting. This could be caused by copying text from Microsoft Word. Umbraco can remove special characters or formatting automatically, so the pasted content will be more suitable for the web. + Paste as raw text without any formatting at all + Paste, but remove formatting (Recommended) + + + Role based protection + using Umbraco's member groups.]]> + You need to create a membergroup before you can use role-based authentication + Error Page + Used when people are logged on, but do not have access + Choose how to restrict access to this page + %0% is now protected + Protection removed from %0% + Login Page + Choose the page that contains the login form + Remove Protection + Select the pages that contain login form and error messages + Pick the roles who have access to this page + Set the login and password for this page + Single user protection + If you just want to setup simple protection using a single login and password + + + + - - + + - + + + - + + + - + + + - Include unpublished subpages - Publishing in progress - please wait... - %0% out of %1% pages have been published... - %0% has been published - %0% and subpages have been published - Publish %0% and all its subpages - Publish
to publish %0% and thereby making its content publicly available.

+ ]]> + + Include unpublished subpages + Publishing in progress - please wait... + %0% out of %1% pages have been published... + %0% has been published + %0% and subpages have been published + Publish %0% and all its subpages + + Publish to publish %0% and thereby making its content publicly available.

You can publish this page and all its subpages by checking Include unpublished subpages below. - ]]>
- - - You have not configured any approved colours - - - enter external link - choose internal page - Caption - Link - New window - Enter a new caption - Enter the link - - - Reset - - - Current version - Red text will not be shown in the selected version. , green means added]]> - Document has been rolled back - This displays the selected version as HTML, if you wish to see the difference between 2 versions at the same time, use the diff view - Rollback to - Select version - View - - - Edit script file - - - Concierge - Content - Courier - Developer - Umbraco Configuration Wizard - Media - Members - Newsletters - Settings - Statistics - Translation - Users - Help - Forms - Analytics - - - go to - Help topics for - Video chapters for - The best Umbraco video tutorials - - - Default template - Dictionary Key - To import a document type, find the ".udt" file on your computer by clicking the "Browse" button and click "Import" (you'll be asked for confirmation on the next screen) - New Tab Title - Node type - Type - Stylesheet - Script - Stylesheet property - Tab - Tab Title - Tabs - Master Content Type enabled - This Content Type uses - as a Master Content Type. Tabs from Master Content Types are not shown and can only be edited on the Master Content Type itself - No properties defined on this tab. Click on the "add a new property" link at the top to create a new property. - Master Document Type - Create matching template - Add icon - - - Sort order - Creation date - Sorting complete. - Drag the different items up or down below to set how they should be arranged. Or click the column headers to sort the entire collection of items -
Do not close this window during sorting]]>
- - - Validation - Validation errors must be fixed before the item can be saved - Failed - Insufficient user permissions, could not complete the operation - Cancelled - Operation was cancelled by a 3rd party add-in - Publishing was cancelled by a 3rd party add-in - Property type already exists - Property type created - DataType: %1%]]> - Propertytype deleted - Document Type saved - Tab created - Tab deleted - Tab with id: %0% deleted - Stylesheet not saved - Stylesheet saved - Stylesheet saved without any errors - Datatype saved - Dictionary item saved - Publishing failed because the parent page isn't published - Content published - and visible at the website - Content saved - Remember to publish to make changes visible - Sent For Approval - Changes have been sent for approval - Media saved - Media saved without any errors - Member saved - Stylesheet Property Saved - Stylesheet saved - Template saved - Error saving user (check log) - User Saved - User type saved - File not saved - file could not be saved. Please check file permissions - File saved - File saved without any errors - Language saved - Media Type saved - Member Type saved - Python script not saved - Python script could not be saved due to error - Python script saved - No errors in python script - Template not saved - Please make sure that you do not have 2 templates with the same alias - Template saved - Template saved without any errors! - XSLT not saved - XSLT contained an error - XSLT could not be saved, check file permissions - XSLT saved - No errors in XSLT - Content unpublished - Partial view saved - Partial view saved without any errors! - Partial view not saved - An error occurred saving the file. - Script view saved - Script view saved without any errors! - Script view not saved - An error occurred saving the file. - An error occurred saving the file. - - - Uses CSS syntax ex: h1, .redHeader, .blueTex - Edit stylesheet - Edit stylesheet property - Name to identify the style property in the rich text editor - Preview - Styles - - - Edit template - Insert content area - Insert content area placeholder - Insert dictionary item - Insert Macro - Insert Umbraco page field - Master template - Quick Guide to Umbraco template tags - Template - - - Choose type of content - Choose a layout - Add a row - Add content - Drop content - Settings applied + ]]> + + + + You have not configured any approved colours + + + You have picked a content item currently deleted or in the recycle bin + You have picked content items currently deleted or in the recycle bin + + + You have picked a media item currently deleted or in the recycle bin + You have picked media items currently deleted or in the recycle bin + Deleted item + Trashed + + + enter external link + choose internal page + Caption + Link + Open in new window + enter the display caption + Enter the link + + + Reset + Define crop + Give the crop an alias and its default width and height + Save crop + Add new crop + + + Current version + Red text will not be shown in the selected version. , green means added]]> + Document has been rolled back + This displays the selected version as HTML, if you wish to see the difference between 2 versions at the same time, use the diff view + Rollback to + Select version + View + + + Edit script file + + + Concierge + Content + Courier + Developer + Umbraco Configuration Wizard + Media + Members + Newsletters + Settings + Statistics + Translation + Users + Help + Forms + Analytics + + + go to + Help topics for + Video chapters for + The best Umbraco video tutorials + + + Default template + Dictionary Key + To import a document type, find the ".udt" file on your computer by clicking the "Browse" button and click "Import" (you'll be asked for confirmation on the next screen) + New Tab Title + Node type + Type + Stylesheet + Script + Stylesheet property + Tab + Tab Title + Tabs + Master Content Type enabled + This Content Type uses + as a Master Content Type. Tabs from Master Content Types are not shown and can only be edited on the Master Content Type itself + No properties defined on this tab. Click on the "add a new property" link at the top to create a new property. + Master Document Type + Create matching template + Add icon + + + Sort order + Creation date + Sorting complete. + Drag the different items up or down below to set how they should be arranged. Or click the column headers to sort the entire collection of items + + + + Validation + Validation errors must be fixed before the item can be saved + Failed + Saved + Insufficient user permissions, could not complete the operation + Cancelled + Operation was cancelled by a 3rd party add-in + Publishing was cancelled by a 3rd party add-in + Property type already exists + Property type created + DataType: %1%]]> + Propertytype deleted + Document Type saved + Tab created + Tab deleted + Tab with id: %0% deleted + Stylesheet not saved + Stylesheet saved + Stylesheet saved without any errors + Datatype saved + Dictionary item saved + Publishing failed because the parent page isn't published + Content published + and visible on the website + Content saved + Remember to publish to make changes visible + Sent For Approval + Changes have been sent for approval + Media saved + Media saved without any errors + Member saved + Stylesheet Property Saved + Stylesheet saved + Template saved + Error saving user (check log) + User Saved + User type saved + User group saved + File not saved + file could not be saved. Please check file permissions + File saved + File saved without any errors + Language saved + Media Type saved + Member Type saved + Python script not saved + Python script could not be saved due to error + Python script saved + No errors in python script + Template not saved + Please make sure that you do not have 2 templates with the same alias + Template saved + Template saved without any errors! + XSLT not saved + XSLT contained an error + XSLT could not be saved, check file permissions + XSLT saved + No errors in XSLT + Content unpublished + Partial view saved + Partial view saved without any errors! + Partial view not saved + An error occurred saving the file. + Permissions saved for + Script view saved + Script view saved without any errors! + Script view not saved + An error occurred saving the file. + An error occurred saving the file. + Deleted %0% user groups + %0% was deleted + Enabled %0% users + An error occurred while enabling the users + Disabled %0% users + An error occurred while disabling the users + %0% is now enabled + An error occurred while enabling the user + %0% is now disabled + An error occurred while disabling the user + User groups have been set + Deleted %0% user groups + %0% was deleted + Unlocked %0% users + An error occurred while unlocking the users + %0% is now unlocked + An error occurred while unlocking the user + Member was exported to file + An error occurred while exporting the member + User %0% was deleted + Invite user + Invitation has been re-sent to %0% + + + Uses CSS syntax ex: h1, .redHeader, .blueTex + Edit stylesheet + Edit stylesheet property + Name to identify the style property in the rich text editor + Preview + Styles + - This content is not allowed here - This content is allowed here + + Edit template - Click to embed - Click to insert image - Image caption... - Write here... + Sections + Insert content area + Insert content area placeholder - Grid Layouts - Layouts are the overall work area for the grid editor, usually you only need one or two different layouts - Add Grid Layout - Adjust the layout by setting column widths and adding additional sections - Row configurations - Rows are predefined cells arranged horizontally - Add row configuration - Adjust the row by setting cell widths and adding additional cells + Insert + Choose what to insert into your template - Columns - Total combined number of columns in the grid layout + Dictionary item + A dictionary item is a placeholder for a translatable piece of text, which makes it easy to create designs for multilingual websites. - Settings - Configure what settings editors can change + Macro + + A Macro is a configurable component which is great for + reusable parts of your design, where you need the option to provide parameters, + such as galleries, forms and lists. + - Styles - Configure what styling editors can change + Value + Displays the value of a named field from the current page, with options to modify the value or fallback to alternative values. - Settings will only save if the entered json configuration is valid + Partial view + + A partial view is a separate template file which can be rendered inside another + template, it's great for reusing markup or for separating complex templates into separate files. + - Allow all editors - Allow all row configurations - Set as default - Choose extra - Choose default - are added - + Master template + No master template + No master - + Render child template + + @RenderBody() placeholder. + ]]> + - Compositions - You have not added any tabs - Add new tab - Add another tab - Inherited from - Add property - Required label - Enable list view - Configures the content item to show a sortable and searchable list of its children, the children will not be shown in the tree + Define a named section + + @section { ... }. This can be rendered in a + specific area of the parent of this template, by using @RenderSection. + ]]> + - Allowed Templates - Choose which templates editors are allowed to use on content of this type + Render a named section + + @RenderSection(name) placeholder. + This renders an area of a child template which is wrapped in a corresponding @section [name]{ ... } definition. + ]]> + - Allow as root - Allow editors to create content of this type in the root of the content tree - Yes - allow content of this type in the root + Section Name + Section is mandatory + + If mandatory, the child template must contain a @section definition, otherwise an error is shown. + - Allowed child node types - Allow content of the specified types to be created underneath content of this type - Choose child node + Query builder + Build a query + items returned, in - Inherit tabs and properties from an existing document type. New tabs will be added to the current document type or merged if a tab with an identical name exists. - This content type is used in a composition, and therefore cannot be composed itself. - There are no content types available to use as a composition. + I want + all content + content of type "%0%" + from + my website + where + and - Available editors - Reuse - Editor settings + is + is not + before + before (including selected date) + after + after (including selected date) + equals + does not equal + contains + does not contain + greater than + greater than or equal to + less than + less than or equal to - Configuration + Id + Name + Created Date + Last Updated Date - Yes, delete + order by + ascending + descending - was moved underneath - was copied underneath - Select the folder to move - Select the folder to copy - to in the tree structure below + Template - All Document types - All Documents - All media items + - using this document type will be deleted permanently, please confirm you want to delete these as well. - using this media type will be deleted permanently, please confirm you want to delete these as well. - using this member type will be deleted permanently, please confirm you want to delete these as well + + Rich Text Editor + Image + Macro + Embed + Headline + Quote + Choose type of content + Choose a layout + Add a row + Add content + Drop content + Settings applied - and all documents using this type - and all media items using this type - and all members using this type + This content is not allowed here + This content is allowed here - using this editor will get updated with the new settings + Click to embed + Click to insert image + Image caption... + Write here... - Member can edit - Show on member profile + Grid Layouts + Layouts are the overall work area for the grid editor, usually you only need one or two different layouts + Add Grid Layout + Adjust the layout by setting column widths and adding additional sections + Row configurations + Rows are predefined cells arranged horizontally + Add row configuration + Adjust the row by setting cell widths and adding additional cells - + Columns + Total combined number of columns in the grid layout - - Alternative field - Alternative Text - Casing - Encoding - Choose field - Convert line breaks - Replaces line breaks with html-tag &lt;br&gt; - Custom Fields - Yes, Date only - Format as date - HTML encode - Will replace special characters by their HTML equivalent. - Will be inserted after the field value - Will be inserted before the field value - Lowercase - None - Insert after field - Insert before field - Recursive - Remove Paragraph tags - Will remove any &lt;P&gt; in the beginning and end of the text - Standard Fields - Uppercase - URL encode - Will format special characters in URLs - Will only be used when the field values above are empty - This field will only be used if the primary field is empty - Yes, with time. Separator: - - - Tasks assigned to you - assigned to you. To see a detailed view including comments, click on "Details" or just the page name. + Settings + Configure what settings editors can change + + Styles + Configure what styling editors can change + + Settings will only save if the entered json configuration is valid + + Allow all editors + Allow all row configurations + Maximum items + Leave blank or set to 0 for unlimited + Set as default + Choose extra + Choose default + are added + + + + + Compositions + You have not added any tabs + Add new tab + Add another tab + Inherited from + Add property + Required label + + Enable list view + Configures the content item to show a sortable and searchable list of its children, the children will not be shown in the tree + + Allowed Templates + Choose which templates editors are allowed to use on content of this type + + Allow as root + Allow editors to create content of this type in the root of the content tree + Yes - allow content of this type in the root + + Allowed child node types + Allow content of the specified types to be created underneath content of this type + + Choose child node + + Inherit tabs and properties from an existing document type. New tabs will be added to the current document type or merged if a tab with an identical name exists. + This content type is used in a composition, and therefore cannot be composed itself. + There are no content types available to use as a composition. + + Available editors + Reuse + Editor settings + + Configuration + + Yes, delete + + was moved underneath + was copied underneath + Select the folder to move + Select the folder to copy + to in the tree structure below + + All Document types + All Documents + All media items + + using this document type will be deleted permanently, please confirm you want to delete these as well. + using this media type will be deleted permanently, please confirm you want to delete these as well. + using this member type will be deleted permanently, please confirm you want to delete these as well + + and all documents using this type + and all media items using this type + and all members using this type + + using this editor will get updated with the new settings + + Member can edit + Allow this property value to be edited by the member on their profile page + Is sensitive data + Hide this property value from content editors that don't have access to view sensitive information + Show on member profile + Allow this property value to be displayed on the member profile page + + tab has no sort order + + Where is this composition used? + This composition is currently used in the composition of the following content types: + + + + Building models + this can take a bit of time, don't worry + Models generated + Models could not be generated + Models generation has failed, see exception in U log + + + + Add fallback field + Fallback field + Add default value + Default value + Fallback field + Default value + Casing + Encoding + Choose field + Convert line breaks + Yes, convert line breaks + Replaces line breaks with 'br' html tag + Custom Fields + Date only + Format and encoding + Format as date + Format the value as a date, or a date with time, according to the active culture + HTML encode + Will replace special characters by their HTML equivalent. + Will be inserted after the field value + Will be inserted before the field value + Lowercase + Modify output + None + Output sample + Insert after field + Insert before field + Recursive + Yes, make it recursive + Separator + Standard Fields + Uppercase + URL encode + Will format special characters in URLs + Will only be used when the field values above are empty + This field will only be used if the primary field is empty + Date and time + + + Tasks assigned to you + + assigned to you. To see a detailed view including comments, click on "Details" or just the page name. You can also download the page as XML directly by clicking the "Download Xml" link.
To close a translation task, please go to the Details view and click the "Close" button. - ]]>
- close task - Translation details - Download all translation tasks as XML - Download XML - Download XML DTD - Fields - Include subpages - + + close task + Translation details + Download all translation tasks as XML + Download XML + Download XML DTD + Fields + Include subpages + + - [%0%] Translation task for %1% - No translator users found. Please create a translator user before you start sending content to translation - Tasks created by you - created by you. To see a detailed view including comments, + ]]> + + [%0%] Translation task for %1% + No translator users found. Please create a translator user before you start sending content to translation + Tasks created by you + + created by you. To see a detailed view including comments, click on "Details" or just the page name. You can also download the page as XML directly by clicking the "Download Xml" link. To close a translation task, please go to the Details view and click the "Close" button. - ]]> - The page '%0%' has been send to translation - Please select the language that the content should be translated into - Send the page '%0%' to translation - Assigned by - Task opened - Total words - Translate to - Translation completed. - You can preview the pages, you've just translated, by clicking below. If the original page is found, you will get a comparison of the 2 pages. - Translation failed, the XML file might be corrupt - Translation options - Translator - Upload translation XML - - - Cache Browser - Recycle Bin - Created packages - Data Types - Dictionary - Installed packages - Install skin - Install starter kit - Languages - Install local package - Macros - Media Types - Members - Member Groups - Roles - Member Types - Document Types - Relation Types - Packages - Packages - Python Files - Install from repository - Install Runway - Runway modules - Scripting Files - Scripts - Stylesheets - Templates - XSLT Files - Analytics - - - New update ready - %0% is ready, click here for download - No connection to server - Error checking for update. Please review trace-stack for further information - - - Administrator - Category field - Change Your Password - New password - Confirm new password - You can change your password for accessing the Umbraco Back Office by filling out the form below and click the 'Change Password' button - Content Channel - Description field - Disable User - Document Type - Editor - Excerpt field - Language - Username - Start Node in Media Library - Sections - Disable Umbraco Access - Old password - Password - Reset password - Your password has been changed! - Please confirm the new password - Enter your new password - Your new password cannot be blank! - Current password - Invalid current password - There was a difference between the new password and the confirmed password. Please try again! - The confirmed password doesn't match the new password! - Replace child node permissions - You are currently modifying permissions for the pages: - Select pages to modify their permissions - Search all children - Start Node in Content - Name - User permissions - User type - User types - Writer - Translator - Change - Your profile - Your recent history - Session expires in - - - Validation - Validate as email - Validate as a number - Validate as a Url - ...or enter a custom validation - Field is mandatory - - - +
+ + + + + + + + + + + + + + +
+
+
+ + + + +
+ + + + +
+

+ Hi %0%, +

+

+ You have been invited by %1% to the Umbraco Back Office. +

+

+ Message from %1%: +
+ %2% +

+ + + + + + +
+ + + + + + +
+ + Click this link to accept the invite + +
+
+

If you cannot click on the link, copy and paste this URL into your browser window:

+ + + + +
+ + %3% + +
+

+
+
+


+
+
+ + ]]> +
+ Invite + Resending invitation... + Delete User + Are you sure you wish to delete this user account? + + + + Validation + Validate as an email address + Validate as a number + Validate as a URL + ...or enter a custom validation + Field is mandatory + Enter a regular expression + You need to add at least + You can only have + items + items selected + Invalid date + Not a number + Invalid email + + + - Value is set to the recommended value: '%0%'. - Value was set to '%1%' for XPath '%2%' in configuration file '%3%'. - Expected value '%1%' for '%2%' in configuration file '%3%', but found '%0%'. - Found unexpected value '%0%' for '%2%' in configuration file '%3%'. + Value is set to the recommended value: '%0%'. + Value was set to '%1%' for XPath '%2%' in configuration file '%3%'. + Expected value '%1%' for '%2%' in configuration file '%3%', but found '%0%'. + Found unexpected value '%0%' for '%2%' in configuration file '%3%'. - - Custom errors are set to '%0%'. - Custom errors are currently set to '%0%'. It is recommended to set this to '%1%' before go live. - Custom errors successfully set to '%0%'. + Custom errors are set to '%0%'. + Custom errors are currently set to '%0%'. It is recommended to set this to '%1%' before go live. + Custom errors successfully set to '%0%'. - MacroErrors are set to '%0%'. - MacroErrors are set to '%0%' which will prevent some or all pages in your site from loading completely if there are any errors in macros. Rectifying this will set the value to '%1%'. - MacroErrors are now set to '%0%'. + MacroErrors are set to '%0%'. + MacroErrors are set to '%0%' which will prevent some or all pages in your site from loading completely if there are any errors in macros. Rectifying this will set the value to '%1%'. + MacroErrors are now set to '%0%'. - - Try Skip IIS Custom Errors is set to '%0%' and you're using IIS version '%1%'. - Try Skip IIS Custom Errors is currently '%0%'. It is recommended to set this to '%1%' for your IIS version (%2%). - Try Skip IIS Custom Errors successfully set to '%0%'. + Try Skip IIS Custom Errors is set to '%0%' and you're using IIS version '%1%'. + Try Skip IIS Custom Errors is currently '%0%'. It is recommended to set this to '%1%' for your IIS version (%2%). + Try Skip IIS Custom Errors successfully set to '%0%'. - - File does not exist: '%0%'. - '%0%' in config file '%1%'.]]> - There was an error, check log for full error: %0%. + + File does not exist: '%0%'. + '%0%' in config file '%1%'.]]> + There was an error, check log for full error: %0%. - Members - Total XML: %0%, Total: %1%, Total invalid: %2% - Media - Total XML: %0%, Total: %1%, Total invalid: %2% - Content - Total XML: %0%, Total published: %1%, Total invalid: %2% + Members - Total XML: %0%, Total: %1%, Total invalid: %2% + Media - Total XML: %0%, Total: %1%, Total invalid: %2% + Content - Total XML: %0%, Total published: %1%, Total invalid: %2% - Your site certificate was marked as valid. - Certificate validation error: '%0%' - Error pinging the URL %0% - '%1%' - You are currently %0% viewing the site using the HTTPS scheme. - The appSetting 'umbracoUseSSL' is set to 'false' in your web.config file. Once you access this site using the HTTPS scheme, that should be set to 'true'. - The appSetting 'umbracoUseSSL' is set to '%0%' in your web.config file, your cookies are %1% marked as secure. - Could not update the 'umbracoUseSSL' setting in your web.config file. Error: %0% + Database - The database schema is correct for this version of Umbraco + %0% problems were detected with your database schema (Check the log for details) + Some errors were detected while validating the database schema against the current version of Umbraco. - - Enable HTTPS - Sets umbracoSSL setting to true in the appSettings of the web.config file. - The appSetting 'umbracoUseSSL' is now set to 'true' in your web.config file, your cookies will be marked as secure. + Your website's certificate is valid. + Certificate validation error: '%0%' + Your website's SSL certificate has expired. + Your website's SSL certificate is expiring in %0% days. + Error pinging the URL %0% - '%1%' + You are currently %0% viewing the site using the HTTPS scheme. + The appSetting 'umbracoUseSSL' is set to 'false' in your web.config file. Once you access this site using the HTTPS scheme, that should be set to 'true'. + The appSetting 'umbracoUseSSL' is set to '%0%' in your web.config file, your cookies are %1% marked as secure. + Could not update the 'umbracoUseSSL' setting in your web.config file. Error: %0% - Fix - Cannot fix a check with a value comparison type of 'ShouldNotEqual'. - Cannot fix a check with a value comparison type of 'ShouldEqual' with a provided value. - Value to fix check not provided. + + Enable HTTPS + Sets umbracoSSL setting to true in the appSettings of the web.config file. + The appSetting 'umbracoUseSSL' is now set to 'true' in your web.config file, your cookies will be marked as secure. - Debug compilation mode is disabled. - Debug compilation mode is currently enabled. It is recommended to disable this setting before go live. - Debug compilation mode successfully disabled. + Fix + Cannot fix a check with a value comparison type of 'ShouldNotEqual'. + Cannot fix a check with a value comparison type of 'ShouldEqual' with a provided value. + Value to fix check not provided. - Trace mode is disabled. - Trace mode is currently enabled. It is recommended to disable this setting before go live. - Trace mode successfully disabled. + Debug compilation mode is disabled. + Debug compilation mode is currently enabled. It is recommended to disable this setting before go live. + Debug compilation mode successfully disabled. - All folders have the correct permissions set. - - %0%.]]> - %0%. If they aren't being written to no action need be taken.]]> + %0%.]]> + %0%. If they aren't being written to no action need be taken.]]> - All files have the correct permissions set. - - %0%.]]> - %0%. If they aren't being written to no action need be taken.]]> + %0%.]]> + %0%. If they aren't being written to no action need be taken.]]> - X-Frame-Options used to control whether a site can be IFRAMEd by another was found.]]> - X-Frame-Options used to control whether a site can be IFRAMEd by another was not found.]]> - Set Header in Config - Adds a value to the httpProtocol/customHeaders section of web.config to prevent the site being IFRAMEd by other websites. - A setting to create a header preventing IFRAMEing of the site by other websites has been added to your web.config file. - Could not update web.config file. Error: %0% + X-Frame-Options used to control whether a site can be IFRAMEd by another was found.]]> + X-Frame-Options used to control whether a site can be IFRAMEd by another was not found.]]> + Set Header in Config + Adds a value to the httpProtocol/customHeaders section of web.config to prevent the site being IFRAMEd by other websites. + A setting to create a header preventing IFRAMEing of the site by other websites has been added to your web.config file. + Could not update web.config file. Error: %0% - - %0%.]]> - No headers revealing information about the website technology were found. + %0%.]]> + No headers revealing information about the website technology were found. - In the Web.config file, system.net/mailsettings could not be found. - In the Web.config file system.net/mailsettings section, the host is not configured. - SMTP settings are configured correctly and the service is operating as expected. - The SMTP server configured with host '%0%' and port '%1%' could not be reached. Please check to ensure the SMTP settings in the Web.config file system.net/mailsettings are correct. + In the Web.config file, system.net/mailsettings could not be found. + In the Web.config file system.net/mailsettings section, the host is not configured. + SMTP settings are configured correctly and the service is operating as expected. + The SMTP server configured with host '%0%' and port '%1%' could not be reached. Please check to ensure the SMTP settings in the Web.config file system.net/mailsettings are correct. - %0%.]]> - %0%.]]> - - - Disable URL tracker - Enable URL tracker - Original URL - Redirected To - No redirects have been made - When a published page gets renamed or moved a redirect will automatically be made to the new page. - Remove - Are you sure you want to remove the redirect from '%0%' to '%1%'? - Redirect URL removed. - Error removing redirect URL. - Are you sure you want to disable the URL tracker? - URL tracker has now been disabled. - Error disabling the URL tracker, more information can be found in your log file. - URL tracker has now been enabled. - Error enabling the URL tracker, more information can be found in your log file. - + %0%.]]> + %0%.]]> +

Results of the scheduled Umbraco Health Checks run on %0% at %1% are as follows:

%2%]]>
+ Umbraco Health Check Status + + + Disable URL tracker + Enable URL tracker + Original URL + Redirected To + No redirects have been made + When a published page gets renamed or moved a redirect will automatically be made to the new page. + Remove + Are you sure you want to remove the redirect from '%0%' to '%1%'? + Redirect URL removed. + Error removing redirect URL. + Are you sure you want to disable the URL tracker? + URL tracker has now been disabled. + Error disabling the URL tracker, more information can be found in your log file. + URL tracker has now been enabled. + Error enabling the URL tracker, more information can be found in your log file. + + + No Dictionary items to choose from + + + characters left + + + Trashed content with Id: {0} related to original parent content with Id: {1} + Trashed media with Id: {0} related to original parent media item with Id: {1} + Cannot automatically restore this item + There is no 'restore' relation found for this node. Use the Move menu item to move it manually. + The item you want to restore it under ('%0%') is in the recycle bin. Use the Move menu item to move the item manually. + diff --git a/WebCms/Umbraco/Config/Lang/en_us.xml b/WebCms/Umbraco/Config/Lang/en_us.xml index 137b3c4..e809699 100644 --- a/WebCms/Umbraco/Config/Lang/en_us.xml +++ b/WebCms/Umbraco/Config/Lang/en_us.xml @@ -1,649 +1,847 @@ - - The Umbraco community - http://our.umbraco.org/documentation/Extending-Umbraco/Language-Files - - - Culture and Hostnames - Audit Trail - Browse Node - Change Document Type - Copy - Create - Create Package - Delete - Disable - Empty recycle bin - Export Document Type - Import Document Type - Import Package - Edit in Canvas - Exit - Move - Notifications - Public access - Publish - Unpublish - Reload - Republish entire site - Set permissions for the page %0% - Choose where to move - to in the tree structure below - Restore - Permissions - Rollback - Send To Publish - Send To Translation - Sort - Send to publication - Translate - Update - Default value - - - Permission denied. - Add new Domain - remove - Invalid node. - Invalid domain format. - Domain has already been assigned. - Language - Domain - New domain '%0%' has been created - Domain '%0%' is deleted - Domain '%0%' has already been assigned - Domain '%0%' has been updated - Edit Current Domains - + The Umbraco community + https://our.umbraco.com/documentation/Extending-Umbraco/Language-Files + + + Culture and Hostnames + Audit Trail + Browse Node + Change Document Type + Copy + Create + Export + Create Package + Create group + Delete + Disable + Empty recycle bin + Enable + Export Document Type + Import Document Type + Import Package + Edit in Canvas + Exit + Move + Notifications + Public access + Publish + Unpublish + Reload + Republish entire site + Rename + Restore + Set permissions for the page %0% + Choose where to move + In the tree structure below + Permissions + Rollback + Send To Publish + Send To Translation + Set group + Sort + Translate + Update + Set permissions + Unlock + Create Content Template + Resend Invitation + + + Content + Administration + Structure + Other + + + Allow access to assign culture and hostnames + Allow access to view a node's history log + Allow access to view a node + Allow access to change document type for a node + Allow access to copy a node + Allow access to create nodes + Allow access to delete nodes + Allow access to move a node + Allow access to set and change public access for a node + Allow access to publish a node + Allow access to change permissions for a node + Allow access to roll back a node to a previous state + Allow access to send a node for approval before publishing + Allow access to send a node for translation + Allow access to change the sort order for nodes + Allow access to translate a node + Allow access to save a node + Allow access to create a Content Template + + + Permission denied. + Add new Domain + remove + Invalid node. + Invalid domain format. + Domain has already been assigned. + Language + Domain + New domain '%0%' has been created + Domain '%0%' is deleted + Domain '%0%' has already been assigned + Domain '%0%' has been updated + Edit Current Domains + + - Inherit - Culture - or inherit culture from parent nodes. Will also apply
- to the current node, unless a domain below applies too.]]>
- Domains - - - Viewing for - - - Clear selection - Select - Select current folder - Do something else - Bold - Cancel Paragraph Indent - Insert form field - Insert graphic headline - Edit Html - Indent Paragraph - Italic - Center - Justify Left - Justify Right - Insert Link - Insert local link (anchor) - Bullet List - Numeric List - Insert macro - Insert picture - Edit relations - Return to list - Save - Save and publish - Save and send for approval - Save list view - Preview - Preview is disabled because there's no template assigned - Choose style - Show styles - Insert table - Generate models - Save and generate models - - - To change the document type for the selected content, first select from the list of valid types for this location. - Then confirm and/or amend the mapping of properties from the current type to the new, and click Save. - The content has been re-published. - Current Property - Current type - The document type cannot be changed, as there are no alternatives valid for this location. An alternative will be valid if it is allowed under the parent of the selected content item and that all existing child content items are allowed to be created under it. - Document Type Changed - Map Properties - Map to Property - New Template - New Type - none - Content - Select New Document Type - The document type of the selected content has been successfully changed to [new type] and the following properties mapped: - to - Could not complete property mapping as one or more properties have more than one mapping defined. - Only alternate types valid for the current location are displayed. - - - Is Published - About this page - Alias - (how would you describe the picture over the phone) - Alternative Links - Click to edit this item - Created by - Original author - Updated by - Created - Date/time this document was created - Document Type - Editing - Remove at - This item has been changed after publication - This item is not published - Last published - There are no items to show - There are no items to show in the list. - Media Type - Link to media item(s) - Member Group - Role - Member Type - No date chosen - Link title - Properties - This document is published but is not visible because the parent '%0%' is unpublished - This document is published but is not in the cache - Could not get the url - This document is published but its url would collide with content %0% - Publish - Publication Status - Publish at - Unpublish at - Clear Date - Sortorder is updated - To sort the nodes, simply drag the nodes or click one of the column headers. You can select multiple nodes by holding the "shift" or "control" key while selecting - Statistics - Title (optional) - Alternative text (optional) - Type - Unpublish - Last edited - Date/time this document was edited - Remove file(s) - Link to document - Member of group(s) - Not a member of group(s) - Child items - Target - This translates to the following time on the server: - What does this mean?]]> - - - Click to upload - Drop your files here... - Link to media - or click here to choose files - Only allowed file types are - Cannot upload this file, it does not have an approved file type - Max file size is - - - Create a new member - All Members - - - Where do you want to create the new %0% - Create an item under - Choose a type and a title - "document types".]]> - "media types".]]> - Document Type without a template - New folder - New data type - - - Browse your website - - Hide - If Umbraco isn't opening, you might need to allow popups from this site - has opened in a new window - Restart - Visit - Welcome - - - Stay - Discard changes - You have unsaved changes - Are you sure you want to navigate away from this page? - you have unsaved changes - - - Done + should be avoided. Better use the culture setting above.]]> +
+ Inherit + Culture + + or inherit culture from parent nodes. Will also apply
+ to the current node, unless a domain below applies too.]]> +
+ Domains + + + Clear selection + Select + Select current folder + Do something else + Bold + Cancel Paragraph Indent + Insert form field + Insert graphic headline + Edit Html + Indent Paragraph + Italic + Center + Justify Left + Justify Right + Insert Link + Insert local link (anchor) + Bullet List + Numeric List + Insert macro + Insert picture + Edit relations + Return to list + Save + Save and publish + Save and schedule + Save and send for approval + Save list view + Preview + Preview is disabled because there's no template assigned + Choose style + Show styles + Insert table + Generate models + Save and generate models + Undo + Redo + Delete tag + Cancel + Confirm + + + Viewing for + Delete Content performed by user + UnPublish performed by user + Save and Publish performed by user + Save Content performed by user + Move Content performed by user + Copy Content performed by user + Content rollback performed by user + Content Send To Publish performed by user + Content Send To Translation performed by user + Copy + Publish + Move + Save + Delete + Unpublish + Rollback + Send To Publish + Send To Translation + - Deleted %0% item - Deleted %0% items - Deleted %0% out of %1% item - Deleted %0% out of %1% items + + To change the document type for the selected content, first select from the list of valid types for this location. + Then confirm and/or amend the mapping of properties from the current type to the new, and click Save. + The content has been re-published. + Current Property + Current type + The document type cannot be changed, as there are no alternatives valid for this location. An alternative will be valid if it is allowed under the parent of the selected content item and that all existing child content items are allowed to be created under it. + Document Type Changed + Map Properties + Map to Property + New Template + New Type + none + Content + Select New Document Type + The document type of the selected content has been successfully changed to [new type] and the following properties mapped: + to + Could not complete property mapping as one or more properties have more than one mapping defined. + Only alternate types valid for the current location are displayed. + + + Is Published + About this page + Alias + (how would you describe the picture over the phone) + Alternative Links + Click to edit this item + Created by + Original author + Updated by + Created + Date/time this document was created + Document Type + Editing + Remove at + This item has been changed after publication + This item is not published + Last published + There are no items to show + There are no items to show in the list. + No content has been added + No members have been added + Media Type + Link to media item(s) + Member Group + Role + Member Type + No changes have been made + No date chosen + Page title + This media item has no link + Properties + This document is published but is not visible because the parent '%0%' is unpublished + This document is published but is not in the cache + Could not get the url + This document is published but its url would collide with content %0% + Publish + Published + Published (pending changes) + Publication Status + Publish at + Unpublish at + Clear Date + Set date + Sortorder is updated + To sort the nodes, simply drag the nodes or click one of the column headers. You can select multiple nodes by holding the "shift" or "control" key while selecting + Statistics + Title (optional) + Alternative text (optional) + Type + Unpublish + Unpublished + Last edited + Date/time this document was edited + Remove file(s) + Link to document + Member of group(s) + Not a member of group(s) + Child items + Target + This translates to the following time on the server: + What does this mean?]]> + Are you sure you want to delete this item? + Property %0% uses editor %1% which is not supported by Nested Content. + Add another text box + Remove this text box + Content root + This value is hidden. If you need access to view this value please contact your website administrator. + This value is hidden. + + + Create a new Content Template from '%0%' + Blank + Select a Content Template + Content Template created + A Content Template was created from '%0%' + Another Content Template with the same name already exists + A Content Template is pre-defined content that an editor can select to use as the basis for creating new content + + + Click to upload + Drop your files here... + Link to media + or click here to choose files + You can drag files here to upload + Only allowed file types are + Cannot upload this file, it does not have an approved file type + Max file size is + Media root + + + Create a new member + All Members + + + Where do you want to create the new %0% + Create an item under + Select the document type you want to make a content template for + Choose a type and a title + "document types".]]> + "media types".]]> + Document Type without a template + New folder + New data type + New javascript file + New empty partial view + New partial view macro + New partial view from snippet + New empty partial view macro + New partial view macro from snippet + New partial view macro (without macro) + + + Browse your website + - Hide + If Umbraco isn't opening, you might need to allow popups from this site + has opened in a new window + Restart + Visit + Welcome + + + Stay + Discard changes + You have unsaved changes + Are you sure you want to navigate away from this page? - you have unsaved changes + Unpublishing will remove this page and all its descendants from the site. + + + Done - Published %0% item - Published %0% items - Published %0% out of %1% item - Published %0% out of %1% items + Deleted %0% item + Deleted %0% items + Deleted %0% out of %1% item + Deleted %0% out of %1% items - Unpublished %0% item - Unpublished %0% items - Unpublished %0% out of %1% item - Unpublished %0% out of %1% items + Published %0% item + Published %0% items + Published %0% out of %1% item + Published %0% out of %1% items - Moved %0% item - Moved %0% items - Moved %0% out of %1% item - Moved %0% out of %1% items + Unpublished %0% item + Unpublished %0% items + Unpublished %0% out of %1% item + Unpublished %0% out of %1% items - Copied %0% item - Copied %0% items - Copied %0% out of %1% item - Copied %0% out of %1% items - - - Name - Manage hostnames - Close this window - Are you sure you want to delete - Are you sure you want to disable - Please check this box to confirm deletion of %0% item(s) - Are you sure? - Are you sure? - Cut - Edit Dictionary Item - Edit Language - Insert local link - Insert character - Insert graphic headline - Insert picture - Insert link - Click to add a Macro - Insert table - Last Edited - Link - Internal link: - When using local links, insert "#" in front of link - Open in new window? - Macro Settings - This macro does not contain any properties you can edit - Paste - Edit Permissions for - The items in the recycle bin are now being deleted. Please do not close this window while this operation takes place - The recycle bin is now empty - When items are deleted from the recycle bin, they will be gone forever - regexlib.com's webservice is currently experiencing some problems, which we have no control over. We are very sorry for this inconvenience.]]> - Search for a regular expression to add validation to a form field. Example: 'email, 'zip-code' 'url' - Remove Macro - Required Field - Site is reindexed - The website cache has been refreshed. All publish content is now up to date. While all unpublished content is still unpublished - The website cache will be refreshed. All published content will be updated, while unpublished content will stay unpublished. - Number of columns - Number of rows - Set a placeholder id by setting an ID on your placeholder you can inject content into this template from child templates, - by referring this ID using a <asp:content /> element.]]> - Select a placeholder id from the list below. You can only - choose Id's from the current template's master.]]> - Click on the image to see full size - Pick item - View Cache Item - Create folder... - Relate to original - Include descendants - The friendliest community - Link to page - Opens the linked document in a new window or tab - Link to media - Select media - Select icon - Select item - Select link - Select macro - Select content - Select member - Select member group - There are no parameters for this macro - External login providers - Exception Details - Stacktrace - Inner Exception - Link your - Un-Link your - account - Select editor - - - Moved %0% item + Moved %0% items + Moved %0% out of %1% item + Moved %0% out of %1% items + + Copied %0% item + Copied %0% items + Copied %0% out of %1% item + Copied %0% out of %1% items + + + Link title + Link + Anchor / querystring + Name + Close this window + Are you sure you want to delete + Are you sure you want to disable + Please check this box to confirm deletion of %0% item(s) + Are you sure? + Are you sure? + Cut + Edit Dictionary Item + Edit Language + Insert local link + Insert character + Insert graphic headline + Insert picture + Insert link + Click to add a Macro + Insert table + Last Edited + Link + Internal link: + When using local links, insert "#" in front of link + Open in new window? + Macro Settings + This macro does not contain any properties you can edit + Paste + Edit permissions for + Set permissions for + Set permissions for %0% for user group %1% + Select the users groups you want to set permissions for + The items in the recycle bin are now being deleted. Please do not close this window while this operation takes place + The recycle bin is now empty + When items are deleted from the recycle bin, they will be gone forever + regexlib.com's webservice is currently experiencing some problems, which we have no control over. We are very sorry for this inconvenience.]]> + Search for a regular expression to add validation to a form field. Example: 'email, 'zip-code' 'url' + Remove Macro + Required Field + Site is reindexed + The website cache has been refreshed. All publish content is now up to date. While all unpublished content is still unpublished + The website cache will be refreshed. All published content will be updated, while unpublished content will stay unpublished. + Number of columns + Number of rows + + Set a placeholder id by setting an ID on your placeholder you can inject content into this template from child templates, + by referring this ID using a <asp:content /> element.]]> + + + Select a placeholder id from the list below. You can only + choose Id's from the current template's master.]]> + + Click on the image to see full size + Pick item + View Cache Item + Create folder... + Relate to original + Include descendants + The friendliest community + Link to page + Opens the linked document in a new window or tab + Link to media + Link to file + Select content start node + Select media + Select icon + Select item + Select link + Select macro + Select content + Select media start node + Select member + Select member group + Select node + Select sections + Select users + No icons were found + There are no parameters for this macro + There are no macros available to insert + External login providers + Exception Details + Stacktrace + Inner Exception + Link your + Un-link your + account + Select editor + Select snippet + + + + %0%' below
You can add additional languages under the 'languages' in the menu on the left - ]]>
- Culture Name - Edit the key of the dictionary item. - - + + Culture Name + Edit the key of the dictionary item. + + - - - - Enter your username - Enter your password - Confirm your password - Name the %0%... - Enter a name... - Label... - Enter a description... - Type to search... - Type to filter... - Type to add tags (press enter after each tag)... - Enter your email - + + Dictionary overview + + + Enter your username + Enter your password + Confirm your password + Name the %0%... + Enter a name... + Enter an email... + Enter a username... + Label... + Enter a description... + Type to search... + Type to filter... + Type to add tags (press enter after each tag)... + Enter your email... + Enter a message... + Your username is usually your email + #value or ?key=value + + + Allow at root + Only Content Types with this checked can be created at the root level of Content and Media trees + Allowed child node types + Document Type Compositions + Create + Delete tab + Description + New tab + Tab + Thumbnail + Enable list view + Configures the content item to show a sortable & searchable list of its children, the children will not be shown in the tree + Current list view + The active list view data type + Create custom list view + Remove custom list view + A content type, media type or member type with this alias already exists + + + Renamed + Enter a new folder name here + %0% was renamed to %1% + + + Add prevalue + Database datatype + Property editor GUID + Property editor + Buttons + Enable advanced settings for + Enable context menu + Maximum default size of inserted images + Related stylesheets + Show label + Width and height + All property types & property data + using this data type will be deleted permanently, please confirm you want to delete these as well + Yes, delete + and all property types & property data using this data type + Select the folder to move + to in the tree structure below + was moved underneath + + + Your data has been saved, but before you can publish this page there are some errors you need to fix first: + The current membership provider does not support changing password (EnablePasswordRetrieval need to be true) + %0% already exists + There were errors: + There were errors: + The password should be a minimum of %0% characters long and contain at least %1% non-alpha numeric character(s) + %0% must be an integer + The %0% field in the %1% tab is mandatory + %0% is a mandatory field + %0% at %1% is not in a correct format + %0% is not in a correct format + + + Received an error from the server + The specified file type has been disallowed by the administrator + NOTE! Even though CodeMirror is enabled by configuration, it is disabled in Internet Explorer because it's not stable enough. + Please fill both alias and name on the new property type! + There is a problem with read/write access to a specific file or folder + Error loading Partial View script (file: %0%) + Error loading userControl '%0%' + Error loading customControl (Assembly: %0%, Type: '%1%') + Error loading MacroEngine script (file: %0%) + "Error parsing XSLT file: %0% + "Error reading XSLT file: %0% + Please enter a title + Please choose a type + You're about to make the picture larger than the original size. Are you sure that you want to proceed? + Error in python script + The python script has not been saved, because it contained error(s) + Startnode deleted, please contact your administrator + Please mark content before changing style + No active styles available + Please place cursor at the left of the two cells you wish to merge + You cannot split a cell that hasn't been merged. + Error in XSLT source + The XSLT has not been saved, because it contained error(s) + There is a configuration error with the data type used for this property, please check the data type + + + Options + About + Action + Actions + Add + Alias + All + Are you sure? + Back + Border + by + Cancel + Cell margin + Choose + Close + Close Window + Comment + Confirm + Constrain + Constrain proportions + Continue + Copy + Create + Database + Date + Default + Delete + Deleted + Deleting... + Design + Dictionary + Dimensions + Down + Download + Edit + Edited + Elements + Email + Error + Find + First + General + Groups + Height + Help + Hide + History + Icon + Import + Info + Inner margin + Insert + Install + Invalid + Justify + Label + Language + Last + Layout + Links + Loading + Locked + Login + Log off + Logout + Macro + Mandatory + Message + Move + More + Name + New + Next + No + of + Off + OK + Open + On + or + Order by + Password + Path + Placeholder ID + One moment please... + Previous + Properties + Email to receive form data + Recycle Bin + Your recycle bin is empty + Remaining + Remove + Rename + Renew + Required + Retrieve + Retry + Permissions + Scheduled Publishing + Search + Sorry, we can not find what you are looking for + No items have been added + Server + Show + Show page on Send + Size + Sort + Status + Submit + Type + Type to search... + under + Up + Update + Upgrade + Upload + Url + User + Username + Value + View + Welcome... + Width + Yes + Folder + Search results + Reorder + I am done reordering + Preview + Change password + to + List view + Saving... + current + Embed + Retrieve + selected + + + Black + Green + Yellow + Orange + Blue + Blue Grey + Grey + Brown + Light Blue + Cyan + Light Green + Lime + Amber + Deep Orange + Red + Pink + Purple + Deep Purple + Indigo + + + Add tab + Add property + Add editor + Add template + Add child node + Add child - - Allow at root - Only Content Types with this checked can be created at the root level of Content and Media trees - Allowed child node types - Document Type Compositions - Create - Delete tab - Description - New tab - Tab - Thumbnail - Enable list view - Configures the content item to show a sortable & searchable list of its children, the children will not be shown in the tree - Current list view - The active list view data type - Create custom list view - Remove custom list view - - - Add prevalue - Database datatype - Property editor GUID - Property editor - Buttons - Enable advanced settings for - Enable context menu - Maximum default size of inserted images - Related stylesheets - Show label - Width and height - - - Your data has been saved, but before you can publish this page there are some errors you need to fix first: - The current membership provider does not support changing password (EnablePasswordRetrieval need to be true) - %0% already exists - There were errors: - There were errors: - The password should be a minimum of %0% characters long and contain at least %1% non-alpha numeric character(s) - %0% must be an integer - The %0% field in the %1% tab is mandatory - %0% is a mandatory field - %0% at %1% is not in a correct format - %0% is not in a correct format - - - Received an error from the server - The specified file type has been disallowed by the administrator - NOTE! Even though CodeMirror is enabled by configuration, it is disabled in Internet Explorer because it's not stable enough. - Please fill both alias and name on the new property type! - There is a problem with read/write access to a specific file or folder - Error loading Partial View script (file: %0%) - Error loading userControl '%0%' - Error loading customControl (Assembly: %0%, Type: '%1%') - Error loading MacroEngine script (file: %0%) - "Error parsing XSLT file: %0% - "Error reading XSLT file: %0% - Please enter a title - Please choose a type - You're about to make the picture larger than the original size. Are you sure that you want to proceed? - Error in python script - The python script has not been saved, because it contained error(s) - Startnode deleted, please contact your administrator - Please mark content before changing style - No active styles available - Please place cursor at the left of the two cells you wish to merge - You cannot split a cell that hasn't been merged. - Error in XSLT source - The XSLT has not been saved, because it contained error(s) - There is a configuration error with the data type used for this property, please check the data type - - - About - Action - Actions - Add - Alias - All - Are you sure? - Back - Border - by - Cancel - Cell margin - Choose - Close - Close Window - Comment - Confirm - Constrain proportions - Continue - Copy - Create - Database - Date - Default - Delete - Deleted - Deleting... - Design - Dimensions - Down - Download - Edit - Edited - Elements - Email - Error - Find - Height - Help - Icon - Import - Inner margin - Insert - Install - Invalid - Justify - Label - Language - Layout - Loading - Locked - Login - Log off - Logout - Macro - Mandatory - Move - More - Name - New - Next - No - of - OK - Open - or - Password - Path - Placeholder ID - One moment please... - Previous - Properties - Email to receive form data - Recycle Bin - Your recycle bin is empty - Remaining - Rename - Renew - Required - Retry - Permissions - Search - Sorry, we can not find what you are looking for - Server - Show - Show page on Send - Size - Sort - Submit - Type - Type to search... - Up - Update - Upgrade - Upload - Url - User - Username - Value - View - Welcome... - Width - Yes - Folder - Search results - Reorder - I am done reordering - Preview - Change password - to - List view - Saving... - current - Embed - selected - - - Black - Green - Yellow - Orange - Blue - Red - - - Add tab - Add property - Add editor - Add template - Add child node - Add child + Edit data type - Edit data type + Navigate sections - Navigate sections + Shortcuts + show shortcuts - Shortcuts - show shortcuts + Toggle list view + Toggle allow as root - Toggle list view - Toggle allow as root - - - Background color - Bold - Text color - Font - Text - - - Page - - - The installer cannot connect to the database. - Could not save the web.config file. Please modify the connection string manually. - Your database has been found and is identified as - Database configuration - Comment/Uncomment lines + Remove line + Copy Lines Up + Copy Lines Down + Move Lines Up + Move Lines Down + + General + Editor + + + Background color + Bold + Text color + Font + Text + + + Page + + + The installer cannot connect to the database. + Could not save the web.config file. Please modify the connection string manually. + Your database has been found and is identified as + Database configuration + + install button to install the Umbraco %0% database - ]]> - Next to proceed.]]> - Database not found! Please check that the information in the "connection string" of the "web.config" file is correct.

+ ]]> +
+ Next to proceed.]]> + + Database not found! Please check that the information in the "connection string" of the "web.config" file is correct.

To proceed, please edit the "web.config" file (using Visual Studio or your favourite text editor), scroll to the bottom, add the connection string for your database in the key named "UmbracoDbDSN" and save the file.

Click the retry button when - done.
- More information on editing web.config here.

]]>
- + done.
+ More information on editing web.config here.

]]> +
+ + Please contact your ISP if necessary. - If you're installing on a local machine or server you might need information from your system administrator.]]> - + + + Press the upgrade button to upgrade your database to Umbraco %0%

Don't worry - no content will be deleted and everything will continue working afterwards!

- ]]>
- - Press Next to + ]]> + + + Press Next to proceed. ]]> - - next to continue the configuration wizard]]> - The Default users' password needs to be changed!]]> - The Default user has been disabled or has no access to Umbraco!

No further actions needs to be taken. Click Next to proceed.]]> - The Default user's password has been successfully changed since the installation!

No further actions needs to be taken. Click Next to proceed.]]> - The password is changed! - - ('admin') and password ('default'). It's important that the password is changed to something unique. - ]]> - Get a great start, watch our introduction videos - By clicking the next button (or modifying the umbracoConfigurationStatus in web.config), you accept the license for this software as specified in the box below. Notice that this Umbraco distribution consists of two different licenses, the open source MIT license for the framework and the Umbraco freeware license that covers the UI. - Not installed yet. - Affected files and folders - More information on setting up permissions for Umbraco here - You need to grant ASP.NET modify permissions to the following files/folders - Your permission settings are almost perfect!

- You can run Umbraco without problems, but you will not be able to install packages which are recommended to take full advantage of Umbraco.]]>
- How to Resolve - Click here to read the text version - video tutorial on setting up folder permissions for Umbraco or read the text version.]]> - Your permission settings might be an issue! + + next to continue the configuration wizard]]> + The Default users' password needs to be changed!]]> + The Default user has been disabled or has no access to Umbraco!

No further actions needs to be taken. Click Next to proceed.]]> + The Default user's password has been successfully changed since the installation!

No further actions needs to be taken. Click Next to proceed.]]> + The password is changed! + Get a great start, watch our introduction videos + By clicking the next button (or modifying the umbracoConfigurationStatus in web.config), you accept the license for this software as specified in the box below. Notice that this Umbraco distribution consists of two different licenses, the open source MIT license for the framework and the Umbraco freeware license that covers the UI. + Not installed yet. + Affected files and folders + More information on setting up permissions for Umbraco here + You need to grant ASP.NET modify permissions to the following files/folders + + Your permission settings are almost perfect!

+ You can run Umbraco without problems, but you will not be able to install packages which are recommended to take full advantage of Umbraco.]]> +
+ How to Resolve + Click here to read the text version + video tutorial on setting up folder permissions for Umbraco or read the text version.]]> + + Your permission settings might be an issue!

- You can run Umbraco without problems, but you will not be able to create folders or install packages which are recommended to take full advantage of Umbraco.]]>
- Your permission settings are not ready for Umbraco! + You can run Umbraco without problems, but you will not be able to create folders or install packages which are recommended to take full advantage of Umbraco.]]> + + + Your permission settings are not ready for Umbraco!

- In order to run Umbraco, you'll need to update your permission settings.]]>
- Your permission settings are perfect!

- You are ready to run Umbraco and install packages!]]>
- Resolving folder issue - Follow this link for more information on problems with ASP.NET and creating folders - Setting up folder permissions - + + + Your permission settings are perfect!

+ You are ready to run Umbraco and install packages!]]> +
+ Resolving folder issue + Follow this link for more information on problems with ASP.NET and creating folders + Setting up folder permissions + + - I want to start from scratch - - + + I want to start from scratch + + learn how) You can still choose to install Runway later on. Please go to the Developer section and choose Packages. - ]]> - You've just set up a clean Umbraco platform. What do you want to do next? - Runway is installed - + + You've just set up a clean Umbraco platform. What do you want to do next? + Runway is installed + + This is our list of recommended modules, check off the ones you would like to install, or view the full list of modules - ]]> - Only recommended for experienced users - I want to start with a simple website - - + + Only recommended for experienced users + I want to start with a simple website + + "Runway" is a simple website providing some basic document types and templates. The installer can set up Runway for you automatically, but you can easily edit, extend or remove it. It's not necessary and you can perfectly use Umbraco without it. However, @@ -654,87 +852,183 @@ Included with Runway: Home page, Getting Started page, Installing Modules page.
Optional Modules: Top Navigation, Sitemap, Contact, Gallery. - ]]>
- What is Runway - Step 1/5 Accept license - Step 2/5: Database configuration - Step 3/5: Validating File Permissions - Step 4/5: Check Umbraco security - Step 5/5: Umbraco is ready to get you started - Thank you for choosing Umbraco - Browse your new site -You installed Runway, so why not see how your new website looks.]]> - Further help and information -Get help from our award winning community, browse the documentation or watch some free videos on how to build a simple site, how to use packages and a quick guide to the Umbraco terminology]]> - Umbraco %0% is installed and ready for use - /web.config file and update the AppSetting key UmbracoConfigurationStatus in the bottom to the value of '%0%'.]]> - started instantly by clicking the "Launch Umbraco" button below.
If you are new to Umbraco, -you can find plenty of resources on our getting started pages.]]>
- Launch Umbraco -To manage your website, simply open the Umbraco back office and start adding content, updating the templates and stylesheets or add new functionality]]> - Connection to database failed. - Umbraco Version 3 - Umbraco Version 4 - Watch - Umbraco %0% for a fresh install or upgrading from version 3.0. + ]]> + + What is Runway + Step 1/5 Accept license + Step 2/5: Database configuration + Step 3/5: Validating File Permissions + Step 4/5: Check Umbraco security + Step 5/5: Umbraco is ready to get you started + Thank you for choosing Umbraco + + Browse your new site +You installed Runway, so why not see how your new website looks.]]> + + + Further help and information +Get help from our award winning community, browse the documentation or watch some free videos on how to build a simple site, how to use packages and a quick guide to the Umbraco terminology]]> + + Umbraco %0% is installed and ready for use + + /web.config file and update the AppSetting key UmbracoConfigurationStatus in the bottom to the value of '%0%'.]]> + + + started instantly by clicking the "Launch Umbraco" button below.
If you are new to Umbraco, +you can find plenty of resources on our getting started pages.]]> +
+ + Launch Umbraco +To manage your website, simply open the Umbraco back office and start adding content, updating the templates and stylesheets or add new functionality]]> + + Connection to database failed. + Umbraco Version 3 + Umbraco Version 4 + Watch + + Umbraco %0% for a fresh install or upgrading from version 3.0.

- Press "next" to start the wizard.]]>
- - - Culture Code - Culture Name - - - You've been idle and logout will automatically occur in - Renew now to save your work - - - Happy super Sunday - Happy manic Monday - Happy tubular Tuesday - Happy wonderful Wednesday - Happy thunderous Thursday - Happy funky Friday - Happy Caturday - Log in below - Sign in with - Session timed out - © 2001 - %0%
Umbraco.com

]]>
- Forgotten password? - An email will be sent to the address specified with a link to reset your password - An email with password reset instructions will be sent to the specified address if it matched our records - Return to login form - Please provide a new password - Your Password has been updated - The link you have clicked on is invalid or has expired - Umbraco: Reset Password - - Your username to login to the Umbraco back-office is: %0%

Click here to reset your password or copy/paste this URL into your browser:

%1%

]]> -
- - - Dashboard - Sections - Content - - - Choose page above... - %0% has been copied to %1% - Select where the document %0% should be copied to below - %0% has been moved to %1% - Select where the document %0% should be moved to below - has been selected as the root of your new content, click 'ok' below. - No node selected yet, please select a node in the list above before clicking 'ok' - The current node is not allowed under the chosen node because of its type - The current node cannot be moved to one of its subpages - The current node cannot exist at the root - The action isn't allowed since you have insufficient permissions on 1 or more child documents. - Relate copied items to original - - - Edit your notification for %0% - "next" to start the wizard.]]> + + + + Culture Code + Culture Name + + + You've been idle and logout will automatically occur in + Renew now to save your work + + + Happy super Sunday + Happy manic Monday + Happy tubular Tuesday + Happy wonderful Wednesday + Happy thunderous Thursday + Happy funky Friday + Happy Caturday + Log in below + Sign in with + Session timed out + © 2001 - %0%
Umbraco.com

]]>
+ Forgotten password? + An email will be sent to the address specified with a link to reset your password + An email with password reset instructions will be sent to the specified address if it matched our records + Show password + Hide password + Return to login form + Please provide a new password + Your Password has been updated + The link you have clicked on is invalid or has expired + Umbraco: Reset Password + + + + + + + + + + + + +
+ + + + + +
+ +
+ +
+
+ + + + + + +
+
+
+ + + + +
+ + + + +
+

+ Password reset requested +

+

+ Your username to login to the Umbraco back-office is: %0% +

+

+ + + + + + +
+ + Click this link to reset your password + +
+

+

If you cannot click on the link, copy and paste this URL into your browser window:

+ + + + +
+ + %1% + +
+

+
+
+


+
+
+ + + ]]> +
+ + + Dashboard + Sections + Content + + + Choose page above... + %0% has been copied to %1% + Select where the document %0% should be copied to below + %0% has been moved to %1% + Select where the document %0% should be moved to below + has been selected as the root of your new content, click 'ok' below. + No node selected yet, please select a node in the list above before clicking 'ok' + The current node is not allowed under the chosen node because of its type + The current node cannot be moved to one of its subpages + The current node cannot exist at the root + The action isn't allowed since you have insufficient permissions on 1 or more child documents. + Relate copied items to original + + + Edit your notification for %0% + + - - Hi %0%

- -

This is an automated mail to inform you that the task '%1%' - has been performed on the page '%2%' - by the user '%3%' -

- -

-

Update summary:

- - %6% + ]]> + + + + + + + + + +
+ + +
+ + + + + +
+ +
+ +
+
-

- - - -

Have a nice day!

- Cheers from the Umbraco robot -

]]>
- [%0%] Notification about %1% performed on %2% - Notifications - - - + + + +
+
+ + + + +
+ + + + +
+

+ Hi %0%, +

+

+ This is an automated mail to inform you that the task '%1%' has been performed on the page '%2%' by the user '%3%' +

+ + + + + + +
+ +
+ EDIT
+
+

+

Update summary:

+ + %6% +
+

+

+ Have a nice day!

+ Cheers from the Umbraco robot +

+
+
+


+
+ + + + + + + ]]> +
+ [%0%] Notification about %1% performed on %2% + Notifications + + + + button and locating the package. Umbraco packages usually have a ".umb" or ".zip" extension. - ]]> - Author - Demonstration - Documentation - Package meta data - Package name - Package doesn't contain any items -
- You can safely remove this from the system by clicking "uninstall package" below.]]>
- No upgrades available - Package options - Package readme - Package repository - Confirm uninstall - Package was uninstalled - The package was successfully uninstalled - Uninstall package - + ]]> + + Drop to upload + or click here to choose package file + Upload package + Install a local package by selecting it from your machine. Only install packages from sources you know and trust + Upload another package + Cancel and upload another package + License + I accept + terms of use + Install package + Finish + Installed packages + You don’t have any packages installed + 'Packages' icon in the top right of your screen]]> + Search for packages + Results for + We couldn’t find anything for + Please try searching for another package or browse through the categories + Popular + New releases + has + karma points + Information + Owner + Contributors + Created + Current version + .NET version + Downloads + Likes + Compatibility + This package is compatible with the following versions of Umbraco, as reported by community members. Full compatability cannot be gauranteed for versions reported below 100% + External sources + Author + Demonstration + Documentation + Package meta data + Package name + Package doesn't contain any items + +
+ You can safely remove this from the system by clicking "uninstall package" below.]]> +
+ No upgrades available + Package options + Package readme + Package repository + Confirm package uninstall + Package was uninstalled + The package was successfully uninstalled + Uninstall package + + Notice: any documents, media etc depending on the items you remove, will stop working, and could lead to system instability, - so uninstall with caution. If in doubt, contact the package author.]]> - Download update from the repository - Upgrade package - Upgrade instructions - There's an upgrade available for this package. You can download it directly from the Umbraco package repository. - Package version - Package version history - View package website - Package already installed - This package cannot be installed, it requires a minimum Umbraco version of %0% - Uninstalling... - Downloading... - Importing... - Installing... - Restarting, please wait... - All done, your browser will now refresh, please wait... - Please click finish to complete installation and reload page. - - - Paste with full formatting (Not recommended) - The text you're trying to paste contains special characters or formatting. This could be caused by copying text from Microsoft Word. Umbraco can remove special characters or formatting automatically, so the pasted content will be more suitable for the web. - Paste as raw text without any formatting at all - Paste, but remove formatting (Recommended) - - - Role based protection - using Umbraco's member groups.]]> - role-based authentication.]]> - Error Page - Used when people are logged on, but do not have access - Choose how to restrict access to this page - %0% is now protected - Protection removed from %0% - Login Page - Choose the page that contains the login form - Remove Protection - Select the pages that contain login form and error messages - Pick the roles who have access to this page - Set the login and password for this page - Single user protection - If you just want to setup simple protection using a single login and password - - - - + + Download update from the repository + Upgrade package + Upgrade instructions + There's an upgrade available for this package. You can download it directly from the Umbraco package repository. + Package version + Package version history + View package website + Package already installed + This package cannot be installed, it requires a minimum Umbraco version of + Uninstalling... + Downloading... + Importing... + Installing... + Restarting, please wait... + All done, your browser will now refresh, please wait... + Please click 'Finish' to complete installation and reload the page. + Uploading package... + + + Paste with full formatting (Not recommended) + The text you're trying to paste contains special characters or formatting. This could be caused by copying text from Microsoft Word. Umbraco can remove special characters or formatting automatically, so the pasted content will be more suitable for the web. + Paste as raw text without any formatting at all + Paste, but remove formatting (Recommended) + + + Role based protection + using Umbraco's member groups.]]> + You need to create a membergroup before you can use role-based authentication + Error Page + Used when people are logged on, but do not have access + Choose how to restrict access to this page + %0% is now protected + Protection removed from %0% + Login Page + Choose the page that contains the login form + Remove Protection + Select the pages that contain login form and error messages + Pick the roles who have access to this page + Set the login and password for this page + Single user protection + If you just want to setup simple protection using a single login and password + + + + - - + + - + + + - + + + - + + + - Include unpublished subpages - Publishing in progress - please wait... - %0% out of %1% pages have been published... - %0% has been published - %0% and subpages have been published - Publish %0% and all its subpages - Publish to publish %0% and thereby making its content publicly available.

+ ]]> +
+ Include unpublished subpages + Publishing in progress - please wait... + %0% out of %1% pages have been published... + %0% has been published + %0% and subpages have been published + Publish %0% and all its subpages + + Publish to publish %0% and thereby making its content publicly available.

You can publish this page and all its subpages by checking Include unpublished subpages below. - ]]>
- - - You have not configured any approved colors - - - enter external link - choose internal page - Caption - Link - Open in new window - enter the display caption - Enter the link - - - Reset - - - Current version - Red text will not be shown in the selected version. , green means added]]> - Document has been rolled back - This displays the selected version as HTML, if you wish to see the difference between 2 versions at the same time, use the diff view - Rollback to - Select version - View - - - Edit script file - - - Concierge - Content - Courier - Developer - Umbraco Configuration Wizard - Media - Members - Newsletters - Settings - Statistics - Translation - Users - Help - Forms - Analytics - - - go to - Help topics for - Video chapters for - The best Umbraco video tutorials - - - Default template - Dictionary Key - To import a document type, find the ".udt" file on your computer by clicking the "Browse" button and click "Import" (you'll be asked for confirmation on the next screen) - New Tab Title - Node type - Type - Stylesheet - Script - Stylesheet property - Tab - Tab Title - Tabs - Master Content Type enabled - This Content Type uses - as a Master Content Type. Tabs from Master Content Types are not shown and can only be edited on the Master Content Type itself - No properties defined on this tab. Click on the "add a new property" link at the top to create a new property. - Master Document Type - Create matching template - Add icon - - - Sort order - Creation date - Sorting complete. - Drag the different items up or down below to set how they should be arranged. Or click the column headers to sort the entire collection of items -
Do not close this window during sorting]]>
- - - Validation - Validation errors must be fixed before the item can be saved - Failed - Insufficient user permissions, could not complete the operation - Cancelled - Operation was cancelled by a 3rd party add-in - Publishing was cancelled by a 3rd party add-in - Property type already exists - Property type created - DataType: %1%]]> - Propertytype deleted - Document Type saved - Tab created - Tab deleted - Tab with id: %0% deleted - Stylesheet not saved - Stylesheet saved - Stylesheet saved without any errors - Datatype saved - Dictionary item saved - Publishing failed because the parent page isn't published - Content published - and visible at the website - Content saved - Remember to publish to make changes visible - Sent For Approval - Changes have been sent for approval - Media saved - Media saved without any errors - Member saved - Stylesheet Property Saved - Stylesheet saved - Template saved - Error saving user (check log) - User Saved - User type saved - File not saved - file could not be saved. Please check file permissions - File saved - File saved without any errors - Language saved - Media Type saved - Member Type saved - Python script not saved - Python script could not be saved due to error - Python script saved - No errors in python script - Template not saved - Please make sure that you do not have 2 templates with the same alias - Template saved - Template saved without any errors! - XSLT not saved - XSLT contained an error - XSLT could not be saved, check file permissions - XSLT saved - No errors in XSLT - Content unpublished - Partial view saved - Partial view saved without any errors! - Partial view not saved - An error occurred saving the file. - Script view saved - Script view saved without any errors! - Script view not saved - An error occurred saving the file. - An error occurred saving the file. - - - Uses CSS syntax ex: h1, .redHeader, .blueTex - Edit stylesheet - Edit stylesheet property - Name to identify the style property in the rich text editor - Preview - Styles - - - Edit template - Insert content area - Insert content area placeholder - Insert dictionary item - Insert Macro - Insert Umbraco page field - Master template - Quick Guide to Umbraco template tags - Template - - - Choose type of content - Choose a layout - Add a row - Add content - Drop content - Settings applied + ]]> +
+ + + You have not configured any approved colors + + + You have picked a content item currently deleted or in the recycle bin + You have picked content items currently deleted or in the recycle bin + + + You have picked a media item currently deleted or in the recycle bin + You have picked media items currently deleted or in the recycle bin + Deleted item + Trashed + + + enter external link + choose internal page + Caption + Link + Open in new window + enter the display caption + Enter the link + + + Reset + Define crop + Give the crop an alias and its default width and height + Save crop + Add new crop + + + Current version + Red text will not be shown in the selected version. , green means added]]> + Document has been rolled back + This displays the selected version as HTML, if you wish to see the difference between 2 versions at the same time, use the diff view + Rollback to + Select version + View + + + Edit script file + + + Concierge + Content + Courier + Developer + Umbraco Configuration Wizard + Media + Members + Newsletters + Settings + Statistics + Translation + Users + Help + Forms + Analytics + + + go to + Help topics for + Video chapters for + The best Umbraco video tutorials + + + Default template + Dictionary Key + To import a document type, find the ".udt" file on your computer by clicking the "Browse" button and click "Import" (you'll be asked for confirmation on the next screen) + New Tab Title + Node type + Type + Stylesheet + Script + Stylesheet property + Tab + Tab Title + Tabs + Master Content Type enabled + This Content Type uses + as a Master Content Type. Tabs from Master Content Types are not shown and can only be edited on the Master Content Type itself + No properties defined on this tab. Click on the "add a new property" link at the top to create a new property. + Master Document Type + Create matching template + Add icon + + + Sort order + Creation date + Sorting complete. + Drag the different items up or down below to set how they should be arranged. Or click the column headers to sort the entire collection of items + + + + Validation + Validation errors must be fixed before the item can be saved + Failed + Saved + Insufficient user permissions, could not complete the operation + Cancelled + Operation was cancelled by a 3rd party add-in + Publishing was cancelled by a 3rd party add-in + Property type already exists + Property type created + DataType: %1%]]> + Propertytype deleted + Document Type saved + Tab created + Tab deleted + Tab with id: %0% deleted + Stylesheet not saved + Stylesheet saved + Stylesheet saved without any errors + Datatype saved + Dictionary item saved + Publishing failed because the parent page isn't published + Content published + and visible on the website + Content saved + Remember to publish to make changes visible + Sent For Approval + Changes have been sent for approval + Media saved + Media saved without any errors + Member saved + Stylesheet Property Saved + Stylesheet saved + Template saved + Error saving user (check log) + User Saved + User type saved + User group saved + File not saved + file could not be saved. Please check file permissions + File saved + File saved without any errors + Language saved + Media Type saved + Member Type saved + Python script not saved + Python script could not be saved due to error + Python script saved + No errors in python script + Template not saved + Please make sure that you do not have 2 templates with the same alias + Template saved + Template saved without any errors! + XSLT not saved + XSLT contained an error + XSLT could not be saved, check file permissions + XSLT saved + No errors in XSLT + Content unpublished + Partial view saved + Partial view saved without any errors! + Partial view not saved + An error occurred saving the file. + Permissions saved for + Script view saved + Script view saved without any errors! + Script view not saved + An error occurred saving the file. + An error occurred saving the file. + Deleted %0% user groups + %0% was deleted + Enabled %0% users + An error occurred while enabling the users + Disabled %0% users + An error occurred while disabling the users + %0% is now enabled + An error occurred while enabling the user + %0% is now disabled + An error occurred while disabling the user + User groups have been set + Deleted %0% user groups + %0% was deleted + Unlocked %0% users + An error occurred while unlocking the users + %0% is now unlocked + An error occurred while unlocking the user + Member was exported to file + An error occurred while exporting the member + User %0% was deleted + Invite user + Invitation has been re-sent to %0% + + + Uses CSS syntax ex: h1, .redHeader, .blueTex + Edit stylesheet + Edit stylesheet property + Name to identify the style property in the rich text editor + Preview + Styles + + + Edit template - This content is not allowed here - This content is allowed here + Sections + Insert content area + Insert content area placeholder - Click to embed - Click to insert image - Image caption... - Write here... + Insert + Choose what to insert into your template - Grid Layouts - Layouts are the overall work area for the grid editor, usually you only need one or two different layouts - Add Grid Layout - Adjust the layout by setting column widths and adding additional sections - Row configurations - Rows are predefined cells arranged horizontally - Add row configuration - Adjust the row by setting cell widths and adding additional cells + Dictionary item + A dictionary item is a placeholder for a translatable piece of text, which makes it easy to create designs for multilingual websites. - Columns - Total combined number of columns in the grid layout + Macro + + A Macro is a configurable component which is great for + reusable parts of your design, where you need the option to provide parameters, + such as galleries, forms and lists. + - Settings - Configure what settings editors can change + Value + Displays the value of a named field from the current page, with options to modify the value or fallback to alternative values. + + Partial view + + A partial view is a separate template file which can be rendered inside another + template, it's great for reusing markup or for separating complex templates into separate files. + + + Master template + No master template + No master + + Render child template + + @RenderBody() placeholder. + ]]> + - Styles - Configure what styling editors can change + Define a named section + + @section { ... }. This can be rendered in a + specific area of the parent of this template, by using @RenderSection. + ]]> + - Settings will only save if the entered json configuration is valid + Render a named section + + @RenderSection(name) placeholder. + This renders an area of a child template which is wrapped in a corresponding @section [name]{ ... } definition. + ]]> + - Allow all editors - Allow all row configurations - Set as default - Choose extra - Choose default - are added - + Section Name + Section is mandatory + + If mandatory, the child template must contain a @section definition, otherwise an error is shown. + - - Compositions - You have not added any tabs - Add new tab - Add another tab - Inherited from - Add property - Required label - Enable list view - Configures the content item to show a sortable and searchable list of its children, the children will not be shown in the tree + Query builder + Build a query + items returned, in - Allowed Templates - Choose which templates editors are allowed to use on content of this type - Allow as root - Allow editors to create content of this type in the root of the content tree - Yes - allow content of this type in the root + I want + all content + content of type "%0%" + from + my website + where + and - Allowed child node types - Allow content of the specified types to be created underneath content of this type + is + is not + before + before (including selected date) + after + after (including selected date) + equals + does not equal + contains + does not contain + greater than + greater than or equal to + less than + less than or equal to - Choose child node - Inherit tabs and properties from an existing document type. New tabs will be added to the current document type or merged if a tab with an identical name exists. - This content type is used in a composition, and therefore cannot be composed itself. - There are no content types available to use as a composition. + Id + Name + Created Date + Last Updated Date - Available editors - Reuse - Editor settings + order by + ascending + descending - Configuration + Template + + + Rich Text Editor + Image + Macro + Embed + Headline + Quote + Choose type of content + Choose a layout + Add a row + Add content + Drop content + Settings applied - Yes, delete + This content is not allowed here + This content is allowed here - was moved underneath - was copied underneath - Select the folder to move - Select the folder to copy - to in the tree structure below + Click to embed + Click to insert image + Image caption... + Write here... - All Document types - All Documents - All media items + Grid Layouts + Layouts are the overall work area for the grid editor, usually you only need one or two different layouts + Add Grid Layout + Adjust the layout by setting column widths and adding additional sections + Row configurations + Rows are predefined cells arranged horizontally + Add row configuration + Adjust the row by setting cell widths and adding additional cells - using this document type will be deleted permanently, please confirm you want to delete these as well. - using this media type will be deleted permanently, please confirm you want to delete these as well. - using this member type will be deleted permanently, please confirm you want to delete these as well + Columns + Total combined number of columns in the grid layout - and all documents using this type - and all media items using this type - and all members using this type + Settings + Configure what settings editors can change - using this editor will get updated with the new settings - Member can edit - Show on member profile - tab has no sort order - + Styles + Configure what styling editors can change - - Building models - this can take abit of time, don't worry - Models generated - Models could not be generated - Models generation has failed, see exception in U log - + Settings will only save if the entered json configuration is valid - - Alternative field - Alternative Text - Casing - Encoding - Choose field - Convert line breaks - Replaces line breaks with html-tag &lt;br&gt; - Custom Fields - Yes, Date only - Format as date - HTML encode - Will replace special characters by their HTML equivalent. - Will be inserted after the field value - Will be inserted before the field value - Lowercase - None - Insert after field - Insert before field - Recursive - Remove Paragraph tags - Will remove any &lt;P&gt; in the beginning and end of the text - Standard Fields - Uppercase - URL encode - Will format special characters in URLs - Will only be used when the field values above are empty - This field will only be used if the primary field is empty - Yes, with time. Separator: - - - Tasks assigned to you - assigned to you. To see a detailed view including comments, click on "Details" or just the page name. + Allow all editors + Allow all row configurations + Maximum items + Leave blank or set to 0 for unlimited + Set as default + Choose extra + Choose default + are added + + + + Compositions + You have not added any tabs + Add new tab + Add another tab + Inherited from + Add property + Required label + + Enable list view + Configures the content item to show a sortable and searchable list of its children, the children will not be shown in the tree + + Allowed Templates + Choose which templates editors are allowed to use on content of this type + Allow as root + Allow editors to create content of this type in the root of the content tree + Yes - allow content of this type in the root + + Allowed child node types + Allow content of the specified types to be created underneath content of this type + + Choose child node + Inherit tabs and properties from an existing document type. New tabs will be added to the current document type or merged if a tab with an identical name exists. + This content type is used in a composition, and therefore cannot be composed itself. + There are no content types available to use as a composition. + + Available editors + Reuse + Editor settings + + Configuration + + Yes, delete + + was moved underneath + was copied underneath + Select the folder to move + Select the folder to copy + to in the tree structure below + + All Document types + All Documents + All media items + + using this document type will be deleted permanently, please confirm you want to delete these as well. + using this media type will be deleted permanently, please confirm you want to delete these as well. + using this member type will be deleted permanently, please confirm you want to delete these as well + + and all documents using this type + and all media items using this type + and all members using this type + + using this editor will get updated with the new settings + + Member can edit + Allow this property value to be edited by the member on their profile page + Is sensitive data + Hide this property value from content editors that don't have access to view sensitive information + Show on member profile + Allow this property value to be displayed on the member profile page + + tab has no sort order + + Where is this composition used? + This composition is currently used in the composition of the following content types: + + + + Building models + this can take a bit of time, don't worry + Models generated + Models could not be generated + Models generation has failed, see exception in U log + + + + Add fallback field + Fallback field + Add default value + Default value + Fallback field + Default value + Casing + Encoding + Choose field + Convert line breaks + Yes, convert line breaks + Replaces line breaks with 'br' html tag + Custom Fields + Date only + Format and encoding + Format as date + Format the value as a date, or a date with time, according to the active culture + HTML encode + Will replace special characters by their HTML equivalent. + Will be inserted after the field value + Will be inserted before the field value + Lowercase + Modify output + None + Output sample + Insert after field + Insert before field + Recursive + Yes, make it recursive + Separator + Standard Fields + Uppercase + URL encode + Will format special characters in URLs + Will only be used when the field values above are empty + This field will only be used if the primary field is empty + Date and time + + + Tasks assigned to you + + assigned to you. To see a detailed view including comments, click on "Details" or just the page name. You can also download the page as XML directly by clicking the "Download Xml" link.
To close a translation task, please go to the Details view and click the "Close" button. - ]]>
- close task - Translation details - Download all translation tasks as XML - Download XML - Download XML DTD - Fields - Include subpages - + + close task + Translation details + Download all translation tasks as XML + Download XML + Download XML DTD + Fields + Include subpages + + - [%0%] Translation task for %1% - No translator users found. Please create a translator user before you start sending content to translation - Tasks created by you - created by you. To see a detailed view including comments, + ]]> + + [%0%] Translation task for %1% + No translator users found. Please create a translator user before you start sending content to translation + Tasks created by you + + created by you. To see a detailed view including comments, click on "Details" or just the page name. You can also download the page as XML directly by clicking the "Download Xml" link. To close a translation task, please go to the Details view and click the "Close" button. - ]]> - The page '%0%' has been send to translation - Please select the language that the content should be translated into - Send the page '%0%' to translation - Assigned by - Task opened - Total words - Translate to - Translation completed. - You can preview the pages, you've just translated, by clicking below. If the original page is found, you will get a comparison of the 2 pages. - Translation failed, the XML file might be corrupt - Translation options - Translator - Upload translation XML - - - Cache Browser - Recycle Bin - Created packages - Data Types - Dictionary - Installed packages - Install skin - Install starter kit - Languages - Install local package - Macros - Media Types - Members - Member Groups - Member Roles - Member Types - Document Types - Relation Types - Packages - Packages - Python Files - Install from repository - Install Runway - Runway modules - Scripting Files - Scripts - Stylesheets - Templates - XSLT Files - Analytics - - - New update ready - %0% is ready, click here for download - No connection to server - Error checking for update. Please review trace-stack for further information - - - Administrator - Category field - Change Your Password - New password - Confirm new password - You can change your password for accessing the Umbraco Back Office by filling out the form below and click the 'Change Password' button - Content Channel - Description field - Disable User - Document Type - Editor - Excerpt field - Language - Login - Start Node in Media Library - Sections - Disable Umbraco Access - Old password - Password - Reset password - Your password has been changed! - Please confirm the new password - Enter your new password - Your new password cannot be blank! - Current password - Invalid current password - There was a difference between the new password and the confirmed password. Please try again! - The confirmed password doesn't match the new password! - Replace child node permissions - You are currently modifying permissions for the pages: - Select pages to modify their permissions - Search all children - Start Node in Content - Name - User permissions - User type - User types - Writer - Translator - Change - Your profile - Your recent history - Session expires in - - - Validation - Validate as email - Validate as a number - Validate as a Url - ...or enter a custom validation - Field is mandatory - - - +
+ + + + + + + + + + + + + + +
+
+
+ + + + +
+ + + + +
+

+ Hi %0%, +

+

+ You have been invited by %1% to the Umbraco Back Office. +

+

+ Message from %1%: +
+ %2% +

+ + + + + + +
+ + + + + + +
+ + Click this link to accept the invite + +
+
+

If you cannot click on the link, copy and paste this URL into your browser window:

+ + + + +
+ + %3% + +
+

+
+
+


+
+
+ + ]]> +
+ Invite + Resending invitation... + Delete User + Are you sure you wish to delete this user account? + + + Validation + Validate as an email address + Validate as a number + Validate as a URL + ...or enter a custom validation + Field is mandatory + Enter a regular expression + You need to add at least + You can only have + items + items selected + Invalid date + Not a number + Invalid email + + + - Value is set to the recommended value: '%0%'. - Value was set to '%1%' for XPath '%2%' in configuration file '%3%'. - Expected value '%1%' for '%2%' in configuration file '%3%', but found '%0%'. - Found unexpected value '%0%' for '%2%' in configuration file '%3%'. + Value is set to the recommended value: '%0%'. + Value was set to '%1%' for XPath '%2%' in configuration file '%3%'. + Expected value '%1%' for '%2%' in configuration file '%3%', but found '%0%'. + Found unexpected value '%0%' for '%2%' in configuration file '%3%'. - - Custom errors are set to '%0%'. - Custom errors are currently set to '%0%'. It is recommended to set this to '%1%' before go live. - Custom errors successfully set to '%0%'. + Custom errors are set to '%0%'. + Custom errors are currently set to '%0%'. It is recommended to set this to '%1%' before go live. + Custom errors successfully set to '%0%'. - MacroErrors are set to '%0%'. - MacroErrors are set to '%0%' which will prevent some or all pages in your site from loading completely if there are any errors in macros. Rectifying this will set the value to '%1%'. - MacroErrors are now set to '%0%'. + MacroErrors are set to '%0%'. + MacroErrors are set to '%0%' which will prevent some or all pages in your site from loading completely if there are any errors in macros. Rectifying this will set the value to '%1%'. + MacroErrors are now set to '%0%'. - - Try Skip IIS Custom Errors is set to '%0%' and you're using IIS version '%1%'. - Try Skip IIS Custom Errors is currently '%0%'. It is recommended to set this to '%1%' for your IIS version (%2%). - Try Skip IIS Custom Errors successfully set to '%0%'. + Try Skip IIS Custom Errors is set to '%0%' and you're using IIS version '%1%'. + Try Skip IIS Custom Errors is currently '%0%'. It is recommended to set this to '%1%' for your IIS version (%2%). + Try Skip IIS Custom Errors successfully set to '%0%'. - - File does not exist: '%0%'. - '%0%' in config file '%1%'.]]> - There was an error, check log for full error: %0%. + + File does not exist: '%0%'. + '%0%' in config file '%1%'.]]> + There was an error, check log for full error: %0%. - Members - Total XML: %0%, Total: %1%, Total invalid: %2% - Media - Total XML: %0%, Total: %1%, Total invalid: %2% - Content - Total XML: %0%, Total published: %1%, Total invalid: %2% + Members - Total XML: %0%, Total: %1%, Total invalid: %2% + Media - Total XML: %0%, Total: %1%, Total invalid: %2% + Content - Total XML: %0%, Total published: %1%, Total invalid: %2% - Your site certificate was marked as valid. - Certificate validation error: '%0%' - Error pinging the URL %0% - '%1%' - You are currently %0% viewing the site using the HTTPS scheme. - The appSetting 'umbracoUseSSL' is set to 'false' in your web.config file. Once you access this site using the HTTPS scheme, that should be set to 'true'. - The appSetting 'umbracoUseSSL' is set to '%0%' in your web.config file, your cookies are %1% marked as secure. - Could not update the 'umbracoUseSSL' setting in your web.config file. Error: %0% + Database - The database schema is correct for this version of Umbraco + %0% problems were detected with your database schema (Check the log for details) + Some errors were detected while validating the database schema against the current version of Umbraco. - - Enable HTTPS - Sets umbracoSSL setting to true in the appSettings of the web.config file. - The appSetting 'umbracoUseSSL' is now set to 'true' in your web.config file, your cookies will be marked as secure. + Your website's certificate is valid. + Certificate validation error: '%0%' + Your website's SSL certificate has expired. + Your website's SSL certificate is expiring in %0% days. + Error pinging the URL %0% - '%1%' + You are currently %0% viewing the site using the HTTPS scheme. + The appSetting 'umbracoUseSSL' is set to 'false' in your web.config file. Once you access this site using the HTTPS scheme, that should be set to 'true'. + The appSetting 'umbracoUseSSL' is set to '%0%' in your web.config file, your cookies are %1% marked as secure. + Could not update the 'umbracoUseSSL' setting in your web.config file. Error: %0% - Fix - Cannot fix a check with a value comparison type of 'ShouldNotEqual'. - Cannot fix a check with a value comparison type of 'ShouldEqual' with a provided value. - Value to fix check not provided. + + Enable HTTPS + Sets umbracoSSL setting to true in the appSettings of the web.config file. + The appSetting 'umbracoUseSSL' is now set to 'true' in your web.config file, your cookies will be marked as secure. - Debug compilation mode is disabled. - Debug compilation mode is currently enabled. It is recommended to disable this setting before go live. - Debug compilation mode successfully disabled. + Fix + Cannot fix a check with a value comparison type of 'ShouldNotEqual'. + Cannot fix a check with a value comparison type of 'ShouldEqual' with a provided value. + Value to fix check not provided. - Trace mode is disabled. - Trace mode is currently enabled. It is recommended to disable this setting before go live. - Trace mode successfully disabled. + Debug compilation mode is disabled. + Debug compilation mode is currently enabled. It is recommended to disable this setting before go live. + Debug compilation mode successfully disabled. - All folders have the correct permissions set. - - %0%.]]> - %0%. If they aren't being written to no action need be taken.]]> + %0%.]]> + %0%. If they aren't being written to no action need be taken.]]> - All files have the correct permissions set. - - %0%.]]> - %0%. If they aren't being written to no action need be taken.]]> + %0%.]]> + %0%. If they aren't being written to no action need be taken.]]> - X-Frame-Options used to control whether a site can be IFRAMEd by another was found.]]> - X-Frame-Options used to control whether a site can be IFRAMEd by another was not found.]]> - Set Header in Config - Adds a value to the httpProtocol/customHeaders section of web.config to prevent the site being IFRAMEd by other websites. - A setting to create a header preventing IFRAMEing of the site by other websites has been added to your web.config file. - Could not update web.config file. Error: %0% + X-Frame-Options used to control whether a site can be IFRAMEd by another was found.]]> + X-Frame-Options used to control whether a site can be IFRAMEd by another was not found.]]> + Set Header in Config + Adds a value to the httpProtocol/customHeaders section of web.config to prevent the site being IFRAMEd by other websites. + A setting to create a header preventing IFRAMEing of the site by other websites has been added to your web.config file. + Could not update web.config file. Error: %0% - - %0%.]]> - No headers revealing information about the website technology were found. + %0%.]]> + No headers revealing information about the website technology were found. - In the Web.config file, system.net/mailsettings could not be found. - In the Web.config file system.net/mailsettings section, the host is not configured. - SMTP settings are configured correctly and the service is operating as expected. - The SMTP server configured with host '%0%' and port '%1%' could not be reached. Please check to ensure the SMTP settings in the Web.config file system.net/mailsettings are correct. + In the Web.config file, system.net/mailsettings could not be found. + In the Web.config file system.net/mailsettings section, the host is not configured. + SMTP settings are configured correctly and the service is operating as expected. + The SMTP server configured with host '%0%' and port '%1%' could not be reached. Please check to ensure the SMTP settings in the Web.config file system.net/mailsettings are correct. - %0%.]]> - %0%.]]> - - - Disable URL tracker - Enable URL tracker - Original URL - Redirected To - No redirects have been made - When a published page gets renamed or moved a redirect will automatically be made to the new page. - Remove - Are you sure you want to remove the redirect from '%0%' to '%1%'? - Redirect URL removed. - Error removing redirect URL. - Are you sure you want to disable the URL tracker? - URL tracker has now been disabled. - Error disabling the URL tracker, more information can be found in your log file. - URL tracker has now been enabled. - Error enabling the URL tracker, more information can be found in your log file. - + %0%.]]> + %0%.]]> +

Results of the scheduled Umbraco Health Checks run on %0% at %1% are as follows:

%2%]]>
+ Umbraco Health Check Status + + + Disable URL tracker + Enable URL tracker + Original URL + Redirected To + No redirects have been made + When a published page gets renamed or moved a redirect will automatically be made to the new page. + Remove + Are you sure you want to remove the redirect from '%0%' to '%1%'? + Redirect URL removed. + Error removing redirect URL. + Are you sure you want to disable the URL tracker? + URL tracker has now been disabled. + Error disabling the URL tracker, more information can be found in your log file. + URL tracker has now been enabled. + Error enabling the URL tracker, more information can be found in your log file. + + + No Dictionary items to choose from + + + characters left + + + Trashed content with Id: {0} related to original parent content with Id: {1} + Trashed media with Id: {0} related to original parent media item with Id: {1} + Cannot automatically restore this item + There is no 'restore' relation found for this node. Use the Move menu item to move it manually. + The item you want to restore it under ('%0%') is in the recycle bin. Use the Move menu item to move the item manually. + + + Select your notifications for + Notification settings saved for +
diff --git a/WebCms/Umbraco/Config/Lang/es.xml b/WebCms/Umbraco/Config/Lang/es.xml index 4d65235..eaf8e2a 100644 --- a/WebCms/Umbraco/Config/Lang/es.xml +++ b/WebCms/Umbraco/Config/Lang/es.xml @@ -1,833 +1,1473 @@ - - The Umbraco community - http://our.umbraco.org/documentation/Extending-Umbraco/Language-Files - - - Administrar hostnames - Auditoría - Nodo de Exploración - Cambiar tipo de documento - Copiar - Crear - Crear Paquete - Borrar - Deshabilitar - Vaciar Papelera - Exportar Documento (tipo) - Importar Documento (tipo) - Importar Paquete - Editar en lienzo - Salir - Mover - Notificaciones - Acceso Público - Publicar - Unpublish - Recargar Nodos - Republicar sitio completo - Permisos - Deshacer - Enviar a Publicar - Enviar a Traducir - Ordernar - Enviar a publicación - Traducir - Actualizar - Valor por defecto - - - Permiso denegado. - Añadir nuevo dominio - quitar - Nodo no válido. - Formato de dominio no válido. - Este dominio ya ha sido asignado. - Language - Dominio - El nuevo dominio %0% ha sido creado - El dominio %0% ha sido borrado - El dominio'%0%' ya ha sido asignado - El dominio %0% ha sido actualizado - Editar dominios actuales - - + The Umbraco community + https://our.umbraco.com/documentation/Extending-Umbraco/Language-Files + + + Administrar hostnames + Auditoría + Nodo de Exploración + Cambiar tipo de documento + Copiar + Crear + Crear Paquete + Crear grupo + Borrar + Deshabilitar + Vaciar Papelera + Activar + Exportar Documento (tipo) + Importar Documento (tipo) + Importar Paquete + Editar en lienzo + Salir + Mover + Notificaciones + Acceso Público + Publicar + Unpublish + Recargar Nodos + Republicar sitio completo + Renombrar + Restaurar + Establecer permisos para la página %0% + Elije dónde mover + En el árbol de contenido + Permisos + Deshacer + Enviar a Publicar + Enviar a Traducir + Establecer grupo + Ordernar + Traducir + Actualizar + Establecer permisos + Desbloquear + Crear Plantilla de Contenido + + + Contenido + Administración + Estructura + Otro + + + Permitir acceso para asignar cultura y dominios + Permitir acceso para ver informes de nodos + Permitir acceso para ver un nodo + Permitir acceso para cambiar el tipo de documento de un nodo + Permitir acceso para copiar un nodo + Permitir acceso para crear nodos + Permitir acceso para borrar nodos + Permitir acceso para mover un nodo + Permitir acceso para establecer y cambiar el acceso público a un nodo + Permitir acceso para publicar un nodo + Permitir acceso para cambiar los permisos para un nodo + Permitir acceso para revertir cambios a un nodo a un estado anterior + Permitir acceso para enviar un nodo a revisión antes de publicarlo + Permitir acceso para enviar un nodo a traducir + Permitir acceso a ordenar nodos + Permitir acceso para traducir un nodo + Permitir acceso para guardar un nodo + Permitir acceso para crear una Plantilla de Contenido + + + Permiso denegado. + Añadir nuevo dominio + quitar + Nodo no válido. + Formato de dominio no válido. + Este dominio ya ha sido asignado. + Language + Dominio + El nuevo dominio %0% ha sido creado + El dominio %0% ha sido borrado + El dominio'%0%' ya ha sido asignado + El dominio %0% ha sido actualizado + Editar dominios actuales + +
Los dominios de un nivel están soportados, por ej. "example.com/en". De todas formas deberían de evitarse. Mejor usar la configuración cultural especificada arriba.]]> -
- Heredar - Cultura - - o hereda la cultura de los nodos padres. También se aplicará
+
+ Heredar + Cultura + + o hereda la cultura de los nodos padres. También se aplicará
para el nodo actual, a menos que un dominio por debajo lo aplique también.]]> -
- Domains - - - Visualización de - - - Seleccionar - Selecciona la carpeta actual - Hacer otra cosa - Negrita - Cancelar Sangría del Párrafo - Insertar campo de formulario - Insertar gráfico de titular - Editar Html - Sangría - Cursiva - Centrar - Alinear a la Izquierda - Alinear a la Derecha - Insertar Link - Insertar link local (anchor) - Lista en Viñetas - Lista Numérica - Insertar macro - Insertar imagen - Editar relaciones - Guardar - Guardar y publicar - Guardar y enviar para aprobación - Previsualizar - La previsualización está deshabilitada porque no hay ninguna plantilla asignada - Elegir estilo - Mostrar estilos - Insertar tabla - Volver al listado - - - Para cambiar el tipo de documento al contenido seleccionado, primero selecciona uno de la lista de tipos válidos. - Entonces confirma el mapeo de propiedades del tipo actual al nuevo y haz click en Guardar. - El contenido se ha vuelto a publicar. - Propiedad actual - Tipo actual - El tipo de contenido no se puede cambiar, porque no hay alternativas válidas para este contenido. - Tipo de documento cambiado - Mapeo de propiedades - Mapea a la propiedad - Nueva plantilla - Nuevo tipo - ninguno - Contenido - Selecciona un nuevo Tipo de Documento - El tipo de documento del contenido seleccionado ha sido cambiado correctamente a [new type] y las siguientes propiedades mapeadas: - a - No se ha podido completar el mapeo de propiedades porque uno o más propiedades tienen más de un mapeo definido. - Solo se muestran otros tipos válidos para el contenido actual. - - - Está publicado - Acerca de - Link alternativo - (como describe la imagen sobre el teléfono) - Vinculos Alternativos - Click para editar esta entrada - Creado por - Autor original - Actualizado por - Creado - Fecha/hora de creación del documento - Tipo de Documento - Editando - Remover el - Esta entrada ha sido modificada después de haber sido publicada - Esta entrada no esta publicada - Último publicado - Tipo de Medio - Miembro de Grupo - Rol - Tipo de miembro - Sin fecha - Título de la página - Propiedades - Este documento ha sido publicado pero no es visible porque el padre '%0%' no esta publicado - Upss: este documento está publicado pero no está en la caché (error interno) - Publicar - Estado de la Publicación - Publicar el - Despublicar el - Fecha de Eliminación - El Orden esta actualizado - Para organizar los nodos, simplemente arrastre los nodos o realice un clic en uno de los encabezados de columna. Puede seleccionar multiple nodos manteniendo presionados "Shift" o "Control" mientras selecciona - Estadísticas - Título (opcional) - Tipo - No Publicar - Última actualización - Fecha/hora este documento fue modificado - Eliminar archivo - Vínculo al documento - Miembro de grupo(s) - No es miembreo de grupo(s) - Nodos hijo - Target - No hay datos que mostrar - - - Haz click para subir archivos - Arrastra los archivos aquí... - - - ¿Dónde quieres crear el nuevo %0% - Crear debajo de - Elije un tipo y un título - "Tipos de documentos".]]> - "Tipos de medios".]]> - - - Navega en tu sitio Web - No volver a mostrar - Si Umbraco no se ha abierto tendrás que permitir ventanas emergentes para este sitio Web - se ha abierto en una nueva ventana - Reinicio - Visita - Bienvenido - - - Stay - Discard changes - You have unsaved changes - Are you sure you want to navigate away from this page? - you have unsaved changes - - - Done +
+ Dominios + + + Visualización de + + + Deshacer selección + Seleccionar + Selecciona la carpeta actual + Hacer otra cosa + Negrita + Cancelar Sangría del Párrafo + Insertar campo de formulario + Insertar gráfico de titular + Editar Html + Sangría + Cursiva + Centrar + Alinear a la Izquierda + Alinear a la Derecha + Insertar Link + Insertar link local (anchor) + Lista en Viñetas + Lista Numérica + Insertar macro + Insertar imagen + Editar relaciones + Volver al listado + Guardar + Guardar y publicar + Guardar y enviar para aprobación + Guardar vista de lista + Previsualizar + La previsualización está deshabilitada porque no hay ninguna plantilla asignada + Elegir estilo + Mostrar estilos + Insertar tabla + Generar modelos + Guardar y generar modelos + Deshacer + Rehacer + + + Para cambiar el tipo de documento al contenido seleccionado, primero selecciona uno de la lista de tipos válidos. + Entonces confirma el mapeo de propiedades del tipo actual al nuevo y haz click en Guardar. + El contenido se ha vuelto a publicar. + Propiedad actual + Tipo actual + El tipo de contenido no se puede cambiar, porque no hay alternativas válidas para este contenido. + Tipo de documento cambiado + Mapeo de propiedades + Mapea a la propiedad + Nueva plantilla + Nuevo tipo + ninguno + Contenido + Selecciona un nuevo Tipo de Documento + El tipo de documento del contenido seleccionado ha sido cambiado correctamente a [new type] y las siguientes propiedades mapeadas: + a + No se ha podido completar el mapeo de propiedades porque uno o más propiedades tienen más de un mapeo definido. + Solo se muestran otros tipos válidos para el contenido actual. + + + Está publicado + Acerca de + Link alternativo + (como describe la imagen sobre el teléfono) + Vinculos Alternativos + Click para editar esta entrada + Creado por + Autor original + Actualizado por + Creado + Fecha/hora de creación del documento + Tipo de Documento + Editando + Remover el + Esta entrada ha sido modificada después de haber sido publicada + Esta entrada no esta publicada + Último publicado + No hay elementos para mostrar + No hay elementos para mostrar en la lista. + No se ha añadido contenido + No se ha añadido ningún miembro + Tipo de Medio + Enlazar a medio + Miembro de Grupo + Rol + Tipo de miembro + Sin fecha + Título de la página + Propiedades + Este documento ha sido publicado pero no es visible porque el padre '%0%' no esta publicado + Upss: este documento está publicado pero no está en la caché (error interno) + No se pudo obtener la url + Este documento está publicado pero su url colisionará con contenido %0% + Publicar + Estado de la Publicación + Publicar el + Despublicar el + Fecha de Eliminación + El Orden esta actualizado + Para organizar los nodos, simplemente arrastre los nodos o realice un clic en uno de los encabezados de columna. Puede seleccionar multiple nodos manteniendo presionados "Shift" o "Control" mientras selecciona + Estadísticas + Título (opcional) + Texto alternativo (opcional) + Tipo + Ocultar + Última actualización + Fecha/hora este documento fue modificado + Eliminar archivo + Vínculo al documento + Miembro de grupo(s) + No es miembreo de grupo(s) + Nodos hijo + Target + Esto se traduce en la siguiente hora en el servidor: + ¿Esto qué significa?]]> + ¿Estás seguro que quieres eliminar este elemento? + Propiedad %0% utiliza editor %1% que no está soportado por Nested Content. + Añadir otra caja de texto + Eliminar caja de texto + Raiz de contenido + + + Crear nueva Plantilla de Contenido desde '%0%' + Vacía + Seleccionar Plantilla de Contenido + Plantilla de Contenido creada + Plantilla de Contenido creada desde '%0%' + Otra Plantilla de Contenido con este nombre ya existe + Una Plantilla de Contenido es contenido predefinido que un editor puede usar como base para crear nuevo contenido + + + Haz click para subir archivos + Arrastra los archivos aquí... + + + Crear nuevo miembro + Todos los miembros + + + ¿Dónde quieres crear el nuevo %0% + Crear debajo de + Selecciona el Tipo de Documento para el que quieres crear una plantilla de contenido + Elije un tipo y un título + "Tipos de documentos".]]> + "Tipos de medios".]]> + Tipo de Documento sin plantilla + Nueva carpeta + Nuevo tipo de dato + Nuevo archivo javascript + Nueva plantilla parcial vacía + Nueva vista parcial de macro + Nueva vista parcial desde snippet + Nueva vista parcial de macro vacía + Nueva vista parcial de macro desde snippet + Nueva vista parcial de macro (sin macro) + + + Navega en tu sitio Web + No volver a mostrar + Si Umbraco no se ha abierto tendrás que permitir ventanas emergentes para este sitio Web + se ha abierto en una nueva ventana + Reinicio + Visita + Bienvenido + + + Permanecer + Descartar cambios + Tienes cambios no guardados + ¿Estás seguro que quieres abandonar la página? Tienes cambios no guardados + + + Hecho - Deleted %0% item - Deleted %0% items - Deleted %0% out of %1% item - Deleted %0% out of %1% items + Borrado %0% elemento + Borrados %0% elementos + Borrado %0% de %1% elemento + Borrados %0% de %1% elementos - Published %0% item - Published %0% items - Published %0% out of %1% item - Published %0% out of %1% items + Publicado %0% elemento + Publicados %0% elementos + Publicado %0% de %1% elemento + Publicados %0% de %1% elementos - Unpublished %0% item - Unpublished %0% items - Unpublished %0% out of %1% item - Unpublished %0% out of %1% items + Ocultar %0% elemento + Ocultar %0% elementos + Ocultado %0% de %1% elemento + Ocultados %0% de %1% elementos - Moved %0% item - Moved %0% items - Moved %0% out of %1% item - Moved %0% out of %1% items + Mover %0% elemento + Mover %0% elementos + Movido %0% de %1% elemento + Movidos %0% de %1% elementos - Copied %0% item - Copied %0% items - Copied %0% out of %1% item - Copied %0% out of %1% items - - - Nombre - Administrar dominios - Cerrar esta ventana - Esta usted seguro que desea borrar - Esta usted seguro que desea deshabilitar - Por favor seleccione esta casilla para confirmar la eliminación de %0% entrada(s) - Esta usted seguro? - Esta usted Seguro? - Cortar - Editar entrada del Diccionario - Editar idioma - Agregar enlace interno - Insertar caracter - Insertar titular gráfico - Insertar imagen - Insertar enlace - Insertar macro - Insertar tabla - Última edición - Enlace - Enlace interno - Al usar enlaces locales, insertar "#" delante del enlace - ¿Abrir en nueva ventana? - Ajustes para la Macro - Esta macro no contiene ninguna propiedad que pueda editar - Pegar - Editar permisos para - Se está vaciando la papelera. No cierre esta ventana mientras se ejecuta este proceso - La papelera está vacía - No podrá recuperar los items una vez sean borrados de la papelera - regexlib.com está experimentando algunos problemas en estos momentos, de los cuales no somos responsables. Pedimos disculpas por las molestias.]]> - Buscar una expresión regular para agregar validación a un campo de formulario. Ejemplo: 'correo electrónico', código postal "," url " - Eliminar macro - Campo obligatorio - El sitio ha sido reindexado - Se ha actualizado la caché y se ha publicado el contenido del sitio web. - La caché del sitio web será actualizada. Todos los contenidos publicados serán actualizados, mientras el contenido no publicado permanecerá no publicado. - Número de columnas - Número de filas - Coloca un 'placeholder' id al colocar un ID en tu 'placeholder' puedes insertar contenido en esta plantilla desde una plantilla hija, referenciando este ID usando un elemento<asp:content />.]]> - Seleccione una tecla de la lista abajo indicada. Sólo puede elegir a partir de la ID de la plantilla actual del dominio. - Haga clic sobre la imagen para verla a tamaño completo. - Seleccionar item - Ver item en la caché - - - Editar las diferentes versiones lingüísticas para la entrada en el diccionario '% 0%' debajo añadir otros idiomas en el menu de 'idiomas' en el menú de la izquierda - - - - Escribe tu nombre de usuario - Escribe tu contraseña - Nombre del %0%... - Escribe un nombre... - Escribe tu búsqueda... - Escribe para filtrar resultados... - - - Permitir en nodo raíz - Sólo tipos de contenido permitidos podrán crearse bajo el nodo raíz de los árboles de Contenido y Media - Tipos de nodos hijos permitidos - Composiciones de Tipo de Documento - Crear - Borrar pestaña - Descripción - Nueva pestaña - Pestaña - Miniatura - Permitir vista de listado - Configura el contenido para mostrar un listado de nodos hijos, en lugar de mostrarlos en forma de árbol - Vista de listado actual - El tipo de vista de listado activa - Crear un tipo de listado personalizado - Quitar el tipo de listado personalizado - - - añadir prevalor - - Tipo de datos GUID - Tipo de datos GUIDprestar control - Botones - Habilitar la configuración avanzada para - Habilitar menú contextual - Por defecto, el tamaño máximo de imágenes insertado - - Mostrar etiqueta - - - - Se ha guardado la información pero debes solucionar los siguientes errores para poder publicar: - La composición actual del proveedor no es compatible con el cambio de la contraseña (Habilitar la contraseña de recuperación es necesaria para que sea cierta) - %0% ya existe - Se han encontrado los siguientes errores: - Se han encontrado los siguientes errores: - La clave debe tener como mínimo %0% caracteres y %1% caracter(es) no alfanuméricos - %0% debe ser un número entero - Debe llenar los campos del %0% al %1% - Debe llenar el campo %0% - Debe poner el formato correcto del %0% al %1% - Debe poner un formato correcto en %0% - - - NOTA: Aunque CodeMirror esté activado en los ajustes de configuracion, no se muestra en Internet Explorer debido a que no es lo suficientemente estable.' - Debe llenar el alias y el nombre en el propertytype - Hay un problema de lectura y escritura al acceder a un archivo o carpeta - - Por favor, elija un tipo - Usted está a punto de hacer la foto más grande que el tamaño original. ¿Está seguro de que desea continuar? - Error en script python - El script python no se ha guardado debido a que contenía error(es) - - Por favor, marque el contenido antes de cambiar de estilo - No active estilos disponibles - - - - El XSLT no se ha guardado, porque contenía un error (s) - - - Acerca de - Acción - Acciones - Añadir - Alias - ¿Está seguro? - Borde - o - Cancelar - Margen de la celda - Elegir - Cerrar - Cerrar ventana - Comentario - Confirmar - Mantener proporciones - Continuar - Copiar - Crear - Base de datos - Fecha - Por defecto - Borrar - Borrado - Borrando... - Diseño - Dimensiones - Abajo - Descargar - Editar - Editado - Elementos - Mail - Error - Buscar - Altura - Ayuda - Icono - Importar - Margen interno - Insertar - Instalar - Justificar - Idioma - Diseño - Cargando - Bloqueado - Iniciar sesión - Cerrar sesión - Cerrar sesión - Macro - Mover - Nombre - New - Próximo - No - de - OK - Abrir - o - Contraseña - Ruta - ID de marcador de posición - Un momento por favor... - Anterior - Propiedades - Mail para recibir los datos del formulario - Papelera - Restantes - Renombrar - Renovar - Reintentar - Permisos - Buscar - Servidor - Mostrar - Mostrar página al enviar - Tamaño - Ordenar - Submit - Tipo - Tipo que buscar... - Arriba - Actualizar - Actualizar - Upload - Url - Usuario - Nombre de usuario - Valor - Ver - Bienvenido... - Ancho - Si - Reorder - I am done reordering - - - Color de fondo - Negritas - Color del texto - Fuente - Texto - - - Página - - - El instalador no puede conectar con la base de datos. - No se ha podido guardar el archivo Web.config. Por favor, modifique la cadena de conexión manualmente. - Su base de datos ha sido encontrada y ha sido identificada como - Configuración de la base de datos - instalar para instalar %0% la base de datos de Umbraco]]> - Próximo para continuar]]> - ¡No se ha encontrado ninguna base de datos! Mira si la información en la "connection string" del “web.config” es correcta.

Para continuar, edite el "web.config" (bien sea usando Visual Studio o su editor de texto preferido), vaya al final del archivo y añada la cadena de conexión para la base de datos con el nombre (key) "umbracoDbDSN" y guarde el archivo.

Pinche en reintentar cuando haya terminado.
Pinche aquí para mayor información de como editar el web.config (en inglés)

]]>
- Por favor, contacta con tu ISP si es necesario. Si estás realizando la instalación en una máquina o servidor local, quizás necesites información de tu administrador de sistemas.]]> - Pinche en actualizar para actualizar la base de datos a Umbraco %0%

Ningún contenido será borrado de la base de datos y seguirá funcionando después de la actualización

]]>
- Pinche en Próximo para continuar. ]]> - próximo para continuar con el asistente de configuración]]> - La contraseña del usuario por defecto debe ser cambiada]]> - El usuario por defecto ha sido desabilitado o ha perdido el acceso a Umbraco!

Pinche en Próximo para continuar.]]> - ¡La contraseña del usuario por defecto ha sido cambiada desde que se instaló!

No hay que realizar ninguna tarea más. Pulsa Siguiente para proseguir.]]> - ¡La constraseña se ha cambiado! - Umbraco crea un usuario por defecto con un nombre de usuario ('admin') y constraseña ('default'). Es importante que la contraseña se cambie a algo único.

Este paso comprobará la contraseña del usuario por defecto y sugerirá si debe cambiarse.

]]>
- Ten un buen comienzo, visita nuestros videos de introducción - Pulsando el botón de Siguiente (o modificando el UmbracoConfigurationStatus en el web.config), aceptar la licencia de este software tal y como se especifica en el cuadro de debajo. Ten en cuenta que esta distribución de Umbraco consta de dos licencias diferentes, la licencia open source MIT para el framework y la licencia Umbraco freeware que cubre la IU. - No ha sido instalado. - Archivos y directorios afectados - Mas información en configurar los permisos para Umbraco aquí - Necesitas dar permisos de modificación a ASP.NET para los siguientes archivos/directorios - ¡Tu configuración de permisos es casi perfecta!

Puedes ejecutar Umbraco sin problemas, pero no podrás instalar paquetes que es algo recomendable para explotar el potencial de Umbraco.]]>
- Como Resolver - Pulsa aquí para leer la versión de texto - video tutoriales acerca de cómo configurar los permisos de los directorios para Umbraco o lee la versión de texto.]]> - ¡La configuración de tus permisos podría ser un problema!

Puedes ejecutar Umbraco sin problemas, pero no serás capaz de crear directorios o instalar paquetes que es algo recomendable para explotar el potencial de Umbraco.]]>
- ¡Tu configuración de permisos no está lista para Umbraco!

Para ejecutar Umbraco, necesitarás actualizar tu configuración de permisos.]]>
- ¡Tu configuración de permisos es perfecta!

¡Estás listo para ejecutar Umbraco e instalar paquetes!]]>
- Resolviendo problemas con directorios - Sigue este enlace para más información sobre problemas con ASP.NET y creación de directorios - Configurando los permisos de directorios - Umbraco necesita permisos de lectura/escritura en algunos directorios para poder almacenar archivos tales como imagenes y PDFs. También almacena datos en la caché para mejorar el rendimiento de su sitio web - Quiero empezar de cero - learn how). Todavía podrás elegir instalar Runway más adelante. Por favor ve a la sección del Desarrollador y elije Paquetes.]]> - Acabas de configurar una nueva plataforma Umbraco. ¿Qué deseas hacer ahora? - Se ha instalado Runway - Esta es nuestra lista de módulos recomendados, selecciona los que desees instalar, o mira la lista completa de módulos ]]> - Sólo recomendado para usuarios expertos - Quiero empezar con un sitio web sencillo - "Runway" es un sitio web sencillo que contiene unos tipos de documentos y plantillas básicos. El instalador puede configurar Runway por ti de forma automática, pero fácilmente puedes editarlo, extenderlo o eliminarlo. No es necesario y puedes usar Umbrao perfectamente sin él. Sin embargo, Runway ofrece unos cimientos sencillos basados en buenas prácticas para iniciarte más rápido que nunca. Si eliges instalar Runway, puedes seleccionar bloques de construcción básicos llamados Módulos de Runway de forma opcional para realzar tus páginas de Runway. Incluido con Runway: Página de inicio, página de Cómo empezar, página de Instalación de módulos.
Módulos opcionales: Navegación superior, Mapa del sitio, Contacto, Galería.
]]>
- ¿Qué es Runway? - Paso 1 de 5. Aceptar los términos de la licencia - Paso 2 de 5. Configuración de la base de datos - Paso 3 de 5. Autorizar / validar permiso en los archivos - Paso 4 de 5. Configurar seguridad en Umbraco - Paso 5 de 5. Umbraco está listo para ser usado - Gracias por elegir Umbraco - Navega a tu nuevo sitio Has instalado Runway, por qué no ves el aspecto de tu nuevo sitio web.]]> - Más ayuda e información Consigue ayuda de nuestra premiada comunidad, navega por la documentación o mira algunos videos gratuitos de cómo crear un sitio sencillo, cómo utilizar los paquetes y una guía rápida de la terminología de Umbraco]]> - Umbraco %0% ha sido instalado y está listo para ser usado - archivo /web.config y actualizar la clave del AppSetting UmbracoConfigurationStatus del final al valor '%0%'.]]> - empezar inmediatamente pulsando el botón "Lanzar Umbraco" de debajo.
Si eres nuevo con Umbraco, puedes encontrar cantidad de recursos en nuestras páginas de cómo empezar.]]>
- Lanzar Umbraco Para administrar tu sitio web, simplemente abre el back office de Umbraco y empieza a añadir contenido, a actualizar plantillas y hojas de estilo o a añadir nueva funcionalidad]]> - No se ha podido establecer la conexión con la base de datos - Umbraco versión 3 - Umbraco versión 4 - Mirar - Umbraco %0% o actualizar la versión 3.0 a Umbraco %0%.

Pinche en "próximo" para empezar con el asistente de configuración.]]>
- - - Código de cultura - Nombre de cultura - - - No ha habido ninguna actividad y su sessión se cerrará en - Renovar su sessión para guardar sus cambios - - - Feliz super domingo - Feliz lunes - Tremendo martes - Maravilloso miércoles - Fantástico jueves - ¡Ya es viernes! - Resplandeciente sábado - Iniciar sesión - La sesión ha caducado - © 2001 - %0%
umbraco.com

]]>
- + Copiar %0% elemento + Copiar %0% elementos + Copiado %0% de %1% elemento + Copiado %0% de %1% elementos + + + Título del vínculo + Vínculo + Nombre + Administrar dominios + Cerrar esta ventana + Esta usted seguro que desea borrar + Esta usted seguro que desea deshabilitar + Por favor seleccione esta casilla para confirmar la eliminación de %0% entrada(s) + Esta usted seguro? + Esta usted Seguro? + Cortar + Editar entrada del Diccionario + Editar idioma + Agregar enlace interno + Insertar caracter + Insertar titular gráfico + Insertar imagen + Insertar enlace + Insertar macro + Insertar tabla + Última edición + Enlace + Enlace interno + Al usar enlaces locales, insertar "#" delante del enlace + ¿Abrir en nueva ventana? + Ajustes para la Macro + Esta macro no contiene ninguna propiedad que pueda editar + Pegar + Editar permisos para + Establecer permisos para + Establecer permisos para %0% para grupo %1% + Selecciona el grupo de usuarios para el cual quieres establecer permisos + Se está vaciando la papelera. No cierre esta ventana mientras se ejecuta este proceso + La papelera está vacía + No podrá recuperar los items una vez sean borrados de la papelera + regexlib.com está experimentando algunos problemas en estos momentos, de los cuales no somos responsables. Pedimos disculpas por las molestias.]]> + Buscar una expresión regular para agregar validación a un campo de formulario. Ejemplo: 'correo electrónico', código postal "," url " + Eliminar macro + Campo obligatorio + El sitio ha sido reindexado + Se ha actualizado la caché y se ha publicado el contenido del sitio web. + La caché del sitio web será actualizada. Todos los contenidos publicados serán actualizados, mientras el contenido no publicado permanecerá no publicado. + Número de columnas + Número de filas + Coloca un 'placeholder' id al colocar un ID en tu 'placeholder' puedes insertar contenido en esta plantilla desde una plantilla hija, referenciando este ID usando un elemento<asp:content />.]]> + Seleccione una tecla de la lista abajo indicada. Sólo puede elegir a partir de la ID de la plantilla actual del dominio. + Haga clic sobre la imagen para verla a tamaño completo. + Seleccionar item + Ver item en la caché + Crear carpeta... + Relacionar con original + Incluir descendientes + La amigable comunidad + Enlazar a página + Abre el documento enlazado en una nueva ventana o Opens the linked document in a new window o pestaña + Enlazar a medio + Enlazar a archivo + Selecciona nodo de inicio de contenido + Selecciona medio + Selecciona icono + Selecciona elemento + Selecciona enlace + Selecciona macro + Selecciona contenido + Selecciona nodo de inicio de medios + Selecciona miembro + Selecciona grupo de miembros + Selecciona nodo + Selecciona secciones + Selecciona usuarios + No se encontraron iconos + No hay parametros para esta macro + No hay maros disponibles para insertar + Proveedores de login externo + Detalles de la Excepción + Stacktrace + Excepción interna + Enlaza tu + Desenlaza tu + Cuenta + Selecciona editor + Selecciona snippet + + + Editar las diferentes versiones lingüísticas para la entrada en el diccionario '% 0%' debajo añadir otros idiomas en el menu de 'idiomas' en el menú de la izquierda + + + + Edita clave de elemento de dictionario. + + + + + + Escribe tu nombre de usuario + Escribe tu contraseña + Confirma tu contraseña + Nombre del %0%... + Escribe un nombre... + Introduce tu email... + Introduce tu nombre de usuario... + Etiqueta... + Introduce una descripción... + Escribe tu búsqueda... + Escribe para filtrar resultados... + Teclea para crear etiquetas (pulsa enter después de cada etiqueta)... + Introduce tu email.... + Introduce un mensaje... + Tu nombre de usuario normalmente es tu e-mail + + + Permitir en nodo raíz + Sólo tipos de contenido permitidos podrán crearse bajo el nodo raíz de los árboles de Contenido y Media + Tipos de nodos hijos permitidos + Composiciones de Tipo de Documento + Crear + Borrar pestaña + Descripción + Nueva pestaña + Pestaña + Miniatura + Permitir vista de listado + Configura el contenido para mostrar un listado de nodos hijos, en lugar de mostrarlos en forma de árbol + Vista de listado actual + El tipo de vista de listado activa + Crear un tipo de listado personalizado + Quitar el tipo de listado personalizado + + + Renombrado + Introduce un nuevo nombre para la carpeta aqui + %0% fue renombrada a %1% + + + añadir prevalor + + + + Tipo de datos GUID + Tipo de datos GUIDprestar control + Botones + Habilitar la configuración avanzada para + Habilitar menú contextual + Por defecto, el tamaño máximo de imágenes insertado + + + + Mostrar etiqueta + + + + Todos los tipos y datos de propiedad + usar este tipo de datos lo borrará permanentemente, por favor confirma que quieres borrarlos también + Sí, borrar + y Todos los tipos y datos de propiedad usando este tipo de datos + Selecciona carpeta para mover + a la estructura de contenido + se movió debajo + + + Se ha guardado la información pero debes solucionar los siguientes errores para poder publicar: + La composición actual del proveedor no es compatible con el cambio de la contraseña (Habilitar la contraseña de recuperación es necesaria para que sea cierta) + %0% ya existe + Se han encontrado los siguientes errores: + Se han encontrado los siguientes errores: + La clave debe tener como mínimo %0% caracteres y %1% caracter(es) no alfanuméricos + %0% debe ser un número entero + Debe llenar los campos del %0% al %1% + Debe llenar el campo %0% + Debe poner el formato correcto del %0% al %1% + Debe poner un formato correcto en %0% + + + Se recibió un error desde el servidor + El tipo de archivo especificado ha sido deshabilitado por el administrador + NOTA: Aunque CodeMirror esté activado en los ajustes de configuracion, no se muestra en Internet Explorer debido a que no es lo suficientemente estable.' + Debe llenar el alias y el nombre en el propertytype + Hay un problema de lectura y escritura al acceder a un archivo o carpeta + Error cargando Vista Parcial (archivo: %0%) + Error cargando userControl '%0%' + Error cargandog customControl (Assembly: %0%, Type: '%1%') + Error cargando MacroEngine script (file: %0%) + "Error analizando archivo XSLT: %0% + "Error leyendo archivo XSLT: %0% + + + + Por favor, elija un tipo + Usted está a punto de hacer la foto más grande que el tamaño original. ¿Está seguro de que desea continuar? + Error en script python + El script python no se ha guardado debido a que contenía error(es) + + + + Por favor, marque el contenido antes de cambiar de estilo + No active estilos disponibles + + + + + + + + + + El XSLT no se ha guardado, porque contenía un error (s) + Hay un error en la configuración el tipo de datos usado para esta propiedad, por favor revisa el tipo de datos. + + + Acerca de + Acción + Acciones + Añadir + Alias + ¿Está seguro? + Borde + o + Cancelar + Margen de la celda + Elegir + Cerrar + Cerrar ventana + Comentario + Confirmar + Mantener proporciones + Continuar + Copiar + Crear + Base de datos + Fecha + Por defecto + Borrar + Borrado + Borrando... + Diseño + Dictionario + Dimensiones + Abajo + Descargar + Editar + Editado + Elementos + Mail + Error + Buscar + Primero + Grupos + Altura + Ayuda + Ocultar + Icono + Importar + Margen interno + Insertar + Instalar + Invalido + Justificar + Etiqueta + Idioma + Ultimo + Diseño + Cargando + Bloqueado + Iniciar sesión + Cerrar sesión + Cerrar sesión + Macro + Obligatorio + Mensaje + Mover + Nombre + New + Próximo + No + de + Desactivado + OK + Abrir + Activado + o + Ordenar por + Contraseña + Ruta + ID de marcador de posición + Un momento por favor... + Anterior + Propiedades + Mail para recibir los datos del formulario + Papelera + Tu papelera está vacía + Restantes + Eliminar + Renombrar + Renovar + Requerido + Recuperar + Reintentar + Permisos + Buscar + Perdona, pero no podemos encontrar lo que buscas + No se han añadido elementos + Servidor + Mostrar + Mostrar página al enviar + Tamaño + Ordenar + Estado + Aceptar + Tipo + Tipo que buscar... + Arriba + Actualizar + Actualizar + Subir + Url + Usuario + Nombre de usuario + Valor + Ver + Bienvenido... + Ancho + Si + Carpeta + Resultados de búsqueda + Reordenar + He terminado de ordenar + Prever + Cambiar contraseña + a + Vista de lista + Guardando... + actual + Insertar + selecionado + + + Negro + Verde + Amarillo + Naranja + Azul + Rojo + + + Añadir pestaña + Añadir propiedad + Añadir editor + Añadir platilla + Añadir nodo hijo + Añadir hijo - - Panel de Administración - Secciones - Contenido - - - Elija una página arriba... - %0% ha sido copiado al %1% - Seleccione donde el documento %0% debe ser copiado abajo - %0% ha sido movido a %1% - Seleccione debajo donde mover el documento %0% - ha sido seleccionado como raíz de su nuevo contenido, haga click sobre 'ok' debajo. - No ha seleccionado ningún nodo. Seleccione un nodo en la lista mostrada arriba antes the pinchar en 'continuar' (continue) - No se puede colgar el nodo actual bajo el nodo elegido debido a su tipo - El nodo actual no puede moverse a ninguna de sus subpáginas - Acción no permitida. No tiene permisos suficientes para uno o más subnodos.' - - - Edite su notificación para %0% - Hola %0% Esto es un e-mail automático para informarle que la tarea '%1%' ha sido realizada sobre la página '%2%' por el usuario '%3%' Vaya a http://%4%/#/content/content/edit/%5% para editarla. ¡Espero que tenga un buen día! Saludos del robot de Umbraco - Hola %0%

Esto es un e-mail generado automáticamente para informarle que la tarea '%1%' ha sido realizada sobre la página '%2%' por el usuario '%3%'

Resumen de actualización:

%6%

¡Espero que tenga un buen día!

Saludos del robot Umbraco.

]]>
- [%0%] Notificación acerca de %1% realizado en %2% - Notificaciones - - - y localizando el paquete. Los paquetes de Umbraco normalmente tienen la extensión ".umb" o ".zip".]]> - Autor - - Documentación - Meta datos del paquete - Nombre del paquete - El paquete no contiene ningún elemento -
Puedes eliminarlo del sistema de forma segura seleccionando la opción "desinstalar paquete" de abajo.]]>
- No hay actualizaciones disponibles - Opciones del paquete - Leeme del paquete - Repositorio de paquetes - Confirma la desinstalación - El paquete ha sido desinstalado - El paquete se ha desinstalado correctamente - Desinstalar paquete - Nota: cualquier documento, archivo etc dependiente de los elementos eliminados, dejará de funcionar, y puede conllevar inestabilidad en el sistema, por lo que lleva cuidado al desinstalar elementos. En caso de duda, contacta con el autor del paquete.]]> - Descargar actualización del repositorio - Actualizar paquete - Instrucciones de actualización - Hay una actualización disponible para este paquete. Puedes descargarla directamente del repositorio de paquetes de Umbraco. - Versión del paquete - Ver página web del paquete - - - Pegar con formato completo (No recomendado) - El texto que estás intentando pegar contiene caractéres o formato especial. El problema puede ser debido al copiar texto desde Microsoft Word. Umbraco puede eliminar estos caractéres o formato especial automáticamente, de esa manera el contenido será más adecuado para la web. - Pegar como texto sin formato - Pegar, pero quitando el formato (Recomendado) - - - Proteccion basada en roles - usando los grupos de miembros de Umbraco.]]> - autenticación basada en roles.]]> - Página de error - Usada cuando alguien hace login, pero no tiene acceso - Elija cómo restringir el acceso a esta página - %0% está protegido - Protección borrada de %0% - Página de login - Elija la página que contenga el formulario de login - Borrar protección - Elija las páginas que contendrán el formulario de login y mensajes de error - Elija los roles que tendrán acceso a esta página - Elija el login y password para esta página - Protección de usuario único - Si sólo necesita configurar una protección simple usando un único login y password - - - %0% no ha podido ser publicado, debido a que una extensión de otro proveedor ha cancelado la acción. - Incluir las páginas hija sin publicar - Publicación en progreso - por favor, espera... - Se han publicado %0% de %1% páginas... - %0% se ha publicado - %0% y sus subpáginas se han publicado - Publicar %0% y todas sus subpáginas - aceptar para publicar %0% y por lo tanto, hacer que su contenido esté disponible al público.

Puedes publicar esta página y todas sus subpáginas marcando publicar todos los hijos debajo. ]]>
- - - Añadir un enlace externo - Añadir un enlace interno - Añadir - Título - Página interna - Enlace - Bajar - Subir - Abrir en una nueva ventana - Quitar el enlace - - - Versión actual - Red el texto de la versión seleccionada no se mostrará. , green means added]]> - Se ha recuperado la última versión del documento. - Esto muestra la versión seleccionada como html, si desea ver la diferencia entre 2 versiones al mismo tiempo, por favor use la vista diff - Volver a - Elija versión - Vista - - - Editar fichero de script - - - Conserje - Contenido - Mensajero - Desarrollador - Asistente de configuración de Umbraco - Media - Miembros - Boletín informativo - Ajustes - Estadísticas - Traducción - Usuarios - Ayuda - Analytics - - - Plantilla por defecto - Clave de diccionario - Para importar un tipo de documento encuentre el fichero ".udt" en su ordenador haciendo click sobre el botón "Navegar" y pulsando "Importar" (se le solicitará confirmación en la siguiente pantalla) - Nuevo nombre de la pestaña - Tipo de nodo - Tipo - Hoja de estilos - Propiedades de la hoja de estilos - Pestaña - Nombre de la pestaña - Pestañas - Tipo de Contenido Maestro activado - Este Tipo de Contenido usa - como Tipo de Contenido Maestro. Las pestañas para los Tipos de Contenido Maestros no se muestran y solo se pueden modificar desde el Tipo de Contenido Maestro - No existen propiedades para esta pestaña. Haga clic en el enlace "añadir nueva propiedad" para crear una nueva propiedad. - Tipo de documento Maestro - Crear template correspondiente - - - Sort order - Creation date - Ordenación completa. - Arrastra las diferentes páginas debajo para colocarlas como deberían estar. O haz click en las cabeceras de las columnas para ordenar todas las páginas - -
No cierre esta ventana mientras se está ordenando ]]>
- - - La publicación fue cancelada por un complemento de terceros - El tipo de propiedad ya existe - Tipo de propiedad creado - Tipo de Dato: %1%]]> - Tipo de propiedad eliminado - Tipo de contenido guardado - Pestaña creada - Pestaña eliminada - Pestaña con id: %0% eliminada - La hoja de estilos no se ha guardado - Hoja de estilos guardada - La hoja de estilos se ha guardado sin errores - Tipo de dato guardado - Elemento del diccionario guardado - La publicación ha fallado porque la página padre no está publicada - Contenido publicado - y visible en el sitio web - Contenido guardado - Recuerda publicar para hacer los cambios visibles - Mandado para ser aprobado - Los cambios se han mandado para ser aprobados - Miembro guardado - Propiedad de la hoja de estilos guardada - Hoja de estilos guardada - Plantilla guardada - Error grabando usuario (comprueba el log) - Usuario grabado - El archivo no se ha guardado - El archivo no se ha grabado. Por favor, comprueba los permisos de los ficheros - Archivo guardado - Archivo grabado sin errores - Lenguaje guardado - El script en Python no se ha guardado - El script en Python no se ha podido guardar debido a un error - Script en Python guardado - No hay errores en el script en Python - La plantilla no se ha guardado - Por favor, asegúrate de que no hay 2 plantillas con el mismo alias - Plantilla guardada - Plantilla guardada sin errores - El XSLT no se ha guardado - El XSLT tenía un error - El XSLT no se ha podido guardar, comprueba los permisos de los ficheros - XSLT guardado - No hay errores en el XSLT - - - Usa sintaxis CSS, p.ej.: h1, .redHeader, .blueTex - Editar hoja de estilos - Editar propiedades de la hoja de estilos - Nombre para identificar la propiedad del estilo en el editor de texto rico - Previsualizar - Estilos - - - Editar plantilla - Insertar área de contenido - Insertar marcador de posición de área de contenido - Insertar objeto del diccionario - Insertar macro - Insertar campo de página de Umbraco - Plantilla principal - Guía rápida sobre las etiquetas de plantilla de Umbraco - Plantilla - - - Insertar control - Choose layout - Añade más filas - Add content - Drop content - Settings applied + Editar tipo de dato - This content is not allowed here - This content is allowed here + Navegar secciones - Plantillas de Grid - Las plantillas son el área de trabajo para el editor de grids, normalmente sólo necesitas una o dos plantillas diferentes - Añadir plantilla de grid - Ajusta la plantilla configurando la anchura de las columnas y añadiendo más secciones + Atajos + mostrar atajos - Configuraciones de filas - Las filas son celdas predefinidas que se disponen horizontalmente - Añade una configuración de fila - Ajusta la fila configurando los anchos de cada celda y añadiendo más celdas + Activar/Desactivar vista de lista + Activar/Desactivar permitir como raiz - Columnas - Número total de columnas en la plantilla del grid + Act/Desact Comentar líneas + Elimiar línea + Copiar líneas arriba + Copiar líneas abajo + Mover líneas arriba + Mover líneas abajo - Configuración - Configura qué ajustes pueden cambiar los editores + General + Editor + + + Color de fondo + Negritas + Color del texto + Fuente + Texto + + + Página + + + El instalador no puede conectar con la base de datos. + No se ha podido guardar el archivo Web.config. Por favor, modifique la cadena de conexión manualmente. + Su base de datos ha sido encontrada y ha sido identificada como + Configuración de la base de datos + instalar para instalar %0% la base de datos de Umbraco]]> + Próximo para continuar]]> + ¡No se ha encontrado ninguna base de datos! Mira si la información en la "connection string" del “web.config” es correcta.

Para continuar, edite el "web.config" (bien sea usando Visual Studio o su editor de texto preferido), vaya al final del archivo y añada la cadena de conexión para la base de datos con el nombre (key) "umbracoDbDSN" y guarde el archivo.

Pinche en reintentar cuando haya terminado.
Pinche aquí para mayor información de como editar el web.config (en inglés)

]]>
+ Por favor, contacta con tu ISP si es necesario. Si estás realizando la instalación en una máquina o servidor local, quizás necesites información de tu administrador de sistemas.]]> + Pinche en actualizar para actualizar la base de datos a Umbraco %0%

Ningún contenido será borrado de la base de datos y seguirá funcionando después de la actualización

]]>
+ Pinche en Próximo para continuar. ]]> + próximo para continuar con el asistente de configuración]]> + La contraseña del usuario por defecto debe ser cambiada]]> + El usuario por defecto ha sido desabilitado o ha perdido el acceso a Umbraco!

Pinche en Próximo para continuar.]]> + ¡La contraseña del usuario por defecto ha sido cambiada desde que se instaló!

No hay que realizar ninguna tarea más. Pulsa Siguiente para proseguir.]]> + ¡La contraseña se ha cambiado! + Ten un buen comienzo, visita nuestros videos de introducción + Pulsando el botón de Siguiente (o modificando el UmbracoConfigurationStatus en el web.config), aceptar la licencia de este software tal y como se especifica en el cuadro de debajo. Ten en cuenta que esta distribución de Umbraco consta de dos licencias diferentes, la licencia open source MIT para el framework y la licencia Umbraco freeware que cubre la IU. + No ha sido instalado. + Archivos y directorios afectados + Mas información en configurar los permisos para Umbraco aquí + Necesitas dar permisos de modificación a ASP.NET para los siguientes archivos/directorios + ¡Tu configuración de permisos es casi perfecta!

Puedes ejecutar Umbraco sin problemas, pero no podrás instalar paquetes que es algo recomendable para explotar el potencial de Umbraco.]]>
+ Como Resolver + Pulsa aquí para leer la versión de texto + video tutoriales acerca de cómo configurar los permisos de los directorios para Umbraco o lee la versión de texto.]]> + ¡La configuración de tus permisos podría ser un problema!

Puedes ejecutar Umbraco sin problemas, pero no serás capaz de crear directorios o instalar paquetes que es algo recomendable para explotar el potencial de Umbraco.]]>
+ ¡Tu configuración de permisos no está lista para Umbraco!

Para ejecutar Umbraco, necesitarás actualizar tu configuración de permisos.]]>
+ ¡Tu configuración de permisos es perfecta!

¡Estás listo para ejecutar Umbraco e instalar paquetes!]]>
+ Resolviendo problemas con directorios + Sigue este enlace para más información sobre problemas con ASP.NET y creación de directorios + Configurando los permisos de directorios + Umbraco necesita permisos de lectura/escritura en algunos directorios para poder almacenar archivos tales como imagenes y PDFs. También almacena datos en la caché para mejorar el rendimiento de su sitio web + Quiero empezar de cero + learn how). Todavía podrás elegir instalar Runway más adelante. Por favor ve a la sección del Desarrollador y elije Paquetes.]]> + Acabas de configurar una nueva plataforma Umbraco. ¿Qué deseas hacer ahora? + Se ha instalado Runway + Esta es nuestra lista de módulos recomendados, selecciona los que desees instalar, o mira la lista completa de módulos ]]> + Sólo recomendado para usuarios expertos + Quiero empezar con un sitio web sencillo + "Runway" es un sitio web sencillo que contiene unos tipos de documentos y plantillas básicos. El instalador puede configurar Runway por ti de forma automática, pero fácilmente puedes editarlo, extenderlo o eliminarlo. No es necesario y puedes usar Umbrao perfectamente sin él. Sin embargo, Runway ofrece unos cimientos sencillos basados en buenas prácticas para iniciarte más rápido que nunca. Si eliges instalar Runway, puedes seleccionar bloques de construcción básicos llamados Módulos de Runway de forma opcional para realzar tus páginas de Runway. Incluido con Runway: Página de inicio, página de Cómo empezar, página de Instalación de módulos.
Módulos opcionales: Navegación superior, Mapa del sitio, Contacto, Galería.
]]>
+ ¿Qué es Runway? + Paso 1 de 5. Aceptar los términos de la licencia + Paso 2 de 5. Configuración de la base de datos + Paso 3 de 5. Autorizar / validar permiso en los archivos + Paso 4 de 5. Configurar seguridad en Umbraco + Paso 5 de 5. Umbraco está listo para ser usado + Gracias por elegir Umbraco + Navega a tu nuevo sitio Has instalado Runway, por qué no ves el aspecto de tu nuevo sitio web.]]> + Más ayuda e información Consigue ayuda de nuestra premiada comunidad, navega por la documentación o mira algunos videos gratuitos de cómo crear un sitio sencillo, cómo utilizar los paquetes y una guía rápida de la terminología de Umbraco]]> + Umbraco %0% ha sido instalado y está listo para ser usado + archivo /web.config y actualizar la clave del AppSetting UmbracoConfigurationStatus del final al valor '%0%'.]]> + empezar inmediatamente pulsando el botón "Lanzar Umbraco" de debajo.
Si eres nuevo con Umbraco, puedes encontrar cantidad de recursos en nuestras páginas de cómo empezar.]]>
+ Lanzar Umbraco Para administrar tu sitio web, simplemente abre el back office de Umbraco y empieza a añadir contenido, a actualizar plantillas y hojas de estilo o a añadir nueva funcionalidad]]> + No se ha podido establecer la conexión con la base de datos + Umbraco versión 3 + Umbraco versión 4 + Mirar + Umbraco %0% o actualizar la versión 3.0 a Umbraco %0%.

Pinche en "próximo" para empezar con el asistente de configuración.]]>
+ + + Código de cultura + Nombre de cultura + + + No ha habido ninguna actividad y su sessión se cerrará en + Renovar su sesión para guardar sus cambios + + + Feliz super domingo + Feliz lunes + Tremendo martes + Maravilloso miércoles + Fantástico jueves + ¡Ya es viernes! + Resplandeciente sábado + Iniciar sesión + La sesión ha caducado + © 2001 - %0%
umbraco.com

]]>
+ ¿Olvidaste tu contraseña? + Enviaremos un email a la dirección especificada con un enlace para restaurar tu contraseña + Un email con instrucciones para restaurar tu contraseña será enviado a la dirección especificada si esta está registrada. + Volver a formularion de acceso + Por favor, introduce una nueva contraseña + Tu contraseña has sido actualizada + El enlace pulsado es inválido o ha caducado + Umbraco: Restaurar contraseña + + + + + + + + + + + + +
+ + + + + +
+ +
+ +
+
+ + + + + + +
+
+
+ + + + +
+ + + + +
+

+ Restauración de contraseña requerida +

+

+ Tu nombre de usuario para acceder al área de administración es: %0% +

+

+ + + + + + +
+ + Pulsa este enlace para restaurar tu contraseña + +
+

+

Si no puedes pulsar en el enlace, copia y pega esta dirección URL en tu navegador:

+ + + + +
+ + %1% + +
+

+
+
+


+
+
+ + + ]]> +
+ + + + Panel de Administración + Secciones + Contenido + + + Elija una página arriba... + %0% ha sido copiado al %1% + Seleccione donde el documento %0% debe ser copiado abajo + %0% ha sido movido a %1% + Seleccione debajo donde mover el documento %0% + ha sido seleccionado como raíz de su nuevo contenido, haga click sobre 'ok' debajo. + No ha seleccionado ningún nodo. Seleccione un nodo en la lista mostrada arriba antes the pinchar en 'continuar' (continue) + No se puede colgar el nodo actual bajo el nodo elegido debido a su tipo + El nodo actual no puede moverse a ninguna de sus subpáginas + El nodo actual no puede existir en la raiz + Acción no permitida. No tiene permisos suficientes para uno o más subnodos.' + Relacionar elemento copiado al original + + + Edite su notificación para %0% + Hola %0% Esto es un e-mail automático para informarle que la tarea '%1%' ha sido realizada sobre la página '%2%' por el usuario '%3%' Vaya a http://%4%/#/content/content/edit/%5% para editarla. ¡Espero que tenga un buen día! Saludos del robot de Umbraco + Hola %0%

Esto es un e-mail generado automáticamente para informarle que la tarea '%1%' ha sido realizada sobre la página '%2%' por el usuario '%3%'

Resumen de actualización:

%6%

¡Espero que tenga un buen día!

Saludos del robot Umbraco.

]]>
+ [%0%] Notificación acerca de %1% realizado en %2% + Notificaciones + + + y localizando el paquete. Los paquetes de Umbraco normalmente tienen la extensión ".umb" o ".zip".]]> + Suelte para subir archivo + o pulse aquí para elegir paquete + Subir paquete + Instala un paquete local seleccionándolo desde tu ordenador. Sólo instala paquetes de fuentes que conoces y en las que confías + Subir otro paquete + Cancelar y subir otro paquete + Licencia + Aceptop + términos de uso + Instalar paquete + Terminar + Paquetes instalados + No tienes instalado ningún paquete + 'Paquetes' en la zona superior derecha de tu pantalla]]> + Buscar paquetes + Resultados para + No pudimos encontrar nada para + Por favor, prueba buscando por otro paquete o navega por las categorías + Popular + Novedades + tiene + puntos de karma + Información + Propetarioa + Contribuidores + Creado + Versión actual + Versión .NET + Descargas + Gustas + Compatibilidad + Este paquete es compatible con las siguientes versiones de Umbraco, declaradas según miembros de la comunidad. No se puede garantizar compatibilidad completa para versiones declaradas debajo del 100% + Fuentes externas + Autor + + + + Documentación + Meta datos del paquete + Nombre del paquete + El paquete no contiene ningún elemento +
Puedes eliminarlo del sistema de forma segura seleccionando la opción "desinstalar paquete" de abajo.]]>
+ No hay actualizaciones disponibles + Opciones del paquete + Leeme del paquete + Repositorio de paquetes + Confirma la desinstalación + El paquete ha sido desinstalado + El paquete se ha desinstalado correctamente + Desinstalar paquete + Nota: cualquier documento, archivo etc dependiente de los elementos eliminados, dejará de funcionar, y puede conllevar inestabilidad en el sistema, por lo que lleva cuidado al desinstalar elementos. En caso de duda, contacta con el autor del paquete.]]> + Descargar actualización del repositorio + Actualizar paquete + Instrucciones de actualización + Hay una actualización disponible para este paquete. Puedes descargarla directamente del repositorio de paquetes de Umbraco. + Versión del paquete + Ver página web del paquete + Paquete ya instalado + Este paquete no se puede instalar, requiere un versión mínima de Umbraco de + Desinstalando... + Descargando... + Importando... + Instalando... + Reiniciando, por favor espera... + Todo hecho, tu navegador se actualizará, por favor espera... + Por favor pulsa 'Terminar' para completar la instalación y actualizar la página. + Subiendo paquete... + + + Pegar con formato completo (No recomendado) + El texto que estás intentando pegar contiene caractéres o formato especial. El problema puede ser debido al copiar texto desde Microsoft Word. Umbraco puede eliminar estos caractéres o formato especial automáticamente, de esa manera el contenido será más adecuado para la web. + Pegar como texto sin formato + Pegar, pero quitando el formato (Recomendado) + + + Proteccion basada en roles + usando los grupos de miembros de Umbraco.]]> + Necesita crear un grupo de miembros antes de poder usar autenticación basada en roles + Página de error + Usada cuando alguien hace login, pero no tiene acceso + Elija cómo restringir el acceso a esta página + %0% está protegido + Protección borrada de %0% + Página de login + Elija la página que contenga el formulario de login + Borrar protección + Elija las páginas que contendrán el formulario de login y mensajes de error + Elija los roles que tendrán acceso a esta página + Elija el login y password para esta página + Protección de usuario único + Si sólo necesita configurar una protección simple usando un único login y password + + + + + + + + + + + + %0% no se pudo publicar debido a que una extensión de otro proveedor ha cancelado la acción. + + + + + Incluir las páginas hija sin publicar + Publicación en progreso - por favor, espera... + Se han publicado %0% de %1% páginas... + %0% se ha publicado + %0% y sus subpáginas se han publicado + Publicar %0% y todas sus subpáginas + aceptar para publicar %0% y por lo tanto, hacer que su contenido esté disponible al público.

Puedes publicar esta página y todas sus subpáginas marcando publicar todos los hijos debajo. ]]>
+ + + No has configurado ningún color + + + Has seleccionado un elemento borrado o en la papelera de reciclaje + Has seleccionado unos elementos borrados o en la papelera de reciclaje + + + Has seleccionado un elemento borrado o en la papelera de reciclaje + Has seleccionado unos elementos borrados o en la papelera de reciclaje + Elemento borrado + + + añadir un enlace externo + elegir un enlace interno + Título + Enlace + Abrir en una nueva ventana + Introduce texto + Introduzce el enlace + + + Reiniciar + Definir corte + Da al corte un alias y su anchura y altura por defecto + Guardar corte + Añadir nuevo corte + + + Versión actual + Red el texto de la versión seleccionada no se mostrará. , green means added]]> + Se ha recuperado la última versión del documento. + Esto muestra la versión seleccionada como html, si desea ver la diferencia entre 2 versiones al mismo tiempo, por favor use la vista diff + Volver a + Elija versión + Ver + + + Editar fichero de script + + + Conserje + Contenido + Mensajero + Desarrollador + Asistente de configuración de Umbraco + Media + Miembros + Boletín informativo + Ajustes + Estadísticas + Traducción + Usuarios + Ayuda + Analisis + + + ir a + Temas de ayuda para + Capítulos de vídeo para + Los mejores tutoriales en video para Umbraco + + + Plantilla por defecto + Clave de diccionario + Para importar un tipo de documento encuentre el fichero ".udt" en su ordenador haciendo click sobre el botón "Navegar" y pulsando "Importar" (se le solicitará confirmación en la siguiente pantalla) + Nuevo nombre de la pestaña + Tipo de nodo + Tipo + Hoja de estilos + Script + Propiedades de la hoja de estilos + Pestaña + Nombre de la pestaña + Pestañas + Tipo de Contenido Maestro activado + Este Tipo de Contenido usa + como Tipo de Contenido Maestro. Las pestañas para los Tipos de Contenido Maestros no se muestran y solo se pueden modificar desde el Tipo de Contenido Maestro + No existen propiedades para esta pestaña. Haga clic en el enlace "añadir nueva propiedad" para crear una nueva propiedad. + Tipo de documento Maestro + Crear plantilla correspondiente + Añadir icono + + + Ordenar + Fecha Creado + Ordenación completa + Arrastra las diferentes páginas debajo para colocarlas como deberían estar. O haz click en las cabeceras de las columnas para ordenar todas las páginas + + + + Validación + Los errors de validación deben ser arreglados antes de que el elemento pueda ser guardado + Fallo + Guardado + Insuficientes permisos de usuario, no se pudo completar la operación + Cancelado + La operación fue cancelada fue cancelada por un complemento de terceros + La publicación fue cancelada por un complemento de terceros + El tipo de propiedad ya existe + Tipo de propiedad creado + Tipo de Dato: %1%]]> + Tipo de propiedad eliminado + Tipo de contenido guardado + Pestaña creada + Pestaña eliminada + Pestaña con id: %0% eliminada + La hoja de estilos no se ha guardado + Hoja de estilos guardada + La hoja de estilos se ha guardado sin errores + Tipo de dato guardado + Elemento del diccionario guardado + La publicación ha fallado porque la página padre no está publicada + Contenido publicado + y visible en el sitio web + Contenido guardado + Recuerda publicar para hacer los cambios visibles + Mandado para ser aprobado + Los cambios se han mandado para ser aprobados + Medio guardado + Medio guardado sin errores + Miembro guardado + Propiedad de la hoja de estilos guardada + Hoja de estilos guardada + Plantilla guardada + Error grabando usuario (comprueba el log) + Usuario grabado + Tipo de usuario guardado + Grupo de usuario guardado + El archivo no se ha guardado + El archivo no se ha grabado. Por favor, comprueba los permisos de los ficheros + Archivo guardado + Archivo grabado sin errores + Lenguaje guardado + Tipo de medio guardado + Tipo de miembro guardado + El script en Python no se ha guardado + El script en Python no se ha podido guardar debido a un error + Script en Python guardado + No hay errores en el script en Python + La plantilla no se ha guardado + Por favor, asegúrate de que no hay 2 plantillas con el mismo alias + Plantilla guardada + Plantilla guardada sin errores + El XSLT no se ha guardado + El XSLT tenía un error + El XSLT no se ha podido guardar, comprueba los permisos de los ficheros + XSLT guardado + No hay errores en el XSLT + Contenido oculto + Vista parcial guardada + Vista parcial guardada sin errores + ista parcial no guardada + Error guardando el archivo. + Permisos guardados para + Script guardado + Script guardado sin errores! + Script no guardado + Error guardando el archivo. + Error guardando el archivo. + Borrados %0% grupos de usuario + %0% fue borrado + %0% usuarios activados + Se produjo un error activando los usuarios + %0% usuarios desactivados + Se produjo un error desactivando los usuarios + %0% usuario activado + Se produjo un error activando el usuario + %0% desactivado + Se produjo un error desactivando el usuario + Grupos de usuario establecidos + %0% grupos de usuario borrados + %0% fue borrado + %0% usuarios desbloquedaos + Error desbloqueando usuarios + %0% está desbloqueado + Error desbloqueando usuario + + + Usa sintaxis CSS, p.ej.: h1, .redHeader, .blueTex + Editar hoja de estilos + Editar propiedades de la hoja de estilos + Nombre para identificar la propiedad del estilo en el editor de texto rico + Previsualizar + Estilos + + + Editar plantilla + Secciones + Insertar área de contenido + Insertar marcador de posición de área de contenido + Insertar + Elije que insertar en tu plantilla + Insertar objeto del diccionario + A dictionary item is a placeholder for a translatable piece of text, which makes it easy to create designs for multilingual websites. + Insertar macro + + Una Macor es un componente configurable que es genial como partes reutilizables de tu diseño, + donde necesites una forma de proporcionar parámetros, + como galerías, formularios y listas. + + Insertar campo de página de Umbraco + Muestra el valor de una propiedad de la página actual, con opciones para modificar el valor or usar valores alternativos. + Vista parcial + + Una vista parcial es una platilla separada que puede ser mostrada dentro de otra plantilla. + Es útil para reutilizar código or para distribuir plantillas complejas en archivos separados. + + Plantilla principal + Sin plantilla principal + Sin principal + + Mostrar plantilla hija + + @RenderBody() como sustituto. + ]]> + + + Define una sección nombrada + + @section { ... }. Esto se puede mostrar en un área específica de la plantilla madre usando @RenderSection. + ]]> + + + Muestra una sección nombrada + + @RenderSection(name) placeholder. + Esto muestra un area de una plantilla hija rodeada de la corresponsiente definición @section [name]{ ... }. + ]]> + + + Nombre de sección + Sección es obligatoria + + Si obligatoria, la plantilla hija debe contener una definición de @section o se mostrará un error. + - Estilos - Configura qué estilos pueden cambiar los editores + Constructor de consultas + Construir consulta + elementos devueltos, en - La configuración sólo se guardará si el json introducido es válido + Quiero + todo contenido + contenido de tipo "%0%" + desde + mi sitio web + donde + y - Permitir todos los controles de edición - Permitir todas las configuraciones de fila - - - Campo opcional - Texto opcional - MAYÚSCULA/minúscula - Elegir campo - Convertir a salto de línea - Reemplaza los saltos de línea con la etiqueta HTML &lt;br&gt; - Si, solamente la fecha - Cambiar formato a fecha - Codificar HTML - Se reemplazarán los caracteres especiales por su código HTML equivalente. - Será insertado después del valor del campo - Será insertado antes del valor del campo - Minúscula - Ninguno/ninguna - Insertar después del campo - Insertar antes del campo - Recursivo - Borrar los tags del párrafo - Borrará cualquier &lt;P&gt; al principio y al final del texto - Mayúscula - Codificar URL - Formateará los caracteres especiales de las URLs - Sólo será usado cuando el campo superior esté vacio - Este campo será usado unicamente si el campo primario está vacío - Si, con el tiempo. Separador: - - - Tareas asignadas a usted - asignadas a usted. Para acceder a la vista detallaa incluyendo comentarios, haga click sobre "Detalles" o sobre el nombre de la página. También puede descargar la página como XML directamente pulsando sobre el enlace "Descarga XML".
Para terminar la tarea de traducción, por favor dirijase a la vista de detalles y haga click sobre el botón de "Cerrar". ]]>
- cerrar tarea - Detalles de traducción - Descargar todas las tareas pendientes de traducción como archivo xml - Descargar xml - Descargar xml DTD - Campos - Incluir subpáginas - es + no es + antes + antes (incluyendo fecha seleccionada) + después + después (incluyendo fecha seleccionada) + igual a + no igual a + contiene + no contiene + mayor que + mayor o igual + menor que + menor o igual a + + Id + Nombre + Creado en + Última actualización + + ordenar por + ascendente + descendente + Guía rápida sobre las etiquetas de plantilla de Umbraco + Plantilla + + + Rich Text Editor + Image + Macro + Embed + Headline + Quote + Insertar control + Elije configuración + Añade más filas + Añadir contenido + Soltar contenido + Settings applied + + Contenido no permitido aquí + Contenido permitido aquí + + Pulse para insertar + Pulse para insertar imagen + Leyenda de imagen... + Escribe aqui... + + Plantillas de Grid + Las plantillas son el área de trabajo para el editor de grids, normalmente sólo necesitas una o dos plantillas diferentes + Añadir plantilla de grid + Ajusta la plantilla configurando la anchura de las columnas y añadiendo más secciones + Configuraciones de filas + Las filas son celdas predefinidas que se disponen horizontalmente + Añade una configuración de fila + Ajusta la fila configurando los anchos de cada celda y añadiendo más celdas + + Columnas + Número total de columnas en la plantilla del grid + + Configuración + Configura qué ajustes pueden cambiar los editores + + + Estilos + Configura qué estilos pueden cambiar los editores + + La configuración sólo se guardará si el json introducido es válido + + Permitir todos los controles de edición + Permitir todas las configuraciones de fila + Dejar en blanco o establece en 0 para ilimitada + + Artículos máximos + Establecer por defecto + Elegir extra + Elegir por defecto + son añadidos + + + + Composiciones + No has añadido nunguna pestaña + Añadir nueva pestaña + Añadir otra pestaña + Heredado de + Añadir propiedad + Etiqueta requerida + + Activar vista de lista + Configura la página para mostrar una lista de sus hijas que puedes ordenar y buscar, los hijas no se mostrarán en el árbol de contenido + + Platillas permitidas + Elije que plantillas se permite a los editores utilizar en contenido de este tipo + + Permitir como raiz + Permite a los editores crear contenido de este tipo en la raiz del árbol de contenido + Si - permitir contenido de este tipo en la raiz + + Tipos de nodos hijos permitidos + Permite contenido de los tipos permitidos ser creados debajo de este tipo de contenido + + Elegir nodo hijo + + Heredar pestañas y propiedades de un tipo de documento existente. Nuevas pestañas serán añadidas al tipo de documento actual o mezcladas si una pestaña con nombre idéntico ya existe. + Este tipo de contenido es usado en una composición, y por tanto no puede no puede ser compuesto. + No hay tipos de contenido disponibles para usar como composición. + + Editores disponibles + Reusar + Configuración de editor + + Configuración + + Si, borrar + + se movió debajo + se copió debajo + Selecciona la carpeta a mover + Selecciona la carpeta a copiar + en la estructura de árbol debajo + + Todos tipos de documentos + Todos los documentos + Todos los tipos de medio + + usar este tipo de documento lo borrará permanentemente, por favor confirma que quieres borrarlos también. + usar este tipo de media lo borrará permanentemente, por favor confirma que quieres borrarlos también. + usar este tipo de miembro lo borrará permanentemente, por favor confirma que quieres borrarlos también. + + y todos los documentos usando este tipo + y todos los medios usando este tipo + y todos los miembros usando este tipo + + al usar de este editor se actualizará con la nueva configuración + + Miembro puede editar + Mostrar en perfil de miembro + pestaña no tiene orden + + + + Construyendo modelos + esto puede llevar un rato, no te preocupes + Modelos generados + Los modelos no se pudieron generar + La generación de los modelos has fallado, ve la excepción en U log + + + Añadir campo de respaldo + Campo de respaldo + Añadir valor por defecto + Valor por defecto + Campo opcional + Texto opcional + MAYÚSCULA/minúscula + Elegir campo + Convertir a salto de línea + Sí, convertir salto de linea + Reemplaza los saltos de línea con la etiqueta HTML &lt;br&gt; + Campos personalizados + Si, solamente la fecha + Formato y codificación + Cambiar formato a fecha + Formatear el valor como una fecha o una fecha con hora , de acuerdo con la cultura activa + Codificar HTML + Se reemplazarán los caracteres especiales por su código HTML equivalente. + Será insertado después del valor del campo + Será insertado antes del valor del campo + Minúscula + Modificar salida + Ninguno/ninguna + Ejemplo de salida + Insertar después del campo + Insertar antes del campo + Recursivo + Sí, hacerlo recursivo + Separador + Campos estándar + Mayúscula + Codificar URL + Formateará los caracteres especiales de las URLs + Sólo será usado cuando el campo superior esté vacio + Este campo será usado unicamente si el campo primario está vacío + Si, con el tiempo. Separador: + + + Tareas asignadas a usted + asignadas a usted. Para acceder a la vista detallaa incluyendo comentarios, haga click sobre "Detalles" o sobre el nombre de la página. También puede descargar la página como XML directamente pulsando sobre el enlace "Descarga XML".
Para terminar la tarea de traducción, por favor dirijase a la vista de detalles y haga click sobre el botón de "Cerrar". ]]>
+ cerrar tarea + Detalles de traducción + Descargar todas las tareas pendientes de traducción como archivo xml + Descargar xml + Descargar xml DTD + Campos + Incluir subpáginas + + - Tarea para tradudir [%0%] por %1% - No se encontraron usuarios traductores. Por favor, crea un usuario traductor antes de empezar a mandar contenido para su traducción - Tareas creadas por ti - creadas por tí. Para ver una vista detallada incluyendo los comentarios, pulsa en "Detalles" o tan solo en el nombre de la página. También puedes descargar la página como XML directamente pulsando en el enlace "Descargar Xml". Para cerrar una tarea de traducción, por favor ve a la vista de Detalles y pulsa el botón de "Cerrar".]]> - La página '%0%' se ha mandado a traducción - Manda la página '%0%' a traducción - Asignada por - Tarea abierta - Total de palabras - Traducir a - Traducción hecha. - Puedes previsualizar las páginas que acabas de traducir, pulsando debajo. Si la página original existe, se mostrará una comparación de las 2 páginas. - La traducción ha fallado. El archivo xml es inválido - Opciones para traducir - Traductor - Subir traducción xml - - - Caché del navegador - Papelera de reciclaje - Paquetes creados - Tipos de datos - Diccionario - Paquetes instalados - Instalar skin - Instalar starter kit - Idiomas - Instalar paquete local - Macros - Tipos de medios - Miembros - Grupos de miembros - Roles - Tipos de miembros - Tipos de documento - Paquetes - Paquetes - Ficheros Python - Instalar desde repositorio - Instalar pasarela - Módulos pasarela - Ficheros de script - Scripts - Hojas de estilo - Plantillas - Archivos XSLT - - - Existe una nueva actualización - %0% esta listo, pulsa aquí para descargar - No hay conexión al servidor - Error al comprobar la actualización. Por favor revisa "trace-stack" para conseguir más información. - - - Administrador - Campo de categoria - Cambiar contraseña - Change Your Password - Confirm new password - Puede cambiar su contraseña para acceder al 'back office' de Umbraco rellenando el siguiente formulario y haciendo clic en el botón 'Cambiar contraseña' - Canal de contenido - Campo descriptivo - Deshabilitar usuario - Tipo de documento - Editor - Campo de citas - Idioma - Login - Nodo de comienzo en la libreria de medios - Secciones - Deshabilitar acceso a Umbraco - Contraseña - Reset password - Su contraseña ha sido cambiada - Por favor confirme su nueva contraseña - Introduzca su nueva contraseña - La nueva contraseña no puede estar vacía - Current password - Invalid current password - La nueva contraseña no coincide con la contraseña de confirmación. Por favor, vuela a intentarlo!' - La contraseña de confirmación no coincide con la nueva contraseña!' - Reemplazar los permisos de los nodos hijo - Estas modificando los permisos para las páginas: - Selecciona las páginas para modificar sus permisos - Buscar en todos los hijos - Nodo de comienzo en contenido - Nombre de usuario - Permisos de usuarios - Tipo de usuario - Tipos de usuarios - Redactor - Tu perfil - Tu historial reciente - La sesión caduca en - + Saludos de parte de el robot de Umbraco + ]]> + + Tarea para tradudir [%0%] por %1% + No se encontraron usuarios traductores. Por favor, crea un usuario traductor antes de empezar a mandar contenido para su traducción + Tareas creadas por ti + creadas por tí. Para ver una vista detallada incluyendo los comentarios, pulsa en "Detalles" o tan solo en el nombre de la página. También puedes descargar la página como XML directamente pulsando en el enlace "Descargar Xml". Para cerrar una tarea de traducción, por favor ve a la vista de Detalles y pulsa el botón de "Cerrar".]]> + La página '%0%' se ha mandado a traducción + Por favor, selecciona el idioma al que el contenido debería ser traducido + Manda la página '%0%' a traducción + Asignada por + Tarea abierta + Total de palabras + Traducir a + Traducción hecha. + Puedes previsualizar las páginas que acabas de traducir, pulsando debajo. Si la página original existe, se mostrará una comparación de las 2 páginas. + La traducción ha fallado. El archivo xml es inválido + Opciones para traducir + Traductor + Subir traducción xml + + + Contenido + Plantillas de Contenido + Media + Caché del navegador + Papelera de reciclaje + Paquetes creados + Tipos de datos + Diccionario + Paquetes instalados + Instalar skin + Instalar starter kit + Idiomas + Instalar paquete local + Macros + Tipos de medios + Miembros + Grupos de miembros + Roles + Tipos de miembros + Tipos de documento + Paquetes + Paquetes + Vistas Parciales + Vistas Parciales para Macros + Ficheros Python + Instalar desde repositorio + Instalar pasarela + Módulos pasarela + Ficheros de script + Scripts + Hojas de estilo + Plantillas + Archivos XSLT + Analíticas + Usuarios + + + Existe una nueva actualización + %0% esta listo, pulsa aquí para descargar + No hay conexión al servidor + Error al comprobar la actualización. Por favor revisa "trace-stack" para conseguir más información. + + + Acceso + Basado en los grupos asignados y los nodos iniciales, el usuario tiene acceso a los siguientes nodos. + Asignar acceso + Administrador + Campo de categoria + Cambiar contraseña + Cambiar foto + Nueva contraseña + no ha sido bloqueado + La contraseña no se ha cambiado + Confirma nueva contraseña + Puede cambiar su contraseña para acceder al 'back office' de Umbraco rellenando el siguiente formulario y haciendo clic en el botón 'Cambiar contraseña' + Canal de contenido + Crear otro usuario + Crear nuevos usuarios para darles acceso a Umbraco. Cuando un nuevo usuario es creado, una nueva contrasela será generada y la podrás compartir con el usuario. + Campo descriptivo + Deshabilitar usuario + Tipo de documento + Editor + Campo de citas + Intentos de acceso fallidos + Ir a perfil de usuario + Añadir grupos para asignar acceso y permisos + Invitar otro usuario + Invita nuevos usuarios para darles acceso a Umbraco. Un email de invitación será enviado al usuario con información sobre cómo acceder a Umbraco. + Idioma + Establecer el idioma que verás en menús y dialogos + Última fecha bloqueado + Último acceso + Última contraseña cambiada + Acceso + Nodo de comienzo en la libreria de medios + Limitar la librería de medios al siguiente nodo de inicio + Nodos de inicio para Medios + Limitar la librería de medios a los siguientes nodos de inicio + Secciones + Deshabilitar acceso a Umbraco + no se ha conectado aún + Contraseña antigüa + Contraseña + Reiniciar contraseña + Su contraseña ha sido cambiada + Por favor confirme su nueva contraseña + Introduzca su nueva contraseña + La nueva contraseña no puede estar vacía + Contraseña actual + Contraseña actual inválida + La nueva contraseña no coincide con la contraseña de confirmación. Por favor, vuela a intentarlo!' + La contraseña de confirmación no coincide con la nueva contraseña!' + Reemplazar los permisos de los nodos hijo + Estas modificando los permisos para las páginas: + Selecciona las páginas para modificar sus permisos + Eliminar photo + Permisos por defecto + Permisos granulares + Establecer permisos para nodos especificos + Perfil + Buscar en todos los hijos + Añadir secciones para dar aceso a usuarios + Seleccionar grupos de usuarios + Nodo de inicio no seleccionado + Nodos de inicio no seleccionado + Activo + Todos + Desactivado + Bloqueado + Invitado + Nodo de comienzo en contenido + Limitar el árbol de contenido a un nodo de inicio específico + Nodos de inicio de contenido + Limitar el árbol de contenido a unos nodos de inicio específicos + Nombre (A-Z) + Nombre (Z-A) + Más nuevo + Más antigüo + Último accesso + Última actualización en usuario + ha sido creado + Se ha creado el nuevo usuario con éxito. Para acceder a Umbraco usa la contraseña siguiente. + Administración de usuario + Nombre de usuario + Permisos de usuarios + Permisos de group de usuario + Grupo de usuario + Grupos ds usuario + ha sido invitado + Se ha enviado una invitación al nuevo usuario con detalles sobre cómo acceder a Umbraco. + ¡Hola y bienvenido a Umbraco!. En un minuto todo estará listo para empezar, sólo necesitamos que configures tu contraseña y una imagen para tu avatar. + Sube una foto para que otros usuarios te reconozcan más fácilmente. + Redactor + Traductor + Cambiar + Tu perfil + Tu historial reciente + La sesión caduca en + Invitar usuario + Crear usuario + Enviar invitatión + Volver a usuarios + Umbraco: Invitación + + + + + + + + + + + + +
+ + + + + +
+ +
+ +
+
+ + + + + + +
+
+
+ + + + +
+ + + + +
+

+ Hi %0%, +

+

+ Has sido invitado por %1% a Umbraco Administración. +

+

+ Mensaje de %1%: +
+ %2% +

+ + + + + + +
+ + + + + + +
+ + Pulsa este enlace para aceptar la invitación + +
+
+

Si no puedes pulsar el enlace, Copia y pega esta URL en tu navegador:

+ + + + +
+ + %3% + +
+

+
+
+


+
+
+ + ]]> +
+ + + + Validación + Validar como email + Validar como número + Validar como Url + ...or introduce tu propia validación + Campo obligatorio + Introduce una expresión regular + Necesitas añadir al menos + Sólo puedes tener + elementos + elementos seleccionados + Fecha no válida + No es un número + Email no válido + + + + El valor fue establecido en el valor recomendado: '%0%'. + El valor fue establecido a '%1%' para XPath '%2%' en fichero de configuración '%3%'. + Valor esperado '%1%' para '%2%' en fichero de configuración '%3%', pero se encontró '%0%'. + Se encontró un valor inesperado '%0%' para '%2%' en fichero de configuración '%3%'. + + + Errores personalizados están establecidos en '%0%'. + Errores personalizados están establecidos en '%0%'. Se recomienda configurar esto en '%1%' antes de publicar el sitio. + Errores personalizados establecidos con éxito a '%0%'. + + MacroErrors establecidos en '%0%'. + MacroErrors están establecidos en '%0%' lo que prevendrá que algunas o todas las página de tu sitio no carguen completamente si hay algún error en una macro. Rectifica esto estableciendo un valor de '%1%'. + MacroErrors están establecidos en '%0%'. + + + Intentar saltar Errores Personalizados de IIS está '%0%' y estás usando IIS versión '%1%'. + Intentar saltar Errores Personalizados de IIS está '%0%'. Se recomienda configurarlo como '%1%' para tu versión (%2%) de IIS. + Intentar saltar Errores Personalizados de IIS se configuró como con '%0%' éxito. + + + Archivo no existe: '%0%'. + '%0%' en archivo de configuración '%1%'.]]> + Hubo un error, revisa los logs para ver el error completo: %0%. + + Miembros - Total XML: %0%, Total: %1%, Total invalidos: %2% + Media - Total XML: %0%, Total: %1%, Total invalidos: %2% + Contenido - Total XML: %0%, Total publicados: %1%, Total invalidos: %2% + + El certificado de tu sitio es válido. + Error validando certificado: '%0%' + El ceriticado SSL de tu sitio ha caducado. + El ceriticado SSL de tu sitio caducará en %0% días. + Error pinging la URL %0% - '%1%' + Actualmente estás %0% viendo el sitio usando el esquema HTTPS. + El appSetting 'umbracoUseSSL' está configurado como 'false' en tu archivo web.config. Una vez que accedes al sitio usando HTTPS, debería ser configuraio como 'true'. + Ele appSetting 'umbracoUseSSL' está configurado como '%0%' en tu archivo your web.config, tus cookies son %1% marcadas como seguras. + No se pudo actualizar 'umbracoUseSSL' en tu archivo web.config. Error: %0% + + + Activar HTTPS + Configura umbracoSSL como true en los appSettings del archivo web.config. + El appSetting 'umbracoUseSSL' está ahora configurado como 'true' en tu archivo web.config, tus cookies se marcarán como seguras. + + Arreglar + No se pudo arreglar chequeo con un valor de comparación 'ShouldNotEqual'. + No se pudo arreglar chequeo con un valor de comparación 'ShouldEqual' con el valor introducido. + Valor para arreglar chequeo no introducido. + + Modo Debug en compilacion está desactivado. + Modo Debug en compilacion está activado. Se recomienda desactivarlo antes de publicar el sitio. + Modo Debug en compilación se ha desactivado correctamente. + + Modo Trace está desactivado. + Modo Trace está activado. Se recomienda desactivarlo antes de publicar el sitio. + Modo Trace se ha desactivado correctamente.. + + Todas las carpetas tienen los permisos correspondientes. + + %0%.]]> + %0%. Opcional.]]> + + Todos los archivos tienen los permisos correspondientes. + + %0%.: %0%.]]> + %0%. Opcional.]]> + + X-Frame-Options usado para controlar si un sitio puede ser IFRAMEd por otra fue encontrado.]]> + X-Frame-Options usado para controlar si un sitio puede ser IFRAMEd por otra no se ha encontrado.]]> + Establecer Header en Config + Adds a value to the httpProtocol/customHeaders section of web.config to prevent the site being IFRAMEd by other websites. + A setting to create a header preventing IFRAMEing of the site by other websites has been added to your web.config file. + Could not update web.config file. Error: %0% + + + %0%.]]> + No se ha encontrado ninguna cabecerá que revele información sobre la tecnología del sitio. + + No se encontró system.net/mailsettings en Web.config. + En la sección system.net/mailsettings section de web.config, el host no está configurado. + Los valores SMTP están configurados correctamente y el servicio opera con normalidad. + El servidor SMTP configurado con host '%0%' y puerto '%1%' no se pudo alcanzar. Por favor revisa que la configuración en la sección system.net/mailsettings del archivo Web.config es correcta. + + %0%.]]> + %0%.]]> +

Los resultados de los Chequeos de Salud de Umbraco programados para ejecutarse el %0% a las %1% son:

%2%]]>
+ Status de los Chequeos de Salud de Umbraco + + + Desactivar URL tracker + Activar URL tracker + URL Original + Redirigido a To + No se ha creado ninguna redirección + Cuando una página es renombrada o movida, una redirección a la nueva página es automáticamente creada. + Eliminar + ¿Estás seguro que quieres eliminar la redirección de '%0%' a '%1%'? + Redirección URL eliminada. + Error removing redirect URL. + ¿Seguro que quieres desactivar URL tracker? + URL tracker ha sido desactivado. + Error desactivando URL tracker, más información se puede encontrar en los logs. + URL tracker ha sido activado. + Error activando URL tracker, más información se puede encontrar en los logs. + + + No hay elementos de Diccionario para elegir + + + caracteres restantes +
diff --git a/WebCms/Umbraco/Config/Lang/fr.xml b/WebCms/Umbraco/Config/Lang/fr.xml index da33449..8215db7 100644 --- a/WebCms/Umbraco/Config/Lang/fr.xml +++ b/WebCms/Umbraco/Config/Lang/fr.xml @@ -2,21 +2,24 @@ The Umbraco community - http://our.umbraco.org/documentation/Extending-Umbraco/Language-Files + https://our.umbraco.com/documentation/Extending-Umbraco/Language-Files - Gérer les noms d'hôtes + Culture et noms d'hôte Informations d'audit Parcourir Changer le type de document Copier Créer + Exporter + Créer un groupe Créer un package Supprimer Désactiver Vider la corbeille - Exporter un type - Importer un type + Activer + Exporter le type de document + Importer un type de document Importer un package Editer dans Canvas Déconnexion @@ -27,17 +30,50 @@ Dépublier Rafraîchir Republier le site tout entier + Renommer Récupérer + Spécifiez les permissions pour la page %0% + Choisissez où déplacer + dans l'arborescence ci-dessous Permissions Version antérieure Envoyer pour publication Envoyer pour traduction + Spécifier le groupe Trier - Envoyer pour publication Traduire Mettre à jour - Valeur par défaut + Spécifier les permissions + Débloquer + Créer un modèle de contenu + Envoyer à nouveau l'invitation + + Contenu + Administration + Structure + Autre + + + Permettre d'attribuer la culture et des noms d'hôte + Permettre d'accéder au journal d'historique d'un noeud + Permettre d'accéder à un noeud + Permettre de modifier le type de document d'un noeud + Permettre de copier un noeud + Permettre de créer des noeuds + Permettre de supprimer des noeuds + Permettre de déplacer un noeud + Permettre de définir et modifier l'accès public à un noeud + Permettre de publier un noeud + Permettre de modifier les permissions pour un noeud + Permettre de revenir à une situation antérieure + Permettre d'envoyer un noeud pour approbation avant publication + Permettre d'envoyer un noeud à la traduction + Permettre de modifier l'ordonnancement des noeuds + Permettre de traduire un noeud + Permettre de sauvegarder un noeud + Permettre la création d'un Modèle de Contenu + Permission refusée. Ajouter un nouveau domaine @@ -45,25 +81,26 @@ Noeud invalide. Domaine invalide. Domaine déjà assigné. - Domaine Langue + Domaine Nouveau domaine '%0%' créé Domaine '%0%' supprimé Domaine '%0%' déjà assigné -
Les domaines contenant un chemin d'un niveau sont autorisés, ex : "example.com/en". Pour autant, cela - devrait être évité. Utilisez plutôt la gestion des noms d'hôte.]]>
Domaine '%0%' mis à jour Editer les domaines actuels + +
Les domaines contenant un chemin d'un niveau sont autorisés, ex : "example.com/en". Pour autant, cela + devrait être évité. Utilisez plutôt la gestion de la culture et des noms d'hôte.]]> +
Hériter Culture - ou hériter de la culture des noeuds parents. S'appliquera aussi
- au noeud courant, à moins qu'un domaine ci-dessous soit aussi d'application.]]>
+ + ou hériter de la culture des noeuds parents. S'appliquera aussi
+ au noeud courant, à moins qu'un domaine ci-dessous soit aussi d'application.]]> +
Domaines - - Aperçu pour - Vider la sélection Choisir @@ -85,11 +122,12 @@ Liste numérique Insérer une macro Insérer une image - Retourner à la liste Editer les relations + Retourner à la liste Sauver Sauver et publier - Sauver et envoyer pour approbation + Sauver et planifier + Sauver et envoyer pour approbation Sauver la mise en page de la liste Prévisualiser La prévisualisation est désactivée car aucun modèle n'a été assigné. @@ -97,7 +135,30 @@ Afficher les styles Insérer un tableau Générer les modèles - Sauver et générer les modèles + Sauver et générer les modèles + Défaire + Refaire + + + Aperçu pour + Suppressions de contenu réalisées par utilisateur + Dépublications réalisées par utilisateur + Sauvegardes et publications réalisées par utilisateur + Sauvegardes de contenu réalisées par utilisateur + Déplacements de contenu réalisés par utilisateur + Copies de contenu réalisées par utilisateur + Récupérations de contenu réalisées par utilisateur + Envois de contenu pour publication réalisés par utilisateur + Envois de contenu pour traduction réalisés par utilisateur + Copie + Publication + Déplacement + Sauvegarde + Suppression + Dépublication + Récupération + Envoi pour publication + Envoi pour traduction Pour changer le type de document du contenu séléctionné, faites d'abord un choix dans la liste des types valides à cet endroit. @@ -139,30 +200,38 @@ Dernière publication Il n'y a aucun élément à afficher Il n'y a aucun élément à afficher dans cette liste. + Aucun contenu n'a encore été ajouté + Aucun membre n'a encore été ajouté Type de Média Lien vers des média(s) Groupe de membres Rôle Type de membre + Aucune modification n'a été faite Aucune date choisie Titre de la page + Ce média n'a pas de lien Propriétés Ce document est publié mais n'est pas visible car son parent '%0%' n'est pas publié Oups : ce document est publié mais n'est pas présent dans le cache (erreur interne Umbraco) Oups: impossible d'obtenir cet url (erreur interne - voir fichier log) Oups: ce document est publié mais son url entrerait en collision avec le contenu %0% Publier + Publié + Publié (changements en cours) Statut de publication Publié le Dépublié le Supprimer la date - Ordre de tri mis à jour + Défininir la date + Ordre de tri mis à jour Pour trier les noeuds, faites-les simplement glisser à l'aide de la souris ou cliquez sur les entêtes de colonne. Vous pouvez séléctionner plusieurs noeuds en gardant la touche "shift" ou "ctrl" enfoncée pendant votre séléction. Statistiques Titre (optionnel) Texte alternatif (optionnel) Type Dépublier + Dépublié Dernière édition Date/heure à laquelle ce document a été édité Supprimer le(s) fichier(s) @@ -172,7 +241,23 @@ Eléments enfants Cible Ceci se traduit par l'heure suivante sur le serveur : - Qu'est-ce que cela signifie?]]> + Qu'est-ce que cela signifie?]]> + Etes-vous certain(e) de vouloir supprimer cet éléménent? + La propriété %0% utilise l'éditeur %1% qui n'est pas supporté par Nested Content. + Ajouter un autre champ texte + Enlever ce champ texte + Racine du contenu + Cette valeur est masquée. Si vous avez besoin de pouvoir accéder à cette valeur, veuillez prendre contact avec l'administrateur du site web. + Cette valeur est masquée. + + + Créer un nouveau Modèle de Contenu à partir de '%0%' + Vide + Sélectionner un Modèle de Contenu + Modèle de Contenu créé + Un modèle de Contenu a été créé à partir de '%0%' + Un autre Modèle de Contenu existe déjà avec le même nom + Un Modèle de Contenu est du contenu pré-défini qu'un éditeur peut sélectionner et utiliser comme base pour la création de nouveau contenu Cliquez pour télécharger @@ -180,9 +265,10 @@ Lien vers le média ou cliquez ici pour choisir un fichier Les seuls types de fichiers autorisés sont - Impossible de télécharger ce fichier, il n'a pas un type de fichier autorisé. + Ce fichier ne peut pas ête chargé, il n'est pas d'un type de fichier autorisé. La taille maximum de fichier est - + Racine du média + Créer un nouveau membre Tous les membres @@ -190,12 +276,20 @@ Où voulez-vous créer le nouveau %0% Créer un élément sous + Sélectionnez le type de document pour lequel vous souhaitez créer un modèle de contenu Choisissez un type et un titre "Types de documents".]]> "Types de médias".]]> Type de document sans modèle Nouveau répertoire Nouveau type de données + Nouveau fichier javascript + Nouvelle vue partielle vide + Nouvelle macro pour vue partielle + Nouvelle vue partielle à partir d'un snippet + Nouvelle macro pour vue partielle vide + Nouvelle macro pour vue partielle à partir d'un snippet + Nouvelle macro pour vue partielle (sans macro) Parcourir votre site @@ -211,7 +305,8 @@ Invalider les changements Vous avez des changements en cours Etes-vous certain(e) de vouloir quitter cette page? - vous avez des changements en cours - + La dépublication va supprimer du site cette page ainsi que tous ses descendants. + Terminé @@ -241,6 +336,8 @@ %0% éléments sur %1% copiés + Titre du lien + Lien Nom Gérer les noms d'hôtes Fermer cette fenêtre @@ -268,7 +365,10 @@ Cette macro ne contient aucune propriété éditable Coller Editer les permissions pour - Les éléments dans la corbeille sont en cours de suppression. Ne fermez pas cette fenêtre avant que cette opération soit terminée. + Définir les permissions pour + Définir les permissions pour %0% pour le groupe d'utilisateurs %1% + Sélectionnez les groupes d'utilisateurs pour lesquels vous souhaitez définir les permissions + Les éléments dans la corbeille sont en cours de suppression. Ne fermez pas cette fenêtre avant que cette opération soit terminée. La corbeille est maintenant vide Les éléments supprimés de la corbeille seront supprimés définitivement regexlib.com rencontre actuellement des problèmes sur lesquels nous n'avons aucun contrôle. Nous sommes sincèrement désolés pour le désagrément.]]> @@ -280,28 +380,41 @@ Le cache du site va être mis à jour. Tous les contenus publiés seront mis à jour. Et tous les contenus dépubliés resteront invisibles. Nombre de colonnes Nombre de lignes - Définir un placeholder ID. En mettant un ID sur votre placeholder, vous pouvez injecter du contenu à cet endroit depuis les modèles enfants, - en faisant référence à cet ID au sein d'un élément <asp:content />.]]> - Séléctionnez un placeholder id dans la liste ci-dessous. Vous pouvez seulement - choisir un ID se trouvant dans le parent du modèle actuel.]]> + + Définir un placeholder ID. En mettant un ID sur votre placeholder, vous pouvez injecter du contenu à cet endroit depuis les modèles enfants, + en faisant référence à cet ID au sein d'un élément <asp:content />.]]> + + + Séléctionnez un placeholder ID dans la liste ci-dessous. Vous pouvez seulement + choisir un ID se trouvant dans le parent du modèle actuel.]]> + Cliquez sur l'image pour la voir en taille réelle Sélectionner un élément Voir l'élément de cache Créer un répertoire... Lier à l'original + Inclure les descendants La communauté la plus amicale Lier à la page Ouvre le document lié dans une nouvelle fenêtre ou un nouvel onglet Lier à un media + Lier à un fichier + Sélectionner le noeud de base du contenu Sélectionner le media Sélectionner l'icône Sélectionner l'élément Sélectionner le lien Sélectionner la macro Sélectionner le contenu + Sélectionner le noeud de base des media Sélectionner le membre Sélectionner le groupe de membres + Sélectionner le noeud + Sélectionner les sections + Sélectionner les utilisateurs + Aucune icone n'a été trouvée Il n'y a pas de paramètres pour cette macro + Il n'y a pas de macro disponible à insérer Fournisseurs externes d'identification Détails de l'exception Trace d'exécution @@ -310,12 +423,22 @@ Enlevez votre compte Sélectionner un éditeur + Selectionner un snippet - + %0%' ci-dessous.
Vous pouvez ajouter d'autres langues depuis le menu ci-dessous "Langues". - ]]>
+ ]]> + Nom de Culture + Modifiez la clé de l'élément de dictionaire. + + + + Aperçu du dictionaire Votre nom d'utilisateur @@ -323,14 +446,17 @@ Confirmation de votre mot de passe Nommer %0%... Entrez un nom... + Entrez un email... + Entrez un nom d'utilisateur... Libellé... Entrez une description... Rechercher... Filtrer... Ajouter des tags (appuyer sur enter entre chaque tag)... Entrez votre email + Entrez un message... + Votre nom d'utilisateur est généralement votre adresse email - Autoriser à la racine Seuls les Types de Contenu qui ont ceci coché peuvent être créés au niveau racine des arborescences de contenu et de media @@ -349,6 +475,11 @@ Créer une liste personnalisée Supprimer la liste personnalisée + + Renommé + Entrez un nouveau nom de répertoire ici + %0% a été renommé en %1% + Ajouter une valeur de base Type de donnée en base de donées @@ -361,6 +492,13 @@ CSS associées Afficher le libellé Largeur et hauteur + Tous les types de propriétés & les données de propriétés + utilisant ce type de données seront supprimés définitivement, veuillez confirmer que vous voulez également les supprimer + Oui, supprimer + et tous les types de propriétés & les données de propriétés utilisant ce type de données + Sélectionnez le répertoire où déplacer + dans l'arborescence ci-dessous + a été déplacé sous Vos données ont été sauvegardées, mais avant de pouvoir publier votre page, il y a des erreurs que vous devez corriger : @@ -402,6 +540,7 @@ Il y a une erreur de configuration du type de données utilisé pour cette propriété, veuillez vérifier le type de données. + Options A propos Action Actions @@ -419,6 +558,7 @@ Fermer la fenêtre Commenter Confirmer + Conserver Conserver les proportions Continuer Copier @@ -430,6 +570,7 @@ Supprimé Suppression... Design + Dictionnaire Dimensions Bas Télécharger @@ -439,18 +580,26 @@ Email Erreur Trouver + Premier + Général + Groupes Hauteur Aide + Cacher + Historique Icône Importer + Info Marge intérieure Insérer Installer Non valide Justifier - Libellé + Libellé Langue + Dernier Mise en page + Liens En cours de chargement Bloqué Connexion @@ -458,6 +607,7 @@ Déconnexion Macro Obligatoire + Message Déplacer Plus Nom @@ -465,9 +615,12 @@ Suivant Non de + Inactif OK Ouvrir + Actif ou + Trier par Mot de passe Chemin Placeholder ID @@ -476,20 +629,25 @@ Propriétés Email de réception des données de formulaire Corbeille - Votre corbeille est vide + Votre corbeille est vide Restant + Enlever Renommer Renouveller Requis + Retrouver Réessayer Permissions + Publication Programmée Rechercher Désolé, nous ne pouvons pas trouver ce que vous recherchez + Aucun élément n'a été ajouté Serveur Montrer Afficher la page à l'envoi Taille Trier + Statut Envoyer Type Rechercher... @@ -516,16 +674,32 @@ Sauvegarde... actuel Intégrer + Retrouver sélectionné + Noir Vert Jaune Orange Bleu + Bleu-gris + Gris + Brun + Bleu Clair + Cyan + Vert Clair + Limon + Ambre + Orange Foncé Rouge + Rose + Mauve + Mauve Foncé + Indigo + Ajouter un onglet Ajouter une propriété @@ -543,6 +717,16 @@ Passer à la vue en liste Basculer vers l'autorisation comme racine + + Commenter/Décommenter les lignes + Supprimer la ligne + Copier les lignes vers le haut + Copier les lignes vers le bas + Déplacer les lignes vers le haut + Déplacer les lignes vers le bas + + Général + Editeur Couleur de fond @@ -559,81 +743,99 @@ Impossible de sauvegarder le fichier web.config. Veuillez modifier la "connection string" manuellement. Votre base de données a été détectée et est identifiée comme étant Configuration de la base de données - + installer pour installer la base de données Umbraco %0% - ]]> + ]]> + Suivant pour poursuivre.]]> - Base de données non trouvée ! Veuillez vérifier les informations de la "connection string" dans le fichier web.config.

+ + Base de données non trouvée ! Veuillez vérifier les informations de la "connection string" dans le fichier web.config.

Pour poursuivre, veuillez éditer le fichier "web.config" (avec Visual Studio ou votre éditeur de texte favori), scroller jusqu'en bas, ajouter le "connection string" pour votre base de données dans la ligne avec la clé "umbracoDbDSN" et sauvegarder le fichier.

Cliquez sur le bouton Réessayer lorsque cela est fait. -
- Plus d'informations sur l'édition du fichier web.config ici.

]]>
- +
+ Plus d'informations sur l'édition du fichier web.config ici.

]]> +
+ + Veuillez contacter votre fournisseur de services internet si nécessaire. - Si vous installez Umbraco sur un ordinateur ou un serveur local, vous aurez peut-être besoin de consulter votre administrateur système.]]> - + + + Appuyez sur le bouton Upgrader pour mettre à jour votre base de données vers Umbraco %0%

N'ayez pas d'inquiétude : aucun contenu ne sera supprimé et tout continuera à fonctionner parfaitement par après !

- ]]>
+ ]]> +
- Appuyez sur Suivant pour + Appuyez sur Suivant pour poursuivre. ]]> - + Suivant pour poursuivre la configuration]]> Le mot de passe par défaut doit être modifié !]]> L'utilisateur par défaut a été désactivé ou n'a pas accès à Umbraco!

Aucune autre action n'est requise. Cliquez sur Suivant pour poursuivre.]]> Le mot de passe par défaut a été modifié avec succès depuis l'installation!

Aucune autre action n'est requise. Cliquez sur Suivant pour poursuivre.]]> Le mot de passe a été modifié ! - - ('admin') et le mot de passe ('default'). Il est important que ce mot de passe soit modifié en quelque-chose de sécurisé et unique. - ]]> Pour bien commencer, regardez nos vidéos d'introduction En cliquant sur le bouton "Suivant" (ou en modifiant umbracoConfigurationStatus dans le fichier web.config), vous acceptez la licence de ce logiciel telle que spécifiée dans le champ ci-dessous. Veuillez noter que cette distribution Umbraco consiste en deux licences différentes, la licence open source MIT pour le framework et la licence Umbraco freeware qui couvre l'UI. Pas encore installé. Fichiers et dossiers concernés Plus d'informations sur la configuration des permissions Vous devez donner à ASP.NET les droits de modification sur les fichiers/dossiers suivants - Vos configurations de permissions sont presque parfaites !

- Vous pouvez faire fonctionner Umbraco sans problèmes, mais vous ne serez pas en mesure d'installer des packages, ce qui est hautement recommandé pour tirer pleinement profit d'Umbraco.]]>
+ + Vos configurations de permissions sont presque parfaites !

+ Vous pouvez faire fonctionner Umbraco sans problèmes, mais vous ne serez pas en mesure d'installer des packages, ce qui est hautement recommandé pour tirer pleinement profit d'Umbraco.]]> +
Comment résoudre Cliquez ici pour lire la version texte tutoriel vidéo sur la définition des permissions des répertoires pour Umbraco, ou lisez la version texte.]]> - Vos configurations de permissions pourraient poser problème ! + + Vos configurations de permissions pourraient poser problème !

- Vous pouvez faire fonctionner Umbraco sans problèmes, mais vous ne serez pas en mesure d'installer des packages, ce qui est hautement recommandé pour tirer pleinement profit d'Umbraco.]]>
- Vos configurations de permissions ne sont pas prêtes pour Umbraco ! + Vous pouvez faire fonctionner Umbraco sans problèmes, mais vous ne serez pas en mesure d'installer des packages, ce qui est hautement recommandé pour tirer pleinement profit d'Umbraco.]]> + + + Vos configurations de permissions ne sont pas prêtes pour Umbraco !

- Pour faire fonctionner Umbraco, vous aurez besoin de mettre à jour les permissions sur les fichiers/dossiers.]]>
- Vos configurations de permissions sont parfaites !

- Vous êtes prêt(e) à faire fonctionner Umbraco et à installer des packages !]]>
+ Pour faire fonctionner Umbraco, vous aurez besoin de mettre à jour les permissions sur les fichiers/dossiers.]]> +
+ + Vos configurations de permissions sont parfaites !

+ Vous êtes prêt(e) à faire fonctionner Umbraco et à installer des packages !]]> +
Résoudre un problème sur un dossier Suivez ce lien pour plus d'informations sur les problèmes avec ASP.NET et la création de dossiers Définir les permissions de dossier - + + ]]> + Je veux démarrer "from scratch" - Apprenez comment) Vous pouvez toujours choisir d'installer Runway plus tard. Pour cela, allez dans la section "Développeur" et sélectionnez "Packages". - ]]> + ]]> + Vous venez de mettre en place une plateforme Umbraco toute nette. Que voulez-vous faire ensuite ? Runway est installé - + Voici la liste des modules recommandés, cochez ceux que vous souhaitez installer, ou regardez la liste complète des modules - ]]> + ]]> + Recommandé uniquement pour les utilisateurs expérimentés Je veux commencer avec un site simple - "Runway" est un site simple qui fournit des types de documents et des modèles de base. L'installateur peut mettre en place Runway automatiquement pour vous, mais vous pouvez facilement l'éditer, l'enrichir, ou le supprimer par la suite. Il n'est pas nécessaire, et vous pouvez parfaitement vous en passer pour utiliser Umbraco. Cela étant dit, @@ -644,7 +846,8 @@ Inclus avec Runway : Home page, Getting Started page, Installing Modules page.
Modules optionnels : Top Navigation, Sitemap, Contact, Gallery. - ]]>
+ ]]> + Qu'est-ce que Runway Etape 1/5 : Accepter la licence Etape 2/5 : Configuration de la base de données @@ -652,24 +855,36 @@ Etape 4/5 : Sécurité Umbraco Etape 5/5 : Umbraco est prêt Merci d'avoir choisi Umbraco - Parcourir votre nouveau site -Vous avez installé Runway, alors pourquoi ne pas jeter un oeil au look de votre nouveau site ?]]> - Aide et informations complémentaires -Obtenez de l'aide de notre "award winning" communauté, parcourez la documentation ou regardez quelques vidéos gratuites sur la manière de construire un site simple, d'utiliser les packages ainsi qu'un guide rapide sur la terminologie Umbraco]]> + + Parcourir votre nouveau site +Vous avez installé Runway, alors pourquoi ne pas jeter un oeil au look de votre nouveau site ?]]> + + + Aide et informations complémentaires +Obtenez de l'aide de notre communauté "award winning", parcourez la documentation ou regardez quelques vidéos gratuites sur la manière de construire un site simple, d'utiliser les packages ainsi qu'un guide rapide sur la terminologie Umbraco]]> + Umbraco %0% est installé et prêt à être utilisé - fichier /web.config et mettre à jour le paramètre AppSetting umbracoConfigurationStatus situé en bas à la valeur '%0%'.]]> - démarrer instantanément en cliquant sur le bouton "Lancer Umbraco" ci-dessous.
-Si vous débutez avec Umbraco, vous pouvez trouver une foule de ressources dans nos pages "Getting Started".]]>
- Lancer Umbraco -Pour gérer votre site, ouvrez simplement le backoffice Umbraco et commencez à ajouter du contenu, à mettre à jour les modèles d'affichage et feuilles de styles ou à ajouter de nouvelles fonctionnalités]]> + + fichier /web.config et mettre à jour le paramètre AppSetting umbracoConfigurationStatus situé en bas à la valeur '%0%'.]]> + + + démarrer instantanément en cliquant sur le bouton "Lancer Umbraco" ci-dessous.
+Si vous débutez avec Umbraco, vous pouvez trouver une foule de ressources dans nos pages "Getting Started".]]> +
+ + Lancer Umbraco +Pour gérer votre site, ouvrez simplement le backoffice Umbraco et commencez à ajouter du contenu, à mettre à jour les modèles d'affichage et feuilles de styles ou à ajouter de nouvelles fonctionnalités]]> + La connexion à la base de données a échoué. Umbraco Version 3 Umbraco Version 4 Regarder - Umbraco %0%, qu'il s'agisse d'une nouvelle installation ou d'une mise à jour à partir de la version 3.0 + + Umbraco %0%, qu'il s'agisse d'une nouvelle installation ou d'une mise à jour à partir de la version 3.0

- Appuyez sur "suivant" pour commencer l'assistant.]]>
+ Appuyez sur "suivant" pour commencer l'assistant.]]> +
Code de la Culture @@ -680,13 +895,13 @@ Pour gérer votre site, ouvrez simplement le backoffice Umbraco et commencez à Renouvellez votre session maintenant pour sauvegarder votre travail - Joyeux dimanche - Joyeux lundi - Joyeux mardi - Joyeux mercredi + Joyeux dimanche détonnant + Joyeux lundi lumineux + Joyeux mardi magique + Joyeux mercredi merveilleux Joyeux jeudi Joyeux vendredi - Joyeux samedi + Joyeux chamedi Connectez-vous ci-dessous Identifiez-vous avec La session a expiré @@ -694,13 +909,95 @@ Pour gérer votre site, ouvrez simplement le backoffice Umbraco et commencez à Mot de passe oublié? Un email contenant un lien pour ré-initialiser votre mot de passe sera envoyé à l'adresse spécifiée Un email contenant les instructions de ré-initialisation de votre mot de passe sera envoyée à l'adresse spécifiée si elle correspond à nos informations. + Montrer le mot de passe + Cacher le mot de passe Revenir au formulaire de connexion Veuillez fournir un nouveau mot de passe Votre mot de passe a été mis à jour Le lien sur lequel vous avez cliqué est non valide ou a expiré. Umbraco: Ré-initialiser le mot de passe - Votre nom d'utilisateur pour vous connecter au back-office Umbraco est : %0%.

Cliquez ici pour ré-initialiser votre mot de passe, ou recopiez cet URL dans votre navigateur :

%1%

]]> + + + + + + + + + + + +
+ + + + + +
+ +
+ +
+
+ + + + + + +
+
+
+ + + + +
+ + + + +
+

+ Une réinitialisation de votre mot de passe a été demandée +

+

+ Votre nom d'utilisateur pour vous connecter au back-office Umbraco est : %0% +

+

+ + + + + + +
+ + Cliquez sur ce lien pour réinitialiser votre mot de passe + +
+

+

Si vous ne pouvez pas cliquer sur le lien, recopiez cet URL dans votre navigateur :

+ + + + +
+ + %1% + +
+

+
+
+


+
+
+ + + ]]>
@@ -724,7 +1021,8 @@ Pour gérer votre site, ouvrez simplement le backoffice Umbraco et commencez à Editez vos notifications pour %0% - + + ]]> + - Hello %0%

- -

Ceci est un email automatique pour vous informer que la tâche '%1%' - a été executée sur la page '%2%' - par l'utilisateur '%3%' -

- -

-

Résumé de la mise à jour :

- - %6% + + + + + + + +
+ + + +
+ + + + + +
+ +
+ +
+
+ + + + + +
+
+
+ + + + +
+ + + + +
+

+ Salut %0%, +

+

+ Ceci est un email automatique pour vous informer que la tâche '%1%' a été exécutée sur la page '%2%' par l'utilisateur '%3%' +

+ + + + + + +
+ +
+ MODIFIER
+
+

+

Résumé de la mise à jour :

+ + %6% +
+

+

+ Bonne journée !

+ Avec les salutations du Robot Umbraco +

+
+
+


+
+
-

- - - -

Bonne journée !

- Avec les salutations du Robot Umbraco -

]]>
+ + + ]]> + La notification [%0%] à propos de %1% a été executée sur %2% Notifications - + et localisez le package. Les packages Umbraco ont généralement une extension ".umb" ou ".zip". - ]]> + ]]> + + Déposez pour uploader + ou cliquez ici pour choisir les fichiers + Uploader un package + Installez un package local en le sélectionnant sur votre ordinateur. Installez uniquement des packages de sources fiables que vous connaissez + Uploader un autre package + Annuler et uploader un autre package + Licence + J'accepte + les conditions d'utilisation + Installer le package + Terminer + Packages installés + Vous n'avez aucun package installé + 'Packages' en haut à droite de votre écran]]> + Chercher des packages + Résultats pour + Nous n'avons rien pu trouver pour + Veuillez essayer de chercher un autre package ou naviguez à travers les catégories + Populaires + Nouvelles releases + a + points de karma + Information + Propriétaire + Contributeurs + Créé + Version actuelle + version .NET + Téléchargements + Coups de coeur + Compatibilité + Ce package est compatible avec les versions suivantes de Umbraco, selon les rapports des membres de la communauté. Une compatibilité complète ne peut pas être garantie pour les versions rapportées sous 100% + Sources externes Auteur Démo Documentation Meta data du package Nom du package Le package ne contient aucun élément -
- Vous pouvez supprimer tranquillement ce package de votre installation en cliquant sur "Désinstaller le package" ci-dessous.]]>
+ +
+ Vous pouvez supprimer tranquillement ce package de votre installation en cliquant sur "Désinstaller le package" ci-dessous.]]> +
Aucune mise à jour disponible Options du package Package readme @@ -789,9 +1177,11 @@ Pour gérer votre site, ouvrez simplement le backoffice Umbraco et commencez à Le package a été désinstallé Le package a été désinstallé avec succès Désinstaller le package - + + Remarque : tous les documents, media etc. dépendant des éléments que vous supprimez vont cesser de fonctionner, ce qui peut provoquer une instabilité du système, - désinstallez donc avec prudence. En cas de doute, contactez l'auteur du package.]]> + désinstallez donc avec prudence. En cas de doute, contactez l'auteur du package.]]> + Télécharger la mise à jour depuis le repository Mettre à jour le package Instructions de mise à jour @@ -807,6 +1197,8 @@ Pour gérer votre site, ouvrez simplement le backoffice Umbraco et commencez à Installation... Redémarrage, veuillez patienter... Terminé, votre navigateur va être rafraîchi, veuillez patienter... + Veuillez cliquer sur terminer pour compléter l'installation et recharger la page. + Package en cours de chargement... Coller en conservant le formatage (non recommandé) @@ -817,7 +1209,7 @@ Pour gérer votre site, ouvrez simplement le backoffice Umbraco et commencez à Protection basée sur les rôles via les groupes de membres Umbraco.]]> - l'authentification basée sur les rôles.]]> + Vous devez créer un groupe avant de pouvoir utiliser l'authentification basée sur les rôles Page d'erreur Utilisé pour les personnes connectées, mais qui n'ont pas accès Choisissez comment restreindre l'accès à cette page @@ -838,32 +1230,51 @@ Pour gérer votre site, ouvrez simplement le backoffice Umbraco et commencez à %0% n'a pas pu être publié car cet élément est programmé pour être publié bientôt. ]]> - + - + + + - + + + - + + + + ]]> + Inclure les pages enfant non publiées Publication en cours - veuillez patienter... %0% pages sur %1% ont été publiées... %0% a été publié %0% et ses pages enfants ont été publiées Publier %0% et toutes ses pages enfant - Publier pour publier %0% et la rendre ainsi accessible publiquement.

+ + Publier pour publier %0% et la rendre ainsi accessible publiquement.

Vous pouvez publier cette page et toutes ses sous-pages en cochant Inclure les pages enfant non pubiées ci-dessous. - ]]>
+ ]]> +
Vous n'avez configuré aucune couleur approuvée - + + Vous avez choisi un élément de contenu actuellement supprimé ou dans la corbeille + Vous avez choisi des éléments de contenu actuellement supprimés ou dans la corbeille + + + Vous avez choisi un élément media actuellement supprimé ou dans la corbeille + Vous avez choisi des éléments media actuellement supprimés ou dans la corbeille + Elément supprimé + + introduire un lien externe choisir une page interne Légende @@ -874,6 +1285,10 @@ Pour gérer votre site, ouvrez simplement le backoffice Umbraco et commencez à Réinitialiser + Définir le recadrage + Donnez un alias au recadrage ainsi que sa largeur et sa hauteur par défaut + Sauvegarder le recadrage + Ajouter un nouveau recadrage Version actuelle @@ -936,12 +1351,13 @@ Pour gérer votre site, ouvrez simplement le backoffice Umbraco et commencez à Date de création Tri achevé. Faites glisser les différents éléments vers le haut ou vers le bas pour définir la manière dont ils doivent être organisés. Ou cliquez sur les entêtes de colonnes pour trier la collection complète d'éléments -
Ne fermez pas cette fenêtre durant le tri.]]>
+ Validation Les erreurs de validation doivent être corrigées avant de pouvoir sauvegarder l'élément Echec + Sauvegardé Permissions utilisateur insuffisantes, l'opération n'a pas pu être complétée Annulation L'opération a été annulée par une extension tierce @@ -975,6 +1391,7 @@ Pour gérer votre site, ouvrez simplement le backoffice Umbraco et commencez à Erreur lors de la sauvegarde de l'utilisateur (consultez les logs) Utilisateur sauvegardé Type d'utilisateur sauvegardé + Groupe d'utilisateurs sauvegardé Fichier non sauvegardé Le fichier n'a pas pu être sauvegardé. Vérifiez les permissions de fichier. Fichier sauvegardé @@ -1000,11 +1417,34 @@ Pour gérer votre site, ouvrez simplement le backoffice Umbraco et commencez à Vue partielle sauvegardée sans erreurs ! Vue partielle non sauvegardée Une erreur est survenue lors de la sauvegarde du fichier. + Permissions sauvegardées pour Vue script sauvegardée Vue script sauvegardée sans erreur ! Vue script non sauvegardée Une erreur est survenue lors de la sauvegarde du fichier. Une erreur est survenue lors de la sauvegarde du fichier. + %0% groupes d'utilisateurs supprimés + %0% a été supprimé + %0% utilisateurs activés + Une erreur est survenue lors de l'activation des utilisateurs + %0% utilisateurs désactivés + Une erreur est survenue lors de la désactivation des utilisateurs + %0% est à présent activé + Une erreur est survenue lors de l'activation de l'utilisateur + %0% est à présent désactivé + Une erreur est survenue lors de la désactivation de l'utilisateur + Les groupes d'utilisateurs ont été définis + %0% groupes d'utilisateurs supprimés + %0% a été supprimé + %0% utilisateurs débloqués + Une erreur est survenue lors du débloquage des utilisateurs + %0% est à présent débloqué + Une erreur est survenue lors du débloquage de l'utilisateur + Le membre a été exporté vers le fichier + Une erreur est survenue lors de l'export du membre + L'utilisateur %0% a été supprimé + Inviter l'utilisateur + L'invitation a été envoyée à nouveau à %0% Utilise la syntaxe CSS. Ex : h1, .redHeader, .blueTex @@ -1014,18 +1454,120 @@ Pour gérer votre site, ouvrez simplement le backoffice Umbraco et commencez à Prévisualiser Styles + Editer le modèle + + Sections Insérer une zone de contenu Insérer un placeholder de zone de contenu - Insérer un élément de dictionnaire - Insérer une Macro - Insérer un champ de la page Umbraco + + Insérer + Choisissez l'élément à insérer dans votre modèle + + Elément de dictionnaire + Un élément de dictionnaire est un espace pour un morceau de texte traduisible, ce qui facilite la création de designs pour des sites web multilangues. + + Macro + + Une Macro est un composant configurable, ce qui est génial pour les parties réutilisables de votre + design où vous devez pouvoir fournir des paramètres, + comme les galeries, les formulaires et les listes. + + + Valeur + Affiche la valeur d'un des champs de la page en cours, avec des options pour modifier la valeur ou spécifier des valeurs alternatives. + + Vue partielle + + Une vue partielle est un fichier modèle séparé qui peut être à l'intérieur d'un aute modèle, + c'est génial pour réutiliser du markup ou pour séparer des modèles complexes en plusieurs fichiers. + + Modèle de base - Guide rapide concernant les tags des modèles Umbraco + Pas de modèle de base + Pas de modèle + + Afficher un modèle enfant + + @RenderBody(). + ]]> + + + + Définir une section nommée + + @section { ... }. Celle-ci peut être affichée dans une région + spécifique du parent de ce modèle, en utilisant @RenderSection. + ]]> + + + Afficher une section nommée + + @RenderSection(name). + Ceci affiche une région d'un modèle enfant qui est entourée d'une définition @section [name]{ ... } correspondante. + ]]> + + + Nom de la section + La section est obligatoire + + Si obligatoire, le modèle enfant doit contenir une définition @section, sinon une erreur est affichée. + + + + Générateur de requêtes + Générer une requête + éléments trouvés, en + + Je veux + tout le contenu + le contenu du type "%0%" + à partir de + mon site web + + et + + est + n'est pas + avant + avant (incluant la date sélectionnée) + après + après (incluant la date sélectionnée) + égal + n'est pas égal + contient + ne contient pas + supérieur à + supérieur ou égal à + inférieur à + inférieur ou égal à + + Id + Nom + Date de Création + Date de Dernière Modification + + trier par + ascendant + descendant + Modèle + + + Rich Text Editor + Image + Macro + Embed + Headline + Quote Choisissez le type de contenu Choisissez une mise en page Ajouter une ligne @@ -1056,7 +1598,6 @@ Pour gérer votre site, ouvrez simplement le backoffice Umbraco et commencez à Paramètres Configurez les paramètres qui peuvent être modifiés par les éditeurs - Styles Configurez les effets de style qui peuvent être modifiés par les éditeurs @@ -1064,13 +1605,16 @@ Pour gérer votre site, ouvrez simplement le backoffice Umbraco et commencez à Autoriser tous les éditeurs Autoriser toutes les configurations de rangées + Eléments maximum + Laisser vide ou mettre à 0 pour un nombre illimté Configurer comme défaut Choisir en plus Choisir le défaut ont été ajoutés - - + + + Compositions Vous n'avez pas ajouté d'onglet Ajouter un nouvel onglet @@ -1084,6 +1628,7 @@ Pour gérer votre site, ouvrez simplement le backoffice Umbraco et commencez à Modèles autorisés Sélectionnez les modèles que les éditeurs sont autorisés à utiliser pour du contenu de ce type. + Autorisé comme racine Autorisez les éditeurs à créer du contenu de ce type à la racine de l'arborescence de contenu. Oui - autoriser du contenu de ce type à la racine @@ -1092,6 +1637,7 @@ Pour gérer votre site, ouvrez simplement le backoffice Umbraco et commencez à Autorisez la création de contenu des types spécifiés sous le contenu de ce type-ci Choisissez les noeuds enfants + Hériter des onglets et propriétés d'un type de document existant. De nouveaux onglets seront ajoutés au type de document actuel, ou fusionnés s'il existe un onglet avec un nom sililaire. Ce type de contenu est utilisé dans une composition, et ne peut donc pas être lui-même un composé. Il n'y a pas de type de contenu disponible à utiliser dans une composition. @@ -1125,40 +1671,57 @@ Pour gérer votre site, ouvrez simplement le backoffice Umbraco et commencez à utilisant cet éditeur seront mis à jour avec la nouvelle configuration Le membre peut éditer - Afficher dans le profil du membre - l'onglet n'a pas d'ordonnancement - - - - Création des modèles - ceci peut prendre un certain temps, ne vous inquiétez pas - Les modèles ont été générés - Les modèles n'ont pas pu être générés - La génération des modèles a échoué, veuillez consulter les erreurs dans le log Umbraco + Autoriser la modification de la valeur de cette propriété par le membre à partir de sa page de profil + Est une donnée sensible + Cacher cette propriété aux éditeurs de contenu qui n'ont pas accès à la visualisation des données sensibles + Afficher dans le profil du membre + Permettre d'afficher la valeur de cette propriété sur la page de profil du membre + + l'onglet n'a pas d'ordre de tri + + Où cette composition est-elle utilisée? + Cette composition est actuellement utilisée dans la composition des types de contenu suivants : - + + Fabrication des modèles + ceci peut prendre un peu de temps, ne vous inquiétez pas + Modèles générés + Les modèles n'ont pas pu être générés + La génération des modèles a échoué, voyez les exceptions dans les U log + + + + Ajouter un champ de rechange + Champ de rechange + Ajouter une valeur par défaut + Valeur par défaut Champ alternatif Texte alternatif Casse Encodage Choisir un champ Convertir les sauts de ligne + Oui, convertir les sauts de ligne Remplace les sauts de ligne avec des balises &lt;br&gt; Champs particuliers Oui, la date seulement + Format et encodage Formater comme une date + Formate la valeur comme une date, ou une date avec l'heure, en fonction de la culture active Encoder en HTML Remplacera les caractères spéciaux par leur équivalent HTML. Sera inséré après la valeur du champ Sera inséré avant la valeur du champ Minuscules + Modifier le résultat Aucun + Example de résultat Insérer après le champ Insérer avant le champ Récursif - Supprimer les balises de paragraphes - Supprimera toute balise &lt;P&gt; au début et à la fin du texte + Oui, rendre récursif + Séparateur Champs standards Majuscules Encode pour URL @@ -1169,10 +1732,12 @@ Pour gérer votre site, ouvrez simplement le backoffice Umbraco et commencez à Tâches qui vous sont assignées - vous sont assignées. Pour voir un aperçu détaillé incluant les commentaires, cliquez sur "Détails" ou juste sur le nom de la page. + + vous sont assignées. Pour voir un aperçu détaillé incluant les commentaires, cliquez sur "Détails" ou juste sur le nom de la page. Vous pouvez aussi télécharger la page au format XML en cliquant sur le lien "Télécharger XML".
Pour clôturer une tâche de traduction, allez sur Détails, puis cliquez sur le bouton "Terminer la tâche". - ]]>
+ ]]> +
Terminer la tâche Détails Télécharger toutes les tâches de traductions au format XML @@ -1180,7 +1745,8 @@ Pour gérer votre site, ouvrez simplement le backoffice Umbraco et commencez à Télécharger la DTD XML Champs Inclure les pages enfants - + + ]]> + [%0%] tâches de traductions pour %1% Aucun utilisateur traducteur trouvé. Veuillez créer un utilisateur traducteur avant d'envoyer du contenu pour traduction Tâches que vous avez créées - que vous avez créées. Pour voir un aperçu détaillé incluant les commentaires, + + que vous avez créées. Pour voir un aperçu détaillé incluant les commentaires, cliquez sur "Détails" ou juste sur le nom de la page. Vous pouvez aussi télécharger la page au format XML en cliquant sur le lien "Télécharger XML". Pour clôturer une tâche de traduction, allez sur Détails, puis cliquez sur le bouton "Terminer tâche". - ]]> + ]]> + La page '%0%' a été envoyée pour traduction Veuillez choisir la langue dans laquelle le contenu doit être traduit Envoyer la page '%0%' pour traduction @@ -1217,6 +1786,9 @@ Pour gérer votre site, ouvrez simplement le backoffice Umbraco et commencez à Uploader le fichier de traduction XML + Contenu + Types de contenu + Media Navigateur de cache Corbeille Packages créés @@ -1237,6 +1809,8 @@ Pour gérer votre site, ouvrez simplement le backoffice Umbraco et commencez à Types de relations Packages Packages + Vues Partielles + Vues Partielles pour les Fichiers Macro Fichiers Python Installer depuis le repository Installer Runway @@ -1247,6 +1821,7 @@ Pour gérer votre site, ouvrez simplement le backoffice Umbraco et commencez à Modèles Fichiers XSLT Analytique + Utilisateurs Nouvelle mise à jour disponible @@ -1255,23 +1830,45 @@ Pour gérer votre site, ouvrez simplement le backoffice Umbraco et commencez à Erreur lors de la recherche de mises à jour. Veuillez vérifier le stack trace pour obtenir plus d'informations sur l'erreur. + Accès + Sur base des groupes et des noeuds de départ, l'utilisateur a accès aux noeuds suivants + Donner accès Administrateur Champ catégorie + Utilisateur créé Changer le mot de passe + Changer la photo Nouveau mot de passe + n'a pas été bloqué + Le mot de passe n'a pas été modifié Confirmez votre nouveau mot de passe Vous pouvez changer votre mot de passe d'accès au Back Office Umbraco en remplissant le formulaire ci-dessous puis en cliquant sur le bouton "Changer le mot de passe" Canal de contenu + Créer un autre utilisateur + Créer de nouveaux utilisateurs pour leur donner accès à Umbraco. Lors de la création d'un nouvel utilisateur, un mot de passe est généré que vous pouvez partager avec ce dernier. Champ description Désactiver l'utilisateur Type de document Editeur Champ extrait + Tentatives de connexion échouées + Voir le profil de l'utilisateur + Ajouter des groupes pour donner les accès et permissions + Inviter un autre utilisateur + Inviter de nouveaux utilisateurs pour leur donner accès à Umbraco. Un email d'invitation sera envoyé à chaque utilisateur avec des informations concernant la connexion à Umbraco. Langue + Spécifiez la langue dans laquelle vous souhaitez voir les menus et dialogues + Date du dernier bloquage + Dernière connexion + Dernière modification du mot de passe Identifiant Noeud de départ dans la librarie de média + Limiter la librairie média à un noeud de départ spécifique + Noeuds de départ dans la librairie de média + Limiter la librairie média à des noeuds de départ spécifique Sections Désactiver l'accès Umbraco + ne s'est pas encore connecté Ancien mot de passe Mot de passe Réinitialiser le mot de passe @@ -1286,19 +1883,153 @@ Pour gérer votre site, ouvrez simplement le backoffice Umbraco et commencez à Remplacer les permissions sur les noeuds enfants Vous êtes en train de modifiez les permissions pour les pages : Choisissez les pages dont les permissions doivent être modifiées + Supprimer la photo + Permissions par défaut + Permissions granulaires + Définir les permissions sur des noeuds spécifiques + Profil Rechercher tous les enfants + Ajouter les sections auxquelles les utilisateurs peuvent accéder + Sélectionner les groupes d'utilisateurs + Aucun noeud de départ sélectionné + Aucun noeud de départ sélectionné + Actif + Tous + Désactivé + Bloqué + Invité Noeud de départ du contenu + Limiter l'arborescence de contenu à un noeud de départ spécifique + Noeuds de départ du contenu + Limiter l'arborescence de contenu à des noeuds de départ spécifiques + Nom (A-Z) + Nom (Z-A) + Plus récent + Plus ancien + Dernière connexion + Dernière mise à jour de l'utilisateur + a été créé + Le nouvel utilisateur a été créé avec succès. Utilisez le mot de passe ci-dessous pour la connexion à Umbraco. + Gestion des utilisateurs Nom d'utilisateur - Permissions utilisateur - Type d'utilisateur - Types d'utilisateurs + Permissions de l'utilisateur + Permissions du groupe d'utilisateurs + Groupe d'utilisateurs + Groupes d'utilisateurs + a été invité + Une invitation a été envoyée au nouvel utilisateur avec les détails concernant la connexion à Umbraco. + Bien le bonjour et bienvenue dans Umbraco! Vous serez prêt.e dans moins d'1 minute, vous devez encore simplement configurer votre mot de passe et ajouter une photo pour votre avatar. + Chargez une photo afin que les autres utilisateurs puissent vous reconnaître facilement. Rédacteur Traducteur Modifier Votre profil Votre historique récent La session expire dans + Inviter un utilisateur + Créer un utilisateur + Envoyer l'invitation + Retour aux utilisateurs + Umbraco: Invitation + + + + + + + + + + + + +
+ + + + + +
+ +
+ +
+
+ + + + + + +
+
+
+ + + + +
+ + + + +
+

+ Salut %0%, +

+

+ Vous avez été invité.e par %1% à accéder au Umbraco Back Office. +

+

+ Message de %1%: +
+ %2% +

+ + + + + + +
+ + + + + + +
+ + Cliquez sur ce lien pour accepter l'invitation + +
+
+

Si vous ne pouvez pas cliquer sur le lien, copiez cet URL dans votre navigateur :

+ + + + +
+ + %3% + +
+

+
+
+


+
+
+ + ]]> +
+ Inviter + Nouvel envoi de l'invitation en cours... + Supprimer l'Utilisateur + Etes-vous certain(e) de vouloir supprimer le compte de cet utilisateur? + Validation Valider comme email @@ -1306,6 +2037,14 @@ Pour gérer votre site, ouvrez simplement le backoffice Umbraco et commencez à Valider comme Url ...ou introduisez une validation spécifique Champ obligatoire + Introduisez une expression régulière + Vous devez ajouter au moins + Vous ne pouvez avoir que + éléments + éléments sélectionnés + Date non valide + Pas un nombre + Email non valide + Send Type Søk... Opp @@ -441,6 +440,17 @@ Ja Mappe Søkeresultater + Sorter + Avslutt sortering + Eksempel + Bytt passord + til + Listevisning + Lagrer... + nåværende + Innbygging + Hent + valgt Bakgrunnsfarge @@ -459,7 +469,7 @@ Databasekonfigurasjon installer-knappen for å installere Umbraco %0% databasen]]> Neste for å fortsette.]]> - Databasen ble ikke funnet! Vennligst sjekk at informasjonen i "connection string" i "web.config"-filen er korrekt.

For å fortsette, vennligst rediger "web.config"-filen (bruk Visual Studio eller din favoritteditor), rull ned til bunnen, og legg til tilkoblingsstrengen for din database i nøkkelen "umbracoDbDSN" og lagre filen.

Klikk prøv på nytt når du er ferdig.
Mer informasjon om redigering av web.config her.

]]>
+ Databasen ble ikke funnet! Vennligst sjekk at informasjonen i "connection string" i "web.config"-filen er korrekt.

For å fortsette, vennligst rediger "web.config"-filen (bruk Visual Studio eller din favoritteditor), rull ned til bunnen, og legg til tilkoblingsstrengen for din database i nøkkelen "umbracoDbDSN" og lagre filen.

Klikk prøv på nytt når du er ferdig.
Mer informasjon om redigering av web.config her.

]]>
Vennligst kontakt din ISP om nødvendig. Hvis du installerer på en lokal maskin eller server, må du kanskje skaffe informasjonen fra din systemadministrator.]]> Trykk på knappen oppgrader for å oppgradere databasen din til Umbraco %0%

Ikke vær urolig - intet innhold vil bli slettet og alt vil fortsette å virke etterpå!

]]>
Trykk Neste for å fortsette.]]> @@ -468,7 +478,6 @@ Standardbrukeren har blitt deaktivert eller har ingen tilgang til Umbraco!

Ingen videre handling er nødvendig. Klikk neste for å fortsette.]]> Passordet til standardbrukeren har blitt forandret etter installasjonen!

Ingen videre handling er nødvendig. Klikk Neste for å fortsette.]]> Passordet er blitt endret! - Umbraco skaper en standard bruker med login ( "admin") og passord ( "default") . Det er viktig at passordet er endret til noe unikt.

Dette trinnet vil sjekke standard brukerens passord og foreslår hvis det må skiftes ]]> Få en god start med våre introduksjonsvideoer Ved å klikke på Neste-knappen (eller endre UmbracoConfigurationStatus i Web.config), godtar du lisensen for denne programvaren som angitt i boksen nedenfor. Legg merke til at denne Umbraco distribusjon består av to ulike lisenser, åpen kilde MIT lisens for rammen og Umbraco frivareverktøy lisens som dekker brukergrensesnittet. Ikke installert. @@ -633,7 +642,7 @@ Vennlig hilsen Umbraco roboten Avansert: Beskytt ved å velge hvilke brukergrupper som har tilgang til siden ved å bruke Umbraco's medlems-grupper]]> - rollebasert autentikasjon.]]> + Du må opprette en medlemsgruppe før du kan bruke rollebasert autentikasjon. Feilside Brukt når personer logger på, men ikke har tilgang Hvordan vil du beskytte siden din? @@ -736,7 +745,7 @@ Vennlig hilsen Umbraco roboten Creation date Sortering ferdig. Dra elementene opp eller ned for å arrangere dem. Du kan også klikke kolonneoverskriftene for å sortere alt på en gang. -
Ikke lukk dette vinduet under sortering]]>
+ En feil oppsto @@ -822,6 +831,12 @@ Vennlig hilsen Umbraco roboten Mal + Rich Text Editor + Image + Macro + Embed + Headline + Quote Sett inn element Velg layout Legg til rad @@ -884,8 +899,6 @@ Vennlig hilsen Umbraco roboten Sett inn etter felt Sett inn før felt Rekursivt - Fjern paragraftagger - Fjerner eventuelle <P> rundt teksten Standardfelter Store bokstaver URL koding @@ -1005,8 +1018,6 @@ Vennlig hilsen Umbraco roboten Startnode Navn Brukertillatelser - Brukertype - Brukertyper Forfatter Oversetter Endre diff --git a/WebCms/Umbraco/Config/Lang/nl.xml b/WebCms/Umbraco/Config/Lang/nl.xml index 9ce9959..aba0e8a 100644 --- a/WebCms/Umbraco/Config/Lang/nl.xml +++ b/WebCms/Umbraco/Config/Lang/nl.xml @@ -2,7 +2,7 @@ The Umbraco community - http://our.umbraco.org/documentation/Extending-Umbraco/Language-Files + https://our.umbraco.com/documentation/Extending-Umbraco/Language-Files Beheer domeinnamen @@ -27,18 +27,22 @@ Depubliceren Nodes opnieuw inladen Herpubliceer de site + Stel rechten voor pagina %0% in + Herstellen Rechten Vorige versies Klaar voor publicatie Klaar voor vertalen Sorteren - Klaar voor publicatie Vertalen Bijwerken - Standaardwaarde + Rechten instellen + Deblokkeer + Content sjabloon aanmaken + Uitnodiging opnieuw versturen - Permission denied. + Toegang geweigerd. Nieuw domein toevoegen verwijderen Ongeldige node. @@ -64,10 +68,10 @@ Tonen voor + Selectie ongedaan maken Selecteren Selecteer huidige map Doe iets anders - Vet Paragraaf uitspringen Voeg formulierveld in @@ -89,11 +93,14 @@ Opslaan Opslaan en publiceren Opslaan en verzenden voor goedkeuring + Sla list view op voorbeeld bekijken Voorbeeld bekijken is uitgeschakeld omdat er geen template is geselecteerd Stijl kiezen Stijlen tonen Tabel invoegen + Genereer models + Opslaan en models genereren Om het documenttype voor de geselecteerde inhoud te wijzigen, selecteert u eerst uit de lijst van geldige types voor deze locatie. @@ -133,7 +140,8 @@ Dit item is gewijzigd na publicatie Dit item is niet gepubliceerd Laatst gepubliceerd op - Nog geen items om weer te geven. + Er zijn geen items om weer te geven + Er zijn geen items om weer te geven. Mediatype Link naar media item(s) Ledengroep @@ -143,7 +151,9 @@ Pagina Titel Eigenschappen Dit document is gepubliceerd maar niet zichtbaar omdat de bovenliggende node '%0%' niet gepubliceerd is - Oeps: dit document is gepubliceerd, maar het is niet in de cache (interne serverfout) + Dit document is gepubliceerd, maar het is niet in de cache (interne serverfout) + Kan de Url niet ophalen + Dit document is gepubliceerd, maar de Url conflicteert met %0% Publiceren Publicatiestatus Publiceren op @@ -161,23 +171,45 @@ Bestand(en) verwijderen Link naar het document Lid van groep(en) - Geen lid van groep(en) - - Kinderen + Geen lid van groep(en) + Subitems Doel + Dit betekend de volgende tijd op de server: + Wat houd dit in?]]> + Ben je er zeker van dat je dit item wilt verwijderen? + Eigenschap %0% gebruikt editor %1% welke niet wordt ondersteund door Nested Content. + Voeg nog een tekstvak toe + Verwijder dit tekstvak + Content root + Deze waarde is verborgen. Indien u toegang nodig heeft om deze waarde te bekijken, contacteer dan uw website administrator. + Deze waarde is verborgen Klik om te uploaden Plaats je bestanden hier... + Link naar media + Of klik hier om bestanden te kiezen + De toegestane bestandtypen zijn + Dit bestand heeft niet het juiste file-type. Dit bestand kan niet geupload worden. + Max file size is + + + Maak nieuwe member aan + Alle Members + + + Nieuw lid aanmaken + Alle Leden Waar wil je de nieuwe %0% aanmaken? Aanmaken onder Kies een type en een titel - "Documenttypes".]]> - "Mediatypes"
.]]> + Document Type zonder template + Nieuwe folder + Nieuw data type Open je website @@ -189,40 +221,43 @@ Welkom - Stay - Discard changes - You have unsaved changes - Are you sure you want to navigate away from this page? - you have unsaved changes + Blijf op deze pagina + Negeer wijzigingen + Wijzigingen niet opgeslagen + Weet je zeker dat deze pagina wilt verlaten? - er zijn onopgeslagen wijzigingen + Depubliceren zal deze pagina en alle onderliggend paginas verwijderen van de site. Done - - Deleted %0% item - Deleted %0% items - Deleted %0% out of %1% item - Deleted %0% out of %1% items - - Published %0% item - Published %0% items - Published %0% out of %1% item - Published %0% out of %1% items - - Unpublished %0% item - Unpublished %0% items - Unpublished %0% out of %1% item - Unpublished %0% out of %1% items - - Moved %0% item - Moved %0% items - Moved %0% out of %1% item - Moved %0% out of %1% items - - Copied %0% item - Copied %0% items - Copied %0% out of %1% item - Copied %0% out of %1% items + + %0% item verwijderd + %0% items verwijderd + Item %0% van de %1% verwijderd + Items %0% van de %1% verwijderd + + %0% item gepubliceerd + %0% items gepubliceerd + Item %0% van de %1% gepubliceerd + Items %0% van de %1% gepubliceerd + + %0% item gedepubliceerd + %0% items gedepubliceerd + Item %0% van de %1% gedepubliceerd + Items %0% van de %1% gedepubliceerd + + %0% item verplaatst + %0% items verplaatst + item %0% van de %1% verplaatst + items %0% van de %1% verplaatst + + %0% item gekopieerd + %0% items gekopieerd + item %0% van de %1% gekopieerd + item %0% van de %1% gekopieerd + Link Titel + Link Naam Beheer domeinnamen Sluit dit venster @@ -269,21 +304,56 @@ Klik op de afbeelding voor volledige grootte Kies een item Toon cache item + Maak folder aan... + Relateer aan origineel + Descendants meenemen + De vriendelijkste community + Link naar pagina + Opent het gelinkte document in een nieuw venster of tab + Link naar media + Selecteer media + Selecteer icoon + Selecteer item + Selecteer link + Selecteer macro + Selecteer content + Selecteer member + Selecteer member group + Er zijn geen parameters voor deze macro + Externe login providers + Error details + Stacktrace + Inner Exception + Link je + De-Link je + account + Selecteer editor Cultuurnaam + Verander de key van het dictionary item. + + + - Typ je gebruikersnaam - Typ je wachtwoord + Typ jouw gebruikersnaam + Typ jouw wachtwoord + Bevestig jouw wachtwoord Benoem de %0%... Typ een naam... + Label... + Voer een omschrijving in... Typ om te zoeken... Typ om te filteren... Typ om tags toe te voegen (druk op enter na elke tag)... + Voer jouw email in + Jouw gebruikersnaam is meestal jouw email @@ -331,10 +401,17 @@ %0% is niet in het correcte formaat + Een error ontvangen van de server Het opgegeven bestandstype is niet toegestaan ​​door de beheerder OPMERKING! Ondanks dat CodeMiror is ingeschakeld, is het uitgeschakeld in Internet Explorer omdat het niet stabiel genoeg is. Zowel de alias als de naam van het nieuwe eigenschappen type moeten worden ingevuld! Er is een probleem met de lees/schrijf rechten op een bestand of map + Error bij het laden van Partial View script (file: %0%) + Error bij het laden van userControl '%0%' + Error bij het laden van customControl (Assembly: %0%, Type: '%1%') + Error bij het laden van MacroEngine script (file: %0%) + "Error bij het parsen van XSLT file: %0% + "Error bij het laden van XSLT file: %0% Vul een titel in Selecteer een type U wilt een afbeelding groter maken dan de originele afmetingen. Weet je zeker dat je wilt doorgaan? @@ -355,102 +432,143 @@ Acties Toevoegen Alias + Alles Weet je het zeker? - Rand - of - Annuleren + Terug + Border + bij + Cancel Cel marge - Kiezen - Sluiten - Venster sluiten - Opmerking - Bevestigen - Verhouding behouden - Doorgaan - Kopiëren + Kies + Sluit + Sluit venster + Comment + Bevestig + Verhoudingen behouden + Ga verder + Copy Aanmaken - Databank + Database Datum Standaard - Verwijderen + Verwijder Verwijderd - Verwijderen... + Aan het verwijderen... Ontwerp Afmetingen - Beneden + Omlaag Download - Aanpassen - Aangepast + Bewerk + Bewerkt Elementen - E-mail + Email Fout - Zoeken + Vind Hoogte Help Icoon - Importeren - Binnenmarge + Import + Binnenste marge Invoegen Installeren - Uitvullen + Ongeldig + Justify + Label Taal - Lay-out - Bezig met laden - Geblokkeerd + Layout + Aan het laden + Gesloten Inloggen - Afmelden - Afmelden + Uitloggen + Uitloggen Macro - Verplaatsen - Meer + Verplicht + Verplaats + meer Naam Nieuw Volgende Nee - van - Ok - Openen + of + OK + Open of Wachtwoord Pad Placeholder ID - Een ogenblik geduld a.u.b. + Een ogenblik geduld aub... Vorige Eigenschappen - E-mail om formulier te ontvangen + Email om formulier resultaten te ontvangen Prullenbak - Overgebleven - Hernoemen + De prullenbak is leeg + Overblijvend + Hernoem Vernieuw Verplicht Opnieuw proberen Rechten - Zoek + Zoeken + We konden helaas niet vinden wat je zocht Server - Tonen - Toon pagina bij versturen - Formaat - Sorteren - Submit - Type - Type om te zoeken... + Toon + Toon pagina na verzenden + Grootte + Sorteer + Verstuur + Typen + Typ om te zoeken... + onder Omhoog - Bijwerken + Update Upgrade Upload Url Gebruiker Gebruikersnaam Waarde - Toon + Bekijk Welkom... Breedte Ja Map - Reorder - I am done reordering - Zoekresultaten + Herschik + Ik ben klaar met herschikken + Voorvertoning + Wachtwoord veranderen + naar + Lijstweergave + Aan het opslaan... + huidig + Embed + geselecteerd + + + Zwart + Groen + Geel + Oranje + Blauw + Rood + + + Tab toevoegen + Property toevoegen + Editor toevoegen + Template toevoegen + Child node toevoegen + Child toevoegen + + Data type bewerken + + Secties navigeren + + Shortcuts + Toon shortcuts + + Toggle lijstweergave + Toggle toestaan op root-niveau Achtergrondkleur @@ -469,7 +587,7 @@ Database configuratie installeren
om de Umbraco %0% database te installeren]]> Volgende
om door te gaan.]]> - De database kon niet gevonden worden! Gelieve na te kijken of de informatie in de "connection string" van het "web.config" bestand correct is.

Om door te gaan, gelieve het "web.config" bestand aan te passen (met behulp van Visual Studio of je favoriete tekstverwerker), scroll in het bestand naar beneden, voeg de connection string voor je database toe in de key met naam "umbracoDbDSN" en sla het bestand op.

Klik op de knop opnieuw proberen als je hiermee klaar bent.
Meer informatie over het aanpassen van de web.config vind je hier.

]]>
+ De database kon niet gevonden worden! Gelieve na te kijken of de informatie in de "connection string" van het "web.config" bestand correct is.

Om door te gaan, gelieve het "web.config" bestand aan te passen (met behulp van Visual Studio of je favoriete tekstverwerker), scroll in het bestand naar beneden, voeg de connection string voor je database toe in de key met naam "umbracoDbDSN" en sla het bestand op.

Klik op de knop opnieuw proberen als je hiermee klaar bent.
Meer informatie over het aanpassen van de web.config vind je hier.

]]>
Gelieve contact op te nemen met je ISP indien nodig. Wanneer je installeert op een lokale computer of server, dan heb je waarschijnlijk informatie nodig van je systeembeheerder.]]> Klik de upgrade knop om je database te upgraden naar Umbraco %0%

Maak je geen zorgen - er zal geen inhoud worden gewist en alles blijft gewoon werken!

]]>
Klik Volgende om verder te gaan.]]> @@ -478,7 +596,6 @@ De default gebruiker is geblokkeerd of heeft geen toegang tot Umbraco!

Geen verdere actie noodzakelijk. Klik Volgende om verder te gaan.]]> Het wachtwoord van de default gebruiker is sinds installatie met succes veranderd.

Geen verdere actie noodzakelijk. Klik Volgende om verder te gaan.]]> Het wachtwoord is veranderd! - Umbraco maakt een default gebruiker aan met login ('admin') and wachtwoord ('default'). Het is belangrijk dat dit wachtwoord wordt veranderd in iets unieks.

Deze stap controleert het password van de default gebruiker en adviseert of het veranderd dient te worden.

]]>
Neem een jumpstart en bekijk onze introductie videos Bekijken Umbraco %0% voor een nieuwe installatie of een upgrade van versie 3.0.

Druk op "volgende" om de wizard te starten.]]>
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Cultuurcode Cultuurnaam @@ -543,12 +711,22 @@ Echter, Runway biedt een gemakkelijke basis om je snel op weg te helpen. Als je Fijne woensdag Fijne donderdag Fijne vrijdag - Fijne zaterdag - + Fijne zaterdag log hieronder in + Inloggen met Sessie is verlopen - © 2001 - %0%
umbraco.com

]]>
+ Wachtwoord vergeten? + Er zal een email worden gestuurd naar het emailadres van jouw account. Hierin staat een link om je wachtwoord te resetten + Een email met daarin de wachtwoord reset uitleg zal worden gestuurd als het emailadres in onze database voorkomt. + Terug naar loginformulier + Geeft alsjeblieft een nieuw wachtwoord op + Je wachtwoord is aangepast + De link die je hebt aangeklikt is niet (meer) geldig. + Umbraco: Wachtwoord Reset + + De gebruikersnaam om in te loggen bij jouw Umbraco omgeving is: %0%

Klik hier om je wachtwoord te resetten of knip/plak deze URL in je browser:

%1%

]]> +
Dashboard @@ -566,7 +744,7 @@ Echter, Runway biedt een gemakkelijke basis om je snel op weg te helpen. Als je De huidige node is niet toegestaan onder de geselecteerde node vanwege het node type De huidige node kan niet naar een van zijn subpagina’s worden verplaatst. De huidige node kan niet worden gebruikt op root-niveau - Deze actie is niet toegestaan omdat je onvoldoende rechten hebt op 1 of meer kinderen. + Deze actie is niet toegestaan omdat je onvoldoende rechten hebt op 1 of meer subitems. Relateer gekopieerde items aan het origineel @@ -646,6 +824,15 @@ Echter, Runway biedt een gemakkelijke basis om je snel op weg te helpen. Als je Package versie Package versiehistorie Bekijk de package website + Package reeds geinstalleerd + Deze package kan niet worden geinstalleerd omdat minimaal Umbraco versie %0% benodigd is. + Aan het deinstalleren... + Aan het downloaden... + Aan het importeren... + Aan het installeren... + Aan het herstarten, een ongenblik geduld aub... + Geinstalleerd! Je browser zal nu automatisch ververst worden... + Kruk op "finish" om de installate te voltooien en de pagina te verversen. Plakken met alle opmaak (Niet aanbevolen) @@ -654,9 +841,9 @@ Echter, Runway biedt een gemakkelijke basis om je snel op weg te helpen. Als je Plakken, en verwijder de opmaak (aanbevolen) - Geavanceerd: Beveilig door de Member Groups te seecteren die toegang hebben op de pagina + Geavanceerd: Beveilig door de Member Groups te selecteren die toegang hebben op de pagina gebruik makend van Umbraco's member groups.]]> - role-based authentication.]]> + Je moet eerst een membergroup maken voordat je kunt werken met role-based authentication. Error Pagina Gebruikt om te tonen als een gebruiker is ingelogd, maar geen rechten heeft om de pagina te bekijken Hoe wil je de pagina beveiligen? @@ -677,37 +864,39 @@ Echter, Runway biedt een gemakkelijke basis om je snel op weg te helpen. Als je %0% kan niet worden gepubliceerd omdat het item is gepland voor release. ]]> + - Inclusief ongepubliceerde kinderen + + Inclusief ongepubliceerde subitems Publicatie in uitvoering - even geduld... %0% van %1% pagina’s zijn gepubliceerd... %0% is gepubliceerd %0% en onderliggende pagina’s zijn gepubliceerd - Publiceer %0% en alle kinderen + Publiceer %0% en alle subitems ok om %0% te publiceren en de wijzigingen zichtbaar te maken voor bezoekers.

- Je kunt deze pagina publiceren en alle onderliggende sub-pagina's door publiceer alle kinderen aan te vinken hieronder. + Je kunt deze pagina publiceren en alle onderliggende sub-pagina's door publiceer alle subitems aan te vinken hieronder. ]]>
Je hebt geen goedgekeurde kleuren geconfigureerd - Externe link toevoegen - Interne link toevoegen - Toevoegen - Bijschrift - Interne pagina - URL - Verplaats omlaag - Verplaats omhoog - Open in nieuw venster - Verwijder link + Externe link toevoegen + Interne link toevoegen + Bijschrift + Link + In een nieuw venster openen + Voer het bijschrijft in + Voer de link in Reset @@ -736,13 +925,17 @@ Echter, Runway biedt een gemakkelijke basis om je snel op weg te helpen. Als je Instellingen Statistieken Vertaling - Gebruikers - Umbraco Contour - + Gebruikers Help Formulieren Analytics + + ga naar + Help onderwerpen voor + Video's voor + De beste Umbraco video tutorials + Standaard template Woordenboek sleutel @@ -762,16 +955,23 @@ Echter, Runway biedt een gemakkelijke basis om je snel op weg te helpen. Als je Geen eigenschappen gedefinieerd op dit tabblad. Klik op de link "voeg een nieuwe eigenschap" aan de bovenkant om een ​​nieuwe eigenschap te creëren. Master Document Type Maak een bijbehorend template + Icon toevoegen Sort order Creation date Sorteren gereed. Sleep de pagina's omhoog of omlaag om de volgorde te veranderen. Of klik op de kolom-header om alle pagina's daarop te sorteren. -
Sluit dit venster niet tijdens het sorteren]]>
+ - Publicatie werd geannuleerd door een 3rd party plug-in + Validatie + Validatiefouten moeten worden opgelost voor dit item kan worden opgeslagen + Mislukt + Wegens onvoldoende rechten kon deze handeling kon niet worden uitegevoerd + Geannuleerd + Uitvoering is g eannuleerd door de plugin van een 3e partij + Publicatie werd geannuleerd doordeeen plugin van een 3e partij Eigenschappen type bestaat al Eigenschappen type aangemaakt Data type: %1%]]> @@ -806,6 +1006,8 @@ Echter, Runway biedt een gemakkelijke basis om je snel op weg te helpen. Als je Bestand opgeslagen Bestand opgeslagen zonder fouten Taal opgeslagen + Media Type opgeslagen + Member Type opgeslagen Python script niet opgeslagen Python script kon niet worden opgeslagen door een fout Python script opeslagen! @@ -824,6 +1026,33 @@ Echter, Runway biedt een gemakkelijke basis om je snel op weg te helpen. Als je Partial view opgeslagen zonder fouten! Partial view niet opgeslagen Er is een fout opgetreden bij het opslaan van het bestand. + Script view opgeslagen + Script view opgeslagen zonder fouten! + Script view niet opgeslagen + Er is een fout opgetreden bij het opslaan van dit bestand. + Er is een fout opgetreden bij het opslaan van dit bestand. + %0% gebruikersgroepen verwijderd + %0% is verwijderd + %0% gebruikers geactiveerd + Er heeft zich een fout voorgedaan tijdens het activeren van de gebruikers + %0% users gedeactiveerd + Er heeft zich een fout voorgedaan tijdens het deactiveren van de gebruikers + %0% is nu geactiveerd + Er heeft zich een fout voorgedaan tijdens het activeren van de gebruiker + %0% is nu gedeactiveerd + Er heeft zich een fout voorgedaan tijdens het deactiveren van de gebruiker + Gebruikers groepen zijn ingesteld + %0% gebruikersgroepen verwijderd + %0% is verwijderd + %0% gebruikers gedeblokkeerd + Er heeft zich een fout voorgedaan tijdens het deblokkeren van de gebruikers + %0% is nu gedeblokkeerd + Er heeft zich een fout voorgedaan tijdens het deblokkeren van de gebruikers + Lid is geexporteerd naar een bestand + Er heeft zich een fout voorgedaan tijdens het exporteren van het lid + Gebruiker %0% is verwijderd + Gebruiker uitnodigen + Uitnodiging is opnieuw gestuurd naar gebruiker %0% Gebruik CSS syntax bijv: h1, .redHeader, .blueTex @@ -845,16 +1074,22 @@ Echter, Runway biedt een gemakkelijke basis om je snel op weg te helpen. Als je Sjabloon + Rich Text Editor + Image + Macro + Embed + Headline + Quote Item toevoegen - Choose a layout - Een rij aan de lay-out toevoegen - teken onderaan en voeg je eerste item toe]]> + Kies de indeling + Kies een indeling voor deze pagina om content toe te kunnen voegen + Plaats een (extra) content blok]]> Drop content - Settings applied - - This content is not allowed here - This content is allowed here + Instellingen toegepast + Deze content is hier niet toegestaan + Deze content is hier toegestaan + Klik om een item te embedden Klik om een afbeelding in te voegen Afbeelding ondertitel... @@ -883,7 +1118,86 @@ Echter, Runway biedt een gemakkelijke basis om je snel op weg te helpen. Als je Alle editors toelaten Alle rijconfiguraties toelaten + Maximale artikelen + Laat dit leeg of is ingesteld op -1 voor onbeperkt + Instellen als standaard + Kies extra + Kies standaard + zijn toegevoegd + + + Composities + Er zijn nog geen tabs toegevoegd + Voeg een nieuwe tab toe + Voeg nog een tab toe + Inherited van + Voeg property toe + Verplicht label + + Zet list view aan + Laat de child nodes van het content item zien als een sorteer- en doorzoekbare lijstweergave zien. Deze child nodes worden dan niet in de boomstructuur getoond. + + Toegestane Templates + Kies welke templates toegestaan zijn om door de editors op dit content-type gebruikt te worden + Sta toe op root-niveau + Sta editors toe om content van dit type aan te maken op root-niveau + Ja - sta content van dit type toe op root-niveau + + Toegestane child node types + Sta contetn van een bepaalde type toe om onder dit type aangemaakt te kunnen worden + + Kies child node + Overerfde tabs en properties van een bestaand document-type. Nieuwe tabs worden toegevoegd aan het huidige document-type of samengevoegd als een tab met de identieke naam al bestaat. + Dit content-type wordt gebruikt in een compositie en kan daarom niet zelf een compositie worden. + Er zijn geen content-typen beschikbaar om als compositie te gebruiken. + + Beschikbare editors + Herbruik + Editor instellingen + + Configuratie + + Ja, verwijder + + is naar onder geplaatst + is naar onder gecopierd + Selecteer de map om te verplaatsen + Selecteer de map om te kopieren + naar de boomstructuur onder + + Alle Document types + Alle documenten + Alle media items + + die gebruik maken van dit document type zullen permanent verwijderd worden. Bevestig aub dat je deze ook wilt verwijderen. + die gebruik maken van dit media type zullen permanent verwijderd worden. Bevestig aub dat je deze ook wilt verwijderen. + die gebruik maken van dit member type zullen permanent verwijderd worden. Bevestig aub dat je deze ook wilt verwijderen. + + en alle documenten van dit type + en alle media items van dit type + en alle leden van dit type + + die gebruik maken van deze editor zullen geupdate worden met deze nieuwe instellingen + + Lid kan bewerken + Toestaan dat deze eigenschap kan worden gewijzigd door het lid op zijn profiel pagina. + Omvat gevoelige gegevens + Verberg deze eigenschap voor de content editor die geen toegang heeft tot het bekijken van gevoelige informatie. + Toon in het profiel van leden + Toelaten dat deze eigenschap wordt getoond op de profiel pagina van het lid. + + tab heeft geen sorteervolgorde + + + + Models aan het gereneren + dit kan enige tijd duren, geduld aub + Models gegenereerd + Models konden niet gegenereerd worden + Models generatie is mislukt, kijk in de Umbraco log voor details + + Alternatief veld Alternatieve tekst @@ -904,8 +1218,6 @@ Echter, Runway biedt een gemakkelijke basis om je snel op weg te helpen. Als je Invoegen na veld Invoegen voor veld Recursief - Verwijder paragraaf tags - tags aan het begin en einde van de tekst worden verwijderd]]> Standaard velden Hoofdletters URL-encoderen @@ -916,7 +1228,8 @@ Echter, Runway biedt een gemakkelijke basis om je snel op weg te helpen. Als je Taken aan jou toegewezen - Sluit taak @@ -932,18 +1245,24 @@ Om een vertalingstaak te sluiten, ga aub naar het detailoverzicht en klik op de Dit is een geautomatiseerde mail om u op de hoogte te brengen dat document '%1%' is aangevraagd voor vertaling naar '%5%' door %2%. - Ga naar http://%3%/Umbraco/translation/default.aspx?id=%4% om te bewerken. + Ga naar http://%3%/translation/details.aspx?id=%4% om te bewerken. + Of log in bij Umbraco om een overzicht te krijgen van al jouw vertalingen. - Een prettige dag! + + Met vriendelijke groet! - Dit is een bericht van uw Content Management Systeem. - + + De Umbraco Robot ]]> [%0%] Vertaalopdracht voor %1% Geen vertaal-gebruikers gevonden. Maak eerst een vertaal-gebruiker aan voordat je pagina's voor vertaling verstuurd Taken aangemaakt door jou - die je aanmaakte. Om een detailweergave met opmerkingen te zien, klik op "Detail" of op de paginanaam. Je kan ook de pagina in XML-formaat downloaden door op de "Download XML"-link te klikken. Om een vertalingstaak te sluiten, klik je op de "Sluiten"-knop in detailweergave.]]> + die je aanmaakte. + Om een detailweergave met opmerkingen te zien, klik op "Detail" of op de paginanaam. + Je kan ook de pagina in XML-formaat downloaden door op de "Download XML"-link te klikken. + Om een vertalingstaak te sluiten, klik je op de "Sluiten"-knop in detailweergave.]]> De pagina '%0%' is verstuurd voor vertaling + Kies de taal waarin deze contetn vertaald moet worden Stuur voor vertaling Toegewezen door Taak geopend @@ -973,7 +1292,8 @@ Om een vertalingstaak te sluiten, ga aub naar het detailoverzicht en klik op de Ledengroepen Rollen Ledentypes - Documenttypes + Documenttypen + RelatieTypen Packages Packages Python-bestanden @@ -985,6 +1305,7 @@ Om een vertalingstaak te sluiten, ga aub naar het detailoverzicht en klik op de Stylesheets Sjablonen XSLT Bestanden + Analytics Nieuwe update beschikbaar @@ -993,23 +1314,46 @@ Om een vertalingstaak te sluiten, ga aub naar het detailoverzicht en klik op de Er is een fout opgetreden bij het zoeken naar een update. Bekijk de trace-stack voor verdere informatie. + Toegang + Gebaseerd op de gebruikers groepen en start pagina's heeft de bruiker toegang tot de volgende pagina's + Toegang geven Beheerders Categorieveld + Gebruiker aangemaakt Verander je wachtwoord + Wijzig je foto Wijzig je wachtwoord + is niet gedeblokkeerd + Het wachtwoord is niet gewijzigd Bevestig nieuw password Je kunt je wachtwoord veranderen door onderstaan formulier in te vullen en op de knop 'Verander wachtwoord' te klikken Inhoudskanaal + Nog een gebruiker aanmaken + Maak nieuwe gebruikers aan om hun toegang te geven tot Umbraco. Wanneer een nieuwe gebruiker wordt aangemaakt wordt er een wachtwoord gegenereerd dat je met hun kan delen. Omschrijving Geblokkeerde gebruiker Documenttype Redacteur Samenvattingsveld + Foute wachtwoord pogingen + Ga naar gebruikersprofiel + Voeg gebruikersgroepen toe om rechten in te stellen + Nog een gebruiker uitnodigen + Nodig gebruikers uit om hen toegang te geven to Umbraco. Een uitnodiging wordt via e-mail verstuurd met instructies hoe de gebruiker kan inloggen. Taal + Stel de taal in die gebruiker zal zien in menu's en dialoog vensters + Laatst geblokkeerd datu, + Laatste keer ingelogd + Laatste keer paswoord gewijzigd Loginnaam Startnode in Mediabibliotheek + Beperk de mediabibliotheek tot een specifieke startnode + Startnodes in Mediabibliotheek + Beperk de mediabibliotheek tot een specifieke startnodes Secties Blokkeer Umbraco toegang + heeft nog niet ingelogd + Oude wachtwoord Wachtwoord Reset wachtwoord Je wachtwoord is veranderd! @@ -1020,20 +1364,288 @@ Om een vertalingstaak te sluiten, ga aub naar het detailoverzicht en klik op de Ongeldig huidig wachtwoord Beide wachtwoorden waren niet hetzelfde. Probeer opnieuw! Beide wachtwoorden zijn niet hetzelfde! - Vervang rechten op de subnodes + Vervang rechten op de subitems U bent momenteel rechten aan het aanpassen voor volgende pagina's: Selecteer pagina's om hun rechten aan te passen - Doorzoek alle subnodes - Startnode in Content + Verwijder je foto + Standaard rechten + Specifieke rechten + Geef rechten op specifieke nodes + Profiel + Doorzoek alle subitems + Geef de gebruiker toegang tot secties + Selecteer een gebruikersgroep + Geen startnode geselecteerd + Geen startnodes geselecteerd + Actief + Alles + Gedeactiveerd + Geblokkeerd + Uitgenodigd + Startnode in Content + Beperk de content toegan een specifieke start node + Startnodes in Content + Beperk de content toegan een specifieke start nodes + Naam (A-Z) + Naam (Z-A) + Nieuwste + Oudste + Laatste inlog + Laatste keer bijgewerkt + is aangemaakt + De gebruiker is aangemaakt. Om in te loggen in Umbraco gebruik je onderstaand wachtwoord. + Gebruikers beheren Gebruikersnaam Gebruikersrechten - Gebruikerstype - Gebruikerstypes + Gebruikersgroep rechten + Gebruikersgroep + Gebruikersgroepen + is uitgenodigd + Een uitnodiging is gestuurd naar de nieuwe gebruiker met informatie over hoe in te loggen in Umbraco + Hallo en welk in Umbraco! Binnen ongeveer 1 minuut kan je aan de slag. Je moet enkel je wachtwoord instellen en een foto toevoegen. + Wijzig je foto zodat andere gebruikers je makkelijk kunnen herkennen. Auteur + Vertaler Wijzig - Je profiel Je recente historie Sessie verloopt over + Gebruiker uitnodigen + Gebruiker aanmaken + Uitnodiging versturen + Terug naar gebruikers + Umbraco: Uitnodiging + + + + + + + + + + + + +
+ + + + + +
+ +
+ +
+
+ + + + + + +
+
+
+ + + + +
+ + + + +
+

+ Hallo %0%, +

+

+ U bent uitgenodigd door %1% om in te loggen in de Umbraco Backoffice. +

+

+ Bericht van %1%: +
+ %2% +

+ + + + + + +
+ + + + + + +
+ + Klik hier om de uitnodiging te accepteren + +
+
+

Als je niet op de link kunt klikken, kopieer deze dan in de adresbalk van je browser:

+ + + + +
+ + %3% + +
+

+
+
+


+
+
+ + ]]> +
+ Uitnodigen + Uitnodiging opnieuw aan het versturen... + Verwijder gebruiker + Ben je zeker dat je deze gebruiker wil verwijderen? + + Validatie + Valideer als email + Valideer als nummer + Valideer als Url + ...of gebruik custom validatie + Veld is verplicht + + + + Waarde is insteld naar the aanbevolen waarde: '%0%'. + Waarde was '%1%' voor XPath '%2%' in configuratie bestand '%3%'. + De verwachtte waarde voor '%2%' is '%1%' in configuratie bestand '%3%', maar is '%0%'. + Onverwachte waarde '%0%' gevonden voor '%2%' in configuratie bestand '%3%'. + + + Custom foutmeldingen zijn ingesteld op '%0%'. + Custom foutmeldingen zijn momenteel '%0%'. Wij raden aan deze aan te passen naar '%1%' voor livegang. + Custom foutmeldingen aangepast naar '%0%'. + + Macro foutmeldingen zijn ingesteld op'%0%'. + Macro foutmeldingen zijn ingesteld op '%0%'. Dit zal er voor zorgen dat bepaalde, of alle, pagina's van de website niet geladen kunnen worden als er errors in een Macro zitten. Corrigeren zal deze waarde aanpassen naar '%1%'. + Macro foutmeldingen zijn aangepast naar '%0%'. + + + Try Skip IIS Custom foutmeldingen is ingesteld op '%0%'. IIS versie '%1%' wordt gebruikt. + Try Skip IIS Custom foutmeldingen is ingesteld op '%0%'. Het wordt voor de gebruikte IIS versie (%2%) aangeraden deze in te stellen op '%1%'. + Try Skip IIS Custom foutmeldingen ingesteld op '%0%'. + + + Het volgende bestand bestaat niet: '%0%'. + '%0%' kon niet gevonden worden in configuratie bestand '%1%'.]]> + Er is een fout opgetreden. Bekijk de log file voor de volledige fout: %0%. + + Members - Totaal XML: %0%, Totaal: %1%, Total incorrect: %2% + Media - Totaal XML: %0%, Totaal: %1%, Total incorrect: %2% + Content - Totaal XML: %0%, Totaal gepubliceerd: %1%, Total incorrect: %2% + + Het cerficaat van de website is ongeldig. + Cerficaat validatie foutmelding: '%0%' + Fout bij pingen van URL %0% - '%1%' + De site wordt momenteel %0% bekeken via HTTPS. + De appSetting 'umbracoUseSSL' in web.config staat op 'false'. Indien HTTPS gebruikt wordt moet deze op 'true' staan. + De appSetting 'umbracoUseSSL' in web.config is ingesteld op '%0%'. Cookies zijn %1% ingesteld als secure. + De 'umbracoUseSSL' waarde in web.config kon niet aangepast worden. Foutmelding: %0% + + + HTTPS inschakelen + Zet in de appSettings van de web.config de umbracoSSL instelling op 'true'. + De appSetting 'umbracoUseSSL' is nu ingesteld op 'true', cookies zullen als 'secure' worden aangemerkt. + + Herstellen + Kan een controle met vergelijkingstype 'ShouldNotEqual' niet herstellen. + Kan een controle met vergelijkingstype 'ShouldNotEqual' en gedefinieerde waarde niet herstellen. + Waarde om te herstellen niet gedefinieerd. + + Debug compiliate mode staat uit. + Debug compiliate mode staat momenteel aan. Wij raden aan deze instelling uit te zetten voor livegang. + Debug compiliate mode uitgezet. + + Trace mode staat uit. + Trace mode staat momenteel aan. Wij raden aan deze instelling uit te zetten voor livegang. + Trace mode uitgezet. + + Alle mappen hebben de juiste permissie-instellingen!. + + %0%.]]> + %0%. Als deze niet in gebruik zijn voor deze omgeving hoeft er geen actie te worden ondernomen.]]> + + All files have the correct permissions set. + + %0%.]]> + %0%. Als deze niet in gebruik zijn voor deze omgeving hoeft er geen actie te worden ondernomen.]]> + + X-Frame-Options header of meta-tag om IFRAMEing door andere websites te voorkomen is aanwezig!]]> + X-Frame-Options header of meta-tag om IFRAMEing door andere websites te voorkomen is NIET aanwezig.]]> + Voorkom IFRAMEing via web.config + Voegt de instelling toe aan de httpProtocol/customHeaders section in web.config om IFRAMEing door andere websites te voorkomen. + De instelling om IFRAMEing door andere websites te voorkomen is toegevoegd aan de web.config! + Web.config kon niet aangepast worden door error: %0% + + + %0%.]]> + Er zijn geen headeres gevonden welke informatie over de gebruikte website technologie prijsgeven! + + In de Web.config werd system.net/mailsettings niet gevonden + In de Web.config sectie system.net/mailsettings is de host niet geconfigureerd. + SMTP instellingen zijn correct ingesteld en werken zoals verwacht. + De SMTP server geconfigureerd met host '%0%' en poort '%1%' kon niet gevonden worden. Controleer of de SMTP instellingen in Web.config file system.net/mailsettings correct zijn. + + %0%.]]> + %0%.]]> + + + URL tracker uitzetten + URL tracker aanzetten + Originele URL + Doorgestuurd naar + Er zijn geen redirects + Er wordt automatisch een redirect aangemaakt als een gepubliceerde pagina hernoemd of verplaatst wordt. + Verwijder + Weet je zeker dat je de redirect van '%0%' naar '%1%' wilt verwijderen? + Redirect URL verwijderd. + Fout bij verwijderen redirect URL. + Weet je zeker dat je de URL tracker wilt uitzetten? + URL tracker staat nu uit. + Fout bij het uitzetten van de URL Tracker. Meer informatie kan gevonden worden in de log file. + URL tracker staat nu aan. + Fout bij het aanzetten van de URL tracker. Meer informatie kan gevonden worden in de log file. + + + Content verwijderd met id : {0} gerelateerd aan aan bovenliggend item met Id: {1} + Media verwijderd met id: {0} gerelateerd aan aan bovenliggend item met Id: {1} + Kan dit item niet automatisch herstellen + Er is geen 'herstel' relatie gevonden voor dit item. Gebruik de "Verplaats" optie om het manueel terug te zetten + Het item dat u wil herstellen onder ('%0%') zit in de prullenbak. Gebruik de "Verplaats" optie om het manueel terug te zetten +
diff --git a/WebCms/Umbraco/Config/Lang/pl.xml b/WebCms/Umbraco/Config/Lang/pl.xml index a356731..960233c 100644 --- a/WebCms/Umbraco/Config/Lang/pl.xml +++ b/WebCms/Umbraco/Config/Lang/pl.xml @@ -2,21 +2,22 @@ The Umbraco community - http://our.umbraco.org/documentation/Extending-Umbraco/Language-Files + https://our.umbraco.com/documentation/Extending-Umbraco/Language-Files Zarządzanie hostami Historia zmian Przeglądaj węzeł + Zmień typ dokumentu Kopiuj Utwórz Stwórz zbiór + Stwórz grupę Usuń Deaktywuj Opróżnij kosz + Aktywuj Eksportuj typ dokumentu - Ekspo .NET' - Eksportuj do .NET' Importuj typ dokumentu Importuj zbiór Edytuj na stronie @@ -25,31 +26,87 @@ Powiadomienia Publiczny dostęp Opublikuj + Cofnij publikację Odśwież węzeł Opublikuj ponownie całą stronę + Zmień nazwę + Przywróć + Ustaw uprawnienia dla strony %0% + Wybierz dokąd przenieść + W strukturze drzewa poniżej Uprawnienia Cofnij Wyślij do publikacji Wyślij do tłumaczenia + Ustaw grupę Sortuj - Zapisz do opublikowania Przetłumacz Aktualizuj + Ustaw uprawnienia + Odblokuj + Stwórz Szablon Zawartości + + Zawartość + Administracja + Struktura + Inne + + + Zezwól na dostęp do przydzielenia języka i hostów + Zezwól na dostęp do wglądu w historię logów węzła + Zezwól na dostęp do widoku węzła + Zezwól na dostęp do zmiany typu dokumentu dla węzła + Zezwól na dostęp do skopiowania węzła + Zezwól na dostęp do stworzenia węzłów + Zezwól na dostęp do usunięcia węzłóws + Zezwól na dostęp do przeniesienia węzła + Zezwól na dostęp do ustawienia i zmiany publicznego dostępu węzła + Zezwól na dostęp do publikacji węzła + Zezwól na dostęp do zmiany uprawnień węzła + Zezwól na dostęp do cofnięcia węzła do poprzedniego stanu + Zezwól na dostęp do wysłania węzła do akceptacji przed publikacją + Zezwól na dostęp do wysłania węzła do tłumaczenia + Zezwól na dostęp do zmiany kolejności sortowania węzłów + Zezwól na dostęp do tłumaczenia węzła + Zezwól na dostęp do zapisania węzła + Zezwól na dostęp do utworzenia Szablonu Zawartości + + Brak odpowiednich uprawnień Dodaj nową domenę + Usuń domenę + Niepoprawny węzeł + Niepoprawny format domeny. + Domena została już przydzielona. + Język Domena Domena '%0%' została utworzona Domena '%0%' została skasowana Domena '%0%' jest aktualnie przypisana - np.: yourdomain.com, www.yourdomain.com Domena '%0%' została zaktualizowana - Edycja aktualnych domen + Edytuj Aktualne Domeny + + + + Odziedziczona + Język + + lub wybierz dziedziczenie języka z węzła rodzica. Zostanie to zastosowane
+ także do obecnego węzła, o ile poniższa domena również do niego należy.]]> +
+ Domeny Wyświetlane dla + Wyczyść sekcję + Wybierz + Wybierz bieżący folder + Zrób coś innego Pogrubienie Zmniejsz wcięcie Wstaw z pola @@ -67,99 +124,190 @@ Wstawianie makra Wstawianie obrazka Edycja relacji + Powrót do listy Zapisz Zapisz i publikuj - Zapisz i wyślij do sprawdzenia + Zapisz i wyślij do zaakceptowania + Zapisz widok listy Podgląd + Podgląd jest wyłączony, ponieważ żaden szablon nie został przydzielony Wybierz styl Pokaż style Wstaw tabelę + Wygeneruj modele + Zapisz i wygeneruj modele + Cofnij + Powtórz + + + Aby zmienić typ dokumentu dla wybranej treści, najpierw wybierz typ z listy typów obowiązujących dla tej lokalizacji. + Następnie potwierdź i/lub zmień mapowanie właściwości z bieżącego typu do nowego i kliknij "Zapisz". + Treść została opublikowana ponownie. + Bieżąca właściwość + Bieżący typ + Typ dokumentu nie może być zmieniony, ponieważ nie istnieją obowiązujące alternatywy dla tej lokalizacji. Alternatywa będzie dostępna, jeśli będzie dozwolona pod rodzicem wybranego elementu zawartości i jeśli wszystkie istniejące dzieci elementu zawartości będą mieć pozwolenie na bycie tworzonym pod rodzicem. + Typ dokumentu został zmieniony + Mapuj Właściwości + Mapuj do Właściwości + Nowy Szablon + Nowy typ + Nic + Zawartość + Wybierz Nowy Typ Dokumentu + Typ dokumentu wybranej zawartości został zmieniony z powodzeniem do [new type] i następujące właściwości zostały zmapowane: + do + Nie można dokończyć mapowania właściwości, ponieważ jedna lub więcej właściwości mają zdefiniowane więcej niż jedno mapowanie. + Wyświetlane są tylko alternatywne typy obowiązujące dla obecnej lokalizacji. + Jest Opublikowany O tej stronie Link alternatywny (jakbyś opisał obrazek nad telefonem) Alternatywne linki Kliknij, aby edytować ten element Utworzone przez + Pierwotny autor + Zaktualizowane przez Data utworzenia + Data/czas stworzenia tego dokumentu Rodzaj dokumentu Edytowanie - Usuń w + Usuń w Ten element został zmieniony po publikacji Element nie jest opublikowany Opublikowane + Nie ma żadnych elementów do wyświetlenia + Nie ma żadnych elementów do wyświetlenia w liście. + Nie dodano żadnej zawartości + Nie dodano żadnych członków Typ mediów + Link do elementu(ów) mediów Członek grupy Rola - Członek typ + Typ członka Brak daty Tytuł strony Właściwości Ten dokument jest opublikowany, ale jest niewidoczny, ponieważ jego rodzic '%0%' nie jest opublikowany + Ten dokument jest opublikowany, ale nie jest w cache. + Nie znaleziono URL + Ten dokument jest opublikowany ale jego URL kolidowałby z elementem treści %0% Publikuj - Status + Status publikacji Opublikuj + Cofnij publikację Data usunięcia Porządek się zmienił - Aby posortować gałęzie, poprostu przeciągnij gałąż lub kliknij na jednym z nagłówków kolumn. Możesz wybrać kilka gałęzi poprzez przytrzymanie klawisza "shift" lub "control" podczas zaznaczania + Aby posortować gałęzie, po prostu przeciągnij gałąż lub kliknij na jednym z nagłówków kolumn. Możesz wybrać kilka gałęzi poprzez przytrzymanie klawisza "shift" lub "control" podczas zaznaczania Statystyki Tytuł (opcjonalny) + Alternatywny tekst (opcjonalny) Typ Cofnij publikację Ostatnio edytowany - Usuń plik + Data/czas edycji dokumentu + Usuń plik(i) Link do dokumentu + Członek grupy (grup) + Nie jest członkiem grupy (grup) + Elementy dzieci + Cel + Oznacza to następującą godzinę na serwerze: + Co to oznacza?]]> + Czy na pewno chcesz usunąć ten element? + Właściwość %0% używa edytora %1%, który nie jest wspierany przez Nested Content. + Dodaj kolejne pole tekstowe + Usuń te pole tekstowe + Korzeń zawartości + + + Stwórz nowy Szablon Zawartości z '%0%' + Pusty + Wybierz Szablon Zawartości + Szablon Zawartości został stworzony + Szablon Zawartości został stworzony z '%0%' + Szablon Zawartości o tej samej nazwie już istnieje + Szablon Zawartości to predefiniowana zawartość, którą edytor może wybrać, aby użyć jej jako podstawę do stworzenia nowej zawartości + + + Kliknij, aby załadować plik + Przerzuć swoje pliki tutaj... + Link do mediów + lub kliknij tutaj, aby wybrać pliki + Jedyne dozwolone typy plików to + Nie można załadować pliku, typ pliku nie jest akceptowany + Maksymalny rozmiar pliku to + Korzeń mediów + + + Stwórz nowego członka + Wszyscy członkowie Gdzie chcesz stworzyć nowy %0%? Utwórz w + Wybierz typ dokumentu, dla którego chcesz stworzyć szablon zawartości Wybierz rodzaj oraz tytuł + "typy dokumentów".]]> + "typy mediów".]]> + Typ Dokumentu bez szablonu + Nowy folder + Nowy typ danych + Nowy plik javascript + Nowy pusty Częściowy Widok + Nowy Częściowy Widok makro + Nowy Częściowy Widok ze snippeta + Nowy pusty Częściowy Widok makro + Nowy Częściowy Widok makro ze snippeta + Nowy Częściowy Widok makro (bez makro) Przeglądaj swoją stronę - Ukryj - Jeśli Umbraco się nie otwiera, prawdopodbnie musisz zezwolić tej stronie na otwieranie wyskakujących okienek + Jeśli Umbraco się nie otwiera, prawdopodobnie musisz zezwolić tej stronie na otwieranie wyskakujących okienek zostało otwarte w nowym oknie Restartuj Odwiedź Witaj - Stay - Discard changes - You have unsaved changes - Are you sure you want to navigate away from this page? - you have unsaved changes + Zostań + Odrzuć zmiany + Masz niezapisane zmiany + Jesteś pewien, że chcesz wyjść ze strony? - masz niezapisane zmiany - Done + Wykonane - Deleted %0% item - Deleted %0% items - Deleted %0% out of %1% item - Deleted %0% out of %1% items + Usunięto %0% element + Usunięto %0% elementy(ów) + Usunięto %0% z %1% elementu + Usunięto %0% z %1% elementów - Published %0% item - Published %0% items - Published %0% out of %1% item - Published %0% out of %1% items + Opublikowano %0% element + Opublikowano %0% elementy(ów) + Opublikowano %0% z %1% elementu + Opublikowano %0% z %1% elementów - Unpublished %0% item - Unpublished %0% items - Unpublished %0% out of %1% item - Unpublished %0% out of %1% items + Cofnięto publikację %0% elementu + Cofnięto publikację %0% elementy(ów) + Cofnięto publikację %0% z %1% elementu + Cofnięto publikację %0% z %1% elementów - Moved %0% item - Moved %0% items - Moved %0% out of %1% item - Moved %0% out of %1% items + Przeniesiono %0% element + Przeniesiono %0% elementy(ów) + Przeniesiono %0% z %1% elementu + Przeniesiono %0% z %1% elementów - Copied %0% item - Copied %0% items - Copied %0% out of %1% item - Copied %0% out of %1% items + Skopiowano %0% element + Skopiowano %0% elementy(ów) + Skopiowano %0% z %1% elementu + Skopiowano %0% z %1% elementów + Tytuł linku + Link Nazwa Zarządzaj nazwami hostów Zamknij to okno @@ -183,45 +331,122 @@ Link wewnętrzny: Kiedy używasz odnośników lokalnych, wstaw znak "#" na początku linku Otworzyć w nowym oknie? - Ustawienia Makra + Ustawienia Makro To makro nie posiada żadnych właściwości, które można edytować Wklej Edytuj Uprawnienia dla + Ustaw uprawnienia dla + Ustaw uprawnienia dla %0% dla grupy użytkownika %1% + Wybierz grupy użytkowników, dla których chcesz ustawić uprawnienia Zawartość kosza jest teraz usuwana. Proszę nie zamykać tego okna do momentu zakończenia procesu. Zawartość kosza została usunięta Usunięcie elementów z kosza powoduje ich trwałe i nieodwracalne skasowanie - regexlib.com aktulanie nie jest dostępny, na co nie mamy wpływu. Bardzo przepraszamy za te utrudnienia.]]> - Przeszukaj dla wyrażeń regularnych aby dodać regułę sprawdzającą do formularza. Np. 'email' 'url' + regexlib.com aktualnie nie jest dostępny, na co nie mamy wpływu. Bardzo przepraszamy za te utrudnienia.]]> + Przeszukaj dla wyrażeń regularnych, aby dodać regułę sprawdzającą do formularza. Np. 'email' 'url' Usuń Makro Pole wymagane Strona została przeindeksowana Cache strony zostało odświeżone. Cała opublikowana zawartość jest teraz aktualna. Natomiast cała nieopublikowana zawartość ciągle nie jest widoczna - Cache strony zostanie odświeżone. Cała zawartość opublikowana będzie aktulana, lecz nieopublikowana zawartość pozostanie niewidoczna + Cache strony zostanie odświeżone. Cała zawartość opublikowana będzie aktualna, lecz nieopublikowana zawartość pozostanie niewidoczna Liczba kolumn Liczba wierszy - Ustaw zastępczy ID Ustawiając ID na tym elemencie możesz później łączyć treść z podrzędnych szablonów, ustawiając dowiązanie do tego ID na elemencie <asp:treści />]]> - Wybierz zastępczy id z poniższej listy. Możesz wybierać tylko spośród id na szablonie nadrzędnym tego formularza.]]> - Kliknij na obrazie, aby zobaczyć je w pełnym rozmiarze + + Ustaw zastępczy ID Ustawiając ID na tym elemencie możesz później łączyć treść z podrzędnych szablonów, + ustawiając dowiązanie do tego ID na elemencie <asp:treści />]]> + + + Wybierz zastępczy ID z poniższej listy. Możesz wybierać tylko + spośród ID na szablonie nadrzędnym tego formularza.]]> + + Kliknij na obrazie, aby zobaczyć go w pełnym rozmiarze Wybierz element Podgląd elementów Cache + Utwórz folder... + Odnieś się do oryginału + Zawrzyj potomków + Najbardziej przyjacielska społeczność + Link do strony + Otwórz zlinkowany dokument w nowym oknie lub zakładce + Link do mediów + Link do plików + Wybierz węzeł początkowy zawartości + Wybierz media + Wybierz ikonę + Wybierz element + Wybierz link + Wybierz makro + Wybierz zawartość + Wybierz węzeł początkowy mediów + Wybierz członka + Wybierz członka grupy + Wybierz węzeł + Wybierz sekcje + Wybierz użytkowników + Nie znaleziono ikon + Te makro nie ma żadnych właściwości + Brak dostępnych makro do wstawienia + Zewnętrzni dostawcy logowania + Sczegóły Wyjątku + Stacktrace + Wewnętrzny wyjątek + Zlinkuj swój + Odlinkuj swój + konto + Wybierz edytora + Wybierz snippet - %0%' poniżej.
-Możesz dodać dodatkowe języki w menu "Języki" po lewej stronie.]]>
+ + %0%' poniżej.
+ Możesz dodać dodatkowe języki w menu "Języki" po lewej stronie.]]> +
Nazwa języka + Edytuj klucz elementu słownika. + + + + + + Wpisz nazwę użytkownika + Wpisz hasło + Potwierdź hasło + Nazwij %0%... + Wpisz nazwę... + Wpisz adres e-mail... + Wpisz nazwę użytkownika... + Etykieta... + Wpisz opis... + Wpisz, aby wyszukać... + Wpisz, aby filtrować... + Wpisz, aby dodać tagi (naciśnij enter po każdym tagu)... + Wpisz adres e-mail + Wpisz wiadomość... + Twoja nazwa użytkownika to przeważnie Twój adres e-mail + Zezwól w korzeniu + Tylko Typ Zawartości, który jest zaznaczony może być stworzony na pozomie korzenia drzew Zawartości i Mediów Dozwolone węzły pochodne + Kompozycje Typu Dokumentu Stwórz Usuń zakładkę Opis Nowa zakładka Zakładka Miniatura + Włącz widok listy + Konfiguruje element treści, aby pokazywał możliwą do sortowania i szukania listę jego dzieci, dzieci nie będą wyświetlone w drzewie + Bieżący widok listy + Typ danych aktywnego widoku listy + Stwórz niestandardowy widok listy + Usuń niestandardowy widok listy Dodaj wartość - Typ bazydanych + Typ bazy danych Edytor GUID Renderuj kontrolkę Przyciski @@ -231,10 +456,17 @@ Możesz dodać dodatkowe języki w menu "Języki" po lewej stronie.]]> Powiązane arkusze stylów Pokaż etykietę Szerokość i wysokość + Wszystkie typy właściwości & dane właściwości + używające tego typu danych zostaną usunięte na zawsze, potwierdź, że chcesz je także usunąć + Tak, usuń + i wszystkie typy właściwości & dane właściwości używające tego typu danych + Wybierz folder do przeniesienia + do w strukturze drzewa poniżej + został przeniesiony poniżej - Dane zostały zapisane, lecz wystąpiły błędy które musisz poprawić przed publikacją strony: - Bieżący dostawca Membership nie obsługuje zmiany hasła (EnablePasswordRetrieval musi mieć wartość true) + Dane zostały zapisane, lecz wystąpiły błędy, które musisz poprawić przed publikacją strony: + Bieżący dostawca członkowstwa nie obsługuje zmiany hasła (EnablePasswordRetrieval musi mieć wartość 'true') %0% już istnieje Wystąpiły błędy: Wystąpiły błędy: @@ -246,30 +478,42 @@ Możesz dodać dodatkowe języki w menu "Języki" po lewej stronie.]]> %0% nie jest w odpowiednim formacie - TRANSLATE ME: 'NOTE! Even though CodeMirror is enabled by configuration, it is disabled in Internet Explorer because it's not stable enough.' - Proszę uzupełnij zarówno alias jak i nazwę dla nowego typu właściwości + Otrzymano błąd serwera + Określony typ pliku został ustawiony jako niedozwolony przez administratora + Pomimo tego, że CodeMirror jest włączony w konfiguracji, jest on wyłączony w Internet Explorerze ze względu na swoją niestabilność. + Proszę uzupełnij zarówno alias, jak i nazwę dla nowego typu właściwości Wystąpił problem podczas zapisu/odczytu wymaganego pliku lub folderu + Wystąpił błąd podczas ładowania skryptu Częściowego Widoku (plik: %0%) + Wystąpił błąd podczas ładowania userControl '%0%' + Wystąpił błąd podczas ładowania customControl (Assembly: %0%, Typ: '%1%') + Wystąpił błąd podczas ładowania skryptu MacroEngine (plik: %0%) + "Wystąpił błąd podczas parsowania pliku XSLT: %0% + "Wystąpił błąd odczytu pliku XSLT: %0% Proszę podać tytuł Proszę wybrać typ Chcesz utworzyć obraz większy niż rozmiar oryginalny. Czy na pewno chcesz kontynuować? - Błąd w skrypcie python - Skrypt python nie został zapisany, ponieważ zawiera błędy + Błąd w skrypcie Python + Skrypt Python nie został zapisany, ponieważ zawiera błędy Węzeł początkowy usunięto, proszę skontaktować się z administratorem Proszę zaznaczyć zawartość przed zmianą stylu Brak dostępnych aktywnych stylów - Proszę ustaw kursor po lewej stronie dwóch cel które chcesz połączyć + Proszę ustaw kursor po lewej stronie dwóch komórek, które chcesz połączyć Nie możesz podzielić komórki, która nie była wcześniej połączona. Błąd w źródle XSLT Plik XSLT nie został zapisany, ponieważ wystąpiły błędy + Wystąpił błąd konfiguracji związany z typem danych użytych we właściwościach, proszę sprawdź typ danych O... Akcja + Akcje Dodaj Alias + Wszystkie Czy jesteś pewny? + Wstecz Ramka - lub + przez Anuluj Marginesy komórki Wybierz @@ -277,6 +521,7 @@ Możesz dodać dodatkowe języki w menu "Języki" po lewej stronie.]]> Zamknij okno Komentarz Potwierdzenie + Zachowaj Zachowaj proporcje Kontynuuj Kopiuj @@ -288,6 +533,7 @@ Możesz dodać dodatkowe języki w menu "Języki" po lewej stronie.]]> Usunięto Usuwanie... Wygląd + Słownik Rozmiary Dół Pobierz @@ -297,6 +543,7 @@ Możesz dodać dodatkowe języki w menu "Języki" po lewej stronie.]]> Email Błąd Znajdź + Pierwszy Wysokość Pomoc Ikona @@ -304,16 +551,20 @@ Możesz dodać dodatkowe języki w menu "Języki" po lewej stronie.]]> Margines wewnętrzny Wstaw Instaluj + Nieprawidłowe Wyrównaj Język - układ + Ostatni + Układ Ładowanie Zablokowany Zaloguj Wyloguj Wyloguj Makro + Obowiązkowy Przenieś + Więcej Nazwa Nowy Dalej @@ -323,19 +574,24 @@ Możesz dodać dodatkowe języki w menu "Języki" po lewej stronie.]]> Otwórz lub Hasło - Scieżka + Ścieżka Zastępczy ID - Proszę zaczekać... + Proszę czekać... Poprzedni Właściwości - E-mail aby otrzymywać dane z formularzy + E-mail, aby otrzymywać dane z formularzy Kosz Pozostało + Usuń Zmień nazwę Odnów + Wymagany + Odzyskaj Ponów próbę Uprawnienia Szukaj + Przepraszamy, ale nie możemy znaleźć tego, czego szukasz + Elementy nie zostały dodane Serwer Pokaż Pokaż stronę "wyślij" @@ -343,29 +599,79 @@ Możesz dodać dodatkowe języki w menu "Języki" po lewej stronie.]]> Sortuj Zatwierdź Typ - Szukaj + Wpisz, aby wyszukać... W górę - Update + Aktualizacja Aktualizacja Wyślij plik - Url + URL Użytkownik - Login + Nazwa użytkownika Wartość Widok Witaj... Szerokość Tak + Folder + Wyniki wyszukiwania Zmień kolejność Kolejność została zmieniona + Podgląd + Zmień hasło + do + Widok listy + Zapisywanie... + bieżący + Osadzony + wybrany + + + Czarny + Zielony + Żółty + Pomarańczowy + Niebieski + Czerwony + + + + Dodaj zakładkę + Dodaj właściwość + Dodaj edytora + Dodaj szablon + Dodaj węzeł dziecka + Dodaj dziecko + + Edytuj typ danych + + Nawiguj sekcje + + Skróty + Pokaż skróty + + Przełącz widok listy + Przełącznik możliwy jako korzeń + + Komentuj/Odkomentuj linie + Usuń linię + Kopiuj linie do góry + Kopiuj linie w dół + Przenieś linie w górę + Przenieś linie w dół + + Ogólne + Edytor + + Kolor tła Pogrubienie Kolor tekstu - Czcionka + Font Tekst + Strona @@ -374,73 +680,142 @@ Możesz dodać dodatkowe języki w menu "Języki" po lewej stronie.]]> Nie udało się zapisać pliku web.config. Zmodyfikuj parametry połączenia ręcznie. Twoja baza danych została znaleziona i zidentyfikowana jako Konfiguracja bazy danych - instaluj aby zainstalować bazę danych Umbraco %0%]]> - Dalej aby kontynuować.]]> - Nie odnaleziono bazy danych! Sprawdź czy informacje w sekcji "connection string" w pliku "web.config" są prawidłowe.

Aby kontynuować, dokonaj edycji pliku "web.config" (używając Visual Studio lub dowolnego edytora tekstu), przemieść kursor na koniec pliku, dodaj parametry połączenia do Twojej bazy danych w kluczu o nazwie "umbracoDbDSN" i zapisz plik.

Kliknij ponów próbę kiedy skończysz.
Tu znajdziesz więcej informacji na temat edycji pliku "web.config".

]]>
- Skontaktuj się z Twoim dostawą usług internetowych jeśli zajdzie taka potrzeba. W przypadku instalacji na lokalnej maszynie lub serwerze możesz potrzebować pomocy administratora.]]> - Naciśnij przycisk aktualizuj by zaktualizować swoją bazę danych do Umbraco %0%

Bez obaw - żadne dane nie zostaną usunięte i wszystko będzie działać jak należy!

]]>
- Naciśnij przycisk Dalej aby kontynuować.]]> - Dalej aby kontynuować kreatora instalacji.]]> + instaluj, aby zainstalować bazę danych Umbraco %0% +]]> + Dalej, aby kontynuować.]]> + Nie odnaleziono bazy danych! Sprawdź, czy informacje w sekcji "connection string" w pliku "web.config" są prawidłowe.

+

Aby kontynuować, dokonaj edycji pliku "web.config" (używając Visual Studio lub dowolnego edytora tekstu), przemieść kursor na koniec pliku, dodaj parametry połączenia do Twojej bazy danych w kluczu o nazwie "umbracoDbDSN" i zapisz plik.

+

+ Kliknij ponów próbę kiedy + skończysz.
+ Tu znajdziesz więcej informacji na temat edycji pliku "web.config".

]]>
+ + Skontaktuj się z Twoim dostawą usług internetowych jeśli zajdzie taka potrzeba. + W przypadku instalacji na lokalnej maszynie lub serwerze możesz potrzebować pomocy administratora.]]> + + Naciśnij przycisk aktualizuj, aby zaktualizować swoją bazę danych do Umbraco %0%

+

+ Bez obaw - żadne dane nie zostaną usunięte i wszystko będzie działać jak należy! +

+ ]]>
+ Naciśnij przycisk Dalej, aby + kontynuować.]]> + Dalej, aby kontynuować kreatora instalacji.]]> Hasło domyślnego użytkownika musi zostać zmienione!]]> - Konto domyślnego użytkownika została wyłączone lub nie ma on dostępu do Umbraco!

Żadne dotatkowe czynności nie są konieczne. Naciśnij Dalej aby kontynuować.]]> - Hasło domyślnego użytkownika zostało zmienione od czasu instalacji!

Żadne dotatkowe czynności nie są konieczne. Naciśnij Dalej aby kontynuować.]]> + Konto domyślnego użytkownika zostało wyłączone lub nie ma on dostępu do Umbraco!

Żadne dodatkowe czynności nie są konieczne. Naciśnij Dalej, aby kontynuować.]]> + Hasło domyślnego użytkownika zostało zmienione od czasu instalacji!

Żadne dodatkowe czynności nie są konieczne. Naciśnij Dalej, aby kontynuować.]]> Hasło zostało zmienione! - Umbraco tworzy domyślne konto użytkownika o loginie ('admin') i haśle ('default'). Jest ważne, by zmienić hasło na inne.

Ten krok sprawdzi, czy hasło domyślnego użytkownika powinno być zmienione.

]]>
Aby szybko wejść w świat Umbraco, obejrzyj nasze filmy wprowadzające Klikając przycisk dalej (lub modyfikując klucz UmbracoConfigurationStatus w pliku web.config), akceptujesz licencję na niniejsze oprogramowanie zgodnie ze specyfikacją w poniższym polu. Zauważ, że ta dystrybucja Umbraco składa się z dwoch licencji - licencja MIT typu open source dla kodu oraz licencja "Umbraco freeware", która dotyczy interfejsu użytkownika. Nie zainstalowane. Zmienione pliki i foldery Więcej informacji na temat ustalania pozwoleń dla Umbraco znajdziesz tutaj Musisz zezwolić procesowi ASP.NET na zmianę poniższych plików/folderów - Twoje ustawienia uprawnień są prawie idealne!

Umbraco będzie działało bez problemów, ale nie będzie możliwa instalacja pakietów, które są rekomendowane aby w pełni wykorzystać możliwości Umbraco.]]>
+ Twoje ustawienia uprawnień są prawie idealne!

+ Umbraco będzie działało bez problemów, ale nie będzie możliwa instalacja pakietów, które są rekomendowane, aby w pełni wykorzystać możliwości Umbraco.]]>
Jak to Rozwiązać Kliknij tutaj, aby przeczytać wersję tekstową - video tutorial pokazujący jak ustawić uprawnienia dostępu do folderów dla Umbraco albo przeczytaj wersję tekstową.]]> - Twoje ustawienia uprawnień mogą stanowić problem! Umbraco będzie działało bez problemów, ale nie będzie możliwa instalacja pakietów, które są rekomendowane aby w pełni wykorzystać możliwości Umbraco.]]> - Twoje ustawienia uprawnień nie są gotowe na Umbraco!

Aby Umbraco mogło działać musisz uaktualnić swoje ustawienia zabezpieczeń.]]>
- Twoje ustawienia uprawnień są idealne!

Umbraco będzie działać bez problemów i będzie można instalować pakiety!]]>
+ samouczek, pokazujący jak ustawić uprawnienia dostępu do folderów dla Umbraco, albo przeczytaj wersję tekstową.]]> + Twoje ustawienia uprawnień mogą stanowić problem! +

+ Umbraco będzie działało bez problemów, ale nie będzie możliwa instalacja pakietów, które są rekomendowane, aby w pełni wykorzystać możliwości Umbraco.]]>
+ Twoje ustawienia uprawnień nie są gotowe na Umbraco! +

+ Aby Umbraco mogło działać musisz uaktualnić swoje ustawienia zabezpieczeń.]]>
+ Twoje ustawienia uprawnień są idealne!

+ Umbraco będzie działać bez problemów i będzie można instalować pakiety!]]>
Rozwiązywanie problemów z folderami Kliknij ten link, aby uzyskać więcej informacji na temat problemów z ASP.NET i tworzeniem folderów. Ustawianie uprawnień dostępu do folderów - Umbraco potrzebuje uprawnień do zapisu/odczytu pewnych katalogów w celu przechowywania plików jak obrazki i PDF'y. Umbraco przechowuje także tymczasowe dane (aka: cache) aby zwiększyć wydajność Twojej strony. - Chce zacząć od zera - dowiedz się jak) Ciągle możesz wybrać, aby zainstalować Runway w późniejszym terminie. W tym celu przejdź do sekcji Deweloper i wybierz Pakiety.]]> + + Chcę zacząć od zera + dowiedz się jak) + Ciągle możesz wybrać, aby zainstalować Runway w późniejszym terminie. W tym celu przejdź do sekcji Deweloper i wybierz Pakiety. + ]]> Właśnie stworzyłeś czystą instalację platformy Umbraco. Co chcesz zrobić teraz? - Pakiet Runway zainstalowany pomyślnie - To jest nasza lista rekomendowanych modułów. Zaznacz te, które chcesz zainstalować lub wyświetl pełną listę modułów ]]> + Pakiet Runway został zainstalowany pomyślnie + + To jest nasza lista rekomendowanych modułów. Zaznacz te, które chcesz zainstalować lub wyświetl pełną listę modułów + ]]> Rekomendowane tylko dla doświadczonych użytkowników - Chce rozpocząć z prostą stroną - Pakiet "Runway" to prosta strona dostarczająca kilka podstawowych typów dokumentów i szablonów. Instalator może automatycznie zainstalować pakiet Runway za Ciebie, ale możesz w łatwy sposób edytować, rozszerzyć lub usunąć go. Nie jest on potrzebny i możesz doskonale używać Umbraco bez niego. Jednakże pakiet Runway oferuje łatwą podstawę bazującą na najlepszych praktych, która pozwolić Ci rozpocząć pracę w mgnieniu oka. Jeśli zdecydujesz się zainstalować pakiet Runway, możesz opcjonalnie wybrać podstawowe klocki zwane Modułami Runway, aby poprawić swoje strony.

Dołączone z pakietem Runway: Strona domowa, strona Jak rozpocząć pracę, strona Instalowanie Modułów.
Opcjonalne moduły:Górna nawigacja, Mapa strony, Formularz kontaktowy, Galeria.
]]>
+ Chcę rozpocząć z prostą stroną + + Pakiet "Runway" to prosta strona, dostarczająca kilku podstawowych typów dokumentów i szablonów. Instalator może automatycznie zainstalować pakiet Runway za Ciebie, + ale możesz w łatwy sposób edytować, rozszerzyć lub usunąć go. Nie jest on potrzebny i możesz doskonale używać Umbraco bez niego. + Jednakże pakiet Runway oferuje łatwą podstawę, bazującą na najlepszych praktykach, która pozwolić Ci rozpocząć pracę w mgnieniu oka. + Jeśli zdecydujesz się zainstalować pakiet Runway, możesz opcjonalnie wybrać podstawowe klocki zwane Modułami Runway, aby poprawić swoje strony. +

+ + Dołączone z pakietem Runway: Strona domowa, strona Jak rozpocząć pracę, strona Instalowanie Modułów.
+ Opcjonalne moduły:Górna nawigacja, Mapa strony, Formularz kontaktowy, Galeria. +
+ ]]>
Co to jest pakiet Runway Krok 1/5 Akceptacja licencji Krok 2/5: Konfiguracja bazy danych Krok 3/5: Sprawdzanie uprawnień plików Krok 4/5: Sprawdzanie zabezpieczeń Umbraco - Krok 5/5: Umbraco jest gotowy do pracy + Krok 5/5: Umbraco jest gotowe do pracy Dziękujemy za wybór Umbraco - Przeglądaj swoją nową stronę Pakiet Runway został zainstalowany, zobacz zatem jak wygląda Twoja nowa strona.]]> - Dalsza pomoc i informacje Zaczerpnij pomocy z naszej nagrodzonej społeczności, przeglądaj dokumentację lub obejrzyj niektóre darmowe filmy o tym jak budować proste strony, jak używać pakietów i szybki przewodnik po terminologii Umbraco]]> + Przeglądaj swoją nową stronę + Pakiet Runway został zainstalowany, zobacz zatem jak wygląda Twoja nowa strona.]]> + Dalsza pomoc i informacje + Zaczerpnij pomocy z naszej nagrodzonej społeczności, przeglądaj dokumentację lub obejrzyj niektóre darmowe filmy o tym, jak budować proste strony, jak używać pakietów i szybki przewodnik po terminologii Umbraco]]> Umbraco %0% zostało zainstalowane i jest gotowe do użycia - plik web.config i zaktualizować klucz AppSetting o nazwie UmbracoConfigurationStatus na dole do wartości '%0%'.]]> - rozpocząć natychmiast klikając przycisk "Uruchom Umbraco" poniżej.
Jeżeli jesteś nowy dla Umbraco znajdziesz mnóstwo materiałów na naszych stronach "jak rozpocząć".]]>
- Uruchom Umbraco Aby zarządzać swoją stroną po prostu otwórz zaplecze Umbraco i zacznij dodawać treść, aktualizować szablony i style lub dodawaj nową funkcjonalność]]> + plik web.config i zaktualizować klucz AppSetting o nazwie UmbracoConfigurationStatus na dole do wartości '%0%'.]]> + rozpocząć natychmiast klikając przycisk "Uruchom Umbraco" poniżej.
Jeżeli jesteś nowy dla Umbraco + znajdziesz mnóstwo materiałów na naszych stronach "jak rozpocząć".]]>
+ Uruchom Umbraco + Aby zarządzać swoją stroną po prostu otwórz zaplecze Umbraco i zacznij dodawać treść, aktualizować szablony i style lub dodawaj nową funkcjonalność]]> Połączenie z bazą danych nie zostało ustanowione. Umbraco wersja 3 Umbraco wersja 4 Zobacz - Umbraco %0% dla świżej instalacji lub aktualizacji z wersji 3.0.

Wciśnij "dalej", aby rozpocząć proces konfigruacji.]]>
+ Umbraco %0% dla świeżej instalacji lub aktualizacji z wersji 3.0. +

+ Wciśnij "dalej", aby rozpocząć proces konfigruacji.]]>
Kod języka Nazwa języka - TRANSLATE ME: 'You've been idle and logout will automatically occur in' - TRANSLATE ME: 'Renew now to save your work' + Z powodu bezczynności na stronie, nastąpi automatyczne wylogowanie + Wznów sesję teraz, aby zapisać swoją pracę + Szczęśliwej super niedzieli + Szczęśliwego maniakalnego poniedziałku + Szczęśliwego świetnego wtorku + Szczęśliwej niesamowitej środy + Szczęśliwego wyjątkowego czwartku + Szczęśliwego odjechanego piątku + Szczęśliwej cudownej soboty + Zaloguj się poniżej + Zaloguj się z + Sesja wygasła © 2001 - %0%
umbraco.com

]]>
- Witamy w Umbraco. Wprowadź swój login oraz hasło w polach poniżej: + Zapomniałeś hasła? + E-mail z linkiem do zresetowania hasła zostanie wysłany na podany adres + E-mail z instrukcjami do zresetowania hasła zostanie wysłany, jeśli zgadza się z naszą bazą danych + Powrót do formularza logowania + Proszę wpisać nowe hasło + Twoje hasło zostało zmienione + Link, na który kliknąłeś jest niewłaściwy lub wygasł + Umbraco: Resetowanie hasła + + Twoja nazwa użytkownika do zalogowania się w Umbraco back-office to: %0%

Kliknij tutaj, aby zresetować Twoje hasło lub kopiuj/wklej ten URL w przeglądarce:

%1%

]]> +
Panel zarządzania @@ -451,34 +826,107 @@ Możesz dodać dodatkowe języki w menu "Języki" po lewej stronie.]]> Wybierz stronę powyżej... %0% zostało skopiowane do %1% Wybierz, gdzie dokument %0% ma zostać skopiowany - %0% został przesunięty do %1% + %0% został przeniesiony do %1% Wskaż gdzie dokument %0% ma zostać przeniesiony - został wybrany jako korzeń nowej treści, kliknik 'ok' poniżej. + został wybrany jako korzeń nowej zawartości, kliknik 'ok' poniżej. Nie wskazano węzła, proszę wybrać węzeł z listy powyżej przed kliknięciem "ok" Typ bieżącego węzła nie jest dozwolony dla wybranego węzła Bieżący węzeł nie może być przeniesiony do jednej z jego podstron - TRANSLATE ME: 'The action isn't allowed since you have insufficient permissions on 1 or more child documents.' + Bieżący węzeł nie może istnieć w korzeniu + Działanie jest niedozwolone, ponieważ nie masz odpowiednich uprawnień w 1 lub więcej dokumentach dzieci. + Powiąż skopiowane elementy z oryginalnymi Edytuj powiadomienie dla %0% - - Witaj %0%

To jest automatyczny e-mail, wysłany aby poinformować Cię, że polecenie '%1%' zostało wykonane na stronie '%2%' przez użytkownika '%3%'

Podsumowanie zmian:

%6%

Miłego dnia!

Pozdrowienia od robota Umbraco

]]>
+ To jest automatyczny e-mail, wysłany, aby poinformować Cię, że polecenie '%1%' + zostało wykonane na stronie '%2%' + przez użytkownika '%3%'. + + Możesz dalej edytować pod adresem http://%4%/#/content/content/edit/%5% + + Miłego dnia! + + Pozdrowienia od robota Umbraco + ]]> + Witaj %0%

+ +

To jest automatyczny e-mail, wysłany, aby poinformować Cię, że polecenie '%1%' + zostało wykonane na stronie '%2%' + przez użytkownika '%3%' +

+ +

+

Podsumowanie zmian:

+ + %6% +
+

+ + + +

Miłego dnia!

+ Pozdrowienia od robota Umbraco +

]]>
[%0%] Powiadomienie o %1% wykonane na %2% Powiadomienie - Wskaż pakiet z twojego komputera, poprzez kliknięcie na przycisk 'Przeglądaj' i wskaż gdzie jest zapisany. Pakiety Umbraco przeważnie posiadają rozszerzenie ".umb" lub ".zip". + + i wskaż gdzie jest zapisany. Pakiety Umbraco przeważnie posiadają rozszerzenie ".umb" lub ".zip". + ]]> + Upuść, aby załadować + lub kliknij tutaj, aby wybrać pliki + Załaduj pakiet + Zainstaluj lokalny pakiet poprzez wybranie go ze swojego komputera. Instaluj jedynie te pakiety, z zaufanych i znanych Tobie źródeł + Załaduj kolejny pakiet + Anuluj i załaduj kolejny pakiet + Licencja + Zgadzam się + zasady użytkowania + Zainstaluj pakiet + Zakończ + Zainstalowane pakiety + Nie masz żadnych zainstalowanych pakietów + 'Pakiety' w prawym górnym rogu ekranu]]> + Szukaj pakietów + Wyniki dla + Nie mogliśmy znaleźć niczego dla + Spróbuj wyszukać kolejny pakiet lub przeszukaj kategorie pakietów + Popularne + Nowe wydania + ma + punktów karmy + Informacja + Właściciel + Kontrybutor + Utworzone + Obecna wersja + wersja .NET + Pobrania + Polubienia + Zgodność + Według raportów członków społeczności, ten pakiet jest zgodny z następującymi wersjami Umbraco. Pełna zgodność nie może być zagwarantowana dla wersji zaraportowanych poniżej 100% + Zewnętrzne źródła Autor Demonstracja Dokumentacja Metadane pakietu Nazwa pakietu Pakiet nie zawiera żadnych elementów -
Możesz bezpiecznie go usunąć z systemu poprzez kliknięcie na przycisku "odinstaluj pakiet"]]>
+
+ Możesz bezpiecznie go usunąć z systemu poprzez kliknięcie na przycisku "odinstaluj pakiet"]]>
Nie ma dostępnych aktualizacji Opcje pakietu Opis pakietu @@ -487,13 +935,26 @@ Miłego dnia!]]> Pakiet został odinstalowany Pakiet został pomyślnie odinstalowany Odinstaluj pakiet - span style="color: Red; font-weight: bold;">Uwaga: wszystkie elementy, media itp w zależności od elementów, które usuwasz przestaną działać, i mogą spowodować niestabilność systemu, więc odinstalowuj z uwagą. W przypadku problemów kontaktuj się z autorem pakietu.]]> + + Uwaga: wszystkie elementy, media, itp. w zależności od elementów, które usuwasz, przestaną działać i mogą spowodować niestabilność systemu, + więc odinstalowuj z uwagą. W przypadku problemów skontaktuj się z autorem pakietu.]]> Pobierz aktualizację z repozytorium Aktualizuj pakiet Instrukcja aktualizacji Jest dostępna aktualizacja dla tego pakietu. Możesz ją pobrać wprost z repozytorium pakietów Umbraco. Wersja pakietu + Historia wersji pakietu Odwiedź stronę pakietu + Pakiet jest już zainstalowany + Ten pakiet nie może być zainstalowany, ponieważ wymaga Umbraco w wersji przynajmniej %0% + Odinstalowywanie... + Pobieranie... + Importowanie... + Instalowanie... + Restartowanie, proszę czekać... + Wszystko skończone, Twoja przeglądarka się teraz odświeży, proszę czekać... + Proszę kliknąć Zakończ, aby zakończyć instalację i przeładować stronę. + Wgrywanie pakietu... Wklej z zachowaniem formatowania (Nie zalecane) @@ -506,46 +967,71 @@ Miłego dnia!]]> użyj grup członkowskich Umbraco ]]> Musisz utworzyć grupę przed użyciem uwierzytelniania opartego na rolach Strona błędu - Używana kiedy użytkownicy są zalogowani, ale nie posiadają dostępu + Używana, kiedy użytkownicy są zalogowani, ale nie posiadają dostępu Wybierz sposób ograniczenia dostępu do tej strony %0% jest teraz zabezpieczona Ze strony %0% usunięto zabezpieczenia dostępu Strona logowania Wybierz stronę z formularzem logowania Usuń ochronę - Wybierz strony, które zawierają formularz logowania i komunikatów o błędach + Wybierz strony, które zawierają formularz logowania i komunikaty o błędach Wybierz role, które mają mieć dostęp do tej strony - Ustaw login i hasło dla tej strony + Ustaw nazwę użytkownika i hasło dla tej strony Ochrona pojedynczego użytkownika - Jeżeli chcesz ustawić prostą ochronę używając pojedynczego loginu i hasła + Jeżeli chcesz ustawić prostą ochronę używając pojedynczej nazwy użytkownika i hasła - %0% nie może zostać opublikowany, ze względu na odwołanie akcji przez rozszerzenie firm trzecich + + + + + + + Dołącz nieopublikowane węzły pochodne (dzieci) Publikacja w toku - proszę czekać... Opublikowano %0% z %1% stron... %0% został opublikowany %0% oraz podstrony zostały opublikowane - Publikuj %0% z wszytkimi podstronami - OK , aby publikować % 0% i spowodować upublicznienie całej treści.

Możesz opublikować tą stronę wraz ze wszystkimi podstronami zaznaczając poniżej publikuj wszystkie węzły pochodne]]>
+ Publikuj %0% ze wszytkimi podstronami + OK , aby publikować % 0% i spowodować upublicznienie całej zawartości.

+ Możesz opublikować tą stronę wraz ze wszystkimi podstronami zaznaczając poniżej publikuj wszystkie węzły pochodne + ]]>
+ + + Nie skonfigurowałeś żadnych zaakceptowanych kolorów - Dodaj link zewnętrzny - Dodaj link wewnętrzny - Dodaj - Opis - Strona wewnętrzna - URL - Przesuń w dół - Przesuń do góry + Wpisz link zewnętrzny + Wybierz link wewnętrzny + Podpis + Link Otwórz w nowym oknie - Usuń link + Wpisz nowy podpis + Wpisz link + + + Resetuj + Zdefiniuj przycięcie + Ustaw alias dla przycięcia, a także jego domyślną szerokość i długość + Zapisz przycięcie + Dodaj nowe przycięcie Aktualna wersja - Red tekst nie będzie pokazany w wybranej wersji, zielony tekst został dodany]]> + Czerwony tekst nie będzie pokazany w wybranej wersji, zielony tekst został dodany]]> Dokument został przywrócony - Tu widać wybraną wersję jako html, jeżeli chcesz zobaczyć różnicę pomiędzy 2 wersjami w tym samym czasie, użyj podglądu róznic + Tu widać wybraną wersję jako html, jeżeli chcesz zobaczyć różnicę pomiędzy 2 wersjami w tym samym czasie, użyj podglądu różnic Cofnij do Wybierz wersję Zobacz @@ -566,6 +1052,15 @@ Miłego dnia!]]> Statystyki Tłumaczenie Użytkownicy + Pomoc + Formularze + Analizy + + + idź do + Tematy pomocy dla + Rozdziały filmów dla + Najlepsze filmy-samouczki Umbraco Domyślny szablon @@ -575,19 +1070,33 @@ Miłego dnia!]]> Typ węzła Typ Arkusz styli + Skrypt Właściwości arkusza styli Zakładka Nazwa zakładki Zakładki + Włączono Główny Typ Treści + Ten Typ Treści używa + jako Główny Typ Treści. Zakładki Głównego Typu Treści nie są wyświetlone i mogą być edytowane jedynie w samym Głównym Typie Treści + Żadne właściwości nie zostały zdefiniowane dla tej zakładki. Kliknij w link "dodaj nową właściwość", który znajduje się na górze strony, aby stworzyć nową właściwość. + Główny Typ Dokumentu + Stwórz pasujący szablon + Dodaj ikonę - Sort order - Creation date + Porządek sortowania + Data utworzenia Sortowanie zakończone. - Przesuń poszczególne elementy w górę oraz w dół aż będą w odpowiedniej kolejności, lub kliknij na nagłówku kolumny aby posortować całą kolekcję elementów -
Nie zamykaj tego okna podczas sortowania]]>
+ Przesuń poszczególne elementy w górę oraz w dół aż będą w odpowiedniej kolejności lub kliknij na nagłówku kolumny, aby posortować całą kolekcję elementów + + Walidacja + Błędy walidacji muszą zostać naprawione zanim element będzie mógł być zapisany + Nie powiodło się + Niewystarczające uprawnienia użytkownika, nie można zakończyć operacji + Anulowane + Operacja została anulowana przez dodatek firmy trzeciej Publikacja została przerwana poprzez dodatek firmy trzeciej Właściwość typu już istnieje Właściwość typu została utworzona @@ -596,34 +1105,39 @@ Miłego dnia!]]> Zakładka została zapisana Zakładkę utworzono Zakładkę usunięto - Usunięto zakładkę o id:%0% + Usunięto zakładkę o ID:%0% Arkusz stylów nie został zapisany Arkusz stylów został zapisany Arkusz stylów został zapisany bez żadnych błędów Typ danych został zapisany Element słownika został zapisany - Publikacja nie powiodła się ponieważ rodzic węzła nie jest opublikowany + Publikacja nie powiodła się, ponieważ rodzic węzła nie jest opublikowany Treść została opublikowana i jest widoczna na stronie Treść została zapisana Pamiętaj, aby opublikować, aby zmiany były widoczne Wysłano do zatwierdzenia Zmiany zostały wysłane do akceptacji + Media zostały zapisane + Media zostały poprawnie zapisane Członek został zapisany Właściwość arkusza stylów została zapisana Arkusz stylów został zapisany Szablon został zapisany Błąd przy zapisie danych użytkownika (sprawdź log) Użytkownik został zapisany + Typ użytkownika został zapisany Plik nie został zapisany Plik nie został zapisany. Sprawdź uprawnienia dostępu do pliku Plik został zapisany Plik został zapisany bez żadnych błędów Język został zapisany - Skrypt python nie został zapisany - Wystąpiły błędy, skrypt nie może zostać zapisany - Skrypt python został zapisany - Brak błędów w skrypcie python + Typ mediów został zapisany + Typ członka został zapisany + Skrypt Python nie został zapisany + Wystąpiły błędy, skrypt Python nie może zostać zapisany + Skrypt Python został zapisany + Brak błędów w skrypcie Python Szablon nie został zapisany Proszę się upewnić że nie ma dwóch szablonów o tym samym aliasie Szablon został zapisany @@ -633,6 +1147,16 @@ Miłego dnia!]]> Nie można zapisać XSLT, sprawdź uprawnienia dostępu do pliku Zapisano XSLT XSLT nie zawiera błedów + Cofnięto publikację treści + Częściowy Widok został zapisany + Częściowy Widok został zapisany bez błędów! + Częściowy Widok nie został zapisany + Wystąpił błąd podczas zapisywania pliku. + Widok skryptu został zapisany + Widok skryptu został zapisany bez błędów! + Widok skryptu nie został zapisany + Wystąpił błąd podczas zapisywania pliku. + Wystąpił błąd podczas zapisywania pliku. Używaj składni CSS np.: h1, .czerwonyNaglowek, .niebieskiTekst @@ -642,86 +1166,270 @@ Miłego dnia!]]> Podgląd Style + Edytuj szablon + + Sekcje Wstaw obszar zawartości Wstaw miejsce dla obszaru zawartości + + Wstaw + Wybierz, co chcesz wstawić do swojego szablonu + Wstaw element słownika - Wstaw makro - Wstaw pole strony Umbraco + Element słownika to miejsce, gdzie można wstawić przetłumaczony tekst, co ułatwia tworzenie projektów dla wielojęzycznych stron. + + Makro + + Makro to konfigurowalny komponent, który sprawdzi się + przy wielokrotnie używanych częściach Twojego projektu, kiedy potrzebujesz opcji dostarczenia parametrów, + takich jak galerie, formularze, czy listy. + + + Wartość + Wyświetla wartość danego pola z bieżącej strony z opcjami modyfikacji wartości lub powrotu do alernatywnych wartości. + + Częściowy Widok + + Częściowy Widok to oddzielny szablon pliku, który może być renderowany wewnątrz innego + szablonu, sprawdzi się w ponownym używaniu markupu lub oddzielaniu złożonych szablonów do oddzielnych plików. + + Główny szablon - Szybki przewodnik po tagach szablonu Umbraco + Brak głównego szablonu + Brak głównego + + Renderuj szablon dziecka + + @RenderBody(). + ]]> + + + + Zdefiniuj nazwaną sekcję + + @section { ... }. Może być to renderowane w + określonym obszarze rodzica tego szablonu, poprzez użycie @RenderSection. + ]]> + + + Renderuj nazwaną sekcję + + @RenderSection(name). + To renderuje obszar w szablonie dziecka, który jest opakowany w odpowiednią definicję @section [name]{ ... }. + ]]> + + + Nazwa Sekcji + Sekcja jest wymagana + + Jeśli wymagana, szablon dziecka musi zawierać definicję @section, w przeciwnym przypadku wystąpi błąd. + + + + Konstruktor zapytań + Zbuduj zapytanie + Element zwrócony, w + + Chcę + całą zawartość + zawartość typu "%0%" + z + mojej strony + gdzie + i + + jest + nie jest + przed + przed (włączając wybraną datę) + po + po (włączając wybraną datę) + równa się + nie równa się + zawiera + nie zawiera + większe niż + większe lub równe niż + mniejsze niż + mniejsze lub równe niż + + ID + Nazwa + Data Utworzenia + Data Ostatniej Aktualizacji + + sortuj + rosnąco + malejąco + Szablon + + - Choose type of content - Choose a layout - Add a row - Add content - Drop content - Settings applied + Rich Text Editor + Image + Macro + Embed + Headline + Quote + Wybierz typ treści + Wybierz układ + Dodaj wiersz + Dodaj zawartość + Upuść zawartość + Zastosowano ustawienia - This content is not allowed here - This content is allowed here + Ta zawartość nie jest tu dozwolona + Ta zawartość jest tu dozwolona - Click to embed - Click to insert image - Image caption... - Write here... + Kliknij, żeby osadzić + Kliknij, żeby dodać obraz + Podpis obrazu... + Pisz tutaj... - Grid Layouts - Layouts are the overall work area for the grid editor, usually you only need one or two different layouts - Add Grid Layout - Adjust the layout by setting column widths and adding additional sections - Row configurations - Rows are predefined cells arranged horizontally - Add row configuration - Adjust the row by setting cell widths and adding additional cells + Układy Siatki + Układy to ogólne pole pracy dla edytora siatki, przeważnie będziesz potrzebować tylko jednego lub dwóch różnych układów + Dodaj Układ Siatki + Dostosuj układ przez ustawienie szerokości kolumn i dodanie dodatkowych sekcji + Konfiguracja rzędów + Rzędy to predefiniowane komórki ułożone poziomo + Dodaj konfigurację rzędu + Dostosuj rząd poprzez ustawienie szerokości komórki i dodanie dodatkowych komórek - Columns - Total combined number of columns in the grid layout + Kolumny + Całkowita liczba wszystkich kolumn w układzie siatki - Settings - Configure what settings editors can change + Ustawienia + Konfiguruj jakie ustawienia może zmieniać edytor - Styles - Configure what styling editors can change + Style + Konfiguruj jakie style może zmieniać edytor - Settings will only save if the entered json configuration is valid + Ustawienia zostaną zapisane tylko jeśli wprowadzona konfiguracja json jest prawidłowa - Allow all editors - Allow all row configurations + Zezwól wszystkim edytorom + Zezwól na konfigurację wszystkich rzędów + Ustaw jako domyślne + Wybierz dodatkowe + Wybierz domyślne + zostały dodane + + + + Kompozycje + Nie dodałeś żadnych zakładek + Dodaj nową zakładkę + Dodaj kolejną zakładkę + Odziedziczone z + Dodaj właściwość + Wymagana etykieta + + Włącz widok listy + Konfiguruje element treści, aby pokazać sortowaną i możliwą do przeszukiwania listę jego dzieci, dzieci nie będą wyświetlone w drzewie + + Dozwolone Szablony + Wybierz, które szablony edytorzy będą mogli używać dla zawartości tego typu + + Zezwól jako korzeń + Zezwól edytorom na tworzenie zawartości tego typu w korzeniu drzewa treści + Tak - zezwól na zawartość tego typu w korzeniu + + Dozwolone typy węzłów dzieci + Zezwól na tworzenie zawartości określonych typów pod zawartością tego typu + + Wybierz węzeł dziecka + + Odziedzicz zakładki i właściwości z istniejącego typu dokumentu. Nowe zakładki będą dodane do bieżącego typu dokumentu lub złączone jeśli zakładka z identyczną nazwą już istnieje. + Ten typ zawartości jest używany w kompozycji, przez co sam nie może być złożony. + Brak możliwych typów zawartości do użycia jako kompozycja. + + Dostępni edytorzy + Użyj ponownie + Ustawienia edytora + + Konfiguracja + + Tak, usuń + + zostało przeniesione poniżej + zostało skopiowane poniżej + Wybierz folder do przeniesienia + Wybierz folder do skopiowania + do w strukturze drzewa poniżej + + Wszystkie typy Dokumentów + Wszystkie Dokumenty + Wszystkie elementy mediów + + używający tego typu dokumentu zostanie usunięty na stałe, proszę potwierdź czy chcesz usunąć także te. + używający tych mediów zostanie usunięty na stałe, proszę potwierdź czy chcesz usunąć także te. + używający tego typu członka zostanie usunięty na stałe, proszę potwierdź czy chcesz usunąć także te + + i wszystkie dokumenty, używające tego typu + i wszystkie media, używające tego typu + i wszyscy członkowie, używający tego typu + + używający tego edytora będzie zaktualizowany o nowe ustawienia + + Członek może edytować + Pokaż na profilu członka + + + + Dodaj pole zastępcze + Pole zastępcze + Dodaj domyślną wartość + Domyślna wartość Pole alternatywne Tekst alternatywny Wielkość liter + Kodowanie Wybierz pole Konwertuj złamania wiersza + Tak, konwertuj złamania wiersza Zamienia złamania wiersza na html-tag &lt;br&gt; + Niestandardowe Pola Tak, tylko data + Format i kodowanie Formatuj jako datę + Formatuj wartość jako datę lub jako datę i czas, zgodnie z aktywną kulturą Kodowanie HTML Zamienia znaki specjalne na ich odpowiedniki HTML Zostanie wstawione za wartością pola Zostanie wstawione przed wartością pola małe znaki + Modyfikuj dane wyjściowe Nic + Próbka danych wyjściowych Wstaw za polem Wstaw przed polem Rekurencyjne - Usuń znaki paragrafu - Usuwa wszystkie &lt;P&gt; z początku i końca tekstu - WIELKIE LITERY + Tak, spraw, aby było to rekurencyjne + Separator + Standardowe Pola + Wielkie litery Kodowanie URL Formatuje znaki specjalne w URLach - Zostanie użyte tylko wtedy gdy wartość pola jest pusta - To pole jest używane tylko wtedy gdy główne pole jest puste + Zostanie użyte tylko wtedy, gdy wartość pola jest pusta + To pole jest używane tylko wtedy, gdy główne pole jest puste Tak, z czasem. Separator: Zadania przypisane dla Ciebie - przypisane do Ciebie
. Aby zobaczyć szczegółowe informacje wraz z komentarzami, kliknij na "Szczegóły" lub po prostu na nazwę strony. Możesz również pobrać stronę jako XML poprzez kliknięcie na link "Pobierz XML".
Aby zamknąć zadanie tłumaczenia, proszę w podglądzie szczegółowym kliknąć przycisk "Zamknij".]]> + przypisane do Ciebie
. Aby zobaczyć szczegółowe informacje wraz z komentarzami, kliknij na "Szczegóły" lub po prostu na nazwę strony. + Możesz również pobrać stronę jako XML poprzez kliknięcie na link "Pobierz XML".
+ Aby zamknąć zadanie tłumaczenia, proszę kliknąć "Zamknij" w podglądzie szczegółowym. + ]]> zamknij zadanie Szczegóły tłumaczenia Pobierz wszystkie tłumaczenia jako XML @@ -729,19 +1437,37 @@ Miłego dnia!]]> Pobierz XML DTD Pola Włączając podstrony - to jest automatyczny mail informujący że dokument %1% został zgłoszony jako wymagający tłumaczenia na '%5%' przez %2%. Wejdź na http://%3%/translation/details.aspx?id=%4% aby edytować. Lub zaloguj się do Umbraco aby zobaczyć wszystkie zadania do tłumaczenia http://%3%.
Życzę miłego dnia! Umbraco robot]]>
+ [%0%] Tłumaczeń dla %1% Nie znaleziono tłumaczy. Proszę utwórz tłumacza przed wysłaniem zawartości do tłumaczenia Zadania stworzone przez Ciebie - stworzone przez Ciebie. Aby zobaczyć szczegółowe informacje wraz z komentarzami, kliknij na "Szczegóły" lub na nazwę strony. Możesz również pobrać stronę jako XML poprzez kliknięcie na link "Pobierz XML".
Aby zamknąć zadanie tłumaczenia, proszę w podglądzie szczegółowym kliknąć przycisk "Zamknij".]]>
+ stworzone przez Ciebie. Aby zobaczyć szczegółowe informacje wraz z komentarzami, + kliknij na "Szczegóły" lub na nazwę strony. Możesz również pobrać stronę jako XML poprzez kliknięcie na link "Pobierz XML". + Aby zamknąć zadanie tłumaczenia, proszę kliknąć "Zamknij" w podglądzie szczegółowym. + ]]> Strona '%0%' została wysłana do tłumaczenia + Proszę wybrać język, na jaki zawartość powinna zostać przetłumaczona Wyślij stronę '%0%' do tłumaczenia Przypisane przez Zadanie otwarte Liczba słów Przetłumacz na Tłumaczenie zakończone. - Możesz podglądnąć stronę, którą właśnie przetłumaczyłeś, poprzez kliknięcie poniżej. Jeżeli strona oryginalna istnieje, możesz porównać obie wersje + Możesz podejrzeć stronę, którą właśnie przetłumaczyłeś, poprzez kliknięcie poniżej. Jeżeli strona oryginalna istnieje, możesz porównać obie wersje Błąd tłumaczenia, plik XML może być uszkodzony Opcje tłumaczeń Tłumacz @@ -754,8 +1480,8 @@ Miłego dnia!]]> Typy danych Słownik Zainstalowane pakiety - TRANSLATE ME: 'Install skin' - TRANSLATE ME: 'Install starter kit' + Zainstaluj skórkę + Zainstaluj Starter Kit Języki Zainstaluj pakiet lokalny Makra @@ -765,21 +1491,25 @@ Miłego dnia!]]> Role Typ członka Typy dokumentów + Typy relacji Pakiety Pakiety Pliki Python Zainstaluj z repozytorium Zainstaluj Runway Moduły Runway - TRANSLATE ME: 'Scripting Files' + Pliki skryptowe Skrypty Arkusze stylów Szablony Pliki XSLT + Analizy + Częściowe Widoki + Pliki Makro Częściowych Widoków Aktualizacja jest gotowa - Gotowe jest %0%, kliknij tutaj aby pobrać + Gotowe jest %0%, kliknij tutaj, aby pobrać Brak połączenia z serwerem Wystąpił błąd podczas sprawdzania aktualizacji. Przeglądnij trace-stack dla dalszych informacji @@ -787,8 +1517,10 @@ Miłego dnia!]]> Administrator Pole kategorii Zmień hasło! - TRANSLATE ME: 'You can change your password for accessing the Umbraco Back Office by filling out the form below and click the 'Change Password' button' - Zawartość + Nowe hasło + Potwierdź nowe hasło + Możesz zmienić swoje hasło w Umbraco Back Office przez wypełnienie formularza poniżej i kliknięcie przycisku "Zmień hasło" + Kanał zawartości Opis Wyłącz użytkownika Typ dokumentu @@ -799,22 +1531,173 @@ Miłego dnia!]]> Węzeł początkowy w bibliotece mediów Sekcje Wyłącz dostęp do Umbraco + Stare hasło Hasło + Zresetuj hasło Twoje hasło zostało zmienione! Proszę potwierdź nowe hasło! Wprowadź nowe hasło Nowe hasło nie może byc puste! - TRANSLATE ME: 'There was a difference between the new password and the confirmed password. Please try again!' - TRANSLATE ME: 'The confirmed password doesn't match the new password!' + Bieżące hasło + Bieżące hasło jest nieprawidłowe + Nowe hasło i potwierdzenie nowego hasła nie są identyczne. Spróbuj ponownie! + Potwierdzone hasło nie jest identyczne z nowym hasłem! Zastąp prawa dostępu dla węzłów potomnych Aktualnie zmieniasz uprawnienia dostępu do stron: Wybierz strony, którym chcesz zmienić prawa dostępu Przeszukaj wszystkie węzły potomne - Węzeł początkowy w treści + Węzeł początkowy w zawartości Nazwa użytkownika Prawa dostępu użytkownika - Typ - Typy użytkowników Pisarz + Tłumacz + Zmień + Twój profil + Twoja historia + Sesja wygaśnie za -
\ No newline at end of file + + Walidacja + Waliduj jako e-mail + Waliduj jako numer + Waliduj jako URL + ...lub wpisz niestandardową walidację + Pole jest wymagane + Wprowadź wyrażenie regularne + Musisz dodać przynajmniej + Możesz mieć jedynie + elementy + wybrane elementy + Niepoprawna data + To nie jest numer + Niepoprawny e-mail + + + + Wartość jest ustawiona na rekomendowaną wartość: '%0%'. + Wartość została ustawiona na '%1%' dla XPath '%2%' w pliku konfiguracyjnym '%3%'. + Oczekiwana jest wartość '%1%' dla '%2%' w pliku konfiguracyjnym '%3%', ale znaleziono '%0%'. + Znaleziono nieoczekiwaną wartość '%0%' dla '%2%' w pliku konfiguracyjnym '%3%'. + + + Niestandardowe błędy są ustawione na '%0%'. + Niestandardowe błędy są obecnie ustawione na '%0%'. Zaleca się ustawienie ich na '%1%' przed wypuszczeniem strony na produkcję. + Niestandardowe błędy zostały z powodzeniem ustawione na '%0%'. + + MacroErrors są ustawione na '%0%'. + MacroErrors są ustawione na '%0%' co uniemożliwi częściowe lub całkowite załadowanie stron w Twojej witrynie jeśli wystąpią jakiekolwiek błędy w makro. Korekta ustawi wartość na '%1%'. + MacroErrors są teraz ustawione na '%0%'. + + + Try Skip IIS Custom Errors jest ustawione na '%0%' a Ty używasz IIS w wersji '%1%'. + Try Skip IIS Custom Errors wynosi obecnie '%0%'. Zalecane jest ustawienie go na '%1%' dla Twojego IIS w wersji (%2%). + Try Skip IIS Custom Errors ustawiono z powodzeniem na '%0%'. + + + Plik nie istnieje: '%0%'. + '%0%' w pliku konfiguracyjnym '%1%'.]]> + Wystąpił błąd, sprawdź logi, aby wyświetlić pełen opis błędu: %0%. + + Członkowie - Suma XML: %0%, Suma: %1%, Suma niepoprawnych: %2% + Media - Suma XML: %0%, Suma: %1%, Suma niepoprawnych: %2% + Zawartość - Suma XML: %0%, Suma opublikowanych: %1%, Suma niepoprawnych: %2% + + Certifikat Twojej strony jest poprawny. + Błąd walidacji certyfikatu: '%0%' + Certyfikat SSL Twojej strony wygasł. + Certyfikat SSL Twojej strony wygaśnie za %0% dni. + Błąd pingowania adresu URL %0% - '%1%' + Oglądasz %0% stronę używając HTTPS. + appSetting 'umbracoUseSSL' został ustawiony na 'false' w Twoim pliku web.config. Po uzyskaniu dostępu do strony, używając HTTPS, powinieneś go ustawić na 'true'. + appSetting 'umbracoUseSSL' został ustawiony na '%0%' w Twoim pliku web.config, Twoje ciasteczka są %1% ustawione jako bezpieczne. + Nie można zaktualizaować ustawień 'umbracoUseSSL' w Twoim pliku web.config file. Błąd: %0% + + + Włącz HTTPS + Ustawia umbracoSSL na 'true' w appSettings pliku web.config. + appSetting 'umbracoUseSSL' jest teraz ustawione na 'true' w Twoim pliku web.config, Twoje ciasteczka będą oznaczone jako bezpieczne. + + Napraw + Nie można naprawić sprawdzenia z wartością typu porównania 'ShouldNotEqual'. + Nie można naprawić sprawdzenia z wartością typu porównania 'ShouldEqual' z wprowadzoną wartością. + Nie wprowadzono wartości do naprawy sprawdzenia. + + Tryb kompilacji debugowania jest wyłączony. + Tryb kompilacji debugowania jest obecnie włączony. Zaleca się wyłączenie tego ustawienia przed wypuszczeniem strony na produkcję. + Tryb komplikacji debugowania został wyłączony z powodzeniem. + + Tryb śledzenia jest wyłączony. + Tryb śledzenia jest obecnie włączony. Zaleca się wyłączenie tego ustawienia przed wypuszczeniem strony na produkcję. + Tryb śledzenia został wyłączony z powodzeniem + + Wszystkie foldery mają ustawione poprawne ustawienia. + + %0%.]]> + %0%. Jeśli nie będzie nic w nich pisane, żadne działania nie muszą być podejmowane.]]> + + Wszystkie pliki mają ustawione poprawne uprawnienia. + + %0%.]]> + %0%. Jeśli nie będzie nic w nich pisane, żadne działania nie muszą być podejmowane.]]> + + X-Frame-Options używany do kontrolowania czy strona może być IFRAME'owana przez inną został znaleziony.]]> + X-Frame-Options używany do kontrolowania czy strona może być IFRAME'owana przez inną nie został znaleziony.]]> + Ustaw nagłówek w Config + Dodaje wartość do sekcji httpProtocol/customHeaders pliku web.config, aby zapobiec IFRAME'owania strony przez inne witryny. + Ustawienie do tworzenia nagłówka, zapobiegającego IFRAME'owania strony przez inne witryny zostało dodane do Twojego pliku web.config. + Nie można zaktualizować pliku web.config. Błąd: %0% + + + %0%.]]> + Nie znaleziono żadnych nagłówków, ujawniających informacji o technologii strony. + + Nie znaleziono system.net/mailsettings w pliku Web.config. + Host nie jest skonfigurowany w sekcji system.net/mailsettings pliku Web.config. + Ustawienia SMTP są skonfigurowane poprawnie i serwis działa według oczekiwań. + Nie można połączyć się z serwerem SMTP skonfigurowanym z hostem '%0%' i portem '%1%'. Proszę sprawdzić ponownie, czy ustawienia system.net/mailsettings w pliku Web.config są poprawne. + + %0%.]]> + %0%.]]> + + + Wyłącz śledzenie URL + Włącz śledzenie URL + Oryginalny URL + Przekierowane do + Nie stworzono żadnych przekierowań + Kiedy nazwa opublikowanej strony zostanie zmieniona lub zostanie ona przeniesiona, zostanie stworzone automatyczne przekierowanie na nową stronę. + Usuń + Czy jesteś pewien, że chcesz usunąć przekierowanie z '%0%' do '%1%'? + Przekierowanie URL zostało usunięte. + Wystąpił błąd podczas usuwania przekierowania URL. + Czy jesteś pewien, że chcesz wyłączyć śledzenie URL? + Śledzenie URL zostało wyłączone. + Wystąpił błąd podczas wyłączania śledzenia URL, więcej informacji znajdziesz w pliku z logami. + Śledzenie URL zostało włączone. + Wystąpił błąd podczas włączania śledzenia URL, więcej informacji znajdziesz w pliku z logami. + + + Brak elementów słownika do wyboru + + + pozostało znaków + + diff --git a/WebCms/Umbraco/Config/Lang/pt.xml b/WebCms/Umbraco/Config/Lang/pt.xml index f02415e..1869346 100644 --- a/WebCms/Umbraco/Config/Lang/pt.xml +++ b/WebCms/Umbraco/Config/Lang/pt.xml @@ -2,7 +2,7 @@ The Umbraco community - http://our.umbraco.org/documentation/Extending-Umbraco/Language-Files + https://our.umbraco.com/documentation/Extending-Umbraco/Language-Files Gerenciar hostnames @@ -15,8 +15,6 @@ Desabilitar Esvaziar Lixeira Exportar Tipo de Documento - Exportar para .NET - Exportar para .NET Importar Tipo de Documento Importar Pacote Editar na Tela @@ -32,7 +30,6 @@ Enviar para Publicação Enviar para Tradução Classificar - Enviar para publicação Traduzir Atualizar @@ -377,7 +374,7 @@ Próximo para prosseguir.]]> Banco de dados não encontrado! Favor checar se a informação no "connection string" do "web.config" esteja correta.

Para prosseguir, favor editar o arquivo "web.config" (usando Visual Studio ou seu editor de texto favorito), role até embaixo, adicione a connection string para seu banco de dados com a chave de nome "UmbracoDbDSN" e salve o arquivo

-

Clique o botão tentar novamente quando terminar.
+

Clique o botão tentar novamente quando terminar.
Mais informações em como editar o web.config aqui.

]]>
Favor contatar seu provedor de internet ou hospedagem web se necessário. Se você estiver instalando em uma máquina ou servidor local é possível que você precise dessas informações por um administrador de sistema.]]> @@ -392,15 +389,6 @@ O usuário padrão foi desabilitado ou não tem acesso à Umbraco!

Nenhuma ação posterior precisa ser tomada. Clique Próximo para prosseguir.]]> A senha do usuário padrão foi alterada com sucesso desde a instalação!

Nenhuma ação posterior é necessária. Clique Próximo para prosseguir.]]> Senha foi alterada! - - Umbraco cria um usuário padrão com o login ('admin') e senha ('default'). É importante que a senha seja alterada para algo único. -

-

- Este passo irá checar a senha do usuário padrão e sugerir uma alteração se necessário. -

- - ]]>
Comece com o pé direito, assista nossos vídeos introdutórios Ao clicar no próximo botão (ou modificando o UmbracoConfigurationStatus no web.config), você aceita a licença deste software cmo especificado na caixa abaixo. Note que esta distribuição de Umbraco consiste em duas licenças diferentes, a licença aberta MIT para a framework e a licença de software livre (freeware) Umbraco que cobre o UI. Nenhum instalado ainda. @@ -576,7 +564,7 @@ Você pode remover com segurança do seu sistema clicando em "desinstalar pacote Proteção baseada em função usando grupos de membros do Umbraco.]]> - autenticação baseada em função.]]> + Você precisa criar um grupo de membros antes que possa usar autenticação baseada em função. Página de Erro Usado quando as pessoas estão logadas, mas não para ter acesso Escolha como restringir o acesso à esta página @@ -658,7 +646,7 @@ Você pode publicar esta página e todas suas sub-páginas ao selecionar pub Creation date Classificação concluída. Arraste os diferentes itens para cima ou para baixo para definir como os mesmos serão arranjados. Ou clique no título da coluna para classificar a coleção completa de itens -
Não feche esta janela durante a classificação]]>
+ Publicação foi cancelada por add-in de terceiros @@ -727,6 +715,12 @@ Você pode publicar esta página e todas suas sub-páginas ao selecionar pub Modelo + Rich Text Editor + Image + Macro + Embed + Headline + Quote Choose type of content Choose a layout Add a row @@ -783,8 +777,6 @@ Você pode publicar esta página e todas suas sub-páginas ao selecionar pub Inserir após campo Inserir antes do campo Recursivo - Remover etiquetas de parágrafo - Removerá quaisquer &lt;P&gt; do começo ao fim do texto Maiúscula Codificar URL Vai formatar caracteres especiais em URLs @@ -901,8 +893,6 @@ Para fechar a tarefa de tradução vá até os detalhes e clique no botão "Fech Nó Inicial do Conteúdo Nome de Usuário Permissões de usuário - Tipo de usuário - Tipos de usuários Escrevente
diff --git a/WebCms/Umbraco/Config/Lang/ru.xml b/WebCms/Umbraco/Config/Lang/ru.xml index 7dcc01b..4f477c6 100644 --- a/WebCms/Umbraco/Config/Lang/ru.xml +++ b/WebCms/Umbraco/Config/Lang/ru.xml @@ -1,8 +1,8 @@ - + The Umbraco community - http://our.umbraco.org/documentation/Extending-Umbraco/Language-Files + https://our.umbraco.com/documentation/Extending-Umbraco/Language-Files Языки и домены @@ -11,11 +11,15 @@ Изменить тип документа Копировать Создать + Создать шаблон содержимого + Создать группу Создать пакет Значение по умолчанию Удалить Отключить Очистить корзину + Включить + Экспорт Экспортировать Импортировать Импортировать пакет @@ -26,18 +30,48 @@ Публичный доступ Опубликовать Обновить узлы + Переименовать Опубликовать весь сайт + Установить разрешения для страницы '%0%' Восстановить Разрешения Откатить Направить на публикацию Направить на перевод + Задать группу + Задать права Сортировать - Направить на публикацию Перевести Скрыть + Разблокировать Обновить + + Содержимое + Администрирование + Структура + Другое + + + Разрешить доступ к назначению языков и доменов + Разрешить доступ к просмотру журнала истории узла + Разрешить доступ на просмотр узла + Разрешить доступ на смену типа документа для узла + Разрешить доступ к копированию узла + Разрешить доступ к созданию узлов + Разрешить доступ к удалению узлов + Разрешить доступ к перемещению узла + Разрешить доступ к установке и изменению правил публичного доступа для узла + Разрешить доступ к публикации узла + Разрешить доступ к изменению прав доступа к узлу + Разрешить доступ на возврат к предыдущим состояниям узла + Разрешить доступ к отправке узла на одобрение перед публикацией + Разрешить доступ к отправке узла на перевод данных + Разрешить доступ к изменению порядка сортировки узлов + Разрешить доступ к переводу данных узла + Разрешить доступ к сохранению узла + Разрешить доступ к созданию шаблона содержимого + Добавить новый домен Домен @@ -64,6 +98,15 @@ Наблюдать за + + Создать новый шаблон содержимого из '%0%' + Пустой + Выбрать шаблон содержимого + Шаблон содержимого создан + Создан шаблон содержимого из '%0%' + Другой шаблон содержимого с таким же названием уже существует + Шаблон содержимого — это предопределенный набор данных, который редактор может использовать для начального заполнения свойств при создании узлов содержимого + Завершено @@ -111,22 +154,24 @@ Нумерованный список Вставить макрос Вставить изображение + Повторить Править связи Вернуться к списку Сохранить Сохранить и построить модели - Сохранить и опубликовать - Сохранить и направить на публикацию + Опубликовать + Запланировать + Направить на публикацию Сохранить список Выбрать Выбрать текущую папку - выбранные Предварительный просмотр Предварительный просмотр запрещен, так как документу не сопоставлен шаблон Другие действия Выбрать стиль Показать стили Вставить таблицу + Отменить Чтобы сменить тип документа для выбранного узла, сначала выберите тип из списка разрешенных для данного расположения. @@ -158,7 +203,20 @@ Желтый Оранжевый Синий + Серо-синий + Серый + Коричневый + Светло-синий + Голубой + Светло-зеленый + Лайм + Янтарный + Рыжий Красный + Розовый + Лиловый + Темно-лиловый + Индиго Об этой странице @@ -168,6 +226,7 @@ Альтернативный текст (необязательно) Элементы списка Нажмите для правки этого элемента + Начальный узел содержимого Создано пользователем Исходный автор Дата создания @@ -177,30 +236,41 @@ Скрыть ВНИМАНИЕ: невозможно получить URL документа (внутренняя ошибка - подробности в системном журнале) Опубликовано + Это значение скрыто. Если Вам нужен доступ к просмотру этого значения, свяжитесь с администратором веб-сайта. + Это значение скрыто. Этот документ был изменен после публикации Этот документ не опубликован Документ опубликован Здесь еще нет элементов. В этом списке пока нет элементов. + Содержимое пока еще не добавлено + Участники пока еще не добавлены Ссылка на медиа-элементы Тип медиа-контента Группа участников - Член групп(ы) + Включен в группу(ы) Роль участника Тип участника + Вы уверены, что хотите удалить этот элемент? + Свойство '%0%' использует редактор '%1%', который не поддерживается для вложенного содержимого. + Не было сделано никаких изменений Дата не указана Заголовок страницы - Не является членом групп(ы) + Этот медиа-элемент не содержит ссылки + Доступные группы Свойства Этот документ опубликован, но скрыт, потому что его родительский документ '%0%' не опубликован ВНИМАНИЕ: этот документ опубликован, но его нет в глобальном кэше (внутренняя ошибка - подробности в системном журнале) Опубликовать + Опубликовано + Опубликовано (есть измененения) Состояние публикации Опубликовать Очистить дату ВНИМАНИЕ: этот документ опубликован, но его URL вступает в противоречие с документом %0% Это время будет соответствовать следующему времени на сервере: - Что это означает?]]> + Что это означает?]]> + Задать дату Порядок сортировки обновлен Для сортировки узлов просто перетаскивайте узлы или нажмите на заголовке столбца. Вы можете выбрать несколько узлов, удерживая клавиши "shift" или "ctrl" при пометке Статистика @@ -208,19 +278,22 @@ Заголовок (необязательно) Тип Скрыть + Распубликовано Распубликовать Последняя правка Дата/время редактирования документа Обновлено Удалить файл Ссылка на документ + Добавить новое поле текста + Удалить это поле текста Композиции Вы не добавили ни одной вкладки Добавить вкладку Добавить новую вкладку - Унаследован от + Унаследовано от Добавить свойство Обязательная метка @@ -239,6 +312,8 @@ Выбрать дочерний узел Унаследовать вкладки и свойства из уже существующего типа документов. Вкладки будут либо добавлены в создаваемый тип, либо в случае совпадения названий вкладок будут добавлены наследуемые свойства. Этот тип документов уже участвует в композиции другого типа, поэтому сам не может быть композицией. + Где используется эта композиция? + Эта композиция сейчас используется при создании следующих типов документов: В настоящее время нет типов документов, допустимых для построения композиции. Доступные редакторы @@ -270,11 +345,16 @@ , использующие этот редактор, будут обновлены с применением этих установок Участник может изменить + Разрешает редактирование значение данного свойства участником в своем профиле + Конфеденциальные данные + Скрывает значение это свойства от редакторов содержимого, не имеющих доступа к конфеденциальной информации Показать в профиле участника + Разрешает показ данного свойства в профиле участника для вкладки не указан порядок сортировки Где вы хотите создать новый %0% + Выберите тип документов, для которого нужно создать шаблон содержимого Создать в узле Новая папка Новый тип данных @@ -282,6 +362,13 @@ "Типы медиа-материалов".]]> Выберите тип и заголовок Тип документа без сопоставленного шаблона + Новый файл javascript + Новое пустое частичное представление + Новый макрос-представление + Новое частичное представление по образцу + Новый пустой макрос-представление + Новый макрос-представление по образцу + Новый макрос-представление (без регистрации макроса) Обзор сайта @@ -318,8 +405,13 @@ Открыть в новом окне? Свойства макроса Этот макрос не имеет редактируемых свойств + Заголовок ссылки + Ни одной пиктограммы не найдено Вставить Изменить разрешения для + Установить разрешения для + Установить права доступа к '%0%' для группы пользователей '%1%' + Выберите группу(ы) пользователей, для которых нужно установить разрешения Все элементы в корзине сейчас удаляются. Пожалуйста, не закрывайте это окно до окончания процесса удаления Корзина пуста Вы больше не сможете восстановить элементы, удаленные из корзины @@ -338,22 +430,31 @@ из нижеследующего списка. Список ограничивается идентификаторами, определенными в родительском шаблоне данного шаблона.]]> Кликните на изображении, чтобы увидеть полноразмерную версию Выберите элемент + Ссылка Просмотр элемента кэша Создать папку... Связать с оригиналом + Включая все дочерние Самое дружелюбное сообщество Ссылка на страницу - Открывает документ по ссылке в новом окне или вкладке браузера - Ссылка на медиа-файл + Открывать ссылку в новом окне или вкладке браузера + Ссылка на медиа-элемент + Ссылка на файл Выбрать медиа + Выбрать начальный узел медиа-библиотеки Выбрать значок Выбрать элемент Выбрать ссылку Выбрать макрос Выбрать содержимое + Выбрать начальный узел содержимого Выбрать участника Выбрать группу участников + Выбрать узел + Выбрать разделы + Выбрать пользователей Это макрос без параметров + Нет макросов, доступных для вставки в редактор Провайдеры аутентификации Подробное сообщение об ошибке Трассировка стека @@ -362,6 +463,7 @@ Разорвать связь учетную запись Выбрать редактор + Выбрать образец + Обзор словаря Допустим как корневой @@ -405,6 +508,16 @@ Сопоставленные стили CSS Показать метку Ширина и высота + ВСЕ типы свойств и данные в свойствах документов, + использующие этот тип данных, будут удалены безвозвратно, подтвердите их удаление + Да, можно удалить + и все типы свойств и данные свойств, использующие этот тип данных + Выберите папку, чтобы переместить в нее + в структуре дерева ниже + был перемещен в папку + + + Нет доступных элементов словаря Ваши данные сохранены, но для того, чтобы опубликовать этот документ, Вы должны сначала исправить следующие ошибки: @@ -463,6 +576,7 @@ Закрыть окно Примечание Подтвердить + Сохранять пропорции Сохранять пропорции Далее Копировать @@ -474,6 +588,7 @@ Удалено Удаляется... Дизайн + Словарь Размеры Вниз Скачать @@ -483,11 +598,17 @@ Email адрес Ошибка Найти + Начало + Общее + Группы Папка Высота Справка + Скрыть + История Иконка Импорт + Инфо Внутренний отступ Вставить Установить @@ -495,39 +616,50 @@ Выравнивание Название Язык + Конец Макет + Ссылки Загрузка БЛОКИРОВКА - Логин + Войти Выйти Выход Макрос Обязательно + Сообщение Больше Переместить Название Новый - Следующий + След Нет + Здесь пока нет элементов из + Выкл Ok Открыть + Вкл + Варианты или + Сортировка по Пароль Путь Идентификатор контейнера Минуточку... - Предыдущий + Пред Свойства Email адрес для получения данных Корзина Ваша корзина пуста Осталось + Удалить Переименовать Обновить Обязательное + Получить Повторить Разрешения + Публикация по расписанию Поиск К сожалению, ничего подходящего не нашлось Результаты поиска @@ -536,6 +668,7 @@ Показать страницу при отправке Размер Сортировать + Состояние Отправить Тип Что искать? @@ -543,7 +676,7 @@ Обновить Обновление Загрузить - URL + Интернет-ссылка Пользователь Имя пользователя Значение @@ -560,7 +693,8 @@ Сохранение... текущий выбрано - Внедрить + Встроить + Получить Цвет фона @@ -570,6 +704,12 @@ Текст + Редактор текста + Изображение + Макрос + Встраивание + Заголовок + Цитата Добавить содержимое Сбросить содержимое Добавить шаблон сетки @@ -603,6 +743,8 @@ Выбрать дополнительно Выбрать по-умолчанию добавлены + Оставьте пустым или задайте 0 для снятия лимита + Максимальное количество Страница @@ -649,9 +791,10 @@ Медиа - всего в XML: %0%, всего: %1%Б с ошибками: %2% Содержимое - всего в XML: %0%, всего опубликовано: %1%, с ошибками: %2% + Ошибка проверки адреса URL %0% - '%1%' + Сертификат Вашего веб-сайта отмечен как проверенный. Ошибка проверки сертификата: '%0%' - Ошибка проверки адреса URL %0% - '%1%' Сейчас Вы %0% просматриваете сайт, используя протокол HTTPS. Параметр 'umbracoUseSSL' в секции 'appSetting' установлен в 'false' в файле web.config. Если Вам необходим доступ к сайту по протоколу HTTPS, нужно установить данный параметр в 'true'. Параметр 'umbracoUseSSL' в секции 'appSetting' в файле установлен в '%0%', значения cookies %1% маркированы как безопасные. @@ -660,7 +803,7 @@ Разрешить HTTPS Устанавливает значение параметра 'umbracoSSL' в 'true' в секции 'appSettings' файла web.config. - Параметр 'umbracoUseSSL' в секции 'appSetting' файлf web.config теперь установлен в 'true', значения cookies будут промаркированы как безопасные. + Параметр 'umbracoUseSSL' в секции 'appSetting' файла web.config теперь установлен в 'true', значения cookies будут промаркированы как безопасные. Исправить Невозможно исправление по результату проверки значения на 'ShouldNotEqual'. @@ -691,10 +834,26 @@ X-Frame-Options, использующийся для управления возможностью помещать сайт в IFRAME на другом сайте.]]> X-Frame-Options, использующийся для управления возможностью помещать сайт в IFRAME на другом сайте, не обнаружен.]]> - Установить заголовок в файле конфигурации Добавляет значение в секцию 'httpProtocol/customHeaders' файла web.config, препятствующее возможному использованию этого сайта внутри IFRAME на другом сайте. Значение, добавляющее заголовок, препятствующий использованию этого сайта внутри IFRAME другого сайта, успешно добавлено в файл web.config. - Невозможно обновить файл web.config. Ошибка: %0% + + Установить заголовок в файле конфигурации + Невозможно обновить файл web.config. Ошибка: %0% + + X-Content-Type-Options, использующиеся для защиты от MIME-уязвимостей, обнаружены.]]> + X-Content-Type-Options, использующиеся для защиты от MIME-уязвимостей, не найдены.]]> + Добавляет значение в секцию httpProtocol/customHeaders файла web.config, препятствующее использованию MIME-уязвимостей. + Значение, добавляющее заголовок, препятствующий использованию MIME-уязвимостей, успешно добавлено в файл web.config. + + Strict-Transport-Security, известный также как HSTS-header, обнаружен.]]> + Strict-Transport-Security не найден.]]> + Добавляет заголовок 'Strict-Transport-Security' и его значение 'max-age=10886400; preload' в секцию httpProtocol/customHeaders файла web.config. Применяйте этот способ только в случае, если доступ к Вашим сайтам будет осуществляться по протоколу https как минимум ближайшие 18 недель. + Заголовок HSTS-header успешно добавлен в файл web.config. + + X-XSS-Protection обнаружен.]]> + X-XSS-Protection не найден.]]> + Добавляет заголовок 'X-XSS-Protection' и его значение '1; mode=block' в секцию httpProtocol/customHeaders файла web.config. + Заголовок X-XSS-Protection успешно добавлен в файл web.config. +
+ + + + + + + + + + + + + + +
+
+
+ + + + +
+ + + + +
+

+ Запрошен сброс пароля +

+

+ Ваше имя пользователя для входа в административную панель Umbraco: %0% +

+

+ + + + + + +
+ + Нажмите на эту ссылку для того, чтобы сбросить пароль + +
+

+

Если Вы не имеете возможности нажать на сслыку, скопируйте следующий адрес (URL) и вставьте в адресную строку Вашего браузера:

+ + + + +
+ + %1% + +
+

+
+
+


+
+
+ + + ]]> @@ -879,6 +1120,7 @@ или нажмите сюда, чтобы выбрать файлы Разрешены только типы файлов: Максимально допустимый размер файла: + Начальный узел медиа Создать нового участника @@ -919,42 +1161,129 @@ Удачи! Генератор уведомлений Umbraco. - ]]> - Здравствуйте, %0%

- -

Это автоматически сгенерированное уведомление. Операция '%1%' - была произведена на странице '%2%' - пользователем '%3%'.

- - - -

-

Сводка обновлений:

- - %6% -
-

- - - -

Удачи!

Генератор уведомлений Umbraco -

]]>
+ ]]> + + + + + + + + + + + + + +
+ + + + + +
+ +
+ +
+
+ + + + + + +
+
+
+ + + + +
+ + + + +
+

+ Здравствуйте, %0%, +

+

+ Это автоматически сгенерированное сообщение, отправленное, чтобы уведомить Вас о том, что операция '%1%' была выполнена на странице '%2%' пользователем '%3%' +

+ + + + + + +
+ +
+ ВНЕСТИ ИЗМЕНЕНИЯ
+
+

+

Обзор обновления:

+ + %6% +
+

+

+ Удачного дня!

+ К Вашим услугам, почтовый робот Umbraco +

+
+
+


+
+
+ + + ]]> +
[%0%] Уведомление об операции %1% над документом %2% Уведомления - и указав на нужный файл. Пакеты Umbraco обычно являются архивами с расширением ".umb" или ".zip". + и указав на нужный файл. Пакеты Umbraco обычно являются архивами с расширением '.zip'. ]]> + Перетащите сюда + или нажмите здесь для выбора файла пакета + Загрузить пакет + Установите локальный пакет из файла, расположенного на Вашем компьютере. Остерегайтесь устанавливать пакеты из непроверенных источников! + Загрузить еще один пакет + Отменить и загрузить другой пакет + Лицензия + Я принимаю + условия использования + Установить пакет + Завершить + Установленные пакеты + Ни одного пакета еще не установлено + 'Packages' наверху справа]]> + Поиск по пакетам + Результаты поиска по + Ничего не найдено по запросу + Пожалуйста, повторите поиск, уточнив запрос, или воспользуйтесь просмотром по категориям + Популярные + Недавно созданные + имеет на счету + очков кармы + Информация + Владелец + Соавторы + Создан + Текущая версия + Версия .NET + Загрузок + Нравится + Совместимость + Этот пакет совместим со следующими версиями Umbraco, по сообщениям участников сообщества. Полная совместимость не гарантируется для версий со значением ниже 100% + Внешние источники Автор Демонстрация Документация (описание) @@ -967,7 +1296,7 @@ Опции пакета Краткий обзор пакета Репозиторий пакета - Подтверждение деинсталляции + Подтверждение деинсталляции пакета Пакет деинсталлирован Указанный пакет успешно удален из системы Деинсталлировать пакет @@ -982,13 +1311,15 @@ История версий пакета Перейти на веб-сайт пакета Этот пакет уже установлен в системе - Этот пакет не может быть установлен, он требует наличия Umbraco версии как минимум %0% + Этот пакет не может быть установлен, он требует наличия Umbraco версии как минимум Удаление... Загрузка... Импорт... Установка... Перезапуск, подождите, пожалуйста... Все готово, сейчас браузер перезагрузит страницу, подождите, пожалуйста... + Пожалуйста, нажмите кнопку 'Завершить' для завершения установки и перезагрузки страницы. + Загрузка пакета... Вставить, полностью сохранив форматирование (не рекомендуется) @@ -998,16 +1329,20 @@ Подтвердите пароль - Укажите Ваш email + Укажите Ваш email... Укажите описание... + Укажите email... + Укажите сообщение... Укажите имя... Укажите теги (нажимайте Enter после каждого тега)... + Укажите имя пользователя... Укажите фильтр... Метка... Назовите %0%... Укажите пароль Что искать... Укажите имя пользователя + Имя пользователя (часто это Ваш email-адрес) Остаться @@ -1018,7 +1353,7 @@ Расширенный: Защита на основе ролей (групп) с использованием групп участников Umbraco.]]> - для применения ролевой модели безопасности.]]> + Вам необходимо создать хотя бы одну группу участников для применения ролевой модели безопасности. Страница сообщения об ошибке Используется в случае, когда пользователь авторизован в системе, но не имеет доступа к документу. Выберите способ ограничения доступа к документу @@ -1080,12 +1415,17 @@ Заголовок - Укажите заголовок + Укажите заголовок ссылки выбрать страницу сайта указать внешнюю ссылку Укажите ссылку Ссылка - В новом окне + Открыть в новом окне + + + Переименована + Укажите здесь новое название для папки + '%0%' была переименована в '%1%' Текущая версия @@ -1104,7 +1444,7 @@ Смотритель Содержимое Курьер - Для Разработчиков + Разработка Формы Помощь Мастер конфигурирования Umbraco @@ -1115,9 +1455,9 @@ Статистика Перевод Пользователи - Добавить значок + Добавить значок в качестве родительского типа. Вкладки родительского типа не показаны и могут быть изменены непосредственно в родительском типе Родительский тип контента разрешен Данный тип контента использует @@ -1154,13 +1494,23 @@ В формате списка Разрешить в качестве корневого + + Закомментировать/раскомментировать строки + Удалить строку + Копировать строки вверх + Копировать строки вниз + Переместить строки вверх + Переместить строки вниз + + Общее + Редактор Порядок сортировки Дата создания Сортировка завершена - Перетаскивайте элементы на нужное место вверх или вниз для определения необходимого Вам порядка сортировки. Также можно щелкнуть по заголовкам столбцов, чтобы отсортировать все элементы сразу. -
Не закрывайте это окно до окончания процесса сортировки.]]>
+ Перетаскивайте элементы на нужное место вверх или вниз для определения необходимого Вам порядка сортировки. Также можно использовать заголовки столбцов, чтобы отсортировать все элементы сразу. + Процесс публикации был отменен установленным пакетом дополнений. @@ -1174,6 +1524,7 @@ Вкладка с идентификатором (id): %0% удалена Документ скрыт (публикация отменена) Стиль CSS не сохранен + При сохранении файла произошла ошибка. Стиль CSS сохранен Стиль CSS сохранен без ошибок Тип данных сохранен @@ -1192,32 +1543,62 @@ Стиль CSS сохранен Шаблон сохранен Произошла ошибка при сохранении пользователя (проверьте журналы ошибок) + Группа пользователей сохранена Пользователь сохранен Тип пользователей сохранен Файл не сохранен Файл не может быть сохранен. Пожалуйста, проверьте установки файловых разрешений Файл сохранен Файл сохранен без ошибок + У текущего пользователя недостаточно прав, невозможно завершить операцию Язык сохранен Тип медиа сохранен Тип участника сохранен + Отменено + Операция отменена установленным сторонним расширением или блоком кода + Ошибка + Сохранено Представление не сохранено Произошла ошибка при сохранении файла Представление сохранено Представление сохранено без ошибок + Права доступа сохранены для Cкрипт Python не сохранен Cкрипт Python не может быть сохранен в связи с ошибками Cкрипт Python сохранен Cкрипт Python сохранен без ошибок + Скрипт не сохранен + При сохранении файла скрипта произошла ошибка + Скрипт сохранен + Файл скрипта сохранен без ошибок Шаблон не сохранен Пожалуйста, проверьте, что нет двух шаблонов с одним и тем же алиасом (названием) Шаблон сохранен Шаблон сохранен без ошибок + Проверка значений + Ошибки, найденные при проверке значений, должны быть исправлены, чтобы было возможно сохранить документ XSLT-документ не сохранен XSLT-документ содержит одну или несколько ошибок XSLT-документ не может быть сохранен, проверьте установки файловых разрешений XSLT-документ сохранен Ошибок в XSLT-документе нет + Удалено %0% групп пользователей + '%0%' была удалена + Активировано %0% пользователей + При активации пользователей произошла ошибка + Заблокировано %0% пользователей + При блокировке пользователей произошла ошибка + '%0%' сейчас активирован + При активации пользователя произошла ошибка + '%0%' сейчас заблокирован + При блокировке пользователя произошла ошибка + Группы пользователей установлены + Разблокировано %0% пользователей + При разблокировке пользователей произошла ошибка + '%0%' сейчас разблокирован + При разблокировке пользователя произошла ошибка + Данные участника успешно экспортированы в файл + Во время экспортирования данных участника произошла ошибка Используется синтаксис селекторов CSS, например: h1, .redHeader, .blueTex @@ -1228,45 +1609,150 @@ Стили - Править шаблон + Изменить шаблон + + Секции Вставить контент-область Вставить контейнер (placeholder) - Вставить статью словаря - Вставить макрос - Вставить поле документа + + Вставить + Выберите, что хотите вставить в шаблон + + Статью словаря + Статья словаря - это контейнер для части текста, переводимой на разные языки, это позволяет упростить создание многоязычных сайтов. + + Макрос + + Макросы - это настраиваемые компоненты, которые хорошо подходят для + реализации переиспользуемых блоков, (особенно, если необходимо менять их внешний вид и/или поведение при помощи параметров) + таких как галереи, формы, списки и т.п. + + + Значение поля + Отображает значение указанного поля данных текущей страницы, + с возможностью указать альтернативные поля и/или подстановку константы. + + + Частичное представление + + Частичное представление - это шаблон в отдельном файле, который может быть вызван для отображения внутри + другого шаблона, хорошо подходит для реализации переиспользуемых фрагментов разметки или для разбиения сложных шаблонов на составные части. + + Мастер-шаблон - Краткая справка по тэгам шаблонов Umbraco + Без мастер-шаблона + Не выбран + + Вставить дочерний шаблон + + @RenderBody() в выбранном месте. + ]]> + + + Определить именованную секцию + + @section { ... }. Такая секци может быть отображена в нужном месте родительского шаблона + при помощи конструкции @RenderSection. + ]]> + + + Вставить именованную секцию + + @RenderSection(name). + Таким образом из дочернего шаблона отображается содержимое внутри конструкции @section [name]{ ... }. + ]]> + + + Название секции + Секция обязательна + + Если секция помечена как обязательная, то дочерний шаблон должен обязательно содержать ее определение @section, в противном случае генерируется ошибка. + + + Генератор запросов + Построить запрос + элементов в результате, за + + Мне нужны + все документы + документы типа "%0%" + из + всего сайта + где + и + + равна + не равна + до + до (включая выбранную дату) + после + после (включая выбранную дату) + равно + не равно + содержит + не содержит + больше, чем + больше или равно + меньше, чем + меньше или равно + + Id + Название + Создан + Обновлен + + сортировать + по возрастанию + по убыванию + Шаблон - Альтернативное поле - Альтернативный текст + Добавить поле замены + Поле замены + Добавить значение по-умолчанию + Значение по-умолчанию + Поле замены + Значение по-умолчанию Регистр Выбрать поле Преобразовать переводы строк - Заменяет переводы строк на тэг html <br> + Да, преобразовывать + Заменяет переводы строк на тэг html 'br' Пользовательские Только дата Кодировка + Форматирование и кодировка Форматировать как дату + Форматировать значение как дату, или как дату и время, в соответствии с текущей культурой Кодировка HTML Заменяет спецсимволы эквивалентами в формате HTML Будет добавлено после поля Будет вставлено перед полем В нижнем регистре + Модификации при выводе -Не указано- + Пример результата Вставить после поля Вставить перед полем Рекурсивно - Удалить тэги параграфов - Удаляются тэги <p> в начале и в конце абзацев + Да, использовать рекурсию + Разделитель Стандартные В верхнем регистре Кодирование URL Форматирование специальных символов в URL Это значение будет использовано только если предыдущие поля пусты Это значение будет использовано только если первичное поле пусто - Дата и время. Разделитель: + Дата и время + + + символов осталось Задачи, назначенные Вам @@ -1320,6 +1806,8 @@ Аналитика Обзор кэша + Содержимое + Шаблоны содержимого Корзина Созданные пакеты Типы данных @@ -1330,6 +1818,7 @@ Языки Установить локальный пакет Макросы + Медиа-материалы Типы медиа-материалов Участники Группы участников @@ -1338,7 +1827,10 @@ Типы документов Пакеты дополнений Пакеты дополнений + Частичные представления + Файлы макросов Скрипты Python + Типы связей Установить из репозитория Установить Runway Модули Runway @@ -1346,6 +1838,7 @@ Скрипты Стили CSS Шаблоны + Пользователи Файлы XSLT @@ -1355,23 +1848,147 @@ Во время проверки обновлений произошла ошибка. Пожалуйста, просмотрите журнал трассировки для получения дополнительной информации. + Доступ + На основании установленных групп и назначенных начальных узлов, пользователь имеет доступ к следующим узлам Администратор + Назначение доступа + Вернуться к пользователям Поле категории + Изменить Изменить пароль Вы можете сменить свой пароль для доступа к административной панели Umbraco, заполнив нижеследующие поля и нажав на кнопку 'Изменить пароль' + Сменить аватар Подтверждение нового пароля Канал содержимого + Создать еще одного пользователя + Создан + Создать пользователя + Создавайте новых пользователей, которым нужен доступ к административной панели Umbraco. При создании пользователя для него генерируется новый первичный пароль, который нужно сообщить пользователю. Поле описания Отключить пользователя Тип документа Редактор Исключить поле + Неудачных попыток входа + К профилю пользователя + Добавьте пользователя в группу(ы) для задания прав доступа + Пригласить + Приглашение в панель администрирования Umbraco + +

Здравствуйте, %0%,

Вы были приглашены пользователем %1%, и Вам предоставлен доступ в панель администрирования Umbraco.

Сообщение от %1%: %2%

Перейдите по этой ссылке, чтобы принять приглашение.

Если Вы не имеете возможности перейти по ссылке, скопируйте нижеследующий текст ссылки и вставьте в адресную строку Вашего браузера.

%3%

]]> + + + + + + + + + + + + +
+ + + + + +
+ +
+ +
+
+ + + + + + +
+
+
+ + + + +
+ + + + +
+

+ Здравствуйте, %0%, +

+

+ Вы были приглашены пользователем %1% в панель администрирования веб-сайта. +

+

+ Сообщение от пользователя %1%: +
+ %2% +

+ + + + + + +
+ + + + + + +
+ + Нажмите на эту ссылку, чтобы принять приглашение + +
+
+

Если Вы не имеете возможности нажать на ссылку, скопируйте следующий адрес (URL) и вставьте в адресную строку Вашего браузера:

+ + + + +
+ + %3% + +
+

+
+
+


+
+
+ + + ]]> +
+ Пригласить еще одного пользователя + Пригласить пользователя + Пригласите новых пользователей, которым нужен доступ к административной панели Umbraco. Приглашенному будет направлено электронное письмо с инструкциями по доступу к Umbraco. Язык + Установите язык отображения интерфейса администрирования + Время последней блокировки + Время последнего входа + Пароль в последний раз менялся Имя входа (логин) - Начальный узел в Медиа-библиотеке + Начальный узел медиа-библиотеки + Можно ограничить доступ к медиа-библиотеке (какой-либо ее части), задав начальный узел + Начальные узлы медиа-библиотеки + Можно ограничить доступ к медиа-библиотеке (каким-либо ее частям), задав перечень начальных узлов Разделы Новый пароль Отключить доступ к административной панели Umbraco + пока еще не входил + пока не блокировался + Пароль не менялся Прежний пароль Пароль Ваш пароль доступа изменен! @@ -1385,15 +2002,47 @@ Заменить разрешения для дочерних документов Вы изменяете разрешения для следующих документов: Выберите документы для изменения их разрешений + Удалить аватар + Права доступа по-умолчанию + Атомарные права доступа + Можно установить права доступа к конкретным узлам + Профиль Сбросить пароль Поиск всех дочерних документов + Выбрать группы пользователей + Отправить приглашение Сессия истекает через - Начальный узел содержимого + Разделы, доступные пользователю + Начальный узел не задан + Начальные узлы не заданы + Имя (А-Я) + Имя (Я-А) + Сначала новые + Сначала старые + Недавно заходившие + Активные + Все + Отключенные + Заблокированные + Приглашенные + Начальный узел содержимого + Можно ограничить доступ к дереву содержимого (какой-либо его части), задав начальный узел + Начальные узлы содержимого + Можно ограничить доступ к дереву содержимого (каким-либо его частям), задав перечень начальных узлов + Был создан + Новый первичный пароль успешно сгенерирован. Для входа используйте пароль, приведенный ниже. Переводчик + Время последнего изменения Имя пользователя + Группа пользователей + Права доступа для группы + Группы пользователей + был приглашен + Новому пользователю было отправлено приглашение, в котором содержатся инструкции для входа в панель Umbraco. + Здравствуйте и добро пожаловать в Umbraco! Все будет готово в течении пары минут, нам лишь нужно задать Ваш пароль для входа и добавить аватар. + Загрузите изображение, это поможет другим пользователям идентифицировать Вас. + Управление пользователями Разрешения для пользователя - Тип пользователя - Типы пользователей Автор Ваша недавняя история Ваш профиль @@ -1405,5 +2054,13 @@ Валидация по формату Url ...или указать свои правила валидации Обязательно к заполнению + Задайте регулярное выражение + Необходимо выбрать как минимум + Возможно выбрать максимум + элементов + элементов + Неверный формат даты + Не является числом + неверный формат email-адреса
diff --git a/WebCms/Umbraco/Config/Lang/sv.xml b/WebCms/Umbraco/Config/Lang/sv.xml index 7e3a11b..58d86fe 100644 --- a/WebCms/Umbraco/Config/Lang/sv.xml +++ b/WebCms/Umbraco/Config/Lang/sv.xml @@ -2,7 +2,7 @@ The Umbraco community - http://our.umbraco.org/documentation/Extending-Umbraco/Language-Files + https://our.umbraco.com/documentation/Extending-Umbraco/Language-Files Hantera domännamn @@ -12,7 +12,6 @@ Kopiera Skapa Skapa paket - Standardvärde Ta bort Avaktivera Töm papperskorgen @@ -32,7 +31,6 @@ Skicka för publicering Skicka för översättning Sortera - Skicka för publicering Översätt Avpublicera Uppdatera @@ -82,12 +80,13 @@ Återvänd till lista Spara Spara och publicera + Spara och schemalägg Spara och skicka för godkännande Välj Välj aktuell mapp Förhandsgranska Förhandsgranskning är avstängt på grund av att det inte finns någon mall tilldelad - Ångra + Annat Välj stil Visa stil Infoga tabell @@ -171,6 +170,7 @@ "dokumenttyper".]]> "mediatyper".]]> Välj typ och rubrik + Dokumenttyp utan sidmall Surfa på din webbplats @@ -412,7 +412,7 @@ Vilken sida skall visas när formuläret är skickat Storlek Sortera - Submit + Skicka Skriv Skriv för att söka... Upp @@ -427,8 +427,17 @@ Bredd Titta på Ja - Reorder - I am done reordering + Sortera + Avsluta sortering + Förhandsvisning + Ändra lösenord + till + Listvy + Sparar... + nuvarande + Inbäddning + Hämta + valgt Bakgrundsfärg @@ -447,7 +456,7 @@ Databaskonfiguration installera]]> Nästa för att fortsätta.]]> - Databasen kunde inte hittas! Kontrollera att informationen i databasanslutnings-inställningarna i filen "web.config" är rätt.

För att fortsätta måste du redigera filen "web.config" (du kan använda Visual Studio eller din favorit text-redigerare), bläddra till slutet, lägg till databasanslutnings-inställningarna för din databas i fältet som heter "umbracoDbDSN" och spara filen.

Klicka på Försök igen knappen när du är klar.
> Mer information om att redigera web.config hittar du här.

]]>
+ Databasen kunde inte hittas! Kontrollera att informationen i databasanslutnings-inställningarna i filen "web.config" är rätt.

För att fortsätta måste du redigera filen "web.config" (du kan använda Visual Studio eller din favorit text-redigerare), bläddra till slutet, lägg till databasanslutnings-inställningarna för din databas i fältet som heter "umbracoDbDSN" och spara filen.

Klicka på Försök igen knappen när du är klar.
> Mer information om att redigera web.config hittar du här.

]]>
Eventuellt kan du behöva kontakta ditt webb-hotell. Om du installerar på en lokal maskin eller server kan du få informationen från din systemadministratör.]]> Tryck Uppgradera knappen för att uppgradera din databas till Umbraco %0%

Du behöver inte vara orolig. Inget innehåll kommer att raderas och efteråt kommer allt att fungera som vanligt!

]]>
Tryck Nästa för att fortsätta.]]> @@ -456,7 +465,6 @@ Standardanvändaren har avaktiverats eller har inte åtkomst till Umbraco!

Du behöver inte göra något ytterligare här. Klicka Next för att fortsätta.]]> Standardanvändarens lösenord har ändrats sedan installationen!

Du behöver inte göra något ytterligare här. Klicka Nästa för att fortsätta.]]> Lösenordet är ändrat! - Umbraco skapar en standardanvändare med login ('admin') och lösenordet ('default'). Det är viktigt att lösenordet ändras till något unikt.

Det här steget kommer kontrollera standardanvändarens lösenord och låta dig vet om det behöver ändras.

]]>
Få en flygande start, kolla på våra introduktionsvideor Genom att klicka på Nästa knappen (eller redigera UmbracoConfigurationStatus i web.config), accepterar du licensavtalet för den här mjukvaran som det är skrivet i rutan nedan. Observera att den här Umbracodistributionen består av två olika licensavtal, "the open source MIT license" för ramverket och "the Umbraco freeware license" som täcker användargränssnittet. Inte installerad än. @@ -593,11 +601,12 @@ Skriv för att söka... Fyll i ditt lösenord Skriv för att lägga till taggar (och tryck enter efter varje tagg)... + Ditt användarnamn är vanligtvis din e-postadress Rollbaserat lösenordsskydd Då används Umbracos medlemsgrupper.]]> - rollbaserat lösenordsskydd.]]> + Du måste skapa en medlemsgrupp innan du kan använda rollbaserat lösenordsskydd. Sida med felmeddelande Används när en användare är inloggad, men saknar rättigheter att se sidan Välj hur du vill lösenordsskydda sidan @@ -636,8 +645,12 @@ Återställ + Definiera beskräning + Ge beskärningen ett alias och dess standardbredd och -höjd + spara beskärning + Lägg till ny beskärning - + Nuvarande version Röd text kommer inte att synas i den valda versionen. , Grön betyder att den har tillkommit]]> Dokumentet har återgått till en tidigare version @@ -691,7 +704,7 @@ Creation date Sortering klar Välj i vilken ordning du vill ha sidorna genom att dra dem upp eller ner i listan. Du kan också klicka på kolumnrubrikerna för att sortera grupper av sidor -
Stäng inte fönstret under tiden sidorna sorteras.]]>
+ Publiceringen avbröts av ett tredjepartstillägg @@ -768,6 +781,12 @@ Sidmall + Rich Text Editor + Image + Macro + Embed + Headline + Quote Lägg till Choose layout Lägg till rad @@ -818,8 +837,6 @@ Infoga efter fält Infoga före fält Rekursiv - Avlägsna stycke-taggar - Kommer att avlägsna alla &lt;P&gt; i början och slutet av texten Standardfält Versaler URL-koda @@ -929,11 +946,9 @@ Startnod i innehåll Användarens namn Användarrättigheter - Användartyp - Användartyper Skribent Din nuvarande historik Översättare Din profil -
\ No newline at end of file + diff --git a/WebCms/Umbraco/Config/Lang/tr.xml b/WebCms/Umbraco/Config/Lang/tr.xml new file mode 100644 index 0000000..d2431e8 --- /dev/null +++ b/WebCms/Umbraco/Config/Lang/tr.xml @@ -0,0 +1,1137 @@ + + + + The Umbraco community + https://our.umbraco.com/documentation/Extending-Umbraco/Language-Files + + + Kültür ve Hostnames + Denetim Trail + Düğüm Araştır + Belge Türü Değiştir + Kopya + Oluştur + Paket Oluştur + Sil + Devre Dışı Bırak + Geri Dönüşümü Boşat + Belge Türü Çıkart + Belge Türü Al + Paket Ekle + Tuval Düzele + Çıkış + Taşı + Bildirimler + Genel Erişim + Yayımla + Yayından Kaldır + Yeniden Yükle + Siteleri Yeniden Yayınla + Düzelt + İzinler + Rollback + Yayın için Gönder + Çeviri Gönder + Sırala + Yayına Gönder + Çevir + Güncelle + Varsayılan Değer + + + İzin reddedildi. + Yeni Domain ekle + kaldır + Geçersiz node. + Geçersiz domain biçimi. + Domain zaten eklenmiş. + Dil + Domain + Yeni domain '%0%' oluşturuldu + Domain '%0%' silindi + Domain '%0%' zaten atanmış + Domain '%0%' güncellendi + Geçerli domain düzenle + + etki /> bir düzey yollar desteklenir
+
+ Miras Al + Kültür + + veya üst düğümleri kültürünü devralır. Ayrıca
geçerli olacaktır + Geçerli düğümün , bir etki altında çok uygulanmadığı sürece .]]> +
+ Domainler + + + Görüntüleniyor + + + Seç + Geçerli klasörü seçin + Başka birşey yapın + Kalın + Paragraf girinti iptal + Form alanı ekle + Grafik başlık ekle + Html Düzenle + Paragraf girintisi + Yatık + Ortalı + Sola Yasla + Sağa Yasla + Link ekle + Yerel bağlantı ekle + Bulet listesi + Sayısal Liste + Macro ekle + Resim ekle + Düzenleme ilişkileri + Listeye Dön + Kaydet + Kaydet ve Yayınla + Kaydet ve Onay için gönder + Önizle + Önizleme kapalı, Atanmış şablon yok + Stili seçin + Stilleri Göster + Tablo Ekle + + + Seçilen içerik için belge türünü değiştirmek için , öncelikle bu konum için geçerli türleri listesinden seçim yapın. + Ardından onaylamak ve / veya yeni akım tip özellikleri haritalama değişiklik ve Kaydet'i tıklatın . + İçerik yeniden yayımlanmıştır . + Güncel Mülkiyet + Güncel tip + Bu konum için geçerli hiçbir alternatifi olduğu gibi belge türü , değiştirilemez . Seçilen içerik öğesinin ebeveyn altında izin verilir ve mevcut tüm alt içerik öğeleri altında oluşturulacak izin eğer bir alternatif geçerli olacaktır . + Belge Türü değiştirildi + harita Özellikleri + Mülkiyet Harita + Yeni Şablon + Yeni Tip + Hiçbiri + İçerik + Yeni Belge Tipi Seçiniz + Seçilen içeriğin belge türü başarıyla [ yeni tip ] değişti ve aşağıdaki özellikleri eşleştirilmiş edilmiştir : + için + Bir veya daha fazla özellikleri olarak mülkiyet haritalama tamamlayamadı birden fazla eşleme tanımlanmış var. + Bulunduğunuz yerin için geçerli Sadece alternatif türleri görüntülenir. + + + Yayımlandı + Bu sayfa hakkında + takma ad + ( nasıl telefon üzerinden resim anlatırsınız ) + Alternatif Linkler + Bu öğeyi düzenlemek için tıklayın + Tarafından yaratıldı + orijinal yazar + tarafından güncellendi + oluşturuldu + Bu belgenin oluşturulduğu tarih / zaman + Belge Türü + kurgu + en kaldır + Bu madde yayınlanmasından sonra değiştirildi + Bu öğe yayınlanmadı + Son yayınlanan + Listede gösterilecek öğe yok. + Medya Türü + Medya öğesinin bağlantı ( lar) + Üye Grubu + rol + Üye Türü + Hiçbir tarih seçildi + Sayfa başlığı + Özellikler + Ebeveyn ' %0% ' yayımlanmamış olduğu için bu belge yayınladı ama görünür değildir + Hata : Bu ​​belge yayınlandı ancak önbellek ( iç hata ) değil + yayınlamak + Yayın Durum + en Yayınla + en yayından + temizle tarihi + Sıralama güncellenir + Düğümlerini sıralamak için, sadece düğümleri sürükleyin veya sütun başlıkları birini tıklatın . Seçerken " shift " veya " kontrol " tuşunu basılı tutarak birden fazla düğüm seçebilirsiniz + istatistik + Başlık (isteğe bağlı) + Alternatif metin (isteğe bağlı) + tip + Yayından + Son düzenleme + Bu belgenin düzenlendiği tarih / zaman + Dosya(ları) kaldırın + Belgeye Bağlantı + Grubun Üyesi(leri) + Grubun bir üyesi değil + Çocuk öğeleri + hedef + + + Yüklemek için tıklayın + Burada açılan dosyaları ... + Medya Linki + + + Nerede yeni %0% yaratmak istiyorsun + Altında bir öğe oluşturun + Bir tür ve bir başlık seçin + "belge türleri".]]> + "ortam türleri".]]> + + + Web sitenizi tarayın + - gizle + CMS açılış değilse , bu siteden pop-up izin gerekebilir + Yeni bir pencere açtı + Tekrar başlat + ziyaret + hoşgeldiniz + + + isim + konak yönetin + Bu pencereyi kapatın + Silmek istediğine emin misin + Eğer devre dışı bırakmak istediğinizden emin misiniz + %0% öğe(lerin) silinmesi onaylamak için bu kutuyu kontrol edin + Emin misiniz? + Emin misiniz? + Kes + Düzenleme Sözlük Öğe + Dil Düzenleme + Yerel bağlantı ekleme + Karakter Ekle + Grafik başlığı ekleyin + Resim ekle + Link Ekle + Bir makro eklemek için tıklayın + Tablo Ekle + Son DÜzenleme + Bağlantı + İç Bağlantı: + Yerel bağlantıları kullanırken, bağlantının önündeki "# " insert + Yeni pencerede aç + Makro ayarları + Düzenleyebileceğiniz Bu makro herhangi bir özellikleri içermiyor + Yapıştır + İzinleri düzenle + Geri dönüşüm kutusu öğeleri şimdi siliniyor. Bu işlem gerçekleşirken bu pencereyi kapatın etmeyiniz + Geri dönüşüm kutusu artık boş + Öğeleri geri dönüşüm kutusu silindiğinde, onlar sonsuza kadar gitmiş olacak + regexlib.com's webcoder şu anda üzerinde hiçbir kontrole sahip bazı sorunları, yaşanıyor. Bu rahatsızlıktan dolayı çok üzgünüz.]]> + Bir düzenli ifade arama form alanına doğrulama ekleyin. Örnek: 'E-posta', zip code 'url' + Makro kaldır + Gerekli alan + Site yeniden indekslendi + Web sitesi yenilendi önbelleği olmuştur. Tüm içerik güncel artık yayımlamak. Tüm yayınlanmamış içeriği hala yayınlanmamış olmakla birlikte + Web sitesi önbelleği yenilenir olacaktır. Yayınlanmamış içerik yayınlanmamış kalacak ise tüm yayınlanan içerik, güncellenecektir. + Sütün sayısı + Satır sayısı + + Yer tutucu kimliği ayarla senin tutucu bir kimlik ayarlayarak Çocuğunuz şablonları bu şablon içine içerik enjekte edebilir, + Bir kullanarak bu kimliği bakarak <asp:content /> element.]]> + + + Yer tutucu kimliği seçin Aşağıdaki listeden. You can sadece +       Geçerli şablonun ustadan kimliği sitesinin seçin.]]> + + Tam boyutta görmek için resmin üzerine tıklayın + öğeyi seçin + Görünüm Önbellek Öğe + + + + %0%
' aşağıda
Sol taraftaki menüden 'diller' başlığı altında ek dil ekleyebilirsiniz + ]]> + + Kültür adı + + + Kullanıcı adınızı giriniz + Parolanızı giriniz + Ad %0%... + Adınızı girin... + Aramak için yazın... + Filtrelemek için yazın... + (Basın, her etiketinden sonra girin) etiket eklemek için yazın ... + + + Root'a izin ver + Bu Sadece İçerik Türleri İçerik ve Medya ağaçların kök düzeyinde oluşturulabilir kontrol + İzin alt düğüm çeşitleri + Belge Türü kompozisyonlar + Oluştur + Sekmesini sil + Tanım + Yeni sekme + Sekme + Küçük resim + Lüste görünümü etkinleştir + Bir sıralanabilir & göstermek için içerik öğesini yapılandırır; kendi çocuklarının aranabilir liste, çocuk ağacında gösterilen olmayacak + Liste görünümü + Etkin liste görünümü veri türü + Özel liste görünüm oluşturun + Özel liste görünümü kaldır + + + Ön değer ekle + Veritabanı veritürü + Mülkiyet editörü GUID + Mülkiyet editörü + Düğmeler + Gelişmiş ayarları etkinleştir... + Bağlam menüsünü etkinleştir + Eklenen görüntülerin maksimum varsayılan boyutu + İlgili stil + Etiketi göster + Yükseklik ve Genişlik + + + Verileriniz kaydedildi, ancak bu sayfayı yayınlamak için önce ilk düzeltmek için gereken bazı hatalar vardır: + Geçerli üyelik sağlayıcısı değişen şifreyi desteklemiyor (EnablePasswordRetrieval doğru olması gerekir) + %0% zaten var + Hatalar vardı: + Hatalar vardı: + Şifre %0% karakter uzunluğunda en az olması ve en az %1% non-alfa sayısal karakter (ler) içermelidir + %0% bir tamsayı olmalıdır + %1% sekmesinde %0% alan zorunludur + %0% zorunlu bir alandır + %0% - %1% bir doğru biçimde değil + %0% Bir doğru biçimde değil + + + Belirtilen dosya türü yönetici tarafından izin verilmeyen olmuştur + NOT! CodeMirror yapılandırma tarafından etkin olsa bile yeterince kararlı değil, çünkü Internet Explorer'da devre dışı bırakılır. + Yeni özellik tipine takma adını ve hem de doldurunuz! + Belirli bir dosya veya klasör için okuma / yazma erişimi olan bir sorun var + Error loading Partial View script (Dosya: %0%) + Error loading userControl '%0%' + Error loading customControl (Assembly: %0%, Type: '%1%') + Error loading MacroEngine script (Dosya: %0%) + "Error parsing XSLT file: %0% + "Error reading XSLT file: %0% + Lütfen bir başlık girin + Lütfen bir tür seçin + Orijinal boyutundan daha resmi büyütmek üzereyiz. Devam etmek istediğinizden emin misiniz? + Python komut dosyası hatası + O hatayı içerdiği için python komut dosyası, kaydedilmemiş (ler) + Silinen düğüm başlatın, lütfen yöneticinize başvurun + Tarzı değiştirmeden önce içerik işaretleyiniz + Henüz aktif stilleri + Birleştirmek istediğiniz iki hücre solundaki imleci Lütfen + Sen birleştirilmiş henüz bir hücreyi bölemezsiniz. + XSLT kaynak hatae + O hatayı içerdiği XSLT, kaydedilmemiş (ler)) + Bu özellik için kullanılan veri türüne sahip bir yapılandırma hatası var, veri türünü kontrol edin + + + Hakkında + Eylem + Eylemler + Ekle + Takma ad + Emin misiniz? + Sınır + tarafında + İptal + hücre marjı + Seçim + Kapat + Pencereyi kapat + Açıklama + Onayla + oranları sınırlamak + Devam et + Kopyala + Oluştur + Veritabanı + Tarih + Standart + Sil + Silindi + Siliniyor... + Dizayn + Boyutlar + Aşağı + İndir + Düzenle + Düzenlendi + Elemanları + E-Posta + Hata + Bul + Yükseklik + Yardım + İkon + İthalat + İç Marj + Ekle + Kur + Satır Uzunluğu + Dil + Düzen + Yükleniyor + Kilitli + Giriş yap + Oturum Kapat + Çıkış yap + Makro + Taşı + Daha + Ad + Yeni + Sonraki + Hayır + arasında + TAMAM + + veya + Parola + Yol + Yer tutucu ID + Bir dakika lütfen... + Önceki + Özellikler + Form verilerini almak için e-posta + Geridönüşüm kutusu + Kalan + Adını Değiştir + Yenile + Gerekli + Tekrar dene + İzinler + Arama + Sunucu + Göster + Gönder sayfasını göster + Boyut + Sırala + Tip + Aramak için yazın... + Yukarı + Güncelle + Yükselt + Yükle + URL + Kullanıcı + Kullanıcı adı + Değer + Görünüm + Hoşgeldiniz... + Genişlik + Evet + Klasör + Arama Sonuçları + + + Arka plan rengi + Kalın + Metin Rengi + Yazı + Metin + + + Sayfa + + + Yükleyici veritabanına bağlanamıyor. + Web.config dosyasını kaydedilemedi. El bağlantı dizesini değiştirin lütfen. + Web.config dosyasını kaydedilemedi. El bağlantı dizesini değiştirin lütfen.... + Veritabanı yapılandırması + + Kurulum için dğümeye basın %0% veritabanı + ]]> + + Sonraki Devam için.]]> + + Veritabanı bulunamadı! "Web.config" nin "bağlantı dizesinde" bilgi dosyası doğru olup olmadığını kontrol edin.

+

Devam etmek için, (Visual Studio veya sevdiğiniz metin editörü kullanarak) "web.config" dosyasını düzenlemek lütfen, altına gidin "UmbracoDbDSN" adlı anahtarı veritabanınız için bağlantı dizesini eklemek ve dosyayı kaydedin.

+

+ Tekrar dene. +
+ + Burada düzenleme web.config Hakkında Daha Fazla Bilgi.

]]> +
+ + + Gerekirse ISS'nize irtibata geçiniz. + Eğer yerel makine veya sunucu üzerinde yükleme ediyorsanız, sistem yöneticinizden bilgi gerekebilir.]]> + + + + CMS %0% için veritabanını yükseltme için yükseltme düğmesine basın +

+

+ Merak etmeyin - hiçbir içerik silinmeyecek ve her şey sonradan çalışmaya devam edecektir! +

+ ]]> +
+ + Sonraki işlem. ]]> + + next to continue the configuration wizard]]> + The Default users' password needs to be changed!]]> + The Default user has been disabled or has no access to Umbraco!

No further actions needs to be taken. Click Next to proceed.]]> + The Default user's password has been successfully changed since the installation!

No further actions needs to be taken. Click Next to proceed.]]> + The password is changed! + Get a great start, watch our introduction videos + By clicking the next button (or modifying the umbracoConfigurationStatus in web.config), you accept the license for this software as specified in the box below. Notice that this Umbraco distribution consists of two different licenses, the open source MIT license for the framework and the Umbraco freeware license that covers the UI. + Not installed yet. + Affected files and folders + More information on setting up permissions for Umbraco here + You need to grant ASP.NET modify permissions to the following files/folders + + Your permission settings are almost perfect!

+ You can run Umbraco without problems, but you will not be able to install packages which are recommended to take full advantage of Umbraco.]]> +
+ How to Resolve + Click here to read the text version + video tutorial on setting up folder permissions for Umbraco or read the text version.]]> + + Your permission settings might be an issue! +

+ You can run Umbraco without problems, but you will not be able to create folders or install packages which are recommended to take full advantage of Umbraco.]]> +
+ + Your permission settings are not ready for Umbraco! +

+ In order to run Umbraco, you'll need to update your permission settings.]]> +
+ + Your permission settings are perfect!

+ You are ready to run Umbraco and install packages!]]> +
+ Resolving folder issue + Follow this link for more information on problems with ASP.NET and creating folders + Setting up folder permissions + + + + Baştan başlamak istiyorum + + learn how) + You can still choose to install Runway later on. Please go to the Developer section and choose Packages. + ]]> + + You've just set up a clean Umbraco platform. What do you want to do next? + Runway is installed + + + This is our list of recommended modules, check off the ones you would like to install, or view the full list of modules + ]]> + + Only recommended for experienced users + I want to start with a simple website + + + "Runway" is a simple website providing some basic document types and templates. The installer can set up Runway for you automatically, + but you can easily edit, extend or remove it. It's not necessary and you can perfectly use Umbraco without it. However, + Runway offers an easy foundation based on best practices to get you started faster than ever. + If you choose to install Runway, you can optionally select basic building blocks called Runway Modules to enhance your Runway pages. +

+ + Included with Runway: Home page, Getting Started page, Installing Modules page.
+ Optional Modules: Top Navigation, Sitemap, Contact, Gallery. +
+ ]]> +
+ What is Runway + Step 1/5 Accept license + Step 2/5: Database configuration + Step 3/5: Validating File Permissions + Step 4/5: Check Umbraco security + Step 5/5: Umbraco is ready to get you started + Thank you for choosing Umbraco + + Browse your new site +You installed Runway, so why not see how your new website looks.]]> + + + Further help and information +Get help from our award winning community, browse the documentation or watch some free videos on how to build a simple site, how to use packages and a quick guide to the Umbraco terminology]]> + + Umbraco %0% is installed and ready for use + + /web.config file and update the AppSetting key UmbracoConfigurationStatus in the bottom to the value of '%0%'.]]> + + + started instantly by clicking the "Launch Umbraco" button below.
If you are new to Umbraco, +you can find plenty of resources on our getting started pages.]]> +
+ + Launch Umbraco +To manage your website, simply open the Umbraco back office and start adding content, updating the templates and stylesheets or add new functionality]]> + + Connection to database failed. + Umbraco Version 3 + Umbraco Version 4 + Watch + + Umbraco %0% for a fresh install or upgrading from version 3.0. +

+ Press "next" to start the wizard.]]> +
+ + + Kültür Kodu + Kültür Adı + + + Sen boşta oldum ve çıkış otomatik olarak gerçekleşecek + İşinizi kaydetmek için şimdi Yenile + + + Pazar + Pazartesi + Salı + Çarşamba + Perşembe + Cuma + İçerik Yönetim Sistemi + Giriş Yapın + Oturum zaman aşımına uğradı + © 2015 - %0%
umbraco.com

]]>
+ + + Gösterge Paneli + Bölümler + İçerik + + + Choose page above... + %0% has been copied to %1% + Select where the document %0% should be copied to below + %0% has been moved to %1% + Select where the document %0% should be moved to below + has been selected as the root of your new content, click 'ok' below. + No node selected yet, please select a node in the list above before clicking 'ok' + The current node is not allowed under the chosen node because of its type + The current node cannot be moved to one of its subpages + The current node cannot exist at the root + The action isn't allowed since you have insufficient permissions on 1 or more child documents. + Relate copied items to original + + + Edit your notification for %0% + + + + + Hi %0%

+ +

This is an automated mail to inform you that the task '%1%' + has been performed on the page '%2%' + by the user '%3%' +

+ +

+

Update summary:

+ + %6% +
+

+ + + +

Have a nice day!

+ Cheers from the Umbraco robot +

]]> +
+ [%0%] Notification about %1% performed on %2% + Notifications + + + + + button and locating the package. Umbraco packages usually have a ".umb" or ".zip" extension. + ]]> + + Author + Demonstration + Documentation + Package meta data + Package name + Package doesn't contain any items + +
+ You can safely remove this from the system by clicking "uninstall package" below.]]> +
+ No upgrades available + Package options + Package readme + Package repository + Confirm uninstall + Package was uninstalled + The package was successfully uninstalled + Uninstall package + + + Notice: any documents, media etc depending on the items you remove, will stop working, and could lead to system instability, + so uninstall with caution. If in doubt, contact the package author.]]> + + Download update from the repository + Upgrade package + Upgrade instructions + There's an upgrade available for this package. You can download it directly from the Umbraco package repository. + Package version + Package version history + View package website + + + Paste with full formatting (Not recommended) + The text you're trying to paste contains special characters or formatting. This could be caused by copying text from Microsoft Word. Umbraco can remove special characters or formatting automatically, so the pasted content will be more suitable for the web. + Paste as raw text without any formatting at all + Paste, but remove formatting (Recommended) + + + Role based protection + using Umbraco's member groups.]]> + You need to create a membergroup before you can use role-based authentication. + Error Page + Used when people are logged on, but do not have access + Choose how to restrict access to this page + %0% is now protected + Protection removed from %0% + Login Page + Choose the page that contains the login form + Remove Protection + Select the pages that contain login form and error messages + Pick the roles who have access to this page + Set the login and password for this page + Single user protection + If you just want to setup simple protection using a single login and password + + + + + + + + + + + + + + + Include unpublished child pages + Publishing in progress - please wait... + %0% out of %1% pages have been published... + %0% has been published + %0% and subpages have been published + Publish %0% and all its subpages + + ok
to publish %0% and thereby making its content publicly available.

+ You can publish this page and all it's sub-pages by checking publish all children below. + ]]> + + + + You have not configured any approved colours + + + harici bağlantı gir + iç sayfa seç + Altyazı + Bağlantı + yeni pencere + Yeni altyazı gir + Bağlantı gir + + + Sıfırla + + + Varolan versiyon + Red text will not be shown in the selected version. , green means added]]> + Document has been rolled back + This displays the selected version as HTML, if you wish to see the difference between 2 versions at the same time, use the diff view + Rollback to + Versiyon seç + Görünüm + + + Düzenleme komut dosyası + + + Kapıcı + İçerik + Kurya + Geliştirici + CMS Yapılandırma Sihirbazı + Medya + Üyeler + Haber Bültenleri + Ayarlar + İstatistik + Çeviri + Kullanıcılar + Yardım + Formlar + Analytics + + + git + Help topics for + Video chapters for + Kayadata + + + Varsayılan şablonu + Sözlük Anahtarı + To import a document type, find the ".udt" file on your computer by clicking the "Browse" button and click "Import" (you'll be asked for confirmation on the next screen) + New Tab Title + Node type + Type + Stylesheet + Script + Stylesheet property + Tab + Tab Title + Tabs + Master Content Type enabled + This Content Type uses + as a Master Content Type. Tabs from Master Content Types are not shown and can only be edited on the Master Content Type itself + No properties defined on this tab. Click on the "add a new property" link at the top to create a new property. + Master Document Type + Create matching template + + + Sorting complete. + Drag the different items up or down below to set how they should be arranged. Or click the column headers to sort the entire collection of items + + + + Hata + Kullanıcı izniniz yeterli olmadığı için, işleminiz gerçekleştirilmedi. + İptal Edildi + İşleminiz 3.Parti yazılım tarafından iptal edildi. + Sayfa yayınlama 3.Parti yazılım tarafından iptal edildi. + Property type zaten bulunuyor + Property type oluşturuldu + DataType: %1%]]> + Propertytype silindi + Döküman Tipi kaydedildi + Tab oluşturuldu + Tab silindi + Tab id: %0% silindi + Stylesheet kaydedilmedi + Stylesheet kaydedildi + Stylesheet sorunsuz kaydedildi + Data tipi kaydedildi + Sözlük elemanı kaydedildi + Bağlı olduğu sayfa yayınlanmadığından dolayı işlem başarısız oldu. + İçerik yayınlandı + ve web sitesinde görülebilir. + İçeirk kaydedildi. + Remember to publish to make changes visible + Onaya gönder + Değişiklikler onaya gönderildi + Medya kaydedildi + Medya sorunsuz kaydedildi + Üye kaydedildi + Stylesheet Property kaydedildi + Stylesheet kaydedildi + Template kaydedildi + Kullanıcı kaydedilirken hata oluştu (log kontrol) + Kullanıcı kaydedildi + Kullanıcı tipi kaydedildi + Dosya kaydedilemedi + dosya kaydedilemedi. Lütfen dosya izinlerini kontrol edin + Dosya kaydedildi + Dosya sorunsuz kaydedildi + Dil kaydedildi + Python script kaydedilemedi + Python scripti oluşan hata nedeniyle kaydedilemedi + Python script kaydedildi + Python scriptinde hata buluanamadı + Template kaydedilmedi + Aynı isim ile 2 template bulunmadığından emin olun + Template kaydedildi + Template sorunsuz kaydedildi! + XSLT kaydedilmedi + XSLT hata içeriyor + XSLT kaydedilemedi, dosya izinlerini kontrol edin + XSLT kaydedildi + XSLT'de hata içermiyor + İçerik yayından kaldırıldı + Partial view kaydedildi + Partial view sorunsuz kaydedildi! + Partial view kaydedilmedi + Dosya kaydedilirken bir hata oluştu. + Script view kaydedildi + Script view sorunsuz kaydedildi! + Script view kaydedilmedi + Dosya kaydedilirken bir hata oluştu. + Dosya kaydedilirken bir hata oluştu. + + + CSS sözdizimi kullanımları. örneğin: h1, .redHeader, .blueTex + Stil dosyası düzenle + Stil dosyası özelliği düzenle + Name to identify the style property in the rich text editor + Ön izleme + Stiller + + + Edit template + Insert content area + Insert content area placeholder + Insert dictionary item + Insert Macro + Insert Umbraco page field + Master template + Quick Guide to Umbraco template tags + Template + + + Rich Text Editor + Image + Macro + Embed + Headline + Quote + Insert control + Choose a layout for the page + below and add your first element]]> + + Click to embed + Click to insert image + Image caption... + Write here... + Grid layouts + Layouts are the overall work area for the grid editor, usually you only need one or two different layouts + Add grid layout + Adjust the layout by setting column widths and adding additional sections + + Row configurations + Rows are predefined cells arranged horizontally + Add row configuration + Adjust the row by setting cell widths and adding additional cells + + Columns + Total combined number of columns in the grid layout + + Settings + Configure what settings editors can change + + + Styles + Configure what styling editors can change + + Settings will only save if the entered json configuration is valid + + Allow all editors + Allow all row configurations + + + Alternative field + Alternative Text + Casing + Encoding + Choose field + Convert line breaks + Replaces line breaks with html-tag &lt;br&gt; + Custom Fields + Yes, Date only + Format as date + HTML encode + Will replace special characters by their HTML equivalent. + Will be inserted after the field value + Will be inserted before the field value + Lowercase + None + Insert after field + Insert before field + Recursive + Remove Paragraph tags + Will remove any &lt;P&gt; in the beginning and end of the text + Standard Fields + Uppercase + URL encode + Will format special characters in URLs + Will only be used when the field values above are empty + This field will only be used if the primary field is empty + Yes, with time. Separator: + + + Tasks assigned to you + + assigned to you. To see a detailed view including comments, click on "Details" or just the page name. + You can also download the page as XML directly by clicking the "Download Xml" link.
+ To close a translation task, please go to the Details view and click the "Close" button. + ]]> +
+ close task + Translation details + Download all translation tasks as XML + Download XML + Download XML DTD + Fields + Include subpages + + + + [%0%] Translation task for %1% + No translator users found. Please create a translator user before you start sending content to translation + Tasks created by you + + created by you. To see a detailed view including comments, + click on "Details" or just the page name. You can also download the page as XML directly by clicking the "Download Xml" link. + To close a translation task, please go to the Details view and click the "Close" button. + ]]> + + The page '%0%' has been send to translation + Send the page '%0%' to translation + Atanan + Görev açıldı + Toplam kelime + Translate to + Çeviri tamamlandı. + You can preview the pages, you've just translated, by clicking below. If the original page is found, you will get a comparison of the 2 pages. + Translation failed, the XML file might be corrupt + Çeviri opsiyonları + Çevirmen + Upload translation XML + + + Cache Browser + Çöp Kutusu + Oluşturulan Paketler + Data Tipleri + Sözlük + Kurulu paketler + Görünüm kur + Başlangıç kiti kur + Diller + Yerel paket yükle + Makrolar + Medya Tipleri + Üyeler + Üye Grupları + Roller + Üye Tipleri + Döküman Tipleri + Paketler + Paketler + Python Dosyaları + Depodan kurulum yap + Install Runway + Runway modülleri + Scripting Dosyaları + Scriptler + Stil dosyaları + Şablonlar + XSLT Dosyaları + Analitikler + + + Yeni bir güncelleme geldi + %0% hazır, indirimek için tıklayın + Sunucu bağlantısı yok + Güncelleme için kontrol hatası. Daha fazla bilgi için iz yığını gözden geçirin. + + + Yöneticiler + Kategori alanı + Şifreni değiştir + Yeni şifre + Yeni şifreyi onayla + Aşağıdaki formu doldurarak CMS Geri Office'i erişmek için parolanızı değiştirmeniz ve ' Şifre Değiştir ' düğmesine tıklayabilirsiniz + İçerik Kanalı + Açıklama alanı + Kullanıcıyı devre dışı bırak + Döküman Tipi + editör + Alıntı alan + Dil + Kullanıcı adı + Medya kütüphane düğüm başlatın + Bölümler + CMS Erişim devre dışı bırakma + Parola + Şifrenizi sıfırlayın + Şifreniz değiştirildi! + Yeni parolayı onaylayın + Yeni şifrenizi girin + Yeni şifre boş olamaz! + Mevcut Şifre + Geçersiz şifre + Yeni şifre ile teyit şifre arasında bir fark yoktu. Lütfen tekrar deneyin! + Teyit şifre yeni bir şifre eşleşmiyor ! + Alt düğümü izinlerini değiştirin + Şu anda sayfaları için izinleri değiştiriyorsunuz: + Onların izinlerini değiştirmek için sayfaları seçin + Tüm alt düğümlerde ara + içerikte düğüm başlat + Kullanıcı adı + Kullanıcı izinleri + Kullanıcı türü + Kullanıcı tipleri + Yazar + Çevirmen + Değiştir + Profiliniz + Son tarih + Oturum sona eriyor + + diff --git a/WebCms/Umbraco/Config/Lang/zh.xml b/WebCms/Umbraco/Config/Lang/zh.xml index 0ab2a54..24331c0 100644 --- a/WebCms/Umbraco/Config/Lang/zh.xml +++ b/WebCms/Umbraco/Config/Lang/zh.xml @@ -1,8 +1,8 @@ - 孙柱梁 - http://our.umbraco.org/documentation/Extending-Umbraco/Language-Files + 黄仁祥(wanddy@163.com) + https://our.umbraco.com/documentation/Extending-Umbraco/Language-Files 管理主机名 @@ -24,16 +24,17 @@ 提醒 公众访问权限 发布 + 取消发布 重新加载节点 重新发布整站 + 恢复 + 为 %0%设置权限 权限 回滚 提交至发布者 发送给翻译 排序 - 提交至发布者 翻译 - 取消发布 更新 @@ -41,24 +42,25 @@ 添加域名 移除 错误的节点 + 域名错误 域名重复 - 域名 语言 + 域名 新域名 '%0%' 已创建 域名 '%0%' 已删除 域名 '%0%' 已使用 + 域名 '%0%' 已更新 + 编辑当前域名 - + https://www.example.com/、example.com/en、……使用 * 代表任意域名,
只需要设置语言部分即可。]]> -
- 域名 '%0%' 已更新 - 域名错误 - 编辑当前域名 + 继承 语言 - 也可以从父节点继承。]]> + + 也可以从父节点继承。]]> 域名 @@ -66,6 +68,10 @@ 查看 + 清除选择 + 选择 + 选择当前目录 + 其它功能 粗体 取消段落缩进 插入表单字段 @@ -83,51 +89,62 @@ 插入宏 插入图片 编辑关联 + 返回列表 保存 保存并发布 保存并提交审核 + 保存列表视图 预览 因未设置模板无法预览 选择样式 显示样式 插入表格 + 生成模型 + 撤销 + 重做 要更改所选节点的文档类型,先在列表中选择合适的文档类型。 然后设置当前文档类型到新文档类型的各字段间的对应映射关系并保存。 - 内容已被重新发布 + 内容已被重新发布 当前属性 当前类型 不能改变文档类型,因为没有可替代的类型。 - 文档类型已更改 + 文档类型已更改 要映射的字段 映射字段 新模板 新类型 - + 内容 选择新的文档类型 选中文档的类型已被成功更改为[new type],以下字段被映射: 不能完成字段映射,因为存在一个字段映射至多字段的问题。 - 仅显示可作为替代的文档类型。 - + 仅显示可作为替代的文档类型。 + + 已发布 关于本页 别名 (图片的替代文本) 替代链接 点击编辑 创建者 + 原作者 + 更新者 创建时间 + 创建此文档的日期/时间 文档类型 编辑 过期于 该项发布之后有更改 该项没有发布 最近发布 - 媒体链接地址 + 没有要显示的项目 + 列表中没有要显示的项目。 媒体类型 + 媒体链接地址 会员组 角色 会员类型 @@ -136,26 +153,61 @@ 属性 该文档不可见,因为其上级 '%0%' 未发布。 该文档已发布,但是没有更新至缓存(内部错误) + 无法获取网址 + 此文档已发布,但其url将与内容相冲突 %0% 发布 发布状态 发布于 + 取消发布于 清空时间 排序完成 拖拽项目或单击列头即可排序,可以按住Shift多选。 统计 标题(可选) + 备选 (可选) 类型 取消发布 最近编辑 + 编辑此文档的日期/时间 移除文件 链接到文档 会员组成员 非会员组成员 + 子项 + 目标 + 这将转换到服务器上的以下时间: + 这是什么意思?]]> + 添加其他文本框 + 删除此文本框 + + + 点击上传 + 将文件放在此处.. + 链接到媒体 + 或单击此处选择文件 + 仅允许的文件类型为 + 最大文件大小为 + + + 创建新成员 + 所有成员 您想在哪里创建 %0% 创建在 选择类型和标题 + "文档类型" 下的 "设置" 部分中启用这些内容。]]> + "媒体类型" 下的 "设置" 部分中启用这些内容。]]> + 没有模板的文档类型 + 新建文件夹 + 新数据类型 + 新建 javascript 文件 + 新建空分部视图 + 新的分部视图宏 + 从代码段中新建分部视图 + 新的空分部视图宏 + 从代码段中新建分部视图宏 + 新的分部视图宏 (不带宏) 浏览您的网站 @@ -167,38 +219,33 @@ 欢迎 - Stay - Discard changes - You have unsaved changes - Are you sure you want to navigate away from this page? - you have unsaved changes + 保持 + 丢弃更改 + 您有未保存的更改 + 确实要离开此页吗?-您有未保存的更改 - - Done - - Deleted %0% item - Deleted %0% items - Deleted %0% out of %1% item - Deleted %0% out of %1% items - - Published %0% item - Published %0% items - Published %0% out of %1% item - Published %0% out of %1% items - - Unpublished %0% item - Unpublished %0% items - Unpublished %0% out of %1% item - Unpublished %0% out of %1% items - - Moved %0% item - Moved %0% items - Moved %0% out of %1% item - Moved %0% out of %1% items - - Copied %0% item - Copied %0% items - Copied %0% out of %1% item - Copied %0% out of %1% items + + 完成 + 已删除 %0% 项 + 已删除 %0% 项 + 已删除 %0% 项,共 %1% 项 + 已删除 %0% 项,共 %1% 项 + 已发布 %0% 项 + 已发布 %0% 项 + 已发布 %0% 项,共 %1% 项 + 已发布 %0% 项,共 %1% 项 + 已取消发布 %0% 项 + 已取消发布 %0% 项 + 已取消发布 %0% 项,共 %1% 项 + 已取消发布 %0% 项,共 %1% 项 + 已移动 %0% 项 + 已移动 %0% 项 + 已移动 %0% 项,共 %1% 项 + 已移动 %0% 项,共 %1% 项 + 已复制 %0% 项 + 已复制 %0% 项 + 已复制 %0% 项,共 %1% 项 + 已复制 %0% 项,共 %1% 项 锚点名称 @@ -240,24 +287,91 @@ 网站缓存将会刷新,所有已发布的内容将会更新。 表格列数 表格行数 - 设置一个占位符id 您可以在子模板中通过该ID来插入内容,引用格式: <asp:content />。]]> - 选择一个占位符id。]]> + + 设置一个占位符id 您可以在子模板中通过该ID来插入内容, + 引用格式: <asp:content />。]]> + + + 选择一个 + 占位符id。]]> + 点击图片查看完整大小 拾取项 查看缓存项 + 创建文件夹... + 与原始连接 + 包括后代 + 最友好的社区 + 链接到页面 + 在新窗口或选项卡中打开链接的文档 + 链接到媒体 + 选择媒体 + 选择图标 + 选择项 + 选择链接 + 选择宏 + 选择内容 + 选择成员 + 选择成员组 + 未找到图标 + 此宏没有参数 + 外部登录提供程序 + 异常详细信息 + 堆栈跟踪 + 内部异常 + 链接您的 + 取消链接您的 + 帐户 + 选择编辑器 + 帐号 + 选择编辑器 + 选择代码段 - %0%’
您可以在左侧的“语言”中添加一种语言]]>
+ + %0%’
您可以在左侧的“语言”中添加一种语言 + ]]> +
语言名称 + 编辑字典项的键。 + + + + + + 输入您的用户名 + 输入您的密码 + 确认密码 + 命名 %0%... + 输入名称... + 标签... + 输入说明... + 输入搜索关键字... + 输入过滤词... + 键入添加tags (在每个tag之后按 enter)... + 输入您的电子邮件 + 您的用户名通常是您的电子邮件 + 允许在根目录 + 只能在内容和媒体树的根级别创建具有选中的内容类型 允许子项节点类型 + 文档类型组合 创建 删除选项卡 描述 新建选项卡 选项卡 缩略图 + 启用列表视图 + 配置内容项以显示可排序和搜索的子项列表, 这些子项将不会显示在树中 + 当前列表视图 + 活动列表视图数据类型 + 创建自定义列表视图 + 删除自定义列表视图 添加预设值 @@ -271,6 +385,13 @@ 关联的样式表 显示标签 宽和高 + 所有属性类型 & 属性数据 + 使用此数据类型将被永久删除, 请确认您还要删除这些 + 是, 删除 + 以及使用此数据类型的所有属性类型 & 属性数据 + 选择要移动的文件夹 + 在树结构下面 + 被移到下面 数据已保存,但是发布前您需要修正一些错误: @@ -286,10 +407,17 @@ %0% 格式不正确 + 从服务器收到错误 该文件类型已被管理员禁用 注意,尽管配置中允许CodeMirror,但是它在IE上不够稳定,所以无法在IE运行。 请为新的属性类型填写名称和别名! 权限有问题,访问指定文件或文件夹失败! + 加载Partial视图脚本时出错(文件: %0%) + 加载 userControl 时出错 '%0%' + 加载 customControl 时出错(程序集: %0%, 类型: '%1%') + 加载 MacroEngine 脚本时出错 (文件: %0%) + "解析 xslt 文件时出错: %0% + "读取 xslt 文件时出错: %0% 请输入标题 请选择类型 图片尺寸大于原始尺寸不会提高图片质量,您确定要把图片尺寸变大吗? @@ -302,13 +430,17 @@ 非合并单元格不能分离。 XSLT源码出错 XSLT未保存,因为包含错误。 + 此属性使用的数据类型存在配置错误, 请检查数据类型 关于 操作 + 操作 添加 别名 + 所有 您确定吗? + 返回 边框 取消 @@ -318,6 +450,7 @@ 关闭窗口 备注 确认 + 约束 强制属性 继续 复制 @@ -329,6 +462,7 @@ 已删除 正在删除… 设计 + 字典 规格 下载 @@ -338,7 +472,7 @@ 邮箱 错误 查找文档 - 文件夹 + 第一 帮助 图标 @@ -346,8 +480,10 @@ 内边距 插入 安装 + 无效 对齐 语言 + 最后 布局 加载中 锁定 @@ -355,7 +491,9 @@ 退出 注销 + 必填项 移动 + 更多 名称 新的 下一步 @@ -373,17 +511,22 @@ 接收数据邮箱 回收站 保持状态中 + 移除 重命名 更新 + 必填 重试 权限 搜索 + 对不起, 我们找不到你要找的东西。 + 未添加任何项目 服务器 显示 在发送时预览 大小 排序 - Submit + 提交 + 类型 输入内容开始查找… @@ -398,9 +541,52 @@ 欢迎… - Reorder - I am done reordering + 文件夹 + 搜索结果 + 重新排序 + 我已结束排序 + 预览 + 更改密码 + + 列表视图 + 保存中... + 当前 + 嵌入 + 已选择 + + + 黑色 + 绿色 + 黄色 + 橙色 + 蓝色 + 红色 + + + + 添加选项卡 + 添加属性 + 添加编辑器 + 添加模板 + 添加子节点 + 添加子项 + 编辑数据类型 + 导航节 + 快捷方式 + 显示快捷方式 + 切换列表视图 + 切换允许作为根 + 注释/取消注释行 + 移除行 + 向上复制行 + 向下复制行 + 向上移动行 + 向下移动线条 + 一般 + 编辑 + + 背景色 粗体 @@ -408,6 +594,7 @@ 字体 文本 + 页面 @@ -416,93 +603,109 @@ 无法保存web.config文件,请手工修改。 发现数据库 数据库配置 - 安装进行 %0% 数据库配置]]> + + 安装进行 %0% 数据库配置 + ]]> + 下一步继续。]]> - 数据库未找到!请检查数据库连接串设置。

+ + 数据库未找到!请检查数据库连接串设置。

您可以自行编辑“web.config”文件,键名为 “UmbracoDbDSN”

- 当自行编辑后,单击重试按钮
。 + 当自行编辑后,单击重试按钮
。 如何编辑web.config

- ]]>
- + ]]> + + + 如有必要,请联系您的系统管理员。 - 如果您是本机安装,请使用管理员账号。 - ]]> - + + + 点击更新来更新系统到 %0%

不用担心更新会丢失数据!

- ]]>
+ ]]> +
点击下一步继续。]]> + 下一步继续]]> 需要修改默认密码!]]> 默认账户已禁用或无权访问系统!

点击下一步继续。]]> 安装过程中默认用户密码已更改

点击下一步继续。]]> 密码已更改 - - 系统创建了一个默认用户(‘admin’)和默认密码(‘default’)。现在密码是随机的。 -

-

- 该步骤建议您修改默认密码。 -

- ]]>
作为入门者,从视频教程开始吧! 点击下一步 (或在Web.config中自行修改UmbracoConfigurationStatus),意味着您接受上述许可协议。 安装失败。 受影响的文件和文件夹 此处查看更多信息 您需要对以下文件和文件夹授于ASP.NET用户修改权限 - 您当前的安全设置满足要求!

- 您可以毫无问题的运行系统,但您不能安装系统所推荐的扩展包的完整功能。 - ]]>
+ + 您当前的安全设置满足要求!

+ 您可以毫无问题的运行系统,但您不能安装系统所推荐的扩展包的完整功能。]]> +
如何解决 点击阅读文字版 视频教程 ]]> - 您当前的安全设置有问题! + + 您当前的安全设置有问题!

- 您可以毫无问题的运行系统,但您不能新建文件夹、也不能安装系统所推荐的包的完整功能。 - ]]>
- 您当前的安全设置不适合于系统! + 您可以毫无问题的运行系统,但您不能新建文件夹、也不能安装系统所推荐的包的完整功能。 ]]> + + + 您当前的安全设置不适合于系统!

- 您需要修改系统访问权限。 - ]]>
- 您当前的权限设置正确!

- 您可以运行系统并安装其它扩展包! - ]]>
+ 您需要修改系统访问权限。]]> +
+ + 您当前的权限设置正确!

+ 您可以运行系统并安装其它扩展包!]]> +
解决文件夹问题 点此查看ASP.NET和创建文件夹的问题解决方案 设置文件夹权限 - + + + 我要从头开始 - + 如何操作?) 您也可以安装晚一些安装“Runway”。 - ]]> + ]]> + 您刚刚安装了一个干净的系统,要继续吗? “Runway”已安装 - + 这是我们推荐的模块,您也可以查看 全部模块 - ]]> + ]]> + 仅推荐高级用户使用 给我一个简单的网站 - + “Runway”是一个简单的,包含文件类型和模板的示例网站。安装程序会自动为您安装。 您可以自行编辑和删除之。 “Runway”为新手提供了最佳的入门功能 +

- Runway: Home page, Getting Started page, Installing Modules page.
- 可选模块: Top Navigation, Sitemap, Contact, Gallery. + Runway: 主页, 开始页, 安装模块页.
+ 可选模块: 顶部导航, 站点地图, 联系我们, 图库.
- ]]>
+ ]]> + “Runway”是什么? 步骤 1/5:接受许可协议 步骤 2/5:数据库配置 @@ -510,23 +713,34 @@ 步骤 4/5:系统安全性 步骤 5/5:一切就绪,可以开始使用系统。 感谢选择我们的产品 - 浏览您的新站点 -您安装了“Runway”,那么来瞧瞧吧。]]> - 更多的帮助信息 -从社区获取帮助]]> + + 浏览您的新站点 +您安装了“Runway”,那么来瞧瞧吧。]]> + + + 更多的帮助信息 +从社区获取帮助]]> + 系统 %0% 安装完毕 - /web.config file 的 AppSetting 键 UmbracoConfigurationStatus'%0%'。]]> + + /web.config file 的 AppSetting 键 + UmbracoConfigurationStatus'%0%'。]]> + 立即开始请点“运行系统”
如果您是新手, 您可以得到相当丰富的学习资源。]]>
- 运行系统 -管理您的网站, 运行后台添加内容,也可以添加模板和功能。 - ]]> + + 运行系统 +管理您的网站, 运行后台添加内容, +也可以添加模板和功能。]]> + 无法连接到数据库。 系统版本 3 系统版本 4 观看 - +
-按 “下一步”进入向导。]]>
+按 “下一步”进入向导。]]> + 语言代码 @@ -537,8 +751,28 @@ 已更新,继续工作。 - © 2001 - %0%

]]>
- 欢迎使用Umbraco,在下方输入用户名和密码 + 星期一快乐 + 星期二快乐 + 星期三快乐 + 星期四快乐 + 星期五快乐 + 星期六快乐 + 星期天快乐 + 在下方登录 + 登录 + 会话超时 + © 2001 - %0%
Umbraco.com

]]>
+ 忘记密码? + 电子邮件将发送到地址指定的链接, 以重置您的密码 + 如果电子邮件与我们的记录相符, 则将发送带有密码重置指令的邮件 + 返回登录表单 + 请提供新密码 + 您的密码已更新 + 您单击的链接无效或已过期 + Umbraco: 重置密码 + + 您的用户名登录到 Umbraco 后台是: %0%

点击 这里 重置密码,或复制链接粘贴到您的浏览器访问:

%1%

]]> +
仪表板 @@ -561,57 +795,103 @@ 为 %0% 编写通知 - + - %0%:

-

您好!这是一封自动发送的邮件,告诉您任务'%1%'已在'%2%'被用户'%3%'执行

- -

+ Have a nice day! + + 来自Umbraco机器人 + ]]> + + + %0%:

+ +

您好!这是一封自动发送的邮件,告诉您任务'%1%' + 已在'%2%' + 被用户'%3%'执行 +

+ +

更新概况:

- - %6% -
-

+ + %6% +
+

-
-
+

祝您愉快!

该信息由系统自动发送 -

- ]]> +

]]> + 在 %2%,[%0%] 关于 %1% 的通告已执行。 通知 - + 选择 ".umb" 或者 ".zip" 文件 - ]]> + ]]> + + 拖入上传 + 或单击此处选择文件 + 上传包 + 通过从计算机中选择一个本地包来安装它。仅从您知道和信任的来源安装软件包 + 上传另一包 + 取消并上载另一个包 + 许可证 + 我接受 + 使用条款 + 安装包 + 完成 + 已安装的软件包 + 您没有安装任何软件包 + "程序包" 图标浏览可用的包]]> + 搜索包 + 结果为 + 我们找不到任何东西 + 请尝试搜索其他包或浏览类别 + 流行 + 新版本 + + karma 点 + 信息 + 所有者 + 贡献者 + 创建 + 当前版本 + .NET 版本 + 下载 + 喜欢 + 兼容性 + 此软件包与社区成员报告的 Umbraco 的以下版本兼容。报告100% 以下版本不能保证完全兼容 + 外部来源 作者 演示 文档 元数据 名称 扩展包不含任何项 -
- 点击下面的“卸载”,您可以安全的删除。 - ]]>
+ +
+ 点击下面的“卸载”,您可以安全的删除。]]> +
无可用更新 选项 说明 @@ -620,16 +900,28 @@ 已卸载 扩展包卸载成功 卸载 - - 注意:卸载包将导致所有依赖该包的东西失效,请确认。 - ]]> + + + 注意: + 卸载包将导致所有依赖该包的东西失效,请确认。 ]]> + 从程序库下载更新 更新扩展包 更新说明 - 扩展包有可用的更新,您可以从程序库网站更新。 + 此软件包有一个可用的升级。您可以直接从 Umbraco 软件包存储库下载。 版本 版本历史 访问扩展包网站 + 已安装软件包 + 此软件包无法安装, 它需要一个最小的 Umbraco 版本的%0% + 卸载中... + 下载中... + 导入中... + 安装中... + 重启中, 请稍候... + 所有完成后, 您的浏览器将立即刷新, 请稍候... + 请单击 "完成" 以完成安装和重新加载页面。 + Uploading package... 带格式粘贴(不推荐) @@ -656,37 +948,61 @@ 如果您只希望提供一个用户名和密码就能访问 - - + + + + + + + + + + + - - + 包含未发布的子项 正在发布,请稍候… %0% 中的 %1% 页面已发布… %0% 已发布 %0% 及其子项已发布 发布 %0% 及其子项 - 确定 发布 %0%

-要发布当前页和所有子页,请选中 全部发布 发布所有子页。 - ]]>
+ + 确定 发布 %0%

+ 要发布当前页和所有子页,请选中 全部发布 发布所有子页。 + ]]> +
+ + + 您没有配置任何认可的颜色 - - - - - - - - - - + 输入外部链接 + 选择内部页面 + 标题 + 链接 + 新窗口 + 输入新标题 + 输入链接 + + + Reset + Define crop + Give the crop an alias and its default width and height + Save crop + Add new crop 当前版本 @@ -701,9 +1017,9 @@ 编辑脚本 - Concierge + 礼宾 内容 - Courier + 导游 开发 Umbraco配置向导 媒体 @@ -713,11 +1029,17 @@ 统计 翻译 用户 + 帮助 + 窗体 + 统计 + + + 转到 + 帮助主题 + 视频章节 + 最佳 Umbraco 视频教程 - 作为主控文档类型. 主控文档类型的标签只能在主控文档类型里修改。 - 主控文档类型激活 - 该文档类型使用 默认模板 字典键 要导入文档类型,请点击“浏览”按钮,再点击“导入”,然后在您电脑上查找 ".udt"文件导入(下一页中需要您再次确认) @@ -725,20 +1047,33 @@ 节点类型 类型 样式表 + 脚本 样式表属性 选项卡 选项卡标题 选项卡 + 主控文档类型激活 + 该文档类型使用 + 作为主控文档类型. 主控文档类型的标签只能在主控文档类型里修改。 没有字段设置在该标签页 + 主控文档类型 + 创建匹配模板 + 添加图标 - Sort order - Creation date + 排序次序 + 创建日期 排序完成。 上下拖拽项目或单击列头进行排序 -
请不要关闭窗口]]>
+ + 验证 + 在保存项之前必须修复验证错误 + 失败 + 用户权限不足, 无法完成操作 + 取消 + 操作被第三方插件取消。 发布因为第三方插件取消 属性类型已存在 属性类型已创建 @@ -748,7 +1083,6 @@ 选项卡已创建 选项卡已删除 id为%0%的选项卡已删除 - 内容已取消发布 样式表未保存 样式表已保存 样式表保存,无错误。 @@ -775,6 +1109,8 @@ 文件保存 文件保存,无错误。 语言已保存 + 已保存媒体类型 + 已保存成员类型 Python脚本未保存 Python脚本因为错误未能保存 Python已保存 @@ -788,10 +1124,16 @@ XSLT无法保存,请检查权限。 XSLT已保存 XSLT无错误 + 未发布内容 片段视图已保存 片段视图保存,无错误。 片段视图未保存 片段视图因为错误未能保存 + 已保存脚本视图 + 脚本视图保存时没有发生任何错误! + 未保存脚本视图 + 保存文件时出错。 + 保存文件时出错。 使用CSS语法,如:h1、.redHeader、.blueTex。 @@ -803,76 +1145,208 @@ 编辑模板 + 部分 插入内容区 插入内容占位符 + 插入 + 选择要插入到模板中的内容 插入字典项 + 字典项是可翻译的文本部分的占位符, 这使得为多语言网站创建设计变得容易。 插入宏 + + 宏是一个可配置的组件, 对于 + 设计的可重用部分, 在这里您需要提供参数的选项, + 如画廊、表格和列表。 + 插入页字段 + 显示当前页中指定字段的值, 其中有用于修改值或回退到替代值的选项。 + 分部视图 + + 分部视图是可以在另一个模板内呈现的单独的模板文件, + 它对于重用标记或将复杂的模板分离到单独的文件中非常重要。 + 母版 - 模板标签快速指南 + 无主模板 + 无主 + 呈现子模板 + + @RenderBody(). + ]]> + + 定义命名节 + + @section { ... }. 这可以呈现在 + 此模板的父级的特定区域, 请使用 @RenderSection. + ]]> + + 呈现命名节 + + @RenderSection(name). + 这将呈现子模板的一个区域, 它被包装在相应的 @section [名称] {...} 定义中. + ]]> + + 节名称 + 节是必需的 + + 如果强制, 子模板必须包含 @section 定义, 否则将显示错误。 + + 查询生成器 + 生成查询 + 返回的项, 在 + 我要 + 所有内容 + 类型 "%0%"的内容 + from + 我的网站 + where + and + is + is not + before + before (包含选定日期) + after + after (包含选定日期) + equals + does not equal + contains + does not contain + greater than + greater than or equal to + less than + less than or equal to + Id + Name + Created Date + Last Updated Date + order by + ascending + descending 模板 + - Choose type of content - Choose a layout - Add a row - Add content - Drop content - Settings applied - - This content is not allowed here - This content is allowed here - - Click to embed - Click to insert image - Image caption... - Write here... - - Grid Layouts - Layouts are the overall work area for the grid editor, usually you only need one or two different layouts - Add Grid Layout - Adjust the layout by setting column widths and adding additional sections - Row configurations - Rows are predefined cells arranged horizontally - Add row configuration - Adjust the row by setting cell widths and adding additional cells - - Columns - Total combined number of columns in the grid layout - - Settings - Configure what settings editors can change - - Styles - Configure what styling editors can change - - Settings will only save if the entered json configuration is valid - - Allow all editors - Allow all row configurations + Rich Text Editor + Image + Macro + Embed + Headline + Quote + 选择内容类别 + 选择一项布局 + 添加一行 + 添加内容 + 丢弃内容 + 设置已应用 + 此处不允许有该内容 + 此处允许有该内容 + 点击嵌入 + 点击添加图片 + 图片说明... + 在这里输入... + 网格布局 + 布局是网格编辑器的整体工作区域, 通常只需要一个或两个不同的布局 + 添加网络布局 + 通过设置列宽并添加其他节来调整版式 + 行配置 + 行是水平排列的预定义单元格 + 添加行配置 + 通过设置单元格宽度和添加其他单元格来调整行 + + 网格布局中的总和列数 + 设置 + 配置编辑器可以更改的设置 + 样式 + 配置编辑器可以更改的样式 + 输入的 json 配置有效, 设置才可保存 + 允许所有的编辑器 + 允许所有行配置 + 设置为默认值 + 选择附加 + 选择默认值 + 已增加 + + + 组合 + 您没有添加任何选项卡 + 添加新选项卡 + 添加其他选项卡 + 继承自 + 添加属性 + 必需的标签 + 启用列表视图 + 配置内容项以显示其子项的可排序和搜索列表, 这些子项将不会显示在树中 + 允许的模板 + 选择允许在该类型的内容上使用哪些模板编辑器 + 允许作为根 + 允许编辑器在内容树的根目录中创建此类型的内容 + 是 - 允许根中的此类型的内容 + 允许的子节点类型 + 允许在该类型的内容下方创建指定类型的内容 + 选择子节点 + 从现有文档类型继承选项卡和属性。如果存在同名的选项卡, 则新选项卡将添加到当前文档类型或合并。 + 此内容类型在组合中使用, 因此不能自行组成。 + 没有可供组合使用的内容类型。 + 可用编辑器 + 重用 + 编辑器设置 + 配置 + 是,删除 + 被移动到下方 + 被复制到下面 + 选择要移动的文件夹 + 选择要复制的文件夹 + 在下面的树结构中 + 所有文档类型 + 所有文档 + 所有媒体项目 + 使用此文档类型将被永久删除, 请确认您还要删除这些文件。 + 使用此媒体类型将被永久删除, 请确认您也要删除这些。 + 使用此成员类型将被永久删除, 请确认您想要删除这些 + 和所有使用此类型的文档 + 和所有使用此类型的媒体项目 + 和使用此类型的所有成员 + 使用此编辑器将用新设置更新 + 成员可编辑 + 显示成员配置文件 + + + 添加后备字段 + 后备字段 + 添加默认值 + 默认值 替代字段 替代文本 大小写 + 编码 选取字段 转换换行符 + 是, 转换换行符 将换行符转化为&lt;br&gt; 自定义字段 是,仅日期 - 编码 + 格式和编码 格式化时间 + 根据活动区域性将该值设置为日期或日期。 HTML编码 将替换HTML中的特殊字符 将在字段值后插入 将在字段值前插入 小写 + 修改输出 + 输出示例 字段后插入 字段前插入 递归 - 移除段落符号 - 将移除&lt;P&gt;标签 + 是, 让它递归 + 分隔符 标准字段 大写 URL编码 @@ -883,10 +1357,12 @@ 标记为您的任务 - 分配给您. 查看详情, 点击“详情”或页名。 + + 分配给您. 查看详情, 点击“详情”或页名。 如果需要XML格式,请点击“下载 XML”链接。
关闭翻译任务,请返回详细视图点击“关闭”按钮。 - ]]>
+ ]]> +
关闭任务 翻译详情 将翻译任务下载为xml @@ -894,7 +1370,8 @@ 下载 XML DTD 字段 包含子页 - + + Have a nice day! + + 来自Umbraco 机器人的祝福 + ]]> + [%0%]翻译任务:%1% 没有翻译员,请创建翻译员角色的用户。 您创建的任务 - 您创建的页面. 查看详情, 点击“详情” 或页名. + + 您创建的页面. 查看详情, 点击“详情” 或页名. 如果需要XML格式,请点击“下载 Xml”链接。
关闭翻译任务,请返回详细视图点击“关闭”按钮。 - ]]>
+ ]]> +
页面'%0%'已经发送给翻译 + 请选择内容应翻译成的语言 发送页面'%0%'以便翻译 分配者 任务开启 @@ -943,8 +1428,11 @@ 角色 会员类型 文档类型 + 关系类型 扩展包 扩展包 + 分部视图 + 分部视图宏文件 Python文件 从在线程序库安装 安装Runway @@ -954,6 +1442,7 @@ 样式表 模板 XSLT文件 + 分析 有可用更新 @@ -965,8 +1454,9 @@ 管理员 分类字段 更改密码 - 要改变密码,请在框中输入新密码,然后单击“更改密码”。 + 更改密码 确认新密码 + 要改变密码,请在框中输入新密码,然后单击“更改密码”。 内容频道 描述字段 禁用用户 @@ -977,16 +1467,16 @@ 登录 默认打开媒体项 区域 - 更改密码 禁用后台管理界面 + 旧密码 密码 重设密码 您的密码已更改! 重输密码 - 当前密码 输入新密码 - 密码错误 新密码不能为空! + 当前密码 + 密码错误 新密码和重输入的密码不一致,请重试! 重输的密码和原密码不一致! 替换子项权限设置 @@ -996,8 +1486,139 @@ 默认打开内容项 用户名 用户权限 - 用户类型 - 用户类型 撰稿人 + 翻译人 + 更改 + 你的资料 + 你最近的历史信息 + 会话过期于 + + + 验证 + 验证为电子邮件 + 验证为数字 + 验证为 url + ...或输入自定义验证 + 字段是强制性的 + 输入正则表达式 + 您需要添加至少 + 你只能有 + + 选定的项 + 无效日期 + 不是一个数字 + 无效的电子邮件 + + + + Value is set to the recommended value: '%0%'. + Value was set to '%1%' for XPath '%2%' in configuration file '%3%'. + Expected value '%1%' for '%2%' in configuration file '%3%', but found '%0%'. + Found unexpected value '%0%' for '%2%' in configuration file '%3%'. + + + Custom errors are set to '%0%'. + Custom errors are currently set to '%0%'. It is recommended to set this to '%1%' before go live. + Custom errors successfully set to '%0%'. + MacroErrors are set to '%0%'. + MacroErrors are set to '%0%' which will prevent some or all pages in your site from loading completely if there are any errors in macros. Rectifying this will set the value to '%1%'. + MacroErrors are now set to '%0%'. + + + Try Skip IIS Custom Errors is set to '%0%' and you're using IIS version '%1%'. + Try Skip IIS Custom Errors is currently '%0%'. It is recommended to set this to '%1%' for your IIS version (%2%). + Try Skip IIS Custom Errors successfully set to '%0%'. + + + File does not exist: '%0%'. + '%0%' in config file '%1%'.]]> + There was an error, check log for full error: %0%. + Members - Total XML: %0%, Total: %1%, Total invalid: %2% + Media - Total XML: %0%, Total: %1%, Total invalid: %2% + Content - Total XML: %0%, Total published: %1%, Total invalid: %2% + Your site certificate was marked as valid. + Certificate validation error: '%0%' + Error pinging the URL %0% - '%1%' + You are currently %0% viewing the site using the HTTPS scheme. + The appSetting 'umbracoUseSSL' is set to 'false' in your web.config file. Once you access this site using the HTTPS scheme, that should be set to 'true'. + The appSetting 'umbracoUseSSL' is set to '%0%' in your web.config file, your cookies are %1% marked as secure. + Could not update the 'umbracoUseSSL' setting in your web.config file. Error: %0% + + + Enable HTTPS + Sets umbracoSSL setting to true in the appSettings of the web.config file. + The appSetting 'umbracoUseSSL' is now set to 'true' in your web.config file, your cookies will be marked as secure. + Fix + Cannot fix a check with a value comparison type of 'ShouldNotEqual'. + Cannot fix a check with a value comparison type of 'ShouldEqual' with a provided value. + Value to fix check not provided. + Debug compilation mode is disabled. + Debug compilation mode is currently enabled. It is recommended to disable this setting before go live. + Debug compilation mode successfully disabled. + Trace mode is disabled. + Trace mode is currently enabled. It is recommended to disable this setting before go live. + Trace mode successfully disabled. + All folders have the correct permissions set. + + %0%.]]> + %0%. If they aren't being written to no action need be taken.]]> + All files have the correct permissions set. + + %0%.]]> + %0%. If they aren't being written to no action need be taken.]]> + X-Frame-Options used to control whether a site can be IFRAMEd by another was found.]]> + X-Frame-Options used to control whether a site can be IFRAMEd by another was not found.]]> + Set Header in Config + Adds a value to the httpProtocol/customHeaders section of web.config to prevent the site being IFRAMEd by other websites. + A setting to create a header preventing IFRAMEing of the site by other websites has been added to your web.config file. + Could not update web.config file. Error: %0% + + + %0%.]]> + No headers revealing information about the website technology were found. + In the Web.config file, system.net/mailsettings could not be found. + In the Web.config file system.net/mailsettings section, the host is not configured. + SMTP settings are configured correctly and the service is operating as expected. + The SMTP server configured with host '%0%' and port '%1%' could not be reached. Please check to ensure the SMTP settings in the Web.config file system.net/mailsettings are correct. + %0%.]]> + %0%.]]> + + + 禁用 url 跟踪程序 + 启用 url 跟踪程序 + 原始网址 + 已重定向至 + 未进行重定向 + 当已发布的页重命名或移动时, 将自动对新页进行重定向。 + 删除 + 确实要删除 "%0%" 到 "%1%" 的重定向吗? + 重定向URL已删除。 + 删除重定向 url 时出错. + 是否确实要禁用 url 跟踪程序? + url 跟踪器现在已被禁用。 + 禁用 url 跟踪程序时出错, 可以在日志文件中找到更多信息。 + 现在已启用 url 跟踪程序。 + 启用 url 跟踪程序时出错, 可以在日志文件中找到更多信息。 + + + 没有可供选择的词典项目 diff --git a/WebCms/Umbraco/Config/Lang/zh_tw.xml b/WebCms/Umbraco/Config/Lang/zh_tw.xml new file mode 100644 index 0000000..1638772 --- /dev/null +++ b/WebCms/Umbraco/Config/Lang/zh_tw.xml @@ -0,0 +1,1353 @@ + + + + The Umbraco community + https://our.umbraco.com/documentation/Extending-Umbraco/Language-Files + + + 管理主機名稱 + 跟蹤審計 + 流覽節點 + 改變文檔類型 + 複製 + 創建 + 創建擴展包 + 刪除 + 禁用 + 清空回收站 + 匯出文檔類型 + 導入文檔類型 + 導入擴展包 + 即時編輯模式 + 退出 + 移動 + 提醒 + 公眾存取權限 + 發佈 + 取消發佈 + 重新載入節點 + 重新發佈整站 + 回復 + 許可權 + 回滾 + 提交至發佈者 + 發送給翻譯 + 排序 + 提交至發佈者 + 翻譯 + 更新 + 預設值 + + + 禁止訪問 + 添加功能變數名稱 + 移除 + 錯誤的節點 + 功能變數名稱錯誤 + 功能變數名稱重複 + 語言 + 功能變數名稱 + 新功能變數名稱 '%0%' 已創建 + 功能變數名稱 '%0%' 已刪除 + 功能變數名稱 '%0%' 已使用 + 功能變數名稱 '%0%' 已更新 + 編輯當前功能變數名稱 + + 繼承 + 語言 + 或從父節點繼承文化設定。
+ 也會改變目前節點設定,除非下方網域有其他項目。]]>
+ 功能變數名稱 + + + 查看 + + + 清除選擇 + 選擇 + 選擇目前資料夾 + 做別的事情 + 粗體 + 取消段落縮進 + 插入表單字段 + 插入圖片標題 + 編輯Html + 段落縮進 + 斜體 + 居中 + 左對齊 + 右對齊 + 插入連結 + 插入本地連結(錨點) + 圓點列表 + 數字清單 + 插入巨集 + 插入圖片 + 編輯關聯 + 回到清單 + 保存 + 保存並發佈 + 保存並提交審核 + 保存清單檢視 + 預覽 + 因未設置範本無法預覽 + 選擇樣式 + 顯示樣式 + 插入表格 + 產生模組 + + + 要更改所選節點的文檔類型,先在列表中選擇合適的文檔類型。 + 然後設置當前文檔類型到新文檔類型的各欄位間的對應映射關係並保存。 + 內容已被重新發佈 + 當前屬性 + 當前類型 + 不能改變文檔類型,因為沒有可替代的類型。 + 文檔類型已更改 + 要映射的欄位 + 映射欄位 + 新範本 + 新類型 + + 內容 + 選擇新的文檔類型 + 選中文檔的類型已被成功更改為[new type],以下欄位被映射: + + 不能完成欄位映射,因為存在一個欄位映射至多欄位的問題。 + 僅顯示可作為替代的文檔類型。 + + + 已發表 + 關於本頁 + 別名 + (圖片的替代文本) + 替代連結 + 點擊編輯 + 創建者 + 創建者 + 更新者 + 創建時間 + 此文件創建的日期時間 + 文檔類型 + 編輯 + 過期於 + 該項發佈之後有更改 + 該項沒有發佈 + 最近發佈 + 沒有可供顯示的項目 + 此列表中沒有可供顯示的項目 + 媒體類型 + 媒體連結位址 + 會員組 + 角色 + 會員類型 + 沒有選擇時間 + 頁標題 + 屬性 + 該文檔不可見,因為其上級 '%0%' 未發佈。 + 糟糕:該文檔已發佈,但是沒有更新至緩存(內部錯誤) + 糟糕:沒辦法連結到此網址(內部錯誤-請參見記錄) + 糟糕:此文件已經發表,但是網址和其他內容相衝 %0% + 發佈 + 發佈狀態 + 發佈於 + 取消發表於 + 清空時間 + 排序完成 + 拖拽項目或按一下列頭即可排序,可以按住Shift多選。 + 統計 + 標題(可選) + 其他說明文字(可選) + 類型 + 取消發佈 + 最近編輯 + 本文件修改時間 + 移除文件 + 連結到文檔 + 會員組成員 + 非會員組成員 + 子項目 + 目標 + 預計發表的時間(伺服器端) + 這是什麼意思?]]> + + + 點選以便上傳 + 拖曳檔案至此... + 媒體連結 + 或按這裡選擇檔案 + 只允許檔案類型為 + 檔案大小上限為 + + + 新增一位會員 + 所有會員 + + + 您想在哪裡創建 %0% + 創建在 + 選擇類型和標題 + "文檔類型"處變更。]]> + "媒體類型"處變更。]]> + 文檔類型沒有相關範本 + 沒有資料夾 + 新資料類別 + + + 流覽您的網站 + - 隱藏 + 如果Umbraco沒有打開,您可能需要允許彈出式視窗。 + 已經在新視窗中打開 + 重啟 + 訪問 + 歡迎 + + + 留下 + 放棄變更 + 您有未存檔的變更 + 您確定要離開本頁? - 您有未存檔的變更 + + + 完成 + 刪除 %0% 個項目 + 刪除 %0% 個項目 + 刪除 %1% 個中的 %0% 個項目 + 刪除 %1% 個中的 %0% 個項目 + 已發佈 %0% 個項目 + 已發佈 %0% 個項目 + 已發佈 %1% 個中的 %0% 個項目 + 已發佈 %1% 個中的 %0% 個項目 + 取消發佈 %0% 個項目 + 取消發佈 %0% 個項目 + 取消發佈 %1 個中的 %0% 個項目 + 取消發佈 %1 個中的 %0% 個項目 + 移動 %0% 個項目 + 移動 %0% 個項目 + 移動 %1 個中的 %0% 個項目 + 移動 %1 個中的 %0% 個項目 + 複製 %0% 個項目 + 複製 %0% 個項目 + 複製 %1 個中的 %0% 個項目 + 複製 %1 個中的 %0% 個項目 + + + 錨點名稱 + 管理主機名稱 + 關閉窗口 + 您確定要刪除嗎 + 您確定要禁用嗎 + 按一下此框確定刪除%0%項 + 您確定嗎? + 您確定嗎? + 剪切 + 編輯字典項 + 編輯語言 + 插入本地連結 + 插入字元 + 插入圖片標題 + 插入圖片 + 插入連結 + 插入巨集 + 插入表格 + 最近編輯 + 連結 + 內部連結: + 本地連結請用“#”號開頭 + 在新視窗中打開? + 巨集設置 + 本巨集沒有包含您可以編輯的屬性 + 粘貼 + 編輯許可權 + 正在清空回收站,請不要關閉窗口。 + 回收站已清空 + 從回收站刪除的項目將不可恢復 + regexlib.com的網站服務目前出現些狀況,而我們無能為力。我們對此不便感到十分抱歉。]]> + 查找規則運算式來驗證輸入,如: 'email、'zip-code'、'url'。 + 移除巨集 + 必填項目 + 網站已重建索引 + 網站緩存已刷新,所有已發佈的內容更新生效。 + 網站緩存將會刷新,所有已發佈的內容將會更新。 + 表格列數 + 表格行數 + 設定預留位置代碼 以便您要在子範本中插入內容到本範本時,填入此代碼到 <asp:content /> 裡面。]]> + 選擇預留位置代碼 於此清單中。您只能選擇目前父範本中的代碼。]]> + 點擊圖片查看完整大小 + 拾取項 + 查看緩存項 + 新增資料夾... + 與原本相關 + 最友善的社群 + 頁面連結 + 打開此連結文檔至新視窗或標籤頁 + 打開此連結文檔至全新視窗 + 打開此連結文檔在原本視窗中 + 媒體連結 + 選擇媒體 + 選擇圖示 + 選擇項目 + 選擇連結 + 選擇巨集 + 選擇內容 + 選擇會員 + 選擇會員群組 + 沒有找到任何圖示 + 本巨集沒有需要參數 + 外部登入提供者 + 例外細節 + 詳細記錄 + 內部例外 + 連結您的 + 取消連結您的 + 帳戶 + 選擇編輯器 + + + %0%' 編輯不同語言版本,
您可以在左方選單「語言」中增添新的語言 + ]]>
+ 語言名稱 + + + 輸入您的使用者名稱 + 輸入您的密碼 + 確認您的密碼 + 命名此 %0%... + 輸入一個名稱 + 標籤... + 輸入一段描述... + 搜尋請輸入... + 過濾請輸入... + 增加標籤(每個標籤後請按輸入鍵)... + 輸入您的電子郵件 + + + 允許放置於根節點 + 只有勾選「允許放置於根節點」的內容種類可以放在內容樹或媒體樹的最頂端 + 允許子項節點類型 + 文檔種類集合 + 創建 + 刪除選項卡 + 描述 + 新建選項卡 + 選項卡 + 縮略圖 + 允許清單檢視 + 允許內容項目顯示成可以排列及搜尋的清單,子項目不會被顯示 + 目前清單檢視 + 作用中的清單檢視資料類別 + 新增自訂清單檢視 + 移除自訂清單檢視 + + + 添加預設值 + 資料庫資料類型 + 資料類型唯一標識 + 渲染控制項 + 按鈕 + 允許高級設置 + 允許快顯功能表 + 插入圖片預設最大 + 關聯的樣式表 + 顯示標籤 + 寬和高 + + + 資料已保存,但是發佈前您需要修正一些錯誤: + 當前成員提供程式不支援修改密碼(EnablePasswordRetrieval的值應該為true) + %0% 已存在 + 發現錯誤: + 發現錯誤: + 密碼最少%0%位元,且至少包含%1%位元非字母數位記號 + %0% 必須是整數 + %1% 中的 %0% 欄位是必填項 + %0% 是必填項 + %1% 中的 %0% 格式不正確 + %0% 格式不正確 + + + 收到伺服器傳來的錯誤 + 該檔案類型已被管理員禁用 + 注意,儘管配置中允許CodeMirror,但是它在IE上不夠穩定,所以無法在IE運行。 + 請為新的屬性類型填寫名稱和別名! + 許可權有問題,訪問指定文檔或資料夾失敗! + 讀取片段視圖腳本錯誤(檔案:%0%) + 讀取使用者控制項 %0% 錯誤 + 讀取使用者控制項 %0% 錯誤(組件:%0%,類別:%1%) + 讀取巨集引擎腳本錯誤(檔案:%0%) + 分析XSLT檔案錯誤:%0% + 讀取XSLT檔案錯誤:%0% + 請輸入標題 + 請選擇類型 + 圖片尺寸大於原始尺寸不會提高圖片品質,您確定要把圖片尺寸變大嗎? + python腳本錯誤 + python腳本未保存,因為包含錯誤。 + 預設打開頁面不存在,請聯繫管理員 + 請先選擇內容,再設置樣式。 + 沒有可用的樣式 + 請把游標放在您要合併的兩個儲存格中的左邊儲存格 + 非合併儲存格不能分離。 + XSLT源碼出錯 + XSLT未保存,因為包含錯誤。 + 這是此屬性所使用的資料類別設定錯誤,請檢查資料類別 + + + 關於 + 操作 + 操作 + 添加 + 別名 + 所有 + 您確定嗎? + 回去 + 邊框 + + 取消 + 儲存格邊距 + 選擇 + 關閉 + 關閉窗口 + 備註 + 確認 + 強制屬性 + 繼續 + 複製 + 創建 + 資料庫 + 時間 + 默認 + 刪除 + 已刪除 + 正在刪除… + 設計 + 規格 + + 下載 + 編輯 + 已編輯 + 元素 + 郵箱 + 錯誤 + 查找文檔 + + 幫助 + 圖示 + 導入 + 內邊距 + 插入 + 安裝 + 不合格 + 對齊 + 語言 + 佈局 + 載入中 + 鎖定 + 登入 + 退出 + 登出 + 巨集 + 必要 + 移動 + 更多 + 名稱 + 新的 + 下一步 + + 屬於 + 確定 + 打開 + + 密碼 + 路徑 + 預留位置代碼 + 請稍候… + 上一步 + 屬性 + 接收資料郵箱 + 回收站 + 保持狀態中 + 重命名 + 更新 + 必要 + 重試 + 許可權 + 搜索 + 伺服器 + 顯示 + 在發送時預覽 + 大小 + 排序 + 送出 + 類型 + 輸入內容開始搜尋… + + 更新 + 更新 + 上傳 + 連結位址 + 用戶 + 用戶名 + + 查看 + 歡迎… + + + 資料夾 + 搜尋結果 + 重新排列 + 我已經完成排列 + 預覽 + 更改密碼 + + 清單檢視 + 存檔中... + 目前 + 內嵌 + 選取的 + + + + + + + + + + + 增加標籤頁 + 增加屬性 + 增加編輯器 + 增加範本 + 增加子節點 + 增加子項目 + 編輯資料類別 + 瀏覽區塊 + 捷徑 + 顯示捷徑 + 開關清單檢視 + 開關是否允許為根項目 + + + 背景色 + 粗體 + 前景色 + 字體 + 文本 + + + 頁面 + + + 無法連接到資料庫。 + 無法保存web.config檔,請手工修改。 + 發現資料庫 + 資料庫配置 + 安裝 按鈕來安裝Umbraco資料庫 %0% + ]]> + 下一步繼續。]]> + 沒有找到資料庫!請確認檔案"web.config"中的字串"connection string"是否正確。

+

請編輯檔案"web.config" (例如使用Visual Studio或您喜歡的編輯器),移動到檔案底部,並在名稱為"UmbracoDbDSN"的字串中設定資料庫連結資訊,並存檔。

+

+ 點選重試按鈕當上述步驟完成。
+ + 在此查詢更多編輯web.config的資訊。

]]>
+ + 若需要時,請聯繫您的網路公司。如果您在本地機器或伺服器安裝的話,您也許需要聯絡系統管理者。]]> + + 點選升級按鈕來升級Umbraco資料庫 %0%

+

+ 請別擔心 - 不會刪除任何資料而且馬上就會繼續運作! +

+ ]]>
+ 點選下一步繼續。]]> + 下一步繼續設定精靈。]]> + 預設使用者的密碼必須更改!]]> + 預設使用者已經被暫停或沒有Umbraco的使用權!

不需更多的操作步驟。點選下一步繼續。]]> + 安裝後預設使用者的密碼已經成功修改!

不需更多的操作步驟。點選下一步繼續。]]> + 密碼已更改 + 作為入門者,從視頻教程開始吧! + 點擊下一步 (或在Web.config中自行修改UmbracoConfigurationStatus),意味著您接受上述授權合約。 + 安裝失敗。 + 受影響的檔和資料夾 + 此處查看更多資訊 + 您需要對以下檔和資料夾授於ASP.NET用戶修改許可權 + 您的權限設定幾近完美!

+ 您可以正常執行Umbraco沒有任何問題,只差您將沒有辦法安裝那些建議需要全部許可權的插件。]]>
+ 如何解決 + 點擊閱讀文字版 + 影片教學來瞭解如何設定Umbraco的資料夾權限或閱讀文字版本。]]> + 您的權限可能有點小問題! +

+ 您可以正常執行Umbraco沒有任何問題,然而您將無法新增資料夾或安裝那些可以讓Umbraco發揮全力的插件。]]>
+ 您的權限設定尚未未完成! +

+ 您需要更新權限設定才能執行Umbraco。]]>
+ 您的權限設定完美無瑕!

+ 您已經準備好執行Umbraco和安裝插件!]]>
+ 解決資料夾問題 + 點此查看ASP.NET和創建資料夾的問題解決方案 + 設置資料夾許可權 + + 我要從頭開始 + 學習該怎麼做) + 您晚點仍可以選擇安裝Runway,請至開發者區域選擇安裝。 + ]]> + 您剛剛安裝了一個乾淨的系統,要繼續嗎? + “Runway”已安裝 + + 這是我們的模組推薦清單,選取您想要安裝的項目,或者至 查詢完整清單。 + ]]> + 僅推薦高級用戶使用 + 給我一個簡單的網站 + + "Runway"是一個提供基本檔案類別和範本的簡單網站。安裝程式會自動幫您設定Runway, + 但你仍可輕易編輯,擴充或移除它。它並非必要項目而且您可以在沒它的情況下完美執行Umbraco。然而, + Runway提供一個輕鬆簡便但基於寶貴經驗的平台讓您可以更快開始。 + 如果您安裝Runway,您還可以選擇名為「Runway模組」的基本區塊來加強Runway頁面。 +

+ + 內含於Runway: 首頁,準備開始頁面,模組安裝頁面。
+ 可選模組: 上方瀏覽列,網站地圖,聯絡,藝廊。 +
+ ]]>
+ “Runway”是什麼? + 步驟 1/5:接受授權合約 + 步驟 2/5:資料庫配置 + 步驟 3/5:文件許可權驗證 + 步驟 4/5:系統安全性 + 步驟 5/5:一切就緒,可以開始使用系統。 + 感謝選擇我們的產品 + 參觀您的新網站 +您剛安裝好Runway,何不瞧瞧它的模樣。]]> + 更多的幫忙與資訊 +從我們獲獎的社群得到幫助,瀏覽文件,或觀看免費影片來瞭解如何輕鬆架設網站,如何使用插件,和瞭解Umbraco項目名稱的快速上手指引。]]> + 系統 %0% 安裝完畢 + /web.config 檔案並且更新AppSetting中的字串UmbracoConfigurationStatus 內容為 '%0%'。]]> + 快速開始指引。
如果您是Umbraco的新成員, +您可以在其中找到相當多的資源。]]>
+ 啟動Umbraco +想要管理您的網站時,只需開啟Umbraco後台便可增加內容,更新範本和樣式表,或增添新功能。]]> + 無法連接到資料庫。 + 系統版本 3 + 系統版本 4 + 觀看 +
+ 點選"下一步"來啟動精靈。]]>
+ + + 語言代碼 + 語言名稱 + + + 使用者在空閒狀態下將會自動登出 + 已更新,繼續工作。 + + + 超級星期天快樂 + 瘋狂星期一快樂 + 熱鬧星期二快樂 + 美妙星期三快樂 + 悅耳星期四快樂 + 時髦星期五快樂 + 喵喵星期六快樂 + 下方登入 + 登入使用 + 連線時間過了 + © 2001 - %0%
Umbraco.com

]]>
+ 忘記密碼? + 一封內有重設密碼連結的電子郵件已經寄出給您 + 一封內有重設密碼連結的電子郵件已經寄到此信箱 + 回到登入畫面 + 請輸入新密碼 + 您的密碼已經更新 + 您點選的連結是無效或過期的 + Umbraco:重設密碼 + 您登入到後台的使用者名稱是:%0%

點選這裡來重設您的密碼或將此連結複製/貼上到您的瀏覽器:

%1%

]]>
+ + + 儀錶板 + 區域 + 內容 + + + 選擇上面的頁面… + %0% 被複製到 %1% + 將 %0% 複製到 + %0% 已被移動到 %1% + 將 %0% 移動到 + 作為內容的根結點,點“確定”。 + 尚未選擇節點,請選擇一個節點點擊“確定”。 + 類型不符不允許選擇 + 該項不能移到其子項 + 當前節點不能建在根節點下 + 您在子項的許可權不夠,不允許該操作。 + 複本和原本建立關聯 + + + 為 %0% 編寫通知 + + 哈嘍 %0%

+ +

這是一封自動產生的信件來通知您 %1% 工作 + 已經在頁面 %2% 上由使用者 %3% 執行完成 +

+ +

+

更新摘要:

+ + %6% +
+

+ + + +

祝您有美好的一天!

+ Umbraco機器人 謹上 +

]]>
+ 在 %2%,[%0%] 關於 %1% 的通告已執行。 + 通知 + + + + 按鈕並點選該檔案。Umbraco擴展包通常有「.zip」的副檔名。 + ]]> + 作者 + 演示 + 文檔 + 中繼資料 + 名稱 + 擴展包不含任何項 +
+ 您可以點選下方「移除擴展包」來安全地移除此項目。]]>
+ 無可用更新 + 選項 + 說明 + 程式庫 + 確認卸載 + 已卸載 + 擴展包卸載成功 + 卸載 + + 注意: 任何文檔,媒體或需要這些項目才能運作的物件將會停止運作,並可能使得系統不穩定, + 請小心移除。若有疑慮,請聯絡擴展包作者。]]> + 從程式庫下載更新 + 更新擴展包 + 更新說明 + 擴展包有可用的更新,您可以從程式庫網站更新。 + 版本 + 版本歷史 + 訪問擴展包網站 + 擴展包已安裝 + 這個擴展包無法安裝,它需要Umbraco至少是版本 %0% + 移除中... + 下載中... + 匯入中... + 安裝中... + 重新啟動中,請稍後... + 都好了,您的瀏覽器將重新整理,請稍待... + + + 帶格式粘貼(不推薦) + 您所粘貼的文本含有特殊字元或格式,Umbraco將清除以適應網頁。 + 無格式粘貼 + 粘貼並移除格式(推薦) + + + 基於角色的保護 + 請使用Umbraco的會員群組。]]> + 使用基於角色的授權需要首先建立會員組。 + 錯誤頁 + 當用戶登錄後訪問沒有許可權的頁時顯示該頁 + 選擇限制訪問此頁的方式 + %0% 現在處於受保護狀態 + %0% 的保護被取消 + 登錄頁 + 選擇公開的登錄入口 + 取消保護 + 選擇一個包含登錄表單和提示資訊的頁 + 選擇訪問該頁的角色類型 + 為此頁設置帳號和密碼 + 單用戶保護 + 如果您只希望提供一個用戶名和密碼就能訪問 + + + + + + + + 包含未發佈的子項 + 正在發佈,請稍候… + %0% 中的 %1% 頁面已發佈… + %0% 已發佈 + %0% 及其子項已發佈 + 發佈 %0% 及其子項 + 發佈按鈕來將%0%的內容設定為公開。

+ 您可以同時發佈本頁以及其子項目若您點選下面的包含子頁。 + ]]>
+ + + 您尚未設定任何許可顏色 + + + 輸入外部連結 + 選擇內部連結 + 標題 + 連結 + 新視窗 + 輸入新標題 + 輸入連結 + + + 重設 + + + 當前版本 + 紅色 文字將不會顯示於所選版本,而綠色表示增加部分。]]> + 文檔已回滾 + 這顯示所選版本的HTML格式,如果您想要比較兩版本的差異,請使用比較檢視 + 回滾至 + 選擇版本 + 查看 + + + 編輯腳本 + + + Concierge + 內容 + Courier + 開發 + 設定精靈 + 媒體 + 會員 + 消息 + 設置 + 統計 + 翻譯 + 用戶 + 說明 + 表單 + 統計 + + + 移至 + 說明主題為 + 影片主題為 + 最好的Umbraco影片教學 + + + 預設範本 + 字典鍵 + 要導入文檔類型,請點擊“流覽”按鈕,再點擊“導入”,然後在您電腦上查找 ".udt"檔導入(下一頁中需要您再次確認) + 新建選項卡標題 + 節點類型 + 類型 + 樣式表 + 腳本 + 樣式表屬性 + 選項卡 + 選項卡標題 + 選項卡 + 主控文件類型啟動 + 該文檔類型使用 + 作為主控文件類型. 主控文件類型的標籤只能在主控文件類型裡修改。 + 沒有欄位設置在該標籤頁 + 主文檔類別 + 新增對應範本 + 增加圖示 + + + 排列順序 + 增添時間 + 排序完成。 + 上下拖拽項目或按一下列頭進行排序 + + + + 驗證 + 驗證錯誤一定要修正才能儲存項目 + 失敗 + 使用者權限不足,無法完成操作 + 已取消 + 操作被協力廠商外掛程式取消 + 發佈被協力廠商外掛程式取消 + 屬性類型已存在 + 屬性類型已創建 + 資料類別:%1%]]> + 屬性類型已刪除 + 內容類別型已保存 + 選項卡已創建 + 選項卡已刪除 + id為%0%的選項卡已刪除 + 樣式表未保存 + 樣式表已保存 + 樣式表保存,無錯誤。 + 資料類型已保存 + 字典項已保存 + 因為上級頁面未發佈導致發佈失敗! + 內容已發佈 + 公眾可見 + 內容已保存 + 請發佈以使更改生效 + 提交審核 + 更改已提交審核 + 媒體已保存 + 媒體已保存 + 會員已保存 + 樣式表屬性已保存 + 樣式表已保存 + 範本已保存 + 保存使用者出錯(請查看日誌) + 用戶已保存 + 用戶類型已保存 + 檔未保存 + 檔無法保存,請檢查許可權。 + 檔保存 + 檔保存,無錯誤。 + 語言已保存 + 媒體類別已儲存 + 會員類別已儲存 + Python腳本未保存 + Python腳本因為錯誤未能保存 + Python已保存 + Python腳本無錯誤 + 範本未保存 + 範本別名相同 + 範本已保存 + 範本保存,無錯誤。 + XSLT未保存 + XSLT有錯誤 + XSLT無法保存,請檢查許可權。 + XSLT已保存 + XSLT無錯誤 + 內容已取消發佈 + 片段視圖已保存 + 片段視圖保存,無錯誤。 + 片段視圖未保存 + 片段視圖因為錯誤未能保存 + 腳本視圖已儲存 + 腳本視圖已儲存,沒有任何錯誤! + 腳本視圖未儲存 + 儲存檔案時發生錯誤 + 儲存檔案時發生錯誤 + + + 使用CSS語法,如:h1、.redHeader、.blueTex。 + 編輯樣式表 + 編輯樣式屬性 + 編輯器中的樣式屬性名 + 預覽 + 樣式 + + + 編輯範本 + 插入內容區 + 插入內容預留位置 + 插入字典項 + 插入巨集 + 插入頁欄位 + 母版 + 範本標籤快速指南 + 範本 + + + Rich Text Editor + Image + Macro + Embed + Headline + Quote + 選擇內容類別 + 選擇排列方式 + 新增一行 + 新增內容 + 放棄內容 + 設定已儲存 + 此處不允許有內容 + 此處允許有內容 + 點選來內嵌 + 點選來插入圖片 + 圖片標題... + 在此填寫... + 網格排列方式 + 排列是指網格編輯器的整體工作區域,通常您只需要一種或兩種排列方式 + 增加網格排列方式 + 藉由設定列寬以及增加新的區域來調整排列方式 + 行設定 + 行是預先水平排列的格子 + 增加行設定 + 藉由設定小格寬度和增添小格來調整此行 + + 網格排列方式的列總數 + 設定 + 調整設定編輯器可以改變的項目 + 樣式 + 調整樣式編輯器可以改變的項目 + 當JSON格式正確時設定才可以儲存 + 允許所有編輯器 + 允許所有行設定 + 定為預設 + 選擇額外 + 選擇預設 + 已增加 + + + 組合 + 您沒有增加任何選項卡 + 增加新的選項卡 + 增加另外的選項卡 + 繼承的表格 + 增加屬性 + 必要標籤 + 允許清單檢視 + 允許內容項目顯示成可以排列及搜尋的清單,子項目不會被顯示 + 允許的範本 + 選擇哪些範本編輯器可以使用於此類別的內容 + 允許為根項目 + 允許編輯器新增此類別的內容為根項目 + 是的 - 允許此類別內容為根項目 + 允許子節點種類 + 允許某些特定種類能夠成為此種類內容的子項目 + 選擇子節點 + 從已存在的文檔類別中繼承選項卡以及屬性。新選項卡將被新增至目前文檔種類或合併至已存在同名的選項卡中。 + 此內容種類已經用於集合中,因此不能重複添加本身。 + 沒有可用於集合的內容種類。 + 可用的編輯器 + 重複使用 + 編輯器設定 + 設定 + 是,刪除 + 已移至下層 + 已複製至下層 + 選擇要移動的資料夾 + 選擇要複製的資料夾 + 至下方樹狀結構 + 所有文檔種類 + 所有文檔 + 所有媒體項目 + 使用此文檔種類的將被永久刪除,請確認您也想要將它們刪除。 + 使用此媒體種類的將被永久刪除,請確認您也想要將它們刪除。 + 使用此會員種類的將被永久刪除,請確認您也想要將它們刪除。 + 以及所有使用此種類的文件項目 + 以及所有使用此種類的媒體項目 + 以及所有使用此種類的會員項目 + 使用此編輯器將會套用新設定 + 會員可以編輯 + 顯示於會員資料 + + + 替代欄位 + 替代文本 + 大小寫 + 編碼 + 選取欄位 + 轉換分行符號 + 將換行符號取代成為HTML標籤 &lt;br&gt; + 自訂欄位 + 是,僅日期 + 格式化時間 + HTML編碼 + 將替換HTML中的特殊字元 + 將在欄位值後插入 + 將在欄位值前插入 + 小寫 + + 欄位後插入 + 欄位前插入 + 遞迴 + 標準欄位 + 大寫 + URL編碼 + 將格式化URL中的特殊字元 + 當上面欄位值為空時使用 + 該欄位僅在主欄位為空時使用 + 是,含時間,分隔符號為: + + + 標記為您的任務 + 指派給您。請按「翻譯詳情」或頁面名稱觀看包含回應的詳細檢視畫面。 + 您也可以將此頁面下載成為XML格式檔案,請按「下載XML」按鈕。
+ 若想要關閉翻譯任務,請至細節頁面點選「結束」按鈕。 + ]]>
+ 關閉任務 + 翻譯詳情 + 將翻譯任務下載為XML + 下載 XML + 下載 XML DTD + 欄位 + 包含子頁 + + [%0%]翻譯任務:%1% + 沒有翻譯員,請創建翻譯員角色的用戶。 + 您創建的任務 + 由您創建的頁面。要瀏覽包含回應的詳細檢視畫面, + 點選「詳情」或頁面名稱。您可以下載此頁面成為XML格式檔案,請點選「下載XML」連結。 + 若要關閉翻譯任務,請至詳情檢視並點選「關閉」按鈕。 + ]]> + 頁面'%0%'已經發送給翻譯 + 請選擇本內容應該被翻譯成的語言 + 發送頁面'%0%'以便翻譯 + 分配者 + 任務開啟 + 總字數 + 翻譯到 + 翻譯完成。 + 您可以流覽剛翻譯的頁面,如果原始頁存在,您將得到兩者的比較。 + 翻譯失敗,XML可能損壞了。 + 翻譯選項 + 翻譯員 + 上傳翻譯的xml + + + 緩存流覽 + 回收站 + 創建擴展包 + 資料類型 + 字典 + 已安裝的擴展包 + 安裝皮膚 + 安裝新手套件 + 語言 + 安裝本地擴展包 + 巨集 + 媒體類型 + 會員 + 會員組 + 角色 + 會員類型 + 文檔類型 + 相關類型 + 擴展包 + 擴展包 + Python文件 + 從線上程式庫安裝 + 安裝Runway + Runway模組 + Scripting文件 + 腳本 + 樣式表 + 範本 + XSLT文件 + 統計 + + + 有可用更新 + %0%已就緒,點擊這裡下載 + 無到伺服器的連接 + 檢查更新失敗 + + + 管理員 + 分類欄位 + 更改密碼 + 更改密碼 + 確認新密碼 + 要改變密碼,請在框中輸入新密碼,然後按一下“更改密碼”。 + 內容頻道 + 描述欄位 + 禁用用戶 + 文檔類型 + 編輯 + 排除欄位 + 語言 + 登錄 + 默認打開媒體項 + 區域 + 禁用後臺管理介面 + 舊的密碼 + 密碼 + 重設密碼 + 您的密碼已更改! + 重輸密碼 + 輸入新密碼 + 新密碼不能為空! + 當前密碼 + 密碼錯誤 + 新密碼和重輸入的密碼不一致,請重試! + 重輸的密碼和原密碼不一致! + 替換子項許可權設置 + 您正在修改存取權限的頁面: + 選擇要修改許可權的頁 + 搜索子物件 + 預設打開內容項 + 用戶名 + 用戶許可權 + 撰稿人 + 翻譯者 + 改變 + 您的個人檔案 + 您的歷程記錄 + 連線到期於 + + + 驗證 + 以電子郵件驗證 + 以數字驗證 + 以網址驗證 + ...或輸入自訂驗證 + 必要欄位 + + + + 數值已設為推薦值:%0% + 在設定檔 %3% 中XPath %2% 的數值設為 %1% 。 + 在設定檔 %3% 中XPath %2% 的預期值設為 %1% ,但卻是 %0%。 + 在設定檔 %3% 中XPath %2% 的值為非預期值 %0%。 + + 自訂錯誤設定為 %0% + 自訂錯誤設定為 %0。建議在上線前改為 %1%。 + 自訂錯誤成功設定為 %0% + 巨集錯誤設為 %0% + 巨集錯誤設為 %0%,如此一來,當巨集有任何錯誤時會阻止某些或全部頁面正常載入。改正會將此設定 %1%。 + 巨集錯誤已設為 %0% + + 嘗試略過IIS自訂錯誤目前設為 %0%,而且您使用的IIS版本為 %1%。 + 嘗試略過IIS自訂錯誤目前設為 %0%,然而在您使用的IIS版本為 %2% 時,建議設定是 %1%。 + 嘗試略過IIS自訂錯誤已成功設為 %0%。 + + 檔案不存在:%0%。 + '%1%'中無法找到'%0%'。]]> + 有錯誤產生,請參閱下列錯誤的紀錄:%0%。 + 成員 - 所有XML:%0%,總共:%1%,不合格:%2% + 媒體 - 所有XML:%0%,總共發佈:%1%,不合格:%2% + 內容 - 所有XML:%0%,總共發佈:%1%,不合格:%2% + 憑證驗證錯誤:%0% + 網址探查錯誤:%0% - '%1%' + 您目前使用HTTPS瀏覽本站:%0% + 在您的web.config檔案中,appSetting的umbracoUseSSL是設為false。當您開始使用HTTPS時,應將其改為 true。 + 在您的web.config檔案中,appSetting的umbracoUseSSL是設為 %0%,您的cookies %0% 標成安全。 + 無法在您的web.config檔案中,更新appSetting的umbracoUseSSL設定,錯誤訊息:%0% + + 開啟HTTPS + 在web.config檔案中,將appSetting的umbracoUseSSL設true。 + 在您的web.config檔案中,appSetting的umbracoUseSSL已設為 true,您的cookies 將被標成安全。 + 修正 + 無法修正比較種類檢查為'ShouldNotEqual'。 + 用提供的數值無法修正比較種類檢查為'ShouldEqual'。 + 沒有提供要修正檢查的數值。 + 偵錯編輯模式關閉。 + 偵錯編輯模式目前已開啟。上線前建議將其關閉。 + 偵錯編輯模式已成功關閉。 + 詳細記錄模式已關閉。 + 詳細記錄模式目前已開啟。上線前建議將其關閉。 + 詳細記錄模式已成功關閉。 + 所有資料夾已有正確權限設定。 + + %0%。]]> + %0%。如果無須寫入,不需採取行動。]]> + 所有檔案已有正確權限設定。 + + %0%。]]> + %0%。如果無須寫入,不需採取行動。]]> + X-Frame-Options 設定能控制網站是否可以被其他人IFRAMEd已找到。]]> + X-Frame-Options 設定能控制網站是否可以被其他人IFRAMEd沒有找到。]]> + 調整設定的標頭 + 在 web.config 的 httpProtocol/customHeaders 區域增加設定來防止本站被別的網站IFRAMEd。 + 在 web.config 的 httpProtocol/customHeaders 區域已經增加設定來防止本站被別的網站IFRAMEd。 + 無法更新web.config檔案,錯誤:%0% + + %0%。]]> + 在標頭中沒有找到揭露網站技術的資訊。 + 在 Web.config 檔案中,找不到 system.net/mailsettings。 + 在 Web.config 檔案中的 system.net/mailsettings,沒有設定 host 。 + SMTP設定正確,而且服務正常運作。 + SMTP伺服器 %0% : %1% 無法連接。請確認在Web.config 檔案中 system.net/mailsettings 設定正確。 + %0%。]]> + %0%。]]> + + + 停止網址追蹤器 + 啟動網址追蹤器 + 原本網址 + 轉址成 + 沒有任何轉址 + 當發佈後的頁面改名或移動時,會自動轉址至新網頁。 + 移除 + 您確定要移除從 %0% 到 %1% 的轉址嗎? + 轉址已移除。 + 移除轉址錯誤。 + + 您確定要停止轉址追蹤器? + 轉址追蹤器已停止。 + 停止轉址追蹤器錯誤,更多資訊請參閱您的紀錄檔。 + 轉址追蹤器已開啟。 + 啟動轉址追蹤器錯誤,更多資訊請參閱您的紀錄檔。 + +
\ No newline at end of file diff --git a/WebCms/Umbraco/Dashboard/UserControlProxy.aspx b/WebCms/Umbraco/Dashboard/UserControlProxy.aspx index e1c897c..ee28d0d 100644 --- a/WebCms/Umbraco/Dashboard/UserControlProxy.aspx +++ b/WebCms/Umbraco/Dashboard/UserControlProxy.aspx @@ -13,6 +13,7 @@ + diff --git a/WebCms/Umbraco/Developer/Macros/editMacro.aspx b/WebCms/Umbraco/Developer/Macros/editMacro.aspx index b2f6eef..a968d0a 100644 --- a/WebCms/Umbraco/Developer/Macros/editMacro.aspx +++ b/WebCms/Umbraco/Developer/Macros/editMacro.aspx @@ -58,6 +58,9 @@ + + + diff --git a/WebCms/Umbraco/Developer/Packages/editPackage.aspx b/WebCms/Umbraco/Developer/Packages/editPackage.aspx index 34310a0..5d8a9c4 100644 --- a/WebCms/Umbraco/Developer/Packages/editPackage.aspx +++ b/WebCms/Umbraco/Developer/Packages/editPackage.aspx @@ -39,7 +39,6 @@ - @@ -191,6 +190,7 @@ + @@ -204,7 +204,7 @@ during installation and uninstallation.
All actions are formed as a xml node, containing data for the action to be performed. - Package actions documentation

-<%@ Import Namespace="umbraco" %> +<%@ Page Language="c#" MasterPageFile="../../masterpages/umbracoPage.Master" +AutoEventWireup="True" Inherits="umbraco.presentation.developer.packages.Installer" Trace="false" ValidateRequest="false" %> <%@ Register TagPrefix="cc1" Namespace="umbraco.uicontrols" Assembly="controls" %> - - - - - - - - - - - - - -
-

- Only install packages from sources you know and trust!

-

- When installing an Umbraco package you should use the same caution as when you install - an application on your computer.

-

- A malicious package could damage your Umbraco installation just like a malicious - application can damage your computer. -

-

- It is recommended to install from the official Umbraco package - repository or a custom repository whenever it's possible. -

-

- - -

-
-
- - -

- -
- - - <%= umbraco.ui.Text("packager", "chooseLocalPackageText") %> - -

-
- - - - -
- - -
-

- This repository requires authentication before you can download any packages from - it.
- Please enter email and password to login. -

-
-
- - - - - - -
- - - - - -
-

- Please note: Installing a package containing several items and - files can take some time. Do not refresh the page or navigate away before, the installer - notifies you once the install is completed. -

-
- - - - - - - - - - - - - - - - - -
-

Binary files in the package!

- - Read more... -
-

- This package contains .NET code. This is not unusual as .NET code - is used for any advanced functionality on an Umbraco powered website.

-

- However, if you don't know the author of the package or are unsure why this package - contains these files, it is adviced not to continue the installation. -

-

- The Files in question:
-

    - -
-

-
-
- -
- - -
-

- Legacy Property editors detected

- Read more... -
-

- This package contains legacy property editors which are not compatible with Umbraco 7

-

- This package may not function correctly if the package developer has not indicated that - it is compatible with version 7. Any DataTypes this package creates that do not have - a Version 7 compatible property editor will be converted to use a Label/NoEdit property editor. -

-
-
-
- - -
-

- Binary file errors detected

- Read more... -
-

- This package contains .NET binary files that might not be compatible with this version of Umbraco. - If you aren't sure what these errors mean or why they are listed please contact the package creator. -

-

- Error report
-

    - -
-

-
-
-
- -
-

- Macro Conflicts in the package!

- Read more... -
-

- This package contains one or more macros which have the same alias as an existing one on your site, based on the Macro Alias. -

-

- If you choose to continue your existing macros will be replaced with the ones from this package. If you do not want to overwrite your existing macros you will need to change their alias. -

-

- The Macros in question:
-

    - -
-

-
-
-
- - -
-

- Template Conflicts in the package!

- Read more... -
-

- This package contains one or more templates which have the same alias as an existing one on your site, based on the Template Alias. -

-

- If you choose to continue your existing template will be replaced with the ones from this package. If you do not want to overwrite your existing templates you will need to change their alias. -

-

- The Templates in question:
-

    - -
-

-
-
-
- - -
-

- Stylesheet Conflicts in the package!

- Read more... -
-

- This package contains one or more stylesheets which have the same alias as an existing one on your site, based on the Stylesheet Name. -

-

- If you choose to continue your existing stylesheets will be replaced with the ones from this package. If you do not want to overwrite your existing stylesheets you will need to change their name. -

-

- The Stylesheets in question:
-

    - -
-

-
-
-
- - -
- - -
-
- -
- - - - - - - + + - - - - -

- All items in the package have been installed

-

- Overview of what was installed can be found under "installed package" in the developer - section.

-

- Uninstall is available at the same location.

-

- - -

- -
-
- - - - -

<%= umbraco.ui.Text("packager", "packageUninstalledText") %>

- -
-
- - - - -
- Please wait while the browser is reloaded... -
- - - -
-
-
diff --git a/WebCms/Umbraco/Developer/Xslt/editXslt.aspx b/WebCms/Umbraco/Developer/Xslt/editXslt.aspx index 4ef6cba..48de8c0 100644 --- a/WebCms/Umbraco/Developer/Xslt/editXslt.aspx +++ b/WebCms/Umbraco/Developer/Xslt/editXslt.aspx @@ -9,7 +9,7 @@ - + @@ -21,8 +21,7 @@ nameTxtBox: $('#<%= xsltFileName.ClientID %>'), originalFileName: '<%= xsltFileName.Text %>', saveButton: $("#<%= ((Control)SaveButton).ClientID %>"), - editorSourceElement: $('#<%= editorSource.ClientID %>'), - skipTestingCheckBox: $("#<%= SkipTesting.ClientID %>"), + editorSourceElement: $('#<%= editorSource.ClientID %>') }); editor.init(); @@ -32,7 +31,7 @@ })(jQuery); //TODO: Move these to EditXslt.js one day - var xsltSnippet = ""; + var xsltSnippet = ""; function xsltVisualize() { xsltSnippet = UmbEditor.IsSimpleEditor @@ -46,7 +45,7 @@ } UmbClientMgr.openModalWindow('<%= Umbraco.Core.IO.IOHelper.ResolveUrl(Umbraco.Core.IO.SystemDirectories.Umbraco) %>/developer/xslt/xsltVisualize.aspx', 'Visualize XSLT', true, 550, 650); - } + } @@ -56,14 +55,11 @@ - + - - - diff --git a/WebCms/Umbraco/Dialogs/SendPublish.aspx b/WebCms/Umbraco/Dialogs/SendPublish.aspx index d948cd9..fd8243b 100644 --- a/WebCms/Umbraco/Dialogs/SendPublish.aspx +++ b/WebCms/Umbraco/Dialogs/SendPublish.aspx @@ -1,13 +1,18 @@ -<%@ Page language="c#" Codebehind="SendPublish.aspx.cs" AutoEventWireup="True" Inherits="umbraco.dialogs.SendPublish" %> - - - - umbraco - <%=umbraco.ui.Text("editContentSendToPublish")%> - - - -

Republish <%=umbraco.ui.Text("editContentSendToPublishText")%>

-
- <%=umbraco.ui.Text("closewindow")%> - - +<%@ Page language="c#" MasterPageFile="../masterpages/umbracoDialog.Master" AutoEventWireup="True" Inherits="umbraco.dialogs.SendPublish" %> +<%@ Register TagPrefix="cc1" Namespace="umbraco.uicontrols" Assembly="controls" %> + + +
+ + + +
<%=umbraco.ui.Text("editContentSendToPublishText")%>
+ +
+
+ + +
diff --git a/WebCms/Umbraco/Dialogs/protectPage.aspx b/WebCms/Umbraco/Dialogs/protectPage.aspx index 6d89a80..7d38373 100644 --- a/WebCms/Umbraco/Dialogs/protectPage.aspx +++ b/WebCms/Umbraco/Dialogs/protectPage.aspx @@ -103,11 +103,10 @@
@@ -137,7 +136,7 @@ - +

Member name already exists, click Change to use a different name or Update to continue

diff --git a/WebCms/Umbraco/Dialogs/publish.aspx b/WebCms/Umbraco/Dialogs/publish.aspx index 9ccb6b1..13b394c 100644 --- a/WebCms/Umbraco/Dialogs/publish.aspx +++ b/WebCms/Umbraco/Dialogs/publish.aspx @@ -80,7 +80,7 @@
-
+
  • diff --git a/WebCms/Umbraco/Dialogs/rollBack.aspx b/WebCms/Umbraco/Dialogs/rollBack.aspx index e1abad9..3bf1c9a 100644 --- a/WebCms/Umbraco/Dialogs/rollBack.aspx +++ b/WebCms/Umbraco/Dialogs/rollBack.aspx @@ -1,4 +1,4 @@ -<%@ Page Language="c#" Codebehind="rollBack.aspx.cs" MasterPageFile="../masterpages/umbracoDialog.Master"AutoEventWireup="True" Inherits="umbraco.presentation.dialogs.rollBack" %> +<%@ Page Language="c#" MasterPageFile="../masterpages/umbracoDialog.Master" AutoEventWireup="True" Inherits="umbraco.presentation.dialogs.rollBack" %> <%@ Register TagPrefix="cc1" Namespace="umbraco.uicontrols" Assembly="controls" %> <%@ Register TagPrefix="umb" Namespace="ClientDependency.Core.Controls" Assembly="ClientDependency.Core" %> diff --git a/WebCms/Umbraco/Dialogs/viewAuditTrail.aspx b/WebCms/Umbraco/Dialogs/viewAuditTrail.aspx index 39f3d59..7a39061 100644 --- a/WebCms/Umbraco/Dialogs/viewAuditTrail.aspx +++ b/WebCms/Umbraco/Dialogs/viewAuditTrail.aspx @@ -7,6 +7,7 @@ diff --git a/WebCms/Umbraco/Install/Views/Index.cshtml b/WebCms/Umbraco/Install/Views/Index.cshtml index 666f6a5..bfbf5c4 100644 --- a/WebCms/Umbraco/Install/Views/Index.cshtml +++ b/WebCms/Umbraco/Install/Views/Index.cshtml @@ -1,4 +1,5 @@ -@using Umbraco.Web +@using Umbraco.Core.Configuration +@using Umbraco.Web @using Umbraco.Web.Install.Controllers @{ Layout = null; diff --git a/WebCms/Umbraco/Install/Views/Web.config b/WebCms/Umbraco/Install/Views/Web.config index d7f2ec9..a95df8b 100644 --- a/WebCms/Umbraco/Install/Views/Web.config +++ b/WebCms/Umbraco/Install/Views/Web.config @@ -1,58 +1,58 @@ - - -
    -
    - - + + +
    +
    + + - - - - - - - - - - - + + + + + + + + + + + - - - + + + - - - - + + + + - - - - - - - + + + + + + + - - + + - - - - - + + + + + diff --git a/WebCms/Umbraco/Js/app.dev.js b/WebCms/Umbraco/Js/app.dev.js new file mode 100644 index 0000000..b1cb385 --- /dev/null +++ b/WebCms/Umbraco/Js/app.dev.js @@ -0,0 +1,17 @@ +var app = angular.module('umbraco', [ + 'umbraco.filters', + 'umbraco.directives', + 'umbraco.resources', + 'umbraco.services', + 'umbraco.httpbackend', + 'ngCookies', + 'ngMobile', + 'ngSanitize', + 'tmh.dynamicLocale' +]); + +/* For Angular 1.4: we need to load in Route, animate and touch seperately + 'ngRoute', + 'ngAnimate', + 'ngTouch' +*/ \ No newline at end of file diff --git a/WebCms/Umbraco/Js/app.js b/WebCms/Umbraco/Js/app.js index 36243ab..92ec03a 100644 --- a/WebCms/Umbraco/Js/app.js +++ b/WebCms/Umbraco/Js/app.js @@ -11,7 +11,7 @@ var app = angular.module('umbraco', [ 'ngMobile', 'tmh.dynamicLocale', 'ngFileUpload', - 'LocalStorageModule' + 'LocalStorageModule' ]); var packages = angular.module("umbraco.packages", []); @@ -22,12 +22,21 @@ var packages = angular.module("umbraco.packages", []); //module is initilized. angular.module("umbraco.views", ["umbraco.viewcache"]); angular.module("umbraco.viewcache", []) - .run(function($rootScope, $templateCache) { + .run(function ($rootScope, $templateCache, localStorageService) { /** For debug mode, always clear template cache to cut down on dev frustration and chrome cache on templates */ if (Umbraco.Sys.ServerVariables.isDebuggingEnabled) { $templateCache.removeAll(); } + else { + var storedVersion = localStorageService.get("umbVersion"); + if (!storedVersion || storedVersion !== Umbraco.Sys.ServerVariables.application.cacheBuster) { + //if the stored version doesn't match our cache bust version, clear the template cache + $templateCache.removeAll(); + //store the current version + localStorageService.set("umbVersion", Umbraco.Sys.ServerVariables.application.cacheBuster); + } + } }) .config([ //This ensures that all of our angular views are cache busted, if the path starts with views/ and ends with .html, then @@ -39,7 +48,7 @@ angular.module("umbraco.viewcache", []) $delegate.get = function (url, config) { if (Umbraco.Sys.ServerVariables.application && url.startsWith("views/") && url.endsWith(".html")) { - var rnd = Umbraco.Sys.ServerVariables.application.version + "." + Umbraco.Sys.ServerVariables.application.cdf; + var rnd = Umbraco.Sys.ServerVariables.application.cacheBuster; var _op = (url.indexOf("?") > 0) ? "&" : "?"; url += _op + "umb__rnd=" + rnd; } diff --git a/WebCms/Umbraco/Js/canvasdesigner.loader.js b/WebCms/Umbraco/Js/canvasdesigner.loader.js index 59d5a4b..c5d2f70 100644 --- a/WebCms/Umbraco/Js/canvasdesigner.loader.js +++ b/WebCms/Umbraco/Js/canvasdesigner.loader.js @@ -11,8 +11,8 @@ LazyLoad.js([ '../js/umbraco.security.js', '../ServerVariables', '../lib/spectrum/spectrum.js', - - '../js/canvasdesigner.panel.js', + '../js/umbraco.canvasdesigner.js', + '../js/canvasdesigner.panel.js' ], function () { jQuery(document).ready(function () { angular.bootstrap(document, ['Umbraco.canvasdesigner']); diff --git a/WebCms/Umbraco/Js/init.js b/WebCms/Umbraco/Js/init.js index ded5ba0..3246282 100644 --- a/WebCms/Umbraco/Js/init.js +++ b/WebCms/Umbraco/Js/init.js @@ -1,34 +1,86 @@ /** Executed when the application starts, binds to events and set global state */ -app.run(['userService', '$log', '$rootScope', '$location', 'navigationService', 'appState', 'editorState', 'fileManager', 'assetsService', 'eventsService', '$cookies', '$templateCache', - function (userService, $log, $rootScope, $location, navigationService, appState, editorState, fileManager, assetsService, eventsService, $cookies, $templateCache) { - +app.run(['userService', '$log', '$rootScope', '$location', 'queryStrings', 'navigationService', 'appState', 'editorState', 'fileManager', 'assetsService', 'eventsService', '$cookies', '$templateCache', 'localStorageService', 'tourService', 'dashboardResource', + function (userService, $log, $rootScope, $location, queryStrings, navigationService, appState, editorState, fileManager, assetsService, eventsService, $cookies, $templateCache, localStorageService, tourService, dashboardResource) { + //This sets the default jquery ajax headers to include our csrf token, we // need to user the beforeSend method because our token changes per user/login so // it cannot be static $.ajaxSetup({ beforeSend: function (xhr) { - xhr.setRequestHeader("X-XSRF-TOKEN", $cookies["XSRF-TOKEN"]); + xhr.setRequestHeader("X-UMB-XSRF-TOKEN", $cookies["UMB-XSRF-TOKEN"]); + if (queryStrings.getParams().umbDebug === "true" || queryStrings.getParams().umbdebug === "true") { + xhr.setRequestHeader("X-UMB-DEBUG", "true"); + } } }); /** Listens for authentication and checks if our required assets are loaded, if/once they are we'll broadcast a ready event */ eventsService.on("app.authenticated", function(evt, data) { + assetsService._loadInitAssets().then(function() { - appState.setGlobalState("isReady", true); - //send the ready event with the included returnToPath,returnToSearch data - eventsService.emit("app.ready", data); - returnToPath = null, returnToSearch = null; + // Loads the user's locale settings for Moment. + userService.loadMomentLocaleForCurrentUser().then(function() { + + //Register all of the tours on the server + tourService.registerAllTours().then(function () { + appReady(data); + + // Auto start intro tour + tourService.getTourByAlias("umbIntroIntroduction").then(function (introTour) { + // start intro tour if it hasn't been completed or disabled + if (introTour && introTour.disabled !== true && introTour.completed !== true) { + tourService.startTour(introTour); + } + }); + + }, function(){ + appReady(data); + }); + }); }); + }); + function appReady(data) { + appState.setGlobalState("isReady", true); + //send the ready event with the included returnToPath,returnToSearch data + eventsService.emit("app.ready", data); + returnToPath = null, returnToSearch = null; + } + /** execute code on each successful route */ $rootScope.$on('$routeChangeSuccess', function(event, current, previous) { - if(current.params.section){ - $rootScope.locationTitle = current.params.section + " - " + $location.$$host; + var deployConfig = Umbraco.Sys.ServerVariables.deploy; + var deployEnv, deployEnvTitle; + if (deployConfig) { + deployEnv = Umbraco.Sys.ServerVariables.deploy.CurrentWorkspace; + deployEnvTitle = "(" + deployEnv + ") "; + } + + if(current.params.section) { + + //Uppercase the current section, content, media, settings, developer, forms + var currentSection = current.params.section.charAt(0).toUpperCase() + current.params.section.slice(1); + + var baseTitle = currentSection + " - " + $location.$$host; + + //Check deploy for Global Umbraco.Sys obj workspace + if(deployEnv){ + $rootScope.locationTitle = deployEnvTitle + baseTitle; + } + else { + $rootScope.locationTitle = baseTitle; + } + } else { + + if(deployEnv) { + $rootScope.locationTitle = deployEnvTitle + "Umbraco - " + $location.$$host; + } + $rootScope.locationTitle = "Umbraco - " + $location.$$host; } @@ -67,4 +119,5 @@ app.run(['userService', '$log', '$rootScope', '$location', 'navigationService', //var touchDevice = ("ontouchstart" in window || window.touch || window.navigator.msMaxTouchPoints === 5 || window.DocumentTouch && document instanceof DocumentTouch); var touchDevice = /android|webos|iphone|ipad|ipod|blackberry|iemobile|touch/i.test(navigator.userAgent.toLowerCase()); appState.setGlobalState("touchDevice", touchDevice); + }]); diff --git a/WebCms/Umbraco/Js/install.loader.js b/WebCms/Umbraco/Js/install.loader.js index 869521e..ead413e 100644 --- a/WebCms/Umbraco/Js/install.loader.js +++ b/WebCms/Umbraco/Js/install.loader.js @@ -1,17 +1,19 @@ -LazyLoad.js( [ - 'lib/jquery/jquery.min.js', - /* 1.1.5 */ - 'lib/angular/1.1.5/angular.min.js', - 'lib/angular/1.1.5/angular-cookies.min.js', - 'lib/angular/1.1.5/angular-mobile.min.js', - 'lib/angular/1.1.5/angular-mocks.js', - 'lib/angular/1.1.5/angular-sanitize.min.js', - 'lib/underscore/underscore-min.js', - 'js/umbraco.installer.js', - 'js/umbraco.directives.js' - ], function () { - jQuery(document).ready(function () { - angular.bootstrap(document, ['ngSanitize', 'umbraco.install', 'umbraco.directives.validation']); - }); - } +LazyLoad.js([ + 'lib/jquery/jquery.min.js', + /* 1.1.5 */ + 'lib/angular/1.1.5/angular.min.js', + 'lib/angular/1.1.5/angular-cookies.min.js', + 'lib/angular/1.1.5/angular-mobile.min.js', + 'lib/angular/1.1.5/angular-mocks.js', + 'lib/angular/1.1.5/angular-sanitize.min.js', + 'lib/underscore/underscore-min.js', + 'lib/angular/angular-ui-sortable.js', + 'js/installer.app.js', + 'js/umbraco.directives.js', + 'js/umbraco.installer.js' +], function () { + jQuery(document).ready(function () { + angular.bootstrap(document, ['umbraco']); + }); +} ); \ No newline at end of file diff --git a/WebCms/Umbraco/Js/installer.app.js b/WebCms/Umbraco/Js/installer.app.js new file mode 100644 index 0000000..0531549 --- /dev/null +++ b/WebCms/Umbraco/Js/installer.app.js @@ -0,0 +1,7 @@ +var app = angular.module('umbraco', [ + 'umbraco.directives', + 'umbraco.install', + 'ngCookies', + 'ngMobile', + 'ngSanitize' +]); \ No newline at end of file diff --git a/WebCms/Umbraco/Js/loader.dev.js b/WebCms/Umbraco/Js/loader.dev.js new file mode 100644 index 0000000..f842c18 --- /dev/null +++ b/WebCms/Umbraco/Js/loader.dev.js @@ -0,0 +1,49 @@ +LazyLoad.js( + [ + 'lib/jquery/jquery.min.js', + 'lib/jquery-ui/jquery-ui.min.js', + + + /* 1.1.5 */ + 'lib/angular/1.1.5/angular.min.js', + 'lib/angular/1.1.5/angular-cookies.min.js', + + 'lib/angular/1.1.5/angular-sanitize.min.js', + + 'lib/angular/angular-ui-sortable.js', + + 'lib/angular/1.1.5/angular-mocks.js', + 'lib/angular/1.1.5/angular-mobile.min.js', + 'lib/underscore/underscore-min.js', + + 'lib/angular-dynamic-locale/tmhDynamicLocale.min.js', + + 'lib/bootstrap/js/bootstrap.2.3.2.min.js', + 'lib/bootstrap-tabdrop/bootstrap-tabdrop.min.js', + 'lib/umbraco/Extensions.js', + + 'lib/umbraco/NamespaceManager.js', + 'lib/umbraco/LegacyUmbClientMgr.js', + 'lib/umbraco/LegacySpeechBubble.js', + + 'js/umbraco.servervariables.js', + 'js/app.dev.js', + 'js/umbraco.httpbackend.js', + 'js/umbraco.testing.js', + + 'js/umbraco.directives.js', + 'js/umbraco.filters.js', + 'js/umbraco.resources.js', + 'js/umbraco.services.js', + 'js/umbraco.security.js', + 'js/umbraco.controllers.js', + 'js/routes.js', + 'js/init.js' + ], + + function () { + jQuery(document).ready(function () { + angular.bootstrap(document, ['umbraco']); + }); + } +); \ No newline at end of file diff --git a/WebCms/Umbraco/Js/routes.js b/WebCms/Umbraco/Js/routes.js index c388ff5..fb78e60 100644 --- a/WebCms/Umbraco/Js/routes.js +++ b/WebCms/Umbraco/Js/routes.js @@ -31,6 +31,12 @@ app.config(function ($routeProvider) { userService.getCurrentUser({ broadcastEvent: broadcast }).then(function (user) { //is auth, check if we allow or reject if (isRequired) { + + //This checks the current section and will force a redirect to 'content' as the default + if ($route.current.params.section.toLowerCase() === "default" || $route.current.params.section.toLowerCase() === "umbraco" || $route.current.params.section === "") { + $route.current.params.section = "content"; + } + // U4-5430, Benjamin Howarth // We need to change the current route params if the user only has access to a single section // To do this we need to grab the current user's allowed sections, then reject the promise with the correct path. @@ -98,14 +104,29 @@ app.config(function ($routeProvider) { resolve: doLogout() }) .when('/:section', { - templateUrl: function (rp) { - if (rp.section.toLowerCase() === "default" || rp.section.toLowerCase() === "umbraco" || rp.section === "") - { - rp.section = "content"; - } - - rp.url = "dashboard.aspx?app=" + rp.section; - return 'views/common/dashboard.html'; + + //This allows us to dynamically change the template for this route since you cannot inject services into the templateUrl method. + template: "
    ", + //This controller will execute for this route, then we can execute some code in order to set the template Url + controller: function ($scope, $route, $routeParams, $location, sectionService) { + + //We are going to check the currently loaded sections for the user and if the section we are navigating + //to has a custom route path we'll use that + sectionService.getSectionsForUser().then(function(sections) { + //find the one we're requesting + var found = _.find(sections, function(s) { + return s.alias === $routeParams.section; + }) + if (found && found.routePath) { + //there's a custom route path so redirect + $location.path(found.routePath); + } + else { + //there's no custom route path so continue as normal + $routeParams.url = "dashboard.aspx?app=" + $routeParams.section; + $scope.templateUrl = 'views/common/dashboard.html'; + } + }); }, resolve: canRoute(true) }) @@ -120,17 +141,32 @@ app.config(function ($routeProvider) { resolve: canRoute(true) }) .when('/:section/:tree/:method', { - templateUrl: function (rp) { - if (!rp.method) - return "views/common/dashboard.html"; + //This allows us to dynamically change the template for this route since you cannot inject services into the templateUrl method. + template: "
    ", + //This controller will execute for this route, then we replace the template dynamnically based on the current tree. + controller: function ($scope, $route, $routeParams, treeService) { - //NOTE: This current isn't utilized by anything but does open up some cool opportunities for - // us since we'll be able to have specialized views for individual sections which is something - // we've never had before. So could utilize this for a new dashboard model when we get native - // angular dashboards working. Perhaps a normal section dashboard would list out the registered - // dashboards (as tabs if we wanted) and each tab could actually be a route link to one of these views? + if (!$routeParams.method) { + $scope.templateUrl = "views/common/dashboard.html"; + } - return ('views/' + rp.tree + '/' + rp.method + '.html'); + // Here we need to figure out if this route is for a package tree and if so then we need + // to change it's convention view path to: + // /App_Plugins/{mypackage}/backoffice/{treetype}/{method}.html + + // otherwise if it is a core tree we use the core paths: + // views/{treetype}/{method}.html + + var packageTreeFolder = treeService.getTreePackageFolder($routeParams.tree); + + if (packageTreeFolder) { + $scope.templateUrl = (Umbraco.Sys.ServerVariables.umbracoSettings.appPluginsPath + + "/" + packageTreeFolder + + "/backoffice/" + $routeParams.tree + "/" + $routeParams.method + ".html"); + } + else { + $scope.templateUrl = ('views/' + $routeParams.tree + '/' + $routeParams.method + '.html'); + } }, resolve: canRoute(true) }) diff --git a/WebCms/Umbraco/Js/umbraco.canvasdesigner.js b/WebCms/Umbraco/Js/umbraco.canvasdesigner.js new file mode 100644 index 0000000..9d60fb8 --- /dev/null +++ b/WebCms/Umbraco/Js/umbraco.canvasdesigner.js @@ -0,0 +1,2450 @@ +(function () { + var app = angular.module('Umbraco.canvasdesigner', [ + 'colorpicker', + 'ui.slider', + 'umbraco.resources', + 'umbraco.services' + ]).controller('Umbraco.canvasdesignerController', function ($scope, $http, $window, $timeout, $location, dialogService) { + var isInit = $location.search().init; + if (isInit === 'true') { + //do not continue, this is the first load of this new window, if this is passed in it means it's been + //initialized by the content editor and then the content editor will actually re-load this window without + //this flag. This is a required trick to get around chrome popup mgr. We don't want to double load preview.aspx + //since that will double prepare the preview documents + return; + } + $scope.isOpen = false; + $scope.frameLoaded = false; + $scope.enableCanvasdesigner = 0; + $scope.googleFontFamilies = {}; + var pageId = $location.search().id; + $scope.pageId = pageId; + $scope.pageUrl = '../dialogs/Preview.aspx?id=' + pageId; + $scope.valueAreLoaded = false; + $scope.devices = [ + { + name: 'desktop', + css: 'desktop', + icon: 'icon-display', + title: 'Desktop' + }, + { + name: 'laptop - 1366px', + css: 'laptop border', + icon: 'icon-laptop', + title: 'Laptop' + }, + { + name: 'iPad portrait - 768px', + css: 'iPad-portrait border', + icon: 'icon-ipad', + title: 'Tablet portrait' + }, + { + name: 'iPad landscape - 1024px', + css: 'iPad-landscape border', + icon: 'icon-ipad flip', + title: 'Tablet landscape' + }, + { + name: 'smartphone portrait - 480px', + css: 'smartphone-portrait border', + icon: 'icon-iphone', + title: 'Smartphone portrait' + }, + { + name: 'smartphone landscape - 320px', + css: 'smartphone-landscape border', + icon: 'icon-iphone flip', + title: 'Smartphone landscape' + } + ]; + $scope.previewDevice = $scope.devices[0]; + var apiController = '../Api/Canvasdesigner/'; + /*****************************************************************************/ + /* Preview devices */ + /*****************************************************************************/ + // Set preview device + $scope.updatePreviewDevice = function (device) { + $scope.previewDevice = device; + }; + /*****************************************************************************/ + /* Exit Preview */ + /*****************************************************************************/ + $scope.exitPreview = function () { + window.top.location.href = '../endPreview.aspx?redir=%2f' + $scope.pageId; + }; + /*****************************************************************************/ + /* UI designer managment */ + /*****************************************************************************/ + // Update all Canvasdesigner config's values from data + var updateConfigValue = function (data) { + var fonts = []; + $.each($scope.canvasdesignerModel.configs, function (indexConfig, config) { + if (config.editors) { + $.each(config.editors, function (indexItem, item) { + /* try to get value */ + try { + if (item.values) { + angular.forEach(Object.keys(item.values), function (key, indexKey) { + if (key != '\'\'') { + var propertyAlias = key.toLowerCase() + item.alias.toLowerCase(); + var newValue = eval('data.' + propertyAlias.replace('@', '')); + if (newValue == '\'\'') { + newValue = ''; + } + item.values[key] = newValue; + } + }); + } + // TODO: special init for font family picker + if (item.type == 'googlefontpicker') { + if (item.values.fontType == 'google' && item.values.fontFamily + item.values.fontWeight && $.inArray(item.values.fontFamily + ':' + item.values.fontWeight, fonts) < 0) { + fonts.splice(0, 0, item.values.fontFamily + ':' + item.values.fontWeight); + } + } + } catch (err) { + console.info('Style parameter not found ' + item.alias); + } + }); + } + }); + // Load google font + $.each(fonts, function (indexFont, font) { + loadGoogleFont(font); + loadGoogleFontInFront(font); + }); + $scope.valueAreLoaded = true; + }; + // Load parameters from GetLessParameters and init data of the Canvasdesigner config + $scope.initCanvasdesigner = function () { + LazyLoad.js(['https://ajax.googleapis.com/ajax/libs/webfont/1/webfont.js']); + $http.get(apiController + 'Load', { params: { pageId: $scope.pageId } }).success(function (data) { + updateConfigValue(data); + $timeout(function () { + $scope.frameLoaded = true; + }, 200); + }); + }; + // Refresh all less parameters for every changes watching canvasdesignerModel + var refreshCanvasdesigner = function () { + var parameters = []; + if ($scope.canvasdesignerModel) { + angular.forEach($scope.canvasdesignerModel.configs, function (config, indexConfig) { + // Get currrent selected element + // TODO + //if ($scope.schemaFocus && angular.lowercase($scope.schemaFocus) == angular.lowercase(config.name)) { + // $scope.currentSelected = config.selector ? config.selector : config.schema; + //} + if (config.editors) { + angular.forEach(config.editors, function (item, indexItem) { + // Add new style + if (item.values) { + angular.forEach(Object.keys(item.values), function (key, indexKey) { + var propertyAlias = key.toLowerCase() + item.alias.toLowerCase(); + var value = eval('item.values.' + key); + parameters.splice(parameters.length + 1, 0, '\'@' + propertyAlias + '\':\'' + value + '\''); + }); + } + }); + } + }); + // Refresh page style + refreshFrontStyles(parameters); + // Refresh layout of selected element + //$timeout(function () { + $scope.positionSelectedHide(); + if ($scope.currentSelected) { + refreshOutlineSelected($scope.currentSelected); + } //}, 200); + } + }; + $scope.createStyle = function () { + $scope.saveLessParameters(false); + }; + $scope.saveStyle = function () { + $scope.saveLessParameters(true); + }; + // Save all parameter in CanvasdesignerParameters.less file + $scope.saveLessParameters = function (inherited) { + var parameters = []; + $.each($scope.canvasdesignerModel.configs, function (indexConfig, config) { + if (config.editors) { + $.each(config.editors, function (indexItem, item) { + if (item.values) { + angular.forEach(Object.keys(item.values), function (key, indexKey) { + var propertyAlias = key.toLowerCase() + item.alias.toLowerCase(); + var value = eval('item.values.' + key); + parameters.splice(parameters.length + 1, 0, '@' + propertyAlias + ':' + value + ';'); + }); + // TODO: special init for font family picker + if (item.type == 'googlefontpicker' && item.values.fontFamily) { + var variant = item.values.fontWeight != '' || item.values.fontStyle != '' ? ':' + item.values.fontWeight + item.values.fontStyle : ''; + var gimport = '@import url(\'https://fonts.googleapis.com/css?family=' + item.values.fontFamily + variant + '\');'; + if ($.inArray(gimport, parameters) < 0) { + parameters.splice(0, 0, gimport); + } + } + } + }); + } + }); + var resultParameters = { + parameters: parameters.join(''), + pageId: $scope.pageId, + inherited: inherited + }; + var transform = function (result) { + return $.param(result); + }; + $('.btn-default-save').attr('disabled', true); + $http.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded'; + $http.post(apiController + 'Save', resultParameters, { + headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' }, + transformRequest: transform + }).success(function (data) { + $('.btn-default-save').attr('disabled', false); + $('#speechbubble').fadeIn('slow').delay(5000).fadeOut('slow'); + }); + }; + // Delete current page Canvasdesigner + $scope.deleteCanvasdesigner = function () { + $('.btn-default-delete').attr('disabled', true); + $http.get(apiController + 'Delete', { params: { pageId: $scope.pageId } }).success(function (data) { + location.reload(); + }); + }; + /*****************************************************************************/ + /* Preset design */ + /*****************************************************************************/ + // Refresh with selected Canvasdesigner palette + $scope.refreshCanvasdesignerByPalette = function (palette) { + updateConfigValue(palette.data); + refreshCanvasdesigner(); + }; + // Hidden botton to make preset from the current settings + $scope.makePreset = function () { + var parameters = []; + $.each($scope.canvasdesignerModel.configs, function (indexConfig, config) { + if (config.editors) { + $.each(config.editors, function (indexItem, item) { + if (item.values) { + angular.forEach(Object.keys(item.values), function (key, indexKey) { + var propertyAlias = key.toLowerCase() + item.alias.toLowerCase(); + var value = eval('item.values.' + key); + var value = value != 0 && (value == undefined || value == '') ? '\'\'' : value; + parameters.splice(parameters.length + 1, 0, '"' + propertyAlias + '":' + ' "' + value + '"'); + }); + } + }); + } + }); + $('.btn-group').append(''); + }; + /*****************************************************************************/ + /* Panel managment */ + /*****************************************************************************/ + $scope.openPreviewDevice = function () { + $scope.showDevicesPreview = true; + $scope.closeIntelCanvasdesigner(); + }; + $scope.closePreviewDevice = function () { + $scope.showDevicesPreview = false; + if ($scope.showStyleEditor) { + $scope.openIntelCanvasdesigner(); + } + }; + $scope.openPalettePicker = function () { + $scope.showPalettePicker = true; + $scope.showStyleEditor = false; + $scope.closeIntelCanvasdesigner(); + }; + $scope.openStyleEditor = function () { + $scope.showStyleEditor = true; + $scope.showPalettePicker = false; + $scope.outlineSelectedHide(); + $scope.openIntelCanvasdesigner(); + }; + // Remove value from field + $scope.removeField = function (field) { + field.value = ''; + }; + // Check if category existe + $scope.hasEditor = function (editors, category) { + var result = false; + angular.forEach(editors, function (item, index) { + if (item.category == category) { + result = true; + } + }); + return result; + }; + $scope.closeFloatPanels = function () { + /* hack to hide color picker */ + $('.spectrumcolorpicker input').spectrum('hide'); + dialogService.close(); + $scope.showPalettePicker = false; + $scope.$apply(); + }; + $scope.clearHighlightedItems = function () { + $.each($scope.canvasdesignerModel.configs, function (indexConfig, config) { + config.highlighted = false; + }); + }; + $scope.setCurrentHighlighted = function (item) { + $scope.clearHighlightedItems(); + item.highlighted = true; + }; + $scope.setCurrentSelected = function (item) { + $scope.currentSelected = item; + $scope.clearSelectedCategory(); + refreshOutlineSelected($scope.currentSelected); + }; + /* Editor categories */ + $scope.getCategories = function (item) { + var propertyCategories = []; + $.each(item.editors, function (indexItem, editor) { + if (editor.category) { + if ($.inArray(editor.category, propertyCategories) < 0) { + propertyCategories.splice(propertyCategories.length + 1, 0, editor.category); + } + } + }); + return propertyCategories; + }; + $scope.setSelectedCategory = function (item) { + $scope.categoriesVisibility = $scope.categoriesVisibility || {}; + $scope.categoriesVisibility[item] = !$scope.categoriesVisibility[item]; + }; + $scope.clearSelectedCategory = function () { + $scope.categoriesVisibility = null; + }; + /*****************************************************************************/ + /* Call function into the front-end */ + /*****************************************************************************/ + var loadGoogleFontInFront = function (font) { + if (document.getElementById('resultFrame').contentWindow.getFont) + document.getElementById('resultFrame').contentWindow.getFont(font); + }; + var refreshFrontStyles = function (parameters) { + if (document.getElementById('resultFrame').contentWindow.refreshLayout) + document.getElementById('resultFrame').contentWindow.refreshLayout(parameters); + }; + var hideUmbracoPreviewBadge = function () { + var iframe = document.getElementById('resultFrame').contentWindow || document.getElementById('resultFrame').contentDocument; + if (iframe.document.getElementById('umbracoPreviewBadge')) + iframe.document.getElementById('umbracoPreviewBadge').style.display = 'none'; + }; + $scope.openIntelCanvasdesigner = function () { + if (document.getElementById('resultFrame').contentWindow.initIntelCanvasdesigner) + document.getElementById('resultFrame').contentWindow.initIntelCanvasdesigner($scope.canvasdesignerModel); + }; + $scope.closeIntelCanvasdesigner = function () { + if (document.getElementById('resultFrame').contentWindow.closeIntelCanvasdesigner) + document.getElementById('resultFrame').contentWindow.closeIntelCanvasdesigner($scope.canvasdesignerModel); + $scope.outlineSelectedHide(); + }; + var refreshOutlineSelected = function (config) { + var schema = config.selector ? config.selector : config.schema; + if (document.getElementById('resultFrame').contentWindow.refreshOutlineSelected) + document.getElementById('resultFrame').contentWindow.refreshOutlineSelected(schema); + }; + $scope.outlineSelectedHide = function () { + $scope.currentSelected = null; + if (document.getElementById('resultFrame').contentWindow.outlineSelectedHide) + document.getElementById('resultFrame').contentWindow.outlineSelectedHide(); + }; + $scope.refreshOutlinePosition = function (config) { + var schema = config.selector ? config.selector : config.schema; + if (document.getElementById('resultFrame').contentWindow.refreshOutlinePosition) + document.getElementById('resultFrame').contentWindow.refreshOutlinePosition(schema); + }; + $scope.positionSelectedHide = function () { + if (document.getElementById('resultFrame').contentWindow.outlinePositionHide) + document.getElementById('resultFrame').contentWindow.outlinePositionHide(); + }; + /*****************************************************************************/ + /* Google font loader, TODO: put together from directive, front and back */ + /*****************************************************************************/ + var webFontScriptLoaded = false; + var loadGoogleFont = function (font) { + if (!webFontScriptLoaded) { + $.getScript('https://ajax.googleapis.com/ajax/libs/webfont/1/webfont.js').done(function () { + webFontScriptLoaded = true; + // Recursively call once webfont script is available. + loadGoogleFont(font); + }).fail(function () { + console.log('error loading webfont'); + }); + } else { + WebFont.load({ + google: { families: [font] }, + loading: function () { + }, + active: function () { + }, + inactive: function () { + } + }); + } + }; + /*****************************************************************************/ + /* Init */ + /*****************************************************************************/ + // Preload of the google font + if ($scope.showStyleEditor) { + $http.get(apiController + 'GetGoogleFont').success(function (data) { + $scope.googleFontFamilies = data; + }); + } + // watch framLoaded, only if iframe page have enableCanvasdesigner() + $scope.$watch('enableCanvasdesigner', function () { + $timeout(function () { + if ($scope.enableCanvasdesigner > 0) { + $scope.$watch('ngRepeatFinished', function (ngRepeatFinishedEvent) { + $timeout(function () { + $scope.initCanvasdesigner(); + }, 200); + }); + $scope.$watch('canvasdesignerModel', function () { + refreshCanvasdesigner(); + }, true); + } + }, 100); + }, true); + }).directive('onFinishRenderFilters', function ($timeout) { + return { + restrict: 'A', + link: function (scope, element, attr) { + if (scope.$last === true) { + $timeout(function () { + scope.$emit('ngRepeatFinished'); + }); + } + } + }; + }).directive('iframeIsLoaded', function ($timeout) { + return { + restrict: 'A', + link: function (scope, element, attr) { + element.load(function () { + var iframe = element.context.contentWindow || element.context.contentDocument; + if (iframe && iframe.document.getElementById('umbracoPreviewBadge')) + iframe.document.getElementById('umbracoPreviewBadge').style.display = 'none'; + if (!document.getElementById('resultFrame').contentWindow.refreshLayout) { + scope.frameLoaded = true; + scope.$apply(); + } + }); + } + }; + }); + /*********************************************************************************************************/ + /* Global function and variable for panel/page com */ + /*********************************************************************************************************/ + var currentTarget = undefined; + var refreshLayout = function (parameters) { + // hide preview badget + $('#umbracoPreviewBadge').hide(); + var string = 'less.modifyVars({' + parameters.join(',') + '})'; + eval(string); + }; + /* Fonts loaded in the Canvasdesigner panel need to be loaded independently in + * the content iframe to allow live previewing. + */ + var webFontScriptLoaded = false; + var getFont = function (font) { + if (!webFontScriptLoaded) { + $.getScript('https://ajax.googleapis.com/ajax/libs/webfont/1/webfont.js').done(function () { + webFontScriptLoaded = true; + // Recursively call once webfont script is available. + getFont(font); + }).fail(function () { + console.log('error loading webfont'); + }); + } else { + WebFont.load({ + google: { families: [font] }, + loading: function () { + }, + active: function () { + }, + inactive: function () { + } + }); + } + }; + var closeIntelCanvasdesigner = function (canvasdesignerModel) { + if (canvasdesignerModel) { + $.each(canvasdesignerModel.configs, function (indexConfig, config) { + if (config.schema) { + $(config.schema).unbind(); + $(config.schema).removeAttr('canvasdesigner-over'); + } + }); + initBodyClickEvent(); + } + }; + var initBodyClickEvent = function () { + $('body').on('click', function () { + if (parent.iframeBodyClick) { + parent.iframeBodyClick(); + } + }); + }; + var initIntelCanvasdesigner = function (canvasdesignerModel) { + if (canvasdesignerModel) { + // Add canvasdesigner-over attr for each schema from config + $.each(canvasdesignerModel.configs, function (indexConfig, config) { + var schema = config.selector ? config.selector : config.schema; + if (schema) { + $(schema).attr('canvasdesigner-over', config.schema); + $(schema).attr('canvasdesigner-over-name', config.name); + $(schema).css('cursor', 'default'); + } + }); + // Outline canvasdesigner-over + $(document).mousemove(function (e) { + e.stopPropagation(); + var target = $(e.target); + while (target.length > 0 && (target.attr('canvasdesigner-over') == undefined || target.attr('canvasdesigner-over') == '')) { + target = target.parent(); + } + if (target.attr('canvasdesigner-over') != undefined && target.attr('canvasdesigner-over') != '') { + target.unbind(); + outlinePosition(target); + parent.onMouseoverCanvasdesignerItem(target.attr('canvasdesigner-over-name'), target); + target.click(function (e) { + e.stopPropagation(); + e.preventDefault(); + //console.info(target.attr('canvasdesigner-over')); + currentTarget = target; + outlineSelected(); + parent.onClickCanvasdesignerItem(target.attr('canvasdesigner-over'), target); + return false; + }); + } else { + outlinePositionHide(); + } + }); + } + }; + var refreshOutlinePosition = function (schema) { + outlinePosition($(schema)); + }; + var outlinePosition = function (oTarget) { + var target = oTarget; + if (target.length > 0 && target.attr('canvasdesigner-over') != undefined && target.attr('canvasdesigner-over') != '') { + var localname = target[0].localName; + var height = $(target).outerHeight(); + var width = $(target).outerWidth(); + var position = $(target).offset(); + var posY = position.top; + //$(window).scrollTop(); + var posX = position.left; + //+ $(window).scrollLeft(); + $('.canvasdesigner-overlay').css('display', 'block'); + $('.canvasdesigner-overlay').css('left', posX); + $('.canvasdesigner-overlay').css('top', posY); + $('.canvasdesigner-overlay').css('width', width + 'px'); + $('.canvasdesigner-overlay').css('height', height + 'px'); + //console.info("element select " + localname); + $('.canvasdesigner-overlay span').html(target.attr('canvasdesigner-over-name')); + } else { + outlinePositionHide(); //console.info("element not found select"); + } + }; + var refreshOutlineSelected = function (schema) { + outlineSelected($(schema)); + }; + var outlineSelected = function (oTarget) { + var target = currentTarget; + if (oTarget) { + currentTarget = oTarget; + target = oTarget; + } + if (target && target.length > 0 && target.attr('canvasdesigner-over') != undefined && target.attr('canvasdesigner-over') != '') { + var localname = target[0].localName; + var height = $(target).outerHeight(); + var width = $(target).outerWidth(); + var position = $(target).offset(); + var posY = position.top; + //$(window).scrollTop(); + var posX = position.left; + //+ $(window).scrollLeft(); + $('.canvasdesigner-overlay-selected').css('display', 'block'); + $('.canvasdesigner-overlay-selected').css('left', posX); + $('.canvasdesigner-overlay-selected').css('top', posY); + $('.canvasdesigner-overlay-selected').css('width', width + 'px'); + $('.canvasdesigner-overlay-selected').css('height', height + 'px'); + //console.info("element select " + localname); + $('.canvasdesigner-overlay-selected span').html(target.attr('canvasdesigner-over-name')); + } else { + outlinePositionHide(); //console.info("element not found select"); + } + }; + var outlinePositionHide = function () { + $('.canvasdesigner-overlay').css('display', 'none'); + }; + var outlineSelectedHide = function () { + currentTarget = undefined; + $('.canvasdesigner-overlay-selected').css('display', 'none'); + }; + var initCanvasdesignerPanel = function () { + $('link[data-title="canvasdesignerCss"]').attr('disabled', 'disabled'); + // First load the canvasdesigner config from file + if (!canvasdesignerConfig) { + console.info('canvasdesigner config not found'); + } + // Add canvasdesigner from HTML 5 data tags + $('[data-canvasdesigner]').each(function (index, value) { + var tagName = $(value).data('canvasdesigner') ? $(value).data('canvasdesigner') : $(value)[0].nodeName.toLowerCase(); + var tagSchema = $(value).data('schema') ? $(value).data('schema') : $(value)[0].nodeName.toLowerCase(); + var tagSelector = $(value).data('selector') ? $(value).data('selector') : tagSchema; + var tagEditors = $(value).data('editors'); + //JSON.parse(...); + canvasdesignerConfig.configs.splice(canvasdesignerConfig.configs.length, 0, { + name: tagName, + schema: tagSchema, + selector: tagSelector, + editors: tagEditors + }); + }); + // For each editor config create a composite alias + $.each(canvasdesignerConfig.configs, function (configIndex, config) { + if (config.editors) { + $.each(config.editors, function (editorIndex, editor) { + var clearSchema = config.schema.replace(/[^a-zA-Z0-9]+/g, '').toLowerCase(); + var clearEditor = JSON.stringify(editor).replace(/[^a-zA-Z0-9]+/g, '').toLowerCase(); + editor.alias = clearSchema + clearEditor; + }); + } + }); + // Create or update the less file + $.ajax({ + url: '/Umbraco/Api/CanvasDesigner/Init', + type: 'POST', + dataType: 'json', + error: function (err) { + alert(err.responseText); + }, + data: { + config: JSON.stringify(canvasdesignerConfig), + pageId: pageId + }, + success: function (data) { + // Add Less link in head + $('head').append(''); + css = $('head').children(':last'); + css.attr({ + rel: 'stylesheet/less', + type: 'text/css', + href: data + }); + //console.info("Less styles are loaded"); + // Init Less.js + $.getScript('/Umbraco/lib/Less/less-1.7.0.min.js', function (data, textStatus, jqxhr) { + // Init panel + if (parent.setFrameIsLoaded) { + parent.setFrameIsLoaded(canvasdesignerConfig, canvasdesignerPalette); + } + }); + } + }); + }; + $(function () { + if (parent.setFrameIsLoaded) { + // Overlay background-color: rgba(28, 203, 255, 0.05); + $('body').append(''); + $('body').append(''); + // Set event for any body click + initBodyClickEvent(); + // Init canvasdesigner panel + initCanvasdesignerPanel(); + } + }); + /*********************************************************************************************************/ + /* Global function and variable for panel/page com */ + /*********************************************************************************************************/ + /* Called for every canvasdesigner-over click */ + var onClickCanvasdesignerItem = function (schema) { + var scope = angular.element($('#canvasdesignerPanel')).scope(); + //if (scope.schemaFocus != schema.toLowerCase()) { + //var notFound = true; + $.each(scope.canvasdesignerModel.configs, function (indexConfig, config) { + if (config.schema && schema.toLowerCase() == config.schema.toLowerCase()) { + scope.currentSelected = config; + } + }); + //} + scope.clearSelectedCategory(); + scope.closeFloatPanels(); + scope.$apply(); + }; + /* Called for every canvasdesigner-over rollover */ + var onMouseoverCanvasdesignerItem = function (name) { + var scope = angular.element($('#canvasdesignerPanel')).scope(); + $.each(scope.canvasdesignerModel.configs, function (indexConfig, config) { + config.highlighted = false; + if (config.name && name.toLowerCase() == config.name.toLowerCase()) { + config.highlighted = true; + } + }); + scope.$apply(); + }; + /* Called when the iframe is first loaded */ + var setFrameIsLoaded = function (canvasdesignerConfig, canvasdesignerPalette) { + var scope = angular.element($('#canvasdesignerPanel')).scope(); + scope.canvasdesignerModel = canvasdesignerConfig; + scope.canvasdesignerPalette = canvasdesignerPalette; + scope.enableCanvasdesigner++; + scope.$apply(); + }; + /* Iframe body click */ + var iframeBodyClick = function () { + var scope = angular.element($('#canvasdesignerPanel')).scope(); + scope.closeFloatPanels(); + }; + /*********************************************************************************************************/ + /* Canvasdesigner setting panel config */ + /*********************************************************************************************************/ + var canvasdesignerConfig = { + configs: [ + { + name: 'Body', + schema: 'body', + selector: 'body', + editors: [ + { + type: 'background', + category: 'Color', + name: 'Background' + }, + { + type: 'color', + category: 'Font', + name: 'Font Color (main)', + css: 'color', + schema: 'body, h1, h2, h3, h4, h5, h6, h7, #nav li a' + }, + { + type: 'color', + category: 'Font', + name: 'Font Color (secondary)', + css: 'color', + schema: 'ul.meta, .byline' + }, + { + type: 'googlefontpicker', + category: 'Font', + name: 'Font Family', + css: 'color', + schema: 'body, h1, h2, h3, h4, h5, h6, h7, .byline, #nav, .button' + } + ] + }, + { + name: 'Nav', + schema: '#nav', + selector: 'nav', + editors: [ + { + type: 'background', + category: 'Color', + name: 'Background' + }, + { + type: 'border', + category: 'Color', + name: 'Border' + }, + { + type: 'color', + category: 'Nav', + name: 'Font Color', + css: 'color', + schema: '#nav li a' + }, + { + type: 'color', + category: 'Nav', + name: 'Font Color (hover / selected)', + css: 'color', + schema: '#nav li:hover a' + }, + { + type: 'color', + category: 'Nav', + name: 'Background Color (hover)', + css: 'background-color', + schema: '#nav li:hover a' + }, + { + type: 'color', + category: 'Nav', + name: 'Background Color (selected)', + css: 'background-color', + schema: '#nav li.current_page_item a' + }, + { + type: 'googlefontpicker', + category: 'Font', + name: 'Font familly' + } + ] + }, + { + name: 'Logo', + schema: '#header .logo div', + selector: '#header .logo div', + editors: [ + { + type: 'color', + category: 'Color', + name: 'Border color', + css: 'border-top-color', + schema: '#header .logo' + }, + { + type: 'padding', + category: 'Position', + name: 'Margin', + enable: [ + 'top', + 'bottom' + ], + schema: '#header' + } + ] + }, + { + name: 'h2', + schema: 'h2', + selector: 'h2 span', + editors: [ + { + type: 'color', + category: 'Color', + name: 'Border color', + css: 'border-top-color', + schema: 'h2.major' + }, + { + type: 'color', + category: 'Font', + name: 'Font color', + css: 'color' + } + ] + }, + { + name: 'h3', + schema: 'h3', + selector: 'h3', + editors: [{ + type: 'color', + category: 'Font', + name: 'Font color', + css: 'color' + }] + }, + { + name: 'Banner Title', + schema: '#banner h2', + selector: '#banner h2', + editors: [ + { + type: 'color', + category: 'Font', + name: 'Font color', + css: 'color' + }, + { + type: 'slider', + category: 'Font', + name: 'Font size', + css: 'font-size', + min: 18, + max: 100 + }, + { + type: 'margin', + category: 'Position', + name: 'Margin' + } + ] + }, + { + name: 'Banner Sub-title', + schema: '#banner .byline', + selector: '#banner .byline', + editors: [ + { + type: 'color', + category: 'Font', + name: 'Font color', + css: 'color' + }, + { + type: 'slider', + category: 'Font', + name: 'Font size', + css: 'font-size', + min: 18, + max: 100 + }, + { + type: 'margin', + category: 'Position', + name: 'Margin' + } + ] + }, + { + name: 'Banner', + schema: '#banner', + selector: '#banner', + editors: [{ + type: 'background', + category: 'Color', + name: 'Background', + css: 'color' + }] + }, + { + name: 'Banner-wrapper', + schema: '#banner-wrapper', + selector: '#banner-wrapper', + editors: [ + { + type: 'background', + category: 'Color', + name: 'Background' + }, + { + type: 'padding', + category: 'Position', + name: 'Padding', + enable: [ + 'top', + 'bottom' + ] + } + ] + }, + { + name: '#main-wrapper', + schema: '#main-wrapper', + selector: '#main-wrapper', + editors: [{ + type: 'border', + category: 'Styling', + name: 'Border', + enable: [ + 'top', + 'bottom' + ] + }] + }, + { + name: 'Image', + schema: '.image,.image img,.image:before', + selector: '.image', + editors: [{ + type: 'radius', + category: 'Styling', + name: 'Radius' + }] + }, + { + name: 'Button', + schema: '.button', + selector: '.button', + editors: [ + { + type: 'color', + category: 'Color', + name: 'Color', + css: 'color' + }, + { + type: 'color', + category: 'Color', + name: 'Background', + css: 'background' + }, + { + type: 'color', + category: 'Color', + name: 'Background Hover', + css: 'background', + schema: '.button:hover' + }, + { + type: 'radius', + category: 'Styling', + name: 'Radius' + } + ] + }, + { + name: 'Button Alt', + schema: '.button-alt', + selector: '.button-alt', + editors: [ + { + type: 'color', + category: 'Color', + name: 'Color', + css: 'color' + }, + { + type: 'color', + category: 'Color', + name: 'Background', + css: 'background' + }, + { + type: 'color', + category: 'Color', + name: 'Background Hover', + css: 'background', + schema: '.button-alt:hover' + } + ] + } + ] + }; + /*********************************************************************************************************/ + /* Canvasdesigner palette tab config */ + /*********************************************************************************************************/ + var canvasdesignerPalette = [ + { + name: 'Default', + color1: 'rgb(193, 202, 197)', + color2: 'rgb(231, 234, 232)', + color3: 'rgb(107, 119, 112)', + color4: 'rgb(227, 218, 168)', + color5: 'rgba(21, 28, 23, 0.95)', + data: { + 'widebodytypewidecategorydimensionnamelayout': 'wide', + 'imageorpatternbodytypebackgroundcategorycolornamebackground': '', + 'colorbodytypebackgroundcategorycolornamebackground': '', + 'colorbodytypecolorcategoryfontnamefontcolormaincsscolorschemabodyh1h2h3h4h5h6h7navlia': 'rgb(107, 119, 112)', + 'colorbodytypecolorcategoryfontnamefontcolorsecondarycsscolorschemaulmetabyline': 'rgb(193, 202, 197)', + 'fontfamilybodytypegooglefontpickercategoryfontnamefontfamilycsscolorschemabodyh1h2h3h4h5h6h7bylinenavbutton': 'Open Sans Condensed', + 'fonttypebodytypegooglefontpickercategoryfontnamefontfamilycsscolorschemabodyh1h2h3h4h5h6h7bylinenavbutton': 'google', + 'fontweightbodytypegooglefontpickercategoryfontnamefontfamilycsscolorschemabodyh1h2h3h4h5h6h7bylinenavbutton': '700', + 'fontstylebodytypegooglefontpickercategoryfontnamefontfamilycsscolorschemabodyh1h2h3h4h5h6h7bylinenavbutton': '', + 'imageorpatternnavtypebackgroundcategorycolornamebackground': '', + 'colornavtypebackgroundcategorycolornamebackground': '', + 'bordersizenavtypebordercategorycolornameborder': '', + 'bordercolornavtypebordercategorycolornameborder': '', + 'bordertypenavtypebordercategorycolornameborder': 'solid', + 'leftbordersizenavtypebordercategorycolornameborder': '', + 'leftbordercolornavtypebordercategorycolornameborder': '', + 'leftbordertypenavtypebordercategorycolornameborder': 'solid', + 'rightbordersizenavtypebordercategorycolornameborder': '', + 'rightbordercolornavtypebordercategorycolornameborder': '', + 'rightbordertypenavtypebordercategorycolornameborder': 'solid', + 'topbordersizenavtypebordercategorycolornameborder': '', + 'topbordercolornavtypebordercategorycolornameborder': '', + 'topbordertypenavtypebordercategorycolornameborder': 'solid', + 'bottombordersizenavtypebordercategorycolornameborder': '', + 'bottombordercolornavtypebordercategorycolornameborder': '', + 'bottombordertypenavtypebordercategorycolornameborder': 'solid', + 'colornavtypecolorcategorynavnamefontcolorcsscolorschemanavlia': 'rgb(107, 119, 112)', + 'colornavtypecolorcategorynavnamefontcolorhoverselectedcsscolorschemanavlihovera': 'rgb(255, 255, 255)', + 'colornavtypecolorcategorynavnamebackgroundcolorhovercssbackgroundcolorschemanavlihovera': 'rgb(193, 202, 197)', + 'colornavtypecolorcategorynavnamebackgroundcolorselectedcssbackgroundcolorschemanavlicurrentpageitema': 'rgb(227, 218, 168)', + 'fontfamilynavtypegooglefontpickercategoryfontnamefontfamilly': '', + 'fonttypenavtypegooglefontpickercategoryfontnamefontfamilly': '', + 'fontweightnavtypegooglefontpickercategoryfontnamefontfamilly': '', + 'fontstylenavtypegooglefontpickercategoryfontnamefontfamilly': '', + 'colorheaderlogodivtypecolorcategorycolornamebordercolorcssbordertopcolorschemaheaderlogo': 'rgb(231, 234, 232)', + 'paddingvalueheaderlogodivtypepaddingcategorypositionnamemarginenabletopbottomschemaheader': '', + 'leftpaddingvalueheaderlogodivtypepaddingcategorypositionnamemarginenabletopbottomschemaheader': '', + 'rightpaddingvalueheaderlogodivtypepaddingcategorypositionnamemarginenabletopbottomschemaheader': '', + 'toppaddingvalueheaderlogodivtypepaddingcategorypositionnamemarginenabletopbottomschemaheader': '172', + 'bottompaddingvalueheaderlogodivtypepaddingcategorypositionnamemarginenabletopbottomschemaheader': '101', + 'colorh2typecolorcategorycolornamebordercolorcssbordertopcolorschemah2major': 'rgb(231, 234, 232)', + 'colorh2typecolorcategoryfontnamefontcolorcsscolor': '', + 'colorh3typecolorcategoryfontnamefontcolorcsscolor': '', + 'colorbannerh2typecolorcategoryfontnamefontcolorcsscolor': '', + 'sliderbannerh2typeslidercategoryfontnamefontsizecssfontsizemin18max100': '45', + 'marginvaluebannerh2typemargincategorypositionnamemargin': '', + 'leftmarginvaluebannerh2typemargincategorypositionnamemargin': '', + 'rightmarginvaluebannerh2typemargincategorypositionnamemargin': '', + 'topmarginvaluebannerh2typemargincategorypositionnamemargin': '', + 'bottommarginvaluebannerh2typemargincategorypositionnamemargin': '', + 'colorbannerbylinetypecolorcategoryfontnamefontcolorcsscolor': '', + 'sliderbannerbylinetypeslidercategoryfontnamefontsizecssfontsizemin18max100': '22', + 'marginvaluebannerbylinetypemargincategorypositionnamemargin': '', + 'leftmarginvaluebannerbylinetypemargincategorypositionnamemargin': '', + 'rightmarginvaluebannerbylinetypemargincategorypositionnamemargin': '', + 'topmarginvaluebannerbylinetypemargincategorypositionnamemargin': '', + 'bottommarginvaluebannerbylinetypemargincategorypositionnamemargin': '', + 'imageorpatternbannertypebackgroundcategorycolornamebackgroundcsscolor': '', + 'colorbannertypebackgroundcategorycolornamebackgroundcsscolor': 'rgba(21, 28, 23, 0.95)', + 'imageorpatternbannerwrappertypebackgroundcategorycolornamebackground': '', + 'colorbannerwrappertypebackgroundcategorycolornamebackground': '', + 'paddingvaluebannerwrappertypepaddingcategorypositionnamepaddingenabletopbottom': '', + 'leftpaddingvaluebannerwrappertypepaddingcategorypositionnamepaddingenabletopbottom': '', + 'rightpaddingvaluebannerwrappertypepaddingcategorypositionnamepaddingenabletopbottom': '', + 'toppaddingvaluebannerwrappertypepaddingcategorypositionnamepaddingenabletopbottom': '123', + 'bottompaddingvaluebannerwrappertypepaddingcategorypositionnamepaddingenabletopbottom': '125', + 'bordersizemainwrappertypebordercategorystylingnameborderenabletopbottom': '', + 'bordercolormainwrappertypebordercategorystylingnameborderenabletopbottom': '', + 'bordertypemainwrappertypebordercategorystylingnameborderenabletopbottom': 'solid', + 'leftbordersizemainwrappertypebordercategorystylingnameborderenabletopbottom': '', + 'leftbordercolormainwrappertypebordercategorystylingnameborderenabletopbottom': '', + 'leftbordertypemainwrappertypebordercategorystylingnameborderenabletopbottom': 'solid', + 'rightbordersizemainwrappertypebordercategorystylingnameborderenabletopbottom': '', + 'rightbordercolormainwrappertypebordercategorystylingnameborderenabletopbottom': '', + 'rightbordertypemainwrappertypebordercategorystylingnameborderenabletopbottom': 'solid', + 'topbordersizemainwrappertypebordercategorystylingnameborderenabletopbottom': '32', + 'topbordercolormainwrappertypebordercategorystylingnameborderenabletopbottom': 'rgb(227, 218, 168)', + 'topbordertypemainwrappertypebordercategorystylingnameborderenabletopbottom': 'solid', + 'bottombordersizemainwrappertypebordercategorystylingnameborderenabletopbottom': '10', + 'bottombordercolormainwrappertypebordercategorystylingnameborderenabletopbottom': 'rgb(193, 202, 197)', + 'bottombordertypemainwrappertypebordercategorystylingnameborderenabletopbottom': 'solid', + 'radiusvalueimageimageimgimagebeforetyperadiuscategorystylingnameradius': '8', + 'topleftradiusvalueimageimageimgimagebeforetyperadiuscategorystylingnameradius': '', + 'toprightradiusvalueimageimageimgimagebeforetyperadiuscategorystylingnameradius': '', + 'bottomleftradiusvalueimageimageimgimagebeforetyperadiuscategorystylingnameradius': '', + 'bottomrightradiusvalueimageimageimgimagebeforetyperadiuscategorystylingnameradius': '', + 'colorbuttontypecolorcategorycolornamecolorcsscolor': 'rgb(255, 255, 255)', + 'colorbuttontypecolorcategorycolornamebackgroundcssbackground': 'rgb(227, 218, 168)', + 'colorbuttontypecolorcategorycolornamebackgroundhovercssbackgroundschemabuttonhover': 'rgb(235, 227, 178)', + 'radiusvaluebuttontyperadiuscategorystylingnameradius': '7', + 'topleftradiusvaluebuttontyperadiuscategorystylingnameradius': '', + 'toprightradiusvaluebuttontyperadiuscategorystylingnameradius': '', + 'bottomleftradiusvaluebuttontyperadiuscategorystylingnameradius': '', + 'bottomrightradiusvaluebuttontyperadiuscategorystylingnameradius': '', + 'colorbuttonalttypecolorcategorycolornamecolorcsscolor': 'rgb(255, 255, 255)', + 'colorbuttonalttypecolorcategorycolornamebackgroundcssbackground': 'rgb(193, 202, 197)', + 'colorbuttonalttypecolorcategorycolornamebackgroundhovercssbackgroundschemabuttonalthover': 'rgb(204, 213, 208)' + } + }, + { + name: 'Blue Alternative', + color1: 'rgb(193, 202, 197)', + color2: 'rgb(231, 234, 232)', + color3: 'rgb(107, 119, 112)', + color4: 'rgb(68, 187, 204)', + color5: 'rgba(21, 28, 23, 0.95)', + data: { + 'widebodytypewidecategorydimensionnamelayout': 'wide', + 'imageorpatternbodytypebackgroundcategorycolornamebackground': '', + 'colorbodytypebackgroundcategorycolornamebackground': '', + 'colorbodytypecolorcategoryfontnamefontcolormaincsscolorschemabodyh1h2h3h4h5h6h7navlia': 'rgb(51, 68, 51)', + 'colorbodytypecolorcategoryfontnamefontcolorsecondarycsscolorschemaulmetabyline': 'rgb(68, 187, 204)', + 'fontfamilybodytypegooglefontpickercategoryfontnamefontfamilycsscolorschemabodyh1h2h3h4h5h6h7bylinenavbutton': 'Alef', + 'fonttypebodytypegooglefontpickercategoryfontnamefontfamilycsscolorschemabodyh1h2h3h4h5h6h7bylinenavbutton': 'google', + 'fontweightbodytypegooglefontpickercategoryfontnamefontfamilycsscolorschemabodyh1h2h3h4h5h6h7bylinenavbutton': 'regular', + 'fontstylebodytypegooglefontpickercategoryfontnamefontfamilycsscolorschemabodyh1h2h3h4h5h6h7bylinenavbutton': '', + 'imageorpatternnavtypebackgroundcategorycolornamebackground': '', + 'colornavtypebackgroundcategorycolornamebackground': '', + 'bordersizenavtypebordercategorycolornameborder': '', + 'bordercolornavtypebordercategorycolornameborder': '', + 'bordertypenavtypebordercategorycolornameborder': 'solid', + 'leftbordersizenavtypebordercategorycolornameborder': '', + 'leftbordercolornavtypebordercategorycolornameborder': '', + 'leftbordertypenavtypebordercategorycolornameborder': 'solid', + 'rightbordersizenavtypebordercategorycolornameborder': '', + 'rightbordercolornavtypebordercategorycolornameborder': '', + 'rightbordertypenavtypebordercategorycolornameborder': 'solid', + 'topbordersizenavtypebordercategorycolornameborder': '', + 'topbordercolornavtypebordercategorycolornameborder': '', + 'topbordertypenavtypebordercategorycolornameborder': 'solid', + 'bottombordersizenavtypebordercategorycolornameborder': '1', + 'bottombordercolornavtypebordercategorycolornameborder': 'rgba(0, 0, 0, 0.05)', + 'bottombordertypenavtypebordercategorycolornameborder': 'solid', + 'colornavtypecolorcategorynavnamefontcolorcsscolorschemanavlia': 'rgb(107, 119, 112)', + 'colornavtypecolorcategorynavnamefontcolorhoverselectedcsscolorschemanavlihovera': 'rgb(255, 255, 255)', + 'colornavtypecolorcategorynavnamebackgroundcolorhovercssbackgroundcolorschemanavlihovera': 'rgb(193, 202, 197)', + 'colornavtypecolorcategorynavnamebackgroundcolorselectedcssbackgroundcolorschemanavlicurrentpageitema': 'rgb(68, 187, 204)', + 'fontfamilynavtypegooglefontpickercategoryfontnamefontfamilly': '', + 'fonttypenavtypegooglefontpickercategoryfontnamefontfamilly': '', + 'fontweightnavtypegooglefontpickercategoryfontnamefontfamilly': '', + 'fontstylenavtypegooglefontpickercategoryfontnamefontfamilly': '', + 'colorheaderlogodivtypecolorcategorycolornamebordercolorcssbordertopcolorschemaheaderlogo': 'rgb(231, 234, 232)', + 'paddingvalueheaderlogodivtypepaddingcategorypositionnamemarginenabletopbottomschemaheader': '', + 'leftpaddingvalueheaderlogodivtypepaddingcategorypositionnamemarginenabletopbottomschemaheader': '', + 'rightpaddingvalueheaderlogodivtypepaddingcategorypositionnamemarginenabletopbottomschemaheader': '', + 'toppaddingvalueheaderlogodivtypepaddingcategorypositionnamemarginenabletopbottomschemaheader': '166', + 'bottompaddingvalueheaderlogodivtypepaddingcategorypositionnamemarginenabletopbottomschemaheader': '91', + 'colorh2typecolorcategorycolornamebordercolorcssbordertopcolorschemah2major': 'rgb(231, 234, 232)', + 'colorh2typecolorcategoryfontnamefontcolorcsscolor': '', + 'colorh3typecolorcategoryfontnamefontcolorcsscolor': '', + 'colorbannerh2typecolorcategoryfontnamefontcolorcsscolor': '', + 'sliderbannerh2typeslidercategoryfontnamefontsizecssfontsizemin18max100': '45', + 'marginvaluebannerh2typemargincategorypositionnamemargin': '', + 'leftmarginvaluebannerh2typemargincategorypositionnamemargin': '', + 'rightmarginvaluebannerh2typemargincategorypositionnamemargin': '', + 'topmarginvaluebannerh2typemargincategorypositionnamemargin': '', + 'bottommarginvaluebannerh2typemargincategorypositionnamemargin': '', + 'colorbannerbylinetypecolorcategoryfontnamefontcolorcsscolor': '', + 'sliderbannerbylinetypeslidercategoryfontnamefontsizecssfontsizemin18max100': '22', + 'marginvaluebannerbylinetypemargincategorypositionnamemargin': '', + 'leftmarginvaluebannerbylinetypemargincategorypositionnamemargin': '', + 'rightmarginvaluebannerbylinetypemargincategorypositionnamemargin': '', + 'topmarginvaluebannerbylinetypemargincategorypositionnamemargin': '', + 'bottommarginvaluebannerbylinetypemargincategorypositionnamemargin': '', + 'imageorpatternbannertypebackgroundcategorycolornamebackgroundcsscolor': '', + 'colorbannertypebackgroundcategorycolornamebackgroundcsscolor': 'rgba(21, 28, 23, 0.95)', + 'imageorpatternbannerwrappertypebackgroundcategorycolornamebackground': '', + 'colorbannerwrappertypebackgroundcategorycolornamebackground': '', + 'paddingvaluebannerwrappertypepaddingcategorypositionnamepaddingenabletopbottom': '', + 'leftpaddingvaluebannerwrappertypepaddingcategorypositionnamepaddingenabletopbottom': '', + 'rightpaddingvaluebannerwrappertypepaddingcategorypositionnamepaddingenabletopbottom': '', + 'toppaddingvaluebannerwrappertypepaddingcategorypositionnamepaddingenabletopbottom': '48', + 'bottompaddingvaluebannerwrappertypepaddingcategorypositionnamepaddingenabletopbottom': '55', + 'bordersizemainwrappertypebordercategorystylingnameborderenabletopbottom': '', + 'bordercolormainwrappertypebordercategorystylingnameborderenabletopbottom': '', + 'bordertypemainwrappertypebordercategorystylingnameborderenabletopbottom': 'solid', + 'leftbordersizemainwrappertypebordercategorystylingnameborderenabletopbottom': '', + 'leftbordercolormainwrappertypebordercategorystylingnameborderenabletopbottom': '', + 'leftbordertypemainwrappertypebordercategorystylingnameborderenabletopbottom': 'solid', + 'rightbordersizemainwrappertypebordercategorystylingnameborderenabletopbottom': '', + 'rightbordercolormainwrappertypebordercategorystylingnameborderenabletopbottom': '', + 'rightbordertypemainwrappertypebordercategorystylingnameborderenabletopbottom': 'solid', + 'topbordersizemainwrappertypebordercategorystylingnameborderenabletopbottom': '10', + 'topbordercolormainwrappertypebordercategorystylingnameborderenabletopbottom': 'rgb(68, 187, 204)', + 'topbordertypemainwrappertypebordercategorystylingnameborderenabletopbottom': 'solid', + 'bottombordersizemainwrappertypebordercategorystylingnameborderenabletopbottom': '10', + 'bottombordercolormainwrappertypebordercategorystylingnameborderenabletopbottom': 'rgb(193, 202, 197)', + 'bottombordertypemainwrappertypebordercategorystylingnameborderenabletopbottom': 'solid', + 'radiusvalueimageimageimgimagebeforetyperadiuscategorystylingnameradius': '0', + 'topleftradiusvalueimageimageimgimagebeforetyperadiuscategorystylingnameradius': '20', + 'toprightradiusvalueimageimageimgimagebeforetyperadiuscategorystylingnameradius': '', + 'bottomleftradiusvalueimageimageimgimagebeforetyperadiuscategorystylingnameradius': '', + 'bottomrightradiusvalueimageimageimgimagebeforetyperadiuscategorystylingnameradius': '20', + 'colorbuttontypecolorcategorycolornamecolorcsscolor': 'rgb(255, 255, 255)', + 'colorbuttontypecolorcategorycolornamebackgroundcssbackground': 'rgb(68, 187, 204)', + 'colorbuttontypecolorcategorycolornamebackgroundhovercssbackgroundschemabuttonhover': 'rgb(133, 220, 232)', + 'radiusvaluebuttontyperadiuscategorystylingnameradius': '7', + 'topleftradiusvaluebuttontyperadiuscategorystylingnameradius': '', + 'toprightradiusvaluebuttontyperadiuscategorystylingnameradius': '', + 'bottomleftradiusvaluebuttontyperadiuscategorystylingnameradius': '', + 'bottomrightradiusvaluebuttontyperadiuscategorystylingnameradius': '', + 'colorbuttonalttypecolorcategorycolornamecolorcsscolor': 'rgb(255, 255, 255)', + 'colorbuttonalttypecolorcategorycolornamebackgroundcssbackground': 'rgb(193, 202, 197)', + 'colorbuttonalttypecolorcategorycolornamebackgroundhovercssbackgroundschemabuttonalthover': 'rgb(204, 213, 208)' + } + }, + { + name: 'Green safe', + color1: 'rgb(193, 202, 197)', + color2: 'rgb(240, 240, 240)', + color3: 'rgb(0, 153, 0)', + color4: 'rgb(0, 51, 0)', + color5: 'rgb(51, 51, 51)', + data: { + 'widebodytypewidecategorydimensionnamelayout': 'box', + 'imageorpatternbodytypebackgroundcategorycolornamebackground': '', + 'colorbodytypebackgroundcategorycolornamebackground': 'rgb(240, 240, 240)', + 'colorbodytypecolorcategoryfontnamefontcolormaincsscolorschemabodyh1h2h3h4h5h6h7navlia': 'rgb(85, 85, 85)', + 'colorbodytypecolorcategoryfontnamefontcolorsecondarycsscolorschemaulmetabyline': 'rgb(0, 153, 0)', + 'fontfamilybodytypegooglefontpickercategoryfontnamefontfamilycsscolorschemabodyh1h2h3h4h5h6h7bylinenavbutton': 'Karma', + 'fonttypebodytypegooglefontpickercategoryfontnamefontfamilycsscolorschemabodyh1h2h3h4h5h6h7bylinenavbutton': 'google', + 'fontweightbodytypegooglefontpickercategoryfontnamefontfamilycsscolorschemabodyh1h2h3h4h5h6h7bylinenavbutton': '300', + 'fontstylebodytypegooglefontpickercategoryfontnamefontfamilycsscolorschemabodyh1h2h3h4h5h6h7bylinenavbutton': '', + 'imageorpatternnavtypebackgroundcategorycolornamebackground': '', + 'colornavtypebackgroundcategorycolornamebackground': 'rgb(0, 51, 0)', + 'bordersizenavtypebordercategorycolornameborder': '', + 'bordercolornavtypebordercategorycolornameborder': '', + 'bordertypenavtypebordercategorycolornameborder': 'solid', + 'leftbordersizenavtypebordercategorycolornameborder': '', + 'leftbordercolornavtypebordercategorycolornameborder': '', + 'leftbordertypenavtypebordercategorycolornameborder': 'solid', + 'rightbordersizenavtypebordercategorycolornameborder': '', + 'rightbordercolornavtypebordercategorycolornameborder': '', + 'rightbordertypenavtypebordercategorycolornameborder': 'solid', + 'topbordersizenavtypebordercategorycolornameborder': '', + 'topbordercolornavtypebordercategorycolornameborder': '', + 'topbordertypenavtypebordercategorycolornameborder': 'solid', + 'bottombordersizenavtypebordercategorycolornameborder': '1', + 'bottombordercolornavtypebordercategorycolornameborder': 'rgba(0, 0, 0, 0.05)', + 'bottombordertypenavtypebordercategorycolornameborder': 'solid', + 'colornavtypecolorcategorynavnamefontcolorcsscolorschemanavlia': 'rgb(187, 187, 187)', + 'colornavtypecolorcategorynavnamefontcolorhoverselectedcsscolorschemanavlihovera': 'rgb(255, 255, 255)', + 'colornavtypecolorcategorynavnamebackgroundcolorhovercssbackgroundcolorschemanavlihovera': 'rgb(0, 153, 0)', + 'colornavtypecolorcategorynavnamebackgroundcolorselectedcssbackgroundcolorschemanavlicurrentpageitema': 'rgb(0, 153, 0)', + 'fontfamilynavtypegooglefontpickercategoryfontnamefontfamilly': '', + 'fonttypenavtypegooglefontpickercategoryfontnamefontfamilly': '', + 'fontweightnavtypegooglefontpickercategoryfontnamefontfamilly': '', + 'fontstylenavtypegooglefontpickercategoryfontnamefontfamilly': '', + 'colorheaderlogodivtypecolorcategorycolornamebordercolorcssbordertopcolorschemaheaderlogo': 'rgb(231, 234, 232)', + 'paddingvalueheaderlogodivtypepaddingcategorypositionnamemarginenabletopbottomschemaheader': '', + 'leftpaddingvalueheaderlogodivtypepaddingcategorypositionnamemarginenabletopbottomschemaheader': '', + 'rightpaddingvalueheaderlogodivtypepaddingcategorypositionnamemarginenabletopbottomschemaheader': '', + 'toppaddingvalueheaderlogodivtypepaddingcategorypositionnamemarginenabletopbottomschemaheader': '151', + 'bottompaddingvalueheaderlogodivtypepaddingcategorypositionnamemarginenabletopbottomschemaheader': '57', + 'colorh2typecolorcategorycolornamebordercolorcssbordertopcolorschemah2major': 'rgb(231, 234, 232)', + 'colorh2typecolorcategoryfontnamefontcolorcsscolor': '', + 'colorh3typecolorcategoryfontnamefontcolorcsscolor': '', + 'colorbannerh2typecolorcategoryfontnamefontcolorcsscolor': 'rgb(0, 153, 0)', + 'sliderbannerh2typeslidercategoryfontnamefontsizecssfontsizemin18max100': '54', + 'marginvaluebannerh2typemargincategorypositionnamemargin': '', + 'leftmarginvaluebannerh2typemargincategorypositionnamemargin': '', + 'rightmarginvaluebannerh2typemargincategorypositionnamemargin': '', + 'topmarginvaluebannerh2typemargincategorypositionnamemargin': '33', + 'bottommarginvaluebannerh2typemargincategorypositionnamemargin': '', + 'colorbannerbylinetypecolorcategoryfontnamefontcolorcsscolor': 'rgb(255, 255, 255)', + 'sliderbannerbylinetypeslidercategoryfontnamefontsizecssfontsizemin18max100': '26', + 'marginvaluebannerbylinetypemargincategorypositionnamemargin': '', + 'leftmarginvaluebannerbylinetypemargincategorypositionnamemargin': '', + 'rightmarginvaluebannerbylinetypemargincategorypositionnamemargin': '', + 'topmarginvaluebannerbylinetypemargincategorypositionnamemargin': '', + 'bottommarginvaluebannerbylinetypemargincategorypositionnamemargin': '30', + 'imageorpatternbannertypebackgroundcategorycolornamebackgroundcsscolor': '', + 'colorbannertypebackgroundcategorycolornamebackgroundcsscolor': 'rgb(51, 51, 51)', + 'imageorpatternbannerwrappertypebackgroundcategorycolornamebackground': '', + 'colorbannerwrappertypebackgroundcategorycolornamebackground': 'rgba(0, 153, 0, 0.15)', + 'paddingvaluebannerwrappertypepaddingcategorypositionnamepaddingenabletopbottom': '', + 'leftpaddingvaluebannerwrappertypepaddingcategorypositionnamepaddingenabletopbottom': '', + 'rightpaddingvaluebannerwrappertypepaddingcategorypositionnamepaddingenabletopbottom': '', + 'toppaddingvaluebannerwrappertypepaddingcategorypositionnamepaddingenabletopbottom': '21', + 'bottompaddingvaluebannerwrappertypepaddingcategorypositionnamepaddingenabletopbottom': '21', + 'bordersizemainwrappertypebordercategorystylingnameborderenabletopbottom': '', + 'bordercolormainwrappertypebordercategorystylingnameborderenabletopbottom': '', + 'bordertypemainwrappertypebordercategorystylingnameborderenabletopbottom': 'solid', + 'leftbordersizemainwrappertypebordercategorystylingnameborderenabletopbottom': '', + 'leftbordercolormainwrappertypebordercategorystylingnameborderenabletopbottom': '', + 'leftbordertypemainwrappertypebordercategorystylingnameborderenabletopbottom': 'solid', + 'rightbordersizemainwrappertypebordercategorystylingnameborderenabletopbottom': '', + 'rightbordercolormainwrappertypebordercategorystylingnameborderenabletopbottom': '', + 'rightbordertypemainwrappertypebordercategorystylingnameborderenabletopbottom': 'solid', + 'topbordersizemainwrappertypebordercategorystylingnameborderenabletopbottom': '1', + 'topbordercolormainwrappertypebordercategorystylingnameborderenabletopbottom': 'rgba(68, 187, 204, 0)', + 'topbordertypemainwrappertypebordercategorystylingnameborderenabletopbottom': 'solid', + 'bottombordersizemainwrappertypebordercategorystylingnameborderenabletopbottom': '10', + 'bottombordercolormainwrappertypebordercategorystylingnameborderenabletopbottom': 'rgb(193, 202, 197)', + 'bottombordertypemainwrappertypebordercategorystylingnameborderenabletopbottom': 'solid', + 'radiusvalueimageimageimgimagebeforetyperadiuscategorystylingnameradius': '8', + 'topleftradiusvalueimageimageimgimagebeforetyperadiuscategorystylingnameradius': '0', + 'toprightradiusvalueimageimageimgimagebeforetyperadiuscategorystylingnameradius': '0', + 'bottomleftradiusvalueimageimageimgimagebeforetyperadiuscategorystylingnameradius': '0', + 'bottomrightradiusvalueimageimageimgimagebeforetyperadiuscategorystylingnameradius': '0', + 'colorbuttontypecolorcategorycolornamecolorcsscolor': 'rgb(255, 255, 255)', + 'colorbuttontypecolorcategorycolornamebackgroundcssbackground': 'rgb(0, 51, 0)', + 'colorbuttontypecolorcategorycolornamebackgroundhovercssbackgroundschemabuttonhover': 'rgba(0, 51, 0, 0.62)', + 'radiusvaluebuttontyperadiuscategorystylingnameradius': '7', + 'topleftradiusvaluebuttontyperadiuscategorystylingnameradius': '', + 'toprightradiusvaluebuttontyperadiuscategorystylingnameradius': '', + 'bottomleftradiusvaluebuttontyperadiuscategorystylingnameradius': '', + 'bottomrightradiusvaluebuttontyperadiuscategorystylingnameradius': '', + 'colorbuttonalttypecolorcategorycolornamecolorcsscolor': 'rgb(255, 255, 255)', + 'colorbuttonalttypecolorcategorycolornamebackgroundcssbackground': 'rgb(193, 202, 197)', + 'colorbuttonalttypecolorcategorycolornamebackgroundhovercssbackgroundschemabuttonalthover': 'rgb(204, 213, 208)' + } + }, + { + name: 'Orange', + color1: 'rgb(193, 202, 197)', + color2: 'rgb(231, 234, 232)', + color3: 'rgb(230, 126, 34)', + color4: 'rgb(211, 84, 0)', + color5: 'rgb(51, 51, 51)', + data: { + 'widebodytypewidecategorydimensionnamelayout': 'wide', + 'imageorpatternbodytypebackgroundcategorycolornamebackground': '', + 'colorbodytypebackgroundcategorycolornamebackground': 'rgb(240, 240, 240)', + 'colorbodytypecolorcategoryfontnamefontcolormaincsscolorschemabodyh1h2h3h4h5h6h7navlia': 'rgb(85, 85, 85)', + 'colorbodytypecolorcategoryfontnamefontcolorsecondarycsscolorschemaulmetabyline': 'rgb(230, 126, 34)', + 'fontfamilybodytypegooglefontpickercategoryfontnamefontfamilycsscolorschemabodyh1h2h3h4h5h6h7bylinenavbutton': 'Lato', + 'fonttypebodytypegooglefontpickercategoryfontnamefontfamilycsscolorschemabodyh1h2h3h4h5h6h7bylinenavbutton': 'google', + 'fontweightbodytypegooglefontpickercategoryfontnamefontfamilycsscolorschemabodyh1h2h3h4h5h6h7bylinenavbutton': '300', + 'fontstylebodytypegooglefontpickercategoryfontnamefontfamilycsscolorschemabodyh1h2h3h4h5h6h7bylinenavbutton': '', + 'imageorpatternnavtypebackgroundcategorycolornamebackground': '', + 'colornavtypebackgroundcategorycolornamebackground': 'rgb(51, 51, 51)', + 'bordersizenavtypebordercategorycolornameborder': '', + 'bordercolornavtypebordercategorycolornameborder': '', + 'bordertypenavtypebordercategorycolornameborder': 'solid', + 'leftbordersizenavtypebordercategorycolornameborder': '', + 'leftbordercolornavtypebordercategorycolornameborder': '', + 'leftbordertypenavtypebordercategorycolornameborder': 'solid', + 'rightbordersizenavtypebordercategorycolornameborder': '', + 'rightbordercolornavtypebordercategorycolornameborder': '', + 'rightbordertypenavtypebordercategorycolornameborder': 'solid', + 'topbordersizenavtypebordercategorycolornameborder': '', + 'topbordercolornavtypebordercategorycolornameborder': '', + 'topbordertypenavtypebordercategorycolornameborder': 'solid', + 'bottombordersizenavtypebordercategorycolornameborder': '1', + 'bottombordercolornavtypebordercategorycolornameborder': 'rgba(0, 0, 0, 0.05)', + 'bottombordertypenavtypebordercategorycolornameborder': 'solid', + 'colornavtypecolorcategorynavnamefontcolorcsscolorschemanavlia': 'rgb(187, 187, 187)', + 'colornavtypecolorcategorynavnamefontcolorhoverselectedcsscolorschemanavlihovera': 'rgb(255, 255, 255)', + 'colornavtypecolorcategorynavnamebackgroundcolorhovercssbackgroundcolorschemanavlihovera': 'rgb(181, 181, 181)', + 'colornavtypecolorcategorynavnamebackgroundcolorselectedcssbackgroundcolorschemanavlicurrentpageitema': 'rgb(211, 84, 0)', + 'fontfamilynavtypegooglefontpickercategoryfontnamefontfamilly': '', + 'fonttypenavtypegooglefontpickercategoryfontnamefontfamilly': '', + 'fontweightnavtypegooglefontpickercategoryfontnamefontfamilly': '', + 'fontstylenavtypegooglefontpickercategoryfontnamefontfamilly': '', + 'colorheaderlogodivtypecolorcategorycolornamebordercolorcssbordertopcolorschemaheaderlogo': 'rgb(231, 234, 232)', + 'paddingvalueheaderlogodivtypepaddingcategorypositionnamemarginenabletopbottomschemaheader': '', + 'leftpaddingvalueheaderlogodivtypepaddingcategorypositionnamemarginenabletopbottomschemaheader': '', + 'rightpaddingvalueheaderlogodivtypepaddingcategorypositionnamemarginenabletopbottomschemaheader': '', + 'toppaddingvalueheaderlogodivtypepaddingcategorypositionnamemarginenabletopbottomschemaheader': '151', + 'bottompaddingvalueheaderlogodivtypepaddingcategorypositionnamemarginenabletopbottomschemaheader': '57', + 'colorh2typecolorcategorycolornamebordercolorcssbordertopcolorschemah2major': 'rgb(231, 234, 232)', + 'colorh2typecolorcategoryfontnamefontcolorcsscolor': '', + 'colorh3typecolorcategoryfontnamefontcolorcsscolor': '', + 'colorbannerh2typecolorcategoryfontnamefontcolorcsscolor': '', + 'sliderbannerh2typeslidercategoryfontnamefontsizecssfontsizemin18max100': '54', + 'marginvaluebannerh2typemargincategorypositionnamemargin': '', + 'leftmarginvaluebannerh2typemargincategorypositionnamemargin': '', + 'rightmarginvaluebannerh2typemargincategorypositionnamemargin': '', + 'topmarginvaluebannerh2typemargincategorypositionnamemargin': '33', + 'bottommarginvaluebannerh2typemargincategorypositionnamemargin': '', + 'colorbannerbylinetypecolorcategoryfontnamefontcolorcsscolor': 'rgb(225, 225, 225)', + 'sliderbannerbylinetypeslidercategoryfontnamefontsizecssfontsizemin18max100': '26', + 'marginvaluebannerbylinetypemargincategorypositionnamemargin': '', + 'leftmarginvaluebannerbylinetypemargincategorypositionnamemargin': '', + 'rightmarginvaluebannerbylinetypemargincategorypositionnamemargin': '', + 'topmarginvaluebannerbylinetypemargincategorypositionnamemargin': '', + 'bottommarginvaluebannerbylinetypemargincategorypositionnamemargin': '30', + 'imageorpatternbannertypebackgroundcategorycolornamebackgroundcsscolor': '', + 'colorbannertypebackgroundcategorycolornamebackgroundcsscolor': 'rgb(230, 126, 34)', + 'imageorpatternbannerwrappertypebackgroundcategorycolornamebackground': '', + 'colorbannerwrappertypebackgroundcategorycolornamebackground': 'rgb(255, 255, 255)', + 'paddingvaluebannerwrappertypepaddingcategorypositionnamepaddingenabletopbottom': '', + 'leftpaddingvaluebannerwrappertypepaddingcategorypositionnamepaddingenabletopbottom': '', + 'rightpaddingvaluebannerwrappertypepaddingcategorypositionnamepaddingenabletopbottom': '', + 'toppaddingvaluebannerwrappertypepaddingcategorypositionnamepaddingenabletopbottom': '21', + 'bottompaddingvaluebannerwrappertypepaddingcategorypositionnamepaddingenabletopbottom': '21', + 'bordersizemainwrappertypebordercategorystylingnameborderenabletopbottom': '', + 'bordercolormainwrappertypebordercategorystylingnameborderenabletopbottom': '', + 'bordertypemainwrappertypebordercategorystylingnameborderenabletopbottom': 'solid', + 'leftbordersizemainwrappertypebordercategorystylingnameborderenabletopbottom': '', + 'leftbordercolormainwrappertypebordercategorystylingnameborderenabletopbottom': '', + 'leftbordertypemainwrappertypebordercategorystylingnameborderenabletopbottom': 'solid', + 'rightbordersizemainwrappertypebordercategorystylingnameborderenabletopbottom': '', + 'rightbordercolormainwrappertypebordercategorystylingnameborderenabletopbottom': '', + 'rightbordertypemainwrappertypebordercategorystylingnameborderenabletopbottom': 'solid', + 'topbordersizemainwrappertypebordercategorystylingnameborderenabletopbottom': '1', + 'topbordercolormainwrappertypebordercategorystylingnameborderenabletopbottom': 'rgba(68, 187, 204, 0)', + 'topbordertypemainwrappertypebordercategorystylingnameborderenabletopbottom': 'solid', + 'bottombordersizemainwrappertypebordercategorystylingnameborderenabletopbottom': '10', + 'bottombordercolormainwrappertypebordercategorystylingnameborderenabletopbottom': 'rgb(193, 202, 197)', + 'bottombordertypemainwrappertypebordercategorystylingnameborderenabletopbottom': 'solid', + 'radiusvalueimageimageimgimagebeforetyperadiuscategorystylingnameradius': '8', + 'topleftradiusvalueimageimageimgimagebeforetyperadiuscategorystylingnameradius': '0', + 'toprightradiusvalueimageimageimgimagebeforetyperadiuscategorystylingnameradius': '0', + 'bottomleftradiusvalueimageimageimgimagebeforetyperadiuscategorystylingnameradius': '0', + 'bottomrightradiusvalueimageimageimgimagebeforetyperadiuscategorystylingnameradius': '0', + 'colorbuttontypecolorcategorycolornamecolorcsscolor': 'rgb(255, 255, 255)', + 'colorbuttontypecolorcategorycolornamebackgroundcssbackground': 'rgb(230, 126, 34)', + 'colorbuttontypecolorcategorycolornamebackgroundhovercssbackgroundschemabuttonhover': 'rgba(230, 126, 34, 0.55)', + 'radiusvaluebuttontyperadiuscategorystylingnameradius': '7', + 'topleftradiusvaluebuttontyperadiuscategorystylingnameradius': '', + 'toprightradiusvaluebuttontyperadiuscategorystylingnameradius': '', + 'bottomleftradiusvaluebuttontyperadiuscategorystylingnameradius': '', + 'bottomrightradiusvaluebuttontyperadiuscategorystylingnameradius': '', + 'colorbuttonalttypecolorcategorycolornamecolorcsscolor': 'rgb(255, 255, 255)', + 'colorbuttonalttypecolorcategorycolornamebackgroundcssbackground': 'rgb(193, 202, 197)', + 'colorbuttonalttypecolorcategorycolornamebackgroundhovercssbackgroundschemabuttonalthover': 'rgb(204, 213, 208)' + } + } + ]; + /*********************************************************************************************************/ + /* Background editor */ + /*********************************************************************************************************/ + angular.module('Umbraco.canvasdesigner').controller('Umbraco.canvasdesigner.background', function ($scope, dialogService) { + if (!$scope.item.values) { + $scope.item.values = { + imageorpattern: '', + color: '' + }; + } + $scope.open = function (field) { + var config = { + template: 'mediaPickerModal.html', + change: function (data) { + $scope.item.values.imageorpattern = data; + }, + callback: function (data) { + $scope.item.values.imageorpattern = data; + }, + cancel: function (data) { + $scope.item.values.imageorpattern = data; + }, + dialogData: $scope.googleFontFamilies, + dialogItem: $scope.item.values.imageorpattern + }; + dialogService.open(config); + }; + }).controller('canvasdesigner.mediaPickerModal', function ($scope, $http, mediaResource, umbRequestHelper, entityResource, mediaHelper) { + if (mediaHelper && mediaHelper.registerFileResolver) { + mediaHelper.registerFileResolver('Umbraco.UploadField', function (property, entity, thumbnail) { + if (thumbnail) { + if (mediaHelper.detectIfImageByExtension(property.value)) { + var thumbnailUrl = umbRequestHelper.getApiUrl('imagesApiBaseUrl', 'GetBigThumbnail', [{ originalImagePath: property.value }]); + return thumbnailUrl; + } else { + return null; + } + } else { + return property.value; + } + }); + } + var modalFieldvalue = $scope.dialogItem; + $scope.currentFolder = {}; + $scope.currentFolder.children = []; + $scope.currentPath = []; + $scope.startNodeId = -1; + $scope.options = { + url: umbRequestHelper.getApiUrl('mediaApiBaseUrl', 'PostAddFile'), + formData: { currentFolder: $scope.startNodeId } + }; + //preload selected item + $scope.selectedMedia = undefined; + $scope.submitFolder = function (e) { + if (e.keyCode === 13) { + e.preventDefault(); + $scope.$parent.data.showFolderInput = false; + if ($scope.$parent.data.newFolder && $scope.$parent.data.newFolder != '') { + mediaResource.addFolder($scope.$parent.data.newFolder, $scope.currentFolder.id).then(function (data) { + $scope.$parent.data.newFolder = undefined; + $scope.gotoFolder(data); + }); + } + } + }; + $scope.gotoFolder = function (folder) { + if (!folder) { + folder = { + id: $scope.startNodeId, + name: 'Media', + icon: 'icon-folder' + }; + } + if (folder.id > 0) { + var matches = _.filter($scope.currentPath, function (value, index) { + if (value.id == folder.id) { + value.indexInPath = index; + return value; + } + }); + if (matches && matches.length > 0) { + $scope.currentPath = $scope.currentPath.slice(0, matches[0].indexInPath + 1); + } else { + $scope.currentPath.push(folder); + } + } else { + $scope.currentPath = []; + } + //mediaResource.rootMedia() + mediaResource.getChildren(folder.id).then(function (data) { + folder.children = data.items ? data.items : []; + angular.forEach(folder.children, function (child) { + child.isFolder = child.contentTypeAlias == 'Folder' ? true : false; + if (!child.isFolder) { + angular.forEach(child.properties, function (property) { + if (property.alias == 'umbracoFile' && property.value) { + child.thumbnail = mediaHelper.resolveFile(child, true); + child.image = property.value; + } + }); + } + }); + $scope.options.formData.currentFolder = folder.id; + $scope.currentFolder = folder; + }); + }; + $scope.iconFolder = 'glyphicons-icon folder-open'; + $scope.selectMedia = function (media) { + if (!media.isFolder) { + //we have 3 options add to collection (if multi) show details, or submit it right back to the callback + $scope.selectedMedia = media; + modalFieldvalue = 'url(' + $scope.selectedMedia.image + ')'; + $scope.change(modalFieldvalue); + } else { + $scope.gotoFolder(media); + } + }; + //default root item + if (!$scope.selectedMedia) { + $scope.gotoFolder(); + } + $scope.submitAndClose = function () { + if (modalFieldvalue != '') { + $scope.submit(modalFieldvalue); + } else { + $scope.cancel(); + } + }; + $scope.cancelAndClose = function () { + $scope.cancel(); + }; + }); + /*********************************************************************************************************/ + /* Background editor */ + /*********************************************************************************************************/ + angular.module('Umbraco.canvasdesigner').controller('Umbraco.canvasdesigner.border', function ($scope, dialogService) { + $scope.defaultBorderList = [ + 'all', + 'left', + 'right', + 'top', + 'bottom' + ]; + $scope.borderList = []; + $scope.bordertypes = [ + 'solid', + 'dashed', + 'dotted' + ]; + $scope.selectedBorder = { + name: 'all', + size: 0, + color: '', + type: '' + }; + $scope.setselectedBorder = function (bordertype) { + if (bordertype == 'all') { + $scope.selectedBorder.name = 'all'; + $scope.selectedBorder.size = $scope.item.values.bordersize; + $scope.selectedBorder.color = $scope.item.values.bordercolor; + $scope.selectedBorder.type = $scope.item.values.bordertype; + } + if (bordertype == 'left') { + $scope.selectedBorder.name = 'left'; + $scope.selectedBorder.size = $scope.item.values.leftbordersize; + $scope.selectedBorder.color = $scope.item.values.leftbordercolor; + $scope.selectedBorder.type = $scope.item.values.leftbordertype; + } + if (bordertype == 'right') { + $scope.selectedBorder.name = 'right'; + $scope.selectedBorder.size = $scope.item.values.rightbordersize; + $scope.selectedBorder.color = $scope.item.values.rightbordercolor; + $scope.selectedBorder.type = $scope.item.values.rightbordertype; + } + if (bordertype == 'top') { + $scope.selectedBorder.name = 'top'; + $scope.selectedBorder.size = $scope.item.values.topbordersize; + $scope.selectedBorder.color = $scope.item.values.topbordercolor; + $scope.selectedBorder.type = $scope.item.values.topbordertype; + } + if (bordertype == 'bottom') { + $scope.selectedBorder.name = 'bottom'; + $scope.selectedBorder.size = $scope.item.values.bottombordersize; + $scope.selectedBorder.color = $scope.item.values.bottombordercolor; + $scope.selectedBorder.type = $scope.item.values.bottombordertype; + } + }; + if (!$scope.item.values) { + $scope.item.values = { + bordersize: '', + bordercolor: '', + bordertype: 'solid', + leftbordersize: '', + leftbordercolor: '', + leftbordertype: 'solid', + rightbordersize: '', + rightbordercolor: '', + rightbordertype: 'solid', + topbordersize: '', + topbordercolor: '', + topbordertype: 'solid', + bottombordersize: '', + bottombordercolor: '', + bottombordertype: 'solid' + }; + } + if ($scope.item.enable) { + angular.forEach($scope.defaultBorderList, function (key, indexKey) { + if ($.inArray(key, $scope.item.enable) >= 0) { + $scope.borderList.splice($scope.borderList.length + 1, 0, key); + } + }); + } else { + $scope.borderList = $scope.defaultBorderList; + } + $scope.$watch('valueAreLoaded', function () { + $scope.setselectedBorder($scope.borderList[0]); + }, false); + $scope.$watch('selectedBorder', function () { + if ($scope.selectedBorder.name == 'all') { + $scope.item.values.bordersize = $scope.selectedBorder.size; + $scope.item.values.bordertype = $scope.selectedBorder.type; + } + if ($scope.selectedBorder.name == 'left') { + $scope.item.values.leftbordersize = $scope.selectedBorder.size; + $scope.item.values.leftbordertype = $scope.selectedBorder.type; + } + if ($scope.selectedBorder.name == 'right') { + $scope.item.values.rightbordersize = $scope.selectedBorder.size; + $scope.item.values.rightbordertype = $scope.selectedBorder.type; + } + if ($scope.selectedBorder.name == 'top') { + $scope.item.values.topbordersize = $scope.selectedBorder.size; + $scope.item.values.topbordertype = $scope.selectedBorder.type; + } + if ($scope.selectedBorder.name == 'bottom') { + $scope.item.values.bottombordersize = $scope.selectedBorder.size; + $scope.item.values.bottombordertype = $scope.selectedBorder.type; + } + }, true); + }); + /*********************************************************************************************************/ + /* color editor */ + /*********************************************************************************************************/ + angular.module('Umbraco.canvasdesigner').controller('Umbraco.canvasdesigner.color', function ($scope) { + if (!$scope.item.values) { + $scope.item.values = { color: '' }; + } + }); + /*********************************************************************************************************/ + /* google font editor */ + /*********************************************************************************************************/ + angular.module('Umbraco.canvasdesigner').controller('Umbraco.canvasdesigner.googlefontpicker', function ($scope, dialogService) { + if (!$scope.item.values) { + $scope.item.values = { + fontFamily: '', + fontType: '', + fontWeight: '', + fontStyle: '' + }; + } + $scope.setStyleVariant = function () { + if ($scope.item.values != undefined) { + return { + 'font-family': $scope.item.values.fontFamily, + 'font-weight': $scope.item.values.fontWeight, + 'font-style': $scope.item.values.fontStyle + }; + } + }; + $scope.open = function (field) { + var config = { + template: 'googlefontdialog.html', + change: function (data) { + $scope.item.values = data; + }, + callback: function (data) { + $scope.item.values = data; + }, + cancel: function (data) { + $scope.item.values = data; + }, + dialogData: $scope.googleFontFamilies, + dialogItem: $scope.item.values + }; + dialogService.open(config); + }; + }).controller('googlefontdialog.controller', function ($scope) { + $scope.safeFonts = [ + 'Arial, Helvetica', + 'Impact', + 'Lucida Sans Unicode', + 'Tahoma', + 'Trebuchet MS', + 'Verdana', + 'Georgia', + 'Times New Roman', + 'Courier New, Courier' + ]; + $scope.fonts = []; + $scope.selectedFont = {}; + var googleGetWeight = function (googleVariant) { + return googleVariant != undefined && googleVariant != '' ? googleVariant.replace('italic', '') : ''; + }; + var googleGetStyle = function (googleVariant) { + var variantStyle = ''; + if (googleVariant != undefined && googleVariant != '' && googleVariant.indexOf('italic') >= 0) { + variantWeight = googleVariant.replace('italic', ''); + variantStyle = 'italic'; + } + return variantStyle; + }; + angular.forEach($scope.safeFonts, function (value, key) { + $scope.fonts.push({ + groupName: 'Safe fonts', + fontType: 'safe', + fontFamily: value, + fontWeight: 'normal', + fontStyle: 'normal' + }); + }); + angular.forEach($scope.dialogData.items, function (value, key) { + var variants = value.variants; + var variant = value.variants.length > 0 ? value.variants[0] : ''; + var fontWeight = googleGetWeight(variant); + var fontStyle = googleGetStyle(variant); + $scope.fonts.push({ + groupName: 'Google fonts', + fontType: 'google', + fontFamily: value.family, + variants: value.variants, + variant: variant, + fontWeight: fontWeight, + fontStyle: fontStyle + }); + }); + $scope.setStyleVariant = function () { + if ($scope.dialogItem != undefined) { + return { + 'font-family': $scope.selectedFont.fontFamily, + 'font-weight': $scope.selectedFont.fontWeight, + 'font-style': $scope.selectedFont.fontStyle + }; + } + }; + function loadFont(font, variant) { + WebFont.load({ + google: { families: [font.fontFamily + ':' + variant] }, + loading: function () { + console.log('loading'); + }, + active: function () { + $scope.selectedFont = font; + $scope.selectedFont.fontWeight = googleGetWeight(variant); + $scope.selectedFont.fontStyle = googleGetStyle(variant); + // If $apply isn't called, the new font family isn't applied until the next user click. + $scope.change({ + fontFamily: $scope.selectedFont.fontFamily, + fontType: $scope.selectedFont.fontType, + fontWeight: $scope.selectedFont.fontWeight, + fontStyle: $scope.selectedFont.fontStyle + }); + } + }); + } + var webFontScriptLoaded = false; + $scope.showFontPreview = function (font, variant) { + if (!variant) + variant = font.variant; + if (font != undefined && font.fontFamily != '' && font.fontType == 'google') { + // Font needs to be independently loaded in the iframe for live preview to work. + document.getElementById('resultFrame').contentWindow.getFont(font.fontFamily + ':' + variant); + if (!webFontScriptLoaded) { + $.getScript('https://ajax.googleapis.com/ajax/libs/webfont/1/webfont.js').done(function () { + webFontScriptLoaded = true; + loadFont(font, variant); + }).fail(function () { + console.log('error loading webfont'); + }); + } else { + loadFont(font, variant); + } + } else { + // Font is available, apply it immediately in modal preview. + $scope.selectedFont = font; + // If $apply isn't called, the new font family isn't applied until the next user click. + $scope.change({ + fontFamily: $scope.selectedFont.fontFamily, + fontType: $scope.selectedFont.fontType, + fontWeight: $scope.selectedFont.fontWeight, + fontStyle: $scope.selectedFont.fontStyle + }); + } + }; + $scope.cancelAndClose = function () { + $scope.cancel(); + }; + $scope.submitAndClose = function () { + $scope.submit({ + fontFamily: $scope.selectedFont.fontFamily, + fontType: $scope.selectedFont.fontType, + fontWeight: $scope.selectedFont.fontWeight, + fontStyle: $scope.selectedFont.fontStyle + }); + }; + if ($scope.dialogItem != undefined) { + angular.forEach($scope.fonts, function (value, key) { + if (value.fontFamily == $scope.dialogItem.fontFamily) { + $scope.selectedFont = value; + $scope.selectedFont.variant = $scope.dialogItem.fontWeight + $scope.dialogItem.fontStyle; + $scope.selectedFont.fontWeight = $scope.dialogItem.fontWeight; + $scope.selectedFont.fontStyle = $scope.dialogItem.fontStyle; + } + }); + } + }); + /*********************************************************************************************************/ + /* grid row editor */ + /*********************************************************************************************************/ + angular.module('Umbraco.canvasdesigner').controller('Umbraco.canvasdesigner.gridRow', function ($scope) { + if (!$scope.item.values) { + $scope.item.values = { fullsize: false }; + } + }); + /*********************************************************************************************************/ + /* Layout */ + /*********************************************************************************************************/ + angular.module('Umbraco.canvasdesigner').controller('Umbraco.canvasdesigner.layout', function ($scope) { + if (!$scope.item.values) { + $scope.item.values = { layout: '' }; + } + }); + /*********************************************************************************************************/ + /* margin editor */ + /*********************************************************************************************************/ + angular.module('Umbraco.canvasdesigner').controller('Umbraco.canvasdesigner.margin', function ($scope, dialogService) { + $scope.defaultmarginList = [ + 'all', + 'left', + 'right', + 'top', + 'bottom' + ]; + $scope.marginList = []; + $scope.selectedmargin = { + name: '', + value: 0 + }; + $scope.setSelectedmargin = function (margintype) { + if (margintype == 'all') { + $scope.selectedmargin.name = 'all'; + $scope.selectedmargin.value = $scope.item.values.marginvalue; + } + if (margintype == 'left') { + $scope.selectedmargin.name = 'left'; + $scope.selectedmargin.value = $scope.item.values.leftmarginvalue; + } + if (margintype == 'right') { + $scope.selectedmargin.name = 'right'; + $scope.selectedmargin.value = $scope.item.values.rightmarginvalue; + } + if (margintype == 'top') { + $scope.selectedmargin.name = 'top'; + $scope.selectedmargin.value = $scope.item.values.topmarginvalue; + } + if (margintype == 'bottom') { + $scope.selectedmargin.name = 'bottom'; + $scope.selectedmargin.value = $scope.item.values.bottommarginvalue; + } + }; + if (!$scope.item.values) { + $scope.item.values = { + marginvalue: $scope.item.defaultValue && $scope.item.defaultValue.length > 0 ? $scope.item.defaultValue[0] : '', + leftmarginvalue: $scope.item.defaultValue && $scope.item.defaultValue.length > 1 ? $scope.item.defaultValue[1] : '', + rightmarginvalue: $scope.item.defaultValue && $scope.item.defaultValue.length > 2 ? $scope.item.defaultValue[2] : '', + topmarginvalue: $scope.item.defaultValue && $scope.item.defaultValue.length > 3 ? $scope.item.defaultValue[3] : '', + bottommarginvalue: $scope.item.defaultValue && $scope.item.defaultValue.length > 4 ? $scope.item.defaultValue[4] : '' + }; + } + if ($scope.item.enable) { + angular.forEach($scope.defaultmarginList, function (key, indexKey) { + if ($.inArray(key, $scope.item.enable) >= 0) { + $scope.marginList.splice($scope.marginList.length + 1, 0, key); + } + }); + } else { + $scope.marginList = $scope.defaultmarginList; + } + $scope.$watch('valueAreLoaded', function () { + $scope.setSelectedmargin($scope.marginList[0]); + }, false); + $scope.$watch('selectedmargin', function () { + if ($scope.selectedmargin.name == 'all') { + $scope.item.values.marginvalue = $scope.selectedmargin.value; + } + if ($scope.selectedmargin.name == 'left') { + $scope.item.values.leftmarginvalue = $scope.selectedmargin.value; + } + if ($scope.selectedmargin.name == 'right') { + $scope.item.values.rightmarginvalue = $scope.selectedmargin.value; + } + if ($scope.selectedmargin.name == 'top') { + $scope.item.values.topmarginvalue = $scope.selectedmargin.value; + } + if ($scope.selectedmargin.name == 'bottom') { + $scope.item.values.bottommarginvalue = $scope.selectedmargin.value; + } + }, true); + }); + /*********************************************************************************************************/ + /* padding editor */ + /*********************************************************************************************************/ + angular.module('Umbraco.canvasdesigner').controller('Umbraco.canvasdesigner.padding', function ($scope, dialogService) { + $scope.defaultPaddingList = [ + 'all', + 'left', + 'right', + 'top', + 'bottom' + ]; + $scope.paddingList = []; + $scope.selectedpadding = { + name: '', + value: 0 + }; + $scope.setSelectedpadding = function (paddingtype) { + if (paddingtype == 'all') { + $scope.selectedpadding.name = 'all'; + $scope.selectedpadding.value = $scope.item.values.paddingvalue; + } + if (paddingtype == 'left') { + $scope.selectedpadding.name = 'left'; + $scope.selectedpadding.value = $scope.item.values.leftpaddingvalue; + } + if (paddingtype == 'right') { + $scope.selectedpadding.name = 'right'; + $scope.selectedpadding.value = $scope.item.values.rightpaddingvalue; + } + if (paddingtype == 'top') { + $scope.selectedpadding.name = 'top'; + $scope.selectedpadding.value = $scope.item.values.toppaddingvalue; + } + if (paddingtype == 'bottom') { + $scope.selectedpadding.name = 'bottom'; + $scope.selectedpadding.value = $scope.item.values.bottompaddingvalue; + } + }; + if (!$scope.item.values) { + $scope.item.values = { + paddingvalue: $scope.item.defaultValue && $scope.item.defaultValue.length > 0 ? $scope.item.defaultValue[0] : '', + leftpaddingvalue: $scope.item.defaultValue && $scope.item.defaultValue.length > 1 ? $scope.item.defaultValue[1] : '', + rightpaddingvalue: $scope.item.defaultValue && $scope.item.defaultValue.length > 2 ? $scope.item.defaultValue[2] : '', + toppaddingvalue: $scope.item.defaultValue && $scope.item.defaultValue.length > 3 ? $scope.item.defaultValue[3] : '', + bottompaddingvalue: $scope.item.defaultValue && $scope.item.defaultValue.length > 4 ? $scope.item.defaultValue[4] : '' + }; + } + if ($scope.item.enable) { + angular.forEach($scope.defaultPaddingList, function (key, indexKey) { + if ($.inArray(key, $scope.item.enable) >= 0) { + $scope.paddingList.splice($scope.paddingList.length + 1, 0, key); + } + }); + } else { + $scope.paddingList = $scope.defaultPaddingList; + } + $scope.$watch('valueAreLoaded', function () { + $scope.setSelectedpadding($scope.paddingList[0]); + }, false); + $scope.$watch('selectedpadding', function () { + if ($scope.selectedpadding.name == 'all') { + $scope.item.values.paddingvalue = $scope.selectedpadding.value; + } + if ($scope.selectedpadding.name == 'left') { + $scope.item.values.leftpaddingvalue = $scope.selectedpadding.value; + } + if ($scope.selectedpadding.name == 'right') { + $scope.item.values.rightpaddingvalue = $scope.selectedpadding.value; + } + if ($scope.selectedpadding.name == 'top') { + $scope.item.values.toppaddingvalue = $scope.selectedpadding.value; + } + if ($scope.selectedpadding.name == 'bottom') { + $scope.item.values.bottompaddingvalue = $scope.selectedpadding.value; + } + }, true); + }); + /*********************************************************************************************************/ + /* radius editor */ + /*********************************************************************************************************/ + angular.module('Umbraco.canvasdesigner').controller('Umbraco.canvasdesigner.radius', function ($scope, dialogService) { + $scope.defaultRadiusList = [ + 'all', + 'topleft', + 'topright', + 'bottomleft', + 'bottomright' + ]; + $scope.radiusList = []; + $scope.selectedradius = { + name: '', + value: 0 + }; + $scope.setSelectedradius = function (radiustype) { + if (radiustype == 'all') { + $scope.selectedradius.name = 'all'; + $scope.selectedradius.value = $scope.item.values.radiusvalue; + } + if (radiustype == 'topleft') { + $scope.selectedradius.name = 'topleft'; + $scope.selectedradius.value = $scope.item.values.topleftradiusvalue; + } + if (radiustype == 'topright') { + $scope.selectedradius.name = 'topright'; + $scope.selectedradius.value = $scope.item.values.toprightradiusvalue; + } + if (radiustype == 'bottomleft') { + $scope.selectedradius.name = 'bottomleft'; + $scope.selectedradius.value = $scope.item.values.bottomleftradiusvalue; + } + if (radiustype == 'bottomright') { + $scope.selectedradius.name = 'bottomright'; + $scope.selectedradius.value = $scope.item.values.bottomrightradiusvalue; + } + }; + if (!$scope.item.values) { + $scope.item.values = { + radiusvalue: $scope.item.defaultValue && $scope.item.defaultValue.length > 0 ? $scope.item.defaultValue[0] : '', + topleftradiusvalue: $scope.item.defaultValue && $scope.item.defaultValue.length > 1 ? $scope.item.defaultValue[1] : '', + toprightradiusvalue: $scope.item.defaultValue && $scope.item.defaultValue.length > 2 ? $scope.item.defaultValue[2] : '', + bottomleftradiusvalue: $scope.item.defaultValue && $scope.item.defaultValue.length > 3 ? $scope.item.defaultValue[3] : '', + bottomrightradiusvalue: $scope.item.defaultValue && $scope.item.defaultValue.length > 4 ? $scope.item.defaultValue[4] : '' + }; + } + if ($scope.item.enable) { + angular.forEach($scope.defaultRadiusList, function (key, indexKey) { + if ($.inArray(key, $scope.item.enable) >= 0) { + $scope.radiusList.splice($scope.radiusList.length + 1, 0, key); + } + }); + } else { + $scope.radiusList = $scope.defaultRadiusList; + } + $scope.$watch('valueAreLoaded', function () { + $scope.setSelectedradius($scope.radiusList[0]); + }, false); + $scope.$watch('selectedradius', function () { + if ($scope.selectedradius.name == 'all') { + $scope.item.values.radiusvalue = $scope.selectedradius.value; + } + if ($scope.selectedradius.name == 'topleft') { + $scope.item.values.topleftradiusvalue = $scope.selectedradius.value; + } + if ($scope.selectedradius.name == 'topright') { + $scope.item.values.toprightradiusvalue = $scope.selectedradius.value; + } + if ($scope.selectedradius.name == 'bottomleft') { + $scope.item.values.bottomleftradiusvalue = $scope.selectedradius.value; + } + if ($scope.selectedradius.name == 'bottomright') { + $scope.item.values.bottomrightradiusvalue = $scope.selectedradius.value; + } + }, true); + }); + /*********************************************************************************************************/ + /* shadow editor */ + /*********************************************************************************************************/ + angular.module('Umbraco.canvasdesigner').controller('Umbraco.canvasdesigner.shadow', function ($scope) { + if (!$scope.item.values) { + $scope.item.values = { shadow: '' }; + } + }); + /*********************************************************************************************************/ + /* slider editor */ + /*********************************************************************************************************/ + angular.module('Umbraco.canvasdesigner').controller('Umbraco.canvasdesigner.slider', function ($scope) { + if (!$scope.item.values) { + $scope.item.values = { slider: '' }; + } + }); + /*********************************************************************************************************/ + /* spectrum color picker directive */ + /*********************************************************************************************************/ + angular.module('colorpicker', ['spectrumcolorpicker']).directive('colorpicker', [ + 'dialogService', + function (dialogService) { + return { + restrict: 'EA', + scope: { ngModel: '=' }, + link: function (scope, $element) { + scope.openColorDialog = function () { + var config = { + template: 'colorModal.html', + change: function (data) { + scope.ngModel = data; + }, + callback: function (data) { + scope.ngModel = data; + }, + cancel: function (data) { + scope.ngModel = data; + }, + dialogItem: scope.ngModel, + scope: scope + }; + dialogService.open(config); + }; + scope.setColor = false; + scope.submitAndClose = function () { + if (scope.ngModel != '') { + scope.setColor = true; + scope.submit(scope.ngModel); + } else { + scope.cancel(); + } + }; + scope.cancelAndClose = function () { + scope.cancel(); + }; + }, + template: '
    ' + '
    ' + '' + '
    ', + replace: true + }; + } + ]); + /*********************************************************************************************************/ + /* jQuery UI Slider plugin wrapper */ + /*********************************************************************************************************/ + angular.module('Umbraco.canvasdesigner').factory('dialogService', function ($rootScope, $q, $http, $timeout, $compile, $templateCache) { + function closeDialog(dialog, destroyScope) { + if (dialog.element) { + dialog.element.removeClass('selected'); + dialog.element.html(''); + if (destroyScope) { + dialog.scope.$destroy(); + } + } + } + function open() { + } + return { + open: function (options) { + var defaults = { + template: '', + callback: undefined, + change: undefined, + cancel: undefined, + element: undefined, + dialogItem: undefined, + dialogData: undefined + }; + var dialog = angular.extend(defaults, options); + var destroyScope = true; + if (options && options.scope) { + destroyScope = false; + } + var scope = options && options.scope || $rootScope.$new(); + // Save original value for cancel action + var originalDialogItem = angular.copy(dialog.dialogItem); + dialog.element = $('.float-panel'); + /************************************/ + // Close dialog if the user clicks outside the dialog. (Not working well with colorpickers and datepickers) + $(document).mousedown(function (e) { + var container = dialog.element; + if (!container.is(e.target) && container.has(e.target).length === 0) { + closeDialog(dialog, destroyScope); + } + }); + /************************************/ + $q.when($templateCache.get(dialog.template) || $http.get(dialog.template, { cache: true }).then(function (res) { + return res.data; + })).then(function onSuccess(template) { + dialog.element.html(template); + $timeout(function () { + $compile(dialog.element)(scope); + }); + dialog.element.addClass('selected'); + scope.cancel = function () { + if (dialog.cancel) { + dialog.cancel(originalDialogItem); + } + closeDialog(dialog, destroyScope); + }; + scope.change = function (data) { + if (dialog.change) { + dialog.change(data); + } + }; + scope.submit = function (data) { + if (dialog.callback) { + dialog.callback(data); + } + closeDialog(dialog, destroyScope); + }; + scope.close = function () { + closeDialog(dialog, destroyScope); + }; + scope.dialogData = dialog.dialogData; + scope.dialogItem = dialog.dialogItem; + dialog.scope = scope; + }); + return dialog; + }, + close: function () { + var modal = $('.float-panel'); + modal.removeClass('selected'); + } + }; + }); + /*********************************************************************************************************/ + /* jQuery UI Slider plugin wrapper */ + /*********************************************************************************************************/ + angular.module('ui.slider', []).value('uiSliderConfig', {}).directive('uiSlider', [ + 'uiSliderConfig', + '$timeout', + function (uiSliderConfig, $timeout) { + uiSliderConfig = uiSliderConfig || {}; + return { + require: 'ngModel', + template: '
    ', + replace: true, + compile: function () { + return function (scope, elm, attrs, ngModel) { + scope.value = ngModel.$viewValue; + function parseNumber(n, decimals) { + return decimals ? parseFloat(n) : parseInt(n); + } + ; + var options = angular.extend(scope.$eval(attrs.uiSlider) || {}, uiSliderConfig); + // Object holding range values + var prevRangeValues = { + min: null, + max: null + }; + // convenience properties + var properties = [ + 'min', + 'max', + 'step' + ]; + var useDecimals = !angular.isUndefined(attrs.useDecimals) ? true : false; + var init = function () { + // When ngModel is assigned an array of values then range is expected to be true. + // Warn user and change range to true else an error occurs when trying to drag handle + if (angular.isArray(ngModel.$viewValue) && options.range !== true) { + console.warn('Change your range option of ui-slider. When assigning ngModel an array of values then the range option should be set to true.'); + options.range = true; + } + // Ensure the convenience properties are passed as options if they're defined + // This avoids init ordering issues where the slider's initial state (eg handle + // position) is calculated using widget defaults + // Note the properties take precedence over any duplicates in options + angular.forEach(properties, function (property) { + if (angular.isDefined(attrs[property])) { + options[property] = parseNumber(attrs[property], useDecimals); + } + }); + elm.find('.slider').slider(options); + init = angular.noop; + }; + // Find out if decimals are to be used for slider + angular.forEach(properties, function (property) { + // support {{}} and watch for updates + attrs.$observe(property, function (newVal) { + if (!!newVal) { + init(); + elm.find('.slider').slider('option', property, parseNumber(newVal, useDecimals)); + } + }); + }); + attrs.$observe('disabled', function (newVal) { + init(); + elm.find('.slider').slider('option', 'disabled', !!newVal); + }); + // Watch ui-slider (byVal) for changes and update + scope.$watch(attrs.uiSlider, function (newVal) { + init(); + if (newVal != undefined) { + elm.find('.slider').slider('option', newVal); + elm.find('.ui-slider-handle').html('' + ui.value + 'px'); + } + }, true); + // Late-bind to prevent compiler clobbering + $timeout(init, 0, true); + // Update model value from slider + elm.find('.slider').bind('slidestop', function (event, ui) { + ngModel.$setViewValue(ui.values || ui.value); + scope.$apply(); + }); + elm.bind('slide', function (event, ui) { + event.stopPropagation(); + elm.find('.slider-input').val(ui.value); + elm.find('.ui-slider-handle').html('' + ui.value + 'px'); + }); + // Update slider from model value + ngModel.$render = function () { + init(); + var method = options.range === true ? 'values' : 'value'; + if (isNaN(ngModel.$viewValue) && !(ngModel.$viewValue instanceof Array)) + ngModel.$viewValue = 0; + if (ngModel.$viewValue == '') + ngModel.$viewValue = 0; + scope.value = ngModel.$viewValue; + // Do some sanity check of range values + if (options.range === true) { + // Check outer bounds for min and max values + if (angular.isDefined(options.min) && options.min > ngModel.$viewValue[0]) { + ngModel.$viewValue[0] = options.min; + } + if (angular.isDefined(options.max) && options.max < ngModel.$viewValue[1]) { + ngModel.$viewValue[1] = options.max; + } + // Check min and max range values + if (ngModel.$viewValue[0] >= ngModel.$viewValue[1]) { + // Min value should be less to equal to max value + if (prevRangeValues.min >= ngModel.$viewValue[1]) + ngModel.$viewValue[0] = prevRangeValues.min; + // Max value should be less to equal to min value + if (prevRangeValues.max <= ngModel.$viewValue[0]) + ngModel.$viewValue[1] = prevRangeValues.max; + } + // Store values for later user + prevRangeValues.min = ngModel.$viewValue[0]; + prevRangeValues.max = ngModel.$viewValue[1]; + } + elm.find('.slider').slider(method, ngModel.$viewValue); + elm.find('.ui-slider-handle').html('' + ngModel.$viewValue + 'px'); + }; + scope.$watch('value', function () { + ngModel.$setViewValue(scope.value); + }, true); + scope.$watch(attrs.ngModel, function () { + if (options.range === true) { + ngModel.$render(); + } + }, true); + function destroy() { + elm.find('.slider').slider('destroy'); + } + elm.find('.slider').bind('$destroy', destroy); + }; + } + }; + } + ]); + /*********************************************************************************************************/ + /* spectrum color picker directive */ + /*********************************************************************************************************/ + angular.module('spectrumcolorpicker', []).directive('spectrum', function () { + return { + restrict: 'E', + transclude: true, + scope: { + colorselected: '=', + setColor: '=', + flat: '=', + showPalette: '=' + }, + link: function (scope, $element) { + var initColor; + $element.find('input').spectrum({ + color: scope.colorselected, + allowEmpty: true, + preferredFormat: 'hex', + showAlpha: true, + showInput: true, + flat: scope.flat, + localStorageKey: 'spectrum.panel', + showPalette: scope.showPalette, + palette: [], + change: function (color) { + if (color) { + scope.colorselected = color.toRgbString(); + } else { + scope.colorselected = ''; + } + scope.$apply(); + }, + move: function (color) { + scope.colorselected = color.toRgbString(); + scope.$apply(); + }, + beforeShow: function (color) { + initColor = angular.copy(scope.colorselected); + $(this).spectrum('container').find('.sp-cancel').click(function (e) { + scope.colorselected = initColor; + scope.$apply(); + }); + } + }); + scope.$watch('setcolor', function (setColor) { + if (scope.$eval(setColor) === true) { + $element.find('input').spectrum('set', scope.colorselected); + } + }, true); + }, + template: '
    ', + replace: true + }; + }); +}()); \ No newline at end of file diff --git a/WebCms/Umbraco/Js/umbraco.controllers.js b/WebCms/Umbraco/Js/umbraco.controllers.js index ce2b5f7..f7d125b 100644 --- a/WebCms/Umbraco/Js/umbraco.controllers.js +++ b/WebCms/Umbraco/Js/umbraco.controllers.js @@ -1,155 +1,155 @@ -/*! umbraco - * https://github.com/umbraco/umbraco-cms/ - * Copyright (c) 2016 Umbraco HQ; - * Licensed - */ - -(function() { - - -/** - * @ngdoc controller - * @name Umbraco.MainController - * @function - * - * @description - * The main application controller - * - */ -function MainController($scope, $rootScope, $location, $routeParams, $timeout, $http, $log, appState, treeService, notificationsService, userService, navigationService, historyService, updateChecker, assetsService, eventsService, umbRequestHelper, tmhDynamicLocale) { - - //the null is important because we do an explicit bool check on this in the view - //the avatar is by default the umbraco logo - $scope.authenticated = null; - $scope.avatar = [ - { value: "assets/img/application/logo.png" }, - { value: "assets/img/application/logo@2x.png" }, - { value: "assets/img/application/logo@3x.png" } - ]; - $scope.touchDevice = appState.getGlobalState("touchDevice"); - - - $scope.removeNotification = function (index) { - notificationsService.remove(index); - }; - - $scope.closeDialogs = function (event) { - //only close dialogs if non-link and non-buttons are clicked - var el = event.target.nodeName; - var els = ["INPUT", "A", "BUTTON"]; - - if (els.indexOf(el) >= 0) { return; } - - var parents = $(event.target).parents("a,button"); - if (parents.length > 0) { - return; - } - - //SD: I've updated this so that we don't close the dialog when clicking inside of the dialog - var nav = $(event.target).parents("#dialog"); - if (nav.length === 1) { - return; - } - - eventsService.emit("app.closeDialogs", event); - }; - - var evts = []; - - //when a user logs out or timesout - evts.push(eventsService.on("app.notAuthenticated", function () { +(function () { + function MainController($scope, $rootScope, $location, $routeParams, $timeout, $http, $log, appState, treeService, notificationsService, userService, navigationService, historyService, updateChecker, assetsService, eventsService, umbRequestHelper, tmhDynamicLocale, localStorageService, tourService) { + //the null is important because we do an explicit bool check on this in the view + //the avatar is by default the umbraco logo $scope.authenticated = null; - $scope.user = null; - })); - - //when the app is read/user is logged in, setup the data - evts.push(eventsService.on("app.ready", function (evt, data) { - - $scope.authenticated = data.authenticated; - $scope.user = data.user; - - updateChecker.check().then(function(update) { - if (update && update !== "null") { - if (update.type !== "None") { - var notification = { - headline: "Update available", - message: "Click to download", - sticky: true, - type: "info", - url: update.url - }; - notificationsService.add(notification); + $scope.avatar = [ + { value: 'assets/img/application/logo.png' }, + { value: 'assets/img/application/logo@2x.png' }, + { value: 'assets/img/application/logo@3x.png' } + ]; + $scope.touchDevice = appState.getGlobalState('touchDevice'); + $scope.removeNotification = function (index) { + notificationsService.remove(index); + }; + $scope.closeDialogs = function (event) { + //only close dialogs if non-link and non-buttons are clicked + var el = event.target.nodeName; + var els = [ + 'INPUT', + 'A', + 'BUTTON' + ]; + if (els.indexOf(el) >= 0) { + return; + } + var parents = $(event.target).parents('a,button'); + if (parents.length > 0) { + return; + } + //SD: I've updated this so that we don't close the dialog when clicking inside of the dialog + var nav = $(event.target).parents('#dialog'); + if (nav.length === 1) { + return; + } + eventsService.emit('app.closeDialogs', event); + }; + var evts = []; + //when a user logs out or timesout + evts.push(eventsService.on('app.notAuthenticated', function () { + $scope.authenticated = null; + $scope.user = null; + })); + evts.push(eventsService.on('app.userRefresh', function (evt) { + userService.refreshCurrentUser().then(function (data) { + $scope.user = data; + //Load locale file + if ($scope.user.locale) { + tmhDynamicLocale.set($scope.user.locale); + } + if ($scope.user.avatars) { + $scope.avatar = []; + if (angular.isArray($scope.user.avatars)) { + for (var i = 0; i < $scope.user.avatars.length; i++) { + $scope.avatar.push({ value: $scope.user.avatars[i] }); + } + } + } + }); + })); + //when the app is ready/user is logged in, setup the data + evts.push(eventsService.on('app.ready', function (evt, data) { + $scope.authenticated = data.authenticated; + $scope.user = data.user; + updateChecker.check().then(function (update) { + if (update && update !== 'null') { + if (update.type !== 'None') { + var notification = { + headline: 'Update available', + message: 'Click to download', + sticky: true, + type: 'info', + url: update.url + }; + notificationsService.add(notification); + } + } + }); + //if the user has changed we need to redirect to the root so they don't try to continue editing the + //last item in the URL (NOTE: the user id can equal zero, so we cannot just do !data.lastUserId since that will resolve to true) + if (data.lastUserId !== undefined && data.lastUserId !== null && data.lastUserId !== data.user.id) { + $location.path('/').search(''); + historyService.removeAll(); + treeService.clearCache(); + //if the user changed, clearout local storage too - could contain sensitive data + localStorageService.clearAll(); + } + //if this is a new login (i.e. the user entered credentials), then clear out local storage - could contain sensitive data + if (data.loginType === 'credentials') { + localStorageService.clearAll(); + } + //Load locale file + if ($scope.user.locale) { + tmhDynamicLocale.set($scope.user.locale); + } + if ($scope.user.avatars) { + $scope.avatar = []; + if (angular.isArray($scope.user.avatars)) { + for (var i = 0; i < $scope.user.avatars.length; i++) { + $scope.avatar.push({ value: $scope.user.avatars[i] }); + } } } + })); + evts.push(eventsService.on('app.ysod', function (name, error) { + $scope.ysodOverlay = { + view: 'ysod', + error: error, + show: true + }; + })); + // manage the help dialog by subscribing to the showHelp appState + $scope.drawer = {}; + evts.push(eventsService.on('appState.drawerState.changed', function (e, args) { + // set view + if (args.key === 'view') { + $scope.drawer.view = args.value; + } + // set custom model + if (args.key === 'model') { + $scope.drawer.model = args.value; + } + // show / hide drawer + if (args.key === 'showDrawer') { + $scope.drawer.show = args.value; + } + })); + evts.push(eventsService.on('appState.tour.start', function (name, args) { + $scope.tour = args; + $scope.tour.show = true; + })); + evts.push(eventsService.on('appState.tour.end', function () { + $scope.tour = null; + })); + evts.push(eventsService.on('appState.tour.complete', function () { + $scope.tour = null; + })); + evts.push(eventsService.on('appState.backdrop', function (name, args) { + $scope.backdrop = args; + })); + //ensure to unregister from all events! + $scope.$on('$destroy', function () { + for (var e in evts) { + eventsService.unsubscribe(evts[e]); + } }); - - //if the user has changed we need to redirect to the root so they don't try to continue editing the - //last item in the URL (NOTE: the user id can equal zero, so we cannot just do !data.lastUserId since that will resolve to true) - if (data.lastUserId !== undefined && data.lastUserId !== null && data.lastUserId !== data.user.id) { - $location.path("/").search(""); - historyService.removeAll(); - treeService.clearCache(); - } - - //Load locale file - if ($scope.user.locale) { - tmhDynamicLocale.set($scope.user.locale); - } - - if ($scope.user.emailHash) { - - //let's attempt to load the avatar, it might not exist or we might not have - // internet access, well get an empty string back - $http.get(umbRequestHelper.getApiUrl("gravatarApiBaseUrl", "GetCurrentUserGravatarUrl")) - .then( - function successCallback(response) { - // if we can't download the gravatar for some reason, an null gets returned, we cannot do anything - if (response.data !== "null") { - if ($scope.user && $scope.user.emailHash) { - var avatarBaseUrl = "https://www.gravatar.com/avatar/"; - var hash = $scope.user.emailHash; - - $scope.avatar = [ - { value: avatarBaseUrl + hash + ".jpg?s=30&d=mm" }, - { value: avatarBaseUrl + hash + ".jpg?s=60&d=mm" }, - { value: avatarBaseUrl + hash + ".jpg?s=90&d=mm" } - ]; - } - } - - }, function errorCallback(response) { - //cannot load it from the server so we cannot do anything - }); - } - })); - - evts.push(eventsService.on("app.ysod", function (name, error) { - $scope.ysodOverlay = { - view: "ysod", - error: error, - show: true - }; - })); - - //ensure to unregister from all events! - $scope.$on('$destroy', function () { - for (var e in evts) { - eventsService.unsubscribe(evts[e]); - } + } + //register it + angular.module('umbraco').controller('Umbraco.MainController', MainController).config(function (tmhDynamicLocaleProvider) { + //Set url for locale files + tmhDynamicLocaleProvider.localeLocationPattern('lib/angular/1.1.5/i18n/angular-locale_{{locale}}.js'); }); - -} - - -//register it -angular.module('umbraco').controller("Umbraco.MainController", MainController). - config(function (tmhDynamicLocaleProvider) { - //Set url for locale files - tmhDynamicLocaleProvider.localeLocationPattern('lib/angular/1.1.5/i18n/angular-locale_{{locale}}.js'); - }); - - -/** + /** * @ngdoc controller * @name Umbraco.NavigationController * @function @@ -159,292 +159,278 @@ angular.module('umbraco').controller("Umbraco.MainController", MainController). * * @param {navigationService} navigationService A reference to the navigationService */ -function NavigationController($scope, $rootScope, $location, $log, $routeParams, $timeout, appState, navigationService, keyboardService, dialogService, historyService, eventsService, sectionResource, angularHelper) { - - //TODO: Need to think about this and an nicer way to acheive what this is doing. - //the tree event handler i used to subscribe to the main tree click events - $scope.treeEventHandler = $({}); - navigationService.setupTreeEvents($scope.treeEventHandler); - - //Put the navigation service on this scope so we can use it's methods/properties in the view. - // IMPORTANT: all properties assigned to this scope are generally available on the scope object on dialogs since - // when we create a dialog we pass in this scope to be used for the dialog's scope instead of creating a new one. - $scope.nav = navigationService; - // TODO: Lets fix this, it is less than ideal to be passing in the navigationController scope to something else to be used as it's scope, - // this is going to lead to problems/confusion. I really don't think passing scope's around is very good practice. - $rootScope.nav = navigationService; - - //set up our scope vars - $scope.showContextMenuDialog = false; - $scope.showContextMenu = false; - $scope.showSearchResults = false; - $scope.menuDialogTitle = null; - $scope.menuActions = []; - $scope.menuNode = null; - - $scope.currentSection = appState.getSectionState("currentSection"); - $scope.showNavigation = appState.getGlobalState("showNavigation"); - - //trigger search with a hotkey: - keyboardService.bind("ctrl+shift+s", function () { - navigationService.showSearch(); - }); - - //trigger dialods with a hotkey: - keyboardService.bind("esc", function () { - eventsService.emit("app.closeDialogs"); - }); - - $scope.selectedId = navigationService.currentId; - - var evts = []; - - //Listen for global state changes - evts.push(eventsService.on("appState.globalState.changed", function(e, args) { - if (args.key === "showNavigation") { - $scope.showNavigation = args.value; - } - })); - - //Listen for menu state changes - evts.push(eventsService.on("appState.menuState.changed", function(e, args) { - if (args.key === "showMenuDialog") { - $scope.showContextMenuDialog = args.value; - } - if (args.key === "showMenu") { - $scope.showContextMenu = args.value; - } - if (args.key === "dialogTitle") { - $scope.menuDialogTitle = args.value; - } - if (args.key === "menuActions") { - $scope.menuActions = args.value; - } - if (args.key === "currentNode") { - $scope.menuNode = args.value; - } - })); - - //Listen for section state changes - evts.push(eventsService.on("appState.treeState.changed", function(e, args) { - var f = args; - if (args.value.root && args.value.root.metaData.containsTrees === false) { - $rootScope.emptySection = true; - } - else { - $rootScope.emptySection = false; - } - })); - - //Listen for section state changes - evts.push(eventsService.on("appState.sectionState.changed", function(e, args) { - //section changed - if (args.key === "currentSection") { - $scope.currentSection = args.value; - } - //show/hide search results - if (args.key === "showSearchResults") { - $scope.showSearchResults = args.value; - } - })); - - //This reacts to clicks passed to the body element which emits a global call to close all dialogs - evts.push(eventsService.on("app.closeDialogs", function(event) { - if (appState.getGlobalState("stickyNavigation")) { - navigationService.hideNavigation(); - //TODO: don't know why we need this? - we are inside of an angular event listener. - angularHelper.safeApply($scope); - } - })); - - //when a user logs out or timesout - evts.push(eventsService.on("app.notAuthenticated", function() { - $scope.authenticated = false; - })); - - //when the application is ready and the user is authorized setup the data - evts.push(eventsService.on("app.ready", function(evt, data) { - $scope.authenticated = true; - })); - - //this reacts to the options item in the tree - //todo, migrate to nav service - $scope.searchShowMenu = function (ev, args) { - //always skip default - args.skipDefault = true; - navigationService.showMenu(ev, args); - }; - - //todo, migrate to nav service - $scope.searchHide = function () { - navigationService.hideSearch(); - }; - - //the below assists with hiding/showing the tree - var treeActive = false; - - //Sets a service variable as soon as the user hovers the navigation with the mouse - //used by the leaveTree method to delay hiding - $scope.enterTree = function (event) { - treeActive = true; - }; - - // Hides navigation tree, with a short delay, is cancelled if the user moves the mouse over the tree again - $scope.leaveTree = function(event) { - //this is a hack to handle IE touch events which freaks out due to no mouse events so the tree instantly shuts down - if (!event) { - return; - } - if (!appState.getGlobalState("touchDevice")) { - treeActive = false; - $timeout(function() { - if (!treeActive) { - navigationService.hideTree(); + function NavigationController($scope, $rootScope, $location, $log, $routeParams, $timeout, appState, navigationService, keyboardService, dialogService, historyService, eventsService, sectionResource, angularHelper) { + //TODO: Need to think about this and an nicer way to acheive what this is doing. + //the tree event handler i used to subscribe to the main tree click events + $scope.treeEventHandler = $({}); + navigationService.setupTreeEvents($scope.treeEventHandler); + //Put the navigation service on this scope so we can use it's methods/properties in the view. + // IMPORTANT: all properties assigned to this scope are generally available on the scope object on dialogs since + // when we create a dialog we pass in this scope to be used for the dialog's scope instead of creating a new one. + $scope.nav = navigationService; + // TODO: Lets fix this, it is less than ideal to be passing in the navigationController scope to something else to be used as it's scope, + // this is going to lead to problems/confusion. I really don't think passing scope's around is very good practice. + $rootScope.nav = navigationService; + //set up our scope vars + $scope.showContextMenuDialog = false; + $scope.showContextMenu = false; + $scope.showSearchResults = false; + $scope.menuDialogTitle = null; + $scope.menuActions = []; + $scope.menuNode = null; + $scope.currentSection = appState.getSectionState('currentSection'); + $scope.showNavigation = appState.getGlobalState('showNavigation'); + //trigger search with a hotkey: + keyboardService.bind('ctrl+shift+s', function () { + navigationService.showSearch(); + }); + //trigger dialods with a hotkey: + keyboardService.bind('esc', function () { + eventsService.emit('app.closeDialogs'); + }); + $scope.selectedId = navigationService.currentId; + var evts = []; + //Listen for global state changes + evts.push(eventsService.on('appState.globalState.changed', function (e, args) { + if (args.key === 'showNavigation') { + $scope.showNavigation = args.value; + } + })); + //Listen for menu state changes + evts.push(eventsService.on('appState.menuState.changed', function (e, args) { + if (args.key === 'showMenuDialog') { + $scope.showContextMenuDialog = args.value; + } + if (args.key === 'showMenu') { + $scope.showContextMenu = args.value; + } + if (args.key === 'dialogTitle') { + $scope.menuDialogTitle = args.value; + } + if (args.key === 'menuActions') { + $scope.menuActions = args.value; + } + if (args.key === 'currentNode') { + $scope.menuNode = args.value; + } + })); + //Listen for section state changes + evts.push(eventsService.on('appState.treeState.changed', function (e, args) { + var f = args; + if (args.value.root && args.value.root.metaData.containsTrees === false) { + $rootScope.emptySection = true; + } else { + $rootScope.emptySection = false; + } + })); + //Listen for section state changes + evts.push(eventsService.on('appState.sectionState.changed', function (e, args) { + //section changed + if (args.key === 'currentSection') { + $scope.currentSection = args.value; + } + //show/hide search results + if (args.key === 'showSearchResults') { + $scope.showSearchResults = args.value; + } + })); + //This reacts to clicks passed to the body element which emits a global call to close all dialogs + evts.push(eventsService.on('app.closeDialogs', function (event) { + if (appState.getGlobalState('stickyNavigation')) { + navigationService.hideNavigation(); + //TODO: don't know why we need this? - we are inside of an angular event listener. + angularHelper.safeApply($scope); + } + })); + //when a user logs out or timesout + evts.push(eventsService.on('app.notAuthenticated', function () { + $scope.authenticated = false; + })); + //when the application is ready and the user is authorized setup the data + evts.push(eventsService.on('app.ready', function (evt, data) { + $scope.authenticated = true; + })); + //this reacts to the options item in the tree + //todo, migrate to nav service + $scope.searchShowMenu = function (ev, args) { + //always skip default + args.skipDefault = true; + navigationService.showMenu(ev, args); + }; + //todo, migrate to nav service + $scope.searchHide = function () { + navigationService.hideSearch(); + }; + //the below assists with hiding/showing the tree + var treeActive = false; + //Sets a service variable as soon as the user hovers the navigation with the mouse + //used by the leaveTree method to delay hiding + $scope.enterTree = function (event) { + treeActive = true; + }; + // Hides navigation tree, with a short delay, is cancelled if the user moves the mouse over the tree again + $scope.leaveTree = function (event) { + //this is a hack to handle IE touch events which freaks out due to no mouse events so the tree instantly shuts down + if (!event) { + return; + } + if (!appState.getGlobalState('touchDevice')) { + treeActive = false; + $timeout(function () { + if (!treeActive) { + navigationService.hideTree(); + } + }, 300); + } + }; + //ensure to unregister from all events! + $scope.$on('$destroy', function () { + for (var e in evts) { + eventsService.unsubscribe(evts[e]); + } + }); + } + //register it + angular.module('umbraco').controller('Umbraco.NavigationController', NavigationController); + /** + * @ngdoc controller + * @name Umbraco.SearchController + * @function + * + * @description + * Controls the search functionality in the site + * + */ + function SearchController($scope, searchService, $log, $location, navigationService, $q) { + $scope.searchTerm = null; + $scope.searchResults = []; + $scope.isSearching = false; + $scope.selectedResult = -1; + $scope.navigateResults = function (ev) { + //38: up 40: down, 13: enter + switch (ev.keyCode) { + case 38: + iterateResults(true); + break; + case 40: + iterateResults(false); + break; + case 13: + if ($scope.selectedItem) { + $location.path($scope.selectedItem.editorPath); + navigationService.hideSearch(); } - }, 300); + break; + } + }; + var group = undefined; + var groupNames = []; + var groupIndex = -1; + var itemIndex = -1; + $scope.selectedItem = undefined; + $scope.clearSearch = function () { + $scope.searchTerm = null; + }; + function iterateResults(up) { + //default group + if (!group) { + for (var g in $scope.groups) { + if ($scope.groups.hasOwnProperty(g)) { + groupNames.push(g); + } + } + //Sorting to match the groups order + groupNames.sort(); + group = $scope.groups[groupNames[0]]; + groupIndex = 0; + } + if (up) { + if (itemIndex === 0) { + if (groupIndex === 0) { + gotoGroup(Object.keys($scope.groups).length - 1, true); + } else { + gotoGroup(groupIndex - 1, true); + } + } else { + gotoItem(itemIndex - 1); + } + } else { + if (itemIndex < group.results.length - 1) { + gotoItem(itemIndex + 1); + } else { + if (groupIndex === Object.keys($scope.groups).length - 1) { + gotoGroup(0); + } else { + gotoGroup(groupIndex + 1); + } + } + } } - }; - - //ensure to unregister from all events! - $scope.$on('$destroy', function () { - for (var e in evts) { - eventsService.unsubscribe(evts[e]); + function gotoGroup(index, up) { + groupIndex = index; + group = $scope.groups[groupNames[groupIndex]]; + if (up) { + gotoItem(group.results.length - 1); + } else { + gotoItem(0); + } } - }); -} - -//register it -angular.module('umbraco').controller("Umbraco.NavigationController", NavigationController); - -/** - * @ngdoc controller - * @name Umbraco.SearchController - * @function - * - * @description - * Controls the search functionality in the site - * - */ -function SearchController($scope, searchService, $log, $location, navigationService, $q) { - - $scope.searchTerm = null; - $scope.searchResults = []; - $scope.isSearching = false; - $scope.selectedResult = -1; - - - $scope.navigateResults = function(ev){ - //38: up 40: down, 13: enter - - switch(ev.keyCode){ - case 38: - iterateResults(true); - break; - case 40: - iterateResults(false); - break; - case 13: - if ($scope.selectedItem) { - $location.path($scope.selectedItem.editorPath); - navigationService.hideSearch(); - } - break; - } - }; - - - var group = undefined; - var groupIndex = -1; - var itemIndex = -1; - $scope.selectedItem = undefined; - - - function iterateResults(up){ - //default group - if(!group){ - group = $scope.groups[0]; - groupIndex = 0; - } - - if(up){ - if(itemIndex === 0){ - if(groupIndex === 0){ - gotoGroup($scope.groups.length-1, true); - }else{ - gotoGroup(groupIndex-1, true); - } - }else{ - gotoItem(itemIndex-1); - } - }else{ - if(itemIndex < group.results.length-1){ - gotoItem(itemIndex+1); - }else{ - if(groupIndex === $scope.groups.length-1){ - gotoGroup(0); - }else{ - gotoGroup(groupIndex+1); - } - } - } - } - - function gotoGroup(index, up){ - groupIndex = index; - group = $scope.groups[groupIndex]; - - if(up){ - gotoItem(group.results.length-1); - }else{ - gotoItem(0); - } - } - - function gotoItem(index){ - itemIndex = index; - $scope.selectedItem = group.results[itemIndex]; - } - - //used to cancel any request in progress if another one needs to take it's place - var canceler = null; - - $scope.$watch("searchTerm", _.debounce(function (newVal, oldVal) { - $scope.$apply(function() { - if ($scope.searchTerm) { - if (newVal !== null && newVal !== undefined && newVal !== oldVal) { - $scope.isSearching = true; - navigationService.showSearch(); - $scope.selectedItem = undefined; - - //a canceler exists, so perform the cancelation operation and reset - if (canceler) { - canceler.resolve(); - canceler = $q.defer(); - } - else { - canceler = $q.defer(); - } - - searchService.searchAll({ term: $scope.searchTerm, canceler: canceler }).then(function(result) { - $scope.groups = _.filter(result, function (group) { return group.results.length > 0; }); - //set back to null so it can be re-created - canceler = null; - }); - } - } - else { - $scope.isSearching = false; - navigationService.hideSearch(); - $scope.selectedItem = undefined; - } - }); - }, 200)); - -} -//register it -angular.module('umbraco').controller("Umbraco.SearchController", SearchController); - -/** + function gotoItem(index) { + itemIndex = index; + $scope.selectedItem = group.results[itemIndex]; + } + //used to cancel any request in progress if another one needs to take it's place + var canceler = null; + $scope.$watch('searchTerm', _.debounce(function (newVal, oldVal) { + $scope.$apply(function () { + $scope.hasResults = false; + if ($scope.searchTerm) { + if (newVal !== null && newVal !== undefined && newVal !== oldVal) { + //Resetting for brand new search + group = undefined; + groupNames = []; + groupIndex = -1; + itemIndex = -1; + $scope.isSearching = true; + navigationService.showSearch(); + $scope.selectedItem = undefined; + //a canceler exists, so perform the cancelation operation and reset + if (canceler) { + canceler.resolve(); + canceler = $q.defer(); + } else { + canceler = $q.defer(); + } + searchService.searchAll({ + term: $scope.searchTerm, + canceler: canceler + }).then(function (result) { + //result is a dictionary of group Title and it's results + var filtered = {}; + _.each(result, function (value, key) { + if (value.results.length > 0) { + filtered[key] = value; + } + }); + $scope.groups = filtered; + // check if search has results + $scope.hasResults = Object.keys($scope.groups).length > 0; + //set back to null so it can be re-created + canceler = null; + $scope.isSearching = false; + }); + } + } else { + $scope.isSearching = false; + navigationService.hideSearch(); + $scope.selectedItem = undefined; + } + }); + }, 200)); + } + //register it + angular.module('umbraco').controller('Umbraco.SearchController', SearchController); + /** * @ngdoc controller * @name Umbraco.MainController * @function @@ -453,148 +439,116 @@ angular.module('umbraco').controller("Umbraco.SearchController", SearchControlle * The controller for the AuthorizeUpgrade login page * */ -function AuthorizeUpgradeController($scope, $window) { - - //Add this method to the scope - this method will be called by the login dialog controller when the login is successful - // then we'll handle the redirect. - $scope.submit = function (event) { - - var qry = $window.location.search.trimStart("?").split("&"); - var redir = _.find(qry, function(item) { - return item.startsWith("redir="); - }); - if (redir) { - $window.location = decodeURIComponent(redir.split("=")[1]); - } - else { - $window.location = "/"; - } - - }; - -} - -angular.module('umbraco').controller("Umbraco.AuthorizeUpgradeController", AuthorizeUpgradeController); -/** - * @ngdoc controller - * @name Umbraco.DashboardController - * @function - * - * @description - * Controls the dashboards of the application - * - */ - -function DashboardController($scope, $routeParams, dashboardResource, localizationService) { - - $scope.page = {}; - $scope.page.nameLocked = true; - $scope.page.loading = true; - - $scope.dashboard = {}; - localizationService.localize("sections_" + $routeParams.section).then(function(name){ - $scope.dashboard.name = name; - }); - - dashboardResource.getDashboard($routeParams.section).then(function(tabs){ - $scope.dashboard.tabs = tabs; - $scope.page.loading = false; - }); -} - - -//register it -angular.module('umbraco').controller("Umbraco.DashboardController", DashboardController); - -angular.module("umbraco") - .controller("Umbraco.Dialogs.ApprovedColorPickerController", function ($scope, $http, umbPropEditorHelper, assetsService) { - assetsService.loadJs("lib/cssparser/cssparser.js") - .then(function () { - - var cssPath = $scope.dialogData.cssPath; - $scope.cssClass = $scope.dialogData.cssClass; - - $scope.classes = []; - - $scope.change = function (newClass) { - $scope.model.value = newClass; - } - - $http.get(cssPath) - .success(function (data) { - var parser = new CSSParser(); - $scope.classes = parser.parse(data, false, false).cssRules; - $scope.classes.splice(0, 0, "noclass"); - }) - - assetsService.loadCss("/App_Plugins/Lecoati.uSky.Grid/lib/uSky.Grid.ApprovedColorPicker.css"); - assetsService.loadCss(cssPath); - }); -}); -function ContentEditDialogController($scope, editorState, $routeParams, $q, $timeout, $window, appState, contentResource, entityResource, navigationService, notificationsService, angularHelper, serverValidationManager, contentEditingHelper, treeService, fileManager, formHelper, umbRequestHelper, umbModelMapper, $http) { - - $scope.defaultButton = null; - $scope.subButtons = []; - var dialogOptions = $scope.$parent.dialogOptions; - - // This is a helper method to reduce the amount of code repitition for actions: Save, Publish, SendToPublish - function performSave(args) { - contentEditingHelper.contentEditorPerformSave({ - statusMessage: args.statusMessage, - saveMethod: args.saveMethod, - scope: $scope, - content: $scope.content - }).then(function (content) { - //success - if (dialogOptions.closeOnSave) { - $scope.submit(content); + function AuthorizeUpgradeController($scope, $window) { + //Add this method to the scope - this method will be called by the login dialog controller when the login is successful + // then we'll handle the redirect. + $scope.submit = function (event) { + var qry = $window.location.search.trimStart('?').split('&'); + var redir = _.find(qry, function (item) { + return item.startsWith('redir='); + }); + if (redir) { + $window.location = decodeURIComponent(redir.split('=')[1]); + } else { + $window.location = '/'; } - - }, function(err) { - //error + }; + } + angular.module('umbraco').controller('Umbraco.AuthorizeUpgradeController', AuthorizeUpgradeController); + /** + * @ngdoc controller + * @name Umbraco.DashboardController + * @function + * + * @description + * Controls the dashboards of the application + * + */ + function DashboardController($scope, $routeParams, dashboardResource, localizationService) { + $scope.page = {}; + $scope.page.nameLocked = true; + $scope.page.loading = true; + $scope.dashboard = {}; + localizationService.localize('sections_' + $routeParams.section).then(function (name) { + $scope.dashboard.name = name; + }); + dashboardResource.getDashboard($routeParams.section).then(function (tabs) { + $scope.dashboard.tabs = tabs; + $scope.page.loading = false; }); } - - function filterTabs(entity, blackList) { - if (blackList) { - _.each(entity.tabs, function (tab) { - tab.hide = _.contains(blackList, tab.alias); + //register it + angular.module('umbraco').controller('Umbraco.DashboardController', DashboardController); + angular.module('umbraco').controller('Umbraco.Dialogs.ApprovedColorPickerController', function ($scope, $http, umbPropEditorHelper, assetsService) { + assetsService.loadJs('lib/cssparser/cssparser.js', $scope).then(function () { + var cssPath = $scope.dialogData.cssPath; + $scope.cssClass = $scope.dialogData.cssClass; + $scope.classes = []; + $scope.change = function (newClass) { + $scope.model.value = newClass; + }; + $http.get(cssPath).success(function (data) { + var parser = new CSSParser(); + $scope.classes = parser.parse(data, false, false).cssRules; + $scope.classes.splice(0, 0, 'noclass'); + }); + assetsService.loadCss('/App_Plugins/Lecoati.uSky.Grid/lib/uSky.Grid.ApprovedColorPicker.css', $scope); + assetsService.loadCss(cssPath, $scope); + }); + }); + function ContentEditDialogController($scope, editorState, $routeParams, $q, $timeout, $window, appState, contentResource, entityResource, navigationService, notificationsService, angularHelper, serverValidationManager, contentEditingHelper, treeService, fileManager, formHelper, umbRequestHelper, umbModelMapper, $http) { + $scope.defaultButton = null; + $scope.subButtons = []; + var dialogOptions = $scope.$parent.dialogOptions; + // This is a helper method to reduce the amount of code repitition for actions: Save, Publish, SendToPublish + function performSave(args) { + contentEditingHelper.contentEditorPerformSave({ + statusMessage: args.statusMessage, + saveMethod: args.saveMethod, + scope: $scope, + content: $scope.content + }).then(function (content) { + //success + if (dialogOptions.closeOnSave) { + $scope.submit(content); + } + }, function (err) { }); } - - return entity; - }; - - function init(content) { - var buttons = contentEditingHelper.configureContentEditorButtons({ - create: $routeParams.create, - content: content, - methods: { - saveAndPublish: $scope.saveAndPublish, - sendToPublish: $scope.sendToPublish, - save: $scope.save, - unPublish: angular.noop + function filterTabs(entity, blackList) { + if (blackList) { + _.each(entity.tabs, function (tab) { + tab.hide = _.contains(blackList, tab.alias); + }); } - }); - $scope.defaultButton = buttons.defaultButton; - $scope.subButtons = buttons.subButtons; - - //This is a total hack but we have really no other way of sharing data to the property editors of this - // content item, so we'll just set the property on the content item directly - $scope.content.isDialogEditor = true; - - editorState.set($scope.content); - } - - //check if the entity is being passed in, otherwise load it from the server - if (angular.isObject(dialogOptions.entity)) { - $scope.loaded = true; - $scope.content = filterTabs(dialogOptions.entity, dialogOptions.tabFilter); - init($scope.content); - } - else { - contentResource.getById(dialogOptions.id) - .then(function(data) { + return entity; + } + ; + function init(content) { + var buttons = contentEditingHelper.configureContentEditorButtons({ + create: $routeParams.create, + content: content, + methods: { + saveAndPublish: $scope.saveAndPublish, + sendToPublish: $scope.sendToPublish, + save: $scope.save, + unPublish: angular.noop + } + }); + $scope.defaultButton = buttons.defaultButton; + $scope.subButtons = buttons.subButtons; + //This is a total hack but we have really no other way of sharing data to the property editors of this + // content item, so we'll just set the property on the content item directly + $scope.content.isDialogEditor = true; + editorState.set($scope.content); + } + //check if the entity is being passed in, otherwise load it from the server + if (angular.isObject(dialogOptions.entity)) { + $scope.loaded = true; + $scope.content = filterTabs(dialogOptions.entity, dialogOptions.tabFilter); + init($scope.content); + } else { + contentResource.getById(dialogOptions.id).then(function (data) { $scope.loaded = true; $scope.content = filterTabs(data, dialogOptions.tabFilter); init($scope.content); @@ -604,1517 +558,1418 @@ function ContentEditDialogController($scope, editorState, $routeParams, $q, $tim // if there are any and then clear them so the collection no longer persists them. serverValidationManager.executeAndClearAllSubscriptions(); }); - } - - $scope.sendToPublish = function () { - performSave({ saveMethod: contentResource.sendToPublish, statusMessage: "Sending..." }); - }; - - $scope.saveAndPublish = function () { - performSave({ saveMethod: contentResource.publish, statusMessage: "Publishing..." }); - }; - - $scope.save = function () { - performSave({ saveMethod: contentResource.save, statusMessage: "Saving..." }); - }; - - // this method is called for all action buttons and then we proxy based on the btn definition - $scope.performAction = function (btn) { - - if (!btn || !angular.isFunction(btn.handler)) { - throw "btn.handler must be a function reference"; } - - if (!$scope.busy) { - btn.handler.apply(this); - } - }; - -} - - -angular.module("umbraco") - .controller("Umbraco.Dialogs.Content.EditController", ContentEditDialogController); -angular.module("umbraco") - .controller("Umbraco.Dialogs.HelpController", function ($scope, $location, $routeParams, helpService, userService, localizationService) { + $scope.sendToPublish = function () { + performSave({ + saveMethod: contentResource.sendToPublish, + statusMessage: 'Sending...' + }); + }; + $scope.saveAndPublish = function () { + performSave({ + saveMethod: contentResource.publish, + statusMessage: 'Publishing...' + }); + }; + $scope.save = function () { + performSave({ + saveMethod: contentResource.save, + statusMessage: 'Saving...' + }); + }; + // this method is called for all action buttons and then we proxy based on the btn definition + $scope.performAction = function (btn) { + if (!btn || !angular.isFunction(btn.handler)) { + throw 'btn.handler must be a function reference'; + } + if (!$scope.busy) { + btn.handler.apply(this); + } + }; + } + angular.module('umbraco').controller('Umbraco.Dialogs.Content.EditController', ContentEditDialogController); + angular.module('umbraco').controller('Umbraco.Dialogs.HelpController', function ($scope, $location, $routeParams, helpService, userService, localizationService) { $scope.section = $routeParams.section; - $scope.version = Umbraco.Sys.ServerVariables.application.version + " assembly: " + Umbraco.Sys.ServerVariables.application.assemblyVersion; - - if(!$scope.section){ - $scope.section = "content"; + $scope.version = Umbraco.Sys.ServerVariables.application.version + ' assembly: ' + Umbraco.Sys.ServerVariables.application.assemblyVersion; + if (!$scope.section) { + $scope.section = 'content'; } - $scope.sectionName = $scope.section; - var rq = {}; rq.section = $scope.section; - //translate section name - localizationService.localize("sections_" + rq.section).then(function (value) { + localizationService.localize('sections_' + rq.section).then(function (value) { $scope.sectionName = value; }); - - userService.getCurrentUser().then(function(user){ - - rq.usertype = user.userType; - rq.lang = user.locale; - - if($routeParams.url){ - rq.path = decodeURIComponent($routeParams.url); - - if(rq.path.indexOf(Umbraco.Sys.ServerVariables.umbracoSettings.umbracoPath) === 0){ - rq.path = rq.path.substring(Umbraco.Sys.ServerVariables.umbracoSettings.umbracoPath.length); - } - - if(rq.path.indexOf(".aspx") > 0){ - rq.path = rq.path.substring(0, rq.path.indexOf(".aspx")); - } - - }else{ - rq.path = rq.section + "/" + $routeParams.tree + "/" + $routeParams.method; - } - - helpService.findHelp(rq).then(function(topics){ - $scope.topics = topics; - }); - - helpService.findVideos(rq).then(function(videos){ - $scope.videos = videos; - }); - + userService.getCurrentUser().then(function (user) { + rq.lang = user.locale; + if ($routeParams.url) { + rq.path = decodeURIComponent($routeParams.url); + if (rq.path.indexOf(Umbraco.Sys.ServerVariables.umbracoSettings.umbracoPath) === 0) { + rq.path = rq.path.substring(Umbraco.Sys.ServerVariables.umbracoSettings.umbracoPath.length); + } + if (rq.path.indexOf('.aspx') > 0) { + rq.path = rq.path.substring(0, rq.path.indexOf('.aspx')); + } + } else { + rq.path = rq.section + '/' + $routeParams.tree + '/' + $routeParams.method; + } + helpService.findHelp(rq).then(function (topics) { + $scope.topics = topics; + }); + helpService.findVideos(rq).then(function (videos) { + $scope.videos = videos; + }); }); - - - }); -//used for the icon picker dialog -angular.module("umbraco") - .controller("Umbraco.Dialogs.IconPickerController", - function ($scope, iconHelper) { - - iconHelper.getIcons().then(function(icons){ - $scope.icons = icons; + }); + //used for the icon picker dialog + angular.module('umbraco').controller('Umbraco.Dialogs.IconPickerController', function ($scope, iconHelper) { + iconHelper.getIcons().then(function (icons) { + $scope.icons = icons; + }); + $scope.submitClass = function (icon) { + if ($scope.color) { + $scope.submit(icon + ' ' + $scope.color); + } else { + $scope.submit(icon); + } + }; + }); + /** + * @ngdoc controller + * @name Umbraco.Dialogs.InsertMacroController + * @function + * + * @description + * The controller for the custom insert macro dialog. Until we upgrade the template editor to be angular this + * is actually loaded into an iframe with full html. + */ + function InsertMacroController($scope, entityResource, macroResource, umbPropEditorHelper, macroService, formHelper) { + /** changes the view to edit the params of the selected macro */ + /** if there is pnly one macro, and it has parameters - editor can skip selecting the Macro **/ + function editParams(insertIfNoParameters) { + //whether to insert the macro in the rich text editor when editParams is called and there are no parameters see U4-10537 + insertIfNoParameters = typeof insertIfNoParameters !== 'undefined' ? insertIfNoParameters : true; + //get the macro params if there are any + macroResource.getMacroParameters($scope.selectedMacro.id).then(function (data) { + //go to next page if there are params otherwise we can just exit + if (!angular.isArray(data) || data.length === 0) { + //we can just exist! + if (insertIfNoParameters) { + submitForm(); + } else { + $scope.wizardStep = 'macroSelect'; + } + } else { + $scope.wizardStep = 'paramSelect'; + $scope.macroParams = data; + //fill in the data if we are editing this macro + if ($scope.dialogData && $scope.dialogData.macroData && $scope.dialogData.macroData.macroParamsDictionary) { + _.each($scope.dialogData.macroData.macroParamsDictionary, function (val, key) { + var prop = _.find($scope.macroParams, function (item) { + return item.alias == key; + }); + if (prop) { + if (_.isString(val)) { + //we need to unescape values as they have most likely been escaped while inserted + val = _.unescape(val); + //detect if it is a json string + if (val.detectIsJson()) { + try { + //Parse it to json + prop.value = angular.fromJson(val); + } catch (e) { + // not json + prop.value = val; + } + } else { + prop.value = val; + } + } else { + prop.value = val; + } + } + }); + } + } }); - - $scope.submitClass = function (icon) { - if($scope.color) { - $scope.submit(icon + " " + $scope.color); - } - else { - $scope.submit(icon); - } - }; - - } - ); -/** - * @ngdoc controller - * @name Umbraco.Dialogs.InsertMacroController - * @function - * - * @description - * The controller for the custom insert macro dialog. Until we upgrade the template editor to be angular this - * is actually loaded into an iframe with full html. - */ -function InsertMacroController($scope, entityResource, macroResource, umbPropEditorHelper, macroService, formHelper) { - - /** changes the view to edit the params of the selected macro */ - function editParams() { - //get the macro params if there are any - macroResource.getMacroParameters($scope.selectedMacro.id) - .then(function (data) { - - //go to next page if there are params otherwise we can just exit - if (!angular.isArray(data) || data.length === 0) { - //we can just exist! - submitForm(); - - } else { - $scope.wizardStep = "paramSelect"; - $scope.macroParams = data; - - //fill in the data if we are editing this macro - if ($scope.dialogData && $scope.dialogData.macroData && $scope.dialogData.macroData.macroParamsDictionary) { - _.each($scope.dialogData.macroData.macroParamsDictionary, function (val, key) { - var prop = _.find($scope.macroParams, function (item) { - return item.alias == key; - }); - if (prop) { - - if (_.isString(val)) { - //we need to unescape values as they have most likely been escaped while inserted - val = _.unescape(val); - - //detect if it is a json string - if (val.detectIsJson()) { - try { - //Parse it to json - prop.value = angular.fromJson(val); - } - catch (e) { - // not json - prop.value = val; - } - } - else { - prop.value = val; - } - } - else { - prop.value = val; - } - } - }); - - } - } - }); - } - - /** submit the filled out macro params */ - function submitForm() { - - //collect the value data, close the dialog and send the data back to the caller - - //create a dictionary for the macro params - var paramDictionary = {}; - _.each($scope.macroParams, function (item) { - - var val = item.value; - - if (item.value != null && item.value != undefined && !_.isString(item.value)) { - try { - val = angular.toJson(val); - } - catch (e) { - // not json - } - } - - //each value needs to be xml escaped!! since the value get's stored as an xml attribute - paramDictionary[item.alias] = _.escape(val); - - }); - - //need to find the macro alias for the selected id - var macroAlias = $scope.selectedMacro.alias; - - //get the syntax based on the rendering engine - var syntax; - if ($scope.dialogData.renderingEngine && $scope.dialogData.renderingEngine === "WebForms") { - syntax = macroService.generateWebFormsSyntax({ macroAlias: macroAlias, macroParamsDictionary: paramDictionary }); - } - else if ($scope.dialogData.renderingEngine && $scope.dialogData.renderingEngine === "Mvc") { - syntax = macroService.generateMvcSyntax({ macroAlias: macroAlias, macroParamsDictionary: paramDictionary }); - } - else { - syntax = macroService.generateMacroSyntax({ macroAlias: macroAlias, macroParamsDictionary: paramDictionary }); - } - - $scope.submit({ syntax: syntax, macroAlias: macroAlias, macroParamsDictionary: paramDictionary }); - } - - $scope.macros = []; - $scope.selectedMacro = null; - $scope.wizardStep = "macroSelect"; - $scope.macroParams = []; - - $scope.submitForm = function () { - - if (formHelper.submitForm({ scope: $scope })) { - - formHelper.resetForm({ scope: $scope }); - - if ($scope.wizardStep === "macroSelect") { - editParams(); - } - else { - submitForm(); - } - - } - }; - - //here we check to see if we've been passed a selected macro and if so we'll set the - //editor to start with parameter editing - if ($scope.dialogData && $scope.dialogData.macroData) { - $scope.wizardStep = "paramSelect"; - } - - //get the macro list - pass in a filter if it is only for rte - entityResource.getAll("Macro", ($scope.dialogData && $scope.dialogData.richTextEditor && $scope.dialogData.richTextEditor === true) ? "UseInEditor=true" : null) - .then(function (data) { - - //if 'allowedMacros' is specified, we need to filter - if (angular.isArray($scope.dialogData.allowedMacros) && $scope.dialogData.allowedMacros.length > 0) { - $scope.macros = _.filter(data, function(d) { - return _.contains($scope.dialogData.allowedMacros, d.alias); - }); - } - else { - $scope.macros = data; - } - - - //check if there's a pre-selected macro and if it exists - if ($scope.dialogData && $scope.dialogData.macroData && $scope.dialogData.macroData.macroAlias) { - var found = _.find(data, function (item) { - return item.alias === $scope.dialogData.macroData.macroAlias; - }); - if (found) { - //select the macro and go to next screen - $scope.selectedMacro = found; - editParams(); - return; - } - } - //we don't have a pre-selected macro so ensure the correct step is set - $scope.wizardStep = "macroSelect"; - }); - - -} - -angular.module("umbraco").controller("Umbraco.Dialogs.InsertMacroController", InsertMacroController); - -/** - * @ngdoc controller - * @name Umbraco.Dialogs.LegacyDeleteController - * @function - * - * @description - * The controller for deleting content - */ -function LegacyDeleteController($scope, legacyResource, treeService, navigationService) { - - $scope.performDelete = function() { - - //mark it for deletion (used in the UI) - $scope.currentNode.loading = true; - - legacyResource.deleteItem({ - nodeId: $scope.currentNode.id, - nodeType: $scope.currentNode.nodeType, - alias: $scope.currentNode.name, - }).then(function () { - $scope.currentNode.loading = false; - //TODO: Need to sync tree, etc... - treeService.removeNode($scope.currentNode); - navigationService.hideMenu(); - }); - - }; - - $scope.cancel = function() { - navigationService.hideDialog(); - }; -} - -angular.module("umbraco").controller("Umbraco.Dialogs.LegacyDeleteController", LegacyDeleteController); - -//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; - - 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: [] - } - - 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) { - - if (!$scope.target.path) { - entityResource.getPath($scope.target.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($scope.target.id).then(function (url) { - $scope.target.url = url; - }); - } - } - - function nodeSelectHandler(ev, args) { - args.event.preventDefault(); - args.event.stopPropagation(); - - if (args.node.metaData.listViewNode) { - //check if list view 'search' node was selected - - $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 ($scope.currentNode) { - //un-select if there's a current one selected - $scope.currentNode.selected = false; - } - - $scope.currentNode = args.node; - $scope.currentNode.selected = true; - $scope.target.id = args.node.id; - $scope.target.name = args.node.name; - - if (args.node.id < 0) { - $scope.target.url = "/"; - } - else { - contentResource.getNiceUrl(args.node.id).then(function (url) { - $scope.target.url = url; - }); - } - - if (!angular.isUndefined($scope.target.isMedia)) { - delete $scope.target.isMedia; - } - } - } - - 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.startMediaId, - 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 = []; - } - - // 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; - }; - - $scope.dialogTreeEventHandler.bind("treeNodeSelect", nodeSelectHandler); - $scope.dialogTreeEventHandler.bind("treeNodeExpanded", nodeExpandedHandler); - - $scope.$on('$destroy', function () { - $scope.dialogTreeEventHandler.unbind("treeNodeSelect", nodeSelectHandler); - $scope.dialogTreeEventHandler.unbind("treeNodeExpanded", nodeExpandedHandler); - }); - }); -angular.module("umbraco").controller("Umbraco.Dialogs.LoginController", - function ($scope, $cookies, localizationService, userService, externalLoginInfo, resetPasswordCodeInfo, $timeout, authResource) { - - var setFieldFocus = function(form, field) { - $timeout(function() { - $("form[name='" + form + "'] input[name='" + field + "']").focus(); - }); - } - - function resetInputValidation() { - $scope.confirmPassword = ""; - $scope.password = ""; - $scope.login = ""; - if ($scope.loginForm) { - $scope.loginForm.username.$setValidity('auth', true); - $scope.loginForm.password.$setValidity('auth', true); - } - if ($scope.requestPasswordResetForm) { - $scope.requestPasswordResetForm.email.$setValidity("auth", true); - } - if ($scope.setPasswordForm) { - $scope.setPasswordForm.password.$setValidity('auth', true); - $scope.setPasswordForm.confirmPassword.$setValidity('auth', true); - } - } - - $scope.allowPasswordReset = Umbraco.Sys.ServerVariables.umbracoSettings.allowPasswordReset; - - $scope.showLogin = function () { - $scope.errorMsg = ""; - resetInputValidation(); - $scope.view = "login"; - setFieldFocus("loginForm", "username"); - } - - $scope.showRequestPasswordReset = function () { - $scope.errorMsg = ""; - resetInputValidation(); - $scope.view = "request-password-reset"; - $scope.showEmailResetConfirmation = false; - setFieldFocus("requestPasswordResetForm", "email"); - } - - $scope.showSetPassword = function () { - $scope.errorMsg = ""; - resetInputValidation(); - $scope.view = "set-password"; - setFieldFocus("setPasswordForm", "password"); - } - - var d = new Date(); - var konamiGreetings = new Array("Suze Sunday", "Malibu Monday", "Tequila Tuesday", "Whiskey Wednesday", "Negroni Day", "Fernet Friday", "Sancerre Saturday"); - var konamiMode = $cookies.konamiLogin; - if (konamiMode == "1") { - $scope.greeting = "Happy " + konamiGreetings[d.getDay()]; - } else { - localizationService.localize("login_greeting" + d.getDay()).then(function (label) { - $scope.greeting = label; - }); // weekday[d.getDay()]; - } - $scope.errorMsg = ""; - - $scope.externalLoginFormAction = Umbraco.Sys.ServerVariables.umbracoUrls.externalLoginsUrl; - $scope.externalLoginProviders = externalLoginInfo.providers; - $scope.externalLoginInfo = externalLoginInfo; - $scope.resetPasswordCodeInfo = resetPasswordCodeInfo; - - $scope.activateKonamiMode = function () { - if ($cookies.konamiLogin == "1") { - // somehow I can't update the cookie value using $cookies, so going native - document.cookie = "konamiLogin=; expires=Thu, 01 Jan 1970 00:00:01 GMT;"; - document.location.reload(); - } else { - document.cookie = "konamiLogin=1; expires=Tue, 01 Jan 2030 00:00:01 GMT;"; - $scope.$apply(function () { - $scope.greeting = "Happy " + konamiGreetings[d.getDay()]; - }); - } - } - - $scope.loginSubmit = function (login, password) { - - //if the login and password are not empty we need to automatically - // validate them - this is because if there are validation errors on the server - // then the user has to change both username & password to resubmit which isn't ideal, - // so if they're not empty, we'll just make sure to set them to valid. - if (login && password && login.length > 0 && password.length > 0) { - $scope.loginForm.username.$setValidity('auth', true); - $scope.loginForm.password.$setValidity('auth', true); - } - - if ($scope.loginForm.$invalid) { - return; - } - - userService.authenticate(login, password) - .then(function (data) { - $scope.submit(true); - }, function (reason) { - $scope.errorMsg = reason.errorMsg; - - //set the form inputs to invalid - $scope.loginForm.username.$setValidity("auth", false); - $scope.loginForm.password.$setValidity("auth", false); - }); - - //setup a watch for both of the model values changing, if they change - // while the form is invalid, then revalidate them so that the form can - // be submitted again. - $scope.loginForm.username.$viewChangeListeners.push(function () { - if ($scope.loginForm.username.$invalid) { - $scope.loginForm.username.$setValidity('auth', true); - } - }); - $scope.loginForm.password.$viewChangeListeners.push(function () { - if ($scope.loginForm.password.$invalid) { - $scope.loginForm.password.$setValidity('auth', true); - } - }); - }; - - $scope.requestPasswordResetSubmit = function (email) { - - if (email && email.length > 0) { - $scope.requestPasswordResetForm.email.$setValidity('auth', true); - } - - $scope.showEmailResetConfirmation = false; - - if ($scope.requestPasswordResetForm.$invalid) { - return; - } - - $scope.errorMsg = ""; - - authResource.performRequestPasswordReset(email) - .then(function () { - //remove the email entered - $scope.email = ""; - $scope.showEmailResetConfirmation = true; - }, function (reason) { - $scope.errorMsg = reason.errorMsg; - $scope.requestPasswordResetForm.email.$setValidity("auth", false); - }); - - $scope.requestPasswordResetForm.email.$viewChangeListeners.push(function () { - if ($scope.requestPasswordResetForm.email.$invalid) { - $scope.requestPasswordResetForm.email.$setValidity('auth', true); - } - }); - }; - - $scope.setPasswordSubmit = function (password, confirmPassword) { - - $scope.showSetPasswordConfirmation = false; - - if (password && confirmPassword && password.length > 0 && confirmPassword.length > 0) { - $scope.setPasswordForm.password.$setValidity('auth', true); - $scope.setPasswordForm.confirmPassword.$setValidity('auth', true); - } - - if ($scope.setPasswordForm.$invalid) { - return; - } - - authResource.performSetPassword($scope.resetPasswordCodeInfo.resetCodeModel.userId, password, confirmPassword, $scope.resetPasswordCodeInfo.resetCodeModel.resetCode) - .then(function () { - $scope.showSetPasswordConfirmation = true; - $scope.resetComplete = true; - - //reset the values in the resetPasswordCodeInfo angular so if someone logs out the change password isn't shown again - resetPasswordCodeInfo.resetCodeModel = null; - - }, function (reason) { - if (reason.data && reason.data.Message) { - $scope.errorMsg = reason.data.Message; - } - else { - $scope.errorMsg = reason.errorMsg; - } - $scope.setPasswordForm.password.$setValidity("auth", false); - $scope.setPasswordForm.confirmPassword.$setValidity("auth", false); - }); - - $scope.setPasswordForm.password.$viewChangeListeners.push(function () { - if ($scope.setPasswordForm.password.$invalid) { - $scope.setPasswordForm.password.$setValidity('auth', true); - } - }); - $scope.setPasswordForm.confirmPassword.$viewChangeListeners.push(function () { - if ($scope.setPasswordForm.confirmPassword.$invalid) { - $scope.setPasswordForm.confirmPassword.$setValidity('auth', true); - } - }); - } - - - //Now, show the correct panel: - - if ($scope.resetPasswordCodeInfo.resetCodeModel) { - $scope.showSetPassword(); - } - else if ($scope.resetPasswordCodeInfo.errors.length > 0) { - $scope.view = "password-reset-code-expired"; - } - else { - $scope.showLogin(); - } - - }); - -//used for the macro picker dialog -angular.module("umbraco").controller("Umbraco.Dialogs.MacroPickerController", function ($scope, macroFactory, umbPropEditorHelper) { - $scope.macros = macroFactory.all(true); - $scope.dialogMode = "list"; - - $scope.configureMacro = function(macro){ - $scope.dialogMode = "configure"; - $scope.dialogData.macro = macroFactory.getMacro(macro.alias); - //set the correct view for each item - for (var i = 0; i < dialogData.macro.properties.length; i++) { - dialogData.macro.properties[i].editorView = umbPropEditorHelper.getViewPath(dialogData.macro.properties[i].view); - } - }; -}); -//used for the media picker dialog -angular.module("umbraco") - .controller("Umbraco.Dialogs.MediaPickerController", - function($scope, mediaResource, umbRequestHelper, entityResource, $log, mediaHelper, mediaTypeHelper, eventsService, treeService) { - - var dialogOptions = $scope.dialogOptions; - - $scope.onlyImages = dialogOptions.onlyImages; - $scope.showDetails = dialogOptions.showDetails; - $scope.multiPicker = (dialogOptions.multiPicker && dialogOptions.multiPicker !== "0") ? true : false; - $scope.startNodeId = dialogOptions.startNodeId ? dialogOptions.startNodeId : -1; - $scope.cropSize = dialogOptions.cropSize; - - //preload selected item - $scope.target = undefined; - if (dialogOptions.currentTarget) { - $scope.target = dialogOptions.currentTarget; - } - - $scope.acceptedMediatypes = []; - mediaTypeHelper.getAllowedImagetypes($scope.startNodeId) - .then(function(types) { - $scope.acceptedMediatypes = types; - }); - - $scope.upload = function(v) { - angular.element(".umb-file-dropzone-directive .file-select").click(); - }; - - $scope.dragLeave = function(el, event) { - $scope.activeDrag = false; - }; - - $scope.dragEnter = function(el, event) { - $scope.activeDrag = true; - }; - - $scope.submitFolder = function(e) { - if (e.keyCode === 13) { - e.preventDefault(); - - mediaResource - .addFolder($scope.newFolderName, $scope.currentFolder.id) - .then(function(data) { - $scope.showFolderInput = false; - $scope.newFolderName = ""; - - //we've added a new folder so lets clear the tree cache for that specific item - treeService.clearCache({ - cacheKey: "__media", //this is the main media tree cache key - childrenOf: data.parentId //clear the children of the parent - }); - - $scope.gotoFolder(data); - }); - } - }; - - $scope.gotoFolder = function(folder) { - if (!folder) { - folder = { id: -1, name: "Media", icon: "icon-folder" }; - } - - if (folder.id > 0) { - entityResource.getAncestors(folder.id, "media") - .then(function(anc) { - // anc.splice(0,1); - $scope.path = _.filter(anc, - function(f) { - return f.path.indexOf($scope.startNodeId) !== -1; - }); - }); - - mediaTypeHelper.getAllowedImagetypes(folder.id) - .then(function(types) { - $scope.acceptedMediatypes = types; - }); - } else { - $scope.path = []; - } - - //mediaResource.rootMedia() - mediaResource.getChildren(folder.id) - .then(function(data) { - $scope.searchTerm = ""; - $scope.images = data.items ? data.items : []; - }); - - $scope.currentFolder = folder; - }; - - - $scope.clickHandler = function(image, ev, select) { - ev.preventDefault(); - - if (image.isFolder && !select) { - $scope.gotoFolder(image); - } else { - eventsService.emit("dialogs.mediaPicker.select", image); - - //we have 3 options add to collection (if multi) show details, or submit it right back to the callback - if ($scope.multiPicker) { - $scope.select(image); - image.cssclass = ($scope.dialogData.selection.indexOf(image) > -1) ? "selected" : ""; - } else if ($scope.showDetails) { - $scope.target = image; - $scope.target.url = mediaHelper.resolveFile(image); - } else { - $scope.submit(image); - } - } - }; - - $scope.exitDetails = function() { - if (!$scope.currentFolder) { - $scope.gotoFolder(); - } - - $scope.target = undefined; - }; - - $scope.onUploadComplete = function() { - $scope.gotoFolder($scope.currentFolder); - }; - - $scope.onFilesQueue = function() { - $scope.activeDrag = false; - }; - - //default root item - if (!$scope.target) { - $scope.gotoFolder({ id: $scope.startNodeId, name: "Media", icon: "icon-folder" }); - } - }); -//used for the member picker dialog -angular.module("umbraco").controller("Umbraco.Dialogs.MemberGroupPickerController", - function($scope, eventsService, entityResource, searchService, $log) { - var dialogOptions = $scope.dialogOptions; - $scope.dialogTreeEventHandler = $({}); - $scope.multiPicker = dialogOptions.multiPicker; - - /** Method used for selecting a node */ - function select(text, id) { - - if (dialogOptions.multiPicker) { - $scope.select(id); - } - else { - $scope.submit(id); - } - } - - function nodeSelectHandler(ev, args) { - args.event.preventDefault(); - args.event.stopPropagation(); - - eventsService.emit("dialogs.memberGroupPicker.select", args); - - //This is a tree node, so we don't have an entity to pass in, it will need to be looked up - //from the server in this method. - select(args.node.name, args.node.id); - - //toggle checked state - args.node.selected = args.node.selected === true ? false : true; - } - - $scope.dialogTreeEventHandler.bind("treeNodeSelect", nodeSelectHandler); - - $scope.$on('$destroy', function () { - $scope.dialogTreeEventHandler.unbind("treeNodeSelect", nodeSelectHandler); - }); - }); -angular.module("umbraco").controller("Umbraco.Dialogs.RteEmbedController", function ($scope, $http, umbRequestHelper) { - $scope.form = {}; - $scope.form.url = ""; - $scope.form.width = 360; - $scope.form.height = 240; - $scope.form.constrain = true; - $scope.form.preview = ""; - $scope.form.success = false; - $scope.form.info = ""; - $scope.form.supportsDimensions = false; - - var origWidth = 500; - var origHeight = 300; - - $scope.showPreview = function() { - - if ($scope.form.url) { - $scope.form.show = true; - $scope.form.preview = "
    "; - $scope.form.info = ""; - $scope.form.success = false; - - $http({ method: 'GET', url: umbRequestHelper.getApiUrl("embedApiBaseUrl", "GetEmbed"), params: { url: $scope.form.url, width: $scope.form.width, height: $scope.form.height } }) - .success(function (data) { - - $scope.form.preview = ""; - - switch (data.Status) { - case 0: - //not supported - $scope.form.info = "Not supported"; - break; - case 1: - //error - $scope.form.info = "Could not embed media - please ensure the URL is valid"; - break; - case 2: - $scope.form.preview = data.Markup; - $scope.form.supportsDimensions = data.SupportsDimensions; - $scope.form.success = true; - break; - } - }) - .error(function () { - $scope.form.supportsDimensions = false; - $scope.form.preview = ""; - $scope.form.info = "Could not embed media - please ensure the URL is valid"; - }); - } else { - $scope.form.supportsDimensions = false; - $scope.form.preview = ""; - $scope.form.info = "Please enter a URL"; - } - }; - - $scope.changeSize = function (type) { - var width, height; - - if ($scope.form.constrain) { - width = parseInt($scope.form.width, 10); - height = parseInt($scope.form.height, 10); - if (type == 'width') { - origHeight = Math.round((width / origWidth) * height); - $scope.form.height = origHeight; - } else { - origWidth = Math.round((height / origHeight) * width); - $scope.form.width = origWidth; - } - } - if ($scope.form.url != "") { - $scope.showPreview(); - } - - }; - - $scope.insert = function(){ - $scope.submit($scope.form.preview); - }; -}); -angular.module("umbraco").controller('Umbraco.Dialogs.Template.QueryBuilderController', - function($scope, $http, dialogService){ - - - $http.get("backoffice/UmbracoApi/TemplateQuery/GetAllowedProperties").then(function(response) { - $scope.properties = response.data; + } + /** submit the filled out macro params */ + function submitForm() { + //collect the value data, close the dialog and send the data back to the caller + //create a dictionary for the macro params + var paramDictionary = {}; + _.each($scope.macroParams, function (item) { + var val = item.value; + if (item.value != null && item.value != undefined && !_.isString(item.value)) { + try { + val = angular.toJson(val); + } catch (e) { + } + } + //each value needs to be xml escaped!! since the value get's stored as an xml attribute + paramDictionary[item.alias] = _.escape(val); }); - - $http.get("backoffice/UmbracoApi/TemplateQuery/GetContentTypes").then(function (response) { - $scope.contentTypes = response.data; + //need to find the macro alias for the selected id + var macroAlias = $scope.selectedMacro.alias; + //get the syntax based on the rendering engine + var syntax; + if ($scope.dialogData.renderingEngine && $scope.dialogData.renderingEngine === 'WebForms') { + syntax = macroService.generateWebFormsSyntax({ + macroAlias: macroAlias, + macroParamsDictionary: paramDictionary + }); + } else if ($scope.dialogData.renderingEngine && $scope.dialogData.renderingEngine === 'Mvc') { + syntax = macroService.generateMvcSyntax({ + macroAlias: macroAlias, + macroParamsDictionary: paramDictionary + }); + } else { + syntax = macroService.generateMacroSyntax({ + macroAlias: macroAlias, + macroParamsDictionary: paramDictionary + }); + } + $scope.submit({ + syntax: syntax, + macroAlias: macroAlias, + macroParamsDictionary: paramDictionary }); - - $http.get("backoffice/UmbracoApi/TemplateQuery/GetFilterConditions").then(function (response) { - $scope.conditions = response.data; + } + $scope.macros = []; + $scope.selectedMacro = null; + $scope.wizardStep = 'macroSelect'; + $scope.macroParams = []; + $scope.submitForm = function () { + if (formHelper.submitForm({ scope: $scope })) { + formHelper.resetForm({ scope: $scope }); + if ($scope.wizardStep === 'macroSelect') { + editParams(true); + } else { + submitForm(); + } + } + }; + //here we check to see if we've been passed a selected macro and if so we'll set the + //editor to start with parameter editing + if ($scope.dialogData && $scope.dialogData.macroData) { + $scope.wizardStep = 'paramSelect'; + } + //get the macro list - pass in a filter if it is only for rte + entityResource.getAll('Macro', $scope.dialogData && $scope.dialogData.richTextEditor && $scope.dialogData.richTextEditor === true ? 'UseInEditor=true' : null).then(function (data) { + //if 'allowedMacros' is specified, we need to filter + if (angular.isArray($scope.dialogData.allowedMacros) && $scope.dialogData.allowedMacros.length > 0) { + $scope.macros = _.filter(data, function (d) { + return _.contains($scope.dialogData.allowedMacros, d.alias); + }); + } else { + $scope.macros = data; + } + //check if there's a pre-selected macro and if it exists + if ($scope.dialogData && $scope.dialogData.macroData && $scope.dialogData.macroData.macroAlias) { + var found = _.find(data, function (item) { + return item.alias === $scope.dialogData.macroData.macroAlias; + }); + if (found) { + //select the macro and go to next screen + $scope.selectedMacro = found; + editParams(true); + return; + } + } + //if there is only one macro in the site and it has parameters, let's not make the editor choose it from a selection of one macro (unless there are no parameters - then weirdly it's a better experience to make that selection) + if ($scope.macros.length == 1) { + $scope.selectedMacro = $scope.macros[0]; + editParams(false); + } else { + //we don't have a pre-selected macro so ensure the correct step is set + $scope.wizardStep = 'macroSelect'; + } + }); + } + angular.module('umbraco').controller('Umbraco.Dialogs.InsertMacroController', InsertMacroController); + /** + * @ngdoc controller + * @name Umbraco.Dialogs.LegacyDeleteController + * @function + * + * @description + * The controller for deleting content + */ + function LegacyDeleteController($scope, legacyResource, treeService, navigationService) { + $scope.performDelete = function () { + //mark it for deletion (used in the UI) + $scope.currentNode.loading = true; + legacyResource.deleteItem({ + nodeId: $scope.currentNode.id, + nodeType: $scope.currentNode.nodeType, + alias: $scope.currentNode.name + }).then(function () { + $scope.currentNode.loading = false; + //TODO: Need to sync tree, etc... + treeService.removeNode($scope.currentNode); + navigationService.hideMenu(); }); - - - $scope.query = { - contentType: { - name: "Everything" - }, - source:{ - name: "My website" - }, - filters:[ - { - property:undefined, - operator: undefined - } - ], - sort:{ - property:{ - alias: "", - name: "", - }, - direction: "ascending" - } - }; - - - - $scope.chooseSource = function(query){ - dialogService.contentPicker({ - callback: function (data) { - - if (data.id > 0) { - query.source = { id: data.id, name: data.name }; - } else { - query.source.name = "My website"; - delete query.source.id; - } - } - }); - }; - - var throttledFunc = _.throttle(function() { - - $http.post("backoffice/UmbracoApi/TemplateQuery/PostTemplateQuery", $scope.query).then(function (response) { - $scope.result = response.data; - }); - - }, 200); - - $scope.$watch("query", function(value) { - throttledFunc(); - }, true); - - $scope.getPropertyOperators = function (property) { - - var conditions = _.filter($scope.conditions, function(condition) { - var index = condition.appliesTo.indexOf(property.type); - return index >= 0; - }); - return conditions; - }; - - - $scope.addFilter = function(query){ - query.filters.push({}); - }; - - $scope.trashFilter = function (query) { - query.filters.splice(query,1); - }; - - $scope.changeSortOrder = function(query){ - if(query.sort.direction === "ascending"){ - query.sort.direction = "descending"; - }else{ - query.sort.direction = "ascending"; - } - }; - - $scope.setSortProperty = function(query, property){ - query.sort.property = property; - if(property.type === "datetime"){ - query.sort.direction = "descending"; - }else{ - query.sort.direction = "ascending"; - } - }; - }); -angular.module("umbraco").controller('Umbraco.Dialogs.Template.SnippetController', - function($scope) { - $scope.type = $scope.dialogOptions.type; - $scope.section = { - name: "", - required: false - }; - }); -//used for the media picker dialog -angular.module("umbraco").controller("Umbraco.Dialogs.TreePickerController", - function ($scope, entityResource, eventsService, $log, searchService, angularHelper, $timeout, localizationService, treeService) { - - var tree = null; - var dialogOptions = $scope.dialogOptions; - $scope.dialogTreeEventHandler = $({}); - $scope.section = dialogOptions.section; - $scope.treeAlias = dialogOptions.treeAlias; - $scope.multiPicker = dialogOptions.multiPicker; - $scope.hideHeader = true; - $scope.searchInfo = { - searchFromId: dialogOptions.startNodeId, - searchFromName: null, - showSearch: false, - results: [], - selectedSearchResults: [] - } - - //create the custom query string param for this tree - $scope.customTreeParams = dialogOptions.startNodeId ? "startNodeId=" + dialogOptions.startNodeId : ""; - $scope.customTreeParams += dialogOptions.customTreeParams ? "&" + dialogOptions.customTreeParams : ""; - - var searchText = "Search..."; - localizationService.localize("general_search").then(function (value) { - searchText = value + "..."; - }); - - // Allow the entity type to be passed in but defaults to Document for backwards compatibility. - var entityType = dialogOptions.entityType ? dialogOptions.entityType : "Document"; - - - //min / max values - if (dialogOptions.minNumber) { - dialogOptions.minNumber = parseInt(dialogOptions.minNumber, 10); - } - if (dialogOptions.maxNumber) { - dialogOptions.maxNumber = parseInt(dialogOptions.maxNumber, 10); - } - - if (dialogOptions.section === "member") { - entityType = "Member"; - } - else if (dialogOptions.section === "media") { - entityType = "Media"; - } - - //Configures filtering - if (dialogOptions.filter) { - - dialogOptions.filterExclude = false; - dialogOptions.filterAdvanced = false; - - //used advanced filtering - if (angular.isFunction(dialogOptions.filter)) { - dialogOptions.filterAdvanced = true; - } - else if (angular.isObject(dialogOptions.filter)) { - dialogOptions.filterAdvanced = true; - } - else { - if (dialogOptions.filter.startsWith("!")) { - dialogOptions.filterExclude = true; - dialogOptions.filter = dialogOptions.filter.substring(1); - } - - //used advanced filtering - if (dialogOptions.filter.startsWith("{")) { - dialogOptions.filterAdvanced = true; - //convert to object - dialogOptions.filter = angular.fromJson(dialogOptions.filter); - } - } - } - - 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 some custom - // children: A node to activate the search, any nodes that have already been - // selected in the search - if (child.metaData.isContainer) { - child.hasChildren = true; - child.children = [ - { - level: child.level + 1, - hasChildren: false, - parent: function () { - return child; - }, - name: searchText, - metaData: { - listViewNode: child, - }, - cssClass: "icon-search", - cssClasses: ["not-published"] - } - ]; - //add base transition classes to this node - child.cssClasses.push("tree-node-slide-up"); - - var listViewResults = _.filter($scope.searchInfo.selectedSearchResults, function(i) { - return i.parentId == child.id; - }); - _.each(listViewResults, function(item) { - child.children.unshift({ - id: item.id, - name: item.name, - cssClass: "icon umb-tree-icon sprTree " + item.icon, - level: child.level + 1, - metaData: { - isSearchResult: true - }, - hasChildren: false, - parent: function () { - return child; - } - }); - }); - } - - //now we need to look in the already selected search results and - // toggle the check boxes for those ones that are listed - var exists = _.find($scope.searchInfo.selectedSearchResults, function (selected) { - return child.id == selected.id; - }); - if (exists) { - child.selected = true; - } - }); - - //check filter - performFiltering(args.children); - } - } - - //gets the tree object when it loads - function treeLoadedHandler(ev, args) { - tree = args.tree; - } - - //wires up selection - function nodeSelectHandler(ev, args) { - args.event.preventDefault(); - args.event.stopPropagation(); - - if (args.node.metaData.listViewNode) { - //check if list view 'search' node was selected - - $scope.searchInfo.showSearch = true; - $scope.searchInfo.searchFromId = args.node.metaData.listViewNode.id; - $scope.searchInfo.searchFromName = args.node.metaData.listViewNode.name; - - //add transition classes - var listViewNode = args.node.parent(); - listViewNode.cssClasses.push('tree-node-slide-up-hide-active'); - } - else if (args.node.metaData.isSearchResult) { - //check if the item selected was a search result from a list view - - //unselect - select(args.node.name, args.node.id); - - //remove it from the list view children - var listView = args.node.parent(); - listView.children = _.reject(listView.children, function(child) { - return child.id == args.node.id; - }); - - //remove it from the custom tracked search result list - $scope.searchInfo.selectedSearchResults = _.reject($scope.searchInfo.selectedSearchResults, function (i) { - return i.id == args.node.id; - }); - } - else { - eventsService.emit("dialogs.treePickerController.select", args); - - if (args.node.filtered) { - return; - } - - //This is a tree node, so we don't have an entity to pass in, it will need to be looked up - //from the server in this method. - select(args.node.name, args.node.id); - - //toggle checked state - args.node.selected = args.node.selected === true ? false : true; - } - } - - /** Method used for selecting a node */ - function select(text, id, entity) { - //if we get the root, we just return a constructed entity, no need for server data - if (id < 0) { - if ($scope.multiPicker) { - $scope.select(id); - } - else { - var node = { - alias: null, - icon: "icon-folder", - id: id, - name: text - }; - $scope.submit(node); - } - } - else { - - if ($scope.multiPicker) { - $scope.select(Number(id)); - } - else { - - $scope.hideSearch(); - - //if an entity has been passed in, use it - if (entity) { - $scope.submit(entity); - } else { - //otherwise we have to get it from the server - entityResource.getById(id, entityType).then(function (ent) { - $scope.submit(ent); - }); - } - } - } - } - - function performFiltering(nodes) { - - if (!dialogOptions.filter) { - return; - } - - //remove any list view search nodes from being filtered since these are special nodes that always must - // be allowed to be clicked on - nodes = _.filter(nodes, function(n) { - return !angular.isObject(n.metaData.listViewNode); - }); - - if (dialogOptions.filterAdvanced) { - - //filter either based on a method or an object - var filtered = angular.isFunction(dialogOptions.filter) - ? _.filter(nodes, dialogOptions.filter) - : _.where(nodes, dialogOptions.filter); - - angular.forEach(filtered, function (value, key) { - value.filtered = true; - if (dialogOptions.filterCssClass) { - if (!value.cssClasses) { - value.cssClasses = []; - } - value.cssClasses.push(dialogOptions.filterCssClass); - } - }); - } else { - var a = dialogOptions.filter.toLowerCase().replace(/\s/g, '').split(','); - angular.forEach(nodes, function (value, key) { - - var found = a.indexOf(value.metaData.contentType.toLowerCase()) >= 0; - - if (!dialogOptions.filterExclude && !found || dialogOptions.filterExclude && found) { - value.filtered = true; - - if (dialogOptions.filterCssClass) { - if (!value.cssClasses) { - value.cssClasses = []; - } - value.cssClasses.push(dialogOptions.filterCssClass); - } - } - }); - } - } - - $scope.multiSubmit = function (result) { - entityResource.getByIds(result, entityType).then(function (ents) { - $scope.submit(ents); - }); - }; - - /** method to select a search result */ - $scope.selectResult = function (evt, result) { - - if (result.filtered) { - return; - } - - result.selected = result.selected === true ? false : true; - - //since result = an entity, we'll pass it in so we don't have to go back to the server - select(result.name, result.id, result); - - //add/remove to our custom tracked list of selected search results - if (result.selected) { - $scope.searchInfo.selectedSearchResults.push(result); - } - else { - $scope.searchInfo.selectedSearchResults = _.reject($scope.searchInfo.selectedSearchResults, function(i) { - return i.id == result.id; - }); - } - - //ensure the tree node in the tree is checked/unchecked if it already exists there - if (tree) { - var found = treeService.getDescendantNode(tree.root, result.id); - if (found) { - found.selected = result.selected; - } - } - - }; - - $scope.hideSearch = function () { - - //Traverse the entire displayed tree and update each node to sync with the selected search results - if (tree) { - - //we need to ensure that any currently displayed nodes that get selected - // from the search get updated to have a check box! - function checkChildren(children) { - _.each(children, function (child) { - //check if the id is in the selection, if so ensure it's flagged as selected - var exists = _.find($scope.searchInfo.selectedSearchResults, function (selected) { - return child.id == selected.id; - }); - //if the curr node exists in selected search results, ensure it's checked - if (exists) { - child.selected = true; - } - //if the curr node does not exist in the selected search result, and the curr node is a child of a list view search result - else if (child.metaData.isSearchResult) { - //if this tree node is under a list view it means that the node was added - // to the tree dynamically under the list view that was searched, so we actually want to remove - // it all together from the tree - var listView = child.parent(); - listView.children = _.reject(listView.children, function(c) { - return c.id == child.id; - }); - } - - //check if the current node is a list view and if so, check if there's any new results - // that need to be added as child nodes to it based on search results selected - if (child.metaData.isContainer) { - - child.cssClasses = _.reject(child.cssClasses, function(c) { - return c === 'tree-node-slide-up-hide-active'; - }); - - var listViewResults = _.filter($scope.searchInfo.selectedSearchResults, function (i) { - return i.parentId == child.id; - }); - _.each(listViewResults, function (item) { - var childExists = _.find(child.children, function(c) { - return c.id == item.id; - }); - if (!childExists) { - var parent = child; - child.children.unshift({ - id: item.id, - name: item.name, - cssClass: "icon umb-tree-icon sprTree " + item.icon, - level: child.level + 1, - metaData: { - isSearchResult: true - }, - hasChildren: false, - parent: function () { - return parent; - } - }); - } - }); - } - - //recurse - if (child.children && child.children.length > 0) { - checkChildren(child.children); - } - }); - } - checkChildren(tree.root.children); - } - - - $scope.searchInfo.showSearch = false; - $scope.searchInfo.searchFromId = dialogOptions.startNodeId; - $scope.searchInfo.searchFromName = null; - $scope.searchInfo.results = []; - } - - $scope.onSearchResults = function(results) { - - //filter all items - this will mark an item as filtered - performFiltering(results); - - //now actually remove all filtered items so they are not even displayed - results = _.filter(results, function(item) { - return !item.filtered; - }); - - $scope.searchInfo.results = results; - - //sync with the curr selected results - _.each($scope.searchInfo.results, function (result) { - var exists = _.find($scope.dialogData.selection, function (selectedId) { - return result.id == selectedId; - }); - if (exists) { - result.selected = true; - } - }); - - $scope.searchInfo.showSearch = true; - }; - - $scope.dialogTreeEventHandler.bind("treeLoaded", treeLoadedHandler); - $scope.dialogTreeEventHandler.bind("treeNodeExpanded", nodeExpandedHandler); - $scope.dialogTreeEventHandler.bind("treeNodeSelect", nodeSelectHandler); - - $scope.$on('$destroy', function () { - $scope.dialogTreeEventHandler.unbind("treeLoaded", treeLoadedHandler); - $scope.dialogTreeEventHandler.unbind("treeNodeExpanded", nodeExpandedHandler); - $scope.dialogTreeEventHandler.unbind("treeNodeSelect", nodeSelectHandler); - }); - }); -angular.module("umbraco") - .controller("Umbraco.Dialogs.UserController", function ($scope, $location, $timeout, userService, historyService, eventsService, externalLoginInfo, authResource, currentUserResource, formHelper) { - + }; + $scope.cancel = function () { + navigationService.hideDialog(); + }; + } + angular.module('umbraco').controller('Umbraco.Dialogs.LegacyDeleteController', LegacyDeleteController); + //used for the media picker dialog + angular.module('umbraco').controller('Umbraco.Dialogs.LinkPickerController', 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 + '...'; + }); + $scope.dialogTreeEventHandler = $({}); + $scope.target = {}; + $scope.searchInfo = { + searchFromId: null, + searchFromName: null, + showSearch: false, + results: [], + selectedSearchResults: [] + }; + 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) { + 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 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.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 + // only do the substring if there's a # or a ? + var indexOfAnchor = $scope.target.url.search(/(#|\?)/); + if (indexOfAnchor > -1) { + // populate the anchor + $scope.target.anchor = $scope.target.url.substring(indexOfAnchor); + // then rewrite the model and populate the link + $scope.target.url = $scope.target.url.substring(0, indexOfAnchor); + } + } + } + if (dialogOptions.anchors) { + $scope.anchorValues = dialogOptions.anchors; + } + function nodeSelectHandler(ev, args) { + args.event.preventDefault(); + args.event.stopPropagation(); + if (args.node.metaData.listViewNode) { + //check if list view 'search' node was selected + $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 ($scope.currentNode) { + //un-select if there's a current one selected + $scope.currentNode.selected = false; + } + $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 (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.target.url = resp.urls[0]; + }); + } + if (!angular.isUndefined($scope.target.isMedia)) { + delete $scope.target.isMedia; + } + } + } + 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.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 + }); + }; + //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.$on('$destroy', function () { + $scope.dialogTreeEventHandler.unbind('treeNodeSelect', nodeSelectHandler); + $scope.dialogTreeEventHandler.unbind('treeNodeExpanded', nodeExpandedHandler); + }); + }); + angular.module('umbraco').controller('Umbraco.Dialogs.LoginController', function ($scope, $cookies, $location, currentUserResource, formHelper, mediaHelper, umbRequestHelper, Upload, localizationService, userService, externalLoginInfo, resetPasswordCodeInfo, $timeout, authResource, dialogService, $q) { + $scope.invitedUser = null; + $scope.invitedUserPasswordModel = { + password: '', + confirmPassword: '', + buttonState: '', + passwordPolicies: null, + passwordPolicyText: '' + }; + $scope.loginStates = { submitButton: 'init' }; + $scope.avatarFile = { + filesHolder: null, + uploadStatus: null, + uploadProgress: 0, + maxFileSize: Umbraco.Sys.ServerVariables.umbracoSettings.maxFileSize + 'KB', + acceptedFileTypes: mediaHelper.formatFileTypes(Umbraco.Sys.ServerVariables.umbracoSettings.imageFileTypes), + uploaded: false + }; + $scope.togglePassword = function () { + var elem = $('form[name=\'loginForm\'] input[name=\'password\']'); + elem.attr('type', elem.attr('type') === 'text' ? 'password' : 'text'); + $('.password-text.show, .password-text.hide').toggle(); + }; + function init() { + // Check if it is a new user + var inviteVal = $location.search().invite; + if (inviteVal && (inviteVal === '1' || inviteVal === '2')) { + $q.all([ + //get the current invite user + authResource.getCurrentInvitedUser().then(function (data) { + $scope.invitedUser = data; + }, function () { + //it failed so we should remove the search + $location.search('invite', null); + }), + //get the membership provider config for password policies + authResource.getMembershipProviderConfig().then(function (data) { + $scope.invitedUserPasswordModel.passwordPolicies = data; + //localize the text + localizationService.localize('errorHandling_errorInPasswordFormat', [ + $scope.invitedUserPasswordModel.passwordPolicies.minPasswordLength, + $scope.invitedUserPasswordModel.passwordPolicies.minNonAlphaNumericChars + ]).then(function (data) { + $scope.invitedUserPasswordModel.passwordPolicyText = data; + }); + }) + ]).then(function () { + $scope.inviteStep = Number(inviteVal); + }); + } + } + $scope.changeAvatar = function (files, event) { + if (files && files.length > 0) { + upload(files[0]); + } + }; + $scope.getStarted = function () { + $location.search('invite', null); + $scope.submit(true); + }; + function upload(file) { + $scope.avatarFile.uploadProgress = 0; + Upload.upload({ + url: umbRequestHelper.getApiUrl('currentUserApiBaseUrl', 'PostSetAvatar'), + fields: {}, + file: file + }).progress(function (evt) { + if ($scope.avatarFile.uploadStatus !== 'done' && $scope.avatarFile.uploadStatus !== 'error') { + // set uploading status on file + $scope.avatarFile.uploadStatus = 'uploading'; + // calculate progress in percentage + var progressPercentage = parseInt(100 * evt.loaded / evt.total, 10); + // set percentage property on file + $scope.avatarFile.uploadProgress = progressPercentage; + } + }).success(function (data, status, headers, config) { + $scope.avatarFile.uploadProgress = 100; + // set done status on file + $scope.avatarFile.uploadStatus = 'done'; + $scope.invitedUser.avatars = data; + $scope.avatarFile.uploaded = true; + }).error(function (evt, status, headers, config) { + // set status done + $scope.avatarFile.uploadStatus = 'error'; + // If file not found, server will return a 404 and display this message + if (status === 404) { + $scope.avatarFile.serverErrorMessage = 'File not found'; + } else if (status == 400) { + //it's a validation error + $scope.avatarFile.serverErrorMessage = evt.message; + } else { + //it's an unhandled error + //if the service returns a detailed error + if (evt.InnerException) { + $scope.avatarFile.serverErrorMessage = evt.InnerException.ExceptionMessage; + //Check if its the common "too large file" exception + if (evt.InnerException.StackTrace && evt.InnerException.StackTrace.indexOf('ValidateRequestEntityLength') > 0) { + $scope.avatarFile.serverErrorMessage = 'File too large to upload'; + } + } else if (evt.Message) { + $scope.avatarFile.serverErrorMessage = evt.Message; + } + } + }); + } + $scope.inviteSavePassword = function () { + if (formHelper.submitForm({ + scope: $scope, + statusMessage: 'Saving...' + })) { + $scope.invitedUserPasswordModel.buttonState = 'busy'; + currentUserResource.performSetInvitedUserPassword($scope.invitedUserPasswordModel.password).then(function (data) { + //success + formHelper.resetForm({ + scope: $scope, + notifications: data.notifications + }); + $scope.invitedUserPasswordModel.buttonState = 'success'; + //set the user and set them as logged in + $scope.invitedUser = data; + userService.setAuthenticationSuccessful(data); + $scope.inviteStep = 2; + }, function (err) { + //error + formHelper.handleError(err); + $scope.invitedUserPasswordModel.buttonState = 'error'; + }); + } + }; + var setFieldFocus = function (form, field) { + $timeout(function () { + $('form[name=\'' + form + '\'] input[name=\'' + field + '\']').focus(); + }); + }; + var twoFactorloginDialog = null; + function show2FALoginDialog(view, callback) { + if (!twoFactorloginDialog) { + twoFactorloginDialog = dialogService.open({ + //very special flag which means that global events cannot close this dialog + manualClose: true, + template: view, + modalClass: 'login-overlay', + animation: 'slide', + show: true, + callback: callback + }); + } + } + function resetInputValidation() { + $scope.confirmPassword = ''; + $scope.password = ''; + $scope.login = ''; + if ($scope.loginForm) { + $scope.loginForm.username.$setValidity('auth', true); + $scope.loginForm.password.$setValidity('auth', true); + } + if ($scope.requestPasswordResetForm) { + $scope.requestPasswordResetForm.email.$setValidity('auth', true); + } + if ($scope.setPasswordForm) { + $scope.setPasswordForm.password.$setValidity('auth', true); + $scope.setPasswordForm.confirmPassword.$setValidity('auth', true); + } + } + $scope.allowPasswordReset = Umbraco.Sys.ServerVariables.umbracoSettings.allowPasswordReset; + $scope.showLogin = function () { + $scope.errorMsg = ''; + resetInputValidation(); + $scope.view = 'login'; + setFieldFocus('loginForm', 'username'); + }; + $scope.showRequestPasswordReset = function () { + $scope.errorMsg = ''; + resetInputValidation(); + $scope.view = 'request-password-reset'; + $scope.showEmailResetConfirmation = false; + setFieldFocus('requestPasswordResetForm', 'email'); + }; + $scope.showSetPassword = function () { + $scope.errorMsg = ''; + resetInputValidation(); + $scope.view = 'set-password'; + setFieldFocus('setPasswordForm', 'password'); + }; + var d = new Date(); + var konamiGreetings = new Array('Suze Sunday', 'Malibu Monday', 'Tequila Tuesday', 'Whiskey Wednesday', 'Negroni Day', 'Fernet Friday', 'Sancerre Saturday'); + var konamiMode = $cookies.konamiLogin; + if (konamiMode == '1') { + $scope.greeting = 'Happy ' + konamiGreetings[d.getDay()]; + } else { + localizationService.localize('login_greeting' + d.getDay()).then(function (label) { + $scope.greeting = label; + }); // weekday[d.getDay()]; + } + $scope.errorMsg = ''; + $scope.externalLoginFormAction = Umbraco.Sys.ServerVariables.umbracoUrls.externalLoginsUrl; + $scope.externalLoginProviders = externalLoginInfo.providers; + $scope.externalLoginInfo = externalLoginInfo; + $scope.resetPasswordCodeInfo = resetPasswordCodeInfo; + $scope.backgroundImage = Umbraco.Sys.ServerVariables.umbracoSettings.loginBackgroundImage; + $scope.activateKonamiMode = function () { + if ($cookies.konamiLogin == '1') { + // somehow I can't update the cookie value using $cookies, so going native + document.cookie = 'konamiLogin=; expires=Thu, 01 Jan 1970 00:00:01 GMT;'; + document.location.reload(); + } else { + document.cookie = 'konamiLogin=1; expires=Tue, 01 Jan 2030 00:00:01 GMT;'; + $scope.$apply(function () { + $scope.greeting = 'Happy ' + konamiGreetings[d.getDay()]; + }); + } + }; + $scope.loginSubmit = function (login, password) { + //TODO: Do validation properly like in the invite password update + //if the login and password are not empty we need to automatically + // validate them - this is because if there are validation errors on the server + // then the user has to change both username & password to resubmit which isn't ideal, + // so if they're not empty, we'll just make sure to set them to valid. + if (login && password && login.length > 0 && password.length > 0) { + $scope.loginForm.username.$setValidity('auth', true); + $scope.loginForm.password.$setValidity('auth', true); + } + if ($scope.loginForm.$invalid) { + return; + } + $scope.loginStates.submitButton = 'busy'; + userService.authenticate(login, password).then(function (data) { + $scope.loginStates.submitButton = 'success'; + $scope.submit(true); + }, function (reason) { + //is Two Factor required? + if (reason.status === 402) { + $scope.errorMsg = 'Additional authentication required'; + show2FALoginDialog(reason.data.twoFactorView, $scope.submit); + } else { + $scope.loginStates.submitButton = 'error'; + $scope.errorMsg = reason.errorMsg; + //set the form inputs to invalid + $scope.loginForm.username.$setValidity('auth', false); + $scope.loginForm.password.$setValidity('auth', false); + } + }); + //setup a watch for both of the model values changing, if they change + // while the form is invalid, then revalidate them so that the form can + // be submitted again. + $scope.loginForm.username.$viewChangeListeners.push(function () { + if ($scope.loginForm.$invalid) { + $scope.loginForm.username.$setValidity('auth', true); + $scope.loginForm.password.$setValidity('auth', true); + } + }); + $scope.loginForm.password.$viewChangeListeners.push(function () { + if ($scope.loginForm.$invalid) { + $scope.loginForm.username.$setValidity('auth', true); + $scope.loginForm.password.$setValidity('auth', true); + } + }); + }; + $scope.requestPasswordResetSubmit = function (email) { + //TODO: Do validation properly like in the invite password update + if (email && email.length > 0) { + $scope.requestPasswordResetForm.email.$setValidity('auth', true); + } + $scope.showEmailResetConfirmation = false; + if ($scope.requestPasswordResetForm.$invalid) { + return; + } + $scope.errorMsg = ''; + authResource.performRequestPasswordReset(email).then(function () { + //remove the email entered + $scope.email = ''; + $scope.showEmailResetConfirmation = true; + }, function (reason) { + $scope.errorMsg = reason.errorMsg; + $scope.requestPasswordResetForm.email.$setValidity('auth', false); + }); + $scope.requestPasswordResetForm.email.$viewChangeListeners.push(function () { + if ($scope.requestPasswordResetForm.email.$invalid) { + $scope.requestPasswordResetForm.email.$setValidity('auth', true); + } + }); + }; + $scope.setPasswordSubmit = function (password, confirmPassword) { + $scope.showSetPasswordConfirmation = false; + if (password && confirmPassword && password.length > 0 && confirmPassword.length > 0) { + $scope.setPasswordForm.password.$setValidity('auth', true); + $scope.setPasswordForm.confirmPassword.$setValidity('auth', true); + } + if ($scope.setPasswordForm.$invalid) { + return; + } + //TODO: All of this logic can/should be shared! We should do validation the nice way instead of all of this manual stuff, see: inviteSavePassword + authResource.performSetPassword($scope.resetPasswordCodeInfo.resetCodeModel.userId, password, confirmPassword, $scope.resetPasswordCodeInfo.resetCodeModel.resetCode).then(function () { + $scope.showSetPasswordConfirmation = true; + $scope.resetComplete = true; + //reset the values in the resetPasswordCodeInfo angular so if someone logs out the change password isn't shown again + resetPasswordCodeInfo.resetCodeModel = null; + }, function (reason) { + if (reason.data && reason.data.Message) { + $scope.errorMsg = reason.data.Message; + } else { + $scope.errorMsg = reason.errorMsg; + } + $scope.setPasswordForm.password.$setValidity('auth', false); + $scope.setPasswordForm.confirmPassword.$setValidity('auth', false); + }); + $scope.setPasswordForm.password.$viewChangeListeners.push(function () { + if ($scope.setPasswordForm.password.$invalid) { + $scope.setPasswordForm.password.$setValidity('auth', true); + } + }); + $scope.setPasswordForm.confirmPassword.$viewChangeListeners.push(function () { + if ($scope.setPasswordForm.confirmPassword.$invalid) { + $scope.setPasswordForm.confirmPassword.$setValidity('auth', true); + } + }); + }; + //Now, show the correct panel: + if ($scope.resetPasswordCodeInfo.resetCodeModel) { + $scope.showSetPassword(); + } else if ($scope.resetPasswordCodeInfo.errors.length > 0) { + $scope.view = 'password-reset-code-expired'; + } else { + $scope.showLogin(); + } + init(); + }); + //used for the macro picker dialog + angular.module('umbraco').controller('Umbraco.Dialogs.MacroPickerController', function ($scope, macroFactory, umbPropEditorHelper) { + $scope.macros = macroFactory.all(true); + $scope.dialogMode = 'list'; + $scope.configureMacro = function (macro) { + $scope.dialogMode = 'configure'; + $scope.dialogData.macro = macroFactory.getMacro(macro.alias); + //set the correct view for each item + for (var i = 0; i < dialogData.macro.properties.length; i++) { + dialogData.macro.properties[i].editorView = umbPropEditorHelper.getViewPath(dialogData.macro.properties[i].view); + } + }; + }); + //used for the media picker dialog + angular.module('umbraco').controller('Umbraco.Dialogs.MediaPickerController', function ($scope, mediaResource, umbRequestHelper, entityResource, $log, mediaHelper, mediaTypeHelper, eventsService, treeService) { + var dialogOptions = $scope.dialogOptions; + $scope.onlyImages = dialogOptions.onlyImages; + $scope.showDetails = dialogOptions.showDetails; + $scope.multiPicker = dialogOptions.multiPicker && dialogOptions.multiPicker !== '0' ? true : false; + $scope.startNodeId = dialogOptions.startNodeId ? dialogOptions.startNodeId : -1; + $scope.cropSize = dialogOptions.cropSize; + //preload selected item + $scope.target = undefined; + if (dialogOptions.currentTarget) { + $scope.target = dialogOptions.currentTarget; + } + $scope.acceptedMediatypes = []; + mediaTypeHelper.getAllowedImagetypes($scope.startNodeId).then(function (types) { + $scope.acceptedMediatypes = types; + }); + $scope.upload = function (v) { + angular.element('.umb-file-dropzone-directive .file-select').click(); + }; + $scope.dragLeave = function (el, event) { + $scope.activeDrag = false; + }; + $scope.dragEnter = function (el, event) { + $scope.activeDrag = true; + }; + $scope.submitFolder = function (e) { + if (e.keyCode === 13) { + e.preventDefault(); + mediaResource.addFolder($scope.newFolderName, $scope.currentFolder.id).then(function (data) { + $scope.showFolderInput = false; + $scope.newFolderName = ''; + //we've added a new folder so lets clear the tree cache for that specific item + treeService.clearCache({ + cacheKey: '__media', + //this is the main media tree cache key + childrenOf: data.parentId + }); + $scope.gotoFolder(data); + }); + } + }; + $scope.gotoFolder = function (folder) { + if (!folder) { + folder = { + id: -1, + name: 'Media', + icon: 'icon-folder' + }; + } + if (folder.id > 0) { + entityResource.getAncestors(folder.id, 'media').then(function (anc) { + // anc.splice(0,1); + $scope.path = _.filter(anc, function (f) { + return f.path.indexOf($scope.startNodeId) !== -1; + }); + }); + mediaTypeHelper.getAllowedImagetypes(folder.id).then(function (types) { + $scope.acceptedMediatypes = types; + }); + } else { + $scope.path = []; + } + //mediaResource.rootMedia() + mediaResource.getChildren(folder.id).then(function (data) { + $scope.searchTerm = ''; + $scope.images = data.items ? data.items : []; + }); + $scope.currentFolder = folder; + }; + $scope.clickHandler = function (image, ev, select) { + ev.preventDefault(); + if (image.isFolder && !select) { + $scope.gotoFolder(image); + } else { + eventsService.emit('dialogs.mediaPicker.select', image); + //we have 3 options add to collection (if multi) show details, or submit it right back to the callback + if ($scope.multiPicker) { + $scope.select(image); + image.cssclass = $scope.dialogData.selection.indexOf(image) > -1 ? 'selected' : ''; + } else if ($scope.showDetails) { + $scope.target = image; + $scope.target.url = mediaHelper.resolveFile(image); + } else { + $scope.submit(image); + } + } + }; + $scope.exitDetails = function () { + if (!$scope.currentFolder) { + $scope.gotoFolder(); + } + $scope.target = undefined; + }; + $scope.onUploadComplete = function () { + $scope.gotoFolder($scope.currentFolder); + }; + $scope.onFilesQueue = function () { + $scope.activeDrag = false; + }; + //default root item + if (!$scope.target) { + $scope.gotoFolder({ + id: $scope.startNodeId, + name: 'Media', + icon: 'icon-folder' + }); + } + }); + //used for the member picker dialog + angular.module('umbraco').controller('Umbraco.Dialogs.MemberGroupPickerController', function ($scope, eventsService, entityResource, searchService, $log) { + var dialogOptions = $scope.dialogOptions; + $scope.dialogTreeEventHandler = $({}); + $scope.multiPicker = dialogOptions.multiPicker; + /** Method used for selecting a node */ + function select(text, id) { + if (dialogOptions.multiPicker) { + $scope.select(id); + } else { + $scope.submit(id); + } + } + function nodeSelectHandler(ev, args) { + args.event.preventDefault(); + args.event.stopPropagation(); + eventsService.emit('dialogs.memberGroupPicker.select', args); + //This is a tree node, so we don't have an entity to pass in, it will need to be looked up + //from the server in this method. + select(args.node.name, args.node.id); + //toggle checked state + args.node.selected = args.node.selected === true ? false : true; + } + $scope.dialogTreeEventHandler.bind('treeNodeSelect', nodeSelectHandler); + $scope.$on('$destroy', function () { + $scope.dialogTreeEventHandler.unbind('treeNodeSelect', nodeSelectHandler); + }); + }); + angular.module('umbraco').controller('Umbraco.Dialogs.RteEmbedController', function ($scope, $http, umbRequestHelper) { + $scope.form = {}; + $scope.form.url = ''; + $scope.form.width = 360; + $scope.form.height = 240; + $scope.form.constrain = true; + $scope.form.preview = ''; + $scope.form.success = false; + $scope.form.info = ''; + $scope.form.supportsDimensions = false; + var origWidth = 500; + var origHeight = 300; + $scope.showPreview = function () { + if ($scope.form.url) { + $scope.form.show = true; + $scope.form.preview = '
    '; + $scope.form.info = ''; + $scope.form.success = false; + $http({ + method: 'GET', + url: umbRequestHelper.getApiUrl('embedApiBaseUrl', 'GetEmbed'), + params: { + url: $scope.form.url, + width: $scope.form.width, + height: $scope.form.height + } + }).success(function (data) { + $scope.form.preview = ''; + switch (data.Status) { + case 0: + //not supported + $scope.form.info = 'Not supported'; + break; + case 1: + //error + $scope.form.info = 'Could not embed media - please ensure the URL is valid'; + break; + case 2: + $scope.form.preview = data.Markup; + $scope.form.supportsDimensions = data.SupportsDimensions; + $scope.form.success = true; + break; + } + }).error(function () { + $scope.form.supportsDimensions = false; + $scope.form.preview = ''; + $scope.form.info = 'Could not embed media - please ensure the URL is valid'; + }); + } else { + $scope.form.supportsDimensions = false; + $scope.form.preview = ''; + $scope.form.info = 'Please enter a URL'; + } + }; + $scope.changeSize = function (type) { + var width, height; + if ($scope.form.constrain) { + width = parseInt($scope.form.width, 10); + height = parseInt($scope.form.height, 10); + if (type == 'width') { + origHeight = Math.round(width / origWidth * height); + $scope.form.height = origHeight; + } else { + origWidth = Math.round(height / origHeight * width); + $scope.form.width = origWidth; + } + } + if ($scope.form.url != '') { + $scope.showPreview(); + } + }; + $scope.insert = function () { + $scope.submit($scope.form.preview); + }; + }); + angular.module('umbraco').controller('Umbraco.Dialogs.Template.QueryBuilderController', function ($scope, $http, dialogService) { + $http.get('backoffice/UmbracoApi/TemplateQuery/GetAllowedProperties').then(function (response) { + $scope.properties = response.data; + }); + $http.get('backoffice/UmbracoApi/TemplateQuery/GetContentTypes').then(function (response) { + $scope.contentTypes = response.data; + }); + $http.get('backoffice/UmbracoApi/TemplateQuery/GetFilterConditions').then(function (response) { + $scope.conditions = response.data; + }); + $scope.query = { + contentType: { name: 'Everything' }, + source: { name: 'My website' }, + filters: [{ + property: undefined, + operator: undefined + }], + sort: { + property: { + alias: '', + name: '' + }, + direction: 'ascending' + } + }; + $scope.chooseSource = function (query) { + dialogService.contentPicker({ + callback: function (data) { + if (data.id > 0) { + query.source = { + id: data.id, + name: data.name + }; + } else { + query.source.name = 'My website'; + delete query.source.id; + } + } + }); + }; + var throttledFunc = _.throttle(function () { + $http.post('backoffice/UmbracoApi/TemplateQuery/PostTemplateQuery', $scope.query).then(function (response) { + $scope.result = response.data; + }); + }, 200); + $scope.$watch('query', function (value) { + throttledFunc(); + }, true); + $scope.getPropertyOperators = function (property) { + var conditions = _.filter($scope.conditions, function (condition) { + var index = condition.appliesTo.indexOf(property.type); + return index >= 0; + }); + return conditions; + }; + $scope.addFilter = function (query) { + query.filters.push({}); + }; + $scope.trashFilter = function (query) { + query.filters.splice(query, 1); + }; + $scope.changeSortOrder = function (query) { + if (query.sort.direction === 'ascending') { + query.sort.direction = 'descending'; + } else { + query.sort.direction = 'ascending'; + } + }; + $scope.setSortProperty = function (query, property) { + query.sort.property = property; + if (property.type === 'datetime') { + query.sort.direction = 'descending'; + } else { + query.sort.direction = 'ascending'; + } + }; + }); + angular.module('umbraco').controller('Umbraco.Dialogs.Template.SnippetController', function ($scope) { + $scope.type = $scope.dialogOptions.type; + $scope.section = { + name: '', + required: false + }; + }); + //used for the media picker dialog + angular.module('umbraco').controller('Umbraco.Dialogs.TreePickerController', function ($scope, entityResource, eventsService, $log, searchService, angularHelper, $timeout, localizationService, treeService) { + var tree = null; + var dialogOptions = $scope.dialogOptions; + $scope.dialogTreeEventHandler = $({}); + $scope.section = dialogOptions.section; + $scope.treeAlias = dialogOptions.treeAlias; + $scope.multiPicker = dialogOptions.multiPicker; + $scope.hideHeader = true; + $scope.searchInfo = { + searchFromId: dialogOptions.startNodeId, + searchFromName: null, + showSearch: false, + results: [], + selectedSearchResults: [] + }; + //create the custom query string param for this tree + $scope.customTreeParams = dialogOptions.startNodeId ? 'startNodeId=' + dialogOptions.startNodeId : ''; + $scope.customTreeParams += dialogOptions.customTreeParams ? '&' + dialogOptions.customTreeParams : ''; + var searchText = 'Search...'; + localizationService.localize('general_search').then(function (value) { + searchText = value + '...'; + }); + // Allow the entity type to be passed in but defaults to Document for backwards compatibility. + var entityType = dialogOptions.entityType ? dialogOptions.entityType : 'Document'; + //min / max values + if (dialogOptions.minNumber) { + dialogOptions.minNumber = parseInt(dialogOptions.minNumber, 10); + } + if (dialogOptions.maxNumber) { + dialogOptions.maxNumber = parseInt(dialogOptions.maxNumber, 10); + } + if (dialogOptions.section === 'member') { + entityType = 'Member'; + } else if (dialogOptions.section === 'media') { + entityType = 'Media'; + } + //Configures filtering + if (dialogOptions.filter) { + dialogOptions.filterExclude = false; + dialogOptions.filterAdvanced = false; + //used advanced filtering + if (angular.isFunction(dialogOptions.filter)) { + dialogOptions.filterAdvanced = true; + } else if (angular.isObject(dialogOptions.filter)) { + dialogOptions.filterAdvanced = true; + } else { + if (dialogOptions.filter.startsWith('!')) { + dialogOptions.filterExclude = true; + dialogOptions.filter = dialogOptions.filter.substring(1); + } + //used advanced filtering + if (dialogOptions.filter.startsWith('{')) { + dialogOptions.filterAdvanced = true; + //convert to object + dialogOptions.filter = angular.fromJson(dialogOptions.filter); + } + } + } + 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 some custom + // children: A node to activate the search, any nodes that have already been + // selected in the search + if (child.metaData.isContainer) { + child.hasChildren = true; + child.children = [{ + level: child.level + 1, + hasChildren: false, + parent: function () { + return child; + }, + name: searchText, + metaData: { listViewNode: child }, + cssClass: 'icon-search', + cssClasses: ['not-published'] + }]; + //add base transition classes to this node + child.cssClasses.push('tree-node-slide-up'); + var listViewResults = _.filter($scope.searchInfo.selectedSearchResults, function (i) { + return i.parentId == child.id; + }); + _.each(listViewResults, function (item) { + child.children.unshift({ + id: item.id, + name: item.name, + cssClass: 'icon umb-tree-icon sprTree ' + item.icon, + level: child.level + 1, + metaData: { isSearchResult: true }, + hasChildren: false, + parent: function () { + return child; + } + }); + }); + } + //now we need to look in the already selected search results and + // toggle the check boxes for those ones that are listed + var exists = _.find($scope.searchInfo.selectedSearchResults, function (selected) { + return child.id == selected.id; + }); + if (exists) { + child.selected = true; + } + }); + //check filter + performFiltering(args.children); + } + } + //gets the tree object when it loads + function treeLoadedHandler(ev, args) { + tree = args.tree; + } + //wires up selection + function nodeSelectHandler(ev, args) { + args.event.preventDefault(); + args.event.stopPropagation(); + if (args.node.metaData.listViewNode) { + //check if list view 'search' node was selected + $scope.searchInfo.showSearch = true; + $scope.searchInfo.searchFromId = args.node.metaData.listViewNode.id; + $scope.searchInfo.searchFromName = args.node.metaData.listViewNode.name; + //add transition classes + var listViewNode = args.node.parent(); + listViewNode.cssClasses.push('tree-node-slide-up-hide-active'); + } else if (args.node.metaData.isSearchResult) { + //check if the item selected was a search result from a list view + //unselect + select(args.node.name, args.node.id); + //remove it from the list view children + var listView = args.node.parent(); + listView.children = _.reject(listView.children, function (child) { + return child.id == args.node.id; + }); + //remove it from the custom tracked search result list + $scope.searchInfo.selectedSearchResults = _.reject($scope.searchInfo.selectedSearchResults, function (i) { + return i.id == args.node.id; + }); + } else { + eventsService.emit('dialogs.treePickerController.select', args); + if (args.node.filtered) { + return; + } + //This is a tree node, so we don't have an entity to pass in, it will need to be looked up + //from the server in this method. + select(args.node.name, args.node.id); + //toggle checked state + args.node.selected = args.node.selected === true ? false : true; + } + } + /** Method used for selecting a node */ + function select(text, id, entity) { + //if we get the root, we just return a constructed entity, no need for server data + if (id < 0) { + if ($scope.multiPicker) { + $scope.select(id); + } else { + var node = { + alias: null, + icon: 'icon-folder', + id: id, + name: text + }; + $scope.submit(node); + } + } else { + if ($scope.multiPicker) { + $scope.select(Number(id)); + } else { + $scope.hideSearch(); + //if an entity has been passed in, use it + if (entity) { + $scope.submit(entity); + } else { + //otherwise we have to get it from the server + entityResource.getById(id, entityType).then(function (ent) { + $scope.submit(ent); + }); + } + } + } + } + function performFiltering(nodes) { + if (!dialogOptions.filter) { + return; + } + //remove any list view search nodes from being filtered since these are special nodes that always must + // be allowed to be clicked on + nodes = _.filter(nodes, function (n) { + return !angular.isObject(n.metaData.listViewNode); + }); + if (dialogOptions.filterAdvanced) { + //filter either based on a method or an object + var filtered = angular.isFunction(dialogOptions.filter) ? _.filter(nodes, dialogOptions.filter) : _.where(nodes, dialogOptions.filter); + angular.forEach(filtered, function (value, key) { + value.filtered = true; + if (dialogOptions.filterCssClass) { + if (!value.cssClasses) { + value.cssClasses = []; + } + value.cssClasses.push(dialogOptions.filterCssClass); + } + }); + } else { + var a = dialogOptions.filter.toLowerCase().replace(/\s/g, '').split(','); + angular.forEach(nodes, function (value, key) { + var found = a.indexOf(value.metaData.contentType.toLowerCase()) >= 0; + if (!dialogOptions.filterExclude && !found || dialogOptions.filterExclude && found) { + value.filtered = true; + if (dialogOptions.filterCssClass) { + if (!value.cssClasses) { + value.cssClasses = []; + } + value.cssClasses.push(dialogOptions.filterCssClass); + } + } + }); + } + } + $scope.multiSubmit = function (result) { + entityResource.getByIds(result, entityType).then(function (ents) { + $scope.submit(ents); + }); + }; + /** method to select a search result */ + $scope.selectResult = function (evt, result) { + if (result.filtered) { + return; + } + result.selected = result.selected === true ? false : true; + //since result = an entity, we'll pass it in so we don't have to go back to the server + select(result.name, result.id, result); + //add/remove to our custom tracked list of selected search results + if (result.selected) { + $scope.searchInfo.selectedSearchResults.push(result); + } else { + $scope.searchInfo.selectedSearchResults = _.reject($scope.searchInfo.selectedSearchResults, function (i) { + return i.id == result.id; + }); + } + //ensure the tree node in the tree is checked/unchecked if it already exists there + if (tree) { + var found = treeService.getDescendantNode(tree.root, result.id); + if (found) { + found.selected = result.selected; + } + } + }; + $scope.hideSearch = function () { + //Traverse the entire displayed tree and update each node to sync with the selected search results + if (tree) { + //we need to ensure that any currently displayed nodes that get selected + // from the search get updated to have a check box! + function checkChildren(children) { + _.each(children, function (child) { + //check if the id is in the selection, if so ensure it's flagged as selected + var exists = _.find($scope.searchInfo.selectedSearchResults, function (selected) { + return child.id == selected.id; + }); + //if the curr node exists in selected search results, ensure it's checked + if (exists) { + child.selected = true; + } //if the curr node does not exist in the selected search result, and the curr node is a child of a list view search result + else if (child.metaData.isSearchResult) { + //if this tree node is under a list view it means that the node was added + // to the tree dynamically under the list view that was searched, so we actually want to remove + // it all together from the tree + var listView = child.parent(); + listView.children = _.reject(listView.children, function (c) { + return c.id == child.id; + }); + } + //check if the current node is a list view and if so, check if there's any new results + // that need to be added as child nodes to it based on search results selected + if (child.metaData.isContainer) { + child.cssClasses = _.reject(child.cssClasses, function (c) { + return c === 'tree-node-slide-up-hide-active'; + }); + var listViewResults = _.filter($scope.searchInfo.selectedSearchResults, function (i) { + return i.parentId == child.id; + }); + _.each(listViewResults, function (item) { + var childExists = _.find(child.children, function (c) { + return c.id == item.id; + }); + if (!childExists) { + var parent = child; + child.children.unshift({ + id: item.id, + name: item.name, + cssClass: 'icon umb-tree-icon sprTree ' + item.icon, + level: child.level + 1, + metaData: { isSearchResult: true }, + hasChildren: false, + parent: function () { + return parent; + } + }); + } + }); + } + //recurse + if (child.children && child.children.length > 0) { + checkChildren(child.children); + } + }); + } + checkChildren(tree.root.children); + } + $scope.searchInfo.showSearch = false; + $scope.searchInfo.searchFromId = dialogOptions.startNodeId; + $scope.searchInfo.searchFromName = null; + $scope.searchInfo.results = []; + }; + $scope.onSearchResults = function (results) { + //filter all items - this will mark an item as filtered + performFiltering(results); + //now actually remove all filtered items so they are not even displayed + results = _.filter(results, function (item) { + return !item.filtered; + }); + $scope.searchInfo.results = results; + //sync with the curr selected results + _.each($scope.searchInfo.results, function (result) { + var exists = _.find($scope.dialogData.selection, function (selectedId) { + return result.id == selectedId; + }); + if (exists) { + result.selected = true; + } + }); + $scope.searchInfo.showSearch = true; + }; + $scope.dialogTreeEventHandler.bind('treeLoaded', treeLoadedHandler); + $scope.dialogTreeEventHandler.bind('treeNodeExpanded', nodeExpandedHandler); + $scope.dialogTreeEventHandler.bind('treeNodeSelect', nodeSelectHandler); + $scope.$on('$destroy', function () { + $scope.dialogTreeEventHandler.unbind('treeLoaded', treeLoadedHandler); + $scope.dialogTreeEventHandler.unbind('treeNodeExpanded', nodeExpandedHandler); + $scope.dialogTreeEventHandler.unbind('treeNodeSelect', nodeSelectHandler); + }); + }); + angular.module('umbraco').controller('Umbraco.Dialogs.UserController', function ($scope, $location, $timeout, userService, historyService, eventsService, externalLoginInfo, authResource, currentUserResource, formHelper) { $scope.history = historyService.getCurrent(); - $scope.version = Umbraco.Sys.ServerVariables.application.version + " assembly: " + Umbraco.Sys.ServerVariables.application.assemblyVersion; + $scope.version = Umbraco.Sys.ServerVariables.application.version + ' assembly: ' + Umbraco.Sys.ServerVariables.application.assemblyVersion; $scope.showPasswordFields = false; - $scope.changePasswordButtonState = "init"; - + $scope.changePasswordButtonState = 'init'; $scope.externalLoginProviders = externalLoginInfo.providers; $scope.externalLinkLoginFormAction = Umbraco.Sys.ServerVariables.umbracoUrls.externalLinkLoginsUrl; var evts = []; - evts.push(eventsService.on("historyService.add", function (e, args) { + evts.push(eventsService.on('historyService.add', function (e, args) { $scope.history = args.all; })); - evts.push(eventsService.on("historyService.remove", function (e, args) { + evts.push(eventsService.on('historyService.remove', function (e, args) { $scope.history = args.all; })); - evts.push(eventsService.on("historyService.removeAll", function (e, args) { + evts.push(eventsService.on('historyService.removeAll', function (e, args) { $scope.history = []; })); - $scope.logout = function () { - //Add event listener for when there are pending changes on an editor which means our route was not successful - var pendingChangeEvent = eventsService.on("valFormManager.pendingChanges", function (e, args) { + var pendingChangeEvent = eventsService.on('valFormManager.pendingChanges', function (e, args) { //one time listener, remove the event pendingChangeEvent(); $scope.close(); }); - - //perform the path change, if it is successful then the promise will resolve otherwise it will fail $scope.close(); - $location.path("/logout"); + $location.path('/logout'); }; - $scope.gotoHistory = function (link) { $location.path(link); $scope.close(); }; - //Manually update the remaining timeout seconds function updateTimeout() { $timeout(function () { @@ -2124,26 +1979,22 @@ angular.module("umbraco") //recurse updateTimeout(); } - - }, 1000, false); // 1 second, do NOT execute a global digest + }, 1000, false); // 1 second, do NOT execute a global digest } - function updateUserInfo() { //get the user userService.getCurrentUser().then(function (user) { $scope.user = user; if ($scope.user) { $scope.remainingAuthSeconds = $scope.user.remainingAuthSeconds; - $scope.canEditProfile = _.indexOf($scope.user.allowedSections, "users") > -1; + $scope.canEditProfile = _.indexOf($scope.user.allowedSections, 'users') > -1; //set the timer updateTimeout(); - - authResource.getCurrentUserLinkedLogins().then(function(logins) { + authResource.getCurrentUserLinkedLogins().then(function (logins) { //reset all to be un-linked for (var provider in $scope.externalLoginProviders) { $scope.externalLoginProviders[provider].linkedProviderKey = undefined; } - //set the linked logins for (var login in logins) { var found = _.find($scope.externalLoginProviders, function (i) { @@ -2157,222 +2008,323 @@ angular.module("umbraco") } }); } - $scope.unlink = function (e, loginProvider, providerKey) { - var result = confirm("Are you sure you want to unlink this account?"); + var result = confirm('Are you sure you want to unlink this account?'); if (!result) { e.preventDefault(); return; } - authResource.unlinkLogin(loginProvider, providerKey).then(function (a, b, c) { updateUserInfo(); }); - } - + }; updateUserInfo(); - //remove all event handlers $scope.$on('$destroy', function () { for (var e = 0; e < evts.length; e++) { evts[e](); } - }); - /* ---------- UPDATE PASSWORD ---------- */ - //create the initial model for change password property editor $scope.changePasswordModel = { - alias: "_umb_password", - view: "changepassword", - config: {}, - value: {} + alias: '_umb_password', + view: 'changepassword', + config: {}, + value: {} }; - //go get the config for the membership provider and add it to the model - currentUserResource.getMembershipProviderConfig().then(function(data) { - $scope.changePasswordModel.config = data; - //ensure the hasPassword config option is set to true (the user of course has a password already assigned) - //this will ensure the oldPassword is shown so they can change it - // disable reset password functionality beacuse it does not make sense inside the backoffice - $scope.changePasswordModel.config.hasPassword = true; - $scope.changePasswordModel.config.disableToggle = true; - $scope.changePasswordModel.config.enableReset = false; + authResource.getMembershipProviderConfig().then(function (data) { + $scope.changePasswordModel.config = data; + //ensure the hasPassword config option is set to true (the user of course has a password already assigned) + //this will ensure the oldPassword is shown so they can change it + // disable reset password functionality beacuse it does not make sense for the current user. + $scope.changePasswordModel.config.hasPassword = true; + $scope.changePasswordModel.config.disableToggle = true; + $scope.changePasswordModel.config.enableReset = false; }); - - $scope.changePassword = function() { - - if (formHelper.submitForm({ scope: $scope })) { - - $scope.changePasswordButtonState = "busy"; - - currentUserResource.changePassword($scope.changePasswordModel.value).then(function(data) { - + $scope.changePassword = function () { + if (formHelper.submitForm({ scope: $scope })) { + $scope.changePasswordButtonState = 'busy'; + currentUserResource.changePassword($scope.changePasswordModel.value).then(function (data) { //if the password has been reset, then update our model if (data.value) { $scope.changePasswordModel.value.generatedPassword = data.value; } - - formHelper.resetForm({ scope: $scope, notifications: data.notifications }); - - $scope.changePasswordButtonState = "success"; - + formHelper.resetForm({ + scope: $scope, + notifications: data.notifications + }); + $scope.changePasswordButtonState = 'success'; }, function (err) { - formHelper.handleError(err); - - $scope.changePasswordButtonState = "error"; - + $scope.changePasswordButtonState = 'error'; }); - } - }; - - $scope.togglePasswordFields = function() { - clearPasswordFields(); - $scope.showPasswordFields = !$scope.showPasswordFields; - } - + $scope.togglePasswordFields = function () { + clearPasswordFields(); + $scope.showPasswordFields = !$scope.showPasswordFields; + }; function clearPasswordFields() { - $scope.changePasswordModel.value.newPassword = ""; - $scope.changePasswordModel.confirm = ""; + $scope.changePasswordModel.value.newPassword = ''; + $scope.changePasswordModel.confirm = ''; } - }); - -/** - * @ngdoc controller - * @name Umbraco.Dialogs.LegacyDeleteController - * @function - * - * @description - * The controller for deleting content - */ -function YsodController($scope, legacyResource, treeService, navigationService) { - - if ($scope.error && $scope.error.data && $scope.error.data.StackTrace) { - //trim whitespace - $scope.error.data.StackTrace = $scope.error.data.StackTrace.trim(); - } - - $scope.closeDialog = function() { - $scope.dismiss(); - }; - -} - -angular.module("umbraco").controller("Umbraco.Dialogs.YsodController", YsodController); - -/** - * @ngdoc controller - * @name Umbraco.LegacyController - * @function - * - * @description - * A controller to control the legacy iframe injection - * -*/ -function LegacyController($scope, $routeParams, $element) { - - var url = decodeURIComponent($routeParams.url.replace(/javascript\:/gi, "")); - //split into path and query - var urlParts = url.split("?"); - var extIndex = urlParts[0].lastIndexOf("."); - var ext = extIndex === -1 ? "" : urlParts[0].substr(extIndex); - //path cannot be a js file - if (ext !== ".js" || ext === "") { - //path cannot contain any of these chars - var toClean = "*(){}[];:<>\\|'\""; - for (var i = 0; i < toClean.length; i++) { - var reg = new RegExp("\\" + toClean[i], "g"); - urlParts[0] = urlParts[0].replace(reg, ""); - } - //join cleaned path and query back together - url = urlParts[0] + (urlParts.length === 1 ? "" : ("?" + urlParts[1])); - $scope.legacyPath = url; - } - else { - throw "Invalid url"; - } -} - -angular.module("umbraco").controller('Umbraco.LegacyController', LegacyController); -/** This controller is simply here to launch the login dialog when the route is explicitly changed to /login */ -angular.module('umbraco').controller("Umbraco.LoginController", function (eventsService, $scope, userService, $location, $rootScope) { - - userService._showLoginDialog(); - - var evtOn = eventsService.on("app.ready", function(evt, data){ - $scope.avatar = "assets/img/application/logo.png"; - - var path = "/"; - - //check if there's a returnPath query string, if so redirect to it - var locationObj = $location.search(); - if (locationObj.returnPath) { - path = decodeURIComponent(locationObj.returnPath); - } - - $location.url(path); - }); - - $scope.$on('$destroy', function () { - eventsService.unsubscribe(evtOn); - }); - -}); - -//used for the media picker dialog -angular.module("umbraco").controller("Umbraco.Notifications.ConfirmRouteChangeController", - function ($scope, $location, $log, notificationsService) { - - $scope.discard = function(not){ - not.args.listener(); - - $location.search(""); - - //we need to break the path up into path and query - var parts = not.args.path.split("?"); - var query = {}; + /** + * @ngdoc controller + * @name Umbraco.Dialogs.LegacyDeleteController + * @function + * + * @description + * The controller for deleting content + */ + function YsodController($scope, legacyResource, treeService, navigationService) { + if ($scope.error && $scope.error.data && $scope.error.data.StackTrace) { + //trim whitespace + $scope.error.data.StackTrace = $scope.error.data.StackTrace.trim(); + } + $scope.closeDialog = function () { + $scope.dismiss(); + }; + } + angular.module('umbraco').controller('Umbraco.Dialogs.YsodController', YsodController); + (function () { + 'use strict'; + function HelpDrawerController($scope, $routeParams, $timeout, dashboardResource, localizationService, userService, eventsService, helpService, appState, tourService, $filter) { + var vm = this; + var evts = []; + vm.title = localizationService.localize('general_help'); + vm.subtitle = 'Umbraco version' + ' ' + Umbraco.Sys.ServerVariables.application.version; + vm.section = $routeParams.section; + vm.tree = $routeParams.tree; + vm.sectionName = ''; + vm.customDashboard = null; + vm.tours = []; + vm.closeDrawer = closeDrawer; + vm.startTour = startTour; + vm.getTourGroupCompletedPercentage = getTourGroupCompletedPercentage; + vm.showTourButton = showTourButton; + function startTour(tour) { + tourService.startTour(tour); + closeDrawer(); + } + function oninit() { + tourService.getGroupedTours().then(function (groupedTours) { + vm.tours = groupedTours; + getTourGroupCompletedPercentage(); + }); + // load custom help dashboard + dashboardResource.getDashboard('user-help').then(function (dashboard) { + vm.customDashboard = dashboard; + }); + if (!vm.section) { + vm.section = 'content'; + } + setSectionName(); + userService.getCurrentUser().then(function (user) { + vm.userType = user.userType; + vm.userLang = user.locale; + vm.hasAccessToSettings = _.contains(user.allowedSections, 'settings'); + evts.push(eventsService.on('appState.treeState.changed', function (e, args) { + handleSectionChange(); + })); + findHelp(vm.section, vm.tree, vm.userType, vm.userLang); + }); + // check if a tour is running - if it is open the matching group + var currentTour = tourService.getCurrentTour(); + if (currentTour) { + openTourGroup(currentTour.alias); + } + } + function closeDrawer() { + appState.setDrawerState('showDrawer', false); + } + function handleSectionChange() { + $timeout(function () { + if (vm.section !== $routeParams.section || vm.tree !== $routeParams.tree) { + vm.section = $routeParams.section; + vm.tree = $routeParams.tree; + setSectionName(); + findHelp(vm.section, vm.tree, vm.userType, vm.userLang); + } + }); + } + function findHelp(section, tree, usertype, userLang) { + if (vm.hasAccessToSettings) { + helpService.getContextHelpForPage(section, tree).then(function (topics) { + vm.topics = topics; + }); + } + var rq = {}; + rq.section = vm.section; + rq.usertype = usertype; + rq.lang = userLang; + if ($routeParams.url) { + rq.path = decodeURIComponent($routeParams.url); + if (rq.path.indexOf(Umbraco.Sys.ServerVariables.umbracoSettings.umbracoPath) === 0) { + rq.path = rq.path.substring(Umbraco.Sys.ServerVariables.umbracoSettings.umbracoPath.length); + } + if (rq.path.indexOf('.aspx') > 0) { + rq.path = rq.path.substring(0, rq.path.indexOf('.aspx')); + } + } else { + rq.path = rq.section + '/' + $routeParams.tree + '/' + $routeParams.method; + } + if (vm.hasAccessToSettings) { + helpService.findVideos(rq).then(function (videos) { + vm.videos = videos; + }); + } + } + function setSectionName() { + // Get section name + var languageKey = 'sections_' + vm.section; + localizationService.localize(languageKey).then(function (value) { + vm.sectionName = value; + }); + } + function showTourButton(index, tourGroup) { + if (index !== 0) { + var prevTour = tourGroup.tours[index - 1]; + if (prevTour.completed) { + return true; + } + } else { + return true; + } + } + function openTourGroup(tourAlias) { + angular.forEach(vm.tours, function (group) { + angular.forEach(group, function (tour) { + if (tour.alias === tourAlias) { + group.open = true; + } + }); + }); + } + function getTourGroupCompletedPercentage() { + // Finding out, how many tours are completed for the progress circle + angular.forEach(vm.tours, function (group) { + var completedTours = 0; + angular.forEach(group.tours, function (tour) { + if (tour.completed) { + completedTours++; + } + }); + group.completedPercentage = Math.round(completedTours / group.tours.length * 100); + }); + } + evts.push(eventsService.on('appState.tour.complete', function (event, tour) { + tourService.getGroupedTours().then(function (groupedTours) { + vm.tours = groupedTours; + openTourGroup(tour.alias); + getTourGroupCompletedPercentage(); + }); + })); + $scope.$on('$destroy', function () { + for (var e in evts) { + eventsService.unsubscribe(evts[e]); + } + }); + oninit(); + } + angular.module('umbraco').controller('Umbraco.Drawers.Help', HelpDrawerController); + }()); + /** + * @ngdoc controller + * @name Umbraco.LegacyController + * @function + * + * @description + * A controller to control the legacy iframe injection + * +*/ + function LegacyController($scope, $routeParams, $element) { + var url = decodeURIComponent($routeParams.url.replace(/javascript\:/gi, '')); + //split into path and query + var urlParts = url.split('?'); + var extIndex = urlParts[0].lastIndexOf('.'); + var ext = extIndex === -1 ? '' : urlParts[0].substr(extIndex); + //path cannot be a js file + if (ext !== '.js' || ext === '') { + //path cannot contain any of these chars + var toClean = '*(){}[];:<>\\|\'"'; + for (var i = 0; i < toClean.length; i++) { + var reg = new RegExp('\\' + toClean[i], 'g'); + urlParts[0] = urlParts[0].replace(reg, ''); + } + //join cleaned path and query back together + url = urlParts[0] + (urlParts.length === 1 ? '' : '?' + urlParts[1]); + $scope.legacyPath = url; + } else { + throw 'Invalid url'; + } + } + angular.module('umbraco').controller('Umbraco.LegacyController', LegacyController); + /** This controller is simply here to launch the login dialog when the route is explicitly changed to /login */ + angular.module('umbraco').controller('Umbraco.LoginController', function (eventsService, $scope, userService, $location, $rootScope) { + userService._showLoginDialog(); + var evtOn = eventsService.on('app.ready', function (evt, data) { + $scope.avatar = 'assets/img/application/logo.png'; + var path = '/'; + //check if there's a returnPath query string, if so redirect to it + var locationObj = $location.search(); + if (locationObj.returnPath) { + path = decodeURIComponent(locationObj.returnPath); + } + $location.url(path); + }); + $scope.$on('$destroy', function () { + eventsService.unsubscribe(evtOn); + }); + }); + //used for the media picker dialog + angular.module('umbraco').controller('Umbraco.Notifications.ConfirmRouteChangeController', function ($scope, $location, $log, notificationsService) { + $scope.discard = function (not) { + not.args.listener(); + $location.search(''); + //we need to break the path up into path and query + var parts = not.args.path.split('?'); + var query = {}; if (parts.length > 1) { - _.each(parts[1].split("&"), function(q) { - var keyVal = q.split("="); + _.each(parts[1].split('&'), function (q) { + var keyVal = q.split('='); query[keyVal[0]] = keyVal[1]; }); } - $location.path(parts[0]).search(query); - notificationsService.remove(not); - }; - - $scope.stay = function(not){ - notificationsService.remove(not); - }; - - }); - (function() { - "use strict"; - - function CompositionsOverlay($scope) { - - var vm = this; - - vm.isSelected = isSelected; - - function isSelected(alias) { - if($scope.model.contentType.compositeContentTypes.indexOf(alias) !== -1) { - return true; + notificationsService.remove(not); + }; + $scope.stay = function (not) { + notificationsService.remove(not); + }; + }); + angular.module('umbraco').controller('Umbraco.Notifications.ConfirmUnpublishController', function ($scope, notificationsService, eventsService) { + $scope.confirm = function (not, action) { + eventsService.emit('content.confirmUnpublish', action); + notificationsService.remove(not); + }; + }); + (function () { + 'use strict'; + function CompositionsOverlay($scope, $location) { + var vm = this; + vm.isSelected = isSelected; + vm.openContentType = openContentType; + function isSelected(alias) { + if ($scope.model.contentType.compositeContentTypes.indexOf(alias) !== -1) { + return true; + } + } + function openContentType(contentType, section) { + var url = (section === 'documentType' ? '/settings/documenttypes/edit/' : '/settings/mediaTypes/edit/') + contentType.id; + $location.path(url); } } - } - - angular.module("umbraco").controller("Umbraco.Overlays.CompositionsOverlay", CompositionsOverlay); - -})(); - -/** + angular.module('umbraco').controller('Umbraco.Overlays.CompositionsOverlay', CompositionsOverlay); + }()); + /** * @ngdoc controller * @name Umbraco.Editors.DocumentType.PropertyController * @function @@ -2380,213 +2332,154 @@ angular.module("umbraco").controller("Umbraco.Notifications.ConfirmRouteChangeCo * @description * The controller for the content type editor property dialog */ - -(function() { - "use strict"; - - function EditorPickerOverlay($scope, dataTypeResource, dataTypeHelper, contentTypeResource, localizationService) { - - var vm = this; - - if (!$scope.model.title) { - $scope.model.title = localizationService.localize("defaultdialogs_selectEditor"); - } - - vm.searchTerm = ""; - vm.showTabs = false; - vm.tabsLoaded = 0; - vm.typesAndEditors = []; - vm.userConfigured = []; - vm.loading = false; - vm.tabs = [{ - active: true, - id: 1, - label: localizationService.localize("contentTypeEditor_availableEditors"), - alias: "Default", - typesAndEditors: [] - }, { - active: false, - id: 2, - label: localizationService.localize("contentTypeEditor_reuse"), - alias: "Reuse", - userConfigured: [] - }]; - - vm.filterItems = filterItems; - vm.showDetailsOverlay = showDetailsOverlay; - vm.hideDetailsOverlay = hideDetailsOverlay; - vm.pickEditor = pickEditor; - vm.pickDataType = pickDataType; - - function activate() { - - getGroupedDataTypes(); - getGroupedPropertyEditors(); - - } - - function getGroupedPropertyEditors() { - - vm.loading = true; - - dataTypeResource.getGroupedPropertyEditors().then(function(data) { - vm.tabs[0].typesAndEditors = data; - vm.typesAndEditors = data; - vm.tabsLoaded = vm.tabsLoaded + 1; - checkIfTabContentIsLoaded(); - }); - - } - - function getGroupedDataTypes() { - - vm.loading = true; - - dataTypeResource.getGroupedDataTypes().then(function(data) { - vm.tabs[1].userConfigured = data; - vm.userConfigured = data; - vm.tabsLoaded = vm.tabsLoaded + 1; - checkIfTabContentIsLoaded(); - }); - - } - - function checkIfTabContentIsLoaded() { - if (vm.tabsLoaded === 2) { - vm.loading = false; - vm.showTabs = true; + (function () { + 'use strict'; + function EditorPickerOverlay($scope, dataTypeResource, dataTypeHelper, contentTypeResource, localizationService) { + var vm = this; + if (!$scope.model.title) { + $scope.model.title = localizationService.localize('defaultdialogs_selectEditor'); } - } - - function filterItems() { - // clear item details - $scope.model.itemDetails = null; - - if (vm.searchTerm) { - vm.showFilterResult = true; - vm.showTabs = false; - } else { - vm.showFilterResult = false; - vm.showTabs = true; + vm.searchTerm = ''; + vm.showTabs = false; + vm.tabsLoaded = 0; + vm.typesAndEditors = []; + vm.userConfigured = []; + vm.loading = false; + vm.tabs = [ + { + active: true, + id: 1, + label: localizationService.localize('contentTypeEditor_availableEditors'), + alias: 'Default', + typesAndEditors: [] + }, + { + active: false, + id: 2, + label: localizationService.localize('contentTypeEditor_reuse'), + alias: 'Reuse', + userConfigured: [] + } + ]; + vm.filterItems = filterItems; + vm.showDetailsOverlay = showDetailsOverlay; + vm.hideDetailsOverlay = hideDetailsOverlay; + vm.pickEditor = pickEditor; + vm.pickDataType = pickDataType; + function activate() { + getGroupedDataTypes(); + getGroupedPropertyEditors(); } - - } - - function showDetailsOverlay(property) { - - var propertyDetails = {}; - propertyDetails.icon = property.icon; - propertyDetails.title = property.name; - - $scope.model.itemDetails = propertyDetails; - - } - - function hideDetailsOverlay() { - $scope.model.itemDetails = null; - } - - function pickEditor(editor) { - - var parentId = -1; - - dataTypeResource.getScaffold(parentId).then(function(dataType) { - - // set alias - dataType.selectedEditor = editor.alias; - - // set name - var nameArray = []; - - if ($scope.model.contentTypeName) { - nameArray.push($scope.model.contentTypeName); - } - - if ($scope.model.property.label) { - nameArray.push($scope.model.property.label); - } - - if (editor.name) { - nameArray.push(editor.name); - } - - // make name - dataType.name = nameArray.join(" - "); - - // get pre values - dataTypeResource.getPreValues(dataType.selectedEditor).then(function(preValues) { - - dataType.preValues = preValues; - - openEditorSettingsOverlay(dataType, true); - + function getGroupedPropertyEditors() { + vm.loading = true; + dataTypeResource.getGroupedPropertyEditors().then(function (data) { + vm.tabs[0].typesAndEditors = data; + vm.typesAndEditors = data; + vm.tabsLoaded = vm.tabsLoaded + 1; + checkIfTabContentIsLoaded(); }); - - }); - - } - - function pickDataType(selectedDataType) { - - dataTypeResource.getById(selectedDataType.id).then(function(dataType) { - contentTypeResource.getPropertyTypeScaffold(dataType.id).then(function(propertyType) { - submitOverlay(dataType, propertyType, false); + } + function getGroupedDataTypes() { + vm.loading = true; + dataTypeResource.getGroupedDataTypes().then(function (data) { + vm.tabs[1].userConfigured = data; + vm.userConfigured = data; + vm.tabsLoaded = vm.tabsLoaded + 1; + checkIfTabContentIsLoaded(); }); - }); - - } - - function openEditorSettingsOverlay(dataType, isNew) { - vm.editorSettingsOverlay = { - title: localizationService.localize("contentTypeEditor_editorSettings"), - dataType: dataType, - view: "views/common/overlays/contenttypeeditor/editorsettings/editorsettings.html", - show: true, - submit: function(model) { - var preValues = dataTypeHelper.createPreValueProps(model.dataType.preValues); - - dataTypeResource.save(model.dataType, preValues, isNew).then(function(newDataType) { - - contentTypeResource.getPropertyTypeScaffold(newDataType.id).then(function(propertyType) { - - submitOverlay(newDataType, propertyType, true); - - vm.editorSettingsOverlay.show = false; - vm.editorSettingsOverlay = null; - - }); - + } + function checkIfTabContentIsLoaded() { + if (vm.tabsLoaded === 2) { + vm.loading = false; + vm.showTabs = true; + } + } + function filterItems() { + // clear item details + $scope.model.itemDetails = null; + if (vm.searchTerm) { + vm.showFilterResult = true; + vm.showTabs = false; + } else { + vm.showFilterResult = false; + vm.showTabs = true; + } + } + function showDetailsOverlay(property) { + var propertyDetails = {}; + propertyDetails.icon = property.icon; + propertyDetails.title = property.name; + $scope.model.itemDetails = propertyDetails; + } + function hideDetailsOverlay() { + $scope.model.itemDetails = null; + } + function pickEditor(editor) { + var parentId = -1; + dataTypeResource.getScaffold(parentId).then(function (dataType) { + // set alias + dataType.selectedEditor = editor.alias; + // set name + var nameArray = []; + if ($scope.model.contentTypeName) { + nameArray.push($scope.model.contentTypeName); + } + if ($scope.model.property.label) { + nameArray.push($scope.model.property.label); + } + if (editor.name) { + nameArray.push(editor.name); + } + // make name + dataType.name = nameArray.join(' - '); + // get pre values + dataTypeResource.getPreValues(dataType.selectedEditor).then(function (preValues) { + dataType.preValues = preValues; + openEditorSettingsOverlay(dataType, true); }); - } - }; - + }); + } + function pickDataType(selectedDataType) { + dataTypeResource.getById(selectedDataType.id).then(function (dataType) { + contentTypeResource.getPropertyTypeScaffold(dataType.id).then(function (propertyType) { + submitOverlay(dataType, propertyType, false); + }); + }); + } + function openEditorSettingsOverlay(dataType, isNew) { + vm.editorSettingsOverlay = { + title: localizationService.localize('contentTypeEditor_editorSettings'), + dataType: dataType, + view: 'views/common/overlays/contenttypeeditor/editorsettings/editorsettings.html', + show: true, + submit: function (model) { + var preValues = dataTypeHelper.createPreValueProps(model.dataType.preValues); + dataTypeResource.save(model.dataType, preValues, isNew).then(function (newDataType) { + contentTypeResource.getPropertyTypeScaffold(newDataType.id).then(function (propertyType) { + submitOverlay(newDataType, propertyType, true); + vm.editorSettingsOverlay.show = false; + vm.editorSettingsOverlay = null; + }); + }); + } + }; + } + function submitOverlay(dataType, propertyType, isNew) { + // update property + $scope.model.property.config = propertyType.config; + $scope.model.property.editor = propertyType.editor; + $scope.model.property.view = propertyType.view; + $scope.model.property.dataTypeId = dataType.id; + $scope.model.property.dataTypeIcon = dataType.icon; + $scope.model.property.dataTypeName = dataType.name; + $scope.model.updateSameDataTypes = isNew; + $scope.model.submit($scope.model); + } + activate(); } - - function submitOverlay(dataType, propertyType, isNew) { - - // update property - $scope.model.property.config = propertyType.config; - $scope.model.property.editor = propertyType.editor; - $scope.model.property.view = propertyType.view; - $scope.model.property.dataTypeId = dataType.id; - $scope.model.property.dataTypeIcon = dataType.icon; - $scope.model.property.dataTypeName = dataType.name; - - $scope.model.updateSameDataTypes = isNew; - - $scope.model.submit($scope.model); - - } - - activate(); - - } - - angular.module("umbraco").controller("Umbraco.Overlays.EditorPickerOverlay", EditorPickerOverlay); - -})(); - -/** + angular.module('umbraco').controller('Umbraco.Overlays.EditorPickerOverlay', EditorPickerOverlay); + }()); + /** * @ngdoc controller * @name Umbraco.Editors.DocumentType.PropertyController * @function @@ -2594,481 +2487,358 @@ angular.module("umbraco").controller("Umbraco.Notifications.ConfirmRouteChangeCo * @description * The controller for the content type editor property dialog */ - - (function() { - "use strict"; - - function PropertySettingsOverlay($scope, contentTypeResource, dataTypeResource, dataTypeHelper, localizationService) { - - var vm = this; - - vm.showValidationPattern = false; - vm.focusOnPatternField = false; - vm.focusOnMandatoryField = false; - vm.selectedValidationType = {}; - vm.validationTypes = [ - { - "name": localizationService.localize("validation_validateAsEmail"), - "key": "email", - "pattern": "[a-zA-Z0-9_\.\+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-\.]+", - "enableEditing": true - }, - { - "name": localizationService.localize("validation_validateAsNumber"), - "key": "number", - "pattern": "^[0-9]*$", - "enableEditing": true - }, - { - "name": localizationService.localize("validation_validateAsUrl"), - "key": "url", - "pattern": "https?\:\/\/[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,}", - "enableEditing": true - }, - { - "name": localizationService.localize("validation_enterCustomValidation"), - "key": "custom", - "pattern": "", - "enableEditing": true - } - ]; - - vm.changeValidationType = changeValidationType; - vm.changeValidationPattern = changeValidationPattern; - vm.openEditorPickerOverlay = openEditorPickerOverlay; - vm.openEditorSettingsOverlay = openEditorSettingsOverlay; - - function activate() { - - matchValidationType(); - - } - - function changeValidationPattern() { - matchValidationType(); - } - - function openEditorPickerOverlay(property) { - - vm.focusOnMandatoryField = false; - - vm.editorPickerOverlay = {}; - vm.editorPickerOverlay.property = $scope.model.property; - vm.editorPickerOverlay.contentTypeName = $scope.model.contentTypeName; - vm.editorPickerOverlay.view = "views/common/overlays/contenttypeeditor/editorpicker/editorpicker.html"; - vm.editorPickerOverlay.show = true; - - vm.editorPickerOverlay.submit = function(model) { - - $scope.model.updateSameDataTypes = model.updateSameDataTypes; - - vm.focusOnMandatoryField = true; - - // update property - property.config = model.property.config; - property.editor = model.property.editor; - property.view = model.property.view; - property.dataTypeId = model.property.dataTypeId; - property.dataTypeIcon = model.property.dataTypeIcon; - property.dataTypeName = model.property.dataTypeName; - - vm.editorPickerOverlay.show = false; - vm.editorPickerOverlay = null; - }; - - vm.editorPickerOverlay.close = function(model) { - vm.editorPickerOverlay.show = false; - vm.editorPickerOverlay = null; - }; - - } - - function openEditorSettingsOverlay(property) { - - vm.focusOnMandatoryField = false; - - // get data type - dataTypeResource.getById(property.dataTypeId).then(function(dataType) { - - vm.editorSettingsOverlay = {}; - vm.editorSettingsOverlay.title = "Editor settings"; - vm.editorSettingsOverlay.view = "views/common/overlays/contenttypeeditor/editorsettings/editorsettings.html"; - vm.editorSettingsOverlay.dataType = dataType; - vm.editorSettingsOverlay.show = true; - - vm.editorSettingsOverlay.submit = function(model) { - - var preValues = dataTypeHelper.createPreValueProps(model.dataType.preValues); - - dataTypeResource.save(model.dataType, preValues, false).then(function(newDataType) { - - contentTypeResource.getPropertyTypeScaffold(newDataType.id).then(function(propertyType) { - - // update editor - property.config = propertyType.config; - property.editor = propertyType.editor; - property.view = propertyType.view; - property.dataTypeId = newDataType.id; - property.dataTypeIcon = newDataType.icon; - property.dataTypeName = newDataType.name; - - // set flag to update same data types - $scope.model.updateSameDataTypes = true; - - vm.focusOnMandatoryField = true; - - vm.editorSettingsOverlay.show = false; - vm.editorSettingsOverlay = null; - - }); - - }); - - }; - - vm.editorSettingsOverlay.close = function(oldModel) { - vm.editorSettingsOverlay.show = false; - vm.editorSettingsOverlay = null; - }; - - }); - - } - - function matchValidationType() { - - if($scope.model.property.validation.pattern !== null && $scope.model.property.validation.pattern !== "" && $scope.model.property.validation.pattern !== undefined) { - - var match = false; - - // find and show if a match from the list has been chosen - angular.forEach(vm.validationTypes, function(validationType, index){ - if($scope.model.property.validation.pattern === validationType.pattern) { - vm.selectedValidationType = vm.validationTypes[index]; - vm.showValidationPattern = true; - match = true; - } - }); - - // if there is no match - choose the custom validation option. - if(!match) { - angular.forEach(vm.validationTypes, function(validationType){ - if(validationType.key === "custom") { - vm.selectedValidationType = validationType; - vm.showValidationPattern = true; - } - }); - } - } - - } - - function changeValidationType(selectedValidationType) { - - if(selectedValidationType) { - $scope.model.property.validation.pattern = selectedValidationType.pattern; - vm.showValidationPattern = true; - - // set focus on textarea - if(selectedValidationType.key === "custom") { - vm.focusOnPatternField = true; - } - - } else { - $scope.model.property.validation.pattern = ""; + (function () { + 'use strict'; + function PropertySettingsOverlay($scope, contentTypeResource, dataTypeResource, dataTypeHelper, localizationService, userService) { + var vm = this; vm.showValidationPattern = false; - } - - } - - activate(); - - } - - angular.module("umbraco").controller("Umbraco.Overlay.PropertySettingsOverlay", PropertySettingsOverlay); - -})(); - - (function() { - "use strict"; - - function CopyOverlay($scope, localizationService, eventsService) { - - var vm = this; - - if(!$scope.model.title) { - $scope.model.title = localizationService.localize("general_copy"); - } - - vm.hideSearch = hideSearch; - vm.selectResult = selectResult; - vm.onSearchResults = onSearchResults; - - var dialogOptions = $scope.model; - var searchText = "Search..."; - var node = dialogOptions.currentNode; - - localizationService.localize("general_search").then(function (value) { - searchText = value + "..."; - }); - - $scope.model.relateToOriginal = true; - $scope.dialogTreeEventHandler = $({}); - - vm.searchInfo = { - searchFromId: null, - searchFromName: null, - showSearch: false, - results: [], - selectedSearchResults: [] - }; - - function nodeSelectHandler(ev, args) { - args.event.preventDefault(); - args.event.stopPropagation(); - - if (args.node.metaData.listViewNode) { - //check if list view 'search' node was selected - - vm.searchInfo.showSearch = true; - vm.searchInfo.searchFromId = args.node.metaData.listViewNode.id; - vm.searchInfo.searchFromName = args.node.metaData.listViewNode.name; - } - else { - //eventsService.emit("editors.content.copyController.select", args); - - if ($scope.model.target) { - //un-select if there's a current one selected - $scope.model.target.selected = false; - } - - $scope.model.target = args.node; - $scope.model.target.selected = true; - } - - } - - 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"] - } - ]; - } - }); - } - } - - function hideSearch() { - vm.searchInfo.showSearch = false; - vm.searchInfo.searchFromId = null; - vm.searchInfo.searchFromName = null; - vm.searchInfo.results = []; - } - - // method to select a search result - function selectResult(evt, result) { - result.selected = result.selected === true ? false : true; - nodeSelectHandler(evt, { event: evt, node: result }); - } - - //callback when there are search results - function onSearchResults(results) { - vm.searchInfo.results = results; - vm.searchInfo.showSearch = true; - } - - $scope.dialogTreeEventHandler.bind("treeNodeSelect", nodeSelectHandler); - $scope.dialogTreeEventHandler.bind("treeNodeExpanded", nodeExpandedHandler); - - $scope.$on('$destroy', function () { - $scope.dialogTreeEventHandler.unbind("treeNodeSelect", nodeSelectHandler); - $scope.dialogTreeEventHandler.unbind("treeNodeExpanded", nodeExpandedHandler); - }); - - - } - - angular.module("umbraco").controller("Umbraco.Overlays.CopyOverlay", CopyOverlay); - -})(); - -(function() { - "use strict"; - - function EmbedOverlay($scope, $http, umbRequestHelper, localizationService) { - - var vm = this; - var origWidth = 500; - var origHeight = 300; - - if(!$scope.model.title) { - $scope.model.title = localizationService.localize("general_embed"); - } - - $scope.model.embed = { - url: "", - width: 360, - height: 240, - constrain: true, - preview: "", - success: false, - info: "", - supportsDimensions: "" - }; - - vm.showPreview = showPreview; - vm.changeSize = changeSize; - - function showPreview() { - - if ($scope.model.embed.url) { - $scope.model.embed.show = true; - $scope.model.embed.preview = "
    "; - $scope.model.embed.info = ""; - $scope.model.embed.success = false; - - $http({ - method: 'GET', - url: umbRequestHelper.getApiUrl("embedApiBaseUrl", "GetEmbed"), - params: { - url: $scope.model.embed.url, - width: $scope.model.embed.width, - height: $scope.model.embed.height - } - }) - .success(function(data) { - - $scope.model.embed.preview = ""; - - switch (data.Status) { - case 0: - //not supported - $scope.model.embed.info = "Not supported"; - break; - case 1: - //error - $scope.model.embed.info = "Could not embed media - please ensure the URL is valid"; - break; - case 2: - $scope.model.embed.preview = data.Markup; - $scope.model.embed.supportsDimensions = data.SupportsDimensions; - $scope.model.embed.success = true; - break; - } - }) - .error(function() { - $scope.model.embed.supportsDimensions = false; - $scope.model.embed.preview = ""; - $scope.model.embed.info = "Could not embed media - please ensure the URL is valid"; - }); - } else { - $scope.model.embed.supportsDimensions = false; - $scope.model.embed.preview = ""; - $scope.model.embed.info = "Please enter a URL"; - } - } - - function changeSize(type) { - - var width, height; - - if ($scope.model.embed.constrain) { - width = parseInt($scope.model.embed.width, 10); - height = parseInt($scope.model.embed.height, 10); - if (type == 'width') { - origHeight = Math.round((width / origWidth) * height); - $scope.model.embed.height = origHeight; - } else { - origWidth = Math.round((height / origHeight) * width); - $scope.model.embed.width = origWidth; + vm.focusOnPatternField = false; + vm.focusOnMandatoryField = false; + vm.selectedValidationType = {}; + vm.validationTypes = [ + { + 'name': localizationService.localize('validation_validateAsEmail'), + 'key': 'email', + 'pattern': '[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+.[a-zA-Z0-9-.]+', + 'enableEditing': true + }, + { + 'name': localizationService.localize('validation_validateAsNumber'), + 'key': 'number', + 'pattern': '^[0-9]*$', + 'enableEditing': true + }, + { + 'name': localizationService.localize('validation_validateAsUrl'), + 'key': 'url', + 'pattern': 'https?://[a-zA-Z0-9-.]+.[a-zA-Z]{2,}', + 'enableEditing': true + }, + { + 'name': localizationService.localize('validation_enterCustomValidation'), + 'key': 'custom', + 'pattern': '', + 'enableEditing': true + } + ]; + vm.changeValidationType = changeValidationType; + vm.changeValidationPattern = changeValidationPattern; + vm.openEditorPickerOverlay = openEditorPickerOverlay; + vm.openEditorSettingsOverlay = openEditorSettingsOverlay; + userService.getCurrentUser().then(function (user) { + vm.showSensitiveData = user.userGroups.indexOf('sensitiveData') != -1; + }); + function activate() { + matchValidationType(); } - } - if ($scope.model.embed.url !== "") { - showPreview(); - } - - } - - } - - angular.module("umbraco").controller("Umbraco.Overlays.EmbedOverlay", EmbedOverlay); - -})(); - -angular.module("umbraco") - .controller("Umbraco.Overlays.HelpController", function ($scope, $location, $routeParams, helpService, userService, localizationService) { + function changeValidationPattern() { + matchValidationType(); + } + function openEditorPickerOverlay(property) { + vm.focusOnMandatoryField = false; + vm.editorPickerOverlay = {}; + vm.editorPickerOverlay.property = $scope.model.property; + vm.editorPickerOverlay.contentTypeName = $scope.model.contentTypeName; + vm.editorPickerOverlay.view = 'views/common/overlays/contenttypeeditor/editorpicker/editorpicker.html'; + vm.editorPickerOverlay.show = true; + vm.editorPickerOverlay.submit = function (model) { + $scope.model.updateSameDataTypes = model.updateSameDataTypes; + vm.focusOnMandatoryField = true; + // update property + property.config = model.property.config; + property.editor = model.property.editor; + property.view = model.property.view; + property.dataTypeId = model.property.dataTypeId; + property.dataTypeIcon = model.property.dataTypeIcon; + property.dataTypeName = model.property.dataTypeName; + vm.editorPickerOverlay.show = false; + vm.editorPickerOverlay = null; + }; + vm.editorPickerOverlay.close = function (model) { + vm.editorPickerOverlay.show = false; + vm.editorPickerOverlay = null; + }; + } + function openEditorSettingsOverlay(property) { + vm.focusOnMandatoryField = false; + // get data type + dataTypeResource.getById(property.dataTypeId).then(function (dataType) { + vm.editorSettingsOverlay = {}; + vm.editorSettingsOverlay.title = 'Editor settings'; + vm.editorSettingsOverlay.view = 'views/common/overlays/contenttypeeditor/editorsettings/editorsettings.html'; + vm.editorSettingsOverlay.dataType = dataType; + vm.editorSettingsOverlay.show = true; + vm.editorSettingsOverlay.submit = function (model) { + var preValues = dataTypeHelper.createPreValueProps(model.dataType.preValues); + dataTypeResource.save(model.dataType, preValues, false).then(function (newDataType) { + contentTypeResource.getPropertyTypeScaffold(newDataType.id).then(function (propertyType) { + // update editor + property.config = propertyType.config; + property.editor = propertyType.editor; + property.view = propertyType.view; + property.dataTypeId = newDataType.id; + property.dataTypeIcon = newDataType.icon; + property.dataTypeName = newDataType.name; + // set flag to update same data types + $scope.model.updateSameDataTypes = true; + vm.focusOnMandatoryField = true; + vm.editorSettingsOverlay.show = false; + vm.editorSettingsOverlay = null; + }); + }); + }; + vm.editorSettingsOverlay.close = function (oldModel) { + vm.editorSettingsOverlay.show = false; + vm.editorSettingsOverlay = null; + }; + }); + } + function matchValidationType() { + if ($scope.model.property.validation.pattern !== null && $scope.model.property.validation.pattern !== '' && $scope.model.property.validation.pattern !== undefined) { + var match = false; + // find and show if a match from the list has been chosen + angular.forEach(vm.validationTypes, function (validationType, index) { + if ($scope.model.property.validation.pattern === validationType.pattern) { + vm.selectedValidationType = vm.validationTypes[index]; + vm.showValidationPattern = true; + match = true; + } + }); + // if there is no match - choose the custom validation option. + if (!match) { + angular.forEach(vm.validationTypes, function (validationType) { + if (validationType.key === 'custom') { + vm.selectedValidationType = validationType; + vm.showValidationPattern = true; + } + }); + } + } + } + function changeValidationType(selectedValidationType) { + if (selectedValidationType) { + $scope.model.property.validation.pattern = selectedValidationType.pattern; + vm.showValidationPattern = true; + // set focus on textarea + if (selectedValidationType.key === 'custom') { + vm.focusOnPatternField = true; + } + } else { + $scope.model.property.validation.pattern = ''; + vm.showValidationPattern = false; + } + } + activate(); + } + angular.module('umbraco').controller('Umbraco.Overlay.PropertySettingsOverlay', PropertySettingsOverlay); + }()); + (function () { + 'use strict'; + function CopyOverlay($scope, localizationService, eventsService, entityHelper) { + var vm = this; + if (!$scope.model.title) { + $scope.model.title = localizationService.localize('general_copy'); + } + vm.hideSearch = hideSearch; + vm.selectResult = selectResult; + vm.onSearchResults = onSearchResults; + var dialogOptions = $scope.model; + var searchText = 'Search...'; + var node = dialogOptions.currentNode; + localizationService.localize('general_search').then(function (value) { + searchText = value + '...'; + }); + $scope.model.relateToOriginal = true; + $scope.dialogTreeEventHandler = $({}); + vm.searchInfo = { + searchFromId: null, + searchFromName: null, + showSearch: false, + results: [], + selectedSearchResults: [] + }; + // get entity type based on the section + $scope.entityType = entityHelper.getEntityTypeFromSection(dialogOptions.section); + function nodeSelectHandler(ev, args) { + if (args && args.event) { + args.event.preventDefault(); + args.event.stopPropagation(); + } + //eventsService.emit("editors.content.copyController.select", args); + if ($scope.model.target) { + //un-select if there's a current one selected + $scope.model.target.selected = false; + } + $scope.model.target = args.node; + $scope.model.target.selected = true; + } + function nodeExpandedHandler(ev, args) { + // open mini list view for list views + if (args.node.metaData.isContainer) { + openMiniListView(args.node); + } + } + function hideSearch() { + vm.searchInfo.showSearch = false; + vm.searchInfo.searchFromId = null; + vm.searchInfo.searchFromName = null; + vm.searchInfo.results = []; + } + // method to select a search result + function selectResult(evt, result) { + result.selected = result.selected === true ? false : true; + nodeSelectHandler(evt, { + event: evt, + node: result + }); + } + //callback when there are search results + function onSearchResults(results) { + vm.searchInfo.results = results; + vm.searchInfo.showSearch = true; + } + $scope.dialogTreeEventHandler.bind('treeNodeSelect', nodeSelectHandler); + $scope.dialogTreeEventHandler.bind('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 }); + }; + $scope.closeMiniListView = function () { + $scope.miniListView = undefined; + }; + function openMiniListView(node) { + $scope.miniListView = node; + } + } + angular.module('umbraco').controller('Umbraco.Overlays.CopyOverlay', CopyOverlay); + }()); + (function () { + 'use strict'; + function EmbedOverlay($scope, $http, umbRequestHelper, localizationService) { + var vm = this; + var origWidth = 500; + var origHeight = 300; + if (!$scope.model.title) { + $scope.model.title = localizationService.localize('general_embed'); + } + $scope.model.embed = { + url: '', + width: 360, + height: 240, + constrain: true, + preview: '', + success: false, + info: '', + supportsDimensions: '' + }; + vm.showPreview = showPreview; + vm.changeSize = changeSize; + function showPreview() { + if ($scope.model.embed.url) { + $scope.model.embed.show = true; + $scope.model.embed.preview = '
    '; + $scope.model.embed.info = ''; + $scope.model.embed.success = false; + $http({ + method: 'GET', + url: umbRequestHelper.getApiUrl('embedApiBaseUrl', 'GetEmbed'), + params: { + url: $scope.model.embed.url, + width: $scope.model.embed.width, + height: $scope.model.embed.height + } + }).success(function (data) { + $scope.model.embed.preview = ''; + switch (data.Status) { + case 0: + //not supported + $scope.model.embed.info = 'Not supported'; + break; + case 1: + //error + $scope.model.embed.info = 'Could not embed media - please ensure the URL is valid'; + break; + case 2: + $scope.model.embed.preview = data.Markup; + $scope.model.embed.supportsDimensions = data.SupportsDimensions; + $scope.model.embed.success = true; + break; + } + }).error(function () { + $scope.model.embed.supportsDimensions = false; + $scope.model.embed.preview = ''; + $scope.model.embed.info = 'Could not embed media - please ensure the URL is valid'; + }); + } else { + $scope.model.embed.supportsDimensions = false; + $scope.model.embed.preview = ''; + $scope.model.embed.info = 'Please enter a URL'; + } + } + function changeSize(type) { + var width, height; + if ($scope.model.embed.constrain) { + width = parseInt($scope.model.embed.width, 10); + height = parseInt($scope.model.embed.height, 10); + if (type == 'width') { + origHeight = Math.round(width / origWidth * height); + $scope.model.embed.height = origHeight; + } else { + origWidth = Math.round(height / origHeight * width); + $scope.model.embed.width = origWidth; + } + } + if ($scope.model.embed.url !== '') { + showPreview(); + } + } + } + angular.module('umbraco').controller('Umbraco.Overlays.EmbedOverlay', EmbedOverlay); + }()); + angular.module('umbraco').controller('Umbraco.Overlays.HelpController', function ($scope, $location, $routeParams, helpService, userService, localizationService) { $scope.section = $routeParams.section; - $scope.version = Umbraco.Sys.ServerVariables.application.version + " assembly: " + Umbraco.Sys.ServerVariables.application.assemblyVersion; - $scope.model.subtitle = "Umbraco version" + " " + $scope.version; - - if(!$scope.model.title) { - $scope.model.title = localizationService.localize("general_help"); + $scope.version = Umbraco.Sys.ServerVariables.application.version + ' assembly: ' + Umbraco.Sys.ServerVariables.application.assemblyVersion; + $scope.model.subtitle = 'Umbraco version' + ' ' + $scope.version; + if (!$scope.model.title) { + $scope.model.title = localizationService.localize('general_help'); } - - if(!$scope.section){ - $scope.section = "content"; + if (!$scope.section) { + $scope.section = 'content'; } - $scope.sectionName = $scope.section; - var rq = {}; rq.section = $scope.section; - //translate section name - localizationService.localize("sections_" + rq.section).then(function (value) { + localizationService.localize('sections_' + rq.section).then(function (value) { $scope.sectionName = value; }); - - userService.getCurrentUser().then(function(user){ - - rq.usertype = user.userType; - rq.lang = user.locale; - - if($routeParams.url){ - rq.path = decodeURIComponent($routeParams.url); - - if(rq.path.indexOf(Umbraco.Sys.ServerVariables.umbracoSettings.umbracoPath) === 0){ - rq.path = rq.path.substring(Umbraco.Sys.ServerVariables.umbracoSettings.umbracoPath.length); - } - - if(rq.path.indexOf(".aspx") > 0){ - rq.path = rq.path.substring(0, rq.path.indexOf(".aspx")); - } - - }else{ - rq.path = rq.section + "/" + $routeParams.tree + "/" + $routeParams.method; - } - - helpService.findHelp(rq).then(function(topics){ - $scope.topics = topics; - }); - - helpService.findVideos(rq).then(function(videos){ - $scope.videos = videos; - }); - + userService.getCurrentUser().then(function (user) { + rq.lang = user.locale; + if ($routeParams.url) { + rq.path = decodeURIComponent($routeParams.url); + if (rq.path.indexOf(Umbraco.Sys.ServerVariables.umbracoSettings.umbracoPath) === 0) { + rq.path = rq.path.substring(Umbraco.Sys.ServerVariables.umbracoSettings.umbracoPath.length); + } + if (rq.path.indexOf('.aspx') > 0) { + rq.path = rq.path.substring(0, rq.path.indexOf('.aspx')); + } + } else { + rq.path = rq.section + '/' + $routeParams.tree + '/' + $routeParams.method; + } + helpService.findHelp(rq).then(function (topics) { + $scope.topics = topics; + }); + helpService.findVideos(rq).then(function (videos) { + $scope.videos = videos; + }); }); - - }); - -/** + /** * @ngdoc controller * @name Umbraco.Editors.DocumentType.PropertyController * @function @@ -3076,241 +2846,535 @@ angular.module("umbraco") * @description * The controller for the content type editor property dialog */ -function IconPickerOverlay($scope, iconHelper, localizationService) { - - $scope.loading = true; - $scope.model.hideSubmitButton = true; - - if (!$scope.model.title) { - $scope.model.title = localizationService.localize("defaultdialogs_selectIcon"); - } - - iconHelper.getIcons().then(function(icons) { - $scope.icons = icons; - $scope.loading = false; - }); - - $scope.selectIcon = function(icon, color) { - $scope.model.icon = icon; - $scope.model.color = color; - $scope.submitForm($scope.model); - }; - -} - -angular.module("umbraco").controller("Umbraco.Overlays.IconPickerOverlay", IconPickerOverlay); - -function ItemPickerOverlay($scope, localizationService) { - - if (!$scope.model.title) { - $scope.model.title = localizationService.localize("defaultdialogs_selectItem"); - } - - $scope.model.hideSubmitButton = true; - - $scope.selectItem = function(item) { - $scope.model.selectedItem = item; - $scope.submitForm($scope.model); - }; - -} - -angular.module("umbraco").controller("Umbraco.Overlays.ItemPickerOverlay", ItemPickerOverlay); - -//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; - - 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: [] - }; - - 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) { - - if (!$scope.model.target.path) { - entityResource.getPath($scope.model.target.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($scope.model.target.id).then(function (url) { - $scope.model.target.url = url; - }); - } - } - - function nodeSelectHandler(ev, args) { - args.event.preventDefault(); - args.event.stopPropagation(); - - if (args.node.metaData.listViewNode) { - //check if list view 'search' node was selected - - $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 ($scope.currentNode) { - //un-select if there's a current one selected - $scope.currentNode.selected = false; - } - - $scope.currentNode = args.node; - $scope.currentNode.selected = true; - $scope.model.target.id = args.node.id; - $scope.model.target.name = args.node.name; - - if (args.node.id < 0) { - $scope.model.target.url = "/"; - } - else { - contentResource.getNiceUrl(args.node.id).then(function (url) { - $scope.model.target.url = url; - }); - } - - if (!angular.isUndefined($scope.model.target.isMedia)) { - delete $scope.model.target.isMedia; - } - } - } - - 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) { - $scope.mediaPickerOverlay = { - view: "mediapicker", - startNodeId: userData.startMediaId, - show: true, - submit: function(model) { - var media = model.selectedImages[0]; - - $scope.model.target.id = media.id; - $scope.model.target.isMedia = true; - $scope.model.target.name = media.name; - $scope.model.target.url = mediaHelper.resolveFile(media); - - $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 = []; - } - - // 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; - }; - - $scope.dialogTreeEventHandler.bind("treeNodeSelect", nodeSelectHandler); - $scope.dialogTreeEventHandler.bind("treeNodeExpanded", nodeExpandedHandler); - - $scope.$on('$destroy', function () { - $scope.dialogTreeEventHandler.unbind("treeNodeSelect", nodeSelectHandler); - $scope.dialogTreeEventHandler.unbind("treeNodeExpanded", nodeExpandedHandler); - }); - }); - -function MacroPickerController($scope, entityResource, macroResource, umbPropEditorHelper, macroService, formHelper, localizationService) { - - - if(!$scope.model.title) { - $scope.model.title = localizationService.localize("defaultdialogs_selectMacro"); - } - - $scope.macros = []; - $scope.model.selectedMacro = null; - $scope.wizardStep = "macroSelect"; - $scope.model.macroParams = []; - $scope.noMacroParams = false; - - $scope.changeMacro = function() { - if ($scope.wizardStep === "macroSelect") { - editParams(); - } else { - submitForm(); + function IconPickerOverlay($scope, iconHelper, localizationService) { + $scope.loading = true; + $scope.model.hideSubmitButton = false; + $scope.colors = [ + { + name: 'Black', + value: 'color-black' + }, + { + name: 'Blue Grey', + value: 'color-blue-grey' + }, + { + name: 'Grey', + value: 'color-grey' + }, + { + name: 'Brown', + value: 'color-brown' + }, + { + name: 'Blue', + value: 'color-blue' + }, + { + name: 'Light Blue', + value: 'color-light-blue' + }, + { + name: 'Indigo', + value: 'color-indigo' + }, + { + name: 'Purple', + value: 'color-purple' + }, + { + name: 'Deep Purple', + value: 'color-deep-purple' + }, + { + name: 'Cyan', + value: 'color-cyan' + }, + { + name: 'Green', + value: 'color-green' + }, + { + name: 'Light Green', + value: 'color-light-green' + }, + { + name: 'Lime', + value: 'color-lime' + }, + { + name: 'Yellow', + value: 'color-yellow' + }, + { + name: 'Amber', + value: 'color-amber' + }, + { + name: 'Orange', + value: 'color-orange' + }, + { + name: 'Deep Orange', + value: 'color-deep-orange' + }, + { + name: 'Red', + value: 'color-red' + }, + { + name: 'Pink', + value: 'color-pink' + } + ]; + if (!$scope.color) { + // Set default selected color to black + $scope.color = $scope.colors[0].value; } - }; - - /** changes the view to edit the params of the selected macro */ - function editParams() { - //get the macro params if there are any - macroResource.getMacroParameters($scope.model.selectedMacro.id) - .then(function (data) { - + ; + if (!$scope.model.title) { + $scope.model.title = localizationService.localize('defaultdialogs_selectIcon'); + } + ; + if ($scope.model.color) { + $scope.color = $scope.model.color; + } + ; + if ($scope.model.icon) { + $scope.icon = $scope.model.icon; + } + ; + iconHelper.getIcons().then(function (icons) { + $scope.icons = icons; + $scope.loading = false; + }); + $scope.selectIcon = function (icon, color) { + $scope.model.icon = icon; + $scope.model.color = color; + $scope.submitForm($scope.model); + }; + var unsubscribe = $scope.$on('formSubmitting', function () { + if ($scope.color) { + $scope.model.color = $scope.color; + } + }); + //when the scope is destroyed we need to unsubscribe + $scope.$on('$destroy', function () { + unsubscribe(); + }); + } + angular.module('umbraco').controller('Umbraco.Overlays.IconPickerOverlay', IconPickerOverlay); + (function () { + 'use strict'; + function InsertOverlayController($scope, localizationService) { + var vm = this; + if (!$scope.model.title) { + $scope.model.title = localizationService.localize('template_insert'); + } + if (!$scope.model.subtitle) { + $scope.model.subtitle = localizationService.localize('template_insertDesc'); + } + vm.openMacroPicker = openMacroPicker; + vm.openPageFieldOverlay = openPageFieldOverlay; + vm.openDictionaryItemOverlay = openDictionaryItemOverlay; + vm.openPartialOverlay = openPartialOverlay; + function openMacroPicker() { + vm.macroPickerOverlay = { + view: 'macropicker', + title: localizationService.localize('template_insertMacro'), + dialogData: {}, + show: true, + submit: function (model) { + $scope.model.insert = { + 'type': 'macro', + 'macroParams': model.macroParams, + 'selectedMacro': model.selectedMacro + }; + $scope.model.submit($scope.model); + vm.macroPickerOverlay.show = false; + vm.macroPickerOverlay = null; + } + }; + } + function openPageFieldOverlay() { + vm.pageFieldOverlay = { + title: localizationService.localize('template_insertPageField'), + description: localizationService.localize('template_insertPageFieldDesc'), + submitButtonLabel: 'Insert', + closeButtonlabel: 'Cancel', + view: 'insertfield', + show: true, + submit: function (model) { + $scope.model.insert = { + 'type': 'umbracoField', + 'umbracoField': model.umbracoField + }; + $scope.model.submit($scope.model); + vm.pageFieldOverlay.show = false; + vm.pageFieldOverlay = null; + }, + close: function (model) { + vm.pageFieldOverlay.show = false; + vm.pageFieldOverlay = null; + } + }; + } + function openDictionaryItemOverlay() { + vm.dictionaryItemOverlay = { + view: 'treepicker', + section: 'settings', + treeAlias: 'dictionary', + entityType: 'dictionary', + multiPicker: false, + title: localizationService.localize('template_insertDictionaryItem'), + description: localizationService.localize('template_insertDictionaryItemDesc'), + emptyStateMessage: localizationService.localize('emptyStates_emptyDictionaryTree'), + show: true, + select: function (node) { + $scope.model.insert = { + 'type': 'dictionary', + 'node': node + }; + $scope.model.submit($scope.model); + vm.dictionaryItemOverlay.show = false; + vm.dictionaryItemOverlay = null; + }, + close: function (model) { + vm.dictionaryItemOverlay.show = false; + vm.dictionaryItemOverlay = null; + } + }; + } + function openPartialOverlay() { + vm.partialItemOverlay = { + view: 'treepicker', + section: 'settings', + treeAlias: 'partialViews', + entityType: 'partialView', + multiPicker: false, + filter: function (i) { + if (i.name.indexOf('.cshtml') === -1 && i.name.indexOf('.vbhtml') === -1) { + return true; + } + }, + filterCssClass: 'not-allowed', + title: localizationService.localize('template_insertPartialView'), + show: true, + select: function (node) { + $scope.model.insert = { + 'type': 'partial', + 'node': node + }; + $scope.model.submit($scope.model); + vm.partialItemOverlay.show = false; + vm.partialItemOverlay = null; + }, + close: function (model) { + vm.partialItemOverlay.show = false; + vm.partialItemOverlay = null; + } + }; + } + } + angular.module('umbraco').controller('Umbraco.Overlays.InsertOverlay', InsertOverlayController); + }()); + (function () { + 'use strict'; + function InsertFieldController($scope, $http, contentTypeResource) { + var vm = this; + vm.field; + vm.altField; + vm.altText; + vm.insertBefore; + vm.insertAfter; + vm.recursive = false; + vm.properties = []; + vm.standardFields = []; + vm.date = false; + vm.dateTime = false; + vm.dateTimeSeparator = ''; + vm.casingUpper = false; + vm.casingLower = false; + vm.encodeHtml = false; + vm.encodeUrl = false; + vm.convertLinebreaks = false; + vm.showAltField = false; + vm.showAltText = false; + vm.setDateOption = setDateOption; + vm.setCasingOption = setCasingOption; + vm.setEncodingOption = setEncodingOption; + vm.generateOutputSample = generateOutputSample; + function onInit() { + // set default title + if (!$scope.model.title) { + $scope.model.title = 'Insert value'; + } + // Load all fields + contentTypeResource.getAllPropertyTypeAliases().then(function (array) { + vm.properties = array; + }); + // Load all standard fields + contentTypeResource.getAllStandardFields().then(function (array) { + vm.standardFields = array; + }); + } + // date formatting + function setDateOption(option) { + if (option === 'date') { + if (vm.date) { + vm.date = false; + } else { + vm.date = true; + vm.dateTime = false; + } + } + if (option === 'dateWithTime') { + if (vm.dateTime) { + vm.dateTime = false; + } else { + vm.date = false; + vm.dateTime = true; + } + } + } + // casing formatting + function setCasingOption(option) { + if (option === 'uppercase') { + if (vm.casingUpper) { + vm.casingUpper = false; + } else { + vm.casingUpper = true; + vm.casingLower = false; + } + } + if (option === 'lowercase') { + if (vm.casingLower) { + vm.casingLower = false; + } else { + vm.casingUpper = false; + vm.casingLower = true; + } + } + } + // encoding formatting + function setEncodingOption(option) { + if (option === 'html') { + if (vm.encodeHtml) { + vm.encodeHtml = false; + } else { + vm.encodeHtml = true; + vm.encodeUrl = false; + } + } + if (option === 'url') { + if (vm.encodeUrl) { + vm.encodeUrl = false; + } else { + vm.encodeHtml = false; + vm.encodeUrl = true; + } + } + } + function generateOutputSample() { + var pageField = (vm.field !== undefined ? '@Umbraco.Field("' + vm.field + '"' : '') + (vm.altField !== undefined ? ', altFieldAlias:"' + vm.altField + '"' : '') + (vm.altText !== undefined ? ', altText:"' + vm.altText + '"' : '') + (vm.insertBefore !== undefined ? ', insertBefore:"' + vm.insertBefore + '"' : '') + (vm.insertAfter !== undefined ? ', insertAfter:"' + vm.insertAfter + '"' : '') + (vm.recursive !== false ? ', recursive: ' + vm.recursive : '') + (vm.date !== false ? ', formatAsDate: ' + vm.date : '') + (vm.dateTime !== false ? ', formatAsDateWithTimeSeparator:"' + vm.dateTimeSeparator + '"' : '') + (vm.casingUpper !== false ? ', casing: ' + 'RenderFieldCaseType.Upper' : '') + (vm.casingLower !== false ? ', casing: ' + 'RenderFieldCaseType.Lower' : '') + (vm.encodeHtml !== false ? ', encoding: ' + 'RenderFieldEncodingType.Html' : '') + (vm.encodeUrl !== false ? ', encoding: ' + 'RenderFieldEncodingType.Url' : '') + (vm.convertLinebreaks !== false ? ', convertLineBreaks: ' + 'true' : '') + (vm.field ? ')' : ''); + $scope.model.umbracoField = pageField; + return pageField; + } + onInit(); + } + angular.module('umbraco').controller('Umbraco.Overlays.InsertFieldController', InsertFieldController); + }()); + function ItemPickerOverlay($scope, localizationService) { + if (!$scope.model.title) { + $scope.model.title = localizationService.localize('defaultdialogs_selectItem'); + } + $scope.model.hideSubmitButton = true; + $scope.selectItem = function (item) { + $scope.model.selectedItem = item; + $scope.submitForm($scope.model); + }; + } + angular.module('umbraco').controller('Umbraco.Overlays.ItemPickerOverlay', ItemPickerOverlay); + //used for the media picker dialog + angular.module('umbraco').controller('Umbraco.Overlays.LinkPickerController', 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 + '...'; + }); + 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.showTarget = $scope.model.hideTarget !== true; + 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) { + //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) { + 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' + }); + }); + } + // 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 + // only do the substring if there's a # or a ? + var indexOfAnchor = $scope.model.target.url.search(/(#|\?)/); + if (indexOfAnchor > -1) { + // populate the anchor + $scope.model.target.anchor = $scope.model.target.url.substring(indexOfAnchor); + // then rewrite the model and populate the link + $scope.model.target.url = $scope.model.target.url.substring(0, indexOfAnchor); + } + } + } else if (dialogOptions.anchors) { + $scope.anchorValues = dialogOptions.anchors; + } + function nodeSelectHandler(ev, args) { + if (args && args.event) { + args.event.preventDefault(); + args.event.stopPropagation(); + } + eventsService.emit('dialogs.linkPicker.select', args); + if ($scope.currentNode) { + //un-select if there's a current one selected + $scope.currentNode.selected = false; + } + $scope.currentNode = args.node; + $scope.currentNode.selected = true; + $scope.model.target.id = args.node.id; + $scope.model.target.udi = args.node.udi; + $scope.model.target.name = args.node.name; + if (args.node.id < 0) { + $scope.model.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]; + }); + } + if (!angular.isUndefined($scope.model.target.isMedia)) { + delete $scope.model.target.isMedia; + } + } + 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.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.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 + }); + }; + //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.$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 }); + }; + $scope.closeMiniListView = function () { + $scope.miniListView = undefined; + }; + function openMiniListView(node) { + $scope.miniListView = node; + } + }); + function MacroPickerController($scope, entityResource, macroResource, umbPropEditorHelper, macroService, formHelper, localizationService) { + if (!$scope.model.title) { + $scope.model.title = localizationService.localize('defaultdialogs_selectMacro'); + } + $scope.macros = []; + $scope.model.selectedMacro = null; + $scope.model.macroParams = []; + $scope.wizardStep = 'macroSelect'; + $scope.noMacroParams = false; + $scope.selectMacro = function (macro) { + $scope.model.selectedMacro = macro; + if ($scope.wizardStep === 'macroSelect') { + editParams(true); + } else { + $scope.model.submit($scope.model); + } + }; + /** changes the view to edit the params of the selected macro */ + /** if there is pnly one macro, and it has parameters - editor can skip selecting the Macro **/ + function editParams(insertIfNoParameters) { + //whether to insert the macro in the rich text editor when editParams is called and there are no parameters see U4-10537 + insertIfNoParameters = typeof insertIfNoParameters !== 'undefined' ? insertIfNoParameters : true; + //get the macro params if there are any + macroResource.getMacroParameters($scope.model.selectedMacro.id).then(function (data) { //go to next page if there are params otherwise we can just exit if (!angular.isArray(data) || data.length === 0) { - - $scope.noMacroParams = true; - + if (insertIfNoParameters) { + $scope.model.submit($scope.model); + } else { + $scope.wizardStep = 'macroSelect'; + } } else { - $scope.wizardStep = "paramSelect"; + $scope.wizardStep = 'paramSelect'; $scope.model.macroParams = data; - //fill in the data if we are editing this macro if ($scope.model.dialogData && $scope.model.dialogData.macroData && $scope.model.dialogData.macroData.macroParamsDictionary) { _.each($scope.model.dialogData.macroData.macroParamsDictionary, function (val, key) { @@ -3318,59 +3382,48 @@ function MacroPickerController($scope, entityResource, macroResource, umbPropEdi return item.alias == key; }); if (prop) { - if (_.isString(val)) { //we need to unescape values as they have most likely been escaped while inserted val = _.unescape(val); - //detect if it is a json string if (val.detectIsJson()) { try { //Parse it to json prop.value = angular.fromJson(val); - } - catch (e) { + } catch (e) { // not json prop.value = val; } - } - else { + } else { prop.value = val; } - } - else { + } else { prop.value = val; } } }); - } } - }); - } - - //here we check to see if we've been passed a selected macro and if so we'll set the - //editor to start with parameter editing - if ($scope.model.dialogData && $scope.model.dialogData.macroData) { - $scope.wizardStep = "paramSelect"; - } - - //get the macro list - pass in a filter if it is only for rte - entityResource.getAll("Macro", ($scope.model.dialogData && $scope.model.dialogData.richTextEditor && $scope.model.dialogData.richTextEditor === true) ? "UseInEditor=true" : null) - .then(function (data) { - + } + //here we check to see if we've been passed a selected macro and if so we'll set the + //editor to start with parameter editing + if ($scope.model.dialogData && $scope.model.dialogData.macroData) { + $scope.wizardStep = 'paramSelect'; + } + //get the macro list - pass in a filter if it is only for rte + entityResource.getAll('Macro', $scope.model.dialogData && $scope.model.dialogData.richTextEditor && $scope.model.dialogData.richTextEditor === true ? 'UseInEditor=true' : null).then(function (data) { + if (angular.isArray(data) && data.length == 0) { + $scope.nomacros = true; + } //if 'allowedMacros' is specified, we need to filter if (angular.isArray($scope.model.dialogData.allowedMacros) && $scope.model.dialogData.allowedMacros.length > 0) { - $scope.macros = _.filter(data, function(d) { + $scope.macros = _.filter(data, function (d) { return _.contains($scope.model.dialogData.allowedMacros, d.alias); }); - } - else { + } else { $scope.macros = data; } - - //check if there's a pre-selected macro and if it exists if ($scope.model.dialogData && $scope.model.dialogData.macroData && $scope.model.dialogData.macroData.macroAlias) { var found = _.find(data, function (item) { @@ -3379,1002 +3432,1232 @@ function MacroPickerController($scope, entityResource, macroResource, umbPropEdi if (found) { //select the macro and go to next screen $scope.model.selectedMacro = found; - editParams(); + editParams(true); return; } } - //we don't have a pre-selected macro so ensure the correct step is set - $scope.wizardStep = "macroSelect"; + //if there is only one macro in the site and it has parameters, let's not make the editor choose it from a selection of one macro (unless there are no parameters - then weirdly it's a better experience to make that selection) + if ($scope.macros.length == 1) { + $scope.model.selectedMacro = $scope.macros[0]; + editParams(false); + } else { + //we don't have a pre-selected macro so ensure the correct step is set + $scope.wizardStep = 'macroSelect'; + } }); - - -} - -angular.module("umbraco").controller("Umbraco.Overlays.MacroPickerController", MacroPickerController); - -//used for the media picker dialog -angular.module("umbraco") - .controller("Umbraco.Overlays.MediaPickerController", - function($scope, mediaResource, umbRequestHelper, entityResource, $log, mediaHelper, mediaTypeHelper, eventsService, treeService, $element, $timeout, $cookies, $cookieStore, localizationService) { - - if (!$scope.model.title) { - $scope.model.title = localizationService.localize("defaultdialogs_selectMedia"); - } - - var dialogOptions = $scope.model; - - $scope.disableFolderSelect = dialogOptions.disableFolderSelect; - $scope.onlyImages = dialogOptions.onlyImages; - $scope.showDetails = dialogOptions.showDetails; - $scope.multiPicker = (dialogOptions.multiPicker && dialogOptions.multiPicker !== "0") ? true : false; - $scope.startNodeId = dialogOptions.startNodeId ? dialogOptions.startNodeId : -1; - $scope.cropSize = dialogOptions.cropSize; - $scope.lastOpenedNode = $cookieStore.get("umbLastOpenedMediaNodeId"); - if ($scope.onlyImages) { - $scope.acceptedFileTypes = mediaHelper - .formatFileTypes(Umbraco.Sys.ServerVariables.umbracoSettings.imageFileTypes); - } else { - $scope.acceptedFileTypes = !mediaHelper - .formatFileTypes(Umbraco.Sys.ServerVariables.umbracoSettings.disallowedUploadFiles); - } - $scope.maxFileSize = Umbraco.Sys.ServerVariables.umbracoSettings.maxFileSize + "KB"; - - $scope.model.selectedImages = []; - - $scope.acceptedMediatypes = []; - mediaTypeHelper.getAllowedImagetypes($scope.startNodeId) - .then(function(types) { - $scope.acceptedMediatypes = types; - }); - - //preload selected item - $scope.target = undefined; - if (dialogOptions.currentTarget) { - $scope.target = dialogOptions.currentTarget; - } - - $scope.upload = function(v) { - angular.element(".umb-file-dropzone-directive .file-select").click(); - }; - - $scope.dragLeave = function(el, event) { - $scope.activeDrag = false; - }; - - $scope.dragEnter = function(el, event) { - $scope.activeDrag = true; - }; - - $scope.submitFolder = function() { - if ($scope.newFolderName) { - mediaResource - .addFolder($scope.newFolderName, $scope.currentFolder.id) - .then(function(data) { - //we've added a new folder so lets clear the tree cache for that specific item - treeService.clearCache({ - cacheKey: "__media", //this is the main media tree cache key - childrenOf: data.parentId //clear the children of the parent - }); - - $scope.gotoFolder(data); - $scope.showFolderInput = false; - $scope.newFolderName = ""; - }); - } else { - $scope.showFolderInput = false; - } - }; - - $scope.enterSubmitFolder = function(event) { - if (event.keyCode === 13) { - $scope.submitFolder(); - event.stopPropagation(); - } - }; - - $scope.gotoFolder = function(folder) { - - if (!$scope.multiPicker) { - deselectAllImages($scope.model.selectedImages); - } - - if (!folder) { - folder = { id: -1, name: "Media", icon: "icon-folder" }; - } - - if (folder.id > 0) { - entityResource.getAncestors(folder.id, "media") - .then(function(anc) { - // anc.splice(0,1); - $scope.path = _.filter(anc, - function(f) { - return f.path.indexOf($scope.startNodeId) !== -1; - }); - }); - - mediaTypeHelper.getAllowedImagetypes(folder.id) - .then(function(types) { - $scope.acceptedMediatypes = types; - }); - } else { - $scope.path = []; - } - - //mediaResource.rootMedia() - mediaResource.getChildren(folder.id) - .then(function(data) { - $scope.searchTerm = ""; - $scope.images = data.items ? data.items : []; - - // set already selected images to selected - for (var folderImageIndex = 0; folderImageIndex < $scope.images.length; folderImageIndex++) { - var folderImage = $scope.images[folderImageIndex]; - var imageIsSelected = false; - - for (var selectedImageIndex = 0; - selectedImageIndex < $scope.model.selectedImages.length; - selectedImageIndex++) { - var selectedImage = $scope.model.selectedImages[selectedImageIndex]; - - if (folderImage.key === selectedImage.key) { - imageIsSelected = true; - } - } - if (imageIsSelected) { - folderImage.selected = true; - } - } - }); - $scope.currentFolder = folder; - - // for some reason i cannot set cookies with cookieStore - document.cookie = "umbLastOpenedMediaNodeId=" + folder.id; - - }; - - $scope.clickHandler = function(image, event, index) { - if (image.isFolder) { - if ($scope.disableFolderSelect) { - $scope.gotoFolder(image); - } else { - eventsService.emit("dialogs.mediaPicker.select", image); - selectImage(image); - } - } else { - eventsService.emit("dialogs.mediaPicker.select", image); - if ($scope.showDetails) { - $scope.target = image; - $scope.target.url = mediaHelper.resolveFile(image); - $scope.openDetailsDialog(); - } else { - selectImage(image); - } - } - }; - - $scope.clickItemName = function(item) { - if (item.isFolder) { - $scope.gotoFolder(item); - } - }; - - function selectImage(image) { - if (image.selected) { - for (var i = 0; $scope.model.selectedImages.length > i; i++) { - var imageInSelection = $scope.model.selectedImages[i]; - if (image.key === imageInSelection.key) { - image.selected = false; - $scope.model.selectedImages.splice(i, 1); - } - } - } else { - if (!$scope.multiPicker) { - deselectAllImages($scope.model.selectedImages); - } - image.selected = true; - $scope.model.selectedImages.push(image); - } - } - - function deselectAllImages(images) { - for (var i = 0; i < images.length; i++) { - var image = images[i]; - image.selected = false; - } - images.length = 0; - } - - $scope.onUploadComplete = function() { - $scope.gotoFolder($scope.currentFolder); - }; - - $scope.onFilesQueue = function() { - $scope.activeDrag = false; - }; - - //default root item - if (!$scope.target) { - if ($scope.lastOpenedNode && $scope.lastOpenedNode !== -1) { - entityResource.getById($scope.lastOpenedNode, "media") - .then(function(node) { - // make sure that las opened node is on the same path as start node - var nodePath = node.path.split(","); - - if (nodePath.indexOf($scope.startNodeId.toString()) !== -1) { - $scope - .gotoFolder({ id: $scope.lastOpenedNode, name: "Media", icon: "icon-folder" }); - } else { - $scope.gotoFolder({ id: $scope.startNodeId, name: "Media", icon: "icon-folder" }); - } - }, - function(err) { - $scope.gotoFolder({ id: $scope.startNodeId, name: "Media", icon: "icon-folder" }); - }); - } else { - $scope.gotoFolder({ id: $scope.startNodeId, name: "Media", icon: "icon-folder" }); - } - } - - $scope.openDetailsDialog = function() { - - $scope.mediaPickerDetailsOverlay = {}; - $scope.mediaPickerDetailsOverlay.show = true; - - $scope.mediaPickerDetailsOverlay.submit = function(model) { - $scope.model.selectedImages.push($scope.target); - $scope.model.submit($scope.model); - - $scope.mediaPickerDetailsOverlay.show = false; - $scope.mediaPickerDetailsOverlay = null; - }; - - $scope.mediaPickerDetailsOverlay.close = function(oldModel) { - $scope.mediaPickerDetailsOverlay.show = false; - $scope.mediaPickerDetailsOverlay = null; - }; - }; - }); - -angular.module("umbraco").controller("Umbraco.Overlays.MediaTypePickerController", - function ($scope) { - - $scope.select = function(mediatype){ - $scope.model.selectedType = mediatype; - $scope.model.submit($scope.model); - $scope.model.show = false; - } - - }); - -//used for the member picker dialog -angular.module("umbraco").controller("Umbraco.Overlays.MemberGroupPickerController", - function($scope, eventsService, entityResource, searchService, $log, localizationService) { - - if(!$scope.model.title) { - $scope.model.title = localizationService.localize("defaultdialogs_selectMemberGroup"); - } - - $scope.dialogTreeEventHandler = $({}); - $scope.multiPicker = $scope.model.multiPicker; - - function activate() { - - if($scope.multiPicker) { - $scope.model.selectedMemberGroups = []; - } else { - $scope.model.selectedMemberGroup = ""; - } - - } - - function selectMemberGroup(id) { - $scope.model.selectedMemberGroup = id; - } - - function selectMemberGroups(id) { - $scope.model.selectedMemberGroups.push(id); - } - - /** Method used for selecting a node */ - function select(text, id) { - - if ($scope.model.multiPicker) { - selectMemberGroups(id); - } - else { - selectMemberGroup(id); - $scope.model.submit($scope.model); - } - } - - function nodeSelectHandler(ev, args) { - args.event.preventDefault(); - args.event.stopPropagation(); - - eventsService.emit("dialogs.memberGroupPicker.select", args); - - //This is a tree node, so we don't have an entity to pass in, it will need to be looked up - //from the server in this method. - select(args.node.name, args.node.id); - - //toggle checked state - args.node.selected = args.node.selected === true ? false : true; - } - - $scope.dialogTreeEventHandler.bind("treeNodeSelect", nodeSelectHandler); - - $scope.$on('$destroy', function () { - $scope.dialogTreeEventHandler.unbind("treeNodeSelect", nodeSelectHandler); - }); - - activate(); - - }); - - (function() { - "use strict"; - - function MoveOverlay($scope, localizationService, eventsService) { - - var vm = this; - - vm.hideSearch = hideSearch; - vm.selectResult = selectResult; - vm.onSearchResults = onSearchResults; - - var dialogOptions = $scope.model; - var searchText = "Search..."; - var node = dialogOptions.currentNode; - - localizationService.localize("general_search").then(function (value) { - searchText = value + "..."; - }); - - if(!$scope.model.title) { - $scope.model.title = localizationService.localize("actions_move"); - } - - $scope.model.relateToOriginal = true; - $scope.dialogTreeEventHandler = $({}); - - vm.searchInfo = { - searchFromId: null, - searchFromName: null, - showSearch: false, - results: [], - selectedSearchResults: [] - }; - - function nodeSelectHandler(ev, args) { - args.event.preventDefault(); - args.event.stopPropagation(); - - if (args.node.metaData.listViewNode) { - //check if list view 'search' node was selected - - vm.searchInfo.showSearch = true; - vm.searchInfo.searchFromId = args.node.metaData.listViewNode.id; - vm.searchInfo.searchFromName = args.node.metaData.listViewNode.name; - } - else { - //eventsService.emit("editors.content.copyController.select", args); - - if ($scope.model.target) { - //un-select if there's a current one selected - $scope.model.target.selected = false; - } - - $scope.model.target = args.node; - $scope.model.target.selected = true; - } - - } - - 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"] - } - ]; - } - }); - } - } - - function hideSearch() { - vm.searchInfo.showSearch = false; - vm.searchInfo.searchFromId = null; - vm.searchInfo.searchFromName = null; - vm.searchInfo.results = []; - } - - // method to select a search result - function selectResult(evt, result) { - result.selected = result.selected === true ? false : true; - nodeSelectHandler(evt, { event: evt, node: result }); - } - - //callback when there are search results - function onSearchResults(results) { - vm.searchInfo.results = results; - vm.searchInfo.showSearch = true; - } - - $scope.dialogTreeEventHandler.bind("treeNodeSelect", nodeSelectHandler); - $scope.dialogTreeEventHandler.bind("treeNodeExpanded", nodeExpandedHandler); - - $scope.$on('$destroy', function () { - $scope.dialogTreeEventHandler.unbind("treeNodeSelect", nodeSelectHandler); - $scope.dialogTreeEventHandler.unbind("treeNodeExpanded", nodeExpandedHandler); - }); - - - } - - angular.module("umbraco").controller("Umbraco.Overlays.MoveOverlay", MoveOverlay); - -})(); - -//used for the media picker dialog -angular.module("umbraco").controller("Umbraco.Overlays.TreePickerController", - function ($scope, entityResource, eventsService, $log, searchService, angularHelper, $timeout, localizationService, treeService) { - - var tree = null; - var dialogOptions = $scope.model; - $scope.dialogTreeEventHandler = $({}); - $scope.section = dialogOptions.section; - $scope.treeAlias = dialogOptions.treeAlias; - $scope.multiPicker = dialogOptions.multiPicker; - $scope.hideHeader = true; - $scope.searchInfo = { - searchFromId: dialogOptions.startNodeId, - searchFromName: null, - showSearch: false, - results: [], - selectedSearchResults: [] - } - - $scope.model.selection = []; - - $scope.init = function(contentType) { - - if(contentType === "content") { - entityType = "Document"; - if(!$scope.model.title) { - $scope.model.title = localizationService.localize("defaultdialogs_selectContent"); - } - } else if(contentType === "member") { - entityType = "Member"; - if(!$scope.model.title) { - $scope.model.title = localizationService.localize("defaultdialogs_selectMember"); - } - } else if(contentType === "media") { - entityType = "Media"; - if(!$scope.model.title) { - $scope.model.title = localizationService.localize("defaultdialogs_selectMedia"); - } - } - } - - //create the custom query string param for this tree - $scope.customTreeParams = dialogOptions.startNodeId ? "startNodeId=" + dialogOptions.startNodeId : ""; - $scope.customTreeParams += dialogOptions.customTreeParams ? "&" + dialogOptions.customTreeParams : ""; - - var searchText = "Search..."; - localizationService.localize("general_search").then(function (value) { - searchText = value + "..."; - }); - - // Allow the entity type to be passed in but defaults to Document for backwards compatibility. - var entityType = dialogOptions.entityType ? dialogOptions.entityType : "Document"; - - - //min / max values - if (dialogOptions.minNumber) { - dialogOptions.minNumber = parseInt(dialogOptions.minNumber, 10); - } - if (dialogOptions.maxNumber) { - dialogOptions.maxNumber = parseInt(dialogOptions.maxNumber, 10); - } - - if (dialogOptions.section === "member") { - entityType = "Member"; - } - else if (dialogOptions.section === "media") { - entityType = "Media"; - } - - //Configures filtering - if (dialogOptions.filter) { - - dialogOptions.filterExclude = false; - dialogOptions.filterAdvanced = false; - - //used advanced filtering - if (angular.isFunction(dialogOptions.filter)) { - dialogOptions.filterAdvanced = true; - } - else if (angular.isObject(dialogOptions.filter)) { - dialogOptions.filterAdvanced = true; - } - else { - if (dialogOptions.filter.startsWith("!")) { - dialogOptions.filterExclude = true; - dialogOptions.filter = dialogOptions.filter.substring(1); - } - - //used advanced filtering - if (dialogOptions.filter.startsWith("{")) { - dialogOptions.filterAdvanced = true; - //convert to object - dialogOptions.filter = angular.fromJson(dialogOptions.filter); - } - } - } - - 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 some custom - // children: A node to activate the search, any nodes that have already been - // selected in the search - if (child.metaData.isContainer) { - child.hasChildren = true; - child.children = [ - { - level: child.level + 1, - hasChildren: false, - parent: function () { - return child; - }, - name: searchText, - metaData: { - listViewNode: child, - }, - cssClass: "icon-search", - cssClasses: ["not-published"] - } - ]; - //add base transition classes to this node - child.cssClasses.push("tree-node-slide-up"); - - var listViewResults = _.filter($scope.searchInfo.selectedSearchResults, function(i) { - return i.parentId == child.id; - }); - _.each(listViewResults, function(item) { - child.children.unshift({ - id: item.id, - name: item.name, - cssClass: "icon umb-tree-icon sprTree " + item.icon, - level: child.level + 1, - metaData: { - isSearchResult: true - }, - hasChildren: false, - parent: function () { - return child; - } - }); - }); - } - - //now we need to look in the already selected search results and - // toggle the check boxes for those ones that are listed - var exists = _.find($scope.searchInfo.selectedSearchResults, function (selected) { - return child.id == selected.id; - }); - if (exists) { - child.selected = true; - } - }); - - //check filter - performFiltering(args.children); - } - } - - //gets the tree object when it loads - function treeLoadedHandler(ev, args) { - tree = args.tree; - } - - //wires up selection - function nodeSelectHandler(ev, args) { - args.event.preventDefault(); - args.event.stopPropagation(); - - if (args.node.metaData.listViewNode) { - //check if list view 'search' node was selected - - $scope.searchInfo.showSearch = true; - $scope.searchInfo.searchFromId = args.node.metaData.listViewNode.id; - $scope.searchInfo.searchFromName = args.node.metaData.listViewNode.name; - - //add transition classes - var listViewNode = args.node.parent(); - listViewNode.cssClasses.push('tree-node-slide-up-hide-active'); - } - else if (args.node.metaData.isSearchResult) { - //check if the item selected was a search result from a list view - - //unselect - select(args.node.name, args.node.id); - - //remove it from the list view children - var listView = args.node.parent(); - listView.children = _.reject(listView.children, function(child) { - return child.id == args.node.id; - }); - - //remove it from the custom tracked search result list - $scope.searchInfo.selectedSearchResults = _.reject($scope.searchInfo.selectedSearchResults, function (i) { - return i.id == args.node.id; - }); - } - else { - eventsService.emit("dialogs.treePickerController.select", args); - - if (args.node.filtered) { - return; - } - - //This is a tree node, so we don't have an entity to pass in, it will need to be looked up - //from the server in this method. - select(args.node.name, args.node.id); - - //toggle checked state - args.node.selected = args.node.selected === true ? false : true; - } - } - - /** Method used for selecting a node */ - function select(text, id, entity) { - //if we get the root, we just return a constructed entity, no need for server data - if (id < 0) { - if ($scope.multiPicker) { - - if (entity) { - multiSelectItem(entity); - } else { - //otherwise we have to get it from the server - entityResource.getById(id, entityType).then(function (ent) { - multiSelectItem(ent); - }); - } - - } - else { - var node = { - alias: null, - icon: "icon-folder", - id: id, - name: text - }; - $scope.model.selection.push(node); - $scope.model.submit($scope.model); - } - } - else { - - if ($scope.multiPicker) { - - if (entity) { - multiSelectItem(entity); - } else { - //otherwise we have to get it from the server - entityResource.getById(id, entityType).then(function (ent) { - multiSelectItem(ent); - }); - } - - } - - else { - - $scope.hideSearch(); - - //if an entity has been passed in, use it - if (entity) { - $scope.model.selection.push(entity); - $scope.model.submit($scope.model); - } else { - //otherwise we have to get it from the server - entityResource.getById(id, entityType).then(function (ent) { - $scope.model.selection.push(ent); - $scope.model.submit($scope.model); - }); - } - } - } - } - - function multiSelectItem(item) { - - var found = false; - var foundIndex = 0; - - if($scope.model.selection.length > 0) { - for(i = 0; $scope.model.selection.length > i; i++) { - var selectedItem = $scope.model.selection[i]; - if(selectedItem.id === item.id) { - found = true; - foundIndex = i; - } - } - } - - if(found) { - $scope.model.selection.splice(foundIndex, 1); - } else { - $scope.model.selection.push(item); - } - - } - - function performFiltering(nodes) { - - if (!dialogOptions.filter) { - return; - } - - //remove any list view search nodes from being filtered since these are special nodes that always must - // be allowed to be clicked on - nodes = _.filter(nodes, function(n) { - return !angular.isObject(n.metaData.listViewNode); - }); - - if (dialogOptions.filterAdvanced) { - - //filter either based on a method or an object - var filtered = angular.isFunction(dialogOptions.filter) - ? _.filter(nodes, dialogOptions.filter) - : _.where(nodes, dialogOptions.filter); - - angular.forEach(filtered, function (value, key) { - value.filtered = true; - if (dialogOptions.filterCssClass) { - if (!value.cssClasses) { - value.cssClasses = []; - } - value.cssClasses.push(dialogOptions.filterCssClass); - } - }); - } else { - var a = dialogOptions.filter.toLowerCase().replace(/\s/g, '').split(','); - angular.forEach(nodes, function (value, key) { - - var found = a.indexOf(value.metaData.contentType.toLowerCase()) >= 0; - - if (!dialogOptions.filterExclude && !found || dialogOptions.filterExclude && found) { - value.filtered = true; - - if (dialogOptions.filterCssClass) { - if (!value.cssClasses) { - value.cssClasses = []; - } - value.cssClasses.push(dialogOptions.filterCssClass); - } - } - }); - } - } - - $scope.multiSubmit = function (result) { - entityResource.getByIds(result, entityType).then(function (ents) { - $scope.submit(ents); - }); - }; - - /** method to select a search result */ - $scope.selectResult = function (evt, result) { - - if (result.filtered) { - return; - } - - result.selected = result.selected === true ? false : true; - - //since result = an entity, we'll pass it in so we don't have to go back to the server - select(result.name, result.id, result); - - //add/remove to our custom tracked list of selected search results - if (result.selected) { - $scope.searchInfo.selectedSearchResults.push(result); - } - else { - $scope.searchInfo.selectedSearchResults = _.reject($scope.searchInfo.selectedSearchResults, function(i) { - return i.id == result.id; - }); - } - - //ensure the tree node in the tree is checked/unchecked if it already exists there - if (tree) { - var found = treeService.getDescendantNode(tree.root, result.id); - if (found) { - found.selected = result.selected; - } - } - - }; - - $scope.hideSearch = function () { - - //Traverse the entire displayed tree and update each node to sync with the selected search results - if (tree) { - - //we need to ensure that any currently displayed nodes that get selected - // from the search get updated to have a check box! - function checkChildren(children) { - _.each(children, function (child) { - //check if the id is in the selection, if so ensure it's flagged as selected - var exists = _.find($scope.searchInfo.selectedSearchResults, function (selected) { - return child.id == selected.id; - }); - //if the curr node exists in selected search results, ensure it's checked - if (exists) { - child.selected = true; - } - //if the curr node does not exist in the selected search result, and the curr node is a child of a list view search result - else if (child.metaData.isSearchResult) { - //if this tree node is under a list view it means that the node was added - // to the tree dynamically under the list view that was searched, so we actually want to remove - // it all together from the tree - var listView = child.parent(); - listView.children = _.reject(listView.children, function(c) { - return c.id == child.id; - }); - } - - //check if the current node is a list view and if so, check if there's any new results - // that need to be added as child nodes to it based on search results selected - if (child.metaData.isContainer) { - - child.cssClasses = _.reject(child.cssClasses, function(c) { - return c === 'tree-node-slide-up-hide-active'; - }); - - var listViewResults = _.filter($scope.searchInfo.selectedSearchResults, function (i) { - return i.parentId == child.id; - }); - _.each(listViewResults, function (item) { - var childExists = _.find(child.children, function(c) { - return c.id == item.id; - }); - if (!childExists) { - var parent = child; - child.children.unshift({ - id: item.id, - name: item.name, - cssClass: "icon umb-tree-icon sprTree " + item.icon, - level: child.level + 1, - metaData: { - isSearchResult: true - }, - hasChildren: false, - parent: function () { - return parent; - } - }); - } - }); - } - - //recurse - if (child.children && child.children.length > 0) { - checkChildren(child.children); - } - }); - } - checkChildren(tree.root.children); - } - - - $scope.searchInfo.showSearch = false; - $scope.searchInfo.searchFromId = dialogOptions.startNodeId; - $scope.searchInfo.searchFromName = null; - $scope.searchInfo.results = []; - } - - $scope.onSearchResults = function(results) { - - //filter all items - this will mark an item as filtered - performFiltering(results); - - //now actually remove all filtered items so they are not even displayed - results = _.filter(results, function(item) { - return !item.filtered; - }); - - $scope.searchInfo.results = results; - - //sync with the curr selected results - _.each($scope.searchInfo.results, function (result) { - var exists = _.find($scope.model.selection, function (selectedId) { - return result.id == selectedId; - }); - if (exists) { - result.selected = true; - } - }); - - $scope.searchInfo.showSearch = true; - }; - - $scope.dialogTreeEventHandler.bind("treeLoaded", treeLoadedHandler); - $scope.dialogTreeEventHandler.bind("treeNodeExpanded", nodeExpandedHandler); - $scope.dialogTreeEventHandler.bind("treeNodeSelect", nodeSelectHandler); - - $scope.$on('$destroy', function () { - $scope.dialogTreeEventHandler.unbind("treeLoaded", treeLoadedHandler); - $scope.dialogTreeEventHandler.unbind("treeNodeExpanded", nodeExpandedHandler); - $scope.dialogTreeEventHandler.unbind("treeNodeSelect", nodeSelectHandler); - }); - }); - -angular.module("umbraco") - .controller("Umbraco.Overlays.UserController", function ($scope, $location, $timeout, userService, historyService, eventsService, externalLoginInfo, authResource, currentUserResource, formHelper, localizationService) { - - $scope.history = historyService.getCurrent(); - $scope.version = Umbraco.Sys.ServerVariables.application.version + " assembly: " + Umbraco.Sys.ServerVariables.application.assemblyVersion; - $scope.showPasswordFields = false; - $scope.changePasswordButtonState = "init"; - $scope.model.subtitle = "Umbraco version" + " " + $scope.version; - - if(!$scope.model.title) { - $scope.model.title = localizationService.localize("general_user"); + } + angular.module('umbraco').controller('Umbraco.Overlays.MacroPickerController', MacroPickerController); + //used for the media picker dialog + angular.module('umbraco').controller('Umbraco.Overlays.MediaPickerController', function ($scope, mediaResource, umbRequestHelper, entityResource, $log, mediaHelper, mediaTypeHelper, eventsService, treeService, $element, $timeout, $cookies, localStorageService, localizationService) { + if (!$scope.model.title) { + $scope.model.title = localizationService.localize('defaultdialogs_selectMedia'); + } + var dialogOptions = $scope.model; + $scope.disableFolderSelect = dialogOptions.disableFolderSelect; + $scope.onlyImages = dialogOptions.onlyImages; + $scope.showDetails = dialogOptions.showDetails; + $scope.multiPicker = dialogOptions.multiPicker && dialogOptions.multiPicker !== '0' ? true : false; + $scope.startNodeId = dialogOptions.startNodeId ? dialogOptions.startNodeId : -1; + $scope.cropSize = dialogOptions.cropSize; + $scope.lastOpenedNode = localStorageService.get('umbLastOpenedMediaNodeId'); + $scope.lockedFolder = true; + var umbracoSettings = Umbraco.Sys.ServerVariables.umbracoSettings; + var allowedUploadFiles = mediaHelper.formatFileTypes(umbracoSettings.allowedUploadFiles); + if ($scope.onlyImages) { + $scope.acceptedFileTypes = mediaHelper.formatFileTypes(umbracoSettings.imageFileTypes); + } else { + // Use whitelist of allowed file types if provided + if (allowedUploadFiles !== '') { + $scope.acceptedFileTypes = allowedUploadFiles; + } else { + // If no whitelist, we pass in a blacklist by adding ! to the file extensions, allowing everything EXCEPT for disallowedUploadFiles + $scope.acceptedFileTypes = !mediaHelper.formatFileTypes(umbracoSettings.disallowedUploadFiles); + } + } + $scope.maxFileSize = umbracoSettings.maxFileSize + 'KB'; + $scope.model.selectedImages = []; + $scope.acceptedMediatypes = []; + mediaTypeHelper.getAllowedImagetypes($scope.startNodeId).then(function (types) { + $scope.acceptedMediatypes = types; + }); + $scope.searchOptions = { + pageNumber: 1, + pageSize: 100, + totalItems: 0, + totalPages: 0, + filter: '' + }; + //preload selected item + $scope.target = undefined; + if (dialogOptions.currentTarget) { + $scope.target = dialogOptions.currentTarget; + } + function onInit() { + if ($scope.startNodeId !== -1) { + entityResource.getById($scope.startNodeId, 'media').then(function (ent) { + $scope.startNodeId = ent.id; + run(); + }); + } else { + run(); + } + } + function run() { + //default root item + if (!$scope.target) { + if ($scope.lastOpenedNode && $scope.lastOpenedNode !== -1) { + entityResource.getById($scope.lastOpenedNode, 'media').then(ensureWithinStartNode, gotoStartNode); + } else { + gotoStartNode(); + } + } else { + //if a target is specified, go look it up - generally this target will just contain ids not the actual full + //media object so we need to look it up + var id = $scope.target.udi ? $scope.target.udi : $scope.target.id; + var altText = $scope.target.altText; + if (id) { + mediaResource.getById(id).then(function (node) { + $scope.target = node; + if (ensureWithinStartNode(node)) { + selectImage(node); + $scope.target.url = mediaHelper.resolveFile(node); + $scope.target.altText = altText; + $scope.openDetailsDialog(); + } + }, gotoStartNode); + } else { + gotoStartNode(); + } + } + } + $scope.upload = function (v) { + angular.element('.umb-file-dropzone-directive .file-select').click(); + }; + $scope.dragLeave = function (el, event) { + $scope.activeDrag = false; + }; + $scope.dragEnter = function (el, event) { + $scope.activeDrag = true; + }; + $scope.submitFolder = function () { + if ($scope.newFolderName) { + $scope.creatingFolder = true; + mediaResource.addFolder($scope.newFolderName, $scope.currentFolder.id).then(function (data) { + //we've added a new folder so lets clear the tree cache for that specific item + treeService.clearCache({ + cacheKey: '__media', + //this is the main media tree cache key + childrenOf: data.parentId + }); + $scope.creatingFolder = false; + $scope.gotoFolder(data); + $scope.showFolderInput = false; + $scope.newFolderName = ''; + }); + } else { + $scope.showFolderInput = false; + } + }; + $scope.enterSubmitFolder = function (event) { + if (event.keyCode === 13) { + $scope.submitFolder(); + event.stopPropagation(); + } + }; + $scope.gotoFolder = function (folder) { + if (!$scope.multiPicker) { + deselectAllImages($scope.model.selectedImages); + } + if (!folder) { + folder = { + id: -1, + name: 'Media', + icon: 'icon-folder' + }; + } + if (folder.id > 0) { + entityResource.getAncestors(folder.id, 'media').then(function (anc) { + $scope.path = _.filter(anc, function (f) { + return f.path.indexOf($scope.startNodeId) !== -1; + }); + }); + } else { + $scope.path = []; + } + mediaTypeHelper.getAllowedImagetypes(folder.id).then(function (types) { + $scope.acceptedMediatypes = types; + }); + $scope.lockedFolder = folder.id === -1 && $scope.model.startNodeIsVirtual; + $scope.currentFolder = folder; + localStorageService.set('umbLastOpenedMediaNodeId', folder.id); + return getChildren(folder.id); + }; + $scope.clickHandler = function (image, event, index) { + if (image.isFolder) { + if ($scope.disableFolderSelect) { + $scope.gotoFolder(image); + } else { + eventsService.emit('dialogs.mediaPicker.select', image); + selectImage(image); + } + } else { + eventsService.emit('dialogs.mediaPicker.select', image); + if ($scope.showDetails) { + $scope.target = image; + // handle both entity and full media object + if (image.image) { + $scope.target.url = image.image; + } else { + $scope.target.url = mediaHelper.resolveFile(image); + } + $scope.openDetailsDialog(); + } else { + selectImage(image); + } + } + }; + $scope.clickItemName = function (item) { + if (item.isFolder) { + $scope.gotoFolder(item); + } + }; + function selectImage(image) { + if (image.selected) { + for (var i = 0; $scope.model.selectedImages.length > i; i++) { + var imageInSelection = $scope.model.selectedImages[i]; + if (image.key === imageInSelection.key) { + image.selected = false; + $scope.model.selectedImages.splice(i, 1); + } + } + } else { + if (!$scope.multiPicker) { + deselectAllImages($scope.model.selectedImages); + } + image.selected = true; + $scope.model.selectedImages.push(image); + } + } + function deselectAllImages(images) { + for (var i = 0; i < images.length; i++) { + var image = images[i]; + image.selected = false; + } + images.length = 0; + } + $scope.onUploadComplete = function (files) { + $scope.gotoFolder($scope.currentFolder).then(function () { + if (files.length === 1 && $scope.model.selectedImages.length === 0) { + selectImage($scope.images[$scope.images.length - 1]); + } + }); + }; + $scope.onFilesQueue = function () { + $scope.activeDrag = false; + }; + function ensureWithinStartNode(node) { + // make sure that last opened node is on the same path as start node + var nodePath = node.path.split(','); + if (nodePath.indexOf($scope.startNodeId.toString()) !== -1) { + $scope.gotoFolder({ + id: $scope.lastOpenedNode, + name: 'Media', + icon: 'icon-folder' + }); + return true; + } else { + $scope.gotoFolder({ + id: $scope.startNodeId, + name: 'Media', + icon: 'icon-folder' + }); + return false; + } + } + function gotoStartNode(err) { + $scope.gotoFolder({ + id: $scope.startNodeId, + name: 'Media', + icon: 'icon-folder' + }); + } + $scope.openDetailsDialog = function () { + $scope.mediaPickerDetailsOverlay = {}; + $scope.mediaPickerDetailsOverlay.show = true; + $scope.mediaPickerDetailsOverlay.submit = function (model) { + $scope.model.selectedImages.push($scope.target); + $scope.model.submit($scope.model); + $scope.mediaPickerDetailsOverlay.show = false; + $scope.mediaPickerDetailsOverlay = null; + }; + $scope.mediaPickerDetailsOverlay.close = function (oldModel) { + $scope.mediaPickerDetailsOverlay.show = false; + $scope.mediaPickerDetailsOverlay = null; + }; + }; + var debounceSearchMedia = _.debounce(function () { + $scope.$apply(function () { + if ($scope.searchOptions.filter) { + searchMedia(); + } else { + // reset pagination + $scope.searchOptions = { + pageNumber: 1, + pageSize: 100, + totalItems: 0, + totalPages: 0, + filter: '' + }; + getChildren($scope.currentFolder.id); + } + }); + }, 500); + $scope.changeSearch = function () { + $scope.loading = true; + debounceSearchMedia(); + }; + $scope.changePagination = function (pageNumber) { + $scope.loading = true; + $scope.searchOptions.pageNumber = pageNumber; + searchMedia(); + }; + function searchMedia() { + $scope.loading = true; + entityResource.getPagedDescendants($scope.startNodeId, 'Media', $scope.searchOptions).then(function (data) { + // update image data to work with image grid + angular.forEach(data.items, function (mediaItem) { + // set thumbnail and src + mediaItem.thumbnail = mediaHelper.resolveFileFromEntity(mediaItem, true); + mediaItem.image = mediaHelper.resolveFileFromEntity(mediaItem, false); + // set properties to match a media object + mediaItem.properties = []; + if (mediaItem.metaData) { + if (mediaItem.metaData.umbracoWidth && mediaItem.metaData.umbracoHeight) { + mediaItem.properties.push({ + alias: 'umbracoWidth', + value: mediaItem.metaData.umbracoWidth.Value + }); + mediaItem.properties.push({ + alias: 'umbracoHeight', + value: mediaItem.metaData.umbracoHeight.Value + }); + } + if (mediaItem.metaData.umbracoFile) { + mediaItem.properties.push({ + alias: 'umbracoFile', + editor: mediaItem.metaData.umbracoFile.PropertyEditorAlias, + value: mediaItem.metaData.umbracoFile.Value + }); + } + } + }); + // update images + $scope.images = data.items ? data.items : []; + // update pagination + if (data.pageNumber > 0) + $scope.searchOptions.pageNumber = data.pageNumber; + if (data.pageSize > 0) + $scope.searchOptions.pageSize = data.pageSize; + $scope.searchOptions.totalItems = data.totalItems; + $scope.searchOptions.totalPages = data.totalPages; + // set already selected images to selected + preSelectImages(); + $scope.loading = false; + }); + } + function getChildren(id) { + $scope.loading = true; + return mediaResource.getChildren(id).then(function (data) { + $scope.searchOptions.filter = ''; + $scope.images = data.items ? data.items : []; + // set already selected images to selected + preSelectImages(); + $scope.loading = false; + }); + } + function preSelectImages() { + for (var folderImageIndex = 0; folderImageIndex < $scope.images.length; folderImageIndex++) { + var folderImage = $scope.images[folderImageIndex]; + var imageIsSelected = false; + if ($scope.model && angular.isArray($scope.model.selectedImages)) { + for (var selectedImageIndex = 0; selectedImageIndex < $scope.model.selectedImages.length; selectedImageIndex++) { + var selectedImage = $scope.model.selectedImages[selectedImageIndex]; + if (folderImage.key === selectedImage.key) { + imageIsSelected = true; + } + } + } + if (imageIsSelected) { + folderImage.selected = true; + } + } + } + onInit(); + }); + angular.module('umbraco').controller('Umbraco.Overlays.MediaTypePickerController', function ($scope) { + $scope.select = function (mediatype) { + $scope.model.selectedType = mediatype; + $scope.model.submit($scope.model); + $scope.model.show = false; + }; + }); + //used for the member picker dialog + angular.module('umbraco').controller('Umbraco.Overlays.MemberGroupPickerController', function ($scope, eventsService, entityResource, searchService, $log, localizationService) { + if (!$scope.model.title) { + $scope.model.title = localizationService.localize('defaultdialogs_selectMemberGroup'); + } + $scope.dialogTreeEventHandler = $({}); + $scope.multiPicker = $scope.model.multiPicker; + function activate() { + if ($scope.multiPicker) { + $scope.model.selectedMemberGroups = []; + } else { + $scope.model.selectedMemberGroup = ''; + } + } + function selectMemberGroup(id) { + $scope.model.selectedMemberGroup = id; + } + function selectMemberGroups(id) { + var index = $scope.model.selectedMemberGroups.indexOf(id); + if (index === -1) { + // If the id does not exists in the array then add it + $scope.model.selectedMemberGroups.push(id); + } else { + // Otherwise we will remove it from the array instead + $scope.model.selectedMemberGroups.splice(index, 1); + } + } + /** Method used for selecting a node */ + function select(text, id) { + if ($scope.model.multiPicker) { + selectMemberGroups(id); + } else { + selectMemberGroup(id); + $scope.model.submit($scope.model); + } + } + function nodeSelectHandler(ev, args) { + args.event.preventDefault(); + args.event.stopPropagation(); + eventsService.emit('dialogs.memberGroupPicker.select', args); + //This is a tree node, so we don't have an entity to pass in, it will need to be looked up + //from the server in this method. + select(args.node.name, args.node.id); + //toggle checked state + args.node.selected = args.node.selected === true ? false : true; + } + $scope.dialogTreeEventHandler.bind('treeNodeSelect', nodeSelectHandler); + $scope.$on('$destroy', function () { + $scope.dialogTreeEventHandler.unbind('treeNodeSelect', nodeSelectHandler); + }); + activate(); + }); + (function () { + 'use strict'; + function MoveOverlay($scope, localizationService, eventsService, entityHelper) { + var vm = this; + vm.hideSearch = hideSearch; + vm.selectResult = selectResult; + vm.onSearchResults = onSearchResults; + var dialogOptions = $scope.model; + var searchText = 'Search...'; + var node = dialogOptions.currentNode; + localizationService.localize('general_search').then(function (value) { + searchText = value + '...'; + }); + if (!$scope.model.title) { + $scope.model.title = localizationService.localize('actions_move'); + } + $scope.model.relateToOriginal = true; + $scope.dialogTreeEventHandler = $({}); + vm.searchInfo = { + searchFromId: null, + searchFromName: null, + showSearch: false, + results: [], + selectedSearchResults: [] + }; + // get entity type based on the section + $scope.entityType = entityHelper.getEntityTypeFromSection(dialogOptions.section); + function nodeSelectHandler(ev, args) { + if (args && args.event) { + args.event.preventDefault(); + args.event.stopPropagation(); + } + //eventsService.emit("editors.content.copyController.select", args); + if ($scope.model.target) { + //un-select if there's a current one selected + $scope.model.target.selected = false; + } + $scope.model.target = args.node; + $scope.model.target.selected = true; + } + function nodeExpandedHandler(ev, args) { + // open mini list view for list views + if (args.node.metaData.isContainer) { + openMiniListView(args.node); + } + } + function hideSearch() { + vm.searchInfo.showSearch = false; + vm.searchInfo.searchFromId = null; + vm.searchInfo.searchFromName = null; + vm.searchInfo.results = []; + } + // method to select a search result + function selectResult(evt, result) { + result.selected = result.selected === true ? false : true; + nodeSelectHandler(evt, { + event: evt, + node: result + }); + } + //callback when there are search results + function onSearchResults(results) { + vm.searchInfo.results = results; + vm.searchInfo.showSearch = true; + } + $scope.dialogTreeEventHandler.bind('treeNodeSelect', nodeSelectHandler); + $scope.dialogTreeEventHandler.bind('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 }); + }; + $scope.closeMiniListView = function () { + $scope.miniListView = undefined; + }; + function openMiniListView(node) { + $scope.miniListView = node; + } + } + angular.module('umbraco').controller('Umbraco.Overlays.MoveOverlay', MoveOverlay); + }()); + (function () { + 'use strict'; + function NodePermissionsController($scope, localizationService) { + var vm = this; + function onInit() { + // set default title + if (!$scope.model.title) { + localizationService.localize('defaultdialogs_permissionsEdit').then(function (value) { + $scope.model.title = value + ' ' + $scope.model.node.name; + }); + } + } + onInit(); + } + angular.module('umbraco').controller('Umbraco.Overlays.NodePermissionsController', NodePermissionsController); + }()); + (function () { + 'use strict'; + function QueryBuilderOverlayController($scope, templateQueryResource, localizationService) { + var everything = ''; + var myWebsite = ''; + var ascendingTranslation = ''; + var descendingTranslation = ''; + var vm = this; + vm.properties = []; + vm.contentTypes = []; + vm.conditions = []; + vm.datePickerConfig = { + pickDate: true, + pickTime: false, + format: 'YYYY-MM-DD' + }; + vm.chooseSource = chooseSource; + vm.getPropertyOperators = getPropertyOperators; + vm.addFilter = addFilter; + vm.trashFilter = trashFilter; + vm.changeSortOrder = changeSortOrder; + vm.setSortProperty = setSortProperty; + vm.setContentType = setContentType; + vm.setFilterProperty = setFilterProperty; + vm.setFilterTerm = setFilterTerm; + vm.changeConstraintValue = changeConstraintValue; + vm.datePickerChange = datePickerChange; + function onInit() { + vm.query = { + contentType: { name: everything }, + source: { name: myWebsite }, + filters: [{ + property: undefined, + operator: undefined + }], + sort: { + property: { + alias: '', + name: '' + }, + direction: 'ascending', + //This is the value for sorting sent to server + translation: { + currentLabel: ascendingTranslation, + //This is the localized UI value in the the dialog + ascending: ascendingTranslation, + descending: descendingTranslation + } + } + }; + templateQueryResource.getAllowedProperties().then(function (properties) { + vm.properties = properties; + }); + templateQueryResource.getContentTypes().then(function (contentTypes) { + vm.contentTypes = contentTypes; + }); + templateQueryResource.getFilterConditions().then(function (conditions) { + vm.conditions = conditions; + }); + throttledFunc(); + } + function chooseSource(query) { + vm.contentPickerOverlay = { + view: 'contentpicker', + show: true, + submit: function (model) { + var selectedNodeId = model.selection[0].id; + var selectedNodeName = model.selection[0].name; + if (selectedNodeId > 0) { + query.source = { + id: selectedNodeId, + name: selectedNodeName + }; + } else { + query.source.name = myWebsite; + delete query.source.id; + } + throttledFunc(); + vm.contentPickerOverlay.show = false; + vm.contentPickerOverlay = null; + }, + close: function (oldModel) { + vm.contentPickerOverlay.show = false; + vm.contentPickerOverlay = null; + } + }; + } + function getPropertyOperators(property) { + var conditions = _.filter(vm.conditions, function (condition) { + var index = condition.appliesTo.indexOf(property.type); + return index >= 0; + }); + return conditions; + } + function addFilter(query) { + query.filters.push({}); + } + function trashFilter(query, filter) { + for (var i = 0; i < query.filters.length; i++) { + if (query.filters[i] == filter) { + query.filters.splice(i, 1); + } + } + //if we remove the last one, add a new one to generate ui for it. + if (query.filters.length == 0) { + query.filters.push({}); + } + } + function changeSortOrder(query) { + if (query.sort.direction === 'ascending') { + query.sort.direction = 'descending'; + query.sort.translation.currentLabel = query.sort.translation.descending; + } else { + query.sort.direction = 'ascending'; + query.sort.translation.currentLabel = query.sort.translation.ascending; + } + throttledFunc(); + } + function setSortProperty(query, property) { + query.sort.property = property; + if (property.type === 'datetime') { + query.sort.direction = 'descending'; + query.sort.translation.currentLabel = query.sort.translation.descending; + } else { + query.sort.direction = 'ascending'; + query.sort.translation.currentLabel = query.sort.translation.ascending; + } + throttledFunc(); + } + function setContentType(contentType) { + vm.query.contentType = contentType; + throttledFunc(); + } + function setFilterProperty(filter, property) { + filter.property = property; + filter.term = {}; + filter.constraintValue = ''; + } + function setFilterTerm(filter, term) { + filter.term = term; + if (filter.constraintValue) { + throttledFunc(); + } + } + function changeConstraintValue() { + throttledFunc(); + } + function datePickerChange(event, filter) { + if (event.date && event.date.isValid()) { + filter.constraintValue = event.date.format(vm.datePickerConfig.format); + throttledFunc(); + } + } + var throttledFunc = _.throttle(function () { + templateQueryResource.postTemplateQuery(vm.query).then(function (response) { + $scope.model.result = response; + }); + }, 200); + localizationService.localizeMany([ + 'template_allContent', + 'template_websiteRoot', + 'template_ascending', + 'template_descending' + ]).then(function (res) { + everything = res[0]; + myWebsite = res[1]; + ascendingTranslation = res[2]; + descendingTranslation = res[3]; + onInit(); + }); + } + angular.module('umbraco').controller('Umbraco.Overlays.QueryBuilderController', QueryBuilderOverlayController); + }()); + (function () { + 'use strict'; + function SectionPickerController($scope, sectionResource, localizationService) { + var vm = this; + vm.sections = []; + vm.loading = false; + vm.selectSection = selectSection; + ////////// + function onInit() { + vm.loading = true; + // set default title + if (!$scope.model.title) { + $scope.model.title = localizationService.localize('defaultdialogs_selectSections'); + } + // make sure we can push to something + if (!$scope.model.selection) { + $scope.model.selection = []; + } + // get sections + sectionResource.getAllSections().then(function (sections) { + vm.sections = sections; + setSectionIcon(vm.sections); + if ($scope.model.selection && $scope.model.selection.length > 0) { + preSelect($scope.model.selection); + } + vm.loading = false; + }); + } + function preSelect(selection) { + angular.forEach(selection, function (selected) { + angular.forEach(vm.sections, function (section) { + if (selected.alias === section.alias) { + section.selected = true; + } + }); + }); + } + function selectSection(section) { + if (!section.selected) { + section.selected = true; + $scope.model.selection.push(section); + } else { + angular.forEach($scope.model.selection, function (selectedSection, index) { + if (selectedSection.alias === section.alias) { + section.selected = false; + $scope.model.selection.splice(index, 1); + } + }); + } + } + function setSectionIcon(sections) { + angular.forEach(sections, function (section) { + section.icon = 'icon-section ' + section.cssclass; + }); + } + onInit(); + } + angular.module('umbraco').controller('Umbraco.Overlays.SectionPickerController', SectionPickerController); + }()); + (function () { + 'use strict'; + function TemplateSectionsOverlayController($scope) { + var vm = this; + $scope.model.mandatoryRenderSection = false; + if (!$scope.model.title) { + $scope.model.title = 'Sections'; + } + vm.select = select; + function onInit() { + if ($scope.model.hasMaster) { + $scope.model.insertType = 'addSection'; + } else { + $scope.model.insertType = 'renderBody'; + } + } + function select(type) { + $scope.model.insertType = type; + } + onInit(); + } + angular.module('umbraco').controller('Umbraco.Overlays.TemplateSectionsOverlay', TemplateSectionsOverlayController); + }()); + //used for the media picker dialog + angular.module('umbraco').controller('Umbraco.Overlays.TreePickerController', function ($scope, $q, entityResource, eventsService, $log, searchService, angularHelper, $timeout, localizationService, treeService, contentResource, mediaResource, memberResource) { + var tree = null; + var dialogOptions = $scope.model; + $scope.treeReady = false; + $scope.dialogTreeEventHandler = $({}); + $scope.section = dialogOptions.section; + $scope.treeAlias = dialogOptions.treeAlias; + $scope.multiPicker = dialogOptions.multiPicker; + $scope.hideHeader = typeof dialogOptions.hideHeader === 'boolean' ? dialogOptions.hideHeader : true; + // if you need to load a not initialized tree set this value to false - default is true + $scope.onlyInitialized = dialogOptions.onlyInitialized; + $scope.searchInfo = { + searchFromId: dialogOptions.startNodeId, + searchFromName: null, + showSearch: false, + results: [], + selectedSearchResults: [] + }; + $scope.model.selection = []; + //Used for toggling an empty-state message + //Some trees can have no items (dictionary & forms email templates) + $scope.hasItems = true; + $scope.emptyStateMessage = dialogOptions.emptyStateMessage; + var node = dialogOptions.currentNode; + //This is called from ng-init + //it turns out it is called from the angular html : / Have a look at views/common / overlays / contentpicker / contentpicker.html you'll see ng-init. + //this is probably an anti pattern IMO and shouldn't be used + $scope.init = function (contentType) { + if (contentType === 'content') { + $scope.entityType = 'Document'; + if (!$scope.model.title) { + $scope.model.title = localizationService.localize('defaultdialogs_selectContent'); + } + } else if (contentType === 'member') { + $scope.entityType = 'Member'; + if (!$scope.model.title) { + $scope.model.title = localizationService.localize('defaultdialogs_selectMember'); + } + } else if (contentType === 'media') { + $scope.entityType = 'Media'; + if (!$scope.model.title) { + $scope.model.title = localizationService.localize('defaultdialogs_selectMedia'); + } + } + }; + var searchText = 'Search...'; + localizationService.localize('general_search').then(function (value) { + searchText = value + '...'; + }); + // Allow the entity type to be passed in but defaults to Document for backwards compatibility. + $scope.entityType = dialogOptions.entityType ? dialogOptions.entityType : 'Document'; + //min / max values + if (dialogOptions.minNumber) { + dialogOptions.minNumber = parseInt(dialogOptions.minNumber, 10); + } + if (dialogOptions.maxNumber) { + dialogOptions.maxNumber = parseInt(dialogOptions.maxNumber, 10); + } + if (dialogOptions.section === 'member') { + $scope.entityType = 'Member'; + } else if (dialogOptions.section === 'media') { + $scope.entityType = 'Media'; + } + // Search and listviews is only working for content, media and member section + var searchableSections = [ + 'content', + 'media', + 'member' + ]; + $scope.enableSearh = searchableSections.indexOf($scope.section) !== -1; + //if a alternative startnode is used, we need to check if it is a container + if ($scope.enableSearh && dialogOptions.startNodeId && dialogOptions.startNodeId !== -1 && dialogOptions.startNodeId !== '-1') { + entityResource.getById(dialogOptions.startNodeId, $scope.entityType).then(function (node) { + if (node.metaData.IsContainer) { + openMiniListView(node); + } + initTree(); + }); + } else { + initTree(); + } + //Configures filtering + if (dialogOptions.filter) { + dialogOptions.filterExclude = false; + dialogOptions.filterAdvanced = false; + //used advanced filtering + if (angular.isFunction(dialogOptions.filter)) { + dialogOptions.filterAdvanced = true; + } else if (angular.isObject(dialogOptions.filter)) { + dialogOptions.filterAdvanced = true; + } else { + if (dialogOptions.filter.startsWith('!')) { + dialogOptions.filterExclude = true; + dialogOptions.filter = dialogOptions.filter.substring(1); + } + //used advanced filtering + if (dialogOptions.filter.startsWith('{')) { + dialogOptions.filterAdvanced = true; + //convert to object + dialogOptions.filter = angular.fromJson(dialogOptions.filter); + } + } + } + function initTree() { + //create the custom query string param for this tree + $scope.customTreeParams = dialogOptions.startNodeId ? 'startNodeId=' + dialogOptions.startNodeId : ''; + $scope.customTreeParams += dialogOptions.customTreeParams ? '&' + dialogOptions.customTreeParams : ''; + $scope.treeReady = true; + } + function nodeExpandedHandler(ev, args) { + // open mini list view for list views + if (args.node.metaData.isContainer) { + openMiniListView(args.node); + } + if (angular.isArray(args.children)) { + //iterate children + _.each(args.children, function (child) { + //now we need to look in the already selected search results and + // toggle the check boxes for those ones that are listed + var exists = _.find($scope.searchInfo.selectedSearchResults, function (selected) { + return child.id == selected.id; + }); + if (exists) { + child.selected = true; + } + }); + //check filter + performFiltering(args.children); + } + } + //gets the tree object when it loads + function treeLoadedHandler(ev, args) { + //args.tree contains children (args.tree.root.children) + $scope.hasItems = args.tree.root.children.length > 0; + tree = args.tree; + var nodeHasPath = typeof node !== 'undefined' && typeof node.path !== 'undefined'; + var startNodeNotDefined = typeof dialogOptions.startNodeId === 'undefined' || dialogOptions.startNodeId === '' || dialogOptions.startNodeId === '-1'; + if (startNodeNotDefined && nodeHasPath) { + $scope.dialogTreeEventHandler.syncTree({ + path: node.path, + activate: false + }); + } + } + //wires up selection + function nodeSelectHandler(ev, args) { + args.event.preventDefault(); + args.event.stopPropagation(); + if (args.node.metaData.isSearchResult) { + //check if the item selected was a search result from a list view + //unselect + select(args.node.name, args.node.id); + //remove it from the list view children + var listView = args.node.parent(); + listView.children = _.reject(listView.children, function (child) { + return child.id == args.node.id; + }); + //remove it from the custom tracked search result list + $scope.searchInfo.selectedSearchResults = _.reject($scope.searchInfo.selectedSearchResults, function (i) { + return i.id == args.node.id; + }); + } else { + eventsService.emit('dialogs.treePickerController.select', args); + if (args.node.filtered) { + return; + } + //This is a tree node, so we don't have an entity to pass in, it will need to be looked up + //from the server in this method. + if ($scope.model.select) { + $scope.model.select(args.node); + } else { + select(args.node.name, args.node.id); + //toggle checked state + args.node.selected = args.node.selected === true ? false : true; + } + } + } + /** Method used for selecting a node */ + function select(text, id, entity) { + //if we get the root, we just return a constructed entity, no need for server data + if (id < 0) { + var rootNode = { + alias: null, + icon: 'icon-folder', + id: id, + name: text + }; + if ($scope.multiPicker) { + if (entity) { + multiSelectItem(entity); + } else { + multiSelectItem(rootNode); + } + } else { + $scope.model.selection.push(rootNode); + $scope.model.submit($scope.model); + } + } else { + if ($scope.multiPicker) { + if (entity) { + multiSelectItem(entity); + } else { + //otherwise we have to get it from the server + entityResource.getById(id, $scope.entityType).then(function (ent) { + multiSelectItem(ent); + }); + } + } else { + $scope.hideSearch(); + //if an entity has been passed in, use it + if (entity) { + $scope.model.selection.push(entity); + $scope.model.submit($scope.model); + } else { + //otherwise we have to get it from the server + entityResource.getById(id, $scope.entityType).then(function (ent) { + $scope.model.selection.push(ent); + $scope.model.submit($scope.model); + }); + } + } + } + } + function multiSelectItem(item) { + var found = false; + var foundIndex = 0; + if ($scope.model.selection.length > 0) { + for (i = 0; $scope.model.selection.length > i; i++) { + var selectedItem = $scope.model.selection[i]; + if (selectedItem.id === item.id) { + found = true; + foundIndex = i; + } + } + } + if (found) { + $scope.model.selection.splice(foundIndex, 1); + } else { + $scope.model.selection.push(item); + } + } + function performFiltering(nodes) { + if (!dialogOptions.filter) { + return; + } + //remove any list view search nodes from being filtered since these are special nodes that always must + // be allowed to be clicked on + nodes = _.filter(nodes, function (n) { + return !angular.isObject(n.metaData.listViewNode); + }); + if (dialogOptions.filterAdvanced) { + //filter either based on a method or an object + var filtered = angular.isFunction(dialogOptions.filter) ? _.filter(nodes, dialogOptions.filter) : _.where(nodes, dialogOptions.filter); + angular.forEach(filtered, function (value, key) { + value.filtered = true; + if (dialogOptions.filterCssClass) { + if (!value.cssClasses) { + value.cssClasses = []; + } + value.cssClasses.push(dialogOptions.filterCssClass); + } + }); + } else { + var a = dialogOptions.filter.toLowerCase().replace(/\s/g, '').split(','); + angular.forEach(nodes, function (value, key) { + var found = a.indexOf(value.metaData.contentType.toLowerCase()) >= 0; + if (!dialogOptions.filterExclude && !found || dialogOptions.filterExclude && found) { + value.filtered = true; + if (dialogOptions.filterCssClass) { + if (!value.cssClasses) { + value.cssClasses = []; + } + value.cssClasses.push(dialogOptions.filterCssClass); + } + } + }); + } + } + $scope.multiSubmit = function (result) { + entityResource.getByIds(result, $scope.entityType).then(function (ents) { + $scope.submit(ents); + }); + }; + /** method to select a search result */ + $scope.selectResult = function (evt, result) { + if (result.filtered) { + return; + } + result.selected = result.selected === true ? false : true; + //since result = an entity, we'll pass it in so we don't have to go back to the server + select(result.name, result.id, result); + //add/remove to our custom tracked list of selected search results + if (result.selected) { + $scope.searchInfo.selectedSearchResults.push(result); + } else { + $scope.searchInfo.selectedSearchResults = _.reject($scope.searchInfo.selectedSearchResults, function (i) { + return i.id == result.id; + }); + } + //ensure the tree node in the tree is checked/unchecked if it already exists there + if (tree) { + var found = treeService.getDescendantNode(tree.root, result.id); + if (found) { + found.selected = result.selected; + } + } + }; + $scope.hideSearch = function () { + //Traverse the entire displayed tree and update each node to sync with the selected search results + if (tree) { + //we need to ensure that any currently displayed nodes that get selected + // from the search get updated to have a check box! + function checkChildren(children) { + _.each(children, function (child) { + //check if the id is in the selection, if so ensure it's flagged as selected + var exists = _.find($scope.searchInfo.selectedSearchResults, function (selected) { + return child.id == selected.id; + }); + //if the curr node exists in selected search results, ensure it's checked + if (exists) { + child.selected = true; + } //if the curr node does not exist in the selected search result, and the curr node is a child of a list view search result + else if (child.metaData.isSearchResult) { + //if this tree node is under a list view it means that the node was added + // to the tree dynamically under the list view that was searched, so we actually want to remove + // it all together from the tree + var listView = child.parent(); + listView.children = _.reject(listView.children, function (c) { + return c.id == child.id; + }); + } + //check if the current node is a list view and if so, check if there's any new results + // that need to be added as child nodes to it based on search results selected + if (child.metaData.isContainer) { + child.cssClasses = _.reject(child.cssClasses, function (c) { + return c === 'tree-node-slide-up-hide-active'; + }); + var listViewResults = _.filter($scope.searchInfo.selectedSearchResults, function (i) { + return i.parentId == child.id; + }); + _.each(listViewResults, function (item) { + var childExists = _.find(child.children, function (c) { + return c.id == item.id; + }); + if (!childExists) { + var parent = child; + child.children.unshift({ + id: item.id, + name: item.name, + cssClass: 'icon umb-tree-icon sprTree ' + item.icon, + level: child.level + 1, + metaData: { isSearchResult: true }, + hasChildren: false, + parent: function () { + return parent; + } + }); + } + }); + } + //recurse + if (child.children && child.children.length > 0) { + checkChildren(child.children); + } + }); + } + checkChildren(tree.root.children); + } + $scope.searchInfo.showSearch = false; + $scope.searchInfo.searchFromId = dialogOptions.startNodeId; + $scope.searchInfo.searchFromName = null; + $scope.searchInfo.results = []; + }; + $scope.onSearchResults = function (results) { + //filter all items - this will mark an item as filtered + performFiltering(results); + //now actually remove all filtered items so they are not even displayed + results = _.filter(results, function (item) { + return !item.filtered; + }); + $scope.searchInfo.results = results; + //sync with the curr selected results + _.each($scope.searchInfo.results, function (result) { + var exists = _.find($scope.model.selection, function (selectedId) { + return result.id == selectedId; + }); + if (exists) { + result.selected = true; + } + }); + $scope.searchInfo.showSearch = true; + }; + $scope.dialogTreeEventHandler.bind('treeLoaded', treeLoadedHandler); + $scope.dialogTreeEventHandler.bind('treeNodeExpanded', nodeExpandedHandler); + $scope.dialogTreeEventHandler.bind('treeNodeSelect', nodeSelectHandler); + $scope.$on('$destroy', function () { + $scope.dialogTreeEventHandler.unbind('treeLoaded', treeLoadedHandler); + $scope.dialogTreeEventHandler.unbind('treeNodeExpanded', nodeExpandedHandler); + $scope.dialogTreeEventHandler.unbind('treeNodeSelect', nodeSelectHandler); + }); + $scope.selectListViewNode = function (node) { + select(node.name, node.id); + //toggle checked state + node.selected = node.selected === true ? false : true; + }; + $scope.closeMiniListView = function () { + $scope.miniListView = undefined; + }; + function openMiniListView(node) { + $scope.miniListView = node; + } + }); + angular.module('umbraco').controller('Umbraco.Overlays.UserController', function ($scope, $location, $timeout, dashboardResource, userService, historyService, eventsService, externalLoginInfo, authResource, currentUserResource, formHelper, localizationService) { + $scope.history = historyService.getCurrent(); + $scope.version = Umbraco.Sys.ServerVariables.application.version + ' assembly: ' + Umbraco.Sys.ServerVariables.application.assemblyVersion; + $scope.showPasswordFields = false; + $scope.changePasswordButtonState = 'init'; + $scope.model.subtitle = 'Umbraco version' + ' ' + $scope.version; + if (!$scope.model.title) { + $scope.model.title = localizationService.localize('general_user'); } - $scope.externalLoginProviders = externalLoginInfo.providers; $scope.externalLinkLoginFormAction = Umbraco.Sys.ServerVariables.umbracoUrls.externalLinkLoginsUrl; var evts = []; - evts.push(eventsService.on("historyService.add", function (e, args) { + evts.push(eventsService.on('historyService.add', function (e, args) { $scope.history = args.all; })); - evts.push(eventsService.on("historyService.remove", function (e, args) { + evts.push(eventsService.on('historyService.remove', function (e, args) { $scope.history = args.all; })); - evts.push(eventsService.on("historyService.removeAll", function (e, args) { + evts.push(eventsService.on('historyService.removeAll', function (e, args) { $scope.history = []; })); - $scope.logout = function () { - //Add event listener for when there are pending changes on an editor which means our route was not successful - var pendingChangeEvent = eventsService.on("valFormManager.pendingChanges", function (e, args) { + var pendingChangeEvent = eventsService.on('valFormManager.pendingChanges', function (e, args) { //one time listener, remove the event pendingChangeEvent(); $scope.model.close(); }); - - //perform the path change, if it is successful then the promise will resolve otherwise it will fail $scope.model.close(); - $location.path("/logout"); + $location.path('/logout'); }; - $scope.gotoHistory = function (link) { $location.path(link); $scope.model.close(); }; - //Manually update the remaining timeout seconds function updateTimeout() { $timeout(function () { @@ -4384,10 +4667,8 @@ angular.module("umbraco") //recurse updateTimeout(); } - - }, 1000, false); // 1 second, do NOT execute a global digest + }, 1000, false); // 1 second, do NOT execute a global digest } - function updateUserInfo() { //get the user userService.getCurrentUser().then(function (user) { @@ -4395,16 +4676,14 @@ angular.module("umbraco") if ($scope.user) { $scope.model.title = user.name; $scope.remainingAuthSeconds = $scope.user.remainingAuthSeconds; - $scope.canEditProfile = _.indexOf($scope.user.allowedSections, "users") > -1; + $scope.canEditProfile = _.indexOf($scope.user.allowedSections, 'users') > -1; //set the timer updateTimeout(); - - authResource.getCurrentUserLinkedLogins().then(function(logins) { + authResource.getCurrentUserLinkedLogins().then(function (logins) { //reset all to be un-linked for (var provider in $scope.externalLoginProviders) { $scope.externalLoginProviders[provider].linkedProviderKey = undefined; } - //set the linked logins for (var login in logins) { var found = _.find($scope.externalLoginProviders, function (i) { @@ -4418,350 +4697,644 @@ angular.module("umbraco") } }); } - $scope.unlink = function (e, loginProvider, providerKey) { - var result = confirm("Are you sure you want to unlink this account?"); + var result = confirm('Are you sure you want to unlink this account?'); if (!result) { e.preventDefault(); return; } - authResource.unlinkLogin(loginProvider, providerKey).then(function (a, b, c) { updateUserInfo(); }); - } - + }; updateUserInfo(); - //remove all event handlers $scope.$on('$destroy', function () { for (var e = 0; e < evts.length; e++) { evts[e](); } - }); - /* ---------- UPDATE PASSWORD ---------- */ - - //create the initial model for change password property editor + //create the initial model for change password $scope.changePasswordModel = { - alias: "_umb_password", - view: "changepassword", - config: {}, - value: {} + config: {}, + value: {} }; - //go get the config for the membership provider and add it to the model - currentUserResource.getMembershipProviderConfig().then(function(data) { - $scope.changePasswordModel.config = data; - //ensure the hasPassword config option is set to true (the user of course has a password already assigned) - //this will ensure the oldPassword is shown so they can change it - // disable reset password functionality beacuse it does not make sense inside the backoffice - $scope.changePasswordModel.config.hasPassword = true; - $scope.changePasswordModel.config.disableToggle = true; - $scope.changePasswordModel.config.enableReset = false; + authResource.getMembershipProviderConfig().then(function (data) { + $scope.changePasswordModel.config = data; + //ensure the hasPassword config option is set to true (the user of course has a password already assigned) + //this will ensure the oldPassword is shown so they can change it + // disable reset password functionality beacuse it does not make sense inside the backoffice + $scope.changePasswordModel.config.hasPassword = true; + $scope.changePasswordModel.config.disableToggle = true; + $scope.changePasswordModel.config.enableReset = false; }); - - $scope.changePassword = function() { - - if (formHelper.submitForm({ scope: $scope })) { - - $scope.changePasswordButtonState = "busy"; - - currentUserResource.changePassword($scope.changePasswordModel.value).then(function(data) { - + $scope.changePassword = function () { + if (formHelper.submitForm({ scope: $scope })) { + $scope.changePasswordButtonState = 'busy'; + currentUserResource.changePassword($scope.changePasswordModel.value).then(function (data) { + //reset old data + clearPasswordFields(); //if the password has been reset, then update our model if (data.value) { $scope.changePasswordModel.value.generatedPassword = data.value; } - - formHelper.resetForm({ scope: $scope, notifications: data.notifications }); - - $scope.changePasswordButtonState = "success"; - $timeout(function() { + formHelper.resetForm({ + scope: $scope, + notifications: data.notifications + }); + $scope.changePasswordButtonState = 'success'; + $timeout(function () { $scope.togglePasswordFields(); }, 2000); - }, function (err) { - formHelper.handleError(err); - - $scope.changePasswordButtonState = "error"; - + $scope.changePasswordButtonState = 'error'; }); - } - }; - - $scope.togglePasswordFields = function() { - clearPasswordFields(); - $scope.showPasswordFields = !$scope.showPasswordFields; - } - + $scope.togglePasswordFields = function () { + clearPasswordFields(); + $scope.showPasswordFields = !$scope.showPasswordFields; + }; function clearPasswordFields() { - $scope.changePasswordModel.value.newPassword = ""; - $scope.changePasswordModel.confirm = ""; + $scope.changePasswordModel.value.oldPassword = ''; + $scope.changePasswordModel.value.newPassword = ''; + $scope.changePasswordModel.value.confirm = ''; } - + dashboardResource.getDashboard('user-dialog').then(function (dashboard) { + $scope.dashboard = dashboard; + }); }); - -angular.module("umbraco") - .controller("Umbraco.Overlays.YsodController", function ($scope, legacyResource, treeService, navigationService, localizationService) { - - if(!$scope.model.title) { - $scope.model.title = localizationService.localize("errors_receivedErrorFromServer"); - } - - if ($scope.model.error && $scope.model.error.data && $scope.model.error.data.StackTrace) { - //trim whitespace - $scope.model.error.data.StackTrace = $scope.model.error.data.StackTrace.trim(); - } - - if ($scope.model.error && $scope.model.error.data) { - $scope.model.error.data.InnerExceptions = []; - var ex = $scope.model.error.data.InnerException; - while (ex) { - if (ex.StackTrace) { - ex.StackTrace = ex.StackTrace.trim(); - } - $scope.model.error.data.InnerExceptions.push(ex); - ex = ex.InnerException; - } - } - - }); - -angular.module("umbraco").controller("Umbraco.Editors.Content.CopyController", - function ($scope, eventsService, contentResource, navigationService, appState, treeService, localizationService, notificationsService) { - - var dialogOptions = $scope.dialogOptions; - var searchText = "Search..."; - localizationService.localize("general_search").then(function (value) { - searchText = value + "..."; - }); - - $scope.relateToOriginal = true; - $scope.recursive = true; - $scope.dialogTreeEventHandler = $({}); - $scope.busy = false; - $scope.searchInfo = { - searchFromId: null, - searchFromName: null, - showSearch: false, - results: [], - selectedSearchResults: [] - } - - var node = dialogOptions.currentNode; - - function nodeSelectHandler(ev, args) { - args.event.preventDefault(); - args.event.stopPropagation(); - - if (args.node.metaData.listViewNode) { - //check if list view 'search' node was selected - - $scope.searchInfo.showSearch = true; - $scope.searchInfo.searchFromId = args.node.metaData.listViewNode.id; - $scope.searchInfo.searchFromName = args.node.metaData.listViewNode.name; - } - else { - eventsService.emit("editors.content.copyController.select", args); - - if ($scope.target) { - //un-select if there's a current one selected - $scope.target.selected = false; - } - - $scope.target = args.node; - $scope.target.selected = true; - } - - } - - 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.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 }); - }; - - //callback when there are search results - $scope.onSearchResults = function (results) { - $scope.searchInfo.results = results; - $scope.searchInfo.showSearch = true; - }; - - $scope.copy = function () { - - $scope.busy = true; - $scope.error = false; - - contentResource.copy({ parentId: $scope.target.id, id: node.id, relateToOriginal: $scope.relateToOriginal, recursive: $scope.recursive }) - .then(function (path) { - $scope.error = false; - $scope.success = true; - $scope.busy = false; - - //get the currently edited node (if any) - var activeNode = appState.getTreeState("selectedNode"); - - //we need to do a double sync here: first sync to the copied content - but don't activate the node, - //then sync to the currenlty edited content (note: this might not be the content that was copied!!) - - navigationService.syncTree({ tree: "content", path: path, forceReload: true, activate: false }).then(function (args) { - if (activeNode) { - var activeNodePath = treeService.getPath(activeNode).join(); - //sync to this node now - depending on what was copied this might already be synced but might not be - navigationService.syncTree({ tree: "content", path: activeNodePath, forceReload: false, activate: true }); + (function () { + 'use strict'; + function UserGroupPickerController($scope, userGroupsResource, localizationService) { + var vm = this; + vm.userGroups = []; + vm.loading = false; + vm.selectUserGroup = selectUserGroup; + ////////// + function onInit() { + vm.loading = true; + // set default title + if (!$scope.model.title) { + $scope.model.title = localizationService.localize('defaultdialogs_selectUsers'); + } + // make sure we can push to something + if (!$scope.model.selection) { + $scope.model.selection = []; + } + // get venues + userGroupsResource.getUserGroups().then(function (userGroups) { + vm.userGroups = userGroups; + if ($scope.model.selection && $scope.model.selection.length > 0) { + preSelect($scope.model.selection); + } + vm.loading = false; + }); + } + function preSelect(selection) { + angular.forEach(selection, function (selected) { + angular.forEach(vm.userGroups, function (userGroup) { + if (selected.id === userGroup.id) { + userGroup.selected = true; } }); - - }, function (err) { - $scope.success = false; - $scope.error = err; - $scope.busy = false; - //show any notifications - if (angular.isArray(err.data.notifications)) { - for (var i = 0; i < err.data.notifications.length; i++) { - notificationsService.showNotification(err.data.notifications[i]); + }); + } + function selectUserGroup(userGroup) { + if (!userGroup.selected) { + userGroup.selected = true; + $scope.model.selection.push(userGroup); + } else { + angular.forEach($scope.model.selection, function (selectedUserGroup, index) { + if (selectedUserGroup.id === userGroup.id) { + userGroup.selected = false; + $scope.model.selection.splice(index, 1); } + }); + } + } + onInit(); + } + angular.module('umbraco').controller('Umbraco.Overlays.UserGroupPickerController', UserGroupPickerController); + }()); + (function () { + 'use strict'; + function UserPickerController($scope, usersResource, localizationService) { + var vm = this; + vm.users = []; + vm.loading = false; + vm.usersOptions = {}; + vm.selectUser = selectUser; + vm.searchUsers = searchUsers; + vm.changePageNumber = changePageNumber; + ////////// + function onInit() { + vm.loading = true; + // set default title + if (!$scope.model.title) { + $scope.model.title = localizationService.localize('defaultdialogs_selectUsers'); + } + // make sure we can push to something + if (!$scope.model.selection) { + $scope.model.selection = []; + } + // get users + getUsers(); + } + function preSelect(selection, users) { + angular.forEach(selection, function (selected) { + angular.forEach(users, function (user) { + if (selected.id === user.id) { + user.selected = true; + } + }); + }); + } + function selectUser(user) { + if (!user.selected) { + user.selected = true; + $scope.model.selection.push(user); + } else { + angular.forEach($scope.model.selection, function (selectedUser, index) { + if (selectedUser.id === user.id) { + user.selected = false; + $scope.model.selection.splice(index, 1); + } + }); + } + } + var search = _.debounce(function () { + $scope.$apply(function () { + getUsers(); + }); + }, 500); + function searchUsers() { + search(); + } + function getUsers() { + vm.loading = true; + // Get users + usersResource.getPagedResults(vm.usersOptions).then(function (users) { + vm.users = users.items; + vm.usersOptions.pageNumber = users.pageNumber; + vm.usersOptions.pageSize = users.pageSize; + vm.usersOptions.totalItems = users.totalItems; + vm.usersOptions.totalPages = users.totalPages; + preSelect($scope.model.selection, vm.users); + vm.loading = false; + }); + } + function changePageNumber(pageNumber) { + vm.usersOptions.pageNumber = pageNumber; + getUsers(); + } + onInit(); + } + angular.module('umbraco').controller('Umbraco.Overlays.UserPickerController', UserPickerController); + }()); + angular.module('umbraco').controller('Umbraco.Overlays.YsodController', function ($scope, legacyResource, treeService, navigationService, localizationService) { + if (!$scope.model.title) { + $scope.model.title = localizationService.localize('errors_receivedErrorFromServer'); + } + if ($scope.model.error && $scope.model.error.data && $scope.model.error.data.StackTrace) { + //trim whitespace + $scope.model.error.data.StackTrace = $scope.model.error.data.StackTrace.trim(); + } + if ($scope.model.error && $scope.model.error.data) { + $scope.model.error.data.InnerExceptions = []; + var ex = $scope.model.error.data.InnerException; + while (ex) { + if (ex.StackTrace) { + ex.StackTrace = ex.StackTrace.trim(); + } + $scope.model.error.data.InnerExceptions.push(ex); + ex = ex.InnerException; + } + } + }); + (function () { + 'use strict'; + function NodeNameController($scope) { + var vm = this; + var element = angular.element($scope.model.currentStep.element); + vm.error = false; + vm.initNextStep = initNextStep; + function initNextStep() { + if (element.val().toLowerCase() === 'home') { + $scope.model.nextStep(); + } else { + vm.error = true; + } + } + } + angular.module('umbraco').controller('Umbraco.Tours.UmbIntroCreateContent.NodeNameController', NodeNameController); + }()); + (function () { + 'use strict'; + function DocTypeNameController($scope) { + var vm = this; + var element = angular.element($scope.model.currentStep.element); + vm.error = false; + vm.initNextStep = initNextStep; + function initNextStep() { + if (element.val().toLowerCase() === 'home page') { + $scope.model.nextStep(); + } else { + vm.error = true; + } + } + } + angular.module('umbraco').controller('Umbraco.Tours.UmbIntroCreateDocType.DocTypeNameController', DocTypeNameController); + }()); + (function () { + 'use strict'; + function PropertyNameController($scope) { + var vm = this; + var element = angular.element($scope.model.currentStep.element); + vm.error = false; + vm.initNextStep = initNextStep; + function initNextStep() { + if (element.val().toLowerCase() === 'welcome text') { + $scope.model.nextStep(); + } else { + vm.error = true; + } + } + } + angular.module('umbraco').controller('Umbraco.Tours.UmbIntroCreateDocType.PropertyNameController', PropertyNameController); + }()); + (function () { + 'use strict'; + function TabNameController($scope) { + var vm = this; + var element = angular.element($scope.model.currentStep.element); + vm.error = false; + vm.initNextStep = initNextStep; + function initNextStep() { + if (element.val().toLowerCase() === 'home') { + $scope.model.nextStep(); + } else { + vm.error = true; + } + } + } + angular.module('umbraco').controller('Umbraco.Tours.UmbIntroCreateDocType.TabNameController', TabNameController); + }()); + (function () { + 'use strict'; + function FolderNameController($scope) { + var vm = this; + var element = angular.element($scope.model.currentStep.element); + vm.error = false; + vm.initNextStep = initNextStep; + function initNextStep() { + if (element.val().toLowerCase() === 'my images') { + $scope.model.nextStep(); + } else { + vm.error = true; + } + } + } + angular.module('umbraco').controller('Umbraco.Tours.UmbIntroMediaSection.FolderNameController', FolderNameController); + }()); + (function () { + 'use strict'; + function UploadImagesController($scope, editorState, mediaResource) { + var vm = this; + var element = angular.element($scope.model.currentStep.element); + vm.error = false; + vm.initNextStep = initNextStep; + function initNextStep() { + vm.error = false; + vm.buttonState = 'busy'; + var currentNode = editorState.getCurrent(); + // make sure we have uploaded at least one image + mediaResource.getChildren(currentNode.id).then(function (data) { + var children = data; + if (children.items && children.items.length > 0) { + $scope.model.nextStep(); + } else { + vm.error = true; + } + vm.buttonState = 'init'; + }); + } + } + angular.module('umbraco').controller('Umbraco.Tours.UmbIntroMediaSection.UploadImagesController', UploadImagesController); + }()); + (function () { + 'use strict'; + function TemplatesTreeController($scope) { + var vm = this; + var eventElement = angular.element($scope.model.currentStep.eventElement); + function onInit() { + // check if tree is already open - if it is - go to next step + if (eventElement.hasClass('icon-navigation-down')) { + $scope.model.nextStep(); + } + } + onInit(); + } + angular.module('umbraco').controller('Umbraco.Tours.UmbIntroRenderInTemplate.TemplatesTreeController', TemplatesTreeController); + }()); + angular.module('umbraco').controller('Umbraco.Editors.Content.CopyController', function ($scope, userService, eventsService, contentResource, navigationService, appState, treeService, localizationService, notificationsService) { + var dialogOptions = $scope.dialogOptions; + var searchText = 'Search...'; + localizationService.localize('general_search').then(function (value) { + searchText = value + '...'; + }); + $scope.relateToOriginal = true; + $scope.recursive = true; + $scope.dialogTreeEventHandler = $({}); + $scope.busy = false; + $scope.searchInfo = { + searchFromId: null, + searchFromName: null, + showSearch: false, + results: [], + selectedSearchResults: [] + }; + $scope.treeModel = { hideHeader: false }; + $scope.toggle = toggleHandler; + userService.getCurrentUser().then(function (userData) { + $scope.treeModel.hideHeader = userData.startContentIds.length > 0 && userData.startContentIds.indexOf(-1) == -1; + }); + var node = dialogOptions.currentNode; + function treeLoadedHandler(ev, args) { + if (node && node.path) { + $scope.dialogTreeEventHandler.syncTree({ + path: node.path, + activate: false + }); + } + } + function nodeSelectHandler(ev, args) { + if (args && args.event) { + args.event.preventDefault(); + args.event.stopPropagation(); + } + eventsService.emit('editors.content.copyController.select', args); + if ($scope.target) { + //un-select if there's a current one selected + $scope.target.selected = false; + } + $scope.target = args.node; + $scope.target.selected = true; + } + function nodeExpandedHandler(ev, args) { + // open mini list view for list views + if (args.node.metaData.isContainer) { + openMiniListView(args.node); + } + } + function toggleHandler(type) { + // If the relateToOriginal toggle is clicked + if (type === 'relate') { + if ($scope.relateToOriginal) { + $scope.relateToOriginal = false; + return; + } + $scope.relateToOriginal = true; + } + // If the recurvise toggle is clicked + if (type === 'recursive') { + if ($scope.recursive) { + $scope.recursive = false; + return; + } + $scope.recursive = true; + } + } + $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 + }); + }; + //callback when there are search results + $scope.onSearchResults = function (results) { + $scope.searchInfo.results = results; + $scope.searchInfo.showSearch = true; + }; + $scope.copy = function () { + $scope.busy = true; + $scope.error = false; + contentResource.copy({ + parentId: $scope.target.id, + id: node.id, + relateToOriginal: $scope.relateToOriginal, + recursive: $scope.recursive + }).then(function (path) { + $scope.error = false; + $scope.success = true; + $scope.busy = false; + //get the currently edited node (if any) + var activeNode = appState.getTreeState('selectedNode'); + //we need to do a double sync here: first sync to the copied content - but don't activate the node, + //then sync to the currenlty edited content (note: this might not be the content that was copied!!) + navigationService.syncTree({ + tree: 'content', + path: path, + forceReload: true, + activate: false + }).then(function (args) { + if (activeNode) { + var activeNodePath = treeService.getPath(activeNode).join(); + //sync to this node now - depending on what was copied this might already be synced but might not be + navigationService.syncTree({ + tree: 'content', + path: activeNodePath, + forceReload: false, + activate: true + }); } }); - }; - - $scope.dialogTreeEventHandler.bind("treeNodeSelect", nodeSelectHandler); - $scope.dialogTreeEventHandler.bind("treeNodeExpanded", nodeExpandedHandler); - - $scope.$on('$destroy', function () { - $scope.dialogTreeEventHandler.unbind("treeNodeSelect", nodeSelectHandler); - $scope.dialogTreeEventHandler.unbind("treeNodeExpanded", nodeExpandedHandler); - }); - }); - -/** - * @ngdoc controller - * @name Umbraco.Editors.Content.CreateController - * @function - * - * @description - * The controller for the content creation dialog - */ -function contentCreateController($scope, $routeParams, contentTypeResource, iconHelper) { - - contentTypeResource.getAllowedTypes($scope.currentNode.id).then(function(data) { - $scope.allowedTypes = iconHelper.formatContentTypeIcons(data); - }); -} - -angular.module('umbraco').controller("Umbraco.Editors.Content.CreateController", contentCreateController); -/** - * @ngdoc controller - * @name Umbraco.Editors.ContentDeleteController - * @function - * - * @description - * The controller for deleting content - */ -function ContentDeleteController($scope, contentResource, treeService, navigationService, editorState, $location, dialogService, notificationsService) { - - $scope.performDelete = function() { - - // stop from firing again on double-click - if ($scope.busy) { return false; } - - //mark it for deletion (used in the UI) - $scope.currentNode.loading = true; - $scope.busy = true; - - contentResource.deleteById($scope.currentNode.id).then(function () { - $scope.currentNode.loading = false; - - //get the root node before we remove it - var rootNode = treeService.getTreeRoot($scope.currentNode); - - treeService.removeNode($scope.currentNode); - - if (rootNode) { - //ensure the recycle bin has child nodes now - var recycleBin = treeService.getDescendantNode(rootNode, -20); - if (recycleBin) { - recycleBin.hasChildren = true; - } - } - - //if the current edited item is the same one as we're deleting, we need to navigate elsewhere - if (editorState.current && editorState.current.id == $scope.currentNode.id) { - - //If the deleted item lived at the root then just redirect back to the root, otherwise redirect to the item's parent - var location = "/content"; - if ($scope.currentNode.parentId.toString() !== "-1") - location = "/content/content/edit/" + $scope.currentNode.parentId; - - $location.path(location); - } - - navigationService.hideMenu(); - }, function(err) { - - $scope.currentNode.loading = false; - $scope.busy = false; - - //check if response is ysod - if (err.status && err.status >= 500) { - dialogService.ysodDialog(err); - } - - if (err.data && angular.isArray(err.data.notifications)) { - for (var i = 0; i < err.data.notifications.length; i++) { - notificationsService.showNotification(err.data.notifications[i]); - } - } - }); - - }; - - $scope.cancel = function() { - navigationService.hideDialog(); - }; -} - -angular.module("umbraco").controller("Umbraco.Editors.Content.DeleteController", ContentDeleteController); - -/** + }, function (err) { + $scope.success = false; + $scope.error = err; + $scope.busy = false; + //show any notifications + if (angular.isArray(err.data.notifications)) { + for (var i = 0; i < err.data.notifications.length; i++) { + notificationsService.showNotification(err.data.notifications[i]); + } + } + }); + }; + $scope.dialogTreeEventHandler.bind('treeLoaded', treeLoadedHandler); + $scope.dialogTreeEventHandler.bind('treeNodeSelect', nodeSelectHandler); + $scope.dialogTreeEventHandler.bind('treeNodeExpanded', nodeExpandedHandler); + $scope.$on('$destroy', function () { + $scope.dialogTreeEventHandler.unbind('treeLoaded', treeLoadedHandler); + $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 }); + }; + $scope.closeMiniListView = function () { + $scope.miniListView = undefined; + }; + function openMiniListView(node) { + $scope.miniListView = node; + } + }); + /** + * @ngdoc controller + * @name Umbraco.Editors.Content.CreateController + * @function + * + * @description + * The controller for the content creation dialog + */ + function contentCreateController($scope, $routeParams, contentTypeResource, iconHelper, $location, navigationService, blueprintConfig) { + function initialize() { + contentTypeResource.getAllowedTypes($scope.currentNode.id).then(function (data) { + $scope.allowedTypes = iconHelper.formatContentTypeIcons(data); + }); + $scope.selectContentType = true; + $scope.selectBlueprint = false; + $scope.allowBlank = blueprintConfig.allowBlank; + } + function close() { + navigationService.hideMenu(); + } + function createBlank(docType) { + $location.path('/content/content/edit/' + $scope.currentNode.id).search('doctype=' + docType.alias + '&create=true'); + close(); + } + function createOrSelectBlueprintIfAny(docType) { + var blueprintIds = _.keys(docType.blueprints || {}); + $scope.docType = docType; + if (blueprintIds.length) { + if (blueprintConfig.skipSelect) { + createFromBlueprint(blueprintIds[0]); + } else { + $scope.selectContentType = false; + $scope.selectBlueprint = true; + } + } else { + createBlank(docType); + } + } + function createFromBlueprint(blueprintId) { + $location.path('/content/content/edit/' + $scope.currentNode.id).search('doctype=' + $scope.docType.alias + '&create=true&blueprintId=' + blueprintId); + close(); + } + $scope.createBlank = createBlank; + $scope.createOrSelectBlueprintIfAny = createOrSelectBlueprintIfAny; + $scope.createFromBlueprint = createFromBlueprint; + initialize(); + } + angular.module('umbraco').controller('Umbraco.Editors.Content.CreateController', contentCreateController); + angular.module('umbraco').value('blueprintConfig', { + skipSelect: false, + allowBlank: true + }); + (function () { + function CreateBlueprintController($scope, contentResource, notificationsService, navigationService, localizationService, formHelper, contentEditingHelper) { + $scope.message = { name: $scope.currentNode.name }; + var successText = {}; + localizationService.localize('blueprints_createBlueprintFrom', ['' + $scope.message.name + '']).then(function (localizedVal) { + $scope.title = localizedVal; + }); + $scope.cancel = function () { + navigationService.hideMenu(); + }; + $scope.create = function () { + if (formHelper.submitForm({ + scope: $scope, + formCtrl: this.blueprintForm, + statusMessage: 'Creating blueprint...' + })) { + contentResource.createBlueprintFromContent($scope.currentNode.id, $scope.message.name).then(function (data) { + formHelper.resetForm({ + scope: $scope, + notifications: data.notifications + }); + navigationService.hideMenu(); + }, function (err) { + contentEditingHelper.handleSaveError({ + redirectOnFailure: false, + err: err + }); + }); + } + }; + } + angular.module('umbraco').controller('Umbraco.Editors.Content.CreateBlueprintController', CreateBlueprintController); + }()); + /** + * @ngdoc controller + * @name Umbraco.Editors.ContentDeleteController + * @function + * + * @description + * The controller for deleting content + */ + function ContentDeleteController($scope, contentResource, treeService, navigationService, editorState, $location, dialogService, notificationsService) { + $scope.performDelete = function () { + // stop from firing again on double-click + if ($scope.busy) { + return false; + } + //mark it for deletion (used in the UI) + $scope.currentNode.loading = true; + $scope.busy = true; + contentResource.deleteById($scope.currentNode.id).then(function () { + $scope.currentNode.loading = false; + //get the root node before we remove it + var rootNode = treeService.getTreeRoot($scope.currentNode); + treeService.removeNode($scope.currentNode); + if (rootNode) { + //ensure the recycle bin has child nodes now + var recycleBin = treeService.getDescendantNode(rootNode, -20); + if (recycleBin) { + recycleBin.hasChildren = true; + } + } + //if the current edited item is the same one as we're deleting, we need to navigate elsewhere + if (editorState.current && editorState.current.id == $scope.currentNode.id) { + //If the deleted item lived at the root then just redirect back to the root, otherwise redirect to the item's parent + var location = '/content'; + if ($scope.currentNode.parentId.toString() !== '-1') + location = '/content/content/edit/' + $scope.currentNode.parentId; + $location.path(location); + } + navigationService.hideMenu(); + }, function (err) { + $scope.currentNode.loading = false; + $scope.busy = false; + //check if response is ysod + if (err.status && err.status >= 500) { + dialogService.ysodDialog(err); + } + if (err.data && angular.isArray(err.data.notifications)) { + for (var i = 0; i < err.data.notifications.length; i++) { + notificationsService.showNotification(err.data.notifications[i]); + } + } + }); + }; + $scope.cancel = function () { + navigationService.hideDialog(); + }; + } + angular.module('umbraco').controller('Umbraco.Editors.Content.DeleteController', ContentDeleteController); + /** * @ngdoc controller * @name Umbraco.Editors.Content.EditController * @function @@ -4769,417 +5342,230 @@ angular.module("umbraco").controller("Umbraco.Editors.Content.DeleteController", * @description * The controller for the content editor */ -function ContentEditController($scope, $rootScope, $routeParams, $q, $timeout, $window, appState, contentResource, entityResource, navigationService, notificationsService, angularHelper, serverValidationManager, contentEditingHelper, treeService, fileManager, formHelper, umbRequestHelper, keyboardService, umbModelMapper, editorState, $http) { - - //setup scope vars - $scope.defaultButton = null; - $scope.subButtons = []; - - $scope.page = {}; - $scope.page.loading = false; - $scope.page.menu = {}; - $scope.page.menu.currentNode = null; - $scope.page.menu.currentSection = appState.getSectionState("currentSection"); - $scope.page.listViewPath = null; - $scope.page.isNew = $routeParams.create; - $scope.page.buttonGroupState = "init"; - - function init(content) { - - var buttons = contentEditingHelper.configureContentEditorButtons({ - create: $routeParams.create, - content: content, - methods: { - saveAndPublish: $scope.saveAndPublish, - sendToPublish: $scope.sendToPublish, - save: $scope.save, - unPublish: $scope.unPublish - } - }); - $scope.defaultButton = buttons.defaultButton; - $scope.subButtons = buttons.subButtons; - - editorState.set($scope.content); - - //We fetch all ancestors of the node to generate the footer breadcrumb navigation - if (!$routeParams.create) { - if (content.parentId && content.parentId != -1) { - entityResource.getAncestors(content.id, "document") - .then(function (anc) { - $scope.ancestors = anc; - }); - } + function ContentEditController($scope, $routeParams, contentResource) { + function scaffoldEmpty() { + return contentResource.getScaffold($routeParams.id, $routeParams.doctype); } - } - - /** Syncs the content item to it's tree node - this occurs on first load and after saving */ - function syncTreeNode(content, path, initialLoad) { - - if (!$scope.content.isChildOfListView) { - navigationService.syncTree({ tree: "content", path: path.split(","), forceReload: initialLoad !== true }).then(function (syncArgs) { - $scope.page.menu.currentNode = syncArgs.node; - }); - } - else if (initialLoad === true) { - - //it's a child item, just sync the ui node to the parent - navigationService.syncTree({ tree: "content", path: path.substring(0, path.lastIndexOf(",")).split(","), forceReload: initialLoad !== true }); - - //if this is a child of a list view and it's the initial load of the editor, we need to get the tree node - // from the server so that we can load in the actions menu. - umbRequestHelper.resourcePromise( - $http.get(content.treeNodeUrl), - 'Failed to retrieve data for child node ' + content.id).then(function (node) { - $scope.page.menu.currentNode = node; - }); + function scaffoldBlueprint() { + return contentResource.getBlueprintScaffold($routeParams.id, $routeParams.blueprintId); } + $scope.contentId = $routeParams.id; + $scope.saveMethod = contentResource.save; + $scope.getMethod = contentResource.getById; + $scope.getScaffoldMethod = $routeParams.blueprintId ? scaffoldBlueprint : scaffoldEmpty; + $scope.page = $routeParams.page; + $scope.isNew = $routeParams.create; } - - // This is a helper method to reduce the amount of code repitition for actions: Save, Publish, SendToPublish - function performSave(args) { - var deferred = $q.defer(); - - $scope.page.buttonGroupState = "busy"; - - contentEditingHelper.contentEditorPerformSave({ - statusMessage: args.statusMessage, - saveMethod: args.saveMethod, - scope: $scope, - content: $scope.content, - action: args.action - }).then(function (data) { - //success - init($scope.content); - syncTreeNode($scope.content, data.path); - - $scope.page.buttonGroupState = "success"; - - deferred.resolve(data); - }, function (err) { - //error - if (err) { - editorState.set($scope.content); - } - - $scope.page.buttonGroupState = "error"; - - deferred.reject(err); - }); - - return deferred.promise; - } - - function resetLastListPageNumber(content) { - // We're using rootScope to store the page number for list views, so if returning to the list - // we can restore the page. If we've moved on to edit a piece of content that's not the list or it's children - // we should remove this so as not to confuse if navigating to a different list - if (!content.isChildOfListView && !content.isContainer) { - $rootScope.lastListViewPageViewed = null; - } - } - - if ($routeParams.create) { - - $scope.page.loading = true; - - //we are creating so get an empty content item - contentResource.getScaffold($routeParams.id, $routeParams.doctype) - .then(function (data) { - - $scope.content = data; - - init($scope.content); - - resetLastListPageNumber($scope.content); - - $scope.page.loading = false; - - }); - } - else { - - $scope.page.loading = true; - - //we are editing so get the content item from the server - contentResource.getById($routeParams.id) - .then(function (data) { - - $scope.content = data; - - if (data.isChildOfListView && data.trashed === false) { - $scope.page.listViewPath = ($routeParams.page) - ? "/content/content/edit/" + data.parentId + "?page=" + $routeParams.page - : "/content/content/edit/" + data.parentId; + angular.module('umbraco').controller('Umbraco.Editors.Content.EditController', ContentEditController); + /** + * @ngdoc controller + * @name Umbraco.Editors.Content.EmptyRecycleBinController + * @function + * + * @description + * The controller for deleting content + */ + function ContentEmptyRecycleBinController($scope, contentResource, treeService, navigationService, notificationsService, $route) { + $scope.busy = false; + $scope.performDelete = function () { + //(used in the UI) + $scope.busy = true; + $scope.currentNode.loading = true; + contentResource.emptyRecycleBin($scope.currentNode.id).then(function (result) { + $scope.busy = false; + $scope.currentNode.loading = false; + //show any notifications + if (angular.isArray(result.notifications)) { + for (var i = 0; i < result.notifications.length; i++) { + notificationsService.showNotification(result.notifications[i]); + } } - - init($scope.content); - - //in one particular special case, after we've created a new item we redirect back to the edit - // route but there might be server validation errors in the collection which we need to display - // after the redirect, so we will bind all subscriptions which will show the server validation errors - // if there are any and then clear them so the collection no longer persists them. - serverValidationManager.executeAndClearAllSubscriptions(); - - syncTreeNode($scope.content, data.path, true); - - resetLastListPageNumber($scope.content); - - $scope.page.loading = false; - + treeService.removeChildNodes($scope.currentNode); + navigationService.hideMenu(); + //reload the current view + $route.reload(); }); + }; + $scope.cancel = function () { + navigationService.hideDialog(); + }; } - - - $scope.unPublish = function () { - - if (formHelper.submitForm({ scope: $scope, statusMessage: "Unpublishing...", skipValidation: true })) { - - $scope.page.buttonGroupState = "busy"; - - contentResource.unPublish($scope.content.id) - .then(function (data) { - - formHelper.resetForm({ scope: $scope, notifications: data.notifications }); - - contentEditingHelper.handleSuccessfulSave({ - scope: $scope, - savedContent: data, - rebindCallback: contentEditingHelper.reBindChangedProperties($scope.content, data) - }); - - init($scope.content); - - syncTreeNode($scope.content, data.path); - - $scope.page.buttonGroupState = "success"; - + angular.module('umbraco').controller('Umbraco.Editors.Content.EmptyRecycleBinController', ContentEmptyRecycleBinController); + angular.module('umbraco').controller('Umbraco.Editors.Content.MoveController', function ($scope, userService, eventsService, contentResource, navigationService, appState, treeService, localizationService, notificationsService) { + var dialogOptions = $scope.dialogOptions; + var searchText = 'Search...'; + localizationService.localize('general_search').then(function (value) { + searchText = value + '...'; + }); + $scope.dialogTreeEventHandler = $({}); + $scope.busy = false; + $scope.searchInfo = { + searchFromId: null, + searchFromName: null, + showSearch: false, + results: [], + selectedSearchResults: [] + }; + $scope.treeModel = { hideHeader: false }; + userService.getCurrentUser().then(function (userData) { + $scope.treeModel.hideHeader = userData.startContentIds.length > 0 && userData.startContentIds.indexOf(-1) == -1; + }); + var node = dialogOptions.currentNode; + function treeLoadedHandler(ev, args) { + if (node && node.path) { + $scope.dialogTreeEventHandler.syncTree({ + path: node.path, + activate: false }); + } } - - }; - - $scope.sendToPublish = function () { - return performSave({ saveMethod: contentResource.sendToPublish, statusMessage: "Sending...", action: "sendToPublish" }); - }; - - $scope.saveAndPublish = function () { - return performSave({ saveMethod: contentResource.publish, statusMessage: "Publishing...", action: "publish" }); - }; - - $scope.save = function () { - return performSave({ saveMethod: contentResource.save, statusMessage: "Saving...", action: "save" }); - }; - - $scope.preview = function (content) { - - - if (!$scope.busy) { - - // Chromes popup blocker will kick in if a window is opened - // outwith the initial scoped request. This trick will fix that. - // - var previewWindow = $window.open('preview/?id=' + content.id, 'umbpreview'); - $scope.save().then(function (data) { - // Build the correct path so both /#/ and #/ work. - var redirect = Umbraco.Sys.ServerVariables.umbracoSettings.umbracoPath + '/preview/?id=' + data.id; - previewWindow.location.href = redirect; + function nodeSelectHandler(ev, args) { + if (args && args.event) { + args.event.preventDefault(); + args.event.stopPropagation(); + } + eventsService.emit('editors.content.moveController.select', args); + if ($scope.target) { + //un-select if there's a current one selected + $scope.target.selected = false; + } + $scope.target = args.node; + $scope.target.selected = true; + } + function nodeExpandedHandler(ev, args) { + // open mini list view for list views + if (args.node.metaData.isContainer) { + openMiniListView(args.node); + } + } + $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 }); - - - } - - }; - -} - -angular.module("umbraco").controller("Umbraco.Editors.Content.EditController", ContentEditController); - -/** - * @ngdoc controller - * @name Umbraco.Editors.Content.EmptyRecycleBinController - * @function - * - * @description - * The controller for deleting content - */ -function ContentEmptyRecycleBinController($scope, contentResource, treeService, navigationService, notificationsService, $route) { - - $scope.busy = false; - - $scope.performDelete = function() { - - //(used in the UI) - $scope.busy = true; - $scope.currentNode.loading = true; - - contentResource.emptyRecycleBin($scope.currentNode.id).then(function (result) { - - $scope.busy = false; - $scope.currentNode.loading = false; - - //show any notifications - if (angular.isArray(result.notifications)) { - for (var i = 0; i < result.notifications.length; i++) { - notificationsService.showNotification(result.notifications[i]); - } - } - - treeService.removeChildNodes($scope.currentNode); - navigationService.hideMenu(); - - //reload the current view - $route.reload(); - }); - - }; - - $scope.cancel = function() { - navigationService.hideDialog(); - }; -} - -angular.module("umbraco").controller("Umbraco.Editors.Content.EmptyRecycleBinController", ContentEmptyRecycleBinController); - -angular.module("umbraco").controller("Umbraco.Editors.Content.MoveController", - function ($scope, eventsService, contentResource, navigationService, appState, treeService, localizationService, notificationsService) { - - var dialogOptions = $scope.dialogOptions; - var searchText = "Search..."; - localizationService.localize("general_search").then(function (value) { - searchText = value + "..."; - }); - - $scope.dialogTreeEventHandler = $({}); - $scope.busy = false; - $scope.searchInfo = { - searchFromId: null, - searchFromName: null, - showSearch: false, - results: [], - selectedSearchResults: [] - } - - var node = dialogOptions.currentNode; - - function nodeSelectHandler(ev, args) { - args.event.preventDefault(); - args.event.stopPropagation(); - - if (args.node.metaData.listViewNode) { - //check if list view 'search' node was selected - - $scope.searchInfo.showSearch = true; - $scope.searchInfo.searchFromId = args.node.metaData.listViewNode.id; - $scope.searchInfo.searchFromName = args.node.metaData.listViewNode.name; - } - else { - eventsService.emit("editors.content.moveController.select", args); - - if ($scope.target) { - //un-select if there's a current one selected - $scope.target.selected = false; - } - - $scope.target = args.node; - $scope.target.selected = true; - } - } - - 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.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 }); - }; - - //callback when there are search results - $scope.onSearchResults = function (results) { - $scope.searchInfo.results = results; - $scope.searchInfo.showSearch = true; - }; - - $scope.move = function () { - - $scope.busy = true; - $scope.error = false; - - contentResource.move({ parentId: $scope.target.id, id: node.id }) - .then(function (path) { - $scope.error = false; - $scope.success = true; - $scope.busy = false; - - //first we need to remove the node that launched the dialog - treeService.removeNode($scope.currentNode); - - //get the currently edited node (if any) - var activeNode = appState.getTreeState("selectedNode"); - - //we need to do a double sync here: first sync to the moved content - but don't activate the node, - //then sync to the currenlty edited content (note: this might not be the content that was moved!!) - - navigationService.syncTree({ tree: "content", path: path, forceReload: true, activate: false }).then(function (args) { - if (activeNode) { - var activeNodePath = treeService.getPath(activeNode).join(); - //sync to this node now - depending on what was copied this might already be synced but might not be - navigationService.syncTree({ tree: "content", path: activeNodePath, forceReload: false, activate: true }); - } - }); - - }, function (err) { - $scope.success = false; - $scope.error = err; - $scope.busy = false; - //show any notifications - if (angular.isArray(err.data.notifications)) { - for (var i = 0; i < err.data.notifications.length; i++) { - notificationsService.showNotification(err.data.notifications[i]); - } + }; + //callback when there are search results + $scope.onSearchResults = function (results) { + $scope.searchInfo.results = results; + $scope.searchInfo.showSearch = true; + }; + $scope.move = function () { + $scope.busy = true; + $scope.error = false; + contentResource.move({ + parentId: $scope.target.id, + id: node.id + }).then(function (path) { + $scope.error = false; + $scope.success = true; + $scope.busy = false; + //first we need to remove the node that launched the dialog + treeService.removeNode($scope.currentNode); + //get the currently edited node (if any) + var activeNode = appState.getTreeState('selectedNode'); + //we need to do a double sync here: first sync to the moved content - but don't activate the node, + //then sync to the currently edited content (note: this might not be the content that was moved!!) + navigationService.syncTree({ + tree: 'content', + path: path, + forceReload: true, + activate: false + }).then(function (args) { + if (activeNode) { + var activeNodePath = treeService.getPath(activeNode).join(); + //sync to this node now - depending on what was copied this might already be synced but might not be + navigationService.syncTree({ + tree: 'content', + path: activeNodePath, + forceReload: false, + activate: true + }); } }); - }; - - $scope.dialogTreeEventHandler.bind("treeNodeSelect", nodeSelectHandler); - $scope.dialogTreeEventHandler.bind("treeNodeExpanded", nodeExpandedHandler); - - $scope.$on('$destroy', function () { - $scope.dialogTreeEventHandler.unbind("treeNodeSelect", nodeSelectHandler); - $scope.dialogTreeEventHandler.unbind("treeNodeExpanded", nodeExpandedHandler); - }); - }); -/** + }, function (err) { + $scope.success = false; + $scope.error = err; + $scope.busy = false; + //show any notifications + if (angular.isArray(err.data.notifications)) { + for (var i = 0; i < err.data.notifications.length; i++) { + notificationsService.showNotification(err.data.notifications[i]); + } + } + }); + }; + $scope.dialogTreeEventHandler.bind('treeLoaded', treeLoadedHandler); + $scope.dialogTreeEventHandler.bind('treeNodeSelect', nodeSelectHandler); + $scope.dialogTreeEventHandler.bind('treeNodeExpanded', nodeExpandedHandler); + $scope.$on('$destroy', function () { + $scope.dialogTreeEventHandler.unbind('treeLoaded', treeLoadedHandler); + $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 }); + }; + $scope.closeMiniListView = function () { + $scope.miniListView = undefined; + }; + function openMiniListView(node) { + $scope.miniListView = node; + } + }); + (function () { + function CreateNotifyController($scope, contentResource, navigationService, angularHelper) { + var vm = this; + var currentForm; + vm.notifyOptions = []; + vm.save = save; + vm.cancel = cancel; + vm.message = { name: $scope.currentNode.name }; + ; + function onInit() { + vm.loading = true; + contentResource.getNotifySettingsById($scope.currentNode.id).then(function (options) { + currentForm = angularHelper.getCurrentForm($scope); + vm.loading = false; + vm.notifyOptions = options; + }); + } + function cancel() { + navigationService.hideMenu(); + } + ; + function save(notifyOptions) { + vm.saveState = 'busy'; + vm.saveError = false; + vm.saveSuccces = false; + var selectedString = ''; + angular.forEach(notifyOptions, function (option) { + if (option.checked === true && option.notifyCode) { + selectedString += option.notifyCode; + } + }); + contentResource.setNotifySettingsById($scope.currentNode.id, selectedString).then(function () { + vm.saveState = 'success'; + vm.saveSuccces = true; + }, function (error) { + vm.saveState = 'error'; + vm.saveError = error; + }); + } + onInit(); + } + angular.module('umbraco').controller('Umbraco.Editors.Content.CreateNotifyController', CreateNotifyController); + }()); + /** * @ngdoc controller * @name Umbraco.Editors.Content.RecycleBinController * @function @@ -5188,908 +5574,1011 @@ angular.module("umbraco").controller("Umbraco.Editors.Content.MoveController", * Controls the recycle bin for content * */ - -function ContentRecycleBinController($scope, $routeParams, contentResource, navigationService, localizationService) { - - //ensures the list view doesn't actually load until we query for the list view config - // for the section - $scope.page = {}; - $scope.page.name = "Recycle Bin"; - $scope.page.nameLocked = true; - - //ensures the list view doesn't actually load until we query for the list view config - // for the section - $scope.listViewPath = null; - - $routeParams.id = "-20"; - contentResource.getRecycleBin().then(function (result) { - //we'll get the 'content item' for the recycle bin, we know that it will contain a single tab and a - // single property, so we'll extract that property (list view) and use it's data. - var listproperty = result.tabs[0].properties[0]; - - _.each(listproperty.config, function (val, key) { - $scope.model.config[key] = val; + function ContentRecycleBinController($scope, $routeParams, contentResource, navigationService, localizationService) { + //ensures the list view doesn't actually load until we query for the list view config + // for the section + $scope.page = {}; + $scope.page.name = 'Recycle Bin'; + $scope.page.nameLocked = true; + //ensures the list view doesn't actually load until we query for the list view config + // for the section + $scope.listViewPath = null; + $routeParams.id = '-20'; + contentResource.getRecycleBin().then(function (result) { + //we'll get the 'content item' for the recycle bin, we know that it will contain a single tab and a + // single property, so we'll extract that property (list view) and use it's data. + var listproperty = result.tabs[0].properties[0]; + _.each(listproperty.config, function (val, key) { + $scope.model.config[key] = val; + }); + $scope.listViewPath = 'views/propertyeditors/listview/listview.html'; }); - $scope.listViewPath = 'views/propertyeditors/listview/listview.html'; - }); - - $scope.model = { config: { entityType: $routeParams.section, layouts: [] } }; - - // sync tree node - navigationService.syncTree({ tree: "content", path: ["-1", $routeParams.id], forceReload: false }); - - localizePageName(); - - function localizePageName() { - - var pageName = "general_recycleBin"; - - localizationService.localize(pageName).then(function (value) { - $scope.page.name = value; + $scope.model = { + config: { + entityType: $routeParams.section, + layouts: [] + } + }; + // sync tree node + navigationService.syncTree({ + tree: 'content', + path: [ + '-1', + $routeParams.id + ], + forceReload: false }); - + localizePageName(); + function localizePageName() { + var pageName = 'general_recycleBin'; + localizationService.localize(pageName).then(function (value) { + $scope.page.name = value; + }); + } } -} - -angular.module('umbraco').controller("Umbraco.Editors.Content.RecycleBinController", ContentRecycleBinController); - -angular.module("umbraco").controller("Umbraco.Editors.Content.RestoreController", - function ($scope, relationResource, contentResource, navigationService, appState, treeService) { - var dialogOptions = $scope.dialogOptions; - - var node = dialogOptions.currentNode; - - $scope.error = null; - $scope.success = false; - - relationResource.getByChildId(node.id, "relateParentDocumentOnDelete").then(function (data) { - + angular.module('umbraco').controller('Umbraco.Editors.Content.RecycleBinController', ContentRecycleBinController); + angular.module('umbraco').controller('Umbraco.Editors.Content.RestoreController', function ($scope, relationResource, contentResource, navigationService, appState, treeService, localizationService) { + var dialogOptions = $scope.dialogOptions; + var node = dialogOptions.currentNode; + $scope.error = null; + $scope.success = false; + relationResource.getByChildId(node.id, 'relateParentDocumentOnDelete').then(function (data) { if (data.length == 0) { $scope.success = false; $scope.error = { - errorMsg: "Cannot automatically restore this item", - data: { - Message: "There is no 'restore' relation found for this node. Use the Move menu item to move it manually." - } - } + errorMsg: localizationService.localize('recycleBin_itemCannotBeRestored'), + data: { Message: localizationService.localize('recycleBin_noRestoreRelation') } + }; return; } - - $scope.relation = data[0]; - - if ($scope.relation.parentId == -1) { - $scope.target = { id: -1, name: "Root" }; - - } else { - contentResource.getById($scope.relation.parentId).then(function (data) { - $scope.target = data; - - }, function (err) { - $scope.success = false; - $scope.error = err; - }); - } - - }, function (err) { - $scope.success = false; - $scope.error = err; - }); - - $scope.restore = function () { - // this code was copied from `content.move.controller.js` - contentResource.move({ parentId: $scope.target.id, id: node.id }) - .then(function (path) { - - $scope.success = true; - - //first we need to remove the node that launched the dialog - treeService.removeNode($scope.currentNode); - - //get the currently edited node (if any) - var activeNode = appState.getTreeState("selectedNode"); - - //we need to do a double sync here: first sync to the moved content - but don't activate the node, - //then sync to the currenlty edited content (note: this might not be the content that was moved!!) - - navigationService.syncTree({ tree: "content", path: path, forceReload: true, activate: false }).then(function (args) { - if (activeNode) { - var activeNodePath = treeService.getPath(activeNode).join(); - //sync to this node now - depending on what was copied this might already be synced but might not be - navigationService.syncTree({ tree: "content", path: activeNodePath, forceReload: false, activate: true }); - } - }); - - }, function (err) { - $scope.success = false; - $scope.error = err; - }); - }; - }); -function startUpVideosDashboardController($scope, xmlhelper, $log, $http) { - $scope.videos = []; - $scope.init = function(url){ - var proxyUrl = "dashboard/feedproxy.aspx?url=" + url; - $http.get(proxyUrl).then(function(data){ - var feed = $(data.data); - $('item', feed).each(function (i, item) { - var video = {}; - video.thumbnail = $(item).find('thumbnail').attr('url'); - video.title = $("title", item).text(); - video.link = $("guid", item).text(); - $scope.videos.push(video); - }); - }); - }; -} - -angular.module("umbraco").controller("Umbraco.Dashboard.StartupVideosController", startUpVideosDashboardController); - - -function startUpDynamicContentController(dashboardResource, assetsService) { - var vm = this; - vm.loading = true; - vm.showDefault = false; - - //proxy remote css through the local server - assetsService.loadCss( dashboardResource.getRemoteDashboardCssUrl("content") ); - dashboardResource.getRemoteDashboardContent("content").then( - function (data) { - - vm.loading = false; - - //test if we have received valid data - //we capture it like this, so we avoid UI errors - which automatically triggers ui based on http response code - if (data && data.sections) { - vm.dashboard = data; - } else{ - vm.showDefault = true; - } - - }, - - function (exception) { - console.error(exception); - vm.loading = false; - vm.showDefault = true; - }); -} - -angular.module("umbraco").controller("Umbraco.Dashboard.StartUpDynamicContentController", startUpDynamicContentController); - - -function FormsController($scope, $route, $cookieStore, packageResource, localizationService) { - $scope.installForms = function(){ - $scope.state = localizationService.localize("packager_installStateDownloading"); - packageResource - .fetch("CD44CF39-3D71-4C19-B6EE-948E1FAF0525") - .then(function(pack) { - $scope.state = localizationService.localize("packager_installStateImporting"); - return packageResource.import(pack); - }, - $scope.error) - .then(function(pack) { - $scope.state = localizationService.localize("packager_installStateInstalling"); - return packageResource.installFiles(pack); - }, - $scope.error) - .then(function(pack) { - $scope.state = localizationService.localize("packager_installStateRestarting"); - return packageResource.installData(pack); - }, - $scope.error) - .then(function(pack) { - $scope.state = localizationService.localize("packager_installStateComplete"); - return packageResource.cleanUp(pack); - }, - $scope.error) - .then($scope.complete, $scope.error); - }; - - $scope.complete = function(result){ - var url = window.location.href + "?init=true"; - $cookieStore.put("umbPackageInstallId", result.packageGuid); - window.location.reload(true); - }; - - $scope.error = function(err){ - $scope.state = undefined; - $scope.error = err; - //This will return a rejection meaning that the promise change above will stop - return $q.reject(); - }; - - - function Video_player (videoId) { - // Get dom elements - this.container = document.getElementById(videoId); - this.video = this.container.getElementsByTagName('video')[0]; - - //Create controls - this.controls = document.createElement('div'); - this.controls.className="video-controls"; - - this.seek_bar = document.createElement('input'); - this.seek_bar.className="seek-bar"; - this.seek_bar.type="range"; - this.seek_bar.setAttribute('value', '0'); - - this.loader = document.createElement('div'); - this.loader.className="loader"; - - this.progress_bar = document.createElement('span'); - this.progress_bar.className="progress-bar"; - - // Insert controls - this.controls.appendChild(this.seek_bar); - this.container.appendChild(this.controls); - this.controls.appendChild(this.loader); - this.loader.appendChild(this.progress_bar); - } - - - Video_player.prototype - .seeking = function() { - // get the value of the seekbar (hidden input[type="range"]) - var time = this.video.duration * (this.seek_bar.value / 100); - - // Update video to seekbar value - this.video.currentTime = time; - }; - - // Stop video when user initiates seeking - Video_player.prototype - .start_seek = function() { - this.video.pause(); - }; - - // Start video when user stops seeking - Video_player.prototype - .stop_seek = function() { - this.video.play(); - }; - - // Update the progressbar (span.loader) according to video.currentTime - Video_player.prototype - .update_progress_bar = function() { - // Get video progress in % - var value = (100 / this.video.duration) * this.video.currentTime; - - // Update progressbar - this.progress_bar.style.width = value + '%'; - }; - - // Bind progressbar to mouse when seeking - Video_player.prototype - .handle_mouse_move = function(event) { - // Get position of progressbar relative to browser window - var pos = this.progress_bar.getBoundingClientRect().left; - - // Make sure event is reckonized cross-browser - event = event || window.event; - - // Update progressbar - this.progress_bar.style.width = (event.clientX - pos) + "px"; - }; - - // Eventlisteners for seeking - Video_player.prototype - .video_event_handler = function(videoPlayer, interval) { - // Update the progress bar - var animate_progress_bar = setInterval(function () { - videoPlayer.update_progress_bar(); - }, interval); - - // Fire when input value changes (user seeking) - videoPlayer.seek_bar - .addEventListener("change", function() { - videoPlayer.seeking(); - }); - - // Fire when user clicks on seekbar - videoPlayer.seek_bar - .addEventListener("mousedown", function (clickEvent) { - // Pause video playback - videoPlayer.start_seek(); - - // Stop updating progressbar according to video progress - clearInterval(animate_progress_bar); - - // Update progressbar to where user clicks - videoPlayer.handle_mouse_move(clickEvent); - - // Bind progressbar to cursor - window.onmousemove = function(moveEvent){ - videoPlayer.handle_mouse_move(moveEvent); - }; - }); - - // Fire when user releases seekbar - videoPlayer.seek_bar - .addEventListener("mouseup", function () { - - // Unbind progressbar from cursor - window.onmousemove = null; - - // Start video playback - videoPlayer.stop_seek(); - - // Animate the progressbar - animate_progress_bar = setInterval(function () { - videoPlayer.update_progress_bar(); - }, interval); - }); - }; - - - var videoPlayer = new Video_player('video_1'); - videoPlayer.video_event_handler(videoPlayer, 17); -} - -angular.module("umbraco").controller("Umbraco.Dashboard.FormsDashboardController", FormsController); - -function startupLatestEditsController($scope) { - -} -angular.module("umbraco").controller("Umbraco.Dashboard.StartupLatestEditsController", startupLatestEditsController); - -function MediaFolderBrowserDashboardController($rootScope, $scope, $location, contentTypeResource, userService) { - - var currentUser = {}; - - userService.getCurrentUser().then(function (user) { - - currentUser = user; - - // check if the user start node is the dashboard - if(currentUser.startMediaId === -1) { - - //get the system media listview - contentTypeResource.getPropertyTypeScaffold(-96) - .then(function(dt) { - - $scope.fakeProperty = { - alias: "contents", - config: dt.config, - description: "", - editor: dt.editor, - hideLabel: true, - id: 1, - label: "Contents:", - validation: { - mandatory: false, - pattern: null - }, - value: "", - view: dt.view - }; - - }); - - } else { - // redirect to start node - $location.path("/media/media/edit/" + currentUser.startMediaId); - } - - }); - -} -angular.module("umbraco").controller("Umbraco.Dashboard.MediaFolderBrowserDashboardController", MediaFolderBrowserDashboardController); - -function ExamineMgmtController($scope, umbRequestHelper, $log, $http, $q, $timeout) { - - $scope.indexerDetails = []; - $scope.searcherDetails = []; - $scope.loading = true; - - function checkProcessing(indexer, checkActionName) { - umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper.getApiUrl("examineMgmtBaseUrl", - checkActionName, - { indexerName: indexer.name })), - 'Failed to check index processing') - .then(function(data) { - - if (data !== null && data !== "null") { - + $scope.relation = data[0]; + if ($scope.relation.parentId == -1) { + $scope.target = { + id: -1, + name: 'Root' + }; + } else { + contentResource.getById($scope.relation.parentId).then(function (data) { + $scope.target = data; + // make sure the target item isn't in the recycle bin + if ($scope.target.path.indexOf('-20') !== -1) { + $scope.error = { + errorMsg: localizationService.localize('recycleBin_itemCannotBeRestored'), + data: { + Message: localizationService.localize('recycleBin_restoreUnderRecycled').then(function (value) { + value.replace('%0%', $scope.target.name); + }) + } + }; + $scope.success = false; + } + }, function (err) { + $scope.success = false; + $scope.error = err; + }); + } + }, function (err) { + $scope.success = false; + $scope.error = err; + }); + $scope.restore = function () { + // this code was copied from `content.move.controller.js` + contentResource.move({ + parentId: $scope.target.id, + id: node.id + }).then(function (path) { + $scope.success = true; + //first we need to remove the node that launched the dialog + treeService.removeNode($scope.currentNode); + //get the currently edited node (if any) + var activeNode = appState.getTreeState('selectedNode'); + //we need to do a double sync here: first sync to the moved content - but don't activate the node, + //then sync to the currenlty edited content (note: this might not be the content that was moved!!) + navigationService.syncTree({ + tree: 'content', + path: path, + forceReload: true, + activate: false + }).then(function (args) { + if (activeNode) { + var activeNodePath = treeService.getPath(activeNode).join(); + //sync to this node now - depending on what was copied this might already be synced but might not be + navigationService.syncTree({ + tree: 'content', + path: activeNodePath, + forceReload: false, + activate: true + }); + } + }); + }, function (err) { + $scope.success = false; + $scope.error = err; + }); + }; + }); + (function () { + 'use strict'; + function ContentRightsController($scope, $timeout, contentResource, localizationService, angularHelper) { + var vm = this; + var currentForm; + vm.availableUserGroups = []; + vm.selectedUserGroups = []; + vm.removedUserGroups = []; + vm.viewState = 'manageGroups'; + vm.labels = {}; + vm.showNotification = false; + vm.setViewSate = setViewSate; + vm.editPermissions = editPermissions; + vm.setPermissions = setPermissions; + vm.save = save; + vm.removePermissions = removePermissions; + vm.cancelManagePermissions = cancelManagePermissions; + vm.closeDialog = closeDialog; + vm.stay = stay; + function onInit() { + vm.loading = true; + contentResource.getDetailedPermissions($scope.currentNode.id).then(function (userGroups) { + initData(userGroups); + vm.loading = false; + currentForm = angularHelper.getCurrentForm($scope); + }); + } + /** + * This will initialize the data and set the correct selectedUserGroups based on the default permissions and explicit permissions assigned + * @param {any} userGroups + */ + function initData(userGroups) { + //reset this + vm.selectedUserGroups = []; + vm.availableUserGroups = userGroups; + angular.forEach(vm.availableUserGroups, function (group) { + if (group.permissions) { + //if there's explicit permissions assigned than it's selected + assignGroupPermissions(group); + } + }); + } + function setViewSate(state) { + vm.viewState = state; + } + function editPermissions(group) { + vm.selectedUserGroup = group; + if (!vm.selectedUserGroup.permissions) { + //if no permissions are explicitly set this means we need to show the defaults + vm.selectedUserGroup.permissions = vm.selectedUserGroup.defaultPermissions; + } + localizationService.localize('defaultdialogs_permissionsSetForGroup', [ + $scope.currentNode.name, + vm.selectedUserGroup.name + ]).then(function (value) { + vm.labels.permissionsSetForGroup = value; + }); + setViewSate('managePermissions'); + } + function assignGroupPermissions(group) { + // clear allowed permissions before we make the list so we don't have duplicates + group.allowedPermissions = []; + // get list of checked permissions + angular.forEach(group.permissions, function (permissionGroup) { + angular.forEach(permissionGroup, function (permission) { + if (permission.checked) { + //the `allowedPermissions` is what will get sent up to the server for saving + group.allowedPermissions.push(permission); + } + }); + }); + if (!group.selected) { + // set to selected so we can remove from the dropdown easily + group.selected = true; + vm.selectedUserGroups.push(group); + //remove from the removed groups if it's been re-added + vm.removedUserGroups = _.reject(vm.removedUserGroups, function (g) { + return g.id == group.id; + }); + } + } + function setPermissions(group) { + assignGroupPermissions(group); + setViewSate('manageGroups'); + } + /** + * This essentially resets the permissions for a group for this content item, it will remove it from the selected list + * @param {any} index + */ + function removePermissions(index) { + // remove as selected so we can select it from the dropdown again + var group = vm.selectedUserGroups[index]; + group.selected = false; + //reset assigned permissions - so it will default back to default permissions + group.permissions = []; + group.allowedPermissions = []; + vm.selectedUserGroups.splice(index, 1); + //track it in the removed so this gets pushed to the server + vm.removedUserGroups.push(group); + } + function cancelManagePermissions() { + setViewSate('manageGroups'); + } + function formatSaveModel(permissionsSave, groupCollection) { + angular.forEach(groupCollection, function (g) { + permissionsSave[g.id] = []; + angular.forEach(g.allowedPermissions, function (p) { + permissionsSave[g.id].push(p.permissionCode); + }); + }); + } + function save() { + vm.saveState = 'busy'; + vm.saveError = false; + vm.saveSuccces = false; + //this is a dictionary that we need to populate + var permissionsSave = {}; + //format the selectedUserGroups, then the removedUserGroups since we want to pass data from both collections up + formatSaveModel(permissionsSave, vm.selectedUserGroups); + formatSaveModel(permissionsSave, vm.removedUserGroups); + var saveModel = { + contentId: $scope.currentNode.id, + permissions: permissionsSave + }; + contentResource.savePermissions(saveModel).then(function (userGroups) { + //re-assign model from server since it could have changed + initData(userGroups); + // clear dirty state on the form so we don't see the discard changes notification + // we use a timeout here because in some cases the initData reformats the userGroups model and triggers a change after the form state was changed + $timeout(function () { + if (currentForm) { + currentForm.$dirty = false; + } + }); + vm.saveState = 'success'; + vm.saveSuccces = true; + }, function (error) { + vm.saveState = 'error'; + vm.saveError = error; + }); + } + function stay() { + vm.showNotification = false; + } + function closeDialog() { + // check if form has been changed. If it has show discard changes notification + if (currentForm && currentForm.$dirty) { + vm.showNotification = true; + } else { + $scope.nav.hideDialog(); + } + } + onInit(); + } + angular.module('umbraco').controller('Umbraco.Editors.Content.RightsController', ContentRightsController); + }()); + /** + * @ngdoc controller + * @name Umbraco.Editors.ContentBlueprint.CreateController + * @function + * + * @description + * The controller for creating content blueprints + */ + function ContentBlueprintCreateController($scope, $location, contentTypeResource, navigationService) { + var vm = this; + var node = $scope.dialogOptions.currentNode; + vm.createBlueprint = createBlueprint; + function onInit() { + vm.loading = true; + contentTypeResource.getAll().then(function (documentTypes) { + vm.documentTypes = documentTypes; + vm.loading = false; + }); + } + function createBlueprint(documentType) { + $location.path('/settings/contentBlueprints/edit/' + node.id).search('create', 'true').search('doctype', documentType.alias); + navigationService.hideMenu(); + } + onInit(); + } + angular.module('umbraco').controller('Umbraco.Editors.ContentBlueprint.CreateController', ContentBlueprintCreateController); + /** + * @ngdoc controller + * @name Umbraco.Editors.ContentBlueprint.DeleteController + * @function + * + * @description + * The controller for deleting content blueprints + */ + function ContentBlueprintDeleteController($scope, contentResource, treeService, navigationService) { + $scope.performDelete = function () { + //mark it for deletion (used in the UI) + $scope.currentNode.loading = true; + contentResource.deleteBlueprint($scope.currentNode.id).then(function () { + $scope.currentNode.loading = false; + //get the root node before we remove it + var rootNode = treeService.getTreeRoot($scope.currentNode); + //TODO: Need to sync tree, etc... + treeService.removeNode($scope.currentNode); + navigationService.hideMenu(); + }); + }; + $scope.cancel = function () { + navigationService.hideDialog(); + }; + } + angular.module('umbraco').controller('Umbraco.Editors.ContentBlueprint.DeleteController', ContentBlueprintDeleteController); + /** + * @ngdoc controller + * @name Umbraco.Editors.Content.EditController + * @function + * + * @description + * The controller for the content editor + */ + function ContentBlueprintEditController($scope, $routeParams, contentResource) { + var excludedProps = [ + '_umb_urls', + '_umb_releasedate', + '_umb_expiredate', + '_umb_template' + ]; + function getScaffold() { + return contentResource.getScaffold(-1, $routeParams.doctype).then(function (scaffold) { + var lastTab = scaffold.tabs[scaffold.tabs.length - 1]; + lastTab.properties = _.filter(lastTab.properties, function (p) { + return excludedProps.indexOf(p.alias) === -1; + }); + scaffold.allowPreview = false; + scaffold.allowedActions = [ + 'A', + 'S', + 'C' + ]; + return scaffold; + }); + } + $scope.contentId = $routeParams.id; + $scope.isNew = $routeParams.id === '-1'; + $scope.saveMethod = contentResource.saveBlueprint; + $scope.getMethod = contentResource.getBlueprintById; + $scope.getScaffoldMethod = getScaffold; + } + angular.module('umbraco').controller('Umbraco.Editors.ContentBlueprint.EditController', ContentBlueprintEditController); + function startUpVideosDashboardController($scope, xmlhelper, $log, $http) { + $scope.videos = []; + $scope.init = function (url) { + var proxyUrl = 'dashboard/feedproxy.aspx?url=' + url; + $http.get(proxyUrl).then(function (data) { + var feed = $(data.data); + $('item', feed).each(function (i, item) { + var video = {}; + video.thumbnail = $(item).find('thumbnail').attr('url'); + video.title = $('title', item).text(); + video.link = $('guid', item).text(); + $scope.videos.push(video); + }); + }); + }; + } + angular.module('umbraco').controller('Umbraco.Dashboard.StartupVideosController', startUpVideosDashboardController); + function startUpDynamicContentController($timeout, $scope, dashboardResource, assetsService, tourService, eventsService) { + var vm = this; + var evts = []; + vm.loading = true; + vm.showDefault = false; + vm.startTour = startTour; + function onInit() { + // load tours + tourService.getGroupedTours().then(function (groupedTours) { + vm.tours = groupedTours; + }); + } + function startTour(tour) { + tourService.startTour(tour); + } + // default dashboard content + vm.defaultDashboard = { + infoBoxes: [ + { + title: 'Documentation', + description: 'Find the answers to your Umbraco questions', + url: 'https://our.umbraco.com/documentation/?utm_source=core&utm_medium=dashboard&utm_content=text&utm_campaign=documentation/' + }, + { + title: 'Community', + description: 'Find the answers or ask your Umbraco questions', + url: 'https://our.umbraco.com/?utm_source=core&utm_medium=dashboard&utm_content=text&utm_campaign=our_forum' + }, + { + title: 'Umbraco.tv', + description: 'Tutorial videos (some are free, some are on subscription)', + url: 'https://umbraco.tv/?utm_source=core&utm_medium=dashboard&utm_content=text&utm_campaign=tutorial_videos' + }, + { + title: 'Training', + description: 'Real-life training and official Umbraco certifications', + url: 'https://umbraco.com/training/?utm_source=core&utm_medium=dashboard&utm_content=text&utm_campaign=training' + } + ], + articles: [ + { + title: 'Umbraco.TV - Learn from the source!', + description: 'Umbraco.TV will help you go from zero to Umbraco hero at a pace that suits you. Our easy to follow online training videos will give you the fundamental knowledge to start building awesome Umbraco websites.', + img: 'views/dashboard/default/umbracotv.jpg', + url: 'https://umbraco.tv/?utm_source=core&utm_medium=dashboard&utm_content=image&utm_campaign=tv', + altText: 'Umbraco.TV - Hours of Umbraco Video Tutorials', + buttonText: 'Visit Umbraco.TV' + }, + { + title: 'Our Umbraco - The Friendliest Community', + description: 'Our Umbraco - the official community site is your one stop for everything Umbraco. Whether you need a question answered or looking for cool plugins, the world\'s best and friendliest community is just a click away.', + img: 'views/dashboard/default/ourumbraco.jpg', + url: 'https://our.umbraco.com/?utm_source=core&utm_medium=dashboard&utm_content=image&utm_campaign=our', + altText: 'Our Umbraco', + buttonText: 'Visit Our Umbraco' + } + ] + }; + evts.push(eventsService.on('appState.tour.complete', function (name, completedTour) { + $timeout(function () { + angular.forEach(vm.tours, function (tourGroup) { + angular.forEach(tourGroup, function (tour) { + if (tour.alias === completedTour.alias) { + tour.completed = true; + } + }); + }); + }); + })); + //proxy remote css through the local server + assetsService.loadCss(dashboardResource.getRemoteDashboardCssUrl('content'), $scope); + dashboardResource.getRemoteDashboardContent('content').then(function (data) { + vm.loading = false; + //test if we have received valid data + //we capture it like this, so we avoid UI errors - which automatically triggers ui based on http response code + if (data && data.sections) { + vm.dashboard = data; + } else { + vm.showDefault = true; + } + }, function (exception) { + console.error(exception); + vm.loading = false; + vm.showDefault = true; + }); + onInit(); + } + angular.module('umbraco').controller('Umbraco.Dashboard.StartUpDynamicContentController', startUpDynamicContentController); + function FormsController($scope, $route, $cookieStore, packageResource, localizationService) { + $scope.installForms = function () { + $scope.state = localizationService.localize('packager_installStateDownloading'); + packageResource.fetch('CD44CF39-3D71-4C19-B6EE-948E1FAF0525').then(function (pack) { + $scope.state = localizationService.localize('packager_installStateImporting'); + return packageResource.import(pack); + }, $scope.error).then(function (pack) { + $scope.state = localizationService.localize('packager_installStateInstalling'); + return packageResource.installFiles(pack); + }, $scope.error).then(function (pack) { + $scope.state = localizationService.localize('packager_installStateRestarting'); + return packageResource.installData(pack); + }, $scope.error).then(function (pack) { + $scope.state = localizationService.localize('packager_installStateComplete'); + return packageResource.cleanUp(pack); + }, $scope.error).then($scope.complete, $scope.error); + }; + $scope.complete = function (result) { + var url = window.location.href + '?init=true'; + $cookieStore.put('umbPackageInstallId', result.packageGuid); + window.location.reload(true); + }; + $scope.error = function (err) { + $scope.state = undefined; + $scope.error = err; + //This will return a rejection meaning that the promise change above will stop + return $q.reject(); + }; + function Video_player(videoId) { + // Get dom elements + this.container = document.getElementById(videoId); + this.video = this.container.getElementsByTagName('video')[0]; + //Create controls + this.controls = document.createElement('div'); + this.controls.className = 'video-controls'; + this.seek_bar = document.createElement('input'); + this.seek_bar.className = 'seek-bar'; + this.seek_bar.type = 'range'; + this.seek_bar.setAttribute('value', '0'); + this.loader = document.createElement('div'); + this.loader.className = 'loader'; + this.progress_bar = document.createElement('span'); + this.progress_bar.className = 'progress-bar'; + // Insert controls + this.controls.appendChild(this.seek_bar); + this.container.appendChild(this.controls); + this.controls.appendChild(this.loader); + this.loader.appendChild(this.progress_bar); + } + Video_player.prototype.seeking = function () { + // get the value of the seekbar (hidden input[type="range"]) + var time = this.video.duration * (this.seek_bar.value / 100); + // Update video to seekbar value + this.video.currentTime = time; + }; + // Stop video when user initiates seeking + Video_player.prototype.start_seek = function () { + this.video.pause(); + }; + // Start video when user stops seeking + Video_player.prototype.stop_seek = function () { + this.video.play(); + }; + // Update the progressbar (span.loader) according to video.currentTime + Video_player.prototype.update_progress_bar = function () { + // Get video progress in % + var value = 100 / this.video.duration * this.video.currentTime; + // Update progressbar + this.progress_bar.style.width = value + '%'; + }; + // Bind progressbar to mouse when seeking + Video_player.prototype.handle_mouse_move = function (event) { + // Get position of progressbar relative to browser window + var pos = this.progress_bar.getBoundingClientRect().left; + // Make sure event is reckonized cross-browser + event = event || window.event; + // Update progressbar + this.progress_bar.style.width = event.clientX - pos + 'px'; + }; + // Eventlisteners for seeking + Video_player.prototype.video_event_handler = function (videoPlayer, interval) { + // Update the progress bar + var animate_progress_bar = setInterval(function () { + videoPlayer.update_progress_bar(); + }, interval); + // Fire when input value changes (user seeking) + videoPlayer.seek_bar.addEventListener('change', function () { + videoPlayer.seeking(); + }); + // Fire when user clicks on seekbar + videoPlayer.seek_bar.addEventListener('mousedown', function (clickEvent) { + // Pause video playback + videoPlayer.start_seek(); + // Stop updating progressbar according to video progress + clearInterval(animate_progress_bar); + // Update progressbar to where user clicks + videoPlayer.handle_mouse_move(clickEvent); + // Bind progressbar to cursor + window.onmousemove = function (moveEvent) { + videoPlayer.handle_mouse_move(moveEvent); + }; + }); + // Fire when user releases seekbar + videoPlayer.seek_bar.addEventListener('mouseup', function () { + // Unbind progressbar from cursor + window.onmousemove = null; + // Start video playback + videoPlayer.stop_seek(); + // Animate the progressbar + animate_progress_bar = setInterval(function () { + videoPlayer.update_progress_bar(); + }, interval); + }); + }; + var videoPlayer = new Video_player('video_1'); + videoPlayer.video_event_handler(videoPlayer, 17); + } + angular.module('umbraco').controller('Umbraco.Dashboard.FormsDashboardController', FormsController); + function startupLatestEditsController($scope) { + } + angular.module('umbraco').controller('Umbraco.Dashboard.StartupLatestEditsController', startupLatestEditsController); + function MediaFolderBrowserDashboardController($rootScope, $scope, $location, contentTypeResource, userService) { + var currentUser = {}; + userService.getCurrentUser().then(function (user) { + currentUser = user; + // check if the user has access to the root which they will require to see this dashboard + if (currentUser.startMediaIds.indexOf(-1) >= 0) { + //get the system media listview + contentTypeResource.getPropertyTypeScaffold(-96).then(function (dt) { + $scope.fakeProperty = { + alias: 'contents', + config: dt.config, + description: '', + editor: dt.editor, + hideLabel: true, + id: 1, + label: 'Contents:', + validation: { + mandatory: false, + pattern: null + }, + value: '', + view: dt.view + }; + }); + } else if (currentUser.startMediaIds.length > 0) { + // redirect to start node + $location.path('/media/media/edit/' + (currentUser.startMediaIds.length === 0 ? -1 : currentUser.startMediaIds[0])); + } + }); + } + angular.module('umbraco').controller('Umbraco.Dashboard.MediaFolderBrowserDashboardController', MediaFolderBrowserDashboardController); + function ExamineMgmtController($scope, umbRequestHelper, $log, $http, $q, $timeout) { + $scope.indexerDetails = []; + $scope.searcherDetails = []; + $scope.loading = true; + function checkProcessing(indexer, checkActionName) { + umbRequestHelper.resourcePromise($http.post(umbRequestHelper.getApiUrl('examineMgmtBaseUrl', checkActionName, { indexerName: indexer.name })), 'Failed to check index processing').then(function (data) { + if (data !== null && data !== 'null') { //copy all resulting properties for (var k in data) { indexer[k] = data[k]; } indexer.isProcessing = false; } else { - $timeout(function() { - //don't continue if we've tried 100 times - if (indexer.processingAttempts < 100) { - checkProcessing(indexer, checkActionName); - //add an attempt - indexer.processingAttempts++; - } else { - //we've exceeded 100 attempts, stop processing - indexer.isProcessing = false; - } - }, - 1000); + $timeout(function () { + //don't continue if we've tried 100 times + if (indexer.processingAttempts < 100) { + checkProcessing(indexer, checkActionName); + //add an attempt + indexer.processingAttempts++; + } else { + //we've exceeded 100 attempts, stop processing + indexer.isProcessing = false; + } + }, 1000); } }); - } - - $scope.search = function(searcher, e) { - if (e && e.keyCode !== 13) { - return; } - - umbRequestHelper.resourcePromise( - $http.get(umbRequestHelper.getApiUrl("examineMgmtBaseUrl", - "GetSearchResults", - { - searcherName: searcher.name, - query: encodeURIComponent(searcher.searchText), - queryType: searcher.searchType - })), - 'Failed to search') - .then(function(searchResults) { + $scope.search = function (searcher, e) { + if (e && e.keyCode !== 13) { + return; + } + umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('examineMgmtBaseUrl', 'GetSearchResults', { + searcherName: searcher.name, + query: encodeURIComponent(searcher.searchText), + queryType: searcher.searchType + })), 'Failed to search').then(function (searchResults) { searcher.isSearching = true; searcher.searchResults = searchResults; }); - } - - $scope.toggle = function(provider, propName) { - if (provider[propName] !== undefined) { - provider[propName] = !provider[propName]; - } else { - provider[propName] = true; - } - } - - $scope.rebuildIndex = function(indexer) { - if (confirm("This will cause the index to be rebuilt. " + - "Depending on how much content there is in your site this could take a while. " + - "It is not recommended to rebuild an index during times of high website traffic " + - "or when editors are editing content.")) { - - indexer.isProcessing = true; - indexer.processingAttempts = 0; - - umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper.getApiUrl("examineMgmtBaseUrl", - "PostRebuildIndex", - { indexerName: indexer.name })), - 'Failed to rebuild index') - .then(function() { - + }; + $scope.toggle = function (provider, propName) { + if (provider[propName] !== undefined) { + provider[propName] = !provider[propName]; + } else { + provider[propName] = true; + } + }; + $scope.rebuildIndex = function (indexer) { + if (confirm('This will cause the index to be rebuilt. ' + 'Depending on how much content there is in your site this could take a while. ' + 'It is not recommended to rebuild an index during times of high website traffic ' + 'or when editors are editing content.')) { + indexer.isProcessing = true; + indexer.processingAttempts = 0; + umbRequestHelper.resourcePromise($http.post(umbRequestHelper.getApiUrl('examineMgmtBaseUrl', 'PostRebuildIndex', { indexerName: indexer.name })), 'Failed to rebuild index').then(function () { //rebuilding has started, nothing is returned accept a 200 status code. //lets poll to see if it is done. - $timeout(function() { - checkProcessing(indexer, "PostCheckRebuildIndex"); - }, - 1000); - + $timeout(function () { + checkProcessing(indexer, 'PostCheckRebuildIndex'); + }, 1000); }); - } - } - - $scope.optimizeIndex = function(indexer) { - if (confirm("This will cause the index to be optimized which will improve its performance. " + - "It is not recommended to optimize an index during times of high website traffic " + - "or when editors are editing content.")) { - indexer.isProcessing = true; - - umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper.getApiUrl("examineMgmtBaseUrl", - "PostOptimizeIndex", - { indexerName: indexer.name })), - 'Failed to optimize index') - .then(function() { - + } + }; + $scope.optimizeIndex = function (indexer) { + if (confirm('This will cause the index to be optimized which will improve its performance. ' + 'It is not recommended to optimize an index during times of high website traffic ' + 'or when editors are editing content.')) { + indexer.isProcessing = true; + umbRequestHelper.resourcePromise($http.post(umbRequestHelper.getApiUrl('examineMgmtBaseUrl', 'PostOptimizeIndex', { indexerName: indexer.name })), 'Failed to optimize index').then(function () { //optimizing has started, nothing is returned accept a 200 status code. //lets poll to see if it is done. - $timeout(function() { - checkProcessing(indexer, "PostCheckOptimizeIndex"); - }, - 1000); - + $timeout(function () { + checkProcessing(indexer, 'PostCheckOptimizeIndex'); + }, 1000); }); - } - } - - $scope.closeSearch = function(searcher) { - searcher.isSearching = true; - } - - //go get the data - - //combine two promises and execute when they are both done - $q.all([ - + } + }; + $scope.closeSearch = function (searcher) { + searcher.isSearching = true; + }; + //go get the data + //combine two promises and execute when they are both done + $q.all([ //get the indexer details - umbRequestHelper.resourcePromise( - $http.get(umbRequestHelper.getApiUrl("examineMgmtBaseUrl", "GetIndexerDetails")), - 'Failed to retrieve indexer details') - .then(function(data) { + umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('examineMgmtBaseUrl', 'GetIndexerDetails')), 'Failed to retrieve indexer details').then(function (data) { $scope.indexerDetails = data; }), - //get the searcher details - umbRequestHelper.resourcePromise( - $http.get(umbRequestHelper.getApiUrl("examineMgmtBaseUrl", "GetSearcherDetails")), - 'Failed to retrieve searcher details') - .then(function(data) { + umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('examineMgmtBaseUrl', 'GetSearcherDetails')), 'Failed to retrieve searcher details').then(function (data) { $scope.searcherDetails = data; for (var s in $scope.searcherDetails) { - $scope.searcherDetails[s].searchType = "text"; + $scope.searcherDetails[s].searchType = 'text'; } }) - ]) - .then(function() { + ]).then(function () { //all init loading is complete $scope.loading = false; }); -} - -angular.module("umbraco").controller("Umbraco.Dashboard.ExamineMgmtController", ExamineMgmtController); -(function() { - "use strict"; - - function HealthCheckController($scope, healthCheckResource) { - var SUCCESS = 0; - var WARNING = 1; - var ERROR = 2; - var INFO = 3; - - var vm = this; - - vm.viewState = "list"; - vm.groups = []; - vm.selectedGroup = {}; - - vm.getStatus = getStatus; - vm.executeAction = executeAction; - vm.checkAllGroups = checkAllGroups; - vm.checkAllInGroup = checkAllInGroup; - vm.openGroup = openGroup; - vm.setViewState = setViewState; - - // Get a (grouped) list of all health checks - healthCheckResource.getAllChecks() - .then(function(response) { + } + angular.module('umbraco').controller('Umbraco.Dashboard.ExamineMgmtController', ExamineMgmtController); + (function () { + 'use strict'; + function HealthCheckController($scope, healthCheckResource) { + var SUCCESS = 0; + var WARNING = 1; + var ERROR = 2; + var INFO = 3; + var vm = this; + vm.viewState = 'list'; + vm.groups = []; + vm.selectedGroup = {}; + vm.getStatus = getStatus; + vm.executeAction = executeAction; + vm.checkAllGroups = checkAllGroups; + vm.checkAllInGroup = checkAllInGroup; + vm.openGroup = openGroup; + vm.setViewState = setViewState; + // Get a (grouped) list of all health checks + healthCheckResource.getAllChecks().then(function (response) { vm.groups = response; }); - - function setGroupGlobalResultType(group) { - var totalSuccess = 0; - var totalError = 0; - var totalWarning = 0; - var totalInfo = 0; - - // count total number of statusses - angular.forEach(group.checks, - function(check) { - angular.forEach(check.status, - function(status) { - switch (status.resultType) { - case SUCCESS: - totalSuccess = totalSuccess + 1; - break; - case WARNING: - totalWarning = totalWarning + 1; - break; - case ERROR: - totalError = totalError + 1; - break; - case INFO: - totalInfo = totalInfo + 1; - break; - } - }); + function setGroupGlobalResultType(group) { + var totalSuccess = 0; + var totalError = 0; + var totalWarning = 0; + var totalInfo = 0; + // count total number of statusses + angular.forEach(group.checks, function (check) { + angular.forEach(check.status, function (status) { + switch (status.resultType) { + case SUCCESS: + totalSuccess = totalSuccess + 1; + break; + case WARNING: + totalWarning = totalWarning + 1; + break; + case ERROR: + totalError = totalError + 1; + break; + case INFO: + totalInfo = totalInfo + 1; + break; + } + }); }); - - group.totalSuccess = totalSuccess; - group.totalError = totalError; - group.totalWarning = totalWarning; - group.totalInfo = totalInfo; - - } - - // Get the status of an individual check - function getStatus(check) { - check.loading = true; - check.status = null; - healthCheckResource.getStatus(check.id) - .then(function(response) { + group.totalSuccess = totalSuccess; + group.totalError = totalError; + group.totalWarning = totalWarning; + group.totalInfo = totalInfo; + } + // Get the status of an individual check + function getStatus(check) { + check.loading = true; + check.status = null; + healthCheckResource.getStatus(check.id).then(function (response) { check.loading = false; check.status = response; }); - } - - function executeAction(check, index, action) { - check.loading = true; - healthCheckResource.executeAction(action) - .then(function(response) { + } + function executeAction(check, index, action) { + check.loading = true; + healthCheckResource.executeAction(action).then(function (response) { check.status[index] = response; check.loading = false; }); - } - - function checkAllGroups(groups) { - // set number of checks which has been executed - for (var i = 0; i < groups.length; i++) { - var group = groups[i]; - checkAllInGroup(group, group.checks); } - vm.groups = groups; - } - - function checkAllInGroup(group, checks) { - group.checkCounter = 0; - group.loading = true; - - angular.forEach(checks, - function(check) { - + function checkAllGroups(groups) { + // set number of checks which has been executed + for (var i = 0; i < groups.length; i++) { + var group = groups[i]; + checkAllInGroup(group, group.checks); + } + vm.groups = groups; + } + function checkAllInGroup(group, checks) { + group.checkCounter = 0; + group.loading = true; + angular.forEach(checks, function (check) { check.loading = true; - - healthCheckResource.getStatus(check.id) - .then(function(response) { - check.status = response; - group.checkCounter = group.checkCounter + 1; - check.loading = false; - - // when all checks are done, set global group result - if (group.checkCounter === checks.length) { - setGroupGlobalResultType(group); - group.loading = false; - } - }); - }); - } - - function openGroup(group) { - vm.selectedGroup = group; - vm.viewState = "details"; - } - - function setViewState(state) { - vm.viewState = state; - - if (state === 'list') { - - for (var i = 0; i < vm.groups.length; i++) { - var group = vm.groups[i]; - setGroupGlobalResultType(group); - } - } - } - } - - angular.module("umbraco").controller("Umbraco.Dashboard.HealthCheckController", HealthCheckController); -})(); - -(function() { - "use strict"; - - function RedirectUrlsController($scope, redirectUrlsResource, notificationsService, localizationService, $q) { - //...todo - //search by url or url part - //search by domain - //display domain in dashboard results? - - //used to cancel any request in progress if another one needs to take it's place - var vm = this; - var canceler = null; - - vm.dashboard = { - searchTerm: "", - loading: false, - urlTrackerDisabled: false, - userIsAdmin: false - }; - - vm.pagination = { - pageIndex: 0, - pageNumber: 1, - totalPages: 1, - pageSize: 20 - }; - - vm.goToPage = goToPage; - vm.search = search; - vm.removeRedirect = removeRedirect; - vm.disableUrlTracker = disableUrlTracker; - vm.enableUrlTracker = enableUrlTracker; - vm.filter = filter; - vm.checkEnabled = checkEnabled; - - function activate() { - vm.checkEnabled().then(function() { - vm.search(); - }); - } - - function checkEnabled() { - vm.dashboard.loading = true; - return redirectUrlsResource.getEnableState().then(function (response) { - vm.dashboard.urlTrackerDisabled = response.enabled !== true; - vm.dashboard.userIsAdmin = response.userIsAdmin; - vm.dashboard.loading = false; - }); - } - - function goToPage(pageNumber) { - vm.pagination.pageIndex = pageNumber - 1; - vm.pagination.pageNumber = pageNumber; - vm.search(); - } - - function search() { - - vm.dashboard.loading = true; - - var searchTerm = vm.dashboard.searchTerm; - if (searchTerm === undefined) { - searchTerm = ""; - } - - redirectUrlsResource.searchRedirectUrls(searchTerm, vm.pagination.pageIndex, vm.pagination.pageSize).then(function(response) { - - vm.redirectUrls = response.searchResults; - - // update pagination - vm.pagination.pageIndex = response.currentPage; - vm.pagination.pageNumber = response.currentPage + 1; - vm.pagination.totalPages = response.pageCount; - - vm.dashboard.loading = false; - - }); - } - - function removeRedirect(redirectToDelete) { - localizationService.localize("redirectUrls_confirmRemove", [redirectToDelete.originalUrl, redirectToDelete.destinationUrl]).then(function (value) { - var toggleConfirm = confirm(value); - - if (toggleConfirm) { - redirectUrlsResource.deleteRedirectUrl(redirectToDelete.redirectId).then(function () { - - var index = vm.redirectUrls.indexOf(redirectToDelete); - vm.redirectUrls.splice(index, 1); - notificationsService.success(localizationService.localize("redirectUrls_redirectRemoved")); - - // check if new redirects needs to be loaded - if (vm.redirectUrls.length === 0 && vm.pagination.totalPages > 1) { - - // if we are not on the first page - get records from the previous - if (vm.pagination.pageIndex > 0) { - vm.pagination.pageIndex = vm.pagination.pageIndex - 1; - vm.pagination.pageNumber = vm.pagination.pageNumber - 1; - } - - search(); + healthCheckResource.getStatus(check.id).then(function (response) { + check.status = response; + group.checkCounter = group.checkCounter + 1; + check.loading = false; + // when all checks are done, set global group result + if (group.checkCounter === checks.length) { + setGroupGlobalResultType(group); + group.loading = false; } - }, function (error) { - notificationsService.error(localizationService.localize("redirectUrls_redirectRemoveError")); }); + }); + } + function openGroup(group) { + vm.selectedGroup = group; + vm.viewState = 'details'; + } + function setViewState(state) { + vm.viewState = state; + if (state === 'list') { + for (var i = 0; i < vm.groups.length; i++) { + var group = vm.groups[i]; + setGroupGlobalResultType(group); + } } - }); - } - - function disableUrlTracker() { - localizationService.localize("redirectUrls_confirmDisable").then(function(value) { - var toggleConfirm = confirm(value); - if (toggleConfirm) { - - redirectUrlsResource.toggleUrlTracker(true).then(function () { - activate(); - notificationsService.success(localizationService.localize("redirectUrls_disabledConfirm")); - }, function (error) { - notificationsService.warning(localizationService.localize("redirectUrls_disableError")); - }); - - } - }); - } - - function enableUrlTracker() { - redirectUrlsResource.toggleUrlTracker(false).then(function() { - activate(); - notificationsService.success(localizationService.localize("redirectUrls_enabledConfirm")); - }, function(error) { - notificationsService.warning(localizationService.localize("redirectUrls_enableError")); - }); - } - - var filterDebounced = _.debounce(function(e) { - - $scope.$apply(function() { - - //a canceler exists, so perform the cancelation operation and reset - if (canceler) { - canceler.resolve(); - canceler = $q.defer(); - } else { - canceler = $q.defer(); - } - - vm.search(); - - }); - - }, 200); - - function filter() { - vm.dashboard.loading = true; - filterDebounced(); - } - - activate(); - - } - - angular.module("umbraco").controller("Umbraco.Dashboard.RedirectUrlsController", RedirectUrlsController); -})(); - -function XmlDataIntegrityReportController($scope, umbRequestHelper, $log, $http) { - - function check(item) { - var action = item.check; - umbRequestHelper.resourcePromise( - $http.get(umbRequestHelper.getApiUrl("xmlDataIntegrityBaseUrl", action)), - 'Failed to retrieve data integrity status') - .then(function(result) { - item.checking = false; - item.invalid = result === "false"; - }); - } - - $scope.fix = function(item) { - var action = item.fix; - if (item.fix) { - if (confirm("This will cause all xml structures for this type to be rebuilt. " + - "Depending on how much content there is in your site this could take a while. " + - "It is not recommended to rebuild xml structures if they are not out of sync, during times of high website traffic " + - "or when editors are editing content.")) { - item.fixing = true; - umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper.getApiUrl("xmlDataIntegrityBaseUrl", action)), - 'Failed to retrieve data integrity status') - .then(function(result) { - item.fixing = false; - item.invalid = result === "false"; - }); } } - } - - $scope.items = { - "contentXml": { - label: "Content in the cmsContentXml table", - checking: true, - fixing: false, - fix: "FixContentXmlTable", - check: "CheckContentXmlTable" - }, - "mediaXml": { - label: "Media in the cmsContentXml table", - checking: true, - fixing: false, - fix: "FixMediaXmlTable", - check: "CheckMediaXmlTable" - }, - "memberXml": { - label: "Members in the cmsContentXml table", - checking: true, - fixing: false, - fix: "FixMembersXmlTable", - check: "CheckMembersXmlTable" + angular.module('umbraco').controller('Umbraco.Dashboard.HealthCheckController', HealthCheckController); + }()); + (function () { + 'use strict'; + function RedirectUrlsController($scope, redirectUrlsResource, notificationsService, localizationService, $q) { + //...todo + //search by url or url part + //search by domain + //display domain in dashboard results? + //used to cancel any request in progress if another one needs to take it's place + var vm = this; + var canceler = null; + vm.dashboard = { + searchTerm: '', + loading: false, + urlTrackerDisabled: false, + userIsAdmin: false + }; + vm.pagination = { + pageIndex: 0, + pageNumber: 1, + totalPages: 1, + pageSize: 20 + }; + vm.goToPage = goToPage; + vm.search = search; + vm.removeRedirect = removeRedirect; + vm.disableUrlTracker = disableUrlTracker; + vm.enableUrlTracker = enableUrlTracker; + vm.filter = filter; + vm.checkEnabled = checkEnabled; + function activate() { + vm.checkEnabled().then(function () { + vm.search(); + }); + } + function checkEnabled() { + vm.dashboard.loading = true; + return redirectUrlsResource.getEnableState().then(function (response) { + vm.dashboard.urlTrackerDisabled = response.enabled !== true; + vm.dashboard.userIsAdmin = response.userIsAdmin; + vm.dashboard.loading = false; + }); + } + function goToPage(pageNumber) { + vm.pagination.pageIndex = pageNumber - 1; + vm.pagination.pageNumber = pageNumber; + vm.search(); + } + function search() { + vm.dashboard.loading = true; + var searchTerm = vm.dashboard.searchTerm; + if (searchTerm === undefined) { + searchTerm = ''; + } + redirectUrlsResource.searchRedirectUrls(searchTerm, vm.pagination.pageIndex, vm.pagination.pageSize).then(function (response) { + vm.redirectUrls = response.searchResults; + // update pagination + vm.pagination.pageIndex = response.currentPage; + vm.pagination.pageNumber = response.currentPage + 1; + vm.pagination.totalPages = response.pageCount; + vm.dashboard.loading = false; + }); + } + function removeRedirect(redirectToDelete) { + localizationService.localize('redirectUrls_confirmRemove', [ + redirectToDelete.originalUrl, + redirectToDelete.destinationUrl + ]).then(function (value) { + var toggleConfirm = confirm(value); + if (toggleConfirm) { + redirectUrlsResource.deleteRedirectUrl(redirectToDelete.redirectId).then(function () { + var index = vm.redirectUrls.indexOf(redirectToDelete); + vm.redirectUrls.splice(index, 1); + notificationsService.success(localizationService.localize('redirectUrls_redirectRemoved')); + // check if new redirects needs to be loaded + if (vm.redirectUrls.length === 0 && vm.pagination.totalPages > 1) { + // if we are not on the first page - get records from the previous + if (vm.pagination.pageIndex > 0) { + vm.pagination.pageIndex = vm.pagination.pageIndex - 1; + vm.pagination.pageNumber = vm.pagination.pageNumber - 1; + } + search(); + } + }, function (error) { + notificationsService.error(localizationService.localize('redirectUrls_redirectRemoveError')); + }); + } + }); + } + function disableUrlTracker() { + localizationService.localize('redirectUrls_confirmDisable').then(function (value) { + var toggleConfirm = confirm(value); + if (toggleConfirm) { + redirectUrlsResource.toggleUrlTracker(true).then(function () { + activate(); + notificationsService.success(localizationService.localize('redirectUrls_disabledConfirm')); + }, function (error) { + notificationsService.warning(localizationService.localize('redirectUrls_disableError')); + }); + } + }); + } + function enableUrlTracker() { + redirectUrlsResource.toggleUrlTracker(false).then(function () { + activate(); + notificationsService.success(localizationService.localize('redirectUrls_enabledConfirm')); + }, function (error) { + notificationsService.warning(localizationService.localize('redirectUrls_enableError')); + }); + } + var filterDebounced = _.debounce(function (e) { + $scope.$apply(function () { + //a canceler exists, so perform the cancelation operation and reset + if (canceler) { + canceler.resolve(); + canceler = $q.defer(); + } else { + canceler = $q.defer(); + } + vm.search(); + }); + }, 200); + function filter() { + vm.dashboard.loading = true; + filterDebounced(); + } + activate(); + } + angular.module('umbraco').controller('Umbraco.Dashboard.RedirectUrlsController', RedirectUrlsController); + }()); + function XmlDataIntegrityReportController($scope, umbRequestHelper, $log, $http) { + function check(item) { + var action = item.check; + umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('xmlDataIntegrityBaseUrl', action)), 'Failed to retrieve data integrity status').then(function (result) { + item.checking = false; + item.invalid = result === 'false'; + }); + } + $scope.fix = function (item) { + var action = item.fix; + if (item.fix) { + if (confirm('This will cause all xml structures for this type to be rebuilt. ' + 'Depending on how much content there is in your site this could take a while. ' + 'It is not recommended to rebuild xml structures if they are not out of sync, during times of high website traffic ' + 'or when editors are editing content.')) { + item.fixing = true; + umbRequestHelper.resourcePromise($http.post(umbRequestHelper.getApiUrl('xmlDataIntegrityBaseUrl', action)), 'Failed to retrieve data integrity status').then(function (result) { + item.fixing = false; + item.invalid = result === 'false'; + }); + } + } + }; + $scope.items = { + 'contentXml': { + label: 'Content in the cmsContentXml table', + checking: true, + fixing: false, + fix: 'FixContentXmlTable', + check: 'CheckContentXmlTable' + }, + 'mediaXml': { + label: 'Media in the cmsContentXml table', + checking: true, + fixing: false, + fix: 'FixMediaXmlTable', + check: 'CheckMediaXmlTable' + }, + 'memberXml': { + label: 'Members in the cmsContentXml table', + checking: true, + fixing: false, + fix: 'FixMembersXmlTable', + check: 'CheckMembersXmlTable' + } + }; + for (var i in $scope.items) { + check($scope.items[i]); } - }; - - for (var i in $scope.items) { - check($scope.items[i]); } -} - -angular.module("umbraco").controller("Umbraco.Dashboard.XmlDataIntegrityReportController", XmlDataIntegrityReportController); -/** + angular.module('umbraco').controller('Umbraco.Dashboard.XmlDataIntegrityReportController', XmlDataIntegrityReportController); + /** * @ngdoc controller * @name Umbraco.Editors.DataType.CreateController * @function @@ -6097,48 +6586,45 @@ angular.module("umbraco").controller("Umbraco.Dashboard.XmlDataIntegrityReportCo * @description * The controller for the data type creation dialog */ -function DataTypeCreateController($scope, $location, navigationService, dataTypeResource, formHelper, appState) { - - $scope.model = { - folderName: "", - creatingFolder: false - }; - - var node = $scope.dialogOptions.currentNode; - - $scope.showCreateFolder = function() { - $scope.model.creatingFolder = true; - } - - $scope.createContainer = function () { - if (formHelper.submitForm({ scope: $scope, formCtrl: this.createFolderForm, statusMessage: "Creating folder..." })) { - dataTypeResource.createContainer(node.id, $scope.model.folderName).then(function (folderId) { - - navigationService.hideMenu(); - var currPath = node.path ? node.path : "-1"; - navigationService.syncTree({ tree: "datatypes", path: currPath + "," + folderId, forceReload: true, activate: true }); - - formHelper.resetForm({ scope: $scope }); - - var section = appState.getSectionState("currentSection"); - - }, function(err) { - - //TODO: Handle errors - }); + function DataTypeCreateController($scope, $location, navigationService, dataTypeResource, formHelper, appState) { + $scope.model = { + folderName: '', + creatingFolder: false + }; + var node = $scope.dialogOptions.currentNode; + $scope.showCreateFolder = function () { + $scope.model.creatingFolder = true; + }; + $scope.createContainer = function () { + if (formHelper.submitForm({ + scope: $scope, + formCtrl: this.createFolderForm, + statusMessage: 'Creating folder...' + })) { + dataTypeResource.createContainer(node.id, $scope.model.folderName).then(function (folderId) { + navigationService.hideMenu(); + var currPath = node.path ? node.path : '-1'; + navigationService.syncTree({ + tree: 'datatypes', + path: currPath + ',' + folderId, + forceReload: true, + activate: true + }); + formHelper.resetForm({ scope: $scope }); + var section = appState.getSectionState('currentSection'); + }, function (err) { + }); + } + ; + }; + $scope.createDataType = function () { + $location.search('create', null); + $location.path('/developer/datatypes/edit/' + node.id).search('create', 'true'); + navigationService.hideMenu(); }; } - - $scope.createDataType = function() { - $location.search('create', null); - $location.path("/developer/datatypes/edit/" + node.id).search("create", "true"); - navigationService.hideMenu(); - } -} - -angular.module('umbraco').controller("Umbraco.Editors.DataType.CreateController", DataTypeCreateController); - -/** + angular.module('umbraco').controller('Umbraco.Editors.DataType.CreateController', DataTypeCreateController); + /** * @ngdoc controller * @name Umbraco.Editors.ContentDeleteController * @function @@ -6146,434 +6632,247 @@ angular.module('umbraco').controller("Umbraco.Editors.DataType.CreateController" * @description * The controller for deleting content */ -function DataTypeDeleteController($scope, dataTypeResource, treeService, navigationService) { - - $scope.performDelete = function() { - - //mark it for deletion (used in the UI) - $scope.currentNode.loading = true; - dataTypeResource.deleteById($scope.currentNode.id).then(function () { - $scope.currentNode.loading = false; - - //get the root node before we remove it - var rootNode = treeService.getTreeRoot($scope.currentNode); - - //TODO: Need to sync tree, etc... - treeService.removeNode($scope.currentNode); - navigationService.hideMenu(); - }); - - }; - - $scope.performContainerDelete = function () { - - //mark it for deletion (used in the UI) - $scope.currentNode.loading = true; - dataTypeResource.deleteContainerById($scope.currentNode.id).then(function () { - $scope.currentNode.loading = false; - - //get the root node before we remove it - var rootNode = treeService.getTreeRoot($scope.currentNode); - - //TODO: Need to sync tree, etc... - treeService.removeNode($scope.currentNode); - navigationService.hideMenu(); - }); - - }; - - $scope.cancel = function() { - navigationService.hideDialog(); - }; -} - -angular.module("umbraco").controller("Umbraco.Editors.DataType.DeleteController", DataTypeDeleteController); - -/** - * @ngdoc controller - * @name Umbraco.Editors.DataType.EditController - * @function - * - * @description - * The controller for the content editor - */ -function DataTypeEditController($scope, $routeParams, $location, appState, navigationService, treeService, dataTypeResource, notificationsService, angularHelper, serverValidationManager, contentEditingHelper, formHelper, editorState, dataTypeHelper, eventsService) { - - //setup scope vars - $scope.page = {}; - $scope.page.loading = false; - $scope.page.nameLocked = false; - $scope.page.menu = {}; - $scope.page.menu.currentSection = appState.getSectionState("currentSection"); - $scope.page.menu.currentNode = null; - var evts = []; - - //method used to configure the pre-values when we retrieve them from the server - function createPreValueProps(preVals) { - $scope.preValues = []; - for (var i = 0; i < preVals.length; i++) { - $scope.preValues.push({ - hideLabel: preVals[i].hideLabel, - alias: preVals[i].key, - description: preVals[i].description, - label: preVals[i].label, - view: preVals[i].view, - value: preVals[i].value - }); - } - } - - //set up the standard data type props - $scope.properties = { - selectedEditor: { - alias: "selectedEditor", - description: "Select a property editor", - label: "Property editor" - }, - selectedEditorId: { - alias: "selectedEditorId", - label: "Property editor alias" - } - }; - - //setup the pre-values as props - $scope.preValues = []; - - if ($routeParams.create) { - - $scope.page.loading = true; - - //we are creating so get an empty data type item - dataTypeResource.getScaffold($routeParams.id) - .then(function(data) { - - $scope.preValuesLoaded = true; - $scope.content = data; - - setHeaderNameState($scope.content); - - //set a shared state - editorState.set($scope.content); - - $scope.page.loading = false; - - }); - } - else { - loadDataType(); - } - - function loadDataType() { - - $scope.page.loading = true; - - //we are editing so get the content item from the server - dataTypeResource.getById($routeParams.id) - .then(function(data) { - - $scope.preValuesLoaded = true; - $scope.content = data; - - createPreValueProps($scope.content.preValues); - - setHeaderNameState($scope.content); - - //share state - editorState.set($scope.content); - - //in one particular special case, after we've created a new item we redirect back to the edit - // route but there might be server validation errors in the collection which we need to display - // after the redirect, so we will bind all subscriptions which will show the server validation errors - // if there are any and then clear them so the collection no longer persists them. - serverValidationManager.executeAndClearAllSubscriptions(); - - navigationService.syncTree({ tree: "datatypes", path: data.path }).then(function (syncArgs) { - $scope.page.menu.currentNode = syncArgs.node; - }); - - $scope.page.loading = false; - - }); - } - - $scope.$watch("content.selectedEditor", function (newVal, oldVal) { - - //when the value changes, we need to dynamically load in the new editor - if (newVal && (newVal != oldVal && (oldVal || $routeParams.create))) { - //we are editing so get the content item from the server - var currDataTypeId = $routeParams.create ? undefined : $routeParams.id; - dataTypeResource.getPreValues(newVal, currDataTypeId) - .then(function (data) { - $scope.preValuesLoaded = true; - $scope.content.preValues = data; - createPreValueProps($scope.content.preValues); - - setHeaderNameState($scope.content); - - //share state - editorState.set($scope.content); - }); - } - }); - - function setHeaderNameState(content) { - - if(content.isSystem == 1) { - $scope.page.nameLocked = true; - } - - } - - $scope.save = function() { - - if (formHelper.submitForm({ scope: $scope, statusMessage: "Saving..." })) { - - $scope.page.saveButtonState = "busy"; - - dataTypeResource.save($scope.content, $scope.preValues, $routeParams.create) - .then(function(data) { - - formHelper.resetForm({ scope: $scope, notifications: data.notifications }); - - contentEditingHelper.handleSuccessfulSave({ - scope: $scope, - savedContent: data, - rebindCallback: function() { - createPreValueProps(data.preValues); - } - }); - - setHeaderNameState($scope.content); - - //share state - editorState.set($scope.content); - - navigationService.syncTree({ tree: "datatypes", path: data.path, forceReload: true }).then(function (syncArgs) { - $scope.page.menu.currentNode = syncArgs.node; - }); - - $scope.page.saveButtonState = "success"; - - dataTypeHelper.rebindChangedProperties($scope.content, data); - - }, function(err) { - - //NOTE: in the case of data type values we are setting the orig/new props - // to be the same thing since that only really matters for content/media. - contentEditingHelper.handleSaveError({ - redirectOnFailure: false, - err: err - }); - - $scope.page.saveButtonState = "error"; - - //share state - editorState.set($scope.content); - - dataTypeHelper.rebindChangedProperties($scope.content, data); - }); - } - - }; - - evts.push(eventsService.on("app.refreshEditor", function(name, error) { - loadDataType(); - })); - - //ensure to unregister from all events! - $scope.$on('$destroy', function () { - for (var e in evts) { - eventsService.unsubscribe(evts[e]); - } - }); - -} - -angular.module("umbraco").controller("Umbraco.Editors.DataType.EditController", DataTypeEditController); - -angular.module("umbraco") -.controller("Umbraco.Editors.DataType.MoveController", - function ($scope, dataTypeResource, treeService, navigationService, notificationsService, appState, eventsService) { - var dialogOptions = $scope.dialogOptions; - $scope.dialogTreeEventHandler = $({}); - - function nodeSelectHandler(ev, args) { - args.event.preventDefault(); - args.event.stopPropagation(); - - if ($scope.target) { - //un-select if there's a current one selected - $scope.target.selected = false; - } - - $scope.target = args.node; - $scope.target.selected = true; - } - - $scope.move = function () { - - $scope.busy = true; - $scope.error = false; - - dataTypeResource.move({ parentId: $scope.target.id, id: dialogOptions.currentNode.id }) - .then(function (path) { - $scope.error = false; - $scope.success = true; - $scope.busy = false; - - //first we need to remove the node that launched the dialog - treeService.removeNode($scope.currentNode); - - //get the currently edited node (if any) - var activeNode = appState.getTreeState("selectedNode"); - - //we need to do a double sync here: first sync to the moved content - but don't activate the node, - //then sync to the currenlty edited content (note: this might not be the content that was moved!!) - - navigationService.syncTree({ tree: "dataTypes", path: path, forceReload: true, activate: false }).then(function (args) { - if (activeNode) { - var activeNodePath = treeService.getPath(activeNode).join(); - //sync to this node now - depending on what was copied this might already be synced but might not be - navigationService.syncTree({ tree: "dataTypes", path: activeNodePath, forceReload: false, activate: true }); - } - }); - - eventsService.emit('app.refreshEditor'); - - }, function (err) { - $scope.success = false; - $scope.error = err; - $scope.busy = false; - //show any notifications - if (angular.isArray(err.data.notifications)) { - for (var i = 0; i < err.data.notifications.length; i++) { - notificationsService.showNotification(err.data.notifications[i]); - } - } - }); + function DataTypeDeleteController($scope, dataTypeResource, treeService, navigationService) { + $scope.performDelete = function () { + //mark it for deletion (used in the UI) + $scope.currentNode.loading = true; + dataTypeResource.deleteById($scope.currentNode.id).then(function () { + $scope.currentNode.loading = false; + //get the root node before we remove it + var rootNode = treeService.getTreeRoot($scope.currentNode); + //TODO: Need to sync tree, etc... + treeService.removeNode($scope.currentNode); + navigationService.hideMenu(); + }); }; - - $scope.dialogTreeEventHandler.bind("treeNodeSelect", nodeSelectHandler); - - $scope.$on('$destroy', function () { - $scope.dialogTreeEventHandler.unbind("treeNodeSelect", nodeSelectHandler); - }); - }); - -angular.module("umbraco") -.controller("Umbraco.Editors.DocumentTypes.CopyController", - function ($scope, contentTypeResource, treeService, navigationService, notificationsService, appState, eventsService) { - var dialogOptions = $scope.dialogOptions; - $scope.dialogTreeEventHandler = $({}); - - function nodeSelectHandler(ev, args) { - args.event.preventDefault(); - args.event.stopPropagation(); - - if ($scope.target) { - //un-select if there's a current one selected - $scope.target.selected = false; - } - - $scope.target = args.node; - $scope.target.selected = true; - } - - $scope.copy = function () { - - $scope.busy = true; - $scope.error = false; - - contentTypeResource.copy({ parentId: $scope.target.id, id: dialogOptions.currentNode.id }) - .then(function (path) { - $scope.error = false; - $scope.success = true; - $scope.busy = false; - - //get the currently edited node (if any) - var activeNode = appState.getTreeState("selectedNode"); - - //we need to do a double sync here: first sync to the copied content - but don't activate the node, - //then sync to the currenlty edited content (note: this might not be the content that was copied!!) - - navigationService.syncTree({ tree: "documentTypes", path: path, forceReload: true, activate: false }).then(function (args) { - if (activeNode) { - var activeNodePath = treeService.getPath(activeNode).join(); - //sync to this node now - depending on what was copied this might already be synced but might not be - navigationService.syncTree({ tree: "documentTypes", path: activeNodePath, forceReload: false, activate: true }); - } - }); - - }, function (err) { - $scope.success = false; - $scope.error = err; - $scope.busy = false; - //show any notifications - if (angular.isArray(err.data.notifications)) { - for (var i = 0; i < err.data.notifications.length; i++) { - notificationsService.showNotification(err.data.notifications[i]); - } - } - }); + $scope.performContainerDelete = function () { + //mark it for deletion (used in the UI) + $scope.currentNode.loading = true; + dataTypeResource.deleteContainerById($scope.currentNode.id).then(function () { + $scope.currentNode.loading = false; + //get the root node before we remove it + var rootNode = treeService.getTreeRoot($scope.currentNode); + //TODO: Need to sync tree, etc... + treeService.removeNode($scope.currentNode); + navigationService.hideMenu(); + }); }; - - $scope.dialogTreeEventHandler.bind("treeNodeSelect", nodeSelectHandler); - - $scope.$on('$destroy', function () { - $scope.dialogTreeEventHandler.unbind("treeNodeSelect", nodeSelectHandler); - }); - }); - -/** + $scope.cancel = function () { + navigationService.hideDialog(); + }; + } + angular.module('umbraco').controller('Umbraco.Editors.DataType.DeleteController', DataTypeDeleteController); + /** * @ngdoc controller - * @name Umbraco.Editors.DocumentType.CreateController + * @name Umbraco.Editors.DataType.EditController * @function * * @description - * The controller for the doc type creation dialog + * The controller for the content editor */ -function DocumentTypesCreateController($scope, $location, navigationService, contentTypeResource, formHelper, appState, notificationsService, localizationService) { - - $scope.model = { - allowCreateFolder: $scope.dialogOptions.currentNode.parentId === null || $scope.dialogOptions.currentNode.nodeType === "container", - folderName: "", - creatingFolder: false, - }; - - var node = $scope.dialogOptions.currentNode, - localizeCreateFolder = localizationService.localize("defaultdialog_createFolder"); - - $scope.showCreateFolder = function() { - $scope.model.creatingFolder = true; - }; - - $scope.createContainer = function() { - - if (formHelper.submitForm({scope: $scope, formCtrl: this.createFolderForm, statusMessage: localizeCreateFolder})) { - - contentTypeResource.createContainer(node.id, $scope.model.folderName).then(function(folderId) { - - navigationService.hideMenu(); - - var currPath = node.path ? node.path : "-1"; - + function DataTypeEditController($scope, $routeParams, $location, appState, navigationService, treeService, dataTypeResource, notificationsService, angularHelper, serverValidationManager, contentEditingHelper, formHelper, editorState, dataTypeHelper, eventsService) { + //setup scope vars + $scope.page = {}; + $scope.page.loading = false; + $scope.page.nameLocked = false; + $scope.page.menu = {}; + $scope.page.menu.currentSection = appState.getSectionState('currentSection'); + $scope.page.menu.currentNode = null; + var evts = []; + //method used to configure the pre-values when we retrieve them from the server + function createPreValueProps(preVals) { + $scope.preValues = []; + for (var i = 0; i < preVals.length; i++) { + $scope.preValues.push({ + hideLabel: preVals[i].hideLabel, + alias: preVals[i].key, + description: preVals[i].description, + label: preVals[i].label, + view: preVals[i].view, + value: preVals[i].value, + config: preVals[i].config + }); + } + } + //set up the standard data type props + $scope.properties = { + selectedEditor: { + alias: 'selectedEditor', + description: 'Select a property editor', + label: 'Property editor' + }, + selectedEditorId: { + alias: 'selectedEditorId', + label: 'Property editor alias' + } + }; + //setup the pre-values as props + $scope.preValues = []; + if ($routeParams.create) { + $scope.page.loading = true; + $scope.showIdentifier = false; + //we are creating so get an empty data type item + dataTypeResource.getScaffold($routeParams.id).then(function (data) { + $scope.preValuesLoaded = true; + $scope.content = data; + setHeaderNameState($scope.content); + //set a shared state + editorState.set($scope.content); + $scope.page.loading = false; + }); + } else { + loadDataType(); + } + function loadDataType() { + $scope.page.loading = true; + $scope.showIdentifier = true; + //we are editing so get the content item from the server + dataTypeResource.getById($routeParams.id).then(function (data) { + $scope.preValuesLoaded = true; + $scope.content = data; + createPreValueProps($scope.content.preValues); + setHeaderNameState($scope.content); + //share state + editorState.set($scope.content); + //in one particular special case, after we've created a new item we redirect back to the edit + // route but there might be server validation errors in the collection which we need to display + // after the redirect, so we will bind all subscriptions which will show the server validation errors + // if there are any and then clear them so the collection no longer persists them. + serverValidationManager.executeAndClearAllSubscriptions(); navigationService.syncTree({ - tree: "documenttypes", - path: currPath + "," + folderId, + tree: 'datatypes', + path: data.path + }).then(function (syncArgs) { + $scope.page.menu.currentNode = syncArgs.node; + }); + $scope.page.loading = false; + }); + } + $scope.$watch('content.selectedEditor', function (newVal, oldVal) { + //when the value changes, we need to dynamically load in the new editor + if (newVal && (newVal != oldVal && (oldVal || $routeParams.create))) { + //we are editing so get the content item from the server + var currDataTypeId = $routeParams.create ? undefined : $routeParams.id; + dataTypeResource.getPreValues(newVal, currDataTypeId).then(function (data) { + $scope.preValuesLoaded = true; + $scope.content.preValues = data; + createPreValueProps($scope.content.preValues); + setHeaderNameState($scope.content); + //share state + editorState.set($scope.content); + }); + } + }); + function setHeaderNameState(content) { + if (content.isSystem == 1) { + $scope.page.nameLocked = true; + } + } + $scope.save = function () { + if (formHelper.submitForm({ + scope: $scope, + statusMessage: 'Saving...' + })) { + $scope.page.saveButtonState = 'busy'; + dataTypeResource.save($scope.content, $scope.preValues, $routeParams.create).then(function (data) { + formHelper.resetForm({ + scope: $scope, + notifications: data.notifications + }); + contentEditingHelper.handleSuccessfulSave({ + scope: $scope, + savedContent: data, + rebindCallback: function () { + createPreValueProps(data.preValues); + } + }); + setHeaderNameState($scope.content); + //share state + editorState.set($scope.content); + navigationService.syncTree({ + tree: 'datatypes', + path: data.path, + forceReload: true + }).then(function (syncArgs) { + $scope.page.menu.currentNode = syncArgs.node; + }); + $scope.page.saveButtonState = 'success'; + dataTypeHelper.rebindChangedProperties($scope.content, data); + }, function (err) { + //NOTE: in the case of data type values we are setting the orig/new props + // to be the same thing since that only really matters for content/media. + contentEditingHelper.handleSaveError({ + redirectOnFailure: false, + err: err + }); + $scope.page.saveButtonState = 'error'; + //share state + editorState.set($scope.content); + }); + } + }; + evts.push(eventsService.on('app.refreshEditor', function (name, error) { + loadDataType(); + })); + //ensure to unregister from all events! + $scope.$on('$destroy', function () { + for (var e in evts) { + eventsService.unsubscribe(evts[e]); + } + }); + } + angular.module('umbraco').controller('Umbraco.Editors.DataType.EditController', DataTypeEditController); + angular.module('umbraco').controller('Umbraco.Editors.DataType.MoveController', function ($scope, dataTypeResource, treeService, navigationService, notificationsService, appState, eventsService) { + var dialogOptions = $scope.dialogOptions; + $scope.dialogTreeEventHandler = $({}); + function nodeSelectHandler(ev, args) { + args.event.preventDefault(); + args.event.stopPropagation(); + if ($scope.target) { + //un-select if there's a current one selected + $scope.target.selected = false; + } + $scope.target = args.node; + $scope.target.selected = true; + } + $scope.move = function () { + $scope.busy = true; + $scope.error = false; + dataTypeResource.move({ + parentId: $scope.target.id, + id: dialogOptions.currentNode.id + }).then(function (path) { + $scope.error = false; + $scope.success = true; + $scope.busy = false; + //first we need to remove the node that launched the dialog + treeService.removeNode($scope.currentNode); + //get the currently edited node (if any) + var activeNode = appState.getTreeState('selectedNode'); + //we need to do a double sync here: first sync to the moved content - but don't activate the node, + //then sync to the currenlty edited content (note: this might not be the content that was moved!!) + navigationService.syncTree({ + tree: 'dataTypes', + path: path, forceReload: true, - activate: true + activate: false + }).then(function (args) { + if (activeNode) { + var activeNodePath = treeService.getPath(activeNode).join(); + //sync to this node now - depending on what was copied this might already be synced but might not be + navigationService.syncTree({ + tree: 'dataTypes', + path: activeNodePath, + forceReload: false, + activate: true + }); + } }); - - formHelper.resetForm({ - scope: $scope - }); - - var section = appState.getSectionState("currentSection"); - - }, function(err) { - + eventsService.emit('app.refreshEditor'); + }, function (err) { + $scope.success = false; $scope.error = err; - + $scope.busy = false; //show any notifications if (angular.isArray(err.data.notifications)) { for (var i = 0; i < err.data.notifications.length; i++) { @@ -6581,507 +6880,323 @@ function DocumentTypesCreateController($scope, $location, navigationService, con } } }); - } - }; - - $scope.createDocType = function() { - $location.search('create', null); - $location.search('notemplate', null); - $location.path("/settings/documenttypes/edit/" + node.id).search("create", "true"); - navigationService.hideMenu(); - }; - - $scope.createComponent = function() { - $location.search('create', null); - $location.search('notemplate', null); - $location.path("/settings/documenttypes/edit/" + node.id).search("create", "true").search("notemplate", "true"); - navigationService.hideMenu(); - }; -} - -angular.module('umbraco').controller("Umbraco.Editors.DocumentTypes.CreateController", DocumentTypesCreateController); - -/** + }; + $scope.dialogTreeEventHandler.bind('treeNodeSelect', nodeSelectHandler); + $scope.$on('$destroy', function () { + $scope.dialogTreeEventHandler.unbind('treeNodeSelect', nodeSelectHandler); + }); + }); + /** * @ngdoc controller - * @name Umbraco.Editors.DocumentType.DeleteController + * @name Umbraco.Editors.Dictionary.CreateController * @function - * + * * @description - * The controller for deleting content + * The controller for creating dictionary items */ -function DocumentTypesDeleteController($scope, dataTypeResource, contentTypeResource, treeService, navigationService) { - - $scope.performDelete = function() { - - //mark it for deletion (used in the UI) - $scope.currentNode.loading = true; - contentTypeResource.deleteById($scope.currentNode.id).then(function () { - $scope.currentNode.loading = false; - - //get the root node before we remove it - var rootNode = treeService.getTreeRoot($scope.currentNode); - - //TODO: Need to sync tree, etc... - treeService.removeNode($scope.currentNode); - navigationService.hideMenu(); + function DictionaryCreateController($scope, $location, dictionaryResource, navigationService, notificationsService, formHelper, appState) { + var vm = this; + vm.itemKey = ''; + function createItem() { + var node = $scope.dialogOptions.currentNode; + dictionaryResource.create(node.id, vm.itemKey).then(function (data) { + navigationService.hideMenu(); + // set new item as active in tree + var currPath = node.path ? node.path : '-1'; + navigationService.syncTree({ + tree: 'dictionary', + path: currPath + ',' + data, + forceReload: true, + activate: true + }); + // reset form state + formHelper.resetForm({ scope: $scope }); + // navigate to edit view + var currentSection = appState.getSectionState('currentSection'); + $location.path('/' + currentSection + '/dictionary/edit/' + data); + }, function (err) { + if (err.data && err.data.message) { + notificationsService.error(err.data.message); + navigationService.hideMenu(); + } + }); + } + vm.createItem = createItem; + } + angular.module('umbraco').controller('Umbraco.Editors.Dictionary.CreateController', DictionaryCreateController); + /** + * @ngdoc controller + * @name Umbraco.Editors.Dictionary.DeleteController + * @function + * + * @description + * The controller for deleting dictionary items + */ + function DictionaryDeleteController($scope, $location, dictionaryResource, treeService, navigationService, appState) { + var vm = this; + function cancel() { + navigationService.hideDialog(); + } + function performDelete() { + // stop from firing again on double-click + if ($scope.busy) { + return false; + } + //mark it for deletion (used in the UI) + $scope.currentNode.loading = true; + $scope.busy = true; + dictionaryResource.deleteById($scope.currentNode.id).then(function () { + $scope.currentNode.loading = false; + // get the parent id + var parentId = $scope.currentNode.parentId; + treeService.removeNode($scope.currentNode); + navigationService.hideMenu(); + var currentSection = appState.getSectionState('currentSection'); + if (parentId !== '-1') { + // set the view of the parent item + $location.path('/' + currentSection + '/dictionary/edit/' + parentId); + } else { + // we have no parent, so redirect to section + $location.path('/' + currentSection + '/'); + } + }); + } + vm.cancel = cancel; + vm.performDelete = performDelete; + } + angular.module('umbraco').controller('Umbraco.Editors.Dictionary.DeleteController', DictionaryDeleteController); + /** + * @ngdoc controller + * @name Umbraco.Editors.Dictionary.EditController + * @function + * + * @description + * The controller for editing dictionary items + */ + function DictionaryEditController($scope, $routeParams, dictionaryResource, treeService, navigationService, appState, editorState, contentEditingHelper, formHelper, notificationsService, localizationService) { + var vm = this; + //setup scope vars + vm.nameDirty = false; + vm.page = {}; + vm.page.loading = false; + vm.page.nameLocked = false; + vm.page.menu = {}; + vm.page.menu.currentSection = appState.getSectionState('currentSection'); + vm.page.menu.currentNode = null; + vm.description = ''; + function loadDictionary() { + vm.page.loading = true; + //we are editing so get the content item from the server + dictionaryResource.getById($routeParams.id).then(function (data) { + bindDictionary(data); + vm.page.loading = false; + }); + } + function createTranslationProperty(translation) { + return { + alias: translation.isoCode, + label: translation.displayName, + hideLabel: false + }; + } + function bindDictionary(data) { + localizationService.localize('dictionaryItem_description').then(function (value) { + vm.description = value.replace('%0%', data.name); + }); + // create data for umb-property displaying + for (var i = 0; i < data.translations.length; i++) { + data.translations[i].property = createTranslationProperty(data.translations[i]); + } + contentEditingHelper.handleSuccessfulSave({ + scope: $scope, + savedContent: data + }); + // set content + vm.content = data; + //share state + editorState.set(vm.content); + navigationService.syncTree({ + tree: 'dictionary', + path: data.path, + forceReload: true + }).then(function (syncArgs) { + vm.page.menu.currentNode = syncArgs.node; + }); + } + function onInit() { + loadDictionary(); + } + function saveDictionary() { + if (formHelper.submitForm({ + scope: $scope, + statusMessage: 'Saving...' + })) { + vm.page.saveButtonState = 'busy'; + dictionaryResource.save(vm.content, vm.nameDirty).then(function (data) { + formHelper.resetForm({ + scope: $scope, + notifications: data.notifications + }); + bindDictionary(data); + vm.page.saveButtonState = 'success'; + }, function (err) { + contentEditingHelper.handleSaveError({ + redirectOnFailure: false, + err: err + }); + notificationsService.error(err.data.message); + vm.page.saveButtonState = 'error'; + }); + } + } + vm.save = saveDictionary; + $scope.$watch('vm.content.name', function (newVal, oldVal) { + //when the value changes, we need to set the name dirty + if (newVal && newVal !== oldVal && typeof oldVal !== 'undefined') { + vm.nameDirty = true; + } }); - - }; - - $scope.performContainerDelete = function() { - - //mark it for deletion (used in the UI) - $scope.currentNode.loading = true; - contentTypeResource.deleteContainerById($scope.currentNode.id).then(function () { - $scope.currentNode.loading = false; - - //get the root node before we remove it - var rootNode = treeService.getTreeRoot($scope.currentNode); - - //TODO: Need to sync tree, etc... - treeService.removeNode($scope.currentNode); - navigationService.hideMenu(); - }); - - }; - - $scope.cancel = function() { - navigationService.hideDialog(); - }; -} - -angular.module("umbraco").controller("Umbraco.Editors.DocumentTypes.DeleteController", DocumentTypesDeleteController); - -/** - * @ngdoc controller - * @name Umbraco.Editors.DocumentType.EditController - * @function - * - * @description - * The controller for the content type editor - */ -(function () { - "use strict"; - - function DocumentTypesEditController($scope, $routeParams, $injector, contentTypeResource, dataTypeResource, editorState, contentEditingHelper, formHelper, navigationService, iconHelper, contentTypeHelper, notificationsService, $filter, $q, localizationService, overlayHelper, eventsService) { - - var vm = this; - var localizeSaving = localizationService.localize("general_saving"); - var evts = []; - - vm.save = save; - - vm.currentNode = null; - vm.contentType = {}; - - vm.page = {}; - vm.page.loading = false; - vm.page.saveButtonState = "init"; - vm.page.navigation = [ - { - "name": localizationService.localize("general_design"), - "icon": "icon-document-dashed-line", - "view": "views/documenttypes/views/design/design.html", - "active": true - }, - { - "name": localizationService.localize("general_listView"), - "icon": "icon-list", - "view": "views/documenttypes/views/listview/listview.html" - }, - { - "name": localizationService.localize("general_rights"), - "icon": "icon-keychain", - "view": "views/documenttypes/views/permissions/permissions.html" - }, - { - "name": localizationService.localize("treeHeaders_templates"), - "icon": "icon-layout", - "view": "views/documenttypes/views/templates/templates.html" - } - ]; - - vm.page.keyboardShortcutsOverview = [ - { - "name": localizationService.localize("main_sections"), - "shortcuts": [ - { - "description": localizationService.localize("shortcuts_navigateSections"), - "keys": [{ "key": "1" }, { "key": "4" }], - "keyRange": true - } - ] - }, - { - "name": localizationService.localize("general_design"), - "shortcuts": [ - { - "description": localizationService.localize("shortcuts_addTab"), - "keys": [{ "key": "alt" }, { "key": "shift" }, { "key": "t" }] - }, - { - "description": localizationService.localize("shortcuts_addProperty"), - "keys": [{ "key": "alt" }, { "key": "shift" }, { "key": "p" }] - }, - { - "description": localizationService.localize("shortcuts_addEditor"), - "keys": [{ "key": "alt" }, { "key": "shift" }, { "key": "e" }] - }, - { - "description": localizationService.localize("shortcuts_editDataType"), - "keys": [{ "key": "alt" }, { "key": "shift" }, { "key": "d" }] - } - ] - }, - { - "name": localizationService.localize("general_listView"), - "shortcuts": [ - { - "description": localizationService.localize("shortcuts_toggleListView"), - "keys": [{ "key": "alt" }, { "key": "shift" }, { "key": "l" }] - } - ] - }, - { - "name": localizationService.localize("general_rights"), - "shortcuts": [ - { - "description": localizationService.localize("shortcuts_toggleAllowAsRoot"), - "keys": [{ "key": "alt" }, { "key": "shift" }, { "key": "r" }] - }, - { - "description": localizationService.localize("shortcuts_addChildNode"), - "keys": [{ "key": "alt" }, { "key": "shift" }, { "key": "c" }] - } - ] - }, - { - "name": localizationService.localize("treeHeaders_templates"), - "shortcuts": [ - { - "description": localizationService.localize("shortcuts_addTemplate"), - "keys": [{ "key": "alt" }, { "key": "shift" }, { "key": "t" }] - } - ] - } - ]; - - contentTypeHelper.checkModelsBuilderStatus().then(function (result) { - vm.page.modelsBuilder = result; - if (result) { - //Models builder mode: - vm.page.defaultButton = { - hotKey: "ctrl+s", - hotKeyWhenHidden: true, - labelKey: "buttons_save", - letter: "S", - type: "submit", - handler: function () { vm.save(); } - }; - vm.page.subButtons = [{ - hotKey: "ctrl+g", - hotKeyWhenHidden: true, - labelKey: "buttons_saveAndGenerateModels", - letter: "G", - handler: function () { - - vm.page.saveButtonState = "busy"; - - vm.save().then(function (result) { - - vm.page.saveButtonState = "busy"; - - localizationService.localize("modelsBuilder_buildingModels").then(function (headerValue) { - localizationService.localize("modelsBuilder_waitingMessage").then(function(msgValue) { - notificationsService.info(headerValue, msgValue); - }); - }); - - contentTypeHelper.generateModels().then(function (result) { - - // generateModels() returns the dashboard content - if (!result.lastError) { - - //re-check model status - contentTypeHelper.checkModelsBuilderStatus().then(function(statusResult) { - vm.page.modelsBuilder = statusResult; - }); - - //clear and add success - vm.page.saveButtonState = "init"; - localizationService.localize("modelsBuilder_modelsGenerated").then(function(value) { - notificationsService.success(value); - }); - - } else { - vm.page.saveButtonState = "error"; - localizationService.localize("modelsBuilder_modelsExceptionInUlog").then(function(value) { - notificationsService.error(value); - }); - } - - }, function () { - vm.page.saveButtonState = "error"; - localizationService.localize("modelsBuilder_modelsGeneratedError").then(function(value) { - notificationsService.error(value); - }); - }); - - }); - - } - }]; - } - }); - - if ($routeParams.create) { - vm.page.loading = true; - - //we are creating so get an empty data type item - contentTypeResource.getScaffold($routeParams.id) - .then(function (dt) { - - init(dt); - - vm.page.loading = false; - - }); - } - else { - loadDocumentType(); - } - - function loadDocumentType() { - - vm.page.loading = true; - - contentTypeResource.getById($routeParams.id).then(function (dt) { - init(dt); - - syncTreeNode(vm.contentType, dt.path, true); - - vm.page.loading = false; - - }); - - } - - - /* ---------- SAVE ---------- */ - - function save() { - - // only save if there is no overlays open - if(overlayHelper.getNumberOfOverlays() === 0) { - - var deferred = $q.defer(); - - vm.page.saveButtonState = "busy"; - - // reformat allowed content types to array if id's - vm.contentType.allowedContentTypes = contentTypeHelper.createIdArray(vm.contentType.allowedContentTypes); - - contentEditingHelper.contentEditorPerformSave({ - statusMessage: localizeSaving, - saveMethod: contentTypeResource.save, - scope: $scope, - content: vm.contentType, - //We do not redirect on failure for doc types - this is because it is not possible to actually save the doc - // type when server side validation fails - as opposed to content where we are capable of saving the content - // item if server side validation fails - redirectOnFailure: false, - // we need to rebind... the IDs that have been created! - rebindCallback: function (origContentType, savedContentType) { - vm.contentType.id = savedContentType.id; - vm.contentType.groups.forEach(function(group) { - if (!group.name) return; - - var k = 0; - while (k < savedContentType.groups.length && savedContentType.groups[k].name != group.name) - k++; - if (k == savedContentType.groups.length) { - group.id = 0; - return; - } - - var savedGroup = savedContentType.groups[k]; - if (!group.id) group.id = savedGroup.id; - - group.properties.forEach(function (property) { - if (property.id || !property.alias) return; - - k = 0; - while (k < savedGroup.properties.length && savedGroup.properties[k].alias != property.alias) - k++; - if (k == savedGroup.properties.length) { - property.id = 0; - return; - } - - var savedProperty = savedGroup.properties[k]; - property.id = savedProperty.id; - }); - }); - } - }).then(function (data) { - //success - syncTreeNode(vm.contentType, data.path); - - vm.page.saveButtonState = "success"; - - deferred.resolve(data); - }, function (err) { - //error - if (err) { - editorState.set($scope.content); - } - else { - localizationService.localize("speechBubbles_validationFailedHeader").then(function (headerValue) { - localizationService.localize("speechBubbles_validationFailedMessage").then(function(msgValue) { - notificationsService.error(headerValue, msgValue); - }); - }); - } - vm.page.saveButtonState = "error"; - - deferred.reject(err); - }); - return deferred.promise; - - } - - } - - function init(contentType) { - - // set all tab to inactive - if (contentType.groups.length !== 0) { - angular.forEach(contentType.groups, function (group) { - - angular.forEach(group.properties, function (property) { - // get data type details for each property - getDataTypeDetails(property); - }); - - }); - } - - // insert template on new doc types - if (!$routeParams.notemplate && contentType.id === 0) { - contentType.defaultTemplate = contentTypeHelper.insertDefaultTemplatePlaceholder(contentType.defaultTemplate); - contentType.allowedTemplates = contentTypeHelper.insertTemplatePlaceholder(contentType.allowedTemplates); - } - - // convert icons for content type - convertLegacyIcons(contentType); - - //set a shared state - editorState.set(contentType); - - vm.contentType = contentType; - } - - function convertLegacyIcons(contentType) { - // make array to store contentType icon - var contentTypeArray = []; - - // push icon to array - contentTypeArray.push({ "icon": contentType.icon }); - - // run through icon method - iconHelper.formatContentTypeIcons(contentTypeArray); - - // set icon back on contentType - contentType.icon = contentTypeArray[0].icon; - } - - function getDataTypeDetails(property) { - if (property.propertyState !== "init") { - - dataTypeResource.getById(property.dataTypeId) - .then(function (dataType) { - property.dataTypeIcon = dataType.icon; - property.dataTypeName = dataType.name; - }); - } - } - - /** Syncs the content type to it's tree node - this occurs on first load and after saving */ - function syncTreeNode(dt, path, initialLoad) { - navigationService.syncTree({ tree: "documenttypes", path: path.split(","), forceReload: initialLoad !== true }).then(function (syncArgs) { - vm.currentNode = syncArgs.node; - }); - } - - evts.push(eventsService.on("app.refreshEditor", function(name, error) { - loadDocumentType(); - })); - - //ensure to unregister from all events! - $scope.$on('$destroy', function () { - for (var e in evts) { - eventsService.unsubscribe(evts[e]); - } - }); - - } - - angular.module("umbraco").controller("Umbraco.Editors.DocumentTypes.EditController", DocumentTypesEditController); -})(); - -angular.module("umbraco") -.controller("Umbraco.Editors.DocumentTypes.MoveController", - function ($scope, contentTypeResource, treeService, navigationService, notificationsService, appState, eventsService) { + onInit(); + } + angular.module('umbraco').controller('Umbraco.Editors.Dictionary.EditController', DictionaryEditController); + /** + * @ngdoc controller + * @name Umbraco.Editors.Dictionary.ListController + * @function + * + * @description + * The controller for listting dictionary items + */ + function DictionaryListController($scope, $location, dictionaryResource, localizationService, appState) { + var vm = this; + vm.title = 'Dictionary overview'; + vm.loading = false; + vm.items = []; + function loadList() { + vm.loading = true; + dictionaryResource.getList().then(function (data) { + vm.items = data; + vm.loading = false; + }); + } + function clickItem(id) { + var currentSection = appState.getSectionState('currentSection'); + $location.path('/' + currentSection + '/dictionary/edit/' + id); + } + vm.clickItem = clickItem; + function onInit() { + localizationService.localize('dictionaryItem_overviewTitle').then(function (value) { + vm.title = value; + }); + loadList(); + } + onInit(); + } + angular.module('umbraco').controller('Umbraco.Editors.Dictionary.ListController', DictionaryListController); + angular.module('umbraco').controller('Umbraco.Editors.DocumentTypes.CopyController', function ($scope, contentTypeResource, treeService, navigationService, notificationsService, appState, eventsService) { var dialogOptions = $scope.dialogOptions; $scope.dialogTreeEventHandler = $({}); - function nodeSelectHandler(ev, args) { args.event.preventDefault(); args.event.stopPropagation(); - if ($scope.target) { //un-select if there's a current one selected $scope.target.selected = false; } - $scope.target = args.node; $scope.target.selected = true; } - - $scope.move = function () { - + $scope.copy = function () { $scope.busy = true; $scope.error = false; - - contentTypeResource.move({ parentId: $scope.target.id, id: dialogOptions.currentNode.id }) - .then(function (path) { - $scope.error = false; - $scope.success = true; - $scope.busy = false; - - //first we need to remove the node that launched the dialog - treeService.removeNode($scope.currentNode); - - //get the currently edited node (if any) - var activeNode = appState.getTreeState("selectedNode"); - - //we need to do a double sync here: first sync to the moved content - but don't activate the node, - //then sync to the currenlty edited content (note: this might not be the content that was moved!!) - - navigationService.syncTree({ tree: "documentTypes", path: path, forceReload: true, activate: false }).then(function (args) { - if (activeNode) { - var activeNodePath = treeService.getPath(activeNode).join(); - //sync to this node now - depending on what was copied this might already be synced but might not be - navigationService.syncTree({ tree: "documentTypes", path: activeNodePath, forceReload: false, activate: true }); - } + contentTypeResource.copy({ + parentId: $scope.target.id, + id: dialogOptions.currentNode.id + }).then(function (path) { + $scope.error = false; + $scope.success = true; + $scope.busy = false; + //get the currently edited node (if any) + var activeNode = appState.getTreeState('selectedNode'); + //we need to do a double sync here: first sync to the copied content - but don't activate the node, + //then sync to the currenlty edited content (note: this might not be the content that was copied!!) + navigationService.syncTree({ + tree: 'documentTypes', + path: path, + forceReload: true, + activate: false + }).then(function (args) { + if (activeNode) { + var activeNodePath = treeService.getPath(activeNode).join(); + //sync to this node now - depending on what was copied this might already be synced but might not be + navigationService.syncTree({ + tree: 'documentTypes', + path: activeNodePath, + forceReload: false, + activate: true + }); + } + }); + }, function (err) { + $scope.success = false; + $scope.error = err; + $scope.busy = false; + //show any notifications + if (angular.isArray(err.data.notifications)) { + for (var i = 0; i < err.data.notifications.length; i++) { + notificationsService.showNotification(err.data.notifications[i]); + } + } + }); + }; + $scope.dialogTreeEventHandler.bind('treeNodeSelect', nodeSelectHandler); + $scope.$on('$destroy', function () { + $scope.dialogTreeEventHandler.unbind('treeNodeSelect', nodeSelectHandler); + }); + }); + /** + * @ngdoc controller + * @name Umbraco.Editors.DocumentType.CreateController + * @function + * + * @description + * The controller for the doc type creation dialog + */ + function DocumentTypesCreateController($scope, $location, navigationService, contentTypeResource, formHelper, appState, notificationsService, localizationService, iconHelper) { + $scope.model = { + allowCreateFolder: $scope.dialogOptions.currentNode.parentId === null || $scope.dialogOptions.currentNode.nodeType === 'container', + folderName: '', + creatingFolder: false, + creatingDoctypeCollection: false + }; + var disableTemplates = Umbraco.Sys.ServerVariables.features.disabledFeatures.disableTemplates; + $scope.model.disableTemplates = disableTemplates; + var node = $scope.dialogOptions.currentNode, localizeCreateFolder = localizationService.localize('defaultdialog_createFolder'); + $scope.showCreateFolder = function () { + $scope.model.creatingFolder = true; + }; + $scope.showCreateDocTypeCollection = function () { + $scope.model.creatingDoctypeCollection = true; + }; + $scope.createContainer = function () { + if (formHelper.submitForm({ + scope: $scope, + formCtrl: this.createFolderForm, + statusMessage: localizeCreateFolder + })) { + contentTypeResource.createContainer(node.id, $scope.model.folderName).then(function (folderId) { + navigationService.hideMenu(); + var currPath = node.path ? node.path : '-1'; + navigationService.syncTree({ + tree: 'documenttypes', + path: currPath + ',' + folderId, + forceReload: true, + activate: true }); - - eventsService.emit('app.refreshEditor'); - + formHelper.resetForm({ scope: $scope }); + var section = appState.getSectionState('currentSection'); }, function (err) { - $scope.success = false; $scope.error = err; - $scope.busy = false; //show any notifications if (angular.isArray(err.data.notifications)) { for (var i = 0; i < err.data.notifications.length; i++) { @@ -7089,16 +7204,571 @@ angular.module("umbraco") } } }); + } }; - - $scope.dialogTreeEventHandler.bind("treeNodeSelect", nodeSelectHandler); - + $scope.createCollection = function () { + if (formHelper.submitForm({ + scope: $scope, + formCtrl: this.createDoctypeCollectionForm, + statusMessage: 'Creating Doctype Collection...' + })) { + // see if we can find matching icons + var collectionIcon = 'icon-folders', collectionItemIcon = 'icon-document'; + iconHelper.getIcons().then(function (icons) { + for (var i = 0; i < icons.length; i++) { + // for matching we'll require a full match for collection, partial match for item + if (icons[i].substring(5) == $scope.model.collectionName.toLowerCase()) { + collectionIcon = icons[i]; + } else if (icons[i].substring(5).indexOf($scope.model.collectionItemName.toLowerCase()) > -1) { + collectionItemIcon = icons[i]; + } + } + contentTypeResource.createCollection(node.id, $scope.model.collectionName, $scope.model.collectionItemName, collectionIcon, collectionItemIcon).then(function (collectionData) { + navigationService.hideMenu(); + $location.search('create', null); + $location.search('notemplate', null); + formHelper.resetForm({ scope: $scope }); + var section = appState.getSectionState('currentSection'); + // redirect to the item id + $location.path('/settings/documenttypes/edit/' + collectionData.ItemId); + }, function (err) { + $scope.error = err; + //show any notifications + if (angular.isArray(err.data.notifications)) { + for (var i = 0; i < err.data.notifications.length; i++) { + notificationsService.showNotification(err.data.notifications[i]); + } + } + }); + }); + } + }; + // Disabling logic for creating document type with template if disableTemplates is set to true + if (!disableTemplates) { + $scope.createDocType = function () { + $location.search('create', null); + $location.search('notemplate', null); + $location.path('/settings/documenttypes/edit/' + node.id).search('create', 'true'); + navigationService.hideMenu(); + }; + } + $scope.createComponent = function () { + $location.search('create', null); + $location.search('notemplate', null); + $location.path('/settings/documenttypes/edit/' + node.id).search('create', 'true').search('notemplate', 'true'); + navigationService.hideMenu(); + }; + } + angular.module('umbraco').controller('Umbraco.Editors.DocumentTypes.CreateController', DocumentTypesCreateController); + /** + * @ngdoc controller + * @name Umbraco.Editors.DocumentType.DeleteController + * @function + * + * @description + * The controller for deleting content + */ + function DocumentTypesDeleteController($scope, dataTypeResource, contentTypeResource, treeService, navigationService) { + $scope.performDelete = function () { + //mark it for deletion (used in the UI) + $scope.currentNode.loading = true; + contentTypeResource.deleteById($scope.currentNode.id).then(function () { + $scope.currentNode.loading = false; + //get the root node before we remove it + var rootNode = treeService.getTreeRoot($scope.currentNode); + //TODO: Need to sync tree, etc... + treeService.removeNode($scope.currentNode); + navigationService.hideMenu(); + }); + }; + $scope.performContainerDelete = function () { + //mark it for deletion (used in the UI) + $scope.currentNode.loading = true; + contentTypeResource.deleteContainerById($scope.currentNode.id).then(function () { + $scope.currentNode.loading = false; + //get the root node before we remove it + var rootNode = treeService.getTreeRoot($scope.currentNode); + //TODO: Need to sync tree, etc... + treeService.removeNode($scope.currentNode); + navigationService.hideMenu(); + }); + }; + $scope.cancel = function () { + navigationService.hideDialog(); + }; + } + angular.module('umbraco').controller('Umbraco.Editors.DocumentTypes.DeleteController', DocumentTypesDeleteController); + /** + * @ngdoc controller + * @name Umbraco.Editors.DocumentType.EditController + * @function + * + * @description + * The controller for the content type editor + */ + (function () { + 'use strict'; + function DocumentTypesEditController($scope, $routeParams, $injector, contentTypeResource, dataTypeResource, editorState, contentEditingHelper, formHelper, navigationService, iconHelper, contentTypeHelper, notificationsService, $filter, $q, localizationService, overlayHelper, eventsService) { + var vm = this; + var localizeSaving = localizationService.localize('general_saving'); + var evts = []; + var disableTemplates = Umbraco.Sys.ServerVariables.features.disabledFeatures.disableTemplates; + var buttons = [ + { + 'name': localizationService.localize('general_design'), + 'alias': 'design', + 'icon': 'icon-document-dashed-line', + 'view': 'views/documenttypes/views/design/design.html', + 'active': true + }, + { + 'name': localizationService.localize('general_listView'), + 'alias': 'listView', + 'icon': 'icon-list', + 'view': 'views/documenttypes/views/listview/listview.html' + }, + { + 'name': localizationService.localize('general_rights'), + 'alias': 'permissions', + 'icon': 'icon-keychain', + 'view': 'views/documenttypes/views/permissions/permissions.html' + }, + { + 'name': localizationService.localize('treeHeaders_templates'), + 'alias': 'templates', + 'icon': 'icon-layout', + 'view': 'views/documenttypes/views/templates/templates.html' + } + ]; + vm.save = save; + vm.currentNode = null; + vm.contentType = {}; + vm.page = {}; + vm.page.loading = false; + vm.page.saveButtonState = 'init'; + vm.page.navigation = []; + loadButtons(); + vm.page.keyboardShortcutsOverview = [ + { + 'name': localizationService.localize('main_sections'), + 'shortcuts': [{ + 'description': localizationService.localize('shortcuts_navigateSections'), + 'keys': [ + { 'key': '1' }, + { 'key': '4' } + ], + 'keyRange': true + }] + }, + { + 'name': localizationService.localize('general_design'), + 'shortcuts': [ + { + 'description': localizationService.localize('shortcuts_addTab'), + 'keys': [ + { 'key': 'alt' }, + { 'key': 'shift' }, + { 'key': 't' } + ] + }, + { + 'description': localizationService.localize('shortcuts_addProperty'), + 'keys': [ + { 'key': 'alt' }, + { 'key': 'shift' }, + { 'key': 'p' } + ] + }, + { + 'description': localizationService.localize('shortcuts_addEditor'), + 'keys': [ + { 'key': 'alt' }, + { 'key': 'shift' }, + { 'key': 'e' } + ] + }, + { + 'description': localizationService.localize('shortcuts_editDataType'), + 'keys': [ + { 'key': 'alt' }, + { 'key': 'shift' }, + { 'key': 'd' } + ] + } + ] + }, + { + 'name': localizationService.localize('general_listView'), + 'shortcuts': [{ + 'description': localizationService.localize('shortcuts_toggleListView'), + 'keys': [ + { 'key': 'alt' }, + { 'key': 'shift' }, + { 'key': 'l' } + ] + }] + }, + { + 'name': localizationService.localize('general_rights'), + 'shortcuts': [ + { + 'description': localizationService.localize('shortcuts_toggleAllowAsRoot'), + 'keys': [ + { 'key': 'alt' }, + { 'key': 'shift' }, + { 'key': 'r' } + ] + }, + { + 'description': localizationService.localize('shortcuts_addChildNode'), + 'keys': [ + { 'key': 'alt' }, + { 'key': 'shift' }, + { 'key': 'c' } + ] + } + ] + }, + { + 'name': localizationService.localize('treeHeaders_templates'), + 'shortcuts': [{ + 'description': localizationService.localize('shortcuts_addTemplate'), + 'keys': [ + { 'key': 'alt' }, + { 'key': 'shift' }, + { 'key': 't' } + ] + }] + } + ]; + contentTypeHelper.checkModelsBuilderStatus().then(function (result) { + vm.page.modelsBuilder = result; + if (result) { + //Models builder mode: + vm.page.defaultButton = { + alias: 'save', + hotKey: 'ctrl+s', + hotKeyWhenHidden: true, + labelKey: 'buttons_save', + letter: 'S', + type: 'submit', + handler: function () { + vm.save(); + } + }; + vm.page.subButtons = [{ + alias: 'saveAndGenerateModels', + hotKey: 'ctrl+g', + hotKeyWhenHidden: true, + labelKey: 'buttons_saveAndGenerateModels', + letter: 'G', + handler: function () { + vm.page.saveButtonState = 'busy'; + vm.save().then(function (result) { + vm.page.saveButtonState = 'busy'; + localizationService.localize('modelsBuilder_buildingModels').then(function (headerValue) { + localizationService.localize('modelsBuilder_waitingMessage').then(function (msgValue) { + notificationsService.info(headerValue, msgValue); + }); + }); + contentTypeHelper.generateModels().then(function (result) { + // generateModels() returns the dashboard content + if (!result.lastError) { + //re-check model status + contentTypeHelper.checkModelsBuilderStatus().then(function (statusResult) { + vm.page.modelsBuilder = statusResult; + }); + //clear and add success + vm.page.saveButtonState = 'init'; + localizationService.localize('modelsBuilder_modelsGenerated').then(function (value) { + notificationsService.success(value); + }); + } else { + vm.page.saveButtonState = 'error'; + localizationService.localize('modelsBuilder_modelsExceptionInUlog').then(function (value) { + notificationsService.error(value); + }); + } + }, function () { + vm.page.saveButtonState = 'error'; + localizationService.localize('modelsBuilder_modelsGeneratedError').then(function (value) { + notificationsService.error(value); + }); + }); + }); + } + }]; + } + }); + if ($routeParams.create) { + vm.page.loading = true; + //we are creating so get an empty data type item + contentTypeResource.getScaffold($routeParams.id).then(function (dt) { + init(dt); + vm.page.loading = false; + }); + } else { + loadDocumentType(); + } + function loadDocumentType() { + vm.page.loading = true; + contentTypeResource.getById($routeParams.id).then(function (dt) { + init(dt); + syncTreeNode(vm.contentType, dt.path, true); + vm.page.loading = false; + }); + } + function loadButtons() { + angular.forEach(buttons, function (val, index) { + if (disableTemplates === true && val.alias === 'templates') { + buttons.splice(index, 1); + } + }); + vm.page.navigation = buttons; + } + /* ---------- SAVE ---------- */ + function save() { + // only save if there is no overlays open + if (overlayHelper.getNumberOfOverlays() === 0) { + var deferred = $q.defer(); + vm.page.saveButtonState = 'busy'; + // reformat allowed content types to array if id's + vm.contentType.allowedContentTypes = contentTypeHelper.createIdArray(vm.contentType.allowedContentTypes); + contentEditingHelper.contentEditorPerformSave({ + statusMessage: localizeSaving, + saveMethod: contentTypeResource.save, + scope: $scope, + content: vm.contentType, + //We do not redirect on failure for doc types - this is because it is not possible to actually save the doc + // type when server side validation fails - as opposed to content where we are capable of saving the content + // item if server side validation fails + redirectOnFailure: false, + // we need to rebind... the IDs that have been created! + rebindCallback: function (origContentType, savedContentType) { + vm.contentType.id = savedContentType.id; + vm.contentType.groups.forEach(function (group) { + if (!group.name) + return; + var k = 0; + while (k < savedContentType.groups.length && savedContentType.groups[k].name != group.name) + k++; + if (k == savedContentType.groups.length) { + group.id = 0; + return; + } + var savedGroup = savedContentType.groups[k]; + if (!group.id) + group.id = savedGroup.id; + group.properties.forEach(function (property) { + if (property.id || !property.alias) + return; + k = 0; + while (k < savedGroup.properties.length && savedGroup.properties[k].alias != property.alias) + k++; + if (k == savedGroup.properties.length) { + property.id = 0; + return; + } + var savedProperty = savedGroup.properties[k]; + property.id = savedProperty.id; + }); + }); + } + }).then(function (data) { + //success + syncTreeNode(vm.contentType, data.path); + vm.page.saveButtonState = 'success'; + deferred.resolve(data); + }, function (err) { + //error + if (err) { + editorState.set($scope.content); + } else { + localizationService.localize('speechBubbles_validationFailedHeader').then(function (headerValue) { + localizationService.localize('speechBubbles_validationFailedMessage').then(function (msgValue) { + notificationsService.error(headerValue, msgValue); + }); + }); + } + vm.page.saveButtonState = 'error'; + deferred.reject(err); + }); + return deferred.promise; + } + } + function init(contentType) { + // set all tab to inactive + if (contentType.groups.length !== 0) { + angular.forEach(contentType.groups, function (group) { + angular.forEach(group.properties, function (property) { + // get data type details for each property + getDataTypeDetails(property); + }); + }); + } + // insert template on new doc types + if (!$routeParams.notemplate && contentType.id === 0) { + contentType.defaultTemplate = contentTypeHelper.insertDefaultTemplatePlaceholder(contentType.defaultTemplate); + contentType.allowedTemplates = contentTypeHelper.insertTemplatePlaceholder(contentType.allowedTemplates); + } + // convert icons for content type + convertLegacyIcons(contentType); + //set a shared state + editorState.set(contentType); + vm.contentType = contentType; + } + function convertLegacyIcons(contentType) { + // make array to store contentType icon + var contentTypeArray = []; + // push icon to array + contentTypeArray.push({ 'icon': contentType.icon }); + // run through icon method + iconHelper.formatContentTypeIcons(contentTypeArray); + // set icon back on contentType + contentType.icon = contentTypeArray[0].icon; + } + function getDataTypeDetails(property) { + if (property.propertyState !== 'init') { + dataTypeResource.getById(property.dataTypeId).then(function (dataType) { + property.dataTypeIcon = dataType.icon; + property.dataTypeName = dataType.name; + }); + } + } + /** Syncs the content type to it's tree node - this occurs on first load and after saving */ + function syncTreeNode(dt, path, initialLoad) { + navigationService.syncTree({ + tree: 'documenttypes', + path: path.split(','), + forceReload: initialLoad !== true + }).then(function (syncArgs) { + vm.currentNode = syncArgs.node; + }); + } + evts.push(eventsService.on('app.refreshEditor', function (name, error) { + loadDocumentType(); + })); + //ensure to unregister from all events! + $scope.$on('$destroy', function () { + for (var e in evts) { + eventsService.unsubscribe(evts[e]); + } + }); + } + angular.module('umbraco').controller('Umbraco.Editors.DocumentTypes.EditController', DocumentTypesEditController); + }()); + angular.module('umbraco').controller('Umbraco.Editors.DocumentTypes.MoveController', function ($scope, contentTypeResource, treeService, navigationService, notificationsService, appState, eventsService) { + var dialogOptions = $scope.dialogOptions; + $scope.dialogTreeEventHandler = $({}); + function nodeSelectHandler(ev, args) { + args.event.preventDefault(); + args.event.stopPropagation(); + if ($scope.target) { + //un-select if there's a current one selected + $scope.target.selected = false; + } + $scope.target = args.node; + $scope.target.selected = true; + } + $scope.move = function () { + $scope.busy = true; + $scope.error = false; + contentTypeResource.move({ + parentId: $scope.target.id, + id: dialogOptions.currentNode.id + }).then(function (path) { + $scope.error = false; + $scope.success = true; + $scope.busy = false; + //first we need to remove the node that launched the dialog + treeService.removeNode($scope.currentNode); + //get the currently edited node (if any) + var activeNode = appState.getTreeState('selectedNode'); + //we need to do a double sync here: first sync to the moved content - but don't activate the node, + //then sync to the currenlty edited content (note: this might not be the content that was moved!!) + navigationService.syncTree({ + tree: 'documentTypes', + path: path, + forceReload: true, + activate: false + }).then(function (args) { + if (activeNode) { + var activeNodePath = treeService.getPath(activeNode).join(); + //sync to this node now - depending on what was copied this might already be synced but might not be + navigationService.syncTree({ + tree: 'documentTypes', + path: activeNodePath, + forceReload: false, + activate: true + }); + } + }); + eventsService.emit('app.refreshEditor'); + }, function (err) { + $scope.success = false; + $scope.error = err; + $scope.busy = false; + //show any notifications + if (angular.isArray(err.data.notifications)) { + for (var i = 0; i < err.data.notifications.length; i++) { + notificationsService.showNotification(err.data.notifications[i]); + } + } + }); + }; + $scope.dialogTreeEventHandler.bind('treeNodeSelect', nodeSelectHandler); $scope.$on('$destroy', function () { - $scope.dialogTreeEventHandler.unbind("treeNodeSelect", nodeSelectHandler); + $scope.dialogTreeEventHandler.unbind('treeNodeSelect', nodeSelectHandler); }); }); - -/** + angular.module('umbraco').controller('Umbraco.Editors.ContentTypeContainers.RenameController', [ + '$scope', + '$injector', + 'navigationService', + 'notificationsService', + 'localizationService', + function (scope, injector, navigationService, notificationsService, localizationService) { + var notificationHeader; + function reportSuccessAndClose(treeName) { + var lastComma = scope.currentNode.path.lastIndexOf(','), path = lastComma === -1 ? scope.currentNode.path : scope.currentNode.path.substring(0, lastComma - 1); + navigationService.syncTree({ + tree: treeName, + path: path, + forceReload: true, + activate: true + }); + localizationService.localize('renamecontainer_folderWasRenamed', [ + scope.currentNode.name, + scope.model.folderName + ]).then(function (msg) { + notificationsService.showNotification({ + type: 0, + header: notificationHeader, + message: msg + }); + }); + navigationService.hideMenu(); + } + localizationService.localize('renamecontainer_renamed').then(function (s) { + notificationHeader = s; + }); + scope.model = { folderName: scope.currentNode.name }; + scope.renameContainer = function (resourceKey, treeName) { + var resource = injector.get(resourceKey); + resource.renameContainer(scope.currentNode.id, scope.model.folderName).then(function () { + reportSuccessAndClose(treeName); + }, function (err) { + scope.error = err; + if (angular.isArray(err.data.notifications)) { + for (var i = 0; i < err.data.notifications.length; i++) { + notificationsService.showNotification(err.data.notifications[i]); + } + } + }); + }; + } + ]); + /** * @ngdoc controller * @name Umbraco.Editors.DocumentType.PropertyController * @function @@ -7106,81 +7776,69 @@ angular.module("umbraco") * @description * The controller for the content type editor property dialog */ -(function() { - 'use strict'; - - function PermissionsController($scope, contentTypeResource, iconHelper, contentTypeHelper, localizationService) { - - /* ----------- SCOPE VARIABLES ----------- */ - - var vm = this; - var childNodeSelectorOverlayTitle = ""; - - vm.contentTypes = []; - vm.selectedChildren = []; - - vm.overlayTitle = ""; - - vm.addChild = addChild; - vm.removeChild = removeChild; - - /* ---------- INIT ---------- */ - - init(); - - function init() { - - childNodeSelectorOverlayTitle = localizationService.localize("contentTypeEditor_chooseChildNode"); - - contentTypeResource.getAll().then(function(contentTypes){ - - vm.contentTypes = contentTypes; - - // convert legacy icons - iconHelper.formatContentTypeIcons(vm.contentTypes); - - vm.selectedChildren = contentTypeHelper.makeObjectArrayFromId($scope.model.allowedContentTypes, vm.contentTypes); - - if($scope.model.id === 0) { - contentTypeHelper.insertChildNodePlaceholder(vm.contentTypes, $scope.model.name, $scope.model.icon, $scope.model.id); + (function () { + 'use strict'; + function PermissionsController($scope, contentTypeResource, iconHelper, contentTypeHelper, localizationService) { + /* ----------- SCOPE VARIABLES ----------- */ + var vm = this; + var childNodeSelectorOverlayTitle = ''; + vm.contentTypes = []; + vm.selectedChildren = []; + vm.overlayTitle = ''; + vm.addChild = addChild; + vm.removeChild = removeChild; + vm.toggle = toggle; + /* ---------- INIT ---------- */ + init(); + function init() { + childNodeSelectorOverlayTitle = localizationService.localize('contentTypeEditor_chooseChildNode'); + contentTypeResource.getAll().then(function (contentTypes) { + vm.contentTypes = contentTypes; + // convert legacy icons + iconHelper.formatContentTypeIcons(vm.contentTypes); + vm.selectedChildren = contentTypeHelper.makeObjectArrayFromId($scope.model.allowedContentTypes, vm.contentTypes); + if ($scope.model.id === 0) { + contentTypeHelper.insertChildNodePlaceholder(vm.contentTypes, $scope.model.name, $scope.model.icon, $scope.model.id); + } + }); + } + function addChild($event) { + vm.childNodeSelectorOverlay = { + view: 'itempicker', + title: childNodeSelectorOverlayTitle, + availableItems: vm.contentTypes, + selectedItems: vm.selectedChildren, + event: $event, + show: true, + submit: function (model) { + vm.selectedChildren.push(model.selectedItem); + $scope.model.allowedContentTypes.push(model.selectedItem.id); + vm.childNodeSelectorOverlay.show = false; + vm.childNodeSelectorOverlay = null; + } + }; + } + function removeChild(selectedChild, index) { + // remove from vm + vm.selectedChildren.splice(index, 1); + // remove from content type model + var selectedChildIndex = $scope.model.allowedContentTypes.indexOf(selectedChild.id); + $scope.model.allowedContentTypes.splice(selectedChildIndex, 1); + } + /** + * Toggle the $scope.model.allowAsRoot value to either true or false + */ + function toggle() { + if ($scope.model.allowAsRoot) { + $scope.model.allowAsRoot = false; + return; } - - }); - + $scope.model.allowAsRoot = true; + } } - - function addChild($event) { - vm.childNodeSelectorOverlay = { - view: "itempicker", - title: childNodeSelectorOverlayTitle, - availableItems: vm.contentTypes, - selectedItems: vm.selectedChildren, - event: $event, - show: true, - submit: function(model) { - vm.selectedChildren.push(model.selectedItem); - $scope.model.allowedContentTypes.push(model.selectedItem.id); - vm.childNodeSelectorOverlay.show = false; - vm.childNodeSelectorOverlay = null; - } - }; - } - - function removeChild(selectedChild, index) { - // remove from vm - vm.selectedChildren.splice(index, 1); - - // remove from content type model - var selectedChildIndex = $scope.model.allowedContentTypes.indexOf(selectedChild.id); - $scope.model.allowedContentTypes.splice(selectedChildIndex, 1); - } - - } - - angular.module("umbraco").controller("Umbraco.Editors.DocumentType.PermissionsController", PermissionsController); -})(); - -/** + angular.module('umbraco').controller('Umbraco.Editors.DocumentType.PermissionsController', PermissionsController); + }()); + /** * @ngdoc controller * @name Umbraco.Editors.DocumentType.TemplatesController * @function @@ -7188,62 +7846,47 @@ angular.module("umbraco") * @description * The controller for the content type editor templates sub view */ -(function() { - 'use strict'; - - function TemplatesController($scope, entityResource, contentTypeHelper, $routeParams) { - - /* ----------- SCOPE VARIABLES ----------- */ - - var vm = this; - - vm.availableTemplates = []; - vm.updateTemplatePlaceholder = false; - - - /* ---------- INIT ---------- */ - - init(); - - function init() { - - entityResource.getAll("Template").then(function(templates){ - - vm.availableTemplates = templates; - - // update placeholder template information on new doc types - if (!$routeParams.notemplate && $scope.model.id === 0) { - vm.updateTemplatePlaceholder = true; - vm.availableTemplates = contentTypeHelper.insertTemplatePlaceholder(vm.availableTemplates); - } - - }); - + (function () { + 'use strict'; + function TemplatesController($scope, entityResource, contentTypeHelper, $routeParams) { + /* ----------- SCOPE VARIABLES ----------- */ + var vm = this; + vm.availableTemplates = []; + vm.updateTemplatePlaceholder = false; + /* ---------- INIT ---------- */ + init(); + function init() { + entityResource.getAll('Template').then(function (templates) { + vm.availableTemplates = templates; + // update placeholder template information on new doc types + if (!$routeParams.notemplate && $scope.model.id === 0) { + vm.updateTemplatePlaceholder = true; + vm.availableTemplates = contentTypeHelper.insertTemplatePlaceholder(vm.availableTemplates); + } + }); + } } - + angular.module('umbraco').controller('Umbraco.Editors.DocumentType.TemplatesController', TemplatesController); + }()); + /** + * @ngdoc controller + * @name Umbraco.Editors.Media.CreateController + * @function + * + * @description + * The controller for the media creation dialog + */ + function mediaCreateController($scope, $routeParams, $location, mediaTypeResource, iconHelper, navigationService) { + mediaTypeResource.getAllowedTypes($scope.currentNode.id).then(function (data) { + $scope.allowedTypes = iconHelper.formatContentTypeIcons(data); + }); + $scope.createMediaItem = function (docType) { + $location.path('/media/media/edit/' + $scope.currentNode.id).search('doctype', docType.alias).search('create', 'true'); + navigationService.hideMenu(); + }; } - - angular.module("umbraco").controller("Umbraco.Editors.DocumentType.TemplatesController", TemplatesController); -})(); - -/** - * @ngdoc controller - * @name Umbraco.Editors.Media.CreateController - * @function - * - * @description - * The controller for the media creation dialog - */ -function mediaCreateController($scope, $routeParams, mediaTypeResource, iconHelper) { - - mediaTypeResource.getAllowedTypes($scope.currentNode.id).then(function(data) { - $scope.allowedTypes = iconHelper.formatContentTypeIcons(data); - }); - -} - -angular.module('umbraco').controller("Umbraco.Editors.Media.CreateController", mediaCreateController); -/** + angular.module('umbraco').controller('Umbraco.Editors.Media.CreateController', mediaCreateController); + /** * @ngdoc controller * @name Umbraco.Editors.ContentDeleteController * @function @@ -7251,224 +7894,178 @@ angular.module('umbraco').controller("Umbraco.Editors.Media.CreateController", m * @description * The controller for deleting content */ -function MediaDeleteController($scope, mediaResource, treeService, navigationService, editorState, $location, dialogService, notificationsService) { - - $scope.performDelete = function() { - - // stop from firing again on double-click - if ($scope.busy) { return false; } - - //mark it for deletion (used in the UI) - $scope.currentNode.loading = true; - $scope.busy = true; - - mediaResource.deleteById($scope.currentNode.id).then(function () { - $scope.currentNode.loading = false; - - //get the root node before we remove it - var rootNode = treeService.getTreeRoot($scope.currentNode); - - treeService.removeNode($scope.currentNode); - - if (rootNode) { - //ensure the recycle bin has child nodes now - var recycleBin = treeService.getDescendantNode(rootNode, -21); - if (recycleBin) { - recycleBin.hasChildren = true; + function MediaDeleteController($scope, mediaResource, treeService, navigationService, editorState, $location, dialogService, notificationsService) { + $scope.performDelete = function () { + // stop from firing again on double-click + if ($scope.busy) { + return false; + } + //mark it for deletion (used in the UI) + $scope.currentNode.loading = true; + $scope.busy = true; + mediaResource.deleteById($scope.currentNode.id).then(function () { + $scope.currentNode.loading = false; + //get the root node before we remove it + var rootNode = treeService.getTreeRoot($scope.currentNode); + treeService.removeNode($scope.currentNode); + if (rootNode) { + //ensure the recycle bin has child nodes now + var recycleBin = treeService.getDescendantNode(rootNode, -21); + if (recycleBin) { + recycleBin.hasChildren = true; + } } - } - - //if the current edited item is the same one as we're deleting, we need to navigate elsewhere - if (editorState.current && editorState.current.id == $scope.currentNode.id) { - - //If the deleted item lived at the root then just redirect back to the root, otherwise redirect to the item's parent - var location = "/media"; - if ($scope.currentNode.parentId.toString() !== "-1") - location = "/media/media/edit/" + $scope.currentNode.parentId; - - $location.path(location); - } - - navigationService.hideMenu(); - - }, function (err) { - - $scope.currentNode.loading = false; - $scope.busy = false; - - //check if response is ysod - if (err.status && err.status >= 500) { - dialogService.ysodDialog(err); - } - - if (err.data && angular.isArray(err.data.notifications)) { - for (var i = 0; i < err.data.notifications.length; i++) { - notificationsService.showNotification(err.data.notifications[i]); + //if the current edited item is the same one as we're deleting, we need to navigate elsewhere + if (editorState.current && editorState.current.id == $scope.currentNode.id) { + //If the deleted item lived at the root then just redirect back to the root, otherwise redirect to the item's parent + var location = '/media'; + if ($scope.currentNode.parentId.toString() !== '-1') + location = '/media/media/edit/' + $scope.currentNode.parentId; + $location.path(location); } + navigationService.hideMenu(); + }, function (err) { + $scope.currentNode.loading = false; + $scope.busy = false; + //check if response is ysod + if (err.status && err.status >= 500) { + dialogService.ysodDialog(err); + } + if (err.data && angular.isArray(err.data.notifications)) { + for (var i = 0; i < err.data.notifications.length; i++) { + notificationsService.showNotification(err.data.notifications[i]); + } + } + }); + }; + $scope.cancel = function () { + navigationService.hideDialog(); + }; + } + angular.module('umbraco').controller('Umbraco.Editors.Media.DeleteController', MediaDeleteController); + /** + * @ngdoc controller + * @name Umbraco.Editors.Media.EditController + * @function + * + * @description + * The controller for the media editor + */ + function mediaEditController($scope, $routeParams, appState, mediaResource, entityResource, navigationService, notificationsService, angularHelper, serverValidationManager, contentEditingHelper, fileManager, treeService, formHelper, umbModelMapper, editorState, umbRequestHelper, $http) { + //setup scope vars + $scope.currentSection = appState.getSectionState('currentSection'); + $scope.currentNode = null; + //the editors affiliated node + $scope.page = {}; + $scope.page.loading = false; + $scope.page.menu = {}; + $scope.page.menu.currentSection = appState.getSectionState('currentSection'); + $scope.page.menu.currentNode = null; + //the editors affiliated node + $scope.page.listViewPath = null; + $scope.page.saveButtonState = 'init'; + /** Syncs the content item to it's tree node - this occurs on first load and after saving */ + function syncTreeNode(content, path, initialLoad) { + if (!$scope.content.isChildOfListView) { + navigationService.syncTree({ + tree: 'media', + path: path.split(','), + forceReload: initialLoad !== true + }).then(function (syncArgs) { + $scope.page.menu.currentNode = syncArgs.node; + }); + } else if (initialLoad === true) { + //it's a child item, just sync the ui node to the parent + navigationService.syncTree({ + tree: 'media', + path: path.substring(0, path.lastIndexOf(',')).split(','), + forceReload: initialLoad !== true + }); + //if this is a child of a list view and it's the initial load of the editor, we need to get the tree node + // from the server so that we can load in the actions menu. + umbRequestHelper.resourcePromise($http.get(content.treeNodeUrl), 'Failed to retrieve data for child node ' + content.id).then(function (node) { + $scope.page.menu.currentNode = node; + }); } - }); - }; - - $scope.cancel = function() { - navigationService.hideDialog(); - }; -} - -angular.module("umbraco").controller("Umbraco.Editors.Media.DeleteController", MediaDeleteController); - -/** - * @ngdoc controller - * @name Umbraco.Editors.Media.EditController - * @function - * - * @description - * The controller for the media editor - */ -function mediaEditController($scope, $routeParams, appState, mediaResource, entityResource, navigationService, notificationsService, angularHelper, serverValidationManager, contentEditingHelper, fileManager, treeService, formHelper, umbModelMapper, editorState, umbRequestHelper, $http) { - - //setup scope vars - $scope.currentSection = appState.getSectionState("currentSection"); - $scope.currentNode = null; //the editors affiliated node - - $scope.page = {}; - $scope.page.loading = false; - $scope.page.menu = {}; - $scope.page.menu.currentSection = appState.getSectionState("currentSection"); - $scope.page.menu.currentNode = null; //the editors affiliated node - $scope.page.listViewPath = null; - $scope.page.saveButtonState = "init"; - - /** Syncs the content item to it's tree node - this occurs on first load and after saving */ - function syncTreeNode(content, path, initialLoad) { - - if (!$scope.content.isChildOfListView) { - navigationService.syncTree({ tree: "media", path: path.split(","), forceReload: initialLoad !== true }).then(function (syncArgs) { - $scope.page.menu.currentNode = syncArgs.node; - }); - } - else if (initialLoad === true) { - - //it's a child item, just sync the ui node to the parent - navigationService.syncTree({ tree: "media", path: path.substring(0, path.lastIndexOf(",")).split(","), forceReload: initialLoad !== true }); - - //if this is a child of a list view and it's the initial load of the editor, we need to get the tree node - // from the server so that we can load in the actions menu. - umbRequestHelper.resourcePromise( - $http.get(content.treeNodeUrl), - 'Failed to retrieve data for child node ' + content.id).then(function (node) { - $scope.page.menu.currentNode = node; - }); - } - } - - if ($routeParams.create) { - - $scope.page.loading = true; - - mediaResource.getScaffold($routeParams.id, $routeParams.doctype) - .then(function (data) { - $scope.content = data; - - editorState.set($scope.content); - - $scope.page.loading = false; - - }); - } - else { - - $scope.page.loading = true; - - mediaResource.getById($routeParams.id) - .then(function (data) { - - $scope.content = data; - - if (data.isChildOfListView && data.trashed === false) { - $scope.page.listViewPath = ($routeParams.page) - ? "/media/media/edit/" + data.parentId + "?page=" + $routeParams.page - : "/media/media/edit/" + data.parentId; - } - - editorState.set($scope.content); - - //in one particular special case, after we've created a new item we redirect back to the edit - // route but there might be server validation errors in the collection which we need to display - // after the redirect, so we will bind all subscriptions which will show the server validation errors - // if there are any and then clear them so the collection no longer persists them. - serverValidationManager.executeAndClearAllSubscriptions(); - - syncTreeNode($scope.content, data.path, true); - - if ($scope.content.parentId && $scope.content.parentId != -1) { - //We fetch all ancestors of the node to generate the footer breadcrump navigation - entityResource.getAncestors($routeParams.id, "media") - .then(function (anc) { - $scope.ancestors = anc; - }); - } - - $scope.page.loading = false; - - }); - } - - $scope.save = function () { - - if (!$scope.busy && formHelper.submitForm({ scope: $scope, statusMessage: "Saving..." })) { - - $scope.busy = true; - $scope.page.saveButtonState = "busy"; - - mediaResource.save($scope.content, $routeParams.create, fileManager.getFiles()) - .then(function(data) { - - formHelper.resetForm({ scope: $scope, notifications: data.notifications }); - - contentEditingHelper.handleSuccessfulSave({ - scope: $scope, - savedContent: data, - rebindCallback: contentEditingHelper.reBindChangedProperties($scope.content, data) - }); - - editorState.set($scope.content); - $scope.busy = false; - - syncTreeNode($scope.content, data.path); - - $scope.page.saveButtonState = "success"; - - }, function(err) { - - contentEditingHelper.handleSaveError({ - err: err, - redirectOnFailure: true, - rebindCallback: contentEditingHelper.reBindChangedProperties($scope.content, err.data) - }); - - //show any notifications - if (angular.isArray(err.data.notifications)) { - for (var i = 0; i < err.data.notifications.length; i++) { - notificationsService.showNotification(err.data.notifications[i]); - } - } - - editorState.set($scope.content); - $scope.busy = false; - $scope.page.saveButtonState = "error"; - - }); - }else{ - $scope.busy = false; - } - - }; -} - -angular.module("umbraco") - .controller("Umbraco.Editors.Media.EditController", mediaEditController); - -/** + } + if ($routeParams.create) { + $scope.page.loading = true; + mediaResource.getScaffold($routeParams.id, $routeParams.doctype).then(function (data) { + $scope.content = data; + editorState.set($scope.content); + // We don't get the info tab from the server from version 7.8 so we need to manually add it + contentEditingHelper.addInfoTab($scope.content.tabs); + $scope.page.loading = false; + }); + } else { + $scope.page.loading = true; + mediaResource.getById($routeParams.id).then(function (data) { + $scope.content = data; + if (data.isChildOfListView && data.trashed === false) { + $scope.page.listViewPath = $routeParams.page ? '/media/media/edit/' + data.parentId + '?page=' + $routeParams.page : '/media/media/edit/' + data.parentId; + } + editorState.set($scope.content); + //in one particular special case, after we've created a new item we redirect back to the edit + // route but there might be server validation errors in the collection which we need to display + // after the redirect, so we will bind all subscriptions which will show the server validation errors + // if there are any and then clear them so the collection no longer persists them. + serverValidationManager.executeAndClearAllSubscriptions(); + syncTreeNode($scope.content, data.path, true); + if ($scope.content.parentId && $scope.content.parentId != -1) { + //We fetch all ancestors of the node to generate the footer breadcrump navigation + entityResource.getAncestors($routeParams.id, 'media').then(function (anc) { + $scope.ancestors = anc; + }); + } + // We don't get the info tab from the server from version 7.8 so we need to manually add it + contentEditingHelper.addInfoTab($scope.content.tabs); + $scope.page.loading = false; + }); + } + $scope.save = function () { + if (!$scope.busy && formHelper.submitForm({ + scope: $scope, + statusMessage: 'Saving...' + })) { + $scope.busy = true; + $scope.page.saveButtonState = 'busy'; + mediaResource.save($scope.content, $routeParams.create, fileManager.getFiles()).then(function (data) { + formHelper.resetForm({ + scope: $scope, + notifications: data.notifications + }); + contentEditingHelper.handleSuccessfulSave({ + scope: $scope, + savedContent: data, + rebindCallback: contentEditingHelper.reBindChangedProperties($scope.content, data) + }); + editorState.set($scope.content); + $scope.busy = false; + syncTreeNode($scope.content, data.path); + $scope.page.saveButtonState = 'success'; + }, function (err) { + contentEditingHelper.handleSaveError({ + err: err, + redirectOnFailure: true, + rebindCallback: contentEditingHelper.reBindChangedProperties($scope.content, err.data) + }); + //show any notifications + if (angular.isArray(err.data.notifications)) { + for (var i = 0; i < err.data.notifications.length; i++) { + notificationsService.showNotification(err.data.notifications[i]); + } + } + editorState.set($scope.content); + $scope.busy = false; + $scope.page.saveButtonState = 'error'; + }); + } else { + $scope.busy = false; + } + }; + } + angular.module('umbraco').controller('Umbraco.Editors.Media.EditController', mediaEditController); + /** * @ngdoc controller * @name Umbraco.Editors.Media.EmptyRecycleBinController * @function @@ -7476,104 +8073,124 @@ angular.module("umbraco") * @description * The controller for deleting media */ -function MediaEmptyRecycleBinController($scope, mediaResource, treeService, navigationService, notificationsService, $route) { - - $scope.busy = false; - - $scope.performDelete = function() { - - //(used in the UI) - $scope.busy = true; - $scope.currentNode.loading = true; - - mediaResource.emptyRecycleBin($scope.currentNode.id).then(function (result) { - - $scope.busy = false; - $scope.currentNode.loading = false; - - //show any notifications - if (angular.isArray(result.notifications)) { - for (var i = 0; i < result.notifications.length; i++) { - notificationsService.showNotification(result.notifications[i]); + function MediaEmptyRecycleBinController($scope, mediaResource, treeService, navigationService, notificationsService, $route) { + $scope.busy = false; + $scope.performDelete = function () { + //(used in the UI) + $scope.busy = true; + $scope.currentNode.loading = true; + mediaResource.emptyRecycleBin($scope.currentNode.id).then(function (result) { + $scope.busy = false; + $scope.currentNode.loading = false; + //show any notifications + if (angular.isArray(result.notifications)) { + for (var i = 0; i < result.notifications.length; i++) { + notificationsService.showNotification(result.notifications[i]); + } } - } - - treeService.removeChildNodes($scope.currentNode); - navigationService.hideMenu(); - - //reload the current view - $route.reload(); + treeService.removeChildNodes($scope.currentNode); + navigationService.hideMenu(); + //reload the current view + $route.reload(); + }); + }; + $scope.cancel = function () { + navigationService.hideDialog(); + }; + } + angular.module('umbraco').controller('Umbraco.Editors.Media.EmptyRecycleBinController', MediaEmptyRecycleBinController); + //used for the media picker dialog + angular.module('umbraco').controller('Umbraco.Editors.Media.MoveController', function ($scope, userService, eventsService, mediaResource, appState, treeService, navigationService) { + var dialogOptions = $scope.dialogOptions; + $scope.dialogTreeEventHandler = $({}); + var node = dialogOptions.currentNode; + $scope.treeModel = { hideHeader: false }; + userService.getCurrentUser().then(function (userData) { + $scope.treeModel.hideHeader = userData.startMediaIds.length > 0 && userData.startMediaIds.indexOf(-1) == -1; }); - - }; - - $scope.cancel = function() { - navigationService.hideDialog(); - }; -} - -angular.module("umbraco").controller("Umbraco.Editors.Media.EmptyRecycleBinController", MediaEmptyRecycleBinController); - -//used for the media picker dialog -angular.module("umbraco").controller("Umbraco.Editors.Media.MoveController", - function ($scope, eventsService, mediaResource, appState, treeService, navigationService) { - var dialogOptions = $scope.dialogOptions; - - $scope.dialogTreeEventHandler = $({}); - var node = dialogOptions.currentNode; - - function nodeSelectHandler(ev, args) { - args.event.preventDefault(); - args.event.stopPropagation(); - - eventsService.emit("editors.media.moveController.select", args); - - if ($scope.target) { - //un-select if there's a current one selected - $scope.target.selected = false; - } - - $scope.target = args.node; - $scope.target.selected = true; - } - - $scope.dialogTreeEventHandler.bind("treeNodeSelect", nodeSelectHandler); - - - $scope.move = function () { - mediaResource.move({ parentId: $scope.target.id, id: node.id }) - .then(function (path) { - $scope.error = false; - $scope.success = true; - - //first we need to remove the node that launched the dialog - treeService.removeNode($scope.currentNode); - - //get the currently edited node (if any) - var activeNode = appState.getTreeState("selectedNode"); - - //we need to do a double sync here: first sync to the moved content - but don't activate the node, - //then sync to the currenlty edited content (note: this might not be the content that was moved!!) - - navigationService.syncTree({ tree: "media", path: path, forceReload: true, activate: false }).then(function (args) { - if (activeNode) { - var activeNodePath = treeService.getPath(activeNode).join(); - //sync to this node now - depending on what was copied this might already be synced but might not be - navigationService.syncTree({ tree: "media", path: activeNodePath, forceReload: false, activate: true }); - } - }); - - }, function (err) { - $scope.success = false; - $scope.error = err; + function treeLoadedHandler(ev, args) { + if (node && node.path) { + $scope.dialogTreeEventHandler.syncTree({ + path: node.path, + activate: false }); - }; - - $scope.$on('$destroy', function () { - $scope.dialogTreeEventHandler.unbind("treeNodeSelect", nodeSelectHandler); - }); - }); -/** + } + } + function nodeSelectHandler(ev, args) { + if (args && args.event) { + args.event.preventDefault(); + args.event.stopPropagation(); + } + eventsService.emit('editors.media.moveController.select', args); + if ($scope.target) { + //un-select if there's a current one selected + $scope.target.selected = false; + } + $scope.target = args.node; + $scope.target.selected = true; + } + function nodeExpandedHandler(ev, args) { + // open mini list view for list views + if (args.node.metaData.isContainer) { + openMiniListView(args.node); + } + } + $scope.dialogTreeEventHandler.bind('treeLoaded', treeLoadedHandler); + $scope.dialogTreeEventHandler.bind('treeNodeSelect', nodeSelectHandler); + $scope.dialogTreeEventHandler.bind('treeNodeExpanded', nodeExpandedHandler); + $scope.move = function () { + mediaResource.move({ + parentId: $scope.target.id, + id: node.id + }).then(function (path) { + $scope.error = false; + $scope.success = true; + //first we need to remove the node that launched the dialog + treeService.removeNode($scope.currentNode); + //get the currently edited node (if any) + var activeNode = appState.getTreeState('selectedNode'); + //we need to do a double sync here: first sync to the moved content - but don't activate the node, + //then sync to the currenlty edited content (note: this might not be the content that was moved!!) + navigationService.syncTree({ + tree: 'media', + path: path, + forceReload: true, + activate: false + }).then(function (args) { + if (activeNode) { + var activeNodePath = treeService.getPath(activeNode).join(); + //sync to this node now - depending on what was copied this might already be synced but might not be + navigationService.syncTree({ + tree: 'media', + path: activeNodePath, + forceReload: false, + activate: true + }); + } + }); + }, function (err) { + $scope.success = false; + $scope.error = err; + }); + }; + $scope.$on('$destroy', function () { + $scope.dialogTreeEventHandler.unbind('treeLoaded', treeLoadedHandler); + $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 }); + }; + $scope.closeMiniListView = function () { + $scope.miniListView = undefined; + }; + function openMiniListView(node) { + $scope.miniListView = node; + } + }); + /** * @ngdoc controller * @name Umbraco.Editors.Content.MediaRecycleBinController * @function @@ -7582,116 +8199,191 @@ angular.module("umbraco").controller("Umbraco.Editors.Media.MoveController", * Controls the recycle bin for media * */ - -function MediaRecycleBinController($scope, $routeParams, mediaResource, navigationService, localizationService) { - - //ensures the list view doesn't actually load until we query for the list view config - // for the section - $scope.page = {}; - $scope.page.name = "Recycle Bin"; - $scope.page.nameLocked = true; - - //ensures the list view doesn't actually load until we query for the list view config - // for the section - $scope.listViewPath = null; - - $routeParams.id = "-21"; - mediaResource.getRecycleBin().then(function (result) { - //we'll get the 'content item' for the recycle bin, we know that it will contain a single tab and a - // single property, so we'll extract that property (list view) and use it's data. - var listproperty = result.tabs[0].properties[0]; - - _.each(listproperty.config, function (val, key) { - $scope.model.config[key] = val; + function MediaRecycleBinController($scope, $routeParams, mediaResource, navigationService, localizationService) { + //ensures the list view doesn't actually load until we query for the list view config + // for the section + $scope.page = {}; + $scope.page.name = 'Recycle Bin'; + $scope.page.nameLocked = true; + //ensures the list view doesn't actually load until we query for the list view config + // for the section + $scope.listViewPath = null; + $routeParams.id = '-21'; + mediaResource.getRecycleBin().then(function (result) { + //we'll get the 'content item' for the recycle bin, we know that it will contain a single tab and a + // single property, so we'll extract that property (list view) and use it's data. + var listproperty = result.tabs[0].properties[0]; + _.each(listproperty.config, function (val, key) { + $scope.model.config[key] = val; + }); + $scope.listViewPath = 'views/propertyeditors/listview/listview.html'; }); - $scope.listViewPath = 'views/propertyeditors/listview/listview.html'; - }); - - $scope.model = { config: { entityType: $routeParams.section, layouts: [] } }; - - // sync tree node - navigationService.syncTree({ tree: "media", path: ["-1", $routeParams.id], forceReload: false }); - - localizePageName(); - - function localizePageName() { - - var pageName = "general_recycleBin"; - - localizationService.localize(pageName).then(function (value) { - $scope.page.name = value; + $scope.model = { + config: { + entityType: $routeParams.section, + layouts: [] + } + }; + // sync tree node + navigationService.syncTree({ + tree: 'media', + path: [ + '-1', + $routeParams.id + ], + forceReload: false }); - + localizePageName(); + function localizePageName() { + var pageName = 'general_recycleBin'; + localizationService.localize(pageName).then(function (value) { + $scope.page.name = value; + }); + } } -} - -angular.module('umbraco').controller("Umbraco.Editors.Media.RecycleBinController", MediaRecycleBinController); - -angular.module("umbraco") -.controller("Umbraco.Editors.MediaTypes.CopyController", - function ($scope, mediaTypeResource, treeService, navigationService, notificationsService, appState, eventsService) { + angular.module('umbraco').controller('Umbraco.Editors.Media.RecycleBinController', MediaRecycleBinController); + angular.module('umbraco').controller('Umbraco.Editors.Media.RestoreController', function ($scope, relationResource, mediaResource, navigationService, appState, treeService, localizationService) { + var dialogOptions = $scope.dialogOptions; + var node = dialogOptions.currentNode; + $scope.error = null; + $scope.success = false; + relationResource.getByChildId(node.id, 'relateParentDocumentOnDelete').then(function (data) { + if (data.length == 0) { + $scope.success = false; + $scope.error = { + errorMsg: localizationService.localize('recycleBin_itemCannotBeRestored'), + data: { Message: localizationService.localize('recycleBin_noRestoreRelation') } + }; + return; + } + $scope.relation = data[0]; + if ($scope.relation.parentId == -1) { + $scope.target = { + id: -1, + name: 'Root' + }; + } else { + mediaResource.getById($scope.relation.parentId).then(function (data) { + $scope.target = data; + // make sure the target item isn't in the recycle bin + if ($scope.target.path.indexOf('-20') !== -1) { + $scope.error = { + errorMsg: localizationService.localize('recycleBin_itemCannotBeRestored'), + data: { + Message: localizationService.localize('recycleBin_restoreUnderRecycled').then(function (value) { + value.replace('%0%', $scope.target.name); + }) + } + }; + $scope.success = false; + } + }, function (err) { + $scope.success = false; + $scope.error = err; + }); + } + }, function (err) { + $scope.success = false; + $scope.error = err; + }); + $scope.restore = function () { + // this code was copied from `content.move.controller.js` + mediaResource.move({ + parentId: $scope.target.id, + id: node.id + }).then(function (path) { + $scope.success = true; + //first we need to remove the node that launched the dialog + treeService.removeNode($scope.currentNode); + //get the currently edited node (if any) + var activeNode = appState.getTreeState('selectedNode'); + //we need to do a double sync here: first sync to the moved media item - but don't activate the node, + //then sync to the currenlty edited media item (note: this might not be the media item that was moved!!) + navigationService.syncTree({ + tree: 'media', + path: path, + forceReload: true, + activate: false + }).then(function (args) { + if (activeNode) { + var activeNodePath = treeService.getPath(activeNode).join(); + //sync to this node now - depending on what was copied this might already be synced but might not be + navigationService.syncTree({ + tree: 'media', + path: activeNodePath, + forceReload: false, + activate: true + }); + } + }); + }, function (err) { + $scope.success = false; + $scope.error = err; + }); + }; + }); + angular.module('umbraco').controller('Umbraco.Editors.MediaTypes.CopyController', function ($scope, mediaTypeResource, treeService, navigationService, notificationsService, appState, eventsService) { var dialogOptions = $scope.dialogOptions; $scope.dialogTreeEventHandler = $({}); - function nodeSelectHandler(ev, args) { args.event.preventDefault(); args.event.stopPropagation(); - if ($scope.target) { //un-select if there's a current one selected $scope.target.selected = false; } - $scope.target = args.node; $scope.target.selected = true; } - $scope.copy = function () { - $scope.busy = true; $scope.error = false; - - mediaTypeResource.copy({ parentId: $scope.target.id, id: dialogOptions.currentNode.id }) - .then(function (path) { - $scope.error = false; - $scope.success = true; - $scope.busy = false; - - //get the currently edited node (if any) - var activeNode = appState.getTreeState("selectedNode"); - - //we need to do a double sync here: first sync to the copied content - but don't activate the node, - //then sync to the currenlty edited content (note: this might not be the content that was copied!!) - - navigationService.syncTree({ tree: "mediaTypes", path: path, forceReload: true, activate: false }).then(function (args) { - if (activeNode) { - var activeNodePath = treeService.getPath(activeNode).join(); - //sync to this node now - depending on what was copied this might already be synced but might not be - navigationService.syncTree({ tree: "mediaTypes", path: activeNodePath, forceReload: false, activate: true }); - } - }); - - }, function (err) { - $scope.success = false; - $scope.error = err; - $scope.busy = false; - //show any notifications - if (angular.isArray(err.data.notifications)) { - for (var i = 0; i < err.data.notifications.length; i++) { - notificationsService.showNotification(err.data.notifications[i]); - } + mediaTypeResource.copy({ + parentId: $scope.target.id, + id: dialogOptions.currentNode.id + }).then(function (path) { + $scope.error = false; + $scope.success = true; + $scope.busy = false; + //get the currently edited node (if any) + var activeNode = appState.getTreeState('selectedNode'); + //we need to do a double sync here: first sync to the copied content - but don't activate the node, + //then sync to the currenlty edited content (note: this might not be the content that was copied!!) + navigationService.syncTree({ + tree: 'mediaTypes', + path: path, + forceReload: true, + activate: false + }).then(function (args) { + if (activeNode) { + var activeNodePath = treeService.getPath(activeNode).join(); + //sync to this node now - depending on what was copied this might already be synced but might not be + navigationService.syncTree({ + tree: 'mediaTypes', + path: activeNodePath, + forceReload: false, + activate: true + }); } }); + }, function (err) { + $scope.success = false; + $scope.error = err; + $scope.busy = false; + //show any notifications + if (angular.isArray(err.data.notifications)) { + for (var i = 0; i < err.data.notifications.length; i++) { + notificationsService.showNotification(err.data.notifications[i]); + } + } + }); }; - - $scope.dialogTreeEventHandler.bind("treeNodeSelect", nodeSelectHandler); - + $scope.dialogTreeEventHandler.bind('treeNodeSelect', nodeSelectHandler); $scope.$on('$destroy', function () { - $scope.dialogTreeEventHandler.unbind("treeNodeSelect", nodeSelectHandler); + $scope.dialogTreeEventHandler.unbind('treeNodeSelect', nodeSelectHandler); }); }); - -/** + /** * @ngdoc controller * @name Umbraco.Editors.MediaType.CreateController * @function @@ -7699,53 +8391,45 @@ angular.module("umbraco") * @description * The controller for the media type creation dialog */ -function MediaTypesCreateController($scope, $location, navigationService, mediaTypeResource, formHelper, appState, localizationService) { - - $scope.model = { - folderName: "", - creatingFolder: false - }; - - var node = $scope.dialogOptions.currentNode, - localizeCreateFolder = localizationService.localize("defaultdialog_createFolder"); - - $scope.showCreateFolder = function() { - $scope.model.creatingFolder = true; - } - - $scope.createContainer = function () { - if (formHelper.submitForm({ - scope: $scope, - formCtrl: this.createFolderForm, - statusMessage: localizeCreateFolder - })) { - mediaTypeResource.createContainer(node.id, $scope.model.folderName).then(function (folderId) { - - navigationService.hideMenu(); - var currPath = node.path ? node.path : "-1"; - navigationService.syncTree({ tree: "mediatypes", path: currPath + "," + folderId, forceReload: true, activate: true }); - - formHelper.resetForm({ scope: $scope }); - - var section = appState.getSectionState("currentSection"); - - }, function(err) { - - //TODO: Handle errors - }); + function MediaTypesCreateController($scope, $location, navigationService, mediaTypeResource, formHelper, appState, localizationService) { + $scope.model = { + folderName: '', + creatingFolder: false + }; + var node = $scope.dialogOptions.currentNode, localizeCreateFolder = localizationService.localize('defaultdialog_createFolder'); + $scope.showCreateFolder = function () { + $scope.model.creatingFolder = true; + }; + $scope.createContainer = function () { + if (formHelper.submitForm({ + scope: $scope, + formCtrl: this.createFolderForm, + statusMessage: localizeCreateFolder + })) { + mediaTypeResource.createContainer(node.id, $scope.model.folderName).then(function (folderId) { + navigationService.hideMenu(); + var currPath = node.path ? node.path : '-1'; + navigationService.syncTree({ + tree: 'mediatypes', + path: currPath + ',' + folderId, + forceReload: true, + activate: true + }); + formHelper.resetForm({ scope: $scope }); + var section = appState.getSectionState('currentSection'); + }, function (err) { + }); + } + ; + }; + $scope.createMediaType = function () { + $location.search('create', null); + $location.path('/settings/mediatypes/edit/' + node.id).search('create', 'true'); + navigationService.hideMenu(); }; } - - $scope.createMediaType = function() { - $location.search('create', null); - $location.path("/settings/mediatypes/edit/" + node.id).search("create", "true"); - navigationService.hideMenu(); - } -} - -angular.module('umbraco').controller("Umbraco.Editors.MediaTypes.CreateController", MediaTypesCreateController); - -/** + angular.module('umbraco').controller('Umbraco.Editors.MediaTypes.CreateController', MediaTypesCreateController); + /** * @ngdoc controller * @name Umbraco.Editors.MediaType.DeleteController * @function @@ -7753,772 +8437,657 @@ angular.module('umbraco').controller("Umbraco.Editors.MediaTypes.CreateControlle * @description * The controller for the media type delete dialog */ -function MediaTypesDeleteController($scope, dataTypeResource, mediaTypeResource, treeService, navigationService) { - - $scope.performDelete = function() { - - //mark it for deletion (used in the UI) - $scope.currentNode.loading = true; - mediaTypeResource.deleteById($scope.currentNode.id).then(function () { - $scope.currentNode.loading = false; - - //get the root node before we remove it - var rootNode = treeService.getTreeRoot($scope.currentNode); - - //TODO: Need to sync tree, etc... - treeService.removeNode($scope.currentNode); - navigationService.hideMenu(); - }); - - }; - - $scope.performContainerDelete = function() { - - //mark it for deletion (used in the UI) - $scope.currentNode.loading = true; - mediaTypeResource.deleteContainerById($scope.currentNode.id).then(function () { - $scope.currentNode.loading = false; - - //get the root node before we remove it - var rootNode = treeService.getTreeRoot($scope.currentNode); - - //TODO: Need to sync tree, etc... - treeService.removeNode($scope.currentNode); - navigationService.hideMenu(); - }); - - }; - - $scope.cancel = function() { - navigationService.hideDialog(); - }; -} - -angular.module("umbraco").controller("Umbraco.Editors.MediaTypes.DeleteController", MediaTypesDeleteController); - -/** - * @ngdoc controller - * @name Umbraco.Editors.MediaType.EditController - * @function - * - * @description - * The controller for the media type editor - */ -(function () { - "use strict"; - - function MediaTypesEditController($scope, $routeParams, mediaTypeResource, dataTypeResource, editorState, contentEditingHelper, formHelper, navigationService, iconHelper, contentTypeHelper, notificationsService, $filter, $q, localizationService, overlayHelper, eventsService) { - - var vm = this; - var localizeSaving = localizationService.localize("general_saving"); - var evts = []; - - vm.save = save; - - vm.currentNode = null; - vm.contentType = {}; - vm.page = {}; - vm.page.loading = false; - vm.page.saveButtonState = "init"; - vm.page.navigation = [ - { - "name": localizationService.localize("general_design"), - "icon": "icon-document-dashed-line", - "view": "views/mediatypes/views/design/design.html", - "active": true - }, - { - "name": localizationService.localize("general_listView"), - "icon": "icon-list", - "view": "views/mediatypes/views/listview/listview.html" - }, - { - "name": localizationService.localize("general_rights"), - "icon": "icon-keychain", - "view": "views/mediatypes/views/permissions/permissions.html" - } - ]; - - vm.page.keyboardShortcutsOverview = [ - { - "name": localizationService.localize("main_sections"), - "shortcuts": [ - { - "description": localizationService.localize("shortcuts_navigateSections"), - "keys": [{ "key": "1" }, { "key": "3" }], - "keyRange": true - } - ] - }, - { - "name": localizationService.localize("general_design"), - "shortcuts": [ - { - "description": localizationService.localize("shortcuts_addTab"), - "keys": [{ "key": "alt" }, { "key": "shift" }, { "key": "t" }] - }, - { - "description": localizationService.localize("shortcuts_addProperty"), - "keys": [{ "key": "alt" }, { "key": "shift" }, { "key": "p" }] - }, - { - "description": localizationService.localize("shortcuts_addEditor"), - "keys": [{ "key": "alt" }, { "key": "shift" }, { "key": "e" }] - }, - { - "description": localizationService.localize("shortcuts_editDataType"), - "keys": [{ "key": "alt" }, { "key": "shift" }, { "key": "d" }] - } - ] - }, - { - "name": localizationService.localize("general_listView"), - "shortcuts": [ - { - "description": localizationService.localize("shortcuts_toggleListView"), - "keys": [{ "key": "alt" }, { "key": "shift" }, { "key": "l" }] - } - ] - }, - { - "name": localizationService.localize("general_rights"), - "shortcuts": [ - { - "description": localizationService.localize("shortcuts_toggleAllowAsRoot"), - "keys": [{ "key": "alt" }, { "key": "shift" }, { "key": "r" }] - }, - { - "description": localizationService.localize("shortcuts_addChildNode"), - "keys": [{ "key": "alt" }, { "key": "shift" }, { "key": "c" }] - } - ] - } - ]; - - contentTypeHelper.checkModelsBuilderStatus().then(function (result) { - vm.page.modelsBuilder = result; - if (result) { - //Models builder mode: - vm.page.defaultButton = { - hotKey: "ctrl+s", - hotKeyWhenHidden: true, - labelKey: "buttons_save", - letter: "S", - type: "submit", - handler: function () { vm.save(); } - }; - vm.page.subButtons = [{ - hotKey: "ctrl+g", - hotKeyWhenHidden: true, - labelKey: "buttons_saveAndGenerateModels", - letter: "G", - handler: function () { - - vm.page.saveButtonState = "busy"; - - vm.save().then(function (result) { - - vm.page.saveButtonState = "busy"; - - localizationService.localize("modelsBuilder_buildingModels").then(function (headerValue) { - localizationService.localize("modelsBuilder_waitingMessage").then(function(msgValue) { - notificationsService.info(headerValue, msgValue); - }); - }); - - contentTypeHelper.generateModels().then(function (result) { - - if (result.success) { - - //re-check model status - contentTypeHelper.checkModelsBuilderStatus().then(function (statusResult) { - vm.page.modelsBuilder = statusResult; - }); - - //clear and add success - vm.page.saveButtonState = "init"; - localizationService.localize("modelsBuilder_modelsGenerated").then(function(value) { - notificationsService.success(value); - }); - - } else { - vm.page.saveButtonState = "error"; - localizationService.localize("modelsBuilder_modelsExceptionInUlog").then(function(value) { - notificationsService.error(value); - }); - } - - }, function () { - vm.page.saveButtonState = "error"; - localizationService.localize("modelsBuilder_modelsGeneratedError").then(function(value) { - notificationsService.error(value); - }); - }); - - }); - - } - }]; - } - }); - - if ($routeParams.create) { - vm.page.loading = true; - - //we are creating so get an empty data type item - mediaTypeResource.getScaffold($routeParams.id) - .then(function(dt) { - init(dt); - - vm.page.loading = false; - }); - } - else { - loadMediaType(); - } - - function loadMediaType() { - vm.page.loading = true; - - mediaTypeResource.getById($routeParams.id).then(function(dt) { - init(dt); - - syncTreeNode(vm.contentType, dt.path, true); - - vm.page.loading = false; - }); - } - - /* ---------- SAVE ---------- */ - - function save() { - - // only save if there is no overlays open - if(overlayHelper.getNumberOfOverlays() === 0) { - - var deferred = $q.defer(); - - vm.page.saveButtonState = "busy"; - - // reformat allowed content types to array if id's - vm.contentType.allowedContentTypes = contentTypeHelper.createIdArray(vm.contentType.allowedContentTypes); - - contentEditingHelper.contentEditorPerformSave({ - statusMessage: localizeSaving, - saveMethod: mediaTypeResource.save, - scope: $scope, - content: vm.contentType, - //We do not redirect on failure for doc types - this is because it is not possible to actually save the doc - // type when server side validation fails - as opposed to content where we are capable of saving the content - // item if server side validation fails - redirectOnFailure: false, - // we need to rebind... the IDs that have been created! - rebindCallback: function (origContentType, savedContentType) { - vm.contentType.id = savedContentType.id; - vm.contentType.groups.forEach(function (group) { - if (!group.name) return; - - var k = 0; - while (k < savedContentType.groups.length && savedContentType.groups[k].name != group.name) - k++; - if (k == savedContentType.groups.length) { - group.id = 0; - return; - } - - var savedGroup = savedContentType.groups[k]; - if (!group.id) group.id = savedGroup.id; - - group.properties.forEach(function (property) { - if (property.id || !property.alias) return; - - k = 0; - while (k < savedGroup.properties.length && savedGroup.properties[k].alias != property.alias) - k++; - if (k == savedGroup.properties.length) { - property.id = 0; - return; - } - - var savedProperty = savedGroup.properties[k]; - property.id = savedProperty.id; - }); - }); - } - }).then(function (data) { - //success - syncTreeNode(vm.contentType, data.path); - - vm.page.saveButtonState = "success"; - - deferred.resolve(data); - }, function (err) { - //error - if (err) { - editorState.set($scope.content); - } - else { - localizationService.localize("speechBubbles_validationFailedHeader").then(function (headerValue) { - localizationService.localize("speechBubbles_validationFailedMessage").then(function (msgValue) { - notificationsService.error(headerValue, msgValue); - }); - }); - } - - vm.page.saveButtonState = "error"; - - deferred.reject(err); - }); - - return deferred.promise; - } - } - - function init(contentType) { - - // set all tab to inactive - if (contentType.groups.length !== 0) { - angular.forEach(contentType.groups, function (group) { - - angular.forEach(group.properties, function (property) { - // get data type details for each property - getDataTypeDetails(property); - }); - - }); - } - - // convert icons for content type - convertLegacyIcons(contentType); - - //set a shared state - editorState.set(contentType); - - vm.contentType = contentType; - } - - function convertLegacyIcons(contentType) { - // make array to store contentType icon - var contentTypeArray = []; - - // push icon to array - contentTypeArray.push({ "icon": contentType.icon }); - - // run through icon method - iconHelper.formatContentTypeIcons(contentTypeArray); - - // set icon back on contentType - contentType.icon = contentTypeArray[0].icon; - } - - function getDataTypeDetails(property) { - if (property.propertyState !== "init") { - - dataTypeResource.getById(property.dataTypeId) - .then(function(dataType) { - property.dataTypeIcon = dataType.icon; - property.dataTypeName = dataType.name; - }); - } - } - - - /** Syncs the content type to it's tree node - this occurs on first load and after saving */ - function syncTreeNode(dt, path, initialLoad) { - navigationService.syncTree({ tree: "mediatypes", path: path.split(","), forceReload: initialLoad !== true }).then(function(syncArgs) { - vm.currentNode = syncArgs.node; - }); - } - - evts.push(eventsService.on("app.refreshEditor", function(name, error) { - loadMediaType(); - })); - - //ensure to unregister from all events! - $scope.$on('$destroy', function () { - for (var e in evts) { - eventsService.unsubscribe(evts[e]); - } - }); - } - - angular.module("umbraco").controller("Umbraco.Editors.MediaTypes.EditController", MediaTypesEditController); -})(); - -angular.module("umbraco") -.controller("Umbraco.Editors.MediaTypes.MoveController", - function ($scope, mediaTypeResource, treeService, navigationService, notificationsService, appState, eventsService) { - + function MediaTypesDeleteController($scope, dataTypeResource, mediaTypeResource, treeService, navigationService) { + $scope.performDelete = function () { + //mark it for deletion (used in the UI) + $scope.currentNode.loading = true; + mediaTypeResource.deleteById($scope.currentNode.id).then(function () { + $scope.currentNode.loading = false; + //get the root node before we remove it + var rootNode = treeService.getTreeRoot($scope.currentNode); + //TODO: Need to sync tree, etc... + treeService.removeNode($scope.currentNode); + navigationService.hideMenu(); + }); + }; + $scope.performContainerDelete = function () { + //mark it for deletion (used in the UI) + $scope.currentNode.loading = true; + mediaTypeResource.deleteContainerById($scope.currentNode.id).then(function () { + $scope.currentNode.loading = false; + //get the root node before we remove it + var rootNode = treeService.getTreeRoot($scope.currentNode); + //TODO: Need to sync tree, etc... + treeService.removeNode($scope.currentNode); + navigationService.hideMenu(); + }); + }; + $scope.cancel = function () { + navigationService.hideDialog(); + }; + } + angular.module('umbraco').controller('Umbraco.Editors.MediaTypes.DeleteController', MediaTypesDeleteController); + /** + * @ngdoc controller + * @name Umbraco.Editors.MediaType.EditController + * @function + * + * @description + * The controller for the media type editor + */ + (function () { + 'use strict'; + function MediaTypesEditController($scope, $routeParams, mediaTypeResource, dataTypeResource, editorState, contentEditingHelper, formHelper, navigationService, iconHelper, contentTypeHelper, notificationsService, $filter, $q, localizationService, overlayHelper, eventsService) { + var vm = this; + var localizeSaving = localizationService.localize('general_saving'); + var evts = []; + vm.save = save; + vm.currentNode = null; + vm.contentType = {}; + vm.page = {}; + vm.page.loading = false; + vm.page.saveButtonState = 'init'; + vm.page.navigation = [ + { + 'name': localizationService.localize('general_design'), + 'icon': 'icon-document-dashed-line', + 'view': 'views/mediatypes/views/design/design.html', + 'active': true + }, + { + 'name': localizationService.localize('general_listView'), + 'icon': 'icon-list', + 'view': 'views/mediatypes/views/listview/listview.html' + }, + { + 'name': localizationService.localize('general_rights'), + 'icon': 'icon-keychain', + 'view': 'views/mediatypes/views/permissions/permissions.html' + } + ]; + vm.page.keyboardShortcutsOverview = [ + { + 'name': localizationService.localize('main_sections'), + 'shortcuts': [{ + 'description': localizationService.localize('shortcuts_navigateSections'), + 'keys': [ + { 'key': '1' }, + { 'key': '3' } + ], + 'keyRange': true + }] + }, + { + 'name': localizationService.localize('general_design'), + 'shortcuts': [ + { + 'description': localizationService.localize('shortcuts_addTab'), + 'keys': [ + { 'key': 'alt' }, + { 'key': 'shift' }, + { 'key': 't' } + ] + }, + { + 'description': localizationService.localize('shortcuts_addProperty'), + 'keys': [ + { 'key': 'alt' }, + { 'key': 'shift' }, + { 'key': 'p' } + ] + }, + { + 'description': localizationService.localize('shortcuts_addEditor'), + 'keys': [ + { 'key': 'alt' }, + { 'key': 'shift' }, + { 'key': 'e' } + ] + }, + { + 'description': localizationService.localize('shortcuts_editDataType'), + 'keys': [ + { 'key': 'alt' }, + { 'key': 'shift' }, + { 'key': 'd' } + ] + } + ] + }, + { + 'name': localizationService.localize('general_listView'), + 'shortcuts': [{ + 'description': localizationService.localize('shortcuts_toggleListView'), + 'keys': [ + { 'key': 'alt' }, + { 'key': 'shift' }, + { 'key': 'l' } + ] + }] + }, + { + 'name': localizationService.localize('general_rights'), + 'shortcuts': [ + { + 'description': localizationService.localize('shortcuts_toggleAllowAsRoot'), + 'keys': [ + { 'key': 'alt' }, + { 'key': 'shift' }, + { 'key': 'r' } + ] + }, + { + 'description': localizationService.localize('shortcuts_addChildNode'), + 'keys': [ + { 'key': 'alt' }, + { 'key': 'shift' }, + { 'key': 'c' } + ] + } + ] + } + ]; + contentTypeHelper.checkModelsBuilderStatus().then(function (result) { + vm.page.modelsBuilder = result; + if (result) { + //Models builder mode: + vm.page.defaultButton = { + hotKey: 'ctrl+s', + hotKeyWhenHidden: true, + labelKey: 'buttons_save', + letter: 'S', + type: 'submit', + handler: function () { + vm.save(); + } + }; + vm.page.subButtons = [{ + hotKey: 'ctrl+g', + hotKeyWhenHidden: true, + labelKey: 'buttons_saveAndGenerateModels', + letter: 'G', + handler: function () { + vm.page.saveButtonState = 'busy'; + vm.save().then(function (result) { + vm.page.saveButtonState = 'busy'; + localizationService.localize('modelsBuilder_buildingModels').then(function (headerValue) { + localizationService.localize('modelsBuilder_waitingMessage').then(function (msgValue) { + notificationsService.info(headerValue, msgValue); + }); + }); + contentTypeHelper.generateModels().then(function (result) { + if (!result.lastError) { + //re-check model status + contentTypeHelper.checkModelsBuilderStatus().then(function (statusResult) { + vm.page.modelsBuilder = statusResult; + }); + //clear and add success + vm.page.saveButtonState = 'init'; + localizationService.localize('modelsBuilder_modelsGenerated').then(function (value) { + notificationsService.success(value); + }); + } else { + vm.page.saveButtonState = 'error'; + localizationService.localize('modelsBuilder_modelsExceptionInUlog').then(function (value) { + notificationsService.error(value); + }); + } + }, function () { + vm.page.saveButtonState = 'error'; + localizationService.localize('modelsBuilder_modelsGeneratedError').then(function (value) { + notificationsService.error(value); + }); + }); + }); + } + }]; + } + }); + if ($routeParams.create) { + vm.page.loading = true; + //we are creating so get an empty data type item + mediaTypeResource.getScaffold($routeParams.id).then(function (dt) { + init(dt); + vm.page.loading = false; + }); + } else { + loadMediaType(); + } + function loadMediaType() { + vm.page.loading = true; + mediaTypeResource.getById($routeParams.id).then(function (dt) { + init(dt); + syncTreeNode(vm.contentType, dt.path, true); + vm.page.loading = false; + }); + } + /* ---------- SAVE ---------- */ + function save() { + // only save if there is no overlays open + if (overlayHelper.getNumberOfOverlays() === 0) { + var deferred = $q.defer(); + vm.page.saveButtonState = 'busy'; + // reformat allowed content types to array if id's + vm.contentType.allowedContentTypes = contentTypeHelper.createIdArray(vm.contentType.allowedContentTypes); + contentEditingHelper.contentEditorPerformSave({ + statusMessage: localizeSaving, + saveMethod: mediaTypeResource.save, + scope: $scope, + content: vm.contentType, + //We do not redirect on failure for doc types - this is because it is not possible to actually save the doc + // type when server side validation fails - as opposed to content where we are capable of saving the content + // item if server side validation fails + redirectOnFailure: false, + // we need to rebind... the IDs that have been created! + rebindCallback: function (origContentType, savedContentType) { + vm.contentType.id = savedContentType.id; + vm.contentType.groups.forEach(function (group) { + if (!group.name) + return; + var k = 0; + while (k < savedContentType.groups.length && savedContentType.groups[k].name != group.name) + k++; + if (k == savedContentType.groups.length) { + group.id = 0; + return; + } + var savedGroup = savedContentType.groups[k]; + if (!group.id) + group.id = savedGroup.id; + group.properties.forEach(function (property) { + if (property.id || !property.alias) + return; + k = 0; + while (k < savedGroup.properties.length && savedGroup.properties[k].alias != property.alias) + k++; + if (k == savedGroup.properties.length) { + property.id = 0; + return; + } + var savedProperty = savedGroup.properties[k]; + property.id = savedProperty.id; + }); + }); + } + }).then(function (data) { + //success + syncTreeNode(vm.contentType, data.path); + vm.page.saveButtonState = 'success'; + deferred.resolve(data); + }, function (err) { + //error + if (err) { + editorState.set($scope.content); + } else { + localizationService.localize('speechBubbles_validationFailedHeader').then(function (headerValue) { + localizationService.localize('speechBubbles_validationFailedMessage').then(function (msgValue) { + notificationsService.error(headerValue, msgValue); + }); + }); + } + vm.page.saveButtonState = 'error'; + deferred.reject(err); + }); + return deferred.promise; + } + } + function init(contentType) { + // set all tab to inactive + if (contentType.groups.length !== 0) { + angular.forEach(contentType.groups, function (group) { + angular.forEach(group.properties, function (property) { + // get data type details for each property + getDataTypeDetails(property); + }); + }); + } + // convert icons for content type + convertLegacyIcons(contentType); + //set a shared state + editorState.set(contentType); + vm.contentType = contentType; + } + function convertLegacyIcons(contentType) { + // make array to store contentType icon + var contentTypeArray = []; + // push icon to array + contentTypeArray.push({ 'icon': contentType.icon }); + // run through icon method + iconHelper.formatContentTypeIcons(contentTypeArray); + // set icon back on contentType + contentType.icon = contentTypeArray[0].icon; + } + function getDataTypeDetails(property) { + if (property.propertyState !== 'init') { + dataTypeResource.getById(property.dataTypeId).then(function (dataType) { + property.dataTypeIcon = dataType.icon; + property.dataTypeName = dataType.name; + }); + } + } + /** Syncs the content type to it's tree node - this occurs on first load and after saving */ + function syncTreeNode(dt, path, initialLoad) { + navigationService.syncTree({ + tree: 'mediatypes', + path: path.split(','), + forceReload: initialLoad !== true + }).then(function (syncArgs) { + vm.currentNode = syncArgs.node; + }); + } + evts.push(eventsService.on('app.refreshEditor', function (name, error) { + loadMediaType(); + })); + //ensure to unregister from all events! + $scope.$on('$destroy', function () { + for (var e in evts) { + eventsService.unsubscribe(evts[e]); + } + }); + } + angular.module('umbraco').controller('Umbraco.Editors.MediaTypes.EditController', MediaTypesEditController); + }()); + angular.module('umbraco').controller('Umbraco.Editors.MediaTypes.MoveController', function ($scope, mediaTypeResource, treeService, navigationService, notificationsService, appState, eventsService) { var dialogOptions = $scope.dialogOptions; $scope.dialogTreeEventHandler = $({}); - function nodeSelectHandler(ev, args) { args.event.preventDefault(); args.event.stopPropagation(); - if ($scope.target) { //un-select if there's a current one selected $scope.target.selected = false; } - $scope.target = args.node; $scope.target.selected = true; } - $scope.move = function () { - $scope.busy = true; $scope.error = false; - - mediaTypeResource.move({ parentId: $scope.target.id, id: dialogOptions.currentNode.id }) - .then(function (path) { - $scope.error = false; - $scope.success = true; - $scope.busy = false; - - //first we need to remove the node that launched the dialog - treeService.removeNode($scope.currentNode); - - //get the currently edited node (if any) - var activeNode = appState.getTreeState("selectedNode"); - - //we need to do a double sync here: first sync to the moved content - but don't activate the node, - //then sync to the currenlty edited content (note: this might not be the content that was moved!!) - - navigationService.syncTree({ tree: "mediaTypes", path: path, forceReload: true, activate: false }).then(function (args) { - if (activeNode) { - var activeNodePath = treeService.getPath(activeNode).join(); - //sync to this node now - depending on what was copied this might already be synced but might not be - navigationService.syncTree({ tree: "mediaTypes", path: activeNodePath, forceReload: false, activate: true }); - } - }); - - eventsService.emit('app.refreshEditor'); - - }, function (err) { - $scope.success = false; - $scope.error = err; - $scope.busy = false; - //show any notifications - if (angular.isArray(err.data.notifications)) { - for (var i = 0; i < err.data.notifications.length; i++) { - notificationsService.showNotification(err.data.notifications[i]); - } + mediaTypeResource.move({ + parentId: $scope.target.id, + id: dialogOptions.currentNode.id + }).then(function (path) { + $scope.error = false; + $scope.success = true; + $scope.busy = false; + //first we need to remove the node that launched the dialog + treeService.removeNode($scope.currentNode); + //get the currently edited node (if any) + var activeNode = appState.getTreeState('selectedNode'); + //we need to do a double sync here: first sync to the moved content - but don't activate the node, + //then sync to the currenlty edited content (note: this might not be the content that was moved!!) + navigationService.syncTree({ + tree: 'mediaTypes', + path: path, + forceReload: true, + activate: false + }).then(function (args) { + if (activeNode) { + var activeNodePath = treeService.getPath(activeNode).join(); + //sync to this node now - depending on what was copied this might already be synced but might not be + navigationService.syncTree({ + tree: 'mediaTypes', + path: activeNodePath, + forceReload: false, + activate: true + }); } }); + eventsService.emit('app.refreshEditor'); + }, function (err) { + $scope.success = false; + $scope.error = err; + $scope.busy = false; + //show any notifications + if (angular.isArray(err.data.notifications)) { + for (var i = 0; i < err.data.notifications.length; i++) { + notificationsService.showNotification(err.data.notifications[i]); + } + } + }); }; - - $scope.dialogTreeEventHandler.bind("treeNodeSelect", nodeSelectHandler); - + $scope.dialogTreeEventHandler.bind('treeNodeSelect', nodeSelectHandler); $scope.$on('$destroy', function () { - $scope.dialogTreeEventHandler.unbind("treeNodeSelect", nodeSelectHandler); + $scope.dialogTreeEventHandler.unbind('treeNodeSelect', nodeSelectHandler); }); }); - -(function() { - 'use strict'; - - function PermissionsController($scope, mediaTypeResource, iconHelper, contentTypeHelper, localizationService) { - - /* ----------- SCOPE VARIABLES ----------- */ - - var vm = this; - var childNodeSelectorOverlayTitle = ""; - - vm.mediaTypes = []; - vm.selectedChildren = []; - - vm.addChild = addChild; - vm.removeChild = removeChild; - - /* ---------- INIT ---------- */ - - init(); - - function init() { - - childNodeSelectorOverlayTitle = localizationService.localize("contentTypeEditor_chooseChildNode"); - - mediaTypeResource.getAll().then(function(mediaTypes){ - - vm.mediaTypes = mediaTypes; - - // convert legacy icons - iconHelper.formatContentTypeIcons(vm.mediaTypes); - - vm.selectedChildren = contentTypeHelper.makeObjectArrayFromId($scope.model.allowedContentTypes, vm.mediaTypes); - - if($scope.model.id === 0) { - contentTypeHelper.insertChildNodePlaceholder(vm.mediaTypes, $scope.model.name, $scope.model.icon, $scope.model.id); + (function () { + 'use strict'; + function PermissionsController($scope, mediaTypeResource, iconHelper, contentTypeHelper, localizationService) { + /* ----------- SCOPE VARIABLES ----------- */ + var vm = this; + var childNodeSelectorOverlayTitle = ''; + vm.mediaTypes = []; + vm.selectedChildren = []; + vm.addChild = addChild; + vm.removeChild = removeChild; + vm.toggle = toggle; + /* ---------- INIT ---------- */ + init(); + function init() { + childNodeSelectorOverlayTitle = localizationService.localize('contentTypeEditor_chooseChildNode'); + mediaTypeResource.getAll().then(function (mediaTypes) { + vm.mediaTypes = mediaTypes; + // convert legacy icons + iconHelper.formatContentTypeIcons(vm.mediaTypes); + vm.selectedChildren = contentTypeHelper.makeObjectArrayFromId($scope.model.allowedContentTypes, vm.mediaTypes); + if ($scope.model.id === 0) { + contentTypeHelper.insertChildNodePlaceholder(vm.mediaTypes, $scope.model.name, $scope.model.icon, $scope.model.id); + } + }); + } + function addChild($event) { + vm.childNodeSelectorOverlay = { + view: 'itempicker', + title: childNodeSelectorOverlayTitle, + availableItems: vm.mediaTypes, + selectedItems: vm.selectedChildren, + event: $event, + show: true, + submit: function (model) { + vm.selectedChildren.push(model.selectedItem); + $scope.model.allowedContentTypes.push(model.selectedItem.id); + vm.childNodeSelectorOverlay.show = false; + vm.childNodeSelectorOverlay = null; + } + }; + } + function removeChild(selectedChild, index) { + // remove from vm + vm.selectedChildren.splice(index, 1); + // remove from content type model + var selectedChildIndex = $scope.model.allowedContentTypes.indexOf(selectedChild.id); + $scope.model.allowedContentTypes.splice(selectedChildIndex, 1); + } + /** + * Toggle the $scope.model.allowAsRoot value to either true or false + */ + function toggle() { + if ($scope.model.allowAsRoot) { + $scope.model.allowAsRoot = false; + return; } - - }); - + $scope.model.allowAsRoot = true; + } } - - function addChild($event) { - vm.childNodeSelectorOverlay = { - view: "itempicker", - title: childNodeSelectorOverlayTitle, - availableItems: vm.mediaTypes, - selectedItems: vm.selectedChildren, - event: $event, - show: true, - submit: function(model) { - vm.selectedChildren.push(model.selectedItem); - $scope.model.allowedContentTypes.push(model.selectedItem.id); - vm.childNodeSelectorOverlay.show = false; - vm.childNodeSelectorOverlay = null; - } - }; - } - - function removeChild(selectedChild, index) { - // remove from vm - vm.selectedChildren.splice(index, 1); - - // remove from content type model - var selectedChildIndex = $scope.model.allowedContentTypes.indexOf(selectedChild.id); - $scope.model.allowedContentTypes.splice(selectedChildIndex, 1); - } - + angular.module('umbraco').controller('Umbraco.Editors.MediaType.PermissionsController', PermissionsController); + }()); + /** + * @ngdoc controller + * @name Umbraco.Editors.Member.CreateController + * @function + * + * @description + * The controller for the member creation dialog + */ + function memberCreateController($scope, $routeParams, memberTypeResource, iconHelper) { + memberTypeResource.getTypes($scope.currentNode.id).then(function (data) { + $scope.allowedTypes = iconHelper.formatContentTypeIcons(data); + }); } - - angular.module("umbraco").controller("Umbraco.Editors.MediaType.PermissionsController", PermissionsController); -})(); - -/** - * @ngdoc controller - * @name Umbraco.Editors.Member.CreateController - * @function - * - * @description - * The controller for the member creation dialog - */ -function memberCreateController($scope, $routeParams, memberTypeResource, iconHelper) { - - memberTypeResource.getTypes($scope.currentNode.id).then(function (data) { - $scope.allowedTypes = iconHelper.formatContentTypeIcons(data); - }); - -} - -angular.module('umbraco').controller("Umbraco.Editors.Member.CreateController", memberCreateController); -/** - * @ngdoc controller - * @name Umbraco.Editors.Member.DeleteController - * @function - * - * @description - * The controller for deleting content - */ -function MemberDeleteController($scope, memberResource, treeService, navigationService, editorState, $location, $routeParams) { - - $scope.performDelete = function() { - - //mark it for deletion (used in the UI) - $scope.currentNode.loading = true; - - memberResource.deleteByKey($scope.currentNode.id).then(function () { - $scope.currentNode.loading = false; - - treeService.removeNode($scope.currentNode); - - //if the current edited item is the same one as we're deleting, we need to navigate elsewhere - if (editorState.current && editorState.current.key == $scope.currentNode.id) { - $location.path("/member/member/list/" + ($routeParams.listName ? $routeParams.listName : 'all-members')); - } - - navigationService.hideMenu(); - }); - - }; - - $scope.cancel = function() { - navigationService.hideDialog(); - }; -} - -angular.module("umbraco").controller("Umbraco.Editors.Member.DeleteController", MemberDeleteController); - -/** - * @ngdoc controller - * @name Umbraco.Editors.Member.EditController - * @function - * - * @description - * The controller for the member editor - */ -function MemberEditController($scope, $routeParams, $location, $q, $window, appState, memberResource, entityResource, navigationService, notificationsService, angularHelper, serverValidationManager, contentEditingHelper, fileManager, formHelper, umbModelMapper, editorState, umbRequestHelper, $http) { - - //setup scope vars - $scope.page = {}; - $scope.page.loading = true; - $scope.page.menu = {}; - $scope.page.menu.currentSection = appState.getSectionState("currentSection"); - $scope.page.menu.currentNode = null; //the editors affiliated node - $scope.page.nameLocked = false; - $scope.page.listViewPath = null; - $scope.page.saveButtonState = "init"; - $scope.busy = false; - - $scope.page.listViewPath = ($routeParams.page && $routeParams.listName) - ? "/member/member/list/" + $routeParams.listName + "?page=" + $routeParams.page - : null; - - //build a path to sync the tree with - function buildTreePath(data) { - return $routeParams.listName ? "-1," + $routeParams.listName : "-1"; - } - - if ($routeParams.create) { - - //if there is no doc type specified then we are going to assume that - // we are not using the umbraco membership provider - if ($routeParams.doctype) { - - //we are creating so get an empty member item - memberResource.getScaffold($routeParams.doctype) - .then(function(data) { - - $scope.content = data; - - setHeaderNameState($scope.content); - - editorState.set($scope.content); - - $scope.page.loading = false; - - }); - } - else { - - memberResource.getScaffold() - .then(function (data) { - $scope.content = data; - - setHeaderNameState($scope.content); - - editorState.set($scope.content); - - $scope.page.loading = false; - - }); - } - - } - else { - //so, we usually refernce all editors with the Int ID, but with members we have - //a different pattern, adding a route-redirect here to handle this: - //isNumber doesnt work here since its seen as a string - - //TODO: Why is this here - I don't understand why this would ever be an integer? This will not work when we support non-umbraco membership providers. - - if ($routeParams.id && $routeParams.id.length < 9) { - - entityResource.getById($routeParams.id, "Member").then(function(entity) { - $location.path("/member/member/edit/" + entity.key); - }); - } - else { - - //we are editing so get the content item from the server - memberResource.getByKey($routeParams.id) - .then(function(data) { - - $scope.content = data; - - setHeaderNameState($scope.content); - - editorState.set($scope.content); - - var path = buildTreePath(data); - - //sync the tree (only for ui purposes) - navigationService.syncTree({ tree: "member", path: path.split(",") }); - - //it's the initial load of the editor, we need to get the tree node - // from the server so that we can load in the actions menu. - umbRequestHelper.resourcePromise( - $http.get(data.treeNodeUrl), - 'Failed to retrieve data for child node ' + data.key).then(function (node) { - $scope.page.menu.currentNode = node; - }); - - //in one particular special case, after we've created a new item we redirect back to the edit - // route but there might be server validation errors in the collection which we need to display - // after the redirect, so we will bind all subscriptions which will show the server validation errors - // if there are any and then clear them so the collection no longer persists them. - serverValidationManager.executeAndClearAllSubscriptions(); - - $scope.page.loading = false; - - }); - } - - } - - function setHeaderNameState(content) { - - if(content.membershipScenario === 0) { - $scope.page.nameLocked = true; - } - - } - - $scope.save = function() { - - if (!$scope.busy && formHelper.submitForm({ scope: $scope, statusMessage: "Saving..." })) { - - $scope.busy = true; - $scope.page.saveButtonState = "busy"; - - memberResource.save($scope.content, $routeParams.create, fileManager.getFiles()) - .then(function(data) { - - formHelper.resetForm({ scope: $scope, notifications: data.notifications }); - - contentEditingHelper.handleSuccessfulSave({ - scope: $scope, - savedContent: data, - //specify a custom id to redirect to since we want to use the GUID - redirectId: data.key, - rebindCallback: contentEditingHelper.reBindChangedProperties($scope.content, data) - }); - - editorState.set($scope.content); - $scope.busy = false; - $scope.page.saveButtonState = "success"; - - var path = buildTreePath(data); - - //sync the tree (only for ui purposes) - navigationService.syncTree({ tree: "member", path: path.split(","), forceReload: true }); - - }, function (err) { - - contentEditingHelper.handleSaveError({ - redirectOnFailure: false, - err: err, - rebindCallback: contentEditingHelper.reBindChangedProperties($scope.content, err.data) - }); - - editorState.set($scope.content); - $scope.busy = false; - $scope.page.saveButtonState = "error"; - - }); - }else{ - $scope.busy = false; - } - - }; - -} - -angular.module("umbraco").controller("Umbraco.Editors.Member.EditController", MemberEditController); - -/** + angular.module('umbraco').controller('Umbraco.Editors.Member.CreateController', memberCreateController); + /** + * @ngdoc controller + * @name Umbraco.Editors.Member.DeleteController + * @function + * + * @description + * The controller for deleting content + */ + function MemberDeleteController($scope, memberResource, treeService, navigationService, editorState, $location, $routeParams) { + $scope.performDelete = function () { + //mark it for deletion (used in the UI) + $scope.currentNode.loading = true; + memberResource.deleteByKey($scope.currentNode.id).then(function () { + $scope.currentNode.loading = false; + treeService.removeNode($scope.currentNode); + //if the current edited item is the same one as we're deleting, we need to navigate elsewhere + if (editorState.current && editorState.current.key == $scope.currentNode.id) { + $location.path('/member/member/list/' + ($routeParams.listName ? $routeParams.listName : 'all-members')); + } + navigationService.hideMenu(); + }); + }; + $scope.cancel = function () { + navigationService.hideDialog(); + }; + } + angular.module('umbraco').controller('Umbraco.Editors.Member.DeleteController', MemberDeleteController); + /** + * @ngdoc controller + * @name Umbraco.Editors.Member.EditController + * @function + * + * @description + * The controller for the member editor + */ + function MemberEditController($scope, $routeParams, $location, $q, $window, appState, memberResource, entityResource, navigationService, notificationsService, angularHelper, serverValidationManager, contentEditingHelper, fileManager, formHelper, umbModelMapper, editorState, umbRequestHelper, $http) { + //setup scope vars + $scope.page = {}; + $scope.page.loading = true; + $scope.page.menu = {}; + $scope.page.menu.currentSection = appState.getSectionState('currentSection'); + $scope.page.menu.currentNode = null; + //the editors affiliated node + $scope.page.nameLocked = false; + $scope.page.listViewPath = null; + $scope.page.saveButtonState = 'init'; + $scope.page.exportButton = 'init'; + $scope.busy = false; + $scope.page.listViewPath = $routeParams.page && $routeParams.listName ? '/member/member/list/' + $routeParams.listName + '?page=' + $routeParams.page : null; + //build a path to sync the tree with + function buildTreePath(data) { + return $routeParams.listName ? '-1,' + $routeParams.listName : '-1'; + } + if ($routeParams.create) { + //if there is no doc type specified then we are going to assume that + // we are not using the umbraco membership provider + if ($routeParams.doctype) { + //we are creating so get an empty member item + memberResource.getScaffold($routeParams.doctype).then(function (data) { + $scope.content = data; + setHeaderNameState($scope.content); + editorState.set($scope.content); + $scope.page.loading = false; + }); + } else { + memberResource.getScaffold().then(function (data) { + $scope.content = data; + setHeaderNameState($scope.content); + editorState.set($scope.content); + $scope.page.loading = false; + }); + } + } else { + //so, we usually refernce all editors with the Int ID, but with members we have + //a different pattern, adding a route-redirect here to handle this: + //isNumber doesnt work here since its seen as a string + //TODO: Why is this here - I don't understand why this would ever be an integer? This will not work when we support non-umbraco membership providers. + if ($routeParams.id && $routeParams.id.length < 9) { + entityResource.getById($routeParams.id, 'Member').then(function (entity) { + $location.path('/member/member/edit/' + entity.key); + }); + } else { + //we are editing so get the content item from the server + memberResource.getByKey($routeParams.id).then(function (data) { + $scope.content = data; + setHeaderNameState($scope.content); + editorState.set($scope.content); + var path = buildTreePath(data); + //sync the tree (only for ui purposes) + navigationService.syncTree({ + tree: 'member', + path: path.split(',') + }); + //it's the initial load of the editor, we need to get the tree node + // from the server so that we can load in the actions menu. + umbRequestHelper.resourcePromise($http.get(data.treeNodeUrl), 'Failed to retrieve data for child node ' + data.key).then(function (node) { + $scope.page.menu.currentNode = node; + }); + //in one particular special case, after we've created a new item we redirect back to the edit + // route but there might be server validation errors in the collection which we need to display + // after the redirect, so we will bind all subscriptions which will show the server validation errors + // if there are any and then clear them so the collection no longer persists them. + serverValidationManager.executeAndClearAllSubscriptions(); + $scope.page.loading = false; + }); + } + } + function setHeaderNameState(content) { + if (content.membershipScenario === 0) { + $scope.page.nameLocked = true; + } + } + $scope.save = function () { + if (!$scope.busy && formHelper.submitForm({ + scope: $scope, + statusMessage: 'Saving...' + })) { + $scope.busy = true; + $scope.page.saveButtonState = 'busy'; + memberResource.save($scope.content, $routeParams.create, fileManager.getFiles()).then(function (data) { + formHelper.resetForm({ + scope: $scope, + notifications: data.notifications + }); + contentEditingHelper.handleSuccessfulSave({ + scope: $scope, + savedContent: data, + //specify a custom id to redirect to since we want to use the GUID + redirectId: data.key, + rebindCallback: contentEditingHelper.reBindChangedProperties($scope.content, data) + }); + editorState.set($scope.content); + $scope.busy = false; + $scope.page.saveButtonState = 'success'; + var path = buildTreePath(data); + //sync the tree (only for ui purposes) + navigationService.syncTree({ + tree: 'member', + path: path.split(','), + forceReload: true + }); + }, function (err) { + contentEditingHelper.handleSaveError({ + redirectOnFailure: false, + err: err, + rebindCallback: contentEditingHelper.reBindChangedProperties($scope.content, err.data) + }); + editorState.set($scope.content); + $scope.busy = false; + $scope.page.saveButtonState = 'error'; + }); + } else { + $scope.busy = false; + } + }; + $scope.export = function () { + var memberKey = $scope.content.key; + memberResource.exportMemberData(memberKey); + }; + } + angular.module('umbraco').controller('Umbraco.Editors.Member.EditController', MemberEditController); + /** * @ngdoc controller * @name Umbraco.Editors.Member.ListController * @function @@ -8526,49 +9095,40 @@ angular.module("umbraco").controller("Umbraco.Editors.Member.EditController", Me * @description * The controller for the member list view */ -function MemberListController($scope, $routeParams, $location, $q, $window, appState, memberResource, entityResource, navigationService, notificationsService, angularHelper, serverValidationManager, contentEditingHelper, fileManager, formHelper, umbModelMapper, editorState, localizationService) { - - //setup scope vars - $scope.currentSection = appState.getSectionState("currentSection"); - $scope.currentNode = null; //the editors affiliated node - - $scope.page = {}; - $scope.page.lockedName = true; - $scope.page.loading = true; - - //we are editing so get the content item from the server - memberResource.getListNode($routeParams.id) - .then(function (data) { - + function MemberListController($scope, $routeParams, $location, $q, $window, appState, memberResource, entityResource, navigationService, notificationsService, angularHelper, serverValidationManager, contentEditingHelper, fileManager, formHelper, umbModelMapper, editorState, localizationService) { + //setup scope vars + $scope.currentSection = appState.getSectionState('currentSection'); + $scope.currentNode = null; + //the editors affiliated node + $scope.page = {}; + $scope.page.lockedName = true; + $scope.page.loading = true; + //we are editing so get the content item from the server + memberResource.getListNode($routeParams.id).then(function (data) { $scope.content = data; - //translate "All Members" - if ($scope.content != null && $scope.content.name != null && $scope.content.name.replace(" ", "").toLowerCase() == "allmembers") { - localizationService.localize("member_allMembers").then(function (value) { + if ($scope.content != null && $scope.content.name != null && $scope.content.name.replace(' ', '').toLowerCase() == 'allmembers') { + localizationService.localize('member_allMembers').then(function (value) { $scope.content.name = value; }); } - editorState.set($scope.content); - - navigationService.syncTree({ tree: "member", path: data.path.split(",") }).then(function (syncArgs) { + navigationService.syncTree({ + tree: 'member', + path: data.path.split(',') + }).then(function (syncArgs) { $scope.currentNode = syncArgs.node; }); - //in one particular special case, after we've created a new item we redirect back to the edit // route but there might be server validation errors in the collection which we need to display // after the redirect, so we will bind all subscriptions which will show the server validation errors // if there are any and then clear them so the collection no longer persists them. serverValidationManager.executeAndClearAllSubscriptions(); - $scope.page.loading = false; - }); -} - -angular.module("umbraco").controller("Umbraco.Editors.Member.ListController", MemberListController); - -/** + } + angular.module('umbraco').controller('Umbraco.Editors.Member.ListController', MemberListController); + /** * @ngdoc controller * @name Umbraco.Editors.MemberType.CreateController * @function @@ -8576,88 +9136,71 @@ angular.module("umbraco").controller("Umbraco.Editors.Member.ListController", Me * @description * The controller for the member type creation dialog */ -function MemberTypesCreateController($scope, $location, navigationService, memberTypeResource, formHelper, appState, localizationService) { - - $scope.model = { - folderName: "", - creatingFolder: false - }; - - var node = $scope.dialogOptions.currentNode, - localizeCreateFolder = localizationService.localize("defaultdialog_createFolder"); - - - $scope.showCreateFolder = function() { - $scope.model.creatingFolder = true; - } - - $scope.createContainer = function () { - if (formHelper.submitForm({ - scope: $scope, - formCtrl: this.createFolderForm, - statusMessage: localizeCreateFolder - })) { - memberTypeResource.createContainer(node.id, $scope.model.folderName).then(function (folderId) { - - navigationService.hideMenu(); - var currPath = node.path ? node.path : "-1"; - navigationService.syncTree({ tree: "membertypes", path: currPath + "," + folderId, forceReload: true, activate: true }); - - formHelper.resetForm({ scope: $scope }); - - var section = appState.getSectionState("currentSection"); - - }, function(err) { - - //TODO: Handle errors - }); + function MemberTypesCreateController($scope, $location, navigationService, memberTypeResource, formHelper, appState, localizationService) { + $scope.model = { + folderName: '', + creatingFolder: false + }; + var node = $scope.dialogOptions.currentNode, localizeCreateFolder = localizationService.localize('defaultdialog_createFolder'); + $scope.showCreateFolder = function () { + $scope.model.creatingFolder = true; + }; + $scope.createContainer = function () { + if (formHelper.submitForm({ + scope: $scope, + formCtrl: this.createFolderForm, + statusMessage: localizeCreateFolder + })) { + memberTypeResource.createContainer(node.id, $scope.model.folderName).then(function (folderId) { + navigationService.hideMenu(); + var currPath = node.path ? node.path : '-1'; + navigationService.syncTree({ + tree: 'membertypes', + path: currPath + ',' + folderId, + forceReload: true, + activate: true + }); + formHelper.resetForm({ scope: $scope }); + var section = appState.getSectionState('currentSection'); + }, function (err) { + }); + } + ; + }; + $scope.createMemberType = function () { + $location.search('create', null); + $location.path('/settings/membertypes/edit/' + node.id).search('create', 'true'); + navigationService.hideMenu(); }; } - - $scope.createMemberType = function() { - $location.search('create', null); - $location.path("/settings/membertypes/edit/" + node.id).search("create", "true"); - navigationService.hideMenu(); - } -} - -angular.module('umbraco').controller("Umbraco.Editors.MemberTypes.CreateController", MemberTypesCreateController); - -/** + angular.module('umbraco').controller('Umbraco.Editors.MemberTypes.CreateController', MemberTypesCreateController); + /** * @ngdoc controller - * @name Umbraco.Editors.DocumentType.DeleteController + * @name Umbraco.Editors.MemberTypes.DeleteController * @function * * @description - * The controller for deleting content + * The controller for deleting member types */ -function MemberTypesDeleteController($scope, memberTypeResource, treeService, navigationService) { - - $scope.performDelete = function() { - - //mark it for deletion (used in the UI) - $scope.currentNode.loading = true; - memberTypeResource.deleteById($scope.currentNode.id).then(function () { - $scope.currentNode.loading = false; - - //get the root node before we remove it - var rootNode = treeService.getTreeRoot($scope.currentNode); - - //TODO: Need to sync tree, etc... - treeService.removeNode($scope.currentNode); - navigationService.hideMenu(); - }); - - }; - - $scope.cancel = function() { - navigationService.hideDialog(); - }; -} - -angular.module("umbraco").controller("Umbraco.Editors.MemberTypes.DeleteController", MemberTypesDeleteController); - -/** + function MemberTypesDeleteController($scope, memberTypeResource, treeService, navigationService) { + $scope.performDelete = function () { + //mark it for deletion (used in the UI) + $scope.currentNode.loading = true; + memberTypeResource.deleteById($scope.currentNode.id).then(function () { + $scope.currentNode.loading = false; + //get the root node before we remove it + var rootNode = treeService.getTreeRoot($scope.currentNode); + //TODO: Need to sync tree, etc... + treeService.removeNode($scope.currentNode); + navigationService.hideMenu(); + }); + }; + $scope.cancel = function () { + navigationService.hideDialog(); + }; + } + angular.module('umbraco').controller('Umbraco.Editors.MemberTypes.DeleteController', MemberTypesDeleteController); + /** * @ngdoc controller * @name Umbraco.Editors.MemberType.EditController * @function @@ -8665,302 +9208,248 @@ angular.module("umbraco").controller("Umbraco.Editors.MemberTypes.DeleteControll * @description * The controller for the member type editor */ -(function () { - "use strict"; - - function MemberTypesEditController($scope, $rootScope, $routeParams, $log, $filter, memberTypeResource, dataTypeResource, editorState, iconHelper, formHelper, navigationService, contentEditingHelper, notificationsService, $q, localizationService, overlayHelper, contentTypeHelper) { - - var vm = this; - var localizeSaving = localizationService.localize("general_saving"); - - vm.save = save; - - vm.currentNode = null; - vm.contentType = {}; - vm.page = {}; - vm.page.loading = false; - vm.page.saveButtonState = "init"; - vm.page.navigation = [ - { - "name": localizationService.localize("general_design"), - "icon": "icon-document-dashed-line", - "view": "views/membertypes/views/design/design.html", - "active": true - } - ]; - - vm.page.keyboardShortcutsOverview = [ - { - "name": localizationService.localize("shortcuts_shortcut"), - "shortcuts": [ - { - "description": localizationService.localize("shortcuts_addTab"), - "keys": [{ "key": "alt" }, { "key": "shift" }, { "key": "t" }] - }, - { - "description": localizationService.localize("shortcuts_addProperty"), - "keys": [{ "key": "alt" }, { "key": "shift" }, { "key": "p" }] - }, - { - "description": localizationService.localize("shortcuts_addEditor"), - "keys": [{ "key": "alt" }, { "key": "shift" }, { "key": "e" }] - }, - { - "description": localizationService.localize("shortcuts_editDataType"), - "keys": [{ "key": "alt" }, { "key": "shift" }, { "key": "d" }] - } - ] - } - ]; - - contentTypeHelper.checkModelsBuilderStatus().then(function (result) { - vm.page.modelsBuilder = result; - if (result) { - //Models builder mode: - vm.page.defaultButton = { - hotKey: "ctrl+s", - hotKeyWhenHidden: true, - labelKey: "buttons_save", - letter: "S", - type: "submit", - handler: function () { vm.save(); } - }; - vm.page.subButtons = [{ - hotKey: "ctrl+g", - hotKeyWhenHidden: true, - labelKey: "buttons_saveAndGenerateModels", - letter: "G", - handler: function () { - - vm.page.saveButtonState = "busy"; - - vm.save().then(function (result) { - - vm.page.saveButtonState = "busy"; - - localizationService.localize("modelsBuilder_buildingModels").then(function (headerValue) { - localizationService.localize("modelsBuilder_waitingMessage").then(function(msgValue) { - notificationsService.info(headerValue, msgValue); - }); - }); - - contentTypeHelper.generateModels().then(function (result) { - - if (result.success) { - - //re-check model status - contentTypeHelper.checkModelsBuilderStatus().then(function (statusResult) { - vm.page.modelsBuilder = statusResult; - }); - - //clear and add success - vm.page.saveButtonState = "init"; - localizationService.localize("modelsBuilder_modelsGenerated").then(function(value) { - notificationsService.success(value); - }); - - } else { - vm.page.saveButtonState = "error"; - localizationService.localize("modelsBuilder_modelsExceptionInUlog").then(function(value) { - notificationsService.error(value); - }); - } - - }, function () { - vm.page.saveButtonState = "error"; - localizationService.localize("modelsBuilder_modelsGeneratedError").then(function(value) { - notificationsService.error(value); - }); - }); - - - }); - - } + (function () { + 'use strict'; + function MemberTypesEditController($scope, $rootScope, $routeParams, $log, $filter, memberTypeResource, dataTypeResource, editorState, iconHelper, formHelper, navigationService, contentEditingHelper, notificationsService, $q, localizationService, overlayHelper, contentTypeHelper) { + var vm = this; + var localizeSaving = localizationService.localize('general_saving'); + vm.save = save; + vm.currentNode = null; + vm.contentType = {}; + vm.page = {}; + vm.page.loading = false; + vm.page.saveButtonState = 'init'; + vm.page.navigation = [{ + 'name': localizationService.localize('general_design'), + 'icon': 'icon-document-dashed-line', + 'view': 'views/membertypes/views/design/design.html', + 'active': true }]; - } - }); - - if ($routeParams.create) { - - vm.page.loading = true; - - //we are creating so get an empty data type item - memberTypeResource.getScaffold($routeParams.id) - .then(function (dt) { - init(dt); - - vm.page.loading = false; - }); - } - else { - - vm.page.loading = true; - - memberTypeResource.getById($routeParams.id).then(function (dt) { - init(dt); - - syncTreeNode(vm.contentType, dt.path, true); - - vm.page.loading = false; - }); - } - - function save() { - // only save if there is no overlays open - if(overlayHelper.getNumberOfOverlays() === 0) { - - var deferred = $q.defer(); - - vm.page.saveButtonState = "busy"; - - contentEditingHelper.contentEditorPerformSave({ - statusMessage: localizeSaving, - saveMethod: memberTypeResource.save, - scope: $scope, - content: vm.contentType, - //We do not redirect on failure for doc types - this is because it is not possible to actually save the doc - // type when server side validation fails - as opposed to content where we are capable of saving the content - // item if server side validation fails - redirectOnFailure: false, - // we need to rebind... the IDs that have been created! - rebindCallback: function (origContentType, savedContentType) { - vm.contentType.id = savedContentType.id; - vm.contentType.groups.forEach(function (group) { - if (!group.name) return; - - var k = 0; - while (k < savedContentType.groups.length && savedContentType.groups[k].name != group.name) - k++; - if (k == savedContentType.groups.length) { - group.id = 0; - return; + vm.page.keyboardShortcutsOverview = [{ + 'name': localizationService.localize('shortcuts_shortcut'), + 'shortcuts': [ + { + 'description': localizationService.localize('shortcuts_addTab'), + 'keys': [ + { 'key': 'alt' }, + { 'key': 'shift' }, + { 'key': 't' } + ] + }, + { + 'description': localizationService.localize('shortcuts_addProperty'), + 'keys': [ + { 'key': 'alt' }, + { 'key': 'shift' }, + { 'key': 'p' } + ] + }, + { + 'description': localizationService.localize('shortcuts_addEditor'), + 'keys': [ + { 'key': 'alt' }, + { 'key': 'shift' }, + { 'key': 'e' } + ] + }, + { + 'description': localizationService.localize('shortcuts_editDataType'), + 'keys': [ + { 'key': 'alt' }, + { 'key': 'shift' }, + { 'key': 'd' } + ] + } + ] + }]; + contentTypeHelper.checkModelsBuilderStatus().then(function (result) { + vm.page.modelsBuilder = result; + if (result) { + //Models builder mode: + vm.page.defaultButton = { + hotKey: 'ctrl+s', + hotKeyWhenHidden: true, + labelKey: 'buttons_save', + letter: 'S', + type: 'submit', + handler: function () { + vm.save(); + } + }; + vm.page.subButtons = [{ + hotKey: 'ctrl+g', + hotKeyWhenHidden: true, + labelKey: 'buttons_saveAndGenerateModels', + letter: 'G', + handler: function () { + vm.page.saveButtonState = 'busy'; + vm.save().then(function (result) { + vm.page.saveButtonState = 'busy'; + localizationService.localize('modelsBuilder_buildingModels').then(function (headerValue) { + localizationService.localize('modelsBuilder_waitingMessage').then(function (msgValue) { + notificationsService.info(headerValue, msgValue); + }); + }); + contentTypeHelper.generateModels().then(function (result) { + if (!result.lastError) { + //re-check model status + contentTypeHelper.checkModelsBuilderStatus().then(function (statusResult) { + vm.page.modelsBuilder = statusResult; + }); + //clear and add success + vm.page.saveButtonState = 'init'; + localizationService.localize('modelsBuilder_modelsGenerated').then(function (value) { + notificationsService.success(value); + }); + } else { + vm.page.saveButtonState = 'error'; + localizationService.localize('modelsBuilder_modelsExceptionInUlog').then(function (value) { + notificationsService.error(value); + }); + } + }, function () { + vm.page.saveButtonState = 'error'; + localizationService.localize('modelsBuilder_modelsGeneratedError').then(function (value) { + notificationsService.error(value); + }); + }); + }); } - - var savedGroup = savedContentType.groups[k]; - if (!group.id) group.id = savedGroup.id; - - group.properties.forEach(function (property) { - if (property.id || !property.alias) return; - - k = 0; - while (k < savedGroup.properties.length && savedGroup.properties[k].alias != property.alias) + }]; + } + }); + if ($routeParams.create) { + vm.page.loading = true; + //we are creating so get an empty data type item + memberTypeResource.getScaffold($routeParams.id).then(function (dt) { + init(dt); + vm.page.loading = false; + }); + } else { + vm.page.loading = true; + memberTypeResource.getById($routeParams.id).then(function (dt) { + init(dt); + syncTreeNode(vm.contentType, dt.path, true); + vm.page.loading = false; + }); + } + function save() { + // only save if there is no overlays open + if (overlayHelper.getNumberOfOverlays() === 0) { + var deferred = $q.defer(); + vm.page.saveButtonState = 'busy'; + contentEditingHelper.contentEditorPerformSave({ + statusMessage: localizeSaving, + saveMethod: memberTypeResource.save, + scope: $scope, + content: vm.contentType, + //We do not redirect on failure for doc types - this is because it is not possible to actually save the doc + // type when server side validation fails - as opposed to content where we are capable of saving the content + // item if server side validation fails + redirectOnFailure: false, + // we need to rebind... the IDs that have been created! + rebindCallback: function (origContentType, savedContentType) { + vm.contentType.id = savedContentType.id; + vm.contentType.groups.forEach(function (group) { + if (!group.name) + return; + var k = 0; + while (k < savedContentType.groups.length && savedContentType.groups[k].name != group.name) k++; - if (k == savedGroup.properties.length) { - property.id = 0; + if (k == savedContentType.groups.length) { + group.id = 0; return; } - - var savedProperty = savedGroup.properties[k]; - property.id = savedProperty.id; + var savedGroup = savedContentType.groups[k]; + if (!group.id) + group.id = savedGroup.id; + group.properties.forEach(function (property) { + if (property.id || !property.alias) + return; + k = 0; + while (k < savedGroup.properties.length && savedGroup.properties[k].alias != property.alias) + k++; + if (k == savedGroup.properties.length) { + property.id = 0; + return; + } + var savedProperty = savedGroup.properties[k]; + property.id = savedProperty.id; + }); }); - }); - } - }).then(function (data) { - //success - syncTreeNode(vm.contentType, data.path); - - vm.page.saveButtonState = "success"; - - deferred.resolve(data); - }, function (err) { - //error - if (err) { - editorState.set($scope.content); - } - else { - localizationService.localize("speechBubbles_validationFailedHeader").then(function (headerValue) { - localizationService.localize("speechBubbles_validationFailedMessage").then(function (msgValue) { - notificationsService.error(headerValue, msgValue); + } + }).then(function (data) { + //success + syncTreeNode(vm.contentType, data.path); + vm.page.saveButtonState = 'success'; + deferred.resolve(data); + }, function (err) { + //error + if (err) { + editorState.set($scope.content); + } else { + localizationService.localize('speechBubbles_validationFailedHeader').then(function (headerValue) { + localizationService.localize('speechBubbles_validationFailedMessage').then(function (msgValue) { + notificationsService.error(headerValue, msgValue); + }); }); - }); - } - - vm.page.saveButtonState = "error"; - - deferred.reject(err); - }); - - return deferred.promise; - } - - } - - function init(contentType) { - - // set all tab to inactive - if (contentType.groups.length !== 0) { - angular.forEach(contentType.groups, function (group) { - - angular.forEach(group.properties, function (property) { - // get data type details for each property - getDataTypeDetails(property); + } + vm.page.saveButtonState = 'error'; + deferred.reject(err); }); - + return deferred.promise; + } + } + function init(contentType) { + // set all tab to inactive + if (contentType.groups.length !== 0) { + angular.forEach(contentType.groups, function (group) { + angular.forEach(group.properties, function (property) { + // get data type details for each property + getDataTypeDetails(property); + }); + }); + } + // convert legacy icons + convertLegacyIcons(contentType); + //set a shared state + editorState.set(contentType); + vm.contentType = contentType; + } + function convertLegacyIcons(contentType) { + // make array to store contentType icon + var contentTypeArray = []; + // push icon to array + contentTypeArray.push({ 'icon': contentType.icon }); + // run through icon method + iconHelper.formatContentTypeIcons(contentTypeArray); + // set icon back on contentType + contentType.icon = contentTypeArray[0].icon; + } + function getDataTypeDetails(property) { + if (property.propertyState !== 'init') { + dataTypeResource.getById(property.dataTypeId).then(function (dataType) { + property.dataTypeIcon = dataType.icon; + property.dataTypeName = dataType.name; + }); + } + } + /** Syncs the content type to it's tree node - this occurs on first load and after saving */ + function syncTreeNode(dt, path, initialLoad) { + navigationService.syncTree({ + tree: 'membertypes', + path: path.split(','), + forceReload: initialLoad !== true + }).then(function (syncArgs) { + vm.currentNode = syncArgs.node; }); } - - // convert legacy icons - convertLegacyIcons(contentType); - - //set a shared state - editorState.set(contentType); - - vm.contentType = contentType; - } - - function convertLegacyIcons(contentType) { - - // make array to store contentType icon - var contentTypeArray = []; - - // push icon to array - contentTypeArray.push({ "icon": contentType.icon }); - - // run through icon method - iconHelper.formatContentTypeIcons(contentTypeArray); - - // set icon back on contentType - contentType.icon = contentTypeArray[0].icon; - - } - - function getDataTypeDetails(property) { - - if (property.propertyState !== "init") { - - dataTypeResource.getById(property.dataTypeId) - .then(function (dataType) { - property.dataTypeIcon = dataType.icon; - property.dataTypeName = dataType.name; - }); - } - } - - /** Syncs the content type to it's tree node - this occurs on first load and after saving */ - function syncTreeNode(dt, path, initialLoad) { - - navigationService.syncTree({ tree: "membertypes", path: path.split(","), forceReload: initialLoad !== true }).then(function (syncArgs) { - vm.currentNode = syncArgs.node; - }); - - } - - - } - - angular.module("umbraco").controller("Umbraco.Editors.MemberTypes.EditController", MemberTypesEditController); - -})(); - -angular.module("umbraco") -.controller("Umbraco.Editors.MemberTypes.MoveController", - function($scope){ - + angular.module('umbraco').controller('Umbraco.Editors.MemberTypes.EditController', MemberTypesEditController); + }()); + angular.module('umbraco').controller('Umbraco.Editors.MemberTypes.MoveController', function ($scope) { }); - -/** + /** * @ngdoc controller * @name Umbraco.Editors.Packages.DeleteController * @function @@ -8968,578 +9457,478 @@ angular.module("umbraco") * @description * The controller for deleting content */ -function PackageDeleteController($scope, packageResource, treeService, navigationService) { - - $scope.performDelete = function() { - - //mark it for deletion (used in the UI) - $scope.currentNode.loading = true; - packageResource.deleteCreatedPackage($scope.currentNode.id).then(function () { - $scope.currentNode.loading = false; - - //get the root node before we remove it - var rootNode = treeService.getTreeRoot($scope.currentNode); - - treeService.removeNode($scope.currentNode); - navigationService.hideMenu(); - }); - - }; - - $scope.cancel = function() { - navigationService.hideDialog(); - }; -} - -angular.module("umbraco").controller("Umbraco.Editors.Packages.DeleteController", PackageDeleteController); - -(function () { - "use strict"; - - function PackagesOverviewController($scope, $route, $location, navigationService, $timeout, localStorageService) { - - //Hack! - // if there is a cookie value for packageInstallUri then we need to redirect there, - // the issue is that we still have webforms and we cannot go to a hash location and then window.reload - // because it will double load it. - // we will refresh and then navigate there. - - var installPackageUri = localStorageService.get("packageInstallUri"); - if (installPackageUri) { - localStorageService.remove("packageInstallUri"); - } - if (installPackageUri && installPackageUri !== "installed") { - //navigate to the custom installer screen, if it is just "installed", then we'll - //show the installed view - $location.path(installPackageUri).search(""); - } - else { - var vm = this; - - vm.page = {}; - vm.page.name = "Packages"; - vm.page.navigation = [ - { - "name": "Packages", - "icon": "icon-cloud", - "view": "views/packager/views/repo.html", - "active": !installPackageUri || installPackageUri === "navigation" - }, - { - "name": "Installed", - "icon": "icon-box", - "view": "views/packager/views/installed.html", - "active": installPackageUri === "installed" - }, - { - "name": "Install local", - "icon": "icon-add", - "view": "views/packager/views/install-local.html", - "active": installPackageUri === "local" - } - ]; - - $timeout(function () { - navigationService.syncTree({ tree: "packager", path: "-1" }); + function PackageDeleteController($scope, packageResource, treeService, navigationService) { + $scope.performDelete = function () { + //mark it for deletion (used in the UI) + $scope.currentNode.loading = true; + packageResource.deleteCreatedPackage($scope.currentNode.id).then(function () { + $scope.currentNode.loading = false; + //get the root node before we remove it + var rootNode = treeService.getTreeRoot($scope.currentNode); + treeService.removeNode($scope.currentNode); + navigationService.hideMenu(); }); - } - + }; + $scope.cancel = function () { + navigationService.hideDialog(); + }; } - - angular.module("umbraco").controller("Umbraco.Editors.Packages.OverviewController", PackagesOverviewController); - -})(); - -(function () { - "use strict"; - - function PackagesInstallLocalController($scope, $route, $location, Upload, umbRequestHelper, packageResource, localStorageService, $timeout, $window, localizationService) { - - var vm = this; - vm.state = "upload"; - - vm.localPackage = {}; - vm.installPackage = installPackage; - vm.installState = { - status: "", - progress:0 - }; - vm.installCompleted = false; - vm.zipFile = { - uploadStatus: "idle", - uploadProgress: 0, - serverErrorMessage: null - }; - - $scope.handleFiles = function (files, event) { - if (files) { - for (var i = 0; i < files.length; i++) { - upload(files[i]); - } + angular.module('umbraco').controller('Umbraco.Editors.Packages.DeleteController', PackageDeleteController); + (function () { + 'use strict'; + function PackagesOverviewController($scope, $route, $location, navigationService, $timeout, localStorageService) { + //Hack! + // if there is a cookie value for packageInstallUri then we need to redirect there, + // the issue is that we still have webforms and we cannot go to a hash location and then window.reload + // because it will double load it. + // we will refresh and then navigate there. + var installPackageUri = localStorageService.get('packageInstallUri'); + if (installPackageUri) { + localStorageService.remove('packageInstallUri'); } - }; - - function upload(file) { - - Upload.upload({ - url: umbRequestHelper.getApiUrl("packageInstallApiBaseUrl", "UploadLocalPackage"), - fields: {}, - file: file - }).progress(function (evt) { - - // set view state to uploading - vm.state = 'uploading'; - - // calculate progress in percentage - var progressPercentage = parseInt(100.0 * evt.loaded / evt.total, 10); - - // set percentage property on file - vm.zipFile.uploadProgress = progressPercentage; - - // set uploading status on file - vm.zipFile.uploadStatus = "uploading"; - - }).success(function (data, status, headers, config) { - - if (data.notifications && data.notifications.length > 0) { - - // set error status on file - vm.zipFile.uploadStatus = "error"; - - // Throw message back to user with the cause of the error - vm.zipFile.serverErrorMessage = data.notifications[0].message; - - } else { - - // set done status on file - vm.zipFile.uploadStatus = "done"; - loadPackage(); - vm.localPackage = data; - } - - }).error(function (evt, status, headers, config) { - - // set status done - vm.zipFile.uploadStatus = "error"; - - // If file not found, server will return a 404 and display this message - if (status === 404) { - vm.zipFile.serverErrorMessage = "File not found"; - } - else if (status == 400) { - //it's a validation error - vm.zipFile.serverErrorMessage = evt.message; - } - else { - //it's an unhandled error - //if the service returns a detailed error - if (evt.InnerException) { - vm.zipFile.serverErrorMessage = evt.InnerException.ExceptionMessage; - - //Check if its the common "too large file" exception - if (evt.InnerException.StackTrace && evt.InnerException.StackTrace.indexOf("ValidateRequestEntityLength") > 0) { - vm.zipFile.serverErrorMessage = "File too large to upload"; - } - - } else if (evt.Message) { - file.serverErrorMessage = evt.Message; + if (installPackageUri && installPackageUri !== 'installed') { + //navigate to the custom installer screen, if it is just "installed", then we'll + //show the installed view + $location.path(installPackageUri).search(''); + } else { + var vm = this; + vm.page = {}; + vm.page.name = 'Packages'; + vm.page.navigation = [ + { + 'name': 'Packages', + 'icon': 'icon-cloud', + 'view': 'views/packager/views/repo.html', + 'active': !installPackageUri || installPackageUri === 'navigation' + }, + { + 'name': 'Installed', + 'icon': 'icon-box', + 'view': 'views/packager/views/installed.html', + 'active': installPackageUri === 'installed' + }, + { + 'name': 'Install local', + 'icon': 'icon-add', + 'view': 'views/packager/views/install-local.html', + 'active': installPackageUri === 'local' + } + ]; + $timeout(function () { + navigationService.syncTree({ + tree: 'packager', + path: '-1' + }); + }); + } + } + angular.module('umbraco').controller('Umbraco.Editors.Packages.OverviewController', PackagesOverviewController); + }()); + (function () { + 'use strict'; + function PackagesInstallLocalController($scope, $route, $location, Upload, umbRequestHelper, packageResource, localStorageService, $timeout, $window, localizationService, $q) { + var vm = this; + vm.state = 'upload'; + vm.localPackage = {}; + vm.installPackage = installPackage; + vm.installState = { + status: '', + progress: 0 + }; + vm.installCompleted = false; + vm.zipFile = { + uploadStatus: 'idle', + uploadProgress: 0, + serverErrorMessage: null + }; + $scope.handleFiles = function (files, event) { + if (files) { + for (var i = 0; i < files.length; i++) { + upload(files[i]); } } - }); - } - - function loadPackage() { - if (vm.zipFile.uploadStatus === "done") { - vm.state = "packageDetails"; + }; + function upload(file) { + Upload.upload({ + url: umbRequestHelper.getApiUrl('packageInstallApiBaseUrl', 'UploadLocalPackage'), + fields: {}, + file: file + }).progress(function (evt) { + // hack: in some browsers the progress event is called after success + // this prevents the UI from going back to a uploading state + if (vm.zipFile.uploadStatus !== 'done' && vm.zipFile.uploadStatus !== 'error') { + // set view state to uploading + vm.state = 'uploading'; + // calculate progress in percentage + var progressPercentage = parseInt(100 * evt.loaded / evt.total, 10); + // set percentage property on file + vm.zipFile.uploadProgress = progressPercentage; + // set uploading status on file + vm.zipFile.uploadStatus = 'uploading'; + } + }).success(function (data, status, headers, config) { + if (data.notifications && data.notifications.length > 0) { + // set error status on file + vm.zipFile.uploadStatus = 'error'; + // Throw message back to user with the cause of the error + vm.zipFile.serverErrorMessage = data.notifications[0].message; + } else { + // set done status on file + vm.zipFile.uploadStatus = 'done'; + loadPackage(); + vm.zipFile.uploadProgress = 100; + vm.localPackage = data; + } + }).error(function (evt, status, headers, config) { + // set status done + vm.zipFile.uploadStatus = 'error'; + // If file not found, server will return a 404 and display this message + if (status === 404) { + vm.zipFile.serverErrorMessage = 'File not found'; + } else if (status == 400) { + //it's a validation error + vm.zipFile.serverErrorMessage = evt.message; + } else { + //it's an unhandled error + //if the service returns a detailed error + if (evt.InnerException) { + vm.zipFile.serverErrorMessage = evt.InnerException.ExceptionMessage; + //Check if its the common "too large file" exception + if (evt.InnerException.StackTrace && evt.InnerException.StackTrace.indexOf('ValidateRequestEntityLength') > 0) { + vm.zipFile.serverErrorMessage = 'File too large to upload'; + } + } else if (evt.Message) { + vm.zipFile.serverErrorMessage = evt.Message; + } + } + }); } + function loadPackage() { + if (vm.zipFile.uploadStatus === 'done') { + vm.state = 'packageDetails'; + } + } + function installPackage() { + vm.installState.status = localizationService.localize('packager_installStateImporting'); + vm.installState.progress = '0'; + packageResource.import(vm.localPackage).then(function (pack) { + vm.installState.progress = '25'; + vm.installState.status = localizationService.localize('packager_installStateInstalling'); + return packageResource.installFiles(pack); + }, installError).then(function (pack) { + vm.installState.status = localizationService.localize('packager_installStateRestarting'); + vm.installState.progress = '50'; + var deferred = $q.defer(); + //check if the app domain is restarted ever 2 seconds + var count = 0; + function checkRestart() { + $timeout(function () { + packageResource.checkRestart(pack).then(function (d) { + count++; + //if there is an id it means it's not restarted yet but we'll limit it to only check 10 times + if (d.isRestarting && count < 10) { + checkRestart(); + } else { + //it's restarted! + deferred.resolve(d); + } + }, installError); + }, 2000); + } + checkRestart(); + return deferred.promise; + }, installError).then(function (pack) { + vm.installState.status = localizationService.localize('packager_installStateRestarting'); + vm.installState.progress = '75'; + return packageResource.installData(pack); + }, installError).then(function (pack) { + vm.installState.status = localizationService.localize('packager_installStateComplete'); + vm.installState.progress = '100'; + return packageResource.cleanUp(pack); + }, installError).then(function (result) { + if (result.postInstallationPath) { + //Put the redirect Uri in a cookie so we can use after reloading + localStorageService.set('packageInstallUri', result.postInstallationPath); + } else { + //set to a constant value so it knows to just go to the installed view + localStorageService.set('packageInstallUri', 'installed'); + } + vm.installState.status = localizationService.localize('packager_installStateCompleted'); + vm.installCompleted = true; + }, installError); + } + function installError() { + //This will return a rejection meaning that the promise change above will stop + return $q.reject(); + } + vm.reloadPage = function () { + //reload on next digest (after cookie) + $timeout(function () { + $window.location.reload(true); + }); + }; } - - function installPackage() { - vm.installState.status = localizationService.localize("packager_installStateImporting"); - vm.installState.progress = "0"; - - packageResource - .import(vm.localPackage) - .then(function(pack) { - vm.installState.progress = "25"; - vm.installState.status = localizationService.localize("packager_installStateInstalling"); - vm.installState.progress = "50"; - return packageResource.installFiles(pack); - }, - installError) - .then(function(pack) { - vm.installState.status = localizationService.localize("packager_installStateRestarting"); - vm.installState.progress = "75"; - return packageResource.installData(pack); - }, - installError) - .then(function(pack) { - vm.installState.status = localizationService.localize("packager_installStateComplete"); - vm.installState.progress = "100"; - return packageResource.cleanUp(pack); - }, - installError) - .then(function(result) { - - if (result.postInstallationPath) { - //Put the redirect Uri in a cookie so we can use after reloading - localStorageService.set("packageInstallUri", result.postInstallationPath); - } - else { - //set to a constant value so it knows to just go to the installed view - localStorageService.set("packageInstallUri", "installed"); - } - - vm.installState.status = localizationService.localize("packager_installStateCompleted"); - vm.installCompleted = true; - - - - }, - installError); - } - - function installError() { - //This will return a rejection meaning that the promise change above will stop - return $q.reject(); - } - - vm.reloadPage = function() { - //reload on next digest (after cookie) - $timeout(function () { - $window.location.reload(true); - }); - } - } - - angular.module("umbraco").controller("Umbraco.Editors.Packages.InstallLocalController", PackagesInstallLocalController); - -})(); - -(function () { - "use strict"; - - function PackagesInstalledController($scope, $route, $location, packageResource, $timeout, $window, localStorageService, localizationService) { - - var vm = this; - - vm.confirmUninstall = confirmUninstall; - vm.uninstallPackage = uninstallPackage; - vm.state = "list"; - vm.installState = { - status: "" - }; - vm.package = {}; - - function init() { - packageResource.getInstalled() - .then(function (packs) { + angular.module('umbraco').controller('Umbraco.Editors.Packages.InstallLocalController', PackagesInstallLocalController); + }()); + (function () { + 'use strict'; + function PackagesInstalledController($scope, $route, $location, packageResource, $timeout, $window, localStorageService, localizationService) { + var vm = this; + vm.confirmUninstall = confirmUninstall; + vm.uninstallPackage = uninstallPackage; + vm.state = 'list'; + vm.installState = { status: '' }; + vm.package = {}; + function init() { + packageResource.getInstalled().then(function (packs) { vm.installedPackages = packs; }); - vm.installState.status = ""; - vm.state = "list"; - } - - function confirmUninstall(pck) { - vm.state = "packageDetails"; - vm.package = pck; - } - - function uninstallPackage(installedPackage) { - vm.installState.status = localizationService.localize("packager_installStateUninstalling"); - vm.installState.progress = "0"; - - packageResource.uninstall(installedPackage.id) - .then(function () { - + vm.installState.status = ''; + vm.state = 'list'; + } + function confirmUninstall(pck) { + vm.state = 'packageDetails'; + vm.package = pck; + } + function uninstallPackage(installedPackage) { + vm.installState.status = localizationService.localize('packager_installStateUninstalling'); + vm.installState.progress = '0'; + packageResource.uninstall(installedPackage.id).then(function () { if (installedPackage.files.length > 0) { - vm.installState.status = localizationService.localize("packager_installStateComplete"); - vm.installState.progress = "100"; - + vm.installState.status = localizationService.localize('packager_installStateComplete'); + vm.installState.progress = '100'; //set this flag so that on refresh it shows the installed packages list - localStorageService.set("packageInstallUri", "installed"); - + localStorageService.set('packageInstallUri', 'installed'); //reload on next digest (after cookie) $timeout(function () { $window.location.reload(true); }); - - } - else { + } else { init(); } }); - } - - init(); - - } - - angular.module("umbraco").controller("Umbraco.Editors.Packages.InstalledController", PackagesInstalledController); - -})(); - -(function () { - "use strict"; - - function PackagesRepoController($scope, $route, $location, $timeout, ourPackageRepositoryResource, $q, packageResource, localStorageService, localizationService) { - - var vm = this; - - vm.packageViewState = "packageList"; - vm.categories = []; - vm.loading = true; - vm.pagination = { - pageNumber: 1, - totalPages: 10, - pageSize: 24 - }; - vm.searchQuery = ""; - vm.installState = { - status: "", - progress: 0, - type: "ok" - }; - vm.selectCategory = selectCategory; - vm.showPackageDetails = showPackageDetails; - vm.setPackageViewState = setPackageViewState; - vm.nextPage = nextPage; - vm.prevPage = prevPage; - vm.goToPage = goToPage; - vm.installPackage = installPackage; - vm.downloadPackage = downloadPackage; - vm.openLightbox = openLightbox; - vm.closeLightbox = closeLightbox; - vm.search = search; - vm.installCompleted = false; - - var currSort = "Latest"; - //used to cancel any request in progress if another one needs to take it's place - var canceler = null; - - function getActiveCategory() { - if (vm.searchQuery !== "") { - return ""; } - for (var i = 0; i < vm.categories.length; i++) { - if (vm.categories[i].active === true) { - return vm.categories[i].name; - } - } - return ""; + init(); } - - function init() { - + angular.module('umbraco').controller('Umbraco.Editors.Packages.InstalledController', PackagesInstalledController); + }()); + (function () { + 'use strict'; + function PackagesRepoController($scope, $route, $location, $timeout, ourPackageRepositoryResource, $q, packageResource, localStorageService, localizationService) { + var vm = this; + vm.packageViewState = 'packageList'; + vm.categories = []; vm.loading = true; - - $q.all([ - ourPackageRepositoryResource.getCategories() - .then(function(cats) { + vm.pagination = { + pageNumber: 1, + totalPages: 10, + pageSize: 24 + }; + vm.searchQuery = ''; + vm.installState = { + status: '', + progress: 0, + type: 'ok' + }; + vm.selectCategory = selectCategory; + vm.showPackageDetails = showPackageDetails; + vm.setPackageViewState = setPackageViewState; + vm.nextPage = nextPage; + vm.prevPage = prevPage; + vm.goToPage = goToPage; + vm.installPackage = installPackage; + vm.downloadPackage = downloadPackage; + vm.openLightbox = openLightbox; + vm.closeLightbox = closeLightbox; + vm.search = search; + vm.installCompleted = false; + var currSort = 'Latest'; + //used to cancel any request in progress if another one needs to take it's place + var canceler = null; + function getActiveCategory() { + if (vm.searchQuery !== '') { + return ''; + } + for (var i = 0; i < vm.categories.length; i++) { + if (vm.categories[i].active === true) { + return vm.categories[i].name; + } + } + return ''; + } + function init() { + vm.loading = true; + $q.all([ + ourPackageRepositoryResource.getCategories().then(function (cats) { vm.categories = cats; }), - ourPackageRepositoryResource.getPopular(8) - .then(function(pack) { + ourPackageRepositoryResource.getPopular(8).then(function (pack) { vm.popular = pack.packages; }), - ourPackageRepositoryResource.search(vm.pagination.pageNumber - 1, vm.pagination.pageSize, currSort) - .then(function(pack) { + ourPackageRepositoryResource.search(vm.pagination.pageNumber - 1, vm.pagination.pageSize, currSort).then(function (pack) { vm.packages = pack.packages; vm.pagination.totalPages = Math.ceil(pack.total / vm.pagination.pageSize); }) - ]) - .then(function() { + ]).then(function () { vm.loading = false; }); - - } - - function selectCategory(selectedCategory, categories) { - var reset = false; - for (var i = 0; i < categories.length; i++) { - var category = categories[i]; - if (category.name === selectedCategory.name && category.active === true) { - //it's already selected, let's unselect to show all again - reset = true; + } + function selectCategory(selectedCategory, categories) { + var reset = false; + for (var i = 0; i < categories.length; i++) { + var category = categories[i]; + if (category.name === selectedCategory.name && category.active === true) { + //it's already selected, let's unselect to show all again + reset = true; + } + category.active = false; } - category.active = false; - } - - vm.loading = true; - vm.searchQuery = ""; - var searchCategory = selectedCategory.name; - if (reset === true) { - searchCategory = ""; - } - - currSort = "Latest"; - - $q.all([ - ourPackageRepositoryResource.getPopular(8, searchCategory) - .then(function(pack) { + vm.loading = true; + vm.searchQuery = ''; + var searchCategory = selectedCategory.name; + if (reset === true) { + searchCategory = ''; + } + currSort = 'Latest'; + $q.all([ + ourPackageRepositoryResource.getPopular(8, searchCategory).then(function (pack) { vm.popular = pack.packages; }), - ourPackageRepositoryResource.search(vm.pagination.pageNumber - 1, vm.pagination.pageSize, currSort, searchCategory, vm.searchQuery) - .then(function(pack) { + ourPackageRepositoryResource.search(vm.pagination.pageNumber - 1, vm.pagination.pageSize, currSort, searchCategory, vm.searchQuery).then(function (pack) { vm.packages = pack.packages; vm.pagination.totalPages = Math.ceil(pack.total / vm.pagination.pageSize); vm.pagination.pageNumber = 1; }) - ]) - .then(function() { + ]).then(function () { vm.loading = false; selectedCategory.active = reset === false; }); - } - - function showPackageDetails(selectedPackage) { - ourPackageRepositoryResource.getDetails(selectedPackage.id) - .then(function (pack) { - packageResource.validateInstalled(pack.name, pack.latestVersion) - .then(function() { - //ok, can install - vm.package = pack; - vm.package.isValid = true; - vm.packageViewState = "packageDetails"; - }, function() { - //nope, cannot install - vm.package = pack; - vm.package.isValid = false; - vm.packageViewState = "packageDetails"; - }) - }); - } - - function setPackageViewState(state) { - if(state) { - vm.packageViewState = state; } - } - - function nextPage(pageNumber) { - ourPackageRepositoryResource.search(pageNumber - 1, vm.pagination.pageSize, currSort, getActiveCategory(), vm.searchQuery) - .then(function (pack) { + function showPackageDetails(selectedPackage) { + ourPackageRepositoryResource.getDetails(selectedPackage.id).then(function (pack) { + packageResource.validateInstalled(pack.name, pack.latestVersion).then(function () { + //ok, can install + vm.package = pack; + vm.package.isValid = true; + vm.packageViewState = 'packageDetails'; + }, function () { + //nope, cannot install + vm.package = pack; + vm.package.isValid = false; + vm.packageViewState = 'packageDetails'; + }); + }); + } + function setPackageViewState(state) { + if (state) { + vm.packageViewState = state; + } + } + function nextPage(pageNumber) { + ourPackageRepositoryResource.search(pageNumber - 1, vm.pagination.pageSize, currSort, getActiveCategory(), vm.searchQuery).then(function (pack) { vm.packages = pack.packages; vm.pagination.totalPages = Math.ceil(pack.total / vm.pagination.pageSize); }); - } - - function prevPage(pageNumber) { - ourPackageRepositoryResource.search(pageNumber - 1, vm.pagination.pageSize, currSort, getActiveCategory(), vm.searchQuery) - .then(function (pack) { + } + function prevPage(pageNumber) { + ourPackageRepositoryResource.search(pageNumber - 1, vm.pagination.pageSize, currSort, getActiveCategory(), vm.searchQuery).then(function (pack) { vm.packages = pack.packages; vm.pagination.totalPages = Math.ceil(pack.total / vm.pagination.pageSize); }); - } - - function goToPage(pageNumber) { - ourPackageRepositoryResource.search(pageNumber - 1, vm.pagination.pageSize, currSort, getActiveCategory(), vm.searchQuery) - .then(function (pack) { + } + function goToPage(pageNumber) { + ourPackageRepositoryResource.search(pageNumber - 1, vm.pagination.pageSize, currSort, getActiveCategory(), vm.searchQuery).then(function (pack) { vm.packages = pack.packages; vm.pagination.totalPages = Math.ceil(pack.total / vm.pagination.pageSize); }); - } - - function downloadPackage(selectedPackage) { - vm.loading = true; - - packageResource - .fetch(selectedPackage.id) - .then(function(pack) { - vm.packageViewState = "packageInstall"; - vm.loading = false; - vm.localPackage = pack; - vm.localPackage.allowed = true; + } + function downloadPackage(selectedPackage) { + vm.loading = true; + packageResource.fetch(selectedPackage.id).then(function (pack) { + vm.packageViewState = 'packageInstall'; + vm.loading = false; + vm.localPackage = pack; + vm.localPackage.allowed = true; }, function (evt, status, headers, config) { - if (status == 400) { //it's a validation error - vm.installState.type = "error"; + vm.installState.type = 'error'; vm.zipFile.serverErrorMessage = evt.message; } }); - } - - function error(e, args) { - //This will return a rejection meaning that the promise change above will stop - return $q.reject(); - } - - function installPackage(selectedPackage) { - - vm.installState.status = localizationService.localize("packager_installStateImporting"); - vm.installState.progress = "0"; - - packageResource - .import(selectedPackage) - .then(function(pack) { - vm.installState.status = localizationService.localize("packager_installStateInstalling"); - vm.installState.progress = "33"; - return packageResource.installFiles(pack); - }, - error) - .then(function(pack) { - vm.installState.status = localizationService.localize("packager_installStateRestarting"); - vm.installState.progress = "66"; - return packageResource.installData(pack); - }, - error) - .then(function(pack) { - vm.installState.status = localizationService.localize("packager_installStateComplete"); - vm.installState.progress = "100"; - return packageResource.cleanUp(pack); - }, - error) - .then(function(result) { - - if (result.postInstallationPath) { - //Put the redirect Uri in a cookie so we can use after reloading - localStorageService.set("packageInstallUri", result.postInstallationPath); - } - - vm.installState.status = localizationService.localize("packager_installStateCompleted"); - vm.installCompleted = true; - - }, - error); - } - - function openLightbox(itemIndex, items) { - vm.lightbox = { - show: true, - items: items, - activeIndex: itemIndex - }; - } - - function closeLightbox() { - vm.lightbox.show = false; - vm.lightbox = null; - } - - - var searchDebounced = _.debounce(function(e) { - - $scope.$apply(function () { - - //a canceler exists, so perform the cancelation operation and reset - if (canceler) { - canceler.resolve(); - canceler = $q.defer(); - } - else { - canceler = $q.defer(); - } - - currSort = vm.searchQuery ? "Default" : "Latest"; - - ourPackageRepositoryResource.search(vm.pagination.pageNumber - 1, - vm.pagination.pageSize, - currSort, - "", - vm.searchQuery, - canceler) - .then(function(pack) { + } + function error(e, args) { + //This will return a rejection meaning that the promise change above will stop + return $q.reject(); + } + function installPackage(selectedPackage) { + vm.installState.status = localizationService.localize('packager_installStateImporting'); + vm.installState.progress = '0'; + packageResource.import(selectedPackage).then(function (pack) { + vm.installState.status = localizationService.localize('packager_installStateInstalling'); + vm.installState.progress = '25'; + return packageResource.installFiles(pack); + }, error).then(function (pack) { + vm.installState.status = localizationService.localize('packager_installStateRestarting'); + vm.installState.progress = '50'; + var deferred = $q.defer(); + //check if the app domain is restarted ever 2 seconds + var count = 0; + function checkRestart() { + $timeout(function () { + packageResource.checkRestart(pack).then(function (d) { + count++; + //if there is an id it means it's not restarted yet but we'll limit it to only check 10 times + if (d.isRestarting && count < 10) { + checkRestart(); + } else { + //it's restarted! + deferred.resolve(d); + } + }, error); + }, 2000); + } + checkRestart(); + return deferred.promise; + }, error).then(function (pack) { + vm.installState.status = localizationService.localize('packager_installStateRestarting'); + vm.installState.progress = '75'; + return packageResource.installData(pack); + }, error).then(function (pack) { + vm.installState.status = localizationService.localize('packager_installStateComplete'); + vm.installState.progress = '100'; + return packageResource.cleanUp(pack); + }, error).then(function (result) { + if (result.postInstallationPath) { + //Put the redirect Uri in a cookie so we can use after reloading + localStorageService.set('packageInstallUri', result.postInstallationPath); + } + vm.installState.status = localizationService.localize('packager_installStateCompleted'); + vm.installCompleted = true; + }, error); + } + function openLightbox(itemIndex, items) { + vm.lightbox = { + show: true, + items: items, + activeIndex: itemIndex + }; + } + function closeLightbox() { + vm.lightbox.show = false; + vm.lightbox = null; + } + var searchDebounced = _.debounce(function (e) { + $scope.$apply(function () { + //a canceler exists, so perform the cancelation operation and reset + if (canceler) { + canceler.resolve(); + canceler = $q.defer(); + } else { + canceler = $q.defer(); + } + currSort = vm.searchQuery ? 'Default' : 'Latest'; + ourPackageRepositoryResource.search(vm.pagination.pageNumber - 1, vm.pagination.pageSize, currSort, '', vm.searchQuery, canceler).then(function (pack) { vm.packages = pack.packages; vm.pagination.totalPages = Math.ceil(pack.total / vm.pagination.pageSize); vm.pagination.pageNumber = 1; @@ -9547,2047 +9936,2873 @@ angular.module("umbraco").controller("Umbraco.Editors.Packages.DeleteController" //set back to null so it can be re-created canceler = null; }); - - }); - - }, 200); - - function search(searchQuery) { - vm.loading = true; - searchDebounced(); + }); + }, 200); + function search(searchQuery) { + vm.loading = true; + searchDebounced(); + } + vm.reloadPage = function () { + //reload on next digest (after cookie) + $timeout(function () { + window.location.reload(true); + }); + }; + init(); } - - vm.reloadPage = function () { - //reload on next digest (after cookie) - $timeout(function () { - window.location.reload(true); - }); - } - - init(); - - } - - angular.module("umbraco").controller("Umbraco.Editors.Packages.RepoController", PackagesRepoController); - -})(); - -function imageFilePickerController($scope) { - - $scope.pick = function() { - $scope.mediaPickerDialog = {}; - $scope.mediaPickerDialog.view = "mediapicker"; - $scope.mediaPickerDialog.show = true; - - $scope.mediaPickerDialog.submit = function(model) { - $scope.model.value = model.selectedImages[0].image; - $scope.mediaPickerDialog.show = false; - $scope.mediaPickerDialog = null; - }; - - $scope.mediaPickerDialog.close = function(oldModel) { - $scope.mediaPickerDialog.show = false; - $scope.mediaPickerDialog = null; - }; - }; - -} - -angular.module('umbraco').controller("Umbraco.PrevalueEditors.ImageFilePickerController", imageFilePickerController); - -//this controller simply tells the dialogs service to open a mediaPicker window -//with a specified callback, this callback will receive an object with a selection on it -function mediaPickerController($scope, dialogService, entityResource, $log, iconHelper) { - - function trim(str, chr) { - var rgxtrim = (!chr) ? new RegExp('^\\s+|\\s+$', 'g') : new RegExp('^' + chr + '+|' + chr + '+$', 'g'); - return str.replace(rgxtrim, ''); - } - - $scope.renderModel = []; - - var dialogOptions = { - multiPicker: false, - entityType: "Media", - section: "media", - treeAlias: "media" - }; - - $scope.openContentPicker = function() { - $scope.contentPickerOverlay = dialogOptions; - $scope.contentPickerOverlay.view = "treePicker"; - $scope.contentPickerOverlay.show = true; - - $scope.contentPickerOverlay.submit = function(model) { - - if ($scope.contentPickerOverlay.multiPicker) { - _.each(model.selection, function (item, i) { - $scope.add(item); - }); - } - else { - $scope.clear(); - $scope.add(model.selection[0]); - } - - $scope.contentPickerOverlay.show = false; - $scope.contentPickerOverlay = null; - }; - - $scope.contentPickerOverlay.close = function(oldModel) { - $scope.contentPickerOverlay.show = false; - $scope.contentPickerOverlay = null; - }; - } - - $scope.remove =function(index, event){ - event.preventDefault(); - $scope.renderModel.splice(index, 1); - }; - - $scope.clear = function() { - $scope.renderModel = []; - }; - - $scope.add = function (item) { - var currIds = _.map($scope.renderModel, function (i) { - return i.id; - }); - if (currIds.indexOf(item.id) < 0) { - item.icon = iconHelper.convertFromLegacyIcon(item.icon); - $scope.renderModel.push({name: item.name, id: item.id, icon: item.icon}); - } - }; - - var unsubscribe = $scope.$on("formSubmitting", function (ev, args) { - var currIds = _.map($scope.renderModel, function (i) { - return i.id; - }); - $scope.model.value = trim(currIds.join(), ","); - }); - - //when the scope is destroyed we need to unsubscribe - $scope.$on('$destroy', function () { - unsubscribe(); - }); - - //load media data - var modelIds = $scope.model.value ? $scope.model.value.split(',') : []; - entityResource.getByIds(modelIds, dialogOptions.entityType).then(function (data) { - _.each(data, function (item, i) { - item.icon = iconHelper.convertFromLegacyIcon(item.icon); - $scope.renderModel.push({ name: item.name, id: item.id, icon: item.icon }); - }); - }); - -} - -angular.module('umbraco').controller("Umbraco.PrevalueEditors.MediaPickerController",mediaPickerController); -angular.module("umbraco").controller("Umbraco.PrevalueEditors.MultiValuesController", - function ($scope, $timeout) { - - //NOTE: We need to make each item an object, not just a string because you cannot 2-way bind to a primitive. - - $scope.newItem = ""; - $scope.hasError = false; - - if (!angular.isArray($scope.model.value)) { - - //make an array from the dictionary - var items = []; - for (var i in $scope.model.value) { - items.push({ - value: $scope.model.value[i].value, - sortOrder: $scope.model.value[i].sortOrder, - id: i - }); - } - - //ensure the items are sorted by the provided sort order - items.sort(function (a, b) { return (a.sortOrder > b.sortOrder) ? 1 : ((b.sortOrder > a.sortOrder) ? -1 : 0); }); - - //now make the editor model the array - $scope.model.value = items; - } - - $scope.remove = function(item, evt) { - evt.preventDefault(); - - $scope.model.value = _.reject($scope.model.value, function (x) { - return x.value === item.value; - }); - - }; - - $scope.add = function (evt) { - evt.preventDefault(); - - - if ($scope.newItem) { - if (!_.contains($scope.model.value, $scope.newItem)) { - $scope.model.value.push({ value: $scope.newItem }); - $scope.newItem = ""; - $scope.hasError = false; - return; - } - } - - //there was an error, do the highlight (will be set back by the directive) - $scope.hasError = true; - }; - - $scope.sortableOptions = { - axis: 'y', - containment: 'parent', - cursor: 'move', - items: '> div.control-group', - tolerance: 'pointer', - update: function (e, ui) { - // Get the new and old index for the moved element (using the text as the identifier, so - // we'd have a problem if two prevalues were the same, but that would be unlikely) - var newIndex = ui.item.index(); - var movedPrevalueText = $('input[type="text"]', ui.item).val(); - var originalIndex = getElementIndexByPrevalueText(movedPrevalueText); - - // Move the element in the model - if (originalIndex > -1) { - var movedElement = $scope.model.value[originalIndex]; - $scope.model.value.splice(originalIndex, 1); - $scope.model.value.splice(newIndex, 0, movedElement); - } - } - }; - - function getElementIndexByPrevalueText(value) { - for (var i = 0; i < $scope.model.value.length; i++) { - if ($scope.model.value[i].value === value) { - return i; - } - } - - return -1; - } - - }); - -//this controller simply tells the dialogs service to open a mediaPicker window -//with a specified callback, this callback will receive an object with a selection on it -angular.module('umbraco') -.controller("Umbraco.PrevalueEditors.TreePickerController", - - function($scope, dialogService, entityResource, $log, iconHelper){ - $scope.renderModel = []; - $scope.ids = []; - - - var config = { - multiPicker: false, - entityType: "Document", - type: "content", - treeAlias: "content" - }; - - if($scope.model.value){ - $scope.ids = $scope.model.value.split(','); - entityResource.getByIds($scope.ids, config.entityType).then(function (data) { - _.each(data, function (item, i) { - item.icon = iconHelper.convertFromLegacyIcon(item.icon); - $scope.renderModel.push({name: item.name, id: item.id, icon: item.icon}); - }); - }); - } - - $scope.openContentPicker = function() { - $scope.treePickerOverlay = {}; - $scope.treePickerOverlay.section = config.type; - $scope.treePickerOverlay.treeAlias = config.treeAlias; - $scope.treePickerOverlay.multiPicker = config.multiPicker; - $scope.treePickerOverlay.view = "treePicker"; - $scope.treePickerOverlay.show = true; - - $scope.treePickerOverlay.submit = function(model) { - - if(config.multiPicker) { - populate(model.selection); - } else { - populate(model.selection[0]); - } - - $scope.treePickerOverlay.show = false; - $scope.treePickerOverlay = null; - }; - - $scope.treePickerOverlay.close = function(oldModel) { - $scope.treePickerOverlay.show = false; - $scope.treePickerOverlay = null; - }; - - } - - $scope.remove =function(index){ - $scope.renderModel.splice(index, 1); - $scope.ids.splice(index, 1); - $scope.model.value = trim($scope.ids.join(), ","); - }; - - $scope.clear = function() { - $scope.model.value = ""; - $scope.renderModel = []; - $scope.ids = []; - }; - - $scope.add =function(item){ - if($scope.ids.indexOf(item.id) < 0){ - item.icon = iconHelper.convertFromLegacyIcon(item.icon); - - $scope.ids.push(item.id); - $scope.renderModel.push({name: item.name, id: item.id, icon: item.icon}); - $scope.model.value = trim($scope.ids.join(), ","); - } - }; - - - var unsubscribe = $scope.$on("formSubmitting", function (ev, args) { - $scope.model.value = trim($scope.ids.join(), ","); - }); - - //when the scope is destroyed we need to unsubscribe - $scope.$on('$destroy', function () { - unsubscribe(); - }); - - function trim(str, chr) { - var rgxtrim = (!chr) ? new RegExp('^\\s+|\\s+$', 'g') : new RegExp('^'+chr+'+|'+chr+'+$', 'g'); - return str.replace(rgxtrim, ''); - } - - function populate(data){ - if(angular.isArray(data)){ - _.each(data, function (item, i) { - $scope.add(item); - }); - }else{ - $scope.clear(); - $scope.add(data); - } - } -}); - -//this controller simply tells the dialogs service to open a mediaPicker window -//with a specified callback, this callback will receive an object with a selection on it -angular.module('umbraco') -.controller("Umbraco.PrevalueEditors.TreeSourceController", - - function($scope, dialogService, entityResource, $log, iconHelper){ - - if (!$scope.model) { - $scope.model = {}; - } - if (!$scope.model.value) { - $scope.model.value = { - type: "content" - }; - } - - if($scope.model.value.id && $scope.model.value.type !== "member"){ - var ent = "Document"; - if($scope.model.value.type === "media"){ - ent = "Media"; - } - - entityResource.getById($scope.model.value.id, ent).then(function(item){ - item.icon = iconHelper.convertFromLegacyIcon(item.icon); - $scope.node = item; - }); - } - - - $scope.openContentPicker =function(){ - $scope.treePickerOverlay = { - view: "treepicker", - section: $scope.model.value.type, - treeAlias: $scope.model.value.type, - multiPicker: false, - show: true, - submit: function(model) { - var item = model.selection[0]; - populate(item); - $scope.treePickerOverlay.show = false; - $scope.treePickerOverlay = null; - } - }; - }; - - $scope.clear = function() { - $scope.model.value.id = undefined; - $scope.node = undefined; - $scope.model.value.query = undefined; - }; - - - //we always need to ensure we dont submit anything broken - var unsubscribe = $scope.$on("formSubmitting", function (ev, args) { - if($scope.model.value.type === "member"){ - $scope.model.value.id = -1; - $scope.model.value.query = ""; - } - }); - - //when the scope is destroyed we need to unsubscribe - $scope.$on('$destroy', function () { - unsubscribe(); - }); - - function populate(item){ - $scope.clear(); - item.icon = iconHelper.convertFromLegacyIcon(item.icon); - $scope.node = item; - $scope.model.value.id = item.id; - } -}); -function booleanEditorController($scope, $rootScope, assetsService) { - - function setupViewModel() { - $scope.renderModel = { - value: false - }; - - if ($scope.model.config && $scope.model.config.default && $scope.model.config.default.toString() === "1" && $scope.model && !$scope.model.value) { - $scope.renderModel.value = true; - } - - if ($scope.model && $scope.model.value && ($scope.model.value.toString() === "1" || angular.lowercase($scope.model.value) === "true")) { - $scope.renderModel.value = true; - } - } - - setupViewModel(); - - $scope.$watch("renderModel.value", function (newVal) { - $scope.model.value = newVal === true ? "1" : "0"; - }); - - //here we declare a special method which will be called whenever the value has changed from the server - //this is instead of doing a watch on the model.value = faster - $scope.model.onValueChanged = function (newVal, oldVal) { - //update the display val again if it has changed from the server - setupViewModel(); - }; - -} -angular.module("umbraco").controller("Umbraco.PropertyEditors.BooleanController", booleanEditorController); - -angular.module("umbraco").controller("Umbraco.PropertyEditors.ChangePasswordController", - function ($scope, $routeParams) { - - function resetModel(isNew) { - //the model config will contain an object, if it does not we'll create defaults - //NOTE: We will not support doing the password regex on the client side because the regex on the server side - //based on the membership provider cannot always be ported to js from .net directly. - /* - { - hasPassword: true/false, - requiresQuestionAnswer: true/false, - enableReset: true/false, - enablePasswordRetrieval: true/false, - minPasswordLength: 10 - } - */ - - //set defaults if they are not available - if (!$scope.model.config || $scope.model.config.disableToggle === undefined) { - $scope.model.config.disableToggle = false; - } - if (!$scope.model.config || $scope.model.config.hasPassword === undefined) { - $scope.model.config.hasPassword = false; - } - if (!$scope.model.config || $scope.model.config.enablePasswordRetrieval === undefined) { - $scope.model.config.enablePasswordRetrieval = true; - } - if (!$scope.model.config || $scope.model.config.requiresQuestionAnswer === undefined) { - $scope.model.config.requiresQuestionAnswer = false; - } - if (!$scope.model.config || $scope.model.config.enableReset === undefined) { - $scope.model.config.enableReset = true; - } - if (!$scope.model.config || $scope.model.config.minPasswordLength === undefined) { - $scope.model.config.minPasswordLength = 0; - } - - //set the model defaults - if (!angular.isObject($scope.model.value)) { - //if it's not an object then just create a new one - $scope.model.value = { - newPassword: null, - oldPassword: null, - reset: null, - answer: null - }; - } - else { - //just reset the values - - if (!isNew) { - //if it is new, then leave the generated pass displayed - $scope.model.value.newPassword = null; - $scope.model.value.oldPassword = null; - } - $scope.model.value.reset = null; - $scope.model.value.answer = null; - } - - //the value to compare to match passwords - if (!isNew) { - $scope.model.confirm = ""; - } - else if ($scope.model.value.newPassword && $scope.model.value.newPassword.length > 0) { - //if it is new and a new password has been set, then set the confirm password too - $scope.model.confirm = $scope.model.value.newPassword; - } - - } - - resetModel($routeParams.create); - - //if there is no password saved for this entity , it must be new so we do not allow toggling of the change password, it is always there - //with validators turned on. - $scope.changing = $scope.model.config.disableToggle === true || !$scope.model.config.hasPassword; - - //we're not currently changing so set the model to null - if (!$scope.changing) { - $scope.model.value = null; - } - - $scope.doChange = function() { - resetModel(); - $scope.changing = true; - //if there was a previously generated password displaying, clear it - $scope.model.value.generatedPassword = null; - }; - - $scope.cancelChange = function() { - $scope.changing = false; - //set model to null - $scope.model.value = null; - }; - - var unsubscribe = []; - - //listen for the saved event, when that occurs we'll - //change to changing = false; - unsubscribe.push($scope.$on("formSubmitted", function() { - if ($scope.model.config.disableToggle === false) { - $scope.changing = false; - } - })); - unsubscribe.push($scope.$on("formSubmitting", function() { - //if there was a previously generated password displaying, clear it - if ($scope.changing && $scope.model.value) { - $scope.model.value.generatedPassword = null; - } - else if (!$scope.changing) { - //we are not changing, so the model needs to be null - $scope.model.value = null; - } - })); - - //when the scope is destroyed we need to unsubscribe - $scope.$on('$destroy', function () { - for (var u in unsubscribe) { - unsubscribe[u](); - } - }); - - $scope.showReset = function() { - return $scope.model.config.hasPassword && $scope.model.config.enableReset; - }; - - $scope.showOldPass = function() { - return $scope.model.config.hasPassword && - !$scope.model.config.allowManuallyChangingPassword && - !$scope.model.config.enablePasswordRetrieval && !$scope.model.value.reset; - }; - - $scope.showNewPass = function () { - return !$scope.model.value.reset; - }; - - $scope.showConfirmPass = function() { - return !$scope.model.value.reset; - }; - - $scope.showCancelBtn = function() { - return $scope.model.config.disableToggle !== true && $scope.model.config.hasPassword; - }; - - }); - -angular.module("umbraco").controller("Umbraco.PropertyEditors.CheckboxListController", - function($scope) { - - if (angular.isObject($scope.model.config.items)) { - - //now we need to format the items in the dictionary because we always want to have an array - var newItems = []; - var vals = _.values($scope.model.config.items); - var keys = _.keys($scope.model.config.items); - for (var i = 0; i < vals.length; i++) { - newItems.push({ id: keys[i], sortOrder: vals[i].sortOrder, value: vals[i].value }); - } - - //ensure the items are sorted by the provided sort order - newItems.sort(function (a, b) { return (a.sortOrder > b.sortOrder) ? 1 : ((b.sortOrder > a.sortOrder) ? -1 : 0); }); - - //re-assign - $scope.model.config.items = newItems; - - } - - function setupViewModel() { - $scope.selectedItems = []; - - //now we need to check if the value is null/undefined, if it is we need to set it to "" so that any value that is set - // to "" gets selected by default - if ($scope.model.value === null || $scope.model.value === undefined) { - $scope.model.value = []; - } - - for (var i = 0; i < $scope.model.config.items.length; i++) { - var isChecked = _.contains($scope.model.value, $scope.model.config.items[i].id); - $scope.selectedItems.push({ - checked: isChecked, - key: $scope.model.config.items[i].id, - val: $scope.model.config.items[i].value - }); - } - - } - - setupViewModel(); - - - //update the model when the items checked changes - $scope.$watch("selectedItems", function(newVal, oldVal) { - - $scope.model.value = []; - for (var x = 0; x < $scope.selectedItems.length; x++) { - if ($scope.selectedItems[x].checked) { - $scope.model.value.push($scope.selectedItems[x].key); - } - } - - }, true); - - //here we declare a special method which will be called whenever the value has changed from the server - //this is instead of doing a watch on the model.value = faster - $scope.model.onValueChanged = function (newVal, oldVal) { - //update the display val again if it has changed from the server - setupViewModel(); - }; - - }); - -function ColorPickerController($scope) { - $scope.toggleItem = function (color) { - if ($scope.model.value == color) { - $scope.model.value = ""; - //this is required to re-validate - $scope.propertyForm.modelValue.$setViewValue($scope.model.value); - } - else { - $scope.model.value = color; - //this is required to re-validate - $scope.propertyForm.modelValue.$setViewValue($scope.model.value); - } - }; - // Method required by the valPropertyValidator directive (returns true if the property editor has at least one color selected) - $scope.validateMandatory = function () { - return { - isValid: !$scope.model.validation.mandatory || ($scope.model.value != null && $scope.model.value != ""), - errorMsg: "Value cannot be empty", - errorKey: "required" - }; - } - $scope.isConfigured = $scope.model.config && $scope.model.config.items && _.keys($scope.model.config.items).length > 0; -} - -angular.module("umbraco").controller("Umbraco.PropertyEditors.ColorPickerController", ColorPickerController); - -angular.module("umbraco").controller("Umbraco.PrevalueEditors.MultiColorPickerController", - function ($scope, $timeout, assetsService, angularHelper, $element) { - //NOTE: We need to make each color an object, not just a string because you cannot 2-way bind to a primitive. - var defaultColor = "000000"; - - $scope.newColor = defaultColor; - $scope.hasError = false; - - assetsService.load([ - //"lib/spectrum/tinycolor.js", - "lib/spectrum/spectrum.js" - ], $scope).then(function () { - var elem = $element.find("input"); - elem.spectrum({ - color: null, - showInitial: false, - chooseText: "choose", // TODO: These can be localised - cancelText: "cancel", // TODO: These can be localised - preferredFormat: "hex", - showInput: true, - clickoutFiresChange: true, - hide: function (color) { - //show the add butotn - $element.find(".btn.add").show(); - }, - change: function (color) { - angularHelper.safeApply($scope, function () { - $scope.newColor = color.toHexString().trimStart("#"); // #ff0000 + angular.module('umbraco').controller('Umbraco.Editors.Packages.RepoController', PackagesRepoController); + }()); + (function () { + 'use strict'; + function PartialViewMacrosCreateController($scope, codefileResource, macroResource, $location, navigationService, formHelper, localizationService, appState) { + var vm = this; + var node = $scope.dialogOptions.currentNode; + var localizeCreateFolder = localizationService.localize('defaultdialog_createFolder'); + vm.snippets = []; + vm.createFolderError = ''; + vm.folderName = ''; + vm.fileName = ''; + vm.showSnippets = false; + vm.creatingFolder = false; + vm.showCreateFolder = showCreateFolder; + vm.createFolder = createFolder; + vm.createFile = createFile; + vm.createFileWithoutMacro = createFileWithoutMacro; + vm.showCreateFromSnippet = showCreateFromSnippet; + vm.createFileFromSnippet = createFileFromSnippet; + function onInit() { + codefileResource.getSnippets('partialViewMacros').then(function (snippets) { + vm.snippets = snippets; + }); + } + function showCreateFolder() { + vm.creatingFolder = true; + } + function createFolder(form) { + if (formHelper.submitForm({ + scope: $scope, + formCtrl: form, + statusMessage: localizeCreateFolder + })) { + codefileResource.createContainer('partialViewMacros', node.id, vm.folderName).then(function (saved) { + navigationService.hideMenu(); + navigationService.syncTree({ + tree: 'partialViewMacros', + path: saved.path, + forceReload: true, + activate: true + }); + formHelper.resetForm({ scope: $scope }); + var section = appState.getSectionState('currentSection'); + }, function (err) { + vm.createFolderError = err; + //show any notifications + formHelper.showNotifications(err.data); }); - }, - show: function() { - //hide the add butotn - $element.find(".btn.add").hide(); } + } + function createFile() { + $location.path('/developer/partialviewmacros/edit/' + node.id).search('create', 'true'); + navigationService.hideMenu(); + } + function createFileWithoutMacro() { + $location.path('/developer/partialviewmacros/edit/' + node.id).search('create', 'true').search('nomacro', 'true'); + navigationService.hideMenu(); + } + function createFileFromSnippet(snippet) { + $location.path('/developer/partialviewmacros/edit/' + node.id).search('create', 'true').search('snippet', snippet.fileName); + navigationService.hideMenu(); + } + function showCreateFromSnippet() { + vm.showSnippets = true; + } + onInit(); + } + angular.module('umbraco').controller('Umbraco.Editors.PartialViewMacros.CreateController', PartialViewMacrosCreateController); + }()); + /** + * @ngdoc controller + * @name Umbraco.Editors.PartialViewMacros.DeleteController + * @function + * + * @description + * The controller for deleting partial view macros + */ + function PartialViewMacrosDeleteController($scope, codefileResource, treeService, navigationService) { + $scope.performDelete = function () { + //mark it for deletion (used in the UI) + $scope.currentNode.loading = true; + codefileResource.deleteByPath('partialViewMacros', $scope.currentNode.id).then(function () { + $scope.currentNode.loading = false; + //get the root node before we remove it + var rootNode = treeService.getTreeRoot($scope.currentNode); + //TODO: Need to sync tree, etc... + treeService.removeNode($scope.currentNode); + navigationService.hideMenu(); }); + }; + $scope.cancel = function () { + navigationService.hideDialog(); + }; + } + angular.module('umbraco').controller('Umbraco.Editors.PartialViewMacros.DeleteController', PartialViewMacrosDeleteController); + (function () { + 'use strict'; + function partialViewMacrosEditController($scope, $routeParams, codefileResource, assetsService, notificationsService, editorState, navigationService, appState, macroService, angularHelper, $timeout, contentEditingHelper, localizationService, templateHelper, macroResource) { + var vm = this; + var localizeSaving = localizationService.localize('general_saving'); + vm.page = {}; + vm.page.loading = true; + vm.partialViewMacroFile = {}; + //menu + vm.page.menu = {}; + vm.page.menu.currentSection = appState.getSectionState('currentSection'); + vm.page.menu.currentNode = null; + // bind functions to view model + vm.save = save; + vm.openPageFieldOverlay = openPageFieldOverlay; + vm.openDictionaryItemOverlay = openDictionaryItemOverlay; + vm.openQueryBuilderOverlay = openQueryBuilderOverlay; + vm.openMacroOverlay = openMacroOverlay; + vm.openInsertOverlay = openInsertOverlay; + /* Functions bound to view model */ + function save() { + vm.page.saveButtonState = 'busy'; + vm.partialViewMacro.content = vm.editor.getValue(); + contentEditingHelper.contentEditorPerformSave({ + statusMessage: localizeSaving, + saveMethod: codefileResource.save, + scope: $scope, + content: vm.partialViewMacro, + // We do not redirect on failure for partial view macros - this is because it is not possible to actually save the partial view + // when server side validation fails - as opposed to content where we are capable of saving the content + // item if server side validation fails + redirectOnFailure: false, + rebindCallback: function (orignal, saved) { + } + }).then(function (saved) { + // create macro if needed + if ($routeParams.create && $routeParams.nomacro !== 'true') { + macroResource.createPartialViewMacroWithFile(saved.virtualPath, saved.name).then(function (created) { + completeSave(saved); + }, function (err) { + //show any notifications + formHelper.showNotifications(err.data); + }); + } else { + completeSave(saved); + } + }, function (err) { + vm.page.saveButtonState = 'error'; + localizationService.localize('speechBubbles_validationFailedHeader').then(function (headerValue) { + localizationService.localize('speechBubbles_validationFailedMessage').then(function (msgValue) { + notificationsService.error(headerValue, msgValue); + }); + }); + }); + } + function completeSave(saved) { + localizationService.localize('speechBubbles_partialViewSavedHeader').then(function (headerValue) { + localizationService.localize('speechBubbles_partialViewSavedText').then(function (msgValue) { + notificationsService.success(headerValue, msgValue); + }); + }); + //check if the name changed, if so we need to redirect + if (vm.partialViewMacro.id !== saved.id) { + contentEditingHelper.redirectToRenamedContent(saved.id); + } else { + vm.page.saveButtonState = 'success'; + vm.partialViewMacro = saved; + //sync state + editorState.set(vm.partialViewMacro); + // normal tree sync + navigationService.syncTree({ + tree: 'partialViewMacros', + path: vm.partialViewMacro.path, + forceReload: true + }).then(function (syncArgs) { + vm.page.menu.currentNode = syncArgs.node; + }); + // clear $dirty state on form + setFormState('pristine'); + } + } + function openInsertOverlay() { + vm.insertOverlay = { + view: 'insert', + allowedTypes: { + macro: true, + dictionary: true, + umbracoField: true + }, + hideSubmitButton: true, + show: true, + submit: function (model) { + switch (model.insert.type) { + case 'macro': + var macroObject = macroService.collectValueData(model.insert.selectedMacro, model.insert.macroParams, 'Mvc'); + insert(macroObject.syntax); + break; + case 'dictionary': + var code = templateHelper.getInsertDictionarySnippet(model.insert.node.name); + insert(code); + break; + case 'umbracoField': + insert(model.insert.umbracoField); + break; + } + vm.insertOverlay.show = false; + vm.insertOverlay = null; + }, + close: function (oldModel) { + // close the dialog + vm.insertOverlay.show = false; + vm.insertOverlay = null; + // focus editor + vm.editor.focus(); + } + }; + } + function openMacroOverlay() { + vm.macroPickerOverlay = { + view: 'macropicker', + dialogData: {}, + show: true, + title: 'Insert macro', + submit: function (model) { + var macroObject = macroService.collectValueData(model.selectedMacro, model.macroParams, 'Mvc'); + insert(macroObject.syntax); + vm.macroPickerOverlay.show = false; + vm.macroPickerOverlay = null; + }, + close: function (oldModel) { + // close the dialog + vm.macroPickerOverlay.show = false; + vm.macroPickerOverlay = null; + // focus editor + vm.editor.focus(); + } + }; + } + function openPageFieldOverlay() { + vm.pageFieldOverlay = { + submitButtonLabel: 'Insert', + closeButtonlabel: 'Cancel', + view: 'insertfield', + show: true, + submit: function (model) { + insert(model.umbracoField); + vm.pageFieldOverlay.show = false; + vm.pageFieldOverlay = null; + }, + close: function (model) { + // close the dialog + vm.pageFieldOverlay.show = false; + vm.pageFieldOverlay = null; + // focus editor + vm.editor.focus(); + } + }; + } + function openDictionaryItemOverlay() { + vm.dictionaryItemOverlay = { + view: 'treepicker', + section: 'settings', + treeAlias: 'dictionary', + entityType: 'dictionary', + multiPicker: false, + show: true, + title: 'Insert dictionary item', + emptyStateMessage: localizationService.localize('emptyStates_emptyDictionaryTree'), + select: function (node) { + var code = templateHelper.getInsertDictionarySnippet(node.name); + insert(code); + vm.dictionaryItemOverlay.show = false; + vm.dictionaryItemOverlay = null; + }, + close: function (model) { + // close dialog + vm.dictionaryItemOverlay.show = false; + vm.dictionaryItemOverlay = null; + // focus editor + vm.editor.focus(); + } + }; + } + function openQueryBuilderOverlay() { + vm.queryBuilderOverlay = { + view: 'querybuilder', + show: true, + title: 'Query for content', + submit: function (model) { + var code = templateHelper.getQuerySnippet(model.result.queryExpression); + insert(code); + vm.queryBuilderOverlay.show = false; + vm.queryBuilderOverlay = null; + }, + close: function (model) { + // close dialog + vm.queryBuilderOverlay.show = false; + vm.queryBuilderOverlay = null; + // focus editor + vm.editor.focus(); + } + }; + } + /* Local functions */ + function init() { + //we need to load this somewhere, for now its here. + assetsService.loadCss('lib/ace-razor-mode/theme/razor_chrome.css', $scope); + if ($routeParams.create) { + var snippet = 'Empty'; + if ($routeParams.snippet) { + snippet = $routeParams.snippet; + } + codefileResource.getScaffold('partialViewMacros', $routeParams.id, snippet).then(function (partialViewMacro) { + if ($routeParams.name) { + partialViewMacro.name = $routeParams.name; + } + ready(partialViewMacro, false); + }); + } else { + codefileResource.getByPath('partialViewMacros', $routeParams.id).then(function (partialViewMacro) { + ready(partialViewMacro, true); + }); + } + } + function ready(partialViewMacro, syncTree) { + vm.page.loading = false; + vm.partialViewMacro = partialViewMacro; + //sync state + editorState.set(vm.partialViewMacro); + if (syncTree) { + navigationService.syncTree({ + tree: 'partialViewMacros', + path: vm.partialViewMacro.path, + forceReload: true + }).then(function (syncArgs) { + vm.page.menu.currentNode = syncArgs.node; + }); + } + // ace configuration + vm.aceOption = { + mode: 'razor', + theme: 'chrome', + showPrintMargin: false, + advanced: { fontSize: '14px' }, + onLoad: function (_editor) { + vm.editor = _editor; + // initial cursor placement + // Keep cursor in name field if we are create a new template + // else set the cursor at the bottom of the code editor + if (!$routeParams.create) { + $timeout(function () { + vm.editor.navigateFileEnd(); + vm.editor.focus(); + persistCurrentLocation(); + }); + } + //change on blur, focus + vm.editor.on('blur', persistCurrentLocation); + vm.editor.on('focus', persistCurrentLocation); + vm.editor.on('change', changeAceEditor); + } + }; + } + function insert(str) { + vm.editor.focus(); + vm.editor.moveCursorToPosition(vm.currentPosition); + vm.editor.insert(str); + // set form state to $dirty + setFormState('dirty'); + } + function persistCurrentLocation() { + vm.currentPosition = vm.editor.getCursorPosition(); + } + function changeAceEditor() { + setFormState('dirty'); + } + function setFormState(state) { + // get the current form + var currentForm = angularHelper.getCurrentForm($scope); + // set state + if (state === 'dirty') { + currentForm.$setDirty(); + } else if (state === 'pristine') { + currentForm.$setPristine(); + } + } + init(); + } + angular.module('umbraco').controller('Umbraco.Editors.PartialViewMacros.EditController', partialViewMacrosEditController); + }()); + (function () { + 'use strict'; + function PartialViewsCreateController($scope, codefileResource, $location, navigationService, formHelper, localizationService, appState) { + var vm = this; + var node = $scope.dialogOptions.currentNode; + var localizeCreateFolder = localizationService.localize('defaultdialog_createFolder'); + vm.snippets = []; + vm.showSnippets = false; + vm.creatingFolder = false; + vm.createFolderError = ''; + vm.folderName = ''; + vm.createPartialView = createPartialView; + vm.showCreateFolder = showCreateFolder; + vm.createFolder = createFolder; + vm.showCreateFromSnippet = showCreateFromSnippet; + function onInit() { + codefileResource.getSnippets('partialViews').then(function (snippets) { + vm.snippets = snippets; + }); + } + function createPartialView(selectedSnippet) { + var snippet = null; + if (selectedSnippet && selectedSnippet.fileName) { + snippet = selectedSnippet.fileName; + } + $location.path('/settings/partialviews/edit/' + node.id).search('create', 'true').search('snippet', snippet); + navigationService.hideMenu(); + } + function showCreateFolder() { + vm.creatingFolder = true; + } + function createFolder(form) { + if (formHelper.submitForm({ + scope: $scope, + formCtrl: form, + statusMessage: localizeCreateFolder + })) { + codefileResource.createContainer('partialViews', node.id, vm.folderName).then(function (saved) { + navigationService.hideMenu(); + navigationService.syncTree({ + tree: 'partialViews', + path: saved.path, + forceReload: true, + activate: true + }); + formHelper.resetForm({ scope: $scope }); + var section = appState.getSectionState('currentSection'); + }, function (err) { + vm.createFolderError = err; + formHelper.showNotifications(err.data); + }); + } + } + function showCreateFromSnippet() { + vm.showSnippets = true; + } + onInit(); + } + angular.module('umbraco').controller('Umbraco.Editors.PartialViews.CreateController', PartialViewsCreateController); + }()); + /** + * @ngdoc controller + * @name Umbraco.Editors.PartialViews.DeleteController + * @function + * + * @description + * The controller for deleting partial views + */ + function PartialViewsDeleteController($scope, codefileResource, treeService, navigationService) { + $scope.performDelete = function () { + //mark it for deletion (used in the UI) + $scope.currentNode.loading = true; + codefileResource.deleteByPath('partialViews', $scope.currentNode.id).then(function () { + $scope.currentNode.loading = false; + //get the root node before we remove it + var rootNode = treeService.getTreeRoot($scope.currentNode); + //TODO: Need to sync tree, etc... + treeService.removeNode($scope.currentNode); + navigationService.hideMenu(); + }); + }; + $scope.cancel = function () { + navigationService.hideDialog(); + }; + } + angular.module('umbraco').controller('Umbraco.Editors.PartialViews.DeleteController', PartialViewsDeleteController); + (function () { + 'use strict'; + function PartialViewsEditController($scope, $routeParams, codefileResource, assetsService, notificationsService, editorState, navigationService, appState, macroService, angularHelper, $timeout, contentEditingHelper, localizationService, templateHelper) { + var vm = this; + var localizeSaving = localizationService.localize('general_saving'); + vm.page = {}; + vm.page.loading = true; + vm.partialView = {}; + //menu + vm.page.menu = {}; + vm.page.menu.currentSection = appState.getSectionState('currentSection'); + vm.page.menu.currentNode = null; + //Used to toggle the keyboard shortcut modal + //From a custom keybinding in ace editor - that conflicts with our own to show the dialog + vm.showKeyboardShortcut = false; + //Keyboard shortcuts for help dialog + vm.page.keyboardShortcutsOverview = []; + vm.page.keyboardShortcutsOverview.push(templateHelper.getGeneralShortcuts()); + vm.page.keyboardShortcutsOverview.push(templateHelper.getEditorShortcuts()); + vm.page.keyboardShortcutsOverview.push(templateHelper.getPartialViewEditorShortcuts()); + // bind functions to view model + vm.save = save; + vm.openPageFieldOverlay = openPageFieldOverlay; + vm.openDictionaryItemOverlay = openDictionaryItemOverlay; + vm.openQueryBuilderOverlay = openQueryBuilderOverlay; + vm.openMacroOverlay = openMacroOverlay; + vm.openInsertOverlay = openInsertOverlay; + /* Functions bound to view model */ + function save() { + vm.page.saveButtonState = 'busy'; + vm.partialView.content = vm.editor.getValue(); + contentEditingHelper.contentEditorPerformSave({ + statusMessage: localizeSaving, + saveMethod: codefileResource.save, + scope: $scope, + content: vm.partialView, + //We do not redirect on failure for partialviews - this is because it is not possible to actually save the partialviews + // type when server side validation fails - as opposed to content where we are capable of saving the content + // item if server side validation fails + redirectOnFailure: false, + rebindCallback: function (orignal, saved) { + } + }).then(function (saved) { + localizationService.localize('speechBubbles_partialViewSavedHeader').then(function (headerValue) { + localizationService.localize('speechBubbles_partialViewSavedText').then(function (msgValue) { + notificationsService.success(headerValue, msgValue); + }); + }); + //check if the name changed, if so we need to redirect + if (vm.partialView.id !== saved.id) { + contentEditingHelper.redirectToRenamedContent(saved.id); + } else { + vm.page.saveButtonState = 'success'; + vm.partialView = saved; + //sync state + editorState.set(vm.partialView); + // normal tree sync + navigationService.syncTree({ + tree: 'partialViews', + path: vm.partialView.path, + forceReload: true + }).then(function (syncArgs) { + vm.page.menu.currentNode = syncArgs.node; + }); + // clear $dirty state on form + setFormState('pristine'); + } + }, function (err) { + vm.page.saveButtonState = 'error'; + localizationService.localize('speechBubbles_validationFailedHeader').then(function (headerValue) { + localizationService.localize('speechBubbles_validationFailedMessage').then(function (msgValue) { + notificationsService.error(headerValue, msgValue); + }); + }); + }); + } + function openInsertOverlay() { + vm.insertOverlay = { + view: 'insert', + allowedTypes: { + macro: true, + dictionary: true, + umbracoField: true + }, + hideSubmitButton: true, + show: true, + submit: function (model) { + switch (model.insert.type) { + case 'macro': + var macroObject = macroService.collectValueData(model.insert.selectedMacro, model.insert.macroParams, 'Mvc'); + insert(macroObject.syntax); + break; + case 'dictionary': + var code = templateHelper.getInsertDictionarySnippet(model.insert.node.name); + insert(code); + break; + case 'umbracoField': + insert(model.insert.umbracoField); + break; + } + vm.insertOverlay.show = false; + vm.insertOverlay = null; + }, + close: function (oldModel) { + // close the dialog + vm.insertOverlay.show = false; + vm.insertOverlay = null; + // focus editor + vm.editor.focus(); + } + }; + } + function openMacroOverlay() { + vm.macroPickerOverlay = { + view: 'macropicker', + dialogData: {}, + show: true, + title: 'Insert macro', + submit: function (model) { + var macroObject = macroService.collectValueData(model.selectedMacro, model.macroParams, 'Mvc'); + insert(macroObject.syntax); + vm.macroPickerOverlay.show = false; + vm.macroPickerOverlay = null; + }, + close: function (oldModel) { + // close the dialog + vm.macroPickerOverlay.show = false; + vm.macroPickerOverlay = null; + // focus editor + vm.editor.focus(); + } + }; + } + function openPageFieldOverlay() { + vm.pageFieldOverlay = { + submitButtonLabel: 'Insert', + closeButtonlabel: 'Cancel', + view: 'insertfield', + show: true, + submit: function (model) { + insert(model.umbracoField); + vm.pageFieldOverlay.show = false; + vm.pageFieldOverlay = null; + }, + close: function (model) { + // close the dialog + vm.pageFieldOverlay.show = false; + vm.pageFieldOverlay = null; + // focus editor + vm.editor.focus(); + } + }; + } + function openDictionaryItemOverlay() { + vm.dictionaryItemOverlay = { + view: 'treepicker', + section: 'settings', + treeAlias: 'dictionary', + entityType: 'dictionary', + multiPicker: false, + show: true, + title: 'Insert dictionary item', + emptyStateMessage: localizationService.localize('emptyStates_emptyDictionaryTree'), + select: function (node) { + var code = templateHelper.getInsertDictionarySnippet(node.name); + insert(code); + vm.dictionaryItemOverlay.show = false; + vm.dictionaryItemOverlay = null; + }, + close: function (model) { + // close dialog + vm.dictionaryItemOverlay.show = false; + vm.dictionaryItemOverlay = null; + // focus editor + vm.editor.focus(); + } + }; + } + function openQueryBuilderOverlay() { + vm.queryBuilderOverlay = { + view: 'querybuilder', + show: true, + title: 'Query for content', + submit: function (model) { + var code = templateHelper.getQuerySnippet(model.result.queryExpression); + insert(code); + vm.queryBuilderOverlay.show = false; + vm.queryBuilderOverlay = null; + }, + close: function (model) { + // close dialog + vm.queryBuilderOverlay.show = false; + vm.queryBuilderOverlay = null; + // focus editor + vm.editor.focus(); + } + }; + } + /* Local functions */ + function init() { + //we need to load this somewhere, for now its here. + assetsService.loadCss('lib/ace-razor-mode/theme/razor_chrome.css', $scope); + if ($routeParams.create) { + var snippet = 'Empty'; + if ($routeParams.snippet) { + snippet = $routeParams.snippet; + } + codefileResource.getScaffold('partialViews', $routeParams.id, snippet).then(function (partialView) { + ready(partialView, false); + }); + } else { + codefileResource.getByPath('partialViews', $routeParams.id).then(function (partialView) { + ready(partialView, true); + }); + } + } + function ready(partialView, syncTree) { + vm.page.loading = false; + vm.partialView = partialView; + //sync state + editorState.set(vm.partialView); + if (syncTree) { + navigationService.syncTree({ + tree: 'partialViews', + path: vm.partialView.path, + forceReload: true + }).then(function (syncArgs) { + vm.page.menu.currentNode = syncArgs.node; + }); + } + // ace configuration + vm.aceOption = { + mode: 'razor', + theme: 'chrome', + showPrintMargin: false, + advanced: { fontSize: '14px' }, + onLoad: function (_editor) { + vm.editor = _editor; + //Update the auto-complete method to use ctrl+alt+space + _editor.commands.bindKey('ctrl-alt-space', 'startAutocomplete'); + //Unassigns the keybinding (That was previously auto-complete) + //As conflicts with our own tree search shortcut + _editor.commands.bindKey('ctrl-space', null); + // Assign new keybinding + _editor.commands.addCommands([ + //Disable (alt+shift+K) + //Conflicts with our own show shortcuts dialog - this overrides it + { + name: 'unSelectOrFindPrevious', + bindKey: 'Alt-Shift-K', + exec: function () { + //Toggle the show keyboard shortcuts overlay + $scope.$apply(function () { + vm.showKeyboardShortcut = !vm.showKeyboardShortcut; + }); + }, + readOnly: true + }, + { + name: 'insertUmbracoValue', + bindKey: 'Alt-Shift-V', + exec: function () { + $scope.$apply(function () { + openPageFieldOverlay(); + }); + }, + readOnly: true + }, + { + name: 'insertDictionary', + bindKey: 'Alt-Shift-D', + exec: function () { + $scope.$apply(function () { + openDictionaryItemOverlay(); + }); + }, + readOnly: true + }, + { + name: 'insertUmbracoMacro', + bindKey: 'Alt-Shift-M', + exec: function () { + $scope.$apply(function () { + openMacroOverlay(); + }); + }, + readOnly: true + }, + { + name: 'insertQuery', + bindKey: 'Alt-Shift-Q', + exec: function () { + $scope.$apply(function () { + openQueryBuilderOverlay(); + }); + }, + readOnly: true + } + ]); + // initial cursor placement + // Keep cursor in name field if we are create a new template + // else set the cursor at the bottom of the code editor + if (!$routeParams.create) { + $timeout(function () { + vm.editor.navigateFileEnd(); + vm.editor.focus(); + persistCurrentLocation(); + }); + } + //change on blur, focus + vm.editor.on('blur', persistCurrentLocation); + vm.editor.on('focus', persistCurrentLocation); + vm.editor.on('change', changeAceEditor); + } + }; + } + function insert(str) { + vm.editor.focus(); + vm.editor.moveCursorToPosition(vm.currentPosition); + vm.editor.insert(str); + // set form state to $dirty + setFormState('dirty'); + } + function persistCurrentLocation() { + vm.currentPosition = vm.editor.getCursorPosition(); + } + function changeAceEditor() { + setFormState('dirty'); + } + function setFormState(state) { + // get the current form + var currentForm = angularHelper.getCurrentForm($scope); + // set state + if (state === 'dirty') { + currentForm.$setDirty(); + } else if (state === 'pristine') { + currentForm.$setPristine(); + } + } + init(); + } + angular.module('umbraco').controller('Umbraco.Editors.PartialViews.EditController', PartialViewsEditController); + }()); + angular.module('umbraco').controller('Umbraco.PrevalueEditors.BooleanController', function ($scope) { + function updateToggleValue() { + $scope.toggleValue = false; + if ($scope.model && $scope.model.value && ($scope.model.value.toString() === '1' || angular.lowercase($scope.model.value) === 'true')) { + $scope.toggleValue = true; + } + } + if ($scope.model.value === null) { + $scope.model.value = '0'; + } + updateToggleValue(); + $scope.toggle = function () { + if ($scope.model.value === 1 || $scope.model.value === '1') { + $scope.model.value = '0'; + updateToggleValue(); + return; + } + $scope.model.value = '1'; + updateToggleValue(); + }; + }); + function imageFilePickerController($scope) { + $scope.add = function () { + $scope.mediaPickerOverlay = { + view: 'mediapicker', + disableFolderSelect: true, + onlyImages: true, + show: true, + submit: function (model) { + $scope.model.value = model.selectedImages[0].image; + $scope.mediaPickerOverlay.show = false; + $scope.mediaPickerOverlay = null; + }, + close: function () { + $scope.mediaPickerOverlay.show = false; + $scope.mediaPickerOverlay = null; + } + }; + }; + $scope.remove = function () { + $scope.model.value = null; + }; + } + angular.module('umbraco').controller('Umbraco.PrevalueEditors.ImageFilePickerController', imageFilePickerController); + //this controller simply tells the dialogs service to open a mediaPicker window + //with a specified callback, this callback will receive an object with a selection on it + function mediaPickerController($scope, dialogService, entityResource, $log, iconHelper) { + function trim(str, chr) { + var rgxtrim = !chr ? new RegExp('^\\s+|\\s+$', 'g') : new RegExp('^' + chr + '+|' + chr + '+$', 'g'); + return str.replace(rgxtrim, ''); + } + $scope.renderModel = []; + $scope.allowRemove = true; + $scope.allowEdit = true; + $scope.sortable = false; + var dialogOptions = { + multiPicker: false, + entityType: 'Media', + section: 'media', + treeAlias: 'media', + idType: 'int' + }; + //combine the dialogOptions with any values returned from the server + if ($scope.model.config) { + angular.extend(dialogOptions, $scope.model.config); + } + $scope.openContentPicker = function () { + $scope.contentPickerOverlay = dialogOptions; + $scope.contentPickerOverlay.view = 'treePicker'; + $scope.contentPickerOverlay.show = true; + $scope.contentPickerOverlay.submit = function (model) { + if ($scope.contentPickerOverlay.multiPicker) { + _.each(model.selection, function (item, i) { + $scope.add(item); + }); + } else { + $scope.clear(); + $scope.add(model.selection[0]); + } + $scope.contentPickerOverlay.show = false; + $scope.contentPickerOverlay = null; + }; + $scope.contentPickerOverlay.close = function (oldModel) { + $scope.contentPickerOverlay.show = false; + $scope.contentPickerOverlay = null; + }; + }; + $scope.remove = function (index) { + $scope.renderModel.splice(index, 1); + }; + $scope.clear = function () { + $scope.renderModel = []; + }; + $scope.add = function (item) { + var itemId = dialogOptions.idType === 'udi' ? item.udi : item.id; + var currIds = _.map($scope.renderModel, function (i) { + return dialogOptions.idType === 'udi' ? i.udi : i.id; + }); + if (currIds.indexOf(itemId) < 0) { + item.icon = iconHelper.convertFromLegacyIcon(item.icon); + $scope.renderModel.push({ + name: item.name, + id: item.id, + icon: item.icon, + udi: item.udi + }); + // store the index of the new item in the renderModel collection so we can find it again + var itemRenderIndex = $scope.renderModel.length - 1; + // get and update the path for the picked node + entityResource.getUrl(item.id, dialogOptions.entityType).then(function (data) { + $scope.renderModel[itemRenderIndex].path = data; + }); + } + }; + var unsubscribe = $scope.$on('formSubmitting', function (ev, args) { + var currIds = _.map($scope.renderModel, function (i) { + return dialogOptions.idType === 'udi' ? i.udi : i.id; + }); + $scope.model.value = trim(currIds.join(), ','); }); - + //when the scope is destroyed we need to unsubscribe + $scope.$on('$destroy', function () { + unsubscribe(); + }); + //load media data + var modelIds = $scope.model.value ? $scope.model.value.split(',') : []; + if (modelIds.length > 0) { + entityResource.getByIds(modelIds, dialogOptions.entityType).then(function (data) { + _.each(data, function (item, i) { + item.icon = iconHelper.convertFromLegacyIcon(item.icon); + $scope.renderModel.push({ + name: item.name, + id: item.id, + icon: item.icon, + udi: item.udi + }); + // store the index of the new item in the renderModel collection so we can find it again + var itemRenderIndex = $scope.renderModel.length - 1; + // get and update the path for the picked node + entityResource.getUrl(item.id, dialogOptions.entityType).then(function (data) { + $scope.renderModel[itemRenderIndex].path = data; + }); + }); + }); + } + } + angular.module('umbraco').controller('Umbraco.PrevalueEditors.MediaPickerController', mediaPickerController); + angular.module('umbraco').controller('Umbraco.PrevalueEditors.MultiValuesController', function ($scope, $timeout) { + //NOTE: We need to make each item an object, not just a string because you cannot 2-way bind to a primitive. + $scope.newItem = ''; + $scope.hasError = false; + $scope.focusOnNew = false; if (!angular.isArray($scope.model.value)) { //make an array from the dictionary var items = []; for (var i in $scope.model.value) { items.push({ - value: $scope.model.value[i], + value: $scope.model.value[i].value, + sortOrder: $scope.model.value[i].sortOrder, id: i }); } + //ensure the items are sorted by the provided sort order + items.sort(function (a, b) { + return a.sortOrder > b.sortOrder ? 1 : b.sortOrder > a.sortOrder ? -1 : 0; + }); //now make the editor model the array $scope.model.value = items; } - $scope.remove = function (item, evt) { - evt.preventDefault(); - $scope.model.value = _.reject($scope.model.value, function (x) { return x.value === item.value; }); - }; - $scope.add = function (evt) { - evt.preventDefault(); - + if ($scope.newItem) { + if (!_.contains($scope.model.value, $scope.newItem)) { + $scope.model.value.push({ value: $scope.newItem }); + $scope.newItem = ''; + $scope.hasError = false; + $scope.focusOnNew = true; + return; + } + } + //there was an error, do the highlight (will be set back by the directive) + $scope.hasError = true; + }; + $scope.sortableOptions = { + axis: 'y', + containment: 'parent', + cursor: 'move', + items: '> div.control-group', + tolerance: 'pointer', + update: function (e, ui) { + // Get the new and old index for the moved element (using the text as the identifier, so + // we'd have a problem if two prevalues were the same, but that would be unlikely) + var newIndex = ui.item.index(); + var movedPrevalueText = $('input[type="text"]', ui.item).val(); + var originalIndex = getElementIndexByPrevalueText(movedPrevalueText); + // Move the element in the model + if (originalIndex > -1) { + var movedElement = $scope.model.value[originalIndex]; + $scope.model.value.splice(originalIndex, 1); + $scope.model.value.splice(newIndex, 0, movedElement); + } + } + }; + $scope.createNew = function (event) { + if (event.keyCode == 13) { + $scope.add(event); + } + }; + function getElementIndexByPrevalueText(value) { + for (var i = 0; i < $scope.model.value.length; i++) { + if ($scope.model.value[i].value === value) { + return i; + } + } + return -1; + } + }); + //this controller simply tells the dialogs service to open a mediaPicker window + //with a specified callback, this callback will receive an object with a selection on it + angular.module('umbraco').controller('Umbraco.PrevalueEditors.TreePickerController', function ($scope, dialogService, entityResource, $log, iconHelper) { + $scope.renderModel = []; + $scope.ids = []; + $scope.allowRemove = true; + $scope.allowEdit = true; + $scope.sortable = false; + var config = { + multiPicker: false, + entityType: 'Document', + type: 'content', + treeAlias: 'content', + idType: 'int' + }; + //combine the config with any values returned from the server + if ($scope.model.config) { + angular.extend(config, $scope.model.config); + } + if ($scope.model.value) { + $scope.ids = $scope.model.value.split(','); + entityResource.getByIds($scope.ids, config.entityType).then(function (data) { + _.each(data, function (item, i) { + item.icon = iconHelper.convertFromLegacyIcon(item.icon); + $scope.renderModel.push({ + name: item.name, + id: item.id, + icon: item.icon, + udi: item.udi + }); + // store the index of the new item in the renderModel collection so we can find it again + var itemRenderIndex = $scope.renderModel.length - 1; + // get and update the path for the picked node + entityResource.getUrl(item.id, config.entityType).then(function (data) { + $scope.renderModel[itemRenderIndex].path = data; + }); + }); + }); + } + $scope.openContentPicker = function () { + $scope.treePickerOverlay = config; + $scope.treePickerOverlay.section = config.type; + $scope.treePickerOverlay.view = 'treePicker'; + $scope.treePickerOverlay.show = true; + $scope.treePickerOverlay.submit = function (model) { + if (config.multiPicker) { + populate(model.selection); + } else { + populate(model.selection[0]); + } + $scope.treePickerOverlay.show = false; + $scope.treePickerOverlay = null; + }; + $scope.treePickerOverlay.close = function (oldModel) { + $scope.treePickerOverlay.show = false; + $scope.treePickerOverlay = null; + }; + }; + $scope.remove = function (index) { + $scope.renderModel.splice(index, 1); + $scope.ids.splice(index, 1); + $scope.model.value = trim($scope.ids.join(), ','); + }; + $scope.clear = function () { + $scope.model.value = ''; + $scope.renderModel = []; + $scope.ids = []; + }; + $scope.add = function (item) { + var itemId = config.idType === 'udi' ? item.udi : item.id; + if ($scope.ids.indexOf(itemId) < 0) { + item.icon = iconHelper.convertFromLegacyIcon(item.icon); + $scope.ids.push(itemId); + $scope.renderModel.push({ + name: item.name, + id: item.id, + icon: item.icon, + udi: item.udi + }); + $scope.model.value = trim($scope.ids.join(), ','); + // store the index of the new item in the renderModel collection so we can find it again + var itemRenderIndex = $scope.renderModel.length - 1; + // get and update the path for the picked node + entityResource.getUrl(item.id, config.entityType).then(function (data) { + $scope.renderModel[itemRenderIndex].path = data; + }); + } + }; + var unsubscribe = $scope.$on('formSubmitting', function (ev, args) { + $scope.model.value = trim($scope.ids.join(), ','); + }); + //when the scope is destroyed we need to unsubscribe + $scope.$on('$destroy', function () { + unsubscribe(); + }); + function trim(str, chr) { + var rgxtrim = !chr ? new RegExp('^\\s+|\\s+$', 'g') : new RegExp('^' + chr + '+|' + chr + '+$', 'g'); + return str.replace(rgxtrim, ''); + } + function populate(data) { + if (angular.isArray(data)) { + _.each(data, function (item, i) { + $scope.add(item); + }); + } else { + $scope.clear(); + $scope.add(data); + } + } + }); + //this controller simply tells the dialogs service to open a mediaPicker window + //with a specified callback, this callback will receive an object with a selection on it + angular.module('umbraco').controller('Umbraco.PrevalueEditors.TreeSourceController', function ($scope, dialogService, entityResource, $log, iconHelper) { + if (!$scope.model) { + $scope.model = {}; + } + if (!$scope.model.value) { + $scope.model.value = { type: 'content' }; + } + if (!$scope.model.config) { + $scope.model.config = { idType: 'int' }; + } + if ($scope.model.value.id && $scope.model.value.type !== 'member') { + var ent = 'Document'; + if ($scope.model.value.type === 'media') { + ent = 'Media'; + } + entityResource.getById($scope.model.value.id, ent).then(function (item) { + item.icon = iconHelper.convertFromLegacyIcon(item.icon); + $scope.node = item; + }); + } + $scope.openContentPicker = function () { + $scope.treePickerOverlay = { + view: 'treepicker', + idType: $scope.model.config.idType, + section: $scope.model.value.type, + treeAlias: $scope.model.value.type, + multiPicker: false, + show: true, + submit: function (model) { + var item = model.selection[0]; + populate(item); + $scope.treePickerOverlay.show = false; + $scope.treePickerOverlay = null; + } + }; + }; + $scope.clear = function () { + $scope.model.value.id = undefined; + $scope.node = undefined; + $scope.model.value.query = undefined; + }; + //we always need to ensure we dont submit anything broken + var unsubscribe = $scope.$on('formSubmitting', function (ev, args) { + if ($scope.model.value.type === 'member') { + $scope.model.value.id = -1; + $scope.model.value.query = ''; + } + }); + //when the scope is destroyed we need to unsubscribe + $scope.$on('$destroy', function () { + unsubscribe(); + }); + function populate(item) { + $scope.clear(); + item.icon = iconHelper.convertFromLegacyIcon(item.icon); + $scope.node = item; + $scope.model.value.id = $scope.model.config.idType === 'udi' ? item.udi : item.id; + } + }); + function booleanEditorController($scope, $rootScope, assetsService) { + function setupViewModel() { + $scope.renderModel = { value: false }; + if ($scope.model.config && $scope.model.config.default && $scope.model.config.default.toString() === '1' && $scope.model && !$scope.model.value) { + $scope.renderModel.value = true; + } + if ($scope.model && $scope.model.value && ($scope.model.value.toString() === '1' || angular.lowercase($scope.model.value) === 'true')) { + $scope.renderModel.value = true; + } + } + setupViewModel(); + if ($scope.model && !$scope.model.value) { + $scope.model.value = $scope.renderModel.value === true ? '1' : '0'; + } + //here we declare a special method which will be called whenever the value has changed from the server + //this is instead of doing a watch on the model.value = faster + $scope.model.onValueChanged = function (newVal, oldVal) { + //update the display val again if it has changed from the server + setupViewModel(); + }; + // Update the value when the toggle is clicked + $scope.toggle = function () { + if ($scope.renderModel.value) { + $scope.model.value = '0'; + setupViewModel(); + return; + } + $scope.model.value = '1'; + setupViewModel(); + }; + } + angular.module('umbraco').controller('Umbraco.PropertyEditors.BooleanController', booleanEditorController); + angular.module('umbraco').controller('Umbraco.PropertyEditors.ChangePasswordController', function ($scope, $routeParams) { + $scope.isNew = $routeParams.create; + function resetModel() { + //the model config will contain an object, if it does not we'll create defaults + //NOTE: We will not support doing the password regex on the client side because the regex on the server side + //based on the membership provider cannot always be ported to js from .net directly. + /* + { + hasPassword: true/false, + requiresQuestionAnswer: true/false, + enableReset: true/false, + enablePasswordRetrieval: true/false, + minPasswordLength: 10 + } + */ + //set defaults if they are not available + if (!$scope.model.config || $scope.model.config.disableToggle === undefined) { + $scope.model.config.disableToggle = false; + } + if (!$scope.model.config || $scope.model.config.hasPassword === undefined) { + $scope.model.config.hasPassword = false; + } + if (!$scope.model.config || $scope.model.config.enablePasswordRetrieval === undefined) { + $scope.model.config.enablePasswordRetrieval = true; + } + if (!$scope.model.config || $scope.model.config.requiresQuestionAnswer === undefined) { + $scope.model.config.requiresQuestionAnswer = false; + } + if (!$scope.model.config || $scope.model.config.enableReset === undefined) { + $scope.model.config.enableReset = true; + } + if (!$scope.model.config || $scope.model.config.minPasswordLength === undefined) { + $scope.model.config.minPasswordLength = 0; + } + //set the model defaults + if (!angular.isObject($scope.model.value)) { + //if it's not an object then just create a new one + $scope.model.value = { + newPassword: null, + oldPassword: null, + reset: null, + answer: null + }; + } else { + //just reset the values + if (!$scope.isNew) { + //if it is new, then leave the generated pass displayed + $scope.model.value.newPassword = null; + $scope.model.value.oldPassword = null; + } + $scope.model.value.reset = null; + $scope.model.value.answer = null; + } + } + resetModel(); + }); + angular.module('umbraco').controller('Umbraco.PropertyEditors.CheckboxListController', function ($scope) { + if (angular.isObject($scope.model.config.items)) { + //now we need to format the items in the dictionary because we always want to have an array + var newItems = []; + var vals = _.values($scope.model.config.items); + var keys = _.keys($scope.model.config.items); + for (var i = 0; i < vals.length; i++) { + newItems.push({ + id: keys[i], + sortOrder: vals[i].sortOrder, + value: vals[i].value + }); + } + //ensure the items are sorted by the provided sort order + newItems.sort(function (a, b) { + return a.sortOrder > b.sortOrder ? 1 : b.sortOrder > a.sortOrder ? -1 : 0; + }); + //re-assign + $scope.model.config.items = newItems; + } + function setupViewModel() { + $scope.selectedItems = []; + //now we need to check if the value is null/undefined, if it is we need to set it to "" so that any value that is set + // to "" gets selected by default + if ($scope.model.value === null || $scope.model.value === undefined) { + $scope.model.value = []; + } + for (var i = 0; i < $scope.model.config.items.length; i++) { + var isChecked = _.contains($scope.model.value, $scope.model.config.items[i].id); + $scope.selectedItems.push({ + checked: isChecked, + key: $scope.model.config.items[i].id, + val: $scope.model.config.items[i].value + }); + } + } + setupViewModel(); + //update the model when the items checked changes + $scope.$watch('selectedItems', function (newVal, oldVal) { + $scope.model.value = []; + for (var x = 0; x < $scope.selectedItems.length; x++) { + if ($scope.selectedItems[x].checked) { + $scope.model.value.push($scope.selectedItems[x].key); + } + } + }, true); + //here we declare a special method which will be called whenever the value has changed from the server + //this is instead of doing a watch on the model.value = faster + $scope.model.onValueChanged = function (newVal, oldVal) { + //update the display val again if it has changed from the server + setupViewModel(); + }; + }); + function ColorPickerController($scope) { + //setup the default config + var config = { + items: [], + multiple: false + }; + //map the user config + angular.extend(config, $scope.model.config); + //map back to the model + $scope.model.config = config; + function convertArrayToDictionaryArray(model) { + //now we need to format the items in the dictionary because we always want to have an array + var newItems = []; + for (var i = 0; i < model.length; i++) { + newItems.push({ + id: model[i], + sortOrder: 0, + value: model[i] + }); + } + return newItems; + } + function convertObjectToDictionaryArray(model) { + //now we need to format the items in the dictionary because we always want to have an array + var newItems = []; + var vals = _.values($scope.model.config.items); + var keys = _.keys($scope.model.config.items); + for (var i = 0; i < vals.length; i++) { + var label = vals[i].value ? vals[i].value : vals[i]; + newItems.push({ + id: keys[i], + sortOrder: vals[i].sortOrder, + value: label + }); + } + return newItems; + } + $scope.isConfigured = $scope.model.config && $scope.model.config.items && _.keys($scope.model.config.items).length > 0; + if ($scope.isConfigured) { + for (var key in $scope.model.config.items) { + if (!$scope.model.config.items[key].hasOwnProperty('value')) + $scope.model.config.items[key] = { + value: $scope.model.config.items[key], + label: $scope.model.config.items[key] + }; + } + $scope.model.useLabel = isTrue($scope.model.config.useLabel); + initActiveColor(); + } + if (!angular.isArray($scope.model.config.items)) { + //make an array from the dictionary + var items = []; + for (var i in $scope.model.config.items) { + var oldValue = $scope.model.config.items[i]; + if (oldValue.hasOwnProperty('value')) { + items.push({ + value: oldValue.value, + label: oldValue.label, + sortOrder: oldValue.sortOrder, + id: i + }); + } else { + items.push({ + value: oldValue, + label: oldValue, + sortOrder: sortOrder, + id: i + }); + } + } + //ensure the items are sorted by the provided sort order + items.sort(function (a, b) { + return a.sortOrder > b.sortOrder ? 1 : b.sortOrder > a.sortOrder ? -1 : 0; + }); + //now make the editor model the array + $scope.model.config.items = items; + } + $scope.toggleItem = function (color) { + var currentColor = $scope.model.value && $scope.model.value.hasOwnProperty('value') ? $scope.model.value.value : $scope.model.value; + var newColor; + if (currentColor === color.value) { + // deselect + $scope.model.value = $scope.model.useLabel ? { + value: '', + label: '' + } : ''; + newColor = ''; + } else { + // select + $scope.model.value = $scope.model.useLabel ? { + value: color.value, + label: color.label + } : color.value; + newColor = color.value; + } + // this is required to re-validate + $scope.propertyForm.modelValue.$setViewValue(newColor); + }; + // Method required by the valPropertyValidator directive (returns true if the property editor has at least one color selected) + $scope.validateMandatory = function () { + var isValid = !$scope.model.validation.mandatory || $scope.model.value != null && $scope.model.value != '' && (!$scope.model.value.hasOwnProperty('value') || $scope.model.value.value !== ''); + return { + isValid: isValid, + errorMsg: 'Value cannot be empty', + errorKey: 'required' + }; + }; + $scope.isConfigured = $scope.model.config && $scope.model.config.items && _.keys($scope.model.config.items).length > 0; + // A color is active if it matches the value and label of the model. + // If the model doesn't store the label, ignore the label during the comparison. + $scope.isActiveColor = function (color) { + // no value + if (!$scope.model.value) + return false; + // Complex color (value and label)? + if (!$scope.model.value.hasOwnProperty('value')) + return $scope.model.value === color.value; + return $scope.model.value.value === color.value && $scope.model.value.label === color.label; + }; + // Finds the color best matching the model's color, + // and sets the model color to that one. This is useful when + // either the value or label was changed on the data type. + function initActiveColor() { + // no value + if (!$scope.model.value) + return; + // Complex color (value and label)? + if (!$scope.model.value.hasOwnProperty('value')) + return; + var modelColor = $scope.model.value.value; + var modelLabel = $scope.model.value.label; + // Check for a full match or partial match. + var foundItem = null; + // Look for a fully matching color. + for (var key in $scope.model.config.items) { + var item = $scope.model.config.items[key]; + if (item.value == modelColor && item.label == modelLabel) { + foundItem = item; + break; + } + } + // Look for a color with a matching value. + if (!foundItem) { + for (var key in $scope.model.config.items) { + var item = $scope.model.config.items[key]; + if (item.value == modelColor) { + foundItem = item; + break; + } + } + } + // Look for a color with a matching label. + if (!foundItem) { + for (var key in $scope.model.config.items) { + var item = $scope.model.config.items[key]; + if (item.label == modelLabel) { + foundItem = item; + break; + } + } + } + // If a match was found, set it as the active color. + if (foundItem) { + $scope.model.value.value = foundItem.value; + $scope.model.value.label = foundItem.label; + } + } + // figures out if a value is trueish enough + function isTrue(bool) { + return !!bool && bool !== '0' && angular.lowercase(bool) !== 'false'; + } + } + angular.module('umbraco').controller('Umbraco.PropertyEditors.ColorPickerController', ColorPickerController); + angular.module('umbraco').controller('Umbraco.PrevalueEditors.MultiColorPickerController', function ($scope, $timeout, assetsService, angularHelper, $element, localizationService, eventsService) { + //NOTE: We need to make each color an object, not just a string because you cannot 2-way bind to a primitive. + var defaultColor = '000000'; + var defaultLabel = null; + $scope.newColor = defaultColor; + $scope.newLavel = defaultLabel; + $scope.hasError = false; + $scope.labels = {}; + var labelKeys = [ + 'general_cancel', + 'general_choose' + ]; + $scope.labelEnabled = false; + eventsService.on('toggleValue', function (e, args) { + $scope.labelEnabled = args.value; + }); + localizationService.localizeMany(labelKeys).then(function (values) { + $scope.labels.cancel = values[0]; + $scope.labels.choose = values[1]; + }); + assetsService.load([//"lib/spectrum/tinycolor.js", + 'lib/spectrum/spectrum.js'], $scope).then(function () { + var elem = $element.find('input[name=\'newColor\']'); + elem.spectrum({ + color: null, + showInitial: false, + chooseText: $scope.labels.choose, + cancelText: $scope.labels.cancel, + preferredFormat: 'hex', + showInput: true, + clickoutFiresChange: true, + hide: function (color) { + //show the add butotn + $element.find('.btn.add').show(); + }, + change: function (color) { + angularHelper.safeApply($scope, function () { + $scope.newColor = color.toHexString().trimStart('#'); // #ff0000 + }); + }, + show: function () { + //hide the add butotn + $element.find('.btn.add').hide(); + } + }); + }); + if (!angular.isArray($scope.model.value)) { + //make an array from the dictionary + var items = []; + for (var i in $scope.model.value) { + var oldValue = $scope.model.value[i]; + if (oldValue.hasOwnProperty('value')) { + items.push({ + value: oldValue.value, + label: oldValue.label, + sortOrder: oldValue.sortOrder, + id: i + }); + } else { + items.push({ + value: oldValue, + label: oldValue, + sortOrder: sortOrder, + id: i + }); + } + } + //ensure the items are sorted by the provided sort order + items.sort(function (a, b) { + return a.sortOrder > b.sortOrder ? 1 : b.sortOrder > a.sortOrder ? -1 : 0; + }); + //now make the editor model the array + $scope.model.value = items; + } + // ensure labels + for (var i = 0; i < $scope.model.value.length; i++) { + var item = $scope.model.value[i]; + item.label = item.hasOwnProperty('label') ? item.label : item.value; + } + function validLabel(label) { + return label !== null && typeof label !== 'undefined' && label !== '' && label.length && label.length > 0; + } + $scope.remove = function (item, evt) { + evt.preventDefault(); + $scope.model.value = _.reject($scope.model.value, function (x) { + return x.value === item.value && x.label === item.label; + }); + }; + $scope.add = function (evt) { + evt.preventDefault(); if ($scope.newColor) { - var exists = _.find($scope.model.value, function(item) { - return item.value.toUpperCase() == $scope.newColor.toUpperCase(); + var newLabel = validLabel($scope.newLabel) ? $scope.newLabel : $scope.newColor; + var exists = _.find($scope.model.value, function (item) { + return item.value.toUpperCase() === $scope.newColor.toUpperCase() || item.label.toUpperCase() === newLabel.toUpperCase(); }); if (!exists) { - $scope.model.value.push({ value: $scope.newColor }); - //$scope.newColor = defaultColor; - // set colorpicker to default color - //var elem = $element.find("input"); - //elem.spectrum("set", $scope.newColor); + $scope.model.value.push({ + value: $scope.newColor, + label: newLabel + }); $scope.hasError = false; return; } - //there was an error, do the highlight (will be set back by the directive) $scope.hasError = true; } - }; - + $scope.sortableOptions = { + axis: 'y', + containment: 'parent', + cursor: 'move', + //handle: ".handle, .thumbnail", + items: '> div.control-group', + tolerance: 'pointer', + update: function (e, ui) { + // Get the new and old index for the moved element (using the text as the identifier, so + // we'd have a problem if two prevalues were the same, but that would be unlikely) + var newIndex = ui.item.index(); + var movedPrevalueText = $('pre', ui.item).text(); + var originalIndex = getElementIndexByPrevalueText(movedPrevalueText); + //// Move the element in the model + if (originalIndex > -1) { + var movedElement = $scope.model.value[originalIndex]; + $scope.model.value.splice(originalIndex, 1); + $scope.model.value.splice(newIndex, 0, movedElement); + } + } + }; + function getElementIndexByPrevalueText(value) { + for (var i = 0; i < $scope.model.value.length; i++) { + if ($scope.model.value[i].value === value) { + return i; + } + } + return -1; + } //load the separate css for the editor to avoid it blocking our js loading - assetsService.loadCss("lib/spectrum/spectrum.css"); + assetsService.loadCss('lib/spectrum/spectrum.css', $scope); }); - -//this controller simply tells the dialogs service to open a mediaPicker window -//with a specified callback, this callback will receive an object with a selection on it - -function contentPickerController($scope, dialogService, entityResource, editorState, $log, iconHelper, $routeParams, fileManager, contentEditingHelper, angularHelper, navigationService, $location) { - - function trim(str, chr) { - var rgxtrim = (!chr) ? new RegExp('^\\s+|\\s+$', 'g') : new RegExp('^' + chr + '+|' + chr + '+$', 'g'); - return str.replace(rgxtrim, ''); - } - - function startWatch() { - //We need to watch our renderModel so that we can update the underlying $scope.model.value properly, this is required - // because the ui-sortable doesn't dispatch an event after the digest of the sort operation. Any of the events for UI sortable - // occur after the DOM has updated but BEFORE the digest has occured so the model has NOT changed yet - it even states so in the docs. - // In their source code there is no event so we need to just subscribe to our model changes here. - //This also makes it easier to manage models, we update one and the rest will just work. - $scope.$watch(function () { - //return the joined Ids as a string to watch - return _.map($scope.renderModel, function (i) { - return i.id; - }).join(); - }, function (newVal) { - var currIds = _.map($scope.renderModel, function (i) { - return i.id; - }); - $scope.model.value = trim(currIds.join(), ","); - - //Validate! - if ($scope.model.config && $scope.model.config.minNumber && parseInt($scope.model.config.minNumber) > $scope.renderModel.length) { - $scope.contentPickerForm.minCount.$setValidity("minCount", false); - } - else { - $scope.contentPickerForm.minCount.$setValidity("minCount", true); - } - - if ($scope.model.config && $scope.model.config.maxNumber && parseInt($scope.model.config.maxNumber) < $scope.renderModel.length) { - $scope.contentPickerForm.maxCount.$setValidity("maxCount", false); - } - else { - $scope.contentPickerForm.maxCount.$setValidity("maxCount", true); - } - }); - } - - $scope.renderModel = []; - - $scope.dialogEditor = editorState && editorState.current && editorState.current.isDialogEditor === true; - - //the default pre-values - var defaultConfig = { - multiPicker: false, - showOpenButton: false, - showEditButton: false, - showPathOnHover: false, - startNode: { - query: "", - type: "content", - id: $scope.model.config.startNodeId ? $scope.model.config.startNodeId : -1 // get start node for simple Content Picker - } - }; - - if ($scope.model.config) { - //merge the server config on top of the default config, then set the server config to use the result - $scope.model.config = angular.extend(defaultConfig, $scope.model.config); - } - - //Umbraco persists boolean for prevalues as "0" or "1" so we need to convert that! - $scope.model.config.multiPicker = ($scope.model.config.multiPicker === "1" ? true : false); - $scope.model.config.showOpenButton = ($scope.model.config.showOpenButton === "1" ? true : false); - $scope.model.config.showEditButton = ($scope.model.config.showEditButton === "1" ? true : false); - $scope.model.config.showPathOnHover = ($scope.model.config.showPathOnHover === "1" ? true : false); - - var entityType = $scope.model.config.startNode.type === "member" - ? "Member" - : $scope.model.config.startNode.type === "media" - ? "Media" - : "Document"; - $scope.allowOpenButton = entityType === "Document" || entityType === "Media"; - $scope.allowEditButton = entityType === "Document"; - - //the dialog options for the picker - var dialogOptions = { - multiPicker: $scope.model.config.multiPicker, - entityType: entityType, - filterCssClass: "not-allowed not-published", - startNodeId: null, - callback: function (data) { - if (angular.isArray(data)) { - _.each(data, function (item, i) { - $scope.add(item); - }); - } else { - $scope.clear(); - $scope.add(data); - } - angularHelper.getCurrentForm($scope).$setDirty(); - }, - treeAlias: $scope.model.config.startNode.type, - section: $scope.model.config.startNode.type - }; - - //since most of the pre-value config's are used in the dialog options (i.e. maxNumber, minNumber, etc...) we'll merge the - // pre-value config on to the dialog options - angular.extend(dialogOptions, $scope.model.config); - - //We need to manually handle the filter for members here since the tree displayed is different and only contains - // searchable list views - if (entityType === "Member") { - //first change the not allowed filter css class - dialogOptions.filterCssClass = "not-allowed"; - var currFilter = dialogOptions.filter; - //now change the filter to be a method - dialogOptions.filter = function(i) { - //filter out the list view nodes - if (i.metaData.isContainer) { - return true; - } - if (!currFilter) { - return false; - } - //now we need to filter based on what is stored in the pre-vals, this logic duplicates what is in the treepicker.controller, - // but not much we can do about that since members require special filtering. - var filterItem = currFilter.toLowerCase().split(','); - var found = filterItem.indexOf(i.metaData.contentType.toLowerCase()) >= 0; - if (!currFilter.startsWith("!") && !found || currFilter.startsWith("!") && found) { - return true; - } - - return false; - } - } - - - //if we have a query for the startnode, we will use that. - if ($scope.model.config.startNode.query) { - var rootId = $routeParams.id; - entityResource.getByQuery($scope.model.config.startNode.query, rootId, "Document").then(function (ent) { - dialogOptions.startNodeId = ent.id; - }); - } else { - dialogOptions.startNodeId = $scope.model.config.startNode.id; - } - - //dialog - $scope.openContentPicker = function() { - $scope.contentPickerOverlay = dialogOptions; - $scope.contentPickerOverlay.view = "treepicker"; - $scope.contentPickerOverlay.show = true; - - $scope.contentPickerOverlay.submit = function(model) { - - if (angular.isArray(model.selection)) { - _.each(model.selection, function (item, i) { - $scope.add(item); - }); - } - - $scope.contentPickerOverlay.show = false; - $scope.contentPickerOverlay = null; - } - - $scope.contentPickerOverlay.close = function(oldModel) { - $scope.contentPickerOverlay.show = false; - $scope.contentPickerOverlay = null; - } - - }; - - $scope.remove = function (index) { - $scope.renderModel.splice(index, 1); - angularHelper.getCurrentForm($scope).$setDirty(); - }; - - $scope.showNode = function (index) { - var item = $scope.renderModel[index]; - var id = item.id; - var section = $scope.model.config.startNode.type.toLowerCase(); - - entityResource.getPath(id, entityType).then(function (path) { - navigationService.changeSection(section); - navigationService.showTree(section, { - tree: section, path: path, forceReload: false, activate: true - }); - var routePath = section + "/" + section + "/edit/" + id.toString(); - $location.path(routePath).search(""); - }); - } - - $scope.add = function (item) { - var currIds = _.map($scope.renderModel, function (i) { - return i.id; - }); - - if (currIds.indexOf(item.id) < 0) { - item.icon = iconHelper.convertFromLegacyIcon(item.icon); - $scope.renderModel.push({ name: item.name, id: item.id, icon: item.icon, path: item.path }); - } - }; - - $scope.clear = function () { - $scope.renderModel = []; - }; - - var unsubscribe = $scope.$on("formSubmitting", function (ev, args) { - var currIds = _.map($scope.renderModel, function (i) { - return i.id; - }); - $scope.model.value = trim(currIds.join(), ","); - }); - - //when the scope is destroyed we need to unsubscribe - $scope.$on('$destroy', function () { - unsubscribe(); - }); - - //load current data - var modelIds = $scope.model.value ? $scope.model.value.split(',') : []; - entityResource.getByIds(modelIds, entityType).then(function (data) { - - //Ensure we populate the render model in the same order that the ids were stored! - _.each(modelIds, function (id, i) { - var entity = _.find(data, function (d) { - return d.id == id; - }); - - if (entity) { - entity.icon = iconHelper.convertFromLegacyIcon(entity.icon); - $scope.renderModel.push({ name: entity.name, id: entity.id, icon: entity.icon, path: entity.path }); - } - - - }); - - //everything is loaded, start the watch on the model - startWatch(); - - }); -} - -angular.module('umbraco').controller("Umbraco.PropertyEditors.ContentPickerController", contentPickerController); - -function dateTimePickerController($scope, notificationsService, assetsService, angularHelper, userService, $element, dateHelper) { - - //setup the default config - var config = { - pickDate: true, - pickTime: true, - useSeconds: true, - format: "YYYY-MM-DD HH:mm:ss", - icons: { - time: "icon-time", - date: "icon-calendar", - up: "icon-chevron-up", - down: "icon-chevron-down" - } - - }; - - //map the user config - $scope.model.config = angular.extend(config, $scope.model.config); - //ensure the format doesn't get overwritten with an empty string - if ($scope.model.config.format === "" || $scope.model.config.format === undefined || $scope.model.config.format === null) { - $scope.model.config.format = $scope.model.config.pickTime ? "YYYY-MM-DD HH:mm:ss" : "YYYY-MM-DD"; - } - - - - $scope.hasDatetimePickerValue = $scope.model.value ? true : false; - $scope.datetimePickerValue = null; - - //hide picker if clicking on the document - $scope.hidePicker = function () { - //$element.find("div:first").datetimepicker("hide"); - // Sometimes the statement above fails and generates errors in the browser console. The following statements fix that. - var dtp = $element.find("div:first"); - if (dtp && dtp.datetimepicker) { - dtp.datetimepicker("hide"); - } - }; - $(document).bind("click", $scope.hidePicker); - - //handles the date changing via the api - function applyDate(e) { - angularHelper.safeApply($scope, function() { - // when a date is changed, update the model - if (e.date && e.date.isValid()) { - $scope.datePickerForm.datepicker.$setValidity("pickerError", true); - $scope.hasDatetimePickerValue = true; - $scope.datetimePickerValue = e.date.format($scope.model.config.format); - } - else { - $scope.hasDatetimePickerValue = false; - $scope.datetimePickerValue = null; - } - - setModelValue(); - - if (!$scope.model.config.pickTime) { - $element.find("div:first").datetimepicker("hide", 0); - } - }); - } - - //sets the scope model value accordingly - this is the value to be sent up to the server and depends on - // if the picker is configured to offset time. We always format the date/time in a specific format for sending - // to the server, this is different from the format used to display the date/time. - function setModelValue() { - if ($scope.hasDatetimePickerValue) { - var elementData = $element.find("div:first").data().DateTimePicker; - if ($scope.model.config.pickTime) { - //check if we are supposed to offset the time - if ($scope.model.value && $scope.model.config.offsetTime === "1" && Umbraco.Sys.ServerVariables.application.serverTimeOffset !== undefined) { - $scope.model.value = dateHelper.convertToServerStringTime(elementData.getDate(), Umbraco.Sys.ServerVariables.application.serverTimeOffset); - $scope.serverTime = dateHelper.convertToServerStringTime(elementData.getDate(), Umbraco.Sys.ServerVariables.application.serverTimeOffset, "YYYY-MM-DD HH:mm:ss Z"); - } - else { - $scope.model.value = elementData.getDate().format("YYYY-MM-DD HH:mm:ss"); - } - } - else { - $scope.model.value = elementData.getDate().format("YYYY-MM-DD"); - } - } - else { - $scope.model.value = null; - } - } - - var picker = null; - - $scope.clearDate = function() { - $scope.hasDatetimePickerValue = false; - $scope.datetimePickerValue = null; - $scope.model.value = null; - $scope.datePickerForm.datepicker.$setValidity("pickerError", true); - } - - $scope.serverTime = null; - $scope.serverTimeNeedsOffsetting = false; - if (Umbraco.Sys.ServerVariables.application.serverTimeOffset !== undefined) { - // Will return something like 120 - var serverOffset = Umbraco.Sys.ServerVariables.application.serverTimeOffset; - - // Will return something like -120 - var localOffset = new Date().getTimezoneOffset(); - - // If these aren't equal then offsetting is needed - // note the minus in front of serverOffset needed - // because C# and javascript return the inverse offset - $scope.serverTimeNeedsOffsetting = (-serverOffset !== localOffset); - } - - //get the current user to see if we can localize this picker - userService.getCurrentUser().then(function (user) { - - assetsService.loadCss('lib/datetimepicker/bootstrap-datetimepicker.min.css').then(function() { - - var filesToLoad = ["lib/moment/moment-with-locales.js", - "lib/datetimepicker/bootstrap-datetimepicker.js"]; - - - $scope.model.config.language = user.locale; - - - assetsService.load(filesToLoad, $scope).then( - function () { - //The Datepicker js and css files are available and all components are ready to use. - - // Get the id of the datepicker button that was clicked - var pickerId = $scope.model.alias; - - var element = $element.find("div:first"); - - // Open the datepicker and add a changeDate eventlistener - element - .datetimepicker(angular.extend({ useCurrent: true }, $scope.model.config)) - .on("dp.change", applyDate) - .on("dp.error", function(a, b, c) { - $scope.hasDatetimePickerValue = false; - $scope.datePickerForm.datepicker.$setValidity("pickerError", false); - }); - - if ($scope.hasDatetimePickerValue) { - var dateVal; - //check if we are supposed to offset the time - if ($scope.model.value && $scope.model.config.offsetTime === "1" && $scope.serverTimeNeedsOffsetting) { - //get the local time offset from the server - dateVal = dateHelper.convertToLocalMomentTime($scope.model.value, Umbraco.Sys.ServerVariables.application.serverTimeOffset); - $scope.serverTime = dateHelper.convertToServerStringTime(dateVal, Umbraco.Sys.ServerVariables.application.serverTimeOffset, "YYYY-MM-DD HH:mm:ss Z"); - } - else { - //create a normal moment , no offset required - var dateVal = $scope.model.value ? moment($scope.model.value, "YYYY-MM-DD HH:mm:ss") : moment(); - } - - element.datetimepicker("setValue", dateVal); - $scope.datetimePickerValue = dateVal.format($scope.model.config.format); - } - - element.find("input").bind("blur", function() { - //we need to force an apply here - $scope.$apply(); - }); - - //Ensure to remove the event handler when this instance is destroyted - $scope.$on('$destroy', function () { - element.find("input").unbind("blur"); - element.datetimepicker("destroy"); - }); - - - var unsubscribe = $scope.$on("formSubmitting", function (ev, args) { - setModelValue(); - }); - //unbind doc click event! - $scope.$on('$destroy', function () { - unsubscribe(); - }); - - - }); - }); - - }); - - var unsubscribe = $scope.$on("formSubmitting", function (ev, args) { - setModelValue(); - }); - - //unbind doc click event! - $scope.$on('$destroy', function () { - $(document).unbind("click", $scope.hidePicker); - unsubscribe(); - }); -} - -angular.module("umbraco").controller("Umbraco.PropertyEditors.DatepickerController", dateTimePickerController); - -angular.module("umbraco").controller("Umbraco.PropertyEditors.DropdownController", - function($scope) { - - //setup the default config - var config = { - items: [], - multiple: false - }; - - //map the user config - angular.extend(config, $scope.model.config); - - //map back to the model - $scope.model.config = config; - - function convertArrayToDictionaryArray(model){ - //now we need to format the items in the dictionary because we always want to have an array - var newItems = []; - for (var i = 0; i < model.length; i++) { - newItems.push({ id: model[i], sortOrder: 0, value: model[i] }); - } - - return newItems; - } - - - function convertObjectToDictionaryArray(model){ - //now we need to format the items in the dictionary because we always want to have an array - var newItems = []; - var vals = _.values($scope.model.config.items); - var keys = _.keys($scope.model.config.items); - - for (var i = 0; i < vals.length; i++) { - var label = vals[i].value ? vals[i].value : vals[i]; - newItems.push({ id: keys[i], sortOrder: vals[i].sortOrder, value: label }); - } - - return newItems; - } - - if (angular.isArray($scope.model.config.items)) { - //PP: I dont think this will happen, but we have tests that expect it to happen.. - //if array is simple values, convert to array of objects - if(!angular.isObject($scope.model.config.items[0])){ - $scope.model.config.items = convertArrayToDictionaryArray($scope.model.config.items); - } - } - else if (angular.isObject($scope.model.config.items)) { - $scope.model.config.items = convertObjectToDictionaryArray($scope.model.config.items); - } - else { - throw "The items property must be either an array or a dictionary"; - } - - - //sort the values - $scope.model.config.items.sort(function (a, b) { return (a.sortOrder > b.sortOrder) ? 1 : ((b.sortOrder > a.sortOrder) ? -1 : 0); }); - - //now we need to check if the value is null/undefined, if it is we need to set it to "" so that any value that is set - // to "" gets selected by default - if ($scope.model.value === null || $scope.model.value === undefined) { - if ($scope.model.config.multiple) { - $scope.model.value = []; - } - else { - $scope.model.value = ""; - } - } - - }); - -/** A drop down list or multi value select list based on an entity type, this can be re-used for any entity types */ -function entityPicker($scope, entityResource) { - - //set the default to DocumentType - if (!$scope.model.config.entityType) { - $scope.model.config.entityType = "DocumentType"; - } - - //Determine the select list options and which value to publish - if (!$scope.model.config.publishBy) { - $scope.selectOptions = "entity.id as entity.name for entity in entities"; - } - else { - $scope.selectOptions = "entity." + $scope.model.config.publishBy + " as entity.name for entity in entities"; - } - - entityResource.getAll($scope.model.config.entityType).then(function (data) { - //convert the ids to strings so the drop downs work properly when comparing - _.each(data, function(d) { - d.id = d.id.toString(); - }); - $scope.entities = data; - }); - - if ($scope.model.value === null || $scope.model.value === undefined) { - if ($scope.model.config.multiple) { - $scope.model.value = []; - } - else { - $scope.model.value = ""; - } - } - else { - //if it's multiple, change the value to an array - if ($scope.model.config.multiple === "1") { - if (_.isString($scope.model.value)) { - $scope.model.value = $scope.model.value.split(','); - } - } - } -} -angular.module('umbraco').controller("Umbraco.PropertyEditors.EntityPickerController", entityPicker); -/** - * @ngdoc controller - * @name Umbraco.Editors.FileUploadController - * @function - * - * @description - * The controller for the file upload property editor. It is important to note that the $scope.model.value - * doesn't necessarily depict what is saved for this property editor. $scope.model.value can be empty when we - * are submitting files because in that case, we are adding files to the fileManager which is what gets peristed - * on the server. However, when we are clearing files, we are setting $scope.model.value to "{clearFiles: true}" - * to indicate on the server that we are removing files for this property. We will keep the $scope.model.value to - * be the name of the file selected (if it is a newly selected file) or keep it to be it's original value, this allows - * for the editors to check if the value has changed and to re-bind the property if that is true. - * -*/ -function fileUploadController($scope, $element, $compile, imageHelper, fileManager, umbRequestHelper, mediaHelper) { - - /** Clears the file collections when content is saving (if we need to clear) or after saved */ - function clearFiles() { - //clear the files collection (we don't want to upload any!) - fileManager.setFiles($scope.model.alias, []); - //clear the current files - $scope.files = []; - - if($scope.propertyForm) { - if ($scope.propertyForm.fileCount) { - //this is required to re-validate - $scope.propertyForm.fileCount.$setViewValue($scope.files.length); - } - } - - } - - /** this method is used to initialize the data and to re-initialize it if the server value is changed */ - function initialize(index) { - - clearFiles(); - - if (!index) { - index = 1; - } - - //this is used in order to tell the umb-single-file-upload directive to - //rebuild the html input control (and thus clearing the selected file) since - //that is the only way to manipulate the html for the file input control. - $scope.rebuildInput = { - index: index - }; - //clear the current files - $scope.files = []; - //store the original value so we can restore it if the user clears and then cancels clearing. - $scope.originalValue = $scope.model.value; - - //create the property to show the list of files currently saved - if ($scope.model.value != "" && $scope.model.value != undefined) { - - var images = $scope.model.value.split(","); - - $scope.persistedFiles = _.map(images, function (item) { - return { file: item, isImage: imageHelper.detectIfImageByExtension(item) }; - }); - } - else { - $scope.persistedFiles = []; - } - - _.each($scope.persistedFiles, function (file) { - - var thumbnailUrl = umbRequestHelper.getApiUrl( - "imagesApiBaseUrl", - "GetBigThumbnail", - [{ originalImagePath: file.file }]); - - var extension = file.file.substring(file.file.lastIndexOf(".") + 1, file.file.length); - - file.thumbnail = thumbnailUrl; - file.extension = extension.toLowerCase(); - }); - - $scope.clearFiles = false; - } - - initialize(); - - // Method required by the valPropertyValidator directive (returns true if the property editor has at least one file selected) - $scope.validateMandatory = function () { - return { - isValid: !$scope.model.validation.mandatory || ((($scope.persistedFiles != null && $scope.persistedFiles.length > 0) || ($scope.files != null && $scope.files.length > 0)) && !$scope.clearFiles), - errorMsg: "Value cannot be empty", - errorKey: "required" - }; - } - - //listen for clear files changes to set our model to be sent up to the server - $scope.$watch("clearFiles", function (isCleared) { - if (isCleared == true) { - $scope.model.value = { clearFiles: true }; - clearFiles(); - } - else { - //reset to original value - $scope.model.value = $scope.originalValue; - //this is required to re-validate - if($scope.propertyForm) { - $scope.propertyForm.fileCount.$setViewValue($scope.files.length); - } - } - }); - - //listen for when a file is selected - $scope.$on("filesSelected", function (event, args) { - $scope.$apply(function () { - //set the files collection - fileManager.setFiles($scope.model.alias, args.files); - //clear the current files - $scope.files = []; - var newVal = ""; - for (var i = 0; i < args.files.length; i++) { - //save the file object to the scope's files collection - $scope.files.push({ alias: $scope.model.alias, file: args.files[i] }); - newVal += args.files[i].name + ","; - } - - //this is required to re-validate - $scope.propertyForm.fileCount.$setViewValue($scope.files.length); - - //set clear files to false, this will reset the model too - $scope.clearFiles = false; - //set the model value to be the concatenation of files selected. Please see the notes - // in the description of this controller, it states that this value isn't actually used for persistence, - // but we need to set it so that the editor and the server can detect that it's been changed, and it is used for validation. - $scope.model.value = { selectedFiles: newVal.trimEnd(",") }; - }); - }); - - //listen for when the model value has changed - $scope.$watch("model.value", function (newVal, oldVal) { - //cannot just check for !newVal because it might be an empty string which we - //want to look for. - if (newVal !== null && newVal !== undefined && newVal !== oldVal) { - //now we need to check if we need to re-initialize our structure which is kind of tricky - // since we only want to do that if the server has changed the value, not if this controller - // has changed the value. There's only 2 scenarios where we change the value internall so - // we know what those values can be, if they are not either of them, then we'll re-initialize. - - if (newVal.clearFiles !== true && newVal !== $scope.originalValue && !newVal.selectedFiles) { - initialize($scope.rebuildInput.index + 1); - } - - } - }); -}; -angular.module("umbraco") - .controller('Umbraco.PropertyEditors.FileUploadController', fileUploadController) - .run(function(mediaHelper, umbRequestHelper, assetsService){ - if (mediaHelper && mediaHelper.registerFileResolver) { - assetsService.load(["lib/moment/moment-with-locales.js"]).then( - function () { - //NOTE: The 'entity' can be either a normal media entity or an "entity" returned from the entityResource - // they contain different data structures so if we need to query against it we need to be aware of this. - mediaHelper.registerFileResolver("Umbraco.UploadField", function(property, entity, thumbnail){ - if (thumbnail) { - if (mediaHelper.detectIfImageByExtension(property.value)) { - //get default big thumbnail from image processor - var thumbnailUrl = property.value + "?rnd=" + moment(entity.updateDate).format("YYYYMMDDHHmmss") + "&width=500&animationprocessmode=first"; - return thumbnailUrl; - } - else { - return null; - } - } - else { - return property.value; - } - }); - } - ); - } - }); - -angular.module("umbraco") - -//this controller is obsolete and should not be used anymore -//it proxies everything to the system media list view which has overtaken -//all the work this property editor used to perform -.controller("Umbraco.PropertyEditors.FolderBrowserController", - function ($rootScope, $scope, contentTypeResource) { - //get the system media listview - contentTypeResource.getPropertyTypeScaffold(-96) - .then(function(dt) { - - $scope.fakeProperty = { - alias: "contents", - config: dt.config, - description: "", - editor: dt.editor, - hideLabel: true, - id: 1, - label: "Contents:", - validation: { - mandatory: false, - pattern: null - }, - value: "", - view: dt.view - }; - + //this controller simply tells the dialogs service to open a mediaPicker window + //with a specified callback, this callback will receive an object with a selection on it + function contentPickerController($scope, entityResource, editorState, iconHelper, $routeParams, angularHelper, navigationService, $location, miniEditorHelper, localizationService) { + var unsubscribe; + function subscribe() { + unsubscribe = $scope.$on('formSubmitting', function (ev, args) { + var currIds = _.map($scope.renderModel, function (i) { + return $scope.model.config.idType === 'udi' ? i.udi : i.id; + }); + $scope.model.value = trim(currIds.join(), ','); + }); + } + function trim(str, chr) { + var rgxtrim = !chr ? new RegExp('^\\s+|\\s+$', 'g') : new RegExp('^' + chr + '+|' + chr + '+$', 'g'); + return str.replace(rgxtrim, ''); + } + function startWatch() { + //We need to watch our renderModel so that we can update the underlying $scope.model.value properly, this is required + // because the ui-sortable doesn't dispatch an event after the digest of the sort operation. Any of the events for UI sortable + // occur after the DOM has updated but BEFORE the digest has occured so the model has NOT changed yet - it even states so in the docs. + // In their source code there is no event so we need to just subscribe to our model changes here. + //This also makes it easier to manage models, we update one and the rest will just work. + $scope.$watch(function () { + //return the joined Ids as a string to watch + return _.map($scope.renderModel, function (i) { + return $scope.model.config.idType === 'udi' ? i.udi : i.id; + }).join(); + }, function (newVal) { + var currIds = _.map($scope.renderModel, function (i) { + return $scope.model.config.idType === 'udi' ? i.udi : i.id; + }); + $scope.model.value = trim(currIds.join(), ','); + //Validate! + if ($scope.model.config && $scope.model.config.minNumber && parseInt($scope.model.config.minNumber) > $scope.renderModel.length) { + $scope.contentPickerForm.minCount.$setValidity('minCount', false); + } else { + $scope.contentPickerForm.minCount.$setValidity('minCount', true); + } + if ($scope.model.config && $scope.model.config.maxNumber && parseInt($scope.model.config.maxNumber) < $scope.renderModel.length) { + $scope.contentPickerForm.maxCount.$setValidity('maxCount', false); + } else { + $scope.contentPickerForm.maxCount.$setValidity('maxCount', true); + } + setSortingState($scope.renderModel); + }); + } + $scope.renderModel = []; + $scope.dialogEditor = editorState && editorState.current && editorState.current.isDialogEditor === true; + //the default pre-values + var defaultConfig = { + multiPicker: false, + showOpenButton: false, + showEditButton: false, + showPathOnHover: false, + maxNumber: 1, + minNumber: 0, + startNode: { + query: '', + type: 'content', + id: $scope.model.config.startNodeId ? $scope.model.config.startNodeId : -1 // get start node for simple Content Picker + } + }; + // sortable options + $scope.sortableOptions = { + axis: 'y', + containment: 'parent', + distance: 10, + opacity: 0.7, + tolerance: 'pointer', + scroll: true, + zIndex: 6000 + }; + if ($scope.model.config) { + //merge the server config on top of the default config, then set the server config to use the result + $scope.model.config = angular.extend(defaultConfig, $scope.model.config); + } + //Umbraco persists boolean for prevalues as "0" or "1" so we need to convert that! + $scope.model.config.multiPicker = $scope.model.config.multiPicker === '1' ? true : false; + $scope.model.config.showOpenButton = $scope.model.config.showOpenButton === '1' ? true : false; + $scope.model.config.showEditButton = $scope.model.config.showEditButton === '1' ? true : false; + $scope.model.config.showPathOnHover = $scope.model.config.showPathOnHover === '1' ? true : false; + var entityType = $scope.model.config.startNode.type === 'member' ? 'Member' : $scope.model.config.startNode.type === 'media' ? 'Media' : 'Document'; + $scope.allowOpenButton = entityType === 'Document'; + $scope.allowEditButton = entityType === 'Document'; + $scope.allowRemoveButton = true; + //the dialog options for the picker + var dialogOptions = { + multiPicker: $scope.model.config.multiPicker, + entityType: entityType, + filterCssClass: 'not-allowed not-published', + startNodeId: null, + currentNode: editorState ? editorState.current : null, + callback: function (data) { + if (angular.isArray(data)) { + _.each(data, function (item, i) { + $scope.add(item); + }); + } else { + $scope.clear(); + $scope.add(data); + } + angularHelper.getCurrentForm($scope).$setDirty(); + }, + treeAlias: $scope.model.config.startNode.type, + section: $scope.model.config.startNode.type, + idType: 'int' + }; + //since most of the pre-value config's are used in the dialog options (i.e. maxNumber, minNumber, etc...) we'll merge the + // pre-value config on to the dialog options + angular.extend(dialogOptions, $scope.model.config); + //We need to manually handle the filter for members here since the tree displayed is different and only contains + // searchable list views + if (entityType === 'Member') { + //first change the not allowed filter css class + dialogOptions.filterCssClass = 'not-allowed'; + var currFilter = dialogOptions.filter; + //now change the filter to be a method + dialogOptions.filter = function (i) { + //filter out the list view nodes + if (i.metaData.isContainer) { + return true; + } + if (!currFilter) { + return false; + } + //now we need to filter based on what is stored in the pre-vals, this logic duplicates what is in the treepicker.controller, + // but not much we can do about that since members require special filtering. + var filterItem = currFilter.toLowerCase().split(','); + var found = filterItem.indexOf(i.metaData.contentType.toLowerCase()) >= 0; + if (!currFilter.startsWith('!') && !found || currFilter.startsWith('!') && found) { + return true; + } + return false; + }; + } + if ($routeParams.section === 'settings' && $routeParams.tree === 'documentTypes') { + //if the content-picker is being rendered inside the document-type editor, we don't need to process the startnode query + dialogOptions.startNodeId = -1; + } else if ($scope.model.config.startNode.query) { + //if we have a query for the startnode, we will use that. + var rootId = $routeParams.id; + entityResource.getByQuery($scope.model.config.startNode.query, rootId, 'Document').then(function (ent) { + dialogOptions.startNodeId = $scope.model.config.idType === 'udi' ? ent.udi : ent.id; + }); + } else { + dialogOptions.startNodeId = $scope.model.config.startNode.id; + } + //dialog + $scope.openContentPicker = function () { + $scope.contentPickerOverlay = dialogOptions; + $scope.contentPickerOverlay.view = 'treepicker'; + $scope.contentPickerOverlay.show = true; + $scope.contentPickerOverlay.submit = function (model) { + if (angular.isArray(model.selection)) { + _.each(model.selection, function (item, i) { + $scope.add(item); + }); + } + $scope.contentPickerOverlay.show = false; + $scope.contentPickerOverlay = null; + }; + $scope.contentPickerOverlay.close = function (oldModel) { + $scope.contentPickerOverlay.show = false; + $scope.contentPickerOverlay = null; + }; + }; + $scope.remove = function (index) { + $scope.renderModel.splice(index, 1); + angularHelper.getCurrentForm($scope).$setDirty(); + }; + $scope.showNode = function (index) { + var item = $scope.renderModel[index]; + var id = item.id; + var section = $scope.model.config.startNode.type.toLowerCase(); + entityResource.getPath(id, entityType).then(function (path) { + navigationService.changeSection(section); + navigationService.showTree(section, { + tree: section, + path: path, + forceReload: false, + activate: true + }); + var routePath = section + '/' + section + '/edit/' + id.toString(); + $location.path(routePath).search(''); + }); + }; + $scope.add = function (item) { + var currIds = _.map($scope.renderModel, function (i) { + return $scope.model.config.idType === 'udi' ? i.udi : i.id; + }); + var itemId = $scope.model.config.idType === 'udi' ? item.udi : item.id; + if (currIds.indexOf(itemId) < 0) { + setEntityUrl(item); + } + }; + $scope.clear = function () { + $scope.renderModel = []; + }; + $scope.openMiniEditor = function (node) { + miniEditorHelper.launchMiniEditor(node).then(function (updatedNode) { + // update the node + node.name = updatedNode.name; + node.published = updatedNode.hasPublishedVersion; + if (entityType !== 'Member') { + entityResource.getUrl(updatedNode.id, entityType).then(function (data) { + node.url = data; + }); + } + }); + }; + //when the scope is destroyed we need to unsubscribe + $scope.$on('$destroy', function () { + if (unsubscribe) { + unsubscribe(); + } }); -}); - -angular.module("umbraco") -.controller("Umbraco.PropertyEditors.GoogleMapsController", - function ($element, $rootScope, $scope, notificationsService, dialogService, assetsService, $log, $timeout) { - - assetsService.loadJs('http://www.google.com/jsapi') - .then(function () { - google.load("maps", "3", - { - callback: initMap, - other_params: "sensor=false" - }); - }); - - function initMap() { - //Google maps is available and all components are ready to use. - var valueArray = $scope.model.value.split(','); - var latLng = new google.maps.LatLng(valueArray[0], valueArray[1]); - var mapDiv = document.getElementById($scope.model.alias + '_map'); - var mapOptions = { - zoom: $scope.model.config.zoom, - center: latLng, - mapTypeId: google.maps.MapTypeId[$scope.model.config.mapType] - }; - var geocoder = new google.maps.Geocoder(); - var map = new google.maps.Map(mapDiv, mapOptions); - - var marker = new google.maps.Marker({ - map: map, - position: latLng, - draggable: true - }); - - google.maps.event.addListener(map, 'click', function (event) { - - dialogService.mediaPicker({ - callback: function (data) { - var image = data.selection[0].src; - - var latLng = event.latLng; - var marker = new google.maps.Marker({ - map: map, - icon: image, - position: latLng, - draggable: true - }); - - google.maps.event.addListener(marker, "dragend", function (e) { - var newLat = marker.getPosition().lat(); - var newLng = marker.getPosition().lng(); - - codeLatLng(marker.getPosition(), geocoder); - - //set the model value - $scope.model.vvalue = newLat + "," + newLng; - }); - - } - }); - }); - - var tabShown = function(e) { - google.maps.event.trigger(map, 'resize'); - }; - - //listen for tab changes - if (tabsCtrl != null) { - tabsCtrl.onTabShown(function (args) { - tabShown(); - }); - } - - $element.closest('.umb-panel.tabbable').on('shown', '.nav-tabs a', tabShown); - - $scope.$on('$destroy', function () { - $element.closest('.umb-panel.tabbable').off('shown', '.nav-tabs a', tabShown); - }); - } - - function codeLatLng(latLng, geocoder) { - geocoder.geocode({ 'latLng': latLng }, - function (results, status) { - if (status == google.maps.GeocoderStatus.OK) { - var location = results[0].formatted_address; - $rootScope.$apply(function () { - notificationsService.success("Peter just went to: ", location); - }); - } - }); - } - - //here we declare a special method which will be called whenever the value has changed from the server - //this is instead of doing a watch on the model.value = faster - $scope.model.onValueChanged = function (newVal, oldVal) { - //update the display val again if it has changed from the server - initMap(); - }; - }); -angular.module("umbraco") - .controller("Umbraco.PropertyEditors.GridPrevalueEditor.LayoutConfigController", - function ($scope) { - - $scope.currentLayout = $scope.model.currentLayout; - $scope.columns = $scope.model.columns; - $scope.rows = $scope.model.rows; - - $scope.scaleUp = function(section, max, overflow){ - var add = 1; - if(overflow !== true){ - add = (max > 1) ? 1 : max; - } - //var add = (max > 1) ? 1 : max; - section.grid = section.grid+add; - }; - - $scope.scaleDown = function(section){ - var remove = (section.grid > 1) ? 1 : 0; - section.grid = section.grid-remove; - }; - - $scope.percentage = function(spans){ - return ((spans / $scope.columns) * 100).toFixed(8); - }; - - $scope.toggleCollection = function(collection, toggle){ - if(toggle){ - collection = []; - }else{ - delete collection; - } - }; - - - - /**************** + var modelIds = $scope.model.value ? $scope.model.value.split(',') : []; + //load current data if anything selected + if (modelIds.length > 0) { + entityResource.getByIds(modelIds, entityType).then(function (data) { + _.each(modelIds, function (id, i) { + var entity = _.find(data, function (d) { + return $scope.model.config.idType === 'udi' ? d.udi == id : d.id == id; + }); + if (entity) { + setEntityUrl(entity); + } + }); + //everything is loaded, start the watch on the model + startWatch(); + subscribe(); + }); + } else { + //everything is loaded, start the watch on the model + startWatch(); + subscribe(); + } + function setEntityUrl(entity) { + // get url for content and media items + if (entityType !== 'Member') { + entityResource.getUrl(entity.id, entityType).then(function (data) { + // update url + angular.forEach($scope.renderModel, function (item) { + if (item.id === entity.id) { + if (entity.trashed) { + item.url = localizationService.dictionary.general_recycleBin; + } else { + item.url = data; + } + } + }); + }); + } + // add the selected item to the renderModel + // if it needs to show a url the item will get + // updated when the url comes back from server + addSelectedItem(entity); + } + function addSelectedItem(item) { + // set icon + if (item.icon) { + item.icon = iconHelper.convertFromLegacyIcon(item.icon); + } + // set default icon + if (!item.icon) { + switch (entityType) { + case 'Document': + item.icon = 'icon-document'; + break; + case 'Media': + item.icon = 'icon-picture'; + break; + case 'Member': + item.icon = 'icon-user'; + break; + } + } + $scope.renderModel.push({ + 'name': item.name, + 'id': item.id, + 'udi': item.udi, + 'icon': item.icon, + 'path': item.path, + 'url': item.url, + 'trashed': item.trashed, + 'published': item.metaData && item.metaData.IsPublished === false && entityType === 'Document' ? false : true // only content supports published/unpublished content so we set everything else to published so the UI looks correct + }); + } + function setSortingState(items) { + // disable sorting if the list only consist of one item + if (items.length > 1) { + $scope.sortableOptions.disabled = false; + } else { + $scope.sortableOptions.disabled = true; + } + } + } + angular.module('umbraco').controller('Umbraco.PropertyEditors.ContentPickerController', contentPickerController); + function dateTimePickerController($scope, notificationsService, assetsService, angularHelper, userService, $element, dateHelper) { + //setup the default config + var config = { + pickDate: true, + pickTime: true, + useSeconds: true, + format: 'YYYY-MM-DD HH:mm:ss', + icons: { + time: 'icon-time', + date: 'icon-calendar', + up: 'icon-chevron-up', + down: 'icon-chevron-down' + } + }; + //map the user config + $scope.model.config = angular.extend(config, $scope.model.config); + //ensure the format doesn't get overwritten with an empty string + if ($scope.model.config.format === '' || $scope.model.config.format === undefined || $scope.model.config.format === null) { + $scope.model.config.format = $scope.model.config.pickTime ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD'; + } + $scope.hasDatetimePickerValue = $scope.model.value ? true : false; + $scope.datetimePickerValue = null; + //hide picker if clicking on the document + $scope.hidePicker = function () { + //$element.find("div:first").datetimepicker("hide"); + // Sometimes the statement above fails and generates errors in the browser console. The following statements fix that. + var dtp = $element.find('div:first'); + if (dtp && dtp.datetimepicker) { + dtp.datetimepicker('hide'); + } + }; + $(document).bind('click', $scope.hidePicker); + //handles the date changing via the api + function applyDate(e) { + angularHelper.safeApply($scope, function () { + // when a date is changed, update the model + if (e.date && e.date.isValid()) { + $scope.datePickerForm.datepicker.$setValidity('pickerError', true); + $scope.hasDatetimePickerValue = true; + $scope.datetimePickerValue = e.date.format($scope.model.config.format); + } else { + $scope.hasDatetimePickerValue = false; + $scope.datetimePickerValue = null; + } + setModelValue(); + if (!$scope.model.config.pickTime) { + $element.find('div:first').datetimepicker('hide', 0); + } + }); + } + //sets the scope model value accordingly - this is the value to be sent up to the server and depends on + // if the picker is configured to offset time. We always format the date/time in a specific format for sending + // to the server, this is different from the format used to display the date/time. + function setModelValue() { + if ($scope.hasDatetimePickerValue) { + var elementData = $element.find('div:first').data().DateTimePicker; + if ($scope.model.config.pickTime) { + //check if we are supposed to offset the time + if ($scope.model.value && $scope.model.config.offsetTime === '1' && Umbraco.Sys.ServerVariables.application.serverTimeOffset !== undefined) { + $scope.model.value = dateHelper.convertToServerStringTime(elementData.getDate(), Umbraco.Sys.ServerVariables.application.serverTimeOffset); + $scope.serverTime = dateHelper.convertToServerStringTime(elementData.getDate(), Umbraco.Sys.ServerVariables.application.serverTimeOffset, 'YYYY-MM-DD HH:mm:ss Z'); + } else { + $scope.model.value = elementData.getDate().format('YYYY-MM-DD HH:mm:ss'); + } + } else { + $scope.model.value = elementData.getDate().format('YYYY-MM-DD'); + } + } else { + $scope.model.value = null; + } + } + var picker = null; + $scope.clearDate = function () { + $scope.hasDatetimePickerValue = false; + $scope.datetimePickerValue = null; + $scope.model.value = null; + $scope.datePickerForm.datepicker.$setValidity('pickerError', true); + }; + $scope.serverTime = null; + $scope.serverTimeNeedsOffsetting = false; + if (Umbraco.Sys.ServerVariables.application.serverTimeOffset !== undefined) { + // Will return something like 120 + var serverOffset = Umbraco.Sys.ServerVariables.application.serverTimeOffset; + // Will return something like -120 + var localOffset = new Date().getTimezoneOffset(); + // If these aren't equal then offsetting is needed + // note the minus in front of serverOffset needed + // because C# and javascript return the inverse offset + $scope.serverTimeNeedsOffsetting = -serverOffset !== localOffset; + } + //get the current user to see if we can localize this picker + userService.getCurrentUser().then(function (user) { + assetsService.loadCss('lib/datetimepicker/bootstrap-datetimepicker.min.css', $scope).then(function () { + var filesToLoad = ['lib/datetimepicker/bootstrap-datetimepicker.js']; + $scope.model.config.language = user.locale; + assetsService.load(filesToLoad, $scope).then(function () { + //The Datepicker js and css files are available and all components are ready to use. + // Get the id of the datepicker button that was clicked + var pickerId = $scope.model.alias; + var element = $element.find('div:first'); + // Open the datepicker and add a changeDate eventlistener + element.datetimepicker(angular.extend({ useCurrent: true }, $scope.model.config)).on('dp.change', applyDate).on('dp.error', function (a, b, c) { + $scope.hasDatetimePickerValue = false; + $scope.datePickerForm.datepicker.$setValidity('pickerError', false); + }); + if ($scope.hasDatetimePickerValue) { + var dateVal; + //check if we are supposed to offset the time + if ($scope.model.value && $scope.model.config.offsetTime === '1' && $scope.serverTimeNeedsOffsetting) { + //get the local time offset from the server + dateVal = dateHelper.convertToLocalMomentTime($scope.model.value, Umbraco.Sys.ServerVariables.application.serverTimeOffset); + $scope.serverTime = dateHelper.convertToServerStringTime(dateVal, Umbraco.Sys.ServerVariables.application.serverTimeOffset, 'YYYY-MM-DD HH:mm:ss Z'); + } else { + //create a normal moment , no offset required + var dateVal = $scope.model.value ? moment($scope.model.value, 'YYYY-MM-DD HH:mm:ss') : moment(); + } + element.datetimepicker('setValue', dateVal); + $scope.datetimePickerValue = dateVal.format($scope.model.config.format); + } + element.find('input').bind('blur', function () { + //we need to force an apply here + $scope.$apply(); + }); + //Ensure to remove the event handler when this instance is destroyted + $scope.$on('$destroy', function () { + element.find('input').unbind('blur'); + element.datetimepicker('destroy'); + }); + var unsubscribe = $scope.$on('formSubmitting', function (ev, args) { + setModelValue(); + }); + //unbind doc click event! + $scope.$on('$destroy', function () { + unsubscribe(); + }); + }); + }); + }); + var unsubscribe = $scope.$on('formSubmitting', function (ev, args) { + setModelValue(); + }); + //unbind doc click event! + $scope.$on('$destroy', function () { + $(document).unbind('click', $scope.hidePicker); + unsubscribe(); + }); + } + angular.module('umbraco').controller('Umbraco.PropertyEditors.DatepickerController', dateTimePickerController); + angular.module('umbraco').controller('Umbraco.PropertyEditors.DropdownController', function ($scope) { + //setup the default config + var config = { + items: [], + multiple: false + }; + //map the user config + angular.extend(config, $scope.model.config); + //map back to the model + $scope.model.config = config; + function convertArrayToDictionaryArray(model) { + //now we need to format the items in the dictionary because we always want to have an array + var newItems = []; + for (var i = 0; i < model.length; i++) { + newItems.push({ + id: model[i], + sortOrder: 0, + value: model[i] + }); + } + return newItems; + } + function convertObjectToDictionaryArray(model) { + //now we need to format the items in the dictionary because we always want to have an array + var newItems = []; + var vals = _.values($scope.model.config.items); + var keys = _.keys($scope.model.config.items); + for (var i = 0; i < vals.length; i++) { + var label = vals[i].value ? vals[i].value : vals[i]; + newItems.push({ + id: keys[i], + sortOrder: vals[i].sortOrder, + value: label + }); + } + return newItems; + } + if (angular.isArray($scope.model.config.items)) { + //PP: I dont think this will happen, but we have tests that expect it to happen.. + //if array is simple values, convert to array of objects + if (!angular.isObject($scope.model.config.items[0])) { + $scope.model.config.items = convertArrayToDictionaryArray($scope.model.config.items); + } + } else if (angular.isObject($scope.model.config.items)) { + $scope.model.config.items = convertObjectToDictionaryArray($scope.model.config.items); + } else { + throw 'The items property must be either an array or a dictionary'; + } + //sort the values + $scope.model.config.items.sort(function (a, b) { + return a.sortOrder > b.sortOrder ? 1 : b.sortOrder > a.sortOrder ? -1 : 0; + }); + //now we need to check if the value is null/undefined, if it is we need to set it to "" so that any value that is set + // to "" gets selected by default + if ($scope.model.value === null || $scope.model.value === undefined) { + if ($scope.model.config.multiple) { + $scope.model.value = []; + } else { + $scope.model.value = ''; + } + } + }); + angular.module('umbraco').controller('Umbraco.PropertyEditors.DropdownFlexibleController', function ($scope) { + //setup the default config + var config = { + items: [], + multiple: false + }; + //map the user config + angular.extend(config, $scope.model.config); + //map back to the model + $scope.model.config = config; + function convertArrayToDictionaryArray(model) { + //now we need to format the items in the dictionary because we always want to have an array + var newItems = []; + for (var i = 0; i < model.length; i++) { + newItems.push({ + id: model[i], + sortOrder: 0, + value: model[i] + }); + } + return newItems; + } + function convertObjectToDictionaryArray(model) { + //now we need to format the items in the dictionary because we always want to have an array + var newItems = []; + var vals = _.values($scope.model.config.items); + var keys = _.keys($scope.model.config.items); + for (var i = 0; i < vals.length; i++) { + var label = vals[i].value ? vals[i].value : vals[i]; + newItems.push({ + id: keys[i], + sortOrder: vals[i].sortOrder, + value: label + }); + } + return newItems; + } + $scope.updateSingleDropdownValue = function () { + $scope.model.value = [$scope.model.singleDropdownValue]; + }; + if (angular.isArray($scope.model.config.items)) { + //PP: I dont think this will happen, but we have tests that expect it to happen.. + //if array is simple values, convert to array of objects + if (!angular.isObject($scope.model.config.items[0])) { + $scope.model.config.items = convertArrayToDictionaryArray($scope.model.config.items); + } + } else if (angular.isObject($scope.model.config.items)) { + $scope.model.config.items = convertObjectToDictionaryArray($scope.model.config.items); + } else { + throw 'The items property must be either an array or a dictionary'; + } + //sort the values + $scope.model.config.items.sort(function (a, b) { + return a.sortOrder > b.sortOrder ? 1 : b.sortOrder > a.sortOrder ? -1 : 0; + }); + //now we need to check if the value is null/undefined, if it is we need to set it to "" so that any value that is set + // to "" gets selected by default + if ($scope.model.value === null || $scope.model.value === undefined) { + if ($scope.model.config.multiple) { + $scope.model.value = []; + } else { + $scope.model.value = ''; + } + } + // if we run in single mode we'll store the value in a local variable + // so we can pass an array as the model as our PropertyValueEditor expects that + $scope.model.singleDropdownValue = ''; + if ($scope.model.config.multiple === '0') { + $scope.model.singleDropdownValue = Array.isArray($scope.model.value) ? $scope.model.value[0] : $scope.model.value; + } + }); + /** A drop down list or multi value select list based on an entity type, this can be re-used for any entity types */ + function entityPicker($scope, entityResource) { + //set the default to DocumentType + if (!$scope.model.config.entityType) { + $scope.model.config.entityType = 'DocumentType'; + } + //Determine the select list options and which value to publish + if (!$scope.model.config.publishBy) { + $scope.selectOptions = 'entity.id as entity.name for entity in entities'; + } else { + $scope.selectOptions = 'entity.' + $scope.model.config.publishBy + ' as entity.name for entity in entities'; + } + entityResource.getAll($scope.model.config.entityType).then(function (data) { + //convert the ids to strings so the drop downs work properly when comparing + _.each(data, function (d) { + d.id = d.id.toString(); + }); + $scope.entities = data; + }); + if ($scope.model.value === null || $scope.model.value === undefined) { + if ($scope.model.config.multiple) { + $scope.model.value = []; + } else { + $scope.model.value = ''; + } + } else { + //if it's multiple, change the value to an array + if ($scope.model.config.multiple === '1') { + if (_.isString($scope.model.value)) { + $scope.model.value = $scope.model.value.split(','); + } + } + } + } + angular.module('umbraco').controller('Umbraco.PropertyEditors.EntityPickerController', entityPicker); + /** + * @ngdoc controller + * @name Umbraco.Editors.FileUploadController + * @function + * + * @description + * The controller for the file upload property editor. It is important to note that the $scope.model.value + * doesn't necessarily depict what is saved for this property editor. $scope.model.value can be empty when we + * are submitting files because in that case, we are adding files to the fileManager which is what gets peristed + * on the server. However, when we are clearing files, we are setting $scope.model.value to "{clearFiles: true}" + * to indicate on the server that we are removing files for this property. We will keep the $scope.model.value to + * be the name of the file selected (if it is a newly selected file) or keep it to be it's original value, this allows + * for the editors to check if the value has changed and to re-bind the property if that is true. + * +*/ + function fileUploadController($scope, $element, $compile, imageHelper, fileManager, umbRequestHelper, mediaHelper) { + /** Clears the file collections when content is saving (if we need to clear) or after saved */ + function clearFiles() { + //clear the files collection (we don't want to upload any!) + fileManager.setFiles($scope.model.alias, []); + //clear the current files + $scope.files = []; + if ($scope.propertyForm) { + if ($scope.propertyForm.fileCount) { + //this is required to re-validate + $scope.propertyForm.fileCount.$setViewValue($scope.files.length); + } + } + } + /** this method is used to initialize the data and to re-initialize it if the server value is changed */ + function initialize(index) { + clearFiles(); + if (!index) { + index = 1; + } + //this is used in order to tell the umb-single-file-upload directive to + //rebuild the html input control (and thus clearing the selected file) since + //that is the only way to manipulate the html for the file input control. + $scope.rebuildInput = { index: index }; + //clear the current files + $scope.files = []; + //store the original value so we can restore it if the user clears and then cancels clearing. + $scope.originalValue = $scope.model.value; + //create the property to show the list of files currently saved + if ($scope.model.value != '' && $scope.model.value != undefined) { + var images = $scope.model.value.split(','); + $scope.persistedFiles = _.map(images, function (item) { + return { + file: item, + isImage: imageHelper.detectIfImageByExtension(item) + }; + }); + } else { + $scope.persistedFiles = []; + } + _.each($scope.persistedFiles, function (file) { + var thumbnailUrl = umbRequestHelper.getApiUrl('imagesApiBaseUrl', 'GetBigThumbnail', [{ originalImagePath: file.file }]); + var extension = file.file.substring(file.file.lastIndexOf('.') + 1, file.file.length); + file.thumbnail = thumbnailUrl + '&rnd=' + Math.random(); + file.extension = extension.toLowerCase(); + }); + $scope.clearFiles = false; + } + initialize(); + // Method required by the valPropertyValidator directive (returns true if the property editor has at least one file selected) + $scope.validateMandatory = function () { + return { + isValid: !$scope.model.validation.mandatory || ($scope.persistedFiles != null && $scope.persistedFiles.length > 0 || $scope.files != null && $scope.files.length > 0) && !$scope.clearFiles, + errorMsg: 'Value cannot be empty', + errorKey: 'required' + }; + }; + //listen for clear files changes to set our model to be sent up to the server + $scope.$watch('clearFiles', function (isCleared) { + if (isCleared == true) { + $scope.model.value = { clearFiles: true }; + clearFiles(); + } else { + //reset to original value + $scope.model.value = $scope.originalValue; + //this is required to re-validate + if ($scope.propertyForm) { + $scope.propertyForm.fileCount.$setViewValue($scope.files.length); + } + } + }); + //listen for when a file is selected + $scope.$on('filesSelected', function (event, args) { + $scope.$apply(function () { + //set the files collection + fileManager.setFiles($scope.model.alias, args.files); + //clear the current files + $scope.files = []; + var newVal = ''; + for (var i = 0; i < args.files.length; i++) { + //save the file object to the scope's files collection + $scope.files.push({ + alias: $scope.model.alias, + file: args.files[i] + }); + newVal += args.files[i].name + ','; + } + //this is required to re-validate + $scope.propertyForm.fileCount.$setViewValue($scope.files.length); + //set clear files to false, this will reset the model too + $scope.clearFiles = false; + //set the model value to be the concatenation of files selected. Please see the notes + // in the description of this controller, it states that this value isn't actually used for persistence, + // but we need to set it so that the editor and the server can detect that it's been changed, and it is used for validation. + $scope.model.value = { selectedFiles: newVal.trimEnd(',') }; + //need to explicity setDirty here as file upload field can't track dirty & we can't use the fileCount (hidden field/model) + $scope.propertyForm.$setDirty(); + }); + }); + //listen for when the model value has changed + $scope.$watch('model.value', function (newVal, oldVal) { + //cannot just check for !newVal because it might be an empty string which we + //want to look for. + if (newVal !== null && newVal !== undefined && newVal !== oldVal) { + // here we need to check if the value change needs to trigger an update in the UI. + // if the value is only changed in the controller and not in the server values, we do not + // want to trigger an update yet. + // we can however no longer rely on checking values in the controller vs. values from the server + // to determine whether to update or not, since you could potentially be uploading a file with + // the exact same name - in that case we need to reinitialize to show the newly uploaded file. + if (newVal.clearFiles !== true && !newVal.selectedFiles) { + initialize($scope.rebuildInput.index + 1); + } + } + }); + } + ; + angular.module('umbraco').controller('Umbraco.PropertyEditors.FileUploadController', fileUploadController).run(function (mediaHelper, umbRequestHelper, assetsService) { + if (mediaHelper && mediaHelper.registerFileResolver) { + //NOTE: The 'entity' can be either a normal media entity or an "entity" returned from the entityResource + // they contain different data structures so if we need to query against it we need to be aware of this. + mediaHelper.registerFileResolver('Umbraco.UploadField', function (property, entity, thumbnail) { + if (thumbnail) { + if (mediaHelper.detectIfImageByExtension(property.value)) { + //get default big thumbnail from image processor + var thumbnailUrl = property.value + '?rnd=' + moment(entity.updateDate).format('YYYYMMDDHHmmss') + '&width=500&animationprocessmode=first'; + return thumbnailUrl; + } else { + return null; + } + } else { + return property.value; + } + }); + } + }); + angular.module('umbraco') //this controller is obsolete and should not be used anymore + //it proxies everything to the system media list view which has overtaken + //all the work this property editor used to perform +.controller('Umbraco.PropertyEditors.FolderBrowserController', function ($rootScope, $scope, contentTypeResource) { + //get the system media listview + contentTypeResource.getPropertyTypeScaffold(-96).then(function (dt) { + $scope.fakeProperty = { + alias: 'contents', + config: dt.config, + description: '', + editor: dt.editor, + hideLabel: true, + id: 1, + label: 'Contents:', + validation: { + mandatory: false, + pattern: null + }, + value: '', + view: dt.view + }; + }); + }); + angular.module('umbraco').controller('Umbraco.PropertyEditors.GoogleMapsController', function ($element, $rootScope, $scope, notificationsService, dialogService, assetsService, $log, $timeout) { + assetsService.loadJs('https://www.google.com/jsapi', $scope).then(function () { + google.load('maps', '3', { + callback: initMap, + other_params: 'sensor=false' + }); + }); + function initMap() { + //Google maps is available and all components are ready to use. + var valueArray = $scope.model.value.split(','); + var latLng = new google.maps.LatLng(valueArray[0], valueArray[1]); + var mapDiv = document.getElementById($scope.model.alias + '_map'); + var mapOptions = { + zoom: $scope.model.config.zoom, + center: latLng, + mapTypeId: google.maps.MapTypeId[$scope.model.config.mapType] + }; + var geocoder = new google.maps.Geocoder(); + var map = new google.maps.Map(mapDiv, mapOptions); + var marker = new google.maps.Marker({ + map: map, + position: latLng, + draggable: true + }); + google.maps.event.addListener(map, 'click', function (event) { + dialogService.mediaPicker({ + callback: function (data) { + var image = data.selection[0].src; + var latLng = event.latLng; + var marker = new google.maps.Marker({ + map: map, + icon: image, + position: latLng, + draggable: true + }); + google.maps.event.addListener(marker, 'dragend', function (e) { + var newLat = marker.getPosition().lat(); + var newLng = marker.getPosition().lng(); + codeLatLng(marker.getPosition(), geocoder); + //set the model value + $scope.model.vvalue = newLat + ',' + newLng; + }); + } + }); + }); + var tabShown = function (e) { + google.maps.event.trigger(map, 'resize'); + }; + //listen for tab changes + if (tabsCtrl != null) { + tabsCtrl.onTabShown(function (args) { + tabShown(); + }); + } + $element.closest('.umb-panel.tabbable').on('shown', '.nav-tabs a', tabShown); + $scope.$on('$destroy', function () { + $element.closest('.umb-panel.tabbable').off('shown', '.nav-tabs a', tabShown); + }); + } + function codeLatLng(latLng, geocoder) { + geocoder.geocode({ 'latLng': latLng }, function (results, status) { + if (status == google.maps.GeocoderStatus.OK) { + var location = results[0].formatted_address; + $rootScope.$apply(function () { + notificationsService.success('Peter just went to: ', location); + }); + } + }); + } + //here we declare a special method which will be called whenever the value has changed from the server + //this is instead of doing a watch on the model.value = faster + $scope.model.onValueChanged = function (newVal, oldVal) { + //update the display val again if it has changed from the server + initMap(); + }; + }); + angular.module('umbraco').controller('Umbraco.PropertyEditors.GridPrevalueEditor.LayoutConfigController', function ($scope) { + $scope.currentLayout = $scope.model.currentLayout; + $scope.columns = $scope.model.columns; + $scope.rows = $scope.model.rows; + $scope.scaleUp = function (section, max, overflow) { + var add = 1; + if (overflow !== true) { + add = max > 1 ? 1 : max; + } + //var add = (max > 1) ? 1 : max; + section.grid = section.grid + add; + }; + $scope.scaleDown = function (section) { + var remove = section.grid > 1 ? 1 : 0; + section.grid = section.grid - remove; + }; + $scope.percentage = function (spans) { + return (spans / $scope.columns * 100).toFixed(8); + }; + $scope.toggleCollection = function (collection, toggle) { + if (toggle) { + collection = []; + } else { + delete collection; + } + }; + /**************** Section *****************/ - $scope.configureSection = function(section, template){ - if(section === undefined){ - var space = ($scope.availableLayoutSpace > 4) ? 4 : $scope.availableLayoutSpace; - section = { - grid: space - }; - template.sections.push(section); - } - - $scope.currentSection = section; - }; - - $scope.deleteSection = function(section, template) { - if ($scope.currentSection === section) { - $scope.currentSection = undefined; - } - var index = template.sections.indexOf(section) - template.sections.splice(index, 1); - }; - - $scope.closeSection = function(){ - $scope.currentSection = undefined; - }; - - $scope.$watch("currentLayout", function(layout){ - if(layout){ - var total = 0; - _.forEach(layout.sections, function(section){ - total = (total + section.grid); - }); - - $scope.availableLayoutSpace = $scope.columns - total; - } - }, true); + $scope.configureSection = function (section, template) { + if (section === undefined) { + var space = $scope.availableLayoutSpace > 4 ? 4 : $scope.availableLayoutSpace; + section = { grid: space }; + template.sections.push(section); + } + $scope.currentSection = section; + }; + $scope.deleteSection = function (section, template) { + if ($scope.currentSection === section) { + $scope.currentSection = undefined; + } + var index = template.sections.indexOf(section); + template.sections.splice(index, 1); + }; + $scope.closeSection = function () { + $scope.currentSection = undefined; + }; + $scope.$watch('currentLayout', function (layout) { + if (layout) { + var total = 0; + _.forEach(layout.sections, function (section) { + total = total + section.grid; + }); + $scope.availableLayoutSpace = $scope.columns - total; + } + }, true); }); - -function RowConfigController($scope) { - - $scope.currentRow = $scope.model.currentRow; - $scope.editors = $scope.model.editors; - $scope.columns = $scope.model.columns; - - $scope.scaleUp = function(section, max, overflow) { - var add = 1; - if (overflow !== true) { - add = (max > 1) ? 1 : max; - } - //var add = (max > 1) ? 1 : max; - section.grid = section.grid + add; - }; - - $scope.scaleDown = function(section) { - var remove = (section.grid > 1) ? 1 : 0; - section.grid = section.grid - remove; - }; - - $scope.percentage = function(spans) { - return ((spans / $scope.columns) * 100).toFixed(8); - }; - - $scope.toggleCollection = function(collection, toggle) { - if (toggle) { - collection = []; - } - else { - delete collection; - } - }; - - - /**************** + function RowConfigController($scope) { + $scope.currentRow = $scope.model.currentRow; + $scope.editors = $scope.model.editors; + $scope.columns = $scope.model.columns; + $scope.scaleUp = function (section, max, overflow) { + var add = 1; + if (overflow !== true) { + add = max > 1 ? 1 : max; + } + //var add = (max > 1) ? 1 : max; + section.grid = section.grid + add; + }; + $scope.scaleDown = function (section) { + var remove = section.grid > 1 ? 1 : 0; + section.grid = section.grid - remove; + }; + $scope.percentage = function (spans) { + return (spans / $scope.columns * 100).toFixed(8); + }; + $scope.toggleCollection = function (collection, toggle) { + if (toggle) { + collection = []; + } else { + delete collection; + } + }; + /**************** area *****************/ - $scope.configureCell = function(cell, row) { - if ($scope.currentCell && $scope.currentCell === cell) { - delete $scope.currentCell; - } - else { - if (cell === undefined) { - var available = $scope.availableRowSpace; - var space = 4; - - if (available < 4 && available > 0) { - space = available; + $scope.configureCell = function (cell, row) { + if ($scope.currentCell && $scope.currentCell === cell) { + delete $scope.currentCell; + } else { + if (cell === undefined) { + var available = $scope.availableRowSpace; + var space = 4; + if (available < 4 && available > 0) { + space = available; + } + cell = { grid: space }; + row.areas.push(cell); } - - cell = { - grid: space - }; - - row.areas.push(cell); + $scope.currentCell = cell; } - $scope.currentCell = cell; - } - }; - - $scope.deleteArea = function (cell, row) { - if ($scope.currentCell === cell) { - $scope.currentCell = undefined; - } - var index = row.areas.indexOf(cell) - row.areas.splice(index, 1); - }; - - $scope.closeArea = function() { - $scope.currentCell = undefined; - }; - - $scope.nameChanged = false; - var originalName = $scope.currentRow.name; - $scope.$watch("currentRow", function(row) { - if (row) { - - var total = 0; - _.forEach(row.areas, function(area) { - total = (total + area.grid); - }); - - $scope.availableRowSpace = $scope.columns - total; - - if (originalName) { - if (originalName != row.name) { - $scope.nameChanged = true; - } - else { - $scope.nameChanged = false; + }; + $scope.deleteArea = function (cell, row) { + if ($scope.currentCell === cell) { + $scope.currentCell = undefined; + } + var index = row.areas.indexOf(cell); + row.areas.splice(index, 1); + }; + $scope.closeArea = function () { + $scope.currentCell = undefined; + }; + $scope.nameChanged = false; + var originalName = $scope.currentRow.name; + $scope.$watch('currentRow', function (row) { + if (row) { + var total = 0; + _.forEach(row.areas, function (area) { + total = total + area.grid; + }); + $scope.availableRowSpace = $scope.columns - total; + if (originalName) { + if (originalName != row.name) { + $scope.nameChanged = true; + } else { + $scope.nameChanged = false; + } } } - } - }, true); - -} - -angular.module("umbraco").controller("Umbraco.PropertyEditors.GridPrevalueEditor.RowConfigController", RowConfigController); - -angular.module("umbraco") - .controller("Umbraco.PropertyEditors.Grid.EmbedController", - function ($scope, $rootScope, $timeout) { - - $scope.setEmbed = function(){ + }, true); + } + angular.module('umbraco').controller('Umbraco.PropertyEditors.GridPrevalueEditor.RowConfigController', RowConfigController); + angular.module('umbraco').controller('Umbraco.PropertyEditors.Grid.EmbedController', function ($scope, $rootScope, $timeout) { + $scope.setEmbed = function () { $scope.embedDialog = {}; - $scope.embedDialog.view = "embed"; + $scope.embedDialog.view = 'embed'; $scope.embedDialog.show = true; - - $scope.embedDialog.submit = function(model) { + $scope.embedDialog.submit = function (model) { $scope.control.value = model.embed.preview; $scope.embedDialog.show = false; $scope.embedDialog = null; }; - - $scope.embedDialog.close = function(oldModel) { + $scope.embedDialog.close = function (oldModel) { $scope.embedDialog.show = false; $scope.embedDialog = null; }; - - }; - - $timeout(function(){ - if($scope.control.$initializing){ - $scope.setEmbed(); - } - }, 200); -}); - -angular.module("umbraco") - .controller("Umbraco.PropertyEditors.Grid.MacroController", - function ($scope, $rootScope, $timeout, dialogService, macroResource, macroService, $routeParams) { - - $scope.title = "Click to insert macro"; - - $scope.setMacro = function(){ - + }; + $timeout(function () { + if ($scope.control.$initializing) { + $scope.setEmbed(); + } + }, 200); + }); + angular.module('umbraco').controller('Umbraco.PropertyEditors.Grid.MacroController', function ($scope, $rootScope, $timeout, dialogService, macroResource, macroService, $routeParams) { + $scope.title = 'Click to insert macro'; + $scope.setMacro = function () { var dialogData = { richTextEditor: true, - macroData: $scope.control.value || { - macroAlias: $scope.control.editor.config && $scope.control.editor.config.macroAlias - ? $scope.control.editor.config.macroAlias : "" - } + macroData: $scope.control.value || { macroAlias: $scope.control.editor.config && $scope.control.editor.config.macroAlias ? $scope.control.editor.config.macroAlias : '' } }; - $scope.macroPickerOverlay = {}; - $scope.macroPickerOverlay.view = "macropicker"; + $scope.macroPickerOverlay.view = 'macropicker'; $scope.macroPickerOverlay.dialogData = dialogData; $scope.macroPickerOverlay.show = true; - - $scope.macroPickerOverlay.submit = function(model) { - + $scope.macroPickerOverlay.submit = function (model) { var macroObject = macroService.collectValueData(model.selectedMacro, model.macroParams, dialogData.renderingEngine); - $scope.control.value = { - macroAlias: macroObject.macroAlias, - macroParamsDictionary: macroObject.macroParamsDictionary + macroAlias: macroObject.macroAlias, + macroParamsDictionary: macroObject.macroParamsDictionary }; - - $scope.setPreview($scope.control.value ); - + $scope.setPreview($scope.control.value); $scope.macroPickerOverlay.show = false; $scope.macroPickerOverlay = null; }; - - $scope.macroPickerOverlay.close = function(oldModel) { + $scope.macroPickerOverlay.close = function (oldModel) { $scope.macroPickerOverlay.show = false; $scope.macroPickerOverlay = null; }; - - }; - - $scope.setPreview = function(macro){ + }; + $scope.setPreview = function (macro) { var contentId = $routeParams.id; - - macroResource.getMacroResultAsHtmlForEditor(macro.macroAlias, contentId, macro.macroParamsDictionary) - .then(function (htmlResult) { + macroResource.getMacroResultAsHtmlForEditor(macro.macroAlias, contentId, macro.macroParamsDictionary).then(function (htmlResult) { $scope.title = macro.macroAlias; - if(htmlResult.trim().length > 0 && htmlResult.indexOf("Macro:") < 0){ + if (htmlResult.trim().length > 0 && htmlResult.indexOf('Macro:') < 0) { $scope.preview = htmlResult; } }); - }; - - $timeout(function(){ - if($scope.control.$initializing){ - $scope.setMacro(); - }else if($scope.control.value){ + $timeout(function () { + if ($scope.control.$initializing) { + $scope.setMacro(); + } else if ($scope.control.value) { $scope.setPreview($scope.control.value); } - }, 200); -}); - -angular.module("umbraco") - .controller("Umbraco.PropertyEditors.Grid.MediaController", - function ($scope, $rootScope, $timeout) { - - $scope.setImage = function(){ + }, 200); + }); + angular.module('umbraco').controller('Umbraco.PropertyEditors.Grid.MediaController', function ($scope, $rootScope, $timeout, userService) { + if (!$scope.model.config.startNodeId) { + userService.getCurrentUser().then(function (userData) { + $scope.model.config.startNodeId = userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0]; + $scope.model.config.startNodeIsVirtual = userData.startMediaIds.length !== 1; + }); + } + $scope.setImage = function () { $scope.mediaPickerOverlay = {}; - $scope.mediaPickerOverlay.view = "mediapicker"; + $scope.mediaPickerOverlay.view = 'mediapicker'; + $scope.mediaPickerOverlay.startNodeId = $scope.model.config && $scope.model.config.startNodeId ? $scope.model.config.startNodeId : undefined; + $scope.mediaPickerOverlay.startNodeIsVirtual = $scope.mediaPickerOverlay.startNodeId ? $scope.model.config.startNodeIsVirtual : undefined; $scope.mediaPickerOverlay.cropSize = $scope.control.editor.config && $scope.control.editor.config.size ? $scope.control.editor.config.size : undefined; $scope.mediaPickerOverlay.showDetails = true; $scope.mediaPickerOverlay.disableFolderSelect = true; $scope.mediaPickerOverlay.onlyImages = true; $scope.mediaPickerOverlay.show = true; - - $scope.mediaPickerOverlay.submit = function(model) { + $scope.mediaPickerOverlay.submit = function (model) { var selectedImage = model.selectedImages[0]; - $scope.control.value = { focalPoint: selectedImage.focalPoint, id: selectedImage.id, + udi: selectedImage.udi, image: selectedImage.image, altText: selectedImage.altText }; - $scope.setUrl(); - $scope.mediaPickerOverlay.show = false; $scope.mediaPickerOverlay = null; }; - - $scope.mediaPickerOverlay.close = function(oldModel) { + $scope.mediaPickerOverlay.close = function (oldModel) { $scope.mediaPickerOverlay.show = false; $scope.mediaPickerOverlay = null; }; }; - - $scope.setUrl = function(){ - - if($scope.control.value.image){ + $scope.setUrl = function () { + if ($scope.control.value.image) { var url = $scope.control.value.image; - - if($scope.control.editor.config && $scope.control.editor.config.size){ - url += "?width=" + $scope.control.editor.config.size.width; - url += "&height=" + $scope.control.editor.config.size.height; - url += "&animationprocessmode=first"; - - if($scope.control.value.focalPoint){ - url += "¢er=" + $scope.control.value.focalPoint.top +"," + $scope.control.value.focalPoint.left; - url += "&mode=crop"; + if ($scope.control.editor.config && $scope.control.editor.config.size) { + url += '?width=' + $scope.control.editor.config.size.width; + url += '&height=' + $scope.control.editor.config.size.height; + url += '&animationprocessmode=first'; + if ($scope.control.value.focalPoint) { + url += '¢er=' + $scope.control.value.focalPoint.top + ',' + $scope.control.value.focalPoint.left; + url += '&mode=crop'; } } - // set default size if no crop present (moved from the view) - if (url.indexOf('?') == -1) - { - url += "?width=800&upscale=false&animationprocessmode=false" + if (url.indexOf('?') == -1) { + url += '?width=800&upscale=false&animationprocessmode=false'; } $scope.url = url; } }; - - $timeout(function(){ - if($scope.control.$initializing){ + $timeout(function () { + if ($scope.control.$initializing) { $scope.setImage(); - }else if($scope.control.value){ + } else if ($scope.control.value) { $scope.setUrl(); } }, 200); -}); - -(function() { - "use strict"; - - function GridRichTextEditorController($scope, tinyMceService, macroService) { - - var vm = this; - - vm.openLinkPicker = openLinkPicker; - vm.openMediaPicker = openMediaPicker; - vm.openMacroPicker = openMacroPicker; - vm.openEmbed = openEmbed; - - function openLinkPicker(editor, currentTarget, anchorElement) { - vm.linkPickerOverlay = { - view: "linkpicker", - currentTarget: currentTarget, - show: true, - submit: function(model) { - tinyMceService.insertLinkInEditor(editor, model.target, anchorElement); - vm.linkPickerOverlay.show = false; - vm.linkPickerOverlay = null; - } - }; - } - - function openMediaPicker(editor, currentTarget, userData) { - vm.mediaPickerOverlay = { - currentTarget: currentTarget, - onlyImages: true, - showDetails: true, - startNodeId: userData.startMediaId, - view: "mediapicker", - show: true, - submit: function(model) { - tinyMceService.insertMediaInEditor(editor, model.selectedImages[0]); - vm.mediaPickerOverlay.show = false; - vm.mediaPickerOverlay = null; - } - }; - } - - function openEmbed(editor) { - vm.embedOverlay = { - view: "embed", - show: true, - submit: function(model) { - tinyMceService.insertEmbeddedMediaInEditor(editor, model.embed.preview); - vm.embedOverlay.show = false; - vm.embedOverlay = null; - } - }; - } - - function openMacroPicker(editor, dialogData) { - vm.macroPickerOverlay = { - view: "macropicker", - dialogData: dialogData, - show: true, - submit: function(model) { - var macroObject = macroService.collectValueData(model.selectedMacro, model.macroParams, dialogData.renderingEngine); - tinyMceService.insertMacroInEditor(editor, macroObject, $scope); - vm.macroPickerOverlay.show = false; - vm.macroPickerOverlay = null; - } - }; - } - - - - } - - angular.module("umbraco").controller("Umbraco.PropertyEditors.Grid.RichTextEditorController", GridRichTextEditorController); - -})(); - -angular.module("umbraco") - .controller("Umbraco.PropertyEditors.Grid.TextStringController", - function ($scope, $rootScope, $timeout, dialogService) { - - - }); - - -angular.module("umbraco") - .controller("Umbraco.PropertyEditors.GridController", - function ($scope, $http, assetsService, localizationService, $rootScope, dialogService, gridService, mediaResource, imageHelper, $timeout, umbRequestHelper, angularHelper) { - + (function () { + 'use strict'; + function GridRichTextEditorController($scope, tinyMceService, macroService, editorState) { + var vm = this; + vm.openLinkPicker = openLinkPicker; + vm.openMediaPicker = openMediaPicker; + vm.openMacroPicker = openMacroPicker; + 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); + vm.linkPickerOverlay.show = false; + vm.linkPickerOverlay = null; + } + }; + } + function openMediaPicker(editor, currentTarget, userData) { + vm.mediaPickerOverlay = { + currentTarget: currentTarget, + onlyImages: true, + showDetails: true, + startNodeId: userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0], + view: 'mediapicker', + show: true, + submit: function (model) { + tinyMceService.insertMediaInEditor(editor, model.selectedImages[0]); + vm.mediaPickerOverlay.show = false; + vm.mediaPickerOverlay = null; + } + }; + } + function openEmbed(editor) { + vm.embedOverlay = { + view: 'embed', + show: true, + submit: function (model) { + tinyMceService.insertEmbeddedMediaInEditor(editor, model.embed.preview); + vm.embedOverlay.show = false; + vm.embedOverlay = null; + } + }; + } + function openMacroPicker(editor, dialogData) { + vm.macroPickerOverlay = { + view: 'macropicker', + dialogData: dialogData, + show: true, + submit: function (model) { + var macroObject = macroService.collectValueData(model.selectedMacro, model.macroParams, dialogData.renderingEngine); + tinyMceService.insertMacroInEditor(editor, macroObject, $scope); + vm.macroPickerOverlay.show = false; + vm.macroPickerOverlay = null; + } + }; + } + } + angular.module('umbraco').controller('Umbraco.PropertyEditors.Grid.RichTextEditorController', GridRichTextEditorController); + }()); + angular.module('umbraco').controller('Umbraco.PropertyEditors.Grid.TextStringController', function ($scope, $rootScope, $timeout, dialogService) { + }); + angular.module('umbraco').controller('Umbraco.PropertyEditors.GridController', function ($scope, $http, assetsService, localizationService, $rootScope, dialogService, gridService, mediaResource, imageHelper, $timeout, umbRequestHelper, angularHelper, $element, eventsService) { // Grid status variables - var placeHolder = ""; + var placeHolder = ''; var currentForm = angularHelper.getCurrentForm($scope); - $scope.currentRow = null; $scope.currentCell = null; $scope.currentToolsControl = null; @@ -11596,118 +12811,99 @@ angular.module("umbraco") $scope.hasSettings = false; $scope.showRowConfigurations = true; $scope.sortMode = false; - $scope.reorderKey = "general_reorder"; - + $scope.reorderKey = 'general_reorder'; // ********************************************* // Sortable options // ********************************************* - var draggedRteSettings; - $scope.sortableOptionsRow = { distance: 10, - cursor: "move", - placeholder: "ui-sortable-placeholder", - handle: ".umb-row-title-bar", - helper: "clone", + cursor: 'move', + placeholder: 'ui-sortable-placeholder', + handle: '.umb-row-title-bar', + helper: 'clone', forcePlaceholderSize: true, - tolerance: "pointer", - zIndex: 999999999999999999, + tolerance: 'pointer', + zIndex: 1000000000000000000, scrollSensitivity: 100, cursorAt: { top: 40, left: 60 }, - sort: function (event, ui) { /* prevent vertical scroll out of the screen */ - var max = $(".umb-grid").width() - 150; - if (parseInt(ui.helper.css("left")) > max) { - ui.helper.css({ "left": max + "px" }); + var max = $('.umb-grid').width() - 150; + if (parseInt(ui.helper.css('left')) > max) { + ui.helper.css({ 'left': max + 'px' }); } - if (parseInt(ui.helper.css("left")) < 20) { - ui.helper.css({ "left": 20 }); + if (parseInt(ui.helper.css('left')) < 20) { + ui.helper.css({ 'left': 20 }); } }, - start: function (e, ui) { - // Fade out row when sorting - ui.item.context.style.display = "block"; - ui.item.context.style.opacity = "0.5"; - + ui.item.context.style.display = 'block'; + ui.item.context.style.opacity = '0.5'; draggedRteSettings = {}; - ui.item.find(".mceNoEditor").each(function () { + ui.item.find('.mceNoEditor').each(function () { // remove all RTEs in the dragged row and save their settings - var id = $(this).attr("id"); - draggedRteSettings[id] = _.findWhere(tinyMCE.editors, { id: id }).settings; - // tinyMCE.execCommand("mceRemoveEditor", false, id); + var id = $(this).attr('id'); + draggedRteSettings[id] = _.findWhere(tinyMCE.editors, { id: id }).settings; // tinyMCE.execCommand("mceRemoveEditor", false, id); }); }, - stop: function (e, ui) { - // Fade in row when sorting stops - ui.item.context.style.opacity = "1"; - + ui.item.context.style.opacity = '1'; // reset all RTEs affected by the dragging - ui.item.parents(".umb-column").find(".mceNoEditor").each(function () { - var id = $(this).attr("id"); + ui.item.parents('.umb-column').find('.mceNoEditor').each(function () { + var id = $(this).attr('id'); draggedRteSettings[id] = draggedRteSettings[id] || _.findWhere(tinyMCE.editors, { id: id }).settings; - tinyMCE.execCommand("mceRemoveEditor", false, id); + tinyMCE.execCommand('mceRemoveEditor', false, id); tinyMCE.init(draggedRteSettings[id]); }); currentForm.$setDirty(); } }; - var notIncludedRte = []; var cancelMove = false; - + var startingArea; $scope.sortableOptionsCell = { distance: 10, - cursor: "move", - placeholder: "ui-sortable-placeholder", - handle: ".umb-control-handle", - helper: "clone", - connectWith: ".umb-cell-inner", + cursor: 'move', + placeholder: 'ui-sortable-placeholder', + handle: '.umb-control-handle', + helper: 'clone', + connectWith: '.umb-cell-inner', forcePlaceholderSize: true, - tolerance: "pointer", - zIndex: 999999999999999999, + tolerance: 'pointer', + zIndex: 1000000000000000000, scrollSensitivity: 100, cursorAt: { top: 45, left: 90 }, - sort: function (event, ui) { - /* prevent vertical scroll out of the screen */ - var position = parseInt(ui.item.parent().offset().left) + parseInt(ui.helper.css("left")) - parseInt($(".umb-grid").offset().left); - var max = $(".umb-grid").width() - 220; + var position = parseInt(ui.item.parent().offset().left) + parseInt(ui.helper.css('left')) - parseInt($('.umb-grid').offset().left); + var max = $('.umb-grid').width() - 220; if (position > max) { - ui.helper.css({ "left": max - parseInt(ui.item.parent().offset().left) + parseInt($(".umb-grid").offset().left) + "px" }); + ui.helper.css({ 'left': max - parseInt(ui.item.parent().offset().left) + parseInt($('.umb-grid').offset().left) + 'px' }); } if (position < 0) { - ui.helper.css({ "left": 0 - parseInt(ui.item.parent().offset().left) + parseInt($(".umb-grid").offset().left) + "px" }); + ui.helper.css({ 'left': 0 - parseInt(ui.item.parent().offset().left) + parseInt($('.umb-grid').offset().left) + 'px' }); } }, - over: function (event, ui) { - var allowedEditors = $(event.target).scope().area.allowed; - - if ($.inArray(ui.item.scope().control.editor.alias, allowedEditors) < 0 && allowedEditors) { - + var area = $(event.target).scope().area; + var allowedEditors = area.allowed; + if ($.inArray(ui.item.scope().control.editor.alias, allowedEditors) < 0 && allowedEditors || startingArea != area && area.maxItems != '' && area.maxItems > 0 && area.maxItems < area.controls.length + 1) { $scope.$apply(function () { $(event.target).scope().area.dropNotAllowed = true; }); - ui.placeholder.hide(); cancelMove = true; - } - else { - if ($(event.target).scope().area.controls.length == 0){ - + } else { + if ($(event.target).scope().area.controls.length == 0) { $scope.$apply(function () { $(event.target).scope().area.dropOnEmpty = true; }); @@ -11718,70 +12914,60 @@ angular.module("umbraco") cancelMove = false; } }, - - out: function(event, ui) { + out: function (event, ui) { $scope.$apply(function () { $(event.target).scope().area.dropNotAllowed = false; $(event.target).scope().area.dropOnEmpty = false; }); }, - update: function (event, ui) { /* add all RTEs which are affected by the dragging */ if (!ui.sender) { if (cancelMove) { ui.item.sortable.cancel(); } - ui.item.parents(".umb-cell.content").find(".mceNoEditor").each(function () { - if ($.inArray($(this).attr("id"), notIncludedRte) < 0) { - notIncludedRte.splice(0, 0, $(this).attr("id")); + ui.item.parents('.umb-cell.content').find('.mceNoEditor').each(function () { + if ($.inArray($(this).attr('id'), notIncludedRte) < 0) { + notIncludedRte.splice(0, 0, $(this).attr('id')); } }); - } - else { - $(event.target).find(".mceNoEditor").each(function () { - if ($.inArray($(this).attr("id"), notIncludedRte) < 0) { - notIncludedRte.splice(0, 0, $(this).attr("id")); + } else { + $(event.target).find('.mceNoEditor').each(function () { + if ($.inArray($(this).attr('id'), notIncludedRte) < 0) { + notIncludedRte.splice(0, 0, $(this).attr('id')); } }); } currentForm.$setDirty(); }, - start: function (e, ui) { - + //Get the starting area for reference + var area = $(e.target).scope().area; + startingArea = area; // fade out control when sorting - ui.item.context.style.display = "block"; - ui.item.context.style.opacity = "0.5"; - + ui.item.context.style.display = 'block'; + ui.item.context.style.opacity = '0.5'; // reset dragged RTE settings in case a RTE isn't dragged draggedRteSettings = undefined; - ui.item.context.style.display = "block"; - ui.item.find(".mceNoEditor").each(function () { + ui.item.context.style.display = 'block'; + ui.item.find('.mceNoEditor').each(function () { notIncludedRte = []; - var editors = _.findWhere(tinyMCE.editors, { id: $(this).attr("id") }); - + var editors = _.findWhere(tinyMCE.editors, { id: $(this).attr('id') }); // save the dragged RTE settings - if(editors) { + if (editors) { draggedRteSettings = editors.settings; - // remove the dragged RTE - tinyMCE.execCommand("mceRemoveEditor", false, $(this).attr("id")); - + tinyMCE.execCommand('mceRemoveEditor', false, $(this).attr('id')); } - }); }, - stop: function (e, ui) { - // Fade in control when sorting stops - ui.item.context.style.opacity = "1"; - - ui.item.parents(".umb-cell-content").find(".mceNoEditor").each(function () { - if ($.inArray($(this).attr("id"), notIncludedRte) < 0) { + ui.item.context.style.opacity = '1'; + ui.item.parents('.umb-cell-content').find('.mceNoEditor').each(function () { + if ($.inArray($(this).attr('id'), notIncludedRte) < 0) { // add all dragged's neighbouring RTEs in the new cell - notIncludedRte.splice(0, 0, $(this).attr("id")); + notIncludedRte.splice(0, 0, $(this).attr('id')); } }); $timeout(function () { @@ -11789,129 +12975,108 @@ angular.module("umbraco") if (draggedRteSettings !== undefined) { tinyMCE.init(draggedRteSettings); } - _.forEach(notIncludedRte, function (id) { // reset all the other RTEs if (draggedRteSettings === undefined || id !== draggedRteSettings.id) { var rteSettings = _.findWhere(tinyMCE.editors, { id: id }).settings; - tinyMCE.execCommand("mceRemoveEditor", false, id); + tinyMCE.execCommand('mceRemoveEditor', false, id); tinyMCE.init(rteSettings); } }); }, 500, false); - $scope.$apply(function () { - var cell = $(e.target).scope().area; cell.hasActiveChild = hasActiveChild(cell, cell.controls); cell.active = false; }); } - }; - - $scope.toggleSortMode = function() { + $scope.toggleSortMode = function () { $scope.sortMode = !$scope.sortMode; - if($scope.sortMode) { - $scope.reorderKey = "general_reorderDone"; + if ($scope.sortMode) { + $scope.reorderKey = 'general_reorderDone'; } else { - $scope.reorderKey = "general_reorder"; + $scope.reorderKey = 'general_reorder'; } }; - - $scope.showReorderButton = function() { - if($scope.model.value && $scope.model.value.sections) { - for(var i = 0; $scope.model.value.sections.length > i; i++) { + $scope.showReorderButton = function () { + if ($scope.model.value && $scope.model.value.sections) { + for (var i = 0; $scope.model.value.sections.length > i; i++) { var section = $scope.model.value.sections[i]; - if(section.rows && section.rows.length > 0) { + if (section.rows && section.rows.length > 0) { return true; } } } }; - // ********************************************* // Add items overlay menu // ********************************************* - $scope.openEditorOverlay = function(event, area, index, key) { - $scope.editorOverlay = { - view: "itempicker", - filter: false, - title: localizationService.localize("grid_insertControl"), - availableItems: area.$allowedEditors, - event: event, - show: true, - submit: function(model) { - $scope.addControl(model.selectedItem, area, index); - $scope.editorOverlay.show = false; - $scope.editorOverlay = null; - } - }; - }; - + $scope.openEditorOverlay = function (event, area, index, key) { + $scope.editorOverlay = { + view: 'itempicker', + filter: area.$allowedEditors.length > 15, + title: localizationService.localize('grid_insertControl'), + availableItems: area.$allowedEditors, + event: event, + show: true, + submit: function (model) { + $scope.addControl(model.selectedItem, area, index); + $scope.editorOverlay.show = false; + $scope.editorOverlay = null; + } + }; + }; // ********************************************* // Template management functions // ********************************************* - $scope.addTemplate = function (template) { $scope.model.value = angular.copy(template); - //default row data _.forEach($scope.model.value.sections, function (section) { $scope.initSection(section); }); }; - - // ********************************************* // Row management function // ********************************************* - - $scope.clickRow = function(index, rows) { + $scope.clickRow = function (index, rows) { rows[index].active = true; }; - - $scope.clickOutsideRow = function(index, rows) { + $scope.clickOutsideRow = function (index, rows) { rows[index].active = false; }; - function getAllowedLayouts(section) { - var layouts = $scope.model.config.items.layouts; - //This will occur if it is a new section which has been // created from a 'template' if (section.allowed && section.allowed.length > 0) { return _.filter(layouts, function (layout) { return _.indexOf(section.allowed, layout.name) >= 0; }); - } - else { - - + } else { return layouts; } } - - $scope.addRow = function (section, layout) { - + $scope.addRow = function (section, layout, isInit) { //copy the selected layout into the rows collection var row = angular.copy(layout); - // Init row value row = $scope.initRow(row); - // Push the new row if (row) { section.rows.push(row); } - - currentForm.$setDirty(); - + if (!isInit) { + currentForm.$setDirty(); + } $scope.showRowConfigurations = false; - + eventsService.emit('grid.rowAdded', { + scope: $scope, + element: $element, + row: row + }); }; - $scope.removeRow = function (section, $index) { if (section.rows.length > 0) { section.rows.splice($index, 1); @@ -11919,89 +13084,79 @@ angular.module("umbraco") $scope.openRTEToolbarId = null; currentForm.$setDirty(); } - - if(section.rows.length === 0) { - $scope.showRowConfigurations = true; + if (section.rows.length === 0) { + $scope.showRowConfigurations = true; } }; - - var shouldApply = function(item, itemType, gridItem) { - if (item.applyTo === undefined || item.applyTo === null || item.applyTo === "") { + var shouldApply = function (item, itemType, gridItem) { + if (item.applyTo === undefined || item.applyTo === null || item.applyTo === '') { return true; } - - if (typeof (item.applyTo) === "string") { + if (typeof item.applyTo === 'string') { return item.applyTo === itemType; } - - if (itemType === "row") { + if (itemType === 'row') { if (item.applyTo.row === undefined) { return false; } - if (item.applyTo.row === null || item.applyTo.row === "") { + if (item.applyTo.row === null || item.applyTo.row === '') { return true; } var rows = item.applyTo.row.split(','); return _.indexOf(rows, gridItem.name) !== -1; - } else if (itemType === "cell") { + } else if (itemType === 'cell') { if (item.applyTo.cell === undefined) { return false; } - if (item.applyTo.cell === null || item.applyTo.cell === "") { + if (item.applyTo.cell === null || item.applyTo.cell === '') { return true; } var cells = item.applyTo.cell.split(','); var cellSize = gridItem.grid.toString(); return _.indexOf(cells, cellSize) !== -1; } - } - + }; $scope.editGridItemSettings = function (gridItem, itemType) { - - placeHolder = "{0}"; - + placeHolder = '{0}'; var styles, config; if (itemType === 'control') { styles = null; config = angular.copy(gridItem.editor.config.settings); } else { - styles = _.filter(angular.copy($scope.model.config.items.styles), function (item) { return shouldApply(item, itemType, gridItem); }); - config = _.filter(angular.copy($scope.model.config.items.config), function (item) { return shouldApply(item, itemType, gridItem); }); + styles = _.filter(angular.copy($scope.model.config.items.styles), function (item) { + return shouldApply(item, itemType, gridItem); + }); + config = _.filter(angular.copy($scope.model.config.items.config), function (item) { + return shouldApply(item, itemType, gridItem); + }); } - - if(angular.isObject(gridItem.config)){ - _.each(config, function(cfg){ + if (angular.isObject(gridItem.config)) { + _.each(config, function (cfg) { var val = gridItem.config[cfg.key]; - if(val){ + if (val) { cfg.value = stripModifier(val, cfg.modifier); } }); } - - if(angular.isObject(gridItem.styles)){ - _.each(styles, function(style){ + if (angular.isObject(gridItem.styles)) { + _.each(styles, function (style) { var val = gridItem.styles[style.key]; - if(val){ + if (val) { style.value = stripModifier(val, style.modifier); } }); } - $scope.gridItemSettingsDialog = {}; - $scope.gridItemSettingsDialog.view = "views/propertyeditors/grid/dialogs/config.html"; - $scope.gridItemSettingsDialog.title = "Settings"; + $scope.gridItemSettingsDialog.view = 'views/propertyeditors/grid/dialogs/config.html'; + $scope.gridItemSettingsDialog.title = 'Settings'; $scope.gridItemSettingsDialog.styles = styles; $scope.gridItemSettingsDialog.config = config; - $scope.gridItemSettingsDialog.show = true; - - $scope.gridItemSettingsDialog.submit = function(model) { - + $scope.gridItemSettingsDialog.submit = function (model) { var styleObject = {}; var configObject = {}; - - _.each(model.styles, function(style){ - if(style.value){ + _.each(model.styles, function (style) { + if (style.value) { styleObject[style.key] = addModifier(style.value, style.modifier); } }); @@ -12010,30 +13165,24 @@ angular.module("umbraco") configObject[cfg.key] = addModifier(cfg.value, cfg.modifier); } }); - gridItem.styles = styleObject; gridItem.config = configObject; gridItem.hasConfig = gridItemHasConfig(styleObject, configObject); - currentForm.$setDirty(); - $scope.gridItemSettingsDialog.show = false; $scope.gridItemSettingsDialog = null; }; - - $scope.gridItemSettingsDialog.close = function(oldModel) { + $scope.gridItemSettingsDialog.close = function (oldModel) { $scope.gridItemSettingsDialog.show = false; $scope.gridItemSettingsDialog = null; }; - }; - function stripModifier(val, modifier) { if (!val || !modifier || modifier.indexOf(placeHolder) < 0) { return val; } else { var paddArray = modifier.split(placeHolder); - if(paddArray.length == 1){ + if (paddArray.length == 1) { if (modifier.indexOf(placeHolder) === 0) { return val.slice(0, -paddArray[0].length); } else { @@ -12047,49 +13196,39 @@ angular.module("umbraco") } } } - - var addModifier = function(val, modifier){ + var addModifier = function (val, modifier) { if (!modifier || modifier.indexOf(placeHolder) < 0) { return val; } else { return modifier.replace(placeHolder, val); } }; - function gridItemHasConfig(styles, config) { - - if(_.isEmpty(styles) && _.isEmpty(config)) { + if (_.isEmpty(styles) && _.isEmpty(config)) { return false; } else { return true; } - } - // ********************************************* // Area management functions // ********************************************* - - $scope.clickCell = function(index, cells, row) { + $scope.clickCell = function (index, cells, row) { cells[index].active = true; row.hasActiveChild = true; }; - - $scope.clickOutsideCell = function(index, cells, row) { + $scope.clickOutsideCell = function (index, cells, row) { cells[index].active = false; row.hasActiveChild = hasActiveChild(row, cells); }; - $scope.cellPreview = function (cell) { if (cell && cell.$allowedEditors) { var editor = cell.$allowedEditors[0]; return editor.icon; } else { - return "icon-layout"; + return 'icon-layout'; } }; - - // ********************************************* // Control management functions // ********************************************* @@ -12097,108 +13236,84 @@ angular.module("umbraco") controls[index].active = true; cell.hasActiveChild = true; }; - $scope.clickOutsideControl = function (index, controls, cell) { controls[index].active = false; cell.hasActiveChild = hasActiveChild(cell, controls); }; - function hasActiveChild(item, children) { - var activeChild = false; - - for(var i = 0; children.length > i; i++) { + for (var i = 0; children.length > i; i++) { var child = children[i]; - - if(child.active) { + if (child.active) { activeChild = true; } } - - if(activeChild) { + if (activeChild) { return true; } - } - - - var guid = (function () { + var guid = function () { function s4() { - return Math.floor((1 + Math.random()) * 0x10000) - .toString(16) - .substring(1); + return Math.floor((1 + Math.random()) * 65536).toString(16).substring(1); } return function () { - return s4() + s4() + "-" + s4() + "-" + s4() + "-" + - s4() + "-" + s4() + s4() + s4(); + return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4(); }; - })(); - + }(); $scope.setUniqueId = function (cell, index) { return guid(); }; - $scope.addControl = function (editor, cell, index, initialize) { - - initialize = (initialize !== false); - + initialize = initialize !== false; var newControl = { value: null, editor: editor, $initializing: initialize }; - if (index === undefined) { index = cell.controls.length; } - newControl.active = true; - //populate control $scope.initControl(newControl, index + 1); - cell.controls.push(newControl); - + eventsService.emit('grid.itemAdded', { + scope: $scope, + element: $element, + cell: cell, + item: newControl + }); }; - $scope.addTinyMce = function (cell) { - var rte = $scope.getEditor("rte"); + var rte = $scope.getEditor('rte'); $scope.addControl(rte, cell); }; - $scope.getEditor = function (alias) { - return _.find($scope.availableEditors, function (editor) { return editor.alias === alias; }); + return _.find($scope.availableEditors, function (editor) { + return editor.alias === alias; + }); }; - $scope.removeControl = function (cell, $index) { $scope.currentControl = null; cell.controls.splice($index, 1); }; - $scope.percentage = function (spans) { - return ((spans / $scope.model.config.items.columns) * 100).toFixed(8); + return (spans / $scope.model.config.items.columns * 100).toFixed(8); }; - - $scope.clearPrompt = function (scopedObject, e) { scopedObject.deletePrompt = false; e.preventDefault(); e.stopPropagation(); }; - $scope.togglePrompt = function (scopedObject) { scopedObject.deletePrompt = !scopedObject.deletePrompt; }; - $scope.hidePrompt = function (scopedObject) { scopedObject.deletePrompt = false; }; - - $scope.toggleAddRow = function() { - $scope.showRowConfigurations = !$scope.showRowConfigurations; + $scope.toggleAddRow = function () { + $scope.showRowConfigurations = !$scope.showRowConfigurations; }; - - // ********************************************* // Initialization // these methods are called from ng-init on the template @@ -12207,57 +13322,43 @@ angular.module("umbraco") // intialization sets non-saved data like percentage sizing, allowed editors and // other data that should all be pre-fixed with $ to strip it out on save // ********************************************* - // ********************************************* // Init template + sections // ********************************************* $scope.initContent = function () { var clear = true; - //settings indicator shortcut - if ( ($scope.model.config.items.config && $scope.model.config.items.config.length > 0) || ($scope.model.config.items.styles && $scope.model.config.items.styles.length > 0)) { + if ($scope.model.config.items.config && $scope.model.config.items.config.length > 0 || $scope.model.config.items.styles && $scope.model.config.items.styles.length > 0) { $scope.hasSettings = true; } - //ensure the grid has a column value set, //if nothing is found, set it to 12 - if ($scope.model.config.items.columns && angular.isString($scope.model.config.items.columns)) { - $scope.model.config.items.columns = parseInt($scope.model.config.items.columns); - } else { + if (!$scope.model.config.items.columns) { $scope.model.config.items.columns = 12; + } else if (angular.isString($scope.model.config.items.columns)) { + $scope.model.config.items.columns = parseInt($scope.model.config.items.columns); } - if ($scope.model.value && $scope.model.value.sections && $scope.model.value.sections.length > 0 && $scope.model.value.sections[0].rows && $scope.model.value.sections[0].rows.length > 0) { - if ($scope.model.value.name && angular.isArray($scope.model.config.items.templates)) { - //This will occur if it is an existing value, in which case // we need to determine which layout was applied by looking up // the name // TODO: We need to change this to an immutable ID!! - var found = _.find($scope.model.config.items.templates, function (t) { return t.name === $scope.model.value.name; }); - if (found && angular.isArray(found.sections) && found.sections.length === $scope.model.value.sections.length) { - //Cool, we've found the template associated with our current value with matching sections counts, now we need to // merge this template data on to our current value (as if it was new) so that we can preserve what is and isn't // allowed for this template based on the current config. - _.each(found.sections, function (templateSection, index) { angular.extend($scope.model.value.sections[index], angular.copy(templateSection)); }); - } } - _.forEach($scope.model.value.sections, function (section, index) { - if (section.grid > 0) { $scope.initSection(section); - //we do this to ensure that the grid can be reset by deleting the last row if (section.rows.length > 0) { clear = false; @@ -12270,27 +13371,22 @@ angular.module("umbraco") $scope.addTemplate($scope.model.config.items.templates[0]); clear = false; } - if (clear) { $scope.model.value = undefined; } }; - $scope.initSection = function (section) { section.$percentage = $scope.percentage(section.grid); - section.$allowedLayouts = getAllowedLayouts(section); - if (!section.rows || section.rows.length === 0) { section.rows = []; - if(section.$allowedLayouts.length === 1){ - $scope.addRow(section, section.$allowedLayouts[0]); + if (section.$allowedLayouts.length === 1) { + $scope.addRow(section, section.$allowedLayouts[0], true); } } else { _.forEach(section.rows, function (row, index) { if (!row.$initialized) { var initd = $scope.initRow(row); - //if init fails, remove if (!initd) { section.rows.splice(index, 1); @@ -12299,22 +13395,19 @@ angular.module("umbraco") } } }); - // if there is more than one row added - hide row add tools $scope.showRowConfigurations = false; } }; - - // ********************************************* // Init layout / row // ********************************************* $scope.initRow = function (row) { - //merge the layout data with the original config data //if there are no config info on this, splice it out - var original = _.find($scope.model.config.items.layouts, function (o) { return o.name === row.name; }); - + var original = _.find($scope.model.config.items.layouts, function (o) { + return o.name === row.name; + }); if (!original) { return null; } else { @@ -12323,21 +13416,15 @@ angular.module("umbraco") original.styles = row.styles; original.config = row.config; original.hasConfig = gridItemHasConfig(row.styles, row.config); - - //sync area configuration _.each(original.areas, function (area, areaIndex) { - - if (area.grid > 0) { var currentArea = row.areas[areaIndex]; - if (currentArea) { area.config = currentArea.config; area.styles = currentArea.styles; area.hasConfig = gridItemHasConfig(currentArea.styles, currentArea.config); } - //set editor permissions if (!area.allowed || area.allowAll === true) { area.$allowedEditors = $scope.availableEditors; @@ -12346,133 +13433,112 @@ angular.module("umbraco") area.$allowedEditors = _.filter($scope.availableEditors, function (editor) { return _.indexOf(area.allowed, editor.alias) >= 0; }); - - if (_.indexOf(area.allowed, "rte") >= 0) { + if (_.indexOf(area.allowed, 'rte') >= 0) { area.$allowsRTE = true; } } - //copy over existing controls into the new areas if (row.areas.length > areaIndex && row.areas[areaIndex].controls) { area.controls = currentArea.controls; - _.forEach(area.controls, function (control, controlIndex) { $scope.initControl(control, controlIndex); }); - } else { //if empty area.controls = []; - //if only one allowed editor - if(area.$allowedEditors.length === 1){ + if (area.$allowedEditors.length === 1) { $scope.addControl(area.$allowedEditors[0], area, 0, false); } } - //set width area.$percentage = $scope.percentage(area.grid); area.$uniqueId = $scope.setUniqueId(); - } else { original.areas.splice(areaIndex, 1); } }); - //replace the old row original.$initialized = true; - //set a disposable unique ID original.$uniqueId = $scope.setUniqueId(); - //set a no disposable unique ID (util for row styling) original.id = !row.id ? $scope.setUniqueId() : row.id; - return original; } - }; - - // ********************************************* // Init control // ********************************************* - $scope.initControl = function (control, index) { control.$index = index; control.$uniqueId = $scope.setUniqueId(); - //error handling in case of missing editor.. //should only happen if stripped earlier if (!control.editor) { - control.$editorPath = "views/propertyeditors/grid/editors/error.html"; + control.$editorPath = 'views/propertyeditors/grid/editors/error.html'; } - if (!control.$editorPath) { var editorConfig = $scope.getEditor(control.editor.alias); - if (editorConfig) { control.editor = editorConfig; - //if its an absolute path - if (control.editor.view.startsWith("/") || control.editor.view.startsWith("~/")) { + if (control.editor.view.startsWith('/') || control.editor.view.startsWith('~/')) { control.$editorPath = umbRequestHelper.convertVirtualToAbsolutePath(control.editor.view); - } - else { + } else { //use convention - control.$editorPath = "views/propertyeditors/grid/editors/" + control.editor.view + ".html"; + control.$editorPath = 'views/propertyeditors/grid/editors/' + control.editor.view + '.html'; } - } - else { - control.$editorPath = "views/propertyeditors/grid/editors/error.html"; + } else { + control.$editorPath = 'views/propertyeditors/grid/editors/error.html'; } } - - }; - - gridService.getGridEditors().then(function (response) { $scope.availableEditors = response.data; - + //Localize the grid editor names + angular.forEach($scope.availableEditors, function (value, key) { + //If no translation is provided, keep using the editor name from the manifest + if (localizationService.dictionary.hasOwnProperty('grid_' + value.alias)) { + value.name = localizationService.localize('grid_' + value.alias); + } + }); $scope.contentReady = true; - // ********************************************* // Init grid // ********************************************* + eventsService.emit('grid.initializing', { + scope: $scope, + element: $element + }); $scope.initContent(); - + eventsService.emit('grid.initialized', { + scope: $scope, + element: $element + }); }); - //Clean the grid value before submitting to the server, we don't need // all of that grid configuration in the value to be stored!! All of that // needs to be merged in at runtime to ensure that the real config values are used // if they are ever updated. - - var unsubscribe = $scope.$on("formSubmitting", function () { - + var unsubscribe = $scope.$on('formSubmitting', function () { if ($scope.model.value && $scope.model.value.sections) { - _.each($scope.model.value.sections, function(section) { + _.each($scope.model.value.sections, function (section) { if (section.rows) { _.each(section.rows, function (row) { if (row.areas) { _.each(row.areas, function (area) { - //Remove the 'editors' - these are the allowed editors, these will // be injected at runtime to this editor, it should not be persisted - if (area.editors) { delete area.editors; } - if (area.controls) { _.each(area.controls, function (control) { if (control.editor) { //replace var alias = control.editor.alias; - control.editor = { - alias: alias - }; + control.editor = { alias: alias }; } }); } @@ -12483,327 +13549,234 @@ angular.module("umbraco") }); } }); - - //when the scope is destroyed we need to unsubscribe - $scope.$on("$destroy", function () { - unsubscribe(); - }); - - }); - -angular.module("umbraco") - .controller("Umbraco.PropertyEditors.GridPrevalueEditorController", - function ($scope, $http, assetsService, $rootScope, dialogService, mediaResource, gridService, imageHelper, $timeout) { - - var emptyModel = { - styles:[ - { - label: "Set a background image", - description: "Set a row background", - key: "background-image", - view: "imagepicker", - modifier: "url({0})" - } - ], - - config:[ - { - label: "Class", - description: "Set a css class", - key: "class", - view: "textstring" - } - ], - - columns: 12, - templates:[ - { - name: "1 column layout", - sections: [ - { - grid: 12, - } - ] - }, - { - name: "2 column layout", - sections: [ - { - grid: 4, - }, - { - grid: 8 - } - ] - } - ], - - - layouts:[ - { - label: "Headline", - name: "Headline", - areas: [ - { - grid: 12, - editors: ["headline"] - } - ] - }, - { - label: "Article", - name: "Article", - areas: [ - { - grid: 4 - }, - { - grid: 8 - } - ] - } - ] - }; - - /**************** - template - *****************/ - - $scope.configureTemplate = function(template) { - - var templatesCopy = angular.copy($scope.model.value.templates); - - if (template === undefined) { - template = { - name: "", - sections: [ - - ] - }; - $scope.model.value.templates.push(template); - } - - $scope.layoutConfigOverlay = {}; - $scope.layoutConfigOverlay.view = "views/propertyEditors/grid/dialogs/layoutconfig.html"; - $scope.layoutConfigOverlay.currentLayout = template; - $scope.layoutConfigOverlay.rows = $scope.model.value.layouts; - $scope.layoutConfigOverlay.columns = $scope.model.value.columns; - $scope.layoutConfigOverlay.show = true; - - $scope.layoutConfigOverlay.submit = function(model) { - $scope.layoutConfigOverlay.show = false; - $scope.layoutConfigOverlay = null; - }; - - $scope.layoutConfigOverlay.close = function(oldModel) { - - //reset templates - $scope.model.value.templates = templatesCopy; - - $scope.layoutConfigOverlay.show = false; - $scope.layoutConfigOverlay = null; - } - - }; - - $scope.deleteTemplate = function(index){ - $scope.model.value.templates.splice(index, 1); - }; - - - /**************** - Row - *****************/ - - $scope.configureLayout = function(layout) { - - var layoutsCopy = angular.copy($scope.model.value.layouts); - - if(layout === undefined){ - layout = { - name: "", - areas:[ - - ] - }; - $scope.model.value.layouts.push(layout); - } - - $scope.rowConfigOverlay = {}; - $scope.rowConfigOverlay.view = "views/propertyEditors/grid/dialogs/rowconfig.html"; - $scope.rowConfigOverlay.currentRow = layout; - $scope.rowConfigOverlay.editors = $scope.editors; - $scope.rowConfigOverlay.columns = $scope.model.value.columns; - $scope.rowConfigOverlay.show = true; - - $scope.rowConfigOverlay.submit = function(model) { - $scope.rowConfigOverlay.show = false; - $scope.rowConfigOverlay = null; - }; - - $scope.rowConfigOverlay.close = function(oldModel) { - $scope.model.value.layouts = layoutsCopy; - $scope.rowConfigOverlay.show = false; - $scope.rowConfigOverlay = null; - }; - - }; - - //var rowDeletesPending = false; - $scope.deleteLayout = function(index) { - - $scope.rowDeleteOverlay = {}; - $scope.rowDeleteOverlay.view = "views/propertyEditors/grid/dialogs/rowdeleteconfirm.html"; - $scope.rowDeleteOverlay.dialogData = { - rowName: $scope.model.value.layouts[index].name - }; - $scope.rowDeleteOverlay.show = true; - - $scope.rowDeleteOverlay.submit = function(model) { - - $scope.model.value.layouts.splice(index, 1); - - $scope.rowDeleteOverlay.show = false; - $scope.rowDeleteOverlay = null; - }; - - $scope.rowDeleteOverlay.close = function(oldModel) { - $scope.rowDeleteOverlay.show = false; - $scope.rowDeleteOverlay = null; - }; - - }; - - - /**************** - utillities - *****************/ - $scope.toggleCollection = function(collection, toggle){ - if(toggle){ - collection = []; - }else{ - delete collection; - } - }; - - $scope.percentage = function(spans){ - return ((spans / $scope.model.value.columns) * 100).toFixed(8); - }; - - $scope.zeroWidthFilter = function (cell) { - return cell.grid > 0; - }; - - /**************** - Config - *****************/ - - $scope.removeConfigValue = function(collection, index){ - collection.splice(index, 1); - }; - - var editConfigCollection = function(configValues, title, callback) { - - $scope.editConfigCollectionOverlay = {}; - $scope.editConfigCollectionOverlay.view = "views/propertyeditors/grid/dialogs/editconfig.html"; - $scope.editConfigCollectionOverlay.config = configValues; - $scope.editConfigCollectionOverlay.title = title; - $scope.editConfigCollectionOverlay.show = true; - - $scope.editConfigCollectionOverlay.submit = function(model) { - - callback(model.config) - - $scope.editConfigCollectionOverlay.show = false; - $scope.editConfigCollectionOverlay = null; - }; - - $scope.editConfigCollectionOverlay.close = function(oldModel) { - $scope.editConfigCollectionOverlay.show = false; - $scope.editConfigCollectionOverlay = null; - }; - - }; - - $scope.editConfig = function() { - editConfigCollection($scope.model.value.config, "Settings", function(data) { - $scope.model.value.config = data; - }); - }; - - $scope.editStyles = function() { - editConfigCollection($scope.model.value.styles, "Styling", function(data){ - $scope.model.value.styles = data; - }); - }; - - /**************** - editors - *****************/ - gridService.getGridEditors().then(function(response){ - $scope.editors = response.data; - }); - - - /* init grid data */ - if (!$scope.model.value || $scope.model.value === "" || !$scope.model.value.templates) { - $scope.model.value = emptyModel; - } else { - - if (!$scope.model.value.columns) { - $scope.model.value.columns = emptyModel.columns; - } - - - if (!$scope.model.value.config) { - $scope.model.value.config = []; - } - - if (!$scope.model.value.styles) { - $scope.model.value.styles = []; - } - } - - /**************** - Clean up - *****************/ - var unsubscribe = $scope.$on("formSubmitting", function (ev, args) { - var ts = $scope.model.value.templates; - var ls = $scope.model.value.layouts; - - _.each(ts, function(t){ - _.each(t.sections, function(section, index){ - if(section.grid === 0){ - t.sections.splice(index, 1); - } - }); - }); - - _.each(ls, function(l){ - _.each(l.areas, function(area, index){ - if(area.grid === 0){ - l.areas.splice(index, 1); - } - }); - }); - }); - //when the scope is destroyed we need to unsubscribe $scope.$on('$destroy', function () { unsubscribe(); }); - }); - -//this controller simply tells the dialogs service to open a mediaPicker window -//with a specified callback, this callback will receive an object with a selection on it -angular.module('umbraco') - .controller("Umbraco.PropertyEditors.ImageCropperController", - function ($rootScope, $routeParams, $scope, $log, mediaHelper, cropperHelper, $timeout, editorState, umbRequestHelper, fileManager, angularHelper) { - + angular.module('umbraco').controller('Umbraco.PropertyEditors.GridPrevalueEditorController', function ($scope, $http, assetsService, $rootScope, dialogService, mediaResource, gridService, imageHelper, $timeout) { + var emptyModel = { + styles: [{ + label: 'Set a background image', + description: 'Set a row background', + key: 'background-image', + view: 'imagepicker', + modifier: 'url({0})' + }], + config: [{ + label: 'Class', + description: 'Set a css class', + key: 'class', + view: 'textstring' + }], + columns: 12, + templates: [ + { + name: '1 column layout', + sections: [{ grid: 12 }] + }, + { + name: '2 column layout', + sections: [ + { grid: 4 }, + { grid: 8 } + ] + } + ], + layouts: [ + { + label: 'Headline', + name: 'Headline', + areas: [{ + grid: 12, + editors: ['headline'] + }] + }, + { + label: 'Article', + name: 'Article', + areas: [ + { grid: 4 }, + { grid: 8 } + ] + } + ] + }; + /**************** + template + *****************/ + $scope.configureTemplate = function (template) { + var templatesCopy = angular.copy($scope.model.value.templates); + if (template === undefined) { + template = { + name: '', + sections: [] + }; + $scope.model.value.templates.push(template); + } + $scope.layoutConfigOverlay = {}; + $scope.layoutConfigOverlay.view = 'views/propertyEditors/grid/dialogs/layoutconfig.html'; + $scope.layoutConfigOverlay.currentLayout = template; + $scope.layoutConfigOverlay.rows = $scope.model.value.layouts; + $scope.layoutConfigOverlay.columns = $scope.model.value.columns; + $scope.layoutConfigOverlay.show = true; + $scope.layoutConfigOverlay.submit = function (model) { + $scope.layoutConfigOverlay.show = false; + $scope.layoutConfigOverlay = null; + }; + $scope.layoutConfigOverlay.close = function (oldModel) { + //reset templates + $scope.model.value.templates = templatesCopy; + $scope.layoutConfigOverlay.show = false; + $scope.layoutConfigOverlay = null; + }; + }; + $scope.deleteTemplate = function (index) { + $scope.model.value.templates.splice(index, 1); + }; + /**************** + Row + *****************/ + $scope.configureLayout = function (layout) { + var layoutsCopy = angular.copy($scope.model.value.layouts); + if (layout === undefined) { + layout = { + name: '', + areas: [] + }; + $scope.model.value.layouts.push(layout); + } + $scope.rowConfigOverlay = {}; + $scope.rowConfigOverlay.view = 'views/propertyEditors/grid/dialogs/rowconfig.html'; + $scope.rowConfigOverlay.currentRow = layout; + $scope.rowConfigOverlay.editors = $scope.editors; + $scope.rowConfigOverlay.columns = $scope.model.value.columns; + $scope.rowConfigOverlay.show = true; + $scope.rowConfigOverlay.submit = function (model) { + $scope.rowConfigOverlay.show = false; + $scope.rowConfigOverlay = null; + }; + $scope.rowConfigOverlay.close = function (oldModel) { + $scope.model.value.layouts = layoutsCopy; + $scope.rowConfigOverlay.show = false; + $scope.rowConfigOverlay = null; + }; + }; + //var rowDeletesPending = false; + $scope.deleteLayout = function (index) { + $scope.rowDeleteOverlay = {}; + $scope.rowDeleteOverlay.view = 'views/propertyEditors/grid/dialogs/rowdeleteconfirm.html'; + $scope.rowDeleteOverlay.dialogData = { rowName: $scope.model.value.layouts[index].name }; + $scope.rowDeleteOverlay.show = true; + $scope.rowDeleteOverlay.submit = function (model) { + $scope.model.value.layouts.splice(index, 1); + $scope.rowDeleteOverlay.show = false; + $scope.rowDeleteOverlay = null; + }; + $scope.rowDeleteOverlay.close = function (oldModel) { + $scope.rowDeleteOverlay.show = false; + $scope.rowDeleteOverlay = null; + }; + }; + /**************** + utillities + *****************/ + $scope.toggleCollection = function (collection, toggle) { + if (toggle) { + collection = []; + } else { + delete collection; + } + }; + $scope.percentage = function (spans) { + return (spans / $scope.model.value.columns * 100).toFixed(8); + }; + $scope.zeroWidthFilter = function (cell) { + return cell.grid > 0; + }; + /**************** + Config + *****************/ + $scope.removeConfigValue = function (collection, index) { + collection.splice(index, 1); + }; + var editConfigCollection = function (configValues, title, callback) { + $scope.editConfigCollectionOverlay = {}; + $scope.editConfigCollectionOverlay.view = 'views/propertyeditors/grid/dialogs/editconfig.html'; + $scope.editConfigCollectionOverlay.config = configValues; + $scope.editConfigCollectionOverlay.title = title; + $scope.editConfigCollectionOverlay.show = true; + $scope.editConfigCollectionOverlay.submit = function (model) { + callback(model.config); + $scope.editConfigCollectionOverlay.show = false; + $scope.editConfigCollectionOverlay = null; + }; + $scope.editConfigCollectionOverlay.close = function (oldModel) { + $scope.editConfigCollectionOverlay.show = false; + $scope.editConfigCollectionOverlay = null; + }; + }; + $scope.editConfig = function () { + editConfigCollection($scope.model.value.config, 'Settings', function (data) { + $scope.model.value.config = data; + }); + }; + $scope.editStyles = function () { + editConfigCollection($scope.model.value.styles, 'Styling', function (data) { + $scope.model.value.styles = data; + }); + }; + /**************** + editors + *****************/ + gridService.getGridEditors().then(function (response) { + $scope.editors = response.data; + }); + /* init grid data */ + if (!$scope.model.value || $scope.model.value === '' || !$scope.model.value.templates) { + $scope.model.value = emptyModel; + } else { + if (!$scope.model.value.columns) { + $scope.model.value.columns = emptyModel.columns; + } + if (!$scope.model.value.config) { + $scope.model.value.config = []; + } + if (!$scope.model.value.styles) { + $scope.model.value.styles = []; + } + } + /**************** + Clean up + *****************/ + var unsubscribe = $scope.$on('formSubmitting', function (ev, args) { + var ts = $scope.model.value.templates; + var ls = $scope.model.value.layouts; + _.each(ts, function (t) { + _.each(t.sections, function (section, index) { + if (section.grid === 0) { + t.sections.splice(index, 1); + } + }); + }); + _.each(ls, function (l) { + _.each(l.areas, function (area, index) { + if (area.grid === 0) { + l.areas.splice(index, 1); + } + }); + }); + }); + //when the scope is destroyed we need to unsubscribe + $scope.$on('$destroy', function () { + unsubscribe(); + }); + }); + //this controller simply tells the dialogs service to open a mediaPicker window + //with a specified callback, this callback will receive an object with a selection on it + angular.module('umbraco').controller('Umbraco.PropertyEditors.ImageCropperController', function ($rootScope, $routeParams, $scope, $log, mediaHelper, cropperHelper, $timeout, editorState, umbRequestHelper, fileManager, angularHelper) { var config = angular.copy($scope.model.config); $scope.imageIsLoaded = false; - //move previously saved value to the editor if ($scope.model.value) { //backwards compat with the old file upload (incase some-one swaps them..) @@ -12813,52 +13786,47 @@ angular.module('umbraco') } else if ($scope.model.value.crops) { //sync any config changes with the editor and drop outdated crops _.each($scope.model.value.crops, function (saved) { - var configured = _.find(config.crops, function (item) { return item.alias === saved.alias }); - + var configured = _.find(config.crops, function (item) { + return item.alias === saved.alias; + }); if (configured && configured.height === saved.height && configured.width === saved.width) { configured.coordinates = saved.coordinates; } }); $scope.model.value.crops = config.crops; - //restore focalpoint if missing if (!$scope.model.value.focalPoint) { - $scope.model.value.focalPoint = { left: 0.5, top: 0.5 }; + $scope.model.value.focalPoint = { + left: 0.5, + top: 0.5 + }; } } - $scope.imageSrc = $scope.model.value.src; } - - //crop a specific crop $scope.crop = function (crop) { $scope.currentCrop = crop; $scope.currentPoint = undefined; }; - //done cropping $scope.done = function () { $scope.currentCrop = undefined; $scope.currentPoint = undefined; }; - //crop a specific crop $scope.clear = function (crop) { //clear current uploaded files fileManager.setFiles($scope.model.alias, []); - //clear the ui $scope.imageSrc = undefined; if ($scope.model.value) { delete $scope.model.value; } - // set form to dirty to tricker discard changes dialog var currForm = angularHelper.getCurrentForm($scope); currForm.$setDirty(); }; - //show previews $scope.togglePreviews = function () { if ($scope.showPreviews) { @@ -12868,287 +13836,247 @@ angular.module('umbraco') $scope.showPreviews = true; } }; - - $scope.imageLoaded = function() { + $scope.imageLoaded = function () { $scope.imageIsLoaded = true; }; - //on image selected, update the cropper - $scope.$on("filesSelected", function (ev, args) { + $scope.$on('filesSelected', function (ev, args) { $scope.model.value = config; - if (args.files && args.files[0]) { - fileManager.setFiles($scope.model.alias, args.files); - var reader = new FileReader(); reader.onload = function (e) { - $scope.$apply(function () { $scope.imageSrc = e.target.result; }); - }; - reader.readAsDataURL(args.files[0]); } }); - - //here we declare a special method which will be called whenever the value has changed from the server $scope.model.onValueChanged = function (newVal, oldVal) { //clear current uploaded files fileManager.setFiles($scope.model.alias, []); }; - - var unsubscribe = $scope.$on("formSubmitting", function () { + var unsubscribe = $scope.$on('formSubmitting', function () { $scope.done(); }); - $scope.$on('$destroy', function () { unsubscribe(); }); - }) - .run(function (mediaHelper, umbRequestHelper) { + }).run(function (mediaHelper, umbRequestHelper) { if (mediaHelper && mediaHelper.registerFileResolver) { - //NOTE: The 'entity' can be either a normal media entity or an "entity" returned from the entityResource // they contain different data structures so if we need to query against it we need to be aware of this. - mediaHelper.registerFileResolver("Umbraco.ImageCropper", function (property, entity, thumbnail) { + mediaHelper.registerFileResolver('Umbraco.ImageCropper', function (property, entity, thumbnail) { if (property.value && property.value.src) { - if (thumbnail === true) { - return property.value.src + "?width=500&mode=max&animationprocessmode=first"; - } - else { + return property.value.src + '?width=500&mode=max&animationprocessmode=first'; + } else { return property.value.src; - } - - //this is a fallback in case the cropper has been asssigned a upload field - } - else if (angular.isString(property.value)) { + } //this is a fallback in case the cropper has been asssigned a upload field + } else if (angular.isString(property.value)) { if (thumbnail) { - if (mediaHelper.detectIfImageByExtension(property.value)) { - - var thumbnailUrl = umbRequestHelper.getApiUrl( - "imagesApiBaseUrl", - "GetBigThumbnail", - [{ originalImagePath: property.value }]); - + var thumbnailUrl = umbRequestHelper.getApiUrl('imagesApiBaseUrl', 'GetBigThumbnail', [{ originalImagePath: property.value }]); return thumbnailUrl; - } - else { + } else { return null; } - - } - else { + } else { return property.value; } } - return null; }); } }); - -angular.module("umbraco").controller("Umbraco.PrevalueEditors.CropSizesController", - function ($scope, $timeout) { - - if (!$scope.model.value) { - $scope.model.value = []; - } - - $scope.remove = function (item, evt) { - evt.preventDefault(); - $scope.model.value = _.reject($scope.model.value, function (x) { - return x.alias === item.alias; - }); - }; - - $scope.edit = function (item, evt) { - evt.preventDefault(); - $scope.newItem = item; - }; - - $scope.cancel = function (evt) { - evt.preventDefault(); - $scope.newItem = null; - }; - - $scope.add = function (evt) { - evt.preventDefault(); - - if ($scope.newItem && $scope.newItem.alias && - angular.isNumber($scope.newItem.width) && angular.isNumber($scope.newItem.height) && - $scope.newItem.width > 0 && $scope.newItem.height > 0) { - - var exists = _.find($scope.model.value, function (item) { return $scope.newItem.alias === item.alias; }); - if (!exists) { - $scope.model.value.push($scope.newItem); - $scope.newItem = {}; - $scope.hasError = false; - return; - } - } - - //there was an error, do the highlight (will be set back by the directive) - $scope.hasError = true; - }; - }); -function includePropsPreValsController($rootScope, $scope, localizationService, contentTypeResource) { - - if (!$scope.model.value) { - $scope.model.value = []; - } - - $scope.propertyAliases = []; - $scope.selectedField = null; - $scope.systemFields = [ - { value: "sortOrder" }, - { value: "updateDate" }, - { value: "updater" }, - { value: "createDate" }, - { value: "owner" }, - { value: "published"}, - { value: "contentTypeAlias" }, - { value: "email" }, - { value: "username" } - ]; - - $scope.getLocalizedKey = function(alias) { - switch (alias) { - case "name": - return "general_name"; - case "sortOrder": - return "general_sort"; - case "updateDate": - return "content_updateDate"; - case "updater": - return "content_updatedBy"; - case "createDate": - return "content_createDate"; - case "owner": - return "content_createBy"; - case "published": - return "content_isPublished"; - case "contentTypeAlias": - //NOTE: This will just be 'Document' type even if it's for media/members since this is just a pre-val editor and we don't have a key for 'Content Type Alias' - return "content_documentType"; - case "email": - return "general_email"; - case "username": - return "general_username"; + angular.module('umbraco').controller('Umbraco.PrevalueEditors.CropSizesController', function ($scope, $timeout) { + if (!$scope.model.value) { + $scope.model.value = []; } - return alias; - } - - $scope.removeField = function(e) { - $scope.model.value = _.reject($scope.model.value, function (x) { - return x.alias === e.alias; - }); - } - - //now we'll localize these strings, for some reason the directive doesn't work inside of the select group with an ng-model declared - _.each($scope.systemFields, function (e, i) { - var key = $scope.getLocalizedKey(e.value); - localizationService.localize(key).then(function (v) { - e.name = v; - - switch (e.value) { - case "updater": - e.name += " (Content only)"; - break; - case "published": - e.name += " (Content only)"; - break; - case "email": - e.name += " (Members only)"; - break; - case "username": - e.name += " (Members only)"; - break; - } - - }); - }); - - // Return a helper with preserved width of cells - var fixHelper = function (e, ui) { - var h = ui.clone(); - - h.children().each(function () { - $(this).width($(this).width()); - }); - h.css("background-color", "lightgray"); - - return h; - }; - - $scope.sortableOptions = { - helper: fixHelper, - handle: ".handle", - opacity: 0.5, - axis: 'y', - containment: 'parent', - cursor: 'move', - items: '> tr', - tolerance: 'pointer', - update: function (e, ui) { - - // Get the new and old index for the moved element (using the text as the identifier) - var newIndex = ui.item.index(); - var movedAlias = $('.alias-value', ui.item).text().trim(); - var originalIndex = getAliasIndexByText(movedAlias); - - // Move the element in the model - if (originalIndex > -1) { - var movedElement = $scope.model.value[originalIndex]; - $scope.model.value.splice(originalIndex, 1); - $scope.model.value.splice(newIndex, 0, movedElement); - } - } - }; - - contentTypeResource.getAllPropertyTypeAliases().then(function(data) { - $scope.propertyAliases = data; - }); - - $scope.addField = function () { - - var val = $scope.selectedField; - var isSystem = val.startsWith("_system_"); - if (isSystem) { - val = val.trimStart("_system_"); - } - - var exists = _.find($scope.model.value, function (i) { - return i.alias === val; - }); - if (!exists) { - $scope.model.value.push({ - alias: val, - isSystem: isSystem ? 1 : 0 + $scope.remove = function (item, evt) { + evt.preventDefault(); + $scope.model.value = _.reject($scope.model.value, function (x) { + return x.alias === item.alias; }); - } - } - - function getAliasIndexByText(value) { - for (var i = 0; i < $scope.model.value.length; i++) { - if ($scope.model.value[i].alias === value) { - return i; + }; + $scope.edit = function (item, evt) { + evt.preventDefault(); + $scope.newItem = item; + }; + $scope.cancel = function (evt) { + evt.preventDefault(); + $scope.newItem = null; + }; + $scope.add = function (evt) { + evt.preventDefault(); + if ($scope.newItem && $scope.newItem.alias && angular.isNumber($scope.newItem.width) && angular.isNumber($scope.newItem.height) && $scope.newItem.width > 0 && $scope.newItem.height > 0) { + var exists = _.find($scope.model.value, function (item) { + return $scope.newItem.alias === item.alias; + }); + if (!exists) { + $scope.model.value.push($scope.newItem); + $scope.newItem = {}; + $scope.hasError = false; + return; + } } + //there was an error, do the highlight (will be set back by the directive) + $scope.hasError = true; + }; + }); + function includePropsPreValsController($rootScope, $scope, localizationService, contentTypeResource) { + if (!$scope.model.value) { + $scope.model.value = []; + } + $scope.hasError = false; + $scope.errorMsg = ''; + $scope.propertyAliases = []; + $scope.selectedField = null; + $scope.systemFields = [ + { value: 'sortOrder' }, + { value: 'updateDate' }, + { value: 'updater' }, + { value: 'createDate' }, + { value: 'owner' }, + { value: 'published' }, + { value: 'contentTypeAlias' }, + { value: 'email' }, + { value: 'username' } + ]; + $scope.getLocalizedKey = function (alias) { + switch (alias) { + case 'name': + return 'general_name'; + case 'sortOrder': + return 'general_sort'; + case 'updateDate': + return 'content_updateDate'; + case 'updater': + return 'content_updatedBy'; + case 'createDate': + return 'content_createDate'; + case 'owner': + return 'content_createBy'; + case 'published': + return 'content_isPublished'; + case 'contentTypeAlias': + //NOTE: This will just be 'Document' type even if it's for media/members since this is just a pre-val editor and we don't have a key for 'Content Type Alias' + return 'content_documentType'; + case 'email': + return 'general_email'; + case 'username': + return 'general_username'; + } + return alias; + }; + $scope.changeField = function () { + $scope.hasError = false; + $scope.errorMsg = ''; + }; + $scope.removeField = function (e) { + $scope.model.value = _.reject($scope.model.value, function (x) { + return x.alias === e.alias; + }); + }; + //now we'll localize these strings, for some reason the directive doesn't work inside of the select group with an ng-model declared + _.each($scope.systemFields, function (e, i) { + var key = $scope.getLocalizedKey(e.value); + localizationService.localize(key).then(function (v) { + e.name = v; + switch (e.value) { + case 'updater': + e.name += ' (Content only)'; + break; + case 'published': + e.name += ' (Content only)'; + break; + case 'email': + e.name += ' (Members only)'; + break; + case 'username': + e.name += ' (Members only)'; + break; + } + }); + }); + // Return a helper with preserved width of cells + var fixHelper = function (e, ui) { + ui.children().each(function () { + $(this).width($(this).width()); + }); + var row = ui.clone(); + row.css('background-color', 'lightgray'); + return row; + }; + $scope.sortableOptions = { + helper: fixHelper, + handle: '.handle', + opacity: 0.5, + axis: 'y', + containment: 'parent', + cursor: 'move', + items: '> tr', + tolerance: 'pointer', + forcePlaceholderSize: true, + start: function (e, ui) { + ui.placeholder.height(ui.item.height()); + }, + update: function (e, ui) { + // Get the new and old index for the moved element (using the text as the identifier) + var newIndex = ui.item.index(); + var movedAlias = $('.alias-value', ui.item).text().trim(); + var originalIndex = getAliasIndexByText(movedAlias); + // Move the element in the model + if (originalIndex > -1) { + var movedElement = $scope.model.value[originalIndex]; + $scope.model.value.splice(originalIndex, 1); + $scope.model.value.splice(newIndex, 0, movedElement); + } + } + }; + contentTypeResource.getAllPropertyTypeAliases().then(function (data) { + $scope.propertyAliases = data; + }); + $scope.addField = function () { + var val = $scope.selectedField; + if (val) { + var isSystem = val.startsWith('_system_'); + if (isSystem) { + val = val.trimStart('_system_'); + } + var exists = _.find($scope.model.value, function (i) { + return i.alias === val; + }); + if (!exists) { + $scope.hasError = false; + $scope.errorMsg = ''; + $scope.model.value.push({ + alias: val, + isSystem: isSystem ? 1 : 0 + }); + } else { + //there was an error, do the highlight (will be set back by the directive) + $scope.hasError = true; + $scope.errorMsg = 'Property is already added'; + } + } else { + $scope.hasError = true; + $scope.errorMsg = 'No property selected'; + } + }; + function getAliasIndexByText(value) { + for (var i = 0; i < $scope.model.value.length; i++) { + if ($scope.model.value[i].alias === value) { + return i; + } + } + return -1; } - - return -1; } - -} - - -angular.module("umbraco").controller("Umbraco.PrevalueEditors.IncludePropertiesListViewController", includePropsPreValsController); -/** + angular.module('umbraco').controller('Umbraco.PrevalueEditors.IncludePropertiesListViewController', includePropsPreValsController); + /** * @ngdoc controller * @name Umbraco.PrevalueEditors.ListViewLayoutsPreValsController * @function @@ -13156,88 +14084,66 @@ angular.module("umbraco").controller("Umbraco.PrevalueEditors.IncludePropertiesL * @description * The controller for configuring layouts for list views */ -(function() { - "use strict"; - - function ListViewLayoutsPreValsController($scope) { - - var vm = this; - vm.focusLayoutName = false; - - vm.layoutsSortableOptions = { - distance: 10, - tolerance: "pointer", - opacity: 0.7, - scroll: true, - cursor: "move", - handle: ".list-view-layout__sort-handle" - }; - - vm.addLayout = addLayout; - vm.showPrompt = showPrompt; - vm.hidePrompt = hidePrompt; - vm.removeLayout = removeLayout; - vm.openIconPicker = openIconPicker; - - function activate() { - - - - } - - function addLayout() { - - vm.focusLayoutName = false; - - var layout = { - "name": "", - "path": "", - "icon": "icon-stop", - "selected": true - }; - - $scope.model.value.push(layout); - - } - - function showPrompt(layout) { - layout.deletePrompt = true; - } - - function hidePrompt(layout) { - layout.deletePrompt = false; - } - - function removeLayout($index, layout) { - $scope.model.value.splice($index, 1); - } - - function openIconPicker(layout) { - vm.iconPickerDialog = { - view: "iconpicker", - show: true, - submit: function(model) { - if (model.color) { - layout.icon = model.icon + " " + model.color; - } else { - layout.icon = model.icon; - } - vm.focusLayoutName = true; - vm.iconPickerDialog.show = false; - vm.iconPickerDialog = null; - } - }; - } - - activate(); - - } - - angular.module("umbraco").controller("Umbraco.PrevalueEditors.ListViewLayoutsPreValsController", ListViewLayoutsPreValsController); - -})(); - -/** + (function () { + 'use strict'; + function ListViewLayoutsPreValsController($scope) { + var vm = this; + vm.focusLayoutName = false; + vm.layoutsSortableOptions = { + distance: 10, + tolerance: 'pointer', + opacity: 0.7, + scroll: true, + cursor: 'move', + handle: '.list-view-layout__sort-handle' + }; + vm.addLayout = addLayout; + vm.showPrompt = showPrompt; + vm.hidePrompt = hidePrompt; + vm.removeLayout = removeLayout; + vm.openIconPicker = openIconPicker; + function activate() { + } + function addLayout() { + vm.focusLayoutName = false; + var layout = { + 'name': '', + 'path': '', + 'icon': 'icon-stop', + 'selected': true + }; + $scope.model.value.push(layout); + } + function showPrompt(layout) { + layout.deletePrompt = true; + } + function hidePrompt(layout) { + layout.deletePrompt = false; + } + function removeLayout($index, layout) { + $scope.model.value.splice($index, 1); + } + function openIconPicker(layout) { + vm.iconPickerDialog = { + view: 'iconpicker', + show: true, + submit: function (model) { + if (model.color) { + layout.icon = model.icon + ' ' + model.color; + } else { + layout.icon = model.icon; + } + vm.focusLayoutName = true; + vm.iconPickerDialog.show = false; + vm.iconPickerDialog = null; + } + }; + } + activate(); + } + angular.module('umbraco').controller('Umbraco.PrevalueEditors.ListViewLayoutsPreValsController', ListViewLayoutsPreValsController); + }()); + /** * @ngdoc controller * @name Umbraco.Editors.DocumentType.EditController * @function @@ -13245,2256 +14151,2456 @@ angular.module("umbraco").controller("Umbraco.PrevalueEditors.IncludePropertiesL * @description * The controller for the content type editor */ -(function() { - "use strict"; - - function ListViewGridLayoutController($scope, $routeParams, mediaHelper, mediaResource, $location, listViewHelper, mediaTypeHelper) { - - var vm = this; - - vm.nodeId = $scope.contentId; - //we pass in a blacklist by adding ! to the file extensions, allowing everything EXCEPT for disallowedUploadFiles - vm.acceptedFileTypes = !mediaHelper.formatFileTypes(Umbraco.Sys.ServerVariables.umbracoSettings.disallowedUploadFiles); - vm.maxFileSize = Umbraco.Sys.ServerVariables.umbracoSettings.maxFileSize + "KB"; - vm.activeDrag = false; - vm.mediaDetailsTooltip = {}; - vm.itemsWithoutFolders = []; - vm.isRecycleBin = $scope.contentId === '-21' || $scope.contentId === '-20'; - vm.acceptedMediatypes = []; - - vm.dragEnter = dragEnter; - vm.dragLeave = dragLeave; - vm.onFilesQueue = onFilesQueue; - vm.onUploadComplete = onUploadComplete; - - vm.hoverMediaItemDetails = hoverMediaItemDetails; - vm.selectContentItem = selectContentItem; - vm.selectItem = selectItem; - vm.selectFolder = selectFolder; - vm.goToItem = goToItem; - - function activate() { - vm.itemsWithoutFolders = filterOutFolders($scope.items); - - if($scope.entityType === 'media') { - mediaTypeHelper.getAllowedImagetypes(vm.nodeId).then(function (types) { - vm.acceptedMediatypes = types; - }); - } - - } - - function filterOutFolders(items) { - - var newArray = []; - - if(items && items.length ) { - - for (var i = 0; items.length > i; i++) { - var item = items[i]; - var isFolder = !mediaHelper.hasFilePropertyType(item); - - if (!isFolder) { - newArray.push(item); - } - } - - } - - return newArray; - } - - function dragEnter(el, event) { - vm.activeDrag = true; - } - - function dragLeave(el, event) { - vm.activeDrag = false; - } - - function onFilesQueue() { - vm.activeDrag = false; - } - - function onUploadComplete() { - $scope.getContent($scope.contentId); - } - - function hoverMediaItemDetails(item, event, hover) { - - if (hover && !vm.mediaDetailsTooltip.show) { - - vm.mediaDetailsTooltip.event = event; - vm.mediaDetailsTooltip.item = item; - vm.mediaDetailsTooltip.show = true; - - } else if (!hover && vm.mediaDetailsTooltip.show) { - - vm.mediaDetailsTooltip.show = false; - - } - - } - - function selectContentItem(item, $event, $index) { - listViewHelper.selectHandler(item, $index, $scope.items, $scope.selection, $event); - } - - function selectItem(item, $event, $index) { - listViewHelper.selectHandler(item, $index, vm.itemsWithoutFolders, $scope.selection, $event); - } - - function selectFolder(folder, $event, $index) { - listViewHelper.selectHandler(folder, $index, $scope.folders, $scope.selection, $event); - } - - function goToItem(item, $event, $index) { - $location.path($scope.entityType + '/' + $scope.entityType + '/edit/' + item.id); - } - - activate(); - - } - - angular.module("umbraco").controller("Umbraco.PropertyEditors.ListView.GridLayoutController", ListViewGridLayoutController); - -})(); - -(function () { - "use strict"; - - function ListViewListLayoutController($scope, listViewHelper, $location, mediaHelper, mediaTypeHelper) { - - var vm = this; - - vm.nodeId = $scope.contentId; - //we pass in a blacklist by adding ! to the file extensions, allowing everything EXCEPT for disallowedUploadFiles - vm.acceptedFileTypes = !mediaHelper.formatFileTypes(Umbraco.Sys.ServerVariables.umbracoSettings.disallowedUploadFiles); - vm.maxFileSize = Umbraco.Sys.ServerVariables.umbracoSettings.maxFileSize + "KB"; - vm.activeDrag = false; - vm.isRecycleBin = $scope.contentId === '-21' || $scope.contentId === '-20'; - vm.acceptedMediatypes = []; - - vm.selectItem = selectItem; - vm.clickItem = clickItem; - vm.selectAll = selectAll; - vm.isSelectedAll = isSelectedAll; - vm.isSortDirection = isSortDirection; - vm.sort = sort; - vm.dragEnter = dragEnter; - vm.dragLeave = dragLeave; - vm.onFilesQueue = onFilesQueue; - vm.onUploadComplete = onUploadComplete; - - function activate() { - - if ($scope.entityType === 'media') { - mediaTypeHelper.getAllowedImagetypes(vm.nodeId).then(function (types) { - vm.acceptedMediatypes = types; - }); + (function () { + 'use strict'; + function ListViewGridLayoutController($scope, $routeParams, mediaHelper, mediaResource, $location, listViewHelper, mediaTypeHelper) { + var vm = this; + var umbracoSettings = Umbraco.Sys.ServerVariables.umbracoSettings; + vm.nodeId = $scope.contentId; + // Use whitelist of allowed file types if provided + vm.acceptedFileTypes = mediaHelper.formatFileTypes(umbracoSettings.allowedUploadFiles); + if (vm.acceptedFileTypes === '') { + // If not provided, we pass in a blacklist by adding ! to the file extensions, allowing everything EXCEPT for disallowedUploadFiles + vm.acceptedFileTypes = !mediaHelper.formatFileTypes(umbracoSettings.disallowedUploadFiles); + } + vm.maxFileSize = umbracoSettings.maxFileSize + 'KB'; + vm.activeDrag = false; + vm.mediaDetailsTooltip = {}; + vm.itemsWithoutFolders = []; + vm.isRecycleBin = $scope.contentId === '-21' || $scope.contentId === '-20'; + vm.acceptedMediatypes = []; + vm.dragEnter = dragEnter; + vm.dragLeave = dragLeave; + vm.onFilesQueue = onFilesQueue; + vm.onUploadComplete = onUploadComplete; + vm.hoverMediaItemDetails = hoverMediaItemDetails; + vm.selectContentItem = selectContentItem; + vm.selectItem = selectItem; + vm.selectFolder = selectFolder; + vm.goToItem = goToItem; + function activate() { + vm.itemsWithoutFolders = filterOutFolders($scope.items); + //no need to make another REST/DB call if this data is not used when we are browsing the bin + if ($scope.entityType === 'media' && !vm.isRecycleBin) { + mediaTypeHelper.getAllowedImagetypes(vm.nodeId).then(function (types) { + vm.acceptedMediatypes = types; + }); + } + } + function filterOutFolders(items) { + var newArray = []; + if (items && items.length) { + for (var i = 0; items.length > i; i++) { + var item = items[i]; + var isFolder = !mediaHelper.hasFilePropertyType(item); + if (!isFolder) { + newArray.push(item); + } + } + } + return newArray; + } + function dragEnter(el, event) { + vm.activeDrag = true; + } + function dragLeave(el, event) { + vm.activeDrag = false; + } + function onFilesQueue() { + vm.activeDrag = false; + } + function onUploadComplete() { + $scope.getContent($scope.contentId); + } + function hoverMediaItemDetails(item, event, hover) { + if (hover && !vm.mediaDetailsTooltip.show) { + vm.mediaDetailsTooltip.event = event; + vm.mediaDetailsTooltip.item = item; + vm.mediaDetailsTooltip.show = true; + } else if (!hover && vm.mediaDetailsTooltip.show) { + vm.mediaDetailsTooltip.show = false; + } + } + function selectContentItem(item, $event, $index) { + listViewHelper.selectHandler(item, $index, $scope.items, $scope.selection, $event); + } + function selectItem(item, $event, $index) { + listViewHelper.selectHandler(item, $index, vm.itemsWithoutFolders, $scope.selection, $event); + } + function selectFolder(folder, $event, $index) { + listViewHelper.selectHandler(folder, $index, $scope.folders, $scope.selection, $event); + } + function goToItem(item, $event, $index) { + $location.path($scope.entityType + '/' + $scope.entityType + '/edit/' + item.id); + } + activate(); } - - } - - function selectAll($event) { - listViewHelper.selectAllItems($scope.items, $scope.selection, $event); - } - - function isSelectedAll() { - return listViewHelper.isSelectedAll($scope.items, $scope.selection); - } - - function selectItem(selectedItem, $index, $event) { - listViewHelper.selectHandler(selectedItem, $index, $scope.items, $scope.selection, $event); - } - - function clickItem(item) { - // if item.id is 2147483647 (int.MaxValue) use item.key - $location.path($scope.entityType + '/' +$scope.entityType + '/edit/' + (item.id === 2147483647 ? item.key : item.id)); - } - - function isSortDirection(col, direction) { - return listViewHelper.setSortingDirection(col, direction, $scope.options); - } - - function sort(field, allow, isSystem) { - if (allow) { - $scope.options.orderBySystemField = isSystem; - listViewHelper.setSorting(field, allow, $scope.options); - $scope.getContent($scope.contentId); - } - } - - // Dropzone upload functions - function dragEnter(el, event) { - vm.activeDrag = true; - } - - function dragLeave(el, event) { - vm.activeDrag = false; - } - - function onFilesQueue() { - vm.activeDrag = false; - } - - function onUploadComplete() { - $scope.getContent($scope.contentId); - } - - activate(); - - } - -angular.module("umbraco").controller("Umbraco.PropertyEditors.ListView.ListLayoutController", ListViewListLayoutController); - -}) (); - -function listViewController($rootScope, $scope, $routeParams, $injector, $cookieStore, notificationsService, iconHelper, dialogService, editorState, localizationService, $location, appState, $timeout, $q, mediaResource, listViewHelper, userService, navigationService, treeService) { - - //this is a quick check to see if we're in create mode, if so just exit - we cannot show children for content - // that isn't created yet, if we continue this will use the parent id in the route params which isn't what - // we want. NOTE: This is just a safety check since when we scaffold an empty model on the server we remove - // the list view tab entirely when it's new. - if ($routeParams.create) { - $scope.isNew = true; - return; - } - - //Now we need to check if this is for media, members or content because that will depend on the resources we use - var contentResource, getContentTypesCallback, getListResultsCallback, deleteItemCallback, getIdCallback, createEditUrlCallback; - - //check the config for the entity type, or the current section name (since the config is only set in c#, not in pre-vals) - if (($scope.model.config.entityType && $scope.model.config.entityType === "member") || (appState.getSectionState("currentSection") === "member")) { - $scope.entityType = "member"; - contentResource = $injector.get('memberResource'); - getContentTypesCallback = $injector.get('memberTypeResource').getTypes; - getListResultsCallback = contentResource.getPagedResults; - deleteItemCallback = contentResource.deleteByKey; - getIdCallback = function (selected) { - var selectedKey = getItemKey(selected.id); - return selectedKey; - }; - createEditUrlCallback = function (item) { - return "/" + $scope.entityType + "/" + $scope.entityType + "/edit/" + item.key + "?page=" + $scope.options.pageNumber + "&listName=" + $scope.contentId; - }; - } - else { - //check the config for the entity type, or the current section name (since the config is only set in c#, not in pre-vals) - if (($scope.model.config.entityType && $scope.model.config.entityType === "media") || (appState.getSectionState("currentSection") === "media")) { - $scope.entityType = "media"; - contentResource = $injector.get('mediaResource'); - getContentTypesCallback = $injector.get('mediaTypeResource').getAllowedTypes; - } - else { - $scope.entityType = "content"; - contentResource = $injector.get('contentResource'); - getContentTypesCallback = $injector.get('contentTypeResource').getAllowedTypes; - } - getListResultsCallback = contentResource.getChildren; - deleteItemCallback = contentResource.deleteById; - getIdCallback = function (selected) { - return selected.id; - }; - createEditUrlCallback = function (item) { - return "/" + $scope.entityType + "/" + $scope.entityType + "/edit/" + item.id + "?page=" + $scope.options.pageNumber; - }; - } - - $scope.pagination = []; - $scope.isNew = false; - $scope.actionInProgress = false; - $scope.selection = []; - $scope.folders = []; - $scope.listViewResultSet = { - totalPages: 0, - items: [] - }; - - $scope.currentNodePermissions = {} - - //Just ensure we do have an editorState - if (editorState.current) { - //Fetch current node allowed actions for the current user - //This is the current node & not each individual child node in the list - var currentUserPermissions = editorState.current.allowedActions; - - //Create a nicer model rather than the funky & hard to remember permissions strings - $scope.currentNodePermissions = { - "canCopy": _.contains(currentUserPermissions, 'O'), //Magic Char = O - "canCreate": _.contains(currentUserPermissions, 'C'), //Magic Char = C - "canDelete": _.contains(currentUserPermissions, 'D'), //Magic Char = D - "canMove": _.contains(currentUserPermissions, 'M'), //Magic Char = M - "canPublish": _.contains(currentUserPermissions, 'U'), //Magic Char = U - "canUnpublish": _.contains(currentUserPermissions, 'U'), //Magic Char = Z (however UI says it can't be set, so if we can publish 'U' we can unpublish) - }; - } - - //when this is null, we don't check permissions - $scope.buttonPermissions = null; - - //When we are dealing with 'content', we need to deal with permissions on child nodes. - // Currently there is no real good way to - if ($scope.entityType === "content") { - - var idsWithPermissions = null; - - $scope.buttonPermissions = { - canCopy: true, - canCreate: true, - canDelete: true, - canMove: true, - canPublish: true, - canUnpublish: true - }; - - $scope.$watch(function () { - return $scope.selection.length; - }, function (newVal, oldVal) { - - if ((idsWithPermissions == null && newVal > 0) || (idsWithPermissions != null)) { - - //get all of the selected ids - var ids = _.map($scope.selection, function (i) { - return i.id.toString(); - }); - - //remove the dictionary items that don't have matching ids - var filtered = {}; - _.each(idsWithPermissions, function (value, key, list) { - if (_.contains(ids, key)) { - filtered[key] = value; - } - }); - idsWithPermissions = filtered; - - //find all ids that we haven't looked up permissions for - var existingIds = _.keys(idsWithPermissions); - var missingLookup = _.map(_.difference(ids, existingIds), function (i) { - return Number(i); - }); - - if (missingLookup.length > 0) { - contentResource.getPermissions(missingLookup).then(function (p) { - $scope.buttonPermissions = listViewHelper.getButtonPermissions(p, idsWithPermissions); - }); - } - else { - $scope.buttonPermissions = listViewHelper.getButtonPermissions({}, idsWithPermissions); - } - } - }); - - } - - $scope.options = { - displayAtTabNumber: $scope.model.config.displayAtTabNumber ? $scope.model.config.displayAtTabNumber : 1, - pageSize: $scope.model.config.pageSize ? $scope.model.config.pageSize : 10, - pageNumber: ($routeParams.page && Number($routeParams.page) != NaN && Number($routeParams.page) > 0) ? $routeParams.page : 1, - filter: '', - orderBy: ($scope.model.config.orderBy ? $scope.model.config.orderBy : 'VersionDate').trim(), - orderDirection: $scope.model.config.orderDirection ? $scope.model.config.orderDirection.trim() : "desc", - orderBySystemField: true, - includeProperties: $scope.model.config.includeProperties ? $scope.model.config.includeProperties : [ - { alias: 'updateDate', header: 'Last edited', isSystem: 1 }, - { alias: 'updater', header: 'Last edited by', isSystem: 1 } - ], - layout: { - layouts: $scope.model.config.layouts, - activeLayout: listViewHelper.getLayout($routeParams.id, $scope.model.config.layouts) - }, - allowBulkPublish: $scope.entityType === 'content' && $scope.model.config.bulkActionPermissions.allowBulkPublish, - allowBulkUnpublish: $scope.entityType === 'content' && $scope.model.config.bulkActionPermissions.allowBulkUnpublish, - allowBulkCopy: $scope.entityType === 'content' && $scope.model.config.bulkActionPermissions.allowBulkCopy, - allowBulkMove: $scope.model.config.bulkActionPermissions.allowBulkMove, - allowBulkDelete: $scope.model.config.bulkActionPermissions.allowBulkDelete - }; - - // Check if selected order by field is actually custom field - for (var j = 0; j < $scope.options.includeProperties.length; j++) { - var includedProperty = $scope.options.includeProperties[j]; - if (includedProperty.alias.toLowerCase() === $scope.options.orderBy.toLowerCase()) { - $scope.options.orderBySystemField = includedProperty.isSystem === 1; - break; - } - } - - //update all of the system includeProperties to enable sorting - _.each($scope.options.includeProperties, function (e, i) { - - //NOTE: special case for contentTypeAlias, it's a system property that cannot be sorted - // to do that, we'd need to update the base query for content to include the content type alias column - // which requires another join and would be slower. BUT We are doing this for members so not sure it makes a diff? - if (e.alias != "contentTypeAlias") { - e.allowSorting = true; - } - - // Another special case for members, only fields on the base table (cmsMember) can be used for sorting - if (e.isSystem && $scope.entityType == "member") { - e.allowSorting = e.alias == 'username' || e.alias == 'email'; - } - - if (e.isSystem) { - //localize the header - var key = getLocalizedKey(e.alias); - localizationService.localize(key).then(function (v) { - e.header = v; - }); - } - }); - - $scope.selectLayout = function (selectedLayout) { - $scope.options.layout.activeLayout = listViewHelper.setLayout($routeParams.id, selectedLayout, $scope.model.config.layouts); - }; - - function showNotificationsAndReset(err, reload, successMsg) { - - //check if response is ysod - if (err.status && err.status >= 500) { - - // Open ysod overlay - $scope.ysodOverlay = { - view: "ysod", - error: err, - show: true - }; - } - - $timeout(function() { - $scope.bulkStatus = ""; - $scope.actionInProgress = false; - }, - 500); - - if (reload === true) { - $scope.reloadView($scope.contentId); - } - - if (err.data && angular.isArray(err.data.notifications)) { - for (var i = 0; i < err.data.notifications.length; i++) { - notificationsService.showNotification(err.data.notifications[i]); - } - } else if (successMsg) { - localizationService.localize("bulk_done") - .then(function(v) { - notificationsService.success(v, successMsg); - }); - } - } - - $scope.next = function (pageNumber) { - $scope.options.pageNumber = pageNumber; - $scope.reloadView($scope.contentId); - }; - - $scope.goToPage = function (pageNumber) { - $scope.options.pageNumber = pageNumber; - $scope.reloadView($scope.contentId); - }; - - $scope.prev = function (pageNumber) { - $scope.options.pageNumber = pageNumber; - $scope.reloadView($scope.contentId); - }; - - - /*Loads the search results, based on parameters set in prev,next,sort and so on*/ - /*Pagination is done by an array of objects, due angularJS's funky way of monitoring state - with simple values */ - - $scope.reloadView = function (id) { - - $scope.viewLoaded = false; - - listViewHelper.clearSelection($scope.listViewResultSet.items, $scope.folders, $scope.selection); - - getListResultsCallback(id, $scope.options).then(function (data) { - - $scope.actionInProgress = false; - $scope.listViewResultSet = data; - - //update all values for display - if ($scope.listViewResultSet.items) { - _.each($scope.listViewResultSet.items, function (e, index) { - setPropertyValues(e); - }); - } - - if ($scope.entityType === 'media') { - - mediaResource.getChildFolders($scope.contentId) - .then(function (folders) { - $scope.folders = folders; - $scope.viewLoaded = true; - }); - - } else { - $scope.viewLoaded = true; - } - - //NOTE: This might occur if we are requesting a higher page number than what is actually available, for example - // if you have more than one page and you delete all items on the last page. In this case, we need to reset to the last - // available page and then re-load again - if ($scope.options.pageNumber > $scope.listViewResultSet.totalPages) { - $scope.options.pageNumber = $scope.listViewResultSet.totalPages; - - //reload! - $scope.reloadView(id); - } - - }); - }; - - var searchListView = _.debounce(function () { - $scope.$apply(function () { - makeSearch(); - }); - }, 500); - - $scope.forceSearch = function (ev) { - //13: enter - switch (ev.keyCode) { - case 13: - makeSearch(); - break; - } - }; - - $scope.enterSearch = function () { - $scope.viewLoaded = false; - searchListView(); - }; - - function makeSearch() { - if ($scope.options.filter !== null && $scope.options.filter !== undefined) { - $scope.options.pageNumber = 1; - //$scope.actionInProgress = true; - $scope.reloadView($scope.contentId); - } - } - - $scope.isAnythingSelected = function () { - if ($scope.selection.length === 0) { - return false; - } else { - return true; - } - }; - - $scope.selectedItemsCount = function () { - return $scope.selection.length; - }; - - $scope.clearSelection = function () { - listViewHelper.clearSelection($scope.listViewResultSet.items, $scope.folders, $scope.selection); - }; - - $scope.getIcon = function (entry) { - return iconHelper.convertFromLegacyIcon(entry.icon); - }; - - function serial(selected, fn, getStatusMsg, index) { - return fn(selected, index).then(function (content) { - index++; - $scope.bulkStatus = getStatusMsg(index, selected.length); - return index < selected.length ? serial(selected, fn, getStatusMsg, index) : content; - }, function (err) { - var reload = index > 0; - showNotificationsAndReset(err, reload); - return err; - }); - } - - function applySelected(fn, getStatusMsg, getSuccessMsg, confirmMsg) { - var selected = $scope.selection; - if (selected.length === 0) - return; - if (confirmMsg && !confirm(confirmMsg)) - return; - - $scope.actionInProgress = true; - $scope.bulkStatus = getStatusMsg(0, selected.length); - - return serial(selected, fn, getStatusMsg, 0).then(function (result) { - // executes once the whole selection has been processed - // in case of an error (caught by serial), result will be the error - if (!(result.data && angular.isArray(result.data.notifications))) - showNotificationsAndReset(result, true, getSuccessMsg(selected.length)); - }); - } - - $scope.delete = function() { - var confirmDeleteText = ""; - - localizationService.localize("defaultdialogs_confirmdelete") - .then(function(value) { - confirmDeleteText = value; - - var attempt = - applySelected( - function(selected, index) { return deleteItemCallback(getIdCallback(selected[index])); }, - function(count, total) { - var key = (total === 1 ? "bulk_deletedItemOfItem" : "bulk_deletedItemOfItems"); - return localizationService.localize(key, [count, total]); - }, - function(total) { - var key = (total === 1 ? "bulk_deletedItem" : "bulk_deletedItems"); - return localizationService.localize(key, [total]); - }, - confirmDeleteText + "?"); - if (attempt) { - attempt.then(function() { - //executes if all is successful, let's sync the tree - var activeNode = appState.getTreeState("selectedNode"); - if (activeNode) { - navigationService.reloadNode(activeNode); - } - }); - } - }); - }; - - $scope.publish = function () { - applySelected( - function (selected, index) { return contentResource.publishById(getIdCallback(selected[index])); }, - function (count, total) { - var key = (total === 1 ? "bulk_publishedItemOfItem" : "bulk_publishedItemOfItems"); - return localizationService.localize(key, [count, total]); - }, - function (total) { - var key = (total === 1 ? "bulk_publishedItem" : "bulk_publishedItems"); - return localizationService.localize(key, [total]); - }); - }; - - $scope.unpublish = function() { - applySelected( - function(selected, index) { return contentResource.unPublish(getIdCallback(selected[index])); }, - function(count, total) { - var key = (total === 1 ? "bulk_unpublishedItemOfItem" : "bulk_unpublishedItemOfItems"); - return localizationService.localize(key, [count, total]); - }, - function(total) { - var key = (total === 1 ? "bulk_unpublishedItem" : "bulk_unpublishedItems"); - return localizationService.localize(key, [total]); - }); - }; - - $scope.move = function() { - $scope.moveDialog = {}; - $scope.moveDialog.title = localizationService.localize("general_move"); - $scope.moveDialog.section = $scope.entityType; - $scope.moveDialog.currentNode = $scope.contentId; - $scope.moveDialog.view = "move"; - $scope.moveDialog.show = true; - - $scope.moveDialog.submit = function(model) { - - if (model.target) { - performMove(model.target); - } - - $scope.moveDialog.show = false; - $scope.moveDialog = null; - }; - - $scope.moveDialog.close = function(oldModel) { - $scope.moveDialog.show = false; - $scope.moveDialog = null; - }; - - }; - - - function performMove(target) { - - //NOTE: With the way this applySelected/serial works, I'm not sure there's a better way currently to return - // a specific value from one of the methods, so we'll have to try this way. Even though the first method - // will fire once per every node moved, the destination path will be the same and we need to use that to sync. - var newPath = null; - applySelected( - function(selected, index) { - return contentResource.move({ parentId: target.id, id: getIdCallback(selected[index]) }) - .then(function(path) { - newPath = path; - return path; - }); - }, - function(count, total) { - var key = (total === 1 ? "bulk_movedItemOfItem" : "bulk_movedItemOfItems"); - return localizationService.localize(key, [count, total]); - }, - function(total) { - var key = (total === 1 ? "bulk_movedItem" : "bulk_movedItems"); - return localizationService.localize(key, [total]); - }) - .then(function() { - //executes if all is successful, let's sync the tree - if (newPath) { - - //we need to do a double sync here: first refresh the node where the content was moved, - // then refresh the node where the content was moved from - navigationService.syncTree({ - tree: target.nodeType, - path: newPath, - forceReload: true, - activate: false - }) - .then(function(args) { - //get the currently edited node (if any) - var activeNode = appState.getTreeState("selectedNode"); - if (activeNode) { - navigationService.reloadNode(activeNode); - } - }); - } - }); - } - - $scope.copy = function () { - $scope.copyDialog = {}; - $scope.copyDialog.title = localizationService.localize("general_copy"); - $scope.copyDialog.section = $scope.entityType; - $scope.copyDialog.currentNode = $scope.contentId; - $scope.copyDialog.view = "copy"; - $scope.copyDialog.show = true; - - $scope.copyDialog.submit = function (model) { - if (model.target) { - performCopy(model.target, model.relateToOriginal); - } - - $scope.copyDialog.show = false; - $scope.copyDialog = null; - }; - - $scope.copyDialog.close = function (oldModel) { - $scope.copyDialog.show = false; - $scope.copyDialog = null; - }; - - }; - - function performCopy(target, relateToOriginal) { - applySelected( - function (selected, index) { return contentResource.copy({ parentId: target.id, id: getIdCallback(selected[index]), relateToOriginal: relateToOriginal }); }, - function (count, total) { - var key = (total === 1 ? "bulk_copiedItemOfItem" : "bulk_copiedItemOfItems"); - return localizationService.localize(key, [count, total]); - }, - function (total) { - var key = (total === 1 ? "bulk_copiedItem" : "bulk_copiedItems"); - return localizationService.localize(key, [total]); - }); - } - - function getCustomPropertyValue(alias, properties) { - var value = ''; - var index = 0; - var foundAlias = false; - for (var i = 0; i < properties.length; i++) { - if (properties[i].alias == alias) { - foundAlias = true; - break; - } - index++; - } - - if (foundAlias) { - value = properties[index].value; - } - - return value; - } - - /** This ensures that the correct value is set for each item in a row, we don't want to call a function during interpolation or ng-bind as performance is really bad that way */ - function setPropertyValues(result) { - - //set the edit url - result.editPath = createEditUrlCallback(result); - - _.each($scope.options.includeProperties, function (e, i) { - - var alias = e.alias; - - // First try to pull the value directly from the alias (e.g. updatedBy) - var value = result[alias]; - - // If this returns an object, look for the name property of that (e.g. owner.name) - if (value === Object(value)) { - value = value['name']; - } - - // If we've got nothing yet, look at a user defined property - if (typeof value === 'undefined') { - value = getCustomPropertyValue(alias, result.properties); - } - - // If we have a date, format it - if (isDate(value)) { - value = value.substring(0, value.length - 3); - } - - // set what we've got on the result - result[alias] = value; - }); - - - } - - function isDate(val) { - if (angular.isString(val)) { - return val.match(/^(\d{4})\-(\d{2})\-(\d{2})\ (\d{2})\:(\d{2})\:(\d{2})$/); - } - return false; - } - - function initView() { - //default to root id if the id is undefined - var id = $routeParams.id; - if (id === undefined) { - id = -1; - } - - $scope.listViewAllowedTypes = getContentTypesCallback(id); - - $scope.contentId = id; - $scope.isTrashed = id === "-20" || id === "-21"; - - $scope.options.allowBulkPublish = $scope.options.allowBulkPublish && !$scope.isTrashed; - $scope.options.allowBulkUnpublish = $scope.options.allowBulkUnpublish && !$scope.isTrashed; - - $scope.options.bulkActionsAllowed = $scope.options.allowBulkPublish || - $scope.options.allowBulkUnpublish || - $scope.options.allowBulkCopy || - $scope.options.allowBulkMove || - $scope.options.allowBulkDelete; - - $scope.reloadView($scope.contentId); - } - - function getLocalizedKey(alias) { - - switch (alias) { - case "sortOrder": - return "general_sort"; - case "updateDate": - return "content_updateDate"; - case "updater": - return "content_updatedBy"; - case "createDate": - return "content_createDate"; - case "owner": - return "content_createBy"; - case "published": - return "content_isPublished"; - case "contentTypeAlias": - //TODO: Check for members - return $scope.entityType === "content" ? "content_documentType" : "content_mediatype"; - case "email": - return "general_email"; - case "username": - return "general_username"; - } - return alias; - } - - function getItemKey(itemId) { - for (var i = 0; i < $scope.listViewResultSet.items.length; i++) { - var item = $scope.listViewResultSet.items[i]; - if (item.id === itemId) { - return item.key; - } - } - } - - //GO! - initView(); -} - - -angular.module("umbraco").controller("Umbraco.PropertyEditors.ListViewController", listViewController); - -function sortByPreValsController($rootScope, $scope, localizationService, editorState, listViewPrevalueHelper) { - //Get the prevalue from the correct place - function getPrevalues() { - if (editorState.current.preValues) { - return editorState.current.preValues; - } - else { - return listViewPrevalueHelper.getPrevalues(); - } - } - - //Watch the prevalues - $scope.$watch(function () { - return _.findWhere(getPrevalues(), { key: "includeProperties" }).value; - }, function () { - populateFields(); - }, true); //Use deep watching, otherwise we won't pick up header changes - - function populateFields() { - // Helper to find a particular value from the list of sort by options - function findFromSortByFields(value) { - return _.find($scope.sortByFields, function (e) { - return e.value.toLowerCase() === value.toLowerCase(); - }); - } - - // Get list of properties assigned as columns of the list view - var propsPreValue = _.findWhere(getPrevalues(), { key: "includeProperties" }); - - // Populate list of options for the default sort (all the columns plus then node name) - $scope.sortByFields = []; - $scope.sortByFields.push({ value: "name", name: "Name", isSystem: 1 }); - if (propsPreValue != undefined) { - for (var i = 0; i < propsPreValue.value.length; i++) { - var value = propsPreValue.value[i]; - $scope.sortByFields.push({ - value: value.alias, - name: value.header, - isSystem: value.isSystem + angular.module('umbraco').controller('Umbraco.PropertyEditors.ListView.GridLayoutController', ListViewGridLayoutController); + }()); + (function () { + 'use strict'; + function ListViewListLayoutController($scope, listViewHelper, $location, mediaHelper, mediaTypeHelper) { + var vm = this; + var umbracoSettings = Umbraco.Sys.ServerVariables.umbracoSettings; + vm.nodeId = $scope.contentId; + // Use whitelist of allowed file types if provided + vm.acceptedFileTypes = mediaHelper.formatFileTypes(umbracoSettings.allowedUploadFiles); + if (vm.acceptedFileTypes === '') { + // If not provided, we pass in a blacklist by adding ! to the file extensions, allowing everything EXCEPT for disallowedUploadFiles + vm.acceptedFileTypes = !mediaHelper.formatFileTypes(umbracoSettings.disallowedUploadFiles); + } + vm.maxFileSize = umbracoSettings.maxFileSize + 'KB'; + vm.activeDrag = false; + vm.isRecycleBin = $scope.contentId === '-21' || $scope.contentId === '-20'; + vm.acceptedMediatypes = []; + vm.selectItem = selectItem; + vm.clickItem = clickItem; + vm.selectAll = selectAll; + vm.isSelectedAll = isSelectedAll; + vm.isSortDirection = isSortDirection; + vm.sort = sort; + vm.dragEnter = dragEnter; + vm.dragLeave = dragLeave; + vm.onFilesQueue = onFilesQueue; + vm.onUploadComplete = onUploadComplete; + markAsSensitive(); + function activate() { + if ($scope.entityType === 'media') { + mediaTypeHelper.getAllowedImagetypes(vm.nodeId).then(function (types) { + vm.acceptedMediatypes = types; + }); + } + } + function selectAll($event) { + listViewHelper.selectAllItems($scope.items, $scope.selection, $event); + } + function isSelectedAll() { + return listViewHelper.isSelectedAll($scope.items, $scope.selection); + } + function selectItem(selectedItem, $index, $event) { + listViewHelper.selectHandler(selectedItem, $index, $scope.items, $scope.selection, $event); + } + function clickItem(item) { + // if item.id is 2147483647 (int.MaxValue) use item.key + $location.path($scope.entityType + '/' + $scope.entityType + '/edit/' + (item.id === 2147483647 ? item.key : item.id)); + } + function isSortDirection(col, direction) { + return listViewHelper.setSortingDirection(col, direction, $scope.options); + } + function sort(field, allow, isSystem) { + if (allow) { + $scope.options.orderBySystemField = isSystem; + listViewHelper.setSorting(field, allow, $scope.options); + $scope.getContent($scope.contentId); + } + } + // Dropzone upload functions + function dragEnter(el, event) { + vm.activeDrag = true; + } + function dragLeave(el, event) { + vm.activeDrag = false; + } + function onFilesQueue() { + vm.activeDrag = false; + } + function onUploadComplete() { + $scope.getContent($scope.contentId); + } + function markAsSensitive() { + angular.forEach($scope.options.includeProperties, function (option) { + option.isSensitive = false; + angular.forEach($scope.items, function (item) { + angular.forEach(item.properties, function (property) { + if (option.alias === property.alias) { + option.isSensitive = property.isSensitive; + } + }); + }); }); } + activate(); } - - // Localize the system fields, for some reason the directive doesn't work inside of the select group with an ng-model declared - var systemFields = [ - { value: "SortOrder", key: "general_sort" }, - { value: "Name", key: "general_name" }, - { value: "VersionDate", key: "content_updateDate" }, - { value: "Updater", key: "content_updatedBy" }, - { value: "CreateDate", key: "content_createDate" }, - { value: "Owner", key: "content_createBy" }, - { value: "ContentTypeAlias", key: "content_documentType" }, - { value: "Published", key: "content_isPublished" }, - { value: "Email", key: "general_email" }, - { value: "Username", key: "general_username" } - ]; - _.each(systemFields, function (e) { - localizationService.localize(e.key).then(function (v) { - - var sortByListValue = findFromSortByFields(e.value); - if (sortByListValue) { - sortByListValue.name = v; - switch (e.value) { - case "Updater": - e.name += " (Content only)"; - break; - case "Published": - e.name += " (Content only)"; - break; - case "Email": - e.name += " (Members only)"; - break; - case "Username": - e.name += " (Members only)"; - break; + angular.module('umbraco').controller('Umbraco.PropertyEditors.ListView.ListLayoutController', ListViewListLayoutController); + }()); + function listViewController($rootScope, $scope, $routeParams, $injector, $cookieStore, notificationsService, iconHelper, dialogService, editorState, localizationService, $location, appState, $timeout, $q, mediaResource, listViewHelper, userService, navigationService, treeService) { + //this is a quick check to see if we're in create mode, if so just exit - we cannot show children for content + // that isn't created yet, if we continue this will use the parent id in the route params which isn't what + // we want. NOTE: This is just a safety check since when we scaffold an empty model on the server we remove + // the list view tab entirely when it's new. + if ($routeParams.create) { + $scope.isNew = true; + return; + } + //Now we need to check if this is for media, members or content because that will depend on the resources we use + var contentResource, getContentTypesCallback, getListResultsCallback, deleteItemCallback, getIdCallback, createEditUrlCallback; + //check the config for the entity type, or the current section name (since the config is only set in c#, not in pre-vals) + if ($scope.model.config.entityType && $scope.model.config.entityType === 'member' || appState.getSectionState('currentSection') === 'member') { + $scope.entityType = 'member'; + contentResource = $injector.get('memberResource'); + getContentTypesCallback = $injector.get('memberTypeResource').getTypes; + getListResultsCallback = contentResource.getPagedResults; + deleteItemCallback = contentResource.deleteByKey; + getIdCallback = function (selected) { + return selected.key; + }; + createEditUrlCallback = function (item) { + return '/' + $scope.entityType + '/' + $scope.entityType + '/edit/' + item.key + '?page=' + $scope.options.pageNumber + '&listName=' + $scope.contentId; + }; + } else { + //check the config for the entity type, or the current section name (since the config is only set in c#, not in pre-vals) + if ($scope.model.config.entityType && $scope.model.config.entityType === 'media' || appState.getSectionState('currentSection') === 'media') { + $scope.entityType = 'media'; + contentResource = $injector.get('mediaResource'); + getContentTypesCallback = $injector.get('mediaTypeResource').getAllowedTypes; + } else { + $scope.entityType = 'content'; + contentResource = $injector.get('contentResource'); + getContentTypesCallback = $injector.get('contentTypeResource').getAllowedTypes; + } + getListResultsCallback = contentResource.getChildren; + deleteItemCallback = contentResource.deleteById; + getIdCallback = function (selected) { + return selected.id; + }; + createEditUrlCallback = function (item) { + return '/' + $scope.entityType + '/' + $scope.entityType + '/edit/' + item.id + '?page=' + $scope.options.pageNumber; + }; + } + $scope.pagination = []; + $scope.isNew = false; + $scope.actionInProgress = false; + $scope.selection = []; + $scope.folders = []; + $scope.listViewResultSet = { + totalPages: 0, + items: [] + }; + //when this is null, we don't check permissions + $scope.currentNodePermissions = null; + if ($scope.entityType === 'content') { + //Just ensure we do have an editorState + if (editorState.current) { + //Fetch current node allowed actions for the current user + //This is the current node & not each individual child node in the list + var currentUserPermissions = editorState.current.allowedActions; + //Create a nicer model rather than the funky & hard to remember permissions strings + $scope.currentNodePermissions = { + 'canCopy': _.contains(currentUserPermissions, 'O'), + //Magic Char = O + 'canCreate': _.contains(currentUserPermissions, 'C'), + //Magic Char = C + 'canDelete': _.contains(currentUserPermissions, 'D'), + //Magic Char = D + 'canMove': _.contains(currentUserPermissions, 'M'), + //Magic Char = M + 'canPublish': _.contains(currentUserPermissions, 'U'), + //Magic Char = U + 'canUnpublish': _.contains(currentUserPermissions, 'U') //Magic Char = Z (however UI says it can't be set, so if we can publish 'U' we can unpublish) + }; + } + } + //when this is null, we don't check permissions + $scope.buttonPermissions = null; + //When we are dealing with 'content', we need to deal with permissions on child nodes. + // Currently there is no real good way to + if ($scope.entityType === 'content') { + var idsWithPermissions = null; + $scope.buttonPermissions = { + canCopy: true, + canCreate: true, + canDelete: true, + canMove: true, + canPublish: true, + canUnpublish: true + }; + $scope.$watch(function () { + return $scope.selection.length; + }, function (newVal, oldVal) { + if (idsWithPermissions == null && newVal > 0 || idsWithPermissions != null) { + //get all of the selected ids + var ids = _.map($scope.selection, function (i) { + return i.id.toString(); + }); + //remove the dictionary items that don't have matching ids + var filtered = {}; + _.each(idsWithPermissions, function (value, key, list) { + if (_.contains(ids, key)) { + filtered[key] = value; + } + }); + idsWithPermissions = filtered; + //find all ids that we haven't looked up permissions for + var existingIds = _.keys(idsWithPermissions); + var missingLookup = _.map(_.difference(ids, existingIds), function (i) { + return Number(i); + }); + if (missingLookup.length > 0) { + contentResource.getPermissions(missingLookup).then(function (p) { + $scope.buttonPermissions = listViewHelper.getButtonPermissions(p, idsWithPermissions); + }); + } else { + $scope.buttonPermissions = listViewHelper.getButtonPermissions({}, idsWithPermissions); } } }); - }); - - // Check existing model value is available in list and ensure a value is set - var existingValue = findFromSortByFields($scope.model.value); - if (existingValue) { - // Set the existing value - // The old implementation pre Umbraco 7.5 used PascalCase aliases, this uses camelCase, so this ensures that any previous value is set - $scope.model.value = existingValue.value; } - else { - // Existing value not found, set to first value - $scope.model.value = $scope.sortByFields[0].value; + $scope.options = { + displayAtTabNumber: $scope.model.config.displayAtTabNumber ? $scope.model.config.displayAtTabNumber : 1, + pageSize: $scope.model.config.pageSize ? $scope.model.config.pageSize : 10, + pageNumber: $routeParams.page && Number($routeParams.page) != NaN && Number($routeParams.page) > 0 ? $routeParams.page : 1, + filter: '', + orderBy: ($scope.model.config.orderBy ? $scope.model.config.orderBy : 'VersionDate').trim(), + orderDirection: $scope.model.config.orderDirection ? $scope.model.config.orderDirection.trim() : 'desc', + orderBySystemField: true, + includeProperties: $scope.model.config.includeProperties ? $scope.model.config.includeProperties : [ + { + alias: 'updateDate', + header: 'Last edited', + isSystem: 1 + }, + { + alias: 'updater', + header: 'Last edited by', + isSystem: 1 + } + ], + layout: { + layouts: $scope.model.config.layouts, + activeLayout: listViewHelper.getLayout($routeParams.id, $scope.model.config.layouts) + }, + allowBulkPublish: $scope.entityType === 'content' && $scope.model.config.bulkActionPermissions.allowBulkPublish, + allowBulkUnpublish: $scope.entityType === 'content' && $scope.model.config.bulkActionPermissions.allowBulkUnpublish, + allowBulkCopy: $scope.entityType === 'content' && $scope.model.config.bulkActionPermissions.allowBulkCopy, + allowBulkMove: $scope.model.config.bulkActionPermissions.allowBulkMove, + allowBulkDelete: $scope.model.config.bulkActionPermissions.allowBulkDelete + }; + // Check if selected order by field is actually custom field + for (var j = 0; j < $scope.options.includeProperties.length; j++) { + var includedProperty = $scope.options.includeProperties[j]; + if (includedProperty.alias.toLowerCase() === $scope.options.orderBy.toLowerCase()) { + $scope.options.orderBySystemField = includedProperty.isSystem === 1; + break; + } + } + //update all of the system includeProperties to enable sorting + _.each($scope.options.includeProperties, function (e, i) { + //NOTE: special case for contentTypeAlias, it's a system property that cannot be sorted + // to do that, we'd need to update the base query for content to include the content type alias column + // which requires another join and would be slower. BUT We are doing this for members so not sure it makes a diff? + if (e.alias != 'contentTypeAlias') { + e.allowSorting = true; + } + // Another special case for members, only fields on the base table (cmsMember) can be used for sorting + if (e.isSystem && $scope.entityType == 'member') { + e.allowSorting = e.alias == 'username' || e.alias == 'email'; + } + if (e.isSystem) { + //localize the header + var key = getLocalizedKey(e.alias); + localizationService.localize(key).then(function (v) { + e.header = v; + }); + } + }); + $scope.selectLayout = function (selectedLayout) { + $scope.options.layout.activeLayout = listViewHelper.setLayout($routeParams.id, selectedLayout, $scope.model.config.layouts); + }; + function showNotificationsAndReset(err, reload, successMsg) { + //check if response is ysod + if (err.status && err.status >= 500) { + // Open ysod overlay + $scope.ysodOverlay = { + view: 'ysod', + error: err, + show: true + }; + } + $timeout(function () { + $scope.bulkStatus = ''; + $scope.actionInProgress = false; + }, 500); + if (reload === true) { + $scope.reloadView($scope.contentId, true); + } + if (err.data && angular.isArray(err.data.notifications)) { + for (var i = 0; i < err.data.notifications.length; i++) { + notificationsService.showNotification(err.data.notifications[i]); + } + } else if (successMsg) { + localizationService.localize('bulk_done').then(function (v) { + notificationsService.success(v, successMsg); + }); + } + } + $scope.next = function (pageNumber) { + $scope.options.pageNumber = pageNumber; + $scope.reloadView($scope.contentId); + }; + $scope.goToPage = function (pageNumber) { + $scope.options.pageNumber = pageNumber; + $scope.reloadView($scope.contentId); + }; + $scope.prev = function (pageNumber) { + $scope.options.pageNumber = pageNumber; + $scope.reloadView($scope.contentId); + }; + /*Loads the search results, based on parameters set in prev,next,sort and so on*/ + /*Pagination is done by an array of objects, due angularJS's funky way of monitoring state + with simple values */ + $scope.getContent = function () { + $scope.reloadView($scope.contentId, true); + }; + $scope.reloadView = function (id, reloadFolders) { + $scope.viewLoaded = false; + listViewHelper.clearSelection($scope.listViewResultSet.items, $scope.folders, $scope.selection); + getListResultsCallback(id, $scope.options).then(function (data) { + $scope.actionInProgress = false; + $scope.listViewResultSet = data; + //update all values for display + if ($scope.listViewResultSet.items) { + _.each($scope.listViewResultSet.items, function (e, index) { + setPropertyValues(e); + }); + } + if (reloadFolders && $scope.entityType === 'media') { + //The folders aren't loaded - we only need to do this once since we're never changing node ids + mediaResource.getChildFolders($scope.contentId).then(function (folders) { + $scope.folders = folders; + $scope.viewLoaded = true; + }); + } else { + $scope.viewLoaded = true; + } + //NOTE: This might occur if we are requesting a higher page number than what is actually available, for example + // if you have more than one page and you delete all items on the last page. In this case, we need to reset to the last + // available page and then re-load again + if ($scope.options.pageNumber > $scope.listViewResultSet.totalPages) { + $scope.options.pageNumber = $scope.listViewResultSet.totalPages; + //reload! + $scope.reloadView(id); + } + }); + }; + var searchListView = _.debounce(function () { + $scope.$apply(function () { + makeSearch(); + }); + }, 500); + $scope.forceSearch = function (ev) { + //13: enter + switch (ev.keyCode) { + case 13: + makeSearch(); + break; + } + }; + $scope.enterSearch = function () { + $scope.viewLoaded = false; + searchListView(); + }; + function makeSearch() { + if ($scope.options.filter !== null && $scope.options.filter !== undefined) { + $scope.options.pageNumber = 1; + $scope.reloadView($scope.contentId); + } + } + $scope.isAnythingSelected = function () { + if ($scope.selection.length === 0) { + return false; + } else { + return true; + } + }; + $scope.selectedItemsCount = function () { + return $scope.selection.length; + }; + $scope.clearSelection = function () { + listViewHelper.clearSelection($scope.listViewResultSet.items, $scope.folders, $scope.selection); + }; + $scope.getIcon = function (entry) { + return iconHelper.convertFromLegacyIcon(entry.icon); + }; + function serial(selected, fn, getStatusMsg, index) { + return fn(selected, index).then(function (content) { + index++; + $scope.bulkStatus = getStatusMsg(index, selected.length); + return index < selected.length ? serial(selected, fn, getStatusMsg, index) : content; + }, function (err) { + var reload = index > 0; + showNotificationsAndReset(err, reload); + return err; + }); + } + function applySelected(fn, getStatusMsg, getSuccessMsg, confirmMsg) { + var selected = $scope.selection; + if (selected.length === 0) + return; + if (confirmMsg && !confirm(confirmMsg)) + return; + $scope.actionInProgress = true; + $scope.bulkStatus = getStatusMsg(0, selected.length); + return serial(selected, fn, getStatusMsg, 0).then(function (result) { + // executes once the whole selection has been processed + // in case of an error (caught by serial), result will be the error + if (!(result.data && angular.isArray(result.data.notifications))) + showNotificationsAndReset(result, true, getSuccessMsg(selected.length)); + }); + } + $scope.delete = function () { + var confirmDeleteText = ''; + localizationService.localize('defaultdialogs_confirmdelete').then(function (value) { + confirmDeleteText = value; + var attempt = applySelected(function (selected, index) { + return deleteItemCallback(getIdCallback(selected[index])); + }, function (count, total) { + var key = total === 1 ? 'bulk_deletedItemOfItem' : 'bulk_deletedItemOfItems'; + return localizationService.localize(key, [ + count, + total + ]); + }, function (total) { + var key = total === 1 ? 'bulk_deletedItem' : 'bulk_deletedItems'; + return localizationService.localize(key, [total]); + }, confirmDeleteText + '?'); + if (attempt) { + attempt.then(function () { + //executes if all is successful, let's sync the tree + var activeNode = appState.getTreeState('selectedNode'); + if (activeNode) { + navigationService.reloadNode(activeNode); + } + }); + } + }); + }; + $scope.publish = function () { + applySelected(function (selected, index) { + return contentResource.publishById(getIdCallback(selected[index])); + }, function (count, total) { + var key = total === 1 ? 'bulk_publishedItemOfItem' : 'bulk_publishedItemOfItems'; + return localizationService.localize(key, [ + count, + total + ]); + }, function (total) { + var key = total === 1 ? 'bulk_publishedItem' : 'bulk_publishedItems'; + return localizationService.localize(key, [total]); + }); + }; + $scope.unpublish = function () { + applySelected(function (selected, index) { + return contentResource.unPublish(getIdCallback(selected[index])); + }, function (count, total) { + var key = total === 1 ? 'bulk_unpublishedItemOfItem' : 'bulk_unpublishedItemOfItems'; + return localizationService.localize(key, [ + count, + total + ]); + }, function (total) { + var key = total === 1 ? 'bulk_unpublishedItem' : 'bulk_unpublishedItems'; + return localizationService.localize(key, [total]); + }); + }; + $scope.move = function () { + $scope.moveDialog = {}; + $scope.moveDialog.title = localizationService.localize('general_move'); + $scope.moveDialog.section = $scope.entityType; + $scope.moveDialog.currentNode = $scope.contentId; + $scope.moveDialog.view = 'move'; + $scope.moveDialog.show = true; + $scope.moveDialog.submit = function (model) { + if (model.target) { + performMove(model.target); + } + $scope.moveDialog.show = false; + $scope.moveDialog = null; + }; + $scope.moveDialog.close = function (oldModel) { + $scope.moveDialog.show = false; + $scope.moveDialog = null; + }; + }; + function performMove(target) { + //NOTE: With the way this applySelected/serial works, I'm not sure there's a better way currently to return + // a specific value from one of the methods, so we'll have to try this way. Even though the first method + // will fire once per every node moved, the destination path will be the same and we need to use that to sync. + var newPath = null; + applySelected(function (selected, index) { + return contentResource.move({ + parentId: target.id, + id: getIdCallback(selected[index]) + }).then(function (path) { + newPath = path; + return path; + }); + }, function (count, total) { + var key = total === 1 ? 'bulk_movedItemOfItem' : 'bulk_movedItemOfItems'; + return localizationService.localize(key, [ + count, + total + ]); + }, function (total) { + var key = total === 1 ? 'bulk_movedItem' : 'bulk_movedItems'; + return localizationService.localize(key, [total]); + }).then(function () { + //executes if all is successful, let's sync the tree + if (newPath) { + //we need to do a double sync here: first refresh the node where the content was moved, + // then refresh the node where the content was moved from + navigationService.syncTree({ + tree: target.nodeType ? target.nodeType : target.metaData.treeAlias, + path: newPath, + forceReload: true, + activate: false + }).then(function (args) { + //get the currently edited node (if any) + var activeNode = appState.getTreeState('selectedNode'); + if (activeNode) { + navigationService.reloadNode(activeNode); + } + }); + } + }); + } + $scope.copy = function () { + $scope.copyDialog = {}; + $scope.copyDialog.title = localizationService.localize('general_copy'); + $scope.copyDialog.section = $scope.entityType; + $scope.copyDialog.currentNode = $scope.contentId; + $scope.copyDialog.view = 'copy'; + $scope.copyDialog.show = true; + $scope.copyDialog.submit = function (model) { + if (model.target) { + performCopy(model.target, model.relateToOriginal); + } + $scope.copyDialog.show = false; + $scope.copyDialog = null; + }; + $scope.copyDialog.close = function (oldModel) { + $scope.copyDialog.show = false; + $scope.copyDialog = null; + }; + }; + function performCopy(target, relateToOriginal) { + applySelected(function (selected, index) { + return contentResource.copy({ + parentId: target.id, + id: getIdCallback(selected[index]), + relateToOriginal: relateToOriginal + }); + }, function (count, total) { + var key = total === 1 ? 'bulk_copiedItemOfItem' : 'bulk_copiedItemOfItems'; + return localizationService.localize(key, [ + count, + total + ]); + }, function (total) { + var key = total === 1 ? 'bulk_copiedItem' : 'bulk_copiedItems'; + return localizationService.localize(key, [total]); + }); + } + function getCustomPropertyValue(alias, properties) { + var value = ''; + var index = 0; + var foundAlias = false; + for (var i = 0; i < properties.length; i++) { + if (properties[i].alias == alias) { + foundAlias = true; + break; + } + index++; + } + if (foundAlias) { + value = properties[index].value; + } + return value; + } + /** This ensures that the correct value is set for each item in a row, we don't want to call a function during interpolation or ng-bind as performance is really bad that way */ + function setPropertyValues(result) { + //set the edit url + result.editPath = createEditUrlCallback(result); + _.each($scope.options.includeProperties, function (e, i) { + var alias = e.alias; + // First try to pull the value directly from the alias (e.g. updatedBy) + var value = result[alias]; + // If this returns an object, look for the name property of that (e.g. owner.name) + if (value === Object(value)) { + value = value['name']; + } + // If we've got nothing yet, look at a user defined property + if (typeof value === 'undefined') { + value = getCustomPropertyValue(alias, result.properties); + } + // If we have a date, format it + if (isDate(value)) { + value = value.substring(0, value.length - 3); + } + // set what we've got on the result + result[alias] = value; + }); + } + function isDate(val) { + if (angular.isString(val)) { + return val.match(/^(\d{4})\-(\d{2})\-(\d{2})\ (\d{2})\:(\d{2})\:(\d{2})$/); + } + return false; + } + function initView() { + //default to root id if the id is undefined + var id = $routeParams.id; + if (id === undefined) { + id = -1; + } + $scope.listViewAllowedTypes = getContentTypesCallback(id); + $scope.contentId = id; + $scope.isTrashed = id === '-20' || id === '-21'; + $scope.options.allowBulkPublish = $scope.options.allowBulkPublish && !$scope.isTrashed; + $scope.options.allowBulkUnpublish = $scope.options.allowBulkUnpublish && !$scope.isTrashed; + $scope.options.bulkActionsAllowed = $scope.options.allowBulkPublish || $scope.options.allowBulkUnpublish || $scope.options.allowBulkCopy || $scope.options.allowBulkMove || $scope.options.allowBulkDelete; + $scope.reloadView($scope.contentId, true); + } + function getLocalizedKey(alias) { + switch (alias) { + case 'sortOrder': + return 'general_sort'; + case 'updateDate': + return 'content_updateDate'; + case 'updater': + return 'content_updatedBy'; + case 'createDate': + return 'content_createDate'; + case 'owner': + return 'content_createBy'; + case 'published': + return 'content_isPublished'; + case 'contentTypeAlias': + //TODO: Check for members + return $scope.entityType === 'content' ? 'content_documentType' : 'content_mediatype'; + case 'email': + return 'general_email'; + case 'username': + return 'general_username'; + } + return alias; + } + function getItemKey(itemId) { + for (var i = 0; i < $scope.listViewResultSet.items.length; i++) { + var item = $scope.listViewResultSet.items[i]; + if (item.id === itemId) { + return item.key; + } + } + } + //GO! + initView(); + } + angular.module('umbraco').controller('Umbraco.PropertyEditors.ListViewController', listViewController); + function sortByPreValsController($rootScope, $scope, localizationService, editorState, listViewPrevalueHelper) { + //Get the prevalue from the correct place + function getPrevalues() { + if (editorState.current.preValues) { + return editorState.current.preValues; + } else { + return listViewPrevalueHelper.getPrevalues(); + } + } + //Watch the prevalues + $scope.$watch(function () { + return _.findWhere(getPrevalues(), { key: 'includeProperties' }).value; + }, function () { + populateFields(); + }, true); + //Use deep watching, otherwise we won't pick up header changes + function populateFields() { + // Helper to find a particular value from the list of sort by options + function findFromSortByFields(value) { + return _.find($scope.sortByFields, function (e) { + return e.value.toLowerCase() === value.toLowerCase(); + }); + } + // Get list of properties assigned as columns of the list view + var propsPreValue = _.findWhere(getPrevalues(), { key: 'includeProperties' }); + // Populate list of options for the default sort (all the columns plus then node name) + $scope.sortByFields = []; + $scope.sortByFields.push({ + value: 'name', + name: 'Name', + isSystem: 1 + }); + if (propsPreValue != undefined) { + for (var i = 0; i < propsPreValue.value.length; i++) { + var value = propsPreValue.value[i]; + $scope.sortByFields.push({ + value: value.alias, + name: value.header, + isSystem: value.isSystem + }); + } + } + // Localize the system fields, for some reason the directive doesn't work inside of the select group with an ng-model declared + var systemFields = [ + { + value: 'SortOrder', + key: 'general_sort' + }, + { + value: 'Name', + key: 'general_name' + }, + { + value: 'VersionDate', + key: 'content_updateDate' + }, + { + value: 'Updater', + key: 'content_updatedBy' + }, + { + value: 'CreateDate', + key: 'content_createDate' + }, + { + value: 'Owner', + key: 'content_createBy' + }, + { + value: 'ContentTypeAlias', + key: 'content_documentType' + }, + { + value: 'Published', + key: 'content_isPublished' + }, + { + value: 'Email', + key: 'general_email' + }, + { + value: 'Username', + key: 'general_username' + } + ]; + _.each(systemFields, function (e) { + localizationService.localize(e.key).then(function (v) { + var sortByListValue = findFromSortByFields(e.value); + if (sortByListValue) { + sortByListValue.name = v; + switch (e.value) { + case 'Updater': + e.name += ' (Content only)'; + break; + case 'Published': + e.name += ' (Content only)'; + break; + case 'Email': + e.name += ' (Members only)'; + break; + case 'Username': + e.name += ' (Members only)'; + break; + } + } + }); + }); + // Check existing model value is available in list and ensure a value is set + var existingValue = findFromSortByFields($scope.model.value); + if (existingValue) { + // Set the existing value + // The old implementation pre Umbraco 7.5 used PascalCase aliases, this uses camelCase, so this ensures that any previous value is set + $scope.model.value = existingValue.value; + } else { + // Existing value not found, set to first value + $scope.model.value = $scope.sortByFields[0].value; + } } } -} - - -angular.module("umbraco").controller("Umbraco.PrevalueEditors.SortByListViewController", sortByPreValsController); -//DO NOT DELETE THIS, this is in use... -angular.module('umbraco') -.controller("Umbraco.PropertyEditors.MacroContainerController", - - function($scope, dialogService, entityResource, macroService){ - $scope.renderModel = []; - - if($scope.model.value){ - var macros = $scope.model.value.split('>'); - - angular.forEach(macros, function(syntax, key){ - if(syntax && syntax.length > 10){ - //re-add the char we split on - syntax = syntax + ">"; - var parsed = macroService.parseMacroSyntax(syntax); - if(!parsed){ - parsed = {}; - } - - parsed.syntax = syntax; - collectDetails(parsed); - $scope.renderModel.push(parsed); - } - }); - } - - - function collectDetails(macro){ - macro.details = ""; - if(macro.macroParamsDictionary){ - angular.forEach((macro.macroParamsDictionary), function(value, key){ - macro.details += key + ": " + value + " "; - }); - } - } - - function openDialog(index){ - var dialogData = { - allowedMacros: $scope.model.config.allowed - }; - - if(index !== null && $scope.renderModel[index]) { - var macro = $scope.renderModel[index]; - dialogData["macroData"] = macro; - } - - $scope.macroPickerOverlay = {}; - $scope.macroPickerOverlay.view = "macropicker"; - $scope.macroPickerOverlay.dialogData = dialogData; - $scope.macroPickerOverlay.show = true; - - $scope.macroPickerOverlay.submit = function(model) { - - var macroObject = macroService.collectValueData(model.selectedMacro, model.macroParams, dialogData.renderingEngine); - collectDetails(macroObject); - - //update the raw syntax and the list... - if(index !== null && $scope.renderModel[index]) { - $scope.renderModel[index] = macroObject; - } else { - $scope.renderModel.push(macroObject); - } - - $scope.macroPickerOverlay.show = false; - $scope.macroPickerOverlay = null; - }; - - $scope.macroPickerOverlay.close = function(oldModel) { - $scope.macroPickerOverlay.show = false; - $scope.macroPickerOverlay = null; - }; - - } - - - - $scope.edit =function(index){ - openDialog(index); - }; - - $scope.add = function () { - - if ($scope.model.config.max && $scope.model.config.max > 0 && $scope.renderModel.length >= $scope.model.config.max) { + angular.module('umbraco').controller('Umbraco.PrevalueEditors.SortByListViewController', sortByPreValsController); + //DO NOT DELETE THIS, this is in use... + angular.module('umbraco').controller('Umbraco.PropertyEditors.MacroContainerController', function ($scope, dialogService, entityResource, macroService) { + $scope.renderModel = []; + $scope.allowOpenButton = true; + $scope.allowRemoveButton = true; + $scope.sortableOptions = {}; + if ($scope.model.value) { + var macros = $scope.model.value.split('>'); + angular.forEach(macros, function (syntax, key) { + if (syntax && syntax.length > 10) { + //re-add the char we split on + syntax = syntax + '>'; + var parsed = macroService.parseMacroSyntax(syntax); + if (!parsed) { + parsed = {}; + } + parsed.syntax = syntax; + collectDetails(parsed); + $scope.renderModel.push(parsed); + setSortingState($scope.renderModel); + } + }); + } + function collectDetails(macro) { + macro.details = ''; + macro.icon = 'icon-settings-alt'; + if (macro.macroParamsDictionary) { + angular.forEach(macro.macroParamsDictionary, function (value, key) { + macro.details += key + ': ' + value + ' '; + }); + } + } + function openDialog(index) { + var dialogData = { allowedMacros: $scope.model.config.allowed }; + if (index !== null && $scope.renderModel[index]) { + var macro = $scope.renderModel[index]; + dialogData['macroData'] = macro; + } + $scope.macroPickerOverlay = {}; + $scope.macroPickerOverlay.view = 'macropicker'; + $scope.macroPickerOverlay.dialogData = dialogData; + $scope.macroPickerOverlay.show = true; + $scope.macroPickerOverlay.submit = function (model) { + var macroObject = macroService.collectValueData(model.selectedMacro, model.macroParams, dialogData.renderingEngine); + collectDetails(macroObject); + //update the raw syntax and the list... + if (index !== null && $scope.renderModel[index]) { + $scope.renderModel[index] = macroObject; + } else { + $scope.renderModel.push(macroObject); + } + setSortingState($scope.renderModel); + $scope.macroPickerOverlay.show = false; + $scope.macroPickerOverlay = null; + }; + $scope.macroPickerOverlay.close = function (oldModel) { + $scope.macroPickerOverlay.show = false; + $scope.macroPickerOverlay = null; + }; + } + $scope.edit = function (index) { + openDialog(index); + }; + $scope.add = function () { + if ($scope.model.config.max && $scope.model.config.max > 0 && $scope.renderModel.length >= $scope.model.config.max) { //cannot add more than the max - return; - } - - openDialog(); - }; - - $scope.remove =function(index){ - $scope.renderModel.splice(index, 1); - }; - - $scope.clear = function() { - $scope.model.value = ""; - $scope.renderModel = []; - }; - - var unsubscribe = $scope.$on("formSubmitting", function (ev, args) { - var syntax = []; - angular.forEach($scope.renderModel, function(value, key){ - syntax.push(value.syntax); - }); - - $scope.model.value = syntax.join(""); - }); - - //when the scope is destroyed we need to unsubscribe - $scope.$on('$destroy', function () { - unsubscribe(); - }); - - - function trim(str, chr) { - var rgxtrim = (!chr) ? new RegExp('^\\s+|\\s+$', 'g') : new RegExp('^'+chr+'+|'+chr+'+$', 'g'); - return str.replace(rgxtrim, ''); - } - -}); - -function MacroListController($scope, entityResource) { - - $scope.items = []; - - entityResource.getAll("Macro").then(function(items) { - _.each(items, function(i) { - $scope.items.push({ name: i.name, alias: i.alias }); + return; + } + openDialog(); + }; + $scope.remove = function (index) { + $scope.renderModel.splice(index, 1); + setSortingState($scope.renderModel); + }; + $scope.clear = function () { + $scope.model.value = ''; + $scope.renderModel = []; + }; + var unsubscribe = $scope.$on('formSubmitting', function (ev, args) { + var syntax = []; + angular.forEach($scope.renderModel, function (value, key) { + syntax.push(value.syntax); + }); + $scope.model.value = syntax.join(''); }); - + //when the scope is destroyed we need to unsubscribe + $scope.$on('$destroy', function () { + unsubscribe(); + }); + function trim(str, chr) { + var rgxtrim = !chr ? new RegExp('^\\s+|\\s+$', 'g') : new RegExp('^' + chr + '+|' + chr + '+$', 'g'); + return str.replace(rgxtrim, ''); + } + function setSortingState(items) { + // disable sorting if the list only consist of one item + if (items.length > 1) { + $scope.sortableOptions.disabled = false; + } else { + $scope.sortableOptions.disabled = true; + } + } }); - - -} - -angular.module("umbraco").controller("Umbraco.PrevalueEditors.MacroList", MacroListController); - -//inject umbracos assetsServce and dialog service -function MarkdownEditorController($scope, $element, assetsService, dialogService, angularHelper, $timeout) { - - //tell the assets service to load the markdown.editor libs from the markdown editors - //plugin folder - - if ($scope.model.value === null || $scope.model.value === "") { - $scope.model.value = $scope.model.config.defaultValue; - } - - function openMediaPicker(callback) { - - $scope.mediaPickerOverlay = {}; - $scope.mediaPickerOverlay.view = "mediaPicker"; - $scope.mediaPickerOverlay.show = true; - $scope.mediaPickerOverlay.disableFolderSelect = true; - - $scope.mediaPickerOverlay.submit = function(model) { - - var selectedImagePath = model.selectedImages[0].image; - callback(selectedImagePath); - - $scope.mediaPickerOverlay.show = false; - $scope.mediaPickerOverlay = null; - }; - - $scope.mediaPickerOverlay.close = function(model) { - $scope.mediaPickerOverlay.show = false; - $scope.mediaPickerOverlay = null; - }; - - } - - assetsService - .load([ - "lib/markdown/markdown.converter.js", - "lib/markdown/markdown.sanitizer.js", - "lib/markdown/markdown.editor.js" - ]) - .then(function () { - - // we need a short delay to wait for the textbox to appear. - setTimeout(function () { - //this function will execute when all dependencies have loaded - // but in the case that they've been previously loaded, we can only - // init the md editor after this digest because the DOM needs to be ready first - // so run the init on a timeout - $timeout(function () { - var converter2 = new Markdown.Converter(); - var editor2 = new Markdown.Editor(converter2, "-" + $scope.model.alias); - editor2.run(); - - //subscribe to the image dialog clicks - editor2.hooks.set("insertImageDialog", function (callback) { - openMediaPicker(callback); - return true; // tell the editor that we'll take care of getting the image 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()) { - angularHelper.getCurrentForm($scope).$setDirty(); - $scope.model.value = $("textarea", $element).val(); - } - }); - - }, 200); - }); - - //load the seperat css for the editor to avoid it blocking our js loading TEMP HACK - assetsService.loadCss("lib/markdown/markdown.css"); - }) -} - -angular.module("umbraco").controller("Umbraco.PropertyEditors.MarkdownEditorController", MarkdownEditorController); - -//this controller simply tells the dialogs service to open a mediaPicker window -//with a specified callback, this callback will receive an object with a selection on it -angular.module('umbraco').controller("Umbraco.PropertyEditors.MediaPickerController", - function ($rootScope, $scope, dialogService, entityResource, mediaResource, mediaHelper, $timeout, userService, $location) { - - //check the pre-values for multi-picker - var multiPicker = $scope.model.config.multiPicker && $scope.model.config.multiPicker !== '0' ? true : false; - var onlyImages = $scope.model.config.onlyImages && $scope.model.config.onlyImages !== '0' ? true : false; - var disableFolderSelect = $scope.model.config.disableFolderSelect && $scope.model.config.disableFolderSelect !== '0' ? true : false; - - if (!$scope.model.config.startNodeId) { - userService.getCurrentUser().then(function (userData) { - $scope.model.config.startNodeId = userData.startMediaId; - }); - } - - function setupViewModel() { - $scope.images = []; - $scope.ids = []; - - if ($scope.model.value) { - var ids = $scope.model.value.split(','); - - //NOTE: We need to use the entityResource NOT the mediaResource here because - // the mediaResource has server side auth configured for which the user must have - // access to the media section, if they don't they'll get auth errors. The entityResource - // acts differently in that it allows access if the user has access to any of the apps that - // might require it's use. Therefore we need to use the metatData property to get at the thumbnail - // value. - - entityResource.getByIds(ids, "Media").then(function (medias) { - - _.each(medias, function (media, i) { - - //only show non-trashed items - if (media.parentId >= -1) { - - if (!media.thumbnail) { - media.thumbnail = mediaHelper.resolveFileFromEntity(media, true); - } - - $scope.images.push(media); - $scope.ids.push(media.id); - } - }); - - $scope.sync(); - }); - } - } - - setupViewModel(); - - $scope.remove = function(index) { - $scope.images.splice(index, 1); - $scope.ids.splice(index, 1); - $scope.sync(); - }; - - $scope.goToItem = function(item) { - $location.path('media/media/edit/' + item.id); - }; - - $scope.add = function() { - - $scope.mediaPickerOverlay = { - view: "mediapicker", - title: "Select media", - startNodeId: $scope.model.config.startNodeId, - multiPicker: multiPicker, - onlyImages: onlyImages, - disableFolderSelect: disableFolderSelect, - show: true, - submit: function(model) { - - _.each(model.selectedImages, function(media, i) { - - if (!media.thumbnail) { - media.thumbnail = mediaHelper.resolveFileFromEntity(media, true); - } - - $scope.images.push(media); - $scope.ids.push(media.id); - }); - - $scope.sync(); - - $scope.mediaPickerOverlay.show = false; - $scope.mediaPickerOverlay = null; - - } - }; - - }; - - $scope.sortableOptions = { - update: function(e, ui) { - var r = []; - //TODO: Instead of doing this with a half second delay would be better to use a watch like we do in the - // content picker. THen we don't have to worry about setting ids, render models, models, we just set one and let the - // watch do all the rest. - $timeout(function(){ - angular.forEach($scope.images, function(value, key){ - r.push(value.id); - }); - - $scope.ids = r; - $scope.sync(); - }, 500, false); - } - }; - - $scope.sync = function() { - $scope.model.value = $scope.ids.join(); - }; - - $scope.showAdd = function () { - if (!multiPicker) { - if ($scope.model.value && $scope.model.value !== "") { - return false; - } - } - return true; - }; - - //here we declare a special method which will be called whenever the value has changed from the server - //this is instead of doing a watch on the model.value = faster - $scope.model.onValueChanged = function (newVal, oldVal) { - //update the display val again if it has changed from the server - setupViewModel(); - }; - - }); - -//this controller simply tells the dialogs service to open a memberPicker window -//with a specified callback, this callback will receive an object with a selection on it -function memberGroupPicker($scope, dialogService){ - - function trim(str, chr) { - var rgxtrim = (!chr) ? new RegExp('^\\s+|\\s+$', 'g') : new RegExp('^' + chr + '+|' + chr + '+$', 'g'); - return str.replace(rgxtrim, ''); - } - - $scope.renderModel = []; - - if ($scope.model.value) { - var modelIds = $scope.model.value.split(','); - _.each(modelIds, function (item, i) { - $scope.renderModel.push({ name: item, id: item, icon: 'icon-users' }); - }); - } - - $scope.openMemberGroupPicker = function() { - - $scope.memberGroupPicker = {}; - $scope.memberGroupPicker.multiPicker = true; - $scope.memberGroupPicker.view = "memberGroupPicker"; - $scope.memberGroupPicker.show = true; - - $scope.memberGroupPicker.submit = function(model) { - - if(model.selectedMemberGroups) { - _.each(model.selectedMemberGroups, function (item, i) { - $scope.add(item); - }); - } - - if(model.selectedMemberGroup) { - $scope.clear(); - $scope.add(model.selectedMemberGroup); - } - - $scope.memberGroupPicker.show = false; - $scope.memberGroupPicker = null; - }; - - $scope.memberGroupPicker.close = function(oldModel) { - $scope.memberGroupPicker.show = false; - $scope.memberGroupPicker = null; - }; - - }; - - $scope.remove =function(index){ - $scope.renderModel.splice(index, 1); - }; - - $scope.add = function (item) { - var currIds = _.map($scope.renderModel, function (i) { - return i.id; - }); - - if (currIds.indexOf(item) < 0) { - $scope.renderModel.push({ name: item, id: item, icon: 'icon-users' }); - } - }; - - $scope.clear = function() { - $scope.renderModel = []; - }; - - var unsubscribe = $scope.$on("formSubmitting", function (ev, args) { - var currIds = _.map($scope.renderModel, function (i) { - return i.id; - }); - $scope.model.value = trim(currIds.join(), ","); - }); - - //when the scope is destroyed we need to unsubscribe - $scope.$on('$destroy', function () { - unsubscribe(); - }); - -} - -angular.module('umbraco').controller("Umbraco.PropertyEditors.MemberGroupPickerController", memberGroupPicker); - -function memberGroupController($rootScope, $scope, dialogService, mediaResource, imageHelper, $log) { - - //set the available to the keys of the dictionary who's value is true - $scope.getAvailable = function () { - var available = []; - for (var n in $scope.model.value) { - if ($scope.model.value[n] === false) { - available.push(n); - } - } - return available; - }; - //set the selected to the keys of the dictionary who's value is true - $scope.getSelected = function () { - var selected = []; - for (var n in $scope.model.value) { - if ($scope.model.value[n] === true) { - selected.push(n); - } - } - return selected; - }; - - $scope.addItem = function(item) { - //keep the model up to date - $scope.model.value[item] = true; - }; - - $scope.removeItem = function (item) { - //keep the model up to date - $scope.model.value[item] = false; - }; - - -} -angular.module('umbraco').controller("Umbraco.PropertyEditors.MemberGroupController", memberGroupController); -//this controller simply tells the dialogs service to open a memberPicker window -//with a specified callback, this callback will receive an object with a selection on it -function memberPickerController($scope, dialogService, entityResource, $log, iconHelper, angularHelper){ - - function trim(str, chr) { - var rgxtrim = (!chr) ? new RegExp('^\\s+|\\s+$', 'g') : new RegExp('^' + chr + '+|' + chr + '+$', 'g'); - return str.replace(rgxtrim, ''); - } - - $scope.renderModel = []; - - var dialogOptions = { - multiPicker: false, - entityType: "Member", - section: "member", - treeAlias: "member", - filter: function(i) { - return i.metaData.isContainer == true; - }, - filterCssClass: "not-allowed", - callback: function(data) { - if (angular.isArray(data)) { - _.each(data, function (item, i) { - $scope.add(item); - }); - } else { - $scope.clear(); - $scope.add(data); - } - angularHelper.getCurrentForm($scope).$setDirty(); - } - }; - - //since most of the pre-value config's are used in the dialog options (i.e. maxNumber, minNumber, etc...) we'll merge the - // pre-value config on to the dialog options - if ($scope.model.config) { - angular.extend(dialogOptions, $scope.model.config); - } - - $scope.openMemberPicker = function() { - $scope.memberPickerOverlay = dialogOptions; - $scope.memberPickerOverlay.view = "memberPicker"; - $scope.memberPickerOverlay.show = true; - - $scope.memberPickerOverlay.submit = function(model) { - - if (model.selection) { - _.each(model.selection, function(item, i) { - $scope.add(item); - }); - } - - $scope.memberPickerOverlay.show = false; - $scope.memberPickerOverlay = null; - }; - - $scope.memberPickerOverlay.close = function(oldModel) { - $scope.memberPickerOverlay.show = false; - $scope.memberPickerOverlay = null; - }; - - }; - - $scope.remove =function(index){ - $scope.renderModel.splice(index, 1); - }; - - $scope.add = function (item) { - var currIds = _.map($scope.renderModel, function (i) { - return i.id; - }); - - if (currIds.indexOf(item.id) < 0) { - item.icon = iconHelper.convertFromLegacyIcon(item.icon); - $scope.renderModel.push({name: item.name, id: item.id, icon: item.icon}); - } - }; - - $scope.clear = function() { - $scope.renderModel = []; - }; - - var unsubscribe = $scope.$on("formSubmitting", function (ev, args) { - var currIds = _.map($scope.renderModel, function (i) { - return i.id; - }); - $scope.model.value = trim(currIds.join(), ","); - }); - - //when the scope is destroyed we need to unsubscribe - $scope.$on('$destroy', function () { - unsubscribe(); - }); - - //load member data - var modelIds = $scope.model.value ? $scope.model.value.split(',') : []; - entityResource.getByIds(modelIds, "Member").then(function (data) { - _.each(data, function (item, i) { - item.icon = iconHelper.convertFromLegacyIcon(item.icon); - $scope.renderModel.push({ name: item.name, id: item.id, icon: item.icon }); - }); - }); -} - - -angular.module('umbraco').controller("Umbraco.PropertyEditors.MemberPickerController", memberPickerController); - -function MultipleTextBoxController($scope) { - - $scope.sortableOptions = { - axis: 'y', - containment: 'parent', - cursor: 'move', - items: '> div.control-group', - tolerance: 'pointer' - }; - - if (!$scope.model.value) { - $scope.model.value = []; - } - - //add any fields that there isn't values for - if ($scope.model.config.min > 0) { - for (var i = 0; i < $scope.model.config.min; i++) { - if ((i + 1) > $scope.model.value.length) { - $scope.model.value.push({ value: "" }); - } - } - } - - $scope.add = function () { - if ($scope.model.config.max <= 0 || $scope.model.value.length < $scope.model.config.max) { - $scope.model.value.push({ value: "" }); - } - }; - - $scope.remove = function(index) { - var remainder = []; - for (var x = 0; x < $scope.model.value.length; x++) { - if (x !== index) { - remainder.push($scope.model.value[x]); - } - } - $scope.model.value = remainder; - }; - -} - -angular.module("umbraco").controller("Umbraco.PropertyEditors.MultipleTextBoxController", MultipleTextBoxController); - -angular.module("umbraco").controller("Umbraco.PropertyEditors.RadioButtonsController", - function($scope) { - + function MacroListController($scope, entityResource) { + $scope.items = []; + entityResource.getAll('Macro').then(function (items) { + _.each(items, function (i) { + $scope.items.push({ + name: i.name, + alias: i.alias + }); + }); + }); + } + angular.module('umbraco').controller('Umbraco.PrevalueEditors.MacroList', MacroListController); + //inject umbracos assetsServce and dialog service + function MarkdownEditorController($scope, $element, assetsService, dialogService, angularHelper, $timeout) { + //tell the assets service to load the markdown.editor libs from the markdown editors + //plugin folder + if ($scope.model.value === null || $scope.model.value === '') { + $scope.model.value = $scope.model.config.defaultValue; + } + function openMediaPicker(callback) { + $scope.mediaPickerOverlay = {}; + $scope.mediaPickerOverlay.view = 'mediaPicker'; + $scope.mediaPickerOverlay.show = true; + $scope.mediaPickerOverlay.disableFolderSelect = true; + $scope.mediaPickerOverlay.submit = function (model) { + var selectedImagePath = model.selectedImages[0].image; + callback(selectedImagePath); + $scope.mediaPickerOverlay.show = false; + $scope.mediaPickerOverlay = null; + }; + $scope.mediaPickerOverlay.close = function (model) { + $scope.mediaPickerOverlay.show = false; + $scope.mediaPickerOverlay = null; + }; + } + assetsService.load([ + 'lib/markdown/markdown.converter.js', + 'lib/markdown/markdown.sanitizer.js', + 'lib/markdown/markdown.editor.js' + ]).then(function () { + // we need a short delay to wait for the textbox to appear. + setTimeout(function () { + //this function will execute when all dependencies have loaded + // but in the case that they've been previously loaded, we can only + // init the md editor after this digest because the DOM needs to be ready first + // so run the init on a timeout + $timeout(function () { + $scope.markdownEditorInitComplete = false; + var converter2 = new Markdown.Converter(); + var editor2 = new Markdown.Editor(converter2, '-' + $scope.model.alias); + editor2.run(); + //subscribe to the image dialog clicks + editor2.hooks.set('insertImageDialog', function (callback) { + openMediaPicker(callback); + return true; // tell the editor that we'll take care of getting the image 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()) { + if ($scope.markdownEditorInitComplete) { + //only set dirty after init load to avoid "unsaved" dialogue when we don't want it + angularHelper.getCurrentForm($scope).$setDirty(); + } else { + $scope.markdownEditorInitComplete = true; + } + $scope.model.value = $('textarea', $element).val(); + } + }); + }, 200); + }); + //load the seperat css for the editor to avoid it blocking our js loading TEMP HACK + assetsService.loadCss('lib/markdown/markdown.css', $scope); + }); + } + angular.module('umbraco').controller('Umbraco.PropertyEditors.MarkdownEditorController', MarkdownEditorController); + //this controller simply tells the dialogs service to open a mediaPicker window + //with a specified callback, this callback will receive an object with a selection on it + angular.module('umbraco').controller('Umbraco.PropertyEditors.MediaPickerController', function ($rootScope, $scope, dialogService, entityResource, mediaResource, mediaHelper, $timeout, userService, $location, localizationService) { + //check the pre-values for multi-picker + var multiPicker = $scope.model.config.multiPicker && $scope.model.config.multiPicker !== '0' ? true : false; + var onlyImages = $scope.model.config.onlyImages && $scope.model.config.onlyImages !== '0' ? true : false; + var disableFolderSelect = $scope.model.config.disableFolderSelect && $scope.model.config.disableFolderSelect !== '0' ? true : false; + if (!$scope.model.config.startNodeId) { + userService.getCurrentUser().then(function (userData) { + $scope.model.config.startNodeId = userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0]; + $scope.model.config.startNodeIsVirtual = userData.startMediaIds.length !== 1; + }); + } + function setupViewModel() { + $scope.images = []; + $scope.ids = []; + $scope.isMultiPicker = multiPicker; + if ($scope.model.value) { + var ids = $scope.model.value.split(','); + //NOTE: We need to use the entityResource NOT the mediaResource here because + // the mediaResource has server side auth configured for which the user must have + // access to the media section, if they don't they'll get auth errors. The entityResource + // acts differently in that it allows access if the user has access to any of the apps that + // might require it's use. Therefore we need to use the metaData property to get at the thumbnail + // value. + entityResource.getByIds(ids, 'Media').then(function (medias) { + // The service only returns item results for ids that exist (deleted items are silently ignored). + // This results in the picked items value to be set to contain only ids of picked items that could actually be found. + // Since a referenced item could potentially be restored later on, instead of changing the selected values here based + // on whether the items exist during a save event - we should keep "placeholder" items for picked items that currently + // could not be fetched. This will preserve references and ensure that the state of an item does not differ depending + // on whether it is simply resaved or not. + // This is done by remapping the int/guid ids into a new array of items, where we create "Deleted item" placeholders + // when there is no match for a selected id. This will ensure that the values being set on save, are the same as before. + medias = _.map(ids, function (id) { + var found = _.find(medias, function (m) { + // We could use coercion (two ='s) here .. but not sure if this works equally well in all browsers and + // it's prone to someone "fixing" it at some point without knowing the effects. Rather use toString() + // compares and be completely sure it works. + return m.udi.toString() === id.toString() || m.id.toString() === id.toString(); + }); + if (found) { + return found; + } else { + return { + name: localizationService.dictionary.mediaPicker_deletedItem, + id: $scope.model.config.idType !== 'udi' ? id : null, + udi: $scope.model.config.idType === 'udi' ? id : null, + icon: 'icon-picture', + thumbnail: null, + trashed: true + }; + } + }); + _.each(medias, function (media, i) { + // if there is no thumbnail, try getting one if the media is not a placeholder item + if (!media.thumbnail && media.id && media.metaData) { + media.thumbnail = mediaHelper.resolveFileFromEntity(media, true); + } + $scope.images.push(media); + if ($scope.model.config.idType === 'udi') { + $scope.ids.push(media.udi); + } else { + $scope.ids.push(media.id); + } + }); + $scope.sync(); + }); + } + } + setupViewModel(); + $scope.remove = function (index) { + $scope.images.splice(index, 1); + $scope.ids.splice(index, 1); + $scope.sync(); + }; + $scope.goToItem = function (item) { + $location.path('media/media/edit/' + item.id); + }; + $scope.add = function () { + $scope.mediaPickerOverlay = { + view: 'mediapicker', + title: 'Select media', + startNodeId: $scope.model.config.startNodeId, + startNodeIsVirtual: $scope.model.config.startNodeIsVirtual, + multiPicker: multiPicker, + onlyImages: onlyImages, + disableFolderSelect: disableFolderSelect, + show: true, + submit: function (model) { + _.each(model.selectedImages, function (media, i) { + // if there is no thumbnail, try getting one if the media is not a placeholder item + if (!media.thumbnail && media.id && media.metaData) { + media.thumbnail = mediaHelper.resolveFileFromEntity(media, true); + } + $scope.images.push(media); + if ($scope.model.config.idType === 'udi') { + $scope.ids.push(media.udi); + } else { + $scope.ids.push(media.id); + } + }); + $scope.sync(); + $scope.mediaPickerOverlay.show = false; + $scope.mediaPickerOverlay = null; + } + }; + }; + $scope.sortableOptions = { + disabled: !$scope.isMultiPicker, + items: 'li:not(.add-wrapper)', + cancel: '.unsortable', + update: function (e, ui) { + var r = []; + // TODO: Instead of doing this with a half second delay would be better to use a watch like we do in the + // content picker. Then we don't have to worry about setting ids, render models, models, we just set one and let the + // watch do all the rest. + $timeout(function () { + angular.forEach($scope.images, function (value, key) { + r.push($scope.model.config.idType === 'udi' ? value.udi : value.id); + }); + $scope.ids = r; + $scope.sync(); + }, 500, false); + } + }; + $scope.sync = function () { + $scope.model.value = $scope.ids.join(); + }; + $scope.showAdd = function () { + if (!multiPicker) { + if ($scope.model.value && $scope.model.value !== '') { + return false; + } + } + return true; + }; + //here we declare a special method which will be called whenever the value has changed from the server + //this is instead of doing a watch on the model.value = faster + $scope.model.onValueChanged = function (newVal, oldVal) { + //update the display val again if it has changed from the server + setupViewModel(); + }; + }); + //this controller simply tells the dialogs service to open a memberPicker window + //with a specified callback, this callback will receive an object with a selection on it + function memberGroupPicker($scope, dialogService) { + function trim(str, chr) { + var rgxtrim = !chr ? new RegExp('^\\s+|\\s+$', 'g') : new RegExp('^' + chr + '+|' + chr + '+$', 'g'); + return str.replace(rgxtrim, ''); + } + $scope.renderModel = []; + $scope.allowRemove = true; + if ($scope.model.value) { + var modelIds = $scope.model.value.split(','); + _.each(modelIds, function (item, i) { + $scope.renderModel.push({ + name: item, + id: item, + icon: 'icon-users' + }); + }); + } + $scope.openMemberGroupPicker = function () { + $scope.memberGroupPicker = {}; + $scope.memberGroupPicker.multiPicker = true; + $scope.memberGroupPicker.view = 'memberGroupPicker'; + $scope.memberGroupPicker.show = true; + $scope.memberGroupPicker.submit = function (model) { + if (model.selectedMemberGroups) { + _.each(model.selectedMemberGroups, function (item, i) { + $scope.add(item); + }); + } + if (model.selectedMemberGroup) { + $scope.clear(); + $scope.add(model.selectedMemberGroup); + } + $scope.memberGroupPicker.show = false; + $scope.memberGroupPicker = null; + }; + $scope.memberGroupPicker.close = function (oldModel) { + $scope.memberGroupPicker.show = false; + $scope.memberGroupPicker = null; + }; + }; + $scope.remove = function (index) { + $scope.renderModel.splice(index, 1); + }; + $scope.add = function (item) { + var currIds = _.map($scope.renderModel, function (i) { + return i.id; + }); + if (currIds.indexOf(item) < 0) { + $scope.renderModel.push({ + name: item, + id: item, + icon: 'icon-users' + }); + } + }; + $scope.clear = function () { + $scope.renderModel = []; + }; + var unsubscribe = $scope.$on('formSubmitting', function (ev, args) { + var currIds = _.map($scope.renderModel, function (i) { + return i.id; + }); + $scope.model.value = trim(currIds.join(), ','); + }); + //when the scope is destroyed we need to unsubscribe + $scope.$on('$destroy', function () { + unsubscribe(); + }); + } + angular.module('umbraco').controller('Umbraco.PropertyEditors.MemberGroupPickerController', memberGroupPicker); + function memberGroupController($rootScope, $scope, dialogService, mediaResource, imageHelper, $log) { + //set the available to the keys of the dictionary who's value is true + $scope.getAvailable = function () { + var available = []; + for (var n in $scope.model.value) { + if ($scope.model.value[n] === false) { + available.push(n); + } + } + return available; + }; + //set the selected to the keys of the dictionary who's value is true + $scope.getSelected = function () { + var selected = []; + for (var n in $scope.model.value) { + if ($scope.model.value[n] === true) { + selected.push(n); + } + } + return selected; + }; + $scope.addItem = function (item) { + //keep the model up to date + $scope.model.value[item] = true; + }; + $scope.removeItem = function (item) { + //keep the model up to date + $scope.model.value[item] = false; + }; + } + angular.module('umbraco').controller('Umbraco.PropertyEditors.MemberGroupController', memberGroupController); + //this controller simply tells the dialogs service to open a memberPicker window + //with a specified callback, this callback will receive an object with a selection on it + function memberPickerController($scope, dialogService, entityResource, $log, iconHelper, angularHelper) { + function trim(str, chr) { + var rgxtrim = !chr ? new RegExp('^\\s+|\\s+$', 'g') : new RegExp('^' + chr + '+|' + chr + '+$', 'g'); + return str.replace(rgxtrim, ''); + } + $scope.renderModel = []; + $scope.allowRemove = true; + var dialogOptions = { + multiPicker: false, + entityType: 'Member', + section: 'member', + treeAlias: 'member', + filter: function (i) { + return i.metaData.isContainer == true; + }, + filterCssClass: 'not-allowed', + callback: function (data) { + if (angular.isArray(data)) { + _.each(data, function (item, i) { + $scope.add(item); + }); + } else { + $scope.clear(); + $scope.add(data); + } + angularHelper.getCurrentForm($scope).$setDirty(); + } + }; + //since most of the pre-value config's are used in the dialog options (i.e. maxNumber, minNumber, etc...) we'll merge the + // pre-value config on to the dialog options + if ($scope.model.config) { + angular.extend(dialogOptions, $scope.model.config); + } + $scope.openMemberPicker = function () { + $scope.memberPickerOverlay = dialogOptions; + $scope.memberPickerOverlay.view = 'memberPicker'; + $scope.memberPickerOverlay.show = true; + $scope.memberPickerOverlay.submit = function (model) { + if (model.selection) { + _.each(model.selection, function (item, i) { + $scope.add(item); + }); + } + $scope.memberPickerOverlay.show = false; + $scope.memberPickerOverlay = null; + }; + $scope.memberPickerOverlay.close = function (oldModel) { + $scope.memberPickerOverlay.show = false; + $scope.memberPickerOverlay = null; + }; + }; + $scope.remove = function (index) { + $scope.renderModel.splice(index, 1); + }; + $scope.add = function (item) { + var currIds = _.map($scope.renderModel, function (i) { + if ($scope.model.config.idType === 'udi') { + return i.udi; + } else { + return i.id; + } + }); + var itemId = $scope.model.config.idType === 'udi' ? item.udi : item.id; + if (currIds.indexOf(itemId) < 0) { + item.icon = iconHelper.convertFromLegacyIcon(item.icon); + $scope.renderModel.push({ + name: item.name, + id: item.id, + udi: item.udi, + icon: item.icon + }); + } + }; + $scope.clear = function () { + $scope.renderModel = []; + }; + var unsubscribe = $scope.$on('formSubmitting', function (ev, args) { + var currIds = _.map($scope.renderModel, function (i) { + if ($scope.model.config.idType === 'udi') { + return i.udi; + } else { + return i.id; + } + }); + $scope.model.value = trim(currIds.join(), ','); + }); + //when the scope is destroyed we need to unsubscribe + $scope.$on('$destroy', function () { + unsubscribe(); + }); + //load member data + var modelIds = $scope.model.value ? $scope.model.value.split(',') : []; + entityResource.getByIds(modelIds, 'Member').then(function (data) { + _.each(data, function (item, i) { + // set default icon if it's missing + item.icon = item.icon ? iconHelper.convertFromLegacyIcon(item.icon) : 'icon-user'; + $scope.renderModel.push({ + name: item.name, + id: item.id, + udi: item.udi, + icon: item.icon + }); + }); + }); + } + angular.module('umbraco').controller('Umbraco.PropertyEditors.MemberPickerController', memberPickerController); + function MultipleTextBoxController($scope, $timeout) { + var backspaceHits = 0; + $scope.sortableOptions = { + axis: 'y', + containment: 'parent', + cursor: 'move', + items: '> div.textbox-wrapper', + tolerance: 'pointer' + }; + if (!$scope.model.value) { + $scope.model.value = []; + } + //add any fields that there isn't values for + if ($scope.model.config.min > 0) { + for (var i = 0; i < $scope.model.config.min; i++) { + if (i + 1 > $scope.model.value.length) { + $scope.model.value.push({ value: '' }); + } + } + } + $scope.addRemoveOnKeyDown = function (event, index) { + var txtBoxValue = $scope.model.value[index]; + event.preventDefault(); + switch (event.keyCode) { + case 13: + if ($scope.model.config.max <= 0 && txtBoxValue.value || $scope.model.value.length < $scope.model.config.max && txtBoxValue.value) { + var newItemIndex = index + 1; + $scope.model.value.splice(newItemIndex, 0, { value: '' }); + //Focus on the newly added value + $scope.model.value[newItemIndex].hasFocus = true; + } + break; + case 8: + if ($scope.model.value.length > $scope.model.config.min) { + var remainder = []; + // Used to require an extra hit on backspace for the field to be removed + if (txtBoxValue.value === '') { + backspaceHits++; + } else { + backspaceHits = 0; + } + if (txtBoxValue.value === '' && backspaceHits === 2) { + for (var x = 0; x < $scope.model.value.length; x++) { + if (x !== index) { + remainder.push($scope.model.value[x]); + } + } + $scope.model.value = remainder; + var prevItemIndex = index - 1; + //Set focus back on false as the directive only watches for true + if (prevItemIndex >= 0) { + $scope.model.value[prevItemIndex].hasFocus = false; + $timeout(function () { + //Focus on the previous value + $scope.model.value[prevItemIndex].hasFocus = true; + }); + } + backspaceHits = 0; + } + } + break; + default: + } + }; + $scope.add = function () { + if ($scope.model.config.max <= 0 || $scope.model.value.length < $scope.model.config.max) { + $scope.model.value.push({ value: '' }); + // focus new value + var newItemIndex = $scope.model.value.length - 1; + $scope.model.value[newItemIndex].hasFocus = true; + } + }; + $scope.remove = function (index) { + var remainder = []; + for (var x = 0; x < $scope.model.value.length; x++) { + if (x !== index) { + remainder.push($scope.model.value[x]); + } + } + $scope.model.value = remainder; + }; + } + angular.module('umbraco').controller('Umbraco.PropertyEditors.MultipleTextBoxController', MultipleTextBoxController); + angular.module('umbraco').controller('Umbraco.PropertyEditors.NestedContent.DocTypePickerController', [ + '$scope', + 'Umbraco.PropertyEditors.NestedContent.Resources', + function ($scope, ncResources) { + $scope.add = function () { + $scope.model.value.push({ + // As per PR #4, all stored content type aliases must be prefixed "nc" for easier recognition. + // For good measure we'll also prefix the tab alias "nc" + ncAlias: '', + ncTabAlias: '', + nameTemplate: '' + }); + }; + $scope.remove = function (index) { + $scope.model.value.splice(index, 1); + }; + $scope.sortableOptions = { + axis: 'y', + cursor: 'move', + handle: '.icon-navigation' + }; + $scope.docTypeTabs = {}; + ncResources.getContentTypes().then(function (docTypes) { + $scope.model.docTypes = docTypes; + // Populate document type tab dictionary + docTypes.forEach(function (value) { + $scope.docTypeTabs[value.alias] = value.tabs; + }); + }); + if (!$scope.model.value) { + $scope.model.value = []; + $scope.add(); + } + } + ]); + angular.module('umbraco').controller('Umbraco.PropertyEditors.NestedContent.PropertyEditorController', [ + '$scope', + '$interpolate', + '$filter', + '$timeout', + 'contentResource', + 'localizationService', + 'iconHelper', + function ($scope, $interpolate, $filter, $timeout, contentResource, localizationService, iconHelper) { + //$scope.model.config.contentTypes; + //$scope.model.config.minItems; + //$scope.model.config.maxItems; + //console.log($scope); + var inited = false; + _.each($scope.model.config.contentTypes, function (contentType) { + contentType.nameExp = !!contentType.nameTemplate ? $interpolate(contentType.nameTemplate) : undefined; + }); + $scope.editIconTitle = ''; + $scope.moveIconTitle = ''; + $scope.deleteIconTitle = ''; + // localize the edit icon title + localizationService.localize('general_edit').then(function (value) { + $scope.editIconTitle = value; + }); + // localize the delete icon title + localizationService.localize('general_delete').then(function (value) { + $scope.deleteIconTitle = value; + }); + // localize the move icon title + localizationService.localize('actions_move').then(function (value) { + $scope.moveIconTitle = value; + }); + $scope.nodes = []; + $scope.currentNode = undefined; + $scope.realCurrentNode = undefined; + $scope.scaffolds = undefined; + $scope.sorting = false; + $scope.minItems = $scope.model.config.minItems || 0; + $scope.maxItems = $scope.model.config.maxItems || 0; + if ($scope.maxItems == 0) + $scope.maxItems = 1000; + $scope.singleMode = $scope.minItems == 1 && $scope.maxItems == 1; + $scope.showIcons = $scope.model.config.showIcons || true; + $scope.wideMode = $scope.model.config.hideLabel == '1'; + $scope.overlayMenu = { + show: false, + style: {}, + showFilter: false + }; + // helper to force the current form into the dirty state + $scope.setDirty = function () { + if ($scope.propertyForm) { + $scope.propertyForm.$setDirty(); + } + }; + $scope.addNode = function (alias) { + var scaffold = $scope.getScaffold(alias); + var newNode = initNode(scaffold, null); + $scope.currentNode = newNode; + $scope.setDirty(); + $scope.closeNodeTypePicker(); + }; + $scope.openNodeTypePicker = function (event) { + if ($scope.nodes.length >= $scope.maxItems) { + return; + } + // this could be used for future limiting on node types + $scope.overlayMenu.scaffolds = []; + _.each($scope.scaffolds, function (scaffold) { + $scope.overlayMenu.scaffolds.push({ + alias: scaffold.contentTypeAlias, + name: scaffold.contentTypeName, + icon: iconHelper.convertFromLegacyIcon(scaffold.icon) + }); + }); + if ($scope.overlayMenu.scaffolds.length == 0) { + return; + } + if ($scope.overlayMenu.scaffolds.length == 1) { + // only one scaffold type - no need to display the picker + $scope.addNode($scope.scaffolds[0].contentTypeAlias); + return; + } + $scope.overlayMenu.show = true; + }; + $scope.closeNodeTypePicker = function () { + $scope.overlayMenu.show = false; + }; + $scope.editNode = function (idx) { + if ($scope.currentNode && $scope.currentNode.key == $scope.nodes[idx].key) { + $scope.currentNode = undefined; + } else { + $scope.currentNode = $scope.nodes[idx]; + } + }; + $scope.deleteNode = function (idx) { + if ($scope.nodes.length > $scope.model.config.minItems) { + if ($scope.model.config.confirmDeletes && $scope.model.config.confirmDeletes == 1) { + localizationService.localize('content_nestedContentDeleteItem').then(function (value) { + if (confirm(value)) { + $scope.nodes.splice(idx, 1); + $scope.setDirty(); + updateModel(); + } + }); + } else { + $scope.nodes.splice(idx, 1); + $scope.setDirty(); + updateModel(); + } + } + }; + $scope.getName = function (idx) { + var name = 'Item ' + (idx + 1); + if ($scope.model.value[idx]) { + var contentType = $scope.getContentTypeConfig($scope.model.value[idx].ncContentTypeAlias); + if (contentType != null && contentType.nameExp) { + // Run the expression against the stored dictionary value, NOT the node object + var item = $scope.model.value[idx]; + // Add a temporary index property + item['$index'] = idx + 1; + var newName = contentType.nameExp(item); + if (newName && (newName = $.trim(newName))) { + name = newName; + } + // Delete the index property as we don't want to persist it + delete item['$index']; + } + } + // Update the nodes actual name value + if ($scope.nodes[idx].name !== name) { + $scope.nodes[idx].name = name; + } + return name; + }; + $scope.getIcon = function (idx) { + var scaffold = $scope.getScaffold($scope.model.value[idx].ncContentTypeAlias); + return scaffold && scaffold.icon ? iconHelper.convertFromLegacyIcon(scaffold.icon) : 'icon-folder'; + }; + $scope.sortableOptions = { + axis: 'y', + cursor: 'move', + handle: '.umb-nested-content__icon--move', + start: function (ev, ui) { + // Yea, yea, we shouldn't modify the dom, sue me + $('#umb-nested-content--' + $scope.model.id + ' .umb-rte textarea').each(function () { + tinymce.execCommand('mceRemoveEditor', false, $(this).attr('id')); + $(this).css('visibility', 'hidden'); + }); + $scope.$apply(function () { + $scope.sorting = true; + }); + }, + update: function (ev, ui) { + $scope.setDirty(); + }, + stop: function (ev, ui) { + $('#umb-nested-content--' + $scope.model.id + ' .umb-rte textarea').each(function () { + tinymce.execCommand('mceAddEditor', true, $(this).attr('id')); + $(this).css('visibility', 'visible'); + }); + $scope.$apply(function () { + $scope.sorting = false; + updateModel(); + }); + } + }; + $scope.getScaffold = function (alias) { + return _.find($scope.scaffolds, function (scaffold) { + return scaffold.contentTypeAlias == alias; + }); + }; + $scope.getContentTypeConfig = function (alias) { + return _.find($scope.model.config.contentTypes, function (contentType) { + return contentType.ncAlias == alias; + }); + }; + var notSupported = [ + 'Umbraco.Tags', + 'Umbraco.UploadField', + 'Umbraco.ImageCropper' + ]; + // Initialize + var scaffoldsLoaded = 0; + $scope.scaffolds = []; + _.each($scope.model.config.contentTypes, function (contentType) { + contentResource.getScaffold(-20, contentType.ncAlias).then(function (scaffold) { + // remove all tabs except the specified tab + var tab = _.find(scaffold.tabs, function (tab) { + return tab.id != 0 && (tab.alias.toLowerCase() == contentType.ncTabAlias.toLowerCase() || contentType.ncTabAlias == ''); + }); + scaffold.tabs = []; + if (tab) { + scaffold.tabs.push(tab); + angular.forEach(tab.properties, function (property) { + if (_.find(notSupported, function (x) { + return x === property.editor; + })) { + property.notSupported = true; + //TODO: Not supported message to be replaced with 'content_nestedContentEditorNotSupported' dictionary key. Currently not possible due to async/timing quirk. + property.notSupportedMessage = 'Property ' + property.label + ' uses editor ' + property.editor + ' which is not supported by Nested Content.'; + } + }); + } + // Store the scaffold object + $scope.scaffolds.push(scaffold); + scaffoldsLoaded++; + initIfAllScaffoldsHaveLoaded(); + }, function (error) { + scaffoldsLoaded++; + initIfAllScaffoldsHaveLoaded(); + }); + }); + var initIfAllScaffoldsHaveLoaded = function () { + // Initialize when all scaffolds have loaded + if ($scope.model.config.contentTypes.length == scaffoldsLoaded) { + // Because we're loading the scaffolds async one at a time, we need to + // sort them explicitly according to the sort order defined by the data type. + var contentTypeAliases = []; + _.each($scope.model.config.contentTypes, function (contentType) { + contentTypeAliases.push(contentType.ncAlias); + }); + $scope.scaffolds = $filter('orderBy')($scope.scaffolds, function (s) { + return contentTypeAliases.indexOf(s.contentTypeAlias); + }); + // Convert stored nodes + if ($scope.model.value) { + for (var i = 0; i < $scope.model.value.length; i++) { + var item = $scope.model.value[i]; + var scaffold = $scope.getScaffold(item.ncContentTypeAlias); + if (scaffold == null) { + // No such scaffold - the content type might have been deleted. We need to skip it. + continue; + } + initNode(scaffold, item); + } + } + // Enforce min items + if ($scope.nodes.length < $scope.model.config.minItems) { + for (var i = $scope.nodes.length; i < $scope.model.config.minItems; i++) { + $scope.addNode($scope.scaffolds[0].contentTypeAlias); + } + } + // If there is only one item, set it as current node + if ($scope.singleMode || $scope.nodes.length == 1 && $scope.maxItems == 1) { + $scope.currentNode = $scope.nodes[0]; + } + $scope.overlayMenu.showFilter = $scope.scaffolds.length > 15; + inited = true; + } + }; + var initNode = function (scaffold, item) { + var node = angular.copy(scaffold); + node.key = item && item.key ? item.key : UUID.generate(); + node.ncContentTypeAlias = scaffold.contentTypeAlias; + for (var t = 0; t < node.tabs.length; t++) { + var tab = node.tabs[t]; + for (var p = 0; p < tab.properties.length; p++) { + var prop = tab.properties[p]; + prop.propertyAlias = prop.alias; + prop.alias = $scope.model.alias + '___' + prop.alias; + // Force validation to occur server side as this is the + // only way we can have consistancy between mandatory and + // regex validation messages. Not ideal, but it works. + prop.validation = { + mandatory: false, + pattern: '' + }; + if (item) { + if (item[prop.propertyAlias]) { + prop.value = item[prop.propertyAlias]; + } + } + } + } + $scope.nodes.push(node); + return node; + }; + var updateModel = function () { + if ($scope.realCurrentNode) { + $scope.$broadcast('ncSyncVal', { key: $scope.realCurrentNode.key }); + } + if (inited) { + var newValues = []; + for (var i = 0; i < $scope.nodes.length; i++) { + var node = $scope.nodes[i]; + var newValue = { + key: node.key, + name: node.name, + ncContentTypeAlias: node.ncContentTypeAlias + }; + for (var t = 0; t < node.tabs.length; t++) { + var tab = node.tabs[t]; + for (var p = 0; p < tab.properties.length; p++) { + var prop = tab.properties[p]; + if (typeof prop.value !== 'function') { + newValue[prop.propertyAlias] = prop.value; + } + } + } + newValues.push(newValue); + } + $scope.model.value = newValues; + } + }; + $scope.$watch('currentNode', function (newVal) { + updateModel(); + $scope.realCurrentNode = newVal; + }); + var unsubscribe = $scope.$on('formSubmitting', function (ev, args) { + updateModel(); + }); + $scope.$on('$destroy', function () { + unsubscribe(); + }); + //TODO: Move this into a shared location? + var UUID = function () { + var self = {}; + var lut = []; + for (var i = 0; i < 256; i++) { + lut[i] = (i < 16 ? '0' : '') + i.toString(16); + } + self.generate = function () { + var d0 = Math.random() * 4294967295 | 0; + var d1 = Math.random() * 4294967295 | 0; + var d2 = Math.random() * 4294967295 | 0; + var d3 = Math.random() * 4294967295 | 0; + return lut[d0 & 255] + lut[d0 >> 8 & 255] + lut[d0 >> 16 & 255] + lut[d0 >> 24 & 255] + '-' + lut[d1 & 255] + lut[d1 >> 8 & 255] + '-' + lut[d1 >> 16 & 15 | 64] + lut[d1 >> 24 & 255] + '-' + lut[d2 & 63 | 128] + lut[d2 >> 8 & 255] + '-' + lut[d2 >> 16 & 255] + lut[d2 >> 24 & 255] + lut[d3 & 255] + lut[d3 >> 8 & 255] + lut[d3 >> 16 & 255] + lut[d3 >> 24 & 255]; + }; + return self; + }(); + } + ]); + angular.module('umbraco').controller('Umbraco.PropertyEditors.RadioButtonsController', function ($scope) { if (angular.isObject($scope.model.config.items)) { - //now we need to format the items in the dictionary because we always want to have an array var newItems = []; var vals = _.values($scope.model.config.items); var keys = _.keys($scope.model.config.items); for (var i = 0; i < vals.length; i++) { - newItems.push({ id: keys[i], sortOrder: vals[i].sortOrder, value: vals[i].value }); + newItems.push({ + id: keys[i], + sortOrder: vals[i].sortOrder, + value: vals[i].value + }); } - //ensure the items are sorted by the provided sort order - newItems.sort(function (a, b) { return (a.sortOrder > b.sortOrder) ? 1 : ((b.sortOrder > a.sortOrder) ? -1 : 0); }); - + newItems.sort(function (a, b) { + return a.sortOrder > b.sortOrder ? 1 : b.sortOrder > a.sortOrder ? -1 : 0; + }); //re-assign $scope.model.config.items = newItems; - } - }); - -/** - * @ngdoc controller - * @name Umbraco.Editors.ReadOnlyValueController - * @function - * - * @description - * The controller for the readonlyvalue property editor. - * This controller offer more functionality than just a simple label as it will be able to apply formatting to the - * value to be displayed. This means that we also have to apply more complex logic of watching the model value when - * it changes because we are creating a new scope value called displayvalue which will never change based on the server data. - * In some cases after a form submission, the server will modify the data that has been persisted, especially in the cases of - * readonlyvalues so we need to ensure that after the form is submitted that the new data is reflected here. -*/ -function ReadOnlyValueController($rootScope, $scope, $filter) { - - function formatDisplayValue() { - - if ($scope.model.config && - angular.isArray($scope.model.config) && - $scope.model.config.length > 0 && - $scope.model.config[0] && - $scope.model.config.filter) { - - if ($scope.model.config.format) { - $scope.displayvalue = $filter($scope.model.config.filter)($scope.model.value, $scope.model.config.format); - } else { - $scope.displayvalue = $filter($scope.model.config.filter)($scope.model.value); - } - } else { - $scope.displayvalue = $scope.model.value; - } - - } - - //format the display value on init: - formatDisplayValue(); - - $scope.$watch("model.value", function (newVal, oldVal) { - //cannot just check for !newVal because it might be an empty string which we - //want to look for. - if (newVal !== null && newVal !== undefined && newVal !== oldVal) { - //update the display val again - formatDisplayValue(); - } - }); -} - -angular.module('umbraco').controller("Umbraco.PropertyEditors.ReadOnlyValueController", ReadOnlyValueController); -angular.module("umbraco") - .controller("Umbraco.PropertyEditors.RelatedLinksController", - function ($rootScope, $scope, dialogService, iconHelper) { - - if (!$scope.model.value) { - $scope.model.value = []; - } - - $scope.model.config.max = isNumeric($scope.model.config.max) && $scope.model.config.max !== 0 ? $scope.model.config.max : Number.MAX_VALUE; - - $scope.newCaption = ''; - $scope.newLink = 'http://'; - $scope.newNewWindow = false; - $scope.newInternal = null; - $scope.newInternalName = ''; - $scope.newInternalIcon = null; - $scope.addExternal = true; - $scope.currentEditLink = null; - $scope.hasError = false; - - $scope.internal = function($event) { - - $scope.currentEditLink = null; - - $scope.contentPickerOverlay = {}; - $scope.contentPickerOverlay.view = "contentpicker"; - $scope.contentPickerOverlay.multiPicker = false; - $scope.contentPickerOverlay.show = true; - - $scope.contentPickerOverlay.submit = function(model) { - - select(model.selection[0]); - - $scope.contentPickerOverlay.show = false; - $scope.contentPickerOverlay = null; - }; - - $scope.contentPickerOverlay.close = function(oldModel) { - $scope.contentPickerOverlay.show = false; - $scope.contentPickerOverlay = null; - }; - - $event.preventDefault(); - }; - - $scope.selectInternal = function($event, link) { - - $scope.currentEditLink = link; - - $scope.contentPickerOverlay = {}; - $scope.contentPickerOverlay.view = "contentpicker"; - $scope.contentPickerOverlay.multiPicker = false; - $scope.contentPickerOverlay.show = true; - - $scope.contentPickerOverlay.submit = function(model) { - - select(model.selection[0]); - - $scope.contentPickerOverlay.show = false; - $scope.contentPickerOverlay = null; - }; - - $scope.contentPickerOverlay.close = function(oldModel) { - $scope.contentPickerOverlay.show = false; - $scope.contentPickerOverlay = null; - }; - - $event.preventDefault(); - - }; - - $scope.edit = function (idx) { - for (var i = 0; i < $scope.model.value.length; i++) { - $scope.model.value[i].edit = false; - } - $scope.model.value[idx].edit = true; - }; - - $scope.saveEdit = function (idx) { - $scope.model.value[idx].title = $scope.model.value[idx].caption; - $scope.model.value[idx].edit = false; - }; - - $scope.delete = function (idx) { - $scope.model.value.splice(idx, 1); - }; - - $scope.add = function ($event) { - if ($scope.newCaption == "") { - $scope.hasError = true; - } else { - if ($scope.addExternal) { - var newExtLink = new function() { - this.caption = $scope.newCaption; - this.link = $scope.newLink; - this.newWindow = $scope.newNewWindow; - this.edit = false; - this.isInternal = false; - this.type = "external"; - this.title = $scope.newCaption; - }; - $scope.model.value.push(newExtLink); - } else { - var newIntLink = new function() { - this.caption = $scope.newCaption; - this.link = $scope.newInternal; - this.newWindow = $scope.newNewWindow; - this.internal = $scope.newInternal; - this.edit = false; - this.isInternal = true; - this.internalName = $scope.newInternalName; - this.internalIcon = $scope.newInternalIcon; - this.type = "internal"; - this.title = $scope.newCaption; - }; - $scope.model.value.push(newIntLink); - } - $scope.newCaption = ''; - $scope.newLink = 'http://'; - $scope.newNewWindow = false; - $scope.newInternal = null; - $scope.newInternalName = ''; - $scope.newInternalIcon = null; - } - $event.preventDefault(); - }; - - $scope.switch = function ($event) { - $scope.addExternal = !$scope.addExternal; - $event.preventDefault(); - }; - - $scope.switchLinkType = function ($event, link) { - link.isInternal = !link.isInternal; - link.type = link.isInternal ? "internal" : "external"; - if (!link.isInternal) - link.link = $scope.newLink; - $event.preventDefault(); - }; - - $scope.move = function (index, direction) { - var temp = $scope.model.value[index]; - $scope.model.value[index] = $scope.model.value[index + direction]; - $scope.model.value[index + direction] = temp; - }; - - //helper for determining if a user can add items - $scope.canAdd = function () { - return $scope.model.config.max <= 0 || $scope.model.config.max > countVisible(); - } - - //helper that returns if an item can be sorted - $scope.canSort = function () { - return countVisible() > 1; - } - - $scope.sortableOptions = { - axis: 'y', - handle: '.handle', - cursor: 'move', - cancel: '.no-drag', - containment: 'parent', - placeholder: 'sortable-placeholder', - forcePlaceholderSize: true, - helper: function (e, ui) { - // When sorting table rows, the cells collapse. This helper fixes that: http://www.foliotek.com/devblog/make-table-rows-sortable-using-jquery-ui-sortable/ - ui.children().each(function () { - $(this).width($(this).width()); - }); - return ui; - }, - items: '> tr:not(.unsortable)', - tolerance: 'pointer', - update: function (e, ui) { - // Get the new and old index for the moved element (using the URL as the identifier) - var newIndex = ui.item.index(); - var movedLinkUrl = ui.item.attr('data-link'); - var originalIndex = getElementIndexByUrl(movedLinkUrl); - - // Move the element in the model - var movedElement = $scope.model.value[originalIndex]; - $scope.model.value.splice(originalIndex, 1); - $scope.model.value.splice(newIndex, 0, movedElement); - }, - start: function (e, ui) { - //ui.placeholder.html(""); - - // Build a placeholder cell that spans all the cells in the row: http://stackoverflow.com/questions/25845310/jquery-ui-sortable-and-table-cell-size - var cellCount = 0; - $('td, th', ui.helper).each(function () { - // For each td or th try and get it's colspan attribute, and add that or 1 to the total - var colspan = 1; - var colspanAttr = $(this).attr('colspan'); - if (colspanAttr > 1) { - colspan = colspanAttr; - } - cellCount += colspan; - }); - - // Add the placeholder UI - note that this is the item's content, so td rather than tr - and set height of tr - ui.placeholder.html('').height(ui.item.height()); - } - }; - - //helper to count what is visible - function countVisible() { - return $scope.model.value.length; - } - - function isNumeric(n) { - return !isNaN(parseFloat(n)) && isFinite(n); - } - - function getElementIndexByUrl(url) { - for (var i = 0; i < $scope.model.value.length; i++) { - if ($scope.model.value[i].link == url) { - return i; - } - } - - return -1; - } - - function select(data) { - if ($scope.currentEditLink != null) { - $scope.currentEditLink.internal = data.id; - $scope.currentEditLink.internalName = data.name; - $scope.currentEditLink.internalIcon = iconHelper.convertFromLegacyIcon(data.icon); - $scope.currentEditLink.link = data.id; - } else { - $scope.newInternal = data.id; - $scope.newInternalName = data.name; - $scope.newInternalIcon = iconHelper.convertFromLegacyIcon(data.icon); - } - } - }); - -angular.module("umbraco") - .controller("Umbraco.PropertyEditors.RTEController", - function ($rootScope, $scope, $q, dialogService, $log, imageHelper, assetsService, $timeout, tinyMceService, angularHelper, stylesheetResource, macroService) { - - $scope.isLoading = true; - - //To id the html textarea we need to use the datetime ticks because we can have multiple rte's per a single property alias - // because now we have to support having 2x (maybe more at some stage) content editors being displayed at once. This is because - // we have this mini content editor panel that can be launched with MNTP. - var d = new Date(); - var n = d.getTime(); - $scope.textAreaHtmlId = $scope.model.alias + "_" + n + "_rte"; - - var alreadyDirty = false; - function syncContent(editor){ - editor.save(); - angularHelper.safeApply($scope, function () { - $scope.model.value = editor.getContent(); - }); - - if (!alreadyDirty) { - //make the form dirty manually so that the track changes works, setting our model doesn't trigger - // the angular bits because tinymce replaces the textarea. - var currForm = angularHelper.getCurrentForm($scope); - currForm.$setDirty(); - alreadyDirty = true; - } - } - - tinyMceService.configuration().then(function (tinyMceConfig) { - - //config value from general tinymce.config file - var validElements = tinyMceConfig.validElements; - - //These are absolutely required in order for the macros to render inline - //we put these as extended elements because they get merged on top of the normal allowed elements by tiny mce - var extendedValidElements = "@[id|class|style],-div[id|dir|class|align|style],ins[datetime|cite],-ul[class|style],-li[class|style],span[id|class|style]"; - - var invalidElements = tinyMceConfig.inValidElements; - var plugins = _.map(tinyMceConfig.plugins, function (plugin) { - if (plugin.useOnFrontend) { - return plugin.name; - } - }).join(" "); - - var editorConfig = $scope.model.config.editor; - if (!editorConfig || angular.isString(editorConfig)) { - editorConfig = tinyMceService.defaultPrevalues(); - } - - //config value on the data type - var toolbar = editorConfig.toolbar.join(" | "); - var stylesheets = []; - var styleFormats = []; - var await = []; - if (!editorConfig.maxImageSize && editorConfig.maxImageSize != 0) { - editorConfig.maxImageSize = tinyMceService.defaultPrevalues().maxImageSize; - } - - //queue file loading - if (typeof tinymce === "undefined") { // Don't reload tinymce if already loaded - await.push(assetsService.loadJs("lib/tinymce/tinymce.min.js", $scope)); - } - - //queue rules loading - angular.forEach(editorConfig.stylesheets, function (val, key) { - stylesheets.push(Umbraco.Sys.ServerVariables.umbracoSettings.cssPath + "/" + val + ".css?" + new Date().getTime()); - await.push(stylesheetResource.getRulesByName(val).then(function (rules) { - angular.forEach(rules, function (rule) { - var r = {}; - r.title = rule.name; - if (rule.selector[0] == ".") { - r.inline = "span"; - r.classes = rule.selector.substring(1); - } - else if (rule.selector[0] == "#") { - r.inline = "span"; - r.attributes = { id: rule.selector.substring(1) }; - } - else if (rule.selector[0] != "." && rule.selector.indexOf(".") > -1) { - var split = rule.selector.split("."); - r.block = split[0]; - r.classes = rule.selector.substring(rule.selector.indexOf(".") + 1).replace(".", " "); - } - else if (rule.selector[0] != "#" && rule.selector.indexOf("#") > -1) { - var split = rule.selector.split("#"); - r.block = split[0]; - r.classes = rule.selector.substring(rule.selector.indexOf("#") + 1); - } - else { - r.block = rule.selector; - } - - styleFormats.push(r); - }); - })); - }); - - - //stores a reference to the editor - var tinyMceEditor = null; - - //wait for queue to end - $q.all(await).then(function () { - - //create a baseline Config to exten upon - var baseLineConfigObj = { - mode: "exact", - skin: "umbraco", - plugins: plugins, - valid_elements: validElements, - invalid_elements: invalidElements, - extended_valid_elements: extendedValidElements, - menubar: false, - statusbar: false, - height: editorConfig.dimensions.height, - width: editorConfig.dimensions.width, - maxImageSize: editorConfig.maxImageSize, - toolbar: toolbar, - content_css: stylesheets, - relative_urls: false, - style_formats: styleFormats - }; - - - if (tinyMceConfig.customConfig) { - - //if there is some custom config, we need to see if the string value of each item might actually be json and if so, we need to - // convert it to json instead of having it as a string since this is what tinymce requires - for (var i in tinyMceConfig.customConfig) { - var val = tinyMceConfig.customConfig[i]; - if (val) { - val = val.toString().trim(); - if (val.detectIsJson()) { - try { - tinyMceConfig.customConfig[i] = JSON.parse(val); - //now we need to check if this custom config key is defined in our baseline, if it is we don't want to - //overwrite the baseline config item if it is an array, we want to concat the items in the array, otherwise - //if it's an object it will overwrite the baseline - if (angular.isArray(baseLineConfigObj[i]) && angular.isArray(tinyMceConfig.customConfig[i])) { - //concat it and below this concat'd array will overwrite the baseline in angular.extend - tinyMceConfig.customConfig[i] = baseLineConfigObj[i].concat(tinyMceConfig.customConfig[i]); - } - } - catch (e) { - //cannot parse, we'll just leave it - } - } - } - } - - angular.extend(baseLineConfigObj, tinyMceConfig.customConfig); - } - - //set all the things that user configs should not be able to override - baseLineConfigObj.elements = $scope.textAreaHtmlId; //this is the exact textarea id to replace! - baseLineConfigObj.setup = function (editor) { - - //set the reference - tinyMceEditor = editor; - - //enable browser based spell checking - editor.on('init', function (e) { - editor.getBody().setAttribute('spellcheck', true); - }); - - //We need to listen on multiple things here because of the nature of tinymce, it doesn't - //fire events when you think! - //The change event doesn't fire when content changes, only when cursor points are changed and undo points - //are created. the blur event doesn't fire if you insert content into the editor with a button and then - //press save. - //We have a couple of options, one is to do a set timeout and check for isDirty on the editor, or we can - //listen to both change and blur and also on our own 'saving' event. I think this will be best because a - //timer might end up using unwanted cpu and we'd still have to listen to our saving event in case they clicked - //save before the timeout elapsed. - - //TODO: We need to re-enable something like this to ensure the track changes is working with tinymce - // so we can detect if the form is dirty or not, Per has some better events to use as this one triggers - // even if you just enter/exit with mouse cursuor which doesn't really mean it's changed. - // see: http://issues.umbraco.org/issue/U4-4485 - //var alreadyDirty = false; - //editor.on('change', function (e) { - // angularHelper.safeApply($scope, function () { - // $scope.model.value = editor.getContent(); - - // if (!alreadyDirty) { - // //make the form dirty manually so that the track changes works, setting our model doesn't trigger - // // the angular bits because tinymce replaces the textarea. - // var currForm = angularHelper.getCurrentForm($scope); - // currForm.$setDirty(); - // alreadyDirty = true; - // } - - // }); - //}); - - //when we leave the editor (maybe) - editor.on('blur', function (e) { - editor.save(); - angularHelper.safeApply($scope, function () { - $scope.model.value = editor.getContent(); - }); - }); - - //when buttons modify content - editor.on('ExecCommand', function (e) { - syncContent(editor); - }); - - // Update model on keypress - editor.on('KeyUp', function (e) { - syncContent(editor); - }); - - // Update model on change, i.e. copy/pasted text, plugins altering content - editor.on('SetContent', function (e) { - if (!e.initial) { - syncContent(editor); - } - }); - - - editor.on('ObjectResized', function (e) { - var qs = "?width=" + e.width + "&height=" + e.height; - var srcAttr = $(e.target).attr("src"); - var path = srcAttr.split("?")[0]; - $(e.target).attr("data-mce-src", path + qs); - - syncContent(editor); - }); - - tinyMceService.createLinkPicker(editor, $scope, function(currentTarget, anchorElement) { - $scope.linkPickerOverlay = { - view: "linkpicker", - currentTarget: currentTarget, - show: true, - submit: function(model) { - tinyMceService.insertLinkInEditor(editor, model.target, anchorElement); - $scope.linkPickerOverlay.show = false; - $scope.linkPickerOverlay = null; - } - }; - }); - - //Create the insert media plugin - tinyMceService.createMediaPicker(editor, $scope, function(currentTarget, userData){ - - $scope.mediaPickerOverlay = { - currentTarget: currentTarget, - onlyImages: true, - showDetails: true, - disableFolderSelect: true, - startNodeId: userData.startMediaId, - view: "mediapicker", - show: true, - submit: function(model) { - tinyMceService.insertMediaInEditor(editor, model.selectedImages[0]); - $scope.mediaPickerOverlay.show = false; - $scope.mediaPickerOverlay = null; - } - }; - - }); - - //Create the embedded plugin - tinyMceService.createInsertEmbeddedMedia(editor, $scope, function() { - - $scope.embedOverlay = { - view: "embed", - show: true, - submit: function(model) { - tinyMceService.insertEmbeddedMediaInEditor(editor, model.embed.preview); - $scope.embedOverlay.show = false; - $scope.embedOverlay = null; - } - }; - - }); - - - //Create the insert macro plugin - tinyMceService.createInsertMacro(editor, $scope, function(dialogData) { - - $scope.macroPickerOverlay = { - view: "macropicker", - dialogData: dialogData, - show: true, - submit: function(model) { - var macroObject = macroService.collectValueData(model.selectedMacro, model.macroParams, dialogData.renderingEngine); - tinyMceService.insertMacroInEditor(editor, macroObject, $scope); - $scope.macroPickerOverlay.show = false; - $scope.macroPickerOverlay = null; - } - }; - - }); - }; - - - - - /** Loads in the editor */ - function loadTinyMce() { - - //we need to add a timeout here, to force a redraw so TinyMCE can find - //the elements needed - $timeout(function () { - tinymce.DOM.events.domLoaded = true; - tinymce.init(baseLineConfigObj); - - $scope.isLoading = false; - - }, 200, false); - } - - - - - loadTinyMce(); - - //here we declare a special method which will be called whenever the value has changed from the server - //this is instead of doing a watch on the model.value = faster - $scope.model.onValueChanged = function (newVal, oldVal) { - //update the display val again if it has changed from the server; - tinyMceEditor.setContent(newVal, { format: 'raw' }); - //we need to manually fire this event since it is only ever fired based on loading from the DOM, this - // is required for our plugins listening to this event to execute - tinyMceEditor.fire('LoadContent', null); - }; - - //listen for formSubmitting event (the result is callback used to remove the event subscription) - var unsubscribe = $scope.$on("formSubmitting", function () { - //TODO: Here we should parse out the macro rendered content so we can save on a lot of bytes in data xfer - // we do parse it out on the server side but would be nice to do that on the client side before as well. - $scope.model.value = tinyMceEditor.getContent(); - }); - - //when the element is disposed we need to unsubscribe! - // NOTE: this is very important otherwise if this is part of a modal, the listener still exists because the dom - // element might still be there even after the modal has been hidden. - $scope.$on('$destroy', function () { - unsubscribe(); - }); - }); - }); - - }); - -angular.module("umbraco").controller("Umbraco.PrevalueEditors.RteController", - function ($scope, $timeout, $log, tinyMceService, stylesheetResource, assetsService) { + /** + * @ngdoc controller + * @name Umbraco.Editors.ReadOnlyValueController + * @function + * + * @description + * The controller for the readonlyvalue property editor. + * This controller offer more functionality than just a simple label as it will be able to apply formatting to the + * value to be displayed. This means that we also have to apply more complex logic of watching the model value when + * it changes because we are creating a new scope value called displayvalue which will never change based on the server data. + * In some cases after a form submission, the server will modify the data that has been persisted, especially in the cases of + * readonlyvalues so we need to ensure that after the form is submitted that the new data is reflected here. +*/ + function ReadOnlyValueController($rootScope, $scope, $filter) { + function formatDisplayValue() { + if ($scope.model.config && angular.isArray($scope.model.config) && $scope.model.config.length > 0 && $scope.model.config[0] && $scope.model.config.filter) { + if ($scope.model.config.format) { + $scope.displayvalue = $filter($scope.model.config.filter)($scope.model.value, $scope.model.config.format); + } else { + $scope.displayvalue = $filter($scope.model.config.filter)($scope.model.value); + } + } else { + $scope.displayvalue = $scope.model.value; + } + } + //format the display value on init: + formatDisplayValue(); + $scope.$watch('model.value', function (newVal, oldVal) { + //cannot just check for !newVal because it might be an empty string which we + //want to look for. + if (newVal !== null && newVal !== undefined && newVal !== oldVal) { + //update the display val again + formatDisplayValue(); + } + }); + } + angular.module('umbraco').controller('Umbraco.PropertyEditors.ReadOnlyValueController', ReadOnlyValueController); + angular.module('umbraco').controller('Umbraco.PropertyEditors.RelatedLinksController', function ($rootScope, $scope, dialogService, iconHelper) { + if (!$scope.model.value) { + $scope.model.value = []; + } + $scope.model.config.max = isNumeric($scope.model.config.max) && $scope.model.config.max !== 0 ? $scope.model.config.max : Number.MAX_VALUE; + $scope.newCaption = ''; + $scope.newLink = 'http://'; + $scope.newNewWindow = false; + $scope.newInternal = null; + $scope.newInternalName = ''; + $scope.newInternalIcon = null; + $scope.addExternal = true; + $scope.currentEditLink = null; + $scope.hasError = false; + $scope.internal = function ($event) { + $scope.currentEditLink = null; + $scope.contentPickerOverlay = {}; + $scope.contentPickerOverlay.view = 'contentpicker'; + $scope.contentPickerOverlay.multiPicker = false; + $scope.contentPickerOverlay.show = true; + $scope.contentPickerOverlay.idType = $scope.model.config.idType ? $scope.model.config.idType : 'int'; + $scope.contentPickerOverlay.submit = function (model) { + select(model.selection[0]); + $scope.contentPickerOverlay.show = false; + $scope.contentPickerOverlay = null; + }; + $scope.contentPickerOverlay.close = function (oldModel) { + $scope.contentPickerOverlay.show = false; + $scope.contentPickerOverlay = null; + }; + $event.preventDefault(); + }; + $scope.selectInternal = function ($event, link) { + $scope.currentEditLink = link; + $scope.contentPickerOverlay = {}; + $scope.contentPickerOverlay.view = 'contentpicker'; + $scope.contentPickerOverlay.multiPicker = false; + $scope.contentPickerOverlay.show = true; + $scope.contentPickerOverlay.idType = $scope.model.config.idType ? $scope.model.config.idType : 'int'; + $scope.contentPickerOverlay.submit = function (model) { + select(model.selection[0]); + $scope.contentPickerOverlay.show = false; + $scope.contentPickerOverlay = null; + }; + $scope.contentPickerOverlay.close = function (oldModel) { + $scope.contentPickerOverlay.show = false; + $scope.contentPickerOverlay = null; + }; + $event.preventDefault(); + }; + $scope.edit = function (idx) { + for (var i = 0; i < $scope.model.value.length; i++) { + $scope.model.value[i].edit = false; + } + $scope.model.value[idx].edit = true; + }; + $scope.saveEdit = function (idx) { + $scope.model.value[idx].title = $scope.model.value[idx].caption; + $scope.model.value[idx].edit = false; + }; + $scope.delete = function (idx) { + $scope.model.value.splice(idx, 1); + }; + $scope.add = function ($event) { + if (!angular.isArray($scope.model.value)) { + $scope.model.value = []; + } + if ($scope.newCaption == '') { + $scope.hasError = true; + } else { + if ($scope.addExternal) { + var newExtLink = new function () { + this.caption = $scope.newCaption; + this.link = $scope.newLink; + this.newWindow = $scope.newNewWindow; + this.edit = false; + this.isInternal = false; + this.type = 'external'; + this.title = $scope.newCaption; + }(); + $scope.model.value.push(newExtLink); + } else { + var newIntLink = new function () { + this.caption = $scope.newCaption; + this.link = $scope.newInternal; + this.newWindow = $scope.newNewWindow; + this.internal = $scope.newInternal; + this.edit = false; + this.isInternal = true; + this.internalName = $scope.newInternalName; + this.internalIcon = $scope.newInternalIcon; + this.type = 'internal'; + this.title = $scope.newCaption; + }(); + $scope.model.value.push(newIntLink); + } + $scope.newCaption = ''; + $scope.newLink = 'http://'; + $scope.newNewWindow = false; + $scope.newInternal = null; + $scope.newInternalName = ''; + $scope.newInternalIcon = null; + } + $event.preventDefault(); + }; + $scope.switch = function ($event) { + $scope.addExternal = !$scope.addExternal; + $event.preventDefault(); + }; + $scope.switchLinkType = function ($event, link) { + link.isInternal = !link.isInternal; + link.type = link.isInternal ? 'internal' : 'external'; + if (!link.isInternal) + link.link = $scope.newLink; + $event.preventDefault(); + }; + $scope.move = function (index, direction) { + var temp = $scope.model.value[index]; + $scope.model.value[index] = $scope.model.value[index + direction]; + $scope.model.value[index + direction] = temp; + }; + //helper for determining if a user can add items + $scope.canAdd = function () { + return $scope.model.config.max <= 0 || $scope.model.config.max > countVisible(); + }; + //helper that returns if an item can be sorted + $scope.canSort = function () { + return countVisible() > 1; + }; + $scope.sortableOptions = { + axis: 'y', + handle: '.handle', + cursor: 'move', + cancel: '.no-drag', + containment: 'parent', + placeholder: 'sortable-placeholder', + forcePlaceholderSize: true, + helper: function (e, ui) { + // When sorting table rows, the cells collapse. This helper fixes that: https://www.foliotek.com/devblog/make-table-rows-sortable-using-jquery-ui-sortable/ + ui.children().each(function () { + $(this).width($(this).width()); + }); + return ui; + }, + items: '> tr:not(.unsortable)', + tolerance: 'pointer', + update: function (e, ui) { + // Get the new and old index for the moved element (using the URL as the identifier) + var newIndex = ui.item.index(); + var movedLinkUrl = ui.item.attr('data-link'); + var originalIndex = getElementIndexByUrl(movedLinkUrl); + // Move the element in the model + var movedElement = $scope.model.value[originalIndex]; + $scope.model.value.splice(originalIndex, 1); + $scope.model.value.splice(newIndex, 0, movedElement); + }, + start: function (e, ui) { + //ui.placeholder.html(""); + // Build a placeholder cell that spans all the cells in the row: https://stackoverflow.com/questions/25845310/jquery-ui-sortable-and-table-cell-size + var cellCount = 0; + $('td, th', ui.helper).each(function () { + // For each td or th try and get it's colspan attribute, and add that or 1 to the total + var colspan = 1; + var colspanAttr = $(this).attr('colspan'); + if (colspanAttr > 1) { + colspan = colspanAttr; + } + cellCount += colspan; + }); + // Add the placeholder UI - note that this is the item's content, so td rather than tr - and set height of tr + ui.placeholder.html('').height(ui.item.height()); + } + }; + //helper to count what is visible + function countVisible() { + return $scope.model.value.length; + } + function isNumeric(n) { + return !isNaN(parseFloat(n)) && isFinite(n); + } + function getElementIndexByUrl(url) { + for (var i = 0; i < $scope.model.value.length; i++) { + if ($scope.model.value[i].link == url) { + return i; + } + } + return -1; + } + function select(data) { + if ($scope.currentEditLink != null) { + $scope.currentEditLink.internal = $scope.model.config.idType === 'udi' ? data.udi : data.id; + $scope.currentEditLink.internalName = data.name; + $scope.currentEditLink.internalIcon = iconHelper.convertFromLegacyIcon(data.icon); + $scope.currentEditLink.link = $scope.model.config.idType === 'udi' ? data.udi : data.id; + } else { + $scope.newInternal = $scope.model.config.idType === 'udi' ? data.udi : data.id; + $scope.newInternalName = data.name; + $scope.newInternalIcon = iconHelper.convertFromLegacyIcon(data.icon); + } + } + }); + angular.module('umbraco').controller('Umbraco.PropertyEditors.RTEController', function ($rootScope, $scope, $q, $locale, dialogService, $log, imageHelper, assetsService, $timeout, tinyMceService, angularHelper, stylesheetResource, macroService, editorState) { + $scope.isLoading = true; + //To id the html textarea we need to use the datetime ticks because we can have multiple rte's per a single property alias + // because now we have to support having 2x (maybe more at some stage) content editors being displayed at once. This is because + // we have this mini content editor panel that can be launched with MNTP. + var d = new Date(); + var n = d.getTime(); + $scope.textAreaHtmlId = $scope.model.alias + '_' + n + '_rte'; + var alreadyDirty = false; + function syncContent(editor) { + editor.save(); + angularHelper.safeApply($scope, function () { + $scope.model.value = editor.getContent(); + }); + if (!alreadyDirty) { + //make the form dirty manually so that the track changes works, setting our model doesn't trigger + // the angular bits because tinymce replaces the textarea. + var currForm = angularHelper.getCurrentForm($scope); + currForm.$setDirty(); + alreadyDirty = true; + } + } + tinyMceService.configuration().then(function (tinyMceConfig) { + //config value from general tinymce.config file + var validElements = tinyMceConfig.validElements; + //These are absolutely required in order for the macros to render inline + //we put these as extended elements because they get merged on top of the normal allowed elements by tiny mce + var extendedValidElements = '@[id|class|style],-div[id|dir|class|align|style],ins[datetime|cite],-ul[class|style],-li[class|style],span[id|class|style]'; + var invalidElements = tinyMceConfig.inValidElements; + var plugins = _.map(tinyMceConfig.plugins, function (plugin) { + if (plugin.useOnFrontend) { + return plugin.name; + } + }).join(' '); + var editorConfig = $scope.model.config.editor; + if (!editorConfig || angular.isString(editorConfig)) { + editorConfig = tinyMceService.defaultPrevalues(); + } + //config value on the data type + var toolbar = editorConfig.toolbar.join(' | '); + var stylesheets = []; + var styleFormats = []; + var await = []; + if (!editorConfig.maxImageSize && editorConfig.maxImageSize != 0) { + editorConfig.maxImageSize = tinyMceService.defaultPrevalues().maxImageSize; + } + //queue file loading + if (typeof tinymce === 'undefined') { + // Don't reload tinymce if already loaded + await.push(assetsService.loadJs('lib/tinymce/tinymce.min.js', $scope)); + } + //queue rules loading + angular.forEach(editorConfig.stylesheets, function (val, key) { + stylesheets.push(Umbraco.Sys.ServerVariables.umbracoSettings.cssPath + '/' + val + '.css?' + new Date().getTime()); + await.push(stylesheetResource.getRulesByName(val).then(function (rules) { + angular.forEach(rules, function (rule) { + var r = {}; + r.title = rule.name; + if (rule.selector[0] == '.') { + r.inline = 'span'; + r.classes = rule.selector.substring(1); + } else if (rule.selector[0] == '#') { + r.inline = 'span'; + r.attributes = { id: rule.selector.substring(1) }; + } else if (rule.selector[0] != '.' && rule.selector.indexOf('.') > -1) { + var split = rule.selector.split('.'); + r.block = split[0]; + r.classes = rule.selector.substring(rule.selector.indexOf('.') + 1).replace('.', ' '); + } else if (rule.selector[0] != '#' && rule.selector.indexOf('#') > -1) { + var split = rule.selector.split('#'); + r.block = split[0]; + r.classes = rule.selector.substring(rule.selector.indexOf('#') + 1); + } else { + r.block = rule.selector; + } + styleFormats.push(r); + }); + })); + }); + //stores a reference to the editor + var tinyMceEditor = null; + // these languages are available for localization + var availableLanguages = [ + 'da', + 'de', + 'en', + 'en_us', + 'fi', + 'fr', + 'he', + 'it', + 'ja', + 'nl', + 'no', + 'pl', + 'pt', + 'ru', + 'sv', + 'zh' + ]; + //define fallback language + var language = 'en_us'; + //get locale from angular and match tinymce format. Angular localization is always in the format of ru-ru, de-de, en-gb, etc. + //wheras tinymce is in the format of ru, de, en, en_us, etc. + var localeId = $locale.id.replace('-', '_'); + //try matching the language using full locale format + var languageMatch = _.find(availableLanguages, function (o) { + return o === localeId; + }); + //if no matches, try matching using only the language + if (languageMatch === undefined) { + var localeParts = localeId.split('_'); + languageMatch = _.find(availableLanguages, function (o) { + return o === localeParts[0]; + }); + } + //if a match was found - set the language + if (languageMatch !== undefined) { + language = languageMatch; + } + //wait for queue to end + $q.all(await).then(function () { + //create a baseline Config to exten upon + var baseLineConfigObj = { + mode: 'exact', + skin: 'umbraco', + plugins: plugins, + valid_elements: validElements, + invalid_elements: invalidElements, + extended_valid_elements: extendedValidElements, + menubar: false, + statusbar: false, + relative_urls: false, + height: editorConfig.dimensions.height, + width: editorConfig.dimensions.width, + maxImageSize: editorConfig.maxImageSize, + toolbar: toolbar, + content_css: stylesheets, + style_formats: styleFormats, + language: language, + //see http://archive.tinymce.com/wiki.php/Configuration:cache_suffix + cache_suffix: '?umb__rnd=' + Umbraco.Sys.ServerVariables.application.cacheBuster + }; + if (tinyMceConfig.customConfig) { + //if there is some custom config, we need to see if the string value of each item might actually be json and if so, we need to + // convert it to json instead of having it as a string since this is what tinymce requires + for (var i in tinyMceConfig.customConfig) { + var val = tinyMceConfig.customConfig[i]; + if (val) { + val = val.toString().trim(); + if (val.detectIsJson()) { + try { + tinyMceConfig.customConfig[i] = JSON.parse(val); + //now we need to check if this custom config key is defined in our baseline, if it is we don't want to + //overwrite the baseline config item if it is an array, we want to concat the items in the array, otherwise + //if it's an object it will overwrite the baseline + if (angular.isArray(baseLineConfigObj[i]) && angular.isArray(tinyMceConfig.customConfig[i])) { + //concat it and below this concat'd array will overwrite the baseline in angular.extend + tinyMceConfig.customConfig[i] = baseLineConfigObj[i].concat(tinyMceConfig.customConfig[i]); + } + } catch (e) { + } + } + if (val === 'true') { + tinyMceConfig.customConfig[i] = true; + } + if (val === 'false') { + tinyMceConfig.customConfig[i] = false; + } + } + } + angular.extend(baseLineConfigObj, tinyMceConfig.customConfig); + } + //set all the things that user configs should not be able to override + baseLineConfigObj.elements = $scope.textAreaHtmlId; + //this is the exact textarea id to replace! + baseLineConfigObj.setup = function (editor) { + //set the reference + tinyMceEditor = editor; + //enable browser based spell checking + editor.on('init', function (e) { + editor.getBody().setAttribute('spellcheck', true); + }); + //We need to listen on multiple things here because of the nature of tinymce, it doesn't + //fire events when you think! + //The change event doesn't fire when content changes, only when cursor points are changed and undo points + //are created. the blur event doesn't fire if you insert content into the editor with a button and then + //press save. + //We have a couple of options, one is to do a set timeout and check for isDirty on the editor, or we can + //listen to both change and blur and also on our own 'saving' event. I think this will be best because a + //timer might end up using unwanted cpu and we'd still have to listen to our saving event in case they clicked + //save before the timeout elapsed. + //TODO: We need to re-enable something like this to ensure the track changes is working with tinymce + // so we can detect if the form is dirty or not, Per has some better events to use as this one triggers + // even if you just enter/exit with mouse cursuor which doesn't really mean it's changed. + // see: http://issues.umbraco.org/issue/U4-4485 + //var alreadyDirty = false; + //editor.on('change', function (e) { + // angularHelper.safeApply($scope, function () { + // $scope.model.value = editor.getContent(); + // if (!alreadyDirty) { + // //make the form dirty manually so that the track changes works, setting our model doesn't trigger + // // the angular bits because tinymce replaces the textarea. + // var currForm = angularHelper.getCurrentForm($scope); + // currForm.$setDirty(); + // alreadyDirty = true; + // } + // }); + //}); + //when we leave the editor (maybe) + editor.on('blur', function (e) { + editor.save(); + angularHelper.safeApply($scope, function () { + $scope.model.value = editor.getContent(); + }); + }); + //when buttons modify content + editor.on('ExecCommand', function (e) { + syncContent(editor); + }); + // Update model on keypress + editor.on('KeyUp', function (e) { + syncContent(editor); + }); + // Update model on change, i.e. copy/pasted text, plugins altering content + editor.on('SetContent', function (e) { + if (!e.initial) { + syncContent(editor); + } + }); + editor.on('ObjectResized', function (e) { + var qs = '?width=' + e.width + '&height=' + e.height + '&mode=max'; + var srcAttr = $(e.target).attr('src'); + var path = srcAttr.split('?')[0]; + $(e.target).attr('data-mce-src', path + qs); + 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); + $scope.linkPickerOverlay.show = false; + $scope.linkPickerOverlay = null; + } + }; + }); + //Create the insert media plugin + tinyMceService.createMediaPicker(editor, $scope, function (currentTarget, userData) { + $scope.mediaPickerOverlay = { + currentTarget: currentTarget, + onlyImages: true, + showDetails: true, + disableFolderSelect: true, + startNodeId: userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0], + startNodeIsVirtual: userData.startMediaIds.length !== 1, + view: 'mediapicker', + show: true, + submit: function (model) { + tinyMceService.insertMediaInEditor(editor, model.selectedImages[0]); + $scope.mediaPickerOverlay.show = false; + $scope.mediaPickerOverlay = null; + } + }; + }); + //Create the embedded plugin + tinyMceService.createInsertEmbeddedMedia(editor, $scope, function () { + $scope.embedOverlay = { + view: 'embed', + show: true, + submit: function (model) { + tinyMceService.insertEmbeddedMediaInEditor(editor, model.embed.preview); + $scope.embedOverlay.show = false; + $scope.embedOverlay = null; + } + }; + }); + //Create the insert macro plugin + tinyMceService.createInsertMacro(editor, $scope, function (dialogData) { + $scope.macroPickerOverlay = { + view: 'macropicker', + dialogData: dialogData, + show: true, + submit: function (model) { + var macroObject = macroService.collectValueData(model.selectedMacro, model.macroParams, dialogData.renderingEngine); + tinyMceService.insertMacroInEditor(editor, macroObject, $scope); + $scope.macroPickerOverlay.show = false; + $scope.macroPickerOverlay = null; + } + }; + }); + }; + /** Loads in the editor */ + function loadTinyMce() { + //we need to add a timeout here, to force a redraw so TinyMCE can find + //the elements needed + $timeout(function () { + tinymce.DOM.events.domLoaded = true; + tinymce.init(baseLineConfigObj); + $scope.isLoading = false; + }, 200, false); + } + loadTinyMce(); + //here we declare a special method which will be called whenever the value has changed from the server + //this is instead of doing a watch on the model.value = faster + $scope.model.onValueChanged = function (newVal, oldVal) { + //update the display val again if it has changed from the server; + //uses an empty string in the editor when the value is null + tinyMceEditor.setContent(newVal || '', { format: 'raw' }); + //we need to manually fire this event since it is only ever fired based on loading from the DOM, this + // is required for our plugins listening to this event to execute + tinyMceEditor.fire('LoadContent', null); + }; + //listen for formSubmitting event (the result is callback used to remove the event subscription) + var unsubscribe = $scope.$on('formSubmitting', function () { + //TODO: Here we should parse out the macro rendered content so we can save on a lot of bytes in data xfer + // we do parse it out on the server side but would be nice to do that on the client side before as well. + $scope.model.value = tinyMceEditor ? tinyMceEditor.getContent() : null; + }); + //when the element is disposed we need to unsubscribe! + // NOTE: this is very important otherwise if this is part of a modal, the listener still exists because the dom + // element might still be there even after the modal has been hidden. + $scope.$on('$destroy', function () { + unsubscribe(); + if (tinyMceEditor !== undefined && tinyMceEditor != null) { + tinyMceEditor.destroy(); + } + }); + }); + }); + }); + angular.module('umbraco').controller('Umbraco.PrevalueEditors.RteController', function ($scope, $timeout, $log, tinyMceService, stylesheetResource, assetsService) { var cfg = tinyMceService.defaultPrevalues(); - - if($scope.model.value){ - if(angular.isString($scope.model.value)){ + if ($scope.model.value) { + if (angular.isString($scope.model.value)) { $scope.model.value = cfg; } - }else{ + } else { $scope.model.value = cfg; } - if (!$scope.model.value.stylesheets) { $scope.model.value.stylesheets = []; } @@ -15504,10 +16610,8 @@ angular.module("umbraco").controller("Umbraco.PrevalueEditors.RteController", if (!$scope.model.value.maxImageSize && $scope.model.value.maxImageSize != 0) { $scope.model.value.maxImageSize = cfg.maxImageSize; } - - tinyMceService.configuration().then(function(config){ + tinyMceService.configuration().then(function (config) { $scope.tinyMceConfig = config; - // extend commands with properties for font-icon and if it is a custom command $scope.tinyMceConfig.commands = _.map($scope.tinyMceConfig.commands, function (obj) { var icon = getFontIcon(obj.frontEndCommand); @@ -15517,575 +16621,2891 @@ angular.module("umbraco").controller("Umbraco.PrevalueEditors.RteController", }); }); }); - - stylesheetResource.getAll().then(function(stylesheets){ + stylesheetResource.getAll().then(function (stylesheets) { $scope.stylesheets = stylesheets; }); - - $scope.selected = function(cmd, alias, lookup){ + $scope.selected = function (cmd, alias, lookup) { if (lookup && angular.isArray(lookup)) { cmd.selected = lookup.indexOf(alias) >= 0; return cmd.selected; } return false; }; - - $scope.selectCommand = function(command){ + $scope.selectCommand = function (command) { var index = $scope.model.value.toolbar.indexOf(command.frontEndCommand); - - if(command.selected && index === -1){ + if (command.selected && index === -1) { $scope.model.value.toolbar.push(command.frontEndCommand); - }else if(index >= 0){ + } else if (index >= 0) { $scope.model.value.toolbar.splice(index, 1); } }; - $scope.selectStylesheet = function (css) { - var index = $scope.model.value.stylesheets.indexOf(css.name); - - if(css.selected && index === -1){ + if (css.selected && index === -1) { $scope.model.value.stylesheets.push(css.name); - }else if(index >= 0){ + } else if (index >= 0) { $scope.model.value.stylesheets.splice(index, 1); } }; - // map properties for specific commands function getFontIcon(alias) { - var icon = { name: alias, isCustom: false }; - + var icon = { + name: alias, + isCustom: false + }; switch (alias) { - case "codemirror": - icon.name = "code"; - icon.isCustom = false; - break; - case "styleselect": - icon.name = "icon-list"; - icon.isCustom = true; - break; - case "umbembeddialog": - icon.name = "icon-tv"; - icon.isCustom = true; - break; - case "umbmediapicker": - icon.name = "icon-picture"; - icon.isCustom = true; - break; - case "umbmacro": - icon.name = "icon-settings-alt"; - icon.isCustom = true; - break; - case "umbmacro": - icon.name = "icon-settings-alt"; - icon.isCustom = true; - break; - default: - icon.name = alias; - icon.isCustom = false; + case 'codemirror': + icon.name = 'code'; + icon.isCustom = false; + break; + case 'styleselect': + case 'fontsizeselect': + icon.name = 'icon-list'; + icon.isCustom = true; + break; + case 'umbembeddialog': + icon.name = 'icon-tv'; + icon.isCustom = true; + break; + case 'umbmediapicker': + icon.name = 'icon-picture'; + icon.isCustom = true; + break; + case 'umbmacro': + icon.name = 'icon-settings-alt'; + icon.isCustom = true; + break; + case 'umbmacro': + icon.name = 'icon-settings-alt'; + icon.isCustom = true; + break; + default: + icon.name = alias; + icon.isCustom = false; } - return icon; } - - var unsubscribe = $scope.$on("formSubmitting", function (ev, args) { - - var commands = _.where($scope.tinyMceConfig.commands, {selected: true}); - $scope.model.value.toolbar = _.pluck(commands, "frontEndCommand"); - + var unsubscribe = $scope.$on('formSubmitting', function (ev, args) { + var commands = _.where($scope.tinyMceConfig.commands, { selected: true }); + $scope.model.value.toolbar = _.pluck(commands, 'frontEndCommand'); }); - // when the scope is destroyed we need to unsubscribe $scope.$on('$destroy', function () { unsubscribe(); }); - // load TinyMCE skin which contains css for font-icons - assetsService.loadCss("lib/tinymce/skins/umbraco/skin.min.css"); - }); -function sliderController($scope, $log, $element, assetsService, angularHelper) { - - //configure some defaults - if (!$scope.model.config.orientation) { - $scope.model.config.orientation = "horizontal"; - } - if (!$scope.model.config.enableRange) { - $scope.model.config.enableRange = false; - } - else { - $scope.model.config.enableRange = $scope.model.config.enableRange === "1" ? true : false; - } - - if (!$scope.model.config.initVal1) { - $scope.model.config.initVal1 = 0; - } - else { - $scope.model.config.initVal1 = parseFloat($scope.model.config.initVal1); - } - if (!$scope.model.config.initVal2) { - $scope.model.config.initVal2 = 0; - } - else { - $scope.model.config.initVal2 = parseFloat($scope.model.config.initVal2); - } - if (!$scope.model.config.minVal) { - $scope.model.config.minVal = 0; - } - else { - $scope.model.config.minVal = parseFloat($scope.model.config.minVal); - } - if (!$scope.model.config.maxVal) { - $scope.model.config.maxVal = 100; - } - else { - $scope.model.config.maxVal = parseFloat($scope.model.config.maxVal); - } - if (!$scope.model.config.step) { - $scope.model.config.step = 1; - } - else { - $scope.model.config.step = parseFloat($scope.model.config.step); - } - - if (!$scope.model.config.handle) { - $scope.model.config.handle = "round"; - } - - if (!$scope.model.config.reversed) { - $scope.model.config.reversed = false; - } - else { - $scope.model.config.reversed = $scope.model.config.reversed === "1" ? true : false; - } - - if (!$scope.model.config.tooltip) { - $scope.model.config.tooltip = "show"; - } - - if (!$scope.model.config.tooltipSplit) { - $scope.model.config.tooltipSplit = false; - } - else { - $scope.model.config.tooltipSplit = $scope.model.config.tooltipSplit === "1" ? true : false; - } - - if ($scope.model.config.tooltipFormat) { - $scope.model.config.formatter = function (value) { - if (angular.isArray(value) && $scope.model.config.enableRange) { - return $scope.model.config.tooltipFormat.replace("{0}", value[0]).replace("{1}", value[1]); - } else { - return $scope.model.config.tooltipFormat.replace("{0}", value); - } - } - } - - if (!$scope.model.config.ticks) { - $scope.model.config.ticks = []; - } - else { - // returns comma-separated string to an array, e.g. [0, 100, 200, 300, 400] - $scope.model.config.ticks = _.map($scope.model.config.ticks.split(','), function (item) { - return parseInt(item.trim()); - }); - } - - if (!$scope.model.config.ticksPositions) { - $scope.model.config.ticksPositions = []; - } - else { - // returns comma-separated string to an array, e.g. [0, 30, 60, 70, 90, 100] - $scope.model.config.ticksPositions = _.map($scope.model.config.ticksPositions.split(','), function (item) { - return parseInt(item.trim()); - }); - console.log($scope.model.config.ticksPositions); - } - - if (!$scope.model.config.ticksLabels) { - $scope.model.config.ticksLabels = []; - } - else { - // returns comma-separated string to an array, e.g. ['$0', '$100', '$200', '$300', '$400'] - $scope.model.config.ticksLabels = _.map($scope.model.config.ticksLabels.split(','), function (item) { - return item.trim(); - }); - } - - if (!$scope.model.config.ticksSnapBounds) { - $scope.model.config.ticksSnapBounds = 0; - } - else { - $scope.model.config.ticksSnapBounds = parseFloat($scope.model.config.ticksSnapBounds); - } - - /** This creates the slider with the model values - it's called on startup and if the model value changes */ - function createSlider() { - - //the value that we'll give the slider - if it's a range, we store our value as a comma separated val but this slider expects an array - var sliderVal = null; - - //configure the model value based on if range is enabled or not - if ($scope.model.config.enableRange == true) { - //If no value saved yet - then use default value - //If it contains a single value - then also create a new array value - if (!$scope.model.value || $scope.model.value.indexOf(",") == -1) { - var i1 = parseFloat($scope.model.config.initVal1); - var i2 = parseFloat($scope.model.config.initVal2); - sliderVal = [ - isNaN(i1) ? $scope.model.config.minVal : (i1 >= $scope.model.config.minVal ? i1 : $scope.model.config.minVal), - isNaN(i2) ? $scope.model.config.maxVal : (i2 > i1 ? (i2 <= $scope.model.config.maxVal ? i2 : $scope.model.config.maxVal) : $scope.model.config.maxVal) - ]; - } - else { - //this will mean it's a delimited value stored in the db, convert it to an array - sliderVal = _.map($scope.model.value.split(','), function (item) { - return parseFloat(item); - }); - } - } - else { - //If no value saved yet - then use default value - if ($scope.model.value) { - sliderVal = parseFloat($scope.model.value); - } - else { - sliderVal = $scope.model.config.initVal1; - } - } - - // Initialise model value if not set - if (!$scope.model.value) { - setModelValueFromSlider(sliderVal); - } - - //initiate slider, add event handler and get the instance reference (stored in data) - var slider = $element.find('.slider-item').bootstrapSlider({ - max: $scope.model.config.maxVal, - min: $scope.model.config.minVal, - orientation: $scope.model.config.orientation, - selection: $scope.model.config.reversed ? "after" : "before", - step: $scope.model.config.step, - precision: $scope.model.config.precision, - tooltip: $scope.model.config.tooltip, - tooltip_split: $scope.model.config.tooltipSplit, - tooltip_position: $scope.model.config.tooltipPosition, - handle: $scope.model.config.handle, - reversed: $scope.model.config.reversed, - ticks: $scope.model.config.ticks, - ticks_positions: $scope.model.config.ticksPositions, - ticks_labels: $scope.model.config.ticksLabels, - ticks_snap_bounds: $scope.model.config.ticksSnapBounds, - formatter: $scope.model.config.formatter, - range: $scope.model.config.enableRange, - //set the slider val - we cannot do this with data- attributes when using ranges - value: sliderVal - }).on('slideStop', function (e) { - var value = e.value; - angularHelper.safeApply($scope, function () { - setModelValueFromSlider(value); - }); - }).data('slider'); - } - - /** Called on start-up when no model value has been applied and on change of the slider via the UI - updates - the model with the currently selected slider value(s) **/ - function setModelValueFromSlider(sliderVal) { - //Get the value from the slider and format it correctly, if it is a range we want a comma delimited value - if ($scope.model.config.enableRange == true) { - $scope.model.value = sliderVal.join(","); - } - else { - $scope.model.value = sliderVal.toString(); - } - } - - //tell the assetsService to load the bootstrap slider - //libs from the plugin folder - assetsService - .loadJs("lib/slider/js/bootstrap-slider.js") - .then(function () { - - createSlider(); - - //here we declare a special method which will be called whenever the value has changed from the server - //this is instead of doing a watch on the model.value = faster - $scope.model.onValueChanged = function (newVal, oldVal) { - if (newVal != oldVal) { - createSlider(); - } - }; - - }); - - //load the separate css for the editor to avoid it blocking our js loading - assetsService.loadCss("lib/slider/bootstrap-slider.css"); - assetsService.loadCss("lib/slider/bootstrap-slider-custom.css"); -} -angular.module("umbraco").controller("Umbraco.PropertyEditors.SliderController", sliderController); -angular.module("umbraco") -.controller("Umbraco.PropertyEditors.TagsController", - function ($rootScope, $scope, $log, assetsService, umbRequestHelper, angularHelper, $timeout, $element, $sanitize) { - - var $typeahead; - - $scope.isLoading = true; - $scope.tagToAdd = ""; - - assetsService.loadJs("lib/typeahead.js/typeahead.bundle.min.js").then(function () { - - $scope.isLoading = false; - - //load current value - - if ($scope.model.value) { - if (!$scope.model.config.storageType || $scope.model.config.storageType !== "Json") { - //it is csv - if (!$scope.model.value) { - $scope.model.value = []; - } - else { - if($scope.model.value.length > 0) { - $scope.model.value = $scope.model.value.split(","); - } - } - } - } - else { - $scope.model.value = []; - } - - // Method required by the valPropertyValidator directive (returns true if the property editor has at least one tag selected) - $scope.validateMandatory = function () { - return { - isValid: !$scope.model.validation.mandatory || ($scope.model.value != null && $scope.model.value.length > 0), - errorMsg: "Value cannot be empty", - errorKey: "required" - }; - } - - //Helper method to add a tag on enter or on typeahead select - function addTag(tagToAdd) { - tagToAdd = String(tagToAdd).htmlEncode(); - if (tagToAdd != null && tagToAdd.length > 0) { - if ($scope.model.value.indexOf(tagToAdd) < 0) { - $scope.model.value.push(tagToAdd); - //this is required to re-validate - $scope.propertyForm.tagCount.$setViewValue($scope.model.value.length); - } - } - } - - $scope.addTagOnEnter = function (e) { - var code = e.keyCode || e.which; - if (code == 13) { //Enter keycode - if ($element.find('.tags-' + $scope.model.alias).parent().find(".tt-dropdown-menu .tt-cursor").length === 0) { - //this is required, otherwise the html form will attempt to submit. - e.preventDefault(); - $scope.addTag(); - } - } - }; - - $scope.addTag = function () { - //ensure that we're not pressing the enter key whilst selecting a typeahead value from the drop down - //we need to use jquery because typeahead duplicates the text box - addTag($scope.tagToAdd); - $scope.tagToAdd = ""; - //this clears the value stored in typeahead so it doesn't try to add the text again - // http://issues.umbraco.org/issue/U4-4947 - $typeahead.typeahead('val', ''); - }; - - - - $scope.removeTag = function (tag) { - var i = $scope.model.value.indexOf(tag); - if (i >= 0) { - $scope.model.value.splice(i, 1); - //this is required to re-validate - $scope.propertyForm.tagCount.$setViewValue($scope.model.value.length); - } - }; - - //vice versa - $scope.model.onValueChanged = function (newVal, oldVal) { - //update the display val again if it has changed from the server - $scope.model.value = newVal; - - if (!$scope.model.config.storageType || $scope.model.config.storageType !== "Json") { - //it is csv - if (!$scope.model.value) { - $scope.model.value = []; - } - else { - $scope.model.value = $scope.model.value.split(","); - } - } - }; - - //configure the tags data source - - //helper method to format the data for bloodhound - function dataTransform(list) { - //transform the result to what bloodhound wants - var tagList = _.map(list, function (i) { - return { value: i.text }; - }); - // remove current tags from the list - return $.grep(tagList, function (tag) { - return ($.inArray(tag.value, $scope.model.value) === -1); - }); - } - - // helper method to remove current tags - function removeCurrentTagsFromSuggestions(suggestions) { - return $.grep(suggestions, function (suggestion) { - return ($.inArray(suggestion.value, $scope.model.value) === -1); - }); - } - - var tagsHound = new Bloodhound({ - datumTokenizer: Bloodhound.tokenizers.obj.whitespace('value'), - queryTokenizer: Bloodhound.tokenizers.whitespace, - dupDetector : function(remoteMatch, localMatch) { - return (remoteMatch["value"] == localMatch["value"]); - }, - //pre-fetch the tags for this category - prefetch: { - url: umbRequestHelper.getApiUrl("tagsDataBaseUrl", "GetTags", [{ tagGroup: $scope.model.config.group }]), - //TTL = 5 minutes - ttl: 300000, - filter: dataTransform - }, - //dynamically get the tags for this category (they may have changed on the server) - remote: { - url: umbRequestHelper.getApiUrl("tagsDataBaseUrl", "GetTags", [{ tagGroup: $scope.model.config.group }]), - filter: dataTransform - } - }); - - tagsHound.initialize(true); - - //configure the type ahead - $timeout(function () { - - $typeahead = $element.find('.tags-' + $scope.model.alias).typeahead( - { - //This causes some strangeness as it duplicates the textbox, best leave off for now. - hint: false, - highlight: true, - cacheKey: new Date(), // Force a cache refresh each time the control is initialized - minLength: 1 - }, { - //see: https://github.com/twitter/typeahead.js/blob/master/doc/jquery_typeahead.md#options - // name = the data set name, we'll make this the tag group name - name: $scope.model.config.group, - displayKey: "value", - source: function (query, cb) { - tagsHound.get(query, function (suggestions) { - cb(removeCurrentTagsFromSuggestions(suggestions)); - }); - }, - }).bind("typeahead:selected", function (obj, datum, name) { - angularHelper.safeApply($scope, function () { - addTag(datum["value"]); - $scope.tagToAdd = ""; - // clear the typed text - $typeahead.typeahead('val', ''); - }); - - }).bind("typeahead:autocompleted", function (obj, datum, name) { - angularHelper.safeApply($scope, function () { - addTag(datum["value"]); - $scope.tagToAdd = ""; - }); - - }).bind("typeahead:opened", function (obj) { - //console.log("opened "); - }); - }); - - $scope.$on('$destroy', function () { - tagsHound.clearPrefetchCache(); - tagsHound.clearRemoteCache(); - $element.find('.tags-' + $scope.model.alias).typeahead('destroy'); - delete tagsHound; - }); - - }); - - } -); -//this controller simply tells the dialogs service to open a mediaPicker window -//with a specified callback, this callback will receive an object with a selection on it -angular.module('umbraco').controller("Umbraco.PropertyEditors.EmbeddedContentController", - function($rootScope, $scope, $log){ - - $scope.showForm = false; - $scope.fakeData = []; - - $scope.create = function(){ - $scope.showForm = true; - $scope.fakeData = angular.copy($scope.model.config.fields); - }; - - $scope.show = function(){ - $scope.showCode = true; - }; - - $scope.add = function(){ - $scope.showForm = false; - if ( !($scope.model.value instanceof Array)) { - $scope.model.value = []; - } - - $scope.model.value.push(angular.copy($scope.fakeData)); - $scope.fakeData = []; - }; -}); -angular.module('umbraco').controller("Umbraco.PropertyEditors.UrlListController", - function($rootScope, $scope, $filter) { - - function formatDisplayValue() { - if (angular.isArray($scope.model.value)) { - //it's the json value - $scope.renderModel = _.map($scope.model.value, function (item) { - return { - url: item.url, - linkText: item.linkText, - urlTarget: (item.target) ? item.target : "_blank", - icon: (item.icon) ? item.icon : "icon-out" - }; - }); - } - else { - //it's the default csv value - $scope.renderModel = _.map($scope.model.value.split(","), function (item) { - return { - url: item, - linkText: "", - urlTarget: ($scope.config && $scope.config.target) ? $scope.config.target : "_blank", - icon: ($scope.config && $scope.config.icon) ? $scope.config.icon : "icon-out" - }; - }); - } - } - - $scope.getUrl = function(valueUrl) { - if (valueUrl.indexOf("/") >= 0) { - return valueUrl; - } - return "#"; - }; - - formatDisplayValue(); - - //here we declare a special method which will be called whenever the value has changed from the server - //this is instead of doing a watch on the model.value = faster - $scope.model.onValueChanged = function(newVal, oldVal) { - //update the display val again - formatDisplayValue(); - }; - - }); - -})(); \ No newline at end of file + assetsService.loadCss('lib/tinymce/skins/umbraco/skin.min.css', $scope); + }); + function sliderController($scope, $log, $element, assetsService, angularHelper) { + //configure some defaults + if (!$scope.model.config.orientation) { + $scope.model.config.orientation = 'horizontal'; + } + if (!$scope.model.config.enableRange) { + $scope.model.config.enableRange = false; + } else { + $scope.model.config.enableRange = $scope.model.config.enableRange === '1' ? true : false; + } + if (!$scope.model.config.initVal1) { + $scope.model.config.initVal1 = 0; + } else { + $scope.model.config.initVal1 = parseFloat($scope.model.config.initVal1); + } + if (!$scope.model.config.initVal2) { + $scope.model.config.initVal2 = 0; + } else { + $scope.model.config.initVal2 = parseFloat($scope.model.config.initVal2); + } + if (!$scope.model.config.minVal) { + $scope.model.config.minVal = 0; + } else { + $scope.model.config.minVal = parseFloat($scope.model.config.minVal); + } + if (!$scope.model.config.maxVal) { + $scope.model.config.maxVal = 100; + } else { + $scope.model.config.maxVal = parseFloat($scope.model.config.maxVal); + } + if (!$scope.model.config.step) { + $scope.model.config.step = 1; + } else { + $scope.model.config.step = parseFloat($scope.model.config.step); + } + if (!$scope.model.config.handle) { + $scope.model.config.handle = 'round'; + } + if (!$scope.model.config.reversed) { + $scope.model.config.reversed = false; + } else { + $scope.model.config.reversed = $scope.model.config.reversed === '1' ? true : false; + } + if (!$scope.model.config.tooltip) { + $scope.model.config.tooltip = 'show'; + } + if (!$scope.model.config.tooltipSplit) { + $scope.model.config.tooltipSplit = false; + } else { + $scope.model.config.tooltipSplit = $scope.model.config.tooltipSplit === '1' ? true : false; + } + if ($scope.model.config.tooltipFormat) { + $scope.model.config.formatter = function (value) { + if (angular.isArray(value) && $scope.model.config.enableRange) { + return $scope.model.config.tooltipFormat.replace('{0}', value[0]).replace('{1}', value[1]); + } else { + return $scope.model.config.tooltipFormat.replace('{0}', value); + } + }; + } + if (!$scope.model.config.ticks) { + $scope.model.config.ticks = []; + } else { + // returns comma-separated string to an array, e.g. [0, 100, 200, 300, 400] + $scope.model.config.ticks = _.map($scope.model.config.ticks.split(','), function (item) { + return parseInt(item.trim()); + }); + } + if (!$scope.model.config.ticksPositions) { + $scope.model.config.ticksPositions = []; + } else { + // returns comma-separated string to an array, e.g. [0, 30, 60, 70, 90, 100] + $scope.model.config.ticksPositions = _.map($scope.model.config.ticksPositions.split(','), function (item) { + return parseInt(item.trim()); + }); + } + if (!$scope.model.config.ticksLabels) { + $scope.model.config.ticksLabels = []; + } else { + // returns comma-separated string to an array, e.g. ['$0', '$100', '$200', '$300', '$400'] + $scope.model.config.ticksLabels = _.map($scope.model.config.ticksLabels.split(','), function (item) { + return item.trim(); + }); + } + if (!$scope.model.config.ticksSnapBounds) { + $scope.model.config.ticksSnapBounds = 0; + } else { + $scope.model.config.ticksSnapBounds = parseFloat($scope.model.config.ticksSnapBounds); + } + /** This creates the slider with the model values - it's called on startup and if the model value changes */ + function createSlider() { + //the value that we'll give the slider - if it's a range, we store our value as a comma separated val but this slider expects an array + var sliderVal = null; + //configure the model value based on if range is enabled or not + if ($scope.model.config.enableRange == true) { + //If no value saved yet - then use default value + //If it contains a single value - then also create a new array value + if (!$scope.model.value || $scope.model.value.indexOf(',') == -1) { + var i1 = parseFloat($scope.model.config.initVal1); + var i2 = parseFloat($scope.model.config.initVal2); + sliderVal = [ + isNaN(i1) ? $scope.model.config.minVal : i1 >= $scope.model.config.minVal ? i1 : $scope.model.config.minVal, + isNaN(i2) ? $scope.model.config.maxVal : i2 >= i1 ? i2 <= $scope.model.config.maxVal ? i2 : $scope.model.config.maxVal : $scope.model.config.maxVal + ]; + } else { + //this will mean it's a delimited value stored in the db, convert it to an array + sliderVal = _.map($scope.model.value.split(','), function (item) { + return parseFloat(item); + }); + } + } else { + //If no value saved yet - then use default value + if ($scope.model.value) { + sliderVal = parseFloat($scope.model.value); + } else { + sliderVal = $scope.model.config.initVal1; + } + } + // Initialise model value if not set + if (!$scope.model.value) { + setModelValueFromSlider(sliderVal); + } + //initiate slider, add event handler and get the instance reference (stored in data) + var slider = $element.find('.slider-item').bootstrapSlider({ + max: $scope.model.config.maxVal, + min: $scope.model.config.minVal, + orientation: $scope.model.config.orientation, + selection: $scope.model.config.reversed ? 'after' : 'before', + step: $scope.model.config.step, + precision: $scope.model.config.precision, + tooltip: $scope.model.config.tooltip, + tooltip_split: $scope.model.config.tooltipSplit, + tooltip_position: $scope.model.config.tooltipPosition, + handle: $scope.model.config.handle, + reversed: $scope.model.config.reversed, + ticks: $scope.model.config.ticks, + ticks_positions: $scope.model.config.ticksPositions, + ticks_labels: $scope.model.config.ticksLabels, + ticks_snap_bounds: $scope.model.config.ticksSnapBounds, + formatter: $scope.model.config.formatter, + range: $scope.model.config.enableRange, + //set the slider val - we cannot do this with data- attributes when using ranges + value: sliderVal + }).on('slideStop', function (e) { + var value = e.value; + angularHelper.safeApply($scope, function () { + setModelValueFromSlider(value); + }); + }).data('slider'); + } + /** Called on start-up when no model value has been applied and on change of the slider via the UI - updates + the model with the currently selected slider value(s) **/ + function setModelValueFromSlider(sliderVal) { + //Get the value from the slider and format it correctly, if it is a range we want a comma delimited value + if ($scope.model.config.enableRange == true) { + $scope.model.value = sliderVal.join(','); + } else { + $scope.model.value = sliderVal.toString(); + } + } + //tell the assetsService to load the bootstrap slider + //libs from the plugin folder + assetsService.loadJs('lib/slider/js/bootstrap-slider.js').then(function () { + createSlider(); + //here we declare a special method which will be called whenever the value has changed from the server + //this is instead of doing a watch on the model.value = faster + $scope.model.onValueChanged = function (newVal, oldVal) { + if (newVal != oldVal) { + createSlider(); + } + }; + }); + //load the separate css for the editor to avoid it blocking our js loading + assetsService.loadCss('lib/slider/bootstrap-slider.css', $scope); + assetsService.loadCss('lib/slider/bootstrap-slider-custom.css', $scope); + } + angular.module('umbraco').controller('Umbraco.PropertyEditors.SliderController', sliderController); + angular.module('umbraco').controller('Umbraco.PropertyEditors.TagsController', function ($rootScope, $scope, $log, assetsService, umbRequestHelper, angularHelper, $timeout, $element) { + var $typeahead; + $scope.isLoading = true; + $scope.tagToAdd = ''; + function setModelValue(val) { + $scope.model.value = val || $scope.model.value; + if ($scope.model.value) { + if (!$scope.model.config.storageType || $scope.model.config.storageType !== 'Json') { + //it is csv + if (!$scope.model.value) { + $scope.model.value = []; + } else { + if ($scope.model.value.length > 0) { + // split the csv string, and remove any duplicate values + var tempArray = $scope.model.value.split(',').map(function (v) { + return v.trim(); + }); + $scope.model.value = tempArray.filter(function (v, i, self) { + return self.indexOf(v) === i; + }); + } + } + } + } else { + $scope.model.value = []; + } + } + assetsService.loadJs('lib/typeahead.js/typeahead.bundle.min.js', $scope).then(function () { + $scope.isLoading = false; + //load current value + setModelValue(); + // Method required by the valPropertyValidator directive (returns true if the property editor has at least one tag selected) + $scope.validateMandatory = function () { + return { + isValid: !$scope.model.validation.mandatory || $scope.model.value != null && $scope.model.value.length > 0, + errorMsg: 'Value cannot be empty', + errorKey: 'required' + }; + }; + //Helper method to add a tag on enter or on typeahead select + function addTag(tagToAdd) { + if (tagToAdd != null && tagToAdd.length > 0) { + if ($scope.model.value.indexOf(tagToAdd) < 0) { + $scope.model.value.push(tagToAdd); + //this is required to re-validate + $scope.propertyForm.tagCount.$setViewValue($scope.model.value.length); + } + } + } + $scope.addTagOnEnter = function (e) { + var code = e.keyCode || e.which; + if (code == 13) { + //Enter keycode + if ($element.find('.tags-' + $scope.model.alias).parent().find('.tt-dropdown-menu .tt-cursor').length === 0) { + //this is required, otherwise the html form will attempt to submit. + e.preventDefault(); + $scope.addTag(); + } + } + }; + $scope.addTag = function () { + //ensure that we're not pressing the enter key whilst selecting a typeahead value from the drop down + //we need to use jquery because typeahead duplicates the text box + addTag($scope.tagToAdd); + $scope.tagToAdd = ''; + //this clears the value stored in typeahead so it doesn't try to add the text again + // https://issues.umbraco.org/issue/U4-4947 + $typeahead.typeahead('val', ''); + }; + // Set the visible prompt to -1 to ensure it will not be visible + $scope.promptIsVisible = '-1'; + $scope.removeTag = function (tag) { + var i = $scope.model.value.indexOf(tag); + if (i >= 0) { + // Make sure to hide the prompt so it does not stay open because another item gets a new number in the array index + $scope.promptIsVisible = '-1'; + // Remove the tag from the index + $scope.model.value.splice(i, 1); + //this is required to re-validate + $scope.propertyForm.tagCount.$setViewValue($scope.model.value.length); + } + }; + $scope.showPrompt = function (idx, tag) { + var i = $scope.model.value.indexOf(tag); + // Make the prompt visible for the clicked tag only + if (i === idx) { + $scope.promptIsVisible = i; + } + }; + $scope.hidePrompt = function () { + $scope.promptIsVisible = '-1'; + }; + //vice versa + $scope.model.onValueChanged = function (newVal, oldVal) { + //update the display val again if it has changed from the server + setModelValue(newVal); + }; + //configure the tags data source + //helper method to format the data for bloodhound + function dataTransform(list) { + //transform the result to what bloodhound wants + var tagList = _.map(list, function (i) { + return { value: i.text }; + }); + // remove current tags from the list + return $.grep(tagList, function (tag) { + return $.inArray(tag.value, $scope.model.value) === -1; + }); + } + // helper method to remove current tags + function removeCurrentTagsFromSuggestions(suggestions) { + return $.grep(suggestions, function (suggestion) { + return $.inArray(suggestion.value, $scope.model.value) === -1; + }); + } + var tagsHound = new Bloodhound({ + datumTokenizer: Bloodhound.tokenizers.obj.whitespace('value'), + queryTokenizer: Bloodhound.tokenizers.whitespace, + dupDetector: function (remoteMatch, localMatch) { + return remoteMatch['value'] == localMatch['value']; + }, + //pre-fetch the tags for this category + prefetch: { + url: umbRequestHelper.getApiUrl('tagsDataBaseUrl', 'GetTags', [{ tagGroup: $scope.model.config.group }]), + //TTL = 5 minutes + ttl: 300000, + filter: dataTransform + }, + //dynamically get the tags for this category (they may have changed on the server) + remote: { + url: umbRequestHelper.getApiUrl('tagsDataBaseUrl', 'GetTags', [{ tagGroup: $scope.model.config.group }]), + filter: dataTransform + } + }); + tagsHound.initialize(true); + //configure the type ahead + $timeout(function () { + $typeahead = $element.find('.tags-' + $scope.model.alias).typeahead({ + //This causes some strangeness as it duplicates the textbox, best leave off for now. + hint: false, + highlight: true, + cacheKey: new Date(), + // Force a cache refresh each time the control is initialized + minLength: 1 + }, { + //see: https://github.com/twitter/typeahead.js/blob/master/doc/jquery_typeahead.md#options + // name = the data set name, we'll make this the tag group name + name: $scope.model.config.group, + displayKey: 'value', + source: function (query, cb) { + tagsHound.get(query, function (suggestions) { + cb(removeCurrentTagsFromSuggestions(suggestions)); + }); + } + }).bind('typeahead:selected', function (obj, datum, name) { + angularHelper.safeApply($scope, function () { + addTag(datum['value']); + $scope.tagToAdd = ''; + // clear the typed text + $typeahead.typeahead('val', ''); + }); + }).bind('typeahead:autocompleted', function (obj, datum, name) { + angularHelper.safeApply($scope, function () { + addTag(datum['value']); + $scope.tagToAdd = ''; + }); + }).bind('typeahead:opened', function (obj) { + }); + }); + $scope.$on('$destroy', function () { + tagsHound.clearPrefetchCache(); + tagsHound.clearRemoteCache(); + $element.find('.tags-' + $scope.model.alias).typeahead('destroy'); + delete tagsHound; + }); + }); + }); + //this controller simply tells the dialogs service to open a mediaPicker window + //with a specified callback, this callback will receive an object with a selection on it + angular.module('umbraco').controller('Umbraco.PropertyEditors.EmbeddedContentController', function ($rootScope, $scope, $log) { + $scope.showForm = false; + $scope.fakeData = []; + $scope.create = function () { + $scope.showForm = true; + $scope.fakeData = angular.copy($scope.model.config.fields); + }; + $scope.show = function () { + $scope.showCode = true; + }; + $scope.add = function () { + $scope.showForm = false; + if (!($scope.model.value instanceof Array)) { + $scope.model.value = []; + } + $scope.model.value.push(angular.copy($scope.fakeData)); + $scope.fakeData = []; + }; + }); + function textAreaController($scope) { + // macro parameter editor doesn't contains a config object, + // so we create a new one to hold any properties + if (!$scope.model.config) { + $scope.model.config = {}; + } + if (!$scope.model.config.maxChars) { + $scope.model.config.maxChars = false; + } + $scope.model.maxlength = false; + if ($scope.model.config && $scope.model.config.maxChars) { + $scope.model.maxlength = true; + if ($scope.model.value == undefined) { + $scope.model.count = $scope.model.config.maxChars * 1; + } else { + $scope.model.count = $scope.model.config.maxChars * 1 - $scope.model.value.length; + } + } + $scope.model.change = function () { + if ($scope.model.config && $scope.model.config.maxChars) { + if ($scope.model.value == undefined) { + $scope.model.count = $scope.model.config.maxChars * 1; + } else { + $scope.model.count = $scope.model.config.maxChars * 1 - $scope.model.value.length; + } + if ($scope.model.count < 0) { + $scope.model.value = $scope.model.value.substring(0, $scope.model.config.maxChars * 1); + $scope.model.count = 0; + } + } + }; + } + angular.module('umbraco').controller('Umbraco.PropertyEditors.textAreaController', textAreaController); + function textboxController($scope) { + // macro parameter editor doesn't contains a config object, + // so we create a new one to hold any properties + if (!$scope.model.config) { + $scope.model.config = {}; + } + $scope.model.maxlength = false; + if ($scope.model.config && $scope.model.config.maxChars) { + $scope.model.maxlength = true; + } + if (!$scope.model.config.maxChars) { + // 500 is the maximum number that can be stored + // in the database, so set it to the max, even + // if no max is specified in the config + $scope.model.config.maxChars = 500; + } + if ($scope.model.maxlength) { + if ($scope.model.value === undefined) { + $scope.model.count = $scope.model.config.maxChars * 1; + } else { + $scope.model.count = $scope.model.config.maxChars * 1 - $scope.model.value.length; + } + } + $scope.model.change = function () { + if ($scope.model.config && $scope.model.config.maxChars) { + if ($scope.model.value === undefined) { + $scope.model.count = $scope.model.config.maxChars * 1; + } else { + $scope.model.count = $scope.model.config.maxChars * 1 - $scope.model.value.length; + } + if ($scope.model.count < 0) { + $scope.model.value = $scope.model.value.substring(0, $scope.model.config.maxChars * 1); + $scope.model.count = 0; + } + } + }; + } + angular.module('umbraco').controller('Umbraco.PropertyEditors.textboxController', textboxController); + angular.module('umbraco').controller('Umbraco.PropertyEditors.UrlListController', function ($rootScope, $scope, $filter) { + function formatDisplayValue() { + if (angular.isArray($scope.model.value)) { + //it's the json value + $scope.renderModel = _.map($scope.model.value, function (item) { + return { + url: item.url, + linkText: item.linkText, + urlTarget: item.target ? item.target : '_blank', + icon: item.icon ? item.icon : 'icon-out' + }; + }); + } else { + //it's the default csv value + $scope.renderModel = _.map($scope.model.value.split(','), function (item) { + return { + url: item, + linkText: '', + urlTarget: $scope.config && $scope.config.target ? $scope.config.target : '_blank', + icon: $scope.config && $scope.config.icon ? $scope.config.icon : 'icon-out' + }; + }); + } + } + $scope.getUrl = function (valueUrl) { + if (valueUrl.indexOf('/') >= 0) { + return valueUrl; + } + return '#'; + }; + formatDisplayValue(); + //here we declare a special method which will be called whenever the value has changed from the server + //this is instead of doing a watch on the model.value = faster + $scope.model.onValueChanged = function (newVal, oldVal) { + //update the display val again + formatDisplayValue(); + }; + }); + (function () { + 'use strict'; + function ScriptsCreateController($scope, $location, navigationService, formHelper, codefileResource, localizationService, appState) { + var vm = this; + var node = $scope.dialogOptions.currentNode; + var localizeCreateFolder = localizationService.localize('defaultdialog_createFolder'); + vm.creatingFolder = false; + vm.folderName = ''; + vm.createFolderError = ''; + vm.fileExtension = ''; + vm.createFile = createFile; + vm.showCreateFolder = showCreateFolder; + vm.createFolder = createFolder; + function createFile() { + $location.path('/settings/scripts/edit/' + node.id).search('create', 'true'); + navigationService.hideMenu(); + } + function showCreateFolder() { + vm.creatingFolder = true; + } + function createFolder(form) { + if (formHelper.submitForm({ + scope: $scope, + formCtrl: form, + statusMessage: localizeCreateFolder + })) { + codefileResource.createContainer('scripts', node.id, vm.folderName).then(function (saved) { + navigationService.hideMenu(); + navigationService.syncTree({ + tree: 'scripts', + path: saved.path, + forceReload: true, + activate: true + }); + formHelper.resetForm({ scope: $scope }); + var section = appState.getSectionState('currentSection'); + }, function (err) { + vm.createFolderError = err; + formHelper.showNotifications(err.data); + }); + } + } + } + angular.module('umbraco').controller('Umbraco.Editors.Scripts.CreateController', ScriptsCreateController); + }()); + /** + * @ngdoc controller + * @name Umbraco.Editors.Scripts.DeleteController + * @function + * + * @description + * The controller for deleting scripts + */ + function ScriptsDeleteController($scope, codefileResource, treeService, navigationService) { + $scope.performDelete = function () { + //mark it for deletion (used in the UI) + $scope.currentNode.loading = true; + codefileResource.deleteByPath('scripts', $scope.currentNode.id).then(function () { + $scope.currentNode.loading = false; + //get the root node before we remove it + var rootNode = treeService.getTreeRoot($scope.currentNode); + //TODO: Need to sync tree, etc... + treeService.removeNode($scope.currentNode); + navigationService.hideMenu(); + }); + }; + $scope.cancel = function () { + navigationService.hideDialog(); + }; + } + angular.module('umbraco').controller('Umbraco.Editors.Scripts.DeleteController', ScriptsDeleteController); + (function () { + 'use strict'; + function ScriptsEditController($scope, $routeParams, $timeout, appState, editorState, navigationService, assetsService, codefileResource, contentEditingHelper, notificationsService, localizationService, templateHelper, angularHelper) { + var vm = this; + var currentPosition = null; + var localizeSaving = localizationService.localize('general_saving'); + vm.page = {}; + vm.page.loading = true; + vm.page.menu = {}; + vm.page.menu.currentSection = appState.getSectionState('currentSection'); + vm.page.menu.currentNode = null; + vm.page.saveButtonState = 'init'; + //Used to toggle the keyboard shortcut modal + //From a custom keybinding in ace editor - that conflicts with our own to show the dialog + vm.showKeyboardShortcut = false; + //Keyboard shortcuts for help dialog + vm.page.keyboardShortcutsOverview = []; + vm.page.keyboardShortcutsOverview.push(templateHelper.getGeneralShortcuts()); + vm.page.keyboardShortcutsOverview.push(templateHelper.getEditorShortcuts()); + vm.script = {}; + // bind functions to view model + vm.save = save; + /* Function bound to view model */ + function save() { + vm.page.saveButtonState = 'busy'; + vm.script.content = vm.editor.getValue(); + contentEditingHelper.contentEditorPerformSave({ + statusMessage: localizeSaving, + saveMethod: codefileResource.save, + scope: $scope, + content: vm.script, + // We do not redirect on failure for scripts - this is because it is not possible to actually save the script + // when server side validation fails - as opposed to content where we are capable of saving the content + // item if server side validation fails + redirectOnFailure: false, + rebindCallback: function (orignal, saved) { + } + }).then(function (saved) { + localizationService.localizeMany([ + 'speechBubbles_fileSavedHeader', + 'speechBubbles_fileSavedText' + ]).then(function (data) { + var header = data[0]; + var message = data[1]; + notificationsService.success(header, message); + }); + //check if the name changed, if so we need to redirect + if (vm.script.id !== saved.id) { + contentEditingHelper.redirectToRenamedContent(saved.id); + } else { + vm.page.saveButtonState = 'success'; + vm.script = saved; + //sync state + editorState.set(vm.script); + // sync tree + navigationService.syncTree({ + tree: 'scripts', + path: vm.script.path, + forceReload: true + }).then(function (syncArgs) { + vm.page.menu.currentNode = syncArgs.node; + }); + } + }, function (err) { + vm.page.saveButtonState = 'error'; + localizationService.localizeMany([ + 'speechBubbles_validationFailedHeader', + 'speechBubbles_validationFailedMessage' + ]).then(function (data) { + var header = data[0]; + var message = data[1]; + notificationsService.error(header, message); + }); + }); + } + /* Local functions */ + function init() { + //we need to load this somewhere, for now its here. + assetsService.loadCss('lib/ace-razor-mode/theme/razor_chrome.css', $scope); + if ($routeParams.create) { + codefileResource.getScaffold('scripts', $routeParams.id).then(function (script) { + ready(script, false); + }); + } else { + codefileResource.getByPath('scripts', $routeParams.id).then(function (script) { + ready(script, true); + }); + } + } + function ready(script, syncTree) { + vm.page.loading = false; + vm.script = script; + //sync state + editorState.set(vm.script); + if (syncTree) { + navigationService.syncTree({ + tree: 'scripts', + path: vm.script.path, + forceReload: true + }).then(function (syncArgs) { + vm.page.menu.currentNode = syncArgs.node; + }); + } + vm.aceOption = { + mode: 'javascript', + theme: 'chrome', + showPrintMargin: false, + advanced: { + fontSize: '14px', + enableSnippets: true, + enableBasicAutocompletion: true, + enableLiveAutocompletion: false + }, + onLoad: function (_editor) { + vm.editor = _editor; + //Update the auto-complete method to use ctrl+alt+space + _editor.commands.bindKey('ctrl-alt-space', 'startAutocomplete'); + //Unassigns the keybinding (That was previously auto-complete) + //As conflicts with our own tree search shortcut + _editor.commands.bindKey('ctrl-space', null); + //TODO: Move all these keybinding config out into some helper/service + _editor.commands.addCommands([//Disable (alt+shift+K) + //Conflicts with our own show shortcuts dialog - this overrides it + { + name: 'unSelectOrFindPrevious', + bindKey: 'Alt-Shift-K', + exec: function () { + //Toggle the show keyboard shortcuts overlay + $scope.$apply(function () { + vm.showKeyboardShortcut = !vm.showKeyboardShortcut; + }); + }, + readOnly: true + }]); + // initial cursor placement + // Keep cursor in name field if we are create a new script + // else set the cursor at the bottom of the code editor + if (!$routeParams.create) { + $timeout(function () { + vm.editor.navigateFileEnd(); + vm.editor.focus(); + }); + } + vm.editor.on('change', changeAceEditor); + } + }; + function changeAceEditor() { + setFormState('dirty'); + } + function setFormState(state) { + // get the current form + var currentForm = angularHelper.getCurrentForm($scope); + // set state + if (state === 'dirty') { + currentForm.$setDirty(); + } else if (state === 'pristine') { + currentForm.$setPristine(); + } + } + } + init(); + } + angular.module('umbraco').controller('Umbraco.Editors.Scripts.EditController', ScriptsEditController); + }()); + /** + * @ngdoc controller + * @name Umbraco.Editors.Templates.DeleteController + * @function + * + * @description + * The controller for the template delete dialog + */ + function TemplatesDeleteController($scope, templateResource, treeService, navigationService) { + $scope.performDelete = function () { + //mark it for deletion (used in the UI) + $scope.currentNode.loading = true; + templateResource.deleteById($scope.currentNode.id).then(function () { + $scope.currentNode.loading = false; + //get the root node before we remove it + var rootNode = treeService.getTreeRoot($scope.currentNode); + //TODO: Need to sync tree, etc... + treeService.removeNode($scope.currentNode); + navigationService.hideMenu(); + }); + }; + $scope.cancel = function () { + navigationService.hideDialog(); + }; + } + angular.module('umbraco').controller('Umbraco.Editors.Templates.DeleteController', TemplatesDeleteController); + (function () { + 'use strict'; + function TemplatesEditController($scope, $routeParams, $timeout, templateResource, assetsService, notificationsService, editorState, navigationService, appState, macroService, treeService, contentEditingHelper, localizationService, angularHelper, templateHelper) { + var vm = this; + var oldMasterTemplateAlias = null; + var localizeSaving = localizationService.localize('general_saving'); + vm.page = {}; + vm.page.loading = true; + vm.templates = []; + //menu + vm.page.menu = {}; + vm.page.menu.currentSection = appState.getSectionState('currentSection'); + vm.page.menu.currentNode = null; + //Used to toggle the keyboard shortcut modal + //From a custom keybinding in ace editor - that conflicts with our own to show the dialog + vm.showKeyboardShortcut = false; + //Keyboard shortcuts for help dialog + vm.page.keyboardShortcutsOverview = []; + vm.page.keyboardShortcutsOverview.push(templateHelper.getGeneralShortcuts()); + vm.page.keyboardShortcutsOverview.push(templateHelper.getEditorShortcuts()); + vm.page.keyboardShortcutsOverview.push(templateHelper.getTemplateEditorShortcuts()); + vm.save = function (suppressNotification) { + vm.page.saveButtonState = 'busy'; + vm.template.content = vm.editor.getValue(); + contentEditingHelper.contentEditorPerformSave({ + statusMessage: localizeSaving, + saveMethod: templateResource.save, + scope: $scope, + content: vm.template, + //We do not redirect on failure for templates - this is because it is not possible to actually save the template + // type when server side validation fails - as opposed to content where we are capable of saving the content + // item if server side validation fails + redirectOnFailure: false, + rebindCallback: function (orignal, saved) { + } + }).then(function (saved) { + if (!suppressNotification) { + localizationService.localizeMany([ + 'speechBubbles_templateSavedHeader', + 'speechBubbles_templateSavedText' + ]).then(function (data) { + var header = data[0]; + var message = data[1]; + notificationsService.success(header, message); + }); + } + vm.page.saveButtonState = 'success'; + vm.template = saved; + //sync state + editorState.set(vm.template); + // sync tree + // if master template alias has changed move the node to it's new location + if (oldMasterTemplateAlias !== vm.template.masterTemplateAlias) { + // When creating a new template the id is -1. Make sure We don't remove the root node. + if (vm.page.menu.currentNode.id !== '-1') { + // move node to new location in tree + //first we need to remove the node that we're working on + treeService.removeNode(vm.page.menu.currentNode); + } + // update stored alias to the new one so the node won't move again unless the alias is changed again + oldMasterTemplateAlias = vm.template.masterTemplateAlias; + navigationService.syncTree({ + tree: 'templates', + path: vm.template.path, + forceReload: true, + activate: true + }).then(function (args) { + vm.page.menu.currentNode = args.node; + }); + } else { + // normal tree sync + navigationService.syncTree({ + tree: 'templates', + path: vm.template.path, + forceReload: true + }).then(function (syncArgs) { + vm.page.menu.currentNode = syncArgs.node; + }); + } + // clear $dirty state on form + setFormState('pristine'); + }, function (err) { + vm.page.saveButtonState = 'error'; + localizationService.localizeMany([ + 'speechBubbles_validationFailedHeader', + 'speechBubbles_validationFailedMessage' + ]).then(function (data) { + var header = data[0]; + var message = data[1]; + notificationsService.error(header, message); + }); + }); + }; + vm.init = function () { + //we need to load this somewhere, for now its here. + assetsService.loadCss('lib/ace-razor-mode/theme/razor_chrome.css', $scope); + //load templates - used in the master template picker + templateResource.getAll().then(function (templates) { + vm.templates = templates; + }); + if ($routeParams.create) { + templateResource.getScaffold($routeParams.id).then(function (template) { + vm.ready(template); + }); + } else { + templateResource.getById($routeParams.id).then(function (template) { + vm.ready(template); + }); + } + }; + vm.ready = function (template) { + vm.page.loading = false; + vm.template = template; + // if this is a new template, bind to the blur event on the name + if ($routeParams.create) { + $timeout(function () { + var nameField = angular.element(document.querySelector('[data-element="editor-name-field"]')); + if (nameField) { + nameField.bind('blur', function (event) { + if (event.target.value) { + vm.save(true); + } + }); + } + }); + } + //sync state + editorState.set(vm.template); + navigationService.syncTree({ + tree: 'templates', + path: vm.template.path, + forceReload: true + }).then(function (syncArgs) { + vm.page.menu.currentNode = syncArgs.node; + }); + // save state of master template to use for comparison when syncing the tree on save + oldMasterTemplateAlias = angular.copy(template.masterTemplateAlias); + // ace configuration + vm.aceOption = { + mode: 'razor', + theme: 'chrome', + showPrintMargin: false, + advanced: { + fontSize: '14px', + enableSnippets: false, + //The Razor mode snippets are awful (Need a way to override these) + enableBasicAutocompletion: true, + enableLiveAutocompletion: false + }, + onLoad: function (_editor) { + vm.editor = _editor; + //Update the auto-complete method to use ctrl+alt+space + _editor.commands.bindKey('ctrl-alt-space', 'startAutocomplete'); + //Unassigns the keybinding (That was previously auto-complete) + //As conflicts with our own tree search shortcut + _editor.commands.bindKey('ctrl-space', null); + // Assign new keybinding + _editor.commands.addCommands([ + //Disable (alt+shift+K) + //Conflicts with our own show shortcuts dialog - this overrides it + { + name: 'unSelectOrFindPrevious', + bindKey: 'Alt-Shift-K', + exec: function () { + //Toggle the show keyboard shortcuts overlay + $scope.$apply(function () { + vm.showKeyboardShortcut = !vm.showKeyboardShortcut; + }); + }, + readOnly: true + }, + { + name: 'insertUmbracoValue', + bindKey: 'Alt-Shift-V', + exec: function () { + $scope.$apply(function () { + openPageFieldOverlay(); + }); + }, + readOnly: true + }, + { + name: 'insertPartialView', + bindKey: 'Alt-Shift-P', + exec: function () { + $scope.$apply(function () { + openPartialOverlay(); + }); + }, + readOnly: true + }, + { + name: 'insertDictionary', + bindKey: 'Alt-Shift-D', + exec: function () { + $scope.$apply(function () { + openDictionaryItemOverlay(); + }); + }, + readOnly: true + }, + { + name: 'insertUmbracoMacro', + bindKey: 'Alt-Shift-M', + exec: function () { + $scope.$apply(function () { + openMacroOverlay(); + }); + }, + readOnly: true + }, + { + name: 'insertQuery', + bindKey: 'Alt-Shift-Q', + exec: function () { + $scope.$apply(function () { + openQueryBuilderOverlay(); + }); + }, + readOnly: true + }, + { + name: 'insertSection', + bindKey: 'Alt-Shift-S', + exec: function () { + $scope.$apply(function () { + openSectionsOverlay(); + }); + }, + readOnly: true + }, + { + name: 'chooseMasterTemplate', + bindKey: 'Alt-Shift-T', + exec: function () { + $scope.$apply(function () { + openMasterTemplateOverlay(); + }); + }, + readOnly: true + } + ]); + // initial cursor placement + // Keep cursor in name field if we are create a new template + // else set the cursor at the bottom of the code editor + if (!$routeParams.create) { + $timeout(function () { + vm.editor.navigateFileEnd(); + vm.editor.focus(); + persistCurrentLocation(); + }); + } + //change on blur, focus + vm.editor.on('blur', persistCurrentLocation); + vm.editor.on('focus', persistCurrentLocation); + vm.editor.on('change', changeAceEditor); + } + }; + }; + vm.openPageFieldOverlay = openPageFieldOverlay; + vm.openDictionaryItemOverlay = openDictionaryItemOverlay; + vm.openQueryBuilderOverlay = openQueryBuilderOverlay; + vm.openMacroOverlay = openMacroOverlay; + vm.openInsertOverlay = openInsertOverlay; + vm.openSectionsOverlay = openSectionsOverlay; + vm.openPartialOverlay = openPartialOverlay; + vm.openMasterTemplateOverlay = openMasterTemplateOverlay; + vm.selectMasterTemplate = selectMasterTemplate; + vm.getMasterTemplateName = getMasterTemplateName; + vm.removeMasterTemplate = removeMasterTemplate; + function openInsertOverlay() { + vm.insertOverlay = { + view: 'insert', + allowedTypes: { + macro: true, + dictionary: true, + partial: true, + umbracoField: true + }, + hideSubmitButton: true, + show: true, + submit: function (model) { + switch (model.insert.type) { + case 'macro': + var macroObject = macroService.collectValueData(model.insert.selectedMacro, model.insert.macroParams, 'Mvc'); + insert(macroObject.syntax); + break; + case 'dictionary': + var code = templateHelper.getInsertDictionarySnippet(model.insert.node.name); + insert(code); + break; + case 'partial': + var code = templateHelper.getInsertPartialSnippet(model.insert.node.parentId, model.insert.node.name); + insert(code); + break; + case 'umbracoField': + insert(model.insert.umbracoField); + break; + } + vm.insertOverlay.show = false; + vm.insertOverlay = null; + }, + close: function (oldModel) { + // close the dialog + vm.insertOverlay.show = false; + vm.insertOverlay = null; + // focus editor + vm.editor.focus(); + } + }; + } + function openMacroOverlay() { + vm.macroPickerOverlay = { + view: 'macropicker', + dialogData: {}, + show: true, + title: localizationService.localize('template_insertMacro'), + submit: function (model) { + var macroObject = macroService.collectValueData(model.selectedMacro, model.macroParams, 'Mvc'); + insert(macroObject.syntax); + vm.macroPickerOverlay.show = false; + vm.macroPickerOverlay = null; + }, + close: function (oldModel) { + // close the dialog + vm.macroPickerOverlay.show = false; + vm.macroPickerOverlay = null; + // focus editor + vm.editor.focus(); + } + }; + } + function openPageFieldOverlay() { + vm.pageFieldOverlay = { + submitButtonLabel: 'Insert', + closeButtonlabel: 'Cancel', + view: 'insertfield', + show: true, + title: localizationService.localize('template_insertPageField'), + submit: function (model) { + insert(model.umbracoField); + vm.pageFieldOverlay.show = false; + vm.pageFieldOverlay = null; + }, + close: function (model) { + // close the dialog + vm.pageFieldOverlay.show = false; + vm.pageFieldOverlay = null; + // focus editor + vm.editor.focus(); + } + }; + } + function openDictionaryItemOverlay() { + vm.dictionaryItemOverlay = { + view: 'treepicker', + section: 'settings', + treeAlias: 'dictionary', + entityType: 'dictionary', + multiPicker: false, + show: true, + title: localizationService.localize('template_insertDictionaryItem'), + emptyStateMessage: localizationService.localize('emptyStates_emptyDictionaryTree'), + select: function (node) { + var code = templateHelper.getInsertDictionarySnippet(node.name); + insert(code); + vm.dictionaryItemOverlay.show = false; + vm.dictionaryItemOverlay = null; + }, + close: function (model) { + // close dialog + vm.dictionaryItemOverlay.show = false; + vm.dictionaryItemOverlay = null; + // focus editor + vm.editor.focus(); + } + }; + } + function openPartialOverlay() { + vm.partialItemOverlay = { + view: 'treepicker', + section: 'settings', + treeAlias: 'partialViews', + entityType: 'partialView', + multiPicker: false, + show: true, + title: localizationService.localize('template_insertPartialView'), + filter: function (i) { + if (i.name.indexOf('.cshtml') === -1 && i.name.indexOf('.vbhtml') === -1) { + return true; + } + }, + filterCssClass: 'not-allowed', + select: function (node) { + var code = templateHelper.getInsertPartialSnippet(node.parentId, node.name); + insert(code); + vm.partialItemOverlay.show = false; + vm.partialItemOverlay = null; + }, + close: function (model) { + // close dialog + vm.partialItemOverlay.show = false; + vm.partialItemOverlay = null; + // focus editor + vm.editor.focus(); + } + }; + } + function openQueryBuilderOverlay() { + vm.queryBuilderOverlay = { + view: 'querybuilder', + show: true, + title: localizationService.localize('template_queryBuilder'), + submit: function (model) { + var code = templateHelper.getQuerySnippet(model.result.queryExpression); + insert(code); + vm.queryBuilderOverlay.show = false; + vm.queryBuilderOverlay = null; + }, + close: function (model) { + // close dialog + vm.queryBuilderOverlay.show = false; + vm.queryBuilderOverlay = null; + // focus editor + vm.editor.focus(); + } + }; + } + function openSectionsOverlay() { + vm.sectionsOverlay = { + view: 'templatesections', + isMaster: vm.template.isMasterTemplate, + submitButtonLabel: 'Insert', + show: true, + submit: function (model) { + if (model.insertType === 'renderBody') { + var code = templateHelper.getRenderBodySnippet(); + insert(code); + } + if (model.insertType === 'renderSection') { + var code = templateHelper.getRenderSectionSnippet(model.renderSectionName, model.mandatoryRenderSection); + insert(code); + } + if (model.insertType === 'addSection') { + var code = templateHelper.getAddSectionSnippet(model.sectionName); + wrap(code); + } + vm.sectionsOverlay.show = false; + vm.sectionsOverlay = null; + }, + close: function (model) { + // close dialog + vm.sectionsOverlay.show = false; + vm.sectionsOverlay = null; + // focus editor + vm.editor.focus(); + } + }; + } + function openMasterTemplateOverlay() { + // make collection of available master templates + var availableMasterTemplates = []; + // filter out the current template and the selected master template + angular.forEach(vm.templates, function (template) { + if (template.alias !== vm.template.alias && template.alias !== vm.template.masterTemplateAlias) { + var templatePathArray = template.path.split(','); + // filter descendant templates of current template + if (templatePathArray.indexOf(String(vm.template.id)) === -1) { + availableMasterTemplates.push(template); + } + } + }); + vm.masterTemplateOverlay = { + view: 'itempicker', + title: localizationService.localize('template_mastertemplate'), + availableItems: availableMasterTemplates, + show: true, + submit: function (model) { + var template = model.selectedItem; + if (template && template.alias) { + vm.template.masterTemplateAlias = template.alias; + setLayout(template.alias + '.cshtml'); + } else { + vm.template.masterTemplateAlias = null; + setLayout(null); + } + vm.masterTemplateOverlay.show = false; + vm.masterTemplateOverlay = null; + }, + close: function (oldModel) { + // close dialog + vm.masterTemplateOverlay.show = false; + vm.masterTemplateOverlay = null; + // focus editor + vm.editor.focus(); + } + }; + } + function selectMasterTemplate(template) { + if (template && template.alias) { + vm.template.masterTemplateAlias = template.alias; + setLayout(template.alias + '.cshtml'); + } else { + vm.template.masterTemplateAlias = null; + setLayout(null); + } + } + function getMasterTemplateName(masterTemplateAlias, templates) { + if (masterTemplateAlias) { + var templateName = ''; + angular.forEach(templates, function (template) { + if (template.alias === masterTemplateAlias) { + templateName = template.name; + } + }); + return templateName; + } + } + function removeMasterTemplate() { + vm.template.masterTemplateAlias = null; + // call set layout with no paramters to set layout to null + setLayout(); + } + function setLayout(templatePath) { + var templateCode = vm.editor.getValue(); + var newValue = templatePath; + var layoutDefRegex = new RegExp('(@{[\\s\\S]*?Layout\\s*?=\\s*?)("[^"]*?"|null)(;[\\s\\S]*?})', 'gi'); + if (newValue !== undefined && newValue !== '') { + if (layoutDefRegex.test(templateCode)) { + // Declaration exists, so just update it + templateCode = templateCode.replace(layoutDefRegex, '$1"' + newValue + '"$3'); + } else { + // Declaration doesn't exist, so prepend to start of doc + //TODO: Maybe insert at the cursor position, rather than just at the top of the doc? + templateCode = '@{\n\tLayout = "' + newValue + '";\n}\n' + templateCode; + } + } else { + if (layoutDefRegex.test(templateCode)) { + // Declaration exists, so just update it + templateCode = templateCode.replace(layoutDefRegex, '$1null$3'); + } + } + vm.editor.setValue(templateCode); + vm.editor.clearSelection(); + vm.editor.navigateFileStart(); + vm.editor.focus(); + // set form state to $dirty + setFormState('dirty'); + } + function insert(str) { + vm.editor.focus(); + vm.editor.moveCursorToPosition(vm.currentPosition); + vm.editor.insert(str); + // set form state to $dirty + setFormState('dirty'); + } + function wrap(str) { + var selectedContent = vm.editor.session.getTextRange(vm.editor.getSelectionRange()); + str = str.replace('{0}', selectedContent); + vm.editor.insert(str); + vm.editor.focus(); + // set form state to $dirty + setFormState('dirty'); + } + function persistCurrentLocation() { + vm.currentPosition = vm.editor.getCursorPosition(); + } + function changeAceEditor() { + setFormState('dirty'); + } + function setFormState(state) { + // get the current form + var currentForm = angularHelper.getCurrentForm($scope); + // set state + if (state === 'dirty') { + currentForm.$setDirty(); + } else if (state === 'pristine') { + currentForm.$setPristine(); + } + } + vm.init(); + } + angular.module('umbraco').controller('Umbraco.Editors.Templates.EditController', TemplatesEditController); + }()); + (function () { + 'use strict'; + function UserGroupEditController($scope, $location, $routeParams, userGroupsResource, localizationService, contentEditingHelper) { + var vm = this; + var localizeSaving = localizationService.localize('general_saving'); + vm.page = {}; + vm.page.rootIcon = 'icon-folder'; + vm.userGroup = {}; + vm.labels = {}; + vm.goToPage = goToPage; + vm.openSectionPicker = openSectionPicker; + vm.openContentPicker = openContentPicker; + vm.openMediaPicker = openMediaPicker; + vm.openUserPicker = openUserPicker; + vm.removeSelectedItem = removeSelectedItem; + vm.clearStartNode = clearStartNode; + vm.save = save; + vm.openGranularPermissionsPicker = openGranularPermissionsPicker; + vm.setPermissionsForNode = setPermissionsForNode; + function init() { + vm.loading = true; + var labelKeys = [ + 'general_cancel', + 'defaultdialogs_selectContentStartNode', + 'defaultdialogs_selectMediaStartNode', + 'defaultdialogs_selectNode', + 'general_groups', + 'content_contentRoot', + 'media_mediaRoot' + ]; + localizationService.localizeMany(labelKeys).then(function (values) { + vm.labels.cancel = values[0]; + vm.labels.selectContentStartNode = values[1]; + vm.labels.selectMediaStartNode = values[2]; + vm.labels.selectNode = values[3]; + vm.labels.groups = values[4]; + vm.labels.contentRoot = values[5]; + vm.labels.mediaRoot = values[6]; + }); + localizationService.localize('general_add').then(function (name) { + vm.labels.add = name; + }); + localizationService.localize('user_noStartNode').then(function (name) { + vm.labels.noStartNode = name; + }); + if ($routeParams.create) { + // get user group scaffold + userGroupsResource.getUserGroupScaffold().then(function (userGroup) { + vm.userGroup = userGroup; + setSectionIcon(vm.userGroup.sections); + makeBreadcrumbs(); + vm.loading = false; + }); + } else { + // get user group + userGroupsResource.getUserGroup($routeParams.id).then(function (userGroup) { + vm.userGroup = userGroup; + formatGranularPermissionSelection(); + setSectionIcon(vm.userGroup.sections); + makeBreadcrumbs(); + vm.loading = false; + }); + } + } + function save() { + vm.page.saveButtonState = 'busy'; + contentEditingHelper.contentEditorPerformSave({ + statusMessage: localizeSaving, + saveMethod: userGroupsResource.saveUserGroup, + scope: $scope, + content: vm.userGroup, + // We do not redirect on failure for users - this is because it is not possible to actually save a user + // when server side validation fails - as opposed to content where we are capable of saving the content + // item if server side validation fails + redirectOnFailure: false, + rebindCallback: function (orignal, saved) { + } + }).then(function (saved) { + vm.userGroup = saved; + formatGranularPermissionSelection(); + setSectionIcon(vm.userGroup.sections); + makeBreadcrumbs(); + vm.page.saveButtonState = 'success'; + }, function (err) { + vm.page.saveButtonState = 'error'; + }); + } + function goToPage(ancestor) { + $location.path(ancestor.path).search('subview', ancestor.subView); + } + function openSectionPicker() { + vm.sectionPicker = { + view: 'sectionpicker', + selection: vm.userGroup.sections, + closeButtonLabel: vm.labels.cancel, + show: true, + submit: function (model) { + vm.sectionPicker.show = false; + vm.sectionPicker = null; + }, + close: function (oldModel) { + if (oldModel.selection) { + vm.userGroup.sections = oldModel.selection; + } + vm.sectionPicker.show = false; + vm.sectionPicker = null; + } + }; + } + function openContentPicker() { + vm.contentPicker = { + title: vm.labels.selectContentStartNode, + view: 'contentpicker', + hideSubmitButton: true, + hideHeader: false, + show: true, + submit: function (model) { + if (model.selection) { + vm.userGroup.contentStartNode = model.selection[0]; + if (vm.userGroup.contentStartNode.id === '-1') { + vm.userGroup.contentStartNode.name = vm.labels.contentRoot; + vm.userGroup.contentStartNode.icon = 'icon-folder'; + } + } + vm.contentPicker.show = false; + vm.contentPicker = null; + }, + close: function (oldModel) { + vm.contentPicker.show = false; + vm.contentPicker = null; + } + }; + } + function openMediaPicker() { + vm.contentPicker = { + title: vm.labels.selectMediaStartNode, + view: 'treepicker', + section: 'media', + treeAlias: 'media', + entityType: 'media', + hideSubmitButton: true, + hideHeader: false, + show: true, + submit: function (model) { + if (model.selection) { + vm.userGroup.mediaStartNode = model.selection[0]; + if (vm.userGroup.mediaStartNode.id === '-1') { + vm.userGroup.mediaStartNode.name = vm.labels.mediaRoot; + vm.userGroup.mediaStartNode.icon = 'icon-folder'; + } + } + vm.contentPicker.show = false; + vm.contentPicker = null; + }, + close: function (oldModel) { + vm.contentPicker.show = false; + vm.contentPicker = null; + } + }; + } + function openUserPicker() { + vm.userPicker = { + view: 'userpicker', + selection: vm.userGroup.users, + show: true, + submit: function (model) { + vm.userPicker.show = false; + vm.userPicker = null; + }, + close: function (oldModel) { + vm.userPicker.show = false; + vm.userPicker = null; + } + }; + } + /** + * The granular permissions structure gets returned from the server in the dictionary format with each key being the permission category + * however the list to display the permissions isn't via the dictionary way so we need to format it + */ + function formatGranularPermissionSelection() { + angular.forEach(vm.userGroup.assignedPermissions, function (node) { + formatGranularPermissionSelectionForNode(node); + }); + } + function formatGranularPermissionSelectionForNode(node) { + //the dictionary is assigned via node.permissions we will reformat to node.allowedPermissions + node.allowedPermissions = []; + angular.forEach(node.permissions, function (permissions, key) { + angular.forEach(permissions, function (p) { + if (p.checked) { + node.allowedPermissions.push(p); + } + }); + }); + } + function openGranularPermissionsPicker() { + vm.contentPicker = { + title: vm.labels.selectNode, + view: 'contentpicker', + hideSubmitButton: true, + show: true, + submit: function (model) { + if (model.selection) { + var node = model.selection[0]; + //check if this is already in our selection + var found = _.find(vm.userGroup.assignedPermissions, function (i) { + return i.id === node.id; + }); + node = found ? found : node; + setPermissionsForNode(node); + } + }, + close: function (oldModel) { + vm.contentPicker.show = false; + vm.contentPicker = null; + } + }; + } + function setPermissionsForNode(node) { + //clone the current defaults to pass to the model + if (!node.permissions) { + node.permissions = angular.copy(vm.userGroup.defaultPermissions); + } + vm.nodePermissions = { + view: 'nodepermissions', + node: node, + show: true, + submit: function (model) { + if (model && model.node && model.node.permissions) { + formatGranularPermissionSelectionForNode(node); + if (!vm.userGroup.assignedPermissions) { + vm.userGroup.assignedPermissions = []; + } + //check if this is already in our selection + var found = _.find(vm.userGroup.assignedPermissions, function (i) { + return i.id === node.id; + }); + if (!found) { + vm.userGroup.assignedPermissions.push(node); + } + } + // close node permisssions overlay + vm.nodePermissions.show = false; + vm.nodePermissions = null; + // close content picker overlay + if (vm.contentPicker) { + vm.contentPicker.show = false; + vm.contentPicker = null; + } + }, + close: function (oldModel) { + vm.nodePermissions.show = false; + vm.nodePermissions = null; + } + }; + } + function removeSelectedItem(index, selection) { + if (selection && selection.length > 0) { + selection.splice(index, 1); + } + } + function clearStartNode(type) { + if (type === 'content') { + vm.userGroup.contentStartNode = null; + } else if (type === 'media') { + vm.userGroup.mediaStartNode = null; + } + } + function makeBreadcrumbs() { + vm.breadcrumbs = [ + { + 'name': vm.labels.groups, + 'path': '/users/users/overview', + 'subView': 'groups' + }, + { 'name': vm.userGroup.name } + ]; + } + function setSectionIcon(sections) { + angular.forEach(sections, function (section) { + section.icon = 'icon-section ' + section.cssclass; + }); + } + init(); + } + angular.module('umbraco').controller('Umbraco.Editors.Users.GroupController', UserGroupEditController); + }()); + (function () { + 'use strict'; + function UsersOverviewController($scope, $location, $timeout, navigationService, localizationService) { + var vm = this; + var usersUri = $location.search().subview; + if (!usersUri) { + $location.search('subview', 'users'); + //exit after this, we don't want to initialize anything further since this + //is going to change the route + return; + } + //note on the below, we dont assign a view unless it's the right route since if we did that it will load in that controller + //for the view which is unecessary and will cause extra overhead/requests to occur + vm.page = {}; + vm.page.name = localizationService.localize('user_userManagement'); + vm.page.navigation = [ + { + 'name': localizationService.localize('sections_users'), + 'icon': 'icon-user', + 'action': function () { + $location.search('subview', 'users'); + }, + 'view': !usersUri || usersUri === 'users' ? 'views/users/views/users/users.html' : null, + 'active': !usersUri || usersUri === 'users' + }, + { + 'name': localizationService.localize('general_groups'), + 'icon': 'icon-users', + 'action': function () { + $location.search('subview', 'groups'); + }, + 'view': usersUri === 'groups' ? 'views/users/views/groups/groups.html' : null, + 'active': usersUri === 'groups' + } + ]; + function init() { + $timeout(function () { + navigationService.syncTree({ + tree: 'users', + path: '-1' + }); + }); + } + init(); + } + angular.module('umbraco').controller('Umbraco.Editors.Users.OverviewController', UsersOverviewController); + }()); + (function () { + 'use strict'; + function UserEditController($scope, eventsService, $q, $timeout, $location, $routeParams, formHelper, usersResource, userService, contentEditingHelper, localizationService, notificationsService, mediaHelper, Upload, umbRequestHelper, usersHelper, authResource, dateHelper) { + var vm = this; + vm.page = {}; + vm.page.rootIcon = 'icon-folder'; + vm.user = { changePassword: null }; + vm.breadcrumbs = []; + vm.avatarFile = {}; + vm.labels = {}; + vm.maxFileSize = Umbraco.Sys.ServerVariables.umbracoSettings.maxFileSize + 'KB'; + vm.acceptedFileTypes = mediaHelper.formatFileTypes(Umbraco.Sys.ServerVariables.umbracoSettings.imageFileTypes); + vm.usernameIsEmail = Umbraco.Sys.ServerVariables.umbracoSettings.usernameIsEmail; + //create the initial model for change password + vm.changePasswordModel = { + config: {}, + isChanging: false + }; + vm.goToPage = goToPage; + vm.openUserGroupPicker = openUserGroupPicker; + vm.openContentPicker = openContentPicker; + vm.openMediaPicker = openMediaPicker; + vm.removeSelectedItem = removeSelectedItem; + vm.disableUser = disableUser; + vm.enableUser = enableUser; + vm.unlockUser = unlockUser; + vm.resendInvite = resendInvite; + vm.deleteNonLoggedInUser = deleteNonLoggedInUser; + vm.changeAvatar = changeAvatar; + vm.clearAvatar = clearAvatar; + vm.save = save; + vm.toggleChangePassword = toggleChangePassword; + function init() { + vm.loading = true; + var labelKeys = [ + 'general_saving', + 'general_cancel', + 'defaultdialogs_selectContentStartNode', + 'defaultdialogs_selectMediaStartNode', + 'sections_users', + 'content_contentRoot', + 'media_mediaRoot', + 'user_noStartNodes', + 'user_defaultInvitationMessage', + 'user_deleteUserConfirmation' + ]; + localizationService.localizeMany(labelKeys).then(function (values) { + vm.labels.saving = values[0]; + vm.labels.cancel = values[1]; + vm.labels.selectContentStartNode = values[2]; + vm.labels.selectMediaStartNode = values[3]; + vm.labels.users = values[4]; + vm.labels.contentRoot = values[5]; + vm.labels.mediaRoot = values[6]; + vm.labels.noStartNodes = values[7]; + vm.labels.defaultInvitationMessage = values[8]; + vm.labels.deleteUserConfirmation = values[9]; + }); + // get user + usersResource.getUser($routeParams.id).then(function (user) { + vm.user = user; + makeBreadcrumbs(vm.user); + setUserDisplayState(); + formatDatesToLocal(vm.user); + vm.usernameIsEmail = Umbraco.Sys.ServerVariables.umbracoSettings.usernameIsEmail && user.email === user.username; + //go get the config for the membership provider and add it to the model + authResource.getMembershipProviderConfig().then(function (data) { + vm.changePasswordModel.config = data; + //the user has a password if they are not states: Invited, NoCredentials + vm.changePasswordModel.config.hasPassword = vm.user.userState !== 3 && vm.user.userState !== 4; + vm.changePasswordModel.config.disableToggle = true; + //this is only relavent for membership providers now (it's basically obsolete) + vm.changePasswordModel.config.enableReset = false; + //in the ASP.NET Identity world, this config option will allow an admin user to change another user's password + //if the user has access to the user section. So if this editor is being access, the user of course has access to this section. + //the authorization check is also done on the server side when submitted. + vm.changePasswordModel.config.allowManuallyChangingPassword = !vm.user.isCurrentUser; + vm.loading = false; + }); + }); + } + function getLocalDate(date, culture, format) { + if (date) { + var dateVal; + var serverOffset = Umbraco.Sys.ServerVariables.application.serverTimeOffset; + var localOffset = new Date().getTimezoneOffset(); + var serverTimeNeedsOffsetting = -serverOffset !== localOffset; + if (serverTimeNeedsOffsetting) { + dateVal = dateHelper.convertToLocalMomentTime(date, serverOffset); + } else { + dateVal = moment(date, 'YYYY-MM-DD HH:mm:ss'); + } + return dateVal.locale(culture).format(format); + } + } + function toggleChangePassword() { + vm.changePasswordModel.isChanging = !vm.changePasswordModel.isChanging; + //reset it + vm.user.changePassword = null; + } + function save() { + if (formHelper.submitForm({ + scope: $scope, + statusMessage: vm.labels.saving + })) { + //anytime a user is changing another user's password, we are in effect resetting it so we need to set that flag here + if (vm.user.changePassword) { + vm.user.changePassword.reset = !vm.user.changePassword.oldPassword && !vm.user.isCurrentUser; + } + vm.page.saveButtonState = 'busy'; + vm.user.resetPasswordValue = null; + //save current nav to be restored later so that the tabs dont change + var currentNav = vm.user.navigation; + usersResource.saveUser(vm.user).then(function (saved) { + //if the user saved, then try to execute all extended save options + extendedSave(saved).then(function (result) { + //if all is good, then reset the form + formHelper.resetForm({ + scope: $scope, + notifications: saved.notifications + }); + }, function (err) { + //otherwise show the notifications for the user being saved + formHelper.showNotifications(saved); + }); + vm.user = _.omit(saved, 'navigation'); + //restore + vm.user.navigation = currentNav; + setUserDisplayState(); + formatDatesToLocal(vm.user); + vm.changePasswordModel.isChanging = false; + //the user has a password if they are not states: Invited, NoCredentials + vm.changePasswordModel.config.hasPassword = vm.user.userState !== 3 && vm.user.userState !== 4; + vm.page.saveButtonState = 'success'; + }, function (err) { + contentEditingHelper.handleSaveError({ + redirectOnFailure: false, + err: err + }); + //show any notifications + if (err.data) { + formHelper.showNotifications(err.data); + } + vm.page.saveButtonState = 'error'; + }); + } + } + /** + * Used to emit the save event and await any async operations being performed by editor extensions + * @param {any} savedUser + */ + function extendedSave(savedUser) { + //used to track any promises added by the event handlers to be awaited + var promises = []; + var args = { + //getPromise: getPromise, + user: savedUser, + //a promise can be added by the event handler if the handler needs an async operation to be awaited + addPromise: function (p) { + promises.push(p); + } + }; + //emit the event + eventsService.emit('editors.user.editController.save', args); + //await all promises to complete + var resultPromise = $q.all(promises); + return resultPromise; + } + function goToPage(ancestor) { + $location.path(ancestor.path).search('subview', ancestor.subView); + } + function openUserGroupPicker() { + vm.userGroupPicker = { + view: 'usergrouppicker', + selection: vm.user.userGroups, + closeButtonLabel: vm.labels.cancel, + show: true, + submit: function (model) { + // apply changes + if (model.selection) { + vm.user.userGroups = model.selection; + } + vm.userGroupPicker.show = false; + vm.userGroupPicker = null; + }, + close: function (oldModel) { + // rollback on close + if (oldModel.selection) { + vm.user.userGroups = oldModel.selection; + } + vm.userGroupPicker.show = false; + vm.userGroupPicker = null; + } + }; + } + function openContentPicker() { + vm.contentPicker = { + title: vm.labels.selectContentStartNode, + view: 'contentpicker', + multiPicker: true, + selection: vm.user.startContentIds, + hideHeader: false, + show: true, + submit: function (model) { + // select items + if (model.selection) { + angular.forEach(model.selection, function (item) { + if (item.id === '-1') { + item.name = vm.labels.contentRoot; + item.icon = 'icon-folder'; + } + multiSelectItem(item, vm.user.startContentIds); + }); + } + // close overlay + vm.contentPicker.show = false; + vm.contentPicker = null; + }, + close: function (oldModel) { + // close overlay + vm.contentPicker.show = false; + vm.contentPicker = null; + } + }; + } + function openMediaPicker() { + vm.mediaPicker = { + title: vm.labels.selectMediaStartNode, + view: 'treepicker', + section: 'media', + treeAlias: 'media', + entityType: 'media', + multiPicker: true, + hideHeader: false, + show: true, + submit: function (model) { + // select items + if (model.selection) { + angular.forEach(model.selection, function (item) { + if (item.id === '-1') { + item.name = vm.labels.mediaRoot; + item.icon = 'icon-folder'; + } + multiSelectItem(item, vm.user.startMediaIds); + }); + } + // close overlay + vm.mediaPicker.show = false; + vm.mediaPicker = null; + }, + close: function (oldModel) { + // close overlay + vm.mediaPicker.show = false; + vm.mediaPicker = null; + } + }; + } + function multiSelectItem(item, selection) { + var found = false; + // check if item is already in the selected list + if (selection.length > 0) { + angular.forEach(selection, function (selectedItem) { + if (selectedItem.udi === item.udi) { + found = true; + } + }); + } + // only add the selected item if it is not already selected + if (!found) { + selection.push(item); + } + } + function removeSelectedItem(index, selection) { + selection.splice(index, 1); + } + function disableUser() { + vm.disableUserButtonState = 'busy'; + usersResource.disableUsers([vm.user.id]).then(function (data) { + vm.user.userState = 1; + setUserDisplayState(); + vm.disableUserButtonState = 'success'; + formHelper.showNotifications(data); + }, function (error) { + vm.disableUserButtonState = 'error'; + formHelper.showNotifications(error.data); + }); + } + function enableUser() { + vm.enableUserButtonState = 'busy'; + usersResource.enableUsers([vm.user.id]).then(function (data) { + vm.user.userState = 0; + setUserDisplayState(); + vm.enableUserButtonState = 'success'; + formHelper.showNotifications(data); + }, function (error) { + vm.enableUserButtonState = 'error'; + formHelper.showNotifications(error.data); + }); + } + function unlockUser() { + vm.unlockUserButtonState = 'busy'; + usersResource.unlockUsers([vm.user.id]).then(function (data) { + vm.user.userState = 0; + vm.user.failedPasswordAttempts = 0; + setUserDisplayState(); + vm.unlockUserButtonState = 'success'; + formHelper.showNotifications(data); + }, function (error) { + vm.unlockUserButtonState = 'error'; + formHelper.showNotifications(error.data); + }); + } + function resendInvite() { + vm.resendInviteButtonState = 'busy'; + if (vm.resendInviteMessage) { + vm.user.message = vm.resendInviteMessage; + } else { + vm.user.message = vm.labels.defaultInvitationMessage; + } + usersResource.inviteUser(vm.user).then(function (data) { + vm.resendInviteButtonState = 'success'; + vm.resendInviteMessage = ''; + formHelper.showNotifications(data); + }, function (error) { + vm.resendInviteButtonState = 'error'; + formHelper.showNotifications(error.data); + }); + } + function deleteNonLoggedInUser() { + vm.deleteNotLoggedInUserButtonState = 'busy'; + var confirmationMessage = vm.labels.deleteUserConfirmation; + if (!confirm(confirmationMessage)) { + vm.deleteNotLoggedInUserButtonState = 'danger'; + return; + } + usersResource.deleteNonLoggedInUser(vm.user.id).then(function (data) { + formHelper.showNotifications(data); + goToPage(vm.breadcrumbs[0]); + }, function (error) { + vm.deleteNotLoggedInUserButtonState = 'error'; + formHelper.showNotifications(error.data); + }); + } + function clearAvatar() { + // get user + usersResource.clearAvatar(vm.user.id).then(function (data) { + vm.user.avatars = data; + }); + } + function changeAvatar(files, event) { + if (files && files.length > 0) { + upload(files[0]); + } + } + ; + function upload(file) { + vm.avatarFile.uploadProgress = 0; + Upload.upload({ + url: umbRequestHelper.getApiUrl('userApiBaseUrl', 'PostSetAvatar', { id: vm.user.id }), + fields: {}, + file: file + }).progress(function (evt) { + if (vm.avatarFile.uploadStatus !== 'done' && vm.avatarFile.uploadStatus !== 'error') { + // set uploading status on file + vm.avatarFile.uploadStatus = 'uploading'; + // calculate progress in percentage + var progressPercentage = parseInt(100 * evt.loaded / evt.total, 10); + // set percentage property on file + vm.avatarFile.uploadProgress = progressPercentage; + } + }).success(function (data, status, headers, config) { + // set done status on file + vm.avatarFile.uploadStatus = 'done'; + vm.avatarFile.uploadProgress = 100; + vm.user.avatars = data; + }).error(function (evt, status, headers, config) { + // set status done + vm.avatarFile.uploadStatus = 'error'; + // If file not found, server will return a 404 and display this message + if (status === 404) { + vm.avatarFile.serverErrorMessage = 'File not found'; + } else if (status == 400) { + //it's a validation error + vm.avatarFile.serverErrorMessage = evt.message; + } else { + //it's an unhandled error + //if the service returns a detailed error + if (evt.InnerException) { + vm.avatarFile.serverErrorMessage = evt.InnerException.ExceptionMessage; + //Check if its the common "too large file" exception + if (evt.InnerException.StackTrace && evt.InnerException.StackTrace.indexOf('ValidateRequestEntityLength') > 0) { + vm.avatarFile.serverErrorMessage = 'File too large to upload'; + } + } else if (evt.Message) { + vm.avatarFile.serverErrorMessage = evt.Message; + } + } + }); + } + function makeBreadcrumbs() { + vm.breadcrumbs = [ + { + 'name': vm.labels.users, + 'path': '/users/users/overview', + 'subView': 'users' + }, + { 'name': vm.user.name } + ]; + } + function setUserDisplayState() { + vm.user.userDisplayState = usersHelper.getUserStateFromValue(vm.user.userState); + } + function formatDatesToLocal(user) { + // get current backoffice user and format dates + userService.getCurrentUser().then(function (currentUser) { + user.formattedLastLogin = getLocalDate(user.lastLoginDate, currentUser.locale, 'LLL'); + user.formattedLastLockoutDate = getLocalDate(user.lastLockoutDate, currentUser.locale, 'LLL'); + user.formattedCreateDate = getLocalDate(user.createDate, currentUser.locale, 'LLL'); + user.formattedUpdateDate = getLocalDate(user.updateDate, currentUser.locale, 'LLL'); + user.formattedLastPasswordChangeDate = getLocalDate(user.lastPasswordChangeDate, currentUser.locale, 'LLL'); + }); + } + init(); + } + angular.module('umbraco').controller('Umbraco.Editors.Users.UserController', UserEditController); + }()); + (function () { + 'use strict'; + function UserGroupsController($scope, $timeout, $location, userService, userGroupsResource, formHelper, localizationService) { + var vm = this; + vm.userGroups = []; + vm.selection = []; + vm.createUserGroup = createUserGroup; + vm.clickUserGroup = clickUserGroup; + vm.clearSelection = clearSelection; + vm.selectUserGroup = selectUserGroup; + vm.deleteUserGroups = deleteUserGroups; + var currentUser = null; + function onInit() { + vm.loading = true; + userService.getCurrentUser().then(function (user) { + currentUser = user; + // Get usergroups + userGroupsResource.getUserGroups({ onlyCurrentUserGroups: false }).then(function (userGroups) { + // only allow editing and selection if user is member of the group or admin + vm.userGroups = _.map(userGroups, function (ug) { + return { + group: ug, + hasAccess: user.userGroups.indexOf(ug.alias) !== -1 || user.userGroups.indexOf('admin') !== -1 + }; + }); + vm.loading = false; + }); + }); + } + function createUserGroup() { + // clear all query params + $location.search({}); + // go to create user group + $location.path('users/users/group/-1').search('create', 'true'); + ; + } + function clickUserGroup(userGroup) { + // only allow editing if user is member of the group or admin + if (currentUser.userGroups.indexOf(userGroup.group.alias) === -1 && currentUser.userGroups.indexOf('admin') === -1) { + return; + } + if (vm.selection.length > 0) { + selectUserGroup(userGroup, vm.selection); + } else { + goToUserGroup(userGroup.group.id); + } + } + function selectUserGroup(userGroup, selection, event) { + // Only allow selection if user is member of the group or admin + if (currentUser.userGroups.indexOf(userGroup.group.alias) === -1 && currentUser.userGroups.indexOf('admin') === -1) { + return; + } + // Disallow selection of the admin/translators group, the checkbox is not visible in the UI, but clicking(and thus selecting) is still possible. + // Currently selection can only be used for deleting, and the Controller will also disallow deleting the admin group. + if (userGroup.group.alias === 'admin' || userGroup.group.alias === 'translator') + return; + if (userGroup.selected) { + var index = selection.indexOf(userGroup.group.id); + selection.splice(index, 1); + userGroup.selected = false; + } else { + userGroup.selected = true; + vm.selection.push(userGroup.group.id); + } + if (event) { + event.preventDefault(); + event.stopPropagation(); + } + } + function deleteUserGroups() { + if (vm.selection.length > 0) { + localizationService.localize('defaultdialogs_confirmdelete').then(function (value) { + var confirmResponse = confirm(value); + if (confirmResponse === true) { + userGroupsResource.deleteUserGroups(vm.selection).then(function (data) { + clearSelection(); + onInit(); + formHelper.showNotifications(data); + }, function (error) { + formHelper.showNotifications(error.data); + }); + } + }); + } + } + function clearSelection() { + angular.forEach(vm.userGroups, function (userGroup) { + userGroup.selected = false; + }); + vm.selection = []; + } + function goToUserGroup(userGroupId) { + $location.path('users/users/group/' + userGroupId).search('create', null); + } + onInit(); + } + angular.module('umbraco').controller('Umbraco.Editors.Users.GroupsController', UserGroupsController); + }()); + (function () { + 'use strict'; + function UsersController($scope, $timeout, $location, $routeParams, usersResource, userGroupsResource, userService, localizationService, contentEditingHelper, usersHelper, formHelper, notificationsService, dateHelper) { + var vm = this; + var localizeSaving = localizationService.localize('general_saving'); + vm.page = {}; + vm.users = []; + vm.userGroups = []; + vm.userStates = []; + vm.selection = []; + vm.newUser = {}; + vm.usersOptions = {}; + vm.userSortData = [ + { + label: 'Name (A-Z)', + key: 'Name', + direction: 'Ascending' + }, + { + label: 'Name (Z-A)', + key: 'Name', + direction: 'Descending' + }, + { + label: 'Newest', + key: 'CreateDate', + direction: 'Descending' + }, + { + label: 'Oldest', + key: 'CreateDate', + direction: 'Ascending' + }, + { + label: 'Last login', + key: 'LastLoginDate', + direction: 'Descending' + } + ]; + angular.forEach(vm.userSortData, function (userSortData) { + var key = 'user_sort' + userSortData.key + userSortData.direction; + localizationService.localize(key).then(function (value) { + var reg = /^\[[\S\s]*]$/g; + var result = reg.test(value); + if (result === false) { + // Only translate if key exists + userSortData.label = value; + } + }); + }); + vm.userStatesFilter = []; + vm.newUser.userGroups = []; + vm.usersViewState = 'overview'; + vm.selectedBulkUserGroups = []; + vm.usernameIsEmail = Umbraco.Sys.ServerVariables.umbracoSettings.usernameIsEmail; + vm.allowDisableUser = true; + vm.allowEnableUser = true; + vm.allowUnlockUser = true; + vm.allowSetUserGroup = true; + vm.layouts = [ + { + 'icon': 'icon-thumbnails-small', + 'path': '1', + 'selected': true + }, + { + 'icon': 'icon-list', + 'path': '2', + 'selected': true + } + ]; + vm.activeLayout = { + 'icon': 'icon-thumbnails-small', + 'path': '1', + 'selected': true + }; + //don't show the invite button if no email is configured + if (Umbraco.Sys.ServerVariables.umbracoSettings.showUserInvite) { + vm.defaultButton = { + labelKey: 'user_inviteUser', + handler: function () { + vm.setUsersViewState('inviteUser'); + } + }; + vm.subButtons = [{ + labelKey: 'user_createUser', + handler: function () { + vm.setUsersViewState('createUser'); + } + }]; + } else { + vm.defaultButton = { + labelKey: 'user_createUser', + handler: function () { + vm.setUsersViewState('createUser'); + } + }; + } + vm.toggleFilter = toggleFilter; + vm.setUsersViewState = setUsersViewState; + vm.selectLayout = selectLayout; + vm.selectUser = selectUser; + vm.clearSelection = clearSelection; + vm.clickUser = clickUser; + vm.disableUsers = disableUsers; + vm.enableUsers = enableUsers; + vm.unlockUsers = unlockUsers; + vm.openBulkUserGroupPicker = openBulkUserGroupPicker; + vm.openUserGroupPicker = openUserGroupPicker; + vm.removeSelectedUserGroup = removeSelectedUserGroup; + vm.selectAll = selectAll; + vm.areAllSelected = areAllSelected; + vm.searchUsers = searchUsers; + vm.getFilterName = getFilterName; + vm.setUserStatesFilter = setUserStatesFilter; + vm.setUserGroupFilter = setUserGroupFilter; + vm.setOrderByFilter = setOrderByFilter; + vm.changePageNumber = changePageNumber; + vm.createUser = createUser; + vm.inviteUser = inviteUser; + vm.getSortLabel = getSortLabel; + vm.toggleNewUserPassword = toggleNewUserPassword; + vm.copySuccess = copySuccess; + vm.copyError = copyError; + vm.goToUser = goToUser; + function init() { + vm.usersOptions.orderBy = 'Name'; + vm.usersOptions.orderDirection = 'Ascending'; + if ($routeParams.create) { + setUsersViewState('createUser'); + } else if ($routeParams.invite) { + setUsersViewState('inviteUser'); + } + // Get users + getUsers(); + // Get user groups + userGroupsResource.getUserGroups({ onlyCurrentUserGroups: false }).then(function (userGroups) { + vm.userGroups = userGroups; + }); + } + function getSortLabel(sortKey, sortDirection) { + var found = _.find(vm.userSortData, function (i) { + return i.key === sortKey && i.direction === sortDirection; + }); + return found ? found.label : sortKey; + } + function toggleFilter(type) { + // hack: on-outside-click prevents us from closing the dropdown when clicking on another link + // so I had to do this manually + switch (type) { + case 'state': + vm.page.showStatusFilter = !vm.page.showStatusFilter; + vm.page.showGroupFilter = false; + vm.page.showOrderByFilter = false; + break; + case 'group': + vm.page.showGroupFilter = !vm.page.showGroupFilter; + vm.page.showStatusFilter = false; + vm.page.showOrderByFilter = false; + break; + case 'orderBy': + vm.page.showOrderByFilter = !vm.page.showOrderByFilter; + vm.page.showStatusFilter = false; + vm.page.showGroupFilter = false; + break; + } + } + function setUsersViewState(state) { + if (state === 'createUser') { + clearAddUserForm(); + $location.search('create', 'true'); + $location.search('invite', null); + } else if (state === 'inviteUser') { + $location.search('create', null); + $location.search('invite', 'true'); + } else if (state === 'overview') { + $location.search('create', null); + $location.search('invite', null); + } + vm.usersViewState = state; + } + function selectLayout(selectedLayout) { + angular.forEach(vm.layouts, function (layout) { + layout.active = false; + }); + selectedLayout.active = true; + vm.activeLayout = selectedLayout; + } + function selectUser(user, selection, event) { + // prevent the current user to be selected + if (!user.isCurrentUser) { + if (user.selected) { + var index = selection.indexOf(user.id); + selection.splice(index, 1); + user.selected = false; + } else { + user.selected = true; + vm.selection.push(user.id); + } + setBulkActions(vm.users); + if (event) { + event.preventDefault(); + event.stopPropagation(); + } + } + } + function clearSelection() { + angular.forEach(vm.users, function (user) { + user.selected = false; + }); + vm.selection = []; + } + function clickUser(user) { + if (vm.selection.length > 0) { + selectUser(user, vm.selection); + } else { + goToUser(user.id); + } + } + function disableUsers() { + vm.disableUserButtonState = 'busy'; + usersResource.disableUsers(vm.selection).then(function (data) { + // update userState + angular.forEach(vm.selection, function (userId) { + var user = getUserFromArrayById(userId, vm.users); + if (user) { + user.userState = 1; + } + }); + // show the correct badges + setUserDisplayState(vm.users); + formHelper.showNotifications(data); + vm.disableUserButtonState = 'init'; + clearSelection(); + }, function (error) { + vm.disableUserButtonState = 'error'; + formHelper.showNotifications(error.data); + }); + } + function enableUsers() { + vm.enableUserButtonState = 'busy'; + usersResource.enableUsers(vm.selection).then(function (data) { + // update userState + angular.forEach(vm.selection, function (userId) { + var user = getUserFromArrayById(userId, vm.users); + if (user) { + user.userState = 0; + } + }); + // show the correct badges + setUserDisplayState(vm.users); + // show notification + formHelper.showNotifications(data); + vm.enableUserButtonState = 'init'; + clearSelection(); + }, function (error) { + vm.enableUserButtonState = 'error'; + formHelper.showNotifications(error.data); + }); + } + function unlockUsers() { + vm.unlockUserButtonState = 'busy'; + usersResource.unlockUsers(vm.selection).then(function (data) { + // update userState + angular.forEach(vm.selection, function (userId) { + var user = getUserFromArrayById(userId, vm.users); + if (user) { + user.userState = 0; + } + }); + // show the correct badges + setUserDisplayState(vm.users); + // show notification + formHelper.showNotifications(data); + vm.unlockUserButtonState = 'init'; + clearSelection(); + }, function (error) { + vm.unlockUserButtonState = 'error'; + formHelper.showNotifications(error.data); + }); + } + function getUserFromArrayById(userId, users) { + return _.find(users, function (u) { + return u.id === userId; + }); + } + function openBulkUserGroupPicker(event) { + var firstSelectedUser = getUserFromArrayById(vm.selection[0], vm.users); + vm.selectedBulkUserGroups = _.clone(firstSelectedUser.userGroups); + vm.userGroupPicker = { + title: localizationService.localize('user_selectUserGroups'), + view: 'usergrouppicker', + selection: vm.selectedBulkUserGroups, + closeButtonLabel: localizationService.localize('general_cancel'), + show: true, + submit: function (model) { + usersResource.setUserGroupsOnUsers(model.selection, vm.selection).then(function (data) { + // sorting to ensure they show up in right order when updating the UI + vm.selectedBulkUserGroups.sort(function (a, b) { + return a.alias > b.alias ? 1 : a.alias < b.alias ? -1 : 0; + }); + // apply changes to UI + _.each(vm.selection, function (userId) { + var user = getUserFromArrayById(userId, vm.users); + user.userGroups = vm.selectedBulkUserGroups; + }); + vm.selectedBulkUserGroups = []; + vm.userGroupPicker.show = false; + vm.userGroupPicker = null; + formHelper.showNotifications(data); + clearSelection(); + }, function (error) { + formHelper.showNotifications(error.data); + }); + }, + close: function (oldModel) { + vm.selectedBulkUserGroups = []; + vm.userGroupPicker.show = false; + vm.userGroupPicker = null; + } + }; + } + function openUserGroupPicker(event) { + vm.userGroupPicker = { + title: localizationService.localize('user_selectUserGroups'), + view: 'usergrouppicker', + selection: vm.newUser.userGroups, + closeButtonLabel: localizationService.localize('general_cancel'), + show: true, + submit: function (model) { + // apply changes + if (model.selection) { + vm.newUser.userGroups = model.selection; + } + vm.userGroupPicker.show = false; + vm.userGroupPicker = null; + }, + close: function (oldModel) { + // rollback on close + if (oldModel.selection) { + vm.newUser.userGroups = oldModel.selection; + } + vm.userGroupPicker.show = false; + vm.userGroupPicker = null; + } + }; + } + function removeSelectedUserGroup(index, selection) { + selection.splice(index, 1); + } + function selectAll() { + if (areAllSelected()) { + vm.selection = []; + angular.forEach(vm.users, function (user) { + user.selected = false; + }); + } else { + // clear selection so we don't add the same user twice + vm.selection = []; + // select all users + angular.forEach(vm.users, function (user) { + // prevent the current user to be selected + if (!user.isCurrentUser) { + user.selected = true; + vm.selection.push(user.id); + } + }); + } + } + function areAllSelected() { + // we need to check if the current user is part of the selection and + // subtract the user from the total selection to find out if all users are selected + var includesCurrentUser = vm.users.some(function (user) { + return user.isCurrentUser === true; + }); + if (includesCurrentUser) { + if (vm.selection.length === vm.users.length - 1) { + return true; + } + } else { + if (vm.selection.length === vm.users.length) { + return true; + } + } + } + var search = _.debounce(function () { + $scope.$apply(function () { + getUsers(); + }); + }, 500); + function searchUsers() { + search(); + } + function getFilterName(array) { + var name = 'All'; + var found = false; + angular.forEach(array, function (item) { + if (item.selected) { + if (!found) { + name = item.name; + found = true; + } else { + name = name + ', ' + item.name; + } + } + }); + return name; + } + function setUserStatesFilter(userState) { + if (!vm.usersOptions.userStates) { + vm.usersOptions.userStates = []; + } + //If the selection is "ALL" then we need to unselect everything else since this is an 'odd' filter + if (userState.key === 'All') { + angular.forEach(vm.userStatesFilter, function (i) { + i.selected = false; + }); + //we can't unselect All + userState.selected = true; + //reset the selection passed to the server + vm.usersOptions.userStates = []; + } else { + angular.forEach(vm.userStatesFilter, function (i) { + if (i.key === 'All') { + i.selected = false; + } + }); + var indexOfAll = vm.usersOptions.userStates.indexOf('All'); + if (indexOfAll >= 0) { + vm.usersOptions.userStates.splice(indexOfAll, 1); + } + } + if (userState.selected) { + vm.usersOptions.userStates.push(userState.key); + } else { + var index = vm.usersOptions.userStates.indexOf(userState.key); + vm.usersOptions.userStates.splice(index, 1); + } + getUsers(); + } + function setUserGroupFilter(userGroup) { + if (!vm.usersOptions.userGroups) { + vm.usersOptions.userGroups = []; + } + if (userGroup.selected) { + vm.usersOptions.userGroups.push(userGroup.alias); + } else { + var index = vm.usersOptions.userGroups.indexOf(userGroup.alias); + vm.usersOptions.userGroups.splice(index, 1); + } + getUsers(); + } + function setOrderByFilter(value, direction) { + vm.usersOptions.orderBy = value; + vm.usersOptions.orderDirection = direction; + getUsers(); + } + function changePageNumber(pageNumber) { + vm.usersOptions.pageNumber = pageNumber; + getUsers(); + } + function createUser(addUserForm) { + if (formHelper.submitForm({ + formCtrl: addUserForm, + scope: $scope, + statusMessage: 'Saving...' + })) { + vm.newUser.id = -1; + vm.newUser.parentId = -1; + vm.page.createButtonState = 'busy'; + usersResource.createUser(vm.newUser).then(function (saved) { + vm.page.createButtonState = 'success'; + vm.newUser = saved; + setUsersViewState('createUserSuccess'); + getUsers(); + }, function (err) { + formHelper.handleError(err); + vm.page.createButtonState = 'error'; + }); + } + } + function inviteUser(addUserForm) { + if (formHelper.submitForm({ + formCtrl: addUserForm, + scope: $scope, + statusMessage: 'Saving...' + })) { + vm.newUser.id = -1; + vm.newUser.parentId = -1; + vm.page.createButtonState = 'busy'; + usersResource.inviteUser(vm.newUser).then(function (saved) { + //success + vm.page.createButtonState = 'success'; + vm.newUser = saved; + setUsersViewState('inviteUserSuccess'); + getUsers(); + }, function (err) { + //error + formHelper.handleError(err); + vm.page.createButtonState = 'error'; + }); + } + } + function toggleNewUserPassword() { + vm.newUser.showPassword = !vm.newUser.showPassword; + } + // copy to clip board success + function copySuccess() { + vm.page.copyPasswordButtonState = 'success'; + } + // copy to clip board error + function copyError() { + vm.page.copyPasswordButtonState = 'error'; + } + function goToUser(userId) { + $location.path('users/users/user/' + userId); + } + // helpers + function getUsers() { + vm.loading = true; + // Get users + usersResource.getPagedResults(vm.usersOptions).then(function (data) { + vm.users = data.items; + vm.usersOptions.pageNumber = data.pageNumber; + vm.usersOptions.pageSize = data.pageSize; + vm.usersOptions.totalItems = data.totalItems; + vm.usersOptions.totalPages = data.totalPages; + formatDates(vm.users); + setUserDisplayState(vm.users); + vm.userStatesFilter = usersHelper.getUserStatesFilter(data.userStates); + vm.loading = false; + }, function (error) { + vm.loading = false; + }); + } + function setUserDisplayState(users) { + angular.forEach(users, function (user) { + user.userDisplayState = usersHelper.getUserStateFromValue(user.userState); + }); + } + function formatDates(users) { + angular.forEach(users, function (user) { + if (user.lastLoginDate) { + var dateVal; + var serverOffset = Umbraco.Sys.ServerVariables.application.serverTimeOffset; + var localOffset = new Date().getTimezoneOffset(); + var serverTimeNeedsOffsetting = -serverOffset !== localOffset; + if (serverTimeNeedsOffsetting) { + dateVal = dateHelper.convertToLocalMomentTime(user.lastLoginDate, serverOffset); + } else { + dateVal = moment(user.lastLoginDate, 'YYYY-MM-DD HH:mm:ss'); + } + // get current backoffice user and format date + userService.getCurrentUser().then(function (currentUser) { + user.formattedLastLogin = dateVal.locale(currentUser.locale).format('LLL'); + }); + } + }); + } + function setBulkActions(users) { + // reset all states + vm.allowDisableUser = true; + vm.allowEnableUser = true; + vm.allowUnlockUser = true; + vm.allowSetUserGroup = true; + var firstSelectedUserGroups; + angular.forEach(users, function (user) { + if (!user.selected) { + return; + } + // if the current user is selected prevent any bulk actions with the user included + if (user.isCurrentUser) { + vm.allowDisableUser = false; + vm.allowEnableUser = false; + vm.allowUnlockUser = false; + vm.allowSetUserGroup = false; + return; + } + if (user.userDisplayState && user.userDisplayState.key === 'Disabled') { + vm.allowDisableUser = false; + } + if (user.userDisplayState && user.userDisplayState.key === 'Active') { + vm.allowEnableUser = false; + } + if (user.userDisplayState && user.userDisplayState.key === 'Invited') { + vm.allowEnableUser = false; + } + if (user.userDisplayState && user.userDisplayState.key === 'LockedOut') { + vm.allowEnableUser = false; + } + if (user.userDisplayState && user.userDisplayState.key !== 'LockedOut') { + vm.allowUnlockUser = false; + } + // store the user group aliases of the first selected user + if (!firstSelectedUserGroups) { + firstSelectedUserGroups = user.userGroups.map(function (ug) { + return ug.alias; + }); + vm.allowSetUserGroup = true; + } else if (vm.allowSetUserGroup === true) { + // for 2nd+ selected user, compare the user group aliases to determine if we should allow bulk editing. + // we don't allow bulk editing of users not currently having the same assigned user groups, as we can't + // really support that in the user group picker. + var userGroups = user.userGroups.map(function (ug) { + return ug.alias; + }); + if (_.difference(firstSelectedUserGroups, userGroups).length > 0) { + vm.allowSetUserGroup = false; + } + } + }); + } + function clearAddUserForm() { + // clear form data + vm.newUser.name = ''; + vm.newUser.email = ''; + vm.newUser.userGroups = []; + vm.newUser.message = ''; + // clear button state + vm.page.createButtonState = 'init'; + } + init(); + } + angular.module('umbraco').controller('Umbraco.Editors.Users.UsersController', UsersController); + }()); +}()); \ No newline at end of file diff --git a/WebCms/Umbraco/Js/umbraco.directives.js b/WebCms/Umbraco/Js/umbraco.directives.js index 2076f87..bcad091 100644 --- a/WebCms/Umbraco/Js/umbraco.directives.js +++ b/WebCms/Umbraco/Js/umbraco.directives.js @@ -1,16 +1,14 @@ -/*! umbraco - * https://github.com/umbraco/umbraco-cms/ - * Copyright (c) 2016 Umbraco HQ; - * Licensed - */ - -(function() { - -angular.module("umbraco.directives", ["umbraco.directives.editors", "umbraco.directives.html", "umbraco.directives.validation", "ui.sortable"]); -angular.module("umbraco.directives.editors", []); -angular.module("umbraco.directives.html", []); -angular.module("umbraco.directives.validation", []); -/** +(function () { + angular.module('umbraco.directives', [ + 'umbraco.directives.editors', + 'umbraco.directives.html', + 'umbraco.directives.validation', + 'ui.sortable' + ]); + angular.module('umbraco.directives.editors', []); + angular.module('umbraco.directives.html', []); + angular.module('umbraco.directives.validation', []); + /** * @ngdoc directive * @name umbraco.directives.directive:autoScale * @element div @@ -28,30 +26,23 @@ angular.module("umbraco.directives.validation", []); * * **/ - -angular.module("umbraco.directives") - .directive('autoScale', function ($window) { - return function (scope, el, attrs) { - - var totalOffset = 0; - var offsety = parseInt(attrs.autoScale, 10); - var window = angular.element($window); - if (offsety !== undefined){ - totalOffset += offsety; - } - - setTimeout(function () { - el.height(window.height() - (el.offset().top + totalOffset)); - }, 500); - - window.bind("resize", function () { - el.height(window.height() - (el.offset().top + totalOffset)); - }); - - }; - }); - -/** + angular.module('umbraco.directives').directive('autoScale', function ($window) { + return function (scope, el, attrs) { + var totalOffset = 0; + var offsety = parseInt(attrs.autoScale, 10); + var window = angular.element($window); + if (offsety !== undefined) { + totalOffset += offsety; + } + setTimeout(function () { + el.height(window.height() - (el.offset().top + totalOffset)); + }, 500); + window.bind('resize', function () { + el.height(window.height() - (el.offset().top + totalOffset)); + }); + }; + }); + /** * @ngdoc directive * @name umbraco.directives.directive:detectFold * @deprecated @@ -59,58 +50,48 @@ angular.module("umbraco.directives") * @description This is used for the editor buttons to ensure they are displayed correctly if the horizontal overflow of the editor * exceeds the height of the window **/ - -angular.module("umbraco.directives.html") - .directive('detectFold', function ($timeout, $log, windowResizeListener) { - return { - require: "^?umbTabs", - restrict: 'A', - link: function (scope, el, attrs, tabsCtrl) { - - var firstRun = false; - var parent = $(".umb-panel-body"); - var winHeight = $(window).height(); - var calculate = function () { - if (el && el.is(":visible") && !el.hasClass("umb-bottom-bar")) { - - //now that the element is visible, set the flag in a couple of seconds, - // this will ensure that loading time of a current tab get's completed and that - // we eventually stop watching to save on CPU time - $timeout(function() { - firstRun = true; - }, 4000); - - //var parent = el.parent(); - var hasOverflow = parent.innerHeight() < parent[0].scrollHeight; - //var belowFold = (el.offset().top + el.height()) > winHeight; - if (hasOverflow) { - el.addClass("umb-bottom-bar"); - - //I wish we didn't have to put this logic here but unfortunately we - // do. This needs to calculate the left offest to place the bottom bar - // depending on if the left column splitter has been moved by the user + angular.module('umbraco.directives.html').directive('detectFold', function ($timeout, $log, windowResizeListener) { + return { + require: '^?umbTabs', + restrict: 'A', + link: function (scope, el, attrs, tabsCtrl) { + var firstRun = false; + var parent = $('.umb-panel-body'); + var winHeight = $(window).height(); + var calculate = function () { + if (el && el.is(':visible') && !el.hasClass('umb-bottom-bar')) { + //now that the element is visible, set the flag in a couple of seconds, + // this will ensure that loading time of a current tab get's completed and that + // we eventually stop watching to save on CPU time + $timeout(function () { + firstRun = true; + }, 4000); + //var parent = el.parent(); + var hasOverflow = parent.innerHeight() < parent[0].scrollHeight; + //var belowFold = (el.offset().top + el.height()) > winHeight; + if (hasOverflow) { + el.addClass('umb-bottom-bar'); + //I wish we didn't have to put this logic here but unfortunately we + // do. This needs to calculate the left offest to place the bottom bar + // depending on if the left column splitter has been moved by the user // (based on the nav-resize directive) - var wrapper = $("#mainwrapper"); - var contentPanel = $("#leftcolumn").next(); - var contentPanelLeftPx = contentPanel.css("left"); - - el.css({ left: contentPanelLeftPx }); - } - } - return firstRun; - }; - - var resizeCallback = function(size) { - winHeight = size.height; - el.removeClass("umb-bottom-bar"); - calculate(); - }; - - windowResizeListener.register(resizeCallback); - - //Only execute the watcher if this tab is the active (first) tab on load, otherwise there's no reason to execute - // the watcher since it will be recalculated when the tab changes! - if (el.closest(".umb-tab-pane").index() === 0) { + var wrapper = $('#mainwrapper'); + var contentPanel = $('#leftcolumn').next(); + var contentPanelLeftPx = contentPanel.css('left'); + el.css({ left: contentPanelLeftPx }); + } + } + return firstRun; + }; + var resizeCallback = function (size) { + winHeight = size.height; + el.removeClass('umb-bottom-bar'); + calculate(); + }; + windowResizeListener.register(resizeCallback); + //Only execute the watcher if this tab is the active (first) tab on load, otherwise there's no reason to execute + // the watcher since it will be recalculated when the tab changes! + if (el.closest('.umb-tab-pane').index() === 0) { //run a watcher to ensure that the calculation occurs until it's firstRun but ensure // the calculations are throttled to save a bit of CPU var listener = scope.$watch(_.throttle(calculate, 1000), function (newVal, oldVal) { @@ -119,185 +100,94 @@ angular.module("umbraco.directives.html") } }); } - - //listen for tab changes + //listen for tab changes if (tabsCtrl != null) { tabsCtrl.onTabShown(function (args) { calculate(); }); } - - //ensure to unregister - scope.$on('$destroy', function() { - windowResizeListener.unregister(resizeCallback); - }); - } - }; - }); -/** -* @ngdoc directive -* @name umbraco.directives.directive:umbItemSorter -* @deprecated -* We plan to remove this directive in the next major version of umbraco (8.0). The directive is not recommended to use. -* @function -* @element ANY -* @restrict E -* @description A re-usable directive for sorting items -**/ - -function umbItemSorter(angularHelper) { - return { - scope: { - model: "=" - }, - restrict: "E", // restrict to an element - replace: true, // replace the html element with the template - templateUrl: 'views/directives/_obsolete/umb-item-sorter.html', - link: function(scope, element, attrs, ctrl) { - var defaultModel = { - okButton: "Ok", - successMsg: "Sorting successful", - complete: false - }; - //assign user vals to default - angular.extend(defaultModel, scope.model); - //re-assign merged to user - scope.model = defaultModel; - - scope.performSort = function() { - scope.$emit("umbItemSorter.sorting", { - sortedItems: scope.model.itemsToSort - }); - }; - - scope.handleCancel = function () { - scope.$emit("umbItemSorter.cancel"); - }; - - scope.handleOk = function() { - scope.$emit("umbItemSorter.ok"); - }; - - //defines the options for the jquery sortable - scope.sortableOptions = { - axis: 'y', - cursor: "move", - placeholder: "ui-sortable-placeholder", - update: function (ev, ui) { - //highlight the item when the position is changed - $(ui.item).effect("highlight", { color: "#049cdb" }, 500); - }, - stop: function (ev, ui) { - //the ui-sortable directive already ensures that our list is re-sorted, so now we just - // need to update the sortOrder to the index of each item - angularHelper.safeApply(scope, function () { - angular.forEach(scope.itemsToSort, function (val, index) { - val.sortOrder = index + 1; - }); - - }); - } - }; - } - }; -} - -angular.module('umbraco.directives').directive("umbItemSorter", umbItemSorter); - -/** -* @ngdoc directive -* @name umbraco.directives.directive:umbContentName -* @deprecated -* We plan to remove this directive in the next major version of umbraco (8.0). The directive is not recommended to use. -* @restrict E -* @function -* @description -* Used by editors that require naming an entity. Shows a textbox/headline with a required validator within it's own form. -**/ - -angular.module("umbraco.directives") - .directive('umbContentName', function ($timeout, localizationService) { - return { - require: "ngModel", - restrict: 'E', - replace: true, - templateUrl: 'views/directives/_obsolete/umb-content-name.html', - - scope: { - placeholder: '@placeholder', - model: '=ngModel', - ngDisabled: '=' - }, - link: function(scope, element, attrs, ngModel) { - - var inputElement = element.find("input"); - if(scope.placeholder && scope.placeholder[0] === "@"){ - localizationService.localize(scope.placeholder.substring(1)) - .then(function(value){ - scope.placeholder = value; - }); - } - - var mX, mY, distance; - - function calculateDistance(elem, mouseX, mouseY) { - - var cx = Math.max(Math.min(mouseX, elem.offset().left + elem.width()), elem.offset().left); - var cy = Math.max(Math.min(mouseY, elem.offset().top + elem.height()), elem.offset().top); - return Math.sqrt((mouseX - cx) * (mouseX - cx) + (mouseY - cy) * (mouseY - cy)); - } - - var mouseMoveDebounce = _.throttle(function (e) { - mX = e.pageX; - mY = e.pageY; - // not focused and not over element - if (!inputElement.is(":focus") && !inputElement.hasClass("ng-invalid")) { - // on page - if (mX >= inputElement.offset().left) { - distance = calculateDistance(inputElement, mX, mY); - if (distance <= 155) { - - distance = 1 - (100 / 150 * distance / 100); - inputElement.css("border", "1px solid rgba(175,175,175, " + distance + ")"); - inputElement.css("background-color", "rgba(255,255,255, " + distance + ")"); - } - } - - } - - }, 15); - - $(document).bind("mousemove", mouseMoveDebounce); - - $timeout(function(){ - if(!scope.model){ - scope.goEdit(); - } - }, 100, false); - - scope.goEdit = function(){ - scope.editMode = true; - - $timeout(function () { - inputElement.focus(); - }, 100, false); - }; - - scope.exitEdit = function(){ - if(scope.model && scope.model !== ""){ - scope.editMode = false; - } - }; - - //unbind doc event! - scope.$on('$destroy', function () { - $(document).unbind("mousemove", mouseMoveDebounce); - }); - } - }; - }); - -/** + //ensure to unregister + scope.$on('$destroy', function () { + windowResizeListener.unregister(resizeCallback); + }); + } + }; + }); + /** +* @ngdoc directive +* @name umbraco.directives.directive:umbContentName +* @deprecated +* We plan to remove this directive in the next major version of umbraco (8.0). The directive is not recommended to use. +* @restrict E +* @function +* @description +* Used by editors that require naming an entity. Shows a textbox/headline with a required validator within it's own form. +**/ + angular.module('umbraco.directives').directive('umbContentName', function ($timeout, localizationService) { + return { + require: 'ngModel', + restrict: 'E', + replace: true, + templateUrl: 'views/directives/_obsolete/umb-content-name.html', + scope: { + placeholder: '@placeholder', + model: '=ngModel', + ngDisabled: '=' + }, + link: function (scope, element, attrs, ngModel) { + var inputElement = element.find('input'); + if (scope.placeholder && scope.placeholder[0] === '@') { + localizationService.localize(scope.placeholder.substring(1)).then(function (value) { + scope.placeholder = value; + }); + } + var mX, mY, distance; + function calculateDistance(elem, mouseX, mouseY) { + var cx = Math.max(Math.min(mouseX, elem.offset().left + elem.width()), elem.offset().left); + var cy = Math.max(Math.min(mouseY, elem.offset().top + elem.height()), elem.offset().top); + return Math.sqrt((mouseX - cx) * (mouseX - cx) + (mouseY - cy) * (mouseY - cy)); + } + var mouseMoveDebounce = _.throttle(function (e) { + mX = e.pageX; + mY = e.pageY; + // not focused and not over element + if (!inputElement.is(':focus') && !inputElement.hasClass('ng-invalid')) { + // on page + if (mX >= inputElement.offset().left) { + distance = calculateDistance(inputElement, mX, mY); + if (distance <= 155) { + distance = 1 - 100 / 150 * distance / 100; + inputElement.css('border', '1px solid rgba(175,175,175, ' + distance + ')'); + inputElement.css('background-color', 'rgba(255,255,255, ' + distance + ')'); + } + } + } + }, 15); + $(document).bind('mousemove', mouseMoveDebounce); + $timeout(function () { + if (!scope.model) { + scope.goEdit(); + } + }, 100, false); + scope.goEdit = function () { + scope.editMode = true; + $timeout(function () { + inputElement.focus(); + }, 100, false); + }; + scope.exitEdit = function () { + if (scope.model && scope.model !== '') { + scope.editMode = false; + } + }; + //unbind doc event! + scope.$on('$destroy', function () { + $(document).unbind('mousemove', mouseMoveDebounce); + }); + } + }; + }); + /** * @ngdoc directive * @name umbraco.directives.directive:umbHeader * @deprecated @@ -307,82 +197,133 @@ angular.module("umbraco.directives") * @description * The header on an editor that contains tabs using bootstrap tabs - THIS IS OBSOLETE, use umbTabHeader instead **/ - -angular.module("umbraco.directives") -.directive('umbHeader', function ($parse, $timeout) { - return { - restrict: 'E', - replace: true, - transclude: 'true', - templateUrl: 'views/directives/_obsolete/umb-header.html', - //create a new isolated scope assigning a tabs property from the attribute 'tabs' - //which is bound to the parent scope property passed in - scope: { - tabs: "=" - }, - link: function (scope, iElement, iAttrs) { - - scope.showTabs = iAttrs.tabs ? true : false; - scope.visibleTabs = []; - - //since tabs are loaded async, we need to put a watch on them to determine - // when they are loaded, then we can close the watch - var tabWatch = scope.$watch("tabs", function (newValue, oldValue) { - - angular.forEach(newValue, function(val, index){ - var tab = {id: val.id, label: val.label}; + angular.module('umbraco.directives').directive('umbHeader', function ($parse, $timeout) { + return { + restrict: 'E', + replace: true, + transclude: 'true', + templateUrl: 'views/directives/_obsolete/umb-header.html', + //create a new isolated scope assigning a tabs property from the attribute 'tabs' + //which is bound to the parent scope property passed in + scope: { tabs: '=' }, + link: function (scope, iElement, iAttrs) { + scope.showTabs = iAttrs.tabs ? true : false; + scope.visibleTabs = []; + //since tabs are loaded async, we need to put a watch on them to determine + // when they are loaded, then we can close the watch + var tabWatch = scope.$watch('tabs', function (newValue, oldValue) { + angular.forEach(newValue, function (val, index) { + var tab = { + id: val.id, + label: val.label + }; scope.visibleTabs.push(tab); - }); - - //don't process if we cannot or have already done so - if (!newValue) {return;} - if (!newValue.length || newValue.length === 0){return;} - - //we need to do a timeout here so that the current sync operation can complete - // and update the UI, then this will fire and the UI elements will be available. - $timeout(function () { - - //use bootstrap tabs API to show the first one - iElement.find(".nav-tabs a:first").tab('show'); - - //enable the tab drop - iElement.find('.nav-pills, .nav-tabs').tabdrop(); - - //ensure to destroy tabdrop (unbinds window resize listeners) - scope.$on('$destroy', function () { - iElement.find('.nav-pills, .nav-tabs').tabdrop("destroy"); }); - - //stop watching now - tabWatch(); - }, 200); - - }); - } - }; -}); - -/** -* @ngdoc directive -* @name umbraco.directives.directive:umbLogin -* @deprecated -* We plan to remove this directive in the next major version of umbraco (8.0). The directive is not recommended to use. -* @function -* @element ANY -* @restrict E -**/ - -function loginDirective() { - return { - restrict: "E", // restrict to an element - replace: true, // replace the html element with the template - templateUrl: 'views/directives/_obsolete/umb-login.html' - }; -} - -angular.module('umbraco.directives').directive("umbLogin", loginDirective); - -/** + //don't process if we cannot or have already done so + if (!newValue) { + return; + } + if (!newValue.length || newValue.length === 0) { + return; + } + //we need to do a timeout here so that the current sync operation can complete + // and update the UI, then this will fire and the UI elements will be available. + $timeout(function () { + //use bootstrap tabs API to show the first one + iElement.find('.nav-tabs a:first').tab('show'); + //enable the tab drop + iElement.find('.nav-pills, .nav-tabs').tabdrop(); + //ensure to destroy tabdrop (unbinds window resize listeners) + scope.$on('$destroy', function () { + iElement.find('.nav-pills, .nav-tabs').tabdrop('destroy'); + }); + //stop watching now + tabWatch(); + }, 200); + }); + } + }; + }); + /** +* @ngdoc directive +* @name umbraco.directives.directive:umbItemSorter +* @deprecated +* We plan to remove this directive in the next major version of umbraco (8.0). The directive is not recommended to use. +* @function +* @element ANY +* @restrict E +* @description A re-usable directive for sorting items +**/ + function umbItemSorter(angularHelper) { + return { + scope: { model: '=' }, + restrict: 'E', + // restrict to an element + replace: true, + // replace the html element with the template + templateUrl: 'views/directives/_obsolete/umb-item-sorter.html', + link: function (scope, element, attrs, ctrl) { + var defaultModel = { + okButton: 'Ok', + successMsg: 'Sorting successful', + complete: false + }; + //assign user vals to default + angular.extend(defaultModel, scope.model); + //re-assign merged to user + scope.model = defaultModel; + scope.performSort = function () { + scope.$emit('umbItemSorter.sorting', { sortedItems: scope.model.itemsToSort }); + }; + scope.handleCancel = function () { + scope.$emit('umbItemSorter.cancel'); + }; + scope.handleOk = function () { + scope.$emit('umbItemSorter.ok'); + }; + //defines the options for the jquery sortable + scope.sortableOptions = { + axis: 'y', + cursor: 'move', + placeholder: 'ui-sortable-placeholder', + update: function (ev, ui) { + //highlight the item when the position is changed + $(ui.item).effect('highlight', { color: '#049cdb' }, 500); + }, + stop: function (ev, ui) { + //the ui-sortable directive already ensures that our list is re-sorted, so now we just + // need to update the sortOrder to the index of each item + angularHelper.safeApply(scope, function () { + angular.forEach(scope.itemsToSort, function (val, index) { + val.sortOrder = index + 1; + }); + }); + } + }; + } + }; + } + angular.module('umbraco.directives').directive('umbItemSorter', umbItemSorter); + /** +* @ngdoc directive +* @name umbraco.directives.directive:umbLogin +* @deprecated +* We plan to remove this directive in the next major version of umbraco (8.0). The directive is not recommended to use. +* @function +* @element ANY +* @restrict E +**/ + function loginDirective() { + return { + restrict: 'E', + // restrict to an element + replace: true, + // replace the html element with the template + templateUrl: 'views/directives/_obsolete/umb-login.html' + }; + } + angular.module('umbraco.directives').directive('umbLogin', loginDirective); + /** * @ngdoc directive * @name umbraco.directives.directive:umbOptionsMenu * @deprecated @@ -391,291 +332,224 @@ angular.module('umbraco.directives').directive("umbLogin", loginDirective); * @element ANY * @restrict E **/ - -angular.module("umbraco.directives") -.directive('umbOptionsMenu', function ($injector, treeService, navigationService, umbModelMapper, appState) { - return { - scope: { - currentSection: "@", - currentNode: "=" - }, - restrict: 'E', - replace: true, - templateUrl: 'views/directives/_obsolete/umb-optionsmenu.html', - link: function (scope, element, attrs, ctrl) { - - //adds a handler to the context menu item click, we need to handle this differently - //depending on what the menu item is supposed to do. - scope.executeMenuItem = function (action) { - navigationService.executeMenuAction(action, scope.currentNode, scope.currentSection); - }; - - //callback method to go and get the options async - scope.getOptions = function () { - - if (!scope.currentNode) { - return; - } - - //when the options item is selected, we need to set the current menu item in appState (since this is synonymous with a menu) - appState.setMenuState("currentNode", scope.currentNode); - - if (!scope.actions) { - treeService.getMenu({ treeNode: scope.currentNode }) - .then(function (data) { + angular.module('umbraco.directives').directive('umbOptionsMenu', function ($injector, treeService, navigationService, umbModelMapper, appState) { + return { + scope: { + currentSection: '@', + currentNode: '=' + }, + restrict: 'E', + replace: true, + templateUrl: 'views/directives/_obsolete/umb-optionsmenu.html', + link: function (scope, element, attrs, ctrl) { + //adds a handler to the context menu item click, we need to handle this differently + //depending on what the menu item is supposed to do. + scope.executeMenuItem = function (action) { + navigationService.executeMenuAction(action, scope.currentNode, scope.currentSection); + }; + //callback method to go and get the options async + scope.getOptions = function () { + if (!scope.currentNode) { + return; + } + //when the options item is selected, we need to set the current menu item in appState (since this is synonymous with a menu) + appState.setMenuState('currentNode', scope.currentNode); + if (!scope.actions) { + treeService.getMenu({ treeNode: scope.currentNode }).then(function (data) { scope.actions = data.menuItems; }); - } - }; - - } - }; -}); - -/** -* @ngdoc directive -* @name umbraco.directives.directive:umbPhotoFolder -* @deprecated -* We plan to remove this directive in the next major version of umbraco (8.0). The directive is not recommended to use. -* @restrict E -**/ - -angular.module("umbraco.directives.html") - .directive('umbPhotoFolder', function($compile, $log, $timeout, $filter, umbPhotoFolderHelper) { - - return { - restrict: 'E', - replace: true, - require: '?ngModel', - terminate: true, - templateUrl: 'views/directives/_obsolete/umb-photo-folder.html', - link: function(scope, element, attrs, ngModel) { - - var lastWatch = null; - - ngModel.$render = function() { - if (ngModel.$modelValue) { - - $timeout(function() { - var photos = ngModel.$modelValue; - - scope.clickHandler = scope.$eval(element.attr('on-click')); - - - var imagesOnly = element.attr('images-only') === "true"; - - - var margin = element.attr('border') ? parseInt(element.attr('border'), 10) : 5; - var startingIndex = element.attr('baseline') ? parseInt(element.attr('baseline'), 10) : 0; - var minWidth = element.attr('min-width') ? parseInt(element.attr('min-width'), 10) : 420; - var minHeight = element.attr('min-height') ? parseInt(element.attr('min-height'), 10) : 100; - var maxHeight = element.attr('max-height') ? parseInt(element.attr('max-height'), 10) : 300; - var idealImgPerRow = element.attr('ideal-items-per-row') ? parseInt(element.attr('ideal-items-per-row'), 10) : 5; - var fixedRowWidth = Math.max(element.width(), minWidth); - - scope.containerStyle = { width: fixedRowWidth + "px" }; - scope.rows = umbPhotoFolderHelper.buildGrid(photos, fixedRowWidth, maxHeight, startingIndex, minHeight, idealImgPerRow, margin, imagesOnly); - - if (attrs.filterBy) { - - //we track the watches that we create, we don't want to create multiple, so clear it - // if it already exists before creating another. - if (lastWatch) { - lastWatch(); - } - - //TODO: Need to debounce this so it doesn't filter too often! - lastWatch = scope.$watch(attrs.filterBy, function (newVal, oldVal) { - if (newVal && newVal !== oldVal) { - var p = $filter('filter')(photos, newVal, false); - scope.baseline = 0; - var m = umbPhotoFolderHelper.buildGrid(p, fixedRowWidth, maxHeight, startingIndex, minHeight, idealImgPerRow, margin, imagesOnly); - scope.rows = m; - } - }); - } - - }, 500); //end timeout - } //end if modelValue - - }; //end $render - } - }; - }); - -/** -* @ngdoc directive -* @name umbraco.directives.directive:umbSort -* @deprecated -* We plan to remove this directive in the next major version of umbraco (8.0). The directive is not recommended to use. -* -* @element div -* @function -* -* @description -* Resize div's automatically to fit to the bottom of the screen, as an optional parameter an y-axis offset can be set -* So if you only want to scale the div to 70 pixels from the bottom you pass "70" -* -* @example -* -* -*
    -*
    -*
    -**/ - -angular.module("umbraco.directives") - .value('umbSortContextInternal',{}) - .directive('umbSort', function($log,umbSortContextInternal) { - return { - require: '?ngModel', - link: function(scope, element, attrs, ngModel) { - var adjustment; - - var cfg = scope.$eval(element.attr('umb-sort')) || {}; - - scope.model = ngModel; - - scope.opts = cfg; - scope.opts.containerSelector= cfg.containerSelector || ".umb-" + cfg.group + "-container", - scope.opts.nested= cfg.nested || true, - scope.opts.drop= cfg.drop || true, - scope.opts.drag= cfg.drag || true, - scope.opts.clone = cfg.clone || "
  • "; - scope.opts.mode = cfg.mode || "list"; - - scope.opts.itemSelectorFull = $.trim(scope.opts.itemPath + " " + scope.opts.itemSelector); - - /* - scope.opts.isValidTarget = function(item, container) { - if(container.el.is(".umb-" + scope.opts.group + "-container")){ - return true; - } - return false; - }; - */ - - element.addClass("umb-sort"); - element.addClass("umb-" + cfg.group + "-container"); - - scope.opts.onDrag = function (item, position) { - if(scope.opts.mode === "list"){ - item.css({ - left: position.left - adjustment.left, - top: position.top - adjustment.top - }); - } - }; - - - scope.opts.onDrop = function (item, targetContainer, _super) { - - if(scope.opts.mode === "list"){ - //list mode - var clonedItem = $(scope.opts.clone).css({height: 0}); - item.after(clonedItem); - clonedItem.animate({'height': item.height()}); - - item.animate(clonedItem.position(), function () { - clonedItem.detach(); - _super(item); - }); - } - - var children = $(scope.opts.itemSelectorFull, targetContainer.el); - var targetIndex = children.index(item); - var targetScope = $(targetContainer.el[0]).scope(); - - - if(targetScope === umbSortContextInternal.sourceScope){ - if(umbSortContextInternal.sourceScope.opts.onSortHandler){ - var _largs = { - oldIndex: umbSortContextInternal.sourceIndex, - newIndex: targetIndex, - scope: umbSortContextInternal.sourceScope - }; - - umbSortContextInternal.sourceScope.opts.onSortHandler.call(this, item, _largs); - } - }else{ - - - if(targetScope.opts.onDropHandler){ - var args = { - sourceScope: umbSortContextInternal.sourceScope, - sourceIndex: umbSortContextInternal.sourceIndex, - sourceContainer: umbSortContextInternal.sourceContainer, - - targetScope: targetScope, - targetIndex: targetIndex, - targetContainer: targetContainer - }; - - targetScope.opts.onDropHandler.call(this, item, args); - } - - if(umbSortContextInternal.sourceScope.opts.onReleaseHandler){ - var _args = { - sourceScope: umbSortContextInternal.sourceScope, - sourceIndex: umbSortContextInternal.sourceIndex, - sourceContainer: umbSortContextInternal.sourceContainer, - - targetScope: targetScope, - targetIndex: targetIndex, - targetContainer: targetContainer - }; - - umbSortContextInternal.sourceScope.opts.onReleaseHandler.call(this, item, _args); - } - } - }; - - scope.changeIndex = function(from, to){ - scope.$apply(function(){ - var i = ngModel.$modelValue.splice(from, 1)[0]; - ngModel.$modelValue.splice(to, 0, i); - }); - }; - - scope.move = function(args){ - var from = args.sourceIndex; - var to = args.targetIndex; - - if(args.sourceContainer === args.targetContainer){ - scope.changeIndex(from, to); - }else{ - scope.$apply(function(){ - var i = args.sourceScope.model.$modelValue.splice(from, 1)[0]; - args.targetScope.model.$modelvalue.splice(to,0, i); - }); - } - }; - - scope.opts.onDragStart = function (item, container, _super) { - var children = $(scope.opts.itemSelectorFull, container.el); - var offset = item.offset(); - - umbSortContextInternal.sourceIndex = children.index(item); - umbSortContextInternal.sourceScope = $(container.el[0]).scope(); - umbSortContextInternal.sourceContainer = container; - - //current.item = ngModel.$modelValue.splice(current.index, 1)[0]; - - var pointer = container.rootGroup.pointer; - adjustment = { - left: pointer.left - offset.left, - top: pointer.top - offset.top - }; - - _super(item, container); - }; - - element.sortable( scope.opts ); - } - }; - - }); -/** + } + }; + } + }; + }); + /** +* @ngdoc directive +* @name umbraco.directives.directive:umbPhotoFolder +* @deprecated +* We plan to remove this directive in the next major version of umbraco (8.0). The directive is not recommended to use. +* @restrict E +**/ + angular.module('umbraco.directives.html').directive('umbPhotoFolder', function ($compile, $log, $timeout, $filter, umbPhotoFolderHelper) { + return { + restrict: 'E', + replace: true, + require: '?ngModel', + terminate: true, + templateUrl: 'views/directives/_obsolete/umb-photo-folder.html', + link: function (scope, element, attrs, ngModel) { + var lastWatch = null; + ngModel.$render = function () { + if (ngModel.$modelValue) { + $timeout(function () { + var photos = ngModel.$modelValue; + scope.clickHandler = scope.$eval(element.attr('on-click')); + var imagesOnly = element.attr('images-only') === 'true'; + var margin = element.attr('border') ? parseInt(element.attr('border'), 10) : 5; + var startingIndex = element.attr('baseline') ? parseInt(element.attr('baseline'), 10) : 0; + var minWidth = element.attr('min-width') ? parseInt(element.attr('min-width'), 10) : 420; + var minHeight = element.attr('min-height') ? parseInt(element.attr('min-height'), 10) : 100; + var maxHeight = element.attr('max-height') ? parseInt(element.attr('max-height'), 10) : 300; + var idealImgPerRow = element.attr('ideal-items-per-row') ? parseInt(element.attr('ideal-items-per-row'), 10) : 5; + var fixedRowWidth = Math.max(element.width(), minWidth); + scope.containerStyle = { width: fixedRowWidth + 'px' }; + scope.rows = umbPhotoFolderHelper.buildGrid(photos, fixedRowWidth, maxHeight, startingIndex, minHeight, idealImgPerRow, margin, imagesOnly); + if (attrs.filterBy) { + //we track the watches that we create, we don't want to create multiple, so clear it + // if it already exists before creating another. + if (lastWatch) { + lastWatch(); + } + //TODO: Need to debounce this so it doesn't filter too often! + lastWatch = scope.$watch(attrs.filterBy, function (newVal, oldVal) { + if (newVal && newVal !== oldVal) { + var p = $filter('filter')(photos, newVal, false); + scope.baseline = 0; + var m = umbPhotoFolderHelper.buildGrid(p, fixedRowWidth, maxHeight, startingIndex, minHeight, idealImgPerRow, margin, imagesOnly); + scope.rows = m; + } + }); + } + }, 500); //end timeout + } //end if modelValue + }; //end $render + } + }; + }); + /** +* @ngdoc directive +* @name umbraco.directives.directive:umbSort +* @deprecated +* We plan to remove this directive in the next major version of umbraco (8.0). The directive is not recommended to use. +* +* @element div +* @function +* +* @description +* Resize div's automatically to fit to the bottom of the screen, as an optional parameter an y-axis offset can be set +* So if you only want to scale the div to 70 pixels from the bottom you pass "70" +* +* @example +* +* +*
    +*
    +*
    +**/ + angular.module('umbraco.directives').value('umbSortContextInternal', {}).directive('umbSort', function ($log, umbSortContextInternal) { + return { + require: '?ngModel', + link: function (scope, element, attrs, ngModel) { + var adjustment; + var cfg = scope.$eval(element.attr('umb-sort')) || {}; + scope.model = ngModel; + scope.opts = cfg; + scope.opts.containerSelector = cfg.containerSelector || '.umb-' + cfg.group + '-container', scope.opts.nested = cfg.nested || true, scope.opts.drop = cfg.drop || true, scope.opts.drag = cfg.drag || true, scope.opts.clone = cfg.clone || '
  • '; + scope.opts.mode = cfg.mode || 'list'; + scope.opts.itemSelectorFull = $.trim(scope.opts.itemPath + ' ' + scope.opts.itemSelector); + /* + scope.opts.isValidTarget = function(item, container) { + if(container.el.is(".umb-" + scope.opts.group + "-container")){ + return true; + } + return false; + }; + */ + element.addClass('umb-sort'); + element.addClass('umb-' + cfg.group + '-container'); + scope.opts.onDrag = function (item, position) { + if (scope.opts.mode === 'list') { + item.css({ + left: position.left - adjustment.left, + top: position.top - adjustment.top + }); + } + }; + scope.opts.onDrop = function (item, targetContainer, _super) { + if (scope.opts.mode === 'list') { + //list mode + var clonedItem = $(scope.opts.clone).css({ height: 0 }); + item.after(clonedItem); + clonedItem.animate({ 'height': item.height() }); + item.animate(clonedItem.position(), function () { + clonedItem.detach(); + _super(item); + }); + } + var children = $(scope.opts.itemSelectorFull, targetContainer.el); + var targetIndex = children.index(item); + var targetScope = $(targetContainer.el[0]).scope(); + if (targetScope === umbSortContextInternal.sourceScope) { + if (umbSortContextInternal.sourceScope.opts.onSortHandler) { + var _largs = { + oldIndex: umbSortContextInternal.sourceIndex, + newIndex: targetIndex, + scope: umbSortContextInternal.sourceScope + }; + umbSortContextInternal.sourceScope.opts.onSortHandler.call(this, item, _largs); + } + } else { + if (targetScope.opts.onDropHandler) { + var args = { + sourceScope: umbSortContextInternal.sourceScope, + sourceIndex: umbSortContextInternal.sourceIndex, + sourceContainer: umbSortContextInternal.sourceContainer, + targetScope: targetScope, + targetIndex: targetIndex, + targetContainer: targetContainer + }; + targetScope.opts.onDropHandler.call(this, item, args); + } + if (umbSortContextInternal.sourceScope.opts.onReleaseHandler) { + var _args = { + sourceScope: umbSortContextInternal.sourceScope, + sourceIndex: umbSortContextInternal.sourceIndex, + sourceContainer: umbSortContextInternal.sourceContainer, + targetScope: targetScope, + targetIndex: targetIndex, + targetContainer: targetContainer + }; + umbSortContextInternal.sourceScope.opts.onReleaseHandler.call(this, item, _args); + } + } + }; + scope.changeIndex = function (from, to) { + scope.$apply(function () { + var i = ngModel.$modelValue.splice(from, 1)[0]; + ngModel.$modelValue.splice(to, 0, i); + }); + }; + scope.move = function (args) { + var from = args.sourceIndex; + var to = args.targetIndex; + if (args.sourceContainer === args.targetContainer) { + scope.changeIndex(from, to); + } else { + scope.$apply(function () { + var i = args.sourceScope.model.$modelValue.splice(from, 1)[0]; + args.targetScope.model.$modelvalue.splice(to, 0, i); + }); + } + }; + scope.opts.onDragStart = function (item, container, _super) { + var children = $(scope.opts.itemSelectorFull, container.el); + var offset = item.offset(); + umbSortContextInternal.sourceIndex = children.index(item); + umbSortContextInternal.sourceScope = $(container.el[0]).scope(); + umbSortContextInternal.sourceContainer = container; + //current.item = ngModel.$modelValue.splice(current.index, 1)[0]; + var pointer = container.rootGroup.pointer; + adjustment = { + left: pointer.left - offset.left, + top: pointer.top - offset.top + }; + _super(item, container); + }; + element.sortable(scope.opts); + } + }; + }); + /** * @ngdoc directive * @name umbraco.directives.directive:umbTabView * @deprecated @@ -683,18 +557,15 @@ angular.module("umbraco.directives") * * @restrict E **/ - -angular.module("umbraco.directives") -.directive('umbTabView', function($timeout, $log){ - return { - restrict: 'E', - replace: true, - transclude: 'true', - templateUrl: 'views/directives/_obsolete/umb-tab-view.html' - }; -}); - -/** + angular.module('umbraco.directives').directive('umbTabView', function ($timeout, $log) { + return { + restrict: 'E', + replace: true, + transclude: 'true', + templateUrl: 'views/directives/_obsolete/umb-tab-view.html' + }; + }); + /** * @ngdoc directive * @name umbraco.directives.directive:umbUploadDropzone * @deprecated @@ -702,17 +573,14 @@ angular.module("umbraco.directives") * * @restrict E **/ - -angular.module("umbraco.directives.html") - .directive('umbUploadDropzone', function(){ - return { - restrict: 'E', - replace: true, - templateUrl: 'views/directives/_obsolete/umb-upload-dropzone.html' - }; - }); - -/** + angular.module('umbraco.directives.html').directive('umbUploadDropzone', function () { + return { + restrict: 'E', + replace: true, + templateUrl: 'views/directives/_obsolete/umb-upload-dropzone.html' + }; + }); + /** * @ngdoc directive * @name umbraco.directives.directive:navResize * @restrict A @@ -720,367 +588,1282 @@ angular.module("umbraco.directives.html") * @description * Handles how the navigation responds to window resizing and controls how the draggable resize panel works **/ -angular.module("umbraco.directives") - .directive('navResize', function (appState, eventsService, windowResizeListener) { + angular.module('umbraco.directives').directive('navResize', function (appState, eventsService, windowResizeListener) { return { restrict: 'A', link: function (scope, element, attrs, ctrl) { - var minScreenSize = 1100; var resizeEnabled = false; - function setTreeMode() { - appState.setGlobalState("showNavigation", appState.getGlobalState("isTablet") === false); + appState.setGlobalState('showNavigation', appState.getGlobalState('isTablet') === false); } - function enableResize() { //only enable when the size is correct and it's not already enabled - if (!resizeEnabled && appState.getGlobalState("isTablet") === false) { - element.resizable( - { - containment: $("#mainwrapper"), + if (!resizeEnabled && appState.getGlobalState('isTablet') === false) { + element.resizable({ + containment: $('#mainwrapper'), autoHide: true, - handles: "e", - alsoResize: ".navigation-inner-container", - resize: function(e, ui) { - var wrapper = $("#mainwrapper"); - var contentPanel = $("#contentwrapper"); - var umbNotification = $("#umb-notifications-wrapper"); - var apps = $("#applications"); - var bottomBar = contentPanel.find(".umb-bottom-bar"); - var navOffeset = $("#navOffset"); - + handles: 'e', + alsoResize: '.navigation-inner-container', + resize: function (e, ui) { + var wrapper = $('#mainwrapper'); + var contentPanel = $('#contentwrapper'); + var umbNotification = $('#umb-notifications-wrapper'); + var apps = $('#applications'); + var bottomBar = contentPanel.find('.umb-bottom-bar'); + var navOffeset = $('#navOffset'); var leftPanelWidth = ui.element.width() + apps.width(); - contentPanel.css({ left: leftPanelWidth }); bottomBar.css({ left: leftPanelWidth }); umbNotification.css({ left: leftPanelWidth }); - - navOffeset.css({ "margin-left": ui.element.outerWidth() }); + navOffeset.css({ 'margin-left': ui.element.outerWidth() }); }, stop: function (e, ui) { - } }); - resizeEnabled = true; } } - function resetResize() { if (resizeEnabled) { //kill the resize - element.resizable("destroy"); - element.css("width", ""); - - var navInnerContainer = element.find(".navigation-inner-container"); - - navInnerContainer.css("width", ""); - $("#contentwrapper").css("left", ""); - $("#umb-notifications-wrapper").css("left", ""); - $("#navOffset").css("margin-left", ""); - + element.resizable('destroy'); + element.css('width', ''); + var navInnerContainer = element.find('.navigation-inner-container'); + navInnerContainer.css('width', ''); + $('#contentwrapper').css('left', ''); + $('#umb-notifications-wrapper').css('left', ''); + $('#navOffset').css('margin-left', ''); resizeEnabled = false; } } - var evts = []; - //Listen for global state changes - evts.push(eventsService.on("appState.globalState.changed", function (e, args) { - if (args.key === "showNavigation") { + evts.push(eventsService.on('appState.globalState.changed', function (e, args) { + if (args.key === 'showNavigation') { if (args.value === false) { resetResize(); - } - else { + } else { enableResize(); } } })); - - var resizeCallback = function(size) { + var resizeCallback = function (size) { //set the global app state - appState.setGlobalState("isTablet", (size.width <= minScreenSize)); + appState.setGlobalState('isTablet', size.width <= minScreenSize); setTreeMode(); }; - windowResizeListener.register(resizeCallback); - //ensure to unregister from all events and kill jquery plugins scope.$on('$destroy', function () { windowResizeListener.unregister(resizeCallback); for (var e in evts) { eventsService.unsubscribe(evts[e]); } - var navInnerContainer = element.find(".navigation-inner-container"); - navInnerContainer.resizable("destroy"); + var navInnerContainer = element.find('.navigation-inner-container'); + navInnerContainer.resizable('destroy'); }); - //init //set the global app state - appState.setGlobalState("isTablet", ($(window).width() <= minScreenSize)); + appState.setGlobalState('isTablet', $(window).width() <= minScreenSize); setTreeMode(); } }; }); - -angular.module("umbraco.directives") -.directive('sectionIcon', function ($compile, iconHelper) { - return { - restrict: 'E', - replace: true, - - link: function (scope, element, attrs) { - - var icon = attrs.icon; - - if (iconHelper.isLegacyIcon(icon)) { - //its a known legacy icon, convert to a new one - element.html(""); - } - else if (iconHelper.isFileBasedIcon(icon)) { - var convert = iconHelper.convertFromLegacyImage(icon); - if(convert){ - element.html(""); - }else{ - element.html(""); + angular.module('umbraco.directives').directive('sectionIcon', function ($compile, iconHelper) { + return { + restrict: 'E', + replace: true, + link: function (scope, element, attrs) { + var icon = attrs.icon; + if (iconHelper.isLegacyIcon(icon)) { + //its a known legacy icon, convert to a new one + element.html(''); + } else if (iconHelper.isFileBasedIcon(icon)) { + var convert = iconHelper.convertFromLegacyImage(icon); + if (convert) { + element.html(''); + } else { + element.html(''); + } //it's a file, normally legacy so look in the icon tray images + } else { + //it's normal + element.html(''); } - //it's a file, normally legacy so look in the icon tray images } - else { - //it's normal - element.html(""); + }; + }); + (function () { + 'use strict'; + function BackdropDirective($timeout, $http) { + function link(scope, el, attr, ctrl) { + var events = []; + scope.clickBackdrop = function (event) { + if (scope.disableEventsOnClick === true) { + event.preventDefault(); + event.stopPropagation(); + } + }; + function onInit() { + if (scope.highlightElement) { + setHighlight(); + } + } + function setHighlight() { + scope.loading = true; + $timeout(function () { + // The element to highlight + var highlightElement = angular.element(scope.highlightElement); + if (highlightElement && highlightElement.length > 0) { + var offset = highlightElement.offset(); + var width = highlightElement.outerWidth(); + var height = highlightElement.outerHeight(); + // Rounding numbers + var topDistance = offset.top.toFixed(); + var topAndHeight = (offset.top + height).toFixed(); + var leftDistance = offset.left.toFixed(); + var leftAndWidth = (offset.left + width).toFixed(); + // The four rectangles + var rectTop = el.find('.umb-backdrop__rect--top'); + var rectRight = el.find('.umb-backdrop__rect--right'); + var rectBottom = el.find('.umb-backdrop__rect--bottom'); + var rectLeft = el.find('.umb-backdrop__rect--left'); + // Add the css + scope.rectTopCss = { + 'height': topDistance, + 'left': leftDistance + 'px', + opacity: scope.backdropOpacity + }; + scope.rectRightCss = { + 'left': leftAndWidth + 'px', + 'top': topDistance + 'px', + 'height': height, + opacity: scope.backdropOpacity + }; + scope.rectBottomCss = { + 'height': '100%', + 'top': topAndHeight + 'px', + 'left': leftDistance + 'px', + opacity: scope.backdropOpacity + }; + scope.rectLeftCss = { + 'width': leftDistance, + opacity: scope.backdropOpacity + }; + // Prevent interaction in the highlighted area + if (scope.highlightPreventClick) { + var preventClickElement = el.find('.umb-backdrop__highlight-prevent-click'); + preventClickElement.css({ + 'width': width, + 'height': height, + 'left': offset.left, + 'top': offset.top + }); + } + } + scope.loading = false; + }); + } + function resize() { + setHighlight(); + } + events.push(scope.$watch('highlightElement', function (newValue, oldValue) { + if (!newValue) { + return; + } + if (newValue === oldValue) { + return; + } + setHighlight(); + })); + $(window).on('resize.umbBackdrop', resize); + scope.$on('$destroy', function () { + // unbind watchers + for (var e in events) { + events[e](); + } + $(window).off('resize.umbBackdrop'); + }); + onInit(); } + var directive = { + transclude: true, + restrict: 'E', + replace: true, + templateUrl: 'views/components/application/umb-backdrop.html', + link: link, + scope: { + backdropOpacity: '=?', + highlightElement: '=?', + highlightPreventClick: '=?', + disableEventsOnClick: '=?' + } + }; + return directive; } - }; -}); -angular.module("umbraco.directives") -.directive('umbContextMenu', function (navigationService) { - return { - scope: { - menuDialogTitle: "@", - currentSection: "@", - currentNode: "=", - menuActions: "=" - }, - restrict: 'E', - replace: true, - templateUrl: 'views/components/application/umb-contextmenu.html', - link: function (scope, element, attrs, ctrl) { - - //adds a handler to the context menu item click, we need to handle this differently - //depending on what the menu item is supposed to do. - scope.executeMenuItem = function (action) { - navigationService.executeMenuAction(action, scope.currentNode, scope.currentSection); - }; - } - }; -}); - -/** -* @ngdoc directive -* @name umbraco.directives.directive:umbNavigation -* @restrict E -**/ -function umbNavigationDirective() { - return { - restrict: "E", // restrict to an element - replace: true, // replace the html element with the template - templateUrl: 'views/components/application/umb-navigation.html' - }; -} - -angular.module('umbraco.directives').directive("umbNavigation", umbNavigationDirective); - -/** -* @ngdoc directive -* @name umbraco.directives.directive:umbSections -* @restrict E -**/ -function sectionsDirective($timeout, $window, navigationService, treeService, sectionResource, appState, eventsService, $location) { - return { - restrict: "E", // restrict to an element - replace: true, // replace the html element with the template - templateUrl: 'views/components/application/umb-sections.html', - link: function (scope, element, attr, ctrl) { - - //setup scope vars - scope.maxSections = 7; - scope.overflowingSections = 0; - scope.sections = []; - scope.currentSection = appState.getSectionState("currentSection"); - scope.showTray = false; //appState.getGlobalState("showTray"); - scope.stickyNavigation = appState.getGlobalState("stickyNavigation"); - scope.needTray = false; - scope.trayAnimation = function() { - if (scope.showTray) { - return 'slide'; - } - else if (scope.showTray === false) { - return 'slide'; - } - else { - return ''; - } - }; - - function loadSections(){ - sectionResource.getSections() - .then(function (result) { - scope.sections = result; - calculateHeight(); - }); - } - - function calculateHeight(){ - $timeout(function(){ - //total height minus room for avatar and help icon - var height = $(window).height()-200; - scope.totalSections = scope.sections.length; - scope.maxSections = Math.floor(height / 70); - scope.needTray = false; - - if(scope.totalSections > scope.maxSections){ - scope.needTray = true; - scope.overflowingSections = scope.maxSections - scope.totalSections; - } - }); - } - - var evts = []; - - //Listen for global state changes - evts.push(eventsService.on("appState.globalState.changed", function(e, args) { - if (args.key === "showTray") { - scope.showTray = args.value; - } - if (args.key === "stickyNavigation") { - scope.stickyNavigation = args.value; - } - })); - - evts.push(eventsService.on("appState.sectionState.changed", function(e, args) { - if (args.key === "currentSection") { - scope.currentSection = args.value; - } - })); - - evts.push(eventsService.on("app.reInitialize", function(e, args) { - //re-load the sections if we're re-initializing (i.e. package installed) - loadSections(); - })); - - //ensure to unregister from all events! - scope.$on('$destroy', function () { - for (var e in evts) { - eventsService.unsubscribe(evts[e]); - } - }); - - //on page resize - window.onresize = calculateHeight; - - scope.avatarClick = function(){ - - if(scope.helpDialog) { - closeHelpDialog(); - } - - if(!scope.userDialog) { - scope.userDialog = { - view: "user", - show: true, - close: function(oldModel) { - closeUserDialog(); - } - }; - } else { - closeUserDialog(); - } - - }; - - function closeUserDialog() { - scope.userDialog.show = false; - scope.userDialog = null; - } - - scope.helpClick = function(){ - - if(scope.userDialog) { - closeUserDialog(); - } - - if(!scope.helpDialog) { - scope.helpDialog = { - view: "help", - show: true, - close: function(oldModel) { - closeHelpDialog(); - } - }; - } else { - closeHelpDialog(); - } - - }; - - function closeHelpDialog() { - scope.helpDialog.show = false; - scope.helpDialog = null; - } - - scope.sectionClick = function (event, section) { - - if (event.ctrlKey || - event.shiftKey || - event.metaKey || // apple - (event.button && event.button === 1) // middle click, >IE9 + everyone else - ) { - return; - } - - if (scope.userDialog) { - closeUserDialog(); - } - if (scope.helpDialog) { - closeHelpDialog(); - } - - navigationService.hideSearch(); - navigationService.showTree(section.alias); - $location.path("/" + section.alias); - }; - - scope.sectionDblClick = function(section){ - navigationService.reloadSection(section.alias); - }; - - scope.trayClick = function () { - // close dialogs - if (scope.userDialog) { - closeUserDialog(); - } - if (scope.helpDialog) { - closeHelpDialog(); - } - - if (appState.getGlobalState("showTray") === true) { - navigationService.hideTray(); - } else { - navigationService.showTray(); - } - }; - - loadSections(); - - } - }; -} - -angular.module('umbraco.directives').directive("umbSections", sectionsDirective); - -/** + angular.module('umbraco.directives').directive('umbBackdrop', BackdropDirective); + }()); + angular.module('umbraco.directives').directive('umbContextMenu', function (navigationService) { + return { + scope: { + menuDialogTitle: '@', + currentSection: '@', + currentNode: '=', + menuActions: '=' + }, + restrict: 'E', + replace: true, + templateUrl: 'views/components/application/umb-contextmenu.html', + link: function (scope, element, attrs, ctrl) { + //adds a handler to the context menu item click, we need to handle this differently + //depending on what the menu item is supposed to do. + scope.executeMenuItem = function (action) { + navigationService.executeMenuAction(action, scope.currentNode, scope.currentSection); + }; + } + }; + }); + /** +@ngdoc directive +@name umbraco.directives.directive:umbDrawer +@restrict E +@scope + +@description +The drawer component is a global component and is already added to the umbraco markup. It is registered in globalState and can be opened and configured by raising events. + +

    Markup example - how to open the drawer

    +
    +    
    + + + + +
    +
    + +

    Controller example - how to open the drawer

    +
    +    (function () {
    +        "use strict";
    +
    +        function DrawerController(appState) {
    +
    +            var vm = this;
    +
    +            vm.toggleDrawer = toggleDrawer;
    +
    +            function toggleDrawer() {
    +
    +                var showDrawer = appState.getDrawerState("showDrawer");            
    +
    +                var model = {
    +                    firstName: "Super",
    +                    lastName: "Man"
    +                };
    +
    +                appState.setDrawerState("view", "/App_Plugins/path/to/drawer.html");
    +                appState.setDrawerState("model", model);
    +                appState.setDrawerState("showDrawer", !showDrawer);
    +                
    +            }
    +
    +        }
    +
    +        angular.module("umbraco").controller("My.DrawerController", DrawerController);
    +
    +    })();
    +
    + +

    Use the following components in the custom drawer to render the content

    +
      +
    • {@link umbraco.directives.directive:umbDrawerView umbDrawerView}
    • +
    • {@link umbraco.directives.directive:umbDrawerHeader umbDrawerHeader}
    • +
    • {@link umbraco.directives.directive:umbDrawerView umbDrawerContent}
    • +
    • {@link umbraco.directives.directive:umbDrawerFooter umbDrawerFooter}
    • +
    + +@param {string} view (binding): Set the drawer view +@param {string} model (binding): Pass in custom data to the drawer + +**/ + function Drawer($location, $routeParams, helpService, userService, localizationService, dashboardResource) { + return { + restrict: 'E', + // restrict to an element + replace: true, + // replace the html element with the template + templateUrl: 'views/components/application/umbdrawer/umb-drawer.html', + transclude: true, + scope: { + view: '=?', + model: '=?' + }, + link: function (scope, element, attr, ctrl) { + function onInit() { + setView(); + } + function setView() { + if (scope.view) { + //we do this to avoid a hidden dialog to start loading unconfigured views before the first activation + var configuredView = scope.view; + if (scope.view.indexOf('.html') === -1) { + var viewAlias = scope.view.toLowerCase(); + configuredView = 'views/common/drawers/' + viewAlias + '/' + viewAlias + '.html'; + } + if (configuredView !== scope.configuredView) { + scope.configuredView = configuredView; + } + } + } + onInit(); + } + }; + } + angular.module('umbraco.directives').directive('umbDrawer', Drawer); + /** +@ngdoc directive +@name umbraco.directives.directive:umbDrawerContent +@restrict E +@scope + +@description +Use this directive to render drawer content + +

    Markup example

    +
    +	
    +        
    +        
    +        
    +
    +        
    +            
    +            
    {{ model | json }}
    +
    + + + + + +
    +
    + + +

    Use in combination with

    +
      +
    • {@link umbraco.directives.directive:umbDrawerView umbDrawerView}
    • +
    • {@link umbraco.directives.directive:umbDrawerHeader umbDrawerHeader}
    • +
    • {@link umbraco.directives.directive:umbDrawerFooter umbDrawerFooter}
    • +
    + +**/ + (function () { + 'use strict'; + function DrawerContentDirective() { + var directive = { + restrict: 'E', + replace: true, + transclude: true, + templateUrl: 'views/components/application/umbdrawer/umb-drawer-content.html' + }; + return directive; + } + angular.module('umbraco.directives').directive('umbDrawerContent', DrawerContentDirective); + }()); + /** +@ngdoc directive +@name umbraco.directives.directive:umbDrawerFooter +@restrict E +@scope + +@description +Use this directive to render a drawer footer + +

    Markup example

    +
    +	
    +        
    +        
    +        
    +
    +        
    +            
    +            
    {{ model | json }}
    +
    + + + + + +
    +
    + +

    Use in combination with

    +
      +
    • {@link umbraco.directives.directive:umbDrawerView umbDrawerView}
    • +
    • {@link umbraco.directives.directive:umbDrawerHeader umbDrawerHeader}
    • +
    • {@link umbraco.directives.directive:umbDrawerContent umbDrawerContent}
    • +
    + +**/ + (function () { + 'use strict'; + function DrawerFooterDirective() { + var directive = { + restrict: 'E', + replace: true, + transclude: true, + templateUrl: 'views/components/application/umbdrawer/umb-drawer-footer.html' + }; + return directive; + } + angular.module('umbraco.directives').directive('umbDrawerFooter', DrawerFooterDirective); + }()); + /** +@ngdoc directive +@name umbraco.directives.directive:umbDrawerHeader +@restrict E +@scope + +@description +Use this directive to render a drawer header + +

    Markup example

    +
    +	
    +        
    +        
    +        
    +
    +        
    +            
    +            
    {{ model | json }}
    +
    + + + + + +
    +
    + +

    Use in combination with

    +
      +
    • {@link umbraco.directives.directive:umbDrawerView umbDrawerView}
    • +
    • {@link umbraco.directives.directive:umbDrawerContent umbDrawerContent}
    • +
    • {@link umbraco.directives.directive:umbDrawerFooter umbDrawerFooter}
    • +
    + +@param {string} title (attribute): Set a drawer title. +@param {string} description (attribute): Set a drawer description. +**/ + (function () { + 'use strict'; + function DrawerHeaderDirective() { + var directive = { + restrict: 'E', + replace: true, + templateUrl: 'views/components/application/umbdrawer/umb-drawer-header.html', + scope: { + 'title': '@?', + 'description': '@?' + } + }; + return directive; + } + angular.module('umbraco.directives').directive('umbDrawerHeader', DrawerHeaderDirective); + }()); + /** +@ngdoc directive +@name umbraco.directives.directive:umbDrawerView +@restrict E +@scope + +@description +Use this directive to render drawer view + +

    Markup example

    +
    +	
    +        
    +        
    +        
    +
    +        
    +            
    +            
    {{ model | json }}
    +
    + + + + + +
    +
    + +

    Use in combination with

    +
      +
    • {@link umbraco.directives.directive:umbDrawerHeader umbDrawerHeader}
    • +
    • {@link umbraco.directives.directive:umbDrawerContent umbDrawerContent}
    • +
    • {@link umbraco.directives.directive:umbDrawerFooter umbDrawerFooter}
    • +
    + +**/ + (function () { + 'use strict'; + function DrawerViewDirective() { + var directive = { + restrict: 'E', + replace: true, + transclude: true, + templateUrl: 'views/components/application/umbdrawer/umb-drawer-view.html' + }; + return directive; + } + angular.module('umbraco.directives').directive('umbDrawerView', DrawerViewDirective); + }()); + /** +* @ngdoc directive +* @name umbraco.directives.directive:umbNavigation +* @restrict E +**/ + function umbNavigationDirective() { + return { + restrict: 'E', + // restrict to an element + replace: true, + // replace the html element with the template + templateUrl: 'views/components/application/umb-navigation.html' + }; + } + angular.module('umbraco.directives').directive('umbNavigation', umbNavigationDirective); + /** +* @ngdoc directive +* @name umbraco.directives.directive:umbSections +* @restrict E +**/ + function sectionsDirective($timeout, $window, navigationService, treeService, sectionService, appState, eventsService, $location, historyService) { + return { + restrict: 'E', + // restrict to an element + replace: true, + // replace the html element with the template + templateUrl: 'views/components/application/umb-sections.html', + link: function (scope, element, attr, ctrl) { + //setup scope vars + scope.maxSections = 7; + scope.overflowingSections = 0; + scope.sections = []; + scope.currentSection = appState.getSectionState('currentSection'); + scope.showTray = false; + //appState.getGlobalState("showTray"); + scope.stickyNavigation = appState.getGlobalState('stickyNavigation'); + scope.needTray = false; + scope.trayAnimation = function () { + if (scope.showTray) { + return 'slide'; + } else if (scope.showTray === false) { + return 'slide'; + } else { + return ''; + } + }; + function loadSections() { + sectionService.getSectionsForUser().then(function (result) { + scope.sections = result; + calculateHeight(); + }); + } + function calculateHeight() { + $timeout(function () { + //total height minus room for avatar and help icon + var height = $(window).height() - 200; + scope.totalSections = scope.sections.length; + scope.maxSections = Math.floor(height / 70); + scope.needTray = false; + if (scope.totalSections > scope.maxSections) { + scope.needTray = true; + scope.overflowingSections = scope.maxSections - scope.totalSections; + } + }); + } + var evts = []; + //Listen for global state changes + evts.push(eventsService.on('appState.globalState.changed', function (e, args) { + if (args.key === 'showTray') { + scope.showTray = args.value; + } + if (args.key === 'stickyNavigation') { + scope.stickyNavigation = args.value; + } + })); + evts.push(eventsService.on('appState.sectionState.changed', function (e, args) { + if (args.key === 'currentSection') { + scope.currentSection = args.value; + } + })); + evts.push(eventsService.on('app.reInitialize', function (e, args) { + //re-load the sections if we're re-initializing (i.e. package installed) + loadSections(); + })); + //ensure to unregister from all events! + scope.$on('$destroy', function () { + for (var e in evts) { + eventsService.unsubscribe(evts[e]); + } + }); + //on page resize + window.onresize = calculateHeight; + scope.avatarClick = function () { + if (scope.helpDialog) { + closeHelpDialog(); + } + if (!scope.userDialog) { + scope.userDialog = { + view: 'user', + show: true, + close: function (oldModel) { + closeUserDialog(); + } + }; + } else { + closeUserDialog(); + } + }; + function closeUserDialog() { + scope.userDialog.show = false; + scope.userDialog = null; + } + //toggle the help dialog by raising the global app state to toggle the help drawer + scope.helpClick = function () { + var showDrawer = appState.getDrawerState('showDrawer'); + var drawer = { + view: 'help', + show: !showDrawer + }; + appState.setDrawerState('view', drawer.view); + appState.setDrawerState('showDrawer', drawer.show); + }; + scope.sectionClick = function (event, section) { + if (event.ctrlKey || event.shiftKey || event.metaKey || event.button && event.button === 1 // middle click, >IE9 + everyone else +) { + return; + } + if (scope.userDialog) { + closeUserDialog(); + } + navigationService.hideSearch(); + navigationService.showTree(section.alias); + //in some cases the section will have a custom route path specified, if there is one we'll use it + if (section.routePath) { + $location.path(section.routePath); + } else { + var lastAccessed = historyService.getLastAccessedItemForSection(section.alias); + var path = lastAccessed != null ? lastAccessed.link : section.alias; + $location.path(path).search(''); + } + }; + scope.sectionDblClick = function (section) { + navigationService.reloadSection(section.alias); + }; + scope.trayClick = function () { + // close dialogs + if (scope.userDialog) { + closeUserDialog(); + } + if (scope.helpDialog) { + closeHelpDialog(); + } + if (appState.getGlobalState('showTray') === true) { + navigationService.hideTray(); + } else { + navigationService.showTray(); + } + }; + loadSections(); + } + }; + } + angular.module('umbraco.directives').directive('umbSections', sectionsDirective); + /** +@ngdoc directive +@name umbraco.directives.directive:umbTour +@restrict E +@scope + +@description +Added in Umbraco 7.8. The tour component is a global component and is already added to the umbraco markup. +In the Umbraco UI the tours live in the "Help drawer" which opens when you click the Help-icon in the bottom left corner of Umbraco. +You can easily add you own tours to the Help-drawer or show and start tours from +anywhere in the Umbraco backoffice. To see a real world example of a custom tour implementation, install The Starter Kit in Umbraco 7.8 + +

    Extending the help drawer with custom tours

    +The easiet way to add new tours to Umbraco is through the Help-drawer. All it requires is a my-tour.json file. +Place the file in App_Plugins/{MyPackage}/backoffice/tours/{my-tour}.json and it will automatically be +picked up by Umbraco and shown in the Help-drawer. + +

    The tour object

    +The tour object consist of two parts - The overall tour configuration and a list of tour steps. We have split up the tour object for a better overview. +
    +// The tour config object
    +{
    +    "name": "My Custom Tour", // (required)
    +    "alias": "myCustomTour", // A unique tour alias (required)
    +    "group": "My Custom Group" // Used to group tours in the help drawer
    +    "groupOrder": 200 // Control the order of tour groups
    +    "allowDisable": // Adds a "Don't" show this tour again"-button to the intro step
    +    "culture" : // From v7.11+. Specifies the culture of the tour (eg. en-US), if set the tour will only be shown to users with this culture set on their profile. If omitted or left empty the tour will be visible to all users
    +    "requiredSections":["content", "media", "mySection"] // Sections that the tour will access while running, if the user does not have access to the required tour sections, the tour will not load.   
    +    "steps": [] // tour steps - see next example
    +}
    +
    +
    +// A tour step object
    +{
    +    "title": "Title",
    +    "content": "

    Step content

    ", + "type": "intro" // makes the step an introduction step, + "element": "[data-element='my-table-row']", // the highlighted element + "event": "click" // forces the user to click the UI to go to next step + "eventElement": "[data-element='my-table-row'] [data-element='my-tour-button']" // specify an element to click inside a highlighted element + "elementPreventClick": false // prevents user interaction in the highlighted element + "backdropOpacity": 0.4 // the backdrop opacity + "view": "" // add a custom view + "customProperties" : {} // add any custom properties needed for the custom view +} +
    + +

    Adding tours to other parts of the Umbraco backoffice

    +It is also possible to add a list of custom tours to other parts of the Umbraco backoffice, +as an example on a Dashboard in a Custom section. You can then use the {@link umbraco.services.tourService tourService} to start and stop tours but you don't have to register them as part of the tour service. + +

    Using the tour service

    +

    Markup example - show custom tour

    +
    +    
    + +
    {{vm.tour.name}}
    + + + + + +
    +
    + +

    Controller example - show custom tour

    +
    +    (function () {
    +        "use strict";
    +
    +        function TourController(tourService) {
    +
    +            var vm = this;
    +
    +            vm.tour = {
    +                "name": "My Custom Tour",
    +                "alias": "myCustomTour",
    +                "steps": [
    +                    {
    +                        "title": "Welcome to My Custom Tour",
    +                        "content": "",
    +                        "type": "intro"
    +                    },
    +                    {
    +                        "element": "[data-element='my-tour-button']",
    +                        "title": "Click the button",
    +                        "content": "Click the button",
    +                        "event": "click"
    +                    }
    +                ]
    +            };
    +
    +            vm.startTour = startTour;
    +
    +            function startTour() {
    +                tourService.startTour(vm.tour);
    +            }
    +
    +        }
    +
    +        angular.module("umbraco").controller("My.TourController", TourController);
    +
    +    })();
    +
    + +

    Custom step views

    +In some cases you will need a custom view for one of your tour steps. +This could be for validation or for running any other custom logic for that step. +We have added a couple of helper components to make it easier to get the step scaffolding to look like a regular tour step. +In the following example you see how to run some custom logic before a step goes to the next step. + +

    Markup example - custom step view

    +
    +    
    + + + + + + + + + + + + + + + + + +
    + + +
    + +
    + +
    + +
    +
    + +

    Controller example - custom step view

    +
    +    (function () {
    +        "use strict";
    +
    +        function StepController() {
    +
    +            var vm = this;
    +            
    +            vm.initNextStep = initNextStep;
    +
    +            function initNextStep() {
    +                // run logic here before going to the next step
    +                $scope.model.nextStep();
    +            }
    +
    +        }
    +
    +        angular.module("umbraco").controller("My.TourStep", StepController);
    +
    +    })();
    +
    + + +

    Related services

    +
      +
    • {@link umbraco.services.tourService tourService}
    • +
    + +@param {string} model (binding): Tour object + +**/ + (function () { + 'use strict'; + function TourDirective($timeout, $http, $q, tourService, backdropService) { + function link(scope, el, attr, ctrl) { + var popover; + var pulseElement; + var pulseTimer; + scope.loadingStep = false; + scope.elementNotFound = false; + scope.model.nextStep = function () { + nextStep(); + }; + scope.model.endTour = function () { + unbindEvent(); + tourService.endTour(scope.model); + backdropService.close(); + }; + scope.model.completeTour = function () { + unbindEvent(); + tourService.completeTour(scope.model).then(function () { + backdropService.close(); + }); + }; + scope.model.disableTour = function () { + unbindEvent(); + tourService.disableTour(scope.model).then(function () { + backdropService.close(); + }); + }; + function onInit() { + popover = el.find('.umb-tour__popover'); + pulseElement = el.find('.umb-tour__pulse'); + popover.hide(); + scope.model.currentStepIndex = 0; + backdropService.open({ disableEventsOnClick: true }); + startStep(); + } + function setView() { + if (scope.model.currentStep.view && scope.model.alias) { + //we do this to avoid a hidden dialog to start loading unconfigured views before the first activation + var configuredView = scope.model.currentStep.view; + if (scope.model.currentStep.view.indexOf('.html') === -1) { + var viewAlias = scope.model.currentStep.view.toLowerCase(); + var tourAlias = scope.model.alias.toLowerCase(); + configuredView = 'views/common/tours/' + tourAlias + '/' + viewAlias + '/' + viewAlias + '.html'; + } + if (configuredView !== scope.configuredView) { + scope.configuredView = configuredView; + } + } else { + scope.configuredView = null; + } + } + function nextStep() { + popover.hide(); + pulseElement.hide(); + $timeout.cancel(pulseTimer); + scope.model.currentStepIndex++; + // make sure we don't go too far + if (scope.model.currentStepIndex !== scope.model.steps.length) { + startStep(); // tour completed - final step + } else { + scope.loadingStep = true; + waitForPendingRerequests().then(function () { + scope.loadingStep = false; + // clear current step + scope.model.currentStep = {}; + // set popover position to center + setPopoverPosition(null); + // remove backdrop hightlight and custom opacity + backdropService.setHighlight(null); + backdropService.setOpacity(null); + }); + } + } + function startStep() { + scope.loadingStep = true; + backdropService.setOpacity(scope.model.steps[scope.model.currentStepIndex].backdropOpacity); + backdropService.setHighlight(null); + waitForPendingRerequests().then(function () { + scope.model.currentStep = scope.model.steps[scope.model.currentStepIndex]; + setView(); + // if highlight element is set - find it + findHighlightElement(); + // if a custom event needs to be bound we do it now + if (scope.model.currentStep.event) { + bindEvent(); + } + scope.loadingStep = false; + }); + } + function findHighlightElement() { + scope.elementNotFound = false; + $timeout(function () { + // clear element when step as marked as intro, so it always displays in the center + if (scope.model.currentStep && scope.model.currentStep.type === 'intro') { + scope.model.currentStep.element = null; + scope.model.currentStep.eventElement = null; + scope.model.currentStep.event = null; + } + // if an element isn't set - show the popover in the center + if (scope.model.currentStep && !scope.model.currentStep.element) { + setPopoverPosition(null); + return; + } + var element = angular.element(scope.model.currentStep.element); + // we couldn't find the element in the dom - abort and show error + if (element.length === 0) { + scope.elementNotFound = true; + setPopoverPosition(null); + return; + } + var scrollParent = element.scrollParent(); + var scrollToCenterOfContainer = element[0].offsetTop - scrollParent[0].clientHeight / 2 + element[0].clientHeight / 2; + // Detect if scroll is needed + if (element[0].offsetTop > scrollParent[0].clientHeight) { + scrollParent.animate({ scrollTop: scrollToCenterOfContainer }, function () { + // Animation complete. + setPopoverPosition(element); + setPulsePosition(); + backdropService.setHighlight(scope.model.currentStep.element, scope.model.currentStep.elementPreventClick); + }); + } else { + setPopoverPosition(element); + setPulsePosition(); + backdropService.setHighlight(scope.model.currentStep.element, scope.model.currentStep.elementPreventClick); + } + }); + } + function setPopoverPosition(element) { + $timeout(function () { + var position = 'center'; + var margin = 20; + var css = {}; + var popoverWidth = popover.outerWidth(); + var popoverHeight = popover.outerHeight(); + var popoverOffset = popover.offset(); + var documentWidth = angular.element(document).width(); + var documentHeight = angular.element(document).height(); + if (element) { + var offset = element.offset(); + var width = element.outerWidth(); + var height = element.outerHeight(); + // messure available space on each side of the target element + var space = { + 'top': offset.top, + 'right': documentWidth - (offset.left + width), + 'bottom': documentHeight - (offset.top + height), + 'left': offset.left + }; + // get the posistion with most available space + position = findMax(space); + if (position === 'top') { + if (offset.left < documentWidth / 2) { + css.top = offset.top - popoverHeight - margin; + css.left = offset.left; + } else { + css.top = offset.top - popoverHeight - margin; + css.left = offset.left - popoverWidth + width; + } + } + if (position === 'right') { + if (offset.top < documentHeight / 2) { + css.top = offset.top; + css.left = offset.left + width + margin; + } else { + css.top = offset.top + height - popoverHeight; + css.left = offset.left + width + margin; + } + } + if (position === 'bottom') { + if (offset.left < documentWidth / 2) { + css.top = offset.top + height + margin; + css.left = offset.left; + } else { + css.top = offset.top + height + margin; + css.left = offset.left - popoverWidth + width; + } + } + if (position === 'left') { + if (offset.top < documentHeight / 2) { + css.top = offset.top; + css.left = offset.left - popoverWidth - margin; + } else { + css.top = offset.top + height - popoverHeight; + css.left = offset.left - popoverWidth - margin; + } + } + } else { + // if there is no dom element center the popover + css.top = 'calc(50% - ' + popoverHeight / 2 + 'px)'; + css.left = 'calc(50% - ' + popoverWidth / 2 + 'px)'; + } + popover.css(css).fadeIn('fast'); + }); + } + function setPulsePosition() { + if (scope.model.currentStep.event) { + pulseTimer = $timeout(function () { + var clickElementSelector = scope.model.currentStep.eventElement ? scope.model.currentStep.eventElement : scope.model.currentStep.element; + var clickElement = $(clickElementSelector); + var offset = clickElement.offset(); + var width = clickElement.outerWidth(); + var height = clickElement.outerHeight(); + pulseElement.css({ + 'width': width, + 'height': height, + 'left': offset.left, + 'top': offset.top + }); + pulseElement.fadeIn(); + }, 1000); + } + } + function waitForPendingRerequests() { + var deferred = $q.defer(); + var timer = window.setInterval(function () { + // check for pending requests both in angular and on the document + if ($http.pendingRequests.length === 0 && document.readyState === 'complete') { + $timeout(function () { + deferred.resolve(); + clearInterval(timer); + }); + } + }, 50); + return deferred.promise; + } + function findMax(obj) { + var keys = Object.keys(obj); + var max = keys[0]; + for (var i = 1, n = keys.length; i < n; ++i) { + var k = keys[i]; + if (obj[k] > obj[max]) { + max = k; + } + } + return max; + } + function bindEvent() { + var bindToElement = scope.model.currentStep.element; + var eventName = scope.model.currentStep.event + '.step-' + scope.model.currentStepIndex; + var removeEventName = 'remove.step-' + scope.model.currentStepIndex; + var handled = false; + if (scope.model.currentStep.eventElement) { + bindToElement = scope.model.currentStep.eventElement; + } + $(bindToElement).on(eventName, function () { + if (!handled) { + unbindEvent(); + nextStep(); + handled = true; + } + }); + // Hack: we do this to handle cases where ng-if is used and removes the element we need to click. + // for some reason it seems the elements gets removed before the event is raised. This is a temp solution which assumes: + // "if you ask me to click on an element, and it suddenly gets removed from the dom, let's go on to the next step". + $(bindToElement).on(removeEventName, function () { + if (!handled) { + unbindEvent(); + nextStep(); + handled = true; + } + }); + } + function unbindEvent() { + var eventName = scope.model.currentStep.event + '.step-' + scope.model.currentStepIndex; + var removeEventName = 'remove.step-' + scope.model.currentStepIndex; + if (scope.model.currentStep.eventElement) { + angular.element(scope.model.currentStep.eventElement).off(eventName); + angular.element(scope.model.currentStep.eventElement).off(removeEventName); + } else { + angular.element(scope.model.currentStep.element).off(eventName); + angular.element(scope.model.currentStep.element).off(removeEventName); + } + } + function resize() { + findHighlightElement(); + } + onInit(); + $(window).on('resize.umbTour', resize); + scope.$on('$destroy', function () { + $(window).off('resize.umbTour'); + unbindEvent(); + $timeout.cancel(pulseTimer); + }); + } + var directive = { + transclude: true, + restrict: 'E', + replace: true, + templateUrl: 'views/components/application/umb-tour.html', + link: link, + scope: { model: '=' } + }; + return directive; + } + angular.module('umbraco.directives').directive('umbTour', TourDirective); + }()); + (function () { + 'use strict'; + function TourStepDirective() { + function link(scope, element, attrs, ctrl) { + scope.close = function () { + if (scope.onClose) { + scope.onClose(); + } + }; + } + var directive = { + restrict: 'E', + replace: true, + transclude: true, + templateUrl: 'views/components/application/umbtour/umb-tour-step.html', + scope: { + size: '@?', + onClose: '&?', + hideClose: '=?' + }, + link: link + }; + return directive; + } + angular.module('umbraco.directives').directive('umbTourStep', TourStepDirective); + }()); + (function () { + 'use strict'; + function TourStepContentDirective() { + var directive = { + restrict: 'E', + replace: true, + transclude: true, + templateUrl: 'views/components/application/umbtour/umb-tour-step-content.html', + scope: { content: '=' } + }; + return directive; + } + angular.module('umbraco.directives').directive('umbTourStepContent', TourStepContentDirective); + }()); + (function () { + 'use strict'; + function TourStepCounterDirective() { + var directive = { + restrict: 'E', + replace: true, + templateUrl: 'views/components/application/umbtour/umb-tour-step-counter.html', + scope: { + currentStep: '=', + totalSteps: '=' + } + }; + return directive; + } + angular.module('umbraco.directives').directive('umbTourStepCounter', TourStepCounterDirective); + }()); + (function () { + 'use strict'; + function TourStepFooterDirective() { + var directive = { + restrict: 'E', + replace: true, + transclude: true, + templateUrl: 'views/components/application/umbtour/umb-tour-step-footer.html' + }; + return directive; + } + angular.module('umbraco.directives').directive('umbTourStepFooter', TourStepFooterDirective); + }()); + (function () { + 'use strict'; + function TourStepHeaderDirective() { + var directive = { + restrict: 'E', + replace: true, + transclude: true, + templateUrl: 'views/components/application/umbtour/umb-tour-step-header.html', + scope: { title: '=' } + }; + return directive; + } + angular.module('umbraco.directives').directive('umbTourStepHeader', TourStepHeaderDirective); + }()); + /** @ngdoc directive @name umbraco.directives.directive:umbButton @restrict E @@ -1139,84 +1922,87 @@ Use this directive to render an umbraco button. The directive can be used to gen @param {callback} action The button action which should be performed when the button is clicked. @param {string=} href Url/Path to navigato to. @param {string=} type Set the button type ("button" or "submit"). -@param {string=} buttonStyle Set the style of the button. The directive uses the default bootstrap styles ("primary", "info", "success", "warning", "danger", "inverse", "link"). +@param {string=} buttonStyle Set the style of the button. The directive uses the default bootstrap styles ("primary", "info", "success", "warning", "danger", "inverse", "link", "block"). Pass in array to add multple styles [success,block]. @param {string=} state Set a progress state on the button ("init", "busy", "success", "error"). @param {string=} shortcut Set a keyboard shortcut for the button ("ctrl+c"). @param {string=} label Set the button label. @param {string=} labelKey Set a localization key to make a multi lingual button ("general_buttonText"). -@param {string=} icon Set a button icon. Can only be used when buttonStyle is "link". +@param {string=} icon Set a button icon. +@param {string=} size Set a button icon ("xs", "m", "l", "xl"). @param {boolean=} disabled Set to true to disable the button. **/ - -(function() { - 'use strict'; - - function ButtonDirective($timeout) { - - function link(scope, el, attr, ctrl) { - - scope.style = null; - - function activate() { - - if (!scope.state) { - scope.state = "init"; + (function () { + 'use strict'; + function ButtonDirective($timeout) { + function link(scope, el, attr, ctrl) { + scope.style = null; + scope.innerState = 'init'; + function activate() { + scope.blockElement = false; + if (scope.buttonStyle) { + // make it possible to pass in multiple styles + if (scope.buttonStyle.startsWith('[') && scope.buttonStyle.endsWith(']')) { + // when using an attr it will always be a string so we need to remove square brackets + // and turn it into and array + var withoutBrackets = scope.buttonStyle.replace(/[\[\]']+/g, ''); + // split array by , + make sure to catch whitespaces + var array = withoutBrackets.split(/\s?,\s?/g); + angular.forEach(array, function (item) { + scope.style = scope.style + ' ' + 'btn-' + item; + if (item === 'block') { + scope.blockElement = true; + } + }); + } else { + scope.style = 'btn-' + scope.buttonStyle; + if (scope.buttonStyle === 'block') { + scope.blockElement = true; + } + } + } + } + activate(); + var unbindStateWatcher = scope.$watch('state', function (newValue, oldValue) { + if (newValue) { + scope.innerState = newValue; + } + if (newValue === 'success' || newValue === 'error') { + $timeout(function () { + scope.innerState = 'init'; + }, 2000); + } + }); + scope.$on('$destroy', function () { + unbindStateWatcher(); + }); } - - if (scope.buttonStyle) { - scope.style = "btn-" + scope.buttonStyle; - } - - } - - activate(); - - var unbindStateWatcher = scope.$watch('state', function(newValue, oldValue) { - - if (newValue === 'success' || newValue === 'error') { - $timeout(function() { - scope.state = 'init'; - }, 2000); - } - - }); - - scope.$on('$destroy', function() { - unbindStateWatcher(); - }); - - } - - var directive = { - transclude: true, - restrict: 'E', - replace: true, - templateUrl: 'views/components/buttons/umb-button.html', - link: link, - scope: { - action: "&?", - href: "@?", - type: "@", - buttonStyle: "@?", - state: "=?", - shortcut: "@?", - shortcutWhenHidden: "@", - label: "@?", - labelKey: "@?", - icon: "@?", - disabled: "=" - } - }; - - return directive; - - } - - angular.module('umbraco.directives').directive('umbButton', ButtonDirective); - -})(); - -/** + var directive = { + transclude: true, + restrict: 'E', + replace: true, + templateUrl: 'views/components/buttons/umb-button.html', + link: link, + scope: { + action: '&?', + href: '@?', + type: '@', + buttonStyle: '@?', + state: '=?', + shortcut: '@?', + shortcutWhenHidden: '@', + label: '@?', + labelKey: '@?', + icon: '@?', + disabled: '=', + size: '@?', + alias: '@?' + } + }; + return directive; + } + angular.module('umbraco.directives').directive('umbButton', ButtonDirective); + }()); + /** @ngdoc directive @name umbraco.directives.directive:umbButtonGroup @restrict E @@ -1306,33 +2092,709 @@ Use this directive to render a button with a dropdown of alternative actions. @param {string=} direction Set the direction of the dropdown ("up", "down"). @param {string=} float Set the float of the dropdown. ("left", "right"). **/ + (function () { + 'use strict'; + function ButtonGroupDirective() { + var directive = { + restrict: 'E', + replace: true, + templateUrl: 'views/components/buttons/umb-button-group.html', + scope: { + defaultButton: '=', + subButtons: '=', + state: '=?', + direction: '@?', + float: '@?' + } + }; + return directive; + } + angular.module('umbraco.directives').directive('umbButtonGroup', ButtonGroupDirective); + }()); + /** +@ngdoc directive +@name umbraco.directives.directive:umbToggle +@restrict E +@scope -(function() { - 'use strict'; +@description +Added in Umbraco version 7.7.0 Use this directive to render an umbraco toggle. - function ButtonGroupDirective() { +

    Markup example

    +
    +    
    - var directive = { - restrict: 'E', - replace: true, - templateUrl: 'views/components/buttons/umb-button-group.html', - scope: { - defaultButton: "=", - subButtons: "=", - state: "=?", - direction: "@?", - float: "@?" - } - }; + + - return directive; - } + + - angular.module('umbraco.directives').directive('umbButtonGroup', ButtonGroupDirective); +
    +
    -})(); - -/** +

    Controller example

    +
    +    (function () {
    +        "use strict";
    +
    +        function Controller() {
    +
    +            var vm = this;
    +            vm.checked = false;
    +
    +            vm.toggle = toggle;
    +
    +            function toggle() {
    +                vm.checked = !vm.checked;
    +            }
    +        }
    +
    +        angular.module("umbraco").controller("My.Controller", Controller);
    +
    +    })();
    +
    + +@param {boolean} checked Set to true or false to toggle the switch. +@param {callback} onClick The function which should be called when the toggle is clicked. +@param {string=} showLabels Set to true or false to show a "On" or "Off" label next to the switch. +@param {string=} labelOn Set a custom label for when the switched is turned on. It will default to "On". +@param {string=} labelOff Set a custom label for when the switched is turned off. It will default to "Off". +@param {string=} labelPosition Sets the label position to the left or right of the switch. It will default to "left" ("left", "right"). +@param {string=} hideIcons Set to true or false to hide the icons on the switch. + +**/ + (function () { + 'use strict'; + function ToggleDirective(localizationService, eventsService) { + function link(scope, el, attr, ctrl) { + scope.displayLabelOn = ''; + scope.displayLabelOff = ''; + function onInit() { + setLabelText(); + eventsService.emit('toggleValue', { value: scope.checked }); + } + function setLabelText() { + // set default label for "on" + if (scope.labelOn) { + scope.displayLabelOn = scope.labelOn; + } else { + localizationService.localize('general_on').then(function (value) { + scope.displayLabelOn = value; + }); + } + // set default label for "Off" + if (scope.labelOff) { + scope.displayLabelOff = scope.labelOff; + } else { + localizationService.localize('general_off').then(function (value) { + scope.displayLabelOff = value; + }); + } + } + scope.click = function () { + if (scope.onClick) { + eventsService.emit('toggleValue', { value: !scope.checked }); + scope.onClick(); + } + }; + onInit(); + } + var directive = { + restrict: 'E', + replace: true, + templateUrl: 'views/components/buttons/umb-toggle.html', + scope: { + checked: '=', + onClick: '&', + labelOn: '@?', + labelOff: '@?', + labelPosition: '@?', + showLabels: '@?', + hideIcons: '@?' + }, + link: link + }; + return directive; + } + angular.module('umbraco.directives').directive('umbToggle', ToggleDirective); + }()); + (function () { + 'use strict'; + function ContentEditController($rootScope, $scope, $routeParams, $q, $timeout, $window, $location, appState, contentResource, entityResource, navigationService, notificationsService, angularHelper, serverValidationManager, contentEditingHelper, treeService, fileManager, formHelper, umbRequestHelper, keyboardService, umbModelMapper, editorState, $http, eventsService, relationResource) { + var evts = []; + //setup scope vars + $scope.defaultButton = null; + $scope.subButtons = []; + $scope.page = {}; + $scope.page.loading = false; + $scope.page.menu = {}; + $scope.page.menu.currentNode = null; + $scope.page.menu.currentSection = appState.getSectionState('currentSection'); + $scope.page.listViewPath = null; + $scope.page.isNew = $scope.isNew ? true : false; + $scope.page.buttonGroupState = 'init'; + $scope.allowOpen = true; + function init(content) { + createButtons(content); + editorState.set($scope.content); + //We fetch all ancestors of the node to generate the footer breadcrumb navigation + if (!$scope.page.isNew) { + if (content.parentId && content.parentId !== -1) { + entityResource.getAncestors(content.id, 'document').then(function (anc) { + $scope.ancestors = anc; + }); + } + } + evts.push(eventsService.on('editors.content.changePublishDate', function (event, args) { + createButtons(args.node); + })); + evts.push(eventsService.on('editors.content.changeUnpublishDate', function (event, args) { + createButtons(args.node); + })); + // We don't get the info tab from the server from version 7.8 so we need to manually add it + contentEditingHelper.addInfoTab($scope.content.tabs); + } + function getNode() { + $scope.page.loading = true; + //we are editing so get the content item from the server + $scope.getMethod()($scope.contentId).then(function (data) { + $scope.content = data; + if (data.isChildOfListView && data.trashed === false) { + $scope.page.listViewPath = $routeParams.page ? '/content/content/edit/' + data.parentId + '?page=' + $routeParams.page : '/content/content/edit/' + data.parentId; + } + init($scope.content); + //in one particular special case, after we've created a new item we redirect back to the edit + // route but there might be server validation errors in the collection which we need to display + // after the redirect, so we will bind all subscriptions which will show the server validation errors + // if there are any and then clear them so the collection no longer persists them. + serverValidationManager.executeAndClearAllSubscriptions(); + syncTreeNode($scope.content, data.path, true); + resetLastListPageNumber($scope.content); + eventsService.emit('content.loaded', { content: $scope.content }); + $scope.page.loading = false; + }); + } + function createButtons(content) { + $scope.page.buttonGroupState = 'init'; + var buttons = contentEditingHelper.configureContentEditorButtons({ + create: $scope.page.isNew, + content: content, + methods: { + saveAndPublish: $scope.saveAndPublish, + sendToPublish: $scope.sendToPublish, + save: $scope.save, + unPublish: $scope.unPublish + } + }); + $scope.defaultButton = buttons.defaultButton; + $scope.subButtons = buttons.subButtons; + } + /** Syncs the content item to it's tree node - this occurs on first load and after saving */ + function syncTreeNode(content, path, initialLoad) { + if (!$scope.content.isChildOfListView) { + navigationService.syncTree({ + tree: $scope.treeAlias, + path: path.split(','), + forceReload: initialLoad !== true + }).then(function (syncArgs) { + $scope.page.menu.currentNode = syncArgs.node; + }); + } else if (initialLoad === true) { + //it's a child item, just sync the ui node to the parent + navigationService.syncTree({ + tree: $scope.treeAlias, + path: path.substring(0, path.lastIndexOf(',')).split(','), + forceReload: initialLoad !== true + }); + //if this is a child of a list view and it's the initial load of the editor, we need to get the tree node + // from the server so that we can load in the actions menu. + umbRequestHelper.resourcePromise($http.get(content.treeNodeUrl), 'Failed to retrieve data for child node ' + content.id).then(function (node) { + $scope.page.menu.currentNode = node; + }); + } + } + // This is a helper method to reduce the amount of code repitition for actions: Save, Publish, SendToPublish + function performSave(args) { + var deferred = $q.defer(); + $scope.page.buttonGroupState = 'busy'; + eventsService.emit('content.saving', { + content: $scope.content, + action: args.action + }); + contentEditingHelper.contentEditorPerformSave({ + statusMessage: args.statusMessage, + saveMethod: args.saveMethod, + scope: $scope, + content: $scope.content, + action: args.action + }).then(function (data) { + //success + init($scope.content); + syncTreeNode($scope.content, data.path); + $scope.page.buttonGroupState = 'success'; + deferred.resolve(data); + eventsService.emit('content.saved', { + content: $scope.content, + action: args.action + }); + }, function (err) { + //error + if (err) { + editorState.set($scope.content); + } + $scope.page.buttonGroupState = 'error'; + deferred.reject(err); + }); + return deferred.promise; + } + function resetLastListPageNumber(content) { + // We're using rootScope to store the page number for list views, so if returning to the list + // we can restore the page. If we've moved on to edit a piece of content that's not the list or it's children + // we should remove this so as not to confuse if navigating to a different list + if (!content.isChildOfListView && !content.isContainer) { + $rootScope.lastListViewPageViewed = null; + } + } + if ($scope.page.isNew) { + $scope.page.loading = true; + //we are creating so get an empty content item + $scope.getScaffoldMethod()().then(function (data) { + $scope.content = data; + init($scope.content); + resetLastListPageNumber($scope.content); + $scope.page.loading = false; + eventsService.emit('content.newReady', { content: $scope.content }); + }); + } else { + getNode(); + } + $scope.unPublish = function () { + // raising the event triggers the confirmation dialog + if (!notificationsService.hasView()) { + notificationsService.add({ view: 'confirmunpublish' }); + } + $scope.page.buttonGroupState = 'busy'; + // actioning the dialog raises the confirmUnpublish event, act on it here + var actioned = $rootScope.$on('content.confirmUnpublish', function (event, confirmed) { + if (confirmed && formHelper.submitForm({ + scope: $scope, + statusMessage: 'Unpublishing...', + skipValidation: true + })) { + eventsService.emit('content.unpublishing', { content: $scope.content }); + contentResource.unPublish($scope.content.id).then(function (data) { + formHelper.resetForm({ + scope: $scope, + notifications: data.notifications + }); + contentEditingHelper.handleSuccessfulSave({ + scope: $scope, + savedContent: data, + rebindCallback: contentEditingHelper.reBindChangedProperties($scope.content, data) + }); + init($scope.content); + syncTreeNode($scope.content, data.path); + $scope.page.buttonGroupState = 'success'; + eventsService.emit('content.unpublished', { content: $scope.content }); + }, function (err) { + formHelper.showNotifications(err.data); + $scope.page.buttonGroupState = 'error'; + }); + } else { + $scope.page.buttonGroupState = 'init'; + } + // unsubscribe to avoid queueing notifications + // listener is re-bound when the unpublish button is clicked so it is created just-in-time + actioned(); + }); + }; + $scope.sendToPublish = function () { + return performSave({ + saveMethod: contentResource.sendToPublish, + statusMessage: 'Sending...', + action: 'sendToPublish' + }); + }; + $scope.saveAndPublish = function () { + return performSave({ + saveMethod: contentResource.publish, + statusMessage: 'Publishing...', + action: 'publish' + }); + }; + $scope.save = function () { + return performSave({ + saveMethod: $scope.saveMethod(), + statusMessage: 'Saving...', + action: 'save' + }); + }; + $scope.preview = function (content) { + if (!$scope.busy) { + // Chromes popup blocker will kick in if a window is opened + // without the initial scoped request. This trick will fix that. + // + var previewWindow = $window.open('preview/?init=true&id=' + content.id, 'umbpreview'); + // Build the correct path so both /#/ and #/ work. + var redirect = Umbraco.Sys.ServerVariables.umbracoSettings.umbracoPath + '/preview/?id=' + content.id; + //The user cannot save if they don't have access to do that, in which case we just want to preview + //and that's it otherwise they'll get an unauthorized access message + if (!_.contains(content.allowedActions, 'A')) { + previewWindow.location.href = redirect; + } else { + $scope.save().then(function (data) { + previewWindow.location.href = redirect; + }); + } + } + }; + $scope.restore = function (content) { + $scope.page.buttonRestore = 'busy'; + relationResource.getByChildId(content.id, 'relateParentDocumentOnDelete').then(function (data) { + var relation = null; + var target = null; + var error = { + headline: 'Cannot automatically restore this item', + content: 'Use the Move menu item to move it manually' + }; + if (data.length == 0) { + notificationsService.error(error.headline, 'There is no \'restore\' relation found for this node. Use the Move menu item to move it manually.'); + $scope.page.buttonRestore = 'error'; + return; + } + relation = data[0]; + if (relation.parentId == -1) { + target = { + id: -1, + name: 'Root' + }; + moveNode(content, target); + } else { + contentResource.getById(relation.parentId).then(function (data) { + target = data; + // make sure the target item isn't in the recycle bin + if (target.path.indexOf('-20') !== -1) { + notificationsService.error(error.headline, 'The item you want to restore it under (' + target.name + ') is in the recycle bin. Use the Move menu item to move the item manually.'); + $scope.page.buttonRestore = 'error'; + return; + } + moveNode(content, target); + }, function (err) { + $scope.page.buttonRestore = 'error'; + notificationsService.error(error.headline, error.content); + }); + } + }, function (err) { + $scope.page.buttonRestore = 'error'; + notificationsService.error(error.headline, error.content); + }); + }; + function moveNode(node, target) { + contentResource.move({ + 'parentId': target.id, + 'id': node.id + }).then(function (path) { + // remove the node that we're working on + if ($scope.page.menu.currentNode) { + treeService.removeNode($scope.page.menu.currentNode); + } + // sync the destination node + navigationService.syncTree({ + tree: 'content', + path: path, + forceReload: true, + activate: false + }); + $scope.page.buttonRestore = 'success'; + notificationsService.success('Successfully restored ' + node.name + ' to ' + target.name); + // reload the node + getNode(); + }, function (err) { + $scope.page.buttonRestore = 'error'; + notificationsService.error('Cannot automatically restore this item', err); + }); + } + //ensure to unregister from all events! + $scope.$on('$destroy', function () { + for (var e in evts) { + eventsService.unsubscribe(evts[e]); + } + }); + } + function createDirective() { + var directive = { + restrict: 'E', + replace: true, + templateUrl: 'views/components/content/edit.html', + controller: 'Umbraco.Editors.Content.EditorDirectiveController', + scope: { + contentId: '=', + isNew: '=?', + treeAlias: '@', + page: '=?', + saveMethod: '&', + getMethod: '&', + getScaffoldMethod: '&?' + } + }; + return directive; + } + angular.module('umbraco.directives').controller('Umbraco.Editors.Content.EditorDirectiveController', ContentEditController); + angular.module('umbraco.directives').directive('contentEditor', createDirective); + }()); + (function () { + 'use strict'; + function ContentNodeInfoDirective($timeout, $location, logResource, eventsService, userService, localizationService, dateHelper) { + function link(scope, element, attrs, ctrl) { + var evts = []; + var isInfoTab = false; + scope.publishStatus = {}; + scope.disableTemplates = Umbraco.Sys.ServerVariables.features.disabledFeatures.disableTemplates; + function onInit() { + // If logged in user has access to the settings section + // show the open anchors - if the user doesn't have + // access, documentType is null, see ContentModelMapper + scope.allowOpen = scope.node.documentType !== null; + scope.datePickerConfig = { + pickDate: true, + pickTime: true, + useSeconds: false, + format: 'YYYY-MM-DD HH:mm', + icons: { + time: 'icon-time', + date: 'icon-calendar', + up: 'icon-chevron-up', + down: 'icon-chevron-down' + } + }; + scope.auditTrailOptions = { 'id': scope.node.id }; + // get available templates + scope.availableTemplates = scope.node.allowedTemplates; + // get document type details + scope.documentType = scope.node.documentType; + // make sure dates are formatted to the user's locale + formatDatesToLocal(); + // Make sure to set the node status + setNodePublishStatus(scope.node); + // Declare a fallback URL for the directive + if (scope.documentType !== null) { + scope.previewOpenUrl = '#/settings/documenttypes/edit/' + scope.documentType.id; + } + } + scope.auditTrailPageChange = function (pageNumber) { + scope.auditTrailOptions.pageNumber = pageNumber; + loadAuditTrail(); + }; + scope.openDocumentType = function (documentType) { + var url = '/settings/documenttypes/edit/' + documentType.id; + $location.url(url); + }; + scope.openTemplate = function () { + var url = '/settings/templates/edit/' + scope.node.templateId; + $location.url(url); + }; + scope.updateTemplate = function (templateAlias) { + // update template value + scope.node.template = templateAlias; + }; + scope.datePickerChange = function (event, type) { + if (type === 'publish') { + setPublishDate(event.date.format('YYYY-MM-DD HH:mm')); + } else if (type === 'unpublish') { + setUnpublishDate(event.date.format('YYYY-MM-DD HH:mm')); + } + }; + scope.clearPublishDate = function () { + clearPublishDate(); + }; + scope.clearUnpublishDate = function () { + clearUnpublishDate(); + }; + function loadAuditTrail() { + scope.loadingAuditTrail = true; + logResource.getPagedEntityLog(scope.auditTrailOptions).then(function (data) { + // get current backoffice user and format dates + userService.getCurrentUser().then(function (currentUser) { + angular.forEach(data.items, function (item) { + item.timestampFormatted = dateHelper.getLocalDate(item.timestamp, currentUser.locale, 'LLL'); + }); + }); + scope.auditTrail = data.items; + scope.auditTrailOptions.pageNumber = data.pageNumber; + scope.auditTrailOptions.pageSize = data.pageSize; + scope.auditTrailOptions.totalItems = data.totalItems; + scope.auditTrailOptions.totalPages = data.totalPages; + setAuditTrailLogTypeColor(scope.auditTrail); + scope.loadingAuditTrail = false; + }); + } + function setAuditTrailLogTypeColor(auditTrail) { + angular.forEach(auditTrail, function (item) { + switch (item.logType) { + case 'Publish': + item.logTypeColor = 'success'; + break; + case 'UnPublish': + case 'Delete': + item.logTypeColor = 'danger'; + break; + default: + item.logTypeColor = 'gray'; + } + }); + } + function setNodePublishStatus(node) { + // deleted node + if (node.trashed === true) { + scope.publishStatus.label = localizationService.localize('general_deleted'); + scope.publishStatus.color = 'danger'; + } + // unpublished node + if (node.published === false && node.trashed === false) { + scope.publishStatus.label = localizationService.localize('content_unpublished'); + scope.publishStatus.color = 'gray'; + } + // published node + if (node.hasPublishedVersion === true && node.publishDate && node.published === true) { + scope.publishStatus.label = localizationService.localize('content_published'); + scope.publishStatus.color = 'success'; + } + // published node with pending changes + if (node.hasPublishedVersion === true && node.publishDate && node.published === false) { + scope.publishStatus.label = localizationService.localize('content_publishedPendingChanges'); + scope.publishStatus.color = 'success'; + } + } + function setPublishDate(date) { + if (!date) { + return; + } + //The date being passed in here is the user's local date/time that they have selected + //we need to convert this date back to the server date on the model. + var serverTime = dateHelper.convertToServerStringTime(moment(date), Umbraco.Sys.ServerVariables.application.serverTimeOffset); + // update publish value + scope.node.releaseDate = serverTime; + // make sure dates are formatted to the user's locale + formatDatesToLocal(); + // emit event + var args = { + node: scope.node, + date: date + }; + eventsService.emit('editors.content.changePublishDate', args); + } + function clearPublishDate() { + // update publish value + scope.node.releaseDate = null; + // emit event + var args = { + node: scope.node, + date: null + }; + eventsService.emit('editors.content.changePublishDate', args); + } + function setUnpublishDate(date) { + if (!date) { + return; + } + //The date being passed in here is the user's local date/time that they have selected + //we need to convert this date back to the server date on the model. + var serverTime = dateHelper.convertToServerStringTime(moment(date), Umbraco.Sys.ServerVariables.application.serverTimeOffset); + // update publish value + scope.node.removeDate = serverTime; + // make sure dates are formatted to the user's locale + formatDatesToLocal(); + // emit event + var args = { + node: scope.node, + date: date + }; + eventsService.emit('editors.content.changeUnpublishDate', args); + } + function clearUnpublishDate() { + // update publish value + scope.node.removeDate = null; + // emit event + var args = { + node: scope.node, + date: null + }; + eventsService.emit('editors.content.changeUnpublishDate', args); + } + function ucfirst(string) { + return string.charAt(0).toUpperCase() + string.slice(1); + } + function formatDatesToLocal() { + // get current backoffice user and format dates + userService.getCurrentUser().then(function (currentUser) { + scope.node.createDateFormatted = dateHelper.getLocalDate(scope.node.createDate, currentUser.locale, 'LLL'); + scope.node.releaseDateYear = scope.node.releaseDate ? ucfirst(dateHelper.getLocalDate(scope.node.releaseDate, currentUser.locale, 'YYYY')) : null; + scope.node.releaseDateMonth = scope.node.releaseDate ? ucfirst(dateHelper.getLocalDate(scope.node.releaseDate, currentUser.locale, 'MMMM')) : null; + scope.node.releaseDateDayNumber = scope.node.releaseDate ? ucfirst(dateHelper.getLocalDate(scope.node.releaseDate, currentUser.locale, 'DD')) : null; + scope.node.releaseDateDay = scope.node.releaseDate ? ucfirst(dateHelper.getLocalDate(scope.node.releaseDate, currentUser.locale, 'dddd')) : null; + scope.node.releaseDateTime = scope.node.releaseDate ? ucfirst(dateHelper.getLocalDate(scope.node.releaseDate, currentUser.locale, 'HH:mm')) : null; + scope.node.removeDateYear = scope.node.removeDate ? ucfirst(dateHelper.getLocalDate(scope.node.removeDate, currentUser.locale, 'YYYY')) : null; + scope.node.removeDateMonth = scope.node.removeDate ? ucfirst(dateHelper.getLocalDate(scope.node.removeDate, currentUser.locale, 'MMMM')) : null; + scope.node.removeDateDayNumber = scope.node.removeDate ? ucfirst(dateHelper.getLocalDate(scope.node.removeDate, currentUser.locale, 'DD')) : null; + scope.node.removeDateDay = scope.node.removeDate ? ucfirst(dateHelper.getLocalDate(scope.node.removeDate, currentUser.locale, 'dddd')) : null; + scope.node.removeDateTime = scope.node.removeDate ? ucfirst(dateHelper.getLocalDate(scope.node.removeDate, currentUser.locale, 'HH:mm')) : null; + }); + } + // load audit trail when on the info tab + evts.push(eventsService.on('app.tabChange', function (event, args) { + $timeout(function () { + if (args.id === -1) { + isInfoTab = true; + loadAuditTrail(); + } else { + isInfoTab = false; + } + }); + })); + // watch for content updates - reload content when node is saved, published etc. + scope.$watch('node.updateDate', function (newValue, oldValue) { + if (!newValue) { + return; + } + if (newValue === oldValue) { + return; + } + if (isInfoTab) { + loadAuditTrail(); + formatDatesToLocal(); + setNodePublishStatus(scope.node); + } + }); + //ensure to unregister from all events! + scope.$on('$destroy', function () { + for (var e in evts) { + eventsService.unsubscribe(evts[e]); + } + }); + onInit(); + } + var directive = { + restrict: 'E', + replace: true, + templateUrl: 'views/components/content/umb-content-node-info.html', + scope: { node: '=' }, + link: link + }; + return directive; + } + angular.module('umbraco.directives').directive('umbContentNodeInfo', ContentNodeInfoDirective); + }()); + /** @ngdoc directive @name umbraco.directives.directive:umbEditorSubHeader @restrict E @@ -1371,27 +2833,20 @@ The sub header is sticky and will follow along down the page when scrolling.
  • {@link umbraco.directives.directive:umbEditorSubHeaderSection umbEditorSubHeaderSection}
**/ - -(function() { - 'use strict'; - - function EditorSubHeaderDirective() { - - var directive = { - transclude: true, - restrict: 'E', - replace: true, - templateUrl: 'views/components/editor/subheader/umb-editor-sub-header.html' - }; - - return directive; - } - - angular.module('umbraco.directives').directive('umbEditorSubHeader', EditorSubHeaderDirective); - -})(); - -/** + (function () { + 'use strict'; + function EditorSubHeaderDirective() { + var directive = { + transclude: true, + restrict: 'E', + replace: true, + templateUrl: 'views/components/editor/subheader/umb-editor-sub-header.html' + }; + return directive; + } + angular.module('umbraco.directives').directive('umbEditorSubHeader', EditorSubHeaderDirective); + }()); + /** @ngdoc directive @name umbraco.directives.directive:umbEditorSubHeaderContentLeft @restrict E @@ -1437,27 +2892,20 @@ Use this directive to left align content in a sub header in the main editor wind
  • {@link umbraco.directives.directive:umbEditorSubHeaderSection umbEditorSubHeaderSection}
  • **/ - -(function() { - 'use strict'; - - function EditorSubHeaderContentLeftDirective() { - - var directive = { - transclude: true, - restrict: 'E', - replace: true, - templateUrl: 'views/components/editor/subheader/umb-editor-sub-header-content-left.html' - }; - - return directive; - } - - angular.module('umbraco.directives').directive('umbEditorSubHeaderContentLeft', EditorSubHeaderContentLeftDirective); - -})(); - -/** + (function () { + 'use strict'; + function EditorSubHeaderContentLeftDirective() { + var directive = { + transclude: true, + restrict: 'E', + replace: true, + templateUrl: 'views/components/editor/subheader/umb-editor-sub-header-content-left.html' + }; + return directive; + } + angular.module('umbraco.directives').directive('umbEditorSubHeaderContentLeft', EditorSubHeaderContentLeftDirective); + }()); + /** @ngdoc directive @name umbraco.directives.directive:umbEditorSubHeaderContentRight @restrict E @@ -1503,27 +2951,20 @@ Use this directive to rigt align content in a sub header in the main editor wind
  • {@link umbraco.directives.directive:umbEditorSubHeaderSection umbEditorSubHeaderSection}
  • **/ - -(function() { - 'use strict'; - - function EditorSubHeaderContentRightDirective() { - - var directive = { - transclude: true, - restrict: 'E', - replace: true, - templateUrl: 'views/components/editor/subheader/umb-editor-sub-header-content-right.html' - }; - - return directive; - } - - angular.module('umbraco.directives').directive('umbEditorSubHeaderContentRight', EditorSubHeaderContentRightDirective); - -})(); - -/** + (function () { + 'use strict'; + function EditorSubHeaderContentRightDirective() { + var directive = { + transclude: true, + restrict: 'E', + replace: true, + templateUrl: 'views/components/editor/subheader/umb-editor-sub-header-content-right.html' + }; + return directive; + } + angular.module('umbraco.directives').directive('umbEditorSubHeaderContentRight', EditorSubHeaderContentRightDirective); + }()); + /** @ngdoc directive @name umbraco.directives.directive:umbEditorSubHeaderSection @restrict E @@ -1577,27 +3018,20 @@ Use this directive to create sections, divided by borders, in a sub header in th
  • {@link umbraco.directives.directive:umbEditorSubHeaderContentRight umbEditorSubHeaderContentRight}
  • **/ - -(function() { - 'use strict'; - - function EditorSubHeaderSectionDirective() { - - var directive = { - transclude: true, - restrict: 'E', - replace: true, - templateUrl: 'views/components/editor/subheader/umb-editor-sub-header-section.html' - }; - - return directive; - } - - angular.module('umbraco.directives').directive('umbEditorSubHeaderSection', EditorSubHeaderSectionDirective); - -})(); - -/** + (function () { + 'use strict'; + function EditorSubHeaderSectionDirective() { + var directive = { + transclude: true, + restrict: 'E', + replace: true, + templateUrl: 'views/components/editor/subheader/umb-editor-sub-header-section.html' + }; + return directive; + } + angular.module('umbraco.directives').directive('umbEditorSubHeaderSection', EditorSubHeaderSectionDirective); + }()); + /** @ngdoc directive @name umbraco.directives.directive:umbBreadcrumbs @restrict E @@ -1639,32 +3073,41 @@ Use this directive to generate a list of breadcrumbs. @param {array} ancestors Array of ancestors @param {string} entityType The content entity type (member, media, content). +@param {callback} Callback when an ancestor is clicked. It will override the default link behaviour. **/ - -(function() { - 'use strict'; - - function BreadcrumbsDirective() { - - var directive = { - restrict: 'E', - replace: true, - templateUrl: 'views/components/editor/umb-breadcrumbs.html', - scope: { - ancestors: "=", - entityType: "@" - } - }; - - return directive; - - } - - angular.module('umbraco.directives').directive('umbBreadcrumbs', BreadcrumbsDirective); - -})(); - -/** + (function () { + 'use strict'; + function BreadcrumbsDirective() { + function link(scope, el, attr, ctrl) { + scope.allowOnOpen = false; + scope.open = function (ancestor) { + if (scope.onOpen && scope.allowOnOpen) { + scope.onOpen({ 'ancestor': ancestor }); + } + }; + function onInit() { + if ('onOpen' in attr) { + scope.allowOnOpen = true; + } + } + onInit(); + } + var directive = { + restrict: 'E', + replace: true, + templateUrl: 'views/components/editor/umb-breadcrumbs.html', + scope: { + ancestors: '=', + entityType: '@', + onOpen: '&' + }, + link: link + }; + return directive; + } + angular.module('umbraco.directives').directive('umbBreadcrumbs', BreadcrumbsDirective); + }()); + /** @ngdoc directive @name umbraco.directives.directive:umbEditorContainer @restrict E @@ -1702,40 +3145,29 @@ Use this directive to construct a main content area inside the main editor windo
  • {@link umbraco.directives.directive:umbEditorFooter umbEditorFooter}
  • **/ - -(function() { - 'use strict'; - - function EditorContainerDirective(overlayHelper) { - - function link(scope, el, attr, ctrl) { - - scope.numberOfOverlays = 0; - - scope.$watch(function(){ - return overlayHelper.getNumberOfOverlays(); - }, function (newValue) { - scope.numberOfOverlays = newValue; - }); - - } - - var directive = { - transclude: true, - restrict: 'E', - replace: true, - templateUrl: 'views/components/editor/umb-editor-container.html', - link: link - }; - - return directive; - } - - angular.module('umbraco.directives').directive('umbEditorContainer', EditorContainerDirective); - -})(); - -/** + (function () { + 'use strict'; + function EditorContainerDirective(overlayHelper) { + function link(scope, el, attr, ctrl) { + scope.numberOfOverlays = 0; + scope.$watch(function () { + return overlayHelper.getNumberOfOverlays(); + }, function (newValue) { + scope.numberOfOverlays = newValue; + }); + } + var directive = { + transclude: true, + restrict: 'E', + replace: true, + templateUrl: 'views/components/editor/umb-editor-container.html', + link: link + }; + return directive; + } + angular.module('umbraco.directives').directive('umbEditorContainer', EditorContainerDirective); + }()); + /** @ngdoc directive @name umbraco.directives.directive:umbEditorFooter @restrict E @@ -1779,27 +3211,20 @@ Use this directive to construct a footer inside the main editor window.
  • {@link umbraco.directives.directive:umbEditorFooterContentRight umbEditorFooterContentRight}
  • **/ - -(function() { - 'use strict'; - - function EditorFooterDirective() { - - var directive = { - transclude: true, - restrict: 'E', - replace: true, - templateUrl: 'views/components/editor/umb-editor-footer.html' - }; - - return directive; - } - - angular.module('umbraco.directives').directive('umbEditorFooter', EditorFooterDirective); - -})(); - -/** + (function () { + 'use strict'; + function EditorFooterDirective() { + var directive = { + transclude: true, + restrict: 'E', + replace: true, + templateUrl: 'views/components/editor/umb-editor-footer.html' + }; + return directive; + } + angular.module('umbraco.directives').directive('umbEditorFooter', EditorFooterDirective); + }()); + /** @ngdoc directive @name umbraco.directives.directive:umbEditorFooterContentLeft @restrict E @@ -1843,27 +3268,20 @@ Use this directive to align content left inside the main editor footer.
  • {@link umbraco.directives.directive:umbEditorFooterContentRight umbEditorFooterContentRight}
  • **/ - -(function() { - 'use strict'; - - function EditorFooterContentLeftDirective() { - - var directive = { - transclude: true, - restrict: 'E', - replace: true, - templateUrl: 'views/components/editor/umb-editor-footer-content-left.html' - }; - - return directive; - } - - angular.module('umbraco.directives').directive('umbEditorFooterContentLeft', EditorFooterContentLeftDirective); - -})(); - -/** + (function () { + 'use strict'; + function EditorFooterContentLeftDirective() { + var directive = { + transclude: true, + restrict: 'E', + replace: true, + templateUrl: 'views/components/editor/umb-editor-footer-content-left.html' + }; + return directive; + } + angular.module('umbraco.directives').directive('umbEditorFooterContentLeft', EditorFooterContentLeftDirective); + }()); + /** @ngdoc directive @name umbraco.directives.directive:umbEditorFooterContentRight @restrict E @@ -1907,27 +3325,20 @@ Use this directive to align content right inside the main editor footer.
  • {@link umbraco.directives.directive:umbEditorFooterContentLeft umbEditorFooterContentLeft}
  • **/ - -(function() { - 'use strict'; - - function EditorFooterContentRightDirective() { - - var directive = { - transclude: true, - restrict: 'E', - replace: true, - templateUrl: 'views/components/editor/umb-editor-footer-content-right.html' - }; - - return directive; - } - - angular.module('umbraco.directives').directive('umbEditorFooterContentRight', EditorFooterContentRightDirective); - -})(); - -/** + (function () { + 'use strict'; + function EditorFooterContentRightDirective() { + var directive = { + transclude: true, + restrict: 'E', + replace: true, + templateUrl: 'views/components/editor/umb-editor-footer-content-right.html' + }; + return directive; + } + angular.module('umbraco.directives').directive('umbEditorFooterContentRight', EditorFooterContentRightDirective); + }()); + /** @ngdoc directive @name umbraco.directives.directive:umbEditorHeader @restrict E @@ -2129,238 +3540,179 @@ Use this directive to construct a header inside the main editor window. @param {boolean=} hideDescription Set to true to hide description. **/ - -(function() { - 'use strict'; - - function EditorHeaderDirective(iconHelper) { - - function link(scope, el, attr, ctrl) { - - scope.openIconPicker = function() { - scope.dialogModel = { - view: "iconpicker", - show: true, - submit: function (model) { - - /* ensure an icon is selected, because on focus on close button + (function () { + 'use strict'; + function EditorHeaderDirective(iconHelper) { + function link(scope, el, attr, ctrl) { + scope.openIconPicker = function () { + scope.dialogModel = { + view: 'iconpicker', + show: true, + icon: scope.icon.split(' ')[0], + color: scope.icon.split(' ')[1], + submit: function (model) { + /* ensure an icon is selected, because on focus on close button or an element in background no icon is submitted. So don't clear/update existing icon/preview. */ - if (model.icon) { - - if (model.color) { - scope.icon = model.icon + " " + model.color; - } else { - scope.icon = model.icon; + if (model.icon) { + if (model.color) { + scope.icon = model.icon + ' ' + model.color; + } else { + scope.icon = model.icon; + } + // set the icon form to dirty + scope.iconForm.$setDirty(); } - - // set form to dirty - ctrl.$setDirty(); + scope.dialogModel.show = false; + scope.dialogModel = null; } - - scope.dialogModel.show = false; - scope.dialogModel = null; + }; + }; + } + var directive = { + transclude: true, + restrict: 'E', + replace: true, + templateUrl: 'views/components/editor/umb-editor-header.html', + scope: { + tabs: '=', + actions: '=', + name: '=', + nameLocked: '=', + menu: '=', + icon: '=', + hideIcon: '@', + alias: '=', + hideAlias: '@', + description: '=', + hideDescription: '@', + descriptionLocked: '@', + navigation: '=', + key: '=' + }, + link: link + }; + return directive; + } + angular.module('umbraco.directives').directive('umbEditorHeader', EditorHeaderDirective); + }()); + (function () { + 'use strict'; + function EditorMenuDirective($injector, treeService, navigationService, umbModelMapper, appState) { + function link(scope, el, attr, ctrl) { + //adds a handler to the context menu item click, we need to handle this differently + //depending on what the menu item is supposed to do. + scope.executeMenuItem = function (action) { + navigationService.executeMenuAction(action, scope.currentNode, scope.currentSection); + }; + //callback method to go and get the options async + scope.getOptions = function () { + if (!scope.currentNode) { + return; + } + //when the options item is selected, we need to set the current menu item in appState (since this is synonymous with a menu) + appState.setMenuState('currentNode', scope.currentNode); + if (!scope.actions) { + treeService.getMenu({ treeNode: scope.currentNode }).then(function (data) { + scope.actions = data.menuItems; + }); } }; + } + var directive = { + restrict: 'E', + replace: true, + templateUrl: 'views/components/editor/umb-editor-menu.html', + link: link, + scope: { + currentNode: '=', + currentSection: '@' + } }; + return directive; } - - var directive = { - require: '^form', - transclude: true, - restrict: 'E', - replace: true, - templateUrl: 'views/components/editor/umb-editor-header.html', - scope: { - tabs: "=", - actions: "=", - name: "=", - nameLocked: "=", - menu: "=", - icon: "=", - hideIcon: "@", - alias: "=", - hideAlias: "@", - description: "=", - hideDescription: "@", - navigation: "=" - }, - link: link - }; - - return directive; - } - - angular.module('umbraco.directives').directive('umbEditorHeader', EditorHeaderDirective); - -})(); - -(function() { - 'use strict'; - - function EditorMenuDirective($injector, treeService, navigationService, umbModelMapper, appState) { - - function link(scope, el, attr, ctrl) { - - //adds a handler to the context menu item click, we need to handle this differently - //depending on what the menu item is supposed to do. - scope.executeMenuItem = function (action) { - navigationService.executeMenuAction(action, scope.currentNode, scope.currentSection); - }; - - //callback method to go and get the options async - scope.getOptions = function () { - - if (!scope.currentNode) { - return; - } - - //when the options item is selected, we need to set the current menu item in appState (since this is synonymous with a menu) - appState.setMenuState("currentNode", scope.currentNode); - - if (!scope.actions) { - treeService.getMenu({ treeNode: scope.currentNode }) - .then(function (data) { - scope.actions = data.menuItems; - }); - } - }; - - } - - var directive = { - restrict: 'E', - replace: true, - templateUrl: 'views/components/editor/umb-editor-menu.html', - link: link, - scope: { - currentNode: "=", - currentSection: "@" - } - }; - - return directive; - } - - angular.module('umbraco.directives').directive('umbEditorMenu', EditorMenuDirective); - -})(); - -(function() { - 'use strict'; - - function EditorNavigationDirective() { - - function link(scope, el, attr, ctrl) { - - scope.showNavigation = true; - - scope.clickNavigationItem = function(selectedItem) { - setItemToActive(selectedItem); - runItemAction(selectedItem); - }; - - function runItemAction(selectedItem) { - if (selectedItem.action) { - selectedItem.action(selectedItem); + angular.module('umbraco.directives').directive('umbEditorMenu', EditorMenuDirective); + }()); + (function () { + 'use strict'; + function EditorNavigationDirective() { + function link(scope, el, attr, ctrl) { + scope.showNavigation = true; + scope.clickNavigationItem = function (selectedItem) { + setItemToActive(selectedItem); + runItemAction(selectedItem); + }; + function runItemAction(selectedItem) { + if (selectedItem.action) { + selectedItem.action(selectedItem); + } + } + function setItemToActive(selectedItem) { + // set all other views to inactive + if (selectedItem.view) { + for (var index = 0; index < scope.navigation.length; index++) { + var item = scope.navigation[index]; + item.active = false; + } + // set view to active + selectedItem.active = true; + } + } + function activate() { + // hide navigation if there is only 1 item + if (scope.navigation.length <= 1) { + scope.showNavigation = false; + } + } + activate(); } - } - - function setItemToActive(selectedItem) { - // set all other views to inactive - if (selectedItem.view) { - - for (var index = 0; index < scope.navigation.length; index++) { - var item = scope.navigation[index]; - item.active = false; - } - - // set view to active - selectedItem.active = true; - + var directive = { + restrict: 'E', + replace: true, + templateUrl: 'views/components/editor/umb-editor-navigation.html', + scope: { navigation: '=' }, + link: link + }; + return directive; + } + angular.module('umbraco.directives.html').directive('umbEditorNavigation', EditorNavigationDirective); + }()); + (function () { + 'use strict'; + function EditorSubViewsDirective() { + function link(scope, el, attr, ctrl) { + scope.activeView = {}; + // set toolbar from selected navigation item + function setActiveView(items) { + for (var index = 0; index < items.length; index++) { + var item = items[index]; + if (item.active && item.view) { + scope.activeView = item; + } + } + } + // watch for navigation changes + scope.$watch('subViews', function (newValue, oldValue) { + if (newValue) { + setActiveView(newValue); + } + }, true); } - } - - function activate() { - - // hide navigation if there is only 1 item - if (scope.navigation.length <= 1) { - scope.showNavigation = false; - } - - } - - activate(); - - } - - var directive = { - restrict: 'E', - replace: true, - templateUrl: 'views/components/editor/umb-editor-navigation.html', - scope: { - navigation: "=" - }, - link: link - }; - - return directive; - } - - angular.module('umbraco.directives.html').directive('umbEditorNavigation', EditorNavigationDirective); - -})(); - -(function() { - 'use strict'; - - function EditorSubViewsDirective() { - - function link(scope, el, attr, ctrl) { - - scope.activeView = {}; - - // set toolbar from selected navigation item - function setActiveView(items) { - - for (var index = 0; index < items.length; index++) { - - var item = items[index]; - - if (item.active && item.view) { - scope.activeView = item; - } - } - } - - // watch for navigation changes - scope.$watch('subViews', function(newValue, oldValue) { - if (newValue) { - setActiveView(newValue); - } - }, true); - - } - - var directive = { - restrict: 'E', - replace: true, - templateUrl: 'views/components/editor/umb-editor-sub-views.html', - scope: { - subViews: "=", - model: "=" - }, - link: link - }; - - return directive; - } - - angular.module('umbraco.directives').directive('umbEditorSubViews', EditorSubViewsDirective); - -})(); - -/** + var directive = { + restrict: 'E', + replace: true, + templateUrl: 'views/components/editor/umb-editor-sub-views.html', + scope: { + subViews: '=', + model: '=' + }, + link: link + }; + return directive; + } + angular.module('umbraco.directives').directive('umbEditorSubViews', EditorSubViewsDirective); + }()); + /** @ngdoc directive @name umbraco.directives.directive:umbEditorView @restrict E @@ -2422,634 +3774,538 @@ Use this directive to construct the main editor window.
  • {@link umbraco.directives.directive:umbEditorFooter umbEditorFooter}
  • **/ - -(function() { - 'use strict'; - - function EditorViewDirective() { - - function link(scope, el, attr) { - - if(attr.footer) { - scope.footer = attr.footer; - } - - } - - var directive = { - transclude: true, - restrict: 'E', - replace: true, - templateUrl: 'views/components/editor/umb-editor-view.html', - link: link - }; - - return directive; - } - - angular.module('umbraco.directives').directive('umbEditorView', EditorViewDirective); - -})(); - -/** + (function () { + 'use strict'; + function EditorViewDirective() { + function link(scope, el, attr) { + if (attr.footer) { + scope.footer = attr.footer; + } + } + var directive = { + transclude: true, + restrict: 'E', + replace: true, + templateUrl: 'views/components/editor/umb-editor-view.html', + link: link + }; + return directive; + } + angular.module('umbraco.directives').directive('umbEditorView', EditorViewDirective); + }()); + /** * @description Utillity directives for key and field events **/ -angular.module('umbraco.directives') - -.directive('onKeyup', function () { - return { - link: function (scope, elm, attrs) { - var f = function () { - scope.$apply(attrs.onKeyup); - }; - elm.on("keyup", f); - scope.$on("$destroy", function(){ elm.off("keyup", f);} ); - } - }; -}) - -.directive('onKeydown', function () { - return { - link: function (scope, elm, attrs) { - var f = function () { - scope.$apply(attrs.onKeydown); - }; - elm.on("keydown", f); - scope.$on("$destroy", function(){ elm.off("keydown", f);} ); - } - }; -}) - -.directive('onBlur', function () { - return { - link: function (scope, elm, attrs) { - var f = function () { - scope.$apply(attrs.onBlur); - }; - elm.on("blur", f); - scope.$on("$destroy", function(){ elm.off("blur", f);} ); - } - }; -}) - -.directive('onFocus', function () { - return { - link: function (scope, elm, attrs) { - var f = function () { - scope.$apply(attrs.onFocus); - }; - elm.on("focus", f); - scope.$on("$destroy", function(){ elm.off("focus", f);} ); - } - }; -}) - -.directive('onDragEnter', function () { - return { - link: function (scope, elm, attrs) { - var f = function () { - scope.$apply(attrs.onDragEnter); - }; - elm.on("dragenter", f); - scope.$on("$destroy", function(){ elm.off("dragenter", f);} ); - } - }; -}) - -.directive('onDragLeave', function () { - return function (scope, elm, attrs) { - var f = function (event) { - var rect = this.getBoundingClientRect(); - var getXY = function getCursorPosition(event) { - var x, y; - - if (typeof event.clientX === 'undefined') { - // try touch screen - x = event.pageX + document.documentElement.scrollLeft; - y = event.pageY + document.documentElement.scrollTop; - } else { - x = event.clientX + document.body.scrollLeft + document.documentElement.scrollLeft; - y = event.clientY + document.body.scrollTop + document.documentElement.scrollTop; - } - - return { x: x, y : y }; - }; - - var e = getXY(event.originalEvent); - - // Check the mouseEvent coordinates are outside of the rectangle - if (e.x > rect.left + rect.width - 1 || e.x < rect.left || e.y > rect.top + rect.height - 1 || e.y < rect.top) { - scope.$apply(attrs.onDragLeave); + angular.module('umbraco.directives').directive('onKeyup', function () { + return { + link: function (scope, elm, attrs) { + var f = function () { + scope.$apply(attrs.onKeyup); + }; + elm.on('keyup', f); + scope.$on('$destroy', function () { + elm.off('keyup', f); + }); } }; - - elm.on("dragleave", f); - scope.$on("$destroy", function(){ elm.off("dragleave", f);} ); - }; -}) - -.directive('onDragOver', function () { - return { - link: function (scope, elm, attrs) { - var f = function () { - scope.$apply(attrs.onDragOver); + }).directive('onKeydown', function () { + return { + link: function (scope, elm, attrs) { + var f = function () { + scope.$apply(attrs.onKeydown); + }; + elm.on('keydown', f); + scope.$on('$destroy', function () { + elm.off('keydown', f); + }); + } + }; + }).directive('onBlur', function () { + return { + link: function (scope, elm, attrs) { + var f = function () { + scope.$apply(attrs.onBlur); + }; + elm.on('blur', f); + scope.$on('$destroy', function () { + elm.off('blur', f); + }); + } + }; + }).directive('onFocus', function () { + return { + link: function (scope, elm, attrs) { + var f = function () { + scope.$apply(attrs.onFocus); + }; + elm.on('focus', f); + scope.$on('$destroy', function () { + elm.off('focus', f); + }); + } + }; + }).directive('onDragEnter', function () { + return { + link: function (scope, elm, attrs) { + var f = function () { + scope.$apply(attrs.onDragEnter); + }; + elm.on('dragenter', f); + scope.$on('$destroy', function () { + elm.off('dragenter', f); + }); + } + }; + }).directive('onDragLeave', function () { + return function (scope, elm, attrs) { + var f = function (event) { + var rect = this.getBoundingClientRect(); + var getXY = function getCursorPosition(event) { + var x, y; + if (typeof event.clientX === 'undefined') { + // try touch screen + x = event.pageX + document.documentElement.scrollLeft; + y = event.pageY + document.documentElement.scrollTop; + } else { + x = event.clientX + document.body.scrollLeft + document.documentElement.scrollLeft; + y = event.clientY + document.body.scrollTop + document.documentElement.scrollTop; + } + return { + x: x, + y: y + }; + }; + var e = getXY(event.originalEvent); + // Check the mouseEvent coordinates are outside of the rectangle + if (e.x > rect.left + rect.width - 1 || e.x < rect.left || e.y > rect.top + rect.height - 1 || e.y < rect.top) { + scope.$apply(attrs.onDragLeave); + } }; - elm.on("dragover", f); - scope.$on("$destroy", function(){ elm.off("dragover", f);} ); - } - }; -}) - -.directive('onDragStart', function () { - return { - link: function (scope, elm, attrs) { - var f = function () { - scope.$apply(attrs.onDragStart); - }; - elm.on("dragstart", f); - scope.$on("$destroy", function(){ elm.off("dragstart", f);} ); - } - }; -}) - -.directive('onDragEnd', function () { - return { - link: function (scope, elm, attrs) { - var f = function () { - scope.$apply(attrs.onDragEnd); - }; - elm.on("dragend", f); - scope.$on("$destroy", function(){ elm.off("dragend", f);} ); - } - }; -}) - -.directive('onDrop', function () { - return { - link: function (scope, elm, attrs) { - var f = function () { - scope.$apply(attrs.onDrop); - }; - elm.on("drop", f); - scope.$on("$destroy", function(){ elm.off("drop", f);} ); - } - }; -}) - -.directive('onOutsideClick', function ($timeout) { - return function (scope, element, attrs) { - - var eventBindings = []; - - function oneTimeClick(event) { + elm.on('dragleave', f); + scope.$on('$destroy', function () { + elm.off('dragleave', f); + }); + }; + }).directive('onDragOver', function () { + return { + link: function (scope, elm, attrs) { + var f = function () { + scope.$apply(attrs.onDragOver); + }; + elm.on('dragover', f); + scope.$on('$destroy', function () { + elm.off('dragover', f); + }); + } + }; + }).directive('onDragStart', function () { + return { + link: function (scope, elm, attrs) { + var f = function () { + scope.$apply(attrs.onDragStart); + }; + elm.on('dragstart', f); + scope.$on('$destroy', function () { + elm.off('dragstart', f); + }); + } + }; + }).directive('onDragEnd', function () { + return { + link: function (scope, elm, attrs) { + var f = function () { + scope.$apply(attrs.onDragEnd); + }; + elm.on('dragend', f); + scope.$on('$destroy', function () { + elm.off('dragend', f); + }); + } + }; + }).directive('onDrop', function () { + return { + link: function (scope, elm, attrs) { + var f = function () { + scope.$apply(attrs.onDrop); + }; + elm.on('drop', f); + scope.$on('$destroy', function () { + elm.off('drop', f); + }); + } + }; + }).directive('onOutsideClick', function ($timeout) { + return function (scope, element, attrs) { + var eventBindings = []; + function oneTimeClick(event) { var el = event.target.nodeName; - //ignore link and button clicks - var els = ["INPUT","A","BUTTON"]; - if(els.indexOf(el) >= 0){return;} - - // ignore children of links and buttons - // ignore clicks on new overlay - var parents = $(event.target).parents("a,button,.umb-overlay"); - if(parents.length > 0){ + var els = [ + 'INPUT', + 'A', + 'BUTTON' + ]; + if (els.indexOf(el) >= 0) { + return; + } + // ignore clicks on new overlay + var parents = $(event.target).parents('a,button,.umb-overlay,.umb-tour'); + if (parents.length > 0) { return; } - // ignore clicks on dialog from old dialog service - var oldDialog = $(el).parents("#old-dialog-service"); + var oldDialog = $(event.target).parents('#old-dialog-service'); if (oldDialog.length === 1) { return; } - // ignore clicks in tinyMCE dropdown(floatpanel) - var floatpanel = $(el).parents(".mce-floatpanel"); + var floatpanel = $(event.target).closest('.mce-floatpanel'); if (floatpanel.length === 1) { return; } - //ignore clicks inside this element - if( $(element).has( $(event.target) ).length > 0 ){ + if ($(element).has($(event.target)).length > 0) { return; } - scope.$apply(attrs.onOutsideClick); - } - - - $timeout(function(){ - - if ("bindClickOn" in attrs) { - - eventBindings.push(scope.$watch(function() { - return attrs.bindClickOn; - }, function(newValue) { - if (newValue === "true") { - $(document).on("click", oneTimeClick); - } else { - $(document).off("click", oneTimeClick); - } - })); - - } else { - $(document).on("click", oneTimeClick); } - - scope.$on("$destroy", function() { - $(document).off("click", oneTimeClick); - - // unbind watchers - for (var e in eventBindings) { - eventBindings[e](); + $timeout(function () { + if ('bindClickOn' in attrs) { + eventBindings.push(scope.$watch(function () { + return attrs.bindClickOn; + }, function (newValue) { + if (newValue === 'true') { + $(document).on('click', oneTimeClick); + } else { + $(document).off('click', oneTimeClick); + } + })); + } else { + $(document).on('click', oneTimeClick); } - + scope.$on('$destroy', function () { + $(document).off('click', oneTimeClick); + // unbind watchers + for (var e in eventBindings) { + eventBindings[e](); + } + }); + }); // Temp removal of 1 sec timeout to prevent bug where overlay does not open. We need to find a better solution. + }; + }).directive('onRightClick', function () { + document.oncontextmenu = function (e) { + if (e.target.hasAttribute('on-right-click')) { + e.preventDefault(); + e.stopPropagation(); + return false; + } + }; + return function (scope, el, attrs) { + el.on('contextmenu', function (e) { + e.preventDefault(); + e.stopPropagation(); + scope.$apply(attrs.onRightClick); + return false; }); - }); // Temp removal of 1 sec timeout to prevent bug where overlay does not open. We need to find a better solution. - - }; -}) - -.directive('onRightClick',function(){ - - document.oncontextmenu = function (e) { - if(e.target.hasAttribute('on-right-click')) { - e.preventDefault(); - e.stopPropagation(); - return false; - } - }; - - return function(scope,el,attrs){ - el.on('contextmenu',function(e){ - e.preventDefault(); - e.stopPropagation(); - scope.$apply(attrs.onRightClick); - return false; - }); - }; -}) - -.directive('onDelayedMouseleave', function ($timeout, $parse) { + }; + }).directive('onDelayedMouseleave', function ($timeout, $parse) { return { - restrict: 'A', - link: function (scope, element, attrs, ctrl) { var active = false; var fn = $parse(attrs.onDelayedMouseleave); - - var leave_f = function(event) { - var callback = function() { - fn(scope, {$event:event}); + var leave_f = function (event) { + var callback = function () { + fn(scope, { $event: event }); }; - active = false; - $timeout(function(){ - if(active === false){ + $timeout(function () { + if (active === false) { scope.$apply(callback); } }, 650); }; - - var enter_f = function(event, args){ + var enter_f = function (event, args) { active = true; }; - - - element.on("mouseleave", leave_f); - element.on("mouseenter", enter_f); - + element.on('mouseleave', leave_f); + element.on('mouseenter', enter_f); //unsub events - scope.$on("$destroy", function(){ - element.off("mouseleave", leave_f); - element.off("mouseenter", enter_f); + scope.$on('$destroy', function () { + element.off('mouseleave', leave_f); + element.off('mouseenter', enter_f); }); } }; }); - -/* + /* - http://vitalets.github.io/checklist-model/ + https://vitalets.github.io/checklist-model/ */ -angular.module('umbraco.directives') -.directive('checklistModel', ['$parse', '$compile', function($parse, $compile) { - // contains - function contains(arr, item) { - if (angular.isArray(arr)) { - for (var i = 0; i < arr.length; i++) { - if (angular.equals(arr[i], item)) { - return true; + angular.module('umbraco.directives').directive('checklistModel', [ + '$parse', + '$compile', + function ($parse, $compile) { + // contains + function contains(arr, item) { + if (angular.isArray(arr)) { + for (var i = 0; i < arr.length; i++) { + if (angular.equals(arr[i], item)) { + return true; + } + } + } + return false; + } + // add + function add(arr, item) { + arr = angular.isArray(arr) ? arr : []; + for (var i = 0; i < arr.length; i++) { + if (angular.equals(arr[i], item)) { + return arr; + } + } + arr.push(item); + return arr; + } + // remove + function remove(arr, item) { + if (angular.isArray(arr)) { + for (var i = 0; i < arr.length; i++) { + if (angular.equals(arr[i], item)) { + arr.splice(i, 1); + break; + } + } + } + return arr; + } + // https://stackoverflow.com/a/19228302/1458162 + function postLinkFn(scope, elem, attrs) { + // compile with `ng-model` pointing to `checked` + $compile(elem)(scope); + // getter / setter for original model + var getter = $parse(attrs.checklistModel); + var setter = getter.assign; + // value added to list + var value = $parse(attrs.checklistValue)(scope.$parent); + // watch UI checked change + scope.$watch('checked', function (newValue, oldValue) { + if (newValue === oldValue) { + return; + } + var current = getter(scope.$parent); + if (newValue === true) { + setter(scope.$parent, add(current, value)); + } else { + setter(scope.$parent, remove(current, value)); + } + }); + // watch original model change + scope.$parent.$watch(attrs.checklistModel, function (newArr, oldArr) { + scope.checked = contains(newArr, value); + }, true); + } + return { + restrict: 'A', + priority: 1000, + terminal: true, + scope: true, + compile: function (tElement, tAttrs) { + if (tElement[0].tagName !== 'INPUT' || !tElement.attr('type', 'checkbox')) { + throw 'checklist-model should be applied to `input[type="checkbox"]`.'; + } + if (!tAttrs.checklistValue) { + throw 'You should provide `checklist-value`.'; + } + // exclude recursion + tElement.removeAttr('checklist-model'); + // local scope var storing individual checkbox model + tElement.attr('ng-model', 'checked'); + return postLinkFn; + } + }; } - } - } - return false; - } - - // add - function add(arr, item) { - arr = angular.isArray(arr) ? arr : []; - for (var i = 0; i < arr.length; i++) { - if (angular.equals(arr[i], item)) { - return arr; - } - } - arr.push(item); - return arr; - } - - // remove - function remove(arr, item) { - if (angular.isArray(arr)) { - for (var i = 0; i < arr.length; i++) { - if (angular.equals(arr[i], item)) { - arr.splice(i, 1); - break; - } - } - } - return arr; - } - - // http://stackoverflow.com/a/19228302/1458162 - function postLinkFn(scope, elem, attrs) { - // compile with `ng-model` pointing to `checked` - $compile(elem)(scope); - - // getter / setter for original model - var getter = $parse(attrs.checklistModel); - var setter = getter.assign; - - // value added to list - var value = $parse(attrs.checklistValue)(scope.$parent); - - // watch UI checked change - scope.$watch('checked', function(newValue, oldValue) { - if (newValue === oldValue) { - return; - } - var current = getter(scope.$parent); - if (newValue === true) { - setter(scope.$parent, add(current, value)); - } else { - setter(scope.$parent, remove(current, value)); - } + ]); + angular.module('umbraco.directives').directive('contenteditable', function () { + return { + require: 'ngModel', + link: function (scope, element, attrs, ngModel) { + function read() { + ngModel.$setViewValue(element.html()); + } + ngModel.$render = function () { + element.html(ngModel.$viewValue || ''); + }; + element.bind('focus', function () { + var range = document.createRange(); + range.selectNodeContents(element[0]); + var sel = window.getSelection(); + sel.removeAllRanges(); + sel.addRange(range); + }); + element.bind('blur keyup change', function () { + scope.$apply(read); + }); + } + }; }); - - // watch original model change - scope.$parent.$watch(attrs.checklistModel, function(newArr, oldArr) { - scope.checked = contains(newArr, value); - }, true); - } - - return { - restrict: 'A', - priority: 1000, - terminal: true, - scope: true, - compile: function(tElement, tAttrs) { - if (tElement[0].tagName !== 'INPUT' || !tElement.attr('type', 'checkbox')) { - throw 'checklist-model should be applied to `input[type="checkbox"]`.'; - } - - if (!tAttrs.checklistValue) { - throw 'You should provide `checklist-value`.'; - } - - // exclude recursion - tElement.removeAttr('checklist-model'); - - // local scope var storing individual checkbox model - tElement.attr('ng-model', 'checked'); - - return postLinkFn; - } - }; -}]); -angular.module("umbraco.directives") -.directive("contenteditable", function() { - - return { - require: "ngModel", - link: function(scope, element, attrs, ngModel) { - - function read() { - ngModel.$setViewValue(element.html()); - } - - ngModel.$render = function() { - element.html(ngModel.$viewValue || ""); - }; - - - element.bind("focus", function(){ - - var range = document.createRange(); - range.selectNodeContents(element[0]); - - var sel = window.getSelection(); - sel.removeAllRanges(); - sel.addRange(range); - - }); - - element.bind("blur keyup change", function() { - scope.$apply(read); - }); - } - - }; - -}); - -/** + /** * @ngdoc directive * @name umbraco.directives.directive:fixNumber * @restrict A * @description Used in conjunction with type='number' input fields to ensure that the bound value is converted to a number when using ng-model * because normally it thinks it's a string and also validation doesn't work correctly due to an angular bug. **/ -function fixNumber($parse) { - return { - restrict: "A", - require: "ngModel", - - link: function (scope, elem, attrs, ctrl) { - - //parse ngModel onload - var modelVal = scope.$eval(attrs.ngModel); - if (modelVal) { - var asNum = parseFloat(modelVal, 10); - if (!isNaN(asNum)) { - $parse(attrs.ngModel).assign(scope, asNum); + function fixNumber($parse) { + return { + restrict: 'A', + require: 'ngModel', + link: function (scope, elem, attrs, ctrl) { + //parse ngModel onload + var modelVal = scope.$eval(attrs.ngModel); + if (modelVal) { + var asNum = parseFloat(modelVal, 10); + if (!isNaN(asNum)) { + $parse(attrs.ngModel).assign(scope, asNum); + } } - } - - //always return an int to the model - ctrl.$parsers.push(function (value) { - if (value === 0) { - return 0; - } - return parseFloat(value || '', 10); - }); - - //always try to format the model value as an int - ctrl.$formatters.push(function (value) { - if (angular.isString(value)) { - return parseFloat(value, 10); - } - return value; - }); - - //This fixes this angular issue: - //https://github.com/angular/angular.js/issues/2144 - // which doesn't actually validate the number input properly since the model only changes when a real number is entered - // but the input box still allows non-numbers to be entered which do not validate (only via html5) - if (typeof elem.prop('validity') === 'undefined') { - return; - } - - elem.bind('input', function (e) { - var validity = elem.prop('validity'); - scope.$apply(function () { - ctrl.$setValidity('number', !validity.badInput); + //always return an int to the model + ctrl.$parsers.push(function (value) { + if (value === 0) { + return 0; + } + return parseFloat(value || '', 10); }); - }); - } - }; -} -angular.module('umbraco.directives').directive("fixNumber", fixNumber); -angular.module("umbraco.directives").directive('focusWhen', function ($timeout) { - return { - restrict: 'A', - link: function (scope, elm, attrs, ctrl) { - attrs.$observe("focusWhen", function (newValue) { - if (newValue === "true") { - $timeout(function () { - elm.focus(); - }); + //always try to format the model value as an int + ctrl.$formatters.push(function (value) { + if (angular.isString(value)) { + return parseFloat(value, 10); + } + return value; + }); + //This fixes this angular issue: + //https://github.com/angular/angular.js/issues/2144 + // which doesn't actually validate the number input properly since the model only changes when a real number is entered + // but the input box still allows non-numbers to be entered which do not validate (only via html5) + if (typeof elem.prop('validity') === 'undefined') { + return; } - }); - } - }; -}); - - -/** + elem.bind('input', function (e) { + var validity = elem.prop('validity'); + scope.$apply(function () { + ctrl.$setValidity('number', !validity.badInput); + }); + }); + } + }; + } + angular.module('umbraco.directives').directive('fixNumber', fixNumber); + angular.module('umbraco.directives').directive('focusWhen', function ($timeout) { + return { + restrict: 'A', + link: function (scope, elm, attrs, ctrl) { + attrs.$observe('focusWhen', function (newValue) { + if (newValue === 'true') { + $timeout(function () { + elm.focus(); + }); + } + }); + } + }; + }); + /** * @ngdoc directive * @name umbraco.directives.directive:hexBgColor * @restrict A * @description Used to set a hex background color on an element, this will detect valid hex and when it is valid it will set the color, otherwise * a color will not be set. **/ -function hexBgColor() { - return { - restrict: "A", - link: function (scope, element, attr, formCtrl) { - - var origColor = null; - if (attr.hexBgOrig) { - //set the orig based on the attribute if there is one - origColor = attr.hexBgOrig; - } - - attr.$observe("hexBgColor", function (newVal) { - if (newVal) { - if (!origColor) { - //get the orig color before changing it - origColor = element.css("border-color"); - } - //validate it - test with and without the leading hash. - if (/^([0-9a-f]{3}|[0-9a-f]{6})$/i.test(newVal)) { - element.css("background-color", "#" + newVal); - return; - } - if (/^#([0-9a-f]{3}|[0-9a-f]{6})$/i.test(newVal)) { - element.css("background-color", newVal); - return; - } + function hexBgColor() { + return { + restrict: 'A', + link: function (scope, element, attr, formCtrl) { + var origColor = null; + if (attr.hexBgOrig) { + //set the orig based on the attribute if there is one + origColor = attr.hexBgOrig; } - element.css("background-color", origColor); - }); - - } - }; -} -angular.module('umbraco.directives').directive("hexBgColor", hexBgColor); -/** + attr.$observe('hexBgColor', function (newVal) { + if (newVal) { + if (!origColor) { + //get the orig color before changing it + origColor = element.css('border-color'); + } + //validate it - test with and without the leading hash. + if (/^([0-9a-f]{3}|[0-9a-f]{6})$/i.test(newVal)) { + element.css('background-color', '#' + newVal); + return; + } + if (/^#([0-9a-f]{3}|[0-9a-f]{6})$/i.test(newVal)) { + element.css('background-color', newVal); + return; + } + } + element.css('background-color', origColor); + }); + } + }; + } + angular.module('umbraco.directives').directive('hexBgColor', hexBgColor); + /** * @ngdoc directive * @name umbraco.directives.directive:hotkey **/ - -angular.module("umbraco.directives") - .directive('hotkey', function($window, keyboardService, $log) { - - return function(scope, el, attrs) { - + angular.module('umbraco.directives').directive('hotkey', function ($window, keyboardService, $log) { + return function (scope, el, attrs) { var options = {}; var keyCombo = attrs.hotkey; - if (!keyCombo) { //support data binding - keyCombo = scope.$eval(attrs["hotkey"]); + keyCombo = scope.$eval(attrs['hotkey']); } - function activate() { - if (keyCombo) { - // disable shortcuts in input fields if keycombo is 1 character if (keyCombo.length === 1) { - options = { - inputDisabled: true - }; + options = { inputDisabled: true }; } - - keyboardService.bind(keyCombo, function() { - + keyboardService.bind(keyCombo, function () { var element = $(el); var activeElementType = document.activeElement.tagName; - var clickableElements = ["A", "BUTTON"]; - - if (element.is("a,div,button,input[type='button'],input[type='submit'],input[type='checkbox']") && !element.is(':disabled')) { - + var clickableElements = [ + 'A', + 'BUTTON' + ]; + if (element.is('a,div,button,input[type=\'button\'],input[type=\'submit\'],input[type=\'checkbox\']') && !element.is(':disabled')) { if (element.is(':visible') || attrs.hotkeyWhenHidden) { - - if (attrs.hotkeyWhen && attrs.hotkeyWhen === "false") { + if (attrs.hotkeyWhen && attrs.hotkeyWhen === 'false') { return; } - // when keycombo is enter and a link or button has focus - click the link or button instead of using the hotkey - if (keyCombo === "enter" && clickableElements.indexOf(activeElementType) === 0) { + if (keyCombo === 'enter' && clickableElements.indexOf(activeElementType) === 0) { document.activeElement.click(); } else { element.click(); } - } - } else { element.focus(); } - }, options); - - el.on('$destroy', function() { + el.on('$destroy', function () { keyboardService.unbind(keyCombo); }); - } - } - activate(); - }; }); - -/** + /** @ngdoc directive @name umbraco.directives.directive:preventDefault @@ -3063,24 +4319,20 @@ Use this directive to prevent default action of an element. Effectively implemen **/ -angular.module("umbraco.directives") - .directive('preventDefault', function() { - return function(scope, element, attrs) { - + angular.module('umbraco.directives').directive('preventDefault', function () { + return function (scope, element, attrs) { var enabled = true; //check if there's a value for the attribute, if there is and it's false then we conditionally don't //prevent default. if (attrs.preventDefault) { - attrs.$observe("preventDefault", function (newVal) { - enabled = (newVal === "false" || newVal === 0 || newVal === false) ? false : true; + attrs.$observe('preventDefault', function (newVal) { + enabled = newVal === 'false' || newVal === 0 || newVal === false ? false : true; }); } - $(element).click(function (event) { if (event.metaKey || event.ctrlKey) { return; - } - else { + } else { if (enabled === true) { event.preventDefault(); } @@ -3088,33 +4340,29 @@ angular.module("umbraco.directives") }); }; }); - -/** + /** * @ngdoc directive * @name umbraco.directives.directive:preventEnterSubmit * @description prevents a form from submitting when the enter key is pressed on an input field **/ -angular.module("umbraco.directives") - .directive('preventEnterSubmit', function() { - return function(scope, element, attrs) { - + angular.module('umbraco.directives').directive('preventEnterSubmit', function () { + return function (scope, element, attrs) { var enabled = true; //check if there's a value for the attribute, if there is and it's false then we conditionally don't //prevent default. if (attrs.preventEnterSubmit) { - attrs.$observe("preventEnterSubmit", function (newVal) { - enabled = (newVal === "false" || newVal === 0 || newVal === false) ? false : true; + attrs.$observe('preventEnterSubmit', function (newVal) { + enabled = newVal === 'false' || newVal === 0 || newVal === false ? false : true; }); } - $(element).keypress(function (event) { if (event.which === 13) { event.preventDefault(); } }); }; - }); -/** + }); + /** * @ngdoc directive * @name umbraco.directives.directive:resizeToContent * @element div @@ -3130,217 +4378,173 @@ angular.module("umbraco.directives") */ -angular.module("umbraco.directives") - .directive('resizeToContent', function ($window, $timeout) { - return function (scope, el, attrs) { - var iframe = el[0]; - var iframeWin = iframe.contentWindow || iframe.contentDocument.parentWindow; - if (iframeWin.document.body) { - - $timeout(function(){ - var height = iframeWin.document.documentElement.scrollHeight || iframeWin.document.body.scrollHeight; - el.height(height); - }, 3000); - } - }; - }); - -angular.module("umbraco.directives") - .directive('selectOnFocus', function () { - return function (scope, el, attrs) { - $(el).bind("click", function () { - var editmode = $(el).data("editmode"); - //If editmode is true a click is handled like a normal click - if (!editmode) { - //Initial click, select entire text - this.select(); - //Set the edit mode so subsequent clicks work normally - $(el).data("editmode", true); + angular.module('umbraco.directives').directive('resizeToContent', function ($window, $timeout) { + return function (scope, el, attrs) { + var iframe = el[0]; + var iframeWin = iframe.contentWindow || iframe.contentDocument.parentWindow; + if (iframeWin.document.body) { + $timeout(function () { + var height = iframeWin.document.documentElement.scrollHeight || iframeWin.document.body.scrollHeight; + el.height(height); + }, 3000); } - }). - bind("blur", function () { - //Reset on focus lost - $(el).data("editmode", false); - }); - }; - }); - -angular.module("umbraco.directives") - .directive('umbAutoFocus', function($timeout) { - - return function(scope, element, attr){ - var update = function() { + }; + }); + angular.module('umbraco.directives').directive('selectOnFocus', function () { + return function (scope, el, attrs) { + $(el).bind('click', function () { + var editmode = $(el).data('editmode'); + //If editmode is true a click is handled like a normal click + if (!editmode) { + //Initial click, select entire text + this.select(); + //Set the edit mode so subsequent clicks work normally + $(el).data('editmode', true); + } + }).bind('blur', function () { + //Reset on focus lost + $(el).data('editmode', false); + }); + }; + }); + angular.module('umbraco.directives').directive('umbAutoFocus', function ($timeout) { + return function (scope, element, attr) { + var update = function () { //if it uses its default naming - if(element.val() === "" || attr.focusOnFilled){ + if (element.val() === '' || attr.focusOnFilled) { element.focus(); } }; - - $timeout(function() { + $timeout(function () { update(); }); - }; -}); - -angular.module("umbraco.directives") - .directive('umbAutoResize', function($timeout) { - return { - require: ["^?umbTabs", "ngModel"], - link: function(scope, element, attr, controllersArr) { - - var domEl = element[0]; - var domElType = domEl.type; - var umbTabsController = controllersArr[0]; - var ngModelController = controllersArr[1]; - - // IE elements - var isIEFlag = false; - var wrapper = angular.element('#umb-ie-resize-input-wrapper'); - var mirror = angular.element(''); - - function isIE() { - - var ua = window.navigator.userAgent; - var msie = ua.indexOf("MSIE "); - - if (msie > 0 || !!navigator.userAgent.match(/Trident.*rv\:11\./) || navigator.userAgent.match(/Edge\/\d+/)) { - return true; - } else { - return false; - } - + }; + }); + angular.module('umbraco.directives').directive('umbAutoResize', function ($timeout) { + return { + require: [ + '^?umbTabs', + 'ngModel' + ], + link: function (scope, element, attr, controllersArr) { + var domEl = element[0]; + var domElType = domEl.type; + var umbTabsController = controllersArr[0]; + var ngModelController = controllersArr[1]; + // IE elements + var isIEFlag = false; + var wrapper = angular.element('#umb-ie-resize-input-wrapper'); + var mirror = angular.element(''); + function isIE() { + var ua = window.navigator.userAgent; + var msie = ua.indexOf('MSIE '); + if (msie > 0 || !!navigator.userAgent.match(/Trident.*rv\:11\./) || navigator.userAgent.match(/Edge\/\d+/)) { + return true; + } else { + return false; + } + } + function activate() { + // check if browser is Internet Explorere + isIEFlag = isIE(); + // scrollWidth on element does not work in IE on inputs + // we have to do some dirty dom element copying. + if (isIEFlag === true && domElType === 'text') { + setupInternetExplorerElements(); + } + } + function setupInternetExplorerElements() { + if (!wrapper.length) { + wrapper = angular.element('
    '); + angular.element('body').append(wrapper); + } + angular.forEach([ + 'fontFamily', + 'fontSize', + 'fontWeight', + 'fontStyle', + 'letterSpacing', + 'textTransform', + 'wordSpacing', + 'textIndent', + 'boxSizing', + 'borderRightWidth', + 'borderLeftWidth', + 'borderLeftStyle', + 'borderRightStyle', + 'paddingLeft', + 'paddingRight', + 'marginLeft', + 'marginRight' + ], function (value) { + mirror.css(value, element.css(value)); + }); + wrapper.append(mirror); + } + function resizeInternetExplorerInput() { + mirror.text(element.val() || attr.placeholder); + element.css('width', mirror.outerWidth() + 1); + } + function resizeInput() { + if (domEl.scrollWidth !== domEl.clientWidth) { + if (ngModelController.$modelValue) { + element.width(domEl.scrollWidth); + } + } + if (!ngModelController.$modelValue && attr.placeholder) { + attr.$set('size', attr.placeholder.length); + element.width('auto'); + } + } + function resizeTextarea() { + if (domEl.scrollHeight !== domEl.clientHeight) { + element.height(domEl.scrollHeight); + } + } + var update = function (force) { + if (force === true) { + if (domElType === 'textarea') { + element.height(0); + } else if (domElType === 'text') { + element.width(0); + } + } + if (isIEFlag === true && domElType === 'text') { + resizeInternetExplorerInput(); + } else { + if (domElType === 'textarea') { + resizeTextarea(); + } else if (domElType === 'text') { + resizeInput(); + } + } + }; + activate(); + //listen for tab changes + if (umbTabsController != null) { + umbTabsController.onTabShown(function (args) { + update(); + }); + } + // listen for ng-model changes + var unbindModelWatcher = scope.$watch(function () { + return ngModelController.$modelValue; + }, function (newValue) { + update(true); + }); + scope.$on('$destroy', function () { + element.unbind('keyup keydown keypress change', update); + element.unbind('blur', update(true)); + unbindModelWatcher(); + // clean up IE dom element + if (isIEFlag === true && domElType === 'text') { + mirror.remove(); + } + }); } - - function activate() { - - // check if browser is Internet Explorere - isIEFlag = isIE(); - - // scrollWidth on element does not work in IE on inputs - // we have to do some dirty dom element copying. - if (isIEFlag === true && domElType === "text") { - setupInternetExplorerElements(); - } - - } - - function setupInternetExplorerElements() { - - if (!wrapper.length) { - wrapper = angular.element('
    '); - angular.element('body').append(wrapper); - } - - angular.forEach(['fontFamily', 'fontSize', 'fontWeight', 'fontStyle', - 'letterSpacing', 'textTransform', 'wordSpacing', 'textIndent', - 'boxSizing', 'borderRightWidth', 'borderLeftWidth', 'borderLeftStyle', 'borderRightStyle', - 'paddingLeft', 'paddingRight', 'marginLeft', 'marginRight' - ], function(value) { - mirror.css(value, element.css(value)); - }); - - wrapper.append(mirror); - - } - - function resizeInternetExplorerInput() { - - mirror.text(element.val() || attr.placeholder); - element.css('width', mirror.outerWidth() + 1); - - } - - function resizeInput() { - - if (domEl.scrollWidth !== domEl.clientWidth) { - if (ngModelController.$modelValue) { - element.width(domEl.scrollWidth); - } - } - - if(!ngModelController.$modelValue && attr.placeholder) { - attr.$set('size', attr.placeholder.length); - element.width('auto'); - } - - } - - function resizeTextarea() { - - if(domEl.scrollHeight !== domEl.clientHeight) { - - element.height(domEl.scrollHeight); - - } - - } - - var update = function(force) { - - - if (force === true) { - - if (domElType === "textarea") { - element.height(0); - } else if (domElType === "text") { - element.width(0); - } - - } - - - if (isIEFlag === true && domElType === "text") { - - resizeInternetExplorerInput(); - - } else { - - if (domElType === "textarea") { - - resizeTextarea(); - - } else if (domElType === "text") { - - resizeInput(); - - } - - } - - }; - - activate(); - - //listen for tab changes - if (umbTabsController != null) { - umbTabsController.onTabShown(function(args) { - update(); - }); - } - - // listen for ng-model changes - var unbindModelWatcher = scope.$watch(function() { - return ngModelController.$modelValue; - }, function(newValue) { - update(true); - }); - - scope.$on('$destroy', function() { - element.unbind('keyup keydown keypress change', update); - element.unbind('blur', update(true)); - unbindModelWatcher(); - - // clean up IE dom element - if (isIEFlag === true && domElType === "text") { - mirror.remove(); - } - - }); - } - }; - }); - -/* + }; + }); + /* example usage: jsonEditing is a string which we edit in a textarea. we try parsing to JSON with each change. when it is valid, propagate model changes via ngModelCtrl @@ -3349,128 +4553,103 @@ use isolate scope to prevent model propagation when invalid - will update manual will override element type to textarea and add own attribute ngModel tied to jsonEditing */ - -angular.module("umbraco.directives") - .directive('umbRawModel', function () { - return { - restrict: 'A', - require: 'ngModel', - template: '', - replace : true, - scope: { - model: '=umbRawModel', - validateOn:'=' - }, - link: function (scope, element, attrs, ngModelCtrl) { - - function setEditing (value) { - scope.jsonEditing = angular.copy( jsonToString(value)); - } - - function updateModel (value) { - scope.model = stringToJson(value); - } - - function setValid() { - ngModelCtrl.$setValidity('json', true); - } - - function setInvalid () { - ngModelCtrl.$setValidity('json', false); - } - - function stringToJson(text) { - try { - return angular.fromJson(text); - } catch (err) { - setInvalid(); - return text; - } - } - - function jsonToString(object) { - // better than JSON.stringify(), because it formats + filters $$hashKey etc. - // NOTE that this will remove all $-prefixed values - return angular.toJson(object, true); - } - - function isValidJson(model) { - var flag = true; - try { - angular.fromJson(model); - } catch (err) { - flag = false; - } - return flag; - } - - //init - setEditing(scope.model); - - var onInputChange = function(newval,oldval){ - if (newval !== oldval) { - if (isValidJson(newval)) { - setValid(); - updateModel(newval); - } else { - setInvalid(); - } - } - }; - - if(scope.validateOn){ - element.on(scope.validateOn, function(){ - scope.$apply(function(){ - onInputChange(scope.jsonEditing); - }); - }); - }else{ - //check for changes going out - scope.$watch('jsonEditing', onInputChange, true); - } - - //check for changes coming in - scope.$watch('model', function (newval, oldval) { - if (newval !== oldval) { - setEditing(newval); - } - }, true); - - } - }; - }); - -(function() { - 'use strict'; - - function SelectWhen($timeout) { - - function link(scope, el, attr, ctrl) { - - attr.$observe("umbSelectWhen", function(newValue) { - if (newValue === "true") { - $timeout(function() { - el.select(); - }); - } - }); - - } - - var directive = { + angular.module('umbraco.directives').directive('umbRawModel', function () { + return { restrict: 'A', - link: link + require: 'ngModel', + template: '', + replace: true, + scope: { + model: '=umbRawModel', + validateOn: '=' + }, + link: function (scope, element, attrs, ngModelCtrl) { + function setEditing(value) { + scope.jsonEditing = angular.copy(jsonToString(value)); + } + function updateModel(value) { + scope.model = stringToJson(value); + } + function setValid() { + ngModelCtrl.$setValidity('json', true); + } + function setInvalid() { + ngModelCtrl.$setValidity('json', false); + } + function stringToJson(text) { + try { + return angular.fromJson(text); + } catch (err) { + setInvalid(); + return text; + } + } + function jsonToString(object) { + // better than JSON.stringify(), because it formats + filters $$hashKey etc. + // NOTE that this will remove all $-prefixed values + return angular.toJson(object, true); + } + function isValidJson(model) { + var flag = true; + try { + angular.fromJson(model); + } catch (err) { + flag = false; + } + return flag; + } + //init + setEditing(scope.model); + var onInputChange = function (newval, oldval) { + if (newval !== oldval) { + if (isValidJson(newval)) { + setValid(); + updateModel(newval); + } else { + setInvalid(); + } + } + }; + if (scope.validateOn) { + element.on(scope.validateOn, function () { + scope.$apply(function () { + onInputChange(scope.jsonEditing); + }); + }); + } else { + //check for changes going out + scope.$watch('jsonEditing', onInputChange, true); + } + //check for changes coming in + scope.$watch('model', function (newval, oldval) { + if (newval !== oldval) { + setEditing(newval); + } + }, true); + } }; - - return directive; - } - - angular.module('umbraco.directives').directive('umbSelectWhen', SelectWhen); - -})(); - -angular.module("umbraco.directives") - .directive('gridRte', function (tinyMceService, stylesheetResource, angularHelper, assetsService, $q, $timeout) { + }); + (function () { + 'use strict'; + function SelectWhen($timeout) { + function link(scope, el, attr, ctrl) { + attr.$observe('umbSelectWhen', function (newValue) { + if (newValue === 'true') { + $timeout(function () { + el.select(); + }); + } + }); + } + var directive = { + restrict: 'A', + link: link + }; + return directive; + } + angular.module('umbraco.directives').directive('umbSelectWhen', SelectWhen); + }()); + angular.module('umbraco.directives').directive('gridRte', function (tinyMceService, stylesheetResource, angularHelper, assetsService, $q, $timeout) { return { scope: { uniqueId: '=', @@ -3478,104 +4657,124 @@ angular.module("umbraco.directives") onClick: '&', onFocus: '&', onBlur: '&', - configuration:"=", - onMediaPickerClick: "=", - onEmbedClick: "=", - onMacroPickerClick: "=", - onLinkPickerClick: "=" + configuration: '=', + onMediaPickerClick: '=', + onEmbedClick: '=', + onMacroPickerClick: '=', + onLinkPickerClick: '=' }, - template: "", + template: '', replace: true, link: function (scope, element, attrs) { - var initTiny = function () { - //we always fetch the default one, and then override parts with our own tinyMceService.configuration().then(function (tinyMceConfig) { - - - //config value from general tinymce.config file var validElements = tinyMceConfig.validElements; - var fallbackStyles = [{title: "Page header", block: "h2"}, {title: "Section header", block: "h3"}, {title: "Paragraph header", block: "h4"}, {title: "Normal", block: "p"}, {title: "Quote", block: "blockquote"}, {title: "Code", block: "code"}]; - + var fallbackStyles = [ + { + title: 'Page header', + block: 'h2' + }, + { + title: 'Section header', + block: 'h3' + }, + { + title: 'Paragraph header', + block: 'h4' + }, + { + title: 'Normal', + block: 'p' + }, + { + title: 'Quote', + block: 'blockquote' + }, + { + title: 'Code', + block: 'code' + } + ]; //These are absolutely required in order for the macros to render inline //we put these as extended elements because they get merged on top of the normal allowed elements by tiny mce - var extendedValidElements = "@[id|class|style],-div[id|dir|class|align|style],ins[datetime|cite],-ul[class|style],-li[class|style],-h1[id|dir|class|align|style],-h2[id|dir|class|align|style],-h3[id|dir|class|align|style],-h4[id|dir|class|align|style],-h5[id|dir|class|align|style],-h6[id|style|dir|class|align],span[id|class|style]"; - + var extendedValidElements = '@[id|class|style],-div[id|dir|class|align|style],ins[datetime|cite],-ul[class|style],-li[class|style],-h1[id|dir|class|align|style],-h2[id|dir|class|align|style],-h3[id|dir|class|align|style],-h4[id|dir|class|align|style],-h5[id|dir|class|align|style],-h6[id|style|dir|class|align],span[id|class|style]'; var invalidElements = tinyMceConfig.inValidElements; var plugins = _.map(tinyMceConfig.plugins, function (plugin) { if (plugin.useOnFrontend) { return plugin.name; } - }).join(" ") + " autoresize"; - + }).join(' ') + ' autoresize'; //config value on the data type - var toolbar = ["code", "styleselect", "bold", "italic", "alignleft", "aligncenter", "alignright", "bullist", "numlist", "link", "umbmediapicker", "umbembeddialog"].join(" | "); + var toolbar = [ + 'code', + 'styleselect', + 'bold', + 'italic', + 'alignleft', + 'aligncenter', + 'alignright', + 'bullist', + 'numlist', + 'link', + 'umbmediapicker', + 'umbembeddialog' + ].join(' | '); var stylesheets = []; - var styleFormats = []; var await = []; - //queue file loading - if (typeof (tinymce) === "undefined") { - await.push(assetsService.loadJs("lib/tinymce/tinymce.min.js", scope)); + if (typeof tinymce === 'undefined') { + await.push(assetsService.loadJs('lib/tinymce/tinymce.min.js', scope)); } - - - if(scope.configuration && scope.configuration.toolbar){ + if (scope.configuration && scope.configuration.toolbar) { toolbar = scope.configuration.toolbar.join(' | '); } - - - if(scope.configuration && scope.configuration.stylesheets){ - angular.forEach(scope.configuration.stylesheets, function(stylesheet, key){ - - stylesheets.push(Umbraco.Sys.ServerVariables.umbracoSettings.cssPath + "/" + stylesheet + ".css"); - await.push(stylesheetResource.getRulesByName(stylesheet).then(function (rules) { - angular.forEach(rules, function (rule) { - var r = {}; - var split = ""; - r.title = rule.name; - if (rule.selector[0] === ".") { - r.inline = "span"; - r.classes = rule.selector.substring(1); - }else if (rule.selector[0] === "#") { - //Even though this will render in the style drop down, it will not actually be applied - // to the elements, don't think TinyMCE even supports this and it doesn't really make much sense - // since only one element can have one id. - r.inline = "span"; - r.attributes = { id: rule.selector.substring(1) }; - }else if (rule.selector[0] !== "." && rule.selector.indexOf(".") > -1) { - split = rule.selector.split("."); - r.block = split[0]; - r.classes = rule.selector.substring(rule.selector.indexOf(".") + 1).replace(".", " "); - }else if (rule.selector[0] !== "#" && rule.selector.indexOf("#") > -1) { - split = rule.selector.split("#"); - r.block = split[0]; - r.classes = rule.selector.substring(rule.selector.indexOf("#") + 1); - }else { - r.block = rule.selector; - } - styleFormats.push(r); - }); - })); + if (scope.configuration && scope.configuration.stylesheets) { + angular.forEach(scope.configuration.stylesheets, function (stylesheet, key) { + stylesheets.push(Umbraco.Sys.ServerVariables.umbracoSettings.cssPath + '/' + stylesheet + '.css'); + await.push(stylesheetResource.getRulesByName(stylesheet).then(function (rules) { + angular.forEach(rules, function (rule) { + var r = {}; + var split = ''; + r.title = rule.name; + if (rule.selector[0] === '.') { + r.inline = 'span'; + r.classes = rule.selector.substring(1); + } else if (rule.selector[0] === '#') { + //Even though this will render in the style drop down, it will not actually be applied + // to the elements, don't think TinyMCE even supports this and it doesn't really make much sense + // since only one element can have one id. + r.inline = 'span'; + r.attributes = { id: rule.selector.substring(1) }; + } else if (rule.selector[0] !== '.' && rule.selector.indexOf('.') > -1) { + split = rule.selector.split('.'); + r.block = split[0]; + r.classes = rule.selector.substring(rule.selector.indexOf('.') + 1).replace('.', ' '); + } else if (rule.selector[0] !== '#' && rule.selector.indexOf('#') > -1) { + split = rule.selector.split('#'); + r.block = split[0]; + r.classes = rule.selector.substring(rule.selector.indexOf('#') + 1); + } else { + r.block = rule.selector; + } + styleFormats.push(r); + }); + })); }); - }else{ - stylesheets.push("views/propertyeditors/grid/config/grid.default.rtestyles.css"); + } else { + stylesheets.push('views/propertyeditors/grid/config/grid.default.rtestyles.css'); styleFormats = fallbackStyles; } - //stores a reference to the editor var tinyMceEditor = null; $q.all(await).then(function () { - var uniqueId = scope.uniqueId; - //create a baseline Config to exten upon var baseLineConfigObj = { - mode: "exact", - skin: "umbraco", + mode: 'exact', + skin: 'umbraco', plugins: plugins, valid_elements: validElements, invalid_elements: invalidElements, @@ -3586,12 +4785,11 @@ angular.module("umbraco.directives") toolbar: toolbar, content_css: stylesheets, style_formats: styleFormats, - autoresize_bottom_margin: 0 + autoresize_bottom_margin: 0, + //see http://archive.tinymce.com/wiki.php/Configuration:cache_suffix + cache_suffix: '?umb__rnd=' + Umbraco.Sys.ServerVariables.application.cacheBuster }; - - if (tinyMceConfig.customConfig) { - //if there is some custom config, we need to see if the string value of each item might actually be json and if so, we need to // convert it to json instead of having it as a string since this is what tinymce requires for (var i in tinyMceConfig.customConfig) { @@ -3608,79 +4806,95 @@ angular.module("umbraco.directives") //concat it and below this concat'd array will overwrite the baseline in angular.extend tinyMceConfig.customConfig[i] = baseLineConfigObj[i].concat(tinyMceConfig.customConfig[i]); } - } - catch (e) { - //cannot parse, we'll just leave it + } catch (e) { } } } + if (val === 'true') { + tinyMceConfig.customConfig[i] = true; + } + if (val === 'false') { + tinyMceConfig.customConfig[i] = false; + } } - angular.extend(baseLineConfigObj, tinyMceConfig.customConfig); } - //set all the things that user configs should not be able to override baseLineConfigObj.elements = uniqueId; baseLineConfigObj.setup = function (editor) { - //set the reference tinyMceEditor = editor; - - //enable browser based spell checking editor.on('init', function (e) { - editor.getBody().setAttribute('spellcheck', true); - //force overflow to hidden to prevent no needed scroll - editor.getBody().style.overflow = "hidden"; - - $timeout(function(){ - if(scope.value === null){ + editor.getBody().style.overflow = 'hidden'; + $timeout(function () { + if (scope.value === null) { editor.focus(); } }, 400); - }); - + // pin toolbar to top of screen if we have focus and it scrolls off the screen + var pinToolbar = function () { + var _toolbar = $(editor.editorContainer).find('.mce-toolbar'); + var toolbarHeight = _toolbar.height(); + var _tinyMce = $(editor.editorContainer); + var tinyMceRect = _tinyMce[0].getBoundingClientRect(); + var tinyMceTop = tinyMceRect.top; + var tinyMceBottom = tinyMceRect.bottom; + var tinyMceWidth = tinyMceRect.width; + var _tinyMceEditArea = _tinyMce.find('.mce-edit-area'); + // set padding in top of mce so the content does not "jump" up + _tinyMceEditArea.css('padding-top', toolbarHeight); + if (tinyMceTop < 160 && 160 + toolbarHeight < tinyMceBottom) { + _toolbar.css('visibility', 'visible').css('position', 'fixed').css('top', '160px').css('margin-top', '0').css('width', tinyMceWidth); + } else { + _toolbar.css('visibility', 'visible').css('position', 'absolute').css('top', 'auto').css('margin-top', '0').css('width', tinyMceWidth); + } + }; + // unpin toolbar to top of screen + var unpinToolbar = function () { + var _toolbar = $(editor.editorContainer).find('.mce-toolbar'); + var _tinyMce = $(editor.editorContainer); + var _tinyMceEditArea = _tinyMce.find('.mce-edit-area'); + // reset padding in top of mce so the content does not "jump" up + _tinyMceEditArea.css('padding-top', '0'); + _toolbar.css('position', 'static'); + }; //when we leave the editor (maybe) editor.on('blur', function (e) { editor.save(); angularHelper.safeApply(scope, function () { scope.value = editor.getContent(); - - var _toolbar = $(editor.editorContainer) - .find(".mce-toolbar"); - - if(scope.onBlur){ + var _toolbar = $(editor.editorContainer).find('.mce-toolbar'); + if (scope.onBlur) { scope.onBlur(); } - + unpinToolbar(); + $('.umb-panel-body').off('scroll', pinToolbar); }); }); - // Focus on editor editor.on('focus', function (e) { angularHelper.safeApply(scope, function () { - - if(scope.onFocus){ + if (scope.onFocus) { scope.onFocus(); } - + pinToolbar(); + $('.umb-panel-body').on('scroll', pinToolbar); }); }); - // Click on editor editor.on('click', function (e) { angularHelper.safeApply(scope, function () { - - if(scope.onClick){ + if (scope.onClick) { scope.onClick(); } - + pinToolbar(); + $('.umb-panel-body').on('scroll', pinToolbar); }); }); - //when buttons modify content editor.on('ExecCommand', function (e) { editor.save(); @@ -3688,7 +4902,6 @@ angular.module("umbraco.directives") scope.value = editor.getContent(); }); }); - // Update model on keypress editor.on('KeyUp', function (e) { editor.save(); @@ -3696,7 +4909,6 @@ angular.module("umbraco.directives") scope.value = editor.getContent(); }); }); - // Update model on change, i.e. copy/pasted text, plugins altering content editor.on('SetContent', function (e) { if (!e.initial) { @@ -3706,47 +4918,39 @@ angular.module("umbraco.directives") }); } }); - editor.on('ObjectResized', function (e) { - var qs = "?width=" + e.width + "&height=" + e.height; - var srcAttr = $(e.target).attr("src"); - var path = srcAttr.split("?")[0]; - $(e.target).attr("data-mce-src", path + qs); + var qs = '?width=' + e.width + '&height=' + e.height; + var srcAttr = $(e.target).attr('src'); + var path = srcAttr.split('?')[0]; + $(e.target).attr('data-mce-src', path + qs); }); - //Create the insert link plugin - tinyMceService.createLinkPicker(editor, scope, function(currentTarget, anchorElement){ - if(scope.onLinkPickerClick) { + tinyMceService.createLinkPicker(editor, scope, function (currentTarget, anchorElement) { + if (scope.onLinkPickerClick) { scope.onLinkPickerClick(editor, currentTarget, anchorElement); } }); - //Create the insert media plugin - tinyMceService.createMediaPicker(editor, scope, function(currentTarget, userData){ - if(scope.onMediaPickerClick) { + tinyMceService.createMediaPicker(editor, scope, function (currentTarget, userData) { + if (scope.onMediaPickerClick) { scope.onMediaPickerClick(editor, currentTarget, userData); } }); - //Create the embedded plugin - tinyMceService.createInsertEmbeddedMedia(editor, scope, function(){ - if(scope.onEmbedClick) { + tinyMceService.createInsertEmbeddedMedia(editor, scope, function () { + if (scope.onEmbedClick) { scope.onEmbedClick(editor); } }); - //Create the insert macro plugin - tinyMceService.createInsertMacro(editor, scope, function(dialogData){ - if(scope.onMacroPickerClick) { + tinyMceService.createInsertMacro(editor, scope, function (dialogData) { + if (scope.onMacroPickerClick) { scope.onMacroPickerClick(editor, dialogData); } }); - }; - /** Loads in the editor */ function loadTinyMce() { - //we need to add a timeout here, to force a redraw so TinyMCE can find //the elements needed $timeout(function () { @@ -3754,9 +4958,7 @@ angular.module("umbraco.directives") tinymce.init(baseLineConfigObj); }, 150, false); } - loadTinyMce(); - //here we declare a special method which will be called whenever the value has changed from the server //this is instead of doing a watch on the model.value = faster //scope.model.onValueChanged = function (newVal, oldVal) { @@ -3766,87 +4968,211 @@ angular.module("umbraco.directives") // // is required for our plugins listening to this event to execute // tinyMceEditor.fire('LoadContent', null); //}; - //listen for formSubmitting event (the result is callback used to remove the event subscription) - var unsubscribe = scope.$on("formSubmitting", function () { + var unsubscribe = scope.$on('formSubmitting', function () { //TODO: Here we should parse out the macro rendered content so we can save on a lot of bytes in data xfer // we do parse it out on the server side but would be nice to do that on the client side before as well. - scope.value = tinyMceEditor.getContent(); + scope.value = tinyMceEditor ? tinyMceEditor.getContent() : null; }); - //when the element is disposed we need to unsubscribe! // NOTE: this is very important otherwise if this is part of a modal, the listener still exists because the dom // element might still be there even after the modal has been hidden. scope.$on('$destroy', function () { unsubscribe(); + if (tinyMceEditor !== undefined && tinyMceEditor != null) { + tinyMceEditor.destroy(); + } }); - }); - }); - }; - initTiny(); - } }; }); - -/** -* @ngdoc directive -* @name umbraco.directives.directive:umbControlGroup -* @restrict E -**/ -angular.module("umbraco.directives.html") - .directive('umbControlGroup', function (localizationService) { - return { - scope: { - label: "@label", - description: "@", - hideLabel: "@", - alias: "@" - }, - require: '?^form', - transclude: true, - restrict: 'E', - replace: true, - templateUrl: 'views/components/html/umb-control-group.html', - link: function (scope, element, attr, formCtrl) { - - scope.formValid = function() { - if (formCtrl) { - return formCtrl.$valid; - } - //there is no form. - return true; - }; - - if (scope.label && scope.label[0] === "@") { - scope.labelstring = localizationService.localize(scope.label.substring(1)); - } - else { - scope.labelstring = scope.label; - } - - if (scope.description && scope.description[0] === "@") { - scope.descriptionstring = localizationService.localize(scope.description.substring(1)); - } - else { - scope.descriptionstring = scope.description; - } - - } - }; - }); - -/** + /** +@ngdoc directive +@name umbraco.directives.directive:umbBox +@restrict E + +@description +Use this directive to render an already styled empty div tag. + +

    Markup example

    +
    +    
    +        
    +        
    +            // Content here
    +        
    +    
    +
    + +

    Use in combination with:

    +
      +
    • {@link umbraco.directives.directive:umbBoxHeader umbBoxHeader}
    • +
    • {@link umbraco.directives.directive:umbBoxContent umbBoxContent}
    • +
    +**/ + (function () { + 'use strict'; + function BoxDirective() { + var directive = { + restrict: 'E', + replace: true, + transclude: true, + templateUrl: 'views/components/html/umb-box/umb-box.html' + }; + return directive; + } + angular.module('umbraco.directives').directive('umbBox', BoxDirective); + }()); + /** +@ngdoc directive +@name umbraco.directives.directive:umbBoxContent +@restrict E + +@description +Use this directive to render an empty container. Recommended to use it inside an {@link umbraco.directives.directive:umbBox umbBox} directive. See documentation for {@link umbraco.directives.directive:umbBox umbBox}. + +

    Markup example

    +
    +    
    +        
    +        
    +            // Content here
    +        
    +    
    +
    + +

    Use in combination with:

    +
      +
    • {@link umbraco.directives.directive:umbBox umbBox}
    • +
    • {@link umbraco.directives.directive:umbBoxHeader umbBoxHeader}
    • +
    +**/ + (function () { + 'use strict'; + function BoxContentDirective() { + var directive = { + restrict: 'E', + replace: true, + transclude: true, + templateUrl: 'views/components/html/umb-box/umb-box-content.html' + }; + return directive; + } + angular.module('umbraco.directives').directive('umbBoxContent', BoxContentDirective); + }()); + /** +@ngdoc directive +@name umbraco.directives.directive:umbBoxHeader +@restrict E +@scope + +@description +Use this directive to construct a title. Recommended to use it inside an {@link umbraco.directives.directive:umbBox umbBox} directive. See documentation for {@link umbraco.directives.directive:umbBox umbBox}. + +

    Markup example

    +
    +    
    +        
    +        
    +            // Content here
    +        
    +    
    +
    + +

    Markup example with using titleKey

    +
    +    
    +        // the title-key property needs an areaAlias_keyAlias from the language files
    +        
    +        
    +            // Content here
    +        
    +    
    +
    +{@link https://our.umbraco.com/documentation/extending/language-files/ Here you can see more about the language files} + +

    Use in combination with:

    +
      +
    • {@link umbraco.directives.directive:umbBox umbBox}
    • +
    • {@link umbraco.directives.directive:umbBoxContent umbBoxContent}
    • +
    + +@param {string=} title (attrbute): Custom title text. +@param {string=} titleKey (attrbute): The translation key from the language xml files. +@param {string=} description (attrbute): Custom description text. +@param {string=} descriptionKey (attrbute): The translation key from the language xml files. +**/ + (function () { + 'use strict'; + function BoxHeaderDirective() { + var directive = { + restrict: 'E', + replace: true, + transclude: true, + templateUrl: 'views/components/html/umb-box/umb-box-header.html', + scope: { + titleKey: '@?', + title: '@?', + descriptionKey: '@?', + description: '@?' + } + }; + return directive; + } + angular.module('umbraco.directives').directive('umbBoxHeader', BoxHeaderDirective); + }()); + /** +* @ngdoc directive +* @name umbraco.directives.directive:umbControlGroup +* @restrict E +**/ + angular.module('umbraco.directives.html').directive('umbControlGroup', function (localizationService) { + return { + scope: { + label: '@label', + description: '@', + hideLabel: '@', + alias: '@', + labelFor: '@', + required: '@?' + }, + require: '?^form', + transclude: true, + restrict: 'E', + replace: true, + templateUrl: 'views/components/html/umb-control-group.html', + link: function (scope, element, attr, formCtrl) { + scope.formValid = function () { + if (formCtrl && scope.labelFor) { + //if a label-for has been set, use that for the validation + return formCtrl[scope.labelFor].$valid; + } + //there is no form. + return true; + }; + if (scope.label && scope.label[0] === '@') { + scope.labelstring = localizationService.localize(scope.label.substring(1)); + } else { + scope.labelstring = scope.label; + } + if (scope.description && scope.description[0] === '@') { + scope.descriptionstring = localizationService.localize(scope.description.substring(1)); + } else { + scope.descriptionstring = scope.description; + } + } + }; + }); + /** * @ngdoc directive * @name umbraco.directives.directive:umbPane * @restrict E **/ -angular.module("umbraco.directives.html") - .directive('umbPane', function () { + angular.module('umbraco.directives.html').directive('umbPane', function () { return { transclude: true, restrict: 'E', @@ -3854,637 +5180,586 @@ angular.module("umbraco.directives.html") templateUrl: 'views/components/html/umb-pane.html' }; }); - -/** -* @ngdoc directive -* @name umbraco.directives.directive:umbPanel -* @restrict E -**/ -angular.module("umbraco.directives.html") - .directive('umbPanel', function($timeout, $log){ - return { - restrict: 'E', - replace: true, - transclude: 'true', - templateUrl: 'views/components/html/umb-panel.html' - }; - }); - -/** + /** +* @ngdoc directive +* @name umbraco.directives.directive:umbPanel +* @restrict E +**/ + angular.module('umbraco.directives.html').directive('umbPanel', function ($timeout, $log) { + return { + restrict: 'E', + replace: true, + transclude: 'true', + templateUrl: 'views/components/html/umb-panel.html' + }; + }); + /** * @ngdoc directive * @name umbraco.directives.directive:umbImageCrop * @restrict E * @function **/ -angular.module("umbraco.directives") - .directive('umbImageCrop', - function ($timeout, localizationService, cropperHelper, $log) { - return { - restrict: 'E', - replace: true, - templateUrl: 'views/components/imaging/umb-image-crop.html', - scope: { - src: '=', - width: '@', - height: '@', - crop: "=", - center: "=", - maxSize: '@' - }, - - link: function(scope, element, attrs) { - scope.width = 400; - scope.height = 320; - - scope.dimensions = { - image: {}, - cropper:{}, - viewport:{}, - margin: 20, - scale: { - min: 0.3, - max: 3, - current: 1 - } - }; - - - //live rendering of viewport and image styles - scope.style = function () { - return { - 'height': (parseInt(scope.dimensions.viewport.height, 10)) + 'px', - 'width': (parseInt(scope.dimensions.viewport.width, 10)) + 'px' - }; - }; - - - //elements - var $viewport = element.find(".viewport"); - var $image = element.find("img"); - var $overlay = element.find(".overlay"); - var $container = element.find(".crop-container"); - - //default constraints for drag n drop - var constraints = {left: {max: scope.dimensions.margin, min: scope.dimensions.margin}, top: {max: scope.dimensions.margin, min: scope.dimensions.margin}, }; - scope.constraints = constraints; - - - //set constaints for cropping drag and drop - var setConstraints = function(){ - constraints.left.min = scope.dimensions.margin + scope.dimensions.cropper.width - scope.dimensions.image.width; - constraints.top.min = scope.dimensions.margin + scope.dimensions.cropper.height - scope.dimensions.image.height; - }; - - - var setDimensions = function(originalImage){ - originalImage.width("auto"); - originalImage.height("auto"); - - var image = {}; - image.originalWidth = originalImage.width(); - image.originalHeight = originalImage.height(); - - image.width = image.originalWidth; - image.height = image.originalHeight; - image.left = originalImage[0].offsetLeft; - image.top = originalImage[0].offsetTop; - - scope.dimensions.image = image; - - //unscaled editor size - //var viewPortW = $viewport.width(); - //var viewPortH = $viewport.height(); - var _viewPortW = parseInt(scope.width, 10); - var _viewPortH = parseInt(scope.height, 10); - - //if we set a constraint we will scale it down if needed - if(scope.maxSize){ - var ratioCalculation = cropperHelper.scaleToMaxSize( - _viewPortW, - _viewPortH, - scope.maxSize); - - //so if we have a max size, override the thumb sizes - _viewPortW = ratioCalculation.width; - _viewPortH = ratioCalculation.height; - } - - scope.dimensions.viewport.width = _viewPortW + 2 * scope.dimensions.margin; - scope.dimensions.viewport.height = _viewPortH + 2 * scope.dimensions.margin; - scope.dimensions.cropper.width = _viewPortW; // scope.dimensions.viewport.width - 2 * scope.dimensions.margin; - scope.dimensions.cropper.height = _viewPortH; // scope.dimensions.viewport.height - 2 * scope.dimensions.margin; - }; - - - //when loading an image without any crop info, we center and fit it - var resizeImageToEditor = function(){ - //returns size fitting the cropper - var size = cropperHelper.calculateAspectRatioFit( - scope.dimensions.image.width, - scope.dimensions.image.height, - scope.dimensions.cropper.width, - scope.dimensions.cropper.height, - true); - - //sets the image size and updates the scope - scope.dimensions.image.width = size.width; - scope.dimensions.image.height = size.height; - - //calculate the best suited ratios - scope.dimensions.scale.min = size.ratio; - scope.dimensions.scale.max = 2; - scope.dimensions.scale.current = size.ratio; - - //center the image - var position = cropperHelper.centerInsideViewPort(scope.dimensions.image, scope.dimensions.cropper); - scope.dimensions.top = position.top; - scope.dimensions.left = position.left; - - setConstraints(); - }; - - //resize to a given ratio - var resizeImageToScale = function(ratio){ - //do stuff - var size = cropperHelper.calculateSizeToRatio(scope.dimensions.image.originalWidth, scope.dimensions.image.originalHeight, ratio); - scope.dimensions.image.width = size.width; - scope.dimensions.image.height = size.height; - - setConstraints(); - validatePosition(scope.dimensions.image.left, scope.dimensions.image.top); - }; - - //resize the image to a predefined crop coordinate - var resizeImageToCrop = function(){ - scope.dimensions.image = cropperHelper.convertToStyle( - scope.crop, - {width: scope.dimensions.image.originalWidth, height: scope.dimensions.image.originalHeight}, - scope.dimensions.cropper, - scope.dimensions.margin); - - var ratioCalculation = cropperHelper.calculateAspectRatioFit( - scope.dimensions.image.originalWidth, - scope.dimensions.image.originalHeight, - scope.dimensions.cropper.width, - scope.dimensions.cropper.height, - true); - - scope.dimensions.scale.current = scope.dimensions.image.ratio; - - //min max based on original width/height - scope.dimensions.scale.min = ratioCalculation.ratio; - scope.dimensions.scale.max = 2; - }; - - - - var validatePosition = function(left, top){ - if(left > constraints.left.max) - { - left = constraints.left.max; - } - - if(left <= constraints.left.min){ - left = constraints.left.min; - } - - if(top > constraints.top.max) - { - top = constraints.top.max; - } - if(top <= constraints.top.min){ - top = constraints.top.min; - } - - if(scope.dimensions.image.left !== left){ - scope.dimensions.image.left = left; - } - - if(scope.dimensions.image.top !== top){ - scope.dimensions.image.top = top; - } - }; - - - //sets scope.crop to the recalculated % based crop - var calculateCropBox = function(){ - scope.crop = cropperHelper.pixelsToCoordinates(scope.dimensions.image, scope.dimensions.cropper.width, scope.dimensions.cropper.height, scope.dimensions.margin); - }; - - - //Drag and drop positioning, using jquery ui draggable - var onStartDragPosition, top, left; - $overlay.draggable({ - drag: function(event, ui) { - scope.$apply(function(){ - validatePosition(ui.position.left, ui.position.top); - }); - }, - stop: function(event, ui){ - scope.$apply(function(){ - //make sure that every validates one more time... - validatePosition(ui.position.left, ui.position.top); - - calculateCropBox(); - scope.dimensions.image.rnd = Math.random(); - }); - } - }); - - - - var init = function(image){ - scope.loaded = false; - - //set dimensions on image, viewport, cropper etc - setDimensions(image); - - //if we have a crop already position the image - if(scope.crop){ - resizeImageToCrop(); - }else{ - resizeImageToEditor(); - } - - //sets constaints for the cropper - setConstraints(); - scope.loaded = true; - }; - - - /// WATCHERS //// - scope.$watchCollection('[width, height]', function(newValues, oldValues){ - //we have to reinit the whole thing if - //one of the external params changes - if(newValues !== oldValues){ - setDimensions($image); - setConstraints(); - } - }); - - var throttledResizing = _.throttle(function(){ - resizeImageToScale(scope.dimensions.scale.current); - calculateCropBox(); - }, 100); - - - //happens when we change the scale - scope.$watch("dimensions.scale.current", function(){ - if(scope.loaded){ - throttledResizing(); - } - }); - - //ie hack - if(window.navigator.userAgent.indexOf("MSIE ")){ - var ranger = element.find("input"); - ranger.bind("change",function(){ - scope.$apply(function(){ - scope.dimensions.scale.current = ranger.val(); - }); - }); - } - - //// INIT ///// - $image.load(function(){ - $timeout(function(){ - init($image); - }); - }); - } - }; - }); - -/** + angular.module('umbraco.directives').directive('umbImageCrop', function ($timeout, localizationService, cropperHelper, $log) { + return { + restrict: 'E', + replace: true, + templateUrl: 'views/components/imaging/umb-image-crop.html', + scope: { + src: '=', + width: '@', + height: '@', + crop: '=', + center: '=', + maxSize: '@' + }, + link: function (scope, element, attrs) { + scope.width = 400; + scope.height = 320; + scope.dimensions = { + image: {}, + cropper: {}, + viewport: {}, + margin: 20, + scale: { + min: 0.3, + max: 3, + current: 1 + } + }; + //live rendering of viewport and image styles + scope.style = function () { + return { + 'height': parseInt(scope.dimensions.viewport.height, 10) + 'px', + 'width': parseInt(scope.dimensions.viewport.width, 10) + 'px' + }; + }; + //elements + var $viewport = element.find('.viewport'); + var $image = element.find('img'); + var $overlay = element.find('.overlay'); + var $container = element.find('.crop-container'); + //default constraints for drag n drop + var constraints = { + left: { + max: scope.dimensions.margin, + min: scope.dimensions.margin + }, + top: { + max: scope.dimensions.margin, + min: scope.dimensions.margin + } + }; + scope.constraints = constraints; + //set constaints for cropping drag and drop + var setConstraints = function () { + constraints.left.min = scope.dimensions.margin + scope.dimensions.cropper.width - scope.dimensions.image.width; + constraints.top.min = scope.dimensions.margin + scope.dimensions.cropper.height - scope.dimensions.image.height; + }; + var setDimensions = function (originalImage) { + originalImage.width('auto'); + originalImage.height('auto'); + var image = {}; + image.originalWidth = originalImage.width(); + image.originalHeight = originalImage.height(); + image.width = image.originalWidth; + image.height = image.originalHeight; + image.left = originalImage[0].offsetLeft; + image.top = originalImage[0].offsetTop; + scope.dimensions.image = image; + //unscaled editor size + //var viewPortW = $viewport.width(); + //var viewPortH = $viewport.height(); + var _viewPortW = parseInt(scope.width, 10); + var _viewPortH = parseInt(scope.height, 10); + //if we set a constraint we will scale it down if needed + if (scope.maxSize) { + var ratioCalculation = cropperHelper.scaleToMaxSize(_viewPortW, _viewPortH, scope.maxSize); + //so if we have a max size, override the thumb sizes + _viewPortW = ratioCalculation.width; + _viewPortH = ratioCalculation.height; + } + scope.dimensions.viewport.width = _viewPortW + 2 * scope.dimensions.margin; + scope.dimensions.viewport.height = _viewPortH + 2 * scope.dimensions.margin; + scope.dimensions.cropper.width = _viewPortW; + // scope.dimensions.viewport.width - 2 * scope.dimensions.margin; + scope.dimensions.cropper.height = _viewPortH; // scope.dimensions.viewport.height - 2 * scope.dimensions.margin; + }; + //when loading an image without any crop info, we center and fit it + var resizeImageToEditor = function () { + //returns size fitting the cropper + var size = cropperHelper.calculateAspectRatioFit(scope.dimensions.image.width, scope.dimensions.image.height, scope.dimensions.cropper.width, scope.dimensions.cropper.height, true); + //sets the image size and updates the scope + scope.dimensions.image.width = size.width; + scope.dimensions.image.height = size.height; + //calculate the best suited ratios + scope.dimensions.scale.min = size.ratio; + scope.dimensions.scale.max = 2; + scope.dimensions.scale.current = size.ratio; + //center the image + var position = cropperHelper.centerInsideViewPort(scope.dimensions.image, scope.dimensions.cropper); + scope.dimensions.top = position.top; + scope.dimensions.left = position.left; + setConstraints(); + }; + //resize to a given ratio + var resizeImageToScale = function (ratio) { + //do stuff + var size = cropperHelper.calculateSizeToRatio(scope.dimensions.image.originalWidth, scope.dimensions.image.originalHeight, ratio); + scope.dimensions.image.width = size.width; + scope.dimensions.image.height = size.height; + setConstraints(); + validatePosition(scope.dimensions.image.left, scope.dimensions.image.top); + }; + //resize the image to a predefined crop coordinate + var resizeImageToCrop = function () { + scope.dimensions.image = cropperHelper.convertToStyle(scope.crop, { + width: scope.dimensions.image.originalWidth, + height: scope.dimensions.image.originalHeight + }, scope.dimensions.cropper, scope.dimensions.margin); + var ratioCalculation = cropperHelper.calculateAspectRatioFit(scope.dimensions.image.originalWidth, scope.dimensions.image.originalHeight, scope.dimensions.cropper.width, scope.dimensions.cropper.height, true); + scope.dimensions.scale.current = scope.dimensions.image.ratio; + //min max based on original width/height + scope.dimensions.scale.min = ratioCalculation.ratio; + scope.dimensions.scale.max = 2; + }; + var validatePosition = function (left, top) { + if (left > constraints.left.max) { + left = constraints.left.max; + } + if (left <= constraints.left.min) { + left = constraints.left.min; + } + if (top > constraints.top.max) { + top = constraints.top.max; + } + if (top <= constraints.top.min) { + top = constraints.top.min; + } + if (scope.dimensions.image.left !== left) { + scope.dimensions.image.left = left; + } + if (scope.dimensions.image.top !== top) { + scope.dimensions.image.top = top; + } + }; + //sets scope.crop to the recalculated % based crop + var calculateCropBox = function () { + scope.crop = cropperHelper.pixelsToCoordinates(scope.dimensions.image, scope.dimensions.cropper.width, scope.dimensions.cropper.height, scope.dimensions.margin); + }; + //Drag and drop positioning, using jquery ui draggable + var onStartDragPosition, top, left; + $overlay.draggable({ + drag: function (event, ui) { + scope.$apply(function () { + validatePosition(ui.position.left, ui.position.top); + }); + }, + stop: function (event, ui) { + scope.$apply(function () { + //make sure that every validates one more time... + validatePosition(ui.position.left, ui.position.top); + calculateCropBox(); + scope.dimensions.image.rnd = Math.random(); + }); + } + }); + var init = function (image) { + scope.loaded = false; + //set dimensions on image, viewport, cropper etc + setDimensions(image); + //if we have a crop already position the image + if (scope.crop) { + resizeImageToCrop(); + } else { + resizeImageToEditor(); + } + //sets constaints for the cropper + setConstraints(); + scope.loaded = true; + }; + /// WATCHERS //// + scope.$watchCollection('[width, height]', function (newValues, oldValues) { + //we have to reinit the whole thing if + //one of the external params changes + if (newValues !== oldValues) { + setDimensions($image); + setConstraints(); + } + }); + var throttledResizing = _.throttle(function () { + resizeImageToScale(scope.dimensions.scale.current); + calculateCropBox(); + }, 100); + //happens when we change the scale + scope.$watch('dimensions.scale.current', function () { + if (scope.loaded) { + throttledResizing(); + } + }); + //ie hack + if (window.navigator.userAgent.indexOf('MSIE ')) { + var ranger = element.find('input'); + ranger.bind('change', function () { + scope.$apply(function () { + scope.dimensions.scale.current = ranger.val(); + }); + }); + } + //// INIT ///// + $image.load(function () { + $timeout(function () { + init($image); + }); + }); + } + }; + }); + /** * @ngdoc directive * @name umbraco.directives.directive:umbImageGravity * @restrict E * @function * @description **/ -angular.module("umbraco.directives") - .directive('umbImageGravity', function ($timeout, localizationService, $log) { - return { - restrict: 'E', - replace: true, - templateUrl: 'views/components/imaging/umb-image-gravity.html', - scope: { - src: '=', - center: "=", - onImageLoaded: "=" - }, - link: function(scope, element, attrs) { - - //Internal values for keeping track of the dot and the size of the editor - scope.dimensions = { - width: 0, - height: 0, - left: 0, - top: 0 - }; - - scope.loaded = false; - - //elements - var $viewport = element.find(".viewport"); - var $image = element.find("img"); - var $overlay = element.find(".overlay"); - - scope.style = function () { - if(scope.dimensions.width <= 0){ - setDimensions(); - } - - return { - 'top': scope.dimensions.top + 'px', - 'left': scope.dimensions.left + 'px' - }; - }; - - scope.setFocalPoint = function(event) { - - scope.$emit("imageFocalPointStart"); - - var offsetX = event.offsetX - 10; - var offsetY = event.offsetY - 10; - - calculateGravity(offsetX, offsetY); - - lazyEndEvent(); - - }; - - var setDimensions = function(){ - scope.dimensions.width = $image.width(); - scope.dimensions.height = $image.height(); - - if(scope.center){ - scope.dimensions.left = scope.center.left * scope.dimensions.width -10; - scope.dimensions.top = scope.center.top * scope.dimensions.height -10; - }else{ - scope.center = { left: 0.5, top: 0.5 }; - } - }; - - var calculateGravity = function(offsetX, offsetY){ - scope.dimensions.left = offsetX; - scope.dimensions.top = offsetY; - - scope.center.left = (scope.dimensions.left+10) / scope.dimensions.width; - scope.center.top = (scope.dimensions.top+10) / scope.dimensions.height; - }; - - var lazyEndEvent = _.debounce(function(){ - scope.$apply(function(){ - scope.$emit("imageFocalPointStop"); - }); - }, 2000); - - - //Drag and drop positioning, using jquery ui draggable - //TODO ensure that the point doesnt go outside the box - $overlay.draggable({ - containment: "parent", - start: function(){ - scope.$apply(function(){ - scope.$emit("imageFocalPointStart"); - }); - }, - stop: function() { - scope.$apply(function(){ - var offsetX = $overlay[0].offsetLeft; - var offsetY = $overlay[0].offsetTop; - calculateGravity(offsetX, offsetY); - }); - - lazyEndEvent(); - } - }); - - //// INIT ///// - $image.load(function(){ - $timeout(function(){ - setDimensions(); - scope.loaded = true; - scope.onImageLoaded(); - }); - }); - - $(window).on('resize.umbImageGravity', function(){ - scope.$apply(function(){ - $timeout(function(){ - setDimensions(); - }); - // Make sure we can find the offset values for the overlay(dot) before calculating - // fixes issue with resize event when printing the page (ex. hitting ctrl+p inside the rte) - if($overlay.is(':visible')) { - var offsetX = $overlay[0].offsetLeft; - var offsetY = $overlay[0].offsetTop; - calculateGravity(offsetX, offsetY); - } - }); + angular.module('umbraco.directives').directive('umbImageGravity', function ($timeout, localizationService, $log) { + return { + restrict: 'E', + replace: true, + templateUrl: 'views/components/imaging/umb-image-gravity.html', + scope: { + src: '=', + center: '=', + onImageLoaded: '=' + }, + link: function (scope, element, attrs) { + //Internal values for keeping track of the dot and the size of the editor + scope.dimensions = { + width: 0, + height: 0, + left: 0, + top: 0 + }; + scope.loaded = false; + //elements + var $viewport = element.find('.viewport'); + var $image = element.find('img'); + var $overlay = element.find('.overlay'); + scope.style = function () { + if (scope.dimensions.width <= 0) { + setDimensions(); + } + return { + 'top': scope.dimensions.top + 'px', + 'left': scope.dimensions.left + 'px' + }; + }; + scope.setFocalPoint = function (event) { + scope.$emit('imageFocalPointStart'); + var offsetX = event.offsetX - 10; + var offsetY = event.offsetY - 10; + calculateGravity(offsetX, offsetY); + lazyEndEvent(); + }; + var setDimensions = function () { + scope.dimensions.width = $image.width(); + scope.dimensions.height = $image.height(); + if (scope.center) { + scope.dimensions.left = scope.center.left * scope.dimensions.width - 10; + scope.dimensions.top = scope.center.top * scope.dimensions.height - 10; + } else { + scope.center = { + left: 0.5, + top: 0.5 + }; + } + }; + var calculateGravity = function (offsetX, offsetY) { + scope.dimensions.left = offsetX; + scope.dimensions.top = offsetY; + scope.center.left = (scope.dimensions.left + 10) / scope.dimensions.width; + scope.center.top = (scope.dimensions.top + 10) / scope.dimensions.height; + }; + var lazyEndEvent = _.debounce(function () { + scope.$apply(function () { + scope.$emit('imageFocalPointStop'); }); - - scope.$on('$destroy', function() { - $(window).off('.umbImageGravity'); - }); - - } - }; - }); - -/** + }, 2000); + //Drag and drop positioning, using jquery ui draggable + //TODO ensure that the point doesnt go outside the box + $overlay.draggable({ + containment: 'parent', + start: function () { + scope.$apply(function () { + scope.$emit('imageFocalPointStart'); + }); + }, + stop: function () { + scope.$apply(function () { + var offsetX = $overlay[0].offsetLeft; + var offsetY = $overlay[0].offsetTop; + calculateGravity(offsetX, offsetY); + }); + lazyEndEvent(); + } + }); + //// INIT ///// + $image.load(function () { + $timeout(function () { + setDimensions(); + scope.loaded = true; + if (angular.isFunction(scope.onImageLoaded)) { + scope.onImageLoaded(); + } + }); + }); + $(window).on('resize.umbImageGravity', function () { + scope.$apply(function () { + $timeout(function () { + setDimensions(); + }); + // Make sure we can find the offset values for the overlay(dot) before calculating + // fixes issue with resize event when printing the page (ex. hitting ctrl+p inside the rte) + if ($overlay.is(':visible')) { + var offsetX = $overlay[0].offsetLeft; + var offsetY = $overlay[0].offsetTop; + calculateGravity(offsetX, offsetY); + } + }); + }); + scope.$on('$destroy', function () { + $(window).off('.umbImageGravity'); + }); + } + }; + }); + /** * @ngdoc directive * @name umbraco.directives.directive:umbImageThumbnail * @restrict E * @function * @description **/ -angular.module("umbraco.directives") - .directive('umbImageThumbnail', - function ($timeout, localizationService, cropperHelper, $log) { - return { - restrict: 'E', - replace: true, - templateUrl: 'views/components/imaging/umb-image-thumbnail.html', - - scope: { - src: '=', - width: '@', - height: '@', - center: "=", - crop: "=", - maxSize: '@' - }, - - link: function(scope, element, attrs) { - //// INIT ///// - var $image = element.find("img"); - scope.loaded = false; - - $image.load(function(){ - $timeout(function(){ - $image.width("auto"); - $image.height("auto"); - - scope.image = {}; - scope.image.width = $image[0].width; - scope.image.height = $image[0].height; - - //we force a lower thumbnail size to fit the max size - //we do not compare to the image dimensions, but the thumbs - if(scope.maxSize){ - var ratioCalculation = cropperHelper.calculateAspectRatioFit( - scope.width, - scope.height, - scope.maxSize, - scope.maxSize, - false); - - //so if we have a max size, override the thumb sizes - scope.width = ratioCalculation.width; - scope.height = ratioCalculation.height; - } - - setPreviewStyle(); - scope.loaded = true; - }); - }); - - /// WATCHERS //// - scope.$watchCollection('[crop, center]', function(newValues, oldValues){ - //we have to reinit the whole thing if - //one of the external params changes - setPreviewStyle(); - }); - - scope.$watch("center", function(){ - setPreviewStyle(); - }, true); - - function setPreviewStyle(){ - if(scope.crop && scope.image){ - scope.preview = cropperHelper.convertToStyle( - scope.crop, - scope.image, - {width: scope.width, height: scope.height}, - 0); - }else if(scope.image){ - - //returns size fitting the cropper - var p = cropperHelper.calculateAspectRatioFit( - scope.image.width, - scope.image.height, - scope.width, - scope.height, - true); - - - if(scope.center){ - var xy = cropperHelper.alignToCoordinates(p, scope.center, {width: scope.width, height: scope.height}); - p.top = xy.top; - p.left = xy.left; - }else{ - - } - - p.position = "absolute"; - scope.preview = p; - } - } - } - }; - }); - -angular.module("umbraco.directives") - - /** + angular.module('umbraco.directives').directive('umbImageThumbnail', function ($timeout, localizationService, cropperHelper, $log) { + return { + restrict: 'E', + replace: true, + templateUrl: 'views/components/imaging/umb-image-thumbnail.html', + scope: { + src: '=', + width: '@', + height: '@', + center: '=', + crop: '=', + maxSize: '@' + }, + link: function (scope, element, attrs) { + //// INIT ///// + var $image = element.find('img'); + scope.loaded = false; + $image.load(function () { + $timeout(function () { + $image.width('auto'); + $image.height('auto'); + scope.image = {}; + scope.image.width = $image[0].width; + scope.image.height = $image[0].height; + //we force a lower thumbnail size to fit the max size + //we do not compare to the image dimensions, but the thumbs + if (scope.maxSize) { + var ratioCalculation = cropperHelper.calculateAspectRatioFit(scope.width, scope.height, scope.maxSize, scope.maxSize, false); + //so if we have a max size, override the thumb sizes + scope.width = ratioCalculation.width; + scope.height = ratioCalculation.height; + } + setPreviewStyle(); + scope.loaded = true; + }); + }); + /// WATCHERS //// + scope.$watchCollection('[crop, center]', function (newValues, oldValues) { + //we have to reinit the whole thing if + //one of the external params changes + setPreviewStyle(); + }); + scope.$watch('center', function () { + setPreviewStyle(); + }, true); + function setPreviewStyle() { + if (scope.crop && scope.image) { + scope.preview = cropperHelper.convertToStyle(scope.crop, scope.image, { + width: scope.width, + height: scope.height + }, 0); + } else if (scope.image) { + //returns size fitting the cropper + var p = cropperHelper.calculateAspectRatioFit(scope.image.width, scope.image.height, scope.width, scope.height, true); + if (scope.center) { + var xy = cropperHelper.alignToCoordinates(p, scope.center, { + width: scope.width, + height: scope.height + }); + p.top = xy.top; + p.left = xy.left; + } else { + } + p.position = 'absolute'; + scope.preview = p; + } + } + } + }; + }); + angular.module('umbraco.directives') /** * @ngdoc directive * @name umbraco.directives.directive:localize * @restrict EA * @function - * @description Localize directive - **/ - .directive('localize', function ($log, localizationService) { + * @description + *
    + * Component
    + * Localize a specific token to put into the HTML as an item + *
    + *
    + * Attribute
    + * Add a HTML attribute to an element containing the HTML attribute name you wish to localise + * Using the format of '@section_key' or 'section_key' + *
    + * ##Usage + *
    +    * 
    +    * Close
    +    * Fallback value
    +    *
    +    * 
    +    * 
    +    * 
    +    * 
    + *
    + **/.directive('localize', function ($log, localizationService) { return { restrict: 'E', - scope:{ - key: '@' - }, + scope: { key: '@' }, replace: true, - link: function (scope, element, attrs) { var key = scope.key; - localizationService.localize(key).then(function(value){ + localizationService.localize(key).then(function (value) { element.html(value); }); } }; - }) - - .directive('localize', function ($log, localizationService) { + }).directive('localize', function ($log, localizationService) { return { restrict: 'A', link: function (scope, element, attrs) { + //Support one or more attribute properties to update var keys = attrs.localize.split(','); - - angular.forEach(keys, function(value, key){ + angular.forEach(keys, function (value, key) { var attr = element.attr(value); - - if(attr){ - if(attr[0] === '@'){ - - var t = localizationService.tokenize(attr.substring(1), scope); - localizationService.localize(t.key, t.tokens).then(function(val){ - element.attr(value, val); - }); - + if (attr) { + if (attr[0] === '@') { + //If the translation key starts with @ then remove it + attr = attr.substring(1); } + var t = localizationService.tokenize(attr, scope); + localizationService.localize(t.key, t.tokens).then(function (val) { + element.attr(value, val); + }); } }); } }; - }); - -/** - * @ngdoc directive - * @name umbraco.directives.directive:umbNotifications - */ - -(function() { - 'use strict'; - - function NotificationDirective(notificationsService) { - - function link(scope, el, attr, ctrl) { - - //subscribes to notifications in the notification service - scope.notifications = notificationsService.current; - scope.$watch('notificationsService.current', function (newVal, oldVal, scope) { - if (newVal) { - scope.notifications = newVal; - } - }); - - } - - var directive = { - restrict: "E", - replace: true, - templateUrl: 'views/components/notifications/umb-notifications.html', - link: link - }; - - return directive; - - } - - angular.module('umbraco.directives').directive('umbNotifications', NotificationDirective); - -})(); - -/** + (function () { + 'use strict'; + function MediaNodeInfoDirective($timeout, $location, eventsService, userService, dateHelper) { + function link(scope, element, attrs, ctrl) { + var evts = []; + function onInit() { + // If logged in user has access to the settings section + // show the open anchors - if the user doesn't have + // access, contentType is null, see MediaModelMapper + scope.allowOpen = scope.node.contentType !== null; + // get document type details + scope.mediaType = scope.node.contentType; + // set the media link initially + setMediaLink(); + // make sure dates are formatted to the user's locale + formatDatesToLocal(); + } + function formatDatesToLocal() { + // get current backoffice user and format dates + userService.getCurrentUser().then(function (currentUser) { + scope.node.createDateFormatted = dateHelper.getLocalDate(scope.node.createDate, currentUser.locale, 'LLL'); + scope.node.updateDateFormatted = dateHelper.getLocalDate(scope.node.updateDate, currentUser.locale, 'LLL'); + }); + } + function setMediaLink() { + scope.nodeUrl = scope.node.mediaLink; + } + scope.openMediaType = function (mediaType) { + // remove first "#" from url if it is prefixed else the path won't work + var url = '/settings/mediaTypes/edit/' + mediaType.id; + $location.path(url); + }; + // watch for content updates - reload content when node is saved, published etc. + scope.$watch('node.updateDate', function (newValue, oldValue) { + if (!newValue) { + return; + } + if (newValue === oldValue) { + return; + } + // Update the media link + setMediaLink(); + // Update the create and update dates + formatDatesToLocal(); + }); + //ensure to unregister from all events! + scope.$on('$destroy', function () { + for (var e in evts) { + eventsService.unsubscribe(evts[e]); + } + }); + onInit(); + } + var directive = { + restrict: 'E', + replace: true, + templateUrl: 'views/components/media/umb-media-node-info.html', + scope: { node: '=' }, + link: link + }; + return directive; + } + angular.module('umbraco.directives').directive('umbMediaNodeInfo', MediaNodeInfoDirective); + }()); + /** + * @ngdoc directive + * @name umbraco.directives.directive:umbNotifications + */ + (function () { + 'use strict'; + function NotificationDirective(notificationsService) { + function link(scope, el, attr, ctrl) { + //subscribes to notifications in the notification service + scope.notifications = notificationsService.current; + scope.$watch('notificationsService.current', function (newVal, oldVal, scope) { + if (newVal) { + scope.notifications = newVal; + } + }); + } + var directive = { + restrict: 'E', + replace: true, + templateUrl: 'views/components/notifications/umb-notifications.html', + link: link + }; + return directive; + } + angular.module('umbraco.directives').directive('umbNotifications', NotificationDirective); + }()); + /** @ngdoc directive @name umbraco.directives.directive:umbOverlay @restrict E @@ -4545,7 +5820,6 @@ angular.module("umbraco.directives")

    General Options

    -Lorem ipsum dolor sit amet.. @@ -4560,7 +5834,7 @@ Lorem ipsum dolor sit amet.. - + @@ -4890,1470 +6164,1434 @@ Opens an overlay to show a custom YSOD.
    @param {string} view Path to view or one of the default view names. @param {string} position The overlay position ("left", "right", "center": "target"). **/ - -(function() { - 'use strict'; - - function OverlayDirective($timeout, formHelper, overlayHelper, localizationService) { - - function link(scope, el, attr, ctrl) { - - scope.directive = { - enableConfirmButton: false - }; - - var overlayNumber = 0; - var numberOfOverlays = 0; - var isRegistered = false; - - var modelCopy = {}; - - function activate() { - - setView(); - - setButtonText(); - - modelCopy = makeModelCopy(scope.model); - - $timeout(function() { - - if (scope.position === "target") { - setTargetPosition(); - } - - // this has to be done inside a timeout to ensure the destroy - // event on other overlays is run before registering a new one - registerOverlay(); - - setOverlayIndent(); - - }); - - } - - function setView() { - - if (scope.view) { - - if (scope.view.indexOf(".html") === -1) { - var viewAlias = scope.view.toLowerCase(); - scope.view = "views/common/overlays/" + viewAlias + "/" + viewAlias + ".html"; - } - - } - - } - - function setButtonText() { - if (!scope.model.closeButtonLabelKey && !scope.model.closeButtonLabel) { - scope.model.closeButtonLabel = localizationService.localize("general_close"); - } - if (!scope.model.submitButtonLabelKey && !scope.model.submitButtonLabel) { - scope.model.submitButtonLabel = localizationService.localize("general_submit"); - } - } - - function registerOverlay() { - - overlayNumber = overlayHelper.registerOverlay(); - - $(document).bind("keydown.overlay-" + overlayNumber, function(event) { - - if (event.which === 27) { - - numberOfOverlays = overlayHelper.getNumberOfOverlays(); - - if(numberOfOverlays === overlayNumber) { - scope.closeOverLay(); - } - - event.preventDefault(); - } - - if (event.which === 13) { - - numberOfOverlays = overlayHelper.getNumberOfOverlays(); - - if(numberOfOverlays === overlayNumber) { - - var activeElementType = document.activeElement.tagName; - var clickableElements = ["A", "BUTTON"]; - var submitOnEnter = document.activeElement.hasAttribute("overlay-submit-on-enter"); - - if(clickableElements.indexOf(activeElementType) === 0) { - document.activeElement.click(); - event.preventDefault(); - } else if(activeElementType === "TEXTAREA" && !submitOnEnter) { - - - } else { - scope.$apply(function () { - scope.submitForm(scope.model); - }); - event.preventDefault(); - } - - } - - } - - }); - - isRegistered = true; - - } - - function unregisterOverlay() { - - if(isRegistered) { - - overlayHelper.unregisterOverlay(); - - $(document).unbind("keydown.overlay-" + overlayNumber); - - isRegistered = false; - } - - } - - function makeModelCopy(object) { - - var newObject = {}; - - for (var key in object) { - if (key !== "event") { - newObject[key] = angular.copy(object[key]); - } - } - - return newObject; - - } - - function setOverlayIndent() { - - var overlayIndex = overlayNumber - 1; - var indentSize = overlayIndex * 20; - var overlayWidth = el.context.clientWidth; - - el.css('width', overlayWidth - indentSize); - - if(scope.position === "center" || scope.position === "target") { - var overlayTopPosition = el.context.offsetTop; - el.css('top', overlayTopPosition + indentSize); - } - - } - - function setTargetPosition() { - - var container = $("#contentwrapper"); - var containerLeft = container[0].offsetLeft; - var containerRight = containerLeft + container[0].offsetWidth; - var containerTop = container[0].offsetTop; - var containerBottom = containerTop + container[0].offsetHeight; - - var mousePositionClickX = null; - var mousePositionClickY = null; - var elementHeight = null; - var elementWidth = null; - - var position = { - right: "inherit", - left: "inherit", - top: "inherit", - bottom: "inherit" - }; - - // if mouse click position is know place element with mouse in center - if (scope.model.event && scope.model.event) { - - // click position - mousePositionClickX = scope.model.event.pageX; - mousePositionClickY = scope.model.event.pageY; - - // element size - elementHeight = el.context.clientHeight; - elementWidth = el.context.clientWidth; - - // move element to this position - position.left = mousePositionClickX - (elementWidth / 2); - position.top = mousePositionClickY - (elementHeight / 2); - - // check to see if element is outside screen - // outside right - if (position.left + elementWidth > containerRight) { - position.right = 10; - position.left = "inherit"; - } - - // outside bottom - if (position.top + elementHeight > containerBottom) { - position.bottom = 10; - position.top = "inherit"; - } - - // outside left - if (position.left < containerLeft) { - position.left = containerLeft + 10; - position.right = "inherit"; - } - - // outside top - if (position.top < containerTop) { - position.top = 10; - position.bottom = "inherit"; - } - - el.css(position); - - } - - } - - scope.submitForm = function(model) { - if(scope.model.submit) { - if (formHelper.submitForm({scope: scope})) { - formHelper.resetForm({ scope: scope }); - - if(scope.model.confirmSubmit && scope.model.confirmSubmit.enable && !scope.directive.enableConfirmButton) { - scope.model.submit(model, modelCopy, scope.directive.enableConfirmButton); - } else { - unregisterOverlay(); - scope.model.submit(model, modelCopy, scope.directive.enableConfirmButton); + (function () { + 'use strict'; + function OverlayDirective($timeout, formHelper, overlayHelper, localizationService) { + function link(scope, el, attr, ctrl) { + scope.directive = { enableConfirmButton: false }; + var overlayNumber = 0; + var numberOfOverlays = 0; + var isRegistered = false; + var modelCopy = {}; + function activate() { + setView(); + setButtonText(); + modelCopy = makeModelCopy(scope.model); + $timeout(function () { + if (scope.position === 'target') { + setTargetPosition(); + } + // this has to be done inside a timeout to ensure the destroy + // event on other overlays is run before registering a new one + registerOverlay(); + setOverlayIndent(); + }); + } + function setView() { + if (scope.view) { + if (scope.view.indexOf('.html') === -1) { + var viewAlias = scope.view.toLowerCase(); + scope.view = 'views/common/overlays/' + viewAlias + '/' + viewAlias + '.html'; + } } - - } - } - }; - - scope.cancelConfirmSubmit = function() { - scope.model.confirmSubmit.show = false; - }; - - scope.closeOverLay = function() { - - unregisterOverlay(); - - if (scope.model.close) { - scope.model = modelCopy; - scope.model.close(scope.model); - } else { - scope.model.show = false; - scope.model = null; + } + function setButtonText() { + if (!scope.model.closeButtonLabelKey && !scope.model.closeButtonLabel) { + scope.model.closeButtonLabel = localizationService.localize('general_close'); + } + if (!scope.model.submitButtonLabelKey && !scope.model.submitButtonLabel) { + scope.model.submitButtonLabel = localizationService.localize('general_submit'); + } + } + function registerOverlay() { + overlayNumber = overlayHelper.registerOverlay(); + $(document).bind('keydown.overlay-' + overlayNumber, function (event) { + if (event.which === 27) { + numberOfOverlays = overlayHelper.getNumberOfOverlays(); + if (numberOfOverlays === overlayNumber) { + scope.$apply(function () { + scope.closeOverLay(); + }); + } + event.preventDefault(); + } + if (event.which === 13) { + numberOfOverlays = overlayHelper.getNumberOfOverlays(); + if (numberOfOverlays === overlayNumber) { + var activeElementType = document.activeElement.tagName; + var clickableElements = [ + 'A', + 'BUTTON' + ]; + var submitOnEnter = document.activeElement.hasAttribute('overlay-submit-on-enter'); + var submitOnEnterValue = submitOnEnter ? document.activeElement.getAttribute('overlay-submit-on-enter') : ''; + if (clickableElements.indexOf(activeElementType) === 0) { + document.activeElement.click(); + event.preventDefault(); + } else if (activeElementType === 'TEXTAREA' && !submitOnEnter) { + } else if (submitOnEnter && submitOnEnterValue === 'false') { + } else { + scope.$apply(function () { + scope.submitForm(scope.model); + }); + event.preventDefault(); + } + } + } + }); + isRegistered = true; + } + function unregisterOverlay() { + if (isRegistered) { + overlayHelper.unregisterOverlay(); + $(document).unbind('keydown.overlay-' + overlayNumber); + isRegistered = false; + } + } + function makeModelCopy(object) { + var newObject = {}; + for (var key in object) { + if (key !== 'event') { + newObject[key] = angular.copy(object[key]); + } + } + return newObject; + } + function setOverlayIndent() { + var overlayIndex = overlayNumber - 1; + var indentSize = overlayIndex * 20; + var overlayWidth = el.context.clientWidth; + el.css('width', overlayWidth - indentSize); + if (scope.position === 'center' || scope.position === 'target') { + var overlayTopPosition = el.context.offsetTop; + el.css('top', overlayTopPosition + indentSize); + } + } + function setTargetPosition() { + var container = $('#contentwrapper'); + var containerLeft = container[0].offsetLeft; + var containerRight = containerLeft + container[0].offsetWidth; + var containerTop = container[0].offsetTop; + var containerBottom = containerTop + container[0].offsetHeight; + var mousePositionClickX = null; + var mousePositionClickY = null; + var elementHeight = null; + var elementWidth = null; + var position = { + right: 'inherit', + left: 'inherit', + top: 'inherit', + bottom: 'inherit' + }; + // if mouse click position is know place element with mouse in center + if (scope.model.event && scope.model.event) { + // click position + mousePositionClickX = scope.model.event.pageX; + mousePositionClickY = scope.model.event.pageY; + // element size + elementHeight = el.context.clientHeight; + elementWidth = el.context.clientWidth; + // move element to this position + position.left = mousePositionClickX - elementWidth / 2; + position.top = mousePositionClickY - elementHeight / 2; + // check to see if element is outside screen + // outside right + if (position.left + elementWidth > containerRight) { + position.right = 10; + position.left = 'inherit'; + } + // outside bottom + if (position.top + elementHeight > containerBottom) { + position.bottom = 10; + position.top = 'inherit'; + } + // outside left + if (position.left < containerLeft) { + position.left = containerLeft + 10; + position.right = 'inherit'; + } + // outside top + if (position.top < containerTop) { + position.top = 10; + position.bottom = 'inherit'; + } + el.css(position); + } + } + scope.submitForm = function (model) { + if (scope.model.submit) { + if (formHelper.submitForm({ scope: scope })) { + formHelper.resetForm({ scope: scope }); + if (scope.model.confirmSubmit && scope.model.confirmSubmit.enable && !scope.directive.enableConfirmButton) { + scope.model.submit(model, modelCopy, scope.directive.enableConfirmButton); + } else { + unregisterOverlay(); + scope.model.submit(model, modelCopy, scope.directive.enableConfirmButton); + } + } + } + }; + scope.cancelConfirmSubmit = function () { + scope.model.confirmSubmit.show = false; + }; + scope.closeOverLay = function () { + unregisterOverlay(); + if (scope.model.close) { + scope.model = modelCopy; + scope.model.close(scope.model); + } else { + scope.model.show = false; + scope.model = null; + } + }; + // angular does not support ng-show on custom directives + // width isolated scopes. So we have to make our own. + if (attr.hasOwnProperty('ngShow')) { + scope.$watch('ngShow', function (value) { + if (value) { + el.show(); + activate(); + } else { + unregisterOverlay(); + el.hide(); + } + }); + } else { + activate(); + } + scope.$on('$destroy', function () { + unregisterOverlay(); + }); } - - }; - - // angular does not support ng-show on custom directives - // width isolated scopes. So we have to make our own. - if (attr.hasOwnProperty("ngShow")) { - scope.$watch("ngShow", function(value) { - if (value) { - el.show(); - activate(); - } else { - unregisterOverlay(); - el.hide(); - } - }); - } else { - activate(); - } - - scope.$on('$destroy', function(){ - unregisterOverlay(); - }); - - } - - var directive = { - transclude: true, - restrict: 'E', - replace: true, - templateUrl: 'views/components/overlays/umb-overlay.html', - scope: { - ngShow: "=", - model: "=", - view: "=", - position: "@" - }, - link: link - }; - - return directive; - } - - angular.module('umbraco.directives').directive('umbOverlay', OverlayDirective); - -})(); - -(function() { - 'use strict'; - - function OverlayBackdropDirective(overlayHelper) { - - function link(scope, el, attr, ctrl) { - - scope.numberOfOverlays = 0; - - scope.$watch(function(){ - return overlayHelper.getNumberOfOverlays(); - }, function (newValue) { - scope.numberOfOverlays = newValue; - }); - - } - - var directive = { - restrict: 'E', - replace: true, - templateUrl: 'views/components/overlays/umb-overlay-backdrop.html', - link: link - }; - - return directive; - - } - - angular.module('umbraco.directives').directive('umbOverlayBackdrop', OverlayBackdropDirective); - -})(); - -/** + var directive = { + transclude: true, + restrict: 'E', + replace: true, + templateUrl: 'views/components/overlays/umb-overlay.html', + scope: { + ngShow: '=', + model: '=', + view: '=', + position: '@' + }, + link: link + }; + return directive; + } + angular.module('umbraco.directives').directive('umbOverlay', OverlayDirective); + }()); + (function () { + 'use strict'; + function OverlayBackdropDirective(overlayHelper) { + function link(scope, el, attr, ctrl) { + scope.numberOfOverlays = 0; + scope.$watch(function () { + return overlayHelper.getNumberOfOverlays(); + }, function (newValue) { + scope.numberOfOverlays = newValue; + }); + } + var directive = { + restrict: 'E', + replace: true, + templateUrl: 'views/components/overlays/umb-overlay-backdrop.html', + link: link + }; + return directive; + } + angular.module('umbraco.directives').directive('umbOverlayBackdrop', OverlayBackdropDirective); + }()); + /** * @ngdoc directive * @name umbraco.directives.directive:umbProperty * @restrict E **/ -angular.module("umbraco.directives") - .directive('umbProperty', function (umbPropEditorHelper) { + angular.module('umbraco.directives').directive('umbProperty', function (umbPropEditorHelper, userService) { return { - scope: { - property: "=" - }, + scope: { property: '=' }, transclude: true, restrict: 'E', - replace: true, + replace: true, templateUrl: 'views/components/property/umb-property.html', - link: function(scope) { - scope.propertyAlias = Umbraco.Sys.ServerVariables.isDebuggingEnabled === true ? scope.property.alias : null; + link: function (scope) { + userService.getCurrentUser().then(function (u) { + var isAdmin = u.userGroups.indexOf('admin') !== -1; + scope.propertyAlias = Umbraco.Sys.ServerVariables.isDebuggingEnabled === true || isAdmin ? scope.property.alias : null; + }); }, //Define a controller for this directive to expose APIs to other directives controller: function ($scope, $timeout) { - var self = this; - //set the API properties/methods - self.property = $scope.property; - self.setPropertyError = function(errorMsg) { + self.setPropertyError = function (errorMsg) { $scope.property.propertyErrorMessage = errorMsg; }; } }; - }); -/** + }); + /** * @ngdoc directive * @function * @name umbraco.directives.directive:umbPropertyEditor * @requires formController * @restrict E **/ - -//share property editor directive function -var _umbPropertyEditor = function (umbPropEditorHelper) { + //share property editor directive function + var _umbPropertyEditor = function (umbPropEditorHelper) { return { scope: { - model: "=", - isPreValue: "@", - preview: "@" + model: '=', + isPreValue: '@', + preview: '@' }, - - require: "^form", + require: '^form', restrict: 'E', - replace: true, + replace: true, templateUrl: 'views/components/property/umb-property-editor.html', link: function (scope, element, attrs, ctrl) { - //we need to copy the form controller val to our isolated scope so that //it get's carried down to the child scopes of this! //we'll also maintain the current form name. scope[ctrl.$name] = ctrl; - - if(!scope.model.alias){ - scope.model.alias = Math.random().toString(36).slice(2); + if (!scope.model.alias) { + scope.model.alias = Math.random().toString(36).slice(2); } - - scope.$watch("model.view", function(val){ + scope.$watch('model.view', function (val) { scope.propertyEditorView = umbPropEditorHelper.getViewPath(scope.model.view, scope.isPreValue); }); } }; }; - -//Preffered is the umb-property-editor as its more explicit - but we keep umb-editor for backwards compat -angular.module("umbraco.directives").directive('umbPropertyEditor', _umbPropertyEditor); -angular.module("umbraco.directives").directive('umbEditor', _umbPropertyEditor); - -angular.module("umbraco.directives.html") - .directive('umbPropertyGroup', function () { + //Preffered is the umb-property-editor as its more explicit - but we keep umb-editor for backwards compat + angular.module('umbraco.directives').directive('umbPropertyEditor', _umbPropertyEditor); + angular.module('umbraco.directives').directive('umbEditor', _umbPropertyEditor); + angular.module('umbraco.directives.html').directive('umbPropertyGroup', function () { return { transclude: true, restrict: 'E', - replace: true, + replace: true, templateUrl: 'views/components/property/umb-property-group.html' }; - }); -/** + }); + /** * @ngdoc directive * @name umbraco.directives.directive:umbTab * @restrict E **/ -angular.module("umbraco.directives") -.directive('umbTab', function ($parse, $timeout) { - return { - restrict: 'E', - replace: true, - transclude: 'true', - templateUrl: 'views/components/tabs/umb-tab.html' - }; -}); - -/** + angular.module('umbraco.directives').directive('umbTab', function ($parse, $timeout) { + return { + restrict: 'E', + replace: true, + transclude: 'true', + templateUrl: 'views/components/tabs/umb-tab.html' + }; + }); + /** * @ngdoc directive * @name umbraco.directives.directive:umbTabs * @restrict A * @description Used to bind to bootstrap tab events so that sub directives can use this API to listen to tab changes **/ -angular.module("umbraco.directives") -.directive('umbTabs', function () { - return { - restrict: 'A', - controller: function ($scope, $element, $attrs) { - - var callbacks = []; - this.onTabShown = function(cb) { - callbacks.push(cb); - }; - - function tabShown(event) { - - var curr = $(event.target); // active tab - var prev = $(event.relatedTarget); // previous tab - - $scope.$apply(); - - for (var c in callbacks) { - callbacks[c].apply(this, [{current: curr, previous: prev}]); + angular.module('umbraco.directives').directive('umbTabs', function () { + return { + restrict: 'A', + controller: function ($scope, $element, $attrs, eventsService) { + var callbacks = []; + this.onTabShown = function (cb) { + callbacks.push(cb); + }; + function tabShown(event) { + var curr = $(event.target); + // active tab + var prev = $(event.relatedTarget); + // previous tab + // emit tab change event + var tabId = Number(curr.context.hash.replace('#tab', '')); + var args = { + id: tabId, + hash: curr.context.hash + }; + eventsService.emit('app.tabChange', args); + $scope.$apply(); + for (var c in callbacks) { + callbacks[c].apply(this, [{ + current: curr, + previous: prev + }]); + } } + //NOTE: it MUST be done this way - binding to an ancestor element that exists + // in the DOM to bind to the dynamic elements that will be created. + // It would be nicer to create this event handler as a directive for which child + // directives can attach to. + $element.on('shown', '.nav-tabs a', tabShown); + //ensure to unregister + $scope.$on('$destroy', function () { + $element.off('shown', '.nav-tabs a', tabShown); + for (var c in callbacks) { + delete callbacks[c]; + } + callbacks = null; + }); } - - //NOTE: it MUST be done this way - binding to an ancestor element that exists - // in the DOM to bind to the dynamic elements that will be created. - // It would be nicer to create this event handler as a directive for which child - // directives can attach to. - $element.on('shown', '.nav-tabs a', tabShown); - - //ensure to unregister - $scope.$on('$destroy', function () { - $element.off('shown', '.nav-tabs a', tabShown); - - for (var c in callbacks) { - delete callbacks[c]; - } - callbacks = null; - }); - } - }; -}); -(function() { - 'use strict'; - - function UmbTabsContentDirective() { - - function link(scope, el, attr, ctrl) { - - scope.view = attr.view; - - } - - var directive = { - restrict: "E", - replace: true, - transclude: 'true', - templateUrl: "views/components/tabs/umb-tabs-content.html", - link: link - }; - - return directive; - } - - angular.module('umbraco.directives').directive('umbTabsContent', UmbTabsContentDirective); - -})(); - -(function() { - 'use strict'; - - function UmbTabsNavDirective($timeout) { - - function link(scope, el, attr) { - - function activate() { - - $timeout(function () { - - //use bootstrap tabs API to show the first one - el.find("a:first").tab('show'); - - //enable the tab drop - el.tabdrop(); - - }); - - } - - var unbindModelWatch = scope.$watch('model', function(newValue, oldValue){ - - activate(); - - }); - - - scope.$on('$destroy', function () { - - //ensure to destroy tabdrop (unbinds window resize listeners) - el.tabdrop("destroy"); - - unbindModelWatch(); - - }); - + }; + }); + (function () { + 'use strict'; + function UmbTabsContentDirective() { + function link(scope, el, attr, ctrl) { + scope.view = attr.view; + } + var directive = { + restrict: 'E', + replace: true, + transclude: 'true', + templateUrl: 'views/components/tabs/umb-tabs-content.html', + link: link + }; + return directive; + } + angular.module('umbraco.directives').directive('umbTabsContent', UmbTabsContentDirective); + }()); + (function () { + 'use strict'; + function UmbTabsNavDirective($timeout) { + function link(scope, el, attr) { + function activate() { + $timeout(function () { + //use bootstrap tabs API to show the first one + el.find('a:first').tab('show'); + //enable the tab drop + el.tabdrop(); + }); + } + var unbindModelWatch = scope.$watch('model', function (newValue, oldValue) { + activate(); + }); + scope.$on('$destroy', function () { + //ensure to destroy tabdrop (unbinds window resize listeners) + el.tabdrop('destroy'); + unbindModelWatch(); + }); + } + var directive = { + restrict: 'E', + replace: true, + templateUrl: 'views/components/tabs/umb-tabs-nav.html', + scope: { + model: '=', + tabdrop: '=', + idSuffix: '@' + }, + link: link + }; + return directive; + } + angular.module('umbraco.directives').directive('umbTabsNav', UmbTabsNavDirective); + }()); + /** +* @ngdoc directive +* @name umbraco.directives.directive:umbTree +* @restrict E +**/ + function umbTreeDirective($compile, $log, $q, $rootScope, treeService, notificationsService, $timeout, userService) { + return { + restrict: 'E', + replace: true, + terminal: false, + scope: { + section: '@', + treealias: '@', + hideoptions: '@', + hideheader: '@', + cachekey: '@', + isdialog: '@', + onlyinitialized: '@', + //Custom query string arguments to pass in to the tree as a string, example: "startnodeid=123&something=value" + customtreeparams: '@', + eventhandler: '=', + enablecheckboxes: '@', + enablelistviewsearch: '@', + enablelistviewexpand: '@' + }, + compile: function (element, attrs) { + //config + //var showheader = (attrs.showheader !== 'false'); + var hideoptions = attrs.hideoptions === 'true' ? 'hide-options' : ''; + var template = '
    • '; + template += '
      ' + '
      ' + ' {{tree.name}}
      ' + '' + '
      '; + template += '
        ' + '' + '
      ' + '
    • ' + '
    '; + element.replaceWith(template); + return function (scope, elem, attr, controller) { + //flag to track the last loaded section when the tree 'un-loads'. We use this to determine if we should + // re-load the tree again. For example, if we hover over 'content' the content tree is shown. Then we hover + // outside of the tree and the tree 'un-loads'. When we re-hover over 'content', we don't want to re-load the + // entire tree again since we already still have it in memory. Of course if the section is different we will + // reload it. This saves a lot on processing if someone is navigating in and out of the same section many times + // since it saves on data retreival and DOM processing. + var lastSection = ''; + //setup a default internal handler + if (!scope.eventhandler) { + scope.eventhandler = $({}); + } + //flag to enable/disable delete animations + var deleteAnimations = false; + /** Helper function to emit tree events */ + function emitEvent(eventName, args) { + if (scope.eventhandler) { + $(scope.eventhandler).trigger(eventName, args); + } + } + /** This will deleteAnimations to true after the current digest */ + function enableDeleteAnimations() { + //do timeout so that it re-enables them after this digest + $timeout(function () { + //enable delete animations + deleteAnimations = true; + }, 0, false); + } + /*this is the only external interface a tree has */ + function setupExternalEvents() { + if (scope.eventhandler) { + scope.eventhandler.clearCache = function (section) { + treeService.clearCache({ section: section }); + }; + scope.eventhandler.load = function (section) { + scope.section = section; + loadTree(); + }; + scope.eventhandler.reloadNode = function (node) { + if (!node) { + node = scope.currentNode; + } + if (node) { + scope.loadChildren(node, true); + } + }; + /** + Used to do the tree syncing. If the args.tree is not specified we are assuming it has been + specified previously using the _setActiveTreeType + */ + scope.eventhandler.syncTree = function (args) { + if (!args) { + throw 'args cannot be null'; + } + if (!args.path) { + throw 'args.path cannot be null'; + } + var deferred = $q.defer(); + //this is super complex but seems to be working in other places, here we're listening for our + // own events, once the tree is sycned we'll resolve our promise. + scope.eventhandler.one('treeSynced', function (e, syncArgs) { + deferred.resolve(syncArgs); + }); + //this should normally be set unless it is being called from legacy + // code, so set the active tree type before proceeding. + if (args.tree) { + loadActiveTree(args.tree); + } + if (angular.isString(args.path)) { + args.path = args.path.replace('"', '').split(','); + } + //reset current node selection + //scope.currentNode = null; + //Filter the path for root node ids (we don't want to pass in -1 or 'init') + args.path = _.filter(args.path, function (item) { + return item !== 'init' && item !== '-1'; + }); + loadPath(args.path, args.forceReload, args.activate); + return deferred.promise; + }; + /** + Internal method that should ONLY be used by the legacy API wrapper, the legacy API used to + have to set an active tree and then sync, the new API does this in one method by using syncTree. + loadChildren is optional but if it is set, it will set the current active tree and load the root + node's children - this is synonymous with the legacy refreshTree method - again should not be used + and should only be used for the legacy code to work. + */ + scope.eventhandler._setActiveTreeType = function (treeAlias, loadChildren) { + loadActiveTree(treeAlias, loadChildren); + }; + } + } + //helper to load a specific path on the active tree as soon as its ready + function loadPath(path, forceReload, activate) { + if (scope.activeTree) { + syncTree(scope.activeTree, path, forceReload, activate); + } else { + scope.eventhandler.one('activeTreeLoaded', function (e, args) { + syncTree(args.tree, path, forceReload, activate); + }); + } + } + //given a tree alias, this will search the current section tree for the specified tree alias and + //set that to the activeTree + //NOTE: loadChildren is ONLY used for legacy purposes, do not use this when syncing the tree as it will cause problems + // since there will be double request and event handling operations. + function loadActiveTree(treeAlias, loadChildren) { + if (!treeAlias) { + return; + } + scope.activeTree = undefined; + function doLoad(tree) { + var childrenAndSelf = [tree].concat(tree.children); + scope.activeTree = _.find(childrenAndSelf, function (node) { + if (node && node.metaData && node.metaData.treeAlias) { + return node.metaData.treeAlias.toUpperCase() === treeAlias.toUpperCase(); + } + return false; + }); + if (!scope.activeTree) { + throw 'Could not find the tree ' + treeAlias + ', activeTree has not been set'; + } + //This is only used for the legacy tree method refreshTree! + if (loadChildren) { + scope.activeTree.expanded = true; + scope.loadChildren(scope.activeTree, false).then(function () { + emitEvent('activeTreeLoaded', { tree: scope.activeTree }); + }); + } else { + emitEvent('activeTreeLoaded', { tree: scope.activeTree }); + } + } + if (scope.tree) { + doLoad(scope.tree.root); + } else { + scope.eventhandler.one('treeLoaded', function (e, args) { + doLoad(args.tree.root); + }); + } + } + /** Method to load in the tree data */ + function loadTree() { + if (!scope.loading && scope.section) { + scope.loading = true; + //anytime we want to load the tree we need to disable the delete animations + deleteAnimations = false; + //default args + var args = { + section: scope.section, + tree: scope.treealias, + cacheKey: scope.cachekey, + isDialog: scope.isdialog ? scope.isdialog : false, + onlyinitialized: scope.onlyinitialized + }; + //add the extra query string params if specified + if (scope.customtreeparams) { + args['queryString'] = scope.customtreeparams; + } + treeService.getTree(args).then(function (data) { + //set the data once we have it + scope.tree = data; + enableDeleteAnimations(); + scope.loading = false; + //set the root as the current active tree + scope.activeTree = scope.tree.root; + emitEvent('treeLoaded', { tree: scope.tree }); + emitEvent('treeNodeExpanded', { + tree: scope.tree, + node: scope.tree.root, + children: scope.tree.root.children + }); + }, function (reason) { + scope.loading = false; + notificationsService.error('Tree Error', reason); + }); + } + } + /** syncs the tree, the treeNode can be ANY tree node in the tree that requires syncing */ + function syncTree(treeNode, path, forceReload, activate) { + deleteAnimations = false; + treeService.syncTree({ + node: treeNode, + path: path, + forceReload: forceReload + }).then(function (data) { + if (activate === undefined || activate === true) { + scope.currentNode = data; + } + emitEvent('treeSynced', { + node: data, + activate: activate + }); + enableDeleteAnimations(); + }); + } + /** Returns the css classses assigned to the node (div element) */ + scope.getNodeCssClass = function (node) { + if (!node) { + return ''; + } + //TODO: This is called constantly because as a method in a template it's re-evaluated pretty much all the time + // it would be better if we could cache the processing. The problem is that some of these things are dynamic. + var css = []; + if (node.cssClasses) { + _.each(node.cssClasses, function (c) { + css.push(c); + }); + } + return css.join(' '); + }; + scope.selectEnabledNodeClass = function (node) { + return node ? node.selected ? 'icon umb-tree-icon sprTree icon-check green temporary' : '' : ''; + }; + /** method to set the current animation for the node. + * This changes dynamically based on if we are changing sections or just loading normal tree data. + * When changing sections we don't want all of the tree-ndoes to do their 'leave' animations. + */ + scope.animation = function () { + if (deleteAnimations && scope.tree && scope.tree.root && scope.tree.root.expanded) { + return { leave: 'tree-node-delete-leave' }; + } else { + return {}; + } + }; + /* helper to force reloading children of a tree node */ + scope.loadChildren = function (node, forceReload) { + var deferred = $q.defer(); + //emit treeNodeExpanding event, if a callback object is set on the tree + emitEvent('treeNodeExpanding', { + tree: scope.tree, + node: node + }); + //standardising + if (!node.children) { + node.children = []; + } + if (forceReload || node.hasChildren && node.children.length === 0) { + //get the children from the tree service + treeService.loadNodeChildren({ + node: node, + section: scope.section + }).then(function (data) { + //emit expanded event + emitEvent('treeNodeExpanded', { + tree: scope.tree, + node: node, + children: data + }); + enableDeleteAnimations(); + deferred.resolve(data); + }); + } else { + emitEvent('treeNodeExpanded', { + tree: scope.tree, + node: node, + children: node.children + }); + node.expanded = true; + enableDeleteAnimations(); + deferred.resolve(node.children); + } + return deferred.promise; + }; + /** + Method called when the options button next to the root node is called. + The tree doesnt know about this, so it raises an event to tell the parent controller + about it. + */ + scope.options = function (n, ev) { + emitEvent('treeOptionsClick', { + element: elem, + node: n, + event: ev + }); + }; + /** + Method called when an item is clicked in the tree, this passes the + DOM element, the tree node object and the original click + and emits it as a treeNodeSelect element if there is a callback object + defined on the tree + */ + scope.select = function (n, ev) { + if (n.metaData && n.metaData.noAccess === true) { + ev.preventDefault(); + return; + } + //on tree select we need to remove the current node - + // whoever handles this will need to make sure the correct node is selected + //reset current node selection + scope.currentNode = null; + emitEvent('treeNodeSelect', { + element: elem, + node: n, + event: ev + }); + }; + scope.altSelect = function (n, ev) { + emitEvent('treeNodeAltSelect', { + element: elem, + tree: scope.tree, + node: n, + event: ev + }); + }; + //watch for section changes + scope.$watch('section', function (newVal, oldVal) { + if (!scope.tree) { + loadTree(); + } + if (!newVal) { + //store the last section loaded + lastSection = oldVal; + } else if (newVal !== oldVal && newVal !== lastSection) { + //only reload the tree data and Dom if the newval is different from the old one + // and if the last section loaded is different from the requested one. + loadTree(); + //store the new section to be loaded as the last section + //clear any active trees to reset lookups + lastSection = newVal; + } + }); + setupExternalEvents(); + loadTree(); + }; + } + }; } - - var directive = { - restrict: "E", - replace: true, - templateUrl: "views/components/tabs/umb-tabs-nav.html", - scope: { - model: "=", - tabdrop: "=" - }, - link: link - }; - - return directive; - } - - angular.module('umbraco.directives').directive('umbTabsNav', UmbTabsNavDirective); - -})(); - -/** -* @ngdoc directive -* @name umbraco.directives.directive:umbTree -* @restrict E -**/ -function umbTreeDirective($compile, $log, $q, $rootScope, treeService, notificationsService, $timeout, userService) { - - return { - restrict: 'E', - replace: true, - terminal: false, - - scope: { - section: '@', - treealias: '@', - hideoptions: '@', - hideheader: '@', - cachekey: '@', - isdialog: '@', - //Custom query string arguments to pass in to the tree as a string, example: "startnodeid=123&something=value" - customtreeparams: '@', - eventhandler: '=', - enablecheckboxes: '@', - enablelistviewsearch: '@' - }, - - compile: function(element, attrs) { - //config - //var showheader = (attrs.showheader !== 'false'); - var hideoptions = (attrs.hideoptions === 'true') ? "hide-options" : ""; - var template = '
    • '; - template += '
      ' + - '
      ' + - ' {{tree.name}}
      ' + - '' + - '
      '; - template += '
        ' + - '' + - '
      ' + - '
    • ' + - '
    '; - - element.replaceWith(template); - - return function(scope, elem, attr, controller) { - - //flag to track the last loaded section when the tree 'un-loads'. We use this to determine if we should - // re-load the tree again. For example, if we hover over 'content' the content tree is shown. Then we hover - // outside of the tree and the tree 'un-loads'. When we re-hover over 'content', we don't want to re-load the - // entire tree again since we already still have it in memory. Of course if the section is different we will - // reload it. This saves a lot on processing if someone is navigating in and out of the same section many times - // since it saves on data retreival and DOM processing. - var lastSection = ""; - - //setup a default internal handler - if (!scope.eventhandler) { - scope.eventhandler = $({}); - } - - //flag to enable/disable delete animations - var deleteAnimations = false; - - - /** Helper function to emit tree events */ - function emitEvent(eventName, args) { - if (scope.eventhandler) { - $(scope.eventhandler).trigger(eventName, args); - } - } - - /** This will deleteAnimations to true after the current digest */ - function enableDeleteAnimations() { - //do timeout so that it re-enables them after this digest - $timeout(function () { - //enable delete animations - deleteAnimations = true; - }, 0, false); - } - - - /*this is the only external interface a tree has */ - function setupExternalEvents() { - if (scope.eventhandler) { - - scope.eventhandler.clearCache = function(section) { - treeService.clearCache({ section: section }); - }; - - scope.eventhandler.load = function(section) { - scope.section = section; - loadTree(); - }; - - scope.eventhandler.reloadNode = function(node) { - - if (!node) { - node = scope.currentNode; - } - - if (node) { - scope.loadChildren(node, true); - } - }; - - /** - Used to do the tree syncing. If the args.tree is not specified we are assuming it has been - specified previously using the _setActiveTreeType - */ - scope.eventhandler.syncTree = function(args) { - if (!args) { - throw "args cannot be null"; - } - if (!args.path) { - throw "args.path cannot be null"; - } - - var deferred = $q.defer(); - - //this is super complex but seems to be working in other places, here we're listening for our - // own events, once the tree is sycned we'll resolve our promise. - scope.eventhandler.one("treeSynced", function (e, syncArgs) { - deferred.resolve(syncArgs); - }); - - //this should normally be set unless it is being called from legacy - // code, so set the active tree type before proceeding. - if (args.tree) { - loadActiveTree(args.tree); - } - - if (angular.isString(args.path)) { - args.path = args.path.replace('"', '').split(','); - } - - //reset current node selection - //scope.currentNode = null; - - //Filter the path for root node ids (we don't want to pass in -1 or 'init') - - args.path = _.filter(args.path, function (item) { return (item !== "init" && item !== "-1"); }); - - //Once those are filtered we need to check if the current user has a special start node id, - // if they do, then we're going to trim the start of the array for anything found from that start node - // and previous so that the tree syncs properly. The tree syncs from the top down and if there are parts - // of the tree's path in there that don't actually exist in the dom/model then syncing will not work. - - userService.getCurrentUser().then(function(userData) { - - var startNodes = [userData.startContentId, userData.startMediaId]; - _.each(startNodes, function (i) { - var found = _.find(args.path, function (p) { - return String(p) === String(i); - }); - if (found) { - args.path = args.path.splice(_.indexOf(args.path, found)); - } - }); - - - loadPath(args.path, args.forceReload, args.activate); - - }); - - - - return deferred.promise; - }; - - /** - Internal method that should ONLY be used by the legacy API wrapper, the legacy API used to - have to set an active tree and then sync, the new API does this in one method by using syncTree. - loadChildren is optional but if it is set, it will set the current active tree and load the root - node's children - this is synonymous with the legacy refreshTree method - again should not be used - and should only be used for the legacy code to work. - */ - scope.eventhandler._setActiveTreeType = function(treeAlias, loadChildren) { - loadActiveTree(treeAlias, loadChildren); - }; - } - } - - - //helper to load a specific path on the active tree as soon as its ready - function loadPath(path, forceReload, activate) { - - if (scope.activeTree) { - syncTree(scope.activeTree, path, forceReload, activate); - } - else { - scope.eventhandler.one("activeTreeLoaded", function (e, args) { - syncTree(args.tree, path, forceReload, activate); - }); - } - } - - - //given a tree alias, this will search the current section tree for the specified tree alias and - //set that to the activeTree - //NOTE: loadChildren is ONLY used for legacy purposes, do not use this when syncing the tree as it will cause problems - // since there will be double request and event handling operations. - function loadActiveTree(treeAlias, loadChildren) { - if (!treeAlias) { - return; - } - - scope.activeTree = undefined; - - function doLoad(tree) { - var childrenAndSelf = [tree].concat(tree.children); - scope.activeTree = _.find(childrenAndSelf, function (node) { - if(node && node.metaData && node.metaData.treeAlias) { - return node.metaData.treeAlias.toUpperCase() === treeAlias.toUpperCase(); - } - return false; - }); - - if (!scope.activeTree) { - throw "Could not find the tree " + treeAlias + ", activeTree has not been set"; - } - - //This is only used for the legacy tree method refreshTree! - if (loadChildren) { - scope.activeTree.expanded = true; - scope.loadChildren(scope.activeTree, false).then(function() { - emitEvent("activeTreeLoaded", { tree: scope.activeTree }); - }); - } - else { - emitEvent("activeTreeLoaded", { tree: scope.activeTree }); - } - } - - if (scope.tree) { - doLoad(scope.tree.root); - } - else { - scope.eventhandler.one("treeLoaded", function(e, args) { - doLoad(args.tree.root); - }); - } - } - - - /** Method to load in the tree data */ - - function loadTree() { - if (!scope.loading && scope.section) { - scope.loading = true; - - //anytime we want to load the tree we need to disable the delete animations - deleteAnimations = false; - - //default args - var args = { section: scope.section, tree: scope.treealias, cacheKey: scope.cachekey, isDialog: scope.isdialog ? scope.isdialog : false }; - - //add the extra query string params if specified - if (scope.customtreeparams) { - args["queryString"] = scope.customtreeparams; - } - - treeService.getTree(args) - .then(function(data) { - //set the data once we have it - scope.tree = data; - - enableDeleteAnimations(); - - scope.loading = false; - - //set the root as the current active tree - scope.activeTree = scope.tree.root; - emitEvent("treeLoaded", { tree: scope.tree }); - emitEvent("treeNodeExpanded", { tree: scope.tree, node: scope.tree.root, children: scope.tree.root.children }); - - }, function(reason) { - scope.loading = false; - notificationsService.error("Tree Error", reason); - }); - } - } - - /** syncs the tree, the treeNode can be ANY tree node in the tree that requires syncing */ - function syncTree(treeNode, path, forceReload, activate) { - - deleteAnimations = false; - - treeService.syncTree({ - node: treeNode, - path: path, - forceReload: forceReload - }).then(function (data) { - - if (activate === undefined || activate === true) { - scope.currentNode = data; - } - - emitEvent("treeSynced", { node: data, activate: activate }); - - enableDeleteAnimations(); - }); - - } - - scope.selectEnabledNodeClass = function (node) { - return node ? - node.selected ? - 'icon umb-tree-icon sprTree icon-check blue temporary' : - '' : - ''; - }; - - /** method to set the current animation for the node. - * This changes dynamically based on if we are changing sections or just loading normal tree data. - * When changing sections we don't want all of the tree-ndoes to do their 'leave' animations. - */ - scope.animation = function() { - if (deleteAnimations && scope.tree && scope.tree.root && scope.tree.root.expanded) { - return { leave: 'tree-node-delete-leave' }; - } - else { - return {}; - } - }; - - /* helper to force reloading children of a tree node */ - scope.loadChildren = function(node, forceReload) { - var deferred = $q.defer(); - - //emit treeNodeExpanding event, if a callback object is set on the tree - emitEvent("treeNodeExpanding", { tree: scope.tree, node: node }); - - //standardising - if (!node.children) { - node.children = []; - } - - if (forceReload || (node.hasChildren && node.children.length === 0)) { - //get the children from the tree service - treeService.loadNodeChildren({ node: node, section: scope.section }) - .then(function(data) { - //emit expanded event - emitEvent("treeNodeExpanded", { tree: scope.tree, node: node, children: data }); - - enableDeleteAnimations(); - - deferred.resolve(data); - }); - } - else { - emitEvent("treeNodeExpanded", { tree: scope.tree, node: node, children: node.children }); - node.expanded = true; - - enableDeleteAnimations(); - - deferred.resolve(node.children); - } - - return deferred.promise; - }; - - /** - Method called when the options button next to the root node is called. - The tree doesnt know about this, so it raises an event to tell the parent controller - about it. - */ - scope.options = function(n, ev) { - emitEvent("treeOptionsClick", { element: elem, node: n, event: ev }); - }; - - /** - Method called when an item is clicked in the tree, this passes the - DOM element, the tree node object and the original click - and emits it as a treeNodeSelect element if there is a callback object - defined on the tree - */ - scope.select = function (n, ev) { - //on tree select we need to remove the current node - - // whoever handles this will need to make sure the correct node is selected - //reset current node selection - scope.currentNode = null; - - emitEvent("treeNodeSelect", { element: elem, node: n, event: ev }); - }; - - scope.altSelect = function(n, ev) { - emitEvent("treeNodeAltSelect", { element: elem, tree: scope.tree, node: n, event: ev }); - }; - - //watch for section changes - scope.$watch("section", function(newVal, oldVal) { - - if (!scope.tree) { - loadTree(); - } - - if (!newVal) { - //store the last section loaded - lastSection = oldVal; - } - else if (newVal !== oldVal && newVal !== lastSection) { - //only reload the tree data and Dom if the newval is different from the old one - // and if the last section loaded is different from the requested one. - loadTree(); - - //store the new section to be loaded as the last section - //clear any active trees to reset lookups - lastSection = newVal; - } - }); - - setupExternalEvents(); - loadTree(); - }; - } - }; -} - -angular.module("umbraco.directives").directive('umbTree', umbTreeDirective); - -/** - * @ngdoc directive - * @name umbraco.directives.directive:umbTreeItem - * @element li - * @function - * - * @description - * Renders a list item, representing a single node in the tree. - * Includes element to toggle children, and a menu toggling button - * - * **note:** This directive is only used internally in the umbTree directive - * - * @example - - - - - - */ -angular.module("umbraco.directives") -.directive('umbTreeItem', function ($compile, $http, $templateCache, $interpolate, $log, $location, $rootScope, $window, treeService, $timeout, localizationService) { - return { - restrict: 'E', - replace: true, - - scope: { - section: '@', - eventhandler: '=', - currentNode: '=', - node: '=', - tree: '=' - }, - - //TODO: Remove more of the binding from this template and move the DOM manipulation to be manually done in the link function, - // this will greatly improve performance since there's potentially a lot of nodes being rendered = a LOT of watches! - - template: '
  • ' + - '
    ' + - //NOTE: This ins element is used to display the search icon if the node is a container/listview and the tree is currently in dialog - //'' + - ' ' + - '' + - '' + - //NOTE: These are the 'option' elipses - '' + - '
    ' + - '
    ' + - '
  • ', - - link: function (scope, element, attrs) { - - localizationService.localize("general_search").then(function (value) { - scope.searchAltText = value; - }); - - //flag to enable/disable delete animations, default for an item is true - var deleteAnimations = true; - - // Helper function to emit tree events - function emitEvent(eventName, args) { - - if (scope.eventhandler) { - $(scope.eventhandler).trigger(eventName, args); - } - } - - // updates the node's DOM/styles - function setupNodeDom(node, tree) { - - //get the first div element - element.children(":first") - //set the padding - .css("padding-left", (node.level * 20) + "px"); - - //toggle visibility of last 'ins' depending on children - //visibility still ensure the space is "reserved", so both nodes with and without children are aligned. - if (!node.hasChildren) { - element.find("ins").last().css("visibility", "hidden"); - } - else { - element.find("ins").last().css("visibility", "visible"); - } - - var icon = element.find("i:first"); - icon.addClass(node.cssClass); - icon.attr("title", node.routePath); - - element.find("a:first").text(node.name); - - if (!node.menuUrl) { - element.find("a.umb-options").remove(); - } - - if (node.style) { - element.find("i:first").attr("style", node.style); - } - } - - //This will deleteAnimations to true after the current digest - function enableDeleteAnimations() { - //do timeout so that it re-enables them after this digest - $timeout(function () { - //enable delete animations - deleteAnimations = true; - }, 0, false); - } - - /** Returns the css classses assigned to the node (div element) */ - scope.getNodeCssClass = function (node) { - if (!node) { - return ''; - } - var css = []; - if (node.cssClasses) { - _.each(node.cssClasses, function(c) { - css.push(c); - }); - } - if (node.selected) { - css.push("umb-tree-node-checked"); - } - return css.join(" "); - }; - - //add a method to the node which we can use to call to update the node data if we need to , - // this is done by sync tree, we don't want to add a $watch for each node as that would be crazy insane slow - // so we have to do this - scope.node.updateNodeData = function (newNode) { - _.extend(scope.node, newNode); - //now update the styles - setupNodeDom(scope.node, scope.tree); - }; - - /** - Method called when the options button next to a node is called - In the main tree this opens the menu, but internally the tree doesnt - know about this, so it simply raises an event to tell the parent controller - about it. - */ - scope.options = function (n, ev) { - emitEvent("treeOptionsClick", { element: element, tree: scope.tree, node: n, event: ev }); - }; - - /** - Method called when an item is clicked in the tree, this passes the - DOM element, the tree node object and the original click - and emits it as a treeNodeSelect element if there is a callback object - defined on the tree - */ - scope.select = function (n, ev) { - if (ev.ctrlKey || - ev.shiftKey || - ev.metaKey || // apple - (ev.button && ev.button === 1) // middle click, >IE9 + everyone else - ) { - return; - } - - emitEvent("treeNodeSelect", { element: element, tree: scope.tree, node: n, event: ev }); - ev.preventDefault(); - }; - - /** - Method called when an item is right-clicked in the tree, this passes the - DOM element, the tree node object and the original click - and emits it as a treeNodeSelect element if there is a callback object - defined on the tree - */ - scope.altSelect = function (n, ev) { - emitEvent("treeNodeAltSelect", { element: element, tree: scope.tree, node: n, event: ev }); - }; - - /** method to set the current animation for the node. - * This changes dynamically based on if we are changing sections or just loading normal tree data. - * When changing sections we don't want all of the tree-ndoes to do their 'leave' animations. - */ - scope.animation = function () { - if (scope.node.showHideAnimation) { - return scope.node.showHideAnimation; - } - if (deleteAnimations && scope.node.expanded) { - return { leave: 'tree-node-delete-leave' }; - } - else { - return {}; - } - }; - - /** - Method called when a node in the tree is expanded, when clicking the arrow - takes the arrow DOM element and node data as parameters - emits treeNodeCollapsing event if already expanded and treeNodeExpanding if collapsed - */ - scope.load = function (node) { - if (node.expanded) { - deleteAnimations = false; - emitEvent("treeNodeCollapsing", { tree: scope.tree, node: node, element: element }); - node.expanded = false; - } - else { - scope.loadChildren(node, false); - } - }; - - /* helper to force reloading children of a tree node */ - scope.loadChildren = function (node, forceReload) { - //emit treeNodeExpanding event, if a callback object is set on the tree - emitEvent("treeNodeExpanding", { tree: scope.tree, node: node }); - - if (node.hasChildren && (forceReload || !node.children || (angular.isArray(node.children) && node.children.length === 0))) { - //get the children from the tree service - treeService.loadNodeChildren({ node: node, section: scope.section }) - .then(function (data) { - //emit expanded event - emitEvent("treeNodeExpanded", { tree: scope.tree, node: node, children: data }); - enableDeleteAnimations(); - }); - } - else { - emitEvent("treeNodeExpanded", { tree: scope.tree, node: node, children: node.children }); - node.expanded = true; - enableDeleteAnimations(); - } - }; - - //if the current path contains the node id, we will auto-expand the tree item children - - setupNodeDom(scope.node, scope.tree); - - var template = '
    '; - var newElement = angular.element(template); - $compile(newElement)(scope); - element.append(newElement); - - } - }; -}); - -/** + angular.module('umbraco.directives').directive('umbTree', umbTreeDirective); + /** + * @ngdoc directive + * @name umbraco.directives.directive:umbTreeItem + * @element li + * @function + * + * @description + * Renders a list item, representing a single node in the tree. + * Includes element to toggle children, and a menu toggling button + * + * **note:** This directive is only used internally in the umbTree directive + * + * @example + + + + + + */ + angular.module('umbraco.directives').directive('umbTreeItem', function ($compile, $http, $templateCache, $interpolate, $log, $location, $rootScope, $window, treeService, $timeout, localizationService) { + return { + restrict: 'E', + replace: true, + scope: { + section: '@', + eventhandler: '=', + currentNode: '=', + enablelistviewexpand: '@', + node: '=', + tree: '=' + }, + //TODO: Remove more of the binding from this template and move the DOM manipulation to be manually done in the link function, + // this will greatly improve performance since there's potentially a lot of nodes being rendered = a LOT of watches! + template: '
  • ' + '
    ' + //NOTE: This ins element is used to display the search icon if the node is a container/listview and the tree is currently in dialog + //'' + + ' ' + '' + '' + //NOTE: These are the 'option' elipses + '' + '
    ' + '
    ' + '
  • ', + link: function (scope, element, attrs) { + localizationService.localize('general_search').then(function (value) { + scope.searchAltText = value; + }); + //flag to enable/disable delete animations, default for an item is true + var deleteAnimations = true; + // Helper function to emit tree events + function emitEvent(eventName, args) { + if (scope.eventhandler) { + $(scope.eventhandler).trigger(eventName, args); + } + } + // updates the node's DOM/styles + function setupNodeDom(node, tree) { + //get the first div element + element.children(':first') //set the padding +.css('padding-left', node.level * 20 + 'px'); + //toggle visibility of last 'ins' depending on children + //visibility still ensure the space is "reserved", so both nodes with and without children are aligned. + if (node.hasChildren || node.metaData.isContainer && scope.enablelistviewexpand === 'true') { + element.find('ins').last().css('visibility', 'visible'); + } else { + element.find('ins').last().css('visibility', 'hidden'); + } + var icon = element.find('i:first'); + icon.addClass(node.cssClass); + icon.attr('title', node.routePath); + element.find('a:first').text(node.name); + if (!node.menuUrl) { + element.find('a.umb-options').remove(); + } + if (node.style) { + element.find('i:first').attr('style', node.style); + } + // add a unique data element to each tree item so it is easy to navigate with code + if (!node.metaData.treeAlias) { + node.dataElement = node.name; + } else { + node.dataElement = node.metaData.treeAlias; + } + } + //This will deleteAnimations to true after the current digest + function enableDeleteAnimations() { + //do timeout so that it re-enables them after this digest + $timeout(function () { + //enable delete animations + deleteAnimations = true; + }, 0, false); + } + /** Returns the css classses assigned to the node (div element) */ + scope.getNodeCssClass = function (node) { + if (!node) { + return ''; + } + //TODO: This is called constantly because as a method in a template it's re-evaluated pretty much all the time + // it would be better if we could cache the processing. The problem is that some of these things are dynamic. + var css = []; + if (node.cssClasses) { + _.each(node.cssClasses, function (c) { + css.push(c); + }); + } + if (node.selected) { + css.push('umb-tree-node-checked'); + } + return css.join(' '); + }; + //add a method to the node which we can use to call to update the node data if we need to , + // this is done by sync tree, we don't want to add a $watch for each node as that would be crazy insane slow + // so we have to do this + scope.node.updateNodeData = function (newNode) { + _.extend(scope.node, newNode); + //now update the styles + setupNodeDom(scope.node, scope.tree); + }; + /** + Method called when the options button next to a node is called + In the main tree this opens the menu, but internally the tree doesnt + know about this, so it simply raises an event to tell the parent controller + about it. + */ + scope.options = function (n, ev) { + emitEvent('treeOptionsClick', { + element: element, + tree: scope.tree, + node: n, + event: ev + }); + }; + /** + Method called when an item is clicked in the tree, this passes the + DOM element, the tree node object and the original click + and emits it as a treeNodeSelect element if there is a callback object + defined on the tree + */ + scope.select = function (n, ev) { + if (ev.ctrlKey || ev.shiftKey || ev.metaKey || ev.button && ev.button === 1 // middle click, >IE9 + everyone else +) { + return; + } + if (n.metaData && n.metaData.noAccess === true) { + ev.preventDefault(); + return; + } + emitEvent('treeNodeSelect', { + element: element, + tree: scope.tree, + node: n, + event: ev + }); + ev.preventDefault(); + }; + /** + Method called when an item is right-clicked in the tree, this passes the + DOM element, the tree node object and the original click + and emits it as a treeNodeSelect element if there is a callback object + defined on the tree + */ + scope.altSelect = function (n, ev) { + emitEvent('treeNodeAltSelect', { + element: element, + tree: scope.tree, + node: n, + event: ev + }); + }; + /** method to set the current animation for the node. + * This changes dynamically based on if we are changing sections or just loading normal tree data. + * When changing sections we don't want all of the tree-ndoes to do their 'leave' animations. + */ + scope.animation = function () { + if (scope.node.showHideAnimation) { + return scope.node.showHideAnimation; + } + if (deleteAnimations && scope.node.expanded) { + return { leave: 'tree-node-delete-leave' }; + } else { + return {}; + } + }; + /** + Method called when a node in the tree is expanded, when clicking the arrow + takes the arrow DOM element and node data as parameters + emits treeNodeCollapsing event if already expanded and treeNodeExpanding if collapsed + */ + scope.load = function (node) { + if (node.expanded && !node.metaData.isContainer) { + deleteAnimations = false; + emitEvent('treeNodeCollapsing', { + tree: scope.tree, + node: node, + element: element + }); + node.expanded = false; + } else { + scope.loadChildren(node, false); + } + }; + /* helper to force reloading children of a tree node */ + scope.loadChildren = function (node, forceReload) { + //emit treeNodeExpanding event, if a callback object is set on the tree + emitEvent('treeNodeExpanding', { + tree: scope.tree, + node: node + }); + if (node.hasChildren && (forceReload || !node.children || angular.isArray(node.children) && node.children.length === 0)) { + //get the children from the tree service + treeService.loadNodeChildren({ + node: node, + section: scope.section + }).then(function (data) { + //emit expanded event + emitEvent('treeNodeExpanded', { + tree: scope.tree, + node: node, + children: data + }); + enableDeleteAnimations(); + }); + } else { + emitEvent('treeNodeExpanded', { + tree: scope.tree, + node: node, + children: node.children + }); + node.expanded = true; + enableDeleteAnimations(); + } + }; + //if the current path contains the node id, we will auto-expand the tree item children + setupNodeDom(scope.node, scope.tree); + // load the children if the current user don't have access to the node + // it is used to auto expand the tree to the start nodes the user has access to + if (scope.node.hasChildren && scope.node.metaData.noAccess) { + scope.loadChildren(scope.node); + } + var template = '
    '; + var newElement = angular.element(template); + $compile(newElement)(scope); + element.append(newElement); + } + }; + }); + /** * @ngdoc directive * @name umbraco.directives.directive:umbTreeSearchBox * @function * @element ANY * @restrict E **/ -function treeSearchBox(localizationService, searchService, $q) { - return { - scope: { - searchFromId: "@", - searchFromName: "@", - showSearch: "@", - section: "@", - hideSearchCallback: "=", - searchCallback: "=" - }, - restrict: "E", // restrict to an element - replace: true, // replace the html element with the template - templateUrl: 'views/components/tree/umb-tree-search-box.html', - link: function (scope, element, attrs, ctrl) { - - scope.term = ""; - scope.hideSearch = function() { - scope.term = ""; - scope.hideSearchCallback(); - }; - - localizationService.localize("general_typeToSearch").then(function (value) { - scope.searchPlaceholderText = value; - }); - - if (!scope.showSearch) { - scope.showSearch = "false"; - } - - //used to cancel any request in progress if another one needs to take it's place - var canceler = null; - - function performSearch() { - if (scope.term) { - scope.results = []; - - //a canceler exists, so perform the cancelation operation and reset - if (canceler) { - canceler.resolve(); - canceler = $q.defer(); + function treeSearchBox(localizationService, searchService, $q) { + return { + scope: { + searchFromId: '@', + searchFromName: '@', + showSearch: '@', + section: '@', + hideSearchCallback: '=', + searchCallback: '=' + }, + restrict: 'E', + // restrict to an element + replace: true, + // replace the html element with the template + templateUrl: 'views/components/tree/umb-tree-search-box.html', + link: function (scope, element, attrs, ctrl) { + scope.term = ''; + scope.hideSearch = function () { + scope.term = ''; + scope.hideSearchCallback(); + }; + localizationService.localize('general_typeToSearch').then(function (value) { + scope.searchPlaceholderText = value; + }); + if (!scope.showSearch) { + scope.showSearch = 'false'; + } + //used to cancel any request in progress if another one needs to take it's place + var canceler = null; + function performSearch() { + if (scope.term) { + scope.results = []; + //a canceler exists, so perform the cancelation operation and reset + if (canceler) { + canceler.resolve(); + canceler = $q.defer(); + } else { + canceler = $q.defer(); + } + var searchArgs = { + term: scope.term, + canceler: canceler + }; + //append a start node context if there is one + if (scope.searchFromId) { + searchArgs['searchFrom'] = scope.searchFromId; + } + searcher(searchArgs).then(function (data) { + scope.searchCallback(data); + //set back to null so it can be re-created + canceler = null; + }); } - else { - canceler = $q.defer(); - } - - var searchArgs = { - term: scope.term, - canceler: canceler - }; - - //append a start node context if there is one - if (scope.searchFromId) { - searchArgs["searchFrom"] = scope.searchFromId; - } - - searcher(searchArgs).then(function (data) { - scope.searchCallback(data); - //set back to null so it can be re-created - canceler = null; + } + scope.$watch('term', _.debounce(function (newVal, oldVal) { + scope.$apply(function () { + if (newVal !== null && newVal !== undefined && newVal !== oldVal) { + performSearch(); + } }); + }, 200)); + var searcher = searchService.searchContent; + //search + if (scope.section === 'member') { + searcher = searchService.searchMembers; + } else if (scope.section === 'media') { + searcher = searchService.searchMedia; } } - - scope.$watch("term", _.debounce(function(newVal, oldVal) { - scope.$apply(function() { - if (newVal !== null && newVal !== undefined && newVal !== oldVal) { - performSearch(); - } - }); - }, 200)); - - var searcher = searchService.searchContent; - //search - if (scope.section === "member") { - searcher = searchService.searchMembers; - } - else if (scope.section === "media") { - searcher = searchService.searchMedia; - } - } - }; -} -angular.module('umbraco.directives').directive("umbTreeSearchBox", treeSearchBox); - -/** + }; + } + angular.module('umbraco.directives').directive('umbTreeSearchBox', treeSearchBox); + /** * @ngdoc directive * @name umbraco.directives.directive:umbTreeSearchResults * @function * @element ANY * @restrict E **/ -function treeSearchResults() { - return { - scope: { - results: "=", - selectResultCallback: "=" - }, - restrict: "E", // restrict to an element - replace: true, // replace the html element with the template - templateUrl: 'views/components/tree/umb-tree-search-results.html', - link: function (scope, element, attrs, ctrl) { - - } - }; -} -angular.module('umbraco.directives').directive("umbTreeSearchResults", treeSearchResults); - -/** -@ngdoc directive -@name umbraco.directives.directive:umbGenerateAlias -@restrict E -@scope - -@description -Use this directive to generate a camelCased umbraco alias. -When the aliasFrom value is changed the directive will get a formatted alias from the server and update the alias model. If "enableLock" is set to true -the directive will use {@link umbraco.directives.directive:umbLockedField umbLockedField} to lock and unlock the alias. - -

    Markup example

    -
    -    
    - - - - - - -
    -
    - -

    Controller example

    -
    -    (function () {
    -        "use strict";
    -
    -        function Controller() {
    -
    -            var vm = this;
    -
    -            vm.name = "";
    -            vm.alias = "";
    -
    -        }
    -
    -        angular.module("umbraco").controller("My.Controller", Controller);
    -    })();
    -
    - -@param {string} alias (binding): The model where the alias is bound. -@param {string} aliasFrom (binding): The model to generate the alias from. -@param {boolean=} enableLock (binding): Set to true to add a lock next to the alias from where it can be unlocked and changed. -**/ - -angular.module("umbraco.directives") - .directive('umbGenerateAlias', function ($timeout, entityResource) { + function treeSearchResults() { return { - restrict: 'E', - templateUrl: 'views/components/umb-generate-alias.html', - replace: true, scope: { - alias: '=', - aliasFrom: '=', - enableLock: '=?', - serverValidationField: '@' + results: '=', + selectResultCallback: '=' }, + restrict: 'E', + // restrict to an element + replace: true, + // replace the html element with the template + templateUrl: 'views/components/tree/umb-tree-search-results.html', link: function (scope, element, attrs, ctrl) { - - var eventBindings = []; - var bindWatcher = true; - var generateAliasTimeout = ""; - var updateAlias = false; - - scope.locked = true; - scope.placeholderText = "Enter alias..."; - - function generateAlias(value) { - - if (generateAliasTimeout) { - $timeout.cancel(generateAliasTimeout); - } - - if( value !== undefined && value !== "" && value !== null) { - - scope.alias = ""; - scope.placeholderText = "Generating Alias..."; - - generateAliasTimeout = $timeout(function () { - updateAlias = true; - entityResource.getSafeAlias(value, true).then(function (safeAlias) { - if (updateAlias) { - scope.alias = safeAlias.alias; - } - }); - }, 500); - - } else { - updateAlias = true; - scope.alias = ""; - scope.placeholderText = "Enter alias..."; - } - - } - - // if alias gets unlocked - stop watching alias - eventBindings.push(scope.$watch('locked', function(newValue, oldValue){ - if(newValue === false) { - bindWatcher = false; - } - })); - - // validate custom entered alias - eventBindings.push(scope.$watch('alias', function(newValue, oldValue){ - - if(scope.alias === "" && bindWatcher === true || scope.alias === null && bindWatcher === true) { - // add watcher - eventBindings.push(scope.$watch('aliasFrom', function(newValue, oldValue) { - if(bindWatcher) { - generateAlias(newValue); - } - })); - } - - })); - - // clean up - scope.$on('$destroy', function(){ - // unbind watchers - for(var e in eventBindings) { - eventBindings[e](); - } - }); - } }; - }); - -/** + } + angular.module('umbraco.directives').directive('umbTreeSearchResults', treeSearchResults); + (function () { + 'use strict'; + function AceEditorDirective(umbAceEditorConfig, assetsService, angularHelper) { + /** + * Sets editor options such as the wrapping mode or the syntax checker. + * + * The supported options are: + * + *
      + *
    • showGutter
    • + *
    • useWrapMode
    • + *
    • onLoad
    • + *
    • theme
    • + *
    • mode
    • + *
    + * + * @param acee + * @param session ACE editor session + * @param {object} opts Options to be set + */ + var setOptions = function (acee, session, opts) { + // sets the ace worker path, if running from concatenated + // or minified source + if (angular.isDefined(opts.workerPath)) { + var config = window.ace.require('ace/config'); + config.set('workerPath', opts.workerPath); + } + // ace requires loading + if (angular.isDefined(opts.require)) { + opts.require.forEach(function (n) { + window.ace.require(n); + }); + } + // Boolean options + if (angular.isDefined(opts.showGutter)) { + acee.renderer.setShowGutter(opts.showGutter); + } + if (angular.isDefined(opts.useWrapMode)) { + session.setUseWrapMode(opts.useWrapMode); + } + if (angular.isDefined(opts.showInvisibles)) { + acee.renderer.setShowInvisibles(opts.showInvisibles); + } + if (angular.isDefined(opts.showIndentGuides)) { + acee.renderer.setDisplayIndentGuides(opts.showIndentGuides); + } + if (angular.isDefined(opts.useSoftTabs)) { + session.setUseSoftTabs(opts.useSoftTabs); + } + if (angular.isDefined(opts.showPrintMargin)) { + acee.setShowPrintMargin(opts.showPrintMargin); + } + // commands + if (angular.isDefined(opts.disableSearch) && opts.disableSearch) { + acee.commands.addCommands([{ + name: 'unfind', + bindKey: { + win: 'Ctrl-F', + mac: 'Command-F' + }, + exec: function () { + return false; + }, + readOnly: true + }]); + } + // Basic options + if (angular.isString(opts.theme)) { + acee.setTheme('ace/theme/' + opts.theme); + } + if (angular.isString(opts.mode)) { + session.setMode('ace/mode/' + opts.mode); + } + // Advanced options + if (angular.isDefined(opts.firstLineNumber)) { + if (angular.isNumber(opts.firstLineNumber)) { + session.setOption('firstLineNumber', opts.firstLineNumber); + } else if (angular.isFunction(opts.firstLineNumber)) { + session.setOption('firstLineNumber', opts.firstLineNumber()); + } + } + // advanced options + var key, obj; + if (angular.isDefined(opts.advanced)) { + for (key in opts.advanced) { + // create a javascript object with the key and value + obj = { + name: key, + value: opts.advanced[key] + }; + // try to assign the option to the ace editor + acee.setOption(obj.name, obj.value); + } + } + // advanced options for the renderer + if (angular.isDefined(opts.rendererOptions)) { + for (key in opts.rendererOptions) { + // create a javascript object with the key and value + obj = { + name: key, + value: opts.rendererOptions[key] + }; + // try to assign the option to the ace editor + acee.renderer.setOption(obj.name, obj.value); + } + } + // onLoad callbacks + angular.forEach(opts.callbacks, function (cb) { + if (angular.isFunction(cb)) { + cb(acee); + } + }); + }; + function link(scope, el, attr, ngModel) { + // Load in ace library + assetsService.load([ + 'lib/ace-builds/src-min-noconflict/ace.js', + 'lib/ace-builds/src-min-noconflict/ext-language_tools.js' + ], scope).then(function () { + if (angular.isUndefined(window.ace)) { + throw new Error('ui-ace need ace to work... (o rly?)'); + } else { + // init editor + init(); + } + }); + function init() { + /** + * Corresponds the umbAceEditorConfig ACE configuration. + * @type object + */ + var options = umbAceEditorConfig.ace || {}; + /** + * umbAceEditorConfig merged with user options via json in attribute or data binding + * @type object + */ + var opts = angular.extend({}, options, scope.umbAceEditor); + //load ace libraries here... + /** + * ACE editor + * @type object + */ + var acee = window.ace.edit(el[0]); + acee.$blockScrolling = Infinity; + /** + * ACE editor session. + * @type object + * @see [EditSession]{@link https://ace.c9.io/#nav=api&api=edit_session} + */ + var session = acee.getSession(); + /** + * Reference to a change listener created by the listener factory. + * @function + * @see listenerFactory.onChange + */ + var onChangeListener; + /** + * Reference to a blur listener created by the listener factory. + * @function + * @see listenerFactory.onBlur + */ + var onBlurListener; + /** + * Calls a callback by checking its existing. The argument list + * is variable and thus this function is relying on the arguments + * object. + * @throws {Error} If the callback isn't a function + */ + var executeUserCallback = function () { + /** + * The callback function grabbed from the array-like arguments + * object. The first argument should always be the callback. + * + * @see [arguments]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions_and_function_scope/arguments} + * @type {*} + */ + var callback = arguments[0]; + /** + * Arguments to be passed to the callback. These are taken + * from the array-like arguments object. The first argument + * is stripped because that should be the callback function. + * + * @see [arguments]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions_and_function_scope/arguments} + * @type {Array} + */ + var args = Array.prototype.slice.call(arguments, 1); + if (angular.isDefined(callback)) { + scope.$evalAsync(function () { + if (angular.isFunction(callback)) { + callback(args); + } else { + throw new Error('ui-ace use a function as callback.'); + } + }); + } + }; + /** + * Listener factory. Until now only change listeners can be created. + * @type object + */ + var listenerFactory = { + /** + * Creates a change listener which propagates the change event + * and the editor session to the callback from the user option + * onChange. It might be exchanged during runtime, if this + * happens the old listener will be unbound. + * + * @param callback callback function defined in the user options + * @see onChangeListener + */ + onChange: function (callback) { + return function (e) { + var newValue = session.getValue(); + angularHelper.safeApply(scope, function () { + scope.model = newValue; + }); + executeUserCallback(callback, e, acee); + }; + }, + /** + * Creates a blur listener which propagates the editor session + * to the callback from the user option onBlur. It might be + * exchanged during runtime, if this happens the old listener + * will be unbound. + * + * @param callback callback function defined in the user options + * @see onBlurListener + */ + onBlur: function (callback) { + return function () { + executeUserCallback(callback, acee); + }; + } + }; + attr.$observe('readonly', function (value) { + acee.setReadOnly(!!value || value === ''); + }); + // Value Blind + if (scope.model) { + session.setValue(scope.model); + } + // Listen for option updates + var updateOptions = function (current, previous) { + if (current === previous) { + return; + } + opts = angular.extend({}, options, scope.umbAceEditor); + opts.callbacks = [opts.onLoad]; + if (opts.onLoad !== options.onLoad) { + // also call the global onLoad handler + opts.callbacks.unshift(options.onLoad); + } + // EVENTS + // unbind old change listener + session.removeListener('change', onChangeListener); + // bind new change listener + onChangeListener = listenerFactory.onChange(opts.onChange); + session.on('change', onChangeListener); + // unbind old blur listener + //session.removeListener('blur', onBlurListener); + acee.removeListener('blur', onBlurListener); + // bind new blur listener + onBlurListener = listenerFactory.onBlur(opts.onBlur); + acee.on('blur', onBlurListener); + setOptions(acee, session, opts); + }; + scope.$watch(scope.umbAceEditor, updateOptions, /* deep watch */ + true); + // set the options here, even if we try to watch later, if this + // line is missing things go wrong (and the tests will also fail) + updateOptions(options); + el.on('$destroy', function () { + acee.session.$stopWorker(); + acee.destroy(); + }); + scope.$watch(function () { + return [ + el[0].offsetWidth, + el[0].offsetHeight + ]; + }, function () { + acee.resize(); + acee.renderer.updateFull(); + }, true); + } + } + var directive = { + restrict: 'EA', + scope: { + 'umbAceEditor': '=', + 'model': '=' + }, + link: link + }; + return directive; + } + angular.module('umbraco.directives').constant('umbAceEditorConfig', {}).directive('umbAceEditor', AceEditorDirective); + }()); + /** @ngdoc directive @name umbraco.directives.directive:umbAvatar @restrict E @@ -6401,32 +7639,92 @@ Use this directive to render an avatar. @param {string} img-src (attribute): The image source to the avatar. @param {string} img-srcset (atribute): Reponsive support for the image source. **/ - -(function() { - 'use strict'; - - function AvatarDirective() { - - var directive = { - restrict: 'E', - replace: true, - templateUrl: 'views/components/umb-avatar.html', - scope: { - size: "@", - imgSrc: "@", - imgSrcset: "@" + (function () { + 'use strict'; + function AvatarDirective() { + function link(scope, element, attrs, ctrl) { + var eventBindings = []; + scope.initials = ''; + function onInit() { + if (!scope.unknownChar) { + scope.unknownChar = '?'; + } + scope.initials = getNameInitials(scope.name); + } + function getNameInitials(name) { + if (name) { + var names = name.split(' '), initials = names[0].substring(0, 1); + if (names.length > 1) { + initials += names[names.length - 1].substring(0, 1); + } + return initials.toUpperCase(); + } + return null; + } + eventBindings.push(scope.$watch('name', function (newValue, oldValue) { + if (newValue === oldValue) { + return; + } + if (oldValue === undefined || newValue === undefined) { + return; + } + scope.initials = getNameInitials(newValue); + })); + onInit(); } - }; - - return directive; - - } - - angular.module('umbraco.directives').directive('umbAvatar', AvatarDirective); - -})(); - -/** + var directive = { + restrict: 'E', + replace: true, + templateUrl: 'views/components/umb-avatar.html', + scope: { + size: '@', + name: '@', + color: '@', + imgSrc: '@', + imgSrcset: '@', + unknownChar: '@' + }, + link: link + }; + return directive; + } + angular.module('umbraco.directives').directive('umbAvatar', AvatarDirective); + }()); + (function () { + 'use strict'; + function BadgeDirective() { + var directive = { + restrict: 'E', + replace: true, + transclude: true, + templateUrl: 'views/components/umb-badge.html', + scope: { + size: '@?', + color: '@?' + } + }; + return directive; + } + angular.module('umbraco.directives').directive('umbBadge', BadgeDirective); + }()); + (function () { + 'use strict'; + function CheckmarkDirective() { + var directive = { + restrict: 'E', + replace: true, + transclude: true, + templateUrl: 'views/components/umb-checkmark.html', + scope: { + size: '@?', + checked: '=' + } + }; + return directive; + } + angular.module('umbraco.directives').directive('umbCheckmark', CheckmarkDirective); + }()); + /** @ngdoc directive @name umbraco.directives.directive:umbChildSelector @restrict E @@ -6539,143 +7837,366 @@ Use this directive to render a ui component for selecting child items to a paren
  • $event: The select event.
  • **/ - -(function() { - 'use strict'; - - function ChildSelectorDirective() { - - function link(scope, el, attr, ctrl) { - - var eventBindings = []; - scope.dialogModel = {}; - scope.showDialog = false; - - scope.removeChild = function(selectedChild, $index) { - if(scope.onRemove) { - scope.onRemove(selectedChild, $index); - } - }; - - scope.addChild = function($event) { - if(scope.onAdd) { - scope.onAdd($event); - } - }; - - function syncParentName() { - - // update name on available item - angular.forEach(scope.availableChildren, function(availableChild){ - if(availableChild.id === scope.parentId) { - availableChild.name = scope.parentName; + (function () { + 'use strict'; + function ChildSelectorDirective() { + function link(scope, el, attr, ctrl) { + var eventBindings = []; + scope.dialogModel = {}; + scope.showDialog = false; + scope.removeChild = function (selectedChild, $index) { + if (scope.onRemove) { + scope.onRemove(selectedChild, $index); + } + }; + scope.addChild = function ($event) { + if (scope.onAdd) { + scope.onAdd($event); + } + }; + function syncParentName() { + // update name on available item + angular.forEach(scope.availableChildren, function (availableChild) { + if (availableChild.id === scope.parentId) { + availableChild.name = scope.parentName; + } + }); + // update name on selected child + angular.forEach(scope.selectedChildren, function (selectedChild) { + if (selectedChild.id === scope.parentId) { + selectedChild.name = scope.parentName; + } + }); } - }); - - // update name on selected child - angular.forEach(scope.selectedChildren, function(selectedChild){ - if(selectedChild.id === scope.parentId) { - selectedChild.name = scope.parentName; + function syncParentIcon() { + // update icon on available item + angular.forEach(scope.availableChildren, function (availableChild) { + if (availableChild.id === scope.parentId) { + availableChild.icon = scope.parentIcon; + } + }); + // update icon on selected child + angular.forEach(scope.selectedChildren, function (selectedChild) { + if (selectedChild.id === scope.parentId) { + selectedChild.icon = scope.parentIcon; + } + }); } - }); - + eventBindings.push(scope.$watch('parentName', function (newValue, oldValue) { + if (newValue === oldValue) { + return; + } + if (oldValue === undefined || newValue === undefined) { + return; + } + syncParentName(); + })); + eventBindings.push(scope.$watch('parentIcon', function (newValue, oldValue) { + if (newValue === oldValue) { + return; + } + if (oldValue === undefined || newValue === undefined) { + return; + } + syncParentIcon(); + })); + // clean up + scope.$on('$destroy', function () { + // unbind watchers + for (var e in eventBindings) { + eventBindings[e](); + } + }); } + var directive = { + restrict: 'E', + replace: true, + templateUrl: 'views/components/umb-child-selector.html', + scope: { + selectedChildren: '=', + availableChildren: '=', + parentName: '=', + parentIcon: '=', + parentId: '=', + onRemove: '=', + onAdd: '=' + }, + link: link + }; + return directive; + } + angular.module('umbraco.directives').directive('umbChildSelector', ChildSelectorDirective); + }()); + /** +@ngdoc directive +@name umbraco.directives.directive:umbClipboard +@restrict E +@scope - function syncParentIcon() { +@description +Added in Umbraco v. 7.7: Use this directive to copy content to the clipboard - // update icon on available item - angular.forEach(scope.availableChildren, function(availableChild){ - if(availableChild.id === scope.parentId) { - availableChild.icon = scope.parentIcon; - } - }); +

    Markup example

    +
    +    
    + + +
    Copy me!
    + + + - // update icon on selected child - angular.forEach(scope.selectedChildren, function(selectedChild){ - if(selectedChild.id === scope.parentId) { - selectedChild.icon = scope.parentIcon; - } - }); + + + + + + + + + +
    +
    + +

    Controller example

    +
    +    (function () {
    +        "use strict";
    +
    +        function Controller() {
    +
    +            var vm = this;
    +
    +            vm.copyText = "Copy text without element";
    +            vm.cutText = "Text to cut";
    +
    +            vm.copySuccess = copySuccess;
    +            vm.copyError = copyError;
    +
    +            function copySuccess() {
    +                vm.clipboardButtonState = "success";
    +            }
    +            
    +            function copyError() {
    +                vm.clipboardButtonState = "error";
                 }
    -
    -            eventBindings.push(scope.$watch('parentName', function(newValue, oldValue){
    -
    -              if (newValue === oldValue) { return; }
    -              if ( oldValue === undefined || newValue === undefined) { return; }
    -
    -              syncParentName();
    -
    -            }));
    -
    -            eventBindings.push(scope.$watch('parentIcon', function(newValue, oldValue){
    -
    -              if (newValue === oldValue) { return; }
    -              if ( oldValue === undefined || newValue === undefined) { return; }
    -
    -              syncParentIcon();
    -            }));
    -
    -            // clean up
    -            scope.$on('$destroy', function(){
    -              // unbind watchers
    -              for(var e in eventBindings) {
    -                eventBindings[e]();
    -               }
    -            });
     
             }
     
    -        var directive = {
    +        angular.module("umbraco").controller("My.ClipBoardController", Controller);
    +
    +    })();
    +
    + +@param {callback} umbClipboardSuccess (expression): Callback function when the content is copied. +@param {callback} umbClipboardError (expression): Callback function if the copy fails. +@param {string} umbClipboardTarget (attribute): The target element to copy. +@param {string} umbClipboardAction (attribute): Specify if you want to copy or cut content ("copy", "cut"). Cut only works on input and textarea elements. +@param {string} umbClipboardText (attribute): Use this attribute if you don't have an element to copy from. + +**/ + (function () { + 'use strict'; + function umbClipboardDirective($timeout, assetsService) { + function link(scope, element, attrs, ctrl) { + var clipboard; + var target = element[0]; + assetsService.loadJs('lib/clipboard/clipboard.min.js', scope).then(function () { + if (scope.umbClipboardTarget) { + target.setAttribute('data-clipboard-target', scope.umbClipboardTarget); + } + if (scope.umbClipboardAction) { + target.setAttribute('data-clipboard-action', scope.umbClipboardAction); + } + if (scope.umbClipboardText) { + target.setAttribute('data-clipboard-text', scope.umbClipboardText); + } + clipboard = new Clipboard(target); + clipboard.on('success', function (e) { + e.clearSelection(); + if (scope.umbClipboardSuccess) { + scope.$apply(function () { + scope.umbClipboardSuccess({ e: e }); + }); + } + }); + clipboard.on('error', function (e) { + if (scope.umbClipboardError) { + scope.$apply(function () { + scope.umbClipboardError({ e: e }); + }); + } + }); + }); + // clean up + scope.$on('$destroy', function () { + clipboard.destroy(); + }); + } + //////////// + var directive = { + restrict: 'A', + scope: { + umbClipboardSuccess: '&?', + umbClipboardError: '&?', + umbClipboardTarget: '@?', + umbClipboardAction: '@?', + umbClipboardText: '=?' + }, + link: link + }; + return directive; + } + angular.module('umbraco.directives').directive('umbClipboard', umbClipboardDirective); + }()); + /** +@ngdoc directive +@name umbraco.directives.directive:umbColorSwatches +@restrict E +@scope +@description +Use this directive to generate color swatches to pick from. +

    Markup example

    +
    +    
    +    
    +
    +@param {array} colors (attribute): The array of colors. +@param {string} colors (attribute): The array of colors. +@param {string} selectedColor (attribute): The selected color. +@param {string} size (attribute): The size (s, m). +@param {function} onSelect (expression): Callback function when the item is selected. +**/ + (function () { + 'use strict'; + function ColorSwatchesDirective() { + function link(scope, el, attr, ctrl) { + scope.setColor = function (color) { + //scope.selectedColor({color: color }); + scope.selectedColor = color; + if (scope.onSelect) { + scope.onSelect(color); + } + }; + } + var directive = { + restrict: 'E', + replace: true, + transclude: true, + templateUrl: 'views/components/umb-color-swatches.html', + scope: { + colors: '=?', + size: '@', + selectedColor: '=', + onSelect: '&' + }, + link: link + }; + return directive; + } + angular.module('umbraco.directives').directive('umbColorSwatches', ColorSwatchesDirective); + }()); + /** +@ngdoc directive +@name umbraco.directives.directive:umbConfirm +@restrict E +@scope + +@description +A confirmation dialog + + +

    Markup example

    +
    +	
    + + + +
    +
    + +

    Controller example

    +
    +	(function () {
    +		"use strict";
    +
    +		function Controller() {
    +
    +            var vm = this;
    +
    +            vm.onConfirm = function() {
    +                alert('Confirm clicked');
    +            };
    +
    +            vm.onCancel = function() {
    +                alert('Cancel clicked');
    +            }
    +
    +
    +        }
    +
    +		angular.module("umbraco").controller("My.Controller", Controller);
    +
    +	})();
    +
    + +@param {string} caption (attribute): The caption shown above the buttons +@param {callback} on-confirm (attribute): The call back when the "OK" button is clicked. If not set the button will not be shown +@param {callback} on-cancel (atribute): The call back when the "Cancel" button is clicked. If not set the button will not be shown +**/ + function confirmDirective() { + return { restrict: 'E', + // restrict to an element replace: true, - templateUrl: 'views/components/umb-child-selector.html', + // replace the html element with the template + templateUrl: 'views/components/umb-confirm.html', scope: { - selectedChildren: '=', - availableChildren: "=", - parentName: "=", - parentIcon: "=", - parentId: "=", - onRemove: "=", - onAdd: "=" + onConfirm: '=', + onCancel: '=', + caption: '@' }, - link: link + link: function (scope, element, attr, ctrl) { + scope.showCancel = false; + scope.showConfirm = false; + if (scope.onConfirm) { + scope.showConfirm = true; + } + if (scope.onCancel) { + scope.showCancel = true; + } + } }; - - return directive; } - - angular.module('umbraco.directives').directive('umbChildSelector', ChildSelectorDirective); - -})(); - -/** - * @ngdoc directive - * @name umbraco.directives.directive:umbConfirm - * @function - * @description - * A confirmation dialog - * - * @restrict E - */ -function confirmDirective() { - return { - restrict: "E", // restrict to an element - replace: true, // replace the html element with the template - templateUrl: 'views/components/umb-confirm.html', - scope: { - onConfirm: '=', - onCancel: '=', - caption: '@' - }, - link: function (scope, element, attr, ctrl) { - - } - }; -} -angular.module('umbraco.directives').directive("umbConfirm", confirmDirective); - -/** + angular.module('umbraco.directives').directive('umbConfirm', confirmDirective); + /** @ngdoc directive @name umbraco.directives.directive:umbConfirmAction @restrict E @@ -6740,48 +8261,37 @@ The prompt can be opened in four direction up, down, left or right.

    @param {callback} onConfirm Callback when the checkmark is clicked. @param {callback} onCancel Callback when the cross is clicked. **/ - -(function() { - 'use strict'; - - function ConfirmAction() { - - function link(scope, el, attr, ctrl) { - - scope.clickConfirm = function() { - if(scope.onConfirm) { - scope.onConfirm(); - } - }; - - scope.clickCancel = function() { - if(scope.onCancel) { - scope.onCancel(); - } - }; - - } - - var directive = { - restrict: 'E', - replace: true, - templateUrl: 'views/components/umb-confirm-action.html', - scope: { - direction: "@", - onConfirm: "&", - onCancel: "&" - }, - link: link - }; - - return directive; - } - - angular.module('umbraco.directives').directive('umbConfirmAction', ConfirmAction); - -})(); - -/** + (function () { + 'use strict'; + function ConfirmAction() { + function link(scope, el, attr, ctrl) { + scope.clickConfirm = function () { + if (scope.onConfirm) { + scope.onConfirm(); + } + }; + scope.clickCancel = function () { + if (scope.onCancel) { + scope.onCancel(); + } + }; + } + var directive = { + restrict: 'E', + replace: true, + templateUrl: 'views/components/umb-confirm-action.html', + scope: { + direction: '@', + onConfirm: '&', + onCancel: '&' + }, + link: link + }; + return directive; + } + angular.module('umbraco.directives').directive('umbConfirmAction', ConfirmAction); + }()); + /** @ngdoc directive @name umbraco.directives.directive:umbContentGrid @restrict E @@ -6885,70 +8395,352 @@ Use this directive to generate a list of content items presented as a flexbox gr
  • $index: The item index
  • **/ - -(function() { - 'use strict'; - - function ContentGridDirective() { - - function link(scope, el, attr, ctrl) { - - scope.clickItem = function(item, $event, $index) { - if(scope.onClick) { - scope.onClick(item, $event, $index); + (function () { + 'use strict'; + function ContentGridDirective() { + function link(scope, el, attr, ctrl) { + scope.clickItem = function (item, $event, $index) { + if (scope.onClick) { + scope.onClick(item, $event, $index); + } + }; + scope.clickItemName = function (item, $event, $index) { + if (scope.onClickName) { + scope.onClickName(item, $event, $index); + } + }; } - }; + var directive = { + restrict: 'E', + replace: true, + templateUrl: 'views/components/umb-content-grid.html', + scope: { + content: '=', + contentProperties: '=', + onClick: '=', + onClickName: '=' + }, + link: link + }; + return directive; + } + angular.module('umbraco.directives').directive('umbContentGrid', ContentGridDirective); + }()); + /** +@ngdoc directive +@name umbraco.directives.directive:umbDateTimePicker +@restrict E +@scope - scope.clickItemName = function(item, $event, $index) { - if(scope.onClickName) { - scope.onClickName(item, $event, $index); +@description +Added in Umbraco version 7.6 +This directive is a wrapper of the bootstrap datetime picker version 3.1.3. Use it to render a date time picker. +For extra details about options and events take a look here: https://eonasdan.github.io/bootstrap-datetimepicker/ + +Use this directive to render a date time picker + +

    Markup example

    +
    +	
    + + + + +
    +
    + +

    Controller example

    +
    +	(function () {
    +		"use strict";
    +
    +		function Controller() {
    +
    +            var vm = this;
    +
    +            vm.date = "";
    +
    +            vm.config = {
    +                pickDate: true,
    +                pickTime: true,
    +                useSeconds: true,
    +                format: "YYYY-MM-DD HH:mm:ss",
    +                icons: {
    +                    time: "icon-time",
    +                    date: "icon-calendar",
    +                    up: "icon-chevron-up",
    +                    down: "icon-chevron-down"
    +                }
    +            };
    +
    +            vm.datePickerChange = datePickerChange;
    +            vm.datePickerError = datePickerError;
    +
    +            function datePickerChange(event) {
    +                // handle change
    +                if(event.date && event.date.isValid()) {
    +                    var date = event.date.format(vm.datePickerConfig.format);
    +                }
                 }
    -         };
     
    -      }
    +            function datePickerError(event) {
    +                // handle error
    +            }
     
    -      var directive = {
    -         restrict: 'E',
    -         replace: true,
    -         templateUrl: 'views/components/umb-content-grid.html',
    -         scope: {
    -            content: '=',
    -            contentProperties: "=",
    -            onClick: "=",
    -            onClickName: "="
    -         },
    -         link: link
    -      };
    +        }
     
    -      return directive;
    -   }
    +		angular.module("umbraco").controller("My.Controller", Controller);
     
    -   angular.module('umbraco.directives').directive('umbContentGrid', ContentGridDirective);
    +	})();
    +
    -})(); - -(function() { - 'use strict'; +@param {object} options (binding): Config object for the date picker. +@param {callback} onHide (callback): Hide callback. +@param {callback} onShow (callback): Show callback. +@param {callback} onChange (callback): Change callback. +@param {callback} onError (callback): Error callback. +@param {callback} onUpdate (callback): Update callback. +**/ + (function () { + 'use strict'; + function DateTimePickerDirective(assetsService) { + function link(scope, element, attrs, ctrl) { + scope.hasTranscludedContent = false; + function onInit() { + // check for transcluded content so we can hide the defualt markup + scope.hasTranscludedContent = element.find('.js-datePicker__transcluded-content')[0].children.length > 0; + // load css file for the date picker + assetsService.loadCss('lib/datetimepicker/bootstrap-datetimepicker.min.css', scope); + // load the js file for the date picker + assetsService.loadJs('lib/datetimepicker/bootstrap-datetimepicker.js', scope).then(function () { + // init date picker + initDatePicker(); + }); + } + function onHide(event) { + if (scope.onHide) { + scope.$apply(function () { + // callback + scope.onHide({ event: event }); + }); + } + } + function onShow() { + if (scope.onShow) { + scope.$apply(function () { + // callback + scope.onShow(); + }); + } + } + function onChange(event) { + if (scope.onChange && event.date && event.date.isValid()) { + scope.$apply(function () { + // callback + scope.onChange({ event: event }); + }); + } + } + function onError(event) { + if (scope.onError) { + scope.$apply(function () { + // callback + scope.onError({ event: event }); + }); + } + } + function onUpdate(event) { + if (scope.onUpdate) { + scope.$apply(function () { + // callback + scope.onUpdate({ event: event }); + }); + } + } + function initDatePicker() { + // Open the datepicker and add a changeDate eventlistener + element.datetimepicker(scope.options).on('dp.hide', onHide).on('dp.show', onShow).on('dp.change', onChange).on('dp.error', onError).on('dp.update', onUpdate); + } + onInit(); + } + var directive = { + restrict: 'E', + replace: true, + transclude: true, + templateUrl: 'views/components/umb-date-time-picker.html', + scope: { + options: '=', + onHide: '&', + onShow: '&', + onChange: '&', + onError: '&', + onUpdate: '&' + }, + link: link + }; + return directive; + } + angular.module('umbraco.directives').directive('umbDateTimePicker', DateTimePickerDirective); + }()); + (function () { + 'use strict'; + function UmbDisableFormValidation() { + var directive = { + restrict: 'A', + require: '?form', + link: function (scope, elm, attrs, ctrl) { + //override the $setValidity function of the form to disable validation + ctrl.$setValidity = function () { + }; + } + }; + return directive; + } + angular.module('umbraco.directives').directive('umbDisableFormValidation', UmbDisableFormValidation); + }()); + /** +@ngdoc directive +@name umbraco.directives.directive:umbDropdown +@restrict E +@scope - function UmbDisableFormValidation() { +@description +Added in versions 7.7.0: Use this component to render a dropdown menu. - var directive = { - restrict: 'A', - require: '?form', - link: function (scope, elm, attrs, ctrl) { - //override the $setValidity function of the form to disable validation - ctrl.$setValidity = function () { }; - } - }; +

    Markup example

    +
    +    
    - return directive; - } +
    - angular.module('umbraco.directives').directive('umbDisableFormValidation', UmbDisableFormValidation); + + -})(); - -/** + + + {{ item.name }} + + + +
    + +
    +
    + +

    Controller example

    +
    +    (function () {
    +        "use strict";
    +
    +        function Controller() {
    +
    +            var vm = this;
    +
    +            vm.dropdownOpen = false;
    +            vm.items = [
    +                { "name": "Item 1" },
    +                { "name": "Item 2" },
    +                { "name": "Item 3" }
    +            ];
    +
    +            vm.toggle = toggle;
    +            vm.close = close;
    +            vm.select = select;
    +
    +            function toggle() {
    +                vm.dropdownOpen = true;
    +            }
    +
    +            function close() {
    +                vm.dropdownOpen = false;
    +            }
    +
    +            function select(item) {
    +                // Do your magic here
    +            }
    +
    +        }
    +
    +        angular.module("umbraco").controller("MyDropdown.Controller", Controller);
    +    })();
    +
    + +

    Use in combination with

    +
      +
    • {@link umbraco.directives.directive:umbDropdownItem umbDropdownItem}
    • +
    • {@link umbraco.directives.directive:umbKeyboardList umbKeyboardList}
    • +
    + +@param {callback} onClose Callback when the dropdown menu closes. When you click outside or press esc. + +**/ + (function () { + 'use strict'; + function umbDropdown($document) { + function link(scope, element, attr, ctrl) { + scope.close = function () { + if (scope.onClose) { + scope.onClose(); + } + }; + // Handle keydown events + function keydown(event) { + // press escape + if (event.keyCode === 27) { + scope.onClose(); + } + } + // 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); + } + var directive = { + restrict: 'E', + replace: true, + transclude: true, + templateUrl: 'views/components/umb-dropdown.html', + scope: { onClose: '&' }, + link: link + }; + return directive; + } + angular.module('umbraco.directives').directive('umbDropdown', umbDropdown); + }()); + /** +@ngdoc directive +@name umbraco.directives.directive:umbDropdownItem +@restrict E + +@description +Added in versions 7.7.0: Use this directive to construct a dropdown item. See documentation for {@link umbraco.directives.directive:umbDropdown umbDropdown}. + +**/ + (function () { + 'use strict'; + function umbDropdownItem() { + var directive = { + restrict: 'E', + replace: true, + transclude: true, + templateUrl: 'views/components/umb-dropdown-item.html' + }; + return directive; + } + angular.module('umbraco.directives').directive('umbDropdownItem', umbDropdownItem); + }()); + /** @ngdoc directive @name umbraco.directives.directive:umbEmptyState @restrict E @@ -6973,31 +8765,24 @@ Use this directive to show an empty state message. @param {string=} size Set the size of the text ("small", "large"). @param {string=} position Set the position of the text ("center"). **/ - -(function() { - 'use strict'; - - function EmptyStateDirective() { - - var directive = { - restrict: 'E', - replace: true, - transclude: true, - templateUrl: 'views/components/umb-empty-state.html', - scope: { - size: '@', - position: '@' - } - }; - - return directive; - } - - angular.module('umbraco.directives').directive('umbEmptyState', EmptyStateDirective); - -})(); - -/** + (function () { + 'use strict'; + function EmptyStateDirective() { + var directive = { + restrict: 'E', + replace: true, + transclude: true, + templateUrl: 'views/components/umb-empty-state.html', + scope: { + size: '@', + position: '@' + } + }; + return directive; + } + angular.module('umbraco.directives').directive('umbEmptyState', EmptyStateDirective); + }()); + /** @ngdoc directive @name umbraco.directives.directive:umbFolderGrid @restrict E @@ -7076,875 +8861,783 @@ Use this directive to generate a list of folders presented as a flexbox grid.
  • $index: The folder index
  • **/ - -(function() { - 'use strict'; - - function FolderGridDirective() { - - function link(scope, el, attr, ctrl) { - - scope.clickFolder = function(folder, $event, $index) { - if(scope.onClick) { - scope.onClick(folder, $event, $index); - } - }; - - scope.clickFolderName = function(folder, $event, $index) { - if(scope.onClickName) { - scope.onClickName(folder, $event, $index); - $event.stopPropagation(); - } - }; - - } - - var directive = { - restrict: 'E', - replace: true, - templateUrl: 'views/components/umb-folder-grid.html', - scope: { - folders: '=', - onClick: "=", - onClickName: "=" - }, - link: link - }; - - return directive; - } - - angular.module('umbraco.directives').directive('umbFolderGrid', FolderGridDirective); - -})(); - -(function() { - 'use strict'; - - function GridSelector() { - - function link(scope, el, attr, ctrl) { - - var eventBindings = []; - scope.dialogModel = {}; - scope.showDialog = false; - scope.itemLabel = ""; - - // set default item name - if(!scope.itemName){ - scope.itemLabel = "item"; - } else { - scope.itemLabel = scope.itemName; - } - - scope.removeItem = function(selectedItem) { - var selectedItemIndex = scope.selectedItems.indexOf(selectedItem); - scope.selectedItems.splice(selectedItemIndex, 1); - }; - - scope.removeDefaultItem = function() { - - // it will be the last item so we can clear the array - scope.selectedItems = []; - - // remove as default item - scope.defaultItem = null; - - }; - - scope.openItemPicker = function($event){ - scope.dialogModel = { - view: "itempicker", - title: "Choose " + scope.itemLabel, - availableItems: scope.availableItems, - selectedItems: scope.selectedItems, - event: $event, - show: true, - submit: function(model) { - scope.selectedItems.push(model.selectedItem); - - // if no default item - set item as default - if(scope.defaultItem === null) { - scope.setAsDefaultItem(model.selectedItem); - } - - scope.dialogModel.show = false; - scope.dialogModel = null; + (function () { + 'use strict'; + function FolderGridDirective() { + function link(scope, el, attr, ctrl) { + scope.clickFolder = function (folder, $event, $index) { + if (scope.onClick) { + scope.onClick(folder, $event, $index); + $event.stopPropagation(); + } + }; + scope.clickFolderName = function (folder, $event, $index) { + if (scope.onClickName) { + scope.onClickName(folder, $event, $index); + $event.stopPropagation(); } }; - }; - - scope.setAsDefaultItem = function(selectedItem) { - - // clear default item - scope.defaultItem = {}; - - // set as default item - scope.defaultItem = selectedItem; - }; - - function updatePlaceholders() { - - // update default item - if(scope.defaultItem !== null && scope.defaultItem.placeholder) { - - scope.defaultItem.name = scope.name; - - if(scope.alias !== null && scope.alias !== undefined) { - scope.defaultItem.alias = scope.alias; - } - - } - - // update selected items - angular.forEach(scope.selectedItems, function(selectedItem) { - if(selectedItem.placeholder) { - - selectedItem.name = scope.name; - - if(scope.alias !== null && scope.alias !== undefined) { - selectedItem.alias = scope.alias; - } - - } - }); - - // update availableItems - angular.forEach(scope.availableItems, function(availableItem) { - if(availableItem.placeholder) { - - availableItem.name = scope.name; - - if(scope.alias !== null && scope.alias !== undefined) { - availableItem.alias = scope.alias; - } - - } - }); - } + var directive = { + restrict: 'E', + replace: true, + templateUrl: 'views/components/umb-folder-grid.html', + scope: { + folders: '=', + onClick: '=', + onClickName: '=' + }, + link: link + }; + return directive; + } + angular.module('umbraco.directives').directive('umbFolderGrid', FolderGridDirective); + }()); + /** +@ngdoc directive +@name umbraco.directives.directive:umbGenerateAlias +@restrict E +@scope - function activate() { +@description +Use this directive to generate a camelCased umbraco alias. +When the aliasFrom value is changed the directive will get a formatted alias from the server and update the alias model. If "enableLock" is set to true +the directive will use {@link umbraco.directives.directive:umbLockedField umbLockedField} to lock and unlock the alias. - // add watchers for updating placeholde name and alias - if(scope.updatePlaceholder) { - eventBindings.push(scope.$watch('name', function(newValue, oldValue){ - updatePlaceholders(); - })); +

    Markup example

    +
    +    
    - eventBindings.push(scope.$watch('alias', function(newValue, oldValue){ - updatePlaceholders(); - })); - } + - } + + - activate(); +
    +
    - // clean up - scope.$on('$destroy', function(){ +

    Controller example

    +
    +    (function () {
    +        "use strict";
     
    -              // clear watchers
    -              for(var e in eventBindings) {
    -                eventBindings[e]();
    -               }
    +        function Controller() {
     
    -            });
    +            var vm = this;
    +
    +            vm.name = "";
    +            vm.alias = "";
     
             }
     
    -        var directive = {
    +        angular.module("umbraco").controller("My.Controller", Controller);
    +    })();
    +
    + +@param {string} alias (binding): The model where the alias is bound. +@param {string} aliasFrom (binding): The model to generate the alias from. +@param {boolean=} enableLock (binding): Set to true to add a lock next to the alias from where it can be unlocked and changed. +**/ + angular.module('umbraco.directives').directive('umbGenerateAlias', function ($timeout, entityResource) { + return { restrict: 'E', + templateUrl: 'views/components/umb-generate-alias.html', replace: true, - templateUrl: 'views/components/umb-grid-selector.html', scope: { - name: "=", - alias: "=", - selectedItems: '=', - availableItems: "=", - defaultItem: "=", - itemName: "@", - updatePlaceholder: "=" + alias: '=', + aliasFrom: '=', + enableLock: '=?', + serverValidationField: '@' }, - link: link - }; - - return directive; - } - - angular.module('umbraco.directives').directive('umbGridSelector', GridSelector); - -})(); - -(function() { - 'use strict'; - - function GroupsBuilderDirective(contentTypeHelper, contentTypeResource, mediaTypeResource, dataTypeHelper, dataTypeResource, $filter, iconHelper, $q, $timeout, notificationsService, localizationService) { - - function link(scope, el, attr, ctrl) { - - var validationTranslated = ""; - var tabNoSortOrderTranslated = ""; - - scope.sortingMode = false; - scope.toolbar = []; - scope.sortableOptionsGroup = {}; - scope.sortableOptionsProperty = {}; - scope.sortingButtonKey = "general_reorder"; - - function activate() { - - setSortingOptions(); - - // set placeholder property on each group - if (scope.model.groups.length !== 0) { - angular.forEach(scope.model.groups, function(group) { - addInitProperty(group); - }); - } - - // add init tab - addInitGroup(scope.model.groups); - - activateFirstGroup(scope.model.groups); - - // localize texts - localizationService.localize("validation_validation").then(function(value) { - validationTranslated = value; - }); - - localizationService.localize("contentTypeEditor_tabHasNoSortOrder").then(function(value) { - tabNoSortOrderTranslated = value; - }); - } - - function setSortingOptions() { - - scope.sortableOptionsGroup = { - distance: 10, - tolerance: "pointer", - opacity: 0.7, - scroll: true, - cursor: "move", - placeholder: "umb-group-builder__group-sortable-placeholder", - zIndex: 6000, - handle: ".umb-group-builder__group-handle", - items: ".umb-group-builder__group-sortable", - start: function(e, ui) { - ui.placeholder.height(ui.item.height()); - }, - stop: function(e, ui) { - updateTabsSortOrder(); - }, - }; - - scope.sortableOptionsProperty = { - distance: 10, - tolerance: "pointer", - connectWith: ".umb-group-builder__properties", - opacity: 0.7, - scroll: true, - cursor: "move", - placeholder: "umb-group-builder__property_sortable-placeholder", - zIndex: 6000, - handle: ".umb-group-builder__property-handle", - items: ".umb-group-builder__property-sortable", - start: function(e, ui) { - ui.placeholder.height(ui.item.height()); - }, - stop: function(e, ui) { - updatePropertiesSortOrder(); - } - }; - - } - - function updateTabsSortOrder() { - - var first = true; - var prevSortOrder = 0; - - scope.model.groups.map(function(group){ - - var index = scope.model.groups.indexOf(group); - - if(group.tabState !== "init") { - - // set the first not inherited tab to sort order 0 - if(!group.inherited && first) { - - // set the first tab sort order to 0 if prev is 0 - if( prevSortOrder === 0 ) { - group.sortOrder = 0; - // when the first tab is inherited and sort order is not 0 - } else { - group.sortOrder = prevSortOrder + 1; - } - - first = false; - - } else if(!group.inherited && !first) { - - // find next group - var nextGroup = scope.model.groups[index + 1]; - - // if a groups is dropped in the middle of to groups with - // same sort order. Give it the dropped group same sort order - if( prevSortOrder === nextGroup.sortOrder ) { - group.sortOrder = prevSortOrder; - } else { - group.sortOrder = prevSortOrder + 1; + link: function (scope, element, attrs, ctrl) { + var eventBindings = []; + var bindWatcher = true; + var generateAliasTimeout = ''; + var updateAlias = false; + scope.locked = true; + scope.placeholderText = 'Enter alias...'; + function generateAlias(value) { + if (generateAliasTimeout) { + $timeout.cancel(generateAliasTimeout); + } + if (value !== undefined && value !== '' && value !== null) { + scope.alias = ''; + scope.placeholderText = 'Generating Alias...'; + generateAliasTimeout = $timeout(function () { + updateAlias = true; + entityResource.getSafeAlias(encodeURIComponent(value), true).then(function (safeAlias) { + if (updateAlias) { + scope.alias = safeAlias.alias; + } + }); + }, 500); + } else { + updateAlias = true; + scope.alias = ''; + scope.placeholderText = 'Enter alias...'; + } } - + // if alias gets unlocked - stop watching alias + eventBindings.push(scope.$watch('locked', function (newValue, oldValue) { + if (newValue === false) { + bindWatcher = false; + } + })); + // validate custom entered alias + eventBindings.push(scope.$watch('alias', function (newValue, oldValue) { + if (scope.alias === '' && bindWatcher === true || scope.alias === null && bindWatcher === true) { + // add watcher + eventBindings.push(scope.$watch('aliasFrom', function (newValue, oldValue) { + if (bindWatcher) { + generateAlias(newValue); + } + })); + } + })); + // clean up + scope.$on('$destroy', function () { + // unbind watchers + for (var e in eventBindings) { + eventBindings[e](); + } + }); } - - // store this tabs sort order as reference for the next - prevSortOrder = group.sortOrder; - - } - - }); - - } - - function filterAvailableCompositions(selectedContentType, selecting) { - - //selecting = true if the user has check the item, false if the user has unchecked the item - - var selectedContentTypeAliases = selecting ? - //the user has selected the item so add to the current list - _.union(scope.compositionsDialogModel.compositeContentTypes, [selectedContentType.alias]) : - //the user has unselected the item so remove from the current list - _.reject(scope.compositionsDialogModel.compositeContentTypes, function(i) { - return i === selectedContentType.alias; - }); - - //get the currently assigned property type aliases - ensure we pass these to the server side filer - var propAliasesExisting = _.filter(_.flatten(_.map(scope.model.groups, function(g) { - return _.map(g.properties, function(p) { - return p.alias; - }); - })), function (f) { - return f !== null && f !== undefined; - }); - - //use a different resource lookup depending on the content type type - var resourceLookup = scope.contentType === "documentType" ? contentTypeResource.getAvailableCompositeContentTypes : mediaTypeResource.getAvailableCompositeContentTypes; - - return resourceLookup(scope.model.id, selectedContentTypeAliases, propAliasesExisting).then(function (filteredAvailableCompositeTypes) { - _.each(scope.compositionsDialogModel.availableCompositeContentTypes, function (current) { - //reset first - current.allowed = true; - //see if this list item is found in the response (allowed) list - var found = _.find(filteredAvailableCompositeTypes, function (f) { - return current.contentType.alias === f.contentType.alias; - }); - - //allow if the item was found in the response (allowed) list - - // and ensure its set to allowed if it is currently checked, - // DO not allow if it's a locked content type. - current.allowed = scope.model.lockedCompositeContentTypes.indexOf(current.contentType.alias) === -1 && - (selectedContentTypeAliases.indexOf(current.contentType.alias) !== -1) || ((found !== null && found !== undefined) ? found.allowed : false); - - }); - }); - } - - function updatePropertiesSortOrder() { - - angular.forEach(scope.model.groups, function(group){ - if( group.tabState !== "init" ) { - group.properties = contentTypeHelper.updatePropertiesSortOrder(group.properties); - } - }); - - } - - function setupAvailableContentTypesModel(result) { - scope.compositionsDialogModel.availableCompositeContentTypes = result; - //iterate each one and set it up - _.each(scope.compositionsDialogModel.availableCompositeContentTypes, function (c) { - //enable it if it's part of the selected model - if (scope.compositionsDialogModel.compositeContentTypes.indexOf(c.contentType.alias) !== -1) { - c.allowed = true; - } - - //set the inherited flags - c.inherited = false; - if (scope.model.lockedCompositeContentTypes.indexOf(c.contentType.alias) > -1) { - c.inherited = true; - } - // convert icons for composite content types - iconHelper.formatContentTypeIcons([c.contentType]); - }); - } - - /* ---------- DELETE PROMT ---------- */ - - scope.togglePrompt = function (object) { - object.deletePrompt = !object.deletePrompt; - }; - - scope.hidePrompt = function (object) { - object.deletePrompt = false; - }; - - /* ---------- TOOLBAR ---------- */ - - scope.toggleSortingMode = function(tool) { - - if (scope.sortingMode === true) { - - var sortOrderMissing = false; - - for (var i = 0; i < scope.model.groups.length; i++) { - var group = scope.model.groups[i]; - if (group.tabState !== "init" && group.sortOrder === undefined) { - sortOrderMissing = true; - group.showSortOrderMissing = true; - notificationsService.error(validationTranslated + ": " + group.name + " " + tabNoSortOrderTranslated); - } - } - - if (!sortOrderMissing) { - scope.sortingMode = false; - scope.sortingButtonKey = "general_reorder"; - } - - } else { - - scope.sortingMode = true; - scope.sortingButtonKey = "general_reorderDone"; - - } - - }; - - scope.openCompositionsDialog = function() { - - scope.compositionsDialogModel = { - title: "Compositions", - contentType: scope.model, - compositeContentTypes: scope.model.compositeContentTypes, - view: "views/common/overlays/contenttypeeditor/compositions/compositions.html", - confirmSubmit: { - title: "Warning", - description: "Removing a composition will delete all the associated property data. Once you save the document type there's no way back, are you sure?", - checkboxLabel: "I know what I'm doing", - enable: true - }, - submit: function(model, oldModel, confirmed) { - - var compositionRemoved = false; - - // check if any compositions has been removed - for(var i = 0; oldModel.compositeContentTypes.length > i; i++) { - - var oldComposition = oldModel.compositeContentTypes[i]; - - if(_.contains(model.compositeContentTypes, oldComposition) === false) { - compositionRemoved = true; - } - - } - - // show overlay confirm box if compositions has been removed. - if(compositionRemoved && confirmed === false) { - - scope.compositionsDialogModel.confirmSubmit.show = true; - - // submit overlay if no compositions has been removed - // or the action has been confirmed + }; + }); + (function () { + 'use strict'; + function GridSelector($location) { + function link(scope, el, attr, ctrl) { + var eventBindings = []; + scope.dialogModel = {}; + scope.showDialog = false; + scope.itemLabel = ''; + // set default item name + if (!scope.itemName) { + scope.itemLabel = 'item'; } else { - - // make sure that all tabs has an init property - if (scope.model.groups.length !== 0) { - angular.forEach(scope.model.groups, function(group) { - addInitProperty(group); - }); - } - - // remove overlay - scope.compositionsDialogModel.show = false; - scope.compositionsDialogModel = null; + scope.itemLabel = scope.itemName; } - - }, - close: function(oldModel) { - - // reset composition changes - scope.model.groups = oldModel.contentType.groups; - scope.model.compositeContentTypes = oldModel.contentType.compositeContentTypes; - - // remove overlay - scope.compositionsDialogModel.show = false; - scope.compositionsDialogModel = null; - - }, - selectCompositeContentType: function (selectedContentType) { - - //first check if this is a new selection - we need to store this value here before any further digests/async - // because after that the scope.model.compositeContentTypes will be populated with the selected value. - var newSelection = scope.model.compositeContentTypes.indexOf(selectedContentType.alias) === -1; - - if (newSelection) { - //merge composition with content type - + scope.removeItem = function (selectedItem) { + var selectedItemIndex = scope.selectedItems.indexOf(selectedItem); + scope.selectedItems.splice(selectedItemIndex, 1); + }; + scope.removeDefaultItem = function () { + // it will be the last item so we can clear the array + scope.selectedItems = []; + // remove as default item + scope.defaultItem = null; + }; + scope.openItemPicker = function ($event) { + scope.dialogModel = { + view: 'itempicker', + title: 'Choose ' + scope.itemLabel, + availableItems: scope.availableItems, + selectedItems: scope.selectedItems, + event: $event, + show: true, + submit: function (model) { + scope.selectedItems.push(model.selectedItem); + // if no default item - set item as default + if (scope.defaultItem === null) { + scope.setAsDefaultItem(model.selectedItem); + } + scope.dialogModel.show = false; + scope.dialogModel = null; + } + }; + }; + scope.openTemplate = function (selectedItem) { + var url = '/settings/templates/edit/' + selectedItem.id; + $location.url(url); + }; + scope.setAsDefaultItem = function (selectedItem) { + // clear default item + scope.defaultItem = {}; + // set as default item + scope.defaultItem = selectedItem; + }; + function updatePlaceholders() { + // update default item + if (scope.defaultItem !== null && scope.defaultItem.placeholder) { + scope.defaultItem.name = scope.name; + if (scope.alias !== null && scope.alias !== undefined) { + scope.defaultItem.alias = scope.alias; + } + } + // update selected items + angular.forEach(scope.selectedItems, function (selectedItem) { + if (selectedItem.placeholder) { + selectedItem.name = scope.name; + if (scope.alias !== null && scope.alias !== undefined) { + selectedItem.alias = scope.alias; + } + } + }); + // update availableItems + angular.forEach(scope.availableItems, function (availableItem) { + if (availableItem.placeholder) { + availableItem.name = scope.name; + if (scope.alias !== null && scope.alias !== undefined) { + availableItem.alias = scope.alias; + } + } + }); + } + function activate() { + // add watchers for updating placeholde name and alias + if (scope.updatePlaceholder) { + eventBindings.push(scope.$watch('name', function (newValue, oldValue) { + updatePlaceholders(); + })); + eventBindings.push(scope.$watch('alias', function (newValue, oldValue) { + updatePlaceholders(); + })); + } + } + activate(); + // clean up + scope.$on('$destroy', function () { + // clear watchers + for (var e in eventBindings) { + eventBindings[e](); + } + }); + } + var directive = { + restrict: 'E', + replace: true, + templateUrl: 'views/components/umb-grid-selector.html', + scope: { + name: '=', + alias: '=', + selectedItems: '=', + availableItems: '=', + defaultItem: '=', + itemName: '@', + updatePlaceholder: '=' + }, + link: link + }; + return directive; + } + angular.module('umbraco.directives').directive('umbGridSelector', GridSelector); + }()); + (function () { + 'use strict'; + function GroupsBuilderDirective(contentTypeHelper, contentTypeResource, mediaTypeResource, dataTypeHelper, dataTypeResource, $filter, iconHelper, $q, $timeout, notificationsService, localizationService) { + function link(scope, el, attr, ctrl) { + var validationTranslated = ''; + var tabNoSortOrderTranslated = ''; + scope.sortingMode = false; + scope.toolbar = []; + scope.sortableOptionsGroup = {}; + scope.sortableOptionsProperty = {}; + scope.sortingButtonKey = 'general_reorder'; + function activate() { + setSortingOptions(); + // set placeholder property on each group + if (scope.model.groups.length !== 0) { + angular.forEach(scope.model.groups, function (group) { + addInitProperty(group); + }); + } + // add init tab + addInitGroup(scope.model.groups); + activateFirstGroup(scope.model.groups); + // localize texts + localizationService.localize('validation_validation').then(function (value) { + validationTranslated = value; + }); + localizationService.localize('contentTypeEditor_tabHasNoSortOrder').then(function (value) { + tabNoSortOrderTranslated = value; + }); + } + function setSortingOptions() { + scope.sortableOptionsGroup = { + distance: 10, + tolerance: 'pointer', + opacity: 0.7, + scroll: true, + cursor: 'move', + placeholder: 'umb-group-builder__group-sortable-placeholder', + zIndex: 6000, + handle: '.umb-group-builder__group-handle', + items: '.umb-group-builder__group-sortable', + start: function (e, ui) { + ui.placeholder.height(ui.item.height()); + }, + stop: function (e, ui) { + updateTabsSortOrder(); + } + }; + scope.sortableOptionsProperty = { + distance: 10, + tolerance: 'pointer', + connectWith: '.umb-group-builder__properties', + opacity: 0.7, + scroll: true, + cursor: 'move', + placeholder: 'umb-group-builder__property_sortable-placeholder', + zIndex: 6000, + handle: '.umb-group-builder__property-handle', + items: '.umb-group-builder__property-sortable', + start: function (e, ui) { + ui.placeholder.height(ui.item.height()); + }, + stop: function (e, ui) { + updatePropertiesSortOrder(); + } + }; + } + function updateTabsSortOrder() { + var first = true; + var prevSortOrder = 0; + scope.model.groups.map(function (group) { + var index = scope.model.groups.indexOf(group); + if (group.tabState !== 'init') { + // set the first not inherited tab to sort order 0 + if (!group.inherited && first) { + // set the first tab sort order to 0 if prev is 0 + if (prevSortOrder === 0) { + group.sortOrder = 0; // when the first tab is inherited and sort order is not 0 + } else { + group.sortOrder = prevSortOrder + 1; + } + first = false; + } else if (!group.inherited && !first) { + // find next group + var nextGroup = scope.model.groups[index + 1]; + // if a groups is dropped in the middle of to groups with + // same sort order. Give it the dropped group same sort order + if (prevSortOrder === nextGroup.sortOrder) { + group.sortOrder = prevSortOrder; + } else { + group.sortOrder = prevSortOrder + 1; + } + } + // store this tabs sort order as reference for the next + prevSortOrder = group.sortOrder; + } + }); + } + function filterAvailableCompositions(selectedContentType, selecting) { + //selecting = true if the user has check the item, false if the user has unchecked the item + var selectedContentTypeAliases = selecting ? //the user has selected the item so add to the current list + _.union(scope.compositionsDialogModel.compositeContentTypes, [selectedContentType.alias]) : //the user has unselected the item so remove from the current list + _.reject(scope.compositionsDialogModel.compositeContentTypes, function (i) { + return i === selectedContentType.alias; + }); + //get the currently assigned property type aliases - ensure we pass these to the server side filer + var propAliasesExisting = _.filter(_.flatten(_.map(scope.model.groups, function (g) { + return _.map(g.properties, function (p) { + return p.alias; + }); + })), function (f) { + return f !== null && f !== undefined; + }); //use a different resource lookup depending on the content type type - var resourceLookup = scope.contentType === "documentType" ? contentTypeResource.getById : mediaTypeResource.getById; - - resourceLookup(selectedContentType.id).then(function (composition) { - //based on the above filtering we shouldn't be able to select an invalid one, but let's be safe and - // double check here. - var overlappingAliases = contentTypeHelper.validateAddingComposition(scope.model, composition); - if (overlappingAliases.length > 0) { - //this will create an invalid composition, need to uncheck it - scope.compositionsDialogModel.compositeContentTypes.splice( - scope.compositionsDialogModel.compositeContentTypes.indexOf(composition.alias), 1); - //dissallow this until something else is unchecked - selectedContentType.allowed = false; - } - else { - contentTypeHelper.mergeCompositeContentType(scope.model, composition); - } - - //based on the selection, we need to filter the available composite types list - filterAvailableCompositions(selectedContentType, newSelection).then(function () { - //TODO: Here we could probably re-enable selection if we previously showed a throbber or something + var resourceLookup = scope.contentType === 'documentType' ? contentTypeResource.getAvailableCompositeContentTypes : mediaTypeResource.getAvailableCompositeContentTypes; + return resourceLookup(scope.model.id, selectedContentTypeAliases, propAliasesExisting).then(function (filteredAvailableCompositeTypes) { + _.each(scope.compositionsDialogModel.availableCompositeContentTypes, function (current) { + //reset first + current.allowed = true; + //see if this list item is found in the response (allowed) list + var found = _.find(filteredAvailableCompositeTypes, function (f) { + return current.contentType.alias === f.contentType.alias; + }); + //allow if the item was found in the response (allowed) list - + // and ensure its set to allowed if it is currently checked, + // DO not allow if it's a locked content type. + current.allowed = scope.model.lockedCompositeContentTypes.indexOf(current.contentType.alias) === -1 && selectedContentTypeAliases.indexOf(current.contentType.alias) !== -1 || (found !== null && found !== undefined ? found.allowed : false); }); }); } - else { - // split composition from content type - contentTypeHelper.splitCompositeContentType(scope.model, selectedContentType); - - //based on the selection, we need to filter the available composite types list - filterAvailableCompositions(selectedContentType, newSelection).then(function () { - //TODO: Here we could probably re-enable selection if we previously showed a throbber or something + function updatePropertiesSortOrder() { + angular.forEach(scope.model.groups, function (group) { + if (group.tabState !== 'init') { + group.properties = contentTypeHelper.updatePropertiesSortOrder(group.properties); + } }); } - + function setupAvailableContentTypesModel(result) { + scope.compositionsDialogModel.availableCompositeContentTypes = result; + //iterate each one and set it up + _.each(scope.compositionsDialogModel.availableCompositeContentTypes, function (c) { + //enable it if it's part of the selected model + if (scope.compositionsDialogModel.compositeContentTypes.indexOf(c.contentType.alias) !== -1) { + c.allowed = true; + } + //set the inherited flags + c.inherited = false; + if (scope.model.lockedCompositeContentTypes.indexOf(c.contentType.alias) > -1) { + c.inherited = true; + } + // convert icons for composite content types + iconHelper.formatContentTypeIcons([c.contentType]); + }); + } + /* ---------- DELETE PROMT ---------- */ + scope.togglePrompt = function (object) { + object.deletePrompt = !object.deletePrompt; + }; + scope.hidePrompt = function (object) { + object.deletePrompt = false; + }; + /* ---------- TOOLBAR ---------- */ + scope.toggleSortingMode = function (tool) { + if (scope.sortingMode === true) { + var sortOrderMissing = false; + for (var i = 0; i < scope.model.groups.length; i++) { + var group = scope.model.groups[i]; + if (group.tabState !== 'init' && group.sortOrder === undefined) { + sortOrderMissing = true; + group.showSortOrderMissing = true; + notificationsService.error(validationTranslated + ': ' + group.name + ' ' + tabNoSortOrderTranslated); + } + } + if (!sortOrderMissing) { + scope.sortingMode = false; + scope.sortingButtonKey = 'general_reorder'; + } + } else { + scope.sortingMode = true; + scope.sortingButtonKey = 'general_reorderDone'; + } + }; + scope.openCompositionsDialog = function () { + scope.compositionsDialogModel = { + title: 'Compositions', + contentType: scope.model, + compositeContentTypes: scope.model.compositeContentTypes, + view: 'views/common/overlays/contenttypeeditor/compositions/compositions.html', + confirmSubmit: { + title: 'Warning', + description: 'Removing a composition will delete all the associated property data. Once you save the document type there\'s no way back, are you sure?', + checkboxLabel: 'I know what I\'m doing', + enable: true + }, + submit: function (model, oldModel, confirmed) { + var compositionRemoved = false; + // check if any compositions has been removed + for (var i = 0; oldModel.compositeContentTypes.length > i; i++) { + var oldComposition = oldModel.compositeContentTypes[i]; + if (_.contains(model.compositeContentTypes, oldComposition) === false) { + compositionRemoved = true; + } + } + // show overlay confirm box if compositions has been removed. + if (compositionRemoved && confirmed === false) { + scope.compositionsDialogModel.confirmSubmit.show = true; // submit overlay if no compositions has been removed + // or the action has been confirmed + } else { + // make sure that all tabs has an init property + if (scope.model.groups.length !== 0) { + angular.forEach(scope.model.groups, function (group) { + addInitProperty(group); + }); + } + // remove overlay + scope.compositionsDialogModel.show = false; + scope.compositionsDialogModel = null; + } + }, + close: function (oldModel) { + // reset composition changes + scope.model.groups = oldModel.contentType.groups; + scope.model.compositeContentTypes = oldModel.contentType.compositeContentTypes; + // remove overlay + scope.compositionsDialogModel.show = false; + scope.compositionsDialogModel = null; + }, + selectCompositeContentType: function (selectedContentType) { + //first check if this is a new selection - we need to store this value here before any further digests/async + // because after that the scope.model.compositeContentTypes will be populated with the selected value. + var newSelection = scope.model.compositeContentTypes.indexOf(selectedContentType.alias) === -1; + if (newSelection) { + //merge composition with content type + //use a different resource lookup depending on the content type type + var resourceLookup = scope.contentType === 'documentType' ? contentTypeResource.getById : mediaTypeResource.getById; + resourceLookup(selectedContentType.id).then(function (composition) { + //based on the above filtering we shouldn't be able to select an invalid one, but let's be safe and + // double check here. + var overlappingAliases = contentTypeHelper.validateAddingComposition(scope.model, composition); + if (overlappingAliases.length > 0) { + //this will create an invalid composition, need to uncheck it + scope.compositionsDialogModel.compositeContentTypes.splice(scope.compositionsDialogModel.compositeContentTypes.indexOf(composition.alias), 1); + //dissallow this until something else is unchecked + selectedContentType.allowed = false; + } else { + contentTypeHelper.mergeCompositeContentType(scope.model, composition); + } + //based on the selection, we need to filter the available composite types list + filterAvailableCompositions(selectedContentType, newSelection).then(function () { + }); + }); + } else { + // split composition from content type + contentTypeHelper.splitCompositeContentType(scope.model, selectedContentType); + //based on the selection, we need to filter the available composite types list + filterAvailableCompositions(selectedContentType, newSelection).then(function () { + }); + } + } + }; + //select which resource methods to use, eg document Type or Media Type versions + var availableContentTypeResource = scope.contentType === 'documentType' ? contentTypeResource.getAvailableCompositeContentTypes : mediaTypeResource.getAvailableCompositeContentTypes; + var whereUsedContentTypeResource = scope.contentType === 'documentType' ? contentTypeResource.getWhereCompositionIsUsedInContentTypes : mediaTypeResource.getWhereCompositionIsUsedInContentTypes; + var countContentTypeResource = scope.contentType === 'documentType' ? contentTypeResource.getCount : mediaTypeResource.getCount; + //get the currently assigned property type aliases - ensure we pass these to the server side filer + var propAliasesExisting = _.filter(_.flatten(_.map(scope.model.groups, function (g) { + return _.map(g.properties, function (p) { + return p.alias; + }); + })), function (f) { + return f !== null && f !== undefined; + }); + $q.all([ + //get available composite types + availableContentTypeResource(scope.model.id, [], propAliasesExisting).then(function (result) { + setupAvailableContentTypesModel(result); + }), + //get where used document types + whereUsedContentTypeResource(scope.model.id).then(function (whereUsed) { + //pass to the dialog model the content type eg documentType or mediaType + scope.compositionsDialogModel.section = scope.contentType; + //pass the list of 'where used' document types + scope.compositionsDialogModel.whereCompositionUsed = whereUsed; + }), + //get content type count + countContentTypeResource().then(function (result) { + scope.compositionsDialogModel.totalContentTypes = parseInt(result, 10); + }) + ]).then(function () { + //resolves when both other promises are done, now show it + scope.compositionsDialogModel.show = true; + }); + }; + /* ---------- GROUPS ---------- */ + scope.addGroup = function (group) { + // set group sort order + var index = scope.model.groups.indexOf(group); + var prevGroup = scope.model.groups[index - 1]; + if (index > 0) { + // set index to 1 higher than the previous groups sort order + group.sortOrder = prevGroup.sortOrder + 1; + } else { + // first group - sort order will be 0 + group.sortOrder = 0; + } + // activate group + scope.activateGroup(group); + }; + scope.activateGroup = function (selectedGroup) { + // set all other groups that are inactive to active + angular.forEach(scope.model.groups, function (group) { + // skip init tab + if (group.tabState !== 'init') { + group.tabState = 'inActive'; + } + }); + selectedGroup.tabState = 'active'; + }; + scope.removeGroup = function (groupIndex) { + scope.model.groups.splice(groupIndex, 1); + addInitGroup(scope.model.groups); + }; + scope.updateGroupTitle = function (group) { + if (group.properties.length === 0) { + addInitProperty(group); + } + }; + scope.changeSortOrderValue = function (group) { + if (group.sortOrder !== undefined) { + group.showSortOrderMissing = false; + } + scope.model.groups = $filter('orderBy')(scope.model.groups, 'sortOrder'); + }; + function addInitGroup(groups) { + // check i init tab already exists + var addGroup = true; + angular.forEach(groups, function (group) { + if (group.tabState === 'init') { + addGroup = false; + } + }); + if (addGroup) { + groups.push({ + properties: [], + parentTabContentTypes: [], + parentTabContentTypeNames: [], + name: '', + tabState: 'init' + }); + } + return groups; + } + function activateFirstGroup(groups) { + if (groups && groups.length > 0) { + var firstGroup = groups[0]; + if (!firstGroup.tabState || firstGroup.tabState === 'inActive') { + firstGroup.tabState = 'active'; + } + } + } + /* ---------- PROPERTIES ---------- */ + scope.addProperty = function (property, group) { + // set property sort order + var index = group.properties.indexOf(property); + var prevProperty = group.properties[index - 1]; + if (index > 0) { + // set index to 1 higher than the previous property sort order + property.sortOrder = prevProperty.sortOrder + 1; + } else { + // first property - sort order will be 0 + property.sortOrder = 0; + } + // open property settings dialog + scope.editPropertyTypeSettings(property, group); + }; + scope.editPropertyTypeSettings = function (property, group) { + if (!property.inherited) { + scope.propertySettingsDialogModel = {}; + scope.propertySettingsDialogModel.title = 'Property settings'; + scope.propertySettingsDialogModel.property = property; + scope.propertySettingsDialogModel.contentType = scope.contentType; + scope.propertySettingsDialogModel.contentTypeName = scope.model.name; + scope.propertySettingsDialogModel.view = 'views/common/overlays/contenttypeeditor/propertysettings/propertysettings.html'; + scope.propertySettingsDialogModel.show = true; + // set state to active to access the preview + property.propertyState = 'active'; + // set property states + property.dialogIsOpen = true; + scope.propertySettingsDialogModel.submit = function (model) { + property.inherited = false; + property.dialogIsOpen = false; + // update existing data types + if (model.updateSameDataTypes) { + updateSameDataTypes(property); + } + // remove dialog + scope.propertySettingsDialogModel.show = false; + scope.propertySettingsDialogModel = null; + // push new init property to group + addInitProperty(group); + // set focus on init property + var numberOfProperties = group.properties.length; + group.properties[numberOfProperties - 1].focus = true; + // push new init tab to the scope + addInitGroup(scope.model.groups); + }; + scope.propertySettingsDialogModel.close = function (oldModel) { + // reset all property changes + property.label = oldModel.property.label; + property.alias = oldModel.property.alias; + property.description = oldModel.property.description; + property.config = oldModel.property.config; + property.editor = oldModel.property.editor; + property.view = oldModel.property.view; + property.dataTypeId = oldModel.property.dataTypeId; + property.dataTypeIcon = oldModel.property.dataTypeIcon; + property.dataTypeName = oldModel.property.dataTypeName; + property.validation.mandatory = oldModel.property.validation.mandatory; + property.validation.pattern = oldModel.property.validation.pattern; + property.showOnMemberProfile = oldModel.property.showOnMemberProfile; + property.memberCanEdit = oldModel.property.memberCanEdit; + property.isSensitiveValue = oldModel.property.isSensitiveValue; + // because we set state to active, to show a preview, we have to check if has been filled out + // label is required so if it is not filled we know it is a placeholder + if (oldModel.property.editor === undefined || oldModel.property.editor === null || oldModel.property.editor === '') { + property.propertyState = 'init'; + } else { + property.propertyState = oldModel.property.propertyState; + } + // remove dialog + scope.propertySettingsDialogModel.show = false; + scope.propertySettingsDialogModel = null; + }; + } + }; + scope.deleteProperty = function (tab, propertyIndex) { + // remove property + tab.properties.splice(propertyIndex, 1); + // if the last property in group is an placeholder - remove add new tab placeholder + if (tab.properties.length === 1 && tab.properties[0].propertyState === 'init') { + angular.forEach(scope.model.groups, function (group, index, groups) { + if (group.tabState === 'init') { + groups.splice(index, 1); + } + }); + } + }; + function addInitProperty(group) { + var addInitPropertyBool = true; + var initProperty = { + label: null, + alias: null, + propertyState: 'init', + validation: { + mandatory: false, + pattern: null + } + }; + // check if there already is an init property + angular.forEach(group.properties, function (property) { + if (property.propertyState === 'init') { + addInitPropertyBool = false; + } + }); + if (addInitPropertyBool) { + group.properties.push(initProperty); + } + return group; + } + function updateSameDataTypes(newProperty) { + // find each property + angular.forEach(scope.model.groups, function (group) { + angular.forEach(group.properties, function (property) { + if (property.dataTypeId === newProperty.dataTypeId) { + // update property data + property.config = newProperty.config; + property.editor = newProperty.editor; + property.view = newProperty.view; + property.dataTypeId = newProperty.dataTypeId; + property.dataTypeIcon = newProperty.dataTypeIcon; + property.dataTypeName = newProperty.dataTypeName; + } + }); + }); + } + var unbindModelWatcher = scope.$watch('model', function (newValue, oldValue) { + if (newValue !== undefined && newValue.groups !== undefined) { + activate(); + } + }); + // clean up + scope.$on('$destroy', function () { + unbindModelWatcher(); + }); } - }; - - var availableContentTypeResource = scope.contentType === "documentType" ? contentTypeResource.getAvailableCompositeContentTypes : mediaTypeResource.getAvailableCompositeContentTypes; - var countContentTypeResource = scope.contentType === "documentType" ? contentTypeResource.getCount : mediaTypeResource.getCount; - - //get the currently assigned property type aliases - ensure we pass these to the server side filer - var propAliasesExisting = _.filter(_.flatten(_.map(scope.model.groups, function(g) { - return _.map(g.properties, function(p) { - return p.alias; - }); - })), function(f) { - return f !== null && f !== undefined; - }); - $q.all([ - //get available composite types - availableContentTypeResource(scope.model.id, [], propAliasesExisting).then(function (result) { - setupAvailableContentTypesModel(result); - }), - //get content type count - countContentTypeResource().then(function(result) { - scope.compositionsDialogModel.totalContentTypes = parseInt(result, 10); - }) - ]).then(function() { - //resolves when both other promises are done, now show it - scope.compositionsDialogModel.show = true; - }); - - }; - - - /* ---------- GROUPS ---------- */ - - scope.addGroup = function(group) { - - // set group sort order - var index = scope.model.groups.indexOf(group); - var prevGroup = scope.model.groups[index - 1]; - - if( index > 0) { - // set index to 1 higher than the previous groups sort order - group.sortOrder = prevGroup.sortOrder + 1; - - } else { - // first group - sort order will be 0 - group.sortOrder = 0; + var directive = { + restrict: 'E', + replace: true, + templateUrl: 'views/components/umb-groups-builder.html', + scope: { + model: '=', + compositions: '=', + sorting: '=', + contentType: '@' + }, + link: link + }; + return directive; } - - // activate group - scope.activateGroup(group); - - }; - - scope.activateGroup = function(selectedGroup) { - - // set all other groups that are inactive to active - angular.forEach(scope.model.groups, function(group) { - // skip init tab - if (group.tabState !== "init") { - group.tabState = "inActive"; - } - }); - - selectedGroup.tabState = "active"; - - }; - - scope.removeGroup = function(groupIndex) { - scope.model.groups.splice(groupIndex, 1); - addInitGroup(scope.model.groups); - }; - - scope.updateGroupTitle = function(group) { - if (group.properties.length === 0) { - addInitProperty(group); - } - }; - - scope.changeSortOrderValue = function(group) { - - if (group.sortOrder !== undefined) { - group.showSortOrderMissing = false; - } - scope.model.groups = $filter('orderBy')(scope.model.groups, 'sortOrder'); - }; - - function addInitGroup(groups) { - - // check i init tab already exists - var addGroup = true; - - angular.forEach(groups, function(group) { - if (group.tabState === "init") { - addGroup = false; - } - }); - - if (addGroup) { - groups.push({ - properties: [], - parentTabContentTypes: [], - parentTabContentTypeNames: [], - name: "", - tabState: "init" - }); - } - - return groups; - } - - function activateFirstGroup(groups) { - if (groups && groups.length > 0) { - var firstGroup = groups[0]; - if(!firstGroup.tabState || firstGroup.tabState === "inActive") { - firstGroup.tabState = "active"; - } - } - } - - /* ---------- PROPERTIES ---------- */ - - scope.addProperty = function(property, group) { - - // set property sort order - var index = group.properties.indexOf(property); - var prevProperty = group.properties[index - 1]; - - if( index > 0) { - // set index to 1 higher than the previous property sort order - property.sortOrder = prevProperty.sortOrder + 1; - - } else { - // first property - sort order will be 0 - property.sortOrder = 0; - } - - // open property settings dialog - scope.editPropertyTypeSettings(property, group); - - }; - - scope.editPropertyTypeSettings = function(property, group) { - - if (!property.inherited && !property.locked) { - - scope.propertySettingsDialogModel = {}; - scope.propertySettingsDialogModel.title = "Property settings"; - scope.propertySettingsDialogModel.property = property; - scope.propertySettingsDialogModel.contentType = scope.contentType; - scope.propertySettingsDialogModel.contentTypeName = scope.model.name; - scope.propertySettingsDialogModel.view = "views/common/overlays/contenttypeeditor/propertysettings/propertysettings.html"; - scope.propertySettingsDialogModel.show = true; - - // set state to active to access the preview - property.propertyState = "active"; - - // set property states - property.dialogIsOpen = true; - - scope.propertySettingsDialogModel.submit = function(model) { - - property.inherited = false; - property.dialogIsOpen = false; - - // update existing data types - if(model.updateSameDataTypes) { - updateSameDataTypes(property); - } - - // remove dialog - scope.propertySettingsDialogModel.show = false; - scope.propertySettingsDialogModel = null; - - // push new init property to group - addInitProperty(group); - - // set focus on init property - var numberOfProperties = group.properties.length; - group.properties[numberOfProperties - 1].focus = true; - - // push new init tab to the scope - addInitGroup(scope.model.groups); - - }; - - scope.propertySettingsDialogModel.close = function(oldModel) { - - // reset all property changes - property.label = oldModel.property.label; - property.alias = oldModel.property.alias; - property.description = oldModel.property.description; - property.config = oldModel.property.config; - property.editor = oldModel.property.editor; - property.view = oldModel.property.view; - property.dataTypeId = oldModel.property.dataTypeId; - property.dataTypeIcon = oldModel.property.dataTypeIcon; - property.dataTypeName = oldModel.property.dataTypeName; - property.validation.mandatory = oldModel.property.validation.mandatory; - property.validation.pattern = oldModel.property.validation.pattern; - property.showOnMemberProfile = oldModel.property.showOnMemberProfile; - property.memberCanEdit = oldModel.property.memberCanEdit; - - // because we set state to active, to show a preview, we have to check if has been filled out - // label is required so if it is not filled we know it is a placeholder - if(oldModel.property.editor === undefined || oldModel.property.editor === null || oldModel.property.editor === "") { - property.propertyState = "init"; - } else { - property.propertyState = oldModel.property.propertyState; - } - - // remove dialog - scope.propertySettingsDialogModel.show = false; - scope.propertySettingsDialogModel = null; - - }; - - } - }; - - scope.deleteProperty = function(tab, propertyIndex) { - - // remove property - tab.properties.splice(propertyIndex, 1); - - // if the last property in group is an placeholder - remove add new tab placeholder - if(tab.properties.length === 1 && tab.properties[0].propertyState === "init") { - - angular.forEach(scope.model.groups, function(group, index, groups){ - if(group.tabState === 'init') { - groups.splice(index, 1); - } - }); - - } - - }; - - function addInitProperty(group) { - - var addInitPropertyBool = true; - var initProperty = { - label: null, - alias: null, - propertyState: "init", - validation: { - mandatory: false, - pattern: null - } - }; - - // check if there already is an init property - angular.forEach(group.properties, function(property) { - if (property.propertyState === "init") { - addInitPropertyBool = false; - } - }); - - if (addInitPropertyBool) { - group.properties.push(initProperty); - } - - return group; - } - - function updateSameDataTypes(newProperty) { - - // find each property - angular.forEach(scope.model.groups, function(group){ - angular.forEach(group.properties, function(property){ - - if(property.dataTypeId === newProperty.dataTypeId) { - - // update property data - property.config = newProperty.config; - property.editor = newProperty.editor; - property.view = newProperty.view; - property.dataTypeId = newProperty.dataTypeId; - property.dataTypeIcon = newProperty.dataTypeIcon; - property.dataTypeName = newProperty.dataTypeName; - - } - - }); - }); - } - - - var unbindModelWatcher = scope.$watch('model', function(newValue, oldValue) { - if (newValue !== undefined && newValue.groups !== undefined) { - activate(); - } - }); - - // clean up - scope.$on('$destroy', function(){ - unbindModelWatcher(); - }); - - } - - var directive = { - restrict: "E", - replace: true, - templateUrl: "views/components/umb-groups-builder.html", - scope: { - model: "=", - compositions: "=", - sorting: "=", - contentType: "@" - }, - link: link - }; - - return directive; - } - - angular.module('umbraco.directives').directive('umbGroupsBuilder', GroupsBuilderDirective); - -})(); - -/** + angular.module('umbraco.directives').directive('umbGroupsBuilder', GroupsBuilderDirective); + }()); + /** @ngdoc directive @name umbraco.directives.directive:umbkeyboardShortcutsOverview @restrict E @@ -8052,40 +9745,61 @@ When this combination is hit an overview is opened with shortcuts based on the m @param {object} model keyboard shortcut model. See description and example above. **/ - -(function() { - 'use strict'; - - function KeyboardShortcutsOverviewDirective() { - - function link(scope, el, attr, ctrl) { - - scope.shortcutOverlay = false; - - scope.toggleShortcutsOverlay = function() { - scope.shortcutOverlay = !scope.shortcutOverlay; - }; - - } - - var directive = { - restrict: 'E', - replace: true, - templateUrl: 'views/components/umb-keyboard-shortcuts-overview.html', - link: link, - scope: { - model: "=" - } - }; - - return directive; - } - - angular.module('umbraco.directives').directive('umbKeyboardShortcutsOverview', KeyboardShortcutsOverviewDirective); - -})(); - -/** + (function () { + 'use strict'; + function KeyboardShortcutsOverviewDirective(platformService) { + function link(scope, el, attr, ctrl) { + var eventBindings = []; + var isMac = platformService.isMac(); + scope.toggleShortcutsOverlay = function () { + scope.showOverlay = !scope.showOverlay; + scope.onToggle(); + }; + function onInit() { + angular.forEach(scope.model, function (shortcutGroup) { + angular.forEach(shortcutGroup.shortcuts, function (shortcut) { + shortcut.platformKeys = []; + // get shortcut keys for mac + if (isMac && shortcut.keys && shortcut.keys.mac) { + shortcut.platformKeys = shortcut.keys.mac; // get shortcut keys for windows + } else if (!isMac && shortcut.keys && shortcut.keys.win) { + shortcut.platformKeys = shortcut.keys.win; // get default shortcut keys + } else if (shortcut.keys && shortcut && shortcut.keys.length > 0) { + shortcut.platformKeys = shortcut.keys; + } + }); + }); + } + onInit(); + eventBindings.push(scope.$watch('model', function (newValue, oldValue) { + if (newValue !== oldValue) { + onInit(); + } + })); + // clean up + scope.$on('$destroy', function () { + // unbind watchers + for (var e in eventBindings) { + eventBindings[e](); + } + }); + } + var directive = { + restrict: 'E', + replace: true, + templateUrl: 'views/components/umb-keyboard-shortcuts-overview.html', + link: link, + scope: { + model: '=', + onToggle: '&', + showOverlay: '=?' + } + }; + return directive; + } + angular.module('umbraco.directives').directive('umbKeyboardShortcutsOverview', KeyboardShortcutsOverviewDirective); + }()); + /** * @ngdoc directive * @name umbraco.directives.directive:umbLaunchMiniEditor * @restrict E @@ -8093,179 +9807,82 @@ When this combination is hit an overview is opened with shortcuts based on the m * @description * Used on a button to launch a mini content editor editor dialog **/ -angular.module("umbraco.directives") - .directive('umbLaunchMiniEditor', function (dialogService, editorState, fileManager, contentEditingHelper) { + angular.module('umbraco.directives').directive('umbLaunchMiniEditor', function (miniEditorHelper) { return { restrict: 'A', replace: false, - scope: { - node: '=umbLaunchMiniEditor', - }, - link: function(scope, element, attrs) { - - var launched = false; - - element.click(function() { - - if (launched === true) { - return; - } - - launched = true; - - //We need to store the current files selected in the file manager locally because the fileManager - // is a singleton and is shared globally. The mini dialog will also be referencing the fileManager - // and we don't want it to be sharing the same files as the main editor. So we'll store the current files locally here, - // clear them out and then launch the dialog. When the dialog closes, we'll reset the fileManager to it's previous state. - var currFiles = _.groupBy(fileManager.getFiles(), "alias"); - fileManager.clearFiles(); - - //We need to store the original editorState entity because it will need to change when the mini editor is loaded so that - // any property editors that are working with editorState get given the correct entity, otherwise strange things will - // start happening. - var currEditorState = editorState.getCurrent(); - - dialogService.open({ - template: "views/common/dialogs/content/edit.html", - id: scope.node.id, - closeOnSave: true, - tabFilter: ["Generic properties"], - callback: function (data) { - - //set the node name back - scope.node.name = data.name; - - //reset the fileManager to what it was - fileManager.clearFiles(); - _.each(currFiles, function (val, key) { - fileManager.setFiles(key, _.map(currFiles['upload'], function (i) { return i.file; })); - }); - - //reset the editor state - editorState.set(currEditorState); - - //Now we need to check if the content item that was edited was actually the same content item - // as the main content editor and if so, update all property data - if (data.id === currEditorState.id) { - var changed = contentEditingHelper.reBindChangedProperties(currEditorState, data); - } - - launched = false; - }, - closeCallback: function () { - //reset the fileManager to what it was - fileManager.clearFiles(); - _.each(currFiles, function (val, key) { - fileManager.setFiles(key, _.map(currFiles['upload'], function (i) { return i.file; })); - }); - - //reset the editor state - editorState.set(currEditorState); - - launched = false; - } - }); - + scope: { node: '=umbLaunchMiniEditor' }, + link: function (scope, element, attrs) { + element.click(function () { + miniEditorHelper.launchMiniEditor(scope.node); }); - } }; - }); -(function() { - 'use strict'; - - function LayoutSelectorDirective() { - - function link(scope, el, attr, ctrl) { - - scope.layoutDropDownIsOpen = false; - scope.showLayoutSelector = true; - - function activate() { - - setVisibility(); - - setActiveLayout(scope.layouts); - - } - - function setVisibility() { - - var numberOfAllowedLayouts = getNumberOfAllowedLayouts(scope.layouts); - - if(numberOfAllowedLayouts === 1) { - scope.showLayoutSelector = false; + }); + (function () { + 'use strict'; + function LayoutSelectorDirective() { + function link(scope, el, attr, ctrl) { + scope.layoutDropDownIsOpen = false; + scope.showLayoutSelector = true; + function activate() { + setVisibility(); + setActiveLayout(scope.layouts); + } + function setVisibility() { + var numberOfAllowedLayouts = getNumberOfAllowedLayouts(scope.layouts); + if (numberOfAllowedLayouts === 1) { + scope.showLayoutSelector = false; + } + } + function getNumberOfAllowedLayouts(layouts) { + var allowedLayouts = 0; + for (var i = 0; layouts.length > i; i++) { + var layout = layouts[i]; + if (layout.selected === true) { + allowedLayouts++; + } + } + return allowedLayouts; + } + function setActiveLayout(layouts) { + for (var i = 0; layouts.length > i; i++) { + var layout = layouts[i]; + if (layout.path === scope.activeLayout.path) { + layout.active = true; + } + } + } + scope.pickLayout = function (selectedLayout) { + if (scope.onLayoutSelect) { + scope.onLayoutSelect(selectedLayout); + scope.layoutDropDownIsOpen = false; + } + }; + scope.toggleLayoutDropdown = function () { + scope.layoutDropDownIsOpen = !scope.layoutDropDownIsOpen; + }; + scope.closeLayoutDropdown = function () { + scope.layoutDropDownIsOpen = false; + }; + activate(); } - - } - - function getNumberOfAllowedLayouts(layouts) { - - var allowedLayouts = 0; - - for (var i = 0; layouts.length > i; i++) { - - var layout = layouts[i]; - - if(layout.selected === true) { - allowedLayouts++; - } - - } - - return allowedLayouts; - } - - function setActiveLayout(layouts) { - - for (var i = 0; layouts.length > i; i++) { - var layout = layouts[i]; - if(layout.path === scope.activeLayout.path) { - layout.active = true; - } - } - - } - - scope.pickLayout = function(selectedLayout) { - if(scope.onLayoutSelect) { - scope.onLayoutSelect(selectedLayout); - scope.layoutDropDownIsOpen = false; - } - }; - - scope.toggleLayoutDropdown = function() { - scope.layoutDropDownIsOpen = !scope.layoutDropDownIsOpen; - }; - - scope.closeLayoutDropdown = function() { - scope.layoutDropDownIsOpen = false; - }; - - activate(); - - } - - var directive = { - restrict: 'E', - replace: true, - templateUrl: 'views/components/umb-layout-selector.html', - scope: { - layouts: '=', - activeLayout: '=', - onLayoutSelect: "=" - }, - link: link - }; - - return directive; - } - - angular.module('umbraco.directives').directive('umbLayoutSelector', LayoutSelectorDirective); - -})(); - -/** + var directive = { + restrict: 'E', + replace: true, + templateUrl: 'views/components/umb-layout-selector.html', + scope: { + layouts: '=', + activeLayout: '=', + onLayoutSelect: '=' + }, + link: link + }; + return directive; + } + angular.module('umbraco.directives').directive('umbLayoutSelector', LayoutSelectorDirective); + }()); + /** @ngdoc directive @name umbraco.directives.directive:umbLightbox @restrict E @@ -8339,277 +9956,191 @@ angular.module("umbraco.directives") @param {callback} onClose Callback when the lightbox is closed. @param {number} activeItemIndex Index of active item. **/ - - -(function() { - 'use strict'; - - function LightboxDirective() { - - function link(scope, el, attr, ctrl) { - - - function activate() { - - var eventBindings = []; - - el.appendTo("body"); - - // clean up - scope.$on('$destroy', function() { - // unbind watchers - for (var e in eventBindings) { - eventBindings[e](); + (function () { + 'use strict'; + function LightboxDirective() { + function link(scope, el, attr, ctrl) { + function activate() { + var eventBindings = []; + el.appendTo('body'); + // clean up + scope.$on('$destroy', function () { + // unbind watchers + for (var e in eventBindings) { + eventBindings[e](); + } + }); + } + scope.next = function () { + var nextItemIndex = scope.activeItemIndex + 1; + if (nextItemIndex < scope.items.length) { + scope.items[scope.activeItemIndex].active = false; + scope.items[nextItemIndex].active = true; + scope.activeItemIndex = nextItemIndex; + } + }; + scope.prev = function () { + var prevItemIndex = scope.activeItemIndex - 1; + if (prevItemIndex >= 0) { + scope.items[scope.activeItemIndex].active = false; + scope.items[prevItemIndex].active = true; + scope.activeItemIndex = prevItemIndex; + } + }; + scope.close = function () { + if (scope.onClose) { + scope.onClose(); + } + }; + activate(); + } + var directive = { + restrict: 'E', + replace: true, + templateUrl: 'views/components/umb-lightbox.html', + scope: { + items: '=', + onClose: '=', + activeItemIndex: '=' + }, + link: link + }; + return directive; + } + angular.module('umbraco.directives').directive('umbLightbox', LightboxDirective); + }()); + (function () { + 'use strict'; + function ListViewLayoutDirective() { + function link(scope, el, attr, ctrl) { + scope.getContent = function (contentId) { + if (scope.onGetContent) { + scope.onGetContent(contentId); + } + }; + } + var directive = { + restrict: 'E', + replace: true, + templateUrl: 'views/components/umb-list-view-layout.html', + scope: { + contentId: '=', + folders: '=', + items: '=', + selection: '=', + options: '=', + entityType: '@', + onGetContent: '=' + }, + link: link + }; + return directive; + } + angular.module('umbraco.directives').directive('umbListViewLayout', ListViewLayoutDirective); + }()); + (function () { + 'use strict'; + function ListViewSettingsDirective(dataTypeResource, dataTypeHelper, listViewPrevalueHelper) { + function link(scope) { + scope.dataType = {}; + scope.editDataTypeSettings = false; + scope.customListViewCreated = false; + /* ---------- INIT ---------- */ + function activate() { + if (scope.enableListView) { + dataTypeResource.getByName(scope.listViewName).then(function (dataType) { + scope.dataType = dataType; + listViewPrevalueHelper.setPrevalues(dataType.preValues); + scope.customListViewCreated = checkForCustomListView(); + }); + } else { + scope.dataType = {}; + } + } + /* ----------- LIST VIEW SETTINGS --------- */ + scope.toggleEditListViewDataTypeSettings = function () { + scope.editDataTypeSettings = !scope.editDataTypeSettings; + }; + scope.saveListViewDataType = function () { + var preValues = dataTypeHelper.createPreValueProps(scope.dataType.preValues); + dataTypeResource.save(scope.dataType, preValues, false).then(function (dataType) { + // store data type + scope.dataType = dataType; + // hide settings panel + scope.editDataTypeSettings = false; + }); + }; + /* ---------- CUSTOM LIST VIEW ---------- */ + scope.createCustomListViewDataType = function () { + dataTypeResource.createCustomListView(scope.modelAlias).then(function (dataType) { + // store data type + scope.dataType = dataType; + // set list view name on scope + scope.listViewName = dataType.name; + // change state to custom list view + scope.customListViewCreated = true; + // show settings panel + scope.editDataTypeSettings = true; + }); + }; + scope.removeCustomListDataType = function () { + scope.editDataTypeSettings = false; + // delete custom list view data type + dataTypeResource.deleteById(scope.dataType.id).then(function (dataType) { + // set list view name on scope + if (scope.contentType === 'documentType') { + scope.listViewName = 'List View - Content'; + } else if (scope.contentType === 'mediaType') { + scope.listViewName = 'List View - Media'; + } + // get default data type + dataTypeResource.getByName(scope.listViewName).then(function (dataType) { + // store data type + scope.dataType = dataType; + // change state to default list view + scope.customListViewCreated = false; + }); + }); + }; + scope.toggle = function () { + if (scope.enableListView) { + scope.enableListView = false; + return; + } + scope.enableListView = true; + }; + /* ----------- SCOPE WATCHERS ----------- */ + var unbindEnableListViewWatcher = scope.$watch('enableListView', function (newValue) { + if (newValue !== undefined) { + activate(); } }); + // clean up + scope.$on('$destroy', function () { + unbindEnableListViewWatcher(); + }); + /* ----------- METHODS ---------- */ + function checkForCustomListView() { + return scope.dataType.name === 'List View - ' + scope.modelAlias; + } } - - scope.next = function() { - - var nextItemIndex = scope.activeItemIndex + 1; - - if( nextItemIndex < scope.items.length) { - scope.items[scope.activeItemIndex].active = false; - scope.items[nextItemIndex].active = true; - scope.activeItemIndex = nextItemIndex; - } + var directive = { + restrict: 'E', + replace: true, + templateUrl: 'views/components/umb-list-view-settings.html', + scope: { + enableListView: '=', + listViewName: '=', + modelAlias: '=', + contentType: '@' + }, + link: link }; - - scope.prev = function() { - - var prevItemIndex = scope.activeItemIndex - 1; - - if( prevItemIndex >= 0) { - scope.items[scope.activeItemIndex].active = false; - scope.items[prevItemIndex].active = true; - scope.activeItemIndex = prevItemIndex; - } - - }; - - scope.close = function() { - if(scope.onClose) { - scope.onClose(); - } - }; - - activate(); - + return directive; } - - var directive = { - restrict: 'E', - replace: true, - templateUrl: 'views/components/umb-lightbox.html', - scope: { - items: '=', - onClose: "=", - activeItemIndex: "=" - }, - link: link - }; - - return directive; - } - - angular.module('umbraco.directives').directive('umbLightbox', LightboxDirective); - -})(); - -(function() { - 'use strict'; - - function ListViewLayoutDirective() { - - function link(scope, el, attr, ctrl) { - - scope.getContent = function(contentId) { - if(scope.onGetContent) { - scope.onGetContent(contentId); - } - }; - - } - - var directive = { - restrict: 'E', - replace: true, - templateUrl: 'views/components/umb-list-view-layout.html', - scope: { - contentId: '=', - folders: '=', - items: '=', - selection: '=', - options: '=', - entityType: '@', - onGetContent: '=' - }, - link: link - }; - - return directive; - } - - angular.module('umbraco.directives').directive('umbListViewLayout', ListViewLayoutDirective); - -})(); - -(function() { - 'use strict'; - - function ListViewSettingsDirective(contentTypeResource, dataTypeResource, dataTypeHelper, listViewPrevalueHelper) { - - function link(scope, el, attr, ctrl) { - - scope.dataType = {}; - scope.editDataTypeSettings = false; - scope.customListViewCreated = false; - - /* ---------- INIT ---------- */ - - function activate() { - - if(scope.enableListView) { - - dataTypeResource.getByName(scope.listViewName) - .then(function(dataType) { - - scope.dataType = dataType; - - listViewPrevalueHelper.setPrevalues(dataType.preValues); - scope.customListViewCreated = checkForCustomListView(); - - }); - - } else { - - scope.dataType = {}; - - } - - } - - /* ----------- LIST VIEW SETTINGS --------- */ - - scope.toggleEditListViewDataTypeSettings = function() { - scope.editDataTypeSettings = !scope.editDataTypeSettings; - }; - - scope.saveListViewDataType = function() { - - var preValues = dataTypeHelper.createPreValueProps(scope.dataType.preValues); - - dataTypeResource.save(scope.dataType, preValues, false).then(function(dataType) { - - // store data type - scope.dataType = dataType; - - // hide settings panel - scope.editDataTypeSettings = false; - - }); - - }; - - - /* ---------- CUSTOM LIST VIEW ---------- */ - - scope.createCustomListViewDataType = function() { - - dataTypeResource.createCustomListView(scope.modelAlias).then(function(dataType) { - - // store data type - scope.dataType = dataType; - - // set list view name on scope - scope.listViewName = dataType.name; - - // change state to custom list view - scope.customListViewCreated = true; - - // show settings panel - scope.editDataTypeSettings = true; - - }); - - }; - - scope.removeCustomListDataType = function() { - - scope.editDataTypeSettings = false; - - // delete custom list view data type - dataTypeResource.deleteById(scope.dataType.id).then(function(dataType) { - - // set list view name on scope - if(scope.contentType === "documentType") { - - scope.listViewName = "List View - Content"; - - } else if(scope.contentType === "mediaType") { - - scope.listViewName = "List View - Media"; - - } - - // get default data type - dataTypeResource.getByName(scope.listViewName) - .then(function(dataType) { - - // store data type - scope.dataType = dataType; - - // change state to default list view - scope.customListViewCreated = false; - - }); - }); - - }; - - /* ----------- SCOPE WATCHERS ----------- */ - var unbindEnableListViewWatcher = scope.$watch('enableListView', function(newValue, oldValue){ - - if(newValue !== undefined) { - activate(); - } - - }); - - // clean up - scope.$on('$destroy', function(){ - unbindEnableListViewWatcher(); - }); - - /* ----------- METHODS ---------- */ - - function checkForCustomListView() { - return scope.dataType.name === "List View - " + scope.modelAlias; - } - - } - - var directive = { - restrict: 'E', - replace: true, - templateUrl: 'views/components/umb-list-view-settings.html', - scope: { - enableListView: "=", - listViewName: "=", - modelAlias: "=", - contentType: "@" - }, - link: link - }; - - return directive; - } - - angular.module('umbraco.directives').directive('umbListViewSettings', ListViewSettingsDirective); - -})(); - -/** + angular.module('umbraco.directives').directive('umbListViewSettings', ListViewSettingsDirective); + }()); + /** @ngdoc directive @name umbraco.directives.directive:umbLoadIndicator @restrict E @@ -8655,26 +10186,19 @@ Use this directive to generate a loading indicator. })(); **/ - -(function() { - 'use strict'; - - function UmbLoadIndicatorDirective() { - - var directive = { - restrict: 'E', - replace: true, - templateUrl: 'views/components/umb-load-indicator.html' - }; - - return directive; - } - - angular.module('umbraco.directives').directive('umbLoadIndicator', UmbLoadIndicatorDirective); - -})(); - -/** + (function () { + 'use strict'; + function UmbLoadIndicatorDirective() { + var directive = { + restrict: 'E', + replace: true, + templateUrl: 'views/components/umb-load-indicator.html' + }; + return directive; + } + angular.module('umbraco.directives').directive('umbLoadIndicator', UmbLoadIndicatorDirective); + }()); + /** @ngdoc directive @name umbraco.directives.directive:umbLockedField @restrict E @@ -8718,74 +10242,55 @@ Use this directive to render a value with a lock next to it. When the lock is cl @param {string=} regexValidation (binding): Set a regex expression for validation of the field. @param {string=} serverValidationField (attribute): Set a server validation field. **/ - -(function() { - 'use strict'; - - function LockedFieldDirective($timeout, localizationService) { - - function link(scope, el, attr, ngModelCtrl) { - - function activate() { - - // if locked state is not defined as an attr set default state - if (scope.locked === undefined || scope.locked === null) { - scope.locked = true; - } - - // if regex validation is not defined as an attr set default state - // if this is set to an empty string then regex validation can be ignored. - if (scope.regexValidation === undefined || scope.regexValidation === null) { - scope.regexValidation = "^[a-zA-Z]\\w.*$"; - } - - if (scope.serverValidationField === undefined || scope.serverValidationField === null) { - scope.serverValidationField = ""; - } - - // if locked state is not defined as an attr set default state - if (scope.placeholderText === undefined || scope.placeholderText === null) { - scope.placeholderText = "Enter value..."; - } - - } - - scope.lock = function() { - scope.locked = true; - }; - - scope.unlock = function() { - scope.locked = false; - }; - - activate(); - - } - - var directive = { - require: "ngModel", - restrict: 'E', - replace: true, - templateUrl: 'views/components/umb-locked-field.html', - scope: { - ngModel: "=", - locked: "=?", - placeholderText: "=?", - regexValidation: "=?", - serverValidationField: "@" - }, - link: link - }; - - return directive; - - } - - angular.module('umbraco.directives').directive('umbLockedField', LockedFieldDirective); - -})(); - -/** + (function () { + 'use strict'; + function LockedFieldDirective($timeout, localizationService) { + function link(scope, el, attr, ngModelCtrl) { + function activate() { + // if locked state is not defined as an attr set default state + if (scope.locked === undefined || scope.locked === null) { + scope.locked = true; + } + // if regex validation is not defined as an attr set default state + // if this is set to an empty string then regex validation can be ignored. + if (scope.regexValidation === undefined || scope.regexValidation === null) { + scope.regexValidation = '^[a-zA-Z]\\w.*$'; + } + if (scope.serverValidationField === undefined || scope.serverValidationField === null) { + scope.serverValidationField = ''; + } + // if locked state is not defined as an attr set default state + if (scope.placeholderText === undefined || scope.placeholderText === null) { + scope.placeholderText = 'Enter value...'; + } + } + scope.lock = function () { + scope.locked = true; + }; + scope.unlock = function () { + scope.locked = false; + }; + activate(); + } + var directive = { + require: 'ngModel', + restrict: 'E', + replace: true, + templateUrl: 'views/components/umb-locked-field.html', + scope: { + ngModel: '=', + locked: '=?', + placeholderText: '=?', + regexValidation: '=?', + serverValidationField: '@' + }, + link: link + }; + return directive; + } + angular.module('umbraco.directives').directive('umbLockedField', LockedFieldDirective); + }()); + /** @ngdoc directive @name umbraco.directives.directive:umbMediaGrid @restrict E @@ -8868,227 +10373,573 @@ Use this directive to generate a thumbnail grid of media items. @param {string=} itemMinHeight (attribute): Sets a min height on the media item thumbnails. **/ - -(function() { - 'use strict'; - - function MediaGridDirective($filter, mediaHelper) { - - function link(scope, el, attr, ctrl) { - - var itemDefaultHeight = 200; - var itemDefaultWidth = 200; - var itemMaxWidth = 200; - var itemMaxHeight = 200; - var itemMinWidth = 125; - var itemMinHeight = 125; - - function activate() { - - if (scope.itemMaxWidth) { - itemMaxWidth = scope.itemMaxWidth; - } - - if (scope.itemMaxHeight) { - itemMaxHeight = scope.itemMaxHeight; - } - - if (scope.itemMinWidth) { - itemMinWidth = scope.itemMinWidth; - } - - if (scope.itemMinWidth) { - itemMinHeight = scope.itemMinHeight; - } - - for (var i = 0; scope.items.length > i; i++) { - var item = scope.items[i]; - setItemData(item); - setOriginalSize(item, itemMaxHeight); - - // remove non images when onlyImages is set to true - if(scope.onlyImages === "true" && !item.isFolder && !item.thumbnail){ - scope.items.splice(i, 1); - i--; + (function () { + 'use strict'; + function MediaGridDirective($filter, mediaHelper) { + function link(scope, el, attr, ctrl) { + var itemDefaultHeight = 200; + var itemDefaultWidth = 200; + var itemMaxWidth = 200; + var itemMaxHeight = 200; + var itemMinWidth = 125; + var itemMinHeight = 125; + function activate() { + if (scope.itemMaxWidth) { + itemMaxWidth = scope.itemMaxWidth; + } + if (scope.itemMaxHeight) { + itemMaxHeight = scope.itemMaxHeight; + } + if (scope.itemMinWidth) { + itemMinWidth = scope.itemMinWidth; + } + if (scope.itemMinHeight) { + itemMinHeight = scope.itemMinHeight; + } + for (var i = 0; scope.items.length > i; i++) { + var item = scope.items[i]; + setItemData(item); + setOriginalSize(item, itemMaxHeight); + // remove non images when onlyImages is set to true + if (scope.onlyImages === 'true' && !item.isFolder && !item.thumbnail) { + scope.items.splice(i, 1); + i--; + } + } + if (scope.items.length > 0) { + setFlexValues(scope.items); } - } - - if (scope.items.length > 0) { - setFlexValues(scope.items); + function setItemData(item) { + // check if item is a folder + if (item.image) { + // if is has an image path, it is not a folder + item.isFolder = false; + } else { + item.isFolder = !mediaHelper.hasFilePropertyType(item); + } + if (!item.isFolder) { + // handle entity + if (item.image) { + item.thumbnail = mediaHelper.resolveFileFromEntity(item, true); + item.extension = mediaHelper.getFileExtension(item.image); // handle full media object + } else { + item.thumbnail = mediaHelper.resolveFile(item, true); + item.image = mediaHelper.resolveFile(item, false); + var fileProp = _.find(item.properties, function (v) { + return v.alias === 'umbracoFile'; + }); + if (fileProp && fileProp.value) { + item.file = fileProp.value; + } + var extensionProp = _.find(item.properties, function (v) { + return v.alias === 'umbracoExtension'; + }); + if (extensionProp && extensionProp.value) { + item.extension = extensionProp.value; + } + } + } } - - } - - function setItemData(item) { - item.isFolder = !mediaHelper.hasFilePropertyType(item); - if (!item.isFolder) { - item.thumbnail = mediaHelper.resolveFile(item, true); - item.image = mediaHelper.resolveFile(item, false); - - var fileProp = _.find(item.properties, function (v) { - return (v.alias === "umbracoFile"); + function setOriginalSize(item, maxHeight) { + //set to a square by default + item.width = itemDefaultWidth; + item.height = itemDefaultHeight; + item.aspectRatio = 1; + var widthProp = _.find(item.properties, function (v) { + return v.alias === 'umbracoWidth'; }); - - if (fileProp && fileProp.value) { - item.file = fileProp.value; + if (widthProp && widthProp.value) { + item.width = parseInt(widthProp.value, 10); + if (isNaN(item.width)) { + item.width = itemDefaultWidth; + } } - - var extensionProp = _.find(item.properties, function (v) { - return (v.alias === "umbracoExtension"); + var heightProp = _.find(item.properties, function (v) { + return v.alias === 'umbracoHeight'; }); - - if (extensionProp && extensionProp.value) { - item.extension = extensionProp.value; + if (heightProp && heightProp.value) { + item.height = parseInt(heightProp.value, 10); + if (isNaN(item.height)) { + item.height = itemDefaultWidth; + } + } + item.aspectRatio = item.width / item.height; + // set max width and height + // landscape + if (item.aspectRatio >= 1) { + if (item.width > itemMaxWidth) { + item.width = itemMaxWidth; + item.height = itemMaxWidth / item.aspectRatio; + } // portrait + } else { + if (item.height > itemMaxHeight) { + item.height = itemMaxHeight; + item.width = itemMaxHeight * item.aspectRatio; + } } } - } - - function setOriginalSize(item, maxHeight) { - - //set to a square by default - item.width = itemDefaultWidth; - item.height = itemDefaultHeight; - item.aspectRatio = 1; - - var widthProp = _.find(item.properties, function(v) { - return (v.alias === "umbracoWidth"); + function setFlexValues(mediaItems) { + var flexSortArray = mediaItems; + var smallestImageWidth = null; + var widestImageAspectRatio = null; + // sort array after image width with the widest image first + flexSortArray = $filter('orderBy')(flexSortArray, 'width', true); + // find widest image aspect ratio + widestImageAspectRatio = flexSortArray[0].aspectRatio; + // find smallest image width + smallestImageWidth = flexSortArray[flexSortArray.length - 1].width; + for (var i = 0; flexSortArray.length > i; i++) { + var mediaItem = flexSortArray[i]; + var flex = 1 / (widestImageAspectRatio / mediaItem.aspectRatio); + if (flex === 0) { + flex = 1; + } + var imageMinFlexWidth = smallestImageWidth * flex; + var flexStyle = { + 'flex': flex + ' 1 ' + imageMinFlexWidth + 'px', + 'max-width': mediaItem.width + 'px', + 'min-width': itemMinWidth + 'px', + 'min-height': itemMinHeight + 'px' + }; + mediaItem.flexStyle = flexStyle; + } + } + scope.clickItem = function (item, $event, $index) { + if (scope.onClick) { + scope.onClick(item, $event, $index); + $event.stopPropagation(); + } + }; + scope.clickItemName = function (item, $event, $index) { + if (scope.onClickName) { + scope.onClickName(item, $event, $index); + $event.stopPropagation(); + } + }; + scope.hoverItemDetails = function (item, $event, hover) { + if (scope.onDetailsHover) { + scope.onDetailsHover(item, $event, hover); + } + }; + var unbindItemsWatcher = scope.$watch('items', function (newValue, oldValue) { + if (angular.isArray(newValue)) { + activate(); + } }); - - if (widthProp && widthProp.value) { - item.width = parseInt(widthProp.value, 10); - if (isNaN(item.width)) { - item.width = itemDefaultWidth; - } - } - - var heightProp = _.find(item.properties, function(v) { - return (v.alias === "umbracoHeight"); + scope.$on('$destroy', function () { + unbindItemsWatcher(); }); - - if (heightProp && heightProp.value) { - item.height = parseInt(heightProp.value, 10); - if (isNaN(item.height)) { - item.height = itemDefaultWidth; - } - } - - item.aspectRatio = item.width / item.height; - - // set max width and height - // landscape - if (item.aspectRatio >= 1) { - if (item.width > itemMaxWidth) { - item.width = itemMaxWidth; - item.height = itemMaxWidth / item.aspectRatio; - } - // portrait - } else { - if (item.height > itemMaxHeight) { - item.height = itemMaxHeight; - item.width = itemMaxHeight * item.aspectRatio; - } - } - } - - function setFlexValues(mediaItems) { - - var flexSortArray = mediaItems; - var smallestImageWidth = null; - var widestImageAspectRatio = null; - - // sort array after image width with the widest image first - flexSortArray = $filter('orderBy')(flexSortArray, 'width', true); - - // find widest image aspect ratio - widestImageAspectRatio = flexSortArray[0].aspectRatio; - - // find smallest image width - smallestImageWidth = flexSortArray[flexSortArray.length - 1].width; - - for (var i = 0; flexSortArray.length > i; i++) { - - var mediaItem = flexSortArray[i]; - var flex = 1 / (widestImageAspectRatio / mediaItem.aspectRatio); - - if (flex === 0) { - flex = 1; - } - - var imageMinFlexWidth = smallestImageWidth * flex; - - var flexStyle = { - "flex": flex + " 1 " + imageMinFlexWidth + "px", - "max-width": mediaItem.width + "px", - "min-width": itemMinWidth + "px", - "min-height": itemMinHeight + "px" - }; - - mediaItem.flexStyle = flexStyle; - - } - - } - - scope.clickItem = function(item, $event, $index) { - if (scope.onClick) { - scope.onClick(item, $event, $index); - } + var directive = { + restrict: 'E', + replace: true, + templateUrl: 'views/components/umb-media-grid.html', + scope: { + items: '=', + onDetailsHover: '=', + onClick: '=', + onClickName: '=', + filterBy: '=', + itemMaxWidth: '@', + itemMaxHeight: '@', + itemMinWidth: '@', + itemMinHeight: '@', + onlyImages: '@' + }, + link: link }; - - scope.clickItemName = function(item, $event, $index) { - if (scope.onClickName) { - scope.onClickName(item, $event, $index); - $event.stopPropagation(); - } - }; - - scope.hoverItemDetails = function(item, $event, hover) { - if (scope.onDetailsHover) { - scope.onDetailsHover(item, $event, hover); - } - }; - - var unbindItemsWatcher = scope.$watch('items', function(newValue, oldValue) { - if (angular.isArray(newValue)) { - activate(); - } - }); - - scope.$on('$destroy', function() { - unbindItemsWatcher(); - }); - + return directive; } + angular.module('umbraco.directives').directive('umbMediaGrid', MediaGridDirective); + }()); + (function () { + 'use strict'; + function MiniListViewDirective(entityResource, iconHelper) { + function link(scope, el, attr, ctrl) { + scope.search = ''; + scope.miniListViews = []; + scope.breadcrumb = []; + var miniListViewsHistory = []; + var goingForward = true; + var skipAnimation = true; + function onInit() { + open(scope.node); + } + function open(node) { + // convert legacy icon for node + if (node && node.icon) { + node.icon = iconHelper.convertFromLegacyIcon(node.icon); + } + goingForward = true; + var miniListView = { + node: node, + loading: true, + pagination: { + pageSize: 10, + pageNumber: 1, + filter: '', + orderDirection: 'Ascending', + orderBy: 'SortOrder', + orderBySystemField: true + } + }; + // clear and push mini list view in dom so we only render 1 view + scope.miniListViews = []; + scope.miniListViews.push(miniListView); + // store in history so we quickly can navigate back + miniListViewsHistory.push(miniListView); + // get children + getChildrenForMiniListView(miniListView); + makeBreadcrumb(); + } + function getChildrenForMiniListView(miniListView) { + // start loading animation list view + miniListView.loading = true; + entityResource.getPagedChildren(miniListView.node.id, scope.entityType, miniListView.pagination).then(function (data) { + // update children + miniListView.children = data.items; + _.each(miniListView.children, function (c) { + // convert legacy icon for node + if (c.icon) { + c.icon = iconHelper.convertFromLegacyIcon(c.icon); + } + // set published state for content + if (c.metaData) { + c.hasChildren = c.metaData.HasChildren; + if (scope.entityType === 'Document') { + c.published = c.metaData.IsPublished; + } + } + }); + // update pagination + miniListView.pagination.totalItems = data.totalItems; + miniListView.pagination.totalPages = data.totalPages; + // stop load indicator + miniListView.loading = false; + }); + } + scope.openNode = function (event, node) { + open(node); + event.stopPropagation(); + }; + scope.selectNode = function (node) { + if (scope.onSelect) { + scope.onSelect({ 'node': node }); + } + }; + /* Pagination */ + scope.goToPage = function (pageNumber, miniListView) { + // set new page number + miniListView.pagination.pageNumber = pageNumber; + // get children + getChildrenForMiniListView(miniListView); + }; + /* Breadcrumb */ + scope.clickBreadcrumb = function (ancestor) { + var found = false; + goingForward = false; + angular.forEach(miniListViewsHistory, function (historyItem, index) { + // We need to make sure we can compare the two id's. + // Some id's are integers and others are strings. + // Members have string ids like "all-members". + if (historyItem.node.id.toString() === ancestor.id.toString()) { + // load the list view from history + scope.miniListViews = []; + scope.miniListViews.push(historyItem); + // clean up history - remove all children after + miniListViewsHistory.splice(index + 1, miniListViewsHistory.length); + found = true; + } + }); + if (!found) { + // if we can't find the view in the history - close the list view + scope.exitMiniListView(); + } + // update the breadcrumb + makeBreadcrumb(); + }; + scope.showBackButton = function () { + // don't show the back button if the start node is a list view + if (scope.node.metaData && scope.node.metaData.IsContainer || scope.node.isContainer) { + return false; + } else { + return true; + } + }; + scope.exitMiniListView = function () { + miniListViewsHistory = []; + scope.miniListViews = []; + if (scope.onClose) { + scope.onClose(); + } + }; + function makeBreadcrumb() { + scope.breadcrumb = []; + angular.forEach(miniListViewsHistory, function (historyItem) { + scope.breadcrumb.push(historyItem.node); + }); + } + /* Search */ + scope.searchMiniListView = function (search, miniListView) { + // set search value + miniListView.pagination.filter = search; + // reset pagination + miniListView.pagination.pageNumber = 1; + // start loading animation list view + miniListView.loading = true; + searchMiniListView(miniListView); + }; + var searchMiniListView = _.debounce(function (miniListView) { + scope.$apply(function () { + getChildrenForMiniListView(miniListView); + }); + }, 500); + /* Animation */ + scope.getMiniListViewAnimation = function () { + // disable the first "slide-in-animation"" if the start node is a list view + if (scope.node.metaData && scope.node.metaData.IsContainer && skipAnimation || scope.node.isContainer && skipAnimation) { + skipAnimation = false; + return; + } + if (goingForward) { + return 'umb-mini-list-view--forward'; + } else { + return 'umb-mini-list-view--backwards'; + } + }; + onInit(); + } + var directive = { + restrict: 'E', + replace: true, + templateUrl: 'views/components/umb-mini-list-view.html', + scope: { + node: '=', + entityType: '@', + startNodeId: '=', + onSelect: '&', + onClose: '&' + }, + link: link + }; + return directive; + } + angular.module('umbraco.directives').directive('umbMiniListView', MiniListViewDirective); + }()); + angular.module('umbraco.directives').directive('umbNestedContentEditor', [function () { + var link = function ($scope) { + // Clone the model because some property editors + // do weird things like updating and config values + // so we want to ensure we start from a fresh every + // time, we'll just sync the value back when we need to + $scope.model = angular.copy($scope.ngModel); + $scope.nodeContext = $scope.model; + // Find the selected tab + var selectedTab = $scope.model.tabs[0]; + if ($scope.tabAlias) { + angular.forEach($scope.model.tabs, function (tab) { + if (tab.alias.toLowerCase() === $scope.tabAlias.toLowerCase()) { + selectedTab = tab; + return; + } + }); + } + $scope.tab = selectedTab; + // Listen for sync request + var unsubscribe = $scope.$on('ncSyncVal', function (ev, args) { + if (args.key === $scope.model.key) { + // Tell inner controls we are submitting + $scope.$broadcast('formSubmitting', { scope: $scope }); + // Sync the values back + angular.forEach($scope.ngModel.tabs, function (tab) { + if (tab.alias.toLowerCase() === selectedTab.alias.toLowerCase()) { + var localPropsMap = selectedTab.properties.reduce(function (map, obj) { + map[obj.alias] = obj; + return map; + }, {}); + angular.forEach(tab.properties, function (prop) { + if (localPropsMap.hasOwnProperty(prop.alias)) { + prop.value = localPropsMap[prop.alias].value; + } + }); + } + }); + } + }); + $scope.$on('$destroy', function () { + unsubscribe(); + }); + }; + return { + restrict: 'E', + replace: true, + templateUrl: Umbraco.Sys.ServerVariables.umbracoSettings.umbracoPath + '/views/propertyeditors/nestedcontent/nestedcontent.editor.html', + scope: { + ngModel: '=', + tabAlias: '=' + }, + link: link + }; + }]); + //angular.module("umbraco.directives").directive('nestedContentSubmitWatcher', function () { + // var link = function (scope) { + // // call the load callback on scope to obtain the ID of this submit watcher + // var id = scope.loadCallback(); + // scope.$on("formSubmitting", function (ev, args) { + // // on the "formSubmitting" event, call the submit callback on scope to notify the nestedContent controller to do it's magic + // if (id === scope.activeSubmitWatcher) { + // scope.submitCallback(); + // } + // }); + // } + // return { + // restrict: "E", + // replace: true, + // template: "", + // scope: { + // loadCallback: '=', + // submitCallback: '=', + // activeSubmitWatcher: '=' + // }, + // link: link + // } + //}); + /** +@ngdoc directive +@name umbraco.directives.directive:umbNodePreview +@restrict E +@scope - var directive = { - restrict: 'E', - replace: true, - templateUrl: 'views/components/umb-media-grid.html', - scope: { - items: '=', - onDetailsHover: "=", - onClick: '=', - onClickName: "=", - filterBy: "=", - itemMaxWidth: "@", - itemMaxHeight: "@", - itemMinWidth: "@", - itemMinHeight: "@", - onlyImages: "@" - }, - link: link - }; +@description +Added in Umbraco v. 7.6: Use this directive to render a node preview. - return directive; - } +

    Markup example

    +
    +    
    + +
    + + +
    + +
    +
    - angular.module('umbraco.directives').directive('umbMediaGrid', MediaGridDirective); +

    Controller example

    +
    +    (function () {
    +        "use strict";
    +    
    +        function Controller() {
    +    
    +            var vm = this;
    +    
    +            vm.allowRemove = true;
    +            vm.allowOpen = true;
    +            vm.sortable = true;
    +    
    +            vm.nodes = [
    +                {
    +                    "icon": "icon-document",
    +                    "name": "My node 1",
    +                    "published": true,
    +                    "description": "A short description of my node"
    +                },
    +                {
    +                    "icon": "icon-document",
    +                    "name": "My node 2",
    +                    "published": true,
    +                    "description": "A short description of my node"
    +                }
    +            ];
    +    
    +            vm.remove = remove;
    +            vm.open = open;
    +    
    +            function remove(index, nodes) {
    +                alert("remove node");
    +            }
    +    
    +            function open(node) {
    +                alert("open node");
    +            }
    +    
    +        }
    +    
    +        angular.module("umbraco").controller("My.NodePreviewController", Controller);
    +    
    +    })();
    +
    -})(); - -/** +@param {string} icon (binding): The node icon. +@param {string} name (binding): The node name. +@param {string} alias (binding): The node document type alias will be displayed on hover if in debug mode or logged in as admin +@param {boolean} published (binding): The node published state. +@param {string} description (binding): A short description. +@param {boolean} sortable (binding): Will add a move cursor on the node preview. Can used in combination with ui-sortable. +@param {boolean} allowRemove (binding): Show/Hide the remove button. +@param {boolean} allowOpen (binding): Show/Hide the open button. +@param {boolean} allowEdit (binding): Show/Hide the edit button (Added in version 7.7.0). +@param {function} onRemove (expression): Callback function when the remove button is clicked. +@param {function} onOpen (expression): Callback function when the open button is clicked. +@param {function} onEdit (expression): Callback function when the edit button is clicked (Added in version 7.7.0). +@param {string} openUrl (binding): Fallback URL for onOpen (Added in version 7.12.0). +@param {string} editUrl (binding): Fallback URL for onEdit (Added in version 7.12.0). +@param {string} removeUrl (binding): Fallback URL for onRemove (Added in version 7.12.0). +**/ + (function () { + 'use strict'; + function NodePreviewDirective(userService) { + function link(scope, el, attr, ctrl) { + if (!scope.editLabelKey) { + scope.editLabelKey = 'general_edit'; + } + userService.getCurrentUser().then(function (u) { + var isAdmin = u.userGroups.indexOf('admin') !== -1; + scope.alias = Umbraco.Sys.ServerVariables.isDebuggingEnabled === true || isAdmin ? scope.alias : null; + }); + } + var directive = { + restrict: 'E', + replace: true, + templateUrl: 'views/components/umb-node-preview.html', + scope: { + icon: '=?', + name: '=', + alias: '=?', + description: '=?', + permissions: '=?', + published: '=?', + sortable: '=?', + allowOpen: '=?', + allowRemove: '=?', + allowEdit: '=?', + onOpen: '&?', + onRemove: '&?', + onEdit: '&?', + openUrl: '=?', + editUrl: '=?', + removeUrl: '=?' + }, + link: link + }; + return directive; + } + angular.module('umbraco.directives').directive('umbNodePreview', NodePreviewDirective); + }()); + /** @ngdoc directive @name umbraco.directives.directive:umbPagination @restrict E @@ -9172,115 +11023,149 @@ Use this directive to generate a pagination.
  • pageNumber: The page number
  • **/ - -(function() { - 'use strict'; - - function PaginationDirective() { - - function link(scope, el, attr, ctrl) { - - function activate() { - - scope.pagination = []; - - var i = 0; - - if (scope.totalPages <= 10) { - for (i = 0; i < scope.totalPages; i++) { - scope.pagination.push({ - val: (i + 1), - isActive: scope.pageNumber === (i + 1) - }); + (function () { + 'use strict'; + function PaginationDirective(localizationService) { + function link(scope, el, attr, ctrl) { + function activate() { + scope.pagination = []; + var i = 0; + if (scope.totalPages <= 10) { + for (i = 0; i < scope.totalPages; i++) { + scope.pagination.push({ + val: i + 1, + isActive: scope.pageNumber === i + 1 + }); + } + } else { + //if there is more than 10 pages, we need to do some fancy bits + //get the max index to start + var maxIndex = scope.totalPages - 10; + //set the start, but it can't be below zero + var start = Math.max(scope.pageNumber - 5, 0); + //ensure that it's not too far either + start = Math.min(maxIndex, start); + for (i = start; i < 10 + start; i++) { + scope.pagination.push({ + val: i + 1, + isActive: scope.pageNumber === i + 1 + }); + } + //now, if the start is greater than 0 then '1' will not be displayed, so do the elipses thing + if (start > 0) { + scope.pagination.unshift({ + name: localizationService.localize('general_first'), + val: 1, + isActive: false + }, { + val: '...', + isActive: false + }); + } + //same for the end + if (start < maxIndex) { + scope.pagination.push({ + val: '...', + isActive: false + }, { + name: localizationService.localize('general_last'), + val: scope.totalPages, + isActive: false + }); + } + } } + scope.next = function () { + if (scope.pageNumber < scope.totalPages) { + scope.pageNumber++; + if (scope.onNext) { + scope.onNext(scope.pageNumber); + } + if (scope.onChange) { + scope.onChange({ 'pageNumber': scope.pageNumber }); + } + } + }; + scope.prev = function (pageNumber) { + if (scope.pageNumber > 1) { + scope.pageNumber--; + if (scope.onPrev) { + scope.onPrev(scope.pageNumber); + } + if (scope.onChange) { + scope.onChange({ 'pageNumber': scope.pageNumber }); + } + } + }; + scope.goToPage = function (pageNumber) { + scope.pageNumber = pageNumber + 1; + if (scope.onGoToPage) { + scope.onGoToPage(scope.pageNumber); + } + if (scope.onChange) { + if (scope.onChange) { + scope.onChange({ 'pageNumber': scope.pageNumber }); + } + } + }; + var unbindPageNumberWatcher = scope.$watchCollection('[pageNumber, totalPages]', function (newValues, oldValues) { + activate(); + }); + scope.$on('$destroy', function () { + unbindPageNumberWatcher(); + }); + activate(); } - else { - //if there is more than 10 pages, we need to do some fancy bits + var directive = { + restrict: 'E', + replace: true, + templateUrl: 'views/components/umb-pagination.html', + scope: { + pageNumber: '=', + totalPages: '=', + onNext: '=', + onPrev: '=', + onGoToPage: '=', + onChange: '&' + }, + link: link + }; + return directive; + } + angular.module('umbraco.directives').directive('umbPagination', PaginationDirective); + }()); + /** +@ngdoc directive +@name umbraco.directives.directive:umbPasswordToggle +@restrict E +@scope - //get the max index to start - var maxIndex = scope.totalPages - 10; - //set the start, but it can't be below zero - var start = Math.max(scope.pageNumber - 5, 0); - //ensure that it's not too far either - start = Math.min(maxIndex, start); +@description +Added in Umbraco v. 7.7.4: Use this directive to render a password toggle. - for (i = start; i < (10 + start) ; i++) { - scope.pagination.push({ - val: (i + 1), - isActive: scope.pageNumber === (i + 1) - }); +**/ + (function () { + 'use strict'; + // comes from https://codepen.io/jakob-e/pen/eNBQaP + // works fine with Angular 1.6.5 - alas not with 1.1.5 - binding issue + function PasswordToggleDirective($compile) { + var directive = { + restrict: 'A', + scope: {}, + link: function (scope, elem, attrs) { + scope.tgl = function () { + elem.attr('type', elem.attr('type') === 'text' ? 'password' : 'text'); + }; + var lnk = angular.element('Toggle'); + $compile(lnk)(scope); + elem.wrap('
    ').after(lnk); } - - //now, if the start is greater than 0 then '1' will not be displayed, so do the elipses thing - if (start > 0) { - scope.pagination.unshift({ name: "First", val: 1, isActive: false }, {val: "...",isActive: false}); - } - - //same for the end - if (start < maxIndex) { - scope.pagination.push({ val: "...", isActive: false }, { name: "Last", val: scope.totalPages, isActive: false }); - } - } - - } - - scope.next = function() { - if (scope.onNext && scope.pageNumber < scope.totalPages) { - scope.pageNumber++; - scope.onNext(scope.pageNumber); - } - }; - - scope.prev = function(pageNumber) { - if (scope.onPrev && scope.pageNumber > 1) { - scope.pageNumber--; - scope.onPrev(scope.pageNumber); - } - }; - - scope.goToPage = function(pageNumber) { - if(scope.onGoToPage) { - scope.pageNumber = pageNumber + 1; - scope.onGoToPage(scope.pageNumber); - } - }; - - var unbindPageNumberWatcher = scope.$watch('pageNumber', function(newValue, oldValue){ - activate(); - }); - - scope.$on('$destroy', function(){ - unbindPageNumberWatcher(); - }); - - activate(); - - } - - var directive = { - restrict: 'E', - replace: true, - templateUrl: 'views/components/umb-pagination.html', - scope: { - pageNumber: "=", - totalPages: "=", - onNext: "=", - onPrev: "=", - onGoToPage: "=" - }, - link: link - }; - - return directive; - - } - - angular.module('umbraco.directives').directive('umbPagination', PaginationDirective); - -})(); - - -/** + }; + return directive; + } + angular.module('umbraco.directives').directive('umbPasswordToggle', PasswordToggleDirective); + }()); + /** @ngdoc directive @name umbraco.directives.directive:umbProgressBar @restrict E @@ -9297,31 +11182,94 @@ Use this directive to generate a progress bar. @param {number} percentage (attribute): The progress in percentage. +@param {string} size (attribute): The size (s, m). + **/ + (function () { + 'use strict'; + function ProgressBarDirective() { + var directive = { + restrict: 'E', + replace: true, + templateUrl: 'views/components/umb-progress-bar.html', + scope: { + percentage: '@', + size: '@?' + } + }; + return directive; + } + angular.module('umbraco.directives').directive('umbProgressBar', ProgressBarDirective); + }()); + /** +@ngdoc directive +@name umbraco.directives.directive:umbProgressCircle +@restrict E +@scope -(function() { - 'use strict'; +@description +Use this directive to render a circular progressbar. - function ProgressBarDirective() { +

    Markup example

    +
    +    
    + + + - var directive = { - restrict: 'E', - replace: true, - templateUrl: 'views/components/umb-progress-bar.html', - scope: { - percentage: "@" +
    +
    + +@param {string} size (attribute): This parameter defines the width and the height of the circle in pixels. +@param {string} percentage (attribute): Takes a number between 0 and 100 and applies it to the circle's highlight length. +@param {string} color (attribute): the color of the highlight (primary, secondary, success, warning, danger). Success by default. +**/ + (function () { + 'use strict'; + function ProgressCircleDirective($http, $timeout) { + function link(scope, element, $filter) { + function onInit() { + // making sure we get the right numbers + var percent = scope.percentage; + if (percent > 100) { + percent = 100; + } else if (percent < 0) { + percent = 0; + } + // calculating the circle's highlight + var circle = element.find('.umb-progress-circle__highlight'); + var r = circle.attr('r'); + var strokeDashArray = r * Math.PI * 2; + // Full circle length + scope.strokeDashArray = strokeDashArray; + var strokeDashOffsetDifference = percent / 100 * strokeDashArray; + var strokeDashOffset = strokeDashArray - strokeDashOffsetDifference; + // Distance for the highlight dash's offset + scope.strokeDashOffset = strokeDashOffset; + // set font size + scope.percentageSize = scope.size * 0.3 + 'px'; + } + onInit(); } - }; - - return directive; - - } - - angular.module('umbraco.directives').directive('umbProgressBar', ProgressBarDirective); - -})(); - -/** + var directive = { + restrict: 'E', + replace: true, + templateUrl: 'views/components/umb-progress-circle.html', + scope: { + size: '@?', + percentage: '@', + color: '@' + }, + link: link + }; + return directive; + } + angular.module('umbraco.directives').directive('umbProgressCircle', ProgressCircleDirective); + }()); + /** @ngdoc directive @name umbraco.directives.directive:umbStickyBar @restrict A @@ -9358,197 +11306,257 @@ Use this directive make an element sticky and follow the page when scrolling. @param {string} scrollableContainer Set the class (".element") or the id ("#element") of the scrollable container element. **/ + (function () { + 'use strict'; + function StickyBarDirective($rootScope) { + function link(scope, el, attr, ctrl) { + var bar = $(el); + var scrollableContainer = null; + var clonedBar = null; + var cloneIsMade = false; + function activate() { + if (attr.scrollableContainer) { + scrollableContainer = $(attr.scrollableContainer); + } else { + scrollableContainer = $(window); + } + scrollableContainer.on('scroll.umbStickyBar', determineVisibility).trigger('scroll'); + $(window).on('resize.umbStickyBar', determineVisibility); + scope.$on('$destroy', function () { + scrollableContainer.off('.umbStickyBar'); + $(window).off('.umbStickyBar'); + }); + } + function determineVisibility() { + var barTop = bar[0].offsetTop; + var scrollTop = scrollableContainer.scrollTop(); + if (scrollTop > barTop) { + if (!cloneIsMade) { + createClone(); + clonedBar.css({ 'visibility': 'visible' }); + } else { + calculateSize(); + } + } else { + if (cloneIsMade) { + //remove cloned element (switched places with original on creation) + bar.remove(); + bar = clonedBar; + clonedBar = null; + bar.removeClass('-umb-sticky-bar'); + bar.css({ + position: 'relative', + 'width': 'auto', + 'height': 'auto', + 'z-index': 'auto', + 'visibility': 'visible' + }); + cloneIsMade = false; + } + } + } + function calculateSize() { + clonedBar.css({ + width: bar.outerWidth(), + height: bar.height() + }); + } + function createClone() { + //switch place with cloned element, to keep binding intact + clonedBar = bar; + bar = clonedBar.clone(); + clonedBar.after(bar); + clonedBar.addClass('-umb-sticky-bar'); + clonedBar.css({ + 'position': 'fixed', + 'z-index': 500, + 'visibility': 'hidden' + }); + cloneIsMade = true; + calculateSize(); + } + activate(); + } + var directive = { + restrict: 'A', + link: link + }; + return directive; + } + angular.module('umbraco.directives').directive('umbStickyBar', StickyBarDirective); + }()); + /** +@ngdoc directive +@name umbraco.directives.directive:umbTable +@restrict E +@scope -(function() { - 'use strict'; +@description +Added in Umbraco v. 7.4: Use this directive to render a data table. - function StickyBarDirective($rootScope) { +

    Markup example

    +
    +    
    + + + + +
    +
    - function link(scope, el, attr, ctrl) { +

    Controller example

    +
    +    (function () {
    +        "use strict";
    +    
    +        function Controller() {
    +    
    +            var vm = this;
    +    
    +            vm.items = [
    +                {
    +                    "icon": "icon-document",
    +                    "name": "My node 1",
    +                    "published": true,
    +                    "description": "A short description of my node",
    +                    "author": "Author 1"
    +                },
    +                {
    +                    "icon": "icon-document",
    +                    "name": "My node 2",
    +                    "published": true,
    +                    "description": "A short description of my node",
    +                    "author": "Author 2"
    +                }
    +            ];
     
    -         var bar = $(el);
    -         var scrollableContainer = null;
    -         var clonedBar = null;
    -         var cloneIsMade = false;
    -         var barTop = bar.context.offsetTop;
    +            vm.options = {
    +                includeProperties: [
    +                    { alias: "description", header: "Description" },
    +                    { alias: "author", header: "Author" }
    +                ]
    +            };
    +    
    +            vm.selectItem = selectItem;
    +            vm.clickItem = clickItem;
    +            vm.selectAll = selectAll;
    +            vm.isSelectedAll = isSelectedAll;
    +            vm.isSortDirection = isSortDirection;
    +            vm.sort = sort;
     
    -         function activate() {
    -
    -            if (attr.scrollableContainer) {
    -               scrollableContainer = $(attr.scrollableContainer);
    -            } else {
    -               scrollableContainer = $(window);
    +            function selectAll($event) {
    +                alert("select all");
                 }
     
    -            scrollableContainer.on('scroll.umbStickyBar', determineVisibility).trigger("scroll");
    -            $(window).on('resize.umbStickyBar', determineVisibility);
    -
    -            scope.$on('$destroy', function() {
    -               scrollableContainer.off('.umbStickyBar');
    -               $(window).off('.umbStickyBar');
    -            });
    -
    -         }
    -
    -         function determineVisibility() {
    -
    -            var scrollTop = scrollableContainer.scrollTop();
    -
    -            if (scrollTop > barTop) {
    -
    -               if (!cloneIsMade) {
    -
    -                  createClone();
    -
    -                  clonedBar.css({
    -                     'visibility': 'visible'
    -                  });
    -
    -               } else {
    -
    -                  calculateSize();
    -
    -               }
    -
    -            } else {
    -
    -               if (cloneIsMade) {
    -
    -                  //remove cloned element (switched places with original on creation)
    -                  bar.remove();
    -                  bar = clonedBar;
    -                  clonedBar = null;
    -
    -                  bar.removeClass('-umb-sticky-bar');
    -                  bar.css({
    -                     position: 'relative',
    -                     'width': 'auto',
    -                     'height': 'auto',
    -                     'z-index': 'auto',
    -                     'visibility': 'visible'
    -                  });
    -
    -                  cloneIsMade = false;
    -
    -               }
    -
    +            function isSelectedAll() {
    +                
    +            }
    +    
    +            function clickItem(item) {
    +                alert("click node");
                 }
     
    -         }
    +            function selectItem(selectedItem, $index, $event) {
    +                alert("select node");
    +            }
    +            
    +            function isSortDirection(col, direction) {
    +                
    +            }
    +            
    +            function sort(field, allow, isSystem) {
    +                
    +            }
    +    
    +        }
    +    
    +        angular.module("umbraco").controller("My.TableController", Controller);
    +    
    +    })();
    +
    - function calculateSize() { - clonedBar.css({ - width: bar.outerWidth(), - height: bar.height() - }); - } - - function createClone() { - //switch place with cloned element, to keep binding intact - clonedBar = bar; - bar = clonedBar.clone(); - clonedBar.after(bar); - clonedBar.addClass('-umb-sticky-bar'); - clonedBar.css({ - 'position': 'fixed', - 'z-index': 500, - 'visibility': 'hidden' - }); - - cloneIsMade = true; - calculateSize(); - - } - - activate(); - - } - - var directive = { - restrict: 'A', - link: link - }; - - return directive; - } - - angular.module('umbraco.directives').directive('umbStickyBar', StickyBarDirective); - -})(); - -(function () { - 'use strict'; - - function TableDirective() { - - function link(scope, el, attr, ctrl) { - - scope.clickItem = function (item, $event) { - if (scope.onClick) { - scope.onClick(item); - $event.stopPropagation(); - } - }; - - scope.selectItem = function (item, $index, $event) { - if (scope.onSelect) { - scope.onSelect(item, $index, $event); - $event.stopPropagation(); - } - }; - - scope.selectAll = function ($event) { - if (scope.onSelectAll) { - scope.onSelectAll($event); - } - }; - - scope.isSelectedAll = function () { - if (scope.onSelectedAll && scope.items && scope.items.length > 0) { - return scope.onSelectedAll(); - } - }; - - scope.isSortDirection = function (col, direction) { - if (scope.onSortingDirection) { - return scope.onSortingDirection(col, direction); - } - }; - - scope.sort = function (field, allow, isSystem) { - if (scope.onSort) { - scope.onSort(field, allow, isSystem); - } - }; - - } - - var directive = { - restrict: 'E', - replace: true, - templateUrl: 'views/components/umb-table.html', - scope: { - items: '=', - itemProperties: '=', - allowSelectAll: '=', - onSelect: '=', - onClick: '=', - onSelectAll: '=', - onSelectedAll: '=', - onSortingDirection: '=', - onSort: '=' - }, - link: link - }; - - return directive; - } - - angular.module('umbraco.directives').directive('umbTable', TableDirective); - -})(); - -/** +@param {string} icon (binding): The node icon. +@param {string} name (binding): The node name. +@param {string} published (binding): The node published state. +@param {function} onSelect (expression): Callback function when the row is selected. +@param {function} onClick (expression): Callback function when the "Name" column link is clicked. +@param {function} onSelectAll (expression): Callback function when selecting all items. +@param {function} onSelectedAll (expression): Callback function when all items are selected. +@param {function} onSortingDirection (expression): Callback function when sorting direction is changed. +@param {function} onSort (expression): Callback function when sorting items. +**/ + (function () { + 'use strict'; + function TableDirective(iconHelper) { + function link(scope, el, attr, ctrl) { + scope.clickItem = function (item, $event) { + if (scope.onClick) { + scope.onClick(item); + $event.stopPropagation(); + } + }; + scope.selectItem = function (item, $index, $event) { + if (scope.onSelect) { + scope.onSelect(item, $index, $event); + $event.stopPropagation(); + } + }; + scope.selectAll = function ($event) { + if (scope.onSelectAll) { + scope.onSelectAll($event); + } + }; + scope.isSelectedAll = function () { + if (scope.onSelectedAll && scope.items && scope.items.length > 0) { + return scope.onSelectedAll(); + } + }; + scope.isSortDirection = function (col, direction) { + if (scope.onSortingDirection) { + return scope.onSortingDirection(col, direction); + } + }; + scope.sort = function (field, allow, isSystem) { + if (scope.onSort) { + scope.onSort(field, allow, isSystem); + } + }; + scope.getIcon = function (entry) { + return iconHelper.convertFromLegacyIcon(entry.icon); + }; + } + var directive = { + restrict: 'E', + replace: true, + templateUrl: 'views/components/umb-table.html', + scope: { + items: '=', + itemProperties: '=', + allowSelectAll: '=', + onSelect: '=', + onClick: '=', + onSelectAll: '=', + onSelectedAll: '=', + onSortingDirection: '=', + onSort: '=' + }, + link: link + }; + return directive; + } + angular.module('umbraco.directives').directive('umbTable', TableDirective); + }()); + /** @ngdoc directive @name umbraco.directives.directive:umbTooltip @restrict E @@ -9615,105 +11623,76 @@ Use this directive to render a tooltip. @param {string} event Set the $event from the target element to position the tooltip relative to the mouse cursor. **/ - -(function() { - 'use strict'; - - function TooltipDirective($timeout) { - - function link(scope, el, attr, ctrl) { - - scope.tooltipStyles = {}; - scope.tooltipStyles.left = 0; - scope.tooltipStyles.top = 0; - - function activate() { - - $timeout(function() { - setTooltipPosition(scope.event); - }); - - } - - function setTooltipPosition(event) { - - var container = $("#contentwrapper"); - var containerLeft = container[0].offsetLeft; - var containerRight = containerLeft + container[0].offsetWidth; - var containerTop = container[0].offsetTop; - var containerBottom = containerTop + container[0].offsetHeight; - - var elementHeight = null; - var elementWidth = null; - - var position = { - right: "inherit", - left: "inherit", - top: "inherit", - bottom: "inherit" + (function () { + 'use strict'; + function TooltipDirective($timeout) { + function link(scope, el, attr, ctrl) { + scope.tooltipStyles = {}; + scope.tooltipStyles.left = 0; + scope.tooltipStyles.top = 0; + function activate() { + $timeout(function () { + setTooltipPosition(scope.event); + }); + } + function setTooltipPosition(event) { + var container = $('#contentwrapper'); + var containerLeft = container[0].offsetLeft; + var containerRight = containerLeft + container[0].offsetWidth; + var containerTop = container[0].offsetTop; + var containerBottom = containerTop + container[0].offsetHeight; + var elementHeight = null; + var elementWidth = null; + var position = { + right: 'inherit', + left: 'inherit', + top: 'inherit', + bottom: 'inherit' + }; + // element size + elementHeight = el.context.clientHeight; + elementWidth = el.context.clientWidth; + position.left = event.pageX - elementWidth / 2; + position.top = event.pageY; + // check to see if element is outside screen + // outside right + if (position.left + elementWidth > containerRight) { + position.right = 10; + position.left = 'inherit'; + } + // outside bottom + if (position.top + elementHeight > containerBottom) { + position.bottom = 10; + position.top = 'inherit'; + } + // outside left + if (position.left < containerLeft) { + position.left = containerLeft + 10; + position.right = 'inherit'; + } + // outside top + if (position.top < containerTop) { + position.top = 10; + position.bottom = 'inherit'; + } + scope.tooltipStyles = position; + el.css(position); + } + activate(); + } + var directive = { + restrict: 'E', + transclude: true, + replace: true, + templateUrl: 'views/components/umb-tooltip.html', + scope: { event: '=' }, + link: link }; - - // element size - elementHeight = el.context.clientHeight; - elementWidth = el.context.clientWidth; - - position.left = event.pageX - (elementWidth / 2); - position.top = event.pageY; - - // check to see if element is outside screen - // outside right - if (position.left + elementWidth > containerRight) { - position.right = 10; - position.left = "inherit"; - } - - // outside bottom - if (position.top + elementHeight > containerBottom) { - position.bottom = 10; - position.top = "inherit"; - } - - // outside left - if (position.left < containerLeft) { - position.left = containerLeft + 10; - position.right = "inherit"; - } - - // outside top - if (position.top < containerTop) { - position.top = 10; - position.bottom = "inherit"; - } - - scope.tooltipStyles = position; - - el.css(position); - - } - - activate(); - - } - - var directive = { - restrict: 'E', - transclude: true, - replace: true, - templateUrl: 'views/components/umb-tooltip.html', - scope: { - event: "=" - }, - link: link - }; - - return directive; - } - - angular.module('umbraco.directives').directive('umbTooltip', TooltipDirective); - -})(); - -/** + return directive; + } + angular.module('umbraco.directives').directive('umbTooltip', TooltipDirective); + }()); + /** * @ngdoc directive * @name umbraco.directives.directive:umbFileDropzone * @restrict E @@ -9721,8 +11700,7 @@ Use this directive to render a tooltip. * @description * Used by editors that require naming an entity. Shows a textbox/headline with a required validator within it's own form. **/ - -/* + /* TODO .directive("umbFileDrop", function ($timeout, $upload, localizationService, umbRequestHelper){ return{ @@ -9733,244 +11711,218 @@ TODO } }) */ - -angular.module("umbraco.directives") - .directive('umbFileDropzone', - function($timeout, Upload, localizationService, umbRequestHelper) { - return { - restrict: 'E', - replace: true, - templateUrl: 'views/components/upload/umb-file-dropzone.html', - scope: { - parentId: '@', - contentTypeAlias: '@', - propertyAlias: '@', - accept: '@', - maxFileSize: '@', - - compact: '@', - hideDropzone: '@', - acceptedMediatypes: '=', - - filesQueued: '=', - handleFile: '=', - filesUploaded: '=' - }, - link: function(scope, element, attrs) { - scope.queue = []; - scope.done = []; - scope.rejected = []; - scope.currentFile = undefined; - - function _filterFile(file) { - var ignoreFileNames = ['Thumbs.db']; - var ignoreFileTypes = ['directory']; - - // ignore files with names from the list - // ignore files with types from the list - // ignore files which starts with "." - if (ignoreFileNames.indexOf(file.name) === -1 && - ignoreFileTypes.indexOf(file.type) === -1 && - file.name.indexOf(".") !== 0) { - return true; - } else { - return false; - } + angular.module('umbraco.directives').directive('umbFileDropzone', function ($timeout, Upload, localizationService, umbRequestHelper) { + return { + restrict: 'E', + replace: true, + templateUrl: 'views/components/upload/umb-file-dropzone.html', + scope: { + parentId: '@', + contentTypeAlias: '@', + propertyAlias: '@', + accept: '@', + maxFileSize: '@', + compact: '@', + hideDropzone: '@', + acceptedMediatypes: '=', + filesQueued: '=', + handleFile: '=', + filesUploaded: '=' + }, + link: function (scope, element, attrs) { + scope.queue = []; + scope.done = []; + scope.rejected = []; + scope.currentFile = undefined; + function _filterFile(file) { + var ignoreFileNames = ['Thumbs.db']; + var ignoreFileTypes = ['directory']; + // ignore files with names from the list + // ignore files with types from the list + // ignore files which starts with "." + if (ignoreFileNames.indexOf(file.name) === -1 && ignoreFileTypes.indexOf(file.type) === -1 && file.name.indexOf('.') !== 0) { + return true; + } else { + return false; } - - function _filesQueued(files, event) { - //Push into the queue - angular.forEach(files, - function(file) { - - if (_filterFile(file) === true) { - - if (file.$error) { - scope.rejected.push(file); - } else { - scope.queue.push(file); - } - } - }); - - //when queue is done, kick the uploader - if (!scope.working) { - // Upload not allowed - if (!scope.acceptedMediatypes || !scope.acceptedMediatypes.length) { - files.map(function(file) { - file.uploadStatus = "error"; - file.serverErrorMessage = "File type is not allowed here"; - scope.rejected.push(file); - }); - scope.queue = []; - } - // One allowed type - if (scope.acceptedMediatypes && scope.acceptedMediatypes.length === 1) { - // Standard setup - set alias to auto select to let the server best decide which media type to use - if (scope.acceptedMediatypes[0].alias === 'Image') { - scope.contentTypeAlias = "umbracoAutoSelect"; - } else { - scope.contentTypeAlias = scope.acceptedMediatypes[0].alias; - } - - _processQueueItem(); - } - // More than one, open dialog - if (scope.acceptedMediatypes && scope.acceptedMediatypes.length > 1) { - _chooseMediaType(); - } - } - } - - function _processQueueItem() { - if (scope.queue.length > 0) { - scope.currentFile = scope.queue.shift(); - _upload(scope.currentFile); - } else if (scope.done.length > 0) { - if (scope.filesUploaded) { - //queue is empty, trigger the done action - scope.filesUploaded(scope.done); - } - - //auto-clear the done queue after 3 secs - var currentLength = scope.done.length; - $timeout(function() { - scope.done.splice(0, currentLength); - }, - 3000); - } - } - - function _upload(file) { - - scope.propertyAlias = scope.propertyAlias ? scope.propertyAlias : "umbracoFile"; - scope.contentTypeAlias = scope.contentTypeAlias ? scope.contentTypeAlias : "Image"; - - Upload.upload({ - url: umbRequestHelper.getApiUrl("mediaApiBaseUrl", "PostAddFile"), - fields: { - 'currentFolder': scope.parentId, - 'contentTypeAlias': scope.contentTypeAlias, - 'propertyAlias': scope.propertyAlias, - 'path': file.path - }, - file: file - }) - .progress(function(evt) { - // calculate progress in percentage - var progressPercentage = parseInt(100.0 * evt.loaded / evt.total, 10); - // set percentage property on file - file.uploadProgress = progressPercentage; - // set uploading status on file - file.uploadStatus = "uploading"; - }) - .success(function(data, status, headers, config) { - if (data.notifications && data.notifications.length > 0) { - // set error status on file - file.uploadStatus = "error"; - // Throw message back to user with the cause of the error - file.serverErrorMessage = data.notifications[0].message; - // Put the file in the rejected pool - scope.rejected.push(file); - } else { - // set done status on file - file.uploadStatus = "done"; - // set date/time for when done - used for sorting - file.doneDate = new Date(); - // Put the file in the done pool - scope.done.push(file); - } - scope.currentFile = undefined; - //after processing, test if everthing is done - _processQueueItem(); - }) - .error(function(evt, status, headers, config) { - // set status done - file.uploadStatus = "error"; - //if the service returns a detailed error - if (evt.InnerException) { - file.serverErrorMessage = evt.InnerException.ExceptionMessage; - //Check if its the common "too large file" exception - if (evt.InnerException.StackTrace && - evt.InnerException.StackTrace.indexOf("ValidateRequestEntityLength") > 0) { - file.serverErrorMessage = "File too large to upload"; - } - } else if (evt.Message) { - file.serverErrorMessage = evt.Message; - } - // If file not found, server will return a 404 and display this message - if (status === 404) { - file.serverErrorMessage = "File not found"; - } - //after processing, test if everthing is done + } + function _filesQueued(files, event) { + //Push into the queue + angular.forEach(files, function (file) { + if (_filterFile(file) === true) { + if (file.$error) { scope.rejected.push(file); - scope.currentFile = undefined; - _processQueueItem(); - }); - } - - function _chooseMediaType() { - scope.mediatypepickerOverlay = { - view: "mediatypepicker", - title: "Choose media type", - acceptedMediatypes: scope.acceptedMediatypes, - hideSubmitButton: true, - show: true, - submit: function(model) { - scope.contentTypeAlias = model.selectedType.alias; - scope.mediatypepickerOverlay.show = false; - scope.mediatypepickerOverlay = null; - _processQueueItem(); - }, - close: function(oldModel) { - - scope.queue.map(function(file) { - file.uploadStatus = "error"; - file.serverErrorMessage = "Cannot upload this file, no mediatype selected"; - scope.rejected.push(file); - }); - scope.queue = []; - scope.mediatypepickerOverlay.show = false; - scope.mediatypepickerOverlay = null; + } else { + scope.queue.push(file); } - }; - } - - scope.handleFiles = function(files, event) { - if (scope.filesQueued) { - scope.filesQueued(files, event); } - _filesQueued(files, event); + }); + //when queue is done, kick the uploader + if (!scope.working) { + // Upload not allowed + if (!scope.acceptedMediatypes || !scope.acceptedMediatypes.length) { + files.map(function (file) { + file.uploadStatus = 'error'; + file.serverErrorMessage = 'File type is not allowed here'; + scope.rejected.push(file); + }); + scope.queue = []; + } + // One allowed type + if (scope.acceptedMediatypes && scope.acceptedMediatypes.length === 1) { + // Standard setup - set alias to auto select to let the server best decide which media type to use + if (scope.acceptedMediatypes[0].alias === 'Image') { + scope.contentTypeAlias = 'umbracoAutoSelect'; + } else { + scope.contentTypeAlias = scope.acceptedMediatypes[0].alias; + } + _processQueueItem(); + } + // More than one, open dialog + if (scope.acceptedMediatypes && scope.acceptedMediatypes.length > 1) { + _chooseMediaType(); + } + } + } + function _processQueueItem() { + if (scope.queue.length > 0) { + scope.currentFile = scope.queue.shift(); + _upload(scope.currentFile); + } else if (scope.done.length > 0) { + if (scope.filesUploaded) { + //queue is empty, trigger the done action + scope.filesUploaded(scope.done); + } + //auto-clear the done queue after 3 secs + var currentLength = scope.done.length; + $timeout(function () { + scope.done.splice(0, currentLength); + }, 3000); + } + } + function _upload(file) { + scope.propertyAlias = scope.propertyAlias ? scope.propertyAlias : 'umbracoFile'; + scope.contentTypeAlias = scope.contentTypeAlias ? scope.contentTypeAlias : 'Image'; + Upload.upload({ + url: umbRequestHelper.getApiUrl('mediaApiBaseUrl', 'PostAddFile'), + fields: { + 'currentFolder': scope.parentId, + 'contentTypeAlias': scope.contentTypeAlias, + 'propertyAlias': scope.propertyAlias, + 'path': file.path + }, + file: file + }).progress(function (evt) { + if (file.uploadStat !== 'done' && file.uploadStat !== 'error') { + // calculate progress in percentage + var progressPercentage = parseInt(100 * evt.loaded / evt.total, 10); + // set percentage property on file + file.uploadProgress = progressPercentage; + // set uploading status on file + file.uploadStatus = 'uploading'; + } + }).success(function (data, status, headers, config) { + if (data.notifications && data.notifications.length > 0) { + // set error status on file + file.uploadStatus = 'error'; + // Throw message back to user with the cause of the error + file.serverErrorMessage = data.notifications[0].message; + // Put the file in the rejected pool + scope.rejected.push(file); + } else { + // set done status on file + file.uploadStatus = 'done'; + file.uploadProgress = 100; + // set date/time for when done - used for sorting + file.doneDate = new Date(); + // Put the file in the done pool + scope.done.push(file); + } + scope.currentFile = undefined; + //after processing, test if everthing is done + _processQueueItem(); + }).error(function (evt, status, headers, config) { + // set status done + file.uploadStatus = 'error'; + //if the service returns a detailed error + if (evt.InnerException) { + file.serverErrorMessage = evt.InnerException.ExceptionMessage; + //Check if its the common "too large file" exception + if (evt.InnerException.StackTrace && evt.InnerException.StackTrace.indexOf('ValidateRequestEntityLength') > 0) { + file.serverErrorMessage = 'File too large to upload'; + } + } else if (evt.Message) { + file.serverErrorMessage = evt.Message; + } + // If file not found, server will return a 404 and display this message + if (status === 404) { + file.serverErrorMessage = 'File not found'; + } + //after processing, test if everthing is done + scope.rejected.push(file); + scope.currentFile = undefined; + _processQueueItem(); + }); + } + function _chooseMediaType() { + scope.mediatypepickerOverlay = { + view: 'mediatypepicker', + title: 'Choose media type', + acceptedMediatypes: scope.acceptedMediatypes, + hideSubmitButton: true, + show: true, + submit: function (model) { + scope.contentTypeAlias = model.selectedType.alias; + scope.mediatypepickerOverlay.show = false; + scope.mediatypepickerOverlay = null; + _processQueueItem(); + }, + close: function (oldModel) { + scope.queue.map(function (file) { + file.uploadStatus = 'error'; + file.serverErrorMessage = 'Cannot upload this file, no mediatype selected'; + scope.rejected.push(file); + }); + scope.queue = []; + scope.mediatypepickerOverlay.show = false; + scope.mediatypepickerOverlay = null; + } }; } - }; - }); - -/** -* @ngdoc directive -* @name umbraco.directives.directive:umbFileUpload -* @function -* @restrict A -* @scope -* @description -* Listens for file input control changes and emits events when files are selected for use in other controllers. -**/ -function umbFileUpload() { - return { - restrict: "A", - scope: true, //create a new scope - link: function (scope, el, attrs) { - el.bind('change', function (event) { - var files = event.target.files; - //emit event upward - scope.$emit("filesSelected", { files: files }); - }); - } - }; -} - -angular.module('umbraco.directives').directive("umbFileUpload", umbFileUpload); -/** + scope.handleFiles = function (files, event) { + if (scope.filesQueued) { + scope.filesQueued(files, event); + } + _filesQueued(files, event); + }; + } + }; + }); + /** +* @ngdoc directive +* @name umbraco.directives.directive:umbFileUpload +* @function +* @restrict A +* @scope +* @description +* Listens for file input control changes and emits events when files are selected for use in other controllers. +**/ + function umbFileUpload() { + return { + restrict: 'A', + scope: true, + //create a new scope + link: function (scope, el, attrs) { + el.bind('change', function (event) { + var files = event.target.files; + //emit event upward + scope.$emit('filesSelected', { files: files }); + }); + } + }; + } + angular.module('umbraco.directives').directive('umbFileUpload', umbFileUpload); + /** * @ngdoc directive * @name umbraco.directives.directive:umbSingleFileUpload * @function @@ -9980,140 +11932,532 @@ angular.module('umbraco.directives').directive("umbFileUpload", umbFileUpload); * A single file upload field that will reset itself based on the object passed in for the rebuild parameter. This * is required because the only way to reset an upload control is to replace it's html. **/ -function umbSingleFileUpload($compile) { - return { - restrict: "E", - scope: { - rebuild: "=" - }, - replace: true, - template: "
    ", - link: function (scope, el, attrs) { - - scope.$watch("rebuild", function (newVal, oldVal) { - if (newVal && newVal !== oldVal) { - //recompile it! - el.html(""); - $compile(el.contents())(scope); + function umbSingleFileUpload($compile) { + return { + restrict: 'E', + scope: { rebuild: '=' }, + replace: true, + template: '
    ', + link: function (scope, el, attrs) { + scope.$watch('rebuild', function (newVal, oldVal) { + if (newVal && newVal !== oldVal) { + //recompile it! + el.html(''); + $compile(el.contents())(scope); + } + }); + } + }; + } + angular.module('umbraco.directives').directive('umbSingleFileUpload', umbSingleFileUpload); + (function () { + 'use strict'; + function ChangePasswordController($scope) { + function resetModel(isNew) { + //the model config will contain an object, if it does not we'll create defaults + //NOTE: We will not support doing the password regex on the client side because the regex on the server side + //based on the membership provider cannot always be ported to js from .net directly. + /* + { + hasPassword: true/false, + requiresQuestionAnswer: true/false, + enableReset: true/false, + enablePasswordRetrieval: true/false, + minPasswordLength: 10 + } + */ + $scope.showReset = false; + //set defaults if they are not available + if ($scope.config.disableToggle === undefined) { + $scope.config.disableToggle = false; + } + if ($scope.config.hasPassword === undefined) { + $scope.config.hasPassword = false; + } + if ($scope.config.enablePasswordRetrieval === undefined) { + $scope.config.enablePasswordRetrieval = true; + } + if ($scope.config.requiresQuestionAnswer === undefined) { + $scope.config.requiresQuestionAnswer = false; + } + //don't enable reset if it is new - that doesn't make sense + if (isNew === 'true') { + $scope.config.enableReset = false; + } else if ($scope.config.enableReset === undefined) { + $scope.config.enableReset = true; + } + if ($scope.config.minPasswordLength === undefined) { + $scope.config.minPasswordLength = 0; + } + //set the model defaults + if (!angular.isObject($scope.passwordValues)) { + //if it's not an object then just create a new one + $scope.passwordValues = { + newPassword: null, + oldPassword: null, + reset: null, + answer: null + }; + } else { + //just reset the values + if (!isNew) { + //if it is new, then leave the generated pass displayed + $scope.passwordValues.newPassword = null; + $scope.passwordValues.oldPassword = null; + } + $scope.passwordValues.reset = null; + $scope.passwordValues.answer = null; + } + //the value to compare to match passwords + if (!isNew) { + $scope.passwordValues.confirm = ''; + } else if ($scope.passwordValues.newPassword && $scope.passwordValues.newPassword.length > 0) { + //if it is new and a new password has been set, then set the confirm password too + $scope.passwordValues.confirm = $scope.passwordValues.newPassword; + } + } + resetModel($scope.isNew); + //if there is no password saved for this entity , it must be new so we do not allow toggling of the change password, it is always there + //with validators turned on. + $scope.changing = $scope.config.disableToggle === true || !$scope.config.hasPassword; + //we're not currently changing so set the model to null + if (!$scope.changing) { + $scope.passwordValues = null; + } + $scope.doChange = function () { + resetModel(); + $scope.changing = true; + //if there was a previously generated password displaying, clear it + $scope.passwordValues.generatedPassword = null; + $scope.passwordValues.confirm = null; + }; + $scope.cancelChange = function () { + $scope.changing = false; + //set model to null + $scope.passwordValues = null; + }; + var unsubscribe = []; + //listen for the saved event, when that occurs we'll + //change to changing = false; + unsubscribe.push($scope.$on('formSubmitted', function () { + if ($scope.config.disableToggle === false) { + $scope.changing = false; + } + })); + unsubscribe.push($scope.$on('formSubmitting', function () { + //if there was a previously generated password displaying, clear it + if ($scope.changing && $scope.passwordValues) { + $scope.passwordValues.generatedPassword = null; + } else if (!$scope.changing) { + //we are not changing, so the model needs to be null + $scope.passwordValues = null; + } + })); + //when the scope is destroyed we need to unsubscribe + $scope.$on('$destroy', function () { + for (var u in unsubscribe) { + unsubscribe[u](); } }); - + $scope.showOldPass = function () { + return $scope.config.hasPassword && !$scope.config.allowManuallyChangingPassword && !$scope.config.enablePasswordRetrieval && !$scope.showReset; + }; + //TODO: I don't think we need this or the cancel button, this can be up to the editor rendering this directive + $scope.showCancelBtn = function () { + return $scope.config.disableToggle !== true && $scope.config.hasPassword; + }; } - }; -} + function ChangePasswordDirective() { + var directive = { + restrict: 'E', + replace: true, + templateUrl: 'views/components/users/change-password.html', + controller: 'Umbraco.Editors.Users.ChangePasswordDirectiveController', + scope: { + isNew: '=?', + passwordValues: '=', + config: '=' + } + }; + return directive; + } + angular.module('umbraco.directives').controller('Umbraco.Editors.Users.ChangePasswordDirectiveController', ChangePasswordController); + angular.module('umbraco.directives').directive('changePassword', ChangePasswordDirective); + }()); + (function () { + 'use strict'; + function PermissionDirective() { + function link(scope, el, attr, ctrl) { + scope.change = function () { + scope.selected = !scope.selected; + if (scope.onChange) { + scope.onChange({ 'selected': scope.selected }); + } + }; + } + var directive = { + restrict: 'E', + replace: true, + templateUrl: 'views/components/users/umb-permission.html', + scope: { + name: '=', + description: '=?', + selected: '=', + onChange: '&' + }, + link: link + }; + return directive; + } + angular.module('umbraco.directives').directive('umbPermission', PermissionDirective); + }()); + /** +@ngdoc directive +@name umbraco.directives.directive:umbUserGroupPreview +@restrict E +@scope -angular.module('umbraco.directives').directive("umbSingleFileUpload", umbSingleFileUpload); -/** +@description +Use this directive to render a user group preview, where you can see the permissions the user or group has in the back office. + +

    Markup example

    +
    +    
    + + +
    +
    + +@param {string} icon (binding): The user group icon. +@param {string} name (binding): The user group name. +@param {array} sections (binding) Lists out the sections where the user has authority to edit. +@param {string} contentStartNode (binding) +
      +
    • The starting point in the tree of the content section.
    • +
    • So the user has no authority to work on other branches, only on this branch in the content section.
    • +
    +@param {boolean} hideContentStartNode (binding) Hides the contentStartNode. +@param {string} mediaStartNode (binding) +
      +
    • The starting point in the tree of the media section.
    • +
    • So the user has no authority to work on other branches, only on this branch in the media section.
    • +
    +@param {boolean} hideMediaStartNode (binding) Hides the mediaStartNode. +@param {array} permissions (binding) A list of permissions, the user can have. +@param {boolean} allowRemove (binding): Shows or Hides the remove button. +@param {function} onRemove (expression): Callback function when the remove button is clicked. +@param {boolean} allowEdit (binding): Shows or Hides the edit button. +@param {function} onEdit (expression): Callback function when the edit button is clicked. +**/ + (function () { + 'use strict'; + function UserGroupPreviewDirective() { + function link(scope, el, attr, ctrl) { + } + var directive = { + restrict: 'E', + replace: true, + templateUrl: 'views/components/users/umb-user-group-preview.html', + scope: { + icon: '=?', + name: '=', + sections: '=?', + contentStartNode: '=?', + hideContentStartNode: '@?', + mediaStartNode: '=?', + hideMediaStartNode: '@?', + permissions: '=?', + allowRemove: '=?', + allowEdit: '=?', + onRemove: '&?', + onEdit: '&?' + }, + link: link + }; + return directive; + } + angular.module('umbraco.directives').directive('umbUserGroupPreview', UserGroupPreviewDirective); + }()); + (function () { + 'use strict'; + function UserPreviewDirective() { + function link(scope, el, attr, ctrl) { + } + var directive = { + restrict: 'E', + replace: true, + templateUrl: 'views/components/users/umb-user-preview.html', + scope: { + avatars: '=?', + name: '=', + allowRemove: '=?', + onRemove: '&?' + }, + link: link + }; + return directive; + } + angular.module('umbraco.directives').directive('umbUserPreview', UserPreviewDirective); + }()); + /** * Konami Code directive for AngularJS * @version v0.0.1 - * @license MIT License, http://www.opensource.org/licenses/MIT + * @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; - - /** + 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); - } - - /** + 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; - } - } - - /** + 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); - } + 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); + } + }; + } + ]); + /** +@ngdoc directive +@name umbraco.directives.directive:umbKeyboardList +@restrict E - // Start listening to key typing. - $document.on('keydown', keydown); +@description +Added in versions 7.7.0: Use this directive to add arrow up and down keyboard shortcuts to a list. Use this together with the {@link umbraco.directives.directive:umbDropdown umbDropdown} component to make easy accessible dropdown menus. - // Stop listening when scope is destroyed. - scope.$on('$destroy', stopListening); - } - }; - }]); -/** +

    Markup example

    +
    +    
    + +
    +
    + +

    Use in combination with

    +
      +
    • {@link umbraco.directives.directive:umbDropdown umbDropdown}
    • +
    + +**/ + angular.module('umbraco.directives').directive('umbKeyboardList', [ + '$document', + '$timeout', + function ($document, $timeout) { + return { + restrict: 'A', + link: function (scope, element, attr) { + var listItems = []; + var currentIndex = 0; + var focusSet = false; + $timeout(function () { + // get list of all links in the list + listItems = element.find('li a'); + }); + // Handle keydown events + function keydown(event) { + $timeout(function () { + checkFocus(); + // arrow down + if (event.keyCode === 40) { + arrowDown(); + } + // arrow up + if (event.keyCode === 38) { + arrowUp(); + } + }); + } + function checkFocus() { + var found = false; + // check if any element has focus + angular.forEach(listItems, function (item, index) { + if ($(item).is(':focus')) { + // if an element already has focus set the + // currentIndex so we navigate from that element + currentIndex = index; + focusSet = true; + found = true; + } + }); + // If we don't find an element with focus we reset the currentIndex and the focusSet flag + // we do this because you can have navigated away from the list with tab and we want to reset it if you navigate back + if (!found) { + currentIndex = 0; + focusSet = false; + } + } + function arrowDown() { + if (currentIndex < listItems.length - 1) { + // only bump the current index if the focus is already + // set else we just want to focus the first element + if (focusSet) { + currentIndex++; + } + listItems[currentIndex].focus(); + focusSet = true; + } + } + function arrowUp() { + if (currentIndex > 0) { + currentIndex--; + listItems[currentIndex].focus(); + } + } + // 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); + } + }; + } + ]); + /** * @ngdoc directive * @name umbraco.directives.directive:noDirtyCheck * @restrict A -* @description Can be attached to form inputs to prevent them from setting the form as dirty (http://stackoverflow.com/questions/17089090/prevent-input-from-setting-form-dirty-angularjs) +* @description Can be attached to form inputs to prevent them from setting the form as dirty (https://stackoverflow.com/questions/17089090/prevent-input-from-setting-form-dirty-angularjs) **/ -function noDirtyCheck() { - return { - restrict: 'A', - require: 'ngModel', - link: function (scope, elm, attrs, ctrl) { - elm.focus(function () { - ctrl.$pristine = false; - }); - } - }; -} -angular.module('umbraco.directives.validation').directive("noDirtyCheck", noDirtyCheck); -(function() { - 'use strict'; - - function SetDirtyOnChange() { - - function link(scope, el, attr, ctrl) { - - var initValue = attr.umbSetDirtyOnChange; - - attr.$observe("umbSetDirtyOnChange", function (newValue) { - if(newValue !== initValue) { - ctrl.$setDirty(); - } - }); - - } - - var directive = { - require: "^form", + function noDirtyCheck() { + return { restrict: 'A', - link: link + require: 'ngModel', + link: function (scope, elm, attrs, ctrl) { + var alwaysFalse = { + get: function () { + return false; + }, + set: function () { + } + }; + Object.defineProperty(ctrl, '$pristine', alwaysFalse); + Object.defineProperty(ctrl, '$dirty', alwaysFalse); + } }; - - return directive; } - - angular.module('umbraco.directives').directive('umbSetDirtyOnChange', SetDirtyOnChange); - -})(); - -/** + angular.module('umbraco.directives.validation').directive('noDirtyCheck', noDirtyCheck); + (function () { + 'use strict'; + function SetDirtyOnChange() { + function link(scope, el, attr, ctrl) { + if (attr.ngModel) { + scope.$watch(attr.ngModel, function (newValue, oldValue) { + if (!newValue) { + return; + } + if (newValue === oldValue) { + return; + } + ctrl.$setDirty(); + }, true); + } else { + var initValue = attr.umbSetDirtyOnChange; + attr.$observe('umbSetDirtyOnChange', function (newValue) { + if (newValue !== initValue) { + ctrl.$setDirty(); + } + }); + } + } + var directive = { + require: '^form', + restrict: 'A', + link: link + }; + return directive; + } + angular.module('umbraco.directives').directive('umbSetDirtyOnChange', SetDirtyOnChange); + }()); + angular.module('umbraco.directives.validation').directive('valCompare', function () { + return { + require: [ + 'ngModel', + '^form' + ], + link: function (scope, elem, attrs, ctrls) { + var ctrl = ctrls[0]; + var formCtrl = ctrls[1]; + var otherInput = formCtrl[attrs.valCompare]; + ctrl.$parsers.push(function (value) { + if (value === otherInput.$viewValue) { + ctrl.$setValidity('valCompare', true); + return value; + } + ctrl.$setValidity('valCompare', false); + }); + otherInput.$parsers.push(function (value) { + ctrl.$setValidity('valCompare', value === ctrl.$viewValue); + return value; + }); + } + }; + }); + /** * General-purpose validator for ngModel. * angular.js comes with several built-in validation mechanism for input fields (ngRequired, ngPattern etc.) but using * an arbitrary validation function requires creation of a custom formatters and / or parsers. @@ -10129,221 +12473,139 @@ angular.module('umbraco.directives.validation').directive("noDirtyCheck", noDirt * If an object literal is passed a key denotes a validation error key while a value should be a validator function. * In both cases validator function should take a value to validate as its argument and should return true/false indicating a validation result. */ - - /* + /* This code comes from the angular UI project, we had to change the directive name and module but other then that its unmodified */ -angular.module('umbraco.directives.validation') -.directive('valCustom', function () { - - return { - restrict: 'A', - require: 'ngModel', - link: function (scope, elm, attrs, ctrl) { - var validateFn, watch, validators = {}, - validateExpr = scope.$eval(attrs.valCustom); - - if (!validateExpr){ return;} - - if (angular.isString(validateExpr)) { - validateExpr = { validator: validateExpr }; - } - - angular.forEach(validateExpr, function (exprssn, key) { - validateFn = function (valueToValidate) { - var expression = scope.$eval(exprssn, { '$value' : valueToValidate }); - if (angular.isObject(expression) && angular.isFunction(expression.then)) { - // expression is a promise - expression.then(function(){ - ctrl.$setValidity(key, true); - }, function(){ - ctrl.$setValidity(key, false); - }); - return valueToValidate; - } else if (expression) { - // expression is true - ctrl.$setValidity(key, true); - return valueToValidate; - } else { - // expression is false - ctrl.$setValidity(key, false); - return undefined; - } + angular.module('umbraco.directives.validation').directive('valCustom', function () { + return { + restrict: 'A', + require: 'ngModel', + link: function (scope, elm, attrs, ctrl) { + var validateFn, watch, validators = {}, validateExpr = scope.$eval(attrs.valCustom); + if (!validateExpr) { + return; + } + if (angular.isString(validateExpr)) { + validateExpr = { validator: validateExpr }; + } + angular.forEach(validateExpr, function (exprssn, key) { + validateFn = function (valueToValidate) { + var expression = scope.$eval(exprssn, { '$value': valueToValidate }); + if (angular.isObject(expression) && angular.isFunction(expression.then)) { + // expression is a promise + expression.then(function () { + ctrl.$setValidity(key, true); + }, function () { + ctrl.$setValidity(key, false); + }); + return valueToValidate; + } else if (expression) { + // expression is true + ctrl.$setValidity(key, true); + return valueToValidate; + } else { + // expression is false + ctrl.$setValidity(key, false); + return undefined; + } + }; + validators[key] = validateFn; + ctrl.$parsers.push(validateFn); + }); + function apply_watch(watch) { + //string - update all validators on expression change + if (angular.isString(watch)) { + scope.$watch(watch, function () { + angular.forEach(validators, function (validatorFn) { + validatorFn(ctrl.$modelValue); + }); + }); + return; + } + //array - update all validators on change of any expression + if (angular.isArray(watch)) { + angular.forEach(watch, function (expression) { + scope.$watch(expression, function () { + angular.forEach(validators, function (validatorFn) { + validatorFn(ctrl.$modelValue); + }); + }); + }); + return; + } + //object - update appropriate validator + if (angular.isObject(watch)) { + angular.forEach(watch, function (expression, validatorKey) { + //value is string - look after one expression + if (angular.isString(expression)) { + scope.$watch(expression, function () { + validators[validatorKey](ctrl.$modelValue); + }); + } + //value is array - look after all expressions in array + if (angular.isArray(expression)) { + angular.forEach(expression, function (intExpression) { + scope.$watch(intExpression, function () { + validators[validatorKey](ctrl.$modelValue); + }); + }); + } + }); + } + } + // Support for val-custom-watch + if (attrs.valCustomWatch) { + apply_watch(scope.$eval(attrs.valCustomWatch)); + } + } }; - validators[key] = validateFn; - - ctrl.$parsers.push(validateFn); - }); - - function apply_watch(watch) - { - //string - update all validators on expression change - if (angular.isString(watch)) - { - scope.$watch(watch, function(){ - angular.forEach(validators, function(validatorFn){ - validatorFn(ctrl.$modelValue); - }); - }); - return; - } - - //array - update all validators on change of any expression - if (angular.isArray(watch)) - { - angular.forEach(watch, function(expression){ - scope.$watch(expression, function() - { - angular.forEach(validators, function(validatorFn){ - validatorFn(ctrl.$modelValue); - }); - }); - }); - return; - } - - //object - update appropriate validator - if (angular.isObject(watch)) - { - angular.forEach(watch, function(expression, validatorKey) - { - //value is string - look after one expression - if (angular.isString(expression)) - { - scope.$watch(expression, function(){ - validators[validatorKey](ctrl.$modelValue); - }); - } - - //value is array - look after all expressions in array - if (angular.isArray(expression)) - { - angular.forEach(expression, function(intExpression) - { - scope.$watch(intExpression, function(){ - validators[validatorKey](ctrl.$modelValue); - }); - }); - } - }); - } - } - // Support for val-custom-watch - if (attrs.valCustomWatch){ - apply_watch( scope.$eval(attrs.valCustomWatch) ); - } - } - }; -}); -/** -* @ngdoc directive -* @name umbraco.directives.directive:valHighlight -* @restrict A -* @description Used on input fields when you want to signal that they are in error, this will highlight the item for 1 second -**/ -function valHighlight($timeout) { - return { - restrict: "A", - link: function (scope, element, attrs, ctrl) { - - attrs.$observe("valHighlight", function (newVal) { - if (newVal === "true") { - element.addClass("highlight-error"); - $timeout(function () { - //set the bound scope property to false - scope[attrs.valHighlight] = false; - }, 1000); - } - else { - element.removeClass("highlight-error"); - } - }); - - } - }; -} -angular.module('umbraco.directives.validation').directive("valHighlight", valHighlight); - -angular.module('umbraco.directives.validation') - .directive('valCompare',function () { - return { - require: "ngModel", - link: function (scope, elem, attrs, ctrl) { - - //TODO: Pretty sure this should be done using a requires ^form in the directive declaration - var otherInput = elem.inheritedData("$formController")[attrs.valCompare]; - - ctrl.$parsers.push(function(value) { - if(value === otherInput.$viewValue) { - ctrl.$setValidity("valCompare", true); - return value; - } - ctrl.$setValidity("valCompare", false); - }); - - otherInput.$parsers.push(function(value) { - ctrl.$setValidity("valCompare", value === ctrl.$viewValue); - return value; - }); - } - }; -}); -/** + }); + /** * @ngdoc directive * @name umbraco.directives.directive:valEmail * @restrict A * @description A custom directive to validate an email address string, this is required because angular's default validator is incorrect. **/ -function valEmail(valEmailExpression) { - - return { - require: 'ngModel', - restrict: "A", - link: function (scope, elm, attrs, ctrl) { - - var patternValidator = function (viewValue) { - //NOTE: we don't validate on empty values, use required validator for that - if (!viewValue || valEmailExpression.EMAIL_REGEXP.test(viewValue)) { - // it is valid - ctrl.$setValidity('valEmail', true); - //assign a message to the validator - ctrl.errorMsg = ""; - return viewValue; - } - else { - // it is invalid, return undefined (no model update) - ctrl.$setValidity('valEmail', false); - //assign a message to the validator - ctrl.errorMsg = "Invalid email"; - return undefined; - } - }; - - //if there is an attribute: type="email" then we need to remove those formatters and parsers - if (attrs.type === "email") { - //we need to remove the existing parsers = the default angular one which is created by - // type="email", but this has a regex issue, so we'll remove that and add our custom one - ctrl.$parsers.pop(); - //we also need to remove the existing formatter - the default angular one will not render - // what it thinks is an invalid email address, so it will just be blank - ctrl.$formatters.pop(); - } - - ctrl.$parsers.push(patternValidator); - } - }; -} - -angular.module('umbraco.directives.validation') - .directive("valEmail", valEmail) - .factory('valEmailExpression', function () { - //NOTE: This is the fixed regex which is part of the newer angular + function valEmail(valEmailExpression) { return { - EMAIL_REGEXP: /^[a-z0-9!#$%&'*+\/=?^_`{|}~.-]+@[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i + require: 'ngModel', + restrict: 'A', + link: function (scope, elm, attrs, ctrl) { + var patternValidator = function (viewValue) { + //NOTE: we don't validate on empty values, use required validator for that + if (!viewValue || valEmailExpression.EMAIL_REGEXP.test(viewValue)) { + // it is valid + ctrl.$setValidity('valEmail', true); + //assign a message to the validator + ctrl.errorMsg = ''; + return viewValue; + } else { + // it is invalid, return undefined (no model update) + ctrl.$setValidity('valEmail', false); + //assign a message to the validator + ctrl.errorMsg = 'Invalid email'; + return undefined; + } + }; + //if there is an attribute: type="email" then we need to remove those formatters and parsers + if (attrs.type === 'email') { + //we need to remove the existing parsers = the default angular one which is created by + // type="email", but this has a regex issue, so we'll remove that and add our custom one + ctrl.$parsers.pop(); + //we also need to remove the existing formatter - the default angular one will not render + // what it thinks is an invalid email address, so it will just be blank + ctrl.$formatters.pop(); + } + ctrl.$parsers.push(patternValidator); + } }; - }); -/** + } + angular.module('umbraco.directives.validation').directive('valEmail', valEmail).factory('valEmailExpression', function () { + var emailRegex = /^[a-z0-9!#$%&'*+\/=?^_`{|}~.-]+@[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i; + return { EMAIL_REGEXP: emailRegex }; + }); + /** * @ngdoc directive * @name umbraco.directives.directive:valFormManager * @restrict A @@ -10357,126 +12619,137 @@ angular.module('umbraco.directives.validation') * Another thing this directive does is to ensure that any .control-group that contains form elements that are invalid will * be marked with the 'error' css class. This ensures that labels included in that control group are styled correctly. **/ -function valFormManager(serverValidationManager, $rootScope, $log, $timeout, notificationsService, eventsService, $routeParams) { - return { - require: "form", - restrict: "A", - controller: function($scope) { - //This exposes an API for direct use with this directive - - var unsubscribe = []; - var self = this; - - //This is basically the same as a directive subscribing to an event but maybe a little - // nicer since the other directive can use this directive's API instead of a magical event - this.onValidationStatusChanged = function (cb) { - unsubscribe.push($scope.$on("valStatusChanged", function(evt, args) { - cb.apply(self, [evt, args]); + function valFormManager(serverValidationManager, $rootScope, $log, $timeout, notificationsService, eventsService, $routeParams) { + return { + require: 'form', + restrict: 'A', + controller: function ($scope) { + //This exposes an API for direct use with this directive + var unsubscribe = []; + var self = this; + //This is basically the same as a directive subscribing to an event but maybe a little + // nicer since the other directive can use this directive's API instead of a magical event + this.onValidationStatusChanged = function (cb) { + unsubscribe.push($scope.$on('valStatusChanged', function (evt, args) { + cb.apply(self, [ + evt, + args + ]); + })); + }; + //Ensure to remove the event handlers when this instance is destroyted + $scope.$on('$destroy', function () { + for (var u in unsubscribe) { + unsubscribe[u](); + } + }); + }, + link: function (scope, element, attr, formCtrl) { + scope.$watch(function () { + return formCtrl.$error; + }, function (e) { + scope.$broadcast('valStatusChanged', { form: formCtrl }); + //find all invalid elements' .control-group's and apply the error class + var inError = element.find('.control-group .ng-invalid').closest('.control-group'); + inError.addClass('error'); + //find all control group's that have no error and ensure the class is removed + var noInError = element.find('.control-group .ng-valid').closest('.control-group').not(inError); + noInError.removeClass('error'); + }, true); + var className = attr.valShowValidation ? attr.valShowValidation : 'show-validation'; + var savingEventName = attr.savingEvent ? attr.savingEvent : 'formSubmitting'; + var savedEvent = attr.savedEvent ? attr.savingEvent : 'formSubmitted'; + //This tracks if the user is currently saving a new item, we use this to determine + // if we should display the warning dialog that they are leaving the page - if a new item + // is being saved we never want to display that dialog, this will also cause problems when there + // are server side validation issues. + var isSavingNewItem = false; + //we should show validation if there are any msgs in the server validation collection + if (serverValidationManager.items.length > 0) { + element.addClass(className); + } + var unsubscribe = []; + //listen for the forms saving event + unsubscribe.push(scope.$on(savingEventName, function (ev, args) { + element.addClass(className); + //set the flag so we can check to see if we should display the error. + isSavingNewItem = $routeParams.create; })); - }; - - //Ensure to remove the event handlers when this instance is destroyted - $scope.$on('$destroy', function () { - for (var u in unsubscribe) { - unsubscribe[u](); - } - }); - }, - link: function (scope, element, attr, formCtrl) { - - scope.$watch(function () { - return formCtrl.$error; - }, function (e) { - scope.$broadcast("valStatusChanged", { form: formCtrl }); - - //find all invalid elements' .control-group's and apply the error class - var inError = element.find(".control-group .ng-invalid").closest(".control-group"); - inError.addClass("error"); - - //find all control group's that have no error and ensure the class is removed - var noInError = element.find(".control-group .ng-valid").closest(".control-group").not(inError); - noInError.removeClass("error"); - - }, true); - - var className = attr.valShowValidation ? attr.valShowValidation : "show-validation"; - var savingEventName = attr.savingEvent ? attr.savingEvent : "formSubmitting"; - var savedEvent = attr.savedEvent ? attr.savingEvent : "formSubmitted"; - - //This tracks if the user is currently saving a new item, we use this to determine - // if we should display the warning dialog that they are leaving the page - if a new item - // is being saved we never want to display that dialog, this will also cause problems when there - // are server side validation issues. - var isSavingNewItem = false; - - //we should show validation if there are any msgs in the server validation collection - if (serverValidationManager.items.length > 0) { - element.addClass(className); + //listen for the forms saved event + unsubscribe.push(scope.$on(savedEvent, function (ev, args) { + //remove validation class + element.removeClass(className); + //clear form state as at this point we retrieve new data from the server + //and all validation will have cleared at this point + formCtrl.$setPristine(); + })); + //This handles the 'unsaved changes' dialog which is triggered when a route is attempting to be changed but + // the form has pending changes + var locationEvent = $rootScope.$on('$locationChangeStart', function (event, nextLocation, currentLocation) { + if (!formCtrl.$dirty || isSavingNewItem) { + return; + } + var path = nextLocation.split('#')[1]; + if (path) { + if (path.indexOf('%253') || path.indexOf('%252')) { + path = decodeURIComponent(path); + } + if (!notificationsService.hasView()) { + var msg = { + view: 'confirmroutechange', + args: { + path: path, + listener: locationEvent + } + }; + notificationsService.add(msg); + } + //prevent the route! + event.preventDefault(); + //raise an event + eventsService.emit('valFormManager.pendingChanges', true); + } + }); + unsubscribe.push(locationEvent); + //Ensure to remove the event handler when this instance is destroyted + scope.$on('$destroy', function () { + for (var u in unsubscribe) { + unsubscribe[u](); + } + }); + $timeout(function () { + formCtrl.$setPristine(); + }, 1000); } - - var unsubscribe = []; - - //listen for the forms saving event - unsubscribe.push(scope.$on(savingEventName, function(ev, args) { - element.addClass(className); - - //set the flag so we can check to see if we should display the error. - isSavingNewItem = $routeParams.create; - })); - - //listen for the forms saved event - unsubscribe.push(scope.$on(savedEvent, function(ev, args) { - //remove validation class - element.removeClass(className); - - //clear form state as at this point we retrieve new data from the server - //and all validation will have cleared at this point - formCtrl.$setPristine(); - })); - - //This handles the 'unsaved changes' dialog which is triggered when a route is attempting to be changed but - // the form has pending changes - var locationEvent = $rootScope.$on('$locationChangeStart', function(event, nextLocation, currentLocation) { - if (!formCtrl.$dirty || isSavingNewItem) { - return; - } - - var path = nextLocation.split("#")[1]; - if (path) { - if (path.indexOf("%253") || path.indexOf("%252")) { - path = decodeURIComponent(path); + }; + } + angular.module('umbraco.directives.validation').directive('valFormManager', valFormManager); + /** +* @ngdoc directive +* @name umbraco.directives.directive:valHighlight +* @restrict A +* @description Used on input fields when you want to signal that they are in error, this will highlight the item for 1 second +**/ + function valHighlight($timeout) { + return { + restrict: 'A', + link: function (scope, element, attrs, ctrl) { + attrs.$observe('valHighlight', function (newVal) { + if (newVal === 'true') { + element.addClass('highlight-error'); + $timeout(function () { + //set the bound scope property to false + scope[attrs.valHighlight] = false; + }, 1000); + } else { + element.removeClass('highlight-error'); } - - if (!notificationsService.hasView()) { - var msg = { view: "confirmroutechange", args: { path: path, listener: locationEvent } }; - notificationsService.add(msg); - } - - //prevent the route! - event.preventDefault(); - - //raise an event - eventsService.emit("valFormManager.pendingChanges", true); - } - - }); - unsubscribe.push(locationEvent); - - //Ensure to remove the event handler when this instance is destroyted - scope.$on('$destroy', function() { - for (var u in unsubscribe) { - unsubscribe[u](); - } - }); - - $timeout(function(){ - formCtrl.$setPristine(); - }, 1000); - } - }; -} -angular.module('umbraco.directives.validation').directive("valFormManager", valFormManager); -/** + }); + } + }; + } + angular.module('umbraco.directives.validation').directive('valHighlight', valHighlight); + /** * @ngdoc directive * @name umbraco.directives.directive:valPropertyMsg * @restrict A @@ -10487,192 +12760,164 @@ angular.module('umbraco.directives.validation').directive("valFormManager", valF * and when an error is detected for this property we'll show the error message. * In order for this directive to work, the valStatusChanged directive must be placed on the containing form. **/ -function valPropertyMsg(serverValidationManager) { - - return { - scope: { - property: "=" - }, - require: "^form", //require that this directive is contained within an ngForm - replace: true, //replace the element with the template - restrict: "E", //restrict to element - template: "
    {{errorMsg}}
    ", - - /** + function valPropertyMsg(serverValidationManager) { + return { + scope: { property: '=' }, + require: '^form', + //require that this directive is contained within an ngForm + replace: true, + //replace the element with the template + restrict: 'E', + //restrict to element + template: '
    {{errorMsg}}
    ', + /** Our directive requries a reference to a form controller which gets passed in to this parameter */ - link: function (scope, element, attrs, formCtrl) { - - var watcher = null; - - // Gets the error message to display - function getErrorMsg() { - //this can be null if no property was assigned - if (scope.property) { - //first try to get the error msg from the server collection - var err = serverValidationManager.getPropertyError(scope.property.alias, ""); - //if there's an error message use it - if (err && err.errorMsg) { - return err.errorMsg; + link: function (scope, element, attrs, formCtrl) { + var watcher = null; + // Gets the error message to display + function getErrorMsg() { + //this can be null if no property was assigned + if (scope.property) { + //first try to get the error msg from the server collection + var err = serverValidationManager.getPropertyError(scope.property.alias, ''); + //if there's an error message use it + if (err && err.errorMsg) { + return err.errorMsg; + } else { + return scope.property.propertyErrorMessage ? scope.property.propertyErrorMessage : 'Property has errors'; + } } - else { - return scope.property.propertyErrorMessage ? scope.property.propertyErrorMessage : "Property has errors"; - } - + return 'Property has errors'; } - return "Property has errors"; - } - - // We need to subscribe to any changes to our model (based on user input) - // This is required because when we have a server error we actually invalidate - // the form which means it cannot be resubmitted. - // So once a field is changed that has a server error assigned to it - // we need to re-validate it for the server side validator so the user can resubmit - // the form. Of course normal client-side validators will continue to execute. - function startWatch() { - //if there's not already a watch - if (!watcher) { - watcher = scope.$watch("property.value", function (newValue, oldValue) { - - if (!newValue || angular.equals(newValue, oldValue)) { - return; - } - - var errCount = 0; - for (var e in formCtrl.$error) { - if (angular.isArray(formCtrl.$error[e])) { - errCount++; + // We need to subscribe to any changes to our model (based on user input) + // This is required because when we have a server error we actually invalidate + // the form which means it cannot be resubmitted. + // So once a field is changed that has a server error assigned to it + // we need to re-validate it for the server side validator so the user can resubmit + // the form. Of course normal client-side validators will continue to execute. + function startWatch() { + //if there's not already a watch + if (!watcher) { + watcher = scope.$watch('property.value', function (newValue, oldValue) { + if (!newValue || angular.equals(newValue, oldValue)) { + return; } + var errCount = 0; + for (var e in formCtrl.$error) { + if (angular.isArray(formCtrl.$error[e])) { + errCount++; + } + } + //we are explicitly checking for valServer errors here, since we shouldn't auto clear + // based on other errors. We'll also check if there's no other validation errors apart from valPropertyMsg, if valPropertyMsg + // is the only one, then we'll clear. + if (errCount === 1 && angular.isArray(formCtrl.$error.valPropertyMsg) || formCtrl.$invalid && angular.isArray(formCtrl.$error.valServer)) { + scope.errorMsg = ''; + formCtrl.$setValidity('valPropertyMsg', true); + stopWatch(); + } + }, true); + } + } + //clear the watch when the property validator is valid again + function stopWatch() { + if (watcher) { + watcher(); + watcher = null; + } + } + //if there's any remaining errors in the server validation service then we should show them. + var showValidation = serverValidationManager.items.length > 0; + var hasError = false; + //create properties on our custom scope so we can use it in our template + scope.errorMsg = ''; + var unsubscribe = []; + //listen for form error changes + unsubscribe.push(scope.$on('valStatusChanged', function (evt, args) { + if (args.form.$invalid) { + //first we need to check if the valPropertyMsg validity is invalid + if (formCtrl.$error.valPropertyMsg && formCtrl.$error.valPropertyMsg.length > 0) { + //since we already have an error we'll just return since this means we've already set the + // hasError and errorMsg properties which occurs below in the serverValidationManager.subscribe + return; + } else if (element.closest('.umb-control-group').find('.ng-invalid').length > 0) { + //check if it's one of the properties that is invalid in the current content property + hasError = true; + //update the validation message if we don't already have one assigned. + if (showValidation && scope.errorMsg === '') { + scope.errorMsg = getErrorMsg(); + } + } else { + hasError = false; + scope.errorMsg = ''; } - - //we are explicitly checking for valServer errors here, since we shouldn't auto clear - // based on other errors. We'll also check if there's no other validation errors apart from valPropertyMsg, if valPropertyMsg - // is the only one, then we'll clear. - - if ((errCount === 1 && angular.isArray(formCtrl.$error.valPropertyMsg)) || (formCtrl.$invalid && angular.isArray(formCtrl.$error.valServer))) { - scope.errorMsg = ""; + } else { + hasError = false; + scope.errorMsg = ''; + } + }, true)); + //listen for the forms saving event + unsubscribe.push(scope.$on('formSubmitting', function (ev, args) { + showValidation = true; + if (hasError && scope.errorMsg === '') { + scope.errorMsg = getErrorMsg(); + } else if (!hasError) { + scope.errorMsg = ''; + stopWatch(); + } + })); + //listen for the forms saved event + unsubscribe.push(scope.$on('formSubmitted', function (ev, args) { + showValidation = false; + scope.errorMsg = ''; + formCtrl.$setValidity('valPropertyMsg', true); + stopWatch(); + })); + //listen for server validation changes + // NOTE: we pass in "" in order to listen for all validation changes to the content property, not for + // validation changes to fields in the property this is because some server side validators may not + // return the field name for which the error belongs too, just the property for which it belongs. + // It's important to note that we need to subscribe to server validation changes here because we always must + // indicate that a content property is invalid at the property level since developers may not actually implement + // the correct field validation in their property editors. + if (scope.property) { + //this can be null if no property was assigned + serverValidationManager.subscribe(scope.property.alias, '', function (isValid, propertyErrors, allErrors) { + hasError = !isValid; + if (hasError) { + //set the error message to the server message + scope.errorMsg = propertyErrors[0].errorMsg; + //flag that the current validator is invalid + formCtrl.$setValidity('valPropertyMsg', false); + startWatch(); + } else { + scope.errorMsg = ''; + //flag that the current validator is valid formCtrl.$setValidity('valPropertyMsg', true); stopWatch(); } - }, true); - } - } - - //clear the watch when the property validator is valid again - function stopWatch() { - if (watcher) { - watcher(); - watcher = null; - } - } - - //if there's any remaining errors in the server validation service then we should show them. - var showValidation = serverValidationManager.items.length > 0; - var hasError = false; - - //create properties on our custom scope so we can use it in our template - scope.errorMsg = ""; - - var unsubscribe = []; - - //listen for form error changes - unsubscribe.push(scope.$on("valStatusChanged", function(evt, args) { - if (args.form.$invalid) { - - //first we need to check if the valPropertyMsg validity is invalid - if (formCtrl.$error.valPropertyMsg && formCtrl.$error.valPropertyMsg.length > 0) { - //since we already have an error we'll just return since this means we've already set the - // hasError and errorMsg properties which occurs below in the serverValidationManager.subscribe - return; - } - else if (element.closest(".umb-control-group").find(".ng-invalid").length > 0) { - //check if it's one of the properties that is invalid in the current content property - hasError = true; - //update the validation message if we don't already have one assigned. - if (showValidation && scope.errorMsg === "") { - scope.errorMsg = getErrorMsg(); - } - } - else { - hasError = false; - scope.errorMsg = ""; - } - } - else { - hasError = false; - scope.errorMsg = ""; - } - }, true)); - - //listen for the forms saving event - unsubscribe.push(scope.$on("formSubmitting", function(ev, args) { - showValidation = true; - if (hasError && scope.errorMsg === "") { - scope.errorMsg = getErrorMsg(); - } - else if (!hasError) { - scope.errorMsg = ""; - stopWatch(); - } - })); - - //listen for the forms saved event - unsubscribe.push(scope.$on("formSubmitted", function(ev, args) { - showValidation = false; - scope.errorMsg = ""; - formCtrl.$setValidity('valPropertyMsg', true); - stopWatch(); - })); - - //listen for server validation changes - // NOTE: we pass in "" in order to listen for all validation changes to the content property, not for - // validation changes to fields in the property this is because some server side validators may not - // return the field name for which the error belongs too, just the property for which it belongs. - // It's important to note that we need to subscribe to server validation changes here because we always must - // indicate that a content property is invalid at the property level since developers may not actually implement - // the correct field validation in their property editors. - - if (scope.property) { //this can be null if no property was assigned - serverValidationManager.subscribe(scope.property.alias, "", function (isValid, propertyErrors, allErrors) { - hasError = !isValid; - if (hasError) { - //set the error message to the server message - scope.errorMsg = propertyErrors[0].errorMsg; - //flag that the current validator is invalid - formCtrl.$setValidity('valPropertyMsg', false); - startWatch(); - } - else { - scope.errorMsg = ""; - //flag that the current validator is valid - formCtrl.$setValidity('valPropertyMsg', true); + }); + //when the element is disposed we need to unsubscribe! + // NOTE: this is very important otherwise when this controller re-binds the previous subscriptsion will remain + // but they are a different callback instance than the above. + element.bind('$destroy', function () { stopWatch(); + serverValidationManager.unsubscribe(scope.property.alias, ''); + }); + } + //when the scope is disposed we need to unsubscribe + scope.$on('$destroy', function () { + for (var u in unsubscribe) { + unsubscribe[u](); } }); - - //when the element is disposed we need to unsubscribe! - // NOTE: this is very important otherwise when this controller re-binds the previous subscriptsion will remain - // but they are a different callback instance than the above. - element.bind('$destroy', function () { - stopWatch(); - serverValidationManager.unsubscribe(scope.property.alias, ""); - }); } - - //when the scope is disposed we need to unsubscribe - scope.$on('$destroy', function () { - for (var u in unsubscribe) { - unsubscribe[u](); - } - }); - } - - - }; -} -angular.module('umbraco.directives.validation').directive("valPropertyMsg", valPropertyMsg); -/** + }; + } + angular.module('umbraco.directives.validation').directive('valPropertyMsg', valPropertyMsg); + /** * @ngdoc directive * @name umbraco.directives.directive:valPropertyValidator * @restrict A @@ -10686,509 +12931,437 @@ angular.module('umbraco.directives.validation').directive("valPropertyMsg", valP This directive should only be used when dealing with complex models, if custom validation needs to be performed with primitive values, use the simpler angular validation directives instead since this will watch the entire model. **/ - -function valPropertyValidator(serverValidationManager) { - return { - scope: { - valPropertyValidator: "=" - }, - - // The element must have ng-model attribute and be inside an umbProperty directive - require: ['ngModel', '?^umbProperty'], - - restrict: "A", - - link: function (scope, element, attrs, ctrls) { - - var modelCtrl = ctrls[0]; - var propCtrl = ctrls.length > 1 ? ctrls[1] : null; - - // Check whether the scope has a valPropertyValidator method - if (!scope.valPropertyValidator || !angular.isFunction(scope.valPropertyValidator)) { - throw new Error('val-property-validator directive must specify a function to call'); - } - - var initResult = scope.valPropertyValidator(); - - // Validation method - var validate = function (viewValue) { - // Calls the validition method - var result = scope.valPropertyValidator(); - if (!result.errorKey || result.isValid === undefined || !result.errorMsg) { - throw "The result object from valPropertyValidator does not contain required properties: isValid, errorKey, errorMsg"; + function valPropertyValidator(serverValidationManager) { + return { + scope: { valPropertyValidator: '=' }, + // The element must have ng-model attribute and be inside an umbProperty directive + require: [ + 'ngModel', + '?^umbProperty' + ], + restrict: 'A', + link: function (scope, element, attrs, ctrls) { + var modelCtrl = ctrls[0]; + var propCtrl = ctrls.length > 1 ? ctrls[1] : null; + // Check whether the scope has a valPropertyValidator method + if (!scope.valPropertyValidator || !angular.isFunction(scope.valPropertyValidator)) { + throw new Error('val-property-validator directive must specify a function to call'); } - if (result.isValid === true) { - // Tell the controller that the value is valid - modelCtrl.$setValidity(result.errorKey, true); - if (propCtrl) { - propCtrl.setPropertyError(null); - } - } - else { - // Tell the controller that the value is invalid - modelCtrl.$setValidity(result.errorKey, false); - if (propCtrl) { - propCtrl.setPropertyError(result.errorMsg); + var initResult = scope.valPropertyValidator(); + // Validation method + var validate = function (viewValue) { + // Calls the validition method + var result = scope.valPropertyValidator(); + if (!result.errorKey || result.isValid === undefined || !result.errorMsg) { + throw 'The result object from valPropertyValidator does not contain required properties: isValid, errorKey, errorMsg'; } - } - }; - - // Parsers are called as soon as the value in the form input is modified - modelCtrl.$parsers.push(validate); - - } - }; -} -angular.module('umbraco.directives.validation').directive("valPropertyValidator", valPropertyValidator); - -/** + if (result.isValid === true) { + // Tell the controller that the value is valid + modelCtrl.$setValidity(result.errorKey, true); + if (propCtrl) { + propCtrl.setPropertyError(null); + } + } else { + // Tell the controller that the value is invalid + modelCtrl.$setValidity(result.errorKey, false); + if (propCtrl) { + propCtrl.setPropertyError(result.errorMsg); + } + } + }; + // Parsers are called as soon as the value in the form input is modified + modelCtrl.$parsers.push(validate); + } + }; + } + angular.module('umbraco.directives.validation').directive('valPropertyValidator', valPropertyValidator); + /** * @ngdoc directive * @name umbraco.directives.directive:valRegex * @restrict A * @description A custom directive to allow for matching a value against a regex string. * NOTE: there's already an ng-pattern but this requires that a regex expression is set, not a regex string **/ -function valRegex() { - - return { - require: 'ngModel', - restrict: "A", - link: function (scope, elm, attrs, ctrl) { - - var flags = ""; - var regex; - var eventBindings = []; - - attrs.$observe("valRegexFlags", function (newVal) { - if (newVal) { - flags = newVal; - } - }); - - attrs.$observe("valRegex", function (newVal) { - if (newVal) { - try { - var resolved = newVal; - if (resolved) { - regex = new RegExp(resolved, flags); - } - else { + function valRegex() { + return { + require: 'ngModel', + restrict: 'A', + link: function (scope, elm, attrs, ctrl) { + var flags = ''; + var regex; + var eventBindings = []; + attrs.$observe('valRegexFlags', function (newVal) { + if (newVal) { + flags = newVal; + } + }); + attrs.$observe('valRegex', function (newVal) { + if (newVal) { + try { + var resolved = newVal; + if (resolved) { + regex = new RegExp(resolved, flags); + } else { + regex = new RegExp(attrs.valRegex, flags); + } + } catch (e) { regex = new RegExp(attrs.valRegex, flags); } } - catch (e) { - regex = new RegExp(attrs.valRegex, flags); + }); + eventBindings.push(scope.$watch('ngModel', function (newValue, oldValue) { + if (newValue && newValue !== oldValue) { + patternValidator(newValue); } - } - }); - - eventBindings.push(scope.$watch('ngModel', function(newValue, oldValue){ - if(newValue && newValue !== oldValue) { - patternValidator(newValue); - } - })); - - var patternValidator = function (viewValue) { - if (regex) { - //NOTE: we don't validate on empty values, use required validator for that - if (!viewValue || regex.test(viewValue.toString())) { - // it is valid - ctrl.$setValidity('valRegex', true); - //assign a message to the validator - ctrl.errorMsg = ""; - return viewValue; + })); + var patternValidator = function (viewValue) { + if (regex) { + //NOTE: we don't validate on empty values, use required validator for that + if (!viewValue || regex.test(viewValue.toString())) { + // it is valid + ctrl.$setValidity('valRegex', true); + //assign a message to the validator + ctrl.errorMsg = ''; + return viewValue; + } else { + // it is invalid, return undefined (no model update) + ctrl.$setValidity('valRegex', false); + //assign a message to the validator + ctrl.errorMsg = 'Value is invalid, it does not match the correct pattern'; + return undefined; + } } - else { - // it is invalid, return undefined (no model update) - ctrl.$setValidity('valRegex', false); - //assign a message to the validator - ctrl.errorMsg = "Value is invalid, it does not match the correct pattern"; - return undefined; + }; + scope.$on('$destroy', function () { + // unbind watchers + for (var e in eventBindings) { + eventBindings[e](); } - } - }; - - scope.$on('$destroy', function(){ - // unbind watchers - for(var e in eventBindings) { - eventBindings[e](); - } - }); - - } - }; -} -angular.module('umbraco.directives.validation').directive("valRegex", valRegex); - -(function() { - 'use strict'; - - function ValRequireComponentDirective() { - - function link(scope, el, attr, ngModel) { - - var unbindModelWatcher = scope.$watch(function () { - return ngModel.$modelValue; - }, function(newValue) { - - if(newValue === undefined || newValue === null || newValue === "") { - ngModel.$setValidity("valRequiredComponent", false); - } else { - ngModel.$setValidity("valRequiredComponent", true); - } - - }); - - // clean up - scope.$on('$destroy', function(){ - unbindModelWatcher(); - }); - + }); + } + }; } - - var directive = { - require: 'ngModel', - restrict: "A", - link: link - }; - - return directive; - } - - angular.module('umbraco.directives').directive('valRequireComponent', ValRequireComponentDirective); - -})(); - -/** + angular.module('umbraco.directives.validation').directive('valRegex', valRegex); + (function () { + 'use strict'; + function ValRequireComponentDirective() { + function link(scope, el, attr, ngModel) { + var unbindModelWatcher = scope.$watch(function () { + return ngModel.$modelValue; + }, function (newValue) { + if (newValue === undefined || newValue === null || newValue === '') { + ngModel.$setValidity('valRequiredComponent', false); + } else { + ngModel.$setValidity('valRequiredComponent', true); + } + }); + // clean up + scope.$on('$destroy', function () { + unbindModelWatcher(); + }); + } + var directive = { + require: 'ngModel', + restrict: 'A', + link: link + }; + return directive; + } + angular.module('umbraco.directives').directive('valRequireComponent', ValRequireComponentDirective); + }()); + /** * @ngdoc directive * @name umbraco.directives.directive:valServer * @restrict A * @description This directive is used to associate a content property with a server-side validation response * so that the validators in angular are updated based on server-side feedback. **/ -function valServer(serverValidationManager) { - return { - require: ['ngModel', '?^umbProperty'], - restrict: "A", - link: function (scope, element, attr, ctrls) { - - var modelCtrl = ctrls[0]; - var umbPropCtrl = ctrls.length > 1 ? ctrls[1] : null; - if (!umbPropCtrl) { - //we cannot proceed, this validator will be disabled - return; - } - - var watcher = null; - - //Need to watch the value model for it to change, previously we had subscribed to - //modelCtrl.$viewChangeListeners but this is not good enough if you have an editor that - // doesn't specifically have a 2 way ng binding. This is required because when we - // have a server error we actually invalidate the form which means it cannot be - // resubmitted. So once a field is changed that has a server error assigned to it - // we need to re-validate it for the server side validator so the user can resubmit - // the form. Of course normal client-side validators will continue to execute. - function startWatch() { - //if there's not already a watch - if (!watcher) { - watcher = scope.$watch(function () { - return modelCtrl.$modelValue; - }, function (newValue, oldValue) { - - if (!newValue || angular.equals(newValue, oldValue)) { - return; - } - - if (modelCtrl.$invalid) { - modelCtrl.$setValidity('valServer', true); - stopWatch(); - } - }, true); + function valServer(serverValidationManager) { + return { + require: [ + 'ngModel', + '?^umbProperty' + ], + restrict: 'A', + link: function (scope, element, attr, ctrls) { + var modelCtrl = ctrls[0]; + var umbPropCtrl = ctrls.length > 1 ? ctrls[1] : null; + if (!umbPropCtrl) { + //we cannot proceed, this validator will be disabled + return; } - } - - function stopWatch() { - if (watcher) { - watcher(); - watcher = null; + var watcher = null; + //Need to watch the value model for it to change, previously we had subscribed to + //modelCtrl.$viewChangeListeners but this is not good enough if you have an editor that + // doesn't specifically have a 2 way ng binding. This is required because when we + // have a server error we actually invalidate the form which means it cannot be + // resubmitted. So once a field is changed that has a server error assigned to it + // we need to re-validate it for the server side validator so the user can resubmit + // the form. Of course normal client-side validators will continue to execute. + function startWatch() { + //if there's not already a watch + if (!watcher) { + watcher = scope.$watch(function () { + return modelCtrl.$modelValue; + }, function (newValue, oldValue) { + if (!newValue || angular.equals(newValue, oldValue)) { + return; + } + if (modelCtrl.$invalid) { + modelCtrl.$setValidity('valServer', true); + stopWatch(); + } + }, true); + } } - } - - var currentProperty = umbPropCtrl.property; - - //default to 'value' if nothing is set - var fieldName = "value"; - if (attr.valServer) { - fieldName = scope.$eval(attr.valServer); - if (!fieldName) { - //eval returned nothing so just use the string - fieldName = attr.valServer; + function stopWatch() { + if (watcher) { + watcher(); + watcher = null; + } } - } - - //subscribe to the server validation changes - serverValidationManager.subscribe(currentProperty.alias, fieldName, function (isValid, propertyErrors, allErrors) { - if (!isValid) { - modelCtrl.$setValidity('valServer', false); - //assign an error msg property to the current validator - modelCtrl.errorMsg = propertyErrors[0].errorMsg; - startWatch(); + var currentProperty = umbPropCtrl.property; + //default to 'value' if nothing is set + var fieldName = 'value'; + if (attr.valServer) { + fieldName = scope.$eval(attr.valServer); + if (!fieldName) { + //eval returned nothing so just use the string + fieldName = attr.valServer; + } } - else { - modelCtrl.$setValidity('valServer', true); - //reset the error message - modelCtrl.errorMsg = ""; + //subscribe to the server validation changes + serverValidationManager.subscribe(currentProperty.alias, fieldName, function (isValid, propertyErrors, allErrors) { + if (!isValid) { + modelCtrl.$setValidity('valServer', false); + //assign an error msg property to the current validator + modelCtrl.errorMsg = propertyErrors[0].errorMsg; + startWatch(); + } else { + modelCtrl.$setValidity('valServer', true); + //reset the error message + modelCtrl.errorMsg = ''; + stopWatch(); + } + }); + //when the element is disposed we need to unsubscribe! + // NOTE: this is very important otherwise when this controller re-binds the previous subscriptsion will remain + // but they are a different callback instance than the above. + element.bind('$destroy', function () { stopWatch(); - } - }); - - //when the element is disposed we need to unsubscribe! - // NOTE: this is very important otherwise when this controller re-binds the previous subscriptsion will remain - // but they are a different callback instance than the above. - element.bind('$destroy', function () { - stopWatch(); - serverValidationManager.unsubscribe(currentProperty.alias, fieldName); - }); - } - }; -} -angular.module('umbraco.directives.validation').directive("valServer", valServer); -/** + serverValidationManager.unsubscribe(currentProperty.alias, fieldName); + }); + } + }; + } + angular.module('umbraco.directives.validation').directive('valServer', valServer); + /** * @ngdoc directive * @name umbraco.directives.directive:valServerField * @restrict A * @description This directive is used to associate a content field (not user defined) with a server-side validation response * so that the validators in angular are updated based on server-side feedback. **/ -function valServerField(serverValidationManager) { - return { - require: 'ngModel', - restrict: "A", - link: function (scope, element, attr, ctrl) { - - var fieldName = null; - var eventBindings = []; - - attr.$observe("valServerField", function (newVal) { - if (newVal && fieldName === null) { - fieldName = newVal; - - //subscribe to the changed event of the view model. This is required because when we - // have a server error we actually invalidate the form which means it cannot be - // resubmitted. So once a field is changed that has a server error assigned to it - // we need to re-validate it for the server side validator so the user can resubmit - // the form. Of course normal client-side validators will continue to execute. - eventBindings.push(scope.$watch('ngModel', function(newValue){ - if (ctrl.$invalid) { - ctrl.$setValidity('valServerField', true); - } - })); - - //subscribe to the server validation changes - serverValidationManager.subscribe(null, fieldName, function (isValid, fieldErrors, allErrors) { - if (!isValid) { - ctrl.$setValidity('valServerField', false); - //assign an error msg property to the current validator - ctrl.errorMsg = fieldErrors[0].errorMsg; - } - else { - ctrl.$setValidity('valServerField', true); - //reset the error message - ctrl.errorMsg = ""; - } - }); - - //when the element is disposed we need to unsubscribe! - // NOTE: this is very important otherwise when this controller re-binds the previous subscriptsion will remain - // but they are a different callback instance than the above. - element.bind('$destroy', function () { - serverValidationManager.unsubscribe(null, fieldName); - }); - } - }); - - scope.$on('$destroy', function(){ - // unbind watchers - for(var e in eventBindings) { - eventBindings[e](); - } - }); - - } - }; -} -angular.module('umbraco.directives.validation').directive("valServerField", valServerField); - -/** + function valServerField(serverValidationManager) { + return { + require: 'ngModel', + restrict: 'A', + link: function (scope, element, attr, ngModel) { + var fieldName = null; + var eventBindings = []; + attr.$observe('valServerField', function (newVal) { + if (newVal && fieldName === null) { + fieldName = newVal; + //subscribe to the changed event of the view model. This is required because when we + // have a server error we actually invalidate the form which means it cannot be + // resubmitted. So once a field is changed that has a server error assigned to it + // we need to re-validate it for the server side validator so the user can resubmit + // the form. Of course normal client-side validators will continue to execute. + eventBindings.push(scope.$watch(function () { + return ngModel.$modelValue; + }, function (newValue) { + if (ngModel.$invalid) { + ngModel.$setValidity('valServerField', true); + } + })); + //subscribe to the server validation changes + serverValidationManager.subscribe(null, fieldName, function (isValid, fieldErrors, allErrors) { + if (!isValid) { + ngModel.$setValidity('valServerField', false); + //assign an error msg property to the current validator + ngModel.errorMsg = fieldErrors[0].errorMsg; + } else { + ngModel.$setValidity('valServerField', true); + //reset the error message + ngModel.errorMsg = ''; + } + }); + //when the element is disposed we need to unsubscribe! + // NOTE: this is very important otherwise when this controller re-binds the previous subscriptsion will remain + // but they are a different callback instance than the above. + element.bind('$destroy', function () { + serverValidationManager.unsubscribe(null, fieldName); + }); + } + }); + scope.$on('$destroy', function () { + // unbind watchers + for (var e in eventBindings) { + eventBindings[e](); + } + }); + } + }; + } + angular.module('umbraco.directives.validation').directive('valServerField', valServerField); + /** * @ngdoc directive * @name umbraco.directives.directive:valSubView * @restrict A * @description Used to show validation warnings for a editor sub view to indicate that the section content has validation errors in its data. * In order for this directive to work, the valFormManager directive must be placed on the containing form. **/ -(function() { - 'use strict'; - - function valSubViewDirective() { - - function link(scope, el, attr, ctrl) { - - var valFormManager = ctrl[1]; - scope.subView.hasError = false; - - //listen for form validation changes - valFormManager.onValidationStatusChanged(function (evt, args) { - if (!args.form.$valid) { - - var subViewContent = el.find(".ng-invalid"); - - if (subViewContent.length > 0) { - scope.subView.hasError = true; - } else { - scope.subView.hasError = false; - } - - } - else { - scope.subView.hasError = false; - } - }); - - } - - var directive = { - require: ['^form', '^valFormManager'], - restrict: "A", - link: link - }; - - return directive; - } - - angular.module('umbraco.directives').directive('valSubView', valSubViewDirective); - -})(); - - -/** + (function () { + 'use strict'; + function valSubViewDirective() { + function link(scope, el, attr, ctrl) { + //if there are no containing form or valFormManager controllers, then we do nothing + if (!ctrl || !angular.isArray(ctrl) || ctrl.length !== 2 || !ctrl[0] || !ctrl[1]) { + return; + } + var valFormManager = ctrl[1]; + scope.subView.hasError = false; + //listen for form validation changes + valFormManager.onValidationStatusChanged(function (evt, args) { + if (!args.form.$valid) { + var subViewContent = el.find('.ng-invalid'); + if (subViewContent.length > 0) { + scope.subView.hasError = true; + } else { + scope.subView.hasError = false; + } + } else { + scope.subView.hasError = false; + } + }); + } + var directive = { + require: [ + '?^form', + '?^valFormManager' + ], + restrict: 'A', + link: link + }; + return directive; + } + angular.module('umbraco.directives').directive('valSubView', valSubViewDirective); + }()); + /** * @ngdoc directive * @name umbraco.directives.directive:valTab * @restrict A * @description Used to show validation warnings for a tab to indicate that the tab content has validations errors in its data. * In order for this directive to work, the valFormManager directive must be placed on the containing form. **/ -function valTab() { - return { - require: ['^form', '^valFormManager'], - restrict: "A", - link: function (scope, element, attr, ctrs) { - - var valFormManager = ctrs[1]; - var tabId = "tab" + scope.tab.id; - scope.tabHasError = false; - - //listen for form validation changes - valFormManager.onValidationStatusChanged(function (evt, args) { - if (!args.form.$valid) { - var tabContent = element.closest(".umb-panel").find("#" + tabId); - //check if the validation messages are contained inside of this tabs - if (tabContent.find(".ng-invalid").length > 0) { - scope.tabHasError = true; + function valTab() { + return { + require: [ + '^form', + '^valFormManager' + ], + restrict: 'A', + link: function (scope, element, attr, ctrs) { + var valFormManager = ctrs[1]; + var tabId = 'tab' + scope.tab.id; + scope.tabHasError = false; + //listen for form validation changes + valFormManager.onValidationStatusChanged(function (evt, args) { + if (!args.form.$valid) { + var tabContent = element.closest('.umb-panel').find('#' + tabId); + //check if the validation messages are contained inside of this tabs + if (tabContent.find('.ng-invalid').length > 0) { + scope.tabHasError = true; + } else { + scope.tabHasError = false; + } } else { scope.tabHasError = false; } - } - else { - scope.tabHasError = false; - } - }); - - } - }; -} -angular.module('umbraco.directives.validation').directive("valTab", valTab); -function valToggleMsg(serverValidationManager) { - return { - require: "^form", - restrict: "A", - - /** + }); + } + }; + } + angular.module('umbraco.directives.validation').directive('valTab', valTab); + function valToggleMsg(serverValidationManager) { + return { + require: '^form', + restrict: 'A', + /** Our directive requries a reference to a form controller which gets passed in to this parameter */ - link: function (scope, element, attr, formCtrl) { - - if (!attr.valToggleMsg){ - throw "valToggleMsg requires that a reference to a validator is specified"; - } - if (!attr.valMsgFor){ - throw "valToggleMsg requires that the attribute valMsgFor exists on the element"; - } - if (!formCtrl[attr.valMsgFor]) { - throw "valToggleMsg cannot find field " + attr.valMsgFor + " on form " + formCtrl.$name; - } - - //if there's any remaining errors in the server validation service then we should show them. - var showValidation = serverValidationManager.items.length > 0; - var hasCustomMsg = element.contents().length > 0; - - //add a watch to the validator for the value (i.e. myForm.value.$error.required ) - scope.$watch(function () { - //sometimes if a dialog closes in the middle of digest we can get null references here - - return (formCtrl && formCtrl[attr.valMsgFor]) ? formCtrl[attr.valMsgFor].$error[attr.valToggleMsg] : null; - }, function () { - //sometimes if a dialog closes in the middle of digest we can get null references here - if ((formCtrl && formCtrl[attr.valMsgFor])) { - if (formCtrl[attr.valMsgFor].$error[attr.valToggleMsg] && showValidation) { + link: function (scope, element, attr, formCtrl) { + if (!attr.valToggleMsg) { + throw 'valToggleMsg requires that a reference to a validator is specified'; + } + if (!attr.valMsgFor) { + throw 'valToggleMsg requires that the attribute valMsgFor exists on the element'; + } + if (!formCtrl[attr.valMsgFor]) { + throw 'valToggleMsg cannot find field ' + attr.valMsgFor + ' on form ' + formCtrl.$name; + } + //if there's any remaining errors in the server validation service then we should show them. + var showValidation = serverValidationManager.items.length > 0; + var hasCustomMsg = element.contents().length > 0; + //add a watch to the validator for the value (i.e. myForm.value.$error.required ) + scope.$watch(function () { + //sometimes if a dialog closes in the middle of digest we can get null references here + return formCtrl && formCtrl[attr.valMsgFor] ? formCtrl[attr.valMsgFor].$error[attr.valToggleMsg] : null; + }, function () { + //sometimes if a dialog closes in the middle of digest we can get null references here + if (formCtrl && formCtrl[attr.valMsgFor]) { + if (formCtrl[attr.valMsgFor].$error[attr.valToggleMsg] && showValidation) { + element.show(); + //display the error message if this element has no contents + if (!hasCustomMsg) { + element.html(formCtrl[attr.valMsgFor].errorMsg); + } + } else { + element.hide(); + } + } + }); + var unsubscribe = []; + //listen for the saving event (the result is a callback method which is called to unsubscribe) + unsubscribe.push(scope.$on('formSubmitting', function (ev, args) { + showValidation = true; + if (formCtrl[attr.valMsgFor].$error[attr.valToggleMsg]) { element.show(); //display the error message if this element has no contents if (!hasCustomMsg) { element.html(formCtrl[attr.valMsgFor].errorMsg); } - } - else { + } else { element.hide(); } - } - }); - - var unsubscribe = []; - - //listen for the saving event (the result is a callback method which is called to unsubscribe) - unsubscribe.push(scope.$on("formSubmitting", function(ev, args) { - showValidation = true; - if (formCtrl[attr.valMsgFor].$error[attr.valToggleMsg]) { - element.show(); - //display the error message if this element has no contents - if (!hasCustomMsg) { - element.html(formCtrl[attr.valMsgFor].errorMsg); - } - } - else { + })); + //listen for the saved event (the result is a callback method which is called to unsubscribe) + unsubscribe.push(scope.$on('formSubmitted', function (ev, args) { + showValidation = false; element.hide(); - } - })); - - //listen for the saved event (the result is a callback method which is called to unsubscribe) - unsubscribe.push(scope.$on("formSubmitted", function(ev, args) { - showValidation = false; - element.hide(); - })); - - //when the element is disposed we need to unsubscribe! - // NOTE: this is very important otherwise if this directive is part of a modal, the listener still exists because the dom - // element might still be there even after the modal has been hidden. - element.bind('$destroy', function () { - for (var u in unsubscribe) { - unsubscribe[u](); - } - }); - - } - }; -} - -/** + })); + //when the element is disposed we need to unsubscribe! + // NOTE: this is very important otherwise if this directive is part of a modal, the listener still exists because the dom + // element might still be there even after the modal has been hidden. + element.bind('$destroy', function () { + for (var u in unsubscribe) { + unsubscribe[u](); + } + }); + } + }; + } + /** * @ngdoc directive * @name umbraco.directives.directive:valToggleMsg * @restrict A @@ -11196,17 +13369,15 @@ function valToggleMsg(serverValidationManager) { * @requires formController * @description This directive will show/hide an error based on: is the value + the given validator invalid? AND, has the form been submitted ? **/ -angular.module('umbraco.directives.validation').directive("valToggleMsg", valToggleMsg); -angular.module('umbraco.directives.validation') -.directive('valTriggerChange', function($sniffer) { - return { - link : function(scope, elem, attrs) { - elem.bind('click', function(){ - $(attrs.valTriggerChange).trigger($sniffer.hasEvent('input') ? 'input' : 'change'); - }); - }, - priority : 1 - }; -}); - -})(); \ No newline at end of file + angular.module('umbraco.directives.validation').directive('valToggleMsg', valToggleMsg); + angular.module('umbraco.directives.validation').directive('valTriggerChange', function ($sniffer) { + return { + link: function (scope, elem, attrs) { + elem.bind('click', function () { + $(attrs.valTriggerChange).trigger($sniffer.hasEvent('input') ? 'input' : 'change'); + }); + }, + priority: 1 + }; + }); +}()); \ No newline at end of file diff --git a/WebCms/Umbraco/Js/umbraco.filters.js b/WebCms/Umbraco/Js/umbraco.filters.js index 3d9e7fb..a17ccd7 100644 --- a/WebCms/Umbraco/Js/umbraco.filters.js +++ b/WebCms/Umbraco/Js/umbraco.filters.js @@ -1,53 +1,128 @@ -/*! umbraco - * https://github.com/umbraco/umbraco-cms/ - * Copyright (c) 2016 Umbraco HQ; - * Licensed - */ - -(function() { - -angular.module('umbraco.filters', []); -angular.module("umbraco.filters") - .filter('compareArrays', function() { +(function () { + angular.module('umbraco.filters', []); + angular.module('umbraco.filters').filter('compareArrays', function () { return function inArray(array, compareArray, compareProperty) { - var result = []; - - angular.forEach(array, function(arrayItem){ - + angular.forEach(array, function (arrayItem) { var exists = false; - - angular.forEach(compareArray, function(compareItem){ - if( arrayItem[compareProperty] === compareItem[compareProperty]) { + angular.forEach(compareArray, function (compareItem) { + if (arrayItem[compareProperty] === compareItem[compareProperty]) { exists = true; } }); - - if(!exists) { + if (!exists) { result.push(arrayItem); } - }); - return result; - }; -}); - -angular.module("umbraco.filters").filter('timespan', function() { - return function(input) { - var sec_num = parseInt(input, 10); - var hours = Math.floor(sec_num / 3600); - var minutes = Math.floor((sec_num - (hours * 3600)) / 60); - var seconds = sec_num - (hours * 3600) - (minutes * 60); - - if (hours < 10) {hours = "0"+hours;} - if (minutes < 10) {minutes = "0"+minutes;} - if (seconds < 10) {seconds = "0"+seconds;} - var time = hours+':'+minutes+':'+seconds; - return time; + }); + // Filter to take a node id and grab it's name instead + // Usage: {{ pickerAlias | ncNodeName }} + // Cache for node names so we don't make a ton of requests + var ncNodeNameCache = { + id: '', + keys: {} }; - }); - - -})(); \ No newline at end of file + angular.module('umbraco.filters').filter('ncNodeName', function (editorState, entityResource) { + function formatLabel(firstNodeName, totalNodes) { + return totalNodes <= 1 ? firstNodeName // If there is more than one item selected, append the additional number of items selected to hint that + : firstNodeName + ' (+' + (totalNodes - 1) + ')'; + } + return function (input) { + // Check we have a value at all + if (input === '' || input.toString() === '0') { + return ''; + } + var currentNode = editorState.getCurrent(); + // Ensure a unique cache per editor instance + var key = 'ncNodeName_' + currentNode.key; + if (ncNodeNameCache.id !== key) { + ncNodeNameCache.id = key; + ncNodeNameCache.keys = {}; + } + // MNTP values are comma separated IDs. We'll only fetch the first one for the NC header. + var ids = input.split(','); + var lookupId = ids[0]; + // See if there is a value in the cache and use that + if (ncNodeNameCache.keys[lookupId]) { + return formatLabel(ncNodeNameCache.keys[lookupId], ids.length); + } + // No value, so go fetch one + // We'll put a temp value in the cache though so we don't + // make a load of requests while we wait for a response + ncNodeNameCache.keys[lookupId] = 'Loading...'; + var type = lookupId.indexOf('umb://media/') === 0 ? 'Media' : lookupId.indexOf('umb://member/') === 0 ? 'Member' : 'Document'; + entityResource.getById(lookupId, type).then(function (ent) { + ncNodeNameCache.keys[lookupId] = ent.name; + }); + // Return the current value for now + return formatLabel(ncNodeNameCache.keys[lookupId], ids.length); + }; + }).filter('ncRichText', function () { + return function (input) { + return $('
    ').html(input).text(); + }; + }); + /** +* @ngdoc filter +* @name umbraco.filters.preserveNewLineInHtml +* @description +* Used when rendering a string as HTML (i.e. with ng-bind-html) to convert line-breaks to
    tags +**/ + angular.module('umbraco.filters').filter('preserveNewLineInHtml', function () { + return function (text) { + if (!text) { + return ''; + } + return text.replace(/\n/g, '
    '); + }; + }); + angular.module('umbraco.filters').filter('timespan', function () { + return function (input) { + var sec_num = parseInt(input, 10); + var hours = Math.floor(sec_num / 3600); + var minutes = Math.floor((sec_num - hours * 3600) / 60); + var seconds = sec_num - hours * 3600 - minutes * 60; + if (hours < 10) { + hours = '0' + hours; + } + if (minutes < 10) { + minutes = '0' + minutes; + } + if (seconds < 10) { + seconds = '0' + seconds; + } + var time = hours + ':' + minutes + ':' + seconds; + return time; + }; + }); + /** + * @ngdoc filter + * @name umbraco.filters.filter:umbWordLimit + * @namespace umbWordLimitFilter + * + * @description + * Limits the number of words in a string to the passed in value + */ + (function () { + 'use strict'; + function umbWordLimitFilter() { + return function (collection, property) { + if (!angular.isString(collection)) { + return collection; + } + if (angular.isUndefined(property)) { + return collection; + } + var newString = ''; + var array = []; + array = collection.split(' ', property); + array.length = property; + newString = array.join(' '); + return newString; + }; + } + angular.module('umbraco.filters').filter('umbWordLimit', umbWordLimitFilter); + }()); +}()); \ No newline at end of file diff --git a/WebCms/Umbraco/Js/umbraco.installer.js b/WebCms/Umbraco/Js/umbraco.installer.js index e7eb30f..bea977d 100644 --- a/WebCms/Umbraco/Js/umbraco.installer.js +++ b/WebCms/Umbraco/Js/umbraco.installer.js @@ -1,468 +1,412 @@ -/*! umbraco - * https://github.com/umbraco/umbraco-cms/ - * Copyright (c) 2016 Umbraco HQ; - * Licensed - */ - -(function() { - - angular.module('umbraco.install', []); -angular.module("umbraco.install").controller("Umbraco.InstallerController", - function ($scope, installerService) { - - //TODO: Decouple the service from the controller - the controller should be responsible - // for the model (state) and the service should be responsible for helping the controller, - // the controller should be passing the model into it's methods for manipulation and not hold - // state. We should not be assigning properties from a service to a controller's scope. - // see: https://github.com/umbraco/Umbraco-CMS/commit/b86ef0d7ac83f699aee35d807f7f7ebb6dd0ed2c#commitcomment-5721204 - - $scope.stepIndex = 0; - //comment this out if you just want to see tips - installerService.init(); - - //uncomment this to see tips - //installerService.switchToFeedback(); - - $scope.installer = installerService.status; - - $scope.forward = function () { - installerService.forward(); - }; - - $scope.backward = function () { - installerService.backward(); - }; - - $scope.install = function () { - installerService.install(); - }; - - $scope.gotoStep = function (step) { - installerService.gotoNamedStep(step); - }; - - $scope.restart = function () { - installerService.gotoStep(0); - }; - }); - -//this ensure that we start with a clean slate on every install and upgrade -angular.module("umbraco.install").run(function ($templateCache) { - $templateCache.removeAll(); -}); -angular.module("umbraco.install").factory('installerService', function($rootScope, $q, $timeout, $http, $location, $log){ - - var _status = { - index: 0, - current: undefined, - steps: undefined, - loading: true, - progress: "100%" - }; - - var factTimer = undefined; - var _installerModel = { - installId: undefined, - instructions: { - } - }; - - //add to umbraco installer facts here - var facts = ['Umbraco helped millions of people watch a man jump from the edge of space', - 'Over 370 000 websites are currently powered by Umbraco', - "At least 2 people have named their cat 'Umbraco'", - 'On an average day, more than 1000 people download Umbraco', - 'umbraco.tv is the premier source of Umbraco video tutorials to get you started', - 'You can find the world\'s friendliest CMS community at our.umbraco.org', - 'You can become a certified Umbraco developer by attending one of the official courses', - 'Umbraco works really well on tablets', - 'You have 100% control over your markup and design when crafting a website in Umbraco', - 'Umbraco is the best of both worlds: 100% free and open source, and backed by a professional and profitable company', - "There's a pretty big chance, you've visited a website powered by Umbraco today", - "'Umbraco-spotting' is the game of spotting big brands running Umbraco", - "At least 4 people have the Umbraco logo tattooed on them", - "'Umbraco' is the danish name for an allen key", - "Umbraco has been around since 2005, that's a looong time in IT", - "More than 400 people from all over the world meet each year in Denmark in June for our annual conference CodeGarden", - "While you are installing Umbraco someone else on the other side of the planet is probably doing it too", - "You can extend Umbraco without modifying the source code using either JavaScript or C#", - "Umbraco was installed in more than 165 countries in 2015" - ]; - - /** +(function () { + angular.module('umbraco.install', ['umbraco.directives']); + angular.module('umbraco.install').controller('Umbraco.InstallerController', function ($scope, installerService) { + //TODO: Decouple the service from the controller - the controller should be responsible + // for the model (state) and the service should be responsible for helping the controller, + // the controller should be passing the model into it's methods for manipulation and not hold + // state. We should not be assigning properties from a service to a controller's scope. + // see: https://github.com/umbraco/Umbraco-CMS/commit/b86ef0d7ac83f699aee35d807f7f7ebb6dd0ed2c#commitcomment-5721204 + $scope.stepIndex = 0; + //comment this out if you just want to see tips + installerService.init(); + //uncomment this to see tips + //installerService.switchToFeedback(); + $scope.installer = installerService.status; + $scope.forward = function () { + installerService.forward(); + }; + $scope.backward = function () { + installerService.backward(); + }; + $scope.install = function () { + installerService.install(); + }; + $scope.gotoStep = function (step) { + installerService.gotoNamedStep(step); + }; + $scope.restart = function () { + installerService.gotoStep(0); + }; + }); + //this ensure that we start with a clean slate on every install and upgrade + angular.module('umbraco.install').run(function ($templateCache) { + $templateCache.removeAll(); + }); + angular.module('umbraco.install').factory('installerService', function ($rootScope, $q, $timeout, $http, $location, $log) { + var _status = { + index: 0, + current: undefined, + steps: undefined, + loading: true, + progress: '100%' + }; + var factTimer = undefined; + var _installerModel = { + installId: undefined, + instructions: {} + }; + //add to umbraco installer facts here + var facts = [ + 'Umbraco helped millions of people watch a man jump from the edge of space', + 'Over 440 000 websites are currently powered by Umbraco', + 'At least 2 people have named their cat \'Umbraco\'', + 'On an average day, more than 1000 people download Umbraco', + 'umbraco.tv is the premier source of Umbraco video tutorials to get you started', + 'You can find the world\'s friendliest CMS community at our.umbraco.com', + 'You can become a certified Umbraco developer by attending one of the official courses', + 'Umbraco works really well on tablets', + 'You have 100% control over your markup and design when crafting a website in Umbraco', + 'Umbraco is the best of both worlds: 100% free and open source, and backed by a professional and profitable company', + 'There\'s a pretty big chance, you\'ve visited a website powered by Umbraco today', + '\'Umbraco-spotting\' is the game of spotting big brands running Umbraco', + 'At least 4 people have the Umbraco logo tattooed on them', + '\'Umbraco\' is the Danish name for an allen key', + 'Umbraco has been around since 2005, that\'s a looong time in IT', + 'More than 600 people from all over the world meet each year in Denmark in May for our annual conference CodeGarden', + 'While you are installing Umbraco someone else on the other side of the planet is probably doing it too', + 'You can extend Umbraco without modifying the source code using either JavaScript or C#', + 'Umbraco has been installed in more than 198 countries' + ]; + /** Returns the description for the step at a given index based on the order of the serverOrder of steps Since they don't execute on the server in the order that they are displayed in the UI. */ - function getDescriptionForStepAtIndex(steps, index) { - var sorted = _.sortBy(steps, "serverOrder"); - if (sorted[index]) { - return sorted[index].description; - } - return null; - } - /* Returns the description for the given step name */ - function getDescriptionForStepName(steps, name) { - var found = _.find(steps, function(i) { - return i.name == name; - }); - return (found) ? found.description : null; - } - - //calculates the offset of the progressbar on the installer - function calculateProgress(steps, next) { - var sorted = _.sortBy(steps, "serverOrder"); - - var pct = "100%"; - for (var i = sorted.length - 1; i >= 0; i--) { - if(sorted[i].name == next){ - pct = Math.floor((i+1) / steps.length * 100) + "%"; - break; - } - } - return pct; - } - - //helpful defaults for the view loading - function resolveView(view){ - - if(view.indexOf(".html") < 0){ - view = view + ".html"; - } - if(view.indexOf("/") < 0){ - view = "views/install/" + view; - } - - return view; - } - - /** Have put this here because we are not referencing our other modules */ - function safeApply (scope, fn) { - if (scope.$$phase || scope.$root.$$phase) { - if (angular.isFunction(fn)) { - fn(); - } - } - else { - if (angular.isFunction(fn)) { - scope.$apply(fn); - } - else { - scope.$apply(); - } - } - } - - var service = { - - status : _status, - //loads the needed steps and sets the intial state - init : function(){ - service.status.loading = true; - if(!_status.all){ - service.getSteps().then(function(response){ - service.status.steps = response.data.steps; - service.status.index = 0; - _installerModel.installId = response.data.installId; - service.findNextStep(); - - $timeout(function(){ - service.status.loading = false; - service.status.configuring = true; - }, 2000); - }); - } - }, - - //loads available packages from our.umbraco.org - getPackages : function(){ - return $http.get(Umbraco.Sys.ServerVariables.installApiBaseUrl + "GetPackages"); - }, - - getSteps : function(){ - return $http.get(Umbraco.Sys.ServerVariables.installApiBaseUrl + "GetSetup"); - }, - - gotoStep : function(index){ - var step = service.status.steps[index]; - step.view = resolveView(step.view); - - if(!step.model){ - step.model = {}; - } - - service.status.index = index; - service.status.current = step; - service.retrieveCurrentStep(); - }, - - gotoNamedStep : function(stepName){ - var step = _.find(service.status.steps, function(s, index){ - if (s.view && s.name === stepName) { - service.status.index = index; - return true; - } - return false; - }); - - step.view = resolveView(step.view); - if(!step.model){ - step.model = {}; - } - service.retrieveCurrentStep(); - service.status.current = step; - }, - - /** - Finds the next step containing a view. If one is found it stores it as the current step - and retreives the step information and returns it, otherwise returns null . - */ - findNextStep : function(){ - var step = _.find(service.status.steps, function(s, index){ - if(s.view && index >= service.status.index){ - service.status.index = index; - return true; - } - return false; - }); - - if (step) { - if (step.view.indexOf(".html") < 0) { - step.view = step.view + ".html"; + function getDescriptionForStepAtIndex(steps, index) { + var sorted = _.sortBy(steps, 'serverOrder'); + if (sorted[index]) { + return sorted[index].description; + } + return null; + } + /* Returns the description for the given step name */ + function getDescriptionForStepName(steps, name) { + var found = _.find(steps, function (i) { + return i.name == name; + }); + return found ? found.description : null; + } + //calculates the offset of the progressbar on the installer + function calculateProgress(steps, next) { + var sorted = _.sortBy(steps, 'serverOrder'); + var pct = '100%'; + for (var i = sorted.length - 1; i >= 0; i--) { + if (sorted[i].name == next) { + pct = Math.floor((i + 1) / steps.length * 100) + '%'; + break; } - - if (step.view.indexOf("/") < 0) { - step.view = "views/install/" + step.view; + } + return pct; + } + //helpful defaults for the view loading + function resolveView(view) { + if (view.indexOf('.html') < 0) { + view = view + '.html'; + } + if (view.indexOf('/') < 0) { + view = 'views/install/' + view; + } + return view; + } + /** Have put this here because we are not referencing our other modules */ + function safeApply(scope, fn) { + if (scope.$$phase || scope.$root.$$phase) { + if (angular.isFunction(fn)) { + fn(); } - + } else { + if (angular.isFunction(fn)) { + scope.$apply(fn); + } else { + scope.$apply(); + } + } + } + var service = { + status: _status, + //loads the needed steps and sets the intial state + init: function () { + service.status.loading = true; + if (!_status.all) { + service.getSteps().then(function (response) { + service.status.steps = response.data.steps; + service.status.index = 0; + _installerModel.installId = response.data.installId; + service.findNextStep(); + $timeout(function () { + service.status.loading = false; + service.status.configuring = true; + }, 2000); + }); + } + }, + //loads available packages from our.umbraco.com + getPackages: function () { + return $http.get(Umbraco.Sys.ServerVariables.installApiBaseUrl + 'GetPackages'); + }, + getSteps: function () { + return $http.get(Umbraco.Sys.ServerVariables.installApiBaseUrl + 'GetSetup'); + }, + gotoStep: function (index) { + var step = service.status.steps[index]; + step.view = resolveView(step.view); if (!step.model) { step.model = {}; } - + service.status.index = index; service.status.current = step; service.retrieveCurrentStep(); - - //returns the next found step - return step; - } - else { - //there are no more steps found containing a view so return null - return null; - } - }, - - storeCurrentStep : function(){ - _installerModel.instructions[service.status.current.name] = service.status.current.model; - }, - - retrieveCurrentStep : function(){ - if(_installerModel.instructions[service.status.current.name]){ - service.status.current.model = _installerModel.instructions[service.status.current.name]; - } - }, - - /** Moves the installer forward to the next view, if there are not more views than the installation will commence */ - forward : function(){ - service.storeCurrentStep(); - service.status.index++; - var found = service.findNextStep(); - if (!found) { - //no more steps were found so start the installation process - service.install(); - } - }, - - backwards : function(){ - service.storeCurrentStep(); - service.gotoStep(service.status.index--); - }, - - install : function(){ - service.storeCurrentStep(); - service.switchToFeedback(); - - service.status.feedback = getDescriptionForStepAtIndex(service.status.steps, 0); - service.status.progress = 0; - - function processInstallStep() { - - $http.post(Umbraco.Sys.ServerVariables.installApiBaseUrl + "PostPerformInstall", _installerModel) - .success(function(data, status, headers, config) { - if (!data.complete) { - - //progress feedback - service.status.progress = calculateProgress(service.status.steps, data.nextStep); - - if (data.view) { - //set the current view and model to whatever the process returns, the view is responsible for retriggering install(); - var v = resolveView(data.view); - service.status.current = { view: v, model: data.model }; - - //turn off loading bar and feedback - service.switchToConfiguration(); - } - else { - var desc = getDescriptionForStepName(service.status.steps, data.nextStep); - if (desc) { - service.status.feedback = desc; - } - processInstallStep(); - } - } - else { - service.complete(); - } - }).error(function(data, status, headers, config) { - //need to handle 500's separately, this will happen if something goes wrong outside - // of the installer (like app startup events or something) and these will get returned as text/html - // not as json. If this happens we can't actually load in external views since they will YSOD as well! + }, + gotoNamedStep: function (stepName) { + var step = _.find(service.status.steps, function (s, index) { + if (s.view && s.name === stepName) { + service.status.index = index; + return true; + } + return false; + }); + step.view = resolveView(step.view); + if (!step.model) { + step.model = {}; + } + service.retrieveCurrentStep(); + service.status.current = step; + }, + /** + Finds the next step containing a view. If one is found it stores it as the current step + and retreives the step information and returns it, otherwise returns null . + */ + findNextStep: function () { + var step = _.find(service.status.steps, function (s, index) { + if (s.view && index >= service.status.index) { + service.status.index = index; + return true; + } + return false; + }); + if (step) { + if (step.view.indexOf('.html') < 0) { + step.view = step.view + '.html'; + } + if (step.view.indexOf('/') < 0) { + step.view = 'views/install/' + step.view; + } + if (!step.model) { + step.model = {}; + } + service.status.current = step; + service.retrieveCurrentStep(); + //returns the next found step + return step; + } else { + //there are no more steps found containing a view so return null + return null; + } + }, + storeCurrentStep: function () { + _installerModel.instructions[service.status.current.name] = service.status.current.model; + }, + retrieveCurrentStep: function () { + if (_installerModel.instructions[service.status.current.name]) { + service.status.current.model = _installerModel.instructions[service.status.current.name]; + } + }, + /** Moves the installer forward to the next view, if there are not more views than the installation will commence */ + forward: function () { + service.storeCurrentStep(); + service.status.index++; + var found = service.findNextStep(); + if (!found) { + //no more steps were found so start the installation process + service.install(); + } + }, + backwards: function () { + service.storeCurrentStep(); + service.gotoStep(service.status.index--); + }, + install: function () { + service.storeCurrentStep(); + service.switchToFeedback(); + service.status.feedback = getDescriptionForStepAtIndex(service.status.steps, 0); + service.status.progress = 0; + function processInstallStep() { + $http.post(Umbraco.Sys.ServerVariables.installApiBaseUrl + 'PostPerformInstall', _installerModel).success(function (data, status, headers, config) { + if (!data.complete) { + //progress feedback + service.status.progress = calculateProgress(service.status.steps, data.nextStep); + if (data.view) { + //set the current view and model to whatever the process returns, the view is responsible for retriggering install(); + var v = resolveView(data.view); + service.status.current = { + view: v, + model: data.model + }; + //turn off loading bar and feedback + service.switchToConfiguration(); + } else { + var desc = getDescriptionForStepName(service.status.steps, data.nextStep); + if (desc) { + service.status.feedback = desc; + } + processInstallStep(); + } + } else { + service.complete(); + } + }).error(function (data, status, headers, config) { + //need to handle 500's separately, this will happen if something goes wrong outside + // of the installer (like app startup events or something) and these will get returned as text/html + // not as json. If this happens we can't actually load in external views since they will YSOD as well! // so we need to display this in our own internal way - - if (status >= 500 && status < 600) { - service.status.current = { view: "ysod", model: null }; - var ysod = data; + if (status >= 500 && status < 600) { + service.status.current = { + view: 'ysod', + model: null + }; + var ysod = data; //we need to manually write the html to the iframe - the html contains full html markup - $timeout(function () { - document.getElementById('ysod').contentDocument.write(ysod); - }, 500); - } - else { - //this is where we handle installer error - var v = data.view ? resolveView(data.view) : resolveView("error"); - var model = data.model ? data.model : data; - service.status.current = { view: v, model: model }; - } - - service.switchToConfiguration(); - - }); - } - processInstallStep(); - }, - - randomFact: function () { - safeApply($rootScope, function() { - service.status.fact = facts[_.random(facts.length - 1)]; - }); - }, - - switchToFeedback : function(){ - service.status.current = undefined; - service.status.loading = true; - service.status.configuring = false; - - //initial fact - service.randomFact(); - - //timed facts - factTimer = window.setInterval(function(){ - service.randomFact(); - },6000); - }, - - switchToConfiguration : function(){ - service.status.loading = false; - service.status.configuring = true; - service.status.feedback = undefined; - service.status.fact = undefined; - - if(factTimer){ - clearInterval(factTimer); - } - }, - - complete : function(){ - - service.status.progress = "100%"; - service.status.done = true; - service.status.feedback = "Redirecting you to Umbraco, please wait"; - service.status.loading = false; - - if(factTimer){ - clearInterval(factTimer); - } - - $timeout(function(){ - window.location.href = Umbraco.Sys.ServerVariables.umbracoBaseUrl; - }, 1500); - } - }; - - return service; -}); - -angular.module("umbraco.install").controller("Umbraco.Installer.DataBaseController", function($scope, $http, installerService){ - - $scope.checking = false; - $scope.dbs = [ - {name: 'Microsoft SQL Server Compact (SQL CE)', id: 0}, - {name: 'Microsoft SQL Server', id: 1}, - { name: 'Microsoft SQL Azure', id: 3 }, - { name: 'MySQL', id: 2 }, - {name: 'Custom connection string', id: -1}]; - - if(installerService.status.current.model.dbType === undefined){ - installerService.status.current.model.dbType = 0; - } - - $scope.validateAndForward = function(){ - if(!$scope.checking && this.myForm.$valid){ - $scope.checking = true; - var model = installerService.status.current.model; - - $http.post(Umbraco.Sys.ServerVariables.installApiBaseUrl + "PostValidateDatabaseConnection", - model).then(function(response){ - - if(response.data === "true"){ - installerService.forward(); - }else{ - $scope.invalidDbDns = true; - } - - $scope.checking = false; - }, function(){ - $scope.invalidDbDns = true; - $scope.checking = false; - }); - } - }; -}); -angular.module("umbraco.install").controller("Umbraco.Installer.PackagesController", function ($scope, installerService) { - - installerService.getPackages().then(function (response) { - $scope.packages = response.data; + $timeout(function () { + document.getElementById('ysod').contentDocument.write(ysod); + }, 500); + } else { + //this is where we handle installer error + var v = data.view ? resolveView(data.view) : resolveView('error'); + var model = data.model ? data.model : data; + service.status.current = { + view: v, + model: model + }; + } + service.switchToConfiguration(); + }); + } + processInstallStep(); + }, + randomFact: function () { + safeApply($rootScope, function () { + service.status.fact = facts[_.random(facts.length - 1)]; + }); + }, + switchToFeedback: function () { + service.status.current = undefined; + service.status.loading = true; + service.status.configuring = false; + //initial fact + service.randomFact(); + //timed facts + factTimer = window.setInterval(function () { + service.randomFact(); + }, 6000); + }, + switchToConfiguration: function () { + service.status.loading = false; + service.status.configuring = true; + service.status.feedback = undefined; + service.status.fact = undefined; + if (factTimer) { + clearInterval(factTimer); + } + }, + complete: function () { + service.status.progress = '100%'; + service.status.done = true; + service.status.feedback = 'Redirecting you to Umbraco, please wait'; + service.status.loading = false; + if (factTimer) { + clearInterval(factTimer); + } + $timeout(function () { + window.location.href = Umbraco.Sys.ServerVariables.umbracoBaseUrl; + }, 1500); + } + }; + return service; }); - - $scope.setPackageAndContinue = function (pckId) { - installerService.status.current.model = pckId; - installerService.forward(); - }; - -}); -angular.module("umbraco.install").controller("Umbraco.Install.UserController", function($scope, installerService) { - - $scope.passwordPattern = /.*/; - $scope.installer.current.model.subscribeToNewsLetter = true; - - if ($scope.installer.current.model.minNonAlphaNumericLength > 0) { - var exp = ""; - for (var i = 0; i < $scope.installer.current.model.minNonAlphaNumericLength; i++) { - exp += ".*[\\W].*"; + angular.module('umbraco.install').controller('Umbraco.Installer.DataBaseController', function ($scope, $http, installerService) { + $scope.checking = false; + $scope.invalidDbDns = false; + $scope.dbs = [ + { + name: 'Microsoft SQL Server Compact (SQL CE)', + id: 0 + }, + { + name: 'Microsoft SQL Server', + id: 1 + }, + { + name: 'Microsoft SQL Azure', + id: 3 + }, + { + name: 'MySQL', + id: 2 + }, + { + name: 'Custom connection string', + id: -1 + } + ]; + if (installerService.status.current.model.dbType === undefined) { + installerService.status.current.model.dbType = 0; } - //replace duplicates - exp = exp.replace(".*.*", ".*"); - $scope.passwordPattern = new RegExp(exp); - } - - $scope.validateAndInstall = function(){ - installerService.install(); - }; - - $scope.validateAndForward = function(){ - if(this.myForm.$valid){ - installerService.forward(); - } - }; - -}); - -})(); \ No newline at end of file + $scope.validateAndForward = function () { + if (!$scope.checking && this.myForm.$valid) { + $scope.checking = true; + $scope.invalidDbDns = false; + var model = installerService.status.current.model; + $http.post(Umbraco.Sys.ServerVariables.installApiBaseUrl + 'PostValidateDatabaseConnection', model).then(function (response) { + if (response.data === 'true') { + installerService.forward(); + } else { + $scope.invalidDbDns = true; + } + $scope.checking = false; + }, function () { + $scope.invalidDbDns = true; + $scope.checking = false; + }); + } + }; + }); + angular.module('umbraco.install').controller('Umbraco.Installer.MachineKeyController', function ($scope, installerService) { + $scope.continue = function () { + installerService.status.current.model = true; + installerService.forward(); + }; + $scope.ignoreKey = function () { + installerService.status.current.model = false; + installerService.forward(); + }; + }); + angular.module('umbraco.install').controller('Umbraco.Installer.PackagesController', function ($scope, installerService) { + installerService.getPackages().then(function (response) { + $scope.packages = response.data; + }); + $scope.setPackageAndContinue = function (pckId) { + installerService.status.current.model = pckId; + installerService.forward(); + }; + }); + angular.module('umbraco.install').controller('Umbraco.Install.UserController', function ($scope, installerService) { + $scope.passwordPattern = /.*/; + $scope.installer.current.model.subscribeToNewsLetter = false; + if ($scope.installer.current.model.minNonAlphaNumericLength > 0) { + var exp = ''; + for (var i = 0; i < $scope.installer.current.model.minNonAlphaNumericLength; i++) { + exp += '.*[\\W].*'; + } + //replace duplicates + exp = exp.replace('.*.*', '.*'); + $scope.passwordPattern = new RegExp(exp); + } + $scope.validateAndInstall = function () { + installerService.install(); + }; + $scope.validateAndForward = function () { + if (this.myForm.$valid) { + installerService.forward(); + } + }; + }); +}()); \ No newline at end of file diff --git a/WebCms/Umbraco/Js/umbraco.resources.js b/WebCms/Umbraco/Js/umbraco.resources.js index d2e1852..451c762 100644 --- a/WebCms/Umbraco/Js/umbraco.resources.js +++ b/WebCms/Umbraco/Js/umbraco.resources.js @@ -1,13 +1,6 @@ -/*! umbraco - * https://github.com/umbraco/umbraco-cms/ - * Copyright (c) 2016 Umbraco HQ; - * Licensed - */ - -(function() { - -angular.module("umbraco.resources", []); -/** +(function () { + angular.module('umbraco.resources', []); + /** * @ngdoc service * @name umbraco.resources.authResource * @description @@ -18,320 +11,257 @@ angular.module("umbraco.resources", []); * @requires umbRequestHelper * @requires angularHelper */ -function authResource($q, $http, umbRequestHelper, angularHelper) { - - return { - - /** - * @ngdoc method - * @name umbraco.resources.authResource#performLogin - * @methodOf umbraco.resources.authResource - * - * @description - * Logs the Umbraco backoffice user in if the credentials are good - * - * ##usage - *
    -         * authResource.performLogin(login, password)
    -         *    .then(function(data) {
    -         *        //Do stuff for login...
    -         *    });
    -         * 
    - * @param {string} login Username of backoffice user - * @param {string} password Password of backoffice user - * @returns {Promise} resourcePromise object - * - */ - performLogin: function (username, password) { - - if (!username || !password) { - return angularHelper.rejectedPromise({ - errorMsg: 'Username or password cannot be empty' - }); - } - - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "authenticationApiBaseUrl", - "PostLogin"), { - username: username, - password: password - }), - 'Login failed for user ' + username); - }, - - /** - * @ngdoc method - * @name umbraco.resources.authResource#performRequestPasswordReset - * @methodOf umbraco.resources.authResource - * - * @description - * Checks to see if the provided email address is a valid user account and sends a link - * to allow them to reset their password - * - * ##usage - *
    -         * authResource.performRequestPasswordReset(email)
    -         *    .then(function(data) {
    -         *        //Do stuff for password reset request...
    -         *    });
    -         * 
    - * @param {string} email Email address of backoffice user - * @returns {Promise} resourcePromise object - * - */ - performRequestPasswordReset: function (email) { - - if (!email) { - return angularHelper.rejectedPromise({ - errorMsg: 'Email address cannot be empty' - }); - } - - //TODO: This validation shouldn't really be done here, the validation on the login dialog - // is pretty hacky which is why this is here, ideally validation on the login dialog would - // be done properly. - var emailRegex = /\S+@\S+\.\S+/; - if (!emailRegex.test(email)) { - return angularHelper.rejectedPromise({ - errorMsg: 'Email address is not valid' - }); - } - - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "authenticationApiBaseUrl", - "PostRequestPasswordReset"), { - email: email - }), - 'Request password reset failed for email ' + email); - }, - - /** - * @ngdoc method - * @name umbraco.resources.authResource#performValidatePasswordResetCode - * @methodOf umbraco.resources.authResource - * - * @description - * Checks to see if the provided password reset code is valid - * - * ##usage - *
    -         * authResource.performValidatePasswordResetCode(resetCode)
    -         *    .then(function(data) {
    -         *        //Allow reset of password
    -         *    });
    -         * 
    - * @param {integer} userId User Id - * @param {string} resetCode Password reset code - * @returns {Promise} resourcePromise object - * - */ - performValidatePasswordResetCode: function (userId, resetCode) { - - if (!userId) { - return angularHelper.rejectedPromise({ - errorMsg: 'User Id cannot be empty' - }); - } - - if (!resetCode) { - return angularHelper.rejectedPromise({ - errorMsg: 'Reset code cannot be empty' - }); - } - - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "authenticationApiBaseUrl", - "PostValidatePasswordResetCode"), - { - userId: userId, - resetCode: resetCode - }), - 'Password reset code validation failed for userId ' + userId + ', code' + resetCode); - }, - - /** - * @ngdoc method - * @name umbraco.resources.authResource#performSetPassword - * @methodOf umbraco.resources.authResource - * - * @description - * Checks to see if the provided password reset code is valid and sets the user's password - * - * ##usage - *
    -         * authResource.performSetPassword(userId, password, confirmPassword, resetCode)
    -         *    .then(function(data) {
    -         *        //Password set
    -         *    });
    -         * 
    - * @param {integer} userId User Id - * @param {string} password New password - * @param {string} confirmPassword Confirmation of new password - * @param {string} resetCode Password reset code - * @returns {Promise} resourcePromise object - * - */ - performSetPassword: function (userId, password, confirmPassword, resetCode) { - - if (userId === undefined || userId === null) { - return angularHelper.rejectedPromise({ - errorMsg: 'User Id cannot be empty' - }); - } - - if (!password) { - return angularHelper.rejectedPromise({ - errorMsg: 'Password cannot be empty' - }); - } - - if (password !== confirmPassword) { - return angularHelper.rejectedPromise({ - errorMsg: 'Password and confirmation do not match' - }); - } - - if (!resetCode) { - return angularHelper.rejectedPromise({ - errorMsg: 'Reset code cannot be empty' - }); - } - - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "authenticationApiBaseUrl", - "PostSetPassword"), - { - userId: userId, - password: password, - resetCode: resetCode - }), - 'Password reset code validation failed for userId ' + userId); - }, - - unlinkLogin: function (loginProvider, providerKey) { - if (!loginProvider || !providerKey) { - return angularHelper.rejectedPromise({ - errorMsg: 'loginProvider or providerKey cannot be empty' - }); - } - - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "authenticationApiBaseUrl", - "PostUnLinkLogin"), { - loginProvider: loginProvider, - providerKey: providerKey - }), - 'Unlinking login provider failed'); - }, - - /** - * @ngdoc method - * @name umbraco.resources.authResource#performLogout - * @methodOf umbraco.resources.authResource - * - * @description - * Logs out the Umbraco backoffice user - * - * ##usage - *
    -         * authResource.performLogout()
    -         *    .then(function(data) {
    -         *        //Do stuff for logging out...
    -         *    });
    -         * 
    - * @returns {Promise} resourcePromise object - * - */ - performLogout: function() { - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "authenticationApiBaseUrl", - "PostLogout"))); - }, - - /** - * @ngdoc method - * @name umbraco.resources.authResource#getCurrentUser - * @methodOf umbraco.resources.authResource - * - * @description - * Sends a request to the server to get the current user details, will return a 401 if the user is not logged in - * - * ##usage - *
    -         * authResource.getCurrentUser()
    -         *    .then(function(data) {
    -         *        //Do stuff for fetching the current logged in Umbraco backoffice user
    -         *    });
    -         * 
    - * @returns {Promise} resourcePromise object - * - */ - getCurrentUser: function () { - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "authenticationApiBaseUrl", - "GetCurrentUser")), - 'Server call failed for getting current user'); - }, - - getCurrentUserLinkedLogins: function () { - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "authenticationApiBaseUrl", - "GetCurrentUserLinkedLogins")), - 'Server call failed for getting current users linked logins'); - }, - - /** - * @ngdoc method - * @name umbraco.resources.authResource#isAuthenticated - * @methodOf umbraco.resources.authResource - * - * @description - * Checks if the user is logged in or not - does not return 401 or 403 - * - * ##usage - *
    -         * authResource.isAuthenticated()
    -         *    .then(function(data) {
    -         *        //Do stuff to check if user is authenticated
    -         *    });
    -         * 
    - * @returns {Promise} resourcePromise object - * - */ - isAuthenticated: function () { - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "authenticationApiBaseUrl", - "IsAuthenticated")), - { + function authResource($q, $http, umbRequestHelper, angularHelper) { + return { + get2FAProviders: function () { + return umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('authenticationApiBaseUrl', 'Get2FAProviders')), 'Could not retrive two factor provider info'); + }, + send2FACode: function (provider) { + return umbRequestHelper.resourcePromise($http.post(umbRequestHelper.getApiUrl('authenticationApiBaseUrl', 'PostSend2FACode'), angular.toJson(provider)), 'Could not send code'); + }, + verify2FACode: function (provider, code) { + return umbRequestHelper.resourcePromise($http.post(umbRequestHelper.getApiUrl('authenticationApiBaseUrl', 'PostVerify2FACode'), { + code: code, + provider: provider + }), 'Could not verify code'); + }, + /** + * @ngdoc method + * @name umbraco.resources.authResource#performLogin + * @methodOf umbraco.resources.authResource + * + * @description + * Logs the Umbraco backoffice user in if the credentials are good + * + * ##usage + *
    +     * authResource.performLogin(login, password)
    +     *    .then(function(data) {
    +     *        //Do stuff for login...
    +     *    });
    +     * 
    + * @param {string} login Username of backoffice user + * @param {string} password Password of backoffice user + * @returns {Promise} resourcePromise object + * + */ + performLogin: function (username, password) { + if (!username || !password) { + return angularHelper.rejectedPromise({ errorMsg: 'Username or password cannot be empty' }); + } + return umbRequestHelper.resourcePromise($http.post(umbRequestHelper.getApiUrl('authenticationApiBaseUrl', 'PostLogin'), { + username: username, + password: password + }), 'Login failed for user ' + username); + }, + /** + * There are not parameters for this since when the user has clicked on their invite email they will be partially + * logged in (but they will not be approved) so we need to use this method to verify the non approved logged in user's details. + * Using the getCurrentUser will not work since that only works for approved users + * @returns {} + */ + getCurrentInvitedUser: function () { + return umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('authenticationApiBaseUrl', 'GetCurrentInvitedUser')), 'Failed to verify invite'); + }, + /** + * @ngdoc method + * @name umbraco.resources.authResource#performRequestPasswordReset + * @methodOf umbraco.resources.authResource + * + * @description + * Checks to see if the provided email address is a valid user account and sends a link + * to allow them to reset their password + * + * ##usage + *
    +     * authResource.performRequestPasswordReset(email)
    +     *    .then(function(data) {
    +     *        //Do stuff for password reset request...
    +     *    });
    +     * 
    + * @param {string} email Email address of backoffice user + * @returns {Promise} resourcePromise object + * + */ + performRequestPasswordReset: function (email) { + if (!email) { + return angularHelper.rejectedPromise({ errorMsg: 'Email address cannot be empty' }); + } + //TODO: This validation shouldn't really be done here, the validation on the login dialog + // is pretty hacky which is why this is here, ideally validation on the login dialog would + // be done properly. + var emailRegex = /\S+@\S+\.\S+/; + if (!emailRegex.test(email)) { + return angularHelper.rejectedPromise({ errorMsg: 'Email address is not valid' }); + } + return umbRequestHelper.resourcePromise($http.post(umbRequestHelper.getApiUrl('authenticationApiBaseUrl', 'PostRequestPasswordReset'), { email: email }), 'Request password reset failed for email ' + email); + }, + /** + * @ngdoc method + * @name umbraco.resources.authResource#performValidatePasswordResetCode + * @methodOf umbraco.resources.authResource + * + * @description + * Checks to see if the provided password reset code is valid + * + * ##usage + *
    +     * authResource.performValidatePasswordResetCode(resetCode)
    +     *    .then(function(data) {
    +     *        //Allow reset of password
    +     *    });
    +     * 
    + * @param {integer} userId User Id + * @param {string} resetCode Password reset code + * @returns {Promise} resourcePromise object + * + */ + performValidatePasswordResetCode: function (userId, resetCode) { + if (!userId) { + return angularHelper.rejectedPromise({ errorMsg: 'User Id cannot be empty' }); + } + if (!resetCode) { + return angularHelper.rejectedPromise({ errorMsg: 'Reset code cannot be empty' }); + } + return umbRequestHelper.resourcePromise($http.post(umbRequestHelper.getApiUrl('authenticationApiBaseUrl', 'PostValidatePasswordResetCode'), { + userId: userId, + resetCode: resetCode + }), 'Password reset code validation failed for userId ' + userId + ', code' + resetCode); + }, + /** + * @ngdoc method + * @name umbraco.resources.currentUserResource#getMembershipProviderConfig + * @methodOf umbraco.resources.currentUserResource + * + * @description + * Gets the configuration of the user membership provider which is used to configure the change password form + */ + getMembershipProviderConfig: function () { + return umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('authenticationApiBaseUrl', 'GetMembershipProviderConfig')), 'Failed to retrieve membership provider config'); + }, + /** + * @ngdoc method + * @name umbraco.resources.authResource#performSetPassword + * @methodOf umbraco.resources.authResource + * + * @description + * Checks to see if the provided password reset code is valid and sets the user's password + * + * ##usage + *
    +     * authResource.performSetPassword(userId, password, confirmPassword, resetCode)
    +     *    .then(function(data) {
    +     *        //Password set
    +     *    });
    +     * 
    + * @param {integer} userId User Id + * @param {string} password New password + * @param {string} confirmPassword Confirmation of new password + * @param {string} resetCode Password reset code + * @returns {Promise} resourcePromise object + * + */ + performSetPassword: function (userId, password, confirmPassword, resetCode) { + if (userId === undefined || userId === null) { + return angularHelper.rejectedPromise({ errorMsg: 'User Id cannot be empty' }); + } + if (!password) { + return angularHelper.rejectedPromise({ errorMsg: 'Password cannot be empty' }); + } + if (password !== confirmPassword) { + return angularHelper.rejectedPromise({ errorMsg: 'Password and confirmation do not match' }); + } + if (!resetCode) { + return angularHelper.rejectedPromise({ errorMsg: 'Reset code cannot be empty' }); + } + return umbRequestHelper.resourcePromise($http.post(umbRequestHelper.getApiUrl('authenticationApiBaseUrl', 'PostSetPassword'), { + userId: userId, + password: password, + resetCode: resetCode + }), 'Password reset code validation failed for userId ' + userId); + }, + unlinkLogin: function (loginProvider, providerKey) { + if (!loginProvider || !providerKey) { + return angularHelper.rejectedPromise({ errorMsg: 'loginProvider or providerKey cannot be empty' }); + } + return umbRequestHelper.resourcePromise($http.post(umbRequestHelper.getApiUrl('authenticationApiBaseUrl', 'PostUnLinkLogin'), { + loginProvider: loginProvider, + providerKey: providerKey + }), 'Unlinking login provider failed'); + }, + /** + * @ngdoc method + * @name umbraco.resources.authResource#performLogout + * @methodOf umbraco.resources.authResource + * + * @description + * Logs out the Umbraco backoffice user + * + * ##usage + *
    +     * authResource.performLogout()
    +     *    .then(function(data) {
    +     *        //Do stuff for logging out...
    +     *    });
    +     * 
    + * @returns {Promise} resourcePromise object + * + */ + performLogout: function () { + return umbRequestHelper.resourcePromise($http.post(umbRequestHelper.getApiUrl('authenticationApiBaseUrl', 'PostLogout'))); + }, + /** + * @ngdoc method + * @name umbraco.resources.authResource#getCurrentUser + * @methodOf umbraco.resources.authResource + * + * @description + * Sends a request to the server to get the current user details, will return a 401 if the user is not logged in + * + * ##usage + *
    +     * authResource.getCurrentUser()
    +     *    .then(function(data) {
    +     *        //Do stuff for fetching the current logged in Umbraco backoffice user
    +     *    });
    +     * 
    + * @returns {Promise} resourcePromise object + * + */ + getCurrentUser: function () { + return umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('authenticationApiBaseUrl', 'GetCurrentUser')), 'Server call failed for getting current user'); + }, + getCurrentUserLinkedLogins: function () { + return umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('authenticationApiBaseUrl', 'GetCurrentUserLinkedLogins')), 'Server call failed for getting current users linked logins'); + }, + /** + * @ngdoc method + * @name umbraco.resources.authResource#isAuthenticated + * @methodOf umbraco.resources.authResource + * + * @description + * Checks if the user is logged in or not - does not return 401 or 403 + * + * ##usage + *
    +     * authResource.isAuthenticated()
    +     *    .then(function(data) {
    +     *        //Do stuff to check if user is authenticated
    +     *    });
    +     * 
    + * @returns {Promise} resourcePromise object + * + */ + isAuthenticated: function () { + return umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('authenticationApiBaseUrl', 'IsAuthenticated')), { success: function (data, status, headers, config) { //if the response is false, they are not logged in so return a rejection - if (data === false || data === "false") { + if (data === false || data === 'false') { return $q.reject('User is not logged in'); } return data; }, - error: function (data, status, headers, config) { + error: function (data, status, headers, config) { return { errorMsg: 'Server call failed for checking authentication', data: data, @@ -339,42 +269,238 @@ function authResource($q, $http, umbRequestHelper, angularHelper) { }; } }); - }, - - /** + }, + /** + * @ngdoc method + * @name umbraco.resources.authResource#getRemainingTimeoutSeconds + * @methodOf umbraco.resources.authResource + * + * @description + * Gets the user's remaining seconds before their login times out + * + * ##usage + *
    +     * authResource.getRemainingTimeoutSeconds()
    +     *    .then(function(data) {
    +     *        //Number of seconds is returned
    +     *    });
    +     * 
    + * @returns {Promise} resourcePromise object + * + */ + getRemainingTimeoutSeconds: function () { + return umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('authenticationApiBaseUrl', 'GetRemainingTimeoutSeconds')), 'Server call failed for checking remaining seconds'); + } + }; + } + angular.module('umbraco.resources').factory('authResource', authResource); + /** + * @ngdoc service + * @name umbraco.resources.codefileResource + * @description Loads in data for files that contain code such as js scripts, partial views and partial view macros + **/ + function codefileResource($q, $http, umbDataFormatter, umbRequestHelper) { + return { + /** * @ngdoc method - * @name umbraco.resources.authResource#getRemainingTimeoutSeconds - * @methodOf umbraco.resources.authResource + * @name umbraco.resources.codefileResource#getByPath + * @methodOf umbraco.resources.codefileResource * * @description - * Gets the user's remaining seconds before their login times out + * Gets a codefile item with a given path * * ##usage *
    -         * authResource.getRemainingTimeoutSeconds()
    -         *    .then(function(data) {
    -         *        //Number of seconds is returned
    +         * codefileResource.getByPath('scripts', 'oooh-la-la.js')
    +         *    .then(function(codefile) {
    +         *        alert('its here!');
              *    });
              * 
    - * @returns {Promise} resourcePromise object + * + *
    +         * codefileResource.getByPath('partialView', 'Grid%2fEditors%2fBase.cshtml')
    +         *    .then(function(codefile) {
    +         *        alert('its here!');
    +         *    });
    +         * 
    + * + * @param {type} the type of script (partialView, partialViewMacro, script) + * @param {virtualpath} the virtual path of the script + * @returns {Promise} resourcePromise object. * */ - getRemainingTimeoutSeconds: function () { - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "authenticationApiBaseUrl", - "GetRemainingTimeoutSeconds")), - 'Server call failed for checking remaining seconds'); - } - - }; -} - -angular.module('umbraco.resources').factory('authResource', authResource); - -/** + getByPath: function (type, virtualpath) { + return umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('codeFileApiBaseUrl', 'GetByPath', [ + { type: type }, + { virtualPath: virtualpath } + ])), 'Failed to retrieve data for ' + type + ' from virtual path ' + virtualpath); + }, + /** + * @ngdoc method + * @name umbraco.resources.codefileResource#getByAlias + * @methodOf umbraco.resources.codefileResource + * + * @description + * Gets a template item with a given alias + * + * ##usage + *
    +         * codefileResource.getByAlias("upload")
    +         *    .then(function(template) {
    +         *        alert('its here!');
    +         *    });
    +         * 
    + * + * @param {String} alias Alias of template to retrieve + * @returns {Promise} resourcePromise object. + * + */ + getByAlias: function (alias) { + return umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('templateApiBaseUrl', 'GetByAlias', [{ alias: alias }])), 'Failed to retrieve data for template with alias: ' + alias); + }, + /** + * @ngdoc method + * @name umbraco.resources.codefileResource#deleteByPath + * @methodOf umbraco.resources.codefileResource + * + * @description + * Deletes a codefile with a given type & path + * + * ##usage + *
    +         * codefileResource.deleteByPath('scripts', 'oooh-la-la.js')
    +         *    .then(function() {
    +         *        alert('its gone!');
    +         *    });
    +         * 
    + * + *
    +         * codefileResource.deleteByPath('partialViews', 'Grid%2fEditors%2fBase.cshtml')
    +         *    .then(function() {
    +         *        alert('its gone!');
    +         *    });
    +         * 
    + * + * @param {type} the type of script (partialViews, partialViewMacros, scripts) + * @param {virtualpath} the virtual path of the script + * @returns {Promise} resourcePromise object. + * + */ + deleteByPath: function (type, virtualpath) { + return umbRequestHelper.resourcePromise($http.post(umbRequestHelper.getApiUrl('codeFileApiBaseUrl', 'Delete', [ + { type: type }, + { virtualPath: virtualpath } + ])), 'Failed to delete item: ' + virtualpath); + }, + /** + * @ngdoc method + * @name umbraco.resources.codefileResource#save + * @methodOf umbraco.resources.codefileResource + * + * @description + * Saves or update a codeFile + * + * ##usage + *
    +         * codefileResource.save(codeFile)
    +         *    .then(function(codeFile) {
    +         *        alert('its saved!');
    +         *    });
    +         * 
    + * + * @param {Object} template object to save + * @returns {Promise} resourcePromise object. + * + */ + save: function (codeFile) { + return umbRequestHelper.resourcePromise($http.post(umbRequestHelper.getApiUrl('codeFileApiBaseUrl', 'PostSave'), codeFile), 'Failed to save data for code file ' + codeFile.virtualPath); + }, + /** + * @ngdoc method + * @name umbraco.resources.codefileResource#getSnippets + * @methodOf umbraco.resources.codefileResource + * + * @description + * Gets code snippets for a given file type + * + * ##usage + *
    +         * codefileResource.getSnippets("partialViews")
    +         *    .then(function(snippets) {
    +         *        alert('its here!');
    +         *    });
    +         * 
    + * + * @param {string} file type: (partialViews, partialViewMacros) + * @returns {Promise} resourcePromise object. + * + */ + getSnippets: function (fileType) { + return umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('codeFileApiBaseUrl', 'GetSnippets?type=' + fileType)), 'Failed to get snippet for' + fileType); + }, + /** + * @ngdoc method + * @name umbraco.resources.codefileResource#getScaffold + * @methodOf umbraco.resources.codefileResource + * + * @description + * Returns a scaffold of an empty codefile item. + * + * The scaffold is used to build editors for code file editors that has not yet been populated with data. + * + * ##usage + *
    +         * codefileResource.getScaffold("partialViews", "Breadcrumb")
    +         *    .then(function(data) {
    +         *        alert('its here!');
    +         *    });
    +         * 
    + * + * @param {string} File type: (scripts, partialViews, partialViewMacros). + * @param {string} Snippet name (Ex. Breadcrumb). + * @returns {Promise} resourcePromise object. + * + */ + getScaffold: function (type, id, snippetName) { + var queryString = '?type=' + type + '&id=' + id; + if (snippetName) { + queryString += '&snippetName=' + snippetName; + } + return umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('codeFileApiBaseUrl', 'GetScaffold' + queryString)), 'Failed to get scaffold for' + type); + }, + /** + * @ngdoc method + * @name umbraco.resources.codefileResource#createContainer + * @methodOf umbraco.resources.codefileResource + * + * @description + * Creates a container/folder + * + * ##usage + *
    +         * codefileResource.createContainer("partialViews", "folder%2ffolder", "folder")
    +         *    .then(function(data) {
    +         *        alert('its here!');
    +         *    });
    +         * 
    + * + * @param {string} File type: (scripts, partialViews, partialViewMacros). + * @param {string} Parent Id: url encoded path + * @param {string} Container name + * @returns {Promise} resourcePromise object. + * + */ + createContainer: function (type, parentId, name) { + return umbRequestHelper.resourcePromise($http.post(umbRequestHelper.getApiUrl('codeFileApiBaseUrl', 'PostCreateContainer', { + type: type, + parentId: parentId, + name: encodeURIComponent(name) + })), 'Failed to create a folder under parent id ' + parentId); + } + }; + } + angular.module('umbraco.resources').factory('codefileResource', codefileResource); + /** * @ngdoc service * @name umbraco.resources.contentResource * @description Handles all transactions of content data @@ -398,36 +524,36 @@ angular.module('umbraco.resources').factory('authResource', authResource); * }); * **/ - -function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { - - /** internal method process the saving of data and post processing the result */ - function saveContentItem(content, action, files) { - return umbRequestHelper.postSaveContent({ - restApiUrl: umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "PostSave"), - content: content, - action: action, - files: files, - dataFormatter: function (c, a) { - return umbDataFormatter.formatContentPostData(c, a); - } - }); - } - - return { - - getRecycleBin: function () { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "GetRecycleBin")), - 'Failed to retrieve data for content recycle bin'); - }, - - /** + function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { + /** internal method process the saving of data and post processing the result */ + function saveContentItem(content, action, files, restApiUrl) { + return umbRequestHelper.postSaveContent({ + restApiUrl: restApiUrl, + content: content, + action: action, + files: files, + dataFormatter: function (c, a) { + return umbDataFormatter.formatContentPostData(c, a); + } + }); + } + return { + savePermissions: function (saveModel) { + if (!saveModel) { + throw 'saveModel cannot be null'; + } + if (!saveModel.contentId) { + throw 'saveModel.contentId cannot be null'; + } + if (!saveModel.permissions) { + throw 'saveModel.permissions cannot be null'; + } + return umbRequestHelper.resourcePromise($http.post(umbRequestHelper.getApiUrl('contentApiBaseUrl', 'PostSaveUserGroupPermissions'), saveModel), 'Failed to save permissions'); + }, + getRecycleBin: function () { + return umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('contentApiBaseUrl', 'GetRecycleBin')), 'Failed to retrieve data for content recycle bin'); + }, + /** * @ngdoc method * @name umbraco.resources.contentResource#sort * @methodOf umbraco.resources.contentResource @@ -449,27 +575,22 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { * @returns {Promise} resourcePromise object. * */ - sort: function (args) { - if (!args) { - throw "args cannot be null"; - } - if (!args.parentId) { - throw "args.parentId cannot be null"; - } - if (!args.sortedIds) { - throw "args.sortedIds cannot be null"; - } - - return umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper.getApiUrl("contentApiBaseUrl", "PostSort"), - { - parentId: args.parentId, - idSortOrder: args.sortedIds - }), - 'Failed to sort content'); - }, - - /** + sort: function (args) { + if (!args) { + throw 'args cannot be null'; + } + if (!args.parentId) { + throw 'args.parentId cannot be null'; + } + if (!args.sortedIds) { + throw 'args.sortedIds cannot be null'; + } + return umbRequestHelper.resourcePromise($http.post(umbRequestHelper.getApiUrl('contentApiBaseUrl', 'PostSort'), { + parentId: args.parentId, + idSortOrder: args.sortedIds + }), 'Failed to sort content'); + }, + /** * @ngdoc method * @name umbraco.resources.contentResource#move * @methodOf umbraco.resources.contentResource @@ -492,27 +613,22 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { * @returns {Promise} resourcePromise object. * */ - move: function (args) { - if (!args) { - throw "args cannot be null"; - } - if (!args.parentId) { - throw "args.parentId cannot be null"; - } - if (!args.id) { - throw "args.id cannot be null"; - } - - return umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper.getApiUrl("contentApiBaseUrl", "PostMove"), - { - parentId: args.parentId, - id: args.id - }), - 'Failed to move content'); - }, - - /** + move: function (args) { + if (!args) { + throw 'args cannot be null'; + } + if (!args.parentId) { + throw 'args.parentId cannot be null'; + } + if (!args.id) { + throw 'args.id cannot be null'; + } + return umbRequestHelper.resourcePromise($http.post(umbRequestHelper.getApiUrl('contentApiBaseUrl', 'PostMove'), { + parentId: args.parentId, + id: args.id + }), 'Failed to move content'); + }, + /** * @ngdoc method * @name umbraco.resources.contentResource#copy * @methodOf umbraco.resources.contentResource @@ -536,24 +652,19 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { * @returns {Promise} resourcePromise object. * */ - copy: function (args) { - if (!args) { - throw "args cannot be null"; - } - if (!args.parentId) { - throw "args.parentId cannot be null"; - } - if (!args.id) { - throw "args.id cannot be null"; - } - - return umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper.getApiUrl("contentApiBaseUrl", "PostCopy"), - args), - 'Failed to copy content'); - }, - - /** + copy: function (args) { + if (!args) { + throw 'args cannot be null'; + } + if (!args.parentId) { + throw 'args.parentId cannot be null'; + } + if (!args.id) { + throw 'args.id cannot be null'; + } + return umbRequestHelper.resourcePromise($http.post(umbRequestHelper.getApiUrl('contentApiBaseUrl', 'PostCopy'), args), 'Failed to copy content'); + }, + /** * @ngdoc method * @name umbraco.resources.contentResource#unPublish * @methodOf umbraco.resources.contentResource @@ -574,20 +685,13 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { * @returns {Promise} resourcePromise object. * */ - unPublish: function (id) { - if (!id) { - throw "id cannot be null"; - } - - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "PostUnPublish", - [{ id: id }])), - 'Failed to publish content with id ' + id); - }, - /** + unPublish: function (id) { + if (!id) { + throw 'id cannot be null'; + } + return umbRequestHelper.resourcePromise($http.post(umbRequestHelper.getApiUrl('contentApiBaseUrl', 'PostUnPublish', [{ id: id }])), 'Failed to publish content with id ' + id); + }, + /** * @ngdoc method * @name umbraco.resources.contentResource#emptyRecycleBin * @methodOf umbraco.resources.contentResource @@ -606,16 +710,10 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { * @returns {Promise} resourcePromise object. * */ - emptyRecycleBin: function () { - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "EmptyRecycleBin")), - 'Failed to empty the recycle bin'); - }, - - /** + emptyRecycleBin: function () { + return umbRequestHelper.resourcePromise($http.post(umbRequestHelper.getApiUrl('contentApiBaseUrl', 'EmptyRecycleBin')), 'Failed to empty the recycle bin'); + }, + /** * @ngdoc method * @name umbraco.resources.contentResource#deleteById * @methodOf umbraco.resources.contentResource @@ -635,17 +733,13 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { * @returns {Promise} resourcePromise object. * */ - deleteById: function (id) { - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "DeleteById", - [{ id: id }])), - 'Failed to delete item ' + id); - }, - - /** + deleteById: function (id) { + return umbRequestHelper.resourcePromise($http.post(umbRequestHelper.getApiUrl('contentApiBaseUrl', 'DeleteById', [{ id: id }])), 'Failed to delete item ' + id); + }, + deleteBlueprint: function (id) { + return umbRequestHelper.resourcePromise($http.post(umbRequestHelper.getApiUrl('contentApiBaseUrl', 'DeleteBlueprint', [{ id: id }])), 'Failed to delete blueprint ' + id); + }, + /** * @ngdoc method * @name umbraco.resources.contentResource#getById * @methodOf umbraco.resources.contentResource @@ -666,17 +760,25 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { * @returns {Promise} resourcePromise object containing the content item. * */ - getById: function (id) { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "GetById", - [{ id: id }])), - 'Failed to retrieve data for content id ' + id); - }, - - /** + getById: function (id) { + return umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('contentApiBaseUrl', 'GetById', [{ id: id }])), 'Failed to retrieve data for content id ' + id); + }, + getBlueprintById: function (id) { + return umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('contentApiBaseUrl', 'GetBlueprintById', [{ id: id }])), 'Failed to retrieve data for content id ' + id); + }, + getNotifySettingsById: function (id) { + return umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('contentApiBaseUrl', 'GetNotificationOptions', [{ contentId: id }])), 'Failed to retrieve data for content id ' + id); + }, + setNotifySettingsById: function (id, options) { + if (!id) { + throw 'contentId cannot be null'; + } + return umbRequestHelper.resourcePromise($http.post(umbRequestHelper.getApiUrl('contentApiBaseUrl', 'PostNotificationOptions', { + contentId: id, + notifyOptions: options + })), 'Failed to set notify settings for content id ' + id); + }, + /** * @ngdoc method * @name umbraco.resources.contentResource#getByIds * @methodOf umbraco.resources.contentResource @@ -697,24 +799,14 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { * @returns {Promise} resourcePromise object containing the content items array. * */ - getByIds: function (ids) { - - var idQuery = ""; - _.each(ids, function (item) { - idQuery += "ids=" + item + "&"; - }); - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "GetByIds", - idQuery)), - 'Failed to retrieve data for content with multiple ids'); - }, - - - /** + getByIds: function (ids) { + var idQuery = ''; + _.each(ids, function (item) { + idQuery += 'ids=' + item + '&'; + }); + return umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('contentApiBaseUrl', 'GetByIds', idQuery)), 'Failed to retrieve data for content with multiple ids'); + }, + /** * @ngdoc method * @name umbraco.resources.contentResource#getScaffold * @methodOf umbraco.resources.contentResource @@ -746,18 +838,19 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { * @returns {Promise} resourcePromise object containing the content scaffold. * */ - getScaffold: function (parentId, alias) { - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "GetEmpty", - [{ contentTypeAlias: alias }, { parentId: parentId }])), - 'Failed to retrieve data for empty content item type ' + alias); - }, - - /** + getScaffold: function (parentId, alias) { + return umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('contentApiBaseUrl', 'GetEmpty', [ + { contentTypeAlias: alias }, + { parentId: parentId } + ])), 'Failed to retrieve data for empty content item type ' + alias); + }, + getBlueprintScaffold: function (parentId, blueprintId) { + return umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('contentApiBaseUrl', 'GetEmpty', [ + { blueprintId: blueprintId }, + { parentId: parentId } + ])), 'Failed to retrieve blueprint for id ' + blueprintId); + }, + /** * @ngdoc method * @name umbraco.resources.contentResource#getNiceUrl * @methodOf umbraco.resources.contentResource @@ -777,16 +870,10 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { * @returns {Promise} resourcePromise object containing the url. * */ - getNiceUrl: function (id) { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "GetNiceUrl", [{ id: id }])), - 'Failed to retrieve url for id:' + id); - }, - - /** + getNiceUrl: function (id) { + return umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('contentApiBaseUrl', 'GetNiceUrl', [{ id: id }])), 'Failed to retrieve url for id:' + id); + }, + /** * @ngdoc method * @name umbraco.resources.contentResource#getChildren * @methodOf umbraco.resources.contentResource @@ -813,63 +900,54 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { * @returns {Promise} resourcePromise object containing an array of content items. * */ - getChildren: function (parentId, options) { - - var defaults = { - pageSize: 0, - pageNumber: 0, - filter: '', - orderDirection: "Ascending", - orderBy: "SortOrder", - orderBySystemField: true - }; - if (options === undefined) { - options = {}; - } - //overwrite the defaults if there are any specified - angular.extend(defaults, options); - //now copy back to the options we will use - options = defaults; - //change asc/desct - if (options.orderDirection === "asc") { - options.orderDirection = "Ascending"; - } - else if (options.orderDirection === "desc") { - options.orderDirection = "Descending"; - } - - //converts the value to a js bool - function toBool(v) { - if (angular.isNumber(v)) { - return v > 0; + getChildren: function (parentId, options) { + var defaults = { + includeProperties: [], + pageSize: 0, + pageNumber: 0, + filter: '', + orderDirection: 'Ascending', + orderBy: 'SortOrder', + orderBySystemField: true + }; + if (options === undefined) { + options = {}; } - if (angular.isString(v)) { - return v === "true"; + //overwrite the defaults if there are any specified + angular.extend(defaults, options); + //now copy back to the options we will use + options = defaults; + //change asc/desct + if (options.orderDirection === 'asc') { + options.orderDirection = 'Ascending'; + } else if (options.orderDirection === 'desc') { + options.orderDirection = 'Descending'; } - if (typeof v === "boolean") { - return v; + //converts the value to a js bool + function toBool(v) { + if (angular.isNumber(v)) { + return v > 0; + } + if (angular.isString(v)) { + return v === 'true'; + } + if (typeof v === 'boolean') { + return v; + } + return false; } - return false; - } - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "GetChildren", - [ - { id: parentId }, - { pageNumber: options.pageNumber }, - { pageSize: options.pageSize }, - { orderBy: options.orderBy }, - { orderDirection: options.orderDirection }, - { orderBySystemField: toBool(options.orderBySystemField) }, - { filter: options.filter } - ])), - 'Failed to retrieve children for content item ' + parentId); - }, - - /** + return umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('contentApiBaseUrl', 'GetChildren', { + id: parentId, + includeProperties: _.pluck(options.includeProperties, 'alias').join(','), + pageNumber: options.pageNumber, + pageSize: options.pageSize, + orderBy: options.orderBy, + orderDirection: options.orderDirection, + orderBySystemField: toBool(options.orderBySystemField), + filter: options.filter + })), 'Failed to retrieve children for content item ' + parentId); + }, + /** * @ngdoc method * @name umbraco.resources.contentResource#hasPermission * @methodOf umbraco.resources.contentResource @@ -891,27 +969,19 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { * @returns {Promise} resourcePromise object. * */ - checkPermission: function (permission, id) { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "HasPermission", - [{ permissionToCheck: permission }, { nodeId: id }])), - 'Failed to check permission for item ' + id); - }, - - getPermissions: function (nodeIds) { - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "GetPermissions"), - nodeIds), - 'Failed to get permissions'); - }, - - /** + checkPermission: function (permission, id) { + return umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('contentApiBaseUrl', 'HasPermission', [ + { permissionToCheck: permission }, + { nodeId: id } + ])), 'Failed to check permission for item ' + id); + }, + getDetailedPermissions: function (contentId) { + return umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('contentApiBaseUrl', 'GetDetailedPermissions', { contentId: contentId })), 'Failed to retrieve permissions for content item ' + contentId); + }, + getPermissions: function (nodeIds) { + return umbRequestHelper.resourcePromise($http.post(umbRequestHelper.getApiUrl('contentApiBaseUrl', 'GetPermissions'), nodeIds), 'Failed to get permissions'); + }, + /** * @ngdoc method * @name umbraco.resources.contentResource#save * @methodOf umbraco.resources.contentResource @@ -939,12 +1009,15 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { * @returns {Promise} resourcePromise object containing the saved content item. * */ - save: function (content, isNew, files) { - return saveContentItem(content, "save" + (isNew ? "New" : ""), files); - }, - - - /** + save: function (content, isNew, files) { + var endpoint = umbRequestHelper.getApiUrl('contentApiBaseUrl', 'PostSave'); + return saveContentItem(content, 'save' + (isNew ? 'New' : ''), files, endpoint); + }, + saveBlueprint: function (content, isNew, files) { + var endpoint = umbRequestHelper.getApiUrl('contentApiBaseUrl', 'PostSaveBlueprint'); + return saveContentItem(content, 'save' + (isNew ? 'New' : ''), files, endpoint); + }, + /** * @ngdoc method * @name umbraco.resources.contentResource#publish * @methodOf umbraco.resources.contentResource @@ -972,12 +1045,11 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { * @returns {Promise} resourcePromise object containing the saved content item. * */ - publish: function (content, isNew, files) { - return saveContentItem(content, "publish" + (isNew ? "New" : ""), files); - }, - - - /** + publish: function (content, isNew, files) { + var endpoint = umbRequestHelper.getApiUrl('contentApiBaseUrl', 'PostSave'); + return saveContentItem(content, 'publish' + (isNew ? 'New' : ''), files, endpoint); + }, + /** * @ngdoc method * @name umbraco.resources.contentResource#sendToPublish * @methodOf umbraco.resources.contentResource @@ -1003,11 +1075,11 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { * @returns {Promise} resourcePromise object containing the saved content item. * */ - sendToPublish: function (content, isNew, files) { - return saveContentItem(content, "sendPublish" + (isNew ? "New" : ""), files); - }, - - /** + sendToPublish: function (content, isNew, files) { + var endpoint = umbRequestHelper.getApiUrl('contentApiBaseUrl', 'PostSave'); + return saveContentItem(content, 'sendPublish' + (isNew ? 'New' : ''), files, endpoint); + }, + /** * @ngdoc method * @name umbraco.resources.contentResource#publishByid * @methodOf umbraco.resources.contentResource @@ -1027,71 +1099,69 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { * @returns {Promise} resourcePromise object containing the published content item. * */ - publishById: function (id) { - - if (!id) { - throw "id cannot be null"; + publishById: function (id) { + if (!id) { + throw 'id cannot be null'; + } + return umbRequestHelper.resourcePromise($http.post(umbRequestHelper.getApiUrl('contentApiBaseUrl', 'PostPublishById', [{ id: id }])), 'Failed to publish content with id ' + id); + }, + createBlueprintFromContent: function (contentId, name) { + return umbRequestHelper.resourcePromise($http.post(umbRequestHelper.getApiUrl('contentApiBaseUrl', 'CreateBlueprintFromContent', { + contentId: contentId, + name: name + })), 'Failed to create blueprint from content with id ' + contentId); } - - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "PostPublishById", - [{ id: id }])), - 'Failed to publish content with id ' + id); - - } - - - }; -} - -angular.module('umbraco.resources').factory('contentResource', contentResource); - -/** + }; + } + angular.module('umbraco.resources').factory('contentResource', contentResource); + /** * @ngdoc service * @name umbraco.resources.contentTypeResource * @description Loads in data for content types **/ -function contentTypeResource($q, $http, umbRequestHelper, umbDataFormatter) { - - return { - - getCount: function () { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "contentTypeApiBaseUrl", - "GetCount")), - 'Failed to retrieve count'); - }, - - getAvailableCompositeContentTypes: function (contentTypeId, filterContentTypes, filterPropertyTypes) { - if (!filterContentTypes) { - filterContentTypes = []; - } - if (!filterPropertyTypes) { - filterPropertyTypes = []; - } - - var query = { - contentTypeId: contentTypeId, - filterContentTypes: filterContentTypes, - filterPropertyTypes: filterPropertyTypes - }; - - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "contentTypeApiBaseUrl", - "GetAvailableCompositeContentTypes"), - query), - 'Failed to retrieve data for content type id ' + contentTypeId); - }, - - - /** + function contentTypeResource($q, $http, umbRequestHelper, umbDataFormatter) { + return { + getCount: function () { + return umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('contentTypeApiBaseUrl', 'GetCount')), 'Failed to retrieve count'); + }, + getAvailableCompositeContentTypes: function (contentTypeId, filterContentTypes, filterPropertyTypes) { + if (!filterContentTypes) { + filterContentTypes = []; + } + if (!filterPropertyTypes) { + filterPropertyTypes = []; + } + var query = { + contentTypeId: contentTypeId, + filterContentTypes: filterContentTypes, + filterPropertyTypes: filterPropertyTypes + }; + return umbRequestHelper.resourcePromise($http.post(umbRequestHelper.getApiUrl('contentTypeApiBaseUrl', 'GetAvailableCompositeContentTypes'), query), 'Failed to retrieve data for content type id ' + contentTypeId); + }, + /** + * @ngdoc method + * @name umbraco.resources.contentTypeResource#getWhereCompositionIsUsedInContentTypes + * @methodOf umbraco.resources.contentTypeResource + * + * @description + * Returns a list of content types which use a specific composition with a given id + * + * ##usage + *
    +         * contentTypeResource.getWhereCompositionIsUsedInContentTypes(1234)
    +         *    .then(function(contentTypeList) {
    +         *        console.log(contentTypeList);
    +         *    });
    +         * 
    + * @param {Int} contentTypeId id of the composition content type to retrieve the list of the content types where it has been used + * @returns {Promise} resourcePromise object. + * + */ + getWhereCompositionIsUsedInContentTypes: function (contentTypeId) { + var query = { contentTypeId: contentTypeId }; + return umbRequestHelper.resourcePromise($http.post(umbRequestHelper.getApiUrl('contentTypeApiBaseUrl', 'GetWhereCompositionIsUsedInContentTypes'), query), 'Failed to retrieve data for content type id ' + contentTypeId); + }, + /** * @ngdoc method * @name umbraco.resources.contentTypeResource#getAllowedTypes * @methodOf umbraco.resources.contentTypeResource @@ -1110,19 +1180,10 @@ function contentTypeResource($q, $http, umbRequestHelper, umbDataFormatter) { * @returns {Promise} resourcePromise object. * */ - getAllowedTypes: function (contentTypeId) { - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "contentTypeApiBaseUrl", - "GetAllowedChildren", - [{ contentId: contentTypeId }])), - 'Failed to retrieve data for content id ' + contentTypeId); - }, - - - /** + getAllowedTypes: function (contentTypeId) { + return umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('contentTypeApiBaseUrl', 'GetAllowedChildren', [{ contentId: contentTypeId }])), 'Failed to retrieve data for content id ' + contentTypeId); + }, + /** * @ngdoc method * @name umbraco.resources.contentTypeResource#getAllPropertyTypeAliases * @methodOf umbraco.resources.contentTypeResource @@ -1133,60 +1194,25 @@ function contentTypeResource($q, $http, umbRequestHelper, umbDataFormatter) { * @returns {Promise} resourcePromise object. * */ - getAllPropertyTypeAliases: function () { - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "contentTypeApiBaseUrl", - "GetAllPropertyTypeAliases")), - 'Failed to retrieve property type aliases'); - }, - - getPropertyTypeScaffold : function (id) { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "contentTypeApiBaseUrl", - "GetPropertyTypeScaffold", - [{ id: id }])), - 'Failed to retrieve property type scaffold'); - }, - - getById: function (id) { - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "contentTypeApiBaseUrl", - "GetById", - [{ id: id }])), - 'Failed to retrieve content type'); - }, - - deleteById: function (id) { - - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "contentTypeApiBaseUrl", - "DeleteById", - [{ id: id }])), - 'Failed to delete content type'); - }, - - deleteContainerById: function (id) { - - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "contentTypeApiBaseUrl", - "DeleteContainer", - [{ id: id }])), - 'Failed to delete content type contaier'); - }, - - /** + getAllPropertyTypeAliases: function () { + return umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('contentTypeApiBaseUrl', 'GetAllPropertyTypeAliases')), 'Failed to retrieve property type aliases'); + }, + getAllStandardFields: function () { + return umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('contentTypeApiBaseUrl', 'GetAllStandardFields')), 'Failed to retrieve standard fields'); + }, + getPropertyTypeScaffold: function (id) { + return umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('contentTypeApiBaseUrl', 'GetPropertyTypeScaffold', [{ id: id }])), 'Failed to retrieve property type scaffold'); + }, + getById: function (id) { + return umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('contentTypeApiBaseUrl', 'GetById', [{ id: id }])), 'Failed to retrieve content type'); + }, + deleteById: function (id) { + return umbRequestHelper.resourcePromise($http.post(umbRequestHelper.getApiUrl('contentTypeApiBaseUrl', 'DeleteById', [{ id: id }])), 'Failed to delete content type'); + }, + deleteContainerById: function (id) { + return umbRequestHelper.resourcePromise($http.post(umbRequestHelper.getApiUrl('contentTypeApiBaseUrl', 'DeleteContainer', [{ id: id }])), 'Failed to delete content type contaier'); + }, + /** * @ngdoc method * @name umbraco.resources.contentTypeResource#getAll * @methodOf umbraco.resources.contentTypeResource @@ -1197,27 +1223,13 @@ function contentTypeResource($q, $http, umbRequestHelper, umbDataFormatter) { * @returns {Promise} resourcePromise object. * */ - getAll: function () { - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "contentTypeApiBaseUrl", - "GetAll")), - 'Failed to retrieve all content types'); - }, - - getScaffold: function (parentId) { - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "contentTypeApiBaseUrl", - "GetEmpty", { parentId: parentId })), - 'Failed to retrieve content type scaffold'); - }, - - /** + getAll: function () { + return umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('contentTypeApiBaseUrl', 'GetAll')), 'Failed to retrieve all content types'); + }, + getScaffold: function (parentId) { + return umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('contentTypeApiBaseUrl', 'GetEmpty', { parentId: parentId })), 'Failed to retrieve content type scaffold'); + }, + /** * @ngdoc method * @name umbraco.resources.contentTypeResource#save * @methodOf umbraco.resources.contentTypeResource @@ -1229,16 +1241,11 @@ function contentTypeResource($q, $http, umbRequestHelper, umbDataFormatter) { * @returns {Promise} resourcePromise object. * */ - save: function (contentType) { - - var saveModel = umbDataFormatter.formatContentTypePostData(contentType); - - return umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper.getApiUrl("contentTypeApiBaseUrl", "PostSave"), saveModel), - 'Failed to save data for content type id ' + contentType.id); - }, - - /** + save: function (contentType) { + var saveModel = umbDataFormatter.formatContentTypePostData(contentType); + return umbRequestHelper.resourcePromise($http.post(umbRequestHelper.getApiUrl('contentTypeApiBaseUrl', 'PostSave'), saveModel), 'Failed to save data for content type id ' + contentType.id); + }, + /** * @ngdoc method * @name umbraco.resources.contentTypeResource#move * @methodOf umbraco.resources.contentTypeResource @@ -1261,71 +1268,86 @@ function contentTypeResource($q, $http, umbRequestHelper, umbDataFormatter) { * @returns {Promise} resourcePromise object. * */ - move: function (args) { - if (!args) { - throw "args cannot be null"; + move: function (args) { + if (!args) { + throw 'args cannot be null'; + } + if (!args.parentId) { + throw 'args.parentId cannot be null'; + } + if (!args.id) { + throw 'args.id cannot be null'; + } + return umbRequestHelper.resourcePromise($http.post(umbRequestHelper.getApiUrl('contentTypeApiBaseUrl', 'PostMove'), { + parentId: args.parentId, + id: args.id + }), 'Failed to move content'); + }, + copy: function (args) { + if (!args) { + throw 'args cannot be null'; + } + if (!args.parentId) { + throw 'args.parentId cannot be null'; + } + if (!args.id) { + throw 'args.id cannot be null'; + } + return umbRequestHelper.resourcePromise($http.post(umbRequestHelper.getApiUrl('contentTypeApiBaseUrl', 'PostCopy'), { + parentId: args.parentId, + id: args.id + }), 'Failed to copy content'); + }, + createContainer: function (parentId, name) { + return umbRequestHelper.resourcePromise($http.post(umbRequestHelper.getApiUrl('contentTypeApiBaseUrl', 'PostCreateContainer', { + parentId: parentId, + name: encodeURIComponent(name) + })), 'Failed to create a folder under parent id ' + parentId); + }, + createCollection: function (parentId, collectionName, collectionItemName, collectionIcon, collectionItemIcon) { + return umbRequestHelper.resourcePromise($http.post(umbRequestHelper.getApiUrl('contentTypeApiBaseUrl', 'PostCreateCollection', { + parentId: parentId, + collectionName: collectionName, + collectionItemName: collectionItemName, + collectionIcon: collectionIcon, + collectionItemIcon: collectionItemIcon + })), 'Failed to create collection under ' + parentId); + }, + renameContainer: function (id, name) { + return umbRequestHelper.resourcePromise($http.post(umbRequestHelper.getApiUrl('contentTypeApiBaseUrl', 'PostRenameContainer', { + id: id, + name: name + })), 'Failed to rename the folder with id ' + id); } - if (!args.parentId) { - throw "args.parentId cannot be null"; - } - if (!args.id) { - throw "args.id cannot be null"; - } - - return umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper.getApiUrl("contentTypeApiBaseUrl", "PostMove"), - { - parentId: args.parentId, - id: args.id - }), - 'Failed to move content'); - }, - - copy: function(args) { - if (!args) { - throw "args cannot be null"; - } - if (!args.parentId) { - throw "args.parentId cannot be null"; - } - if (!args.id) { - throw "args.id cannot be null"; - } - - return umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper.getApiUrl("contentTypeApiBaseUrl", "PostCopy"), - { - parentId: args.parentId, - id: args.id - }), - 'Failed to copy content'); - }, - - createContainer: function(parentId, name) { - - return umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper.getApiUrl("contentTypeApiBaseUrl", "PostCreateContainer", { parentId: parentId, name: name })), - 'Failed to create a folder under parent id ' + parentId); - - } - - }; -} -angular.module('umbraco.resources').factory('contentTypeResource', contentTypeResource); - -/** + }; + } + angular.module('umbraco.resources').factory('contentTypeResource', contentTypeResource); + /** * @ngdoc service * @name umbraco.resources.currentUserResource * @description Used for read/updates for the currently logged in user * * **/ -function currentUserResource($q, $http, umbRequestHelper) { - - //the factory object returned - return { - - /** + function currentUserResource($q, $http, umbRequestHelper, umbDataFormatter) { + //the factory object returned + return { + saveTourStatus: function (tourStatus) { + if (!tourStatus) { + return angularHelper.rejectedPromise({ errorMsg: 'tourStatus cannot be empty' }); + } + return umbRequestHelper.resourcePromise($http.post(umbRequestHelper.getApiUrl('currentUserApiBaseUrl', 'PostSetUserTour'), tourStatus), 'Failed to save tour status'); + }, + getTours: function () { + return umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('currentUserApiBaseUrl', 'GetUserTours')), 'Failed to get tours'); + }, + performSetInvitedUserPassword: function (newPassword) { + if (!newPassword) { + return angularHelper.rejectedPromise({ errorMsg: 'newPassword cannot be empty' }); + } + return umbRequestHelper.resourcePromise($http.post(umbRequestHelper.getApiUrl('currentUserApiBaseUrl', 'PostSetInvitedUserPassword'), angular.toJson(newPassword)), 'Failed to change password'); + }, + /** * @ngdoc method * @name umbraco.resources.currentUserResource#changePassword * @methodOf umbraco.resources.currentUserResource @@ -1336,47 +1358,25 @@ function currentUserResource($q, $http, umbRequestHelper) { * @returns {Promise} resourcePromise object containing the user array. * */ - changePassword: function (changePasswordArgs) { - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "currentUserApiBaseUrl", - "PostChangePassword"), - changePasswordArgs), - 'Failed to change password'); - }, - - /** - * @ngdoc method - * @name umbraco.resources.currentUserResource#getMembershipProviderConfig - * @methodOf umbraco.resources.currentUserResource - * - * @description - * Gets the configuration of the user membership provider which is used to configure the change password form - */ - getMembershipProviderConfig: function () { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "currentUserApiBaseUrl", - "GetMembershipProviderConfig")), - 'Failed to retrieve membership provider config'); - }, - }; -} - -angular.module('umbraco.resources').factory('currentUserResource', currentUserResource); - -/** + changePassword: function (changePasswordArgs) { + changePasswordArgs = umbDataFormatter.formatChangePasswordModel(changePasswordArgs); + if (!changePasswordArgs) { + throw 'No password data to change'; + } + return umbRequestHelper.resourcePromise($http.post(umbRequestHelper.getApiUrl('currentUserApiBaseUrl', 'PostChangePassword'), changePasswordArgs), 'Failed to change password'); + } + }; + } + angular.module('umbraco.resources').factory('currentUserResource', currentUserResource); + /** * @ngdoc service * @name umbraco.resources.dashboardResource * @description Handles loading the dashboard manifest **/ -function dashboardResource($q, $http, umbRequestHelper) { - //the factory object returned - return { - - /** + function dashboardResource($q, $http, umbRequestHelper) { + //the factory object returned + return { + /** * @ngdoc method * @name umbraco.resources.dashboardResource#getDashboard * @methodOf umbraco.resources.dashboardResource @@ -1388,17 +1388,10 @@ function dashboardResource($q, $http, umbRequestHelper) { * @returns {Promise} resourcePromise object containing the user array. * */ - getDashboard: function (section) { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "dashboardApiBaseUrl", - "GetDashboard", - [{ section: section }])), - 'Failed to get dashboard ' + section); - }, - - /** + getDashboard: function (section) { + return umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('dashboardApiBaseUrl', 'GetDashboard', [{ section: section }])), 'Failed to get dashboard ' + section); + }, + /** * @ngdoc method * @name umbraco.resources.dashboardResource#getRemoteDashboardContent * @methodOf umbraco.resources.dashboardResource @@ -1410,53 +1403,33 @@ function dashboardResource($q, $http, umbRequestHelper) { * @returns {Promise} resourcePromise object containing the user array. * */ - getRemoteDashboardContent: function (section, baseurl) { - - //build request values with optional params - var values = [{ section: section }]; - if (baseurl) - { - values.push({ baseurl: baseurl }); + getRemoteDashboardContent: function (section, baseurl) { + //build request values with optional params + var values = [{ section: section }]; + if (baseurl) { + values.push({ baseurl: baseurl }); + } + return umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('dashboardApiBaseUrl', 'GetRemoteDashboardContent', values)), 'Failed to get dashboard content'); + }, + getRemoteDashboardCssUrl: function (section, baseurl) { + //build request values with optional params + var values = [{ section: section }]; + if (baseurl) { + values.push({ baseurl: baseurl }); + } + return umbRequestHelper.getApiUrl('dashboardApiBaseUrl', 'GetRemoteDashboardCss', values); } - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "dashboardApiBaseUrl", - "GetRemoteDashboardContent", - values)), "Failed to get dashboard content"); - }, - - getRemoteDashboardCssUrl: function (section, baseurl) { - - //build request values with optional params - var values = [{ section: section }]; - if (baseurl) { - values.push({ baseurl: baseurl }); - } - - return umbRequestHelper.getApiUrl( - "dashboardApiBaseUrl", - "GetRemoteDashboardCss", - values); - } - - - - }; -} - -angular.module('umbraco.resources').factory('dashboardResource', dashboardResource); -/** + }; + } + angular.module('umbraco.resources').factory('dashboardResource', dashboardResource); + /** * @ngdoc service * @name umbraco.resources.dataTypeResource * @description Loads in data for data types **/ -function dataTypeResource($q, $http, umbDataFormatter, umbRequestHelper) { - - return { - - /** + function dataTypeResource($q, $http, umbDataFormatter, umbRequestHelper) { + return { + /** * @ngdoc method * @name umbraco.resources.dataTypeResource#getPreValues * @methodOf umbraco.resources.dataTypeResource @@ -1477,22 +1450,16 @@ function dataTypeResource($q, $http, umbDataFormatter, umbRequestHelper) { * @returns {Promise} resourcePromise object. * */ - getPreValues: function (editorAlias, dataTypeId) { - - if (!dataTypeId) { - dataTypeId = -1; - } - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "dataTypeApiBaseUrl", - "GetPreValues", - [{ editorAlias: editorAlias }, { dataTypeId: dataTypeId }])), - "Failed to retrieve pre values for editor alias " + editorAlias); - }, - - /** + getPreValues: function (editorAlias, dataTypeId) { + if (!dataTypeId) { + dataTypeId = -1; + } + return umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('dataTypeApiBaseUrl', 'GetPreValues', [ + { editorAlias: editorAlias }, + { dataTypeId: dataTypeId } + ])), 'Failed to retrieve pre values for editor alias ' + editorAlias); + }, + /** * @ngdoc method * @name umbraco.resources.dataTypeResource#getById * @methodOf umbraco.resources.dataTypeResource @@ -1512,18 +1479,10 @@ function dataTypeResource($q, $http, umbDataFormatter, umbRequestHelper) { * @returns {Promise} resourcePromise object. * */ - getById: function (id) { - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "dataTypeApiBaseUrl", - "GetById", - [{ id: id }])), - "Failed to retrieve data for data type id " + id); - }, - - /** + getById: function (id) { + return umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('dataTypeApiBaseUrl', 'GetById', [{ id: id }])), 'Failed to retrieve data for data type id ' + id); + }, + /** * @ngdoc method * @name umbraco.resources.dataTypeResource#getByName * @methodOf umbraco.resources.dataTypeResource @@ -1543,56 +1502,22 @@ function dataTypeResource($q, $http, umbDataFormatter, umbRequestHelper) { * @returns {Promise} resourcePromise object. * */ - getByName: function (name) { - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "dataTypeApiBaseUrl", - "GetByName", - [{ name: name }])), - "Failed to retrieve data for data type with name: " + name); - }, - - getAll: function () { - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "dataTypeApiBaseUrl", - "GetAll")), - "Failed to retrieve data"); - }, - - getGroupedDataTypes: function () { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "dataTypeApiBaseUrl", - "GetGroupedDataTypes")), - "Failed to retrieve data"); - }, - - getGroupedPropertyEditors : function(){ - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "dataTypeApiBaseUrl", - "GetGroupedPropertyEditors")), - "Failed to retrieve data"); - }, - - getAllPropertyEditors: function () { - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "dataTypeApiBaseUrl", - "GetAllPropertyEditors")), - "Failed to retrieve data"); - }, - - /** + getByName: function (name) { + return umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('dataTypeApiBaseUrl', 'GetByName', [{ name: name }])), 'Failed to retrieve data for data type with name: ' + name); + }, + getAll: function () { + return umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('dataTypeApiBaseUrl', 'GetAll')), 'Failed to retrieve data'); + }, + getGroupedDataTypes: function () { + return umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('dataTypeApiBaseUrl', 'GetGroupedDataTypes')), 'Failed to retrieve data'); + }, + getGroupedPropertyEditors: function () { + return umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('dataTypeApiBaseUrl', 'GetGroupedPropertyEditors')), 'Failed to retrieve data'); + }, + getAllPropertyEditors: function () { + return umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('dataTypeApiBaseUrl', 'GetAllPropertyEditors')), 'Failed to retrieve data'); + }, + /** * @ngdoc method * @name umbraco.resources.contentResource#getScaffold * @methodOf umbraco.resources.contentResource @@ -1619,16 +1544,10 @@ function dataTypeResource($q, $http, umbDataFormatter, umbRequestHelper) { * @returns {Promise} resourcePromise object containing the data type scaffold. * */ - getScaffold: function (parentId) { - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "dataTypeApiBaseUrl", - "GetEmpty", { parentId: parentId })), - "Failed to retrieve data for empty datatype"); - }, - /** + getScaffold: function (parentId) { + return umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('dataTypeApiBaseUrl', 'GetEmpty', { parentId: parentId })), 'Failed to retrieve data for empty datatype'); + }, + /** * @ngdoc method * @name umbraco.resources.dataTypeResource#deleteById * @methodOf umbraco.resources.dataTypeResource @@ -1648,30 +1567,13 @@ function dataTypeResource($q, $http, umbDataFormatter, umbRequestHelper) { * @returns {Promise} resourcePromise object. * */ - deleteById: function(id) { - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "dataTypeApiBaseUrl", - "DeleteById", - [{ id: id }])), - "Failed to delete item " + id); - }, - - deleteContainerById: function (id) { - - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "dataTypeApiBaseUrl", - "DeleteContainer", - [{ id: id }])), - 'Failed to delete content type contaier'); - }, - - - - /** + deleteById: function (id) { + return umbRequestHelper.resourcePromise($http.post(umbRequestHelper.getApiUrl('dataTypeApiBaseUrl', 'DeleteById', [{ id: id }])), 'Failed to delete item ' + id); + }, + deleteContainerById: function (id) { + return umbRequestHelper.resourcePromise($http.post(umbRequestHelper.getApiUrl('dataTypeApiBaseUrl', 'DeleteContainer', [{ id: id }])), 'Failed to delete content type contaier'); + }, + /** * @ngdoc method * @name umbraco.resources.dataTypeResource#getCustomListView * @methodOf umbraco.resources.dataTypeResource @@ -1690,19 +1592,10 @@ function dataTypeResource($q, $http, umbDataFormatter, umbRequestHelper) { * @returns {Promise} resourcePromise object containing the listview datatype. * */ - - getCustomListView: function (contentTypeAlias) { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "dataTypeApiBaseUrl", - "GetCustomListView", - { contentTypeAlias: contentTypeAlias } - )), - "Failed to retrieve data for custom listview datatype"); - }, - - /** + getCustomListView: function (contentTypeAlias) { + return umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('dataTypeApiBaseUrl', 'GetCustomListView', { contentTypeAlias: contentTypeAlias })), 'Failed to retrieve data for custom listview datatype'); + }, + /** * @ngdoc method * @name umbraco.resources.dataTypeResource#createCustomListView * @methodOf umbraco.resources.dataTypeResource @@ -1720,18 +1613,10 @@ function dataTypeResource($q, $http, umbDataFormatter, umbRequestHelper) { * @returns {Promise} resourcePromise object containing the listview datatype. * */ - createCustomListView: function (contentTypeAlias) { - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "dataTypeApiBaseUrl", - "PostCreateCustomListView", - { contentTypeAlias: contentTypeAlias } - )), - "Failed to create a custom listview datatype"); - }, - - /** + createCustomListView: function (contentTypeAlias) { + return umbRequestHelper.resourcePromise($http.post(umbRequestHelper.getApiUrl('dataTypeApiBaseUrl', 'PostCreateCustomListView', { contentTypeAlias: contentTypeAlias })), 'Failed to create a custom listview datatype'); + }, + /** * @ngdoc method * @name umbraco.resources.dataTypeResource#save * @methodOf umbraco.resources.dataTypeResource @@ -1745,16 +1630,11 @@ function dataTypeResource($q, $http, umbDataFormatter, umbRequestHelper) { * @returns {Promise} resourcePromise object. * */ - save: function (dataType, preValues, isNew) { - - var saveModel = umbDataFormatter.formatDataTypePostData(dataType, preValues, "save" + (isNew ? "New" : "")); - - return umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper.getApiUrl("dataTypeApiBaseUrl", "PostSave"), saveModel), - "Failed to save data for data type id " + dataType.id); - }, - - /** + save: function (dataType, preValues, isNew) { + var saveModel = umbDataFormatter.formatDataTypePostData(dataType, preValues, 'save' + (isNew ? 'New' : '')); + return umbRequestHelper.resourcePromise($http.post(umbRequestHelper.getApiUrl('dataTypeApiBaseUrl', 'PostSave'), saveModel), 'Failed to save data for data type id ' + dataType.id); + }, + /** * @ngdoc method * @name umbraco.resources.dataTypeResource#move * @methodOf umbraco.resources.dataTypeResource @@ -1777,42 +1657,165 @@ function dataTypeResource($q, $http, umbDataFormatter, umbRequestHelper) { * @returns {Promise} resourcePromise object. * */ - move: function (args) { - if (!args) { - throw "args cannot be null"; + move: function (args) { + if (!args) { + throw 'args cannot be null'; + } + if (!args.parentId) { + throw 'args.parentId cannot be null'; + } + if (!args.id) { + throw 'args.id cannot be null'; + } + return umbRequestHelper.resourcePromise($http.post(umbRequestHelper.getApiUrl('dataTypeApiBaseUrl', 'PostMove'), { + parentId: args.parentId, + id: args.id + }), 'Failed to move content'); + }, + createContainer: function (parentId, name) { + return umbRequestHelper.resourcePromise($http.post(umbRequestHelper.getApiUrl('dataTypeApiBaseUrl', 'PostCreateContainer', { + parentId: parentId, + name: encodeURIComponent(name) + })), 'Failed to create a folder under parent id ' + parentId); + }, + renameContainer: function (id, name) { + return umbRequestHelper.resourcePromise($http.post(umbRequestHelper.getApiUrl('dataTypeApiBaseUrl', 'PostRenameContainer', { + id: id, + name: name + })), 'Failed to rename the folder with id ' + id); } - if (!args.parentId) { - throw "args.parentId cannot be null"; - } - if (!args.id) { - throw "args.id cannot be null"; - } - - return umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper.getApiUrl("dataTypeApiBaseUrl", "PostMove"), - { - parentId: args.parentId, - id: args.id - }), - 'Failed to move content'); - }, - - createContainer: function (parentId, name) { - - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "dataTypeApiBaseUrl", - "PostCreateContainer", - { parentId: parentId, name: name })), - 'Failed to create a folder under parent id ' + parentId); + }; + } + angular.module('umbraco.resources').factory('dataTypeResource', dataTypeResource); + /** + * @ngdoc service + * @name umbraco.resources.dictionaryResource + * @description Loads in data for dictionary items +**/ + function dictionaryResource($q, $http, $location, umbRequestHelper, umbDataFormatter) { + /** + * @ngdoc method + * @name umbraco.resources.dictionaryResource#deleteById + * @methodOf umbraco.resources.dictionaryResource + * + * @description + * Deletes a dictionary item with a given id + * + * ##usage + *
    +         * dictionaryResource.deleteById(1234)
    +         *    .then(function() {
    +         *        alert('its gone!');
    +         *    });
    +         * 
    + * + * @param {Int} id id of dictionary item to delete + * @returns {Promise} resourcePromise object. + * + **/ + function deleteById(id) { + return umbRequestHelper.resourcePromise($http.post(umbRequestHelper.getApiUrl('dictionaryApiBaseUrl', 'DeleteById', [{ id: id }])), 'Failed to delete item ' + id); } - }; -} - -angular.module("umbraco.resources").factory("dataTypeResource", dataTypeResource); - -/** + /** + * @ngdoc method + * @name umbraco.resources.dictionaryResource#create + * @methodOf umbraco.resources.dictionaryResource + * + * @description + * Creates a dictionary item with the gieven key and parent id + * + * ##usage + *
    +         * dictionaryResource.create(1234,"Item key")
    +         *    .then(function() {
    +         *        alert('its created!');
    +         *    });
    +         * 
    + * + * @param {Int} parentid the parentid of the new dictionary item + * @param {String} key the key of the new dictionary item + * @returns {Promise} resourcePromise object. + * + **/ + function create(parentid, key) { + return umbRequestHelper.resourcePromise($http.post(umbRequestHelper.getApiUrl('dictionaryApiBaseUrl', 'Create', { + parentId: parentid, + key: key + })), 'Failed to create item '); + } + /** + * @ngdoc method + * @name umbraco.resources.dictionaryResource#deleteById + * @methodOf umbraco.resources.dictionaryResource + * + * @description + * Gets a dictionary item with a given id + * + * ##usage + *
    +         * dictionaryResource.getById(1234)
    +         *    .then(function() {
    +         *        alert('Found it!');
    +         *    });
    +         * 
    + * + * @param {Int} id id of dictionary item to get + * @returns {Promise} resourcePromise object. + * + **/ + function getById(id) { + return umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('dictionaryApiBaseUrl', 'GetById', [{ id: id }])), 'Failed to get item ' + id); + } + /** + * @ngdoc method + * @name umbraco.resources.dictionaryResource#save + * @methodOf umbraco.resources.dictionaryResource + * + * @description + * Updates a dictionary + * + * @param {Object} dictionary dictionary object to update + * @param {Bool} nameIsDirty set to true if the name has been changed + * @returns {Promise} resourcePromise object. + * + */ + function save(dictionary, nameIsDirty) { + var saveModel = umbDataFormatter.formatDictionaryPostData(dictionary, nameIsDirty); + return umbRequestHelper.resourcePromise($http.post(umbRequestHelper.getApiUrl('dictionaryApiBaseUrl', 'PostSave'), saveModel), 'Failed to save data for dictionary id ' + dictionary.id); + } + /** + * @ngdoc method + * @name umbraco.resources.dictionaryResource#getList + * @methodOf umbraco.resources.dictionaryResource + * + * @description + * Gets a list of all dictionary items + * + * ##usage + *
    +         * dictionaryResource.getList()
    +         *    .then(function() {
    +         *        alert('Found it!');
    +         *    });
    +         * 
    + * + * @returns {Promise} resourcePromise object. + * + **/ + function getList() { + return umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('dictionaryApiBaseUrl', 'getList')), 'Failed to get list'); + } + var resource = { + deleteById: deleteById, + create: create, + getById: getById, + save: save, + getList: getList + }; + return resource; + } + angular.module('umbraco.resources').factory('dictionaryResource', dictionaryResource); + /** * @ngdoc service * @name umbraco.resources.entityResource * @description Loads in basic data for all entities @@ -1845,22 +1848,20 @@ angular.module("umbraco.resources").factory("dataTypeResource", dataTypeResource * - Domain * - DataType **/ -function entityResource($q, $http, umbRequestHelper) { - - //the factory object returned - return { - - getSafeAlias: function (value, camelCase) { - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "entityApiBaseUrl", - "GetSafeAlias", { value: value, camelCase: camelCase })), - 'Failed to retrieve content type scaffold'); - }, - - /** + function entityResource($q, $http, umbRequestHelper) { + //the factory object returned + return { + getSafeAlias: function (value, camelCase) { + if (!value) { + return ''; + } + value = value.replace('#', ''); + return umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('entityApiBaseUrl', 'GetSafeAlias', { + value: value, + camelCase: camelCase + })), 'Failed to retrieve content type scaffold'); + }, + /** * @ngdoc method * @name umbraco.resources.entityResource#getPath * @methodOf umbraco.resources.entityResource @@ -1870,7 +1871,7 @@ function entityResource($q, $http, umbRequestHelper) { * * ##usage *
    -         * entityResource.getPath(id)
    +         * entityResource.getPath(id, type)
              *    .then(function(pathArray) {
              *        alert('its here!');
              *    });
    @@ -1881,17 +1882,46 @@ function entityResource($q, $http, umbRequestHelper) {
              * @returns {Promise} resourcePromise object containing the url.
              *
              */
    -        getPath: function (id, type) {
    -            return umbRequestHelper.resourcePromise(
    -               $http.get(
    -                   umbRequestHelper.getApiUrl(
    -                       "entityApiBaseUrl",
    -                       "GetPath",
    -                       [{ id: id }, {type: type }])),
    -               'Failed to retrieve path for id:' + id);
    -        },
    -
    -        /**
    +            getPath: function (id, type) {
    +                if (id === -1 || id === '-1') {
    +                    return '-1';
    +                }
    +                return umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('entityApiBaseUrl', 'GetPath', [
    +                    { id: id },
    +                    { type: type }
    +                ])), 'Failed to retrieve path for id:' + id);
    +            },
    +            /**
    +         * @ngdoc method
    +         * @name umbraco.resources.entityResource#getUrl
    +         * @methodOf umbraco.resources.entityResource
    +         *
    +         * @description
    +         * Returns a url, given a node ID and type
    +         *
    +         * ##usage
    +         * 
    +         * entityResource.getUrl(id, type)
    +         *    .then(function(url) {
    +         *        alert('its here!');
    +         *    });
    +         * 
    + * + * @param {Int} id Id of node to return the public url to + * @param {string} type Object type name + * @returns {Promise} resourcePromise object containing the url. + * + */ + getUrl: function (id, type) { + if (id === -1 || id === '-1') { + return ''; + } + return umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('entityApiBaseUrl', 'GetUrl', [ + { id: id }, + { type: type } + ])), 'Failed to retrieve url for id:' + id); + }, + /** * @ngdoc method * @name umbraco.resources.entityResource#getById * @methodOf umbraco.resources.entityResource @@ -1914,17 +1944,16 @@ function entityResource($q, $http, umbRequestHelper) { * @returns {Promise} resourcePromise object containing the entity. * */ - getById: function (id, type) { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "entityApiBaseUrl", - "GetById", - [{ id: id}, {type: type }])), - 'Failed to retrieve entity data for id ' + id); - }, - - /** + getById: function (id, type) { + if (id === -1 || id === '-1') { + return null; + } + return umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('entityApiBaseUrl', 'GetById', [ + { id: id }, + { type: type } + ])), 'Failed to retrieve entity data for id ' + id); + }, + /** * @ngdoc method * @name umbraco.resources.entityResource#getByIds * @methodOf umbraco.resources.entityResource @@ -1947,30 +1976,11 @@ function entityResource($q, $http, umbRequestHelper) { * @returns {Promise} resourcePromise object containing the entity array. * */ - getByIds: function (ids, type) { - - var query = ""; - _.each(ids, function(item) { - query += "ids=" + item + "&"; - }); - - // if ids array is empty we need a empty variable in the querystring otherwise the service returns a error - if (ids.length === 0) { - query += "ids=&"; - } - - query += "type=" + type; - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "entityApiBaseUrl", - "GetByIds", - query)), - 'Failed to retrieve entity data for ids ' + ids); - }, - - /** + getByIds: function (ids, type) { + var query = 'type=' + type; + return umbRequestHelper.resourcePromise($http.post(umbRequestHelper.getApiUrl('entityApiBaseUrl', 'GetByIds', query), { ids: ids }), 'Failed to retrieve entity data for ids ' + ids); + }, + /** * @ngdoc method * @name umbraco.resources.entityResource#getByQuery * @methodOf umbraco.resources.entityResource @@ -1994,17 +2004,14 @@ function entityResource($q, $http, umbRequestHelper) { * @returns {Promise} resourcePromise object containing the entity. * */ - getByQuery: function (query, nodeContextId, type) { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "entityApiBaseUrl", - "GetByQuery", - [{ query: query }, { nodeContextId: nodeContextId }, { type: type }])), - 'Failed to retrieve entity data for query ' + query); - }, - - /** + getByQuery: function (query, nodeContextId, type) { + return umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('entityApiBaseUrl', 'GetByQuery', [ + { query: query }, + { nodeContextId: nodeContextId }, + { type: type } + ])), 'Failed to retrieve entity data for query ' + query); + }, + /** * @ngdoc method * @name umbraco.resources.entityResource#getAll * @methodOf umbraco.resources.entityResource @@ -2029,28 +2036,19 @@ function entityResource($q, $http, umbRequestHelper) { * @returns {Promise} resourcePromise object containing the entity. * */ - getAll: function (type, postFilter, postFilterParams) { - - //need to build the query string manually - var query = "type=" + type + "&postFilter=" + (postFilter ? postFilter : ""); - if (postFilter && postFilterParams) { - var counter = 0; - _.each(postFilterParams, function(val, key) { - query += "&postFilterParams[" + counter + "].key=" + key + "&postFilterParams[" + counter + "].value=" + val; - counter++; - }); - } - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "entityApiBaseUrl", - "GetAll", - query)), - 'Failed to retrieve entity data for type ' + type); - }, - - /** + getAll: function (type, postFilter, postFilterParams) { + //need to build the query string manually + var query = 'type=' + type + '&postFilter=' + (postFilter ? postFilter : ''); + if (postFilter && postFilterParams) { + var counter = 0; + _.each(postFilterParams, function (val, key) { + query += '&postFilterParams[' + counter + '].key=' + key + '&postFilterParams[' + counter + '].value=' + val; + counter++; + }); + } + return umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('entityApiBaseUrl', 'GetAll', query)), 'Failed to retrieve entity data for type ' + type); + }, + /** * @ngdoc method * @name umbraco.resources.entityResource#getAncestors * @methodOf umbraco.resources.entityResource @@ -2063,40 +2061,150 @@ function entityResource($q, $http, umbRequestHelper) { * @returns {Promise} resourcePromise object containing the entity. * */ - getAncestors: function (id, type) { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "entityApiBaseUrl", - "GetAncestors", - [{id: id}, {type: type}])), - 'Failed to retrieve ancestor data for id ' + id); - }, - - /** + getAncestors: function (id, type) { + return umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('entityApiBaseUrl', 'GetAncestors', [ + { id: id }, + { type: type } + ])), 'Failed to retrieve ancestor data for id ' + id); + }, + /** * @ngdoc method - * @name umbraco.resources.entityResource#getAncestors + * @name umbraco.resources.entityResource#getChildren * @methodOf umbraco.resources.entityResource * * @description * Gets children entities for a given item * - * + * @param {Int} parentid id of content item to return children of * @param {string} type Object type name * @returns {Promise} resourcePromise object containing the entity. * */ - getChildren: function (id, type) { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "entityApiBaseUrl", - "GetChildren", - [{ id: id }, { type: type }])), - 'Failed to retrieve child data for id ' + id); - }, - - /** + getChildren: function (id, type) { + return umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('entityApiBaseUrl', 'GetChildren', [ + { id: id }, + { type: type } + ])), 'Failed to retrieve child data for id ' + id); + }, + /** + * @ngdoc method + * @name umbraco.resources.entityResource#getPagedChildren + * @methodOf umbraco.resources.entityResource + * + * @description + * Gets paged children of a content item with a given id + * + * ##usage + *
    +          * entityResource.getPagedChildren(1234, "Content", {pageSize: 10, pageNumber: 2})
    +          *    .then(function(contentArray) {
    +          *        var children = contentArray; 
    +          *        alert('they are here!');
    +          *    });
    +          * 
    + * + * @param {Int} parentid id of content item to return children of + * @param {string} type Object type name + * @param {Object} options optional options object + * @param {Int} options.pageSize if paging data, number of nodes per page, default = 1 + * @param {Int} options.pageNumber if paging data, current page index, default = 100 + * @param {String} options.filter if provided, query will only return those with names matching the filter + * @param {String} options.orderDirection can be `Ascending` or `Descending` - Default: `Ascending` + * @param {String} options.orderBy property to order items by, default: `SortOrder` + * @returns {Promise} resourcePromise object containing an array of content items. + * + */ + getPagedChildren: function (parentId, type, options) { + var defaults = { + pageSize: 1, + pageNumber: 100, + filter: '', + orderDirection: 'Ascending', + orderBy: 'SortOrder' + }; + if (options === undefined) { + options = {}; + } + //overwrite the defaults if there are any specified + angular.extend(defaults, options); + //now copy back to the options we will use + options = defaults; + //change asc/desct + if (options.orderDirection === 'asc') { + options.orderDirection = 'Ascending'; + } else if (options.orderDirection === 'desc') { + options.orderDirection = 'Descending'; + } + return umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('entityApiBaseUrl', 'GetPagedChildren', { + id: parentId, + type: type, + pageNumber: options.pageNumber, + pageSize: options.pageSize, + orderBy: options.orderBy, + orderDirection: options.orderDirection, + filter: encodeURIComponent(options.filter) + })), 'Failed to retrieve child data for id ' + parentId); + }, + /** + * @ngdoc method + * @name umbraco.resources.entityResource#getPagedDescendants + * @methodOf umbraco.resources.entityResource + * + * @description + * Gets paged descendants of a content item with a given id + * + * ##usage + *
    +          * entityResource.getPagedDescendants(1234, "Document", {pageSize: 10, pageNumber: 2})
    +          *    .then(function(contentArray) {
    +          *        var children = contentArray; 
    +          *        alert('they are here!');
    +          *    });
    +          * 
    + * + * @param {Int} parentid id of content item to return descendants of + * @param {string} type Object type name + * @param {Object} options optional options object + * @param {Int} options.pageSize if paging data, number of nodes per page, default = 100 + * @param {Int} options.pageNumber if paging data, current page index, default = 1 + * @param {String} options.filter if provided, query will only return those with names matching the filter + * @param {String} options.orderDirection can be `Ascending` or `Descending` - Default: `Ascending` + * @param {String} options.orderBy property to order items by, default: `SortOrder` + * @returns {Promise} resourcePromise object containing an array of content items. + * + */ + getPagedDescendants: function (parentId, type, options) { + var defaults = { + pageSize: 100, + pageNumber: 1, + filter: '', + orderDirection: 'Ascending', + orderBy: 'SortOrder' + }; + if (options === undefined) { + options = {}; + } + //overwrite the defaults if there are any specified + angular.extend(defaults, options); + //now copy back to the options we will use + options = defaults; + //change asc/desct + if (options.orderDirection === 'asc') { + options.orderDirection = 'Ascending'; + } else if (options.orderDirection === 'desc') { + options.orderDirection = 'Descending'; + } + return umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('entityApiBaseUrl', 'GetPagedDescendants', { + id: parentId, + type: type, + pageNumber: options.pageNumber, + pageSize: options.pageSize, + orderBy: options.orderBy, + orderDirection: options.orderDirection, + filter: encodeURIComponent(options.filter) + })), 'Failed to retrieve child data for id ' + parentId); + }, + /** * @ngdoc method * @name umbraco.resources.entityResource#search * @methodOf umbraco.resources.entityResource @@ -2118,30 +2226,21 @@ function entityResource($q, $http, umbRequestHelper) { * @returns {Promise} resourcePromise object containing the entity array. * */ - search: function (query, type, searchFrom, canceler) { - - var args = [{ query: query }, { type: type }]; - if (searchFrom) { - args.push({ searchFrom: searchFrom }); - } - - var httpConfig = {}; - if (canceler) { - httpConfig["timeout"] = canceler; - } - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "entityApiBaseUrl", - "Search", - args), - httpConfig), - 'Failed to retrieve entity data for query ' + query); - }, - - - /** + search: function (query, type, searchFrom, canceler) { + var args = [ + { query: query }, + { type: type } + ]; + if (searchFrom) { + args.push({ searchFrom: searchFrom }); + } + var httpConfig = {}; + if (canceler) { + httpConfig['timeout'] = canceler; + } + return umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('entityApiBaseUrl', 'Search', args), httpConfig), 'Failed to retrieve entity data for query ' + query); + }, + /** * @ngdoc method * @name umbraco.resources.entityResource#searchAll * @methodOf umbraco.resources.entityResource @@ -2162,29 +2261,17 @@ function entityResource($q, $http, umbRequestHelper) { * @returns {Promise} resourcePromise object containing the entity array. * */ - searchAll: function (query, canceler) { - - var httpConfig = {}; - if (canceler) { - httpConfig["timeout"] = canceler; + searchAll: function (query, canceler) { + var httpConfig = {}; + if (canceler) { + httpConfig['timeout'] = canceler; + } + return umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('entityApiBaseUrl', 'SearchAll', [{ query: query }]), httpConfig), 'Failed to retrieve entity data for query ' + query); } - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "entityApiBaseUrl", - "SearchAll", - [{ query: query }]), - httpConfig), - 'Failed to retrieve entity data for query ' + query); - } - - }; -} - -angular.module('umbraco.resources').factory('entityResource', entityResource); - -/** + }; + } + angular.module('umbraco.resources').factory('entityResource', entityResource); + /** * @ngdoc service * @name umbraco.resources.healthCheckResource * @function @@ -2192,12 +2279,10 @@ angular.module('umbraco.resources').factory('entityResource', entityResource); * @description * Used by the health check dashboard to get checks and send requests to fix checks. */ -(function () { - 'use strict'; - - function healthCheckResource($http, umbRequestHelper) { - - /** + (function () { + 'use strict'; + function healthCheckResource($http, umbRequestHelper) { + /** * @ngdoc function * @name umbraco.resources.healthCheckService#getAllChecks * @methodOf umbraco.resources.healthCheckResource @@ -2206,14 +2291,10 @@ angular.module('umbraco.resources').factory('entityResource', entityResource); * @description * Called to get all available health checks */ - function getAllChecks() { - return umbRequestHelper.resourcePromise( - $http.get(Umbraco.Sys.ServerVariables.umbracoUrls.healthCheckBaseUrl + "GetAllHealthChecks"), - "Failed to retrieve health checks" - ); - } - - /** + function getAllChecks() { + return umbRequestHelper.resourcePromise($http.get(Umbraco.Sys.ServerVariables.umbracoUrls.healthCheckBaseUrl + 'GetAllHealthChecks'), 'Failed to retrieve health checks'); + } + /** * @ngdoc function * @name umbraco.resources.healthCheckService#getStatus * @methodOf umbraco.resources.healthCheckResource @@ -2222,14 +2303,10 @@ angular.module('umbraco.resources').factory('entityResource', entityResource); * @description * Called to get execute a health check and return the check status */ - function getStatus(id) { - return umbRequestHelper.resourcePromise( - $http.get(Umbraco.Sys.ServerVariables.umbracoUrls.healthCheckBaseUrl + 'GetStatus?id=' + id), - 'Failed to retrieve status for health check with ID ' + id - ); - } - - /** + function getStatus(id) { + return umbRequestHelper.resourcePromise($http.get(Umbraco.Sys.ServerVariables.umbracoUrls.healthCheckBaseUrl + 'GetStatus?id=' + id), 'Failed to retrieve status for health check with ID ' + id); + } + /** * @ngdoc function * @name umbraco.resources.healthCheckService#executeAction * @methodOf umbraco.resources.healthCheckResource @@ -2238,71 +2315,96 @@ angular.module('umbraco.resources').factory('entityResource', entityResource); * @description * Called to execute a health check action (rectifying an issue) */ - function executeAction(action) { - return umbRequestHelper.resourcePromise( - $http.post(Umbraco.Sys.ServerVariables.umbracoUrls.healthCheckBaseUrl + 'ExecuteAction', action), - 'Failed to execute action with alias ' + action.alias + ' and healthCheckId + ' + action.healthCheckId - ); + function executeAction(action) { + return umbRequestHelper.resourcePromise($http.post(Umbraco.Sys.ServerVariables.umbracoUrls.healthCheckBaseUrl + 'ExecuteAction', action), 'Failed to execute action with alias ' + action.alias + ' and healthCheckId + ' + action.healthCheckId); + } + var resource = { + getAllChecks: getAllChecks, + getStatus: getStatus, + executeAction: executeAction + }; + return resource; } - - var resource = { - getAllChecks: getAllChecks, - getStatus: getStatus, - executeAction: executeAction - }; - - return resource; - - } - - - angular.module('umbraco.resources').factory('healthCheckResource', healthCheckResource); - - -})(); - -/** + angular.module('umbraco.resources').factory('healthCheckResource', healthCheckResource); + }()); + /** * @ngdoc service * @name umbraco.resources.legacyResource * @description Handles legacy dialog requests **/ -function legacyResource($q, $http, umbRequestHelper) { - - //the factory object returned - return { - /** Loads in the data to display the section list */ - deleteItem: function (args) { - - if (!args.nodeId || !args.nodeType || !args.alias) { - throw "The args parameter is not formatted correct, it requires properties: nodeId, nodeType, alias"; - } - - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "legacyApiBaseUrl", - "DeleteLegacyItem", - [{ nodeId: args.nodeId }, { nodeType: args.nodeType }, { alias: args.alias }])), - 'Failed to delete item ' + args.nodeId); - - } - }; -} - -angular.module('umbraco.resources').factory('legacyResource', legacyResource); -/** + function legacyResource($q, $http, umbRequestHelper) { + //the factory object returned + return { + /** Loads in the data to display the section list */ + deleteItem: function (args) { + if (!args.nodeId || !args.nodeType || !args.alias) { + throw 'The args parameter is not formatted correct, it requires properties: nodeId, nodeType, alias'; + } + return umbRequestHelper.resourcePromise($http.post(umbRequestHelper.getApiUrl('legacyApiBaseUrl', 'DeleteLegacyItem', [ + { nodeId: args.nodeId }, + { nodeType: args.nodeType }, + { alias: args.alias } + ])), 'Failed to delete item ' + args.nodeId); + } + }; + } + angular.module('umbraco.resources').factory('legacyResource', legacyResource); + /** * @ngdoc service * @name umbraco.resources.logResource * @description Retrives log history from umbraco * * **/ -function logResource($q, $http, umbRequestHelper) { - - //the factory object returned - return { - - /** + function logResource($q, $http, umbRequestHelper) { + //the factory object returned + return { + getPagedEntityLog: function (options) { + var defaults = { + pageSize: 10, + pageNumber: 1, + orderDirection: 'Descending' + }; + if (options === undefined) { + options = {}; + } + //overwrite the defaults if there are any specified + angular.extend(defaults, options); + //now copy back to the options we will use + options = defaults; + //change asc/desct + if (options.orderDirection === 'asc') { + options.orderDirection = 'Ascending'; + } else if (options.orderDirection === 'desc') { + options.orderDirection = 'Descending'; + } + if (options.id === undefined || options.id === null) { + throw 'options.id is required'; + } + return umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('logApiBaseUrl', 'GetPagedEntityLog', options)), 'Failed to retrieve log data for id'); + }, + getPagedUserLog: function (options) { + var defaults = { + pageSize: 10, + pageNumber: 1, + orderDirection: 'Descending' + }; + if (options === undefined) { + options = {}; + } + //overwrite the defaults if there are any specified + angular.extend(defaults, options); + //now copy back to the options we will use + options = defaults; + //change asc/desct + if (options.orderDirection === 'asc') { + options.orderDirection = 'Ascending'; + } else if (options.orderDirection === 'desc') { + options.orderDirection = 'Descending'; + } + return umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('logApiBaseUrl', 'GetPagedEntityLog', options)), 'Failed to retrieve log data for id'); + }, + /** * @ngdoc method * @name umbraco.resources.logResource#getEntityLog * @methodOf umbraco.resources.logResource @@ -2322,17 +2424,10 @@ function logResource($q, $http, umbRequestHelper) { * @returns {Promise} resourcePromise object containing the log. * */ - getEntityLog: function (id) { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "logApiBaseUrl", - "GetEntityLog", - [{ id: id }])), - 'Failed to retrieve user data for id ' + id); - }, - - /** + getEntityLog: function (id) { + return umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('logApiBaseUrl', 'GetEntityLog', [{ id: id }])), 'Failed to retrieve user data for id ' + id); + }, + /** * @ngdoc method * @name umbraco.resources.logResource#getUserLog * @methodOf umbraco.resources.logResource @@ -2353,17 +2448,13 @@ function logResource($q, $http, umbRequestHelper) { * @returns {Promise} resourcePromise object containing the log. * */ - getUserLog: function (type, since) { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "logApiBaseUrl", - "GetCurrentUserLog", - [{ logtype: type, sinceDate: since }])), - 'Failed to retrieve user data for id ' + id); - }, - - /** + getUserLog: function (type, since) { + return umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('logApiBaseUrl', 'GetCurrentUserLog', [ + { logtype: type }, + { sinceDate: since } + ])), 'Failed to retrieve log data for current user of type ' + type + ' since ' + since); + }, + /** * @ngdoc method * @name umbraco.resources.logResource#getLog * @methodOf umbraco.resources.logResource @@ -2384,32 +2475,25 @@ function logResource($q, $http, umbRequestHelper) { * @returns {Promise} resourcePromise object containing the log. * */ - getLog: function (type, since) { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "logApiBaseUrl", - "GetLog", - [{ logtype: type, sinceDate: since }])), - 'Failed to retrieve user data for id ' + id); - } - }; -} - -angular.module('umbraco.resources').factory('logResource', logResource); - -/** + getLog: function (type, since) { + return umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('logApiBaseUrl', 'GetLog', [ + { logtype: type }, + { sinceDate: since } + ])), 'Failed to retrieve log data of type ' + type + ' since ' + since); + } + }; + } + angular.module('umbraco.resources').factory('logResource', logResource); + /** * @ngdoc service * @name umbraco.resources.macroResource * @description Deals with data for macros - * + * **/ -function macroResource($q, $http, umbRequestHelper) { - - //the factory object returned - return { - - /** + function macroResource($q, $http, umbRequestHelper) { + //the factory object returned + return { + /** * @ngdoc method * @name umbraco.resources.macroResource#getMacroParameters * @methodOf umbraco.resources.macroResource @@ -2420,17 +2504,10 @@ function macroResource($q, $http, umbRequestHelper) { * @param {int} macroId The macro id to get parameters for * */ - getMacroParameters: function (macroId) { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "macroApiBaseUrl", - "GetMacroParameters", - [{ macroId: macroId }])), - 'Failed to retrieve macro parameters for macro with id ' + macroId); - }, - - /** + getMacroParameters: function (macroId) { + return umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('macroApiBaseUrl', 'GetMacroParameters', [{ macroId: macroId }])), 'Failed to retrieve macro parameters for macro with id ' + macroId); + }, + /** * @ngdoc method * @name umbraco.resources.macroResource#getMacroResult * @methodOf umbraco.resources.macroResource @@ -2443,58 +2520,50 @@ function macroResource($q, $http, umbRequestHelper) { * @param {Array} macroParamDictionary A dictionary of macro parameters * */ - getMacroResultAsHtmlForEditor: function (macroAlias, pageId, macroParamDictionary) { - - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "macroApiBaseUrl", - "GetMacroResultAsHtmlForEditor"), { - macroAlias: macroAlias, - pageId: pageId, - macroParams: macroParamDictionary - }), - 'Failed to retrieve macro result for macro with alias ' + macroAlias); - } - }; -} - -angular.module('umbraco.resources').factory('macroResource', macroResource); - -/** + getMacroResultAsHtmlForEditor: function (macroAlias, pageId, macroParamDictionary) { + return umbRequestHelper.resourcePromise($http.post(umbRequestHelper.getApiUrl('macroApiBaseUrl', 'GetMacroResultAsHtmlForEditor'), { + macroAlias: macroAlias, + pageId: pageId, + macroParams: macroParamDictionary + }), 'Failed to retrieve macro result for macro with alias ' + macroAlias); + }, + /** + * + * @param {} filename + * @returns {} + */ + createPartialViewMacroWithFile: function (virtualPath, filename) { + return umbRequestHelper.resourcePromise($http.post(umbRequestHelper.getApiUrl('macroApiBaseUrl', 'CreatePartialViewMacroWithFile'), { + virtualPath: virtualPath, + filename: filename + }), 'Failed to create macro "' + filename + '"'); + } + }; + } + angular.module('umbraco.resources').factory('macroResource', macroResource); + /** * @ngdoc service * @name umbraco.resources.mediaResource * @description Loads in data for media **/ -function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { - - /** internal method process the saving of data and post processing the result */ - function saveMediaItem(content, action, files) { - return umbRequestHelper.postSaveContent({ - restApiUrl: umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "PostSave"), - content: content, - action: action, - files: files, - dataFormatter: function (c, a) { - return umbDataFormatter.formatMediaPostData(c, a); - } - }); - } - - return { - - getRecycleBin: function () { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "GetRecycleBin")), - 'Failed to retrieve data for media recycle bin'); - }, - - /** + function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { + /** internal method process the saving of data and post processing the result */ + function saveMediaItem(content, action, files) { + return umbRequestHelper.postSaveContent({ + restApiUrl: umbRequestHelper.getApiUrl('mediaApiBaseUrl', 'PostSave'), + content: content, + action: action, + files: files, + dataFormatter: function (c, a) { + return umbDataFormatter.formatMediaPostData(c, a); + } + }); + } + return { + getRecycleBin: function () { + return umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('mediaApiBaseUrl', 'GetRecycleBin')), 'Failed to retrieve data for media recycle bin'); + }, + /** * @ngdoc method * @name umbraco.resources.mediaResource#sort * @methodOf umbraco.resources.mediaResource @@ -2516,27 +2585,22 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { * @returns {Promise} resourcePromise object. * */ - sort: function (args) { - if (!args) { - throw "args cannot be null"; - } - if (!args.parentId) { - throw "args.parentId cannot be null"; - } - if (!args.sortedIds) { - throw "args.sortedIds cannot be null"; - } - - return umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper.getApiUrl("mediaApiBaseUrl", "PostSort"), - { - parentId: args.parentId, - idSortOrder: args.sortedIds - }), - 'Failed to sort media'); - }, - - /** + sort: function (args) { + if (!args) { + throw 'args cannot be null'; + } + if (!args.parentId) { + throw 'args.parentId cannot be null'; + } + if (!args.sortedIds) { + throw 'args.sortedIds cannot be null'; + } + return umbRequestHelper.resourcePromise($http.post(umbRequestHelper.getApiUrl('mediaApiBaseUrl', 'PostSort'), { + parentId: args.parentId, + idSortOrder: args.sortedIds + }), 'Failed to sort media'); + }, + /** * @ngdoc method * @name umbraco.resources.mediaResource#move * @methodOf umbraco.resources.mediaResource @@ -2559,28 +2623,22 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { * @returns {Promise} resourcePromise object. * */ - move: function (args) { - if (!args) { - throw "args cannot be null"; - } - if (!args.parentId) { - throw "args.parentId cannot be null"; - } - if (!args.id) { - throw "args.id cannot be null"; - } - - return umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper.getApiUrl("mediaApiBaseUrl", "PostMove"), - { - parentId: args.parentId, - id: args.id - }), - 'Failed to move media'); - }, - - - /** + move: function (args) { + if (!args) { + throw 'args cannot be null'; + } + if (!args.parentId) { + throw 'args.parentId cannot be null'; + } + if (!args.id) { + throw 'args.id cannot be null'; + } + return umbRequestHelper.resourcePromise($http.post(umbRequestHelper.getApiUrl('mediaApiBaseUrl', 'PostMove'), { + parentId: args.parentId, + id: args.id + }), 'Failed to move media'); + }, + /** * @ngdoc method * @name umbraco.resources.mediaResource#getById * @methodOf umbraco.resources.mediaResource @@ -2601,18 +2659,10 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { * @returns {Promise} resourcePromise object containing the media item. * */ - getById: function (id) { - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "GetById", - [{ id: id }])), - 'Failed to retrieve data for media id ' + id); - }, - - /** + getById: function (id) { + return umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('mediaApiBaseUrl', 'GetById', [{ id: id }])), 'Failed to retrieve data for media id ' + id); + }, + /** * @ngdoc method * @name umbraco.resources.mediaResource#deleteById * @methodOf umbraco.resources.mediaResource @@ -2632,17 +2682,10 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { * @returns {Promise} resourcePromise object. * */ - deleteById: function (id) { - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "DeleteById", - [{ id: id }])), - 'Failed to delete item ' + id); - }, - - /** + deleteById: function (id) { + return umbRequestHelper.resourcePromise($http.post(umbRequestHelper.getApiUrl('mediaApiBaseUrl', 'DeleteById', [{ id: id }])), 'Failed to delete item ' + id); + }, + /** * @ngdoc method * @name umbraco.resources.mediaResource#getByIds * @methodOf umbraco.resources.mediaResource @@ -2663,23 +2706,14 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { * @returns {Promise} resourcePromise object containing the media items array. * */ - getByIds: function (ids) { - - var idQuery = ""; - _.each(ids, function (item) { - idQuery += "ids=" + item + "&"; - }); - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "GetByIds", - idQuery)), - 'Failed to retrieve data for media ids ' + ids); - }, - - /** + getByIds: function (ids) { + var idQuery = ''; + _.each(ids, function (item) { + idQuery += 'ids=' + item + '&'; + }); + return umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('mediaApiBaseUrl', 'GetByIds', idQuery)), 'Failed to retrieve data for media ids ' + ids); + }, + /** * @ngdoc method * @name umbraco.resources.mediaResource#getScaffold * @methodOf umbraco.resources.mediaResource @@ -2711,30 +2745,16 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { * @returns {Promise} resourcePromise object containing the media scaffold. * */ - getScaffold: function (parentId, alias) { - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "GetEmpty", - [{ contentTypeAlias: alias }, { parentId: parentId }])), - 'Failed to retrieve data for empty media item type ' + alias); - - }, - - rootMedia: function () { - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "GetRootMedia")), - 'Failed to retrieve data for root media'); - - }, - - /** + getScaffold: function (parentId, alias) { + return umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('mediaApiBaseUrl', 'GetEmpty', [ + { contentTypeAlias: alias }, + { parentId: parentId } + ])), 'Failed to retrieve data for empty media item type ' + alias); + }, + rootMedia: function () { + return umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('mediaApiBaseUrl', 'GetRootMedia')), 'Failed to retrieve data for root media'); + }, + /** * @ngdoc method * @name umbraco.resources.mediaResource#getChildren * @methodOf umbraco.resources.mediaResource @@ -2761,63 +2781,52 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { * @returns {Promise} resourcePromise object containing an array of content items. * */ - getChildren: function (parentId, options) { - - var defaults = { - pageSize: 0, - pageNumber: 0, - filter: '', - orderDirection: "Ascending", - orderBy: "SortOrder", - orderBySystemField: true - }; - if (options === undefined) { - options = {}; - } - //overwrite the defaults if there are any specified - angular.extend(defaults, options); - //now copy back to the options we will use - options = defaults; - //change asc/desct - if (options.orderDirection === "asc") { - options.orderDirection = "Ascending"; - } - else if (options.orderDirection === "desc") { - options.orderDirection = "Descending"; - } - - //converts the value to a js bool - function toBool(v) { - if (angular.isNumber(v)) { - return v > 0; + getChildren: function (parentId, options) { + var defaults = { + pageSize: 0, + pageNumber: 0, + filter: '', + orderDirection: 'Ascending', + orderBy: 'SortOrder', + orderBySystemField: true + }; + if (options === undefined) { + options = {}; } - if (angular.isString(v)) { - return v === "true"; + //overwrite the defaults if there are any specified + angular.extend(defaults, options); + //now copy back to the options we will use + options = defaults; + //change asc/desct + if (options.orderDirection === 'asc') { + options.orderDirection = 'Ascending'; + } else if (options.orderDirection === 'desc') { + options.orderDirection = 'Descending'; } - if (typeof v === "boolean") { - return v; + //converts the value to a js bool + function toBool(v) { + if (angular.isNumber(v)) { + return v > 0; + } + if (angular.isString(v)) { + return v === 'true'; + } + if (typeof v === 'boolean') { + return v; + } + return false; } - return false; - } - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "GetChildren", - [ - { id: parentId }, - { pageNumber: options.pageNumber }, - { pageSize: options.pageSize }, - { orderBy: options.orderBy }, - { orderDirection: options.orderDirection }, - { orderBySystemField: toBool(options.orderBySystemField) }, - { filter: options.filter } - ])), - 'Failed to retrieve children for media item ' + parentId); - }, - - /** + return umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('mediaApiBaseUrl', 'GetChildren', [ + { id: parentId }, + { pageNumber: options.pageNumber }, + { pageSize: options.pageSize }, + { orderBy: options.orderBy }, + { orderDirection: options.orderDirection }, + { orderBySystemField: toBool(options.orderBySystemField) }, + { filter: options.filter } + ])), 'Failed to retrieve children for media item ' + parentId); + }, + /** * @ngdoc method * @name umbraco.resources.mediaResource#save * @methodOf umbraco.resources.mediaResource @@ -2845,11 +2854,10 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { * @returns {Promise} resourcePromise object containing the saved media item. * */ - save: function (media, isNew, files) { - return saveMediaItem(media, "save" + (isNew ? "New" : ""), files); - }, - - /** + save: function (media, isNew, files) { + return saveMediaItem(media, 'save' + (isNew ? 'New' : ''), files); + }, + /** * @ngdoc method * @name umbraco.resources.mediaResource#addFolder * @methodOf umbraco.resources.mediaResource @@ -2870,18 +2878,13 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { * @returns {Promise} resourcePromise object. * */ - addFolder: function (name, parentId) { - return umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper - .getApiUrl("mediaApiBaseUrl", "PostAddFolder"), - { - name: name, - parentId: parentId - }), - 'Failed to add folder'); - }, - - /** + addFolder: function (name, parentId) { + return umbRequestHelper.resourcePromise($http.post(umbRequestHelper.getApiUrl('mediaApiBaseUrl', 'PostAddFolder'), { + name: name, + parentId: parentId + }), 'Failed to add folder'); + }, + /** * @ngdoc method * @name umbraco.resources.mediaResource#getChildFolders * @methodOf umbraco.resources.mediaResource @@ -2890,7 +2893,9 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { * Retrieves all media children with types used as folders. * Uses the convention of looking for media items with mediaTypes ending in * *Folder so will match "Folder", "bannerFolder", "secureFolder" etc, - * + * + * NOTE: This will return a max of 500 folders, if more is required it needs to be paged + * * ##usage *
               * mediaResource.getChildFolders(1234)
    @@ -2903,23 +2908,14 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) {
               * @returns {Promise} resourcePromise object.
               *
               */
    -        getChildFolders: function (parentId) {
    -            if (!parentId) {
    -                parentId = -1;
    -            }
    -
    -            return umbRequestHelper.resourcePromise(
    -                  $http.get(
    -                        umbRequestHelper.getApiUrl(
    -                              "mediaApiBaseUrl",
    -                              "GetChildFolders",
    -                              [
    -                                    { id: parentId }
    -                              ])),
    -                  'Failed to retrieve child folders for media item ' + parentId);
    -        },
    -
    -        /**
    +            getChildFolders: function (parentId) {
    +                if (!parentId) {
    +                    parentId = -1;
    +                }
    +                //NOTE: This will return a max of 500 folders, if more is required it needs to be paged
    +                return umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('mediaApiBaseUrl', 'GetChildFolders', { id: parentId })), 'Failed to retrieve child folders for media item ' + parentId);
    +            },
    +            /**
               * @ngdoc method
               * @name umbraco.resources.mediaResource#emptyRecycleBin
               * @methodOf umbraco.resources.mediaResource
    @@ -2938,61 +2934,92 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) {
               * @returns {Promise} resourcePromise object.
               *
               */
    -        emptyRecycleBin: function () {
    -            return umbRequestHelper.resourcePromise(
    -                   $http.post(
    -                         umbRequestHelper.getApiUrl(
    -                               "mediaApiBaseUrl",
    -                               "EmptyRecycleBin")),
    -                   'Failed to empty the recycle bin');
    -        }
    -    };
    -}
    -
    -angular.module('umbraco.resources').factory('mediaResource', mediaResource);
    -
    -/**
    +            emptyRecycleBin: function () {
    +                return umbRequestHelper.resourcePromise($http.post(umbRequestHelper.getApiUrl('mediaApiBaseUrl', 'EmptyRecycleBin')), 'Failed to empty the recycle bin');
    +            },
    +            /**
    +          * @ngdoc method
    +          * @name umbraco.resources.mediaResource#search
    +          * @methodOf umbraco.resources.mediaResource
    +          *
    +          * @description
    +          * Paginated search for media items starting on the supplied nodeId
    +          *
    +          * ##usage
    +          * 
    +          * mediaResource.search("my search", 1, 100, -1)
    +          *    .then(function(searchResult) {
    +          *        alert('it's here!');
    +          *    });
    +          * 
    + * + * @param {string} query The search query + * @param {int} pageNumber The page number + * @param {int} pageSize The number of media items on a page + * @param {int} searchFrom NodeId to search from (-1 for root) + * @returns {Promise} resourcePromise object. + * + */ + search: function (query, pageNumber, pageSize, searchFrom) { + var args = [ + { 'query': query }, + { 'pageNumber': pageNumber }, + { 'pageSize': pageSize }, + { 'searchFrom': searchFrom } + ]; + return umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('mediaApiBaseUrl', 'Search', args)), 'Failed to retrieve media items for search: ' + query); + } + }; + } + angular.module('umbraco.resources').factory('mediaResource', mediaResource); + /** * @ngdoc service * @name umbraco.resources.mediaTypeResource * @description Loads in data for media types **/ -function mediaTypeResource($q, $http, umbRequestHelper, umbDataFormatter) { - - return { - - getCount: function () { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "mediaTypeApiBaseUrl", - "GetCount")), - 'Failed to retrieve count'); - }, - - getAvailableCompositeContentTypes: function (contentTypeId, filterContentTypes, filterPropertyTypes) { - if (!filterContentTypes) { - filterContentTypes = []; - } - if (!filterPropertyTypes) { - filterPropertyTypes = []; - } - - var query = { - contentTypeId: contentTypeId, - filterContentTypes: filterContentTypes, - filterPropertyTypes: filterPropertyTypes - }; - - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "mediaTypeApiBaseUrl", - "GetAvailableCompositeMediaTypes"), - query), - 'Failed to retrieve data for content type id ' + contentTypeId); - }, - - /** + function mediaTypeResource($q, $http, umbRequestHelper, umbDataFormatter) { + return { + getCount: function () { + return umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('mediaTypeApiBaseUrl', 'GetCount')), 'Failed to retrieve count'); + }, + getAvailableCompositeContentTypes: function (contentTypeId, filterContentTypes, filterPropertyTypes) { + if (!filterContentTypes) { + filterContentTypes = []; + } + if (!filterPropertyTypes) { + filterPropertyTypes = []; + } + var query = { + contentTypeId: contentTypeId, + filterContentTypes: filterContentTypes, + filterPropertyTypes: filterPropertyTypes + }; + return umbRequestHelper.resourcePromise($http.post(umbRequestHelper.getApiUrl('mediaTypeApiBaseUrl', 'GetAvailableCompositeMediaTypes'), query), 'Failed to retrieve data for content type id ' + contentTypeId); + }, + /** + * @ngdoc method + * @name umbraco.resources.mediaTypeResource#getWhereCompositionIsUsedInContentTypes + * @methodOf umbraco.resources.mediaTypeResource + * + * @description + * Returns a list of media types which use a specific composition with a given id + * + * ##usage + *
    +         * mediaTypeResource.getWhereCompositionIsUsedInContentTypes(1234)
    +         *    .then(function(mediaTypeList) {
    +         *        console.log(mediaTypeList);
    +         *    });
    +         * 
    + * @param {Int} contentTypeId id of the composition content type to retrieve the list of the media types where it has been used + * @returns {Promise} resourcePromise object. + * + */ + getWhereCompositionIsUsedInContentTypes: function (contentTypeId) { + var query = { contentTypeId: contentTypeId }; + return umbRequestHelper.resourcePromise($http.post(umbRequestHelper.getApiUrl('mediaTypeApiBaseUrl', 'GetWhereCompositionIsUsedInContentTypes'), query), 'Failed to retrieve data for content type id ' + contentTypeId); + }, + /** * @ngdoc method * @name umbraco.resources.mediaTypeResource#getAllowedTypes * @methodOf umbraco.resources.mediaTypeResource @@ -3011,80 +3038,41 @@ function mediaTypeResource($q, $http, umbRequestHelper, umbDataFormatter) { * @returns {Promise} resourcePromise object. * */ - getAllowedTypes: function (mediaId) { - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "mediaTypeApiBaseUrl", - "GetAllowedChildren", - [{ contentId: mediaId }])), - 'Failed to retrieve allowed types for media id ' + mediaId); - }, - - getById: function (id) { - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "mediaTypeApiBaseUrl", - "GetById", - [{ id: id }])), - 'Failed to retrieve content type'); - }, - - getAll: function () { - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "mediaTypeApiBaseUrl", - "GetAll")), - 'Failed to retrieve all content types'); - }, - - getScaffold: function (parentId) { - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "mediaTypeApiBaseUrl", - "GetEmpty", { parentId: parentId })), - 'Failed to retrieve content type scaffold'); - }, - - deleteById: function (id) { - - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "mediaTypeApiBaseUrl", - "DeleteById", - [{ id: id }])), - 'Failed to retrieve content type'); - }, - - deleteContainerById: function (id) { - - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "mediaTypeApiBaseUrl", - "DeleteContainer", - [{ id: id }])), - 'Failed to delete content type contaier'); - }, - - save: function (contentType) { - - var saveModel = umbDataFormatter.formatContentTypePostData(contentType); - - return umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper.getApiUrl("mediaTypeApiBaseUrl", "PostSave"), saveModel), - 'Failed to save data for content type id ' + contentType.id); - }, - - /** + getAllowedTypes: function (mediaId) { + return umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('mediaTypeApiBaseUrl', 'GetAllowedChildren', [{ contentId: mediaId }])), 'Failed to retrieve allowed types for media id ' + mediaId); + }, + getById: function (id) { + return umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('mediaTypeApiBaseUrl', 'GetById', [{ id: id }])), 'Failed to retrieve content type'); + }, + getAll: function () { + return umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('mediaTypeApiBaseUrl', 'GetAll')), 'Failed to retrieve all content types'); + }, + getScaffold: function (parentId) { + return umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('mediaTypeApiBaseUrl', 'GetEmpty', { parentId: parentId })), 'Failed to retrieve content type scaffold'); + }, + deleteById: function (id) { + return umbRequestHelper.resourcePromise($http.post(umbRequestHelper.getApiUrl('mediaTypeApiBaseUrl', 'DeleteById', [{ id: id }])), 'Failed to retrieve content type'); + }, + deleteContainerById: function (id) { + return umbRequestHelper.resourcePromise($http.post(umbRequestHelper.getApiUrl('mediaTypeApiBaseUrl', 'DeleteContainer', [{ id: id }])), 'Failed to delete content type contaier'); + }, + /** + * @ngdoc method + * @name umbraco.resources.mediaTypeResource#save + * @methodOf umbraco.resources.mediaTypeResource + * + * @description + * Saves or update a media type + * + * @param {Object} content data type object to create/update + * @returns {Promise} resourcePromise object. + * + */ + save: function (contentType) { + var saveModel = umbDataFormatter.formatContentTypePostData(contentType); + return umbRequestHelper.resourcePromise($http.post(umbRequestHelper.getApiUrl('mediaTypeApiBaseUrl', 'PostSave'), saveModel), 'Failed to save data for content type id ' + contentType.id); + }, + /** * @ngdoc method * @name umbraco.resources.mediaTypeResource#move * @methodOf umbraco.resources.mediaTypeResource @@ -3107,162 +3095,125 @@ function mediaTypeResource($q, $http, umbRequestHelper, umbDataFormatter) { * @returns {Promise} resourcePromise object. * */ - move: function (args) { - if (!args) { - throw "args cannot be null"; + move: function (args) { + if (!args) { + throw 'args cannot be null'; + } + if (!args.parentId) { + throw 'args.parentId cannot be null'; + } + if (!args.id) { + throw 'args.id cannot be null'; + } + return umbRequestHelper.resourcePromise($http.post(umbRequestHelper.getApiUrl('mediaTypeApiBaseUrl', 'PostMove'), { + parentId: args.parentId, + id: args.id + }), 'Failed to move content'); + }, + copy: function (args) { + if (!args) { + throw 'args cannot be null'; + } + if (!args.parentId) { + throw 'args.parentId cannot be null'; + } + if (!args.id) { + throw 'args.id cannot be null'; + } + return umbRequestHelper.resourcePromise($http.post(umbRequestHelper.getApiUrl('mediaTypeApiBaseUrl', 'PostCopy'), { + parentId: args.parentId, + id: args.id + }), 'Failed to copy content'); + }, + createContainer: function (parentId, name) { + return umbRequestHelper.resourcePromise($http.post(umbRequestHelper.getApiUrl('mediaTypeApiBaseUrl', 'PostCreateContainer', { + parentId: parentId, + name: encodeURIComponent(name) + })), 'Failed to create a folder under parent id ' + parentId); + }, + renameContainer: function (id, name) { + return umbRequestHelper.resourcePromise($http.post(umbRequestHelper.getApiUrl('mediaTypeApiBaseUrl', 'PostRenameContainer', { + id: id, + name: name + })), 'Failed to rename the folder with id ' + id); } - if (!args.parentId) { - throw "args.parentId cannot be null"; - } - if (!args.id) { - throw "args.id cannot be null"; - } - - return umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper.getApiUrl("mediaTypeApiBaseUrl", "PostMove"), - { - parentId: args.parentId, - id: args.id - }), - 'Failed to move content'); - }, - - copy: function (args) { - if (!args) { - throw "args cannot be null"; - } - if (!args.parentId) { - throw "args.parentId cannot be null"; - } - if (!args.id) { - throw "args.id cannot be null"; - } - - return umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper.getApiUrl("mediaTypeApiBaseUrl", "PostCopy"), - { - parentId: args.parentId, - id: args.id - }), - 'Failed to copy content'); - }, - - createContainer: function(parentId, name) { - - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "mediaTypeApiBaseUrl", - "PostCreateContainer", - { parentId: parentId, name: name })), - 'Failed to create a folder under parent id ' + parentId); - } - - }; -} -angular.module('umbraco.resources').factory('mediaTypeResource', mediaTypeResource); - -/** + }; + } + angular.module('umbraco.resources').factory('mediaTypeResource', mediaTypeResource); + /** * @ngdoc service * @name umbraco.resources.memberResource * @description Loads in data for members **/ -function memberResource($q, $http, umbDataFormatter, umbRequestHelper) { - - /** internal method process the saving of data and post processing the result */ - function saveMember(content, action, files) { - - return umbRequestHelper.postSaveContent({ - restApiUrl: umbRequestHelper.getApiUrl( - "memberApiBaseUrl", - "PostSave"), - content: content, - action: action, - files: files, - dataFormatter: function (c, a) { - return umbDataFormatter.formatMemberPostData(c, a); - } - }); - } - - return { - - getPagedResults: function (memberTypeAlias, options) { - - if (memberTypeAlias === 'all-members') { - memberTypeAlias = null; - } - - var defaults = { - pageSize: 25, - pageNumber: 1, - filter: '', - orderDirection: "Ascending", - orderBy: "LoginName", - orderBySystemField: true - }; - if (options === undefined) { - options = {}; - } - //overwrite the defaults if there are any specified - angular.extend(defaults, options); - //now copy back to the options we will use - options = defaults; - //change asc/desct - if (options.orderDirection === "asc") { - options.orderDirection = "Ascending"; - } - else if (options.orderDirection === "desc") { - options.orderDirection = "Descending"; - } - - //converts the value to a js bool - function toBool(v) { - if (angular.isNumber(v)) { - return v > 0; + function memberResource($q, $http, umbDataFormatter, umbRequestHelper) { + /** internal method process the saving of data and post processing the result */ + function saveMember(content, action, files) { + return umbRequestHelper.postSaveContent({ + restApiUrl: umbRequestHelper.getApiUrl('memberApiBaseUrl', 'PostSave'), + content: content, + action: action, + files: files, + dataFormatter: function (c, a) { + return umbDataFormatter.formatMemberPostData(c, a); } - if (angular.isString(v)) { - return v === "true"; + }); + } + return { + getPagedResults: function (memberTypeAlias, options) { + if (memberTypeAlias === 'all-members') { + memberTypeAlias = null; } - if (typeof v === "boolean") { - return v; + var defaults = { + pageSize: 25, + pageNumber: 1, + filter: '', + orderDirection: 'Ascending', + orderBy: 'LoginName', + orderBySystemField: true + }; + if (options === undefined) { + options = {}; } - return false; - } - - var params = [ - { pageNumber: options.pageNumber }, - { pageSize: options.pageSize }, - { orderBy: options.orderBy }, - { orderDirection: options.orderDirection }, - { orderBySystemField: toBool(options.orderBySystemField) }, - { filter: options.filter } - ]; - if (memberTypeAlias != null) { - params.push({ memberTypeAlias: memberTypeAlias }); - } - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "memberApiBaseUrl", - "GetPagedResults", - params)), - 'Failed to retrieve member paged result'); - }, - - getListNode: function (listName) { - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "memberApiBaseUrl", - "GetListNodeDisplay", - [{ listName: listName }])), - 'Failed to retrieve data for member list ' + listName); - }, - - /** + //overwrite the defaults if there are any specified + angular.extend(defaults, options); + //now copy back to the options we will use + options = defaults; + //change asc/desct + if (options.orderDirection === 'asc') { + options.orderDirection = 'Ascending'; + } else if (options.orderDirection === 'desc') { + options.orderDirection = 'Descending'; + } + //converts the value to a js bool + function toBool(v) { + if (angular.isNumber(v)) { + return v > 0; + } + if (angular.isString(v)) { + return v === 'true'; + } + if (typeof v === 'boolean') { + return v; + } + return false; + } + var params = [ + { pageNumber: options.pageNumber }, + { pageSize: options.pageSize }, + { orderBy: options.orderBy }, + { orderDirection: options.orderDirection }, + { orderBySystemField: toBool(options.orderBySystemField) }, + { filter: options.filter } + ]; + if (memberTypeAlias != null) { + params.push({ memberTypeAlias: memberTypeAlias }); + } + return umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('memberApiBaseUrl', 'GetPagedResults', params)), 'Failed to retrieve member paged result'); + }, + getListNode: function (listName) { + return umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('memberApiBaseUrl', 'GetListNodeDisplay', [{ listName: listName }])), 'Failed to retrieve data for member list ' + listName); + }, + /** * @ngdoc method * @name umbraco.resources.memberResource#getByKey * @methodOf umbraco.resources.memberResource @@ -3283,18 +3234,10 @@ function memberResource($q, $http, umbDataFormatter, umbRequestHelper) { * @returns {Promise} resourcePromise object containing the member item. * */ - getByKey: function (key) { - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "memberApiBaseUrl", - "GetByKey", - [{ key: key }])), - 'Failed to retrieve data for member id ' + key); - }, - - /** + getByKey: function (key) { + return umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('memberApiBaseUrl', 'GetByKey', [{ key: key }])), 'Failed to retrieve data for member id ' + key); + }, + /** * @ngdoc method * @name umbraco.resources.memberResource#deleteByKey * @methodOf umbraco.resources.memberResource @@ -3314,17 +3257,10 @@ function memberResource($q, $http, umbDataFormatter, umbRequestHelper) { * @returns {Promise} resourcePromise object. * */ - deleteByKey: function (key) { - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "memberApiBaseUrl", - "DeleteByKey", - [{ key: key }])), - 'Failed to delete item ' + key); - }, - - /** + deleteByKey: function (key) { + return umbRequestHelper.resourcePromise($http.post(umbRequestHelper.getApiUrl('memberApiBaseUrl', 'DeleteByKey', [{ key: key }])), 'Failed to delete item ' + key); + }, + /** * @ngdoc method * @name umbraco.resources.memberResource#getScaffold * @methodOf umbraco.resources.memberResource @@ -3354,29 +3290,14 @@ function memberResource($q, $http, umbDataFormatter, umbRequestHelper) { * @returns {Promise} resourcePromise object containing the member scaffold. * */ - getScaffold: function (alias) { - - if (alias) { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "memberApiBaseUrl", - "GetEmpty", - [{ contentTypeAlias: alias }])), - 'Failed to retrieve data for empty member item type ' + alias); - } - else { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "memberApiBaseUrl", - "GetEmpty")), - 'Failed to retrieve data for empty member item type ' + alias); - } - - }, - - /** + getScaffold: function (alias) { + if (alias) { + return umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('memberApiBaseUrl', 'GetEmpty', [{ contentTypeAlias: alias }])), 'Failed to retrieve data for empty member item type ' + alias); + } else { + return umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('memberApiBaseUrl', 'GetEmpty')), 'Failed to retrieve data for empty member item type ' + alias); + } + }, + /** * @ngdoc method * @name umbraco.resources.memberResource#save * @methodOf umbraco.resources.memberResource @@ -3404,104 +3325,61 @@ function memberResource($q, $http, umbDataFormatter, umbRequestHelper) { * @returns {Promise} resourcePromise object containing the saved media item. * */ - save: function (member, isNew, files) { - return saveMember(member, "save" + (isNew ? "New" : ""), files); - } - }; -} - -angular.module('umbraco.resources').factory('memberResource', memberResource); - -/** + save: function (member, isNew, files) { + return saveMember(member, 'save' + (isNew ? 'New' : ''), files); + } + }; + } + angular.module('umbraco.resources').factory('memberResource', memberResource); + /** * @ngdoc service * @name umbraco.resources.memberTypeResource * @description Loads in data for member types **/ -function memberTypeResource($q, $http, umbRequestHelper, umbDataFormatter) { - - return { - - getAvailableCompositeContentTypes: function (contentTypeId, filterContentTypes, filterPropertyTypes) { - if (!filterContentTypes) { - filterContentTypes = []; - } - if (!filterPropertyTypes) { - filterPropertyTypes = []; - } - - var query = ""; - _.each(filterContentTypes, function (item) { - query += "filterContentTypes=" + item + "&"; - }); - // if filterContentTypes array is empty we need a empty variable in the querystring otherwise the service returns a error - if (filterContentTypes.length === 0) { - query += "filterContentTypes=&"; - } - _.each(filterPropertyTypes, function (item) { - query += "filterPropertyTypes=" + item + "&"; - }); - // if filterPropertyTypes array is empty we need a empty variable in the querystring otherwise the service returns a error - if (filterPropertyTypes.length === 0) { - query += "filterPropertyTypes=&"; - } - query += "contentTypeId=" + contentTypeId; - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "memberTypeApiBaseUrl", - "GetAvailableCompositeMemberTypes", - query)), - 'Failed to retrieve data for content type id ' + contentTypeId); - }, - - //return all member types - getTypes: function () { - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "memberTypeApiBaseUrl", - "GetAllTypes")), - 'Failed to retrieve data for member types id'); - }, - - getById: function (id) { - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "memberTypeApiBaseUrl", - "GetById", - [{ id: id }])), - 'Failed to retrieve content type'); - }, - - deleteById: function (id) { - - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "memberTypeApiBaseUrl", - "DeleteById", - [{ id: id }])), - 'Failed to delete member type'); - }, - - getScaffold: function () { - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "memberTypeApiBaseUrl", - "GetEmpty")), - 'Failed to retrieve content type scaffold'); - }, - - /** + function memberTypeResource($q, $http, umbRequestHelper, umbDataFormatter) { + return { + getAvailableCompositeContentTypes: function (contentTypeId, filterContentTypes, filterPropertyTypes) { + if (!filterContentTypes) { + filterContentTypes = []; + } + if (!filterPropertyTypes) { + filterPropertyTypes = []; + } + var query = ''; + _.each(filterContentTypes, function (item) { + query += 'filterContentTypes=' + item + '&'; + }); + // if filterContentTypes array is empty we need a empty variable in the querystring otherwise the service returns a error + if (filterContentTypes.length === 0) { + query += 'filterContentTypes=&'; + } + _.each(filterPropertyTypes, function (item) { + query += 'filterPropertyTypes=' + item + '&'; + }); + // if filterPropertyTypes array is empty we need a empty variable in the querystring otherwise the service returns a error + if (filterPropertyTypes.length === 0) { + query += 'filterPropertyTypes=&'; + } + query += 'contentTypeId=' + contentTypeId; + return umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('memberTypeApiBaseUrl', 'GetAvailableCompositeMemberTypes', query)), 'Failed to retrieve data for content type id ' + contentTypeId); + }, + //return all member types + getTypes: function () { + return umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('memberTypeApiBaseUrl', 'GetAllTypes')), 'Failed to retrieve data for member types id'); + }, + getById: function (id) { + return umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('memberTypeApiBaseUrl', 'GetById', [{ id: id }])), 'Failed to retrieve content type'); + }, + deleteById: function (id) { + return umbRequestHelper.resourcePromise($http.post(umbRequestHelper.getApiUrl('memberTypeApiBaseUrl', 'DeleteById', [{ id: id }])), 'Failed to delete member type'); + }, + getScaffold: function () { + return umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('memberTypeApiBaseUrl', 'GetEmpty')), 'Failed to retrieve content type scaffold'); + }, + /** * @ngdoc method - * @name umbraco.resources.contentTypeResource#save - * @methodOf umbraco.resources.contentTypeResource + * @name umbraco.resources.memberTypeResource#save + * @methodOf umbraco.resources.memberTypeResource * * @description * Saves or update a member type @@ -3510,98 +3388,70 @@ function memberTypeResource($q, $http, umbRequestHelper, umbDataFormatter) { * @returns {Promise} resourcePromise object. * */ - save: function (contentType) { - - var saveModel = umbDataFormatter.formatContentTypePostData(contentType); - - return umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper.getApiUrl("memberTypeApiBaseUrl", "PostSave"), saveModel), - 'Failed to save data for member type id ' + contentType.id); - } - - }; -} -angular.module('umbraco.resources').factory('memberTypeResource', memberTypeResource); - -/** + save: function (contentType) { + var saveModel = umbDataFormatter.formatContentTypePostData(contentType); + return umbRequestHelper.resourcePromise($http.post(umbRequestHelper.getApiUrl('memberTypeApiBaseUrl', 'PostSave'), saveModel), 'Failed to save data for member type id ' + contentType.id); + } + }; + } + angular.module('umbraco.resources').factory('memberTypeResource', memberTypeResource); + angular.module('umbraco.resources').factory('Umbraco.PropertyEditors.NestedContent.Resources', function ($q, $http, umbRequestHelper) { + return { + getContentTypes: function () { + var url = Umbraco.Sys.ServerVariables.umbracoSettings.umbracoPath + '/backoffice/UmbracoApi/NestedContent/GetContentTypes'; + return umbRequestHelper.resourcePromise($http.get(url), 'Failed to retrieve content types'); + } + }; + }); + /** * @ngdoc service * @name umbraco.resources.ourPackageRepositoryResource * @description handles data for package installations **/ -function ourPackageRepositoryResource($q, $http, umbDataFormatter, umbRequestHelper) { - - //var baseurl = "http://localhost:24292/webapi/packages/v1"; - var baseurl = "https://our.umbraco.org/webapi/packages/v1"; - - return { - - getDetails: function (packageId) { - - return umbRequestHelper.resourcePromise( - $http.get(baseurl + "/" + packageId), - 'Failed to get package details'); - }, - - getCategories: function () { - - return umbRequestHelper.resourcePromise( - $http.get(baseurl), - 'Failed to query packages'); - }, - - getPopular: function (maxResults, category) { - - if (maxResults === undefined) { - maxResults = 10; + function ourPackageRepositoryResource($q, $http, umbDataFormatter, umbRequestHelper) { + var baseurl = Umbraco.Sys.ServerVariables.umbracoUrls.packagesRestApiBaseUrl; + return { + getDetails: function (packageId) { + return umbRequestHelper.resourcePromise($http.get(baseurl + '/' + packageId + '?version=' + Umbraco.Sys.ServerVariables.application.version), 'Failed to get package details'); + }, + getCategories: function () { + return umbRequestHelper.resourcePromise($http.get(baseurl), 'Failed to query packages'); + }, + getPopular: function (maxResults, category) { + if (maxResults === undefined) { + maxResults = 10; + } + if (category === undefined) { + category = ''; + } + return umbRequestHelper.resourcePromise($http.get(baseurl + '?pageIndex=0&pageSize=' + maxResults + '&category=' + category + '&order=Popular&version=' + Umbraco.Sys.ServerVariables.application.version), 'Failed to query packages'); + }, + search: function (pageIndex, pageSize, orderBy, category, query, canceler) { + var httpConfig = {}; + if (canceler) { + httpConfig['timeout'] = canceler; + } + if (category === undefined) { + category = ''; + } + if (query === undefined) { + query = ''; + } + //order by score if there is nothing set + var order = !orderBy ? '&order=Default' : '&order=' + orderBy; + return umbRequestHelper.resourcePromise($http.get(baseurl + '?pageIndex=' + pageIndex + '&pageSize=' + pageSize + '&category=' + category + '&query=' + query + order + '&version=' + Umbraco.Sys.ServerVariables.application.version), httpConfig, 'Failed to query packages'); } - if (category === undefined) { - category = ""; - } - - return umbRequestHelper.resourcePromise( - $http.get(baseurl + "?pageIndex=0&pageSize=" + maxResults + "&category=" + category + "&order=Popular&version=" + Umbraco.Sys.ServerVariables.application.version), - 'Failed to query packages'); - }, - - search: function (pageIndex, pageSize, orderBy, category, query, canceler) { - - var httpConfig = {}; - if (canceler) { - httpConfig["timeout"] = canceler; - } - - if (category === undefined) { - category = ""; - } - if (query === undefined) { - query = ""; - } - - //order by score if there is nothing set - var order = !orderBy ? "&order=Default" : ("&order=" + orderBy); - - return umbRequestHelper.resourcePromise( - $http.get(baseurl + "?pageIndex=" + pageIndex + "&pageSize=" + pageSize + "&category=" + category + "&query=" + query + order + "&version=" + Umbraco.Sys.ServerVariables.application.version), - httpConfig, - 'Failed to query packages'); - } - - - }; -} - -angular.module('umbraco.resources').factory('ourPackageRepositoryResource', ourPackageRepositoryResource); - -/** + }; + } + angular.module('umbraco.resources').factory('ourPackageRepositoryResource', ourPackageRepositoryResource); + /** * @ngdoc service * @name umbraco.resources.packageInstallResource * @description handles data for package installations **/ -function packageResource($q, $http, umbDataFormatter, umbRequestHelper) { - - return { - - /** + function packageResource($q, $http, umbDataFormatter, umbRequestHelper) { + return { + /** * @ngdoc method * @name umbraco.resources.packageInstallResource#getInstalled * @methodOf umbraco.resources.packageInstallResource @@ -3609,49 +3459,28 @@ function packageResource($q, $http, umbDataFormatter, umbRequestHelper) { * @description * Gets a list of installed packages */ - getInstalled: function() { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "packageInstallApiBaseUrl", - "GetInstalled")), - 'Failed to get installed packages'); - }, - - validateInstalled: function (name, version) { - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "packageInstallApiBaseUrl", - "ValidateInstalled", { name: name, version: version })), - 'Failed to validate package ' + name); - }, - - deleteCreatedPackage: function (packageId) { - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "packageInstallApiBaseUrl", - "DeleteCreatedPackage", { packageId: packageId })), - 'Failed to delete package ' + packageId); - }, - - uninstall: function(packageId) { - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "packageInstallApiBaseUrl", - "Uninstall", { packageId: packageId })), - 'Failed to uninstall package'); - }, - - /** + getInstalled: function () { + return umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('packageInstallApiBaseUrl', 'GetInstalled')), 'Failed to get installed packages'); + }, + validateInstalled: function (name, version) { + return umbRequestHelper.resourcePromise($http.post(umbRequestHelper.getApiUrl('packageInstallApiBaseUrl', 'ValidateInstalled', { + name: name, + version: version + })), 'Failed to validate package ' + name); + }, + deleteCreatedPackage: function (packageId) { + return umbRequestHelper.resourcePromise($http.post(umbRequestHelper.getApiUrl('packageInstallApiBaseUrl', 'DeleteCreatedPackage', { packageId: packageId })), 'Failed to delete package ' + packageId); + }, + uninstall: function (packageId) { + return umbRequestHelper.resourcePromise($http.post(umbRequestHelper.getApiUrl('packageInstallApiBaseUrl', 'Uninstall', { packageId: packageId })), 'Failed to uninstall package'); + }, + /** * @ngdoc method * @name umbraco.resources.packageInstallResource#fetchPackage * @methodOf umbraco.resources.packageInstallResource * * @description - * Downloads a package file from our.umbraco.org to the website server. + * Downloads a package file from our.umbraco.com to the website server. * * ##usage *
    @@ -3664,18 +3493,11 @@ function packageResource($q, $http, umbDataFormatter, umbRequestHelper) {
              * @param {String} the unique package ID
              * @returns {String} path to the downloaded zip file.
              *
    -         */  
    -        fetch: function (id) {
    -            return umbRequestHelper.resourcePromise(
    -               $http.get(
    -                   umbRequestHelper.getApiUrl(
    -                       "packageInstallApiBaseUrl",
    -                       "Fetch",
    -                       [{ packageGuid: id }])),
    -               'Failed to download package with guid ' + id);
    -        },
    -        
    -        /**
    +         */
    +            fetch: function (id) {
    +                return umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('packageInstallApiBaseUrl', 'Fetch', [{ packageGuid: id }])), 'Failed to download package with guid ' + id);
    +            },
    +            /**
              * @ngdoc method
              * @name umbraco.resources.packageInstallResource#createmanifest
              * @methodOf umbraco.resources.packageInstallResource
    @@ -3697,51 +3519,26 @@ function packageResource($q, $http, umbDataFormatter, umbRequestHelper) {
              * @param {String} folder the path to the temporary folder containing files
              * @returns {Int} the ID assigned to the saved package manifest
              *
    -         */ 
    -        import: function (package) {
    -           
    -            return umbRequestHelper.resourcePromise(
    -                $http.post(
    -                  umbRequestHelper.getApiUrl(
    -                      "packageInstallApiBaseUrl",
    -                      "Import"), package),
    -              'Failed to install package. Error during the step "Import" ');
    -        }, 
    -
    -        installFiles: function (package) {
    -            return umbRequestHelper.resourcePromise(
    -                $http.post(
    -                  umbRequestHelper.getApiUrl(
    -                      "packageInstallApiBaseUrl",
    -                      "InstallFiles"), package),
    -              'Failed to install package. Error during the step "InstallFiles" ');
    -        }, 
    -
    -        installData: function (package) {
    -           
    -            return umbRequestHelper.resourcePromise(
    -                $http.post(
    -                  umbRequestHelper.getApiUrl(
    -                      "packageInstallApiBaseUrl",
    -                      "InstallData"), package),
    -              'Failed to install package. Error during the step "InstallData" ');
    -        }, 
    -
    -        cleanUp: function (package) {
    -           
    -            return umbRequestHelper.resourcePromise(
    -                $http.post(
    -                  umbRequestHelper.getApiUrl(
    -                      "packageInstallApiBaseUrl",
    -                      "CleanUp"), package),
    -              'Failed to install package. Error during the step "CleanUp" ');
    -        }
    -    };
    -}
    -
    -angular.module('umbraco.resources').factory('packageResource', packageResource);
    -
    -/**
    +         */
    +            import: function (package) {
    +                return umbRequestHelper.resourcePromise($http.post(umbRequestHelper.getApiUrl('packageInstallApiBaseUrl', 'Import'), package), 'Failed to install package. Error during the step "Import" ');
    +            },
    +            installFiles: function (package) {
    +                return umbRequestHelper.resourcePromise($http.post(umbRequestHelper.getApiUrl('packageInstallApiBaseUrl', 'InstallFiles'), package), 'Failed to install package. Error during the step "InstallFiles" ');
    +            },
    +            checkRestart: function (package) {
    +                return umbRequestHelper.resourcePromise($http.post(umbRequestHelper.getApiUrl('packageInstallApiBaseUrl', 'CheckRestart'), package), 'Failed to install package. Error during the step "CheckRestart" ');
    +            },
    +            installData: function (package) {
    +                return umbRequestHelper.resourcePromise($http.post(umbRequestHelper.getApiUrl('packageInstallApiBaseUrl', 'InstallData'), package), 'Failed to install package. Error during the step "InstallData" ');
    +            },
    +            cleanUp: function (package) {
    +                return umbRequestHelper.resourcePromise($http.post(umbRequestHelper.getApiUrl('packageInstallApiBaseUrl', 'CleanUp'), package), 'Failed to install package. Error during the step "CleanUp" ');
    +            }
    +        };
    +    }
    +    angular.module('umbraco.resources').factory('packageResource', packageResource);
    +    /**
      * @ngdoc service
      * @name umbraco.resources.redirectUrlResource
      * @function
    @@ -3749,12 +3546,10 @@ angular.module('umbraco.resources').factory('packageResource', packageResource);
      * @description
      * Used by the redirect url dashboard to get urls and send requests to remove redirects.
      */
    -(function() {
    -    'use strict';
    -
    -    function redirectUrlsResource($http, umbRequestHelper) {
    -
    -        /**
    +    (function () {
    +        'use strict';
    +        function redirectUrlsResource($http, umbRequestHelper) {
    +            /**
              * @ngdoc function
              * @name umbraco.resources.redirectUrlResource#searchRedirectUrls
              * @methodOf umbraco.resources.redirectUrlResource
    @@ -3773,28 +3568,17 @@ angular.module('umbraco.resources').factory('packageResource', packageResource);
              * @param {Int} pageIndex index of the page to retrive items from
              * @param {Int} pageSize The number of items on a page
              */
    -        function searchRedirectUrls(searchTerm, pageIndex, pageSize) {
    -
    -            return umbRequestHelper.resourcePromise(
    -                $http.get(
    -                    umbRequestHelper.getApiUrl(
    -                        "redirectUrlManagementApiBaseUrl",
    -                        "SearchRedirectUrls",
    -                        { searchTerm: searchTerm, page: pageIndex, pageSize: pageSize })),
    -                'Failed to retrieve data for searching redirect urls');
    -        }
    -
    -        function getEnableState() {
    -
    -            return umbRequestHelper.resourcePromise(
    -                $http.get(
    -                    umbRequestHelper.getApiUrl(
    -                        "redirectUrlManagementApiBaseUrl",
    -                        "GetEnableState")),
    -                'Failed to retrieve data to check if the 301 redirect is enabled');
    -        }
    -
    -        /**
    +            function searchRedirectUrls(searchTerm, pageIndex, pageSize) {
    +                return umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('redirectUrlManagementApiBaseUrl', 'SearchRedirectUrls', {
    +                    searchTerm: searchTerm,
    +                    page: pageIndex,
    +                    pageSize: pageSize
    +                })), 'Failed to retrieve data for searching redirect urls');
    +            }
    +            function getEnableState() {
    +                return umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('redirectUrlManagementApiBaseUrl', 'GetEnableState')), 'Failed to retrieve data to check if the 301 redirect is enabled');
    +            }
    +            /**
              * @ngdoc function
              * @name umbraco.resources.redirectUrlResource#deleteRedirectUrl
              * @methodOf umbraco.resources.redirectUrlResource
    @@ -3811,16 +3595,10 @@ angular.module('umbraco.resources').factory('packageResource', packageResource);
              * 
    * @param {Int} id Id of the redirect */ - function deleteRedirectUrl(id) { - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "redirectUrlManagementApiBaseUrl", - "DeleteRedirectUrl", { id: id })), - 'Failed to remove redirect'); - } - - /** + function deleteRedirectUrl(id) { + return umbRequestHelper.resourcePromise($http.post(umbRequestHelper.getApiUrl('redirectUrlManagementApiBaseUrl', 'DeleteRedirectUrl', { id: id })), 'Failed to remove redirect'); + } + /** * @ngdoc function * @name umbraco.resources.redirectUrlResource#toggleUrlTracker * @methodOf umbraco.resources.redirectUrlResource @@ -3837,39 +3615,27 @@ angular.module('umbraco.resources').factory('packageResource', packageResource); *
    * @param {Bool} disable true/false to disable/enable the url tracker */ - function toggleUrlTracker(disable) { - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "redirectUrlManagementApiBaseUrl", - "ToggleUrlTracker", { disable: disable })), - 'Failed to toggle redirect url tracker'); + function toggleUrlTracker(disable) { + return umbRequestHelper.resourcePromise($http.post(umbRequestHelper.getApiUrl('redirectUrlManagementApiBaseUrl', 'ToggleUrlTracker', { disable: disable })), 'Failed to toggle redirect url tracker'); + } + var resource = { + searchRedirectUrls: searchRedirectUrls, + deleteRedirectUrl: deleteRedirectUrl, + toggleUrlTracker: toggleUrlTracker, + getEnableState: getEnableState + }; + return resource; } - - var resource = { - searchRedirectUrls: searchRedirectUrls, - deleteRedirectUrl: deleteRedirectUrl, - toggleUrlTracker: toggleUrlTracker, - getEnableState: getEnableState - }; - - return resource; - - } - - angular.module('umbraco.resources').factory('redirectUrlsResource', redirectUrlsResource); - -})(); - -/** + angular.module('umbraco.resources').factory('redirectUrlsResource', redirectUrlsResource); + }()); + /** * @ngdoc service * @name umbraco.resources.relationResource * @description Handles loading of relation data **/ -function relationResource($q, $http, umbRequestHelper) { - return { - - /** + function relationResource($q, $http, umbRequestHelper) { + return { + /** * @ngdoc method * @name umbraco.resources.relationResource#getByChildId * @methodOf umbraco.resources.relationResource @@ -3882,18 +3648,13 @@ function relationResource($q, $http, umbRequestHelper) { * @returns {Promise} resourcePromise object containing the relations array. * */ - getByChildId: function (id, alias) { - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "relationApiBaseUrl", - "GetByChildId", - [{ childId: id, relationTypeAlias: alias }])), - "Failed to get relation by child ID " + id + " and type of " + alias); - }, - - /** + getByChildId: function (id, alias) { + return umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('relationApiBaseUrl', 'GetByChildId', [{ + childId: id, + relationTypeAlias: alias + }])), 'Failed to get relation by child ID ' + id + ' and type of ' + alias); + }, + /** * @ngdoc method * @name umbraco.resources.relationResource#deleteById * @methodOf umbraco.resources.relationResource @@ -3913,62 +3674,46 @@ function relationResource($q, $http, umbRequestHelper) { * @returns {Promise} resourcePromise object. * */ - deleteById: function (id) { - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "relationApiBaseUrl", - "DeleteById", - [{ id: id }])), - 'Failed to delete item ' + id); - } - }; -} - -angular.module('umbraco.resources').factory('relationResource', relationResource); -/** + deleteById: function (id) { + return umbRequestHelper.resourcePromise($http.post(umbRequestHelper.getApiUrl('relationApiBaseUrl', 'DeleteById', [{ id: id }])), 'Failed to delete item ' + id); + } + }; + } + angular.module('umbraco.resources').factory('relationResource', relationResource); + /** * @ngdoc service * @name umbraco.resources.sectionResource * @description Loads in data for section **/ -function sectionResource($q, $http, umbRequestHelper) { - - /** internal method to get the tree app url */ - function getSectionsUrl(section) { - return Umbraco.Sys.ServerVariables.sectionApiBaseUrl + "GetSections"; + function sectionResource($q, $http, umbRequestHelper) { + /** internal method to get the tree app url */ + function getSectionsUrl(section) { + return Umbraco.Sys.ServerVariables.sectionApiBaseUrl + 'GetSections'; + } + //the factory object returned + return { + /** Loads in the data to display the section list */ + getSections: function () { + return umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('sectionApiBaseUrl', 'GetSections')), 'Failed to retrieve data for sections'); + }, + /** Loads in all available sections */ + getAllSections: function () { + return umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('sectionApiBaseUrl', 'GetAllSections')), 'Failed to retrieve data for sections'); + } + }; } - - //the factory object returned - return { - /** Loads in the data to display the section list */ - getSections: function () { - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "sectionApiBaseUrl", - "GetSections")), - 'Failed to retrieve data for sections'); - - } - }; -} - -angular.module('umbraco.resources').factory('sectionResource', sectionResource); - -/** + angular.module('umbraco.resources').factory('sectionResource', sectionResource); + /** * @ngdoc service * @name umbraco.resources.stylesheetResource * @description service to retrieve available stylesheets * * **/ -function stylesheetResource($q, $http, umbRequestHelper) { - - //the factory object returned - return { - - /** + function stylesheetResource($q, $http, umbRequestHelper) { + //the factory object returned + return { + /** * @ngdoc method * @name umbraco.resources.stylesheetResource#getAll * @methodOf umbraco.resources.stylesheetResource @@ -3987,16 +3732,10 @@ function stylesheetResource($q, $http, umbRequestHelper) { * @returns {Promise} resourcePromise object containing the stylesheets. * */ - getAll: function () { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "stylesheetApiBaseUrl", - "GetAll")), - 'Failed to retrieve stylesheets '); - }, - - /** + getAll: function () { + return umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('stylesheetApiBaseUrl', 'GetAll')), 'Failed to retrieve stylesheets '); + }, + /** * @ngdoc method * @name umbraco.resources.stylesheetResource#getRulesByName * @methodOf umbraco.resources.stylesheetResource @@ -4015,132 +3754,773 @@ function stylesheetResource($q, $http, umbRequestHelper) { * @returns {Promise} resourcePromise object containing the rules. * */ - getRulesByName: function (name) { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "stylesheetApiBaseUrl", - "GetRulesByName", - [{ name: name }])), - 'Failed to retrieve stylesheets '); + getRulesByName: function (name) { + return umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('stylesheetApiBaseUrl', 'GetRulesByName', [{ name: name }])), 'Failed to retrieve stylesheets '); + } + }; + } + angular.module('umbraco.resources').factory('stylesheetResource', stylesheetResource); + /** + * @ngdoc service + * @name umbraco.resources.templateResource + * @description Loads in data for templates + **/ + function templateResource($q, $http, umbDataFormatter, umbRequestHelper) { + return { + /** + * @ngdoc method + * @name umbraco.resources.templateResource#getById + * @methodOf umbraco.resources.templateResource + * + * @description + * Gets a template item with a given id + * + * ##usage + *
    +         * templateResource.getById(1234)
    +         *    .then(function(template) {
    +         *        alert('its here!');
    +         *    });
    +         * 
    + * + * @param {Int} id id of template to retrieve + * @returns {Promise} resourcePromise object. + * + */ + getById: function (id) { + return umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('templateApiBaseUrl', 'GetById', [{ id: id }])), 'Failed to retrieve data for template id ' + id); + }, + /** + * @ngdoc method + * @name umbraco.resources.templateResource#getByAlias + * @methodOf umbraco.resources.templateResource + * + * @description + * Gets a template item with a given alias + * + * ##usage + *
    +         * templateResource.getByAlias("upload")
    +         *    .then(function(template) {
    +         *        alert('its here!');
    +         *    });
    +         * 
    + * + * @param {String} alias Alias of template to retrieve + * @returns {Promise} resourcePromise object. + * + */ + getByAlias: function (alias) { + return umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('templateApiBaseUrl', 'GetByAlias', [{ alias: alias }])), 'Failed to retrieve data for template with alias: ' + alias); + }, + /** + * @ngdoc method + * @name umbraco.resources.templateResource#getAll + * @methodOf umbraco.resources.templateResource + * + * @description + * Gets all templates + * + * ##usage + *
    +         * templateResource.getAll()
    +         *    .then(function(templates) {
    +         *        alert('its here!');
    +         *    });
    +         * 
    + * + * @returns {Promise} resourcePromise object. + * + */ + getAll: function () { + return umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('templateApiBaseUrl', 'GetAll')), 'Failed to retrieve data'); + }, + /** + * @ngdoc method + * @name umbraco.resources.templateResource#getScaffold + * @methodOf umbraco.resources.templateResource + * + * @description + * Returns a scaffold of an empty template item + * + * The scaffold is used to build editors for templates that has not yet been populated with data. + * + * ##usage + *
    +         * templateResource.getScaffold()
    +         *    .then(function(template) {
    +         *        alert('its here!');
    +         *    });
    +         * 
    + * + * @returns {Promise} resourcePromise object containing the template scaffold. + * + */ + getScaffold: function (id) { + return umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('templateApiBaseUrl', 'GetScaffold', [{ id: id }])), 'Failed to retrieve data for empty template'); + }, + /** + * @ngdoc method + * @name umbraco.resources.templateResource#deleteById + * @methodOf umbraco.resources.templateResource + * + * @description + * Deletes a template with a given id + * + * ##usage + *
    +         * templateResource.deleteById(1234)
    +         *    .then(function() {
    +         *        alert('its gone!');
    +         *    });
    +         * 
    + * + * @param {Int} id id of template to delete + * @returns {Promise} resourcePromise object. + * + */ + deleteById: function (id) { + return umbRequestHelper.resourcePromise($http.post(umbRequestHelper.getApiUrl('templateApiBaseUrl', 'DeleteById', [{ id: id }])), 'Failed to delete item ' + id); + }, + /** + * @ngdoc method + * @name umbraco.resources.templateResource#save + * @methodOf umbraco.resources.templateResource + * + * @description + * Saves or update a template + * + * ##usage + *
    +         * templateResource.save(template)
    +         *    .then(function(template) {
    +         *        alert('its saved!');
    +         *    });
    +         * 
    + * + * @param {Object} template object to save + * @returns {Promise} resourcePromise object. + * + */ + save: function (template) { + return umbRequestHelper.resourcePromise($http.post(umbRequestHelper.getApiUrl('templateApiBaseUrl', 'PostSave'), template), 'Failed to save data for template id ' + template.id); + } + }; + } + angular.module('umbraco.resources').factory('templateResource', templateResource); + /** + * @ngdoc service + * @name umbraco.resources.templateQueryResource + * @function + * + * @description + * Used by the query builder + */ + (function () { + 'use strict'; + function templateQueryResource($http, umbRequestHelper) { + /** + * @ngdoc function + * @name umbraco.resources.templateQueryResource#getAllowedProperties + * @methodOf umbraco.resources.templateQueryResource + * @function + * + * @description + * Called to get allowed properties + * ##usage + *
    +         * templateQueryResource.getAllowedProperties()
    +         *    .then(function(response) {
    +         *
    +         *    });
    +         * 
    + */ + function getAllowedProperties() { + return umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('templateQueryApiBaseUrl', 'GetAllowedProperties')), 'Failed to retrieve properties'); + } + /** + * @ngdoc function + * @name umbraco.resources.templateQueryResource#getContentTypes + * @methodOf umbraco.resources.templateQueryResource + * @function + * + * @description + * Called to get content types + * ##usage + *
    +         * templateQueryResource.getContentTypes()
    +         *    .then(function(response) {
    +         *
    +         *    });
    +         * 
    + */ + function getContentTypes() { + return umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('templateQueryApiBaseUrl', 'GetContentTypes')), 'Failed to retrieve content types'); + } + /** + * @ngdoc function + * @name umbraco.resources.templateQueryResource#getFilterConditions + * @methodOf umbraco.resources.templateQueryResource + * @function + * + * @description + * Called to the filter conditions + * ##usage + *
    +         * templateQueryResource.getFilterConditions()
    +         *    .then(function(response) {
    +         *
    +         *    });
    +         * 
    + */ + function getFilterConditions() { + return umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('templateQueryApiBaseUrl', 'GetFilterConditions')), 'Failed to retrieve filter conditions'); + } + /** + * @ngdoc function + * @name umbraco.resources.templateQueryResource#postTemplateQuery + * @methodOf umbraco.resources.templateQueryResource + * @function + * + * @description + * Called to get content types + * ##usage + *
    +         * var query = {
    +         *     contentType: {
    +         *         name: "Everything"
    +         *      },
    +         *      source: {
    +         *          name: "My website"
    +         *      },
    +         *      filters: [
    +         *          {
    +         *              property: undefined,
    +         *              operator: undefined
    +         *          }
    +         *      ],
    +         *      sort: {
    +         *          property: {
    +         *              alias: "",
    +         *              name: "",
    +         *          },
    +         *          direction: "ascending"
    +         *      }
    +         *  };
    +         * 
    +         * templateQueryResource.postTemplateQuery(query)
    +         *    .then(function(response) {
    +         *
    +         *    });
    +         * 
    + * @param {object} query Query to build result + */ + function postTemplateQuery(query) { + return umbRequestHelper.resourcePromise($http.post(umbRequestHelper.getApiUrl('templateQueryApiBaseUrl', 'PostTemplateQuery'), query), 'Failed to retrieve query'); + } + var resource = { + getAllowedProperties: getAllowedProperties, + getContentTypes: getContentTypes, + getFilterConditions: getFilterConditions, + postTemplateQuery: postTemplateQuery + }; + return resource; } - }; -} - -angular.module('umbraco.resources').factory('stylesheetResource', stylesheetResource); - -/** + angular.module('umbraco.resources').factory('templateQueryResource', templateQueryResource); + }()); + /** + * @ngdoc service + * @name umbraco.resources.usersResource + * @function + * + * @description + * Used by the users section to get users and send requests to create, invite, delete, etc. users. + */ + (function () { + 'use strict'; + function tourResource($http, umbRequestHelper, $q, umbDataFormatter) { + function getTours() { + return umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('tourApiBaseUrl', 'GetTours')), 'Failed to get tours'); + } + var resource = { getTours: getTours }; + return resource; + } + angular.module('umbraco.resources').factory('tourResource', tourResource); + }()); + /** * @ngdoc service * @name umbraco.resources.treeResource * @description Loads in data for trees **/ -function treeResource($q, $http, umbRequestHelper) { - - /** internal method to get the tree node's children url */ - function getTreeNodesUrl(node) { - if (!node.childNodesUrl) { - throw "No childNodesUrl property found on the tree node, cannot load child nodes"; + function treeResource($q, $http, umbRequestHelper) { + /** internal method to get the tree node's children url */ + function getTreeNodesUrl(node) { + if (!node.childNodesUrl) { + throw 'No childNodesUrl property found on the tree node, cannot load child nodes'; + } + return node.childNodesUrl; } - return node.childNodesUrl; - } - - /** internal method to get the tree menu url */ - function getTreeMenuUrl(node) { - if (!node.menuUrl) { - return null; + /** internal method to get the tree menu url */ + function getTreeMenuUrl(node) { + if (!node.menuUrl) { + return null; + } + return node.menuUrl; } - return node.menuUrl; + //the factory object returned + return { + /** Loads in the data to display the nodes menu */ + loadMenu: function (node) { + var treeMenuUrl = getTreeMenuUrl(node); + if (treeMenuUrl !== undefined && treeMenuUrl !== null && treeMenuUrl.length > 0) { + return umbRequestHelper.resourcePromise($http.get(getTreeMenuUrl(node)), 'Failed to retrieve data for a node\'s menu ' + node.id); + } else { + return $q.reject({ errorMsg: 'No tree menu url defined for node ' + node.id }); + } + }, + /** Loads in the data to display the nodes for an application */ + loadApplication: function (options) { + if (!options || !options.section) { + throw 'The object specified for does not contain a \'section\' property'; + } + if (!options.tree) { + options.tree = ''; + } + if (!options.isDialog) { + options.isDialog = false; + } + //create the query string for the tree request, these are the mandatory options: + var query = 'application=' + options.section + '&tree=' + options.tree + '&isDialog=' + options.isDialog; + //if you need to load a not initialized tree set this value to false - default is true + if (options.onlyinitialized) { + query += '&onlyInitialized=' + options.onlyinitialized; + } + //the options can contain extra query string parameters + if (options.queryString) { + query += '&' + options.queryString; + } + return umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('treeApplicationApiBaseUrl', 'GetApplicationTrees', query)), 'Failed to retrieve data for application tree ' + options.section); + }, + /** Loads in the data to display the child nodes for a given node */ + loadNodes: function (options) { + if (!options || !options.node) { + throw 'The options parameter object does not contain the required properties: \'node\''; + } + return umbRequestHelper.resourcePromise($http.get(getTreeNodesUrl(options.node)), 'Failed to retrieve data for child nodes ' + options.node.nodeId); + } + }; } - - //the factory object returned - return { - - /** Loads in the data to display the nodes menu */ - loadMenu: function (node) { - var treeMenuUrl = getTreeMenuUrl(node); - if (treeMenuUrl !== undefined && treeMenuUrl !== null && treeMenuUrl.length > 0) { - return umbRequestHelper.resourcePromise( - $http.get(getTreeMenuUrl(node)), - "Failed to retrieve data for a node's menu " + node.id); - } else { - return $q.reject({ - errorMsg: "No tree menu url defined for node " + node.id + angular.module('umbraco.resources').factory('treeResource', treeResource); + /** + * @ngdoc service + * @name umbraco.resources.usersResource + * @function + * + * @description + * Used by the users section to get users and send requests to create, invite, delete, etc. users. + */ + (function () { + 'use strict'; + function userGroupsResource($http, umbRequestHelper, $q, umbDataFormatter) { + function getUserGroupScaffold() { + return umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('userGroupsApiBaseUrl', 'GetEmptyUserGroup')), 'Failed to get the user group scaffold'); + } + function saveUserGroup(userGroup, isNew) { + if (!userGroup) { + throw 'userGroup not specified'; + } + //need to convert the user data into the correctly formatted save data - it is *not* the same and we don't want to over-post + var formattedSaveData = umbDataFormatter.formatUserGroupPostData(userGroup, 'save' + (isNew ? 'New' : '')); + return umbRequestHelper.resourcePromise($http.post(umbRequestHelper.getApiUrl('userGroupsApiBaseUrl', 'PostSaveUserGroup'), formattedSaveData), 'Failed to save user group'); + } + function getUserGroup(id) { + return umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('userGroupsApiBaseUrl', 'GetUserGroup', { id: id })), 'Failed to retrieve data for user group ' + id); + } + function getUserGroups(args) { + if (!args) { + args = { onlyCurrentUserGroups: true }; + } + if (args.onlyCurrentUserGroups === undefined || args.onlyCurrentUserGroups === null) { + args.onlyCurrentUserGroups = true; + } + return umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('userGroupsApiBaseUrl', 'GetUserGroups', args)), 'Failed to retrieve user groups'); + } + function deleteUserGroups(userGroupIds) { + var query = 'userGroupIds=' + userGroupIds.join('&userGroupIds='); + return umbRequestHelper.resourcePromise($http.post(umbRequestHelper.getApiUrl('userGroupsApiBaseUrl', 'PostDeleteUserGroups', query)), 'Failed to delete user groups'); + } + var resource = { + saveUserGroup: saveUserGroup, + getUserGroup: getUserGroup, + getUserGroups: getUserGroups, + getUserGroupScaffold: getUserGroupScaffold, + deleteUserGroups: deleteUserGroups + }; + return resource; + } + angular.module('umbraco.resources').factory('userGroupsResource', userGroupsResource); + }()); + /** + * @ngdoc service + * @name umbraco.resources.usersResource + * @function + * + * @description + * Used by the users section to get users and send requests to create, invite, disable, etc. users. + */ + (function () { + 'use strict'; + function usersResource($http, umbRequestHelper, $q, umbDataFormatter) { + /** + * @ngdoc method + * @name umbraco.resources.usersResource#clearAvatar + * @methodOf umbraco.resources.usersResource + * + * @description + * Deletes the user avatar + * + * ##usage + *
    +          * usersResource.clearAvatar(1)
    +          *    .then(function() {
    +          *        alert("avatar is gone");
    +          *    });
    +          * 
    + * + * @param {Array} id id of user. + * @returns {Promise} resourcePromise object. + * + */ + function clearAvatar(userId) { + return umbRequestHelper.resourcePromise($http.post(umbRequestHelper.getApiUrl('userApiBaseUrl', 'PostClearAvatar', { id: userId })), 'Failed to clear the user avatar ' + userId); + } + /** + * @ngdoc method + * @name umbraco.resources.usersResource#disableUsers + * @methodOf umbraco.resources.usersResource + * + * @description + * Disables a collection of users + * + * ##usage + *
    +          * usersResource.disableUsers([1, 2, 3, 4, 5])
    +          *    .then(function() {
    +          *        alert("users were disabled");
    +          *    });
    +          * 
    + * + * @param {Array} ids ids of users to disable. + * @returns {Promise} resourcePromise object. + * + */ + function disableUsers(userIds) { + if (!userIds) { + throw 'userIds not specified'; + } + //we need to create a custom query string for the usergroup array, so create it now and we can append the user groups if needed + var qry = 'userIds=' + userIds.join('&userIds='); + return umbRequestHelper.resourcePromise($http.post(umbRequestHelper.getApiUrl('userApiBaseUrl', 'PostDisableUsers', qry)), 'Failed to disable the users ' + userIds.join(',')); + } + /** + * @ngdoc method + * @name umbraco.resources.usersResource#enableUsers + * @methodOf umbraco.resources.usersResource + * + * @description + * Enables a collection of users + * + * ##usage + *
    +          * usersResource.enableUsers([1, 2, 3, 4, 5])
    +          *    .then(function() {
    +          *        alert("users were enabled");
    +          *    });
    +          * 
    + * + * @param {Array} ids ids of users to enable. + * @returns {Promise} resourcePromise object. + * + */ + function enableUsers(userIds) { + if (!userIds) { + throw 'userIds not specified'; + } + //we need to create a custom query string for the usergroup array, so create it now and we can append the user groups if needed + var qry = 'userIds=' + userIds.join('&userIds='); + return umbRequestHelper.resourcePromise($http.post(umbRequestHelper.getApiUrl('userApiBaseUrl', 'PostEnableUsers', qry)), 'Failed to enable the users ' + userIds.join(',')); + } + /** + * @ngdoc method + * @name umbraco.resources.usersResource#unlockUsers + * @methodOf umbraco.resources.usersResource + * + * @description + * Unlocks a collection of users + * + * ##usage + *
    +          * usersResource.unlockUsers([1, 2, 3, 4, 5])
    +          *    .then(function() {
    +          *        alert("users were unlocked");
    +          *    });
    +          * 
    + * + * @param {Array} ids ids of users to unlock. + * @returns {Promise} resourcePromise object. + * + */ + function unlockUsers(userIds) { + if (!userIds) { + throw 'userIds not specified'; + } + //we need to create a custom query string for the usergroup array, so create it now and we can append the user groups if needed + var qry = 'userIds=' + userIds.join('&userIds='); + return umbRequestHelper.resourcePromise($http.post(umbRequestHelper.getApiUrl('userApiBaseUrl', 'PostUnlockUsers', qry)), 'Failed to enable the users ' + userIds.join(',')); + } + /** + * @ngdoc method + * @name umbraco.resources.usersResource#setUserGroupsOnUsers + * @methodOf umbraco.resources.usersResource + * + * @description + * Overwrites the existing user groups on a collection of users + * + * ##usage + *
    +          * usersResource.setUserGroupsOnUsers(['admin', 'editor'], [1, 2, 3, 4, 5])
    +          *    .then(function() {
    +          *        alert("users were updated");
    +          *    });
    +          * 
    + * + * @param {Array} userGroupAliases aliases of user groups. + * @param {Array} ids ids of users to update. + * @returns {Promise} resourcePromise object. + * + */ + function setUserGroupsOnUsers(userGroups, userIds) { + var userGroupAliases = userGroups.map(function (o) { + return o.alias; }); + var query = 'userGroupAliases=' + userGroupAliases.join('&userGroupAliases=') + '&userIds=' + userIds.join('&userIds='); + return umbRequestHelper.resourcePromise($http.post(umbRequestHelper.getApiUrl('userApiBaseUrl', 'PostSetUserGroupsOnUsers', query)), 'Failed to set user groups ' + userGroupAliases.join(',') + ' on the users ' + userIds.join(',')); } - }, - - /** Loads in the data to display the nodes for an application */ - loadApplication: function (options) { - - if (!options || !options.section) { - throw "The object specified for does not contain a 'section' property"; + /** + * @ngdoc method + * @name umbraco.resources.usersResource#getPagedResults + * @methodOf umbraco.resources.usersResource + * + * @description + * Get users + * + * ##usage + *
    +          * usersResource.getPagedResults({pageSize: 10, pageNumber: 2})
    +          *    .then(function(data) {
    +          *        var users = data.items;
    +          *        alert('they are here!');
    +          *    });
    +          * 
    + * + * @param {Object} options optional options object + * @param {Int} options.pageSize if paging data, number of users per page, default = 25 + * @param {Int} options.pageNumber if paging data, current page index, default = 1 + * @param {String} options.filter if provided, query will only return those with names matching the filter + * @param {String} options.orderDirection can be `Ascending` or `Descending` - Default: `Ascending` + * @param {String} options.orderBy property to order users by, default: `Username` + * @param {Array} options.userGroups property to filter users by user group + * @param {Array} options.userStates property to filter users by user state + * @returns {Promise} resourcePromise object containing an array of content items. + * + */ + function getPagedResults(options) { + var defaults = { + pageSize: 25, + pageNumber: 1, + filter: '', + orderDirection: 'Ascending', + orderBy: 'Username', + userGroups: [], + userStates: [] + }; + if (options === undefined) { + options = {}; + } + //overwrite the defaults if there are any specified + angular.extend(defaults, options); + //now copy back to the options we will use + options = defaults; + //change asc/desct + if (options.orderDirection === 'asc') { + options.orderDirection = 'Ascending'; + } else if (options.orderDirection === 'desc') { + options.orderDirection = 'Descending'; + } + var params = { + pageNumber: options.pageNumber, + pageSize: options.pageSize, + orderBy: options.orderBy, + orderDirection: options.orderDirection, + filter: options.filter + }; + //we need to create a custom query string for the usergroup array, so create it now and we can append the user groups if needed + var qry = umbRequestHelper.dictionaryToQueryString(params); + if (options.userGroups.length > 0) { + //we need to create a custom query string for an array + qry += '&userGroups=' + options.userGroups.join('&userGroups='); + } + if (options.userStates.length > 0) { + //we need to create a custom query string for an array + qry += '&userStates=' + options.userStates.join('&userStates='); + } + return umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('userApiBaseUrl', 'GetPagedUsers', qry)), 'Failed to retrieve users paged result'); } - - if(!options.tree){ - options.tree = ""; + /** + * @ngdoc method + * @name umbraco.resources.usersResource#getUser + * @methodOf umbraco.resources.usersResource + * + * @description + * Gets a user + * + * ##usage + *
    +          * usersResource.getUser(1)
    +          *    .then(function(user) {
    +          *        alert("It's here");
    +          *    });
    +          * 
    + * + * @param {Int} userId user id. + * @returns {Promise} resourcePromise object containing the user. + * + */ + function getUser(userId) { + return umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('userApiBaseUrl', 'GetById', { id: userId })), 'Failed to retrieve data for user ' + userId); } - if (!options.isDialog) { - options.isDialog = false; + /** + * @ngdoc method + * @name umbraco.resources.usersResource#createUser + * @methodOf umbraco.resources.usersResource + * + * @description + * Creates a new user + * + * ##usage + *
    +          * usersResource.createUser(user)
    +          *    .then(function(newUser) {
    +          *        alert("It's here");
    +          *    });
    +          * 
    + * + * @param {Object} user user to create + * @returns {Promise} resourcePromise object containing the new user. + * + */ + function createUser(user) { + if (!user) { + throw 'user not specified'; + } + //need to convert the user data into the correctly formatted save data - it is *not* the same and we don't want to over-post + var formattedSaveData = umbDataFormatter.formatUserPostData(user); + return umbRequestHelper.resourcePromise($http.post(umbRequestHelper.getApiUrl('userApiBaseUrl', 'PostCreateUser'), formattedSaveData), 'Failed to save user'); } - - //create the query string for the tree request, these are the mandatory options: - var query = "application=" + options.section + "&tree=" + options.tree + "&isDialog=" + options.isDialog; - - //the options can contain extra query string parameters - if (options.queryString) { - query += "&" + options.queryString; + /** + * @ngdoc method + * @name umbraco.resources.usersResource#inviteUser + * @methodOf umbraco.resources.usersResource + * + * @description + * Creates and sends an email invitation to a new user + * + * ##usage + *
    +          * usersResource.inviteUser(user)
    +          *    .then(function(newUser) {
    +          *        alert("It's here");
    +          *    });
    +          * 
    + * + * @param {Object} user user to invite + * @returns {Promise} resourcePromise object containing the new user. + * + */ + function inviteUser(user) { + if (!user) { + throw 'user not specified'; + } + //need to convert the user data into the correctly formatted save data - it is *not* the same and we don't want to over-post + var formattedSaveData = umbDataFormatter.formatUserPostData(user); + return umbRequestHelper.resourcePromise($http.post(umbRequestHelper.getApiUrl('userApiBaseUrl', 'PostInviteUser'), formattedSaveData), 'Failed to invite user'); } - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "treeApplicationApiBaseUrl", - "GetApplicationTrees", - query)), - 'Failed to retrieve data for application tree ' + options.section); - }, - - /** Loads in the data to display the child nodes for a given node */ - loadNodes: function (options) { - - if (!options || !options.node) { - throw "The options parameter object does not contain the required properties: 'node'"; + /** + * @ngdoc method + * @name umbraco.resources.usersResource#saveUser + * @methodOf umbraco.resources.usersResource + * + * @description + * Saves a user + * + * ##usage + *
    +          * usersResource.saveUser(user)
    +          *    .then(function(updatedUser) {
    +          *        alert("It's here");
    +          *    });
    +          * 
    + * + * @param {Object} user object to save + * @returns {Promise} resourcePromise object containing the updated user. + * + */ + function saveUser(user) { + if (!user) { + throw 'user not specified'; + } + //need to convert the user data into the correctly formatted save data - it is *not* the same and we don't want to over-post + var formattedSaveData = umbDataFormatter.formatUserPostData(user); + return umbRequestHelper.resourcePromise($http.post(umbRequestHelper.getApiUrl('userApiBaseUrl', 'PostSaveUser'), formattedSaveData), 'Failed to save user'); } - - return umbRequestHelper.resourcePromise( - $http.get(getTreeNodesUrl(options.node)), - 'Failed to retrieve data for child nodes ' + options.node.nodeId); + /** + * @ngdoc method + * @name umbraco.resources.usersResource#deleteNonLoggedInUser + * @methodOf umbraco.resources.usersResource + * + * @description + * Deletes a user that hasn't already logged in (and hence we know has made no content updates that would create related records) + * + * ##usage + *
    +          * usersResource.deleteNonLoggedInUser(1)
    +          *    .then(function() {
    +          *        alert("user was deleted");
    +          *    });
    +          * 
    + * + * @param {Int} userId user id. + * @returns {Promise} resourcePromise object. + * + */ + function deleteNonLoggedInUser(userId) { + return umbRequestHelper.resourcePromise($http.post(umbRequestHelper.getApiUrl('userApiBaseUrl', 'PostDeleteNonLoggedInUser', { id: userId })), 'Failed to delete the user ' + userId); + } + var resource = { + disableUsers: disableUsers, + enableUsers: enableUsers, + unlockUsers: unlockUsers, + setUserGroupsOnUsers: setUserGroupsOnUsers, + getPagedResults: getPagedResults, + getUser: getUser, + createUser: createUser, + inviteUser: inviteUser, + saveUser: saveUser, + deleteNonLoggedInUser: deleteNonLoggedInUser, + clearAvatar: clearAvatar + }; + return resource; } - }; -} - -angular.module('umbraco.resources').factory('treeResource', treeResource); - -/** - * @ngdoc service - * @name umbraco.resources.userResource - **/ -function userResource($q, $http, umbDataFormatter, umbRequestHelper) { - - return { - - disableUser: function (userId) { - - if (!userId) { - throw "userId not specified"; - } - - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "userApiBaseUrl", - "PostDisableUser", [{ userId: userId }])), - 'Failed to disable the user ' + userId); - } - }; -} - -angular.module('umbraco.resources').factory('userResource', userResource); - - -})(); \ No newline at end of file + angular.module('umbraco.resources').factory('usersResource', usersResource); + }()); +}()); \ No newline at end of file diff --git a/WebCms/Umbraco/Js/umbraco.security.js b/WebCms/Umbraco/Js/umbraco.security.js index 1d7f8cf..659d5cb 100644 --- a/WebCms/Umbraco/Js/umbraco.security.js +++ b/WebCms/Umbraco/Js/umbraco.security.js @@ -1,206 +1,197 @@ -/*! umbraco - * https://github.com/umbraco/umbraco-cms/ - * Copyright (c) 2016 Umbraco HQ; - * Licensed - */ - -(function() { - -//TODO: This is silly and unecessary to have a separate module for this -angular.module('umbraco.security.retryQueue', []); -angular.module('umbraco.security.interceptor', ['umbraco.security.retryQueue']); -angular.module('umbraco.security', ['umbraco.security.retryQueue', 'umbraco.security.interceptor']); -//TODO: This is silly and unecessary to have a separate module for this -angular.module('umbraco.security.retryQueue', []) - -// This is a generic retry queue for security failures. Each item is expected to expose two functions: retry and cancel. -.factory('securityRetryQueue', ['$q', '$log', function ($q, $log) { - - var retryQueue = []; - var retryUser = null; - - var service = { - // The security service puts its own handler in here! - onItemAddedCallbacks: [], - - hasMore: function() { - return retryQueue.length > 0; - }, - push: function(retryItem) { - retryQueue.push(retryItem); - // Call all the onItemAdded callbacks - angular.forEach(service.onItemAddedCallbacks, function(cb) { - try { - cb(retryItem); - } catch(e) { - $log.error('securityRetryQueue.push(retryItem): callback threw an error' + e); +(function () { + angular.module('umbraco.security.retryQueue', []); + angular.module('umbraco.security.interceptor', ['umbraco.security.retryQueue']); + angular.module('umbraco.security', [ + 'umbraco.security.retryQueue', + 'umbraco.security.interceptor' + ]); + //TODO: This is silly and unecessary to have a separate module for this + angular.module('umbraco.security.retryQueue', []) // This is a generic retry queue for security failures. Each item is expected to expose two functions: retry and cancel. +.factory('securityRetryQueue', [ + '$q', + '$log', + function ($q, $log) { + var retryQueue = []; + var retryUser = null; + var service = { + // The security service puts its own handler in here! + onItemAddedCallbacks: [], + hasMore: function () { + return retryQueue.length > 0; + }, + push: function (retryItem) { + retryQueue.push(retryItem); + // Call all the onItemAdded callbacks + angular.forEach(service.onItemAddedCallbacks, function (cb) { + try { + cb(retryItem); + } catch (e) { + $log.error('securityRetryQueue.push(retryItem): callback threw an error' + e); + } + }); + }, + pushRetryFn: function (reason, userName, retryFn) { + // The reason parameter is optional + if (arguments.length === 2) { + retryFn = userName; + userName = reason; + reason = undefined; + } + if (retryUser && retryUser !== userName || userName === null) { + throw new Error('invalid user'); + } + retryUser = userName; + // The deferred object that will be resolved or rejected by calling retry or cancel + var deferred = $q.defer(); + var retryItem = { + reason: reason, + retry: function () { + // Wrap the result of the retryFn into a promise if it is not already + $q.when(retryFn()).then(function (value) { + // If it was successful then resolve our deferred + deferred.resolve(value); + }, function (value) { + // Othewise reject it + deferred.reject(value); + }); + }, + cancel: function () { + // Give up on retrying and reject our deferred + deferred.reject(); + } + }; + service.push(retryItem); + return deferred.promise; + }, + retryReason: function () { + return service.hasMore() && retryQueue[0].reason; + }, + cancelAll: function () { + while (service.hasMore()) { + retryQueue.shift().cancel(); + } + retryUser = null; + }, + retryAll: function (userName) { + if (retryUser == null) { + return; + } + if (retryUser !== userName) { + service.cancelAll(); + return; + } + while (service.hasMore()) { + retryQueue.shift().retry(); + } + } + }; + return service; } - }); - }, - pushRetryFn: function(reason, userName, retryFn) { - // The reason parameter is optional - if ( arguments.length === 2) { - retryFn = userName; - userName = reason; - reason = undefined; - } - - if ((retryUser && retryUser !== userName) || userName === null) { - throw new Error('invalid user'); - } - - retryUser = userName; - - // The deferred object that will be resolved or rejected by calling retry or cancel - var deferred = $q.defer(); - var retryItem = { - reason: reason, - retry: function() { - // Wrap the result of the retryFn into a promise if it is not already - $q.when(retryFn()).then(function(value) { - // If it was successful then resolve our deferred - deferred.resolve(value); - }, function(value) { - // Othewise reject it - deferred.reject(value); - }); - }, - cancel: function() { - // Give up on retrying and reject our deferred - deferred.reject(); - } - }; - service.push(retryItem); - return deferred.promise; - }, - retryReason: function() { - return service.hasMore() && retryQueue[0].reason; - }, - cancelAll: function() { - while(service.hasMore()) { - retryQueue.shift().cancel(); - } - retryUser = null; - }, - retryAll: function (userName) { - - if (retryUser == null) { - return; - } - - if (retryUser !== userName) { - service.cancelAll(); - return; - } - - while(service.hasMore()) { - retryQueue.shift().retry(); - } - } - }; - return service; -}]); -angular.module('umbraco.security.interceptor') - // This http interceptor listens for authentication successes and failures - .factory('securityInterceptor', ['$injector', 'securityRetryQueue', 'notificationsService', 'requestInterceptorFilter', function ($injector, queue, notifications, requestInterceptorFilter) { - return function(promise) { - - return promise.then( - function(originalResponse) { + ]); + angular.module('umbraco.security.interceptor') // This http interceptor listens for authentication successes and failures +.factory('securityInterceptor', [ + '$injector', + 'securityRetryQueue', + 'notificationsService', + 'eventsService', + 'requestInterceptorFilter', + function ($injector, queue, notifications, eventsService, requestInterceptorFilter) { + return function (promise) { + return promise.then(function (originalResponse) { // Intercept successful requests - //Here we'll check if our custom header is in the response which indicates how many seconds the user's session has before it //expires. Then we'll update the user in the user service accordingly. var headers = originalResponse.headers(); - if (headers["x-umb-user-seconds"]) { + if (headers['x-umb-user-seconds']) { // We must use $injector to get the $http service to prevent circular dependency var userService = $injector.get('userService'); - userService.setUserTimeout(headers["x-umb-user-seconds"]); + userService.setUserTimeout(headers['x-umb-user-seconds']); + } + //this checks if the user's values have changed, in which case we need to update the user details throughout + //the back office similar to how we do when a user logs in + if (headers['x-umb-user-modified']) { + eventsService.emit('app.userRefresh'); } - return promise; - }, function(originalResponse) { + }, function (originalResponse) { // Intercept failed requests - - //Here we'll check if we should ignore the error, this will be based on an original header set - var headers = originalResponse.config ? originalResponse.config.headers : {}; - if (headers["x-umb-ignore-error"] === "ignore") { + // Make sure we have the configuration of the request (don't we always?) + var config = originalResponse.config ? originalResponse.config : {}; + // Make sure we have an object for the headers of the request + var headers = config.headers ? config.headers : {}; + //Here we'll check if we should ignore the error (either based on the original header set or the request configuration) + if (headers['x-umb-ignore-error'] === 'ignore' || config.umbIgnoreErrors === true || angular.isArray(config.umbIgnoreStatus) && config.umbIgnoreStatus.indexOf(originalResponse.status) !== -1) { //exit/ignore return promise; } - var filtered = _.find(requestInterceptorFilter(), function(val) { - return originalResponse.config.url.indexOf(val) > 0; + var filtered = _.find(requestInterceptorFilter(), function (val) { + return config.url.indexOf(val) > 0; }); if (filtered) { return promise; } - //A 401 means that the user is not logged in - if (originalResponse.status === 401) { - - var userService = $injector.get('userService'); // see above - - //Associate the user name with the retry to ensure we retry for the right user - promise = userService.getCurrentUser() - .then(function (user) { - var userName = user ? user.name : null; - //The request bounced because it was not authorized - add a new request to the retry queue - return queue.pushRetryFn('unauthorized-server', userName, function retryRequest() { - // We must use $injector to get the $http service to prevent circular dependency - return $injector.get('$http')(originalResponse.config); - }); + if (originalResponse.status === 401 && !originalResponse.config.url.endsWith('umbraco/backoffice/UmbracoApi/Authentication/GetCurrentUser')) { + var userService = $injector.get('userService'); + // see above + //Associate the user name with the retry to ensure we retry for the right user + promise = userService.getCurrentUser().then(function (user) { + var userName = user ? user.name : null; + //The request bounced because it was not authorized - add a new request to the retry queue + return queue.pushRetryFn('unauthorized-server', userName, function retryRequest() { + // We must use $injector to get the $http service to prevent circular dependency + return $injector.get('$http')(originalResponse.config); + }); }); - } - else if (originalResponse.status === 404) { - + } else if (originalResponse.status === 404) { //a 404 indicates that the request was not found - this could be due to a non existing url, or it could //be due to accessing a url with a parameter that doesn't exist, either way we should notifiy the user about it - - var errMsg = "The URL returned a 404 (not found):
    " + originalResponse.config.url.split('?')[0] + ""; + var errMsg = 'The URL returned a 404 (not found):
    ' + originalResponse.config.url.split('?')[0] + ''; if (originalResponse.data && originalResponse.data.ExceptionMessage) { - errMsg += "
    with error:
    " + originalResponse.data.ExceptionMessage + ""; + errMsg += '
    with error:
    ' + originalResponse.data.ExceptionMessage + ''; } if (originalResponse.config.data) { - errMsg += "
    with data:
    " + angular.toJson(originalResponse.config.data) + "
    Contact your administrator for information."; + errMsg += '
    with data:
    ' + angular.toJson(originalResponse.config.data) + '
    Contact your administrator for information.'; } - - notifications.error( - "Request error", - errMsg); - - } - else if (originalResponse.status === 403) { + notifications.error('Request error', errMsg); + } else if (originalResponse.status === 403) { //if the status was a 403 it means the user didn't have permission to do what the request was trying to do. //How do we deal with this now, need to tell the user somehow that they don't have permission to do the thing that was //requested. We can either deal with this globally here, or we can deal with it globally for individual requests on the umbRequestHelper, // or completely custom for services calling resources. - //http://issues.umbraco.org/issue/U4-2749 - //It was decided to just put these messages into the normal status messages. - - var msg = "Unauthorized access to URL:
    " + originalResponse.config.url.split('?')[0] + ""; + var msg = 'Unauthorized access to URL:
    ' + originalResponse.config.url.split('?')[0] + ''; if (originalResponse.config.data) { - msg += "
    with data:
    " + angular.toJson(originalResponse.config.data) + "
    Contact your administrator for information."; + msg += '
    with data:
    ' + angular.toJson(originalResponse.config.data) + '
    Contact your administrator for information.'; } - - notifications.error( - "Authorization error", - msg); + notifications.error('Authorization error', msg); } - return promise; }); + }; + } + ]) //used to set headers on all requests where necessary +.factory('umbracoRequestInterceptor', function ($q, queryStrings) { + return { + //dealing with requests: + 'request': function (config) { + if (queryStrings.getParams().umbDebug === 'true' || queryStrings.getParams().umbdebug === 'true') { + config.headers['X-UMB-DEBUG'] = 'true'; + } + return config; + } }; - }]) - - .value('requestInterceptorFilter', function() { - return ["www.gravatar.com"]; - }) - - // We have to add the interceptor to the queue as a string because the interceptor depends upon service instances that are not available in the config block. - .config(['$httpProvider', function ($httpProvider) { - $httpProvider.responseInterceptors.push('securityInterceptor'); - }]); - -})(); \ No newline at end of file + }).value('requestInterceptorFilter', function () { + return ['www.gravatar.com']; + }) // We have to add the interceptor to the queue as a string because the interceptor depends upon service instances that are not available in the config block. +.config([ + '$httpProvider', + function ($httpProvider) { + $httpProvider.defaults.xsrfHeaderName = 'X-UMB-XSRF-TOKEN'; + $httpProvider.defaults.xsrfCookieName = 'UMB-XSRF-TOKEN'; + $httpProvider.responseInterceptors.push('securityInterceptor'); + $httpProvider.interceptors.push('umbracoRequestInterceptor'); + } + ]); +}()); \ No newline at end of file diff --git a/WebCms/Umbraco/Js/umbraco.services.js b/WebCms/Umbraco/Js/umbraco.services.js index f5410fa..43cccf5 100644 --- a/WebCms/Umbraco/Js/umbraco.services.js +++ b/WebCms/Umbraco/Js/umbraco.services.js @@ -1,170 +1,154 @@ -/*! umbraco - * https://github.com/umbraco/umbraco-cms/ - * Copyright (c) 2016 Umbraco HQ; - * Licensed - */ - -(function() { - -angular.module("umbraco.services", ["umbraco.security", "umbraco.resources"]); - -/** - * @ngdoc service - * @name umbraco.services.angularHelper - * @function - * - * @description - * Some angular helper/extension methods - */ -function angularHelper($log, $q) { - return { - - /** - * @ngdoc function - * @name umbraco.services.angularHelper#rejectedPromise - * @methodOf umbraco.services.angularHelper - * @function - * - * @description - * In some situations we need to return a promise as a rejection, normally based on invalid data. This - * is a wrapper to do that so we can save on writing a bit of code. - * - * @param {object} objReject The object to send back with the promise rejection - */ - rejectedPromise: function (objReject) { - var deferred = $q.defer(); - //return an error object including the error message for UI - deferred.reject(objReject); - return deferred.promise; - }, - - /** - * @ngdoc function - * @name safeApply - * @methodOf umbraco.services.angularHelper - * @function - * - * @description - * This checks if a digest/apply is already occuring, if not it will force an apply call - */ - safeApply: function (scope, fn) { - if (scope.$$phase || scope.$root.$$phase) { - if (angular.isFunction(fn)) { - fn(); - } - } - else { - if (angular.isFunction(fn)) { - scope.$apply(fn); - } - else { - scope.$apply(); - } - } - }, - - /** - * @ngdoc function - * @name getCurrentForm - * @methodOf umbraco.services.angularHelper - * @function - * - * @description - * Returns the current form object applied to the scope or null if one is not found - */ - getCurrentForm: function (scope) { - - //NOTE: There isn't a way in angular to get a reference to the current form object since the form object - // is just defined as a property of the scope when it is named but you'll always need to know the name which - // isn't very convenient. If we want to watch for validation changes we need to get a form reference. - // The way that we detect the form object is a bit hackerific in that we detect all of the required properties - // that exist on a form object. - // - //The other way to do it in a directive is to require "^form", but in a controller the only other way to do it - // is to inject the $element object and use: $element.inheritedData('$formController'); - - var form = null; - //var requiredFormProps = ["$error", "$name", "$dirty", "$pristine", "$valid", "$invalid", "$addControl", "$removeControl", "$setValidity", "$setDirty"]; - var requiredFormProps = ["$addControl", "$removeControl", "$setValidity", "$setDirty", "$setPristine"]; - - // a method to check that the collection of object prop names contains the property name expected - function propertyExists(objectPropNames) { - //ensure that every required property name exists on the current scope property - return _.every(requiredFormProps, function (item) { - - return _.contains(objectPropNames, item); - }); - } - - for (var p in scope) { - - if (_.isObject(scope[p]) && p !== "this" && p.substr(0, 1) !== "$") { - //get the keys of the property names for the current property - var props = _.keys(scope[p]); - //if the length isn't correct, try the next prop - if (props.length < requiredFormProps.length) { - continue; - } - - //ensure that every required property name exists on the current scope property - var containProperty = propertyExists(props); - - if (containProperty) { - form = scope[p]; - break; - } - } - } - - return form; - }, - - /** - * @ngdoc function - * @name validateHasForm - * @methodOf umbraco.services.angularHelper - * @function - * - * @description - * This will validate that the current scope has an assigned form object, if it doesn't an exception is thrown, if - * it does we return the form object. - */ - getRequiredCurrentForm: function (scope) { - var currentForm = this.getCurrentForm(scope); - if (!currentForm || !currentForm.$name) { - throw "The current scope requires a current form object (or ng-form) with a name assigned to it"; - } - return currentForm; - }, - - /** - * @ngdoc function - * @name getNullForm - * @methodOf umbraco.services.angularHelper - * @function - * - * @description - * Returns a null angular FormController, mostly for use in unit tests - * NOTE: This is actually the same construct as angular uses internally for creating a null form but they don't expose - * any of this publicly to us, so we need to create our own. - * - * @param {string} formName The form name to assign - */ - getNullForm: function (formName) { - return { - $addControl: angular.noop, - $removeControl: angular.noop, - $setValidity: angular.noop, - $setDirty: angular.noop, - $setPristine: angular.noop, - $name: formName - //NOTE: we don't include the 'properties', just the methods. - }; - } - }; -} -angular.module('umbraco.services').factory('angularHelper', angularHelper); -/** +(function () { + angular.module('umbraco.services', [ + 'umbraco.security', + 'umbraco.resources' + ]); + /** + * @ngdoc service + * @name umbraco.services.angularHelper + * @function + * + * @description + * Some angular helper/extension methods + */ + function angularHelper($log, $q) { + return { + /** + * @ngdoc function + * @name umbraco.services.angularHelper#rejectedPromise + * @methodOf umbraco.services.angularHelper + * @function + * + * @description + * In some situations we need to return a promise as a rejection, normally based on invalid data. This + * is a wrapper to do that so we can save on writing a bit of code. + * + * @param {object} objReject The object to send back with the promise rejection + */ + rejectedPromise: function (objReject) { + var deferred = $q.defer(); + //return an error object including the error message for UI + deferred.reject(objReject); + return deferred.promise; + }, + /** + * @ngdoc function + * @name safeApply + * @methodOf umbraco.services.angularHelper + * @function + * + * @description + * This checks if a digest/apply is already occuring, if not it will force an apply call + */ + safeApply: function (scope, fn) { + if (scope.$$phase || scope.$root.$$phase) { + if (angular.isFunction(fn)) { + fn(); + } + } else { + if (angular.isFunction(fn)) { + scope.$apply(fn); + } else { + scope.$apply(); + } + } + }, + /** + * @ngdoc function + * @name getCurrentForm + * @methodOf umbraco.services.angularHelper + * @function + * + * @description + * Returns the current form object applied to the scope or null if one is not found + */ + getCurrentForm: function (scope) { + //NOTE: There isn't a way in angular to get a reference to the current form object since the form object + // is just defined as a property of the scope when it is named but you'll always need to know the name which + // isn't very convenient. If we want to watch for validation changes we need to get a form reference. + // The way that we detect the form object is a bit hackerific in that we detect all of the required properties + // that exist on a form object. + // + //The other way to do it in a directive is to require "^form", but in a controller the only other way to do it + // is to inject the $element object and use: $element.inheritedData('$formController'); + var form = null; + //var requiredFormProps = ["$error", "$name", "$dirty", "$pristine", "$valid", "$invalid", "$addControl", "$removeControl", "$setValidity", "$setDirty"]; + var requiredFormProps = [ + '$addControl', + '$removeControl', + '$setValidity', + '$setDirty', + '$setPristine' + ]; + // a method to check that the collection of object prop names contains the property name expected + function propertyExists(objectPropNames) { + //ensure that every required property name exists on the current scope property + return _.every(requiredFormProps, function (item) { + return _.contains(objectPropNames, item); + }); + } + for (var p in scope) { + if (_.isObject(scope[p]) && p !== 'this' && p.substr(0, 1) !== '$') { + //get the keys of the property names for the current property + var props = _.keys(scope[p]); + //if the length isn't correct, try the next prop + if (props.length < requiredFormProps.length) { + continue; + } + //ensure that every required property name exists on the current scope property + var containProperty = propertyExists(props); + if (containProperty) { + form = scope[p]; + break; + } + } + } + return form; + }, + /** + * @ngdoc function + * @name validateHasForm + * @methodOf umbraco.services.angularHelper + * @function + * + * @description + * This will validate that the current scope has an assigned form object, if it doesn't an exception is thrown, if + * it does we return the form object. + */ + getRequiredCurrentForm: function (scope) { + var currentForm = this.getCurrentForm(scope); + if (!currentForm || !currentForm.$name) { + throw 'The current scope requires a current form object (or ng-form) with a name assigned to it'; + } + return currentForm; + }, + /** + * @ngdoc function + * @name getNullForm + * @methodOf umbraco.services.angularHelper + * @function + * + * @description + * Returns a null angular FormController, mostly for use in unit tests + * NOTE: This is actually the same construct as angular uses internally for creating a null form but they don't expose + * any of this publicly to us, so we need to create our own. + * + * @param {string} formName The form name to assign + */ + getNullForm: function (formName) { + return { + $addControl: angular.noop, + $removeControl: angular.noop, + $setValidity: angular.noop, + $setDirty: angular.noop, + $setPristine: angular.noop, + $name: formName //NOTE: we don't include the 'properties', just the methods. + }; + } + }; + } + angular.module('umbraco.services').factory('angularHelper', angularHelper); + /** * @ngdoc service * @name umbraco.services.appState * @function @@ -198,71 +182,73 @@ angular.module('umbraco.services').factory('angularHelper', angularHelper); * }); *
    */ -function appState(eventsService) { - - //Define all variables here - we are never returning this objects so they cannot be publicly mutable - // changed, we only expose methods to interact with the values. - - var globalState = { - showNavigation: null, - touchDevice: null, - showTray: null, - stickyNavigation: null, - navMode: null, - isReady: null, - isTablet: null - }; - - var sectionState = { - //The currently active section - currentSection: null, - showSearchResults: null - }; - - var treeState = { - //The currently selected node - selectedNode: null, - //The currently loaded root node reference - depending on the section loaded this could be a section root or a normal root. - //We keep this reference so we can lookup nodes to interact with in the UI via the tree service - currentRootNode: null - }; - - var menuState = { - //this list of menu items to display - menuActions: null, - //the title to display in the context menu dialog - dialogTitle: null, - //The tree node that the ctx menu is launched for - currentNode: null, - //Whether the menu's dialog is being shown or not - showMenuDialog: null, - //Whether the context menu is being shown or not - showMenu: null - }; - - /** function to validate and set the state on a state object */ - function setState(stateObj, key, value, stateObjName) { - if (!_.has(stateObj, key)) { - throw "The variable " + key + " does not exist in " + stateObjName; + function appState(eventsService) { + //Define all variables here - we are never returning this objects so they cannot be publicly mutable + // changed, we only expose methods to interact with the values. + var globalState = { + showNavigation: null, + touchDevice: null, + showTray: null, + stickyNavigation: null, + navMode: null, + isReady: null, + isTablet: null + }; + var sectionState = { + //The currently active section + currentSection: null, + showSearchResults: null + }; + var treeState = { + //The currently selected node + selectedNode: null, + //The currently loaded root node reference - depending on the section loaded this could be a section root or a normal root. + //We keep this reference so we can lookup nodes to interact with in the UI via the tree service + currentRootNode: null + }; + var menuState = { + //this list of menu items to display + menuActions: null, + //the title to display in the context menu dialog + dialogTitle: null, + //The tree node that the ctx menu is launched for + currentNode: null, + //Whether the menu's dialog is being shown or not + showMenuDialog: null, + //Whether the context menu is being shown or not + showMenu: null + }; + var drawerState = { + //this view to show + view: null, + // bind custom values to the drawer + model: null, + //Whether the drawer is being shown or not + showDrawer: null + }; + /** function to validate and set the state on a state object */ + function setState(stateObj, key, value, stateObjName) { + if (!_.has(stateObj, key)) { + throw 'The variable ' + key + ' does not exist in ' + stateObjName; + } + var changed = stateObj[key] !== value; + stateObj[key] = value; + if (changed) { + eventsService.emit('appState.' + stateObjName + '.changed', { + key: key, + value: value + }); + } } - var changed = stateObj[key] !== value; - stateObj[key] = value; - if (changed) { - eventsService.emit("appState." + stateObjName + ".changed", { key: key, value: value }); + /** function to validate and set the state on a state object */ + function getState(stateObj, key, stateObjName) { + if (!_.has(stateObj, key)) { + throw 'The variable ' + key + ' does not exist in ' + stateObjName; + } + return stateObj[key]; } - } - - /** function to validate and set the state on a state object */ - function getState(stateObj, key, stateObjName) { - if (!_.has(stateObj, key)) { - throw "The variable " + key + " does not exist in " + stateObjName; - } - return stateObj[key]; - } - - return { - - /** + return { + /** * @ngdoc function * @name umbraco.services.angularHelper#getGlobalState * @methodOf umbraco.services.appState @@ -273,11 +259,10 @@ function appState(eventsService) { * to be publicly mutable and allow setting arbitrary values * */ - getGlobalState: function (key) { - return getState(globalState, key, "globalState"); - }, - - /** + getGlobalState: function (key) { + return getState(globalState, key, 'globalState'); + }, + /** * @ngdoc function * @name umbraco.services.angularHelper#setGlobalState * @methodOf umbraco.services.appState @@ -287,11 +272,10 @@ function appState(eventsService) { * Sets a global state value by key * */ - setGlobalState: function (key, value) { - setState(globalState, key, value, "globalState"); - }, - - /** + setGlobalState: function (key, value) { + setState(globalState, key, value, 'globalState'); + }, + /** * @ngdoc function * @name umbraco.services.angularHelper#getSectionState * @methodOf umbraco.services.appState @@ -302,11 +286,10 @@ function appState(eventsService) { * to be publicly mutable and allow setting arbitrary values * */ - getSectionState: function (key) { - return getState(sectionState, key, "sectionState"); - }, - - /** + getSectionState: function (key) { + return getState(sectionState, key, 'sectionState'); + }, + /** * @ngdoc function * @name umbraco.services.angularHelper#setSectionState * @methodOf umbraco.services.appState @@ -316,11 +299,10 @@ function appState(eventsService) { * Sets a section state value by key * */ - setSectionState: function(key, value) { - setState(sectionState, key, value, "sectionState"); - }, - - /** + setSectionState: function (key, value) { + setState(sectionState, key, value, 'sectionState'); + }, + /** * @ngdoc function * @name umbraco.services.angularHelper#getTreeState * @methodOf umbraco.services.appState @@ -331,11 +313,10 @@ function appState(eventsService) { * to be publicly mutable and allow setting arbitrary values * */ - getTreeState: function (key) { - return getState(treeState, key, "treeState"); - }, - - /** + getTreeState: function (key) { + return getState(treeState, key, 'treeState'); + }, + /** * @ngdoc function * @name umbraco.services.angularHelper#setTreeState * @methodOf umbraco.services.appState @@ -345,11 +326,10 @@ function appState(eventsService) { * Sets a section state value by key * */ - setTreeState: function (key, value) { - setState(treeState, key, value, "treeState"); - }, - - /** + setTreeState: function (key, value) { + setState(treeState, key, value, 'treeState'); + }, + /** * @ngdoc function * @name umbraco.services.angularHelper#getMenuState * @methodOf umbraco.services.appState @@ -360,11 +340,10 @@ function appState(eventsService) { * to be publicly mutable and allow setting arbitrary values * */ - getMenuState: function (key) { - return getState(menuState, key, "menuState"); - }, - - /** + getMenuState: function (key) { + return getState(menuState, key, 'menuState'); + }, + /** * @ngdoc function * @name umbraco.services.angularHelper#setMenuState * @methodOf umbraco.services.appState @@ -374,15 +353,40 @@ function appState(eventsService) { * Sets a section state value by key * */ - setMenuState: function (key, value) { - setState(menuState, key, value, "menuState"); - }, - - }; -} -angular.module('umbraco.services').factory('appState', appState); - -/** + setMenuState: function (key, value) { + setState(menuState, key, value, 'menuState'); + }, + /** + * @ngdoc function + * @name umbraco.services.angularHelper#getDrawerState + * @methodOf umbraco.services.appState + * @function + * + * @description + * Returns the current drawer state value by key - we do not return an object here - we do NOT want this + * to be publicly mutable and allow setting arbitrary values + * + */ + getDrawerState: function (key) { + return getState(drawerState, key, 'drawerState'); + }, + /** + * @ngdoc function + * @name umbraco.services.angularHelper#setDrawerState + * @methodOf umbraco.services.appState + * @function + * + * @description + * Sets a drawer state value by key + * + */ + setDrawerState: function (key, value) { + setState(drawerState, key, value, 'drawerState'); + } + }; + } + angular.module('umbraco.services').factory('appState', appState); + /** * @ngdoc service * @name umbraco.services.editorState * @function @@ -393,12 +397,10 @@ angular.module('umbraco.services').factory('appState', appState); * * it is possible to modify this object, so should be used with care */ -angular.module('umbraco.services').factory("editorState", function() { - - var current = null; - var state = { - - /** + angular.module('umbraco.services').factory('editorState', function () { + var current = null; + var state = { + /** * @ngdoc function * @name umbraco.services.angularHelper#set * @methodOf umbraco.services.editorState @@ -410,11 +412,10 @@ angular.module('umbraco.services').factory("editorState", function() { * like the content editor, where the model is modified by several * child controllers. */ - set: function (entity) { - current = entity; - }, - - /** + set: function (entity) { + current = entity; + }, + /** * @ngdoc function * @name umbraco.services.angularHelper#reset * @methodOf umbraco.services.editorState @@ -424,11 +425,10 @@ angular.module('umbraco.services').factory("editorState", function() { * Since the editorstate entity is read-only, you cannot set it to null * only through the reset() method */ - reset: function() { - current = null; - }, - - /** + reset: function () { + current = null; + }, + /** * @ngdoc function * @name umbraco.services.angularHelper#getCurrent * @methodOf umbraco.services.editorState @@ -443,1184 +443,1174 @@ angular.module('umbraco.services').factory("editorState", function() { * editorState.current can not be overwritten, you should only read values from it * since modifying individual properties should be handled by the property editors */ - getCurrent: function() { - return current; - } - }; - - //TODO: This shouldn't be removed! use getCurrent() method instead of a hacked readonly property which is confusing. - - //create a get/set property but don't allow setting - Object.defineProperty(state, "current", { - get: function () { - return current; - }, - set: function (value) { - throw "Use editorState.set to set the value of the current entity"; - }, + getCurrent: function () { + return current; + } + }; + //TODO: This shouldn't be removed! use getCurrent() method instead of a hacked readonly property which is confusing. + //create a get/set property but don't allow setting + Object.defineProperty(state, 'current', { + get: function () { + return current; + }, + set: function (value) { + throw 'Use editorState.set to set the value of the current entity'; + } + }); + return state; }); - - return state; -}); -/** - * @ngdoc service - * @name umbraco.services.assetsService - * - * @requires $q - * @requires angularHelper - * - * @description - * Promise-based utillity service to lazy-load client-side dependencies inside angular controllers. - * - * ##usage - * To use, simply inject the assetsService into any controller that needs it, and make - * sure the umbraco.services module is accesible - which it should be by default. - * - *
    - *      angular.module("umbraco").controller("my.controller". function(assetsService){
    - *          assetsService.load(["script.js", "styles.css"], $scope).then(function(){
    - *                 //this code executes when the dependencies are done loading
    - *          });
    - *      });
    - * 
    - * - * You can also load individual files, which gives you greater control over what attibutes are passed to the file, as well as timeout - * - *
    - *      angular.module("umbraco").controller("my.controller". function(assetsService){
    - *          assetsService.loadJs("script.js", $scope, {charset: 'utf-8'}, 10000 }).then(function(){
    - *                 //this code executes when the script is done loading
    - *          });
    - *      });
    - * 
    - * - * For these cases, there are 2 individual methods, one for javascript, and one for stylesheets: - * - *
    - *      angular.module("umbraco").controller("my.controller". function(assetsService){
    - *          assetsService.loadCss("stye.css", $scope, {media: 'print'}, 10000 }).then(function(){
    - *                 //loadcss cannot determine when the css is done loading, so this will trigger instantly
    - *          });
    - *      });
    - * 
    - */ -angular.module('umbraco.services') -.factory('assetsService', function ($q, $log, angularHelper, umbRequestHelper, $rootScope, $http) { - - var initAssetsLoaded = false; - var appendRnd = function (url) { - //if we don't have a global umbraco obj yet, the app is bootstrapping - if (!Umbraco.Sys.ServerVariables.application) { - return url; - } - - var rnd = Umbraco.Sys.ServerVariables.application.version + "." + Umbraco.Sys.ServerVariables.application.cdf; - var _op = (url.indexOf("?") > 0) ? "&" : "?"; - url = url + _op + "umb__rnd=" + rnd; - return url; - }; - - function convertVirtualPath(path) { - //make this work for virtual paths - if (path.startsWith("~/")) { - path = umbRequestHelper.convertVirtualToAbsolutePath(path); - } - return path; - } - - var service = { - loadedAssets: {}, - - _getAssetPromise: function (path) { - - if (this.loadedAssets[path]) { - return this.loadedAssets[path]; - } else { - var deferred = $q.defer(); - this.loadedAssets[path] = { deferred: deferred, state: "new", path: path }; - return this.loadedAssets[path]; - } - }, - /** - Internal method. This is called when the application is loading and the user is already authenticated, or once the user is authenticated. - There's a few assets the need to be loaded for the application to function but these assets require authentication to load. - */ - _loadInitAssets: function () { - var deferred = $q.defer(); - //here we need to ensure the required application assets are loaded - if (initAssetsLoaded === false) { - var self = this; - self.loadJs(umbRequestHelper.getApiUrl("serverVarsJs", "", ""), $rootScope).then(function () { - initAssetsLoaded = true; - - //now we need to go get the legacyTreeJs - but this can be done async without waiting. - self.loadJs(umbRequestHelper.getApiUrl("legacyTreeJs", "", ""), $rootScope); - - deferred.resolve(); - }); - } - else { - deferred.resolve(); - } - return deferred.promise; - }, - - /** - * @ngdoc method - * @name umbraco.services.assetsService#loadCss - * @methodOf umbraco.services.assetsService - * - * @description - * Injects a file as a stylesheet into the document head - * - * @param {String} path path to the css file to load - * @param {Scope} scope optional scope to pass into the loader - * @param {Object} keyvalue collection of attributes to pass to the stylesheet element - * @param {Number} timeout in milliseconds - * @returns {Promise} Promise object which resolves when the file has loaded - */ - loadCss: function (path, scope, attributes, timeout) { - - path = convertVirtualPath(path); - - var asset = this._getAssetPromise(path); // $q.defer(); - var t = timeout || 5000; - var a = attributes || undefined; - - if (asset.state === "new") { - asset.state = "loading"; - LazyLoad.css(appendRnd(path), function () { - if (!scope) { - asset.state = "loaded"; - asset.deferred.resolve(true); - } else { - asset.state = "loaded"; - angularHelper.safeApply(scope, function () { - asset.deferred.resolve(true); - }); - } - }); - } else if (asset.state === "loaded") { - asset.deferred.resolve(true); - } - return asset.deferred.promise; - }, - - /** - * @ngdoc method - * @name umbraco.services.assetsService#loadJs - * @methodOf umbraco.services.assetsService - * - * @description - * Injects a file as a javascript into the document - * - * @param {String} path path to the js file to load - * @param {Scope} scope optional scope to pass into the loader - * @param {Object} keyvalue collection of attributes to pass to the script element - * @param {Number} timeout in milliseconds - * @returns {Promise} Promise object which resolves when the file has loaded - */ - loadJs: function (path, scope, attributes, timeout) { - - path = convertVirtualPath(path); - - var asset = this._getAssetPromise(path); // $q.defer(); - var t = timeout || 5000; - var a = attributes || undefined; - - if (asset.state === "new") { - asset.state = "loading"; - - LazyLoad.js(appendRnd(path), function () { - if (!scope) { - asset.state = "loaded"; - asset.deferred.resolve(true); - } else { - asset.state = "loaded"; - angularHelper.safeApply(scope, function () { - asset.deferred.resolve(true); - }); - } - }); - - } else if (asset.state === "loaded") { - asset.deferred.resolve(true); - } - - return asset.deferred.promise; - }, - - /** - * @ngdoc method - * @name umbraco.services.assetsService#load - * @methodOf umbraco.services.assetsService - * - * @description - * Injects a collection of files, this can be ONLY js files - * - * - * @param {Array} pathArray string array of paths to the files to load - * @param {Scope} scope optional scope to pass into the loader - * @returns {Promise} Promise object which resolves when all the files has loaded - */ - load: function (pathArray, scope) { - var promise; - - if (!angular.isArray(pathArray)) { - throw "pathArray must be an array"; - } - - var nonEmpty = _.reject(pathArray, function (item) { - return item === undefined || item === ""; - }); - - - //don't load anything if there's nothing to load - if (nonEmpty.length > 0) { - var promises = []; - var assets = []; - - //compile a list of promises - //blocking - _.each(nonEmpty, function (path) { - - path = convertVirtualPath(path); - - var asset = service._getAssetPromise(path); - //if not previously loaded, add to list of promises - if (asset.state !== "loaded") { - if (asset.state === "new") { - asset.state = "loading"; - assets.push(asset); - } - - //we need to always push to the promises collection to monitor correct - //execution - promises.push(asset.deferred.promise); - } - }); - - - //gives a central monitoring of all assets to load - promise = $q.all(promises); - - _.each(assets, function (asset) { - LazyLoad.js(appendRnd(asset.path), function () { - asset.state = "loaded"; - if (!scope) { - asset.deferred.resolve(true); - } - else { - angularHelper.safeApply(scope, function () { - asset.deferred.resolve(true); - }); - } - }); - }); - } - else { - //return and resolve - var deferred = $q.defer(); - promise = deferred.promise; - deferred.resolve(true); - } - - - return promise; - } - }; - - return service; -}); - -/** -* @ngdoc service -* @name umbraco.services.contentEditingHelper -* @description A helper service for most editors, some methods are specific to content/media/member model types but most are used by -* all editors to share logic and reduce the amount of replicated code among editors. -**/ -function contentEditingHelper(fileManager, $q, $location, $routeParams, notificationsService, serverValidationManager, dialogService, formHelper, appState) { - - function isValidIdentifier(id){ - //empty id <= 0 - if(angular.isNumber(id) && id > 0){ - return true; - } - - //empty guid - if(id === "00000000-0000-0000-0000-000000000000"){ - return false; - } - - //empty string / alias - if(id === ""){ - return false; - } - - return true; - } - - return { - - /** Used by the content editor and mini content editor to perform saving operations */ - contentEditorPerformSave: function (args) { - if (!angular.isObject(args)) { - throw "args must be an object"; - } - if (!args.scope) { - throw "args.scope is not defined"; - } - if (!args.content) { - throw "args.content is not defined"; - } - if (!args.statusMessage) { - throw "args.statusMessage is not defined"; - } - if (!args.saveMethod) { - throw "args.saveMethod is not defined"; - } - - var redirectOnFailure = args.redirectOnFailure !== undefined ? args.redirectOnFailure : true; - - var self = this; - - //we will use the default one for content if not specified - var rebindCallback = args.rebindCallback === undefined ? self.reBindChangedProperties : args.rebindCallback; - - var deferred = $q.defer(); - - if (!args.scope.busy && formHelper.submitForm({ scope: args.scope, statusMessage: args.statusMessage, action: args.action })) { - - args.scope.busy = true; - - args.saveMethod(args.content, $routeParams.create, fileManager.getFiles()) - .then(function (data) { - - formHelper.resetForm({ scope: args.scope, notifications: data.notifications }); - - self.handleSuccessfulSave({ - scope: args.scope, - savedContent: data, - rebindCallback: function() { - rebindCallback.apply(self, [args.content, data]); - } - }); - - args.scope.busy = false; - deferred.resolve(data); - - }, function (err) { - self.handleSaveError({ - redirectOnFailure: redirectOnFailure, - err: err, - rebindCallback: function() { - rebindCallback.apply(self, [args.content, err.data]); - } - }); - //show any notifications - if (angular.isArray(err.data.notifications)) { - for (var i = 0; i < err.data.notifications.length; i++) { - notificationsService.showNotification(err.data.notifications[i]); - } - } - args.scope.busy = false; - deferred.reject(err); - }); - } - else { - deferred.reject(); - } - - return deferred.promise; - }, - - - /** Returns the action button definitions based on what permissions the user has. - The content.allowedActions parameter contains a list of chars, each represents a button by permission so - here we'll build the buttons according to the chars of the user. */ - configureContentEditorButtons: function (args) { - - if (!angular.isObject(args)) { - throw "args must be an object"; - } - if (!args.content) { - throw "args.content is not defined"; - } - if (!args.methods) { - throw "args.methods is not defined"; - } - if (!args.methods.saveAndPublish || !args.methods.sendToPublish || !args.methods.save || !args.methods.unPublish) { - throw "args.methods does not contain all required defined methods"; - } - - var buttons = { - defaultButton: null, - subButtons: [] - }; - - function createButtonDefinition(ch) { - switch (ch) { - case "U": - //publish action - return { - letter: ch, - labelKey: "buttons_saveAndPublish", - handler: args.methods.saveAndPublish, - hotKey: "ctrl+p", - hotKeyWhenHidden: true - }; - case "H": - //send to publish - return { - letter: ch, - labelKey: "buttons_saveToPublish", - handler: args.methods.sendToPublish, - hotKey: "ctrl+p", - hotKeyWhenHidden: true - }; - case "A": - //save - return { - letter: ch, - labelKey: "buttons_save", - handler: args.methods.save, - hotKey: "ctrl+s", - hotKeyWhenHidden: true - }; - case "Z": - //unpublish - return { - letter: ch, - labelKey: "content_unPublish", - handler: args.methods.unPublish, - hotKey: "ctrl+u", - hotKeyWhenHidden: true - }; - default: - return null; - } - } - - //reset - buttons.subButtons = []; - - //This is the ideal button order but depends on circumstance, we'll use this array to create the button list - // Publish, SendToPublish, Save - var buttonOrder = ["U", "H", "A"]; - - //Create the first button (primary button) - //We cannot have the Save or SaveAndPublish buttons if they don't have create permissions when we are creating a new item. - if (!args.create || _.contains(args.content.allowedActions, "C")) { - for (var b in buttonOrder) { - if (_.contains(args.content.allowedActions, buttonOrder[b])) { - buttons.defaultButton = createButtonDefinition(buttonOrder[b]); - break; - } - } - } - - //Now we need to make the drop down button list, this is also slightly tricky because: - //We cannot have any buttons if there's no default button above. - //We cannot have the unpublish button (Z) when there's no publish permission. - //We cannot have the unpublish button (Z) when the item is not published. - if (buttons.defaultButton) { - - //get the last index of the button order - var lastIndex = _.indexOf(buttonOrder, buttons.defaultButton.letter); - //add the remaining - for (var i = lastIndex + 1; i < buttonOrder.length; i++) { - if (_.contains(args.content.allowedActions, buttonOrder[i])) { - buttons.subButtons.push(createButtonDefinition(buttonOrder[i])); - } - } - - - //if we are not creating, then we should add unpublish too, - // so long as it's already published and if the user has access to publish - if (!args.create) { - if (args.content.publishDate && _.contains(args.content.allowedActions, "U")) { - buttons.subButtons.push(createButtonDefinition("Z")); - } - } - } - - return buttons; - }, - - /** - * @ngdoc method - * @name umbraco.services.contentEditingHelper#getAllProps - * @methodOf umbraco.services.contentEditingHelper - * @function - * - * @description - * Returns all propertes contained for the content item (since the normal model has properties contained inside of tabs) - */ - getAllProps: function (content) { - var allProps = []; - - for (var i = 0; i < content.tabs.length; i++) { - for (var p = 0; p < content.tabs[i].properties.length; p++) { - allProps.push(content.tabs[i].properties[p]); - } - } - - return allProps; - }, - - - /** - * @ngdoc method - * @name umbraco.services.contentEditingHelper#configureButtons - * @methodOf umbraco.services.contentEditingHelper - * @function - * - * @description - * Returns a letter array for buttons, with the primary one first based on content model, permissions and editor state - */ - getAllowedActions : function(content, creating){ - - //This is the ideal button order but depends on circumstance, we'll use this array to create the button list - // Publish, SendToPublish, Save - var actionOrder = ["U", "H", "A"]; - var defaultActions; - var actions = []; - - //Create the first button (primary button) - //We cannot have the Save or SaveAndPublish buttons if they don't have create permissions when we are creating a new item. - if (!creating || _.contains(content.allowedActions, "C")) { - for (var b in actionOrder) { - if (_.contains(content.allowedActions, actionOrder[b])) { - defaultAction = actionOrder[b]; - break; - } - } - } - - actions.push(defaultAction); - - //Now we need to make the drop down button list, this is also slightly tricky because: - //We cannot have any buttons if there's no default button above. - //We cannot have the unpublish button (Z) when there's no publish permission. - //We cannot have the unpublish button (Z) when the item is not published. - if (defaultAction) { - //get the last index of the button order - var lastIndex = _.indexOf(actionOrder, defaultAction); - - //add the remaining - for (var i = lastIndex + 1; i < actionOrder.length; i++) { - if (_.contains(content.allowedActions, actionOrder[i])) { - actions.push(actionOrder[i]); - } - } - - //if we are not creating, then we should add unpublish too, - // so long as it's already published and if the user has access to publish - if (!creating) { - if (content.publishDate && _.contains(content.allowedActions,"U")) { - actions.push("Z"); - } - } - } - - return actions; - }, - - /** - * @ngdoc method - * @name umbraco.services.contentEditingHelper#getButtonFromAction - * @methodOf umbraco.services.contentEditingHelper - * @function - * - * @description - * Returns a button object to render a button for the tabbed editor - * currently only returns built in system buttons for content and media actions - * returns label, alias, action char and hot-key - */ - getButtonFromAction : function(ch){ - switch (ch) { - case "U": - return { - letter: ch, - labelKey: "buttons_saveAndPublish", - handler: "saveAndPublish", - hotKey: "ctrl+p" - }; - case "H": - //send to publish - return { - letter: ch, - labelKey: "buttons_saveToPublish", - handler: "sendToPublish", - hotKey: "ctrl+p" - }; - case "A": - return { - letter: ch, - labelKey: "buttons_save", - handler: "save", - hotKey: "ctrl+s" - }; - case "Z": - return { - letter: ch, - labelKey: "content_unPublish", - handler: "unPublish" - }; - - default: - return null; - } - - }, - /** - * @ngdoc method - * @name umbraco.services.contentEditingHelper#reBindChangedProperties - * @methodOf umbraco.services.contentEditingHelper - * @function - * - * @description - * re-binds all changed property values to the origContent object from the savedContent object and returns an array of changed properties. - */ - reBindChangedProperties: function (origContent, savedContent) { - - var changed = []; - - //get a list of properties since they are contained in tabs - var allOrigProps = this.getAllProps(origContent); - var allNewProps = this.getAllProps(savedContent); - - function getNewProp(alias) { - return _.find(allNewProps, function (item) { - return item.alias === alias; - }); - } - - //a method to ignore built-in prop changes - var shouldIgnore = function(propName) { - return _.some(["tabs", "notifications", "ModelState", "tabs", "properties"], function(i) { - return i === propName; - }); - }; - //check for changed built-in properties of the content - for (var o in origContent) { - - //ignore the ones listed in the array - if (shouldIgnore(o)) { - continue; - } - - if (!_.isEqual(origContent[o], savedContent[o])) { - origContent[o] = savedContent[o]; - } - } - - //check for changed properties of the content - for (var p in allOrigProps) { - var newProp = getNewProp(allOrigProps[p].alias); - if (newProp && !_.isEqual(allOrigProps[p].value, newProp.value)) { - - //they have changed so set the origContent prop to the new one - var origVal = allOrigProps[p].value; - allOrigProps[p].value = newProp.value; - - //instead of having a property editor $watch their expression to check if it has - // been updated, instead we'll check for the existence of a special method on their model - // and just call it. - if (angular.isFunction(allOrigProps[p].onValueChanged)) { - //send the newVal + oldVal - allOrigProps[p].onValueChanged(allOrigProps[p].value, origVal); - } - - changed.push(allOrigProps[p]); - } - } - - return changed; - }, - - /** - * @ngdoc function - * @name umbraco.services.contentEditingHelper#handleSaveError - * @methodOf umbraco.services.contentEditingHelper - * @function - * - * @description - * A function to handle what happens when we have validation issues from the server side - */ - handleSaveError: function (args) { - - if (!args.err) { - throw "args.err cannot be null"; - } - if (args.redirectOnFailure === undefined || args.redirectOnFailure === null) { - throw "args.redirectOnFailure must be set to true or false"; - } - - //When the status is a 400 status with a custom header: X-Status-Reason: Validation failed, we have validation errors. - //Otherwise the error is probably due to invalid data (i.e. someone mucking around with the ids or something). - //Or, some strange server error - if (args.err.status === 400) { - //now we need to look through all the validation errors - if (args.err.data && (args.err.data.ModelState)) { - - //wire up the server validation errs - formHelper.handleServerValidation(args.err.data.ModelState); - - if (!args.redirectOnFailure || !this.redirectToCreatedContent(args.err.data.id, args.err.data.ModelState)) { - //we are not redirecting because this is not new content, it is existing content. In this case - // we need to detect what properties have changed and re-bind them with the server data. Then we need - // to re-bind any server validation errors after the digest takes place. - - if (args.rebindCallback && angular.isFunction(args.rebindCallback)) { - args.rebindCallback(); - } - - serverValidationManager.executeAndClearAllSubscriptions(); - } - - //indicates we've handled the server result - return true; - } - else { - dialogService.ysodDialog(args.err); - } - } - else { - dialogService.ysodDialog(args.err); - } - - return false; - }, - - /** - * @ngdoc function - * @name umbraco.services.contentEditingHelper#handleSuccessfulSave - * @methodOf umbraco.services.contentEditingHelper - * @function - * - * @description - * A function to handle when saving a content item is successful. This will rebind the values of the model that have changed - * ensure the notifications are displayed and that the appropriate events are fired. This will also check if we need to redirect - * when we're creating new content. - */ - handleSuccessfulSave: function (args) { - - if (!args) { - throw "args cannot be null"; - } - if (!args.savedContent) { - throw "args.savedContent cannot be null"; - } - - if (!this.redirectToCreatedContent(args.redirectId ? args.redirectId : args.savedContent.id)) { - - //we are not redirecting because this is not new content, it is existing content. In this case - // we need to detect what properties have changed and re-bind them with the server data. - //call the callback - if (args.rebindCallback && angular.isFunction(args.rebindCallback)) { - args.rebindCallback(); - } - } - }, - - /** - * @ngdoc function - * @name umbraco.services.contentEditingHelper#redirectToCreatedContent - * @methodOf umbraco.services.contentEditingHelper - * @function - * - * @description - * Changes the location to be editing the newly created content after create was successful. - * We need to decide if we need to redirect to edito mode or if we will remain in create mode. - * We will only need to maintain create mode if we have not fulfilled the basic requirements for creating an entity which is at least having a name and ID - */ - redirectToCreatedContent: function (id, modelState) { - - //only continue if we are currently in create mode and if there is no 'Name' modelstate errors - // since we need at least a name to create content. - if ($routeParams.create && (isValidIdentifier(id) && (!modelState || !modelState["Name"]))) { - - //need to change the location to not be in 'create' mode. Currently the route will be something like: - // /belle/#/content/edit/1234?doctype=newsArticle&create=true - // but we need to remove everything after the query so that it is just: - // /belle/#/content/edit/9876 (where 9876 is the new id) - - //clear the query strings - $location.search(""); - - //change to new path - $location.path("/" + $routeParams.section + "/" + $routeParams.tree + "/" + $routeParams.method + "/" + id); - //don't add a browser history for this - $location.replace(); - return true; - } - return false; - } - }; -} -angular.module('umbraco.services').factory('contentEditingHelper', contentEditingHelper); - -/** + /** + * @ngdoc service + * @name umbraco.services.assetsService + * + * @requires $q + * @requires angularHelper + * + * @description + * Promise-based utillity service to lazy-load client-side dependencies inside angular controllers. + * + * ##usage + * To use, simply inject the assetsService into any controller that needs it, and make + * sure the umbraco.services module is accesible - which it should be by default. + * + *
    + *      angular.module("umbraco").controller("my.controller". function(assetsService){
    + *          assetsService.load(["script.js", "styles.css"], $scope).then(function(){
    + *                 //this code executes when the dependencies are done loading
    + *          });
    + *      });
    + * 
    + * + * You can also load individual files, which gives you greater control over what attibutes are passed to the file, as well as timeout + * + *
    + *      angular.module("umbraco").controller("my.controller". function(assetsService){
    + *          assetsService.loadJs("script.js", $scope, {charset: 'utf-8'}, 10000 }).then(function(){
    + *                 //this code executes when the script is done loading
    + *          });
    + *      });
    + * 
    + * + * For these cases, there are 2 individual methods, one for javascript, and one for stylesheets: + * + *
    + *      angular.module("umbraco").controller("my.controller". function(assetsService){
    + *          assetsService.loadCss("stye.css", $scope, {media: 'print'}, 10000 }).then(function(){
    + *                 //loadcss cannot determine when the css is done loading, so this will trigger instantly
    + *          });
    + *      });
    + * 
    + */ + angular.module('umbraco.services').factory('assetsService', function ($q, $log, angularHelper, umbRequestHelper, $rootScope, $http) { + var initAssetsLoaded = false; + function appendRnd(url) { + //if we don't have a global umbraco obj yet, the app is bootstrapping + if (!Umbraco.Sys.ServerVariables.application) { + return url; + } + var rnd = Umbraco.Sys.ServerVariables.application.cacheBuster; + var _op = url.indexOf('?') > 0 ? '&' : '?'; + url = url + _op + 'umb__rnd=' + rnd; + return url; + } + ; + function convertVirtualPath(path) { + //make this work for virtual paths + if (path.startsWith('~/')) { + path = umbRequestHelper.convertVirtualToAbsolutePath(path); + } + return path; + } + var service = { + loadedAssets: {}, + _getAssetPromise: function (path) { + if (this.loadedAssets[path]) { + return this.loadedAssets[path]; + } else { + var deferred = $q.defer(); + this.loadedAssets[path] = { + deferred: deferred, + state: 'new', + path: path + }; + return this.loadedAssets[path]; + } + }, + /** + Internal method. This is called when the application is loading and the user is already authenticated, or once the user is authenticated. + There's a few assets the need to be loaded for the application to function but these assets require authentication to load. + */ + _loadInitAssets: function () { + var deferred = $q.defer(); + //here we need to ensure the required application assets are loaded + if (initAssetsLoaded === false) { + var self = this; + self.loadJs(umbRequestHelper.getApiUrl('serverVarsJs', '', ''), $rootScope).then(function () { + initAssetsLoaded = true; + //now we need to go get the legacyTreeJs - but this can be done async without waiting. + self.loadJs(umbRequestHelper.getApiUrl('legacyTreeJs', '', ''), $rootScope); + deferred.resolve(); + }); + } else { + deferred.resolve(); + } + return deferred.promise; + }, + /** + * @ngdoc method + * @name umbraco.services.assetsService#loadCss + * @methodOf umbraco.services.assetsService + * + * @description + * Injects a file as a stylesheet into the document head + * + * @param {String} path path to the css file to load + * @param {Scope} scope optional scope to pass into the loader + * @param {Object} keyvalue collection of attributes to pass to the stylesheet element + * @param {Number} timeout in milliseconds + * @returns {Promise} Promise object which resolves when the file has loaded + */ + loadCss: function (path, scope, attributes, timeout) { + path = convertVirtualPath(path); + var asset = this._getAssetPromise(path); + // $q.defer(); + var t = timeout || 5000; + var a = attributes || undefined; + if (asset.state === 'new') { + asset.state = 'loading'; + LazyLoad.css(appendRnd(path), function () { + if (!scope) { + scope = $rootScope; + } + asset.state = 'loaded'; + angularHelper.safeApply(scope, function () { + asset.deferred.resolve(true); + }); + }); + } else if (asset.state === 'loaded') { + asset.deferred.resolve(true); + } + return asset.deferred.promise; + }, + /** + * @ngdoc method + * @name umbraco.services.assetsService#loadJs + * @methodOf umbraco.services.assetsService + * + * @description + * Injects a file as a javascript into the document + * + * @param {String} path path to the js file to load + * @param {Scope} scope optional scope to pass into the loader + * @param {Object} keyvalue collection of attributes to pass to the script element + * @param {Number} timeout in milliseconds + * @returns {Promise} Promise object which resolves when the file has loaded + */ + loadJs: function (path, scope, attributes, timeout) { + path = convertVirtualPath(path); + var asset = this._getAssetPromise(path); + // $q.defer(); + var t = timeout || 5000; + var a = attributes || undefined; + if (asset.state === 'new') { + asset.state = 'loading'; + LazyLoad.js(appendRnd(path), function () { + if (!scope) { + scope = $rootScope; + } + asset.state = 'loaded'; + angularHelper.safeApply(scope, function () { + asset.deferred.resolve(true); + }); + }); + } else if (asset.state === 'loaded') { + asset.deferred.resolve(true); + } + return asset.deferred.promise; + }, + /** + * @ngdoc method + * @name umbraco.services.assetsService#load + * @methodOf umbraco.services.assetsService + * + * @description + * Injects a collection of css and js files + * + * + * @param {Array} pathArray string array of paths to the files to load + * @param {Scope} scope optional scope to pass into the loader + * @returns {Promise} Promise object which resolves when all the files has loaded + */ + load: function (pathArray, scope) { + var promise; + if (!angular.isArray(pathArray)) { + throw 'pathArray must be an array'; + } + // Check to see if there's anything to load, resolve promise if not + var nonEmpty = _.reject(pathArray, function (item) { + return item === undefined || item === ''; + }); + if (nonEmpty.length === 0) { + var deferred = $q.defer(); + promise = deferred.promise; + deferred.resolve(true); + return promise; + } + //compile a list of promises + //blocking + var promises = []; + var assets = []; + _.each(nonEmpty, function (path) { + path = convertVirtualPath(path); + var asset = service._getAssetPromise(path); + //if not previously loaded, add to list of promises + if (asset.state !== 'loaded') { + if (asset.state === 'new') { + asset.state = 'loading'; + assets.push(asset); + } + //we need to always push to the promises collection to monitor correct execution + promises.push(asset.deferred.promise); + } + }); + //gives a central monitoring of all assets to load + promise = $q.all(promises); + // Split into css and js asset arrays, and use LazyLoad on each array + var cssAssets = _.filter(assets, function (asset) { + return asset.path.match(/(\.css$|\.css\?)/ig); + }); + var jsAssets = _.filter(assets, function (asset) { + return asset.path.match(/(\.js$|\.js\?)/ig); + }); + function assetLoaded(asset) { + asset.state = 'loaded'; + if (!scope) { + scope = $rootScope; + } + angularHelper.safeApply(scope, function () { + asset.deferred.resolve(true); + }); + } + if (cssAssets.length > 0) { + var cssPaths = _.map(cssAssets, function (asset) { + return appendRnd(asset.path); + }); + LazyLoad.css(cssPaths, function () { + _.each(cssAssets, assetLoaded); + }); + } + if (jsAssets.length > 0) { + var jsPaths = _.map(jsAssets, function (asset) { + return appendRnd(asset.path); + }); + LazyLoad.js(jsPaths, function () { + _.each(jsAssets, assetLoaded); + }); + } + return promise; + } + }; + return service; + }); + /** + @ngdoc service + * @name umbraco.services.backdropService + * + * @description + * Added in Umbraco 7.8. Application-wide service for handling backdrops. + * + */ + (function () { + 'use strict'; + function backdropService(eventsService) { + var args = { + opacity: null, + element: null, + elementPreventClick: false, + disableEventsOnClick: false, + show: false + }; + /** + * @ngdoc method + * @name umbraco.services.backdropService#open + * @methodOf umbraco.services.backdropService + * + * @description + * Raises an event to open a backdrop + * @param {Object} options The backdrop options + * @param {Number} options.opacity Sets the opacity on the backdrop (default 0.4) + * @param {DomElement} options.element Highlights a DOM-element (HTML-selector) + * @param {Boolean} options.elementPreventClick Adds blocking element on top of highligted area to prevent all clicks + * @param {Boolean} options.disableEventsOnClick Disables all raised events when the backdrop is clicked + */ + function open(options) { + if (options && options.element) { + args.element = options.element; + } + if (options && options.disableEventsOnClick) { + args.disableEventsOnClick = options.disableEventsOnClick; + } + args.show = true; + eventsService.emit('appState.backdrop', args); + } + /** + * @ngdoc method + * @name umbraco.services.backdropService#close + * @methodOf umbraco.services.backdropService + * + * @description + * Raises an event to close the backdrop + * + */ + function close() { + args.element = null; + args.show = false; + eventsService.emit('appState.backdrop', args); + } + /** + * @ngdoc method + * @name umbraco.services.backdropService#setOpacity + * @methodOf umbraco.services.backdropService + * + * @description + * Raises an event which updates the opacity option on the backdrop + */ + function setOpacity(opacity) { + args.opacity = opacity; + eventsService.emit('appState.backdrop', args); + } + /** + * @ngdoc method + * @name umbraco.services.backdropService#setHighlight + * @methodOf umbraco.services.backdropService + * + * @description + * Raises an event which updates the element option on the backdrop + */ + function setHighlight(element, preventClick) { + args.element = element; + args.elementPreventClick = preventClick; + eventsService.emit('appState.backdrop', args); + } + var service = { + open: open, + close: close, + setOpacity: setOpacity, + setHighlight: setHighlight + }; + return service; + } + angular.module('umbraco.services').factory('backdropService', backdropService); + }()); + /** +* @ngdoc service +* @name umbraco.services.contentEditingHelper +* @description A helper service for most editors, some methods are specific to content/media/member model types but most are used by +* all editors to share logic and reduce the amount of replicated code among editors. +**/ + function contentEditingHelper(fileManager, $q, $location, $routeParams, notificationsService, localizationService, serverValidationManager, dialogService, formHelper, appState) { + function isValidIdentifier(id) { + //empty id <= 0 + if (angular.isNumber(id) && id > 0) { + return true; + } + //empty guid + if (id === '00000000-0000-0000-0000-000000000000') { + return false; + } + //empty string / alias + if (id === '') { + return false; + } + return true; + } + return { + /** Used by the content editor and mini content editor to perform saving operations */ + //TODO: Make this a more helpful/reusable method for other form operations! we can simplify this form most forms + contentEditorPerformSave: function (args) { + if (!angular.isObject(args)) { + throw 'args must be an object'; + } + if (!args.scope) { + throw 'args.scope is not defined'; + } + if (!args.content) { + throw 'args.content is not defined'; + } + if (!args.statusMessage) { + throw 'args.statusMessage is not defined'; + } + if (!args.saveMethod) { + throw 'args.saveMethod is not defined'; + } + var redirectOnFailure = args.redirectOnFailure !== undefined ? args.redirectOnFailure : true; + var self = this; + //we will use the default one for content if not specified + var rebindCallback = args.rebindCallback === undefined ? self.reBindChangedProperties : args.rebindCallback; + var deferred = $q.defer(); + if (!args.scope.busy && formHelper.submitForm({ + scope: args.scope, + statusMessage: args.statusMessage, + action: args.action + })) { + args.scope.busy = true; + args.saveMethod(args.content, $routeParams.create, fileManager.getFiles()).then(function (data) { + formHelper.resetForm({ + scope: args.scope, + notifications: data.notifications + }); + self.handleSuccessfulSave({ + scope: args.scope, + savedContent: data, + rebindCallback: function () { + rebindCallback.apply(self, [ + args.content, + data + ]); + } + }); + args.scope.busy = false; + deferred.resolve(data); + }, function (err) { + self.handleSaveError({ + redirectOnFailure: redirectOnFailure, + err: err, + rebindCallback: function () { + rebindCallback.apply(self, [ + args.content, + err.data + ]); + } + }); + //show any notifications + if (angular.isArray(err.data.notifications)) { + for (var i = 0; i < err.data.notifications.length; i++) { + notificationsService.showNotification(err.data.notifications[i]); + } + } + args.scope.busy = false; + deferred.reject(err); + }); + } else { + deferred.reject(); + } + return deferred.promise; + }, + /** Used by the content editor and media editor to add an info tab to the tabs array (normally known as the properties tab) */ + addInfoTab: function (tabs) { + var infoTab = { + 'alias': '_umb_infoTab', + 'id': -1, + 'label': 'Info', + 'properties': [] + }; + // first check if tab is already added + var foundInfoTab = false; + angular.forEach(tabs, function (tab) { + if (tab.id === infoTab.id && tab.alias === infoTab.alias) { + foundInfoTab = true; + } + }); + // add info tab if is is not found + if (!foundInfoTab) { + localizationService.localize('general_info').then(function (value) { + infoTab.label = value; + tabs.push(infoTab); + }); + } + }, + /** Returns the action button definitions based on what permissions the user has. + The content.allowedActions parameter contains a list of chars, each represents a button by permission so + here we'll build the buttons according to the chars of the user. */ + configureContentEditorButtons: function (args) { + if (!angular.isObject(args)) { + throw 'args must be an object'; + } + if (!args.content) { + throw 'args.content is not defined'; + } + if (!args.methods) { + throw 'args.methods is not defined'; + } + if (!args.methods.saveAndPublish || !args.methods.sendToPublish || !args.methods.save || !args.methods.unPublish) { + throw 'args.methods does not contain all required defined methods'; + } + var buttons = { + defaultButton: null, + subButtons: [] + }; + function createButtonDefinition(ch) { + switch (ch) { + case 'U': + //publish action + return { + letter: ch, + labelKey: 'buttons_saveAndPublish', + handler: args.methods.saveAndPublish, + hotKey: 'ctrl+p', + hotKeyWhenHidden: true, + alias: 'saveAndPublish' + }; + case 'H': + //send to publish + return { + letter: ch, + labelKey: 'buttons_saveToPublish', + handler: args.methods.sendToPublish, + hotKey: 'ctrl+p', + hotKeyWhenHidden: true, + alias: 'sendToPublish' + }; + case 'A': + //save + return { + letter: ch, + labelKey: 'buttons_save', + handler: args.methods.save, + hotKey: 'ctrl+s', + hotKeyWhenHidden: true, + alias: 'save' + }; + case 'Z': + //unpublish + return { + letter: ch, + labelKey: 'content_unPublish', + handler: args.methods.unPublish, + hotKey: 'ctrl+u', + hotKeyWhenHidden: true, + alias: 'unpublish' + }; + default: + return null; + } + } + //reset + buttons.subButtons = []; + //This is the ideal button order but depends on circumstance, we'll use this array to create the button list + // Publish, SendToPublish, Save + var buttonOrder = [ + 'U', + 'H', + 'A' + ]; + //Create the first button (primary button) + //We cannot have the Save or SaveAndPublish buttons if they don't have create permissions when we are creating a new item. + //Another tricky rule is if they only have Create + Browse permissions but not Save but if it's being created then they will + // require the Save button in order to create. + //So this code is going to create the primary button (either Publish, SendToPublish, Save) if we are not in create mode + // or if the user has access to create. + if (!args.create || _.contains(args.content.allowedActions, 'C')) { + for (var b in buttonOrder) { + if (_.contains(args.content.allowedActions, buttonOrder[b])) { + buttons.defaultButton = createButtonDefinition(buttonOrder[b]); + break; + } + } + //Here's the special check, if the button still isn't set and we are creating and they have create access + //we need to add the Save button + if (!buttons.defaultButton && args.create && _.contains(args.content.allowedActions, 'C')) { + buttons.defaultButton = createButtonDefinition('A'); + } + } + //Now we need to make the drop down button list, this is also slightly tricky because: + //We cannot have any buttons if there's no default button above. + //We cannot have the unpublish button (Z) when there's no publish permission. + //We cannot have the unpublish button (Z) when the item is not published. + if (buttons.defaultButton) { + //get the last index of the button order + var lastIndex = _.indexOf(buttonOrder, buttons.defaultButton.letter); + //add the remaining + for (var i = lastIndex + 1; i < buttonOrder.length; i++) { + if (_.contains(args.content.allowedActions, buttonOrder[i])) { + buttons.subButtons.push(createButtonDefinition(buttonOrder[i])); + } + } + // if we are not creating, then we should add unpublish too, + // so long as it's already published and if the user has access to publish + // and the user has access to unpublish (may have been removed via Event) + if (!args.create) { + if (args.content.publishDate && _.contains(args.content.allowedActions, 'U') && _.contains(args.content.allowedActions, 'Z')) { + buttons.subButtons.push(createButtonDefinition('Z')); + } + } + } + // If we have a scheduled publish or unpublish date change the default button to + // "save" and update the label to "save and schedule + if (args.content.releaseDate || args.content.removeDate) { + // if save button is alread the default don't change it just update the label + if (buttons.defaultButton && buttons.defaultButton.letter === 'A') { + buttons.defaultButton.labelKey = 'buttons_saveAndSchedule'; + return buttons; + } + if (buttons.defaultButton && buttons.subButtons && buttons.subButtons.length > 0) { + // save a copy of the default so we can push it to the sub buttons later + var defaultButtonCopy = angular.copy(buttons.defaultButton); + var newSubButtons = []; + // if save button is not the default button - find it and make it the default + angular.forEach(buttons.subButtons, function (subButton) { + if (subButton.letter === 'A') { + buttons.defaultButton = subButton; + buttons.defaultButton.labelKey = 'buttons_saveAndSchedule'; + } else { + newSubButtons.push(subButton); + } + }); + // push old default button into subbuttons + newSubButtons.push(defaultButtonCopy); + buttons.subButtons = newSubButtons; + } + } + return buttons; + }, + /** + * @ngdoc method + * @name umbraco.services.contentEditingHelper#getAllProps + * @methodOf umbraco.services.contentEditingHelper + * @function + * + * @description + * Returns all propertes contained for the content item (since the normal model has properties contained inside of tabs) + */ + getAllProps: function (content) { + var allProps = []; + for (var i = 0; i < content.tabs.length; i++) { + for (var p = 0; p < content.tabs[i].properties.length; p++) { + allProps.push(content.tabs[i].properties[p]); + } + } + return allProps; + }, + /** + * @ngdoc method + * @name umbraco.services.contentEditingHelper#configureButtons + * @methodOf umbraco.services.contentEditingHelper + * @function + * + * @description + * Returns a letter array for buttons, with the primary one first based on content model, permissions and editor state + */ + getAllowedActions: function (content, creating) { + //This is the ideal button order but depends on circumstance, we'll use this array to create the button list + // Publish, SendToPublish, Save + var actionOrder = [ + 'U', + 'H', + 'A' + ]; + var defaultActions; + var actions = []; + //Create the first button (primary button) + //We cannot have the Save or SaveAndPublish buttons if they don't have create permissions when we are creating a new item. + if (!creating || _.contains(content.allowedActions, 'C')) { + for (var b in actionOrder) { + if (_.contains(content.allowedActions, actionOrder[b])) { + defaultAction = actionOrder[b]; + break; + } + } + } + actions.push(defaultAction); + //Now we need to make the drop down button list, this is also slightly tricky because: + //We cannot have any buttons if there's no default button above. + //We cannot have the unpublish button (Z) when there's no publish permission. + //We cannot have the unpublish button (Z) when the item is not published. + if (defaultAction) { + //get the last index of the button order + var lastIndex = _.indexOf(actionOrder, defaultAction); + //add the remaining + for (var i = lastIndex + 1; i < actionOrder.length; i++) { + if (_.contains(content.allowedActions, actionOrder[i])) { + actions.push(actionOrder[i]); + } + } + //if we are not creating, then we should add unpublish too, + // so long as it's already published and if the user has access to publish + if (!creating) { + if (content.publishDate && _.contains(content.allowedActions, 'U')) { + actions.push('Z'); + } + } + } + return actions; + }, + /** + * @ngdoc method + * @name umbraco.services.contentEditingHelper#getButtonFromAction + * @methodOf umbraco.services.contentEditingHelper + * @function + * + * @description + * Returns a button object to render a button for the tabbed editor + * currently only returns built in system buttons for content and media actions + * returns label, alias, action char and hot-key + */ + getButtonFromAction: function (ch) { + switch (ch) { + case 'U': + return { + letter: ch, + labelKey: 'buttons_saveAndPublish', + handler: 'saveAndPublish', + hotKey: 'ctrl+p' + }; + case 'H': + //send to publish + return { + letter: ch, + labelKey: 'buttons_saveToPublish', + handler: 'sendToPublish', + hotKey: 'ctrl+p' + }; + case 'A': + return { + letter: ch, + labelKey: 'buttons_save', + handler: 'save', + hotKey: 'ctrl+s' + }; + case 'Z': + return { + letter: ch, + labelKey: 'content_unPublish', + handler: 'unPublish' + }; + default: + return null; + } + }, + /** + * @ngdoc method + * @name umbraco.services.contentEditingHelper#reBindChangedProperties + * @methodOf umbraco.services.contentEditingHelper + * @function + * + * @description + * re-binds all changed property values to the origContent object from the savedContent object and returns an array of changed properties. + */ + reBindChangedProperties: function (origContent, savedContent) { + var changed = []; + //get a list of properties since they are contained in tabs + var allOrigProps = this.getAllProps(origContent); + var allNewProps = this.getAllProps(savedContent); + function getNewProp(alias) { + return _.find(allNewProps, function (item) { + return item.alias === alias; + }); + } + //a method to ignore built-in prop changes + var shouldIgnore = function (propName) { + return _.some([ + 'tabs', + 'notifications', + 'ModelState', + 'tabs', + 'properties' + ], function (i) { + return i === propName; + }); + }; + //check for changed built-in properties of the content + for (var o in origContent) { + //ignore the ones listed in the array + if (shouldIgnore(o)) { + continue; + } + if (!_.isEqual(origContent[o], savedContent[o])) { + origContent[o] = savedContent[o]; + } + } + //check for changed properties of the content + for (var p in allOrigProps) { + var newProp = getNewProp(allOrigProps[p].alias); + if (newProp && !_.isEqual(allOrigProps[p].value, newProp.value)) { + //they have changed so set the origContent prop to the new one + var origVal = allOrigProps[p].value; + allOrigProps[p].value = newProp.value; + //instead of having a property editor $watch their expression to check if it has + // been updated, instead we'll check for the existence of a special method on their model + // and just call it. + if (angular.isFunction(allOrigProps[p].onValueChanged)) { + //send the newVal + oldVal + allOrigProps[p].onValueChanged(allOrigProps[p].value, origVal); + } + changed.push(allOrigProps[p]); + } + } + return changed; + }, + /** + * @ngdoc function + * @name umbraco.services.contentEditingHelper#handleSaveError + * @methodOf umbraco.services.contentEditingHelper + * @function + * + * @description + * A function to handle what happens when we have validation issues from the server side + */ + handleSaveError: function (args) { + if (!args.err) { + throw 'args.err cannot be null'; + } + if (args.redirectOnFailure === undefined || args.redirectOnFailure === null) { + throw 'args.redirectOnFailure must be set to true or false'; + } + //When the status is a 400 status with a custom header: X-Status-Reason: Validation failed, we have validation errors. + //Otherwise the error is probably due to invalid data (i.e. someone mucking around with the ids or something). + //Or, some strange server error + if (args.err.status === 400) { + //now we need to look through all the validation errors + if (args.err.data && args.err.data.ModelState) { + //wire up the server validation errs + formHelper.handleServerValidation(args.err.data.ModelState); + if (!args.redirectOnFailure || !this.redirectToCreatedContent(args.err.data.id, args.err.data.ModelState)) { + //we are not redirecting because this is not new content, it is existing content. In this case + // we need to detect what properties have changed and re-bind them with the server data. Then we need + // to re-bind any server validation errors after the digest takes place. + if (args.rebindCallback && angular.isFunction(args.rebindCallback)) { + args.rebindCallback(); + } + serverValidationManager.executeAndClearAllSubscriptions(); + } + //indicates we've handled the server result + return true; + } + } + return false; + }, + /** + * @ngdoc function + * @name umbraco.services.contentEditingHelper#handleSuccessfulSave + * @methodOf umbraco.services.contentEditingHelper + * @function + * + * @description + * A function to handle when saving a content item is successful. This will rebind the values of the model that have changed + * ensure the notifications are displayed and that the appropriate events are fired. This will also check if we need to redirect + * when we're creating new content. + */ + handleSuccessfulSave: function (args) { + if (!args) { + throw 'args cannot be null'; + } + if (!args.savedContent) { + throw 'args.savedContent cannot be null'; + } + if (!this.redirectToCreatedContent(args.redirectId ? args.redirectId : args.savedContent.id)) { + //we are not redirecting because this is not new content, it is existing content. In this case + // we need to detect what properties have changed and re-bind them with the server data. + //call the callback + if (args.rebindCallback && angular.isFunction(args.rebindCallback)) { + args.rebindCallback(); + } + } + }, + /** + * @ngdoc function + * @name umbraco.services.contentEditingHelper#redirectToCreatedContent + * @methodOf umbraco.services.contentEditingHelper + * @function + * + * @description + * Changes the location to be editing the newly created content after create was successful. + * We need to decide if we need to redirect to edito mode or if we will remain in create mode. + * We will only need to maintain create mode if we have not fulfilled the basic requirements for creating an entity which is at least having a name and ID + */ + redirectToCreatedContent: function (id, modelState) { + //only continue if we are currently in create mode and if there is no 'Name' modelstate errors + // since we need at least a name to create content. + if ($routeParams.create && (isValidIdentifier(id) && (!modelState || !modelState['Name']))) { + //need to change the location to not be in 'create' mode. Currently the route will be something like: + // /belle/#/content/edit/1234?doctype=newsArticle&create=true + // but we need to remove everything after the query so that it is just: + // /belle/#/content/edit/9876 (where 9876 is the new id) + //clear the query strings + $location.search(''); + //change to new path + $location.path('/' + $routeParams.section + '/' + $routeParams.tree + '/' + $routeParams.method + '/' + id); + //don't add a browser history for this + $location.replace(); + return true; + } + return false; + }, + /** + * @ngdoc function + * @name umbraco.services.contentEditingHelper#redirectToRenamedContent + * @methodOf umbraco.services.contentEditingHelper + * @function + * + * @description + * For some editors like scripts or entites that have names as ids, these names can change and we need to redirect + * to their new paths, this is helper method to do that. + */ + redirectToRenamedContent: function (id) { + //clear the query strings + $location.search(''); + //change to new path + $location.path('/' + $routeParams.section + '/' + $routeParams.tree + '/' + $routeParams.method + '/' + id); + //don't add a browser history for this + $location.replace(); + return true; + } + }; + } + angular.module('umbraco.services').factory('contentEditingHelper', contentEditingHelper); + /** * @ngdoc service * @name umbraco.services.contentTypeHelper * @description A helper service for the content type editor **/ -function contentTypeHelper(contentTypeResource, dataTypeResource, $filter, $injector, $q) { - - var contentTypeHelperService = { - - createIdArray: function(array) { - - var newArray = []; - - angular.forEach(array, function(arrayItem){ - - if(angular.isObject(arrayItem)) { - newArray.push(arrayItem.id); - } else { - newArray.push(arrayItem); - } - - }); - - return newArray; - - }, - - generateModels: function () { - var deferred = $q.defer(); - var modelsResource = $injector.has("modelsBuilderResource") ? $injector.get("modelsBuilderResource") : null; - var modelsBuilderEnabled = Umbraco.Sys.ServerVariables.umbracoPlugins.modelsBuilder.enabled; - if (modelsBuilderEnabled && modelsResource) { - modelsResource.buildModels().then(function(result) { - deferred.resolve(result); - - //just calling this to get the servar back to life - modelsResource.getModelsOutOfDateStatus(); - - }, function(e) { - deferred.reject(e); + function contentTypeHelper(contentTypeResource, dataTypeResource, $filter, $injector, $q) { + var contentTypeHelperService = { + createIdArray: function (array) { + var newArray = []; + angular.forEach(array, function (arrayItem) { + if (angular.isObject(arrayItem)) { + newArray.push(arrayItem.id); + } else { + newArray.push(arrayItem); + } }); - } - else { - deferred.resolve(false); - } - return deferred.promise; - }, - - checkModelsBuilderStatus: function () { - var deferred = $q.defer(); - var modelsResource = $injector.has("modelsBuilderResource") ? $injector.get("modelsBuilderResource") : null; - var modelsBuilderEnabled = (Umbraco && Umbraco.Sys && Umbraco.Sys.ServerVariables && Umbraco.Sys.ServerVariables.umbracoPlugins && Umbraco.Sys.ServerVariables.umbracoPlugins.modelsBuilder && Umbraco.Sys.ServerVariables.umbracoPlugins.modelsBuilder.enabled === true); - - if (modelsBuilderEnabled && modelsResource) { - modelsResource.getModelsOutOfDateStatus().then(function(result) { - //Generate models buttons should be enabled if it is 0 - deferred.resolve(result.status === 0); - }); - } - else { - deferred.resolve(false); - } - return deferred.promise; - }, - - makeObjectArrayFromId: function (idArray, objectArray) { - var newArray = []; - - for (var idIndex = 0; idArray.length > idIndex; idIndex++) { - var id = idArray[idIndex]; - - for (var objectIndex = 0; objectArray.length > objectIndex; objectIndex++) { - var object = objectArray[objectIndex]; - if (id === object.id) { - newArray.push(object); - } - } - - } - - return newArray; - }, - - validateAddingComposition: function(contentType, compositeContentType) { - - //Validate that by adding this group that we are not adding duplicate property type aliases - - var propertiesAdding = _.flatten(_.map(compositeContentType.groups, function(g) { - return _.map(g.properties, function(p) { - return p.alias; - }); - })); - var propAliasesExisting = _.filter(_.flatten(_.map(contentType.groups, function(g) { - return _.map(g.properties, function(p) { - return p.alias; - }); - })), function(f) { - return f !== null && f !== undefined; - }); - - var intersec = _.intersection(propertiesAdding, propAliasesExisting); - if (intersec.length > 0) { - //return the overlapping property aliases - return intersec; - } - - //no overlapping property aliases - return []; - }, - - mergeCompositeContentType: function(contentType, compositeContentType) { - - //Validate that there are no overlapping aliases - var overlappingAliases = this.validateAddingComposition(contentType, compositeContentType); - if (overlappingAliases.length > 0) { - throw new Error("Cannot add this composition, these properties already exist on the content type: " + overlappingAliases.join()); - } - - angular.forEach(compositeContentType.groups, function(compositionGroup) { - - // order composition groups based on sort order - compositionGroup.properties = $filter('orderBy')(compositionGroup.properties, 'sortOrder'); - - // get data type details - angular.forEach(compositionGroup.properties, function(property) { - dataTypeResource.getById(property.dataTypeId) - .then(function(dataType) { - property.dataTypeIcon = dataType.icon; - property.dataTypeName = dataType.name; + return newArray; + }, + generateModels: function () { + var deferred = $q.defer(); + var modelsResource = $injector.has('modelsBuilderResource') ? $injector.get('modelsBuilderResource') : null; + var modelsBuilderEnabled = Umbraco.Sys.ServerVariables.umbracoPlugins.modelsBuilder.enabled; + if (modelsBuilderEnabled && modelsResource) { + modelsResource.buildModels().then(function (result) { + deferred.resolve(result); + //just calling this to get the servar back to life + modelsResource.getModelsOutOfDateStatus(); + }, function (e) { + deferred.reject(e); }); - }); - - // set inherited state on tab - compositionGroup.inherited = true; - - // set inherited state on properties - angular.forEach(compositionGroup.properties, function(compositionProperty) { - compositionProperty.inherited = true; - }); - - // set tab state - compositionGroup.tabState = "inActive"; - - // if groups are named the same - merge the groups - angular.forEach(contentType.groups, function(contentTypeGroup) { - - if (contentTypeGroup.name === compositionGroup.name) { - - // set flag to show if properties has been merged into a tab - compositionGroup.groupIsMerged = true; - - // make group inherited - contentTypeGroup.inherited = true; - - // add properties to the top of the array - contentTypeGroup.properties = compositionGroup.properties.concat(contentTypeGroup.properties); - - // update sort order on all properties in merged group - contentTypeGroup.properties = contentTypeHelperService.updatePropertiesSortOrder(contentTypeGroup.properties); - - // make parentTabContentTypeNames to an array so we can push values - if (contentTypeGroup.parentTabContentTypeNames === null || contentTypeGroup.parentTabContentTypeNames === undefined) { - contentTypeGroup.parentTabContentTypeNames = []; - } - - // push name to array of merged composite content types - contentTypeGroup.parentTabContentTypeNames.push(compositeContentType.name); - - // make parentTabContentTypes to an array so we can push values - if (contentTypeGroup.parentTabContentTypes === null || contentTypeGroup.parentTabContentTypes === undefined) { - contentTypeGroup.parentTabContentTypes = []; - } - - // push id to array of merged composite content types - contentTypeGroup.parentTabContentTypes.push(compositeContentType.id); - - // get sort order from composition - contentTypeGroup.sortOrder = compositionGroup.sortOrder; - - // splice group to the top of the array - var contentTypeGroupCopy = angular.copy(contentTypeGroup); - var index = contentType.groups.indexOf(contentTypeGroup); - contentType.groups.splice(index, 1); - contentType.groups.unshift(contentTypeGroupCopy); - - } - - }); - - // if group is not merged - push it to the end of the array - before init tab - if (compositionGroup.groupIsMerged === false || compositionGroup.groupIsMerged === undefined) { - - // make parentTabContentTypeNames to an array so we can push values - if (compositionGroup.parentTabContentTypeNames === null || compositionGroup.parentTabContentTypeNames === undefined) { - compositionGroup.parentTabContentTypeNames = []; - } - - // push name to array of merged composite content types - compositionGroup.parentTabContentTypeNames.push(compositeContentType.name); - - // make parentTabContentTypes to an array so we can push values - if (compositionGroup.parentTabContentTypes === null || compositionGroup.parentTabContentTypes === undefined) { - compositionGroup.parentTabContentTypes = []; - } - - // push id to array of merged composite content types - compositionGroup.parentTabContentTypes.push(compositeContentType.id); - - // push group before placeholder tab - contentType.groups.unshift(compositionGroup); - - } - - }); - - // sort all groups by sortOrder property - contentType.groups = $filter('orderBy')(contentType.groups, 'sortOrder'); - - return contentType; - - }, - - splitCompositeContentType: function (contentType, compositeContentType) { - - var groups = []; - - angular.forEach(contentType.groups, function(contentTypeGroup){ - - if( contentTypeGroup.tabState !== "init" ) { - - var idIndex = contentTypeGroup.parentTabContentTypes.indexOf(compositeContentType.id); - var nameIndex = contentTypeGroup.parentTabContentTypeNames.indexOf(compositeContentType.name); - var groupIndex = contentType.groups.indexOf(contentTypeGroup); - - - if( idIndex !== -1 ) { - - var properties = []; - - // remove all properties from composite content type - angular.forEach(contentTypeGroup.properties, function(property){ - if(property.contentTypeId !== compositeContentType.id) { - properties.push(property); - } - }); - - // set new properties array to properties - contentTypeGroup.properties = properties; - - // remove composite content type name and id from inherited arrays - contentTypeGroup.parentTabContentTypes.splice(idIndex, 1); - contentTypeGroup.parentTabContentTypeNames.splice(nameIndex, 1); - - // remove inherited state if there are no inherited properties - if(contentTypeGroup.parentTabContentTypes.length === 0) { - contentTypeGroup.inherited = false; + } else { + deferred.resolve(false); + } + return deferred.promise; + }, + checkModelsBuilderStatus: function () { + var deferred = $q.defer(); + var modelsResource = $injector.has('modelsBuilderResource') ? $injector.get('modelsBuilderResource') : null; + var modelsBuilderEnabled = Umbraco && Umbraco.Sys && Umbraco.Sys.ServerVariables && Umbraco.Sys.ServerVariables.umbracoPlugins && Umbraco.Sys.ServerVariables.umbracoPlugins.modelsBuilder && Umbraco.Sys.ServerVariables.umbracoPlugins.modelsBuilder.enabled === true; + if (modelsBuilderEnabled && modelsResource) { + modelsResource.getModelsOutOfDateStatus().then(function (result) { + //Generate models buttons should be enabled if it is 0 + deferred.resolve(result.status === 0); + }); + } else { + deferred.resolve(false); + } + return deferred.promise; + }, + makeObjectArrayFromId: function (idArray, objectArray) { + var newArray = []; + for (var idIndex = 0; idArray.length > idIndex; idIndex++) { + var id = idArray[idIndex]; + for (var objectIndex = 0; objectArray.length > objectIndex; objectIndex++) { + var object = objectArray[objectIndex]; + if (id === object.id) { + newArray.push(object); } - - // remove group if there are no properties left - if(contentTypeGroup.properties.length > 1) { - //contentType.groups.splice(groupIndex, 1); + } + } + return newArray; + }, + validateAddingComposition: function (contentType, compositeContentType) { + //Validate that by adding this group that we are not adding duplicate property type aliases + var propertiesAdding = _.flatten(_.map(compositeContentType.groups, function (g) { + return _.map(g.properties, function (p) { + return p.alias; + }); + })); + var propAliasesExisting = _.filter(_.flatten(_.map(contentType.groups, function (g) { + return _.map(g.properties, function (p) { + return p.alias; + }); + })), function (f) { + return f !== null && f !== undefined; + }); + var intersec = _.intersection(propertiesAdding, propAliasesExisting); + if (intersec.length > 0) { + //return the overlapping property aliases + return intersec; + } + //no overlapping property aliases + return []; + }, + mergeCompositeContentType: function (contentType, compositeContentType) { + //Validate that there are no overlapping aliases + var overlappingAliases = this.validateAddingComposition(contentType, compositeContentType); + if (overlappingAliases.length > 0) { + throw new Error('Cannot add this composition, these properties already exist on the content type: ' + overlappingAliases.join()); + } + angular.forEach(compositeContentType.groups, function (compositionGroup) { + // order composition groups based on sort order + compositionGroup.properties = $filter('orderBy')(compositionGroup.properties, 'sortOrder'); + // get data type details + angular.forEach(compositionGroup.properties, function (property) { + dataTypeResource.getById(property.dataTypeId).then(function (dataType) { + property.dataTypeIcon = dataType.icon; + property.dataTypeName = dataType.name; + }); + }); + // set inherited state on tab + compositionGroup.inherited = true; + // set inherited state on properties + angular.forEach(compositionGroup.properties, function (compositionProperty) { + compositionProperty.inherited = true; + }); + // set tab state + compositionGroup.tabState = 'inActive'; + // if groups are named the same - merge the groups + angular.forEach(contentType.groups, function (contentTypeGroup) { + if (contentTypeGroup.name === compositionGroup.name) { + // set flag to show if properties has been merged into a tab + compositionGroup.groupIsMerged = true; + // make group inherited + contentTypeGroup.inherited = true; + // add properties to the top of the array + contentTypeGroup.properties = compositionGroup.properties.concat(contentTypeGroup.properties); + // update sort order on all properties in merged group + contentTypeGroup.properties = contentTypeHelperService.updatePropertiesSortOrder(contentTypeGroup.properties); + // make parentTabContentTypeNames to an array so we can push values + if (contentTypeGroup.parentTabContentTypeNames === null || contentTypeGroup.parentTabContentTypeNames === undefined) { + contentTypeGroup.parentTabContentTypeNames = []; + } + // push name to array of merged composite content types + contentTypeGroup.parentTabContentTypeNames.push(compositeContentType.name); + // make parentTabContentTypes to an array so we can push values + if (contentTypeGroup.parentTabContentTypes === null || contentTypeGroup.parentTabContentTypes === undefined) { + contentTypeGroup.parentTabContentTypes = []; + } + // push id to array of merged composite content types + contentTypeGroup.parentTabContentTypes.push(compositeContentType.id); + // get sort order from composition + contentTypeGroup.sortOrder = compositionGroup.sortOrder; + // splice group to the top of the array + var contentTypeGroupCopy = angular.copy(contentTypeGroup); + var index = contentType.groups.indexOf(contentTypeGroup); + contentType.groups.splice(index, 1); + contentType.groups.unshift(contentTypeGroupCopy); + } + }); + // if group is not merged - push it to the end of the array - before init tab + if (compositionGroup.groupIsMerged === false || compositionGroup.groupIsMerged === undefined) { + // make parentTabContentTypeNames to an array so we can push values + if (compositionGroup.parentTabContentTypeNames === null || compositionGroup.parentTabContentTypeNames === undefined) { + compositionGroup.parentTabContentTypeNames = []; + } + // push name to array of merged composite content types + compositionGroup.parentTabContentTypeNames.push(compositeContentType.name); + // make parentTabContentTypes to an array so we can push values + if (compositionGroup.parentTabContentTypes === null || compositionGroup.parentTabContentTypes === undefined) { + compositionGroup.parentTabContentTypes = []; + } + // push id to array of merged composite content types + compositionGroup.parentTabContentTypes.push(compositeContentType.id); + // push group before placeholder tab + contentType.groups.unshift(compositionGroup); + } + }); + // sort all groups by sortOrder property + contentType.groups = $filter('orderBy')(contentType.groups, 'sortOrder'); + return contentType; + }, + splitCompositeContentType: function (contentType, compositeContentType) { + var groups = []; + angular.forEach(contentType.groups, function (contentTypeGroup) { + if (contentTypeGroup.tabState !== 'init') { + var idIndex = contentTypeGroup.parentTabContentTypes.indexOf(compositeContentType.id); + var nameIndex = contentTypeGroup.parentTabContentTypeNames.indexOf(compositeContentType.name); + var groupIndex = contentType.groups.indexOf(contentTypeGroup); + if (idIndex !== -1) { + var properties = []; + // remove all properties from composite content type + angular.forEach(contentTypeGroup.properties, function (property) { + if (property.contentTypeId !== compositeContentType.id) { + properties.push(property); + } + }); + // set new properties array to properties + contentTypeGroup.properties = properties; + // remove composite content type name and id from inherited arrays + contentTypeGroup.parentTabContentTypes.splice(idIndex, 1); + contentTypeGroup.parentTabContentTypeNames.splice(nameIndex, 1); + // remove inherited state if there are no inherited properties + if (contentTypeGroup.parentTabContentTypes.length === 0) { + contentTypeGroup.inherited = false; + } + // remove group if there are no properties left + if (contentTypeGroup.properties.length > 1) { + //contentType.groups.splice(groupIndex, 1); + groups.push(contentTypeGroup); + } + } else { groups.push(contentTypeGroup); } - } else { - groups.push(contentTypeGroup); + groups.push(contentTypeGroup); } - - } else { - groups.push(contentTypeGroup); - } - - // update sort order on properties - contentTypeGroup.properties = contentTypeHelperService.updatePropertiesSortOrder(contentTypeGroup.properties); - - }); - - contentType.groups = groups; - - }, - - updatePropertiesSortOrder: function (properties) { - - var sortOrder = 0; - - angular.forEach(properties, function(property) { - if( !property.inherited && property.propertyState !== "init") { - property.sortOrder = sortOrder; + // update sort order on properties + contentTypeGroup.properties = contentTypeHelperService.updatePropertiesSortOrder(contentTypeGroup.properties); + }); + contentType.groups = groups; + }, + updatePropertiesSortOrder: function (properties) { + var sortOrder = 0; + angular.forEach(properties, function (property) { + if (!property.inherited && property.propertyState !== 'init') { + property.sortOrder = sortOrder; + } + sortOrder++; + }); + return properties; + }, + getTemplatePlaceholder: function () { + var templatePlaceholder = { + 'name': '', + 'icon': 'icon-layout', + 'alias': 'templatePlaceholder', + 'placeholder': true + }; + return templatePlaceholder; + }, + insertDefaultTemplatePlaceholder: function (defaultTemplate) { + // get template placeholder + var templatePlaceholder = contentTypeHelperService.getTemplatePlaceholder(); + // add as default template + defaultTemplate = templatePlaceholder; + return defaultTemplate; + }, + insertTemplatePlaceholder: function (array) { + // get template placeholder + var templatePlaceholder = contentTypeHelperService.getTemplatePlaceholder(); + // add as selected item + array.push(templatePlaceholder); + return array; + }, + insertChildNodePlaceholder: function (array, name, icon, id) { + var placeholder = { + 'name': name, + 'icon': icon, + 'id': id + }; + array.push(placeholder); } - sortOrder++; - }); - - return properties; - - }, - - getTemplatePlaceholder: function() { - - var templatePlaceholder = { - "name": "", - "icon": "icon-layout", - "alias": "templatePlaceholder", - "placeholder": true - }; - - return templatePlaceholder; - - }, - - insertDefaultTemplatePlaceholder: function(defaultTemplate) { - - // get template placeholder - var templatePlaceholder = contentTypeHelperService.getTemplatePlaceholder(); - - // add as default template - defaultTemplate = templatePlaceholder; - - return defaultTemplate; - - }, - - insertTemplatePlaceholder: function(array) { - - // get template placeholder - var templatePlaceholder = contentTypeHelperService.getTemplatePlaceholder(); - - // add as selected item - array.push(templatePlaceholder); - - return array; - - }, - - insertChildNodePlaceholder: function (array, name, icon, id) { - - var placeholder = { - "name": name, - "icon": icon, - "id": id - }; - - array.push(placeholder); - - } - - }; - - return contentTypeHelperService; -} -angular.module('umbraco.services').factory('contentTypeHelper', contentTypeHelper); - -/** + }; + return contentTypeHelperService; + } + angular.module('umbraco.services').factory('contentTypeHelper', contentTypeHelper); + /** * @ngdoc service * @name umbraco.services.cropperHelper * @description A helper object used for dealing with image cropper data **/ -function cropperHelper(umbRequestHelper, $http) { - var service = { - - /** + function cropperHelper(umbRequestHelper, $http) { + var service = { + /** * @ngdoc method * @name umbraco.services.cropperHelper#configuration * @methodOf umbraco.services.cropperHelper @@ -1629,1375 +1619,1357 @@ function cropperHelper(umbRequestHelper, $http) { * Returns a collection of plugins available to the tinyMCE editor * */ - configuration: function (mediaTypeAlias) { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "imageCropperApiBaseUrl", - "GetConfiguration", - [{ mediaTypeAlias: mediaTypeAlias}])), - 'Failed to retrieve tinymce configuration'); - }, - - - //utill for getting either min/max aspect ratio to scale image after - calculateAspectRatioFit : function(srcWidth, srcHeight, maxWidth, maxHeight, maximize) { - var ratio = [maxWidth / srcWidth, maxHeight / srcHeight ]; - - if(maximize){ - ratio = Math.max(ratio[0], ratio[1]); - }else{ - ratio = Math.min(ratio[0], ratio[1]); - } - - return { width:srcWidth*ratio, height:srcHeight*ratio, ratio: ratio}; - }, - - //utill for scaling width / height given a ratio - calculateSizeToRatio : function(srcWidth, srcHeight, ratio) { - return { width:srcWidth*ratio, height:srcHeight*ratio, ratio: ratio}; - }, - - scaleToMaxSize : function(srcWidth, srcHeight, maxSize) { - - var retVal = {height: srcHeight, width: srcWidth}; - - if(srcWidth > maxSize ||srcHeight > maxSize){ - var ratio = [maxSize / srcWidth, maxSize / srcHeight ]; - ratio = Math.min(ratio[0], ratio[1]); - - retVal.height = srcHeight * ratio; - retVal.width = srcWidth * ratio; - } - - return retVal; - }, - - //returns a ng-style object with top,left,width,height pixel measurements - //expects {left,right,top,bottom} - {width,height}, {width,height}, int - //offset is just to push the image position a number of pixels from top,left - convertToStyle : function(coordinates, originalSize, viewPort, offset){ - - var coordinates_px = service.coordinatesToPixels(coordinates, originalSize, offset); - var _offset = offset || 0; - - var x = 1 - (coordinates.x1 + Math.abs(coordinates.x2)); - var left_of_x = originalSize.width * x; - var ratio = viewPort.width / left_of_x; - - var style = { - position: "absolute", - top: -(coordinates_px.y1*ratio)+ _offset, - left: -(coordinates_px.x1* ratio)+ _offset, - width: Math.floor(originalSize.width * ratio), - height: Math.floor(originalSize.height * ratio), - originalWidth: originalSize.width, - originalHeight: originalSize.height, - ratio: ratio - }; - - return style; - }, - - - coordinatesToPixels : function(coordinates, originalSize, offset){ - - var coordinates_px = { - x1: Math.floor(coordinates.x1 * originalSize.width), - y1: Math.floor(coordinates.y1 * originalSize.height), - x2: Math.floor(coordinates.x2 * originalSize.width), - y2: Math.floor(coordinates.y2 * originalSize.height) - }; - - return coordinates_px; - }, - - pixelsToCoordinates : function(image, width, height, offset){ - - var x1_px = Math.abs(image.left-offset); - var y1_px = Math.abs(image.top-offset); - - var x2_px = image.width - (x1_px + width); - var y2_px = image.height - (y1_px + height); - - - //crop coordinates in % - var crop = {}; - crop.x1 = x1_px / image.width; - crop.y1 = y1_px / image.height; - crop.x2 = x2_px / image.width; - crop.y2 = y2_px / image.height; - - for(var coord in crop){ - if(crop[coord] < 0){ - crop[coord] = 0; - } - } - - return crop; - }, - - centerInsideViewPort : function(img, viewport){ - var left = viewport.width/ 2 - img.width / 2, - top = viewport.height / 2 - img.height / 2; - - return {left: left, top: top}; - }, - - alignToCoordinates : function(image, center, viewport){ - - var min_left = (image.width) - (viewport.width); - var min_top = (image.height) - (viewport.height); - - var c_top = -(center.top * image.height) + (viewport.height / 2); - var c_left = -(center.left * image.width) + (viewport.width / 2); - - if(c_top < -min_top){ - c_top = -min_top; - } - if(c_top > 0){ - c_top = 0; - } - if(c_left < -min_left){ - c_left = -min_left; - } - if(c_left > 0){ - c_left = 0; - } - return {left: c_left, top: c_top}; - }, - - - syncElements : function(source, target){ - target.height(source.height()); - target.width(source.width()); - - target.css({ - "top": source[0].offsetTop, - "left": source[0].offsetLeft - }); - } - }; - - return service; -} - -angular.module('umbraco.services').factory('cropperHelper', cropperHelper); - -/** + configuration: function (mediaTypeAlias) { + return umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('imageCropperApiBaseUrl', 'GetConfiguration', [{ mediaTypeAlias: mediaTypeAlias }])), 'Failed to retrieve tinymce configuration'); + }, + //utill for getting either min/max aspect ratio to scale image after + calculateAspectRatioFit: function (srcWidth, srcHeight, maxWidth, maxHeight, maximize) { + var ratio = [ + maxWidth / srcWidth, + maxHeight / srcHeight + ]; + if (maximize) { + ratio = Math.max(ratio[0], ratio[1]); + } else { + ratio = Math.min(ratio[0], ratio[1]); + } + return { + width: srcWidth * ratio, + height: srcHeight * ratio, + ratio: ratio + }; + }, + //utill for scaling width / height given a ratio + calculateSizeToRatio: function (srcWidth, srcHeight, ratio) { + return { + width: srcWidth * ratio, + height: srcHeight * ratio, + ratio: ratio + }; + }, + scaleToMaxSize: function (srcWidth, srcHeight, maxSize) { + var retVal = { + height: srcHeight, + width: srcWidth + }; + if (srcWidth > maxSize || srcHeight > maxSize) { + var ratio = [ + maxSize / srcWidth, + maxSize / srcHeight + ]; + ratio = Math.min(ratio[0], ratio[1]); + retVal.height = srcHeight * ratio; + retVal.width = srcWidth * ratio; + } + return retVal; + }, + //returns a ng-style object with top,left,width,height pixel measurements + //expects {left,right,top,bottom} - {width,height}, {width,height}, int + //offset is just to push the image position a number of pixels from top,left + convertToStyle: function (coordinates, originalSize, viewPort, offset) { + var coordinates_px = service.coordinatesToPixels(coordinates, originalSize, offset); + var _offset = offset || 0; + var x = 1 - (coordinates.x1 + Math.abs(coordinates.x2)); + var left_of_x = originalSize.width * x; + var ratio = viewPort.width / left_of_x; + var style = { + position: 'absolute', + top: -(coordinates_px.y1 * ratio) + _offset, + left: -(coordinates_px.x1 * ratio) + _offset, + width: Math.floor(originalSize.width * ratio), + height: Math.floor(originalSize.height * ratio), + originalWidth: originalSize.width, + originalHeight: originalSize.height, + ratio: ratio + }; + return style; + }, + coordinatesToPixels: function (coordinates, originalSize, offset) { + var coordinates_px = { + x1: Math.floor(coordinates.x1 * originalSize.width), + y1: Math.floor(coordinates.y1 * originalSize.height), + x2: Math.floor(coordinates.x2 * originalSize.width), + y2: Math.floor(coordinates.y2 * originalSize.height) + }; + return coordinates_px; + }, + pixelsToCoordinates: function (image, width, height, offset) { + var x1_px = Math.abs(image.left - offset); + var y1_px = Math.abs(image.top - offset); + var x2_px = image.width - (x1_px + width); + var y2_px = image.height - (y1_px + height); + //crop coordinates in % + var crop = {}; + crop.x1 = x1_px / image.width; + crop.y1 = y1_px / image.height; + crop.x2 = x2_px / image.width; + crop.y2 = y2_px / image.height; + for (var coord in crop) { + if (crop[coord] < 0) { + crop[coord] = 0; + } + } + return crop; + }, + centerInsideViewPort: function (img, viewport) { + var left = viewport.width / 2 - img.width / 2, top = viewport.height / 2 - img.height / 2; + return { + left: left, + top: top + }; + }, + alignToCoordinates: function (image, center, viewport) { + var min_left = image.width - viewport.width; + var min_top = image.height - viewport.height; + var c_top = -(center.top * image.height) + viewport.height / 2; + var c_left = -(center.left * image.width) + viewport.width / 2; + if (c_top < -min_top) { + c_top = -min_top; + } + if (c_top > 0) { + c_top = 0; + } + if (c_left < -min_left) { + c_left = -min_left; + } + if (c_left > 0) { + c_left = 0; + } + return { + left: c_left, + top: c_top + }; + }, + syncElements: function (source, target) { + target.height(source.height()); + target.width(source.width()); + target.css({ + 'top': source[0].offsetTop, + 'left': source[0].offsetLeft + }); + } + }; + return service; + } + angular.module('umbraco.services').factory('cropperHelper', cropperHelper); + /** * @ngdoc service * @name umbraco.services.dataTypeHelper * @description A helper service for data types **/ -function dataTypeHelper() { - - var dataTypeHelperService = { - - createPreValueProps: function(preVals) { - - var preValues = []; - - for (var i = 0; i < preVals.length; i++) { - preValues.push({ - hideLabel: preVals[i].hideLabel, - alias: preVals[i].key, - description: preVals[i].description, - label: preVals[i].label, - view: preVals[i].view, - value: preVals[i].value - }); - } - - return preValues; - - }, - - rebindChangedProperties: function (origContent, savedContent) { - - //a method to ignore built-in prop changes - var shouldIgnore = function (propName) { - return _.some(["notifications", "ModelState"], function (i) { - return i === propName; - }); - }; - //check for changed built-in properties of the content - for (var o in origContent) { - - //ignore the ones listed in the array - if (shouldIgnore(o)) { - continue; + function dataTypeHelper() { + var dataTypeHelperService = { + createPreValueProps: function (preVals) { + var preValues = []; + for (var i = 0; i < preVals.length; i++) { + preValues.push({ + hideLabel: preVals[i].hideLabel, + alias: preVals[i].key, + description: preVals[i].description, + label: preVals[i].label, + view: preVals[i].view, + value: preVals[i].value + }); } - - if (!_.isEqual(origContent[o], savedContent[o])) { - origContent[o] = savedContent[o]; + return preValues; + }, + rebindChangedProperties: function (origContent, savedContent) { + //a method to ignore built-in prop changes + var shouldIgnore = function (propName) { + return _.some([ + 'notifications', + 'ModelState' + ], function (i) { + return i === propName; + }); + }; + //check for changed built-in properties of the content + for (var o in origContent) { + //ignore the ones listed in the array + if (shouldIgnore(o)) { + continue; + } + if (!_.isEqual(origContent[o], savedContent[o])) { + origContent[o] = savedContent[o]; + } + } + } + }; + return dataTypeHelperService; + } + angular.module('umbraco.services').factory('dataTypeHelper', dataTypeHelper); + /** + * @ngdoc service + * @name umbraco.services.dialogService + * + * @requires $rootScope + * @requires $compile + * @requires $http + * @requires $log + * @requires $q + * @requires $templateCache + * + * @description + * Application-wide service for handling modals, overlays and dialogs + * By default it injects the passed template url into a div to body of the document + * And renders it, but does also support rendering items in an iframe, incase + * serverside processing is needed, or its a non-angular page + * + * ##usage + * To use, simply inject the dialogService into any controller that needs it, and make + * sure the umbraco.services module is accesible - which it should be by default. + * + *
    + *    var dialog = dialogService.open({template: 'path/to/page.html', show: true, callback: done});
    + *    functon done(data){
    + *      //The dialog has been submitted
    + *      //data contains whatever the dialog has selected / attached
    + *    }
    + * 
    + */ + angular.module('umbraco.services').factory('dialogService', function ($rootScope, $compile, $http, $timeout, $q, $templateCache, appState, eventsService) { + var dialogs = []; + /** Internal method that removes all dialogs */ + function removeAllDialogs(args) { + for (var i = 0; i < dialogs.length; i++) { + var dialog = dialogs[i]; + //very special flag which means that global events cannot close this dialog - currently only used on the login + // dialog since it's special and cannot be closed without logging in. + if (!dialog.manualClose) { + dialog.close(args); } } } + /** Internal method that closes the dialog properly and cleans up resources */ + function closeDialog(dialog) { + if (dialog.element) { + dialog.element.modal('hide'); + //this is not entirely enough since the damn webforms scriploader still complains + if (dialog.iframe) { + dialog.element.find('iframe').attr('src', 'about:blank'); + } + dialog.scope.$destroy(); + //we need to do more than just remove the element, this will not destroy the + // scope in angular 1.1x, in angular 1.2x this is taken care of but if we dont + // take care of this ourselves we have memory leaks. + dialog.element.remove(); + //remove 'this' dialog from the dialogs array + dialogs = _.reject(dialogs, function (i) { + return i === dialog; + }); + } + } + /** Internal method that handles opening all dialogs */ + function openDialog(options) { + var defaults = { + container: $('body'), + animation: 'fade', + modalClass: 'umb-modal', + width: '100%', + inline: false, + iframe: false, + show: true, + template: 'views/common/notfound.html', + callback: undefined, + closeCallback: undefined, + element: undefined, + // It will set this value as a property on the dialog controller's scope as dialogData, + // used to pass in custom data to the dialog controller's $scope. Though this is near identical to + // the dialogOptions property that is also set the the dialog controller's $scope object. + // So there's basically 2 ways of doing the same thing which we're now stuck with and in fact + // dialogData has another specially attached property called .selection which gets used. + dialogData: undefined + }; + var dialog = angular.extend(defaults, options); + //NOTE: People should NOT pass in a scope object that is legacy functoinality and causes problems. We will ALWAYS + // destroy the scope when the dialog is closed regardless if it is in use elsewhere which is why it shouldn't be done. + var scope = options.scope || $rootScope.$new(); + //Modal dom obj and set id to old-dialog-service - used until we get all dialogs moved the the new overlay directive + dialog.element = $('
    '); + var id = 'old-dialog-service'; + if (options.inline) { + dialog.animation = ''; + } else { + dialog.element.addClass('modal'); + dialog.element.addClass('hide'); + } + //set the id and add classes + dialog.element.attr('id', id).addClass(dialog.animation).addClass(dialog.modalClass); + //push the modal into the global modal collection + //we halt the .push because a link click will trigger a closeAll right away + $timeout(function () { + dialogs.push(dialog); + }, 500); + dialog.close = function (data) { + if (dialog.closeCallback) { + dialog.closeCallback(data); + } + closeDialog(dialog); + }; + //if iframe is enabled, inject that instead of a template + if (dialog.iframe) { + var html = $(''); + dialog.element.html(html); + //append to body or whatever element is passed in as options.containerElement + dialog.container.append(dialog.element); + // Compile modal content + $timeout(function () { + $compile(dialog.element)(dialog.scope); + }); + dialog.element.css('width', dialog.width); + //Autoshow + if (dialog.show) { + dialog.element.modal('show'); + } + dialog.scope = scope; + return dialog; + } else { + //We need to load the template with an httpget and once it's loaded we'll compile and assign the result to the container + // object. However since the result could be a promise or just data we need to use a $q.when. We still need to return the + // $modal object so we'll actually return the modal object synchronously without waiting for the promise. Otherwise this openDialog + // method will always need to return a promise which gets nasty because of promises in promises plus the result just needs a reference + // to the $modal object which will not change (only it's contents will change). + $q.when($templateCache.get(dialog.template) || $http.get(dialog.template, { cache: true }).then(function (res) { + return res.data; + })).then(function onSuccess(template) { + // Build modal object + dialog.element.html(template); + //append to body or other container element + dialog.container.append(dialog.element); + // Compile modal content + $timeout(function () { + $compile(dialog.element)(scope); + }); + scope.dialogOptions = dialog; + //Scope to handle data from the modal form + scope.dialogData = dialog.dialogData ? dialog.dialogData : {}; + scope.dialogData.selection = []; + // Provide scope display functions + //this passes the modal to the current scope + scope.$modal = function (name) { + dialog.element.modal(name); + }; + scope.swipeHide = function (e) { + if (appState.getGlobalState('touchDevice')) { + var selection = window.getSelection(); + if (selection.type !== 'Range') { + scope.hide(); + } + } + }; + //NOTE: Same as 'close' without the callbacks + scope.hide = function () { + closeDialog(dialog); + }; + //basic events for submitting and closing + scope.submit = function (data) { + if (dialog.callback) { + dialog.callback(data); + } + closeDialog(dialog); + }; + scope.close = function (data) { + dialog.close(data); + }; + //NOTE: This can ONLY ever be used to show the dialog if dialog.show is false (autoshow). + // You CANNOT call show() after you call hide(). hide = close, they are the same thing and once + // a dialog is closed it's resources are disposed of. + scope.show = function () { + if (dialog.manualClose === true) { + //show and configure that the keyboard events are not enabled on this modal + dialog.element.modal({ keyboard: false }); + } else { + //just show normally + dialog.element.modal('show'); + } + }; + scope.select = function (item) { + var i = scope.dialogData.selection.indexOf(item); + if (i < 0) { + scope.dialogData.selection.push(item); + } else { + scope.dialogData.selection.splice(i, 1); + } + }; + //NOTE: Same as 'close' without the callbacks + scope.dismiss = scope.hide; + // Emit modal events + angular.forEach([ + 'show', + 'shown', + 'hide', + 'hidden' + ], function (name) { + dialog.element.on(name, function (ev) { + scope.$emit('modal-' + name, ev); + }); + }); + // Support autofocus attribute + dialog.element.on('shown', function (event) { + $('input[autofocus]', dialog.element).first().trigger('focus'); + }); + dialog.scope = scope; + //Autoshow + if (dialog.show) { + scope.show(); + } + }); + //Return the modal object outside of the promise! + return dialog; + } + } + /** Handles the closeDialogs event */ + eventsService.on('app.closeDialogs', function (evt, args) { + removeAllDialogs(args); + }); + return { + /** + * @ngdoc method + * @name umbraco.services.dialogService#open + * @methodOf umbraco.services.dialogService + * + * @description + * Opens a modal rendering a given template url. + * + * @param {Object} options rendering options + * @param {DomElement} options.container the DOM element to inject the modal into, by default set to body + * @param {Function} options.callback function called when the modal is submitted + * @param {String} options.template the url of the template + * @param {String} options.animation animation csss class, by default set to "fade" + * @param {String} options.modalClass modal css class, by default "umb-modal" + * @param {Bool} options.show show the modal instantly + * @param {Bool} options.iframe load template in an iframe, only needed for serverside templates + * @param {Int} options.width set a width on the modal, only needed for iframes + * @param {Bool} options.inline strips the modal from any animation and wrappers, used when you want to inject a dialog into an existing container + * @returns {Object} modal object + */ + open: function (options) { + return openDialog(options); + }, + /** + * @ngdoc method + * @name umbraco.services.dialogService#close + * @methodOf umbraco.services.dialogService + * + * @description + * Closes a specific dialog + * @param {Object} dialog the dialog object to close + * @param {Object} args if specified this object will be sent to any callbacks registered on the dialogs. + */ + close: function (dialog, args) { + if (dialog) { + dialog.close(args); + } + }, + /** + * @ngdoc method + * @name umbraco.services.dialogService#closeAll + * @methodOf umbraco.services.dialogService + * + * @description + * Closes all dialogs + * @param {Object} args if specified this object will be sent to any callbacks registered on the dialogs. + */ + closeAll: function (args) { + removeAllDialogs(args); + }, + /** + * @ngdoc method + * @name umbraco.services.dialogService#mediaPicker + * @methodOf umbraco.services.dialogService + * + * @description + * Opens a media picker in a modal, the callback returns an array of selected media items + * @param {Object} options mediapicker dialog options object + * @param {Boolean} options.onlyImages Only display files that have an image file-extension + * @param {Function} options.callback callback function + * @returns {Object} modal object + */ + mediaPicker: function (options) { + options.template = 'views/common/dialogs/mediaPicker.html'; + options.show = true; + return openDialog(options); + }, + /** + * @ngdoc method + * @name umbraco.services.dialogService#contentPicker + * @methodOf umbraco.services.dialogService + * + * @description + * Opens a content picker tree in a modal, the callback returns an array of selected documents + * @param {Object} options content picker dialog options object + * @param {Boolean} options.multiPicker should the picker return one or multiple items + * @param {Function} options.callback callback function + * @returns {Object} modal object + */ + contentPicker: function (options) { + options.treeAlias = 'content'; + options.section = 'content'; + return this.treePicker(options); + }, + /** + * @ngdoc method + * @name umbraco.services.dialogService#linkPicker + * @methodOf umbraco.services.dialogService + * + * @description + * Opens a link picker tree in a modal, the callback returns a single link + * @param {Object} options content picker dialog options object + * @param {Function} options.callback callback function + * @returns {Object} modal object + */ + linkPicker: function (options) { + options.template = 'views/common/dialogs/linkPicker.html'; + options.show = true; + return openDialog(options); + }, + /** + * @ngdoc method + * @name umbraco.services.dialogService#macroPicker + * @methodOf umbraco.services.dialogService + * + * @description + * Opens a mcaro picker in a modal, the callback returns a object representing the macro and it's parameters + * @param {Object} options macropicker dialog options object + * @param {Function} options.callback callback function + * @returns {Object} modal object + */ + macroPicker: function (options) { + options.template = 'views/common/dialogs/insertmacro.html'; + options.show = true; + options.modalClass = 'span7 umb-modal'; + return openDialog(options); + }, + /** + * @ngdoc method + * @name umbraco.services.dialogService#memberPicker + * @methodOf umbraco.services.dialogService + * + * @description + * Opens a member picker in a modal, the callback returns a object representing the selected member + * @param {Object} options member picker dialog options object + * @param {Boolean} options.multiPicker should the tree pick one or multiple members before returning + * @param {Function} options.callback callback function + * @returns {Object} modal object + */ + memberPicker: function (options) { + options.treeAlias = 'member'; + options.section = 'member'; + return this.treePicker(options); + }, + /** + * @ngdoc method + * @name umbraco.services.dialogService#memberGroupPicker + * @methodOf umbraco.services.dialogService + * + * @description + * Opens a member group picker in a modal, the callback returns a object representing the selected member + * @param {Object} options member group picker dialog options object + * @param {Boolean} options.multiPicker should the tree pick one or multiple members before returning + * @param {Function} options.callback callback function + * @returns {Object} modal object + */ + memberGroupPicker: function (options) { + options.template = 'views/common/dialogs/memberGroupPicker.html'; + options.show = true; + return openDialog(options); + }, + /** + * @ngdoc method + * @name umbraco.services.dialogService#iconPicker + * @methodOf umbraco.services.dialogService + * + * @description + * Opens a icon picker in a modal, the callback returns a object representing the selected icon + * @param {Object} options iconpicker dialog options object + * @param {Function} options.callback callback function + * @returns {Object} modal object + */ + iconPicker: function (options) { + options.template = 'views/common/dialogs/iconPicker.html'; + options.show = true; + return openDialog(options); + }, + /** + * @ngdoc method + * @name umbraco.services.dialogService#treePicker + * @methodOf umbraco.services.dialogService + * + * @description + * Opens a tree picker in a modal, the callback returns a object representing the selected tree item + * @param {Object} options iconpicker dialog options object + * @param {String} options.section tree section to display + * @param {String} options.treeAlias specific tree to display + * @param {Boolean} options.multiPicker should the tree pick one or multiple items before returning + * @param {Function} options.callback callback function + * @returns {Object} modal object + */ + treePicker: function (options) { + options.template = 'views/common/dialogs/treePicker.html'; + options.show = true; + return openDialog(options); + }, + /** + * @ngdoc method + * @name umbraco.services.dialogService#propertyDialog + * @methodOf umbraco.services.dialogService + * + * @description + * Opens a dialog with a chosen property editor in, a value can be passed to the modal, and this value is returned in the callback + * @param {Object} options mediapicker dialog options object + * @param {Function} options.callback callback function + * @param {String} editor editor to use to edit a given value and return on callback + * @param {Object} value value sent to the property editor + * @returns {Object} modal object + */ + //TODO: Wtf does this do? I don't think anything! + propertyDialog: function (options) { + options.template = 'views/common/dialogs/property.html'; + options.show = true; + return openDialog(options); + }, + /** + * @ngdoc method + * @name umbraco.services.dialogService#embedDialog + * @methodOf umbraco.services.dialogService + * @description + * Opens a dialog to an embed dialog + */ + embedDialog: function (options) { + options.template = 'views/common/dialogs/rteembed.html'; + options.show = true; + return openDialog(options); + }, + /** + * @ngdoc method + * @name umbraco.services.dialogService#ysodDialog + * @methodOf umbraco.services.dialogService + * + * @description + * Opens a dialog to show a custom YSOD + */ + ysodDialog: function (ysodError) { + var newScope = $rootScope.$new(); + newScope.error = ysodError; + return openDialog({ + modalClass: 'umb-modal wide ysod', + scope: newScope, + //callback: options.callback, + template: 'views/common/dialogs/ysod.html', + show: true + }); + }, + confirmDialog: function (ysodError) { + options.template = 'views/common/dialogs/confirm.html'; + options.show = true; + return openDialog(options); + } + }; + }); + (function () { + 'use strict'; + function entityHelper() { + function getEntityTypeFromSection(section) { + if (section === 'member') { + return 'Member'; + } else if (section === 'media') { + return 'Media'; + } else { + return 'Document'; + } + } + //////////// + var service = { getEntityTypeFromSection: getEntityTypeFromSection }; + return service; + } + angular.module('umbraco.services').factory('entityHelper', entityHelper); + }()); + /** Used to broadcast and listen for global events and allow the ability to add async listeners to the callbacks */ + /* + Core app events: - }; - - return dataTypeHelperService; -} -angular.module('umbraco.services').factory('dataTypeHelper', dataTypeHelper); -/** - * @ngdoc service - * @name umbraco.services.dialogService - * - * @requires $rootScope - * @requires $compile - * @requires $http - * @requires $log - * @requires $q - * @requires $templateCache - * - * @description - * Application-wide service for handling modals, overlays and dialogs - * By default it injects the passed template url into a div to body of the document - * And renders it, but does also support rendering items in an iframe, incase - * serverside processing is needed, or its a non-angular page - * - * ##usage - * To use, simply inject the dialogService into any controller that needs it, and make - * sure the umbraco.services module is accesible - which it should be by default. - * - *
    - *    var dialog = dialogService.open({template: 'path/to/page.html', show: true, callback: done});
    - *    functon done(data){
    - *      //The dialog has been submitted
    - *      //data contains whatever the dialog has selected / attached
    - *    }
    - * 
    - */ - -angular.module('umbraco.services') -.factory('dialogService', function ($rootScope, $compile, $http, $timeout, $q, $templateCache, appState, eventsService) { - - var dialogs = []; - - /** Internal method that removes all dialogs */ - function removeAllDialogs(args) { - for (var i = 0; i < dialogs.length; i++) { - var dialog = dialogs[i]; - - //very special flag which means that global events cannot close this dialog - currently only used on the login - // dialog since it's special and cannot be closed without logging in. - if (!dialog.manualClose) { - dialog.close(args); - } - - } - } - - /** Internal method that closes the dialog properly and cleans up resources */ - function closeDialog(dialog) { - - if (dialog.element) { - dialog.element.modal('hide'); - - //this is not entirely enough since the damn webforms scriploader still complains - if (dialog.iframe) { - dialog.element.find("iframe").attr("src", "about:blank"); - } - - dialog.scope.$destroy(); - - //we need to do more than just remove the element, this will not destroy the - // scope in angular 1.1x, in angular 1.2x this is taken care of but if we dont - // take care of this ourselves we have memory leaks. - dialog.element.remove(); - - //remove 'this' dialog from the dialogs array - dialogs = _.reject(dialogs, function (i) { return i === dialog; }); - } - } - - /** Internal method that handles opening all dialogs */ - function openDialog(options) { - var defaults = { - container: $("body"), - animation: "fade", - modalClass: "umb-modal", - width: "100%", - inline: false, - iframe: false, - show: true, - template: "views/common/notfound.html", - callback: undefined, - closeCallback: undefined, - element: undefined, - // It will set this value as a property on the dialog controller's scope as dialogData, - // used to pass in custom data to the dialog controller's $scope. Though this is near identical to - // the dialogOptions property that is also set the the dialog controller's $scope object. - // So there's basically 2 ways of doing the same thing which we're now stuck with and in fact - // dialogData has another specially attached property called .selection which gets used. - dialogData: undefined - }; - - var dialog = angular.extend(defaults, options); - - //NOTE: People should NOT pass in a scope object that is legacy functoinality and causes problems. We will ALWAYS - // destroy the scope when the dialog is closed regardless if it is in use elsewhere which is why it shouldn't be done. - var scope = options.scope || $rootScope.$new(); - - //Modal dom obj and set id to old-dialog-service - used until we get all dialogs moved the the new overlay directive - dialog.element = $('
    '); - var id = "old-dialog-service"; - - if (options.inline) { - dialog.animation = ""; - } - else { - dialog.element.addClass("modal"); - dialog.element.addClass("hide"); - } - - //set the id and add classes - dialog.element - .attr('id', id) - .addClass(dialog.animation) - .addClass(dialog.modalClass); - - //push the modal into the global modal collection - //we halt the .push because a link click will trigger a closeAll right away - $timeout(function () { - dialogs.push(dialog); - }, 500); - - - dialog.close = function (data) { - if (dialog.closeCallback) { - dialog.closeCallback(data); - } - - closeDialog(dialog); - }; - - //if iframe is enabled, inject that instead of a template - if (dialog.iframe) { - var html = $(""); - dialog.element.html(html); - - //append to body or whatever element is passed in as options.containerElement - dialog.container.append(dialog.element); - - // Compile modal content - $timeout(function () { - $compile(dialog.element)(dialog.scope); - }); - - dialog.element.css("width", dialog.width); - - //Autoshow - if (dialog.show) { - dialog.element.modal('show'); - } - - dialog.scope = scope; - return dialog; - } - else { - - //We need to load the template with an httpget and once it's loaded we'll compile and assign the result to the container - // object. However since the result could be a promise or just data we need to use a $q.when. We still need to return the - // $modal object so we'll actually return the modal object synchronously without waiting for the promise. Otherwise this openDialog - // method will always need to return a promise which gets nasty because of promises in promises plus the result just needs a reference - // to the $modal object which will not change (only it's contents will change). - $q.when($templateCache.get(dialog.template) || $http.get(dialog.template, { cache: true }).then(function (res) { return res.data; })) - .then(function onSuccess(template) { - - // Build modal object - dialog.element.html(template); - - //append to body or other container element - dialog.container.append(dialog.element); - - // Compile modal content - $timeout(function () { - $compile(dialog.element)(scope); - }); - - scope.dialogOptions = dialog; - - //Scope to handle data from the modal form - scope.dialogData = dialog.dialogData ? dialog.dialogData : {}; - scope.dialogData.selection = []; - - // Provide scope display functions - //this passes the modal to the current scope - scope.$modal = function (name) { - dialog.element.modal(name); - }; - - scope.swipeHide = function (e) { - - if (appState.getGlobalState("touchDevice")) { - var selection = window.getSelection(); - if (selection.type !== "Range") { - scope.hide(); - } - } - }; - - //NOTE: Same as 'close' without the callbacks - scope.hide = function () { - closeDialog(dialog); - }; - - //basic events for submitting and closing - scope.submit = function (data) { - if (dialog.callback) { - dialog.callback(data); - } - - closeDialog(dialog); - }; - - scope.close = function (data) { - dialog.close(data); - }; - - //NOTE: This can ONLY ever be used to show the dialog if dialog.show is false (autoshow). - // You CANNOT call show() after you call hide(). hide = close, they are the same thing and once - // a dialog is closed it's resources are disposed of. - scope.show = function () { - if (dialog.manualClose === true) { - //show and configure that the keyboard events are not enabled on this modal - dialog.element.modal({ keyboard: false }); - } - else { - //just show normally - dialog.element.modal('show'); - } - - }; - - scope.select = function (item) { - var i = scope.dialogData.selection.indexOf(item); - if (i < 0) { - scope.dialogData.selection.push(item); - } else { - scope.dialogData.selection.splice(i, 1); - } - }; - - //NOTE: Same as 'close' without the callbacks - scope.dismiss = scope.hide; - - // Emit modal events - angular.forEach(['show', 'shown', 'hide', 'hidden'], function (name) { - dialog.element.on(name, function (ev) { - scope.$emit('modal-' + name, ev); - }); - }); - - // Support autofocus attribute - dialog.element.on('shown', function (event) { - $('input[autofocus]', dialog.element).first().trigger('focus'); - }); - - dialog.scope = scope; - - //Autoshow - if (dialog.show) { - scope.show(); - } - - }); - - //Return the modal object outside of the promise! - return dialog; - } - } - - /** Handles the closeDialogs event */ - eventsService.on("app.closeDialogs", function (evt, args) { - removeAllDialogs(args); - }); - - return { - /** - * @ngdoc method - * @name umbraco.services.dialogService#open - * @methodOf umbraco.services.dialogService - * - * @description - * Opens a modal rendering a given template url. - * - * @param {Object} options rendering options - * @param {DomElement} options.container the DOM element to inject the modal into, by default set to body - * @param {Function} options.callback function called when the modal is submitted - * @param {String} options.template the url of the template - * @param {String} options.animation animation csss class, by default set to "fade" - * @param {String} options.modalClass modal css class, by default "umb-modal" - * @param {Bool} options.show show the modal instantly - * @param {Bool} options.iframe load template in an iframe, only needed for serverside templates - * @param {Int} options.width set a width on the modal, only needed for iframes - * @param {Bool} options.inline strips the modal from any animation and wrappers, used when you want to inject a dialog into an existing container - * @returns {Object} modal object - */ - open: function (options) { - return openDialog(options); - }, - - /** - * @ngdoc method - * @name umbraco.services.dialogService#close - * @methodOf umbraco.services.dialogService - * - * @description - * Closes a specific dialog - * @param {Object} dialog the dialog object to close - * @param {Object} args if specified this object will be sent to any callbacks registered on the dialogs. - */ - close: function (dialog, args) { - if (dialog) { - dialog.close(args); - } - }, - - /** - * @ngdoc method - * @name umbraco.services.dialogService#closeAll - * @methodOf umbraco.services.dialogService - * - * @description - * Closes all dialogs - * @param {Object} args if specified this object will be sent to any callbacks registered on the dialogs. - */ - closeAll: function (args) { - removeAllDialogs(args); - }, - - /** - * @ngdoc method - * @name umbraco.services.dialogService#mediaPicker - * @methodOf umbraco.services.dialogService - * - * @description - * Opens a media picker in a modal, the callback returns an array of selected media items - * @param {Object} options mediapicker dialog options object - * @param {Boolean} options.onlyImages Only display files that have an image file-extension - * @param {Function} options.callback callback function - * @returns {Object} modal object - */ - mediaPicker: function (options) { - options.template = 'views/common/dialogs/mediaPicker.html'; - options.show = true; - return openDialog(options); - }, - - - /** - * @ngdoc method - * @name umbraco.services.dialogService#contentPicker - * @methodOf umbraco.services.dialogService - * - * @description - * Opens a content picker tree in a modal, the callback returns an array of selected documents - * @param {Object} options content picker dialog options object - * @param {Boolean} options.multiPicker should the picker return one or multiple items - * @param {Function} options.callback callback function - * @returns {Object} modal object - */ - contentPicker: function (options) { - - options.treeAlias = "content"; - options.section = "content"; - - return this.treePicker(options); - }, - - /** - * @ngdoc method - * @name umbraco.services.dialogService#linkPicker - * @methodOf umbraco.services.dialogService - * - * @description - * Opens a link picker tree in a modal, the callback returns a single link - * @param {Object} options content picker dialog options object - * @param {Function} options.callback callback function - * @returns {Object} modal object - */ - linkPicker: function (options) { - options.template = 'views/common/dialogs/linkPicker.html'; - options.show = true; - return openDialog(options); - }, - - /** - * @ngdoc method - * @name umbraco.services.dialogService#macroPicker - * @methodOf umbraco.services.dialogService - * - * @description - * Opens a mcaro picker in a modal, the callback returns a object representing the macro and it's parameters - * @param {Object} options macropicker dialog options object - * @param {Function} options.callback callback function - * @returns {Object} modal object - */ - macroPicker: function (options) { - options.template = 'views/common/dialogs/insertmacro.html'; - options.show = true; - options.modalClass = "span7 umb-modal"; - return openDialog(options); - }, - - /** - * @ngdoc method - * @name umbraco.services.dialogService#memberPicker - * @methodOf umbraco.services.dialogService - * - * @description - * Opens a member picker in a modal, the callback returns a object representing the selected member - * @param {Object} options member picker dialog options object - * @param {Boolean} options.multiPicker should the tree pick one or multiple members before returning - * @param {Function} options.callback callback function - * @returns {Object} modal object - */ - memberPicker: function (options) { - - options.treeAlias = "member"; - options.section = "member"; - - return this.treePicker(options); - }, - - /** - * @ngdoc method - * @name umbraco.services.dialogService#memberGroupPicker - * @methodOf umbraco.services.dialogService - * - * @description - * Opens a member group picker in a modal, the callback returns a object representing the selected member - * @param {Object} options member group picker dialog options object - * @param {Boolean} options.multiPicker should the tree pick one or multiple members before returning - * @param {Function} options.callback callback function - * @returns {Object} modal object - */ - memberGroupPicker: function (options) { - options.template = 'views/common/dialogs/memberGroupPicker.html'; - options.show = true; - return openDialog(options); - }, - - /** - * @ngdoc method - * @name umbraco.services.dialogService#iconPicker - * @methodOf umbraco.services.dialogService - * - * @description - * Opens a icon picker in a modal, the callback returns a object representing the selected icon - * @param {Object} options iconpicker dialog options object - * @param {Function} options.callback callback function - * @returns {Object} modal object - */ - iconPicker: function (options) { - options.template = 'views/common/dialogs/iconPicker.html'; - options.show = true; - return openDialog(options); - }, - - /** - * @ngdoc method - * @name umbraco.services.dialogService#treePicker - * @methodOf umbraco.services.dialogService - * - * @description - * Opens a tree picker in a modal, the callback returns a object representing the selected tree item - * @param {Object} options iconpicker dialog options object - * @param {String} options.section tree section to display - * @param {String} options.treeAlias specific tree to display - * @param {Boolean} options.multiPicker should the tree pick one or multiple items before returning - * @param {Function} options.callback callback function - * @returns {Object} modal object - */ - treePicker: function (options) { - options.template = 'views/common/dialogs/treePicker.html'; - options.show = true; - return openDialog(options); - }, - - /** - * @ngdoc method - * @name umbraco.services.dialogService#propertyDialog - * @methodOf umbraco.services.dialogService - * - * @description - * Opens a dialog with a chosen property editor in, a value can be passed to the modal, and this value is returned in the callback - * @param {Object} options mediapicker dialog options object - * @param {Function} options.callback callback function - * @param {String} editor editor to use to edit a given value and return on callback - * @param {Object} value value sent to the property editor - * @returns {Object} modal object - */ - //TODO: Wtf does this do? I don't think anything! - propertyDialog: function (options) { - options.template = 'views/common/dialogs/property.html'; - options.show = true; - return openDialog(options); - }, - - /** - * @ngdoc method - * @name umbraco.services.dialogService#embedDialog - * @methodOf umbraco.services.dialogService - * @description - * Opens a dialog to an embed dialog - */ - embedDialog: function (options) { - options.template = 'views/common/dialogs/rteembed.html'; - options.show = true; - return openDialog(options); - }, - /** - * @ngdoc method - * @name umbraco.services.dialogService#ysodDialog - * @methodOf umbraco.services.dialogService - * - * @description - * Opens a dialog to show a custom YSOD - */ - ysodDialog: function (ysodError) { - - var newScope = $rootScope.$new(); - newScope.error = ysodError; - return openDialog({ - modalClass: "umb-modal wide ysod", - scope: newScope, - //callback: options.callback, - template: 'views/common/dialogs/ysod.html', - show: true - }); - }, - - confirmDialog: function (ysodError) { - - options.template = 'views/common/dialogs/confirm.html'; - options.show = true; - return openDialog(options); - } - }; -}); - -/** Used to broadcast and listen for global events and allow the ability to add async listeners to the callbacks */ - -/* - Core app events: - - app.ready - app.authenticated - app.notAuthenticated - app.closeDialogs -*/ - -function eventsService($q, $rootScope) { - - return { - - /** raise an event with a given name, returns an array of promises for each listener */ - emit: function (name, args) { - - //there are no listeners - if (!$rootScope.$$listeners[name]) { - return; - //return []; - } - - //send the event - $rootScope.$emit(name, args); - - - //PP: I've commented out the below, since we currently dont - // expose the eventsService as a documented api - // and think we need to figure out our usecases for this - // since the below modifies the return value of the then on() method - /* - //setup a deferred promise for each listener - var deferred = []; - for (var i = 0; i < $rootScope.$$listeners[name].length; i++) { - deferred.push($q.defer()); - }*/ - - //create a new event args object to pass to the - // $emit containing methods that will allow listeners - // to return data in an async if required - /* - var eventArgs = { - args: args, - reject: function (a) { - deferred.pop().reject(a); - }, - resolve: function (a) { - deferred.pop().resolve(a); - } - };*/ - - - - /* - //return an array of promises - var promises = _.map(deferred, function(p) { - return p.promise; - }); - return promises;*/ - }, - - /** subscribe to a method, or use scope.$on = same thing */ - on: function(name, callback) { - return $rootScope.$on(name, callback); - }, - - /** pass in the result of 'on' to this method, or just call the method returned from 'on' to unsubscribe */ - unsubscribe: function(handle) { - if (angular.isFunction(handle)) { - handle(); - } - } - }; -} - -angular.module('umbraco.services').factory('eventsService', eventsService); -/** - * @ngdoc service - * @name umbraco.services.fileManager - * @function - * - * @description - * Used by editors to manage any files that require uploading with the posted data, normally called by property editors - * that need to attach files. - * When a route changes successfully, we ensure that the collection is cleared. - */ -function fileManager() { - - var fileCollection = []; - - return { - /** - * @ngdoc function - * @name umbraco.services.fileManager#addFiles - * @methodOf umbraco.services.fileManager - * @function - * - * @description - * Attaches files to the current manager for the current editor for a particular property, if an empty array is set - * for the files collection that effectively clears the files for the specified editor. - */ - setFiles: function(propertyAlias, files) { - //this will clear the files for the current property and then add the new ones for the current property - fileCollection = _.reject(fileCollection, function (item) { - return item.alias === propertyAlias; - }); - for (var i = 0; i < files.length; i++) { - //save the file object to the files collection - fileCollection.push({ alias: propertyAlias, file: files[i] }); - } - }, - - /** - * @ngdoc function - * @name umbraco.services.fileManager#getFiles - * @methodOf umbraco.services.fileManager - * @function - * - * @description - * Returns all of the files attached to the file manager - */ - getFiles: function() { - return fileCollection; - }, - - /** - * @ngdoc function - * @name umbraco.services.fileManager#clearFiles - * @methodOf umbraco.services.fileManager - * @function - * - * @description - * Removes all files from the manager - */ - clearFiles: function () { - fileCollection = []; - } -}; -} - -angular.module('umbraco.services').factory('fileManager', fileManager); -/** - * @ngdoc service - * @name umbraco.services.formHelper - * @function - * - * @description - * A utility class used to streamline how forms are developed, to ensure that validation is check and displayed consistently and to ensure that the correct events - * fire when they need to. - */ -function formHelper(angularHelper, serverValidationManager, $timeout, notificationsService, dialogService, localizationService) { - return { - - /** - * @ngdoc function - * @name umbraco.services.formHelper#submitForm - * @methodOf umbraco.services.formHelper - * @function - * - * @description - * Called by controllers when submitting a form - this ensures that all client validation is checked, - * server validation is cleared, that the correct events execute and status messages are displayed. - * This returns true if the form is valid, otherwise false if form submission cannot continue. - * - * @param {object} args An object containing arguments for form submission - */ - submitForm: function (args) { - - var currentForm; - - if (!args) { - throw "args cannot be null"; - } - if (!args.scope) { - throw "args.scope cannot be null"; - } - if (!args.formCtrl) { - //try to get the closest form controller - currentForm = angularHelper.getRequiredCurrentForm(args.scope); - } - else { - currentForm = args.formCtrl; - } - //if no statusPropertyName is set we'll default to formStatus. - if (!args.statusPropertyName) { - args.statusPropertyName = "formStatus"; - } - //if no statusTimeout is set, we'll default to 2500 ms - if (!args.statusTimeout) { - args.statusTimeout = 2500; - } - - //the first thing any form must do is broadcast the formSubmitting event - args.scope.$broadcast("formSubmitting", { scope: args.scope, action: args.action }); - - //then check if the form is valid - if (!args.skipValidation) { - if (currentForm.$invalid) { - return false; - } - } - - //reset the server validations - serverValidationManager.reset(); - - //check if a form status should be set on the scope - if (args.statusMessage) { - args.scope[args.statusPropertyName] = args.statusMessage; - - //clear the message after the timeout - $timeout(function () { - args.scope[args.statusPropertyName] = undefined; - }, args.statusTimeout); - } - - return true; - }, - - /** - * @ngdoc function - * @name umbraco.services.formHelper#submitForm - * @methodOf umbraco.services.formHelper - * @function - * - * @description - * Called by controllers when a form has been successfully submitted. the correct events execute - * and that the notifications are displayed if there are any. - * - * @param {object} args An object containing arguments for form submission - */ - resetForm: function (args) { - if (!args) { - throw "args cannot be null"; - } - if (!args.scope) { - throw "args.scope cannot be null"; - } - - //if no statusPropertyName is set we'll default to formStatus. - if (!args.statusPropertyName) { - args.statusPropertyName = "formStatus"; - } - //clear the status - args.scope[args.statusPropertyName] = null; - - if (angular.isArray(args.notifications)) { - for (var i = 0; i < args.notifications.length; i++) { - notificationsService.showNotification(args.notifications[i]); - } - } - - args.scope.$broadcast("formSubmitted", { scope: args.scope }); - }, - - /** - * @ngdoc function - * @name umbraco.services.formHelper#handleError - * @methodOf umbraco.services.formHelper - * @function - * - * @description - * Needs to be called when a form submission fails, this will wire up all server validation errors in ModelState and - * add the correct messages to the notifications. If a server error has occurred this will show a ysod. - * - * @param {object} err The error object returned from the http promise - */ - handleError: function (err) { - //When the status is a 400 status with a custom header: X-Status-Reason: Validation failed, we have validation errors. - //Otherwise the error is probably due to invalid data (i.e. someone mucking around with the ids or something). - //Or, some strange server error - if (err.status === 400) { - //now we need to look through all the validation errors - if (err.data && (err.data.ModelState)) { - - //wire up the server validation errs - this.handleServerValidation(err.data.ModelState); - - //execute all server validation events and subscribers - serverValidationManager.executeAndClearAllSubscriptions(); - } - else { - dialogService.ysodDialog(err); - } - } - else { - dialogService.ysodDialog(err); - } - }, - - /** - * @ngdoc function - * @name umbraco.services.formHelper#handleServerValidation - * @methodOf umbraco.services.formHelper - * @function - * - * @description - * This wires up all of the server validation model state so that valServer and valServerField directives work - * - * @param {object} err The error object returned from the http promise - */ - handleServerValidation: function (modelState) { - for (var e in modelState) { - - //This is where things get interesting.... - // We need to support validation for all editor types such as both the content and content type editors. - // The Content editor ModelState is quite specific with the way that Properties are validated especially considering - // that each property is a User Developer property editor. - // The way that Content Type Editor ModelState is created is simply based on the ASP.Net validation data-annotations - // system. - // So, to do this (since we need to support backwards compat), we need to hack a little bit. For Content Properties, - // which are user defined, we know that they will exist with a prefixed ModelState of "_Properties.", so if we detect - // this, then we know it's a Property. - - //the alias in model state can be in dot notation which indicates - // * the first part is the content property alias - // * the second part is the field to which the valiation msg is associated with - //There will always be at least 2 parts for properties since all model errors for properties are prefixed with "Properties" - //If it is not prefixed with "Properties" that means the error is for a field of the object directly. - - var parts = e.split("."); - - //Check if this is for content properties - specific to content/media/member editors because those are special - // user defined properties with custom controls. - if (parts.length > 1 && parts[0] === "_Properties") { - - var propertyAlias = parts[1]; - - //if it contains 2 '.' then we will wire it up to a property's field - if (parts.length > 2) { - //add an error with a reference to the field for which the validation belongs too - serverValidationManager.addPropertyError(propertyAlias, parts[2], modelState[e][0]); - } - else { - //add a generic error for the property, no reference to a specific field - serverValidationManager.addPropertyError(propertyAlias, "", modelState[e][0]); - } - - } - else { - - //Everthing else is just a 'Field'... the field name could contain any level of 'parts' though, for example: - // Groups[0].Properties[2].Alias - serverValidationManager.addFieldError(e, modelState[e][0]); - } - - //add to notifications - notificationsService.error("Validation", modelState[e][0]); - - } - } - }; -} -angular.module('umbraco.services').factory('formHelper', formHelper); -angular.module('umbraco.services') - .factory('gridService', function ($http, $q){ - - var configPath = Umbraco.Sys.ServerVariables.umbracoUrls.gridConfig; + app.ready + app.authenticated + app.notAuthenticated + app.closeDialogs + app.ysod + app.reInitialize + app.userRefresh +*/ + function eventsService($q, $rootScope) { + return { + /** raise an event with a given name */ + emit: function (name, args) { + //there are no listeners + if (!$rootScope.$$listeners[name]) { + return; + } + //send the event + $rootScope.$emit(name, args); + }, + /** subscribe to a method, or use scope.$on = same thing */ + on: function (name, callback) { + return $rootScope.$on(name, callback); + }, + /** pass in the result of 'on' to this method, or just call the method returned from 'on' to unsubscribe */ + unsubscribe: function (handle) { + if (angular.isFunction(handle)) { + handle(); + } + } + }; + } + angular.module('umbraco.services').factory('eventsService', eventsService); + /** + * @ngdoc service + * @name umbraco.services.fileManager + * @function + * + * @description + * Used by editors to manage any files that require uploading with the posted data, normally called by property editors + * that need to attach files. + * When a route changes successfully, we ensure that the collection is cleared. + */ + function fileManager() { + var fileCollection = []; + return { + /** + * @ngdoc function + * @name umbraco.services.fileManager#addFiles + * @methodOf umbraco.services.fileManager + * @function + * + * @description + * Attaches files to the current manager for the current editor for a particular property, if an empty array is set + * for the files collection that effectively clears the files for the specified editor. + */ + setFiles: function (propertyAlias, files) { + //this will clear the files for the current property and then add the new ones for the current property + fileCollection = _.reject(fileCollection, function (item) { + return item.alias === propertyAlias; + }); + for (var i = 0; i < files.length; i++) { + //save the file object to the files collection + fileCollection.push({ + alias: propertyAlias, + file: files[i] + }); + } + }, + /** + * @ngdoc function + * @name umbraco.services.fileManager#getFiles + * @methodOf umbraco.services.fileManager + * @function + * + * @description + * Returns all of the files attached to the file manager + */ + getFiles: function () { + return fileCollection; + }, + /** + * @ngdoc function + * @name umbraco.services.fileManager#clearFiles + * @methodOf umbraco.services.fileManager + * @function + * + * @description + * Removes all files from the manager + */ + clearFiles: function () { + fileCollection = []; + } + }; + } + angular.module('umbraco.services').factory('fileManager', fileManager); + /** + * @ngdoc service + * @name umbraco.services.formHelper + * @function + * + * @description + * A utility class used to streamline how forms are developed, to ensure that validation is check and displayed consistently and to ensure that the correct events + * fire when they need to. + */ + function formHelper(angularHelper, serverValidationManager, $timeout, notificationsService, dialogService, localizationService) { + return { + /** + * @ngdoc function + * @name umbraco.services.formHelper#submitForm + * @methodOf umbraco.services.formHelper + * @function + * + * @description + * Called by controllers when submitting a form - this ensures that all client validation is checked, + * server validation is cleared, that the correct events execute and status messages are displayed. + * This returns true if the form is valid, otherwise false if form submission cannot continue. + * + * @param {object} args An object containing arguments for form submission + */ + submitForm: function (args) { + var currentForm; + if (!args) { + throw 'args cannot be null'; + } + if (!args.scope) { + throw 'args.scope cannot be null'; + } + if (!args.formCtrl) { + //try to get the closest form controller + currentForm = angularHelper.getRequiredCurrentForm(args.scope); + } else { + currentForm = args.formCtrl; + } + //if no statusPropertyName is set we'll default to formStatus. + if (!args.statusPropertyName) { + args.statusPropertyName = 'formStatus'; + } + //if no statusTimeout is set, we'll default to 2500 ms + if (!args.statusTimeout) { + args.statusTimeout = 2500; + } + //the first thing any form must do is broadcast the formSubmitting event + args.scope.$broadcast('formSubmitting', { + scope: args.scope, + action: args.action + }); + //then check if the form is valid + if (!args.skipValidation) { + if (currentForm.$invalid) { + return false; + } + } + //reset the server validations + serverValidationManager.reset(); + //check if a form status should be set on the scope + if (args.statusMessage) { + args.scope[args.statusPropertyName] = args.statusMessage; + //clear the message after the timeout + $timeout(function () { + args.scope[args.statusPropertyName] = undefined; + }, args.statusTimeout); + } + return true; + }, + /** + * @ngdoc function + * @name umbraco.services.formHelper#submitForm + * @methodOf umbraco.services.formHelper + * @function + * + * @description + * Called by controllers when a form has been successfully submitted. the correct events execute + * and that the notifications are displayed if there are any. + * + * @param {object} args An object containing arguments for form submission + */ + resetForm: function (args) { + if (!args) { + throw 'args cannot be null'; + } + if (!args.scope) { + throw 'args.scope cannot be null'; + } + //if no statusPropertyName is set we'll default to formStatus. + if (!args.statusPropertyName) { + args.statusPropertyName = 'formStatus'; + } + //clear the status + args.scope[args.statusPropertyName] = null; + this.showNotifications(args); + args.scope.$broadcast('formSubmitted', { scope: args.scope }); + }, + showNotifications: function (args) { + if (!args || !args.notifications) { + return false; + } + if (angular.isArray(args.notifications)) { + for (var i = 0; i < args.notifications.length; i++) { + notificationsService.showNotification(args.notifications[i]); + } + return true; + } + return false; + }, + /** + * @ngdoc function + * @name umbraco.services.formHelper#handleError + * @methodOf umbraco.services.formHelper + * @function + * + * @description + * Needs to be called when a form submission fails, this will wire up all server validation errors in ModelState and + * add the correct messages to the notifications. If a server error has occurred this will show a ysod. + * + * @param {object} err The error object returned from the http promise + */ + handleError: function (err) { + //When the status is a 400 status with a custom header: X-Status-Reason: Validation failed, we have validation errors. + //Otherwise the error is probably due to invalid data (i.e. someone mucking around with the ids or something). + //Or, some strange server error + if (err.status === 400) { + //now we need to look through all the validation errors + if (err.data && err.data.ModelState) { + //wire up the server validation errs + this.handleServerValidation(err.data.ModelState); + //execute all server validation events and subscribers + serverValidationManager.executeAndClearAllSubscriptions(); + } else { + dialogService.ysodDialog(err); + } + } + }, + /** + * @ngdoc function + * @name umbraco.services.formHelper#handleServerValidation + * @methodOf umbraco.services.formHelper + * @function + * + * @description + * This wires up all of the server validation model state so that valServer and valServerField directives work + * + * @param {object} err The error object returned from the http promise + */ + handleServerValidation: function (modelState) { + for (var e in modelState) { + //This is where things get interesting.... + // We need to support validation for all editor types such as both the content and content type editors. + // The Content editor ModelState is quite specific with the way that Properties are validated especially considering + // that each property is a User Developer property editor. + // The way that Content Type Editor ModelState is created is simply based on the ASP.Net validation data-annotations + // system. + // So, to do this (since we need to support backwards compat), we need to hack a little bit. For Content Properties, + // which are user defined, we know that they will exist with a prefixed ModelState of "_Properties.", so if we detect + // this, then we know it's a Property. + //the alias in model state can be in dot notation which indicates + // * the first part is the content property alias + // * the second part is the field to which the valiation msg is associated with + //There will always be at least 2 parts for properties since all model errors for properties are prefixed with "Properties" + //If it is not prefixed with "Properties" that means the error is for a field of the object directly. + var parts = e.split('.'); + //Check if this is for content properties - specific to content/media/member editors because those are special + // user defined properties with custom controls. + if (parts.length > 1 && parts[0] === '_Properties') { + var propertyAlias = parts[1]; + //if it contains 2 '.' then we will wire it up to a property's field + if (parts.length > 2) { + //add an error with a reference to the field for which the validation belongs too + serverValidationManager.addPropertyError(propertyAlias, parts[2], modelState[e][0]); + } else { + //add a generic error for the property, no reference to a specific field + serverValidationManager.addPropertyError(propertyAlias, '', modelState[e][0]); + } + } else { + //Everthing else is just a 'Field'... the field name could contain any level of 'parts' though, for example: + // Groups[0].Properties[2].Alias + serverValidationManager.addFieldError(e, modelState[e][0]); + } + //add to notifications + notificationsService.error('Validation', modelState[e][0]); + } + } + }; + } + angular.module('umbraco.services').factory('formHelper', formHelper); + angular.module('umbraco.services').factory('gridService', function ($http, $q) { + var configPath = Umbraco.Sys.ServerVariables.umbracoUrls.gridConfig; var service = { - getGridEditors: function () { - return $http.get(configPath); - } - }; - - return service; - - }); - -angular.module('umbraco.services') - .factory('helpService', function ($http, $q){ - var helpTopics = {}; - - var defaultUrl = "http://our.umbraco.org/rss/help"; - var tvUrl = "http://umbraco.tv/feeds/help"; - - function getCachedHelp(url){ - if(helpTopics[url]){ - return helpTopics[cacheKey]; - }else{ - return null; - } - } - - function setCachedHelp(url, data){ - helpTopics[url] = data; - } - - function fetchUrl(url){ - var deferred = $q.defer(); - var found = getCachedHelp(url); - - if(found){ - deferred.resolve(found); - }else{ - - var proxyUrl = "dashboard/feedproxy.aspx?url=" + url; - $http.get(proxyUrl).then(function(data){ - var feed = $(data.data); - var topics = []; - - $('item', feed).each(function (i, item) { - var topic = {}; - topic.thumbnail = $(item).find('thumbnail').attr('url'); - topic.title = $("title", item).text(); - topic.link = $("guid", item).text(); - topic.description = $("description", item).text(); - topics.push(topic); - }); - - setCachedHelp(topics); - deferred.resolve(topics); - }); - } - - return deferred.promise; - } - - - - var service = { - findHelp: function (args) { - var url = service.getUrl(defaultUrl, args); - return fetchUrl(url); - }, - - findVideos: function (args) { - var url = service.getUrl(tvUrl, args); - return fetchUrl(url); - }, - - getUrl: function(url, args){ - return url + "?" + $.param(args); - } - }; - - return service; - - }); -/** - * @ngdoc service - * @name umbraco.services.historyService - * - * @requires $rootScope - * @requires $timeout - * @requires angularHelper - * - * @description - * Service to handle the main application navigation history. Responsible for keeping track - * of where a user navigates to, stores an icon, url and name in a collection, to make it easy - * for the user to go back to a previous editor / action - * - * **Note:** only works with new angular-based editors, not legacy ones - * - * ##usage - * To use, simply inject the historyService into any controller that needs it, and make - * sure the umbraco.services module is accesible - which it should be by default. - * - *
    - *      angular.module("umbraco").controller("my.controller". function(historyService){
    - *         historyService.add({
    - *								icon: "icon-class",
    - *								name: "Editing 'articles',
    - *								link: "/content/edit/1234"}
    - *							);
    - *      }); 
    - * 
    - */ -angular.module('umbraco.services') -.factory('historyService', function ($rootScope, $timeout, angularHelper, eventsService) { - - var nArray = []; - - function add(item) { - - if (!item) { - return null; - } - - var listWithoutThisItem = _.reject(nArray, function(i) { - return i.link === item.link; - }); - - //put it at the top and reassign - listWithoutThisItem.splice(0, 0, item); - nArray = listWithoutThisItem; - return nArray[0]; - - } - - return { - /** - * @ngdoc method - * @name umbraco.services.historyService#add - * @methodOf umbraco.services.historyService - * - * @description - * Adds a given history item to the users history collection. - * - * @param {Object} item the history item - * @param {String} item.icon icon css class for the list, ex: "icon-image", "icon-doc" - * @param {String} item.link route to the editor, ex: "/content/edit/1234" - * @param {String} item.name friendly name for the history listing - * @returns {Object} history item object - */ - add: function (item) { - var icon = item.icon || "icon-file"; - angularHelper.safeApply($rootScope, function () { - var result = add({ name: item.name, icon: icon, link: item.link, time: new Date() }); - eventsService.emit("historyService.add", {added: result, all: nArray}); - return result; - }); - }, - /** - * @ngdoc method - * @name umbraco.services.historyService#remove - * @methodOf umbraco.services.historyService - * - * @description - * Removes a history item from the users history collection, given an index to remove from. - * - * @param {Int} index index to remove item from - */ - remove: function (index) { - angularHelper.safeApply($rootScope, function() { - var result = nArray.splice(index, 1); - eventsService.emit("historyService.remove", { removed: result, all: nArray }); - }); - }, - - /** - * @ngdoc method - * @name umbraco.services.historyService#removeAll - * @methodOf umbraco.services.historyService - * - * @description - * Removes all history items from the users history collection - */ - removeAll: function () { - angularHelper.safeApply($rootScope, function() { - nArray = []; - eventsService.emit("historyService.removeAll"); - }); - }, - - /** - * @ngdoc method - * @name umbraco.services.historyService#getCurrent - * @methodOf umbraco.services.historyService - * - * @description - * Method to return the current history collection. - * - */ - getCurrent: function(){ - return nArray; - } - }; -}); -/** + getGridEditors: function () { + return $http.get(configPath); + } + }; + return service; + }); + angular.module('umbraco.services').factory('helpService', function ($http, $q, umbRequestHelper) { + var helpTopics = {}; + var defaultUrl = 'https://our.umbraco.com/rss/help'; + var tvUrl = 'https://umbraco.tv/feeds/help'; + function getCachedHelp(url) { + if (helpTopics[url]) { + return helpTopics[cacheKey]; + } else { + return null; + } + } + function setCachedHelp(url, data) { + helpTopics[url] = data; + } + function fetchUrl(url) { + var deferred = $q.defer(); + var found = getCachedHelp(url); + if (found) { + deferred.resolve(found); + } else { + var proxyUrl = 'dashboard/feedproxy.aspx?url=' + url; + $http.get(proxyUrl).then(function (data) { + var feed = $(data.data); + var topics = []; + $('item', feed).each(function (i, item) { + var topic = {}; + topic.thumbnail = $(item).find('thumbnail').attr('url'); + topic.title = $('title', item).text(); + topic.link = $('guid', item).text(); + topic.description = $('description', item).text(); + topics.push(topic); + }); + setCachedHelp(topics); + deferred.resolve(topics); + }); + } + return deferred.promise; + } + var service = { + findHelp: function (args) { + var url = service.getUrl(defaultUrl, args); + return fetchUrl(url); + }, + findVideos: function (args) { + var url = service.getUrl(tvUrl, args); + return fetchUrl(url); + }, + getContextHelpForPage: function (section, tree, baseurl) { + var qs = '?section=' + section + '&tree=' + tree; + if (tree) { + qs += '&tree=' + tree; + } + if (baseurl) { + qs += '&baseurl=' + encodeURIComponent(baseurl); + } + var url = umbRequestHelper.getApiUrl('helpApiBaseUrl', 'GetContextHelpForPage' + qs); + return umbRequestHelper.resourcePromise($http.get(url), 'Failed to get lessons content'); + }, + getUrl: function (url, args) { + return url + '?' + $.param(args); + } + }; + return service; + }); + /** + * @ngdoc service + * @name umbraco.services.historyService + * + * @requires $rootScope + * @requires $timeout + * @requires angularHelper + * + * @description + * Service to handle the main application navigation history. Responsible for keeping track + * of where a user navigates to, stores an icon, url and name in a collection, to make it easy + * for the user to go back to a previous editor / action + * + * **Note:** only works with new angular-based editors, not legacy ones + * + * ##usage + * To use, simply inject the historyService into any controller that needs it, and make + * sure the umbraco.services module is accesible - which it should be by default. + * + *
    + *      angular.module("umbraco").controller("my.controller". function(historyService){
    + *         historyService.add({
    + *								icon: "icon-class",
    + *								name: "Editing 'articles',
    + *								link: "/content/edit/1234"}
    + *							);
    + *      }); 
    + * 
    + */ + angular.module('umbraco.services').factory('historyService', function ($rootScope, $timeout, angularHelper, eventsService) { + var nArray = []; + function add(item) { + if (!item) { + return null; + } + var listWithoutThisItem = _.reject(nArray, function (i) { + return i.link === item.link; + }); + //put it at the top and reassign + listWithoutThisItem.splice(0, 0, item); + nArray = listWithoutThisItem; + return nArray[0]; + } + return { + /** + * @ngdoc method + * @name umbraco.services.historyService#add + * @methodOf umbraco.services.historyService + * + * @description + * Adds a given history item to the users history collection. + * + * @param {Object} item the history item + * @param {String} item.icon icon css class for the list, ex: "icon-image", "icon-doc" + * @param {String} item.link route to the editor, ex: "/content/edit/1234" + * @param {String} item.name friendly name for the history listing + * @returns {Object} history item object + */ + add: function (item) { + var icon = item.icon || 'icon-file'; + angularHelper.safeApply($rootScope, function () { + var result = add({ + name: item.name, + icon: icon, + link: item.link, + time: new Date() + }); + eventsService.emit('historyService.add', { + added: result, + all: nArray + }); + return result; + }); + }, + /** + * @ngdoc method + * @name umbraco.services.historyService#remove + * @methodOf umbraco.services.historyService + * + * @description + * Removes a history item from the users history collection, given an index to remove from. + * + * @param {Int} index index to remove item from + */ + remove: function (index) { + angularHelper.safeApply($rootScope, function () { + var result = nArray.splice(index, 1); + eventsService.emit('historyService.remove', { + removed: result, + all: nArray + }); + }); + }, + /** + * @ngdoc method + * @name umbraco.services.historyService#removeAll + * @methodOf umbraco.services.historyService + * + * @description + * Removes all history items from the users history collection + */ + removeAll: function () { + angularHelper.safeApply($rootScope, function () { + nArray = []; + eventsService.emit('historyService.removeAll'); + }); + }, + /** + * @ngdoc method + * @name umbraco.services.historyService#getCurrent + * @methodOf umbraco.services.historyService + * + * @description + * Method to return the current history collection. + * + */ + getCurrent: function () { + return nArray; + }, + /** + * @ngdoc method + * @name umbraco.services.historyService#getLastAccessedItemForSection + * @methodOf umbraco.services.historyService + * + * @description + * Method to return the item that was last accessed in the given section + * + * @param {string} sectionAlias Alias of the section to return the last accessed item for. + */ + getLastAccessedItemForSection: function (sectionAlias) { + for (var i = 0, len = nArray.length; i < len; i++) { + var item = nArray[i]; + if (item.link.indexOf(sectionAlias + '/') === 0) { + return item; + } + } + return null; + } + }; + }); + /** * @ngdoc service * @name umbraco.services.iconHelper * @description A helper service for dealing with icons, mostly dealing with legacy tree icons **/ -function iconHelper($q, $timeout) { - - var converter = [ - { oldIcon: ".sprNew", newIcon: "add" }, - { oldIcon: ".sprDelete", newIcon: "remove" }, - { oldIcon: ".sprMove", newIcon: "enter" }, - { oldIcon: ".sprCopy", newIcon: "documents" }, - { oldIcon: ".sprSort", newIcon: "navigation-vertical" }, - { oldIcon: ".sprPublish", newIcon: "globe" }, - { oldIcon: ".sprRollback", newIcon: "undo" }, - { oldIcon: ".sprProtect", newIcon: "lock" }, - { oldIcon: ".sprAudit", newIcon: "time" }, - { oldIcon: ".sprNotify", newIcon: "envelope" }, - { oldIcon: ".sprDomain", newIcon: "home" }, - { oldIcon: ".sprPermission", newIcon: "lock" }, - { oldIcon: ".sprRefresh", newIcon: "refresh" }, - { oldIcon: ".sprBinEmpty", newIcon: "trash" }, - { oldIcon: ".sprExportDocumentType", newIcon: "download-alt" }, - { oldIcon: ".sprImportDocumentType", newIcon: "page-up" }, - { oldIcon: ".sprLiveEdit", newIcon: "edit" }, - { oldIcon: ".sprCreateFolder", newIcon: "add" }, - { oldIcon: ".sprPackage2", newIcon: "box" }, - { oldIcon: ".sprLogout", newIcon: "logout" }, - { oldIcon: ".sprSave", newIcon: "save" }, - { oldIcon: ".sprSendToTranslate", newIcon: "envelope-alt" }, - { oldIcon: ".sprToPublish", newIcon: "mail-forward" }, - { oldIcon: ".sprTranslate", newIcon: "comments" }, - { oldIcon: ".sprUpdate", newIcon: "save" }, - - { oldIcon: ".sprTreeSettingDomain", newIcon: "icon-home" }, - { oldIcon: ".sprTreeDoc", newIcon: "icon-document" }, - { oldIcon: ".sprTreeDoc2", newIcon: "icon-diploma-alt" }, - { oldIcon: ".sprTreeDoc3", newIcon: "icon-notepad" }, - { oldIcon: ".sprTreeDoc4", newIcon: "icon-newspaper-alt" }, - { oldIcon: ".sprTreeDoc5", newIcon: "icon-notepad-alt" }, - - { oldIcon: ".sprTreeDocPic", newIcon: "icon-picture" }, - { oldIcon: ".sprTreeFolder", newIcon: "icon-folder" }, - { oldIcon: ".sprTreeFolder_o", newIcon: "icon-folder" }, - { oldIcon: ".sprTreeMediaFile", newIcon: "icon-music" }, - { oldIcon: ".sprTreeMediaMovie", newIcon: "icon-movie" }, - { oldIcon: ".sprTreeMediaPhoto", newIcon: "icon-picture" }, - - { oldIcon: ".sprTreeMember", newIcon: "icon-user" }, - { oldIcon: ".sprTreeMemberGroup", newIcon: "icon-users" }, - { oldIcon: ".sprTreeMemberType", newIcon: "icon-users" }, - - { oldIcon: ".sprTreeNewsletter", newIcon: "icon-file-text-alt" }, - { oldIcon: ".sprTreePackage", newIcon: "icon-box" }, - { oldIcon: ".sprTreeRepository", newIcon: "icon-server-alt" }, - - { oldIcon: ".sprTreeSettingDataType", newIcon: "icon-autofill" }, - - //TODO: - /* + function iconHelper($q, $timeout) { + var converter = [ + { + oldIcon: '.sprNew', + newIcon: 'add' + }, + { + oldIcon: '.sprDelete', + newIcon: 'remove' + }, + { + oldIcon: '.sprMove', + newIcon: 'enter' + }, + { + oldIcon: '.sprCopy', + newIcon: 'documents' + }, + { + oldIcon: '.sprSort', + newIcon: 'navigation-vertical' + }, + { + oldIcon: '.sprPublish', + newIcon: 'globe' + }, + { + oldIcon: '.sprRollback', + newIcon: 'undo' + }, + { + oldIcon: '.sprProtect', + newIcon: 'lock' + }, + { + oldIcon: '.sprAudit', + newIcon: 'time' + }, + { + oldIcon: '.sprNotify', + newIcon: 'envelope' + }, + { + oldIcon: '.sprDomain', + newIcon: 'home' + }, + { + oldIcon: '.sprPermission', + newIcon: 'lock' + }, + { + oldIcon: '.sprRefresh', + newIcon: 'refresh' + }, + { + oldIcon: '.sprBinEmpty', + newIcon: 'trash' + }, + { + oldIcon: '.sprExportDocumentType', + newIcon: 'download-alt' + }, + { + oldIcon: '.sprImportDocumentType', + newIcon: 'page-up' + }, + { + oldIcon: '.sprLiveEdit', + newIcon: 'edit' + }, + { + oldIcon: '.sprCreateFolder', + newIcon: 'add' + }, + { + oldIcon: '.sprPackage2', + newIcon: 'box' + }, + { + oldIcon: '.sprLogout', + newIcon: 'logout' + }, + { + oldIcon: '.sprSave', + newIcon: 'save' + }, + { + oldIcon: '.sprSendToTranslate', + newIcon: 'envelope-alt' + }, + { + oldIcon: '.sprToPublish', + newIcon: 'mail-forward' + }, + { + oldIcon: '.sprTranslate', + newIcon: 'comments' + }, + { + oldIcon: '.sprUpdate', + newIcon: 'save' + }, + { + oldIcon: '.sprTreeSettingDomain', + newIcon: 'icon-home' + }, + { + oldIcon: '.sprTreeDoc', + newIcon: 'icon-document' + }, + { + oldIcon: '.sprTreeDoc2', + newIcon: 'icon-diploma-alt' + }, + { + oldIcon: '.sprTreeDoc3', + newIcon: 'icon-notepad' + }, + { + oldIcon: '.sprTreeDoc4', + newIcon: 'icon-newspaper-alt' + }, + { + oldIcon: '.sprTreeDoc5', + newIcon: 'icon-notepad-alt' + }, + { + oldIcon: '.sprTreeDocPic', + newIcon: 'icon-picture' + }, + { + oldIcon: '.sprTreeFolder', + newIcon: 'icon-folder' + }, + { + oldIcon: '.sprTreeFolder_o', + newIcon: 'icon-folder' + }, + { + oldIcon: '.sprTreeMediaFile', + newIcon: 'icon-music' + }, + { + oldIcon: '.sprTreeMediaMovie', + newIcon: 'icon-movie' + }, + { + oldIcon: '.sprTreeMediaPhoto', + newIcon: 'icon-picture' + }, + { + oldIcon: '.sprTreeMember', + newIcon: 'icon-user' + }, + { + oldIcon: '.sprTreeMemberGroup', + newIcon: 'icon-users' + }, + { + oldIcon: '.sprTreeMemberType', + newIcon: 'icon-users' + }, + { + oldIcon: '.sprTreeNewsletter', + newIcon: 'icon-file-text-alt' + }, + { + oldIcon: '.sprTreePackage', + newIcon: 'icon-box' + }, + { + oldIcon: '.sprTreeRepository', + newIcon: 'icon-server-alt' + }, + { + oldIcon: '.sprTreeSettingDataType', + newIcon: 'icon-autofill' + }, + //TODO: + /* { oldIcon: ".sprTreeSettingAgent", newIcon: "" }, { oldIcon: ".sprTreeSettingCss", newIcon: "" }, { oldIcon: ".sprTreeSettingCssItem", newIcon: "" }, @@ -3013,166 +2985,179 @@ function iconHelper($q, $timeout) { { oldIcon: ".sprTreeUserGroup", newIcon: "" }, { oldIcon: ".sprTreeUserType", newIcon: "" }, */ - - { oldIcon: "folder.png", newIcon: "icon-folder" }, - { oldIcon: "mediaphoto.gif", newIcon: "icon-picture" }, - { oldIcon: "mediafile.gif", newIcon: "icon-document" }, - - { oldIcon: ".sprTreeDeveloperCacheItem", newIcon: "icon-box" }, - { oldIcon: ".sprTreeDeveloperCacheTypes", newIcon: "icon-box" }, - { oldIcon: ".sprTreeDeveloperMacro", newIcon: "icon-cogs" }, - { oldIcon: ".sprTreeDeveloperRegistry", newIcon: "icon-windows" }, - { oldIcon: ".sprTreeDeveloperPython", newIcon: "icon-linux" } - ]; - - var imageConverter = [ - {oldImage: "contour.png", newIcon: "icon-umb-contour"} - ]; - - var collectedIcons; - - return { - - /** Used by the create dialogs for content/media types to format the data so that the thumbnails are styled properly */ - formatContentTypeThumbnails: function (contentTypes) { - for (var i = 0; i < contentTypes.length; i++) { - - if (contentTypes[i].thumbnailIsClass === undefined || contentTypes[i].thumbnailIsClass) { - contentTypes[i].cssClass = this.convertFromLegacyIcon(contentTypes[i].thumbnail); - }else { - contentTypes[i].style = "background-image: url('" + contentTypes[i].thumbnailFilePath + "');height:36px; background-position:4px 0px; background-repeat: no-repeat;background-size: 35px 35px;"; - //we need an 'icon-' class in there for certain styles to work so if it is image based we'll add this - contentTypes[i].cssClass = "custom-file"; - } + { + oldIcon: 'folder.png', + newIcon: 'icon-folder' + }, + { + oldIcon: 'mediaphoto.gif', + newIcon: 'icon-picture' + }, + { + oldIcon: 'mediafile.gif', + newIcon: 'icon-document' + }, + { + oldIcon: '.sprTreeDeveloperCacheItem', + newIcon: 'icon-box' + }, + { + oldIcon: '.sprTreeDeveloperCacheTypes', + newIcon: 'icon-box' + }, + { + oldIcon: '.sprTreeDeveloperMacro', + newIcon: 'icon-cogs' + }, + { + oldIcon: '.sprTreeDeveloperRegistry', + newIcon: 'icon-windows' + }, + { + oldIcon: '.sprTreeDeveloperPython', + newIcon: 'icon-linux' } - return contentTypes; - }, - formatContentTypeIcons: function (contentTypes) { - for (var i = 0; i < contentTypes.length; i++) { - if (!contentTypes[i].icon) { - //just to be safe (e.g. when focus was on close link and hitting save) - contentTypes[i].icon = "icon-document"; // default icon - } else { - contentTypes[i].icon = this.convertFromLegacyIcon(contentTypes[i].icon); + ]; + var imageConverter = [{ + oldImage: 'contour.png', + newIcon: 'icon-umb-contour' + }]; + var collectedIcons; + return { + /** Used by the create dialogs for content/media types to format the data so that the thumbnails are styled properly */ + formatContentTypeThumbnails: function (contentTypes) { + for (var i = 0; i < contentTypes.length; i++) { + if (contentTypes[i].thumbnailIsClass === undefined || contentTypes[i].thumbnailIsClass) { + contentTypes[i].cssClass = this.convertFromLegacyIcon(contentTypes[i].thumbnail); + } else { + contentTypes[i].style = 'background-image: url(\'' + contentTypes[i].thumbnailFilePath + '\');height:36px; background-position:4px 0px; background-repeat: no-repeat;background-size: 35px 35px;'; + //we need an 'icon-' class in there for certain styles to work so if it is image based we'll add this + contentTypes[i].cssClass = 'custom-file'; + } } - - //couldnt find replacement - if(contentTypes[i].icon.indexOf(".") > 0){ - contentTypes[i].icon = "icon-document-dashed-line"; + return contentTypes; + }, + formatContentTypeIcons: function (contentTypes) { + for (var i = 0; i < contentTypes.length; i++) { + if (!contentTypes[i].icon) { + //just to be safe (e.g. when focus was on close link and hitting save) + contentTypes[i].icon = 'icon-document'; // default icon + } else { + contentTypes[i].icon = this.convertFromLegacyIcon(contentTypes[i].icon); + } + //couldnt find replacement + if (contentTypes[i].icon.indexOf('.') > 0) { + contentTypes[i].icon = 'icon-document-dashed-line'; + } + } + return contentTypes; + }, + /** If the icon is file based (i.e. it has a file path) */ + isFileBasedIcon: function (icon) { + //if it doesn't start with a '.' but contains one then we'll assume it's file based + if (icon.startsWith('..') || !icon.startsWith('.') && icon.indexOf('.') > 1) { + return true; } - } - return contentTypes; - }, - /** If the icon is file based (i.e. it has a file path) */ - isFileBasedIcon: function (icon) { - //if it doesn't start with a '.' but contains one then we'll assume it's file based - if (icon.startsWith('..') || (!icon.startsWith('.') && icon.indexOf('.') > 1)) { - return true; - } - return false; - }, - /** If the icon is legacy */ - isLegacyIcon: function (icon) { - if(!icon) { return false; - } - - if(icon.startsWith('..')){ + }, + /** If the icon is legacy */ + isLegacyIcon: function (icon) { + if (!icon) { + return false; + } + if (icon.startsWith('..')) { + return false; + } + if (icon.startsWith('.')) { + return true; + } return false; - } - - if (icon.startsWith('.')) { - return true; - } - return false; - }, - /** If the tree node has a legacy icon */ - isLegacyTreeNodeIcon: function(treeNode){ - if (treeNode.iconIsClass) { - return this.isLegacyIcon(treeNode.icon); - } - return false; - }, - - /** Return a list of icons, optionally filter them */ - /** It fetches them directly from the active stylesheets in the browser */ - getIcons: function(){ - var deferred = $q.defer(); - $timeout(function(){ - if(collectedIcons){ - deferred.resolve(collectedIcons); - }else{ - collectedIcons = []; - var c = ".icon-"; - - for (var i = document.styleSheets.length - 1; i >= 0; i--) { - var classes = document.styleSheets[i].rules || document.styleSheets[i].cssRules; - - if (classes !== null) { - for(var x=0;x0){ - s = s.substring(0, hasSpace); - } - var hasPseudo = s.indexOf(":"); - if(hasPseudo>0){ - s = s.substring(0, hasPseudo); - } - - if(collectedIcons.indexOf(s) < 0){ - collectedIcons.push(s); + }, + /** If the tree node has a legacy icon */ + isLegacyTreeNodeIcon: function (treeNode) { + if (treeNode.iconIsClass) { + return this.isLegacyIcon(treeNode.icon); + } + return false; + }, + /** Return a list of icons, optionally filter them */ + /** It fetches them directly from the active stylesheets in the browser */ + getIcons: function () { + var deferred = $q.defer(); + $timeout(function () { + if (collectedIcons) { + deferred.resolve(collectedIcons); + } else { + collectedIcons = []; + var c = '.icon-'; + for (var i = document.styleSheets.length - 1; i >= 0; i--) { + var classes = null; + try { + classes = document.styleSheets[i].rules || document.styleSheets[i].cssRules; + } catch (e) { + console.warn('Can\'t read the css rules of: ' + document.styleSheets[i].href, e); + continue; + } + if (classes !== null) { + for (var x = 0; x < classes.length; x++) { + var cur = classes[x]; + if (cur.selectorText && cur.selectorText.indexOf(c) === 0) { + var s = cur.selectorText.substring(1); + var hasSpace = s.indexOf(' '); + if (hasSpace > 0) { + s = s.substring(0, hasSpace); + } + var hasPseudo = s.indexOf(':'); + if (hasPseudo > 0) { + s = s.substring(0, hasPseudo); + } + if (collectedIcons.indexOf(s) < 0) { + collectedIcons.push(s); + } } } } } + deferred.resolve(collectedIcons); } - deferred.resolve(collectedIcons); + }, 100); + return deferred.promise; + }, + /** Converts the icon from legacy to a new one if an old one is detected */ + convertFromLegacyIcon: function (icon) { + if (this.isLegacyIcon(icon)) { + //its legacy so convert it if we can + var found = _.find(converter, function (item) { + return item.oldIcon.toLowerCase() === icon.toLowerCase(); + }); + return found ? found.newIcon : icon; } - }, 100); - - return deferred.promise; - }, - - /** Converts the icon from legacy to a new one if an old one is detected */ - convertFromLegacyIcon: function (icon) { - if (this.isLegacyIcon(icon)) { - //its legacy so convert it if we can - var found = _.find(converter, function (item) { - return item.oldIcon.toLowerCase() === icon.toLowerCase(); - }); - return (found ? found.newIcon : icon); - } - return icon; - }, - - convertFromLegacyImage: function (icon) { + return icon; + }, + convertFromLegacyImage: function (icon) { var found = _.find(imageConverter, function (item) { return item.oldImage.toLowerCase() === icon.toLowerCase(); }); - return (found ? found.newIcon : undefined); - }, - - /** If we detect that the tree node has legacy icons that can be converted, this will convert them */ - convertFromLegacyTreeNodeIcon: function (treeNode) { - if (this.isLegacyTreeNodeIcon(treeNode)) { - return this.convertFromLegacyIcon(treeNode.icon); + return found ? found.newIcon : undefined; + }, + /** If we detect that the tree node has legacy icons that can be converted, this will convert them */ + convertFromLegacyTreeNodeIcon: function (treeNode) { + if (this.isLegacyTreeNodeIcon(treeNode)) { + return this.convertFromLegacyIcon(treeNode.icon); + } + return treeNode.icon; } - return treeNode.icon; - } - }; -} -angular.module('umbraco.services').factory('iconHelper', iconHelper); -/** + }; + } + angular.module('umbraco.services').factory('iconHelper', iconHelper); + /** * @ngdoc service * @name umbraco.services.imageHelper * @deprecated **/ -function imageHelper(umbRequestHelper, mediaHelper) { - return { - /** + function imageHelper(umbRequestHelper, mediaHelper) { + return { + /** * @ngdoc function * @name umbraco.services.imageHelper#getImagePropertyValue * @methodOf umbraco.services.imageHelper @@ -3180,10 +3165,10 @@ function imageHelper(umbRequestHelper, mediaHelper) { * * @deprecated */ - getImagePropertyValue: function (options) { - return mediaHelper.getImagePropertyValue(options); - }, - /** + getImagePropertyValue: function (options) { + return mediaHelper.getImagePropertyValue(options); + }, + /** * @ngdoc function * @name umbraco.services.imageHelper#getThumbnail * @methodOf umbraco.services.imageHelper @@ -3191,11 +3176,10 @@ function imageHelper(umbRequestHelper, mediaHelper) { * * @deprecated */ - getThumbnail: function (options) { - return mediaHelper.getThumbnail(options); - }, - - /** + getThumbnail: function (options) { + return mediaHelper.getThumbnail(options); + }, + /** * @ngdoc function * @name umbraco.services.imageHelper#scaleToMaxSize * @methodOf umbraco.services.imageHelper @@ -3203,11 +3187,10 @@ function imageHelper(umbRequestHelper, mediaHelper) { * * @deprecated */ - scaleToMaxSize: function (maxSize, width, height) { - return mediaHelper.scaleToMaxSize(maxSize, width, height); - }, - - /** + scaleToMaxSize: function (maxSize, width, height) { + return mediaHelper.scaleToMaxSize(maxSize, width, height); + }, + /** * @ngdoc function * @name umbraco.services.imageHelper#getThumbnailFromPath * @methodOf umbraco.services.imageHelper @@ -3215,11 +3198,10 @@ function imageHelper(umbRequestHelper, mediaHelper) { * * @deprecated */ - getThumbnailFromPath: function (imagePath) { - return mediaHelper.getThumbnailFromPath(imagePath); - }, - - /** + getThumbnailFromPath: function (imagePath) { + return mediaHelper.getThumbnailFromPath(imagePath); + }, + /** * @ngdoc function * @name umbraco.services.imageHelper#detectIfImageByExtension * @methodOf umbraco.services.imageHelper @@ -3227,336 +3209,317 @@ function imageHelper(umbRequestHelper, mediaHelper) { * * @deprecated */ - detectIfImageByExtension: function (imagePath) { - return mediaHelper.detectIfImageByExtension(imagePath); + detectIfImageByExtension: function (imagePath) { + return mediaHelper.detectIfImageByExtension(imagePath); + } + }; + } + angular.module('umbraco.services').factory('imageHelper', imageHelper); + (function () { + 'use strict'; + function javascriptLibraryService($q, $http, umbRequestHelper) { + var existingLocales = []; + function getSupportedLocalesForMoment() { + var deferred = $q.defer(); + if (existingLocales.length === 0) { + umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('backOfficeAssetsApiBaseUrl', 'GetSupportedMomentLocales')), 'Failed to get cultures').then(function (locales) { + existingLocales = locales; + deferred.resolve(existingLocales); + }); + } else { + deferred.resolve(existingLocales); + } + return deferred.promise; + } + var service = { getSupportedLocalesForMoment: getSupportedLocalesForMoment }; + return service; } - }; -} -angular.module('umbraco.services').factory('imageHelper', imageHelper); -// This service was based on OpenJS library available in BSD License -// http://www.openjs.com/scripts/events/keyboard_shortcuts/index.php - -function keyboardService($window, $timeout) { - - var keyboardManagerService = {}; - - var defaultOpt = { - 'type': 'keydown', - 'propagate': false, - 'inputDisabled': false, - 'target': $window.document, - 'keyCode': false - }; - - // Work around for stupid Shift key bug created by using lowercase - as a result the shift+num combination was broken - var shift_nums = { - "`": "~", - "1": "!", - "2": "@", - "3": "#", - "4": "$", - "5": "%", - "6": "^", - "7": "&", - "8": "*", - "9": "(", - "0": ")", - "-": "_", - "=": "+", - ";": ":", - "'": "\"", - ",": "<", - ".": ">", - "/": "?", - "\\": "|" - }; - - // Special Keys - and their codes - var special_keys = { - 'esc': 27, - 'escape': 27, - 'tab': 9, - 'space': 32, - 'return': 13, - 'enter': 13, - 'backspace': 8, - - 'scrolllock': 145, - 'scroll_lock': 145, - 'scroll': 145, - 'capslock': 20, - 'caps_lock': 20, - 'caps': 20, - 'numlock': 144, - 'num_lock': 144, - 'num': 144, - - 'pause': 19, - 'break': 19, - - 'insert': 45, - 'home': 36, - 'delete': 46, - 'end': 35, - - 'pageup': 33, - 'page_up': 33, - 'pu': 33, - - 'pagedown': 34, - 'page_down': 34, - 'pd': 34, - - 'left': 37, - 'up': 38, - 'right': 39, - 'down': 40, - - 'f1': 112, - 'f2': 113, - 'f3': 114, - 'f4': 115, - 'f5': 116, - 'f6': 117, - 'f7': 118, - 'f8': 119, - 'f9': 120, - 'f10': 121, - 'f11': 122, - 'f12': 123 - }; - - var isMac = navigator.platform.toUpperCase().indexOf('MAC')>=0; - - // The event handler for bound element events - function eventHandler(e) { - e = e || $window.event; - - var code, k; - - // Find out which key is pressed - if (e.keyCode) - { - code = e.keyCode; - } - else if (e.which) { - code = e.which; - } - - var character = String.fromCharCode(code).toLowerCase(); - - if (code === 188){character = ",";} // If the user presses , when the type is onkeydown - if (code === 190){character = ".";} // If the user presses , when the type is onkeydown - - var propagate = true; - - //Now we need to determine which shortcut this event is for, we'll do this by iterating over each - //registered shortcut to find the match. We use Find here so that the loop exits as soon - //as we've found the one we're looking for - _.find(_.keys(keyboardManagerService.keyboardEvent), function(key) { - - var shortcutLabel = key; - var shortcutVal = keyboardManagerService.keyboardEvent[key]; - - // Key Pressed - counts the number of valid keypresses - if it is same as the number of keys, the shortcut function is invoked - var kp = 0; - - // Some modifiers key - var modifiers = { - shift: { - wanted: false, - pressed: e.shiftKey ? true : false - }, - ctrl: { - wanted: false, - pressed: e.ctrlKey ? true : false - }, - alt: { - wanted: false, - pressed: e.altKey ? true : false - }, - meta: { //Meta is Mac specific - wanted: false, - pressed: e.metaKey ? true : false - } - }; - - var keys = shortcutLabel.split("+"); - var opt = shortcutVal.opt; - var callback = shortcutVal.callback; - - // Foreach keys in label (split on +) - var l = keys.length; - for (var i = 0; i < l; i++) { - - var k = keys[i]; - switch (k) { - case 'ctrl': - case 'control': - kp++; - modifiers.ctrl.wanted = true; - break; - case 'shift': - case 'alt': - case 'meta': - kp++; - modifiers[k].wanted = true; - break; - } - - if (k.length > 1) { // If it is a special key - if (special_keys[k] === code) { - kp++; - } - } - else if (opt['keyCode']) { // If a specific key is set into the config - if (opt['keyCode'] === code) { - kp++; - } - } - else { // The special keys did not match - if (character === k) { - kp++; - } - else { - if (shift_nums[character] && e.shiftKey) { // Stupid Shift key bug created by using lowercase - character = shift_nums[character]; - if (character === k) { - kp++; - } - } - } - } - - } //for end - - if (kp === keys.length && - modifiers.ctrl.pressed === modifiers.ctrl.wanted && - modifiers.shift.pressed === modifiers.shift.wanted && - modifiers.alt.pressed === modifiers.alt.wanted && - modifiers.meta.pressed === modifiers.meta.wanted) { - - //found the right callback! - - // Disable event handler when focus input and textarea - if (opt['inputDisabled']) { - var elt; - if (e.target) { - elt = e.target; - } else if (e.srcElement) { - elt = e.srcElement; - } - - if (elt.nodeType === 3) { elt = elt.parentNode; } - if (elt.tagName === 'INPUT' || elt.tagName === 'TEXTAREA') { - //This exits the Find loop - return true; - } - } - - $timeout(function () { - callback(e); - }, 1); - - if (!opt['propagate']) { // Stop the event - propagate = false; - } - - //This exits the Find loop - return true; - } - - //we haven't found one so continue looking - return false; - - }); - - // Stop the event if required - if (!propagate) { - // e.cancelBubble is supported by IE - this will kill the bubbling process. - e.cancelBubble = true; - e.returnValue = false; - - // e.stopPropagation works in Firefox. - if (e.stopPropagation) { - e.stopPropagation(); - e.preventDefault(); - } - return false; - } - } - - // Store all keyboard combination shortcuts - keyboardManagerService.keyboardEvent = {}; - - // Add a new keyboard combination shortcut - keyboardManagerService.bind = function (label, callback, opt) { - - //replace ctrl key with meta key - if(isMac && label !== "ctrl+space"){ - label = label.replace("ctrl","meta"); - } - - var elt; - // Initialize opt object - opt = angular.extend({}, defaultOpt, opt); - label = label.toLowerCase(); - elt = opt.target; - if(typeof opt.target === 'string'){ - elt = document.getElementById(opt.target); - } - - //Ensure we aren't double binding to the same element + type otherwise we'll end up multi-binding - // and raising events for now reason. So here we'll check if the event is already registered for the element - var boundValues = _.values(keyboardManagerService.keyboardEvent); - var found = _.find(boundValues, function (i) { - return i.target === elt && i.event === opt['type']; - }); - - // Store shortcut - keyboardManagerService.keyboardEvent[label] = { - 'callback': callback, - 'target': elt, - 'opt': opt - }; - - if (!found) { - //Attach the function with the event - if (elt.addEventListener) { - elt.addEventListener(opt['type'], eventHandler, false); - } else if (elt.attachEvent) { - elt.attachEvent('on' + opt['type'], eventHandler); - } else { - elt['on' + opt['type']] = eventHandler; - } - } - - }; - // Remove the shortcut - just specify the shortcut and I will remove the binding - keyboardManagerService.unbind = function (label) { - label = label.toLowerCase(); - var binding = keyboardManagerService.keyboardEvent[label]; - delete(keyboardManagerService.keyboardEvent[label]); - - if(!binding){return;} - - var type = binding['event'], - elt = binding['target'], - callback = binding['callback']; - - if(elt.detachEvent){ - elt.detachEvent('on' + type, callback); - }else if(elt.removeEventListener){ - elt.removeEventListener(type, callback, false); - }else{ - elt['on'+type] = false; - } - }; - // - - return keyboardManagerService; -} angular.module('umbraco.services').factory('keyboardService', ['$window', '$timeout', keyboardService]); -/** + angular.module('umbraco.services').factory('javascriptLibraryService', javascriptLibraryService); + }()); + // This service was based on OpenJS library available in BSD License + // http://www.openjs.com/scripts/events/keyboard_shortcuts/index.php + function keyboardService($window, $timeout) { + var keyboardManagerService = {}; + var defaultOpt = { + 'type': 'keydown', + 'propagate': false, + 'inputDisabled': false, + 'target': $window.document, + 'keyCode': false + }; + // Work around for stupid Shift key bug created by using lowercase - as a result the shift+num combination was broken + var shift_nums = { + '`': '~', + '1': '!', + '2': '@', + '3': '#', + '4': '$', + '5': '%', + '6': '^', + '7': '&', + '8': '*', + '9': '(', + '0': ')', + '-': '_', + '=': '+', + ';': ':', + '\'': '"', + ',': '<', + '.': '>', + '/': '?', + '\\': '|' + }; + // Special Keys - and their codes + var special_keys = { + 'esc': 27, + 'escape': 27, + 'tab': 9, + 'space': 32, + 'return': 13, + 'enter': 13, + 'backspace': 8, + 'scrolllock': 145, + 'scroll_lock': 145, + 'scroll': 145, + 'capslock': 20, + 'caps_lock': 20, + 'caps': 20, + 'numlock': 144, + 'num_lock': 144, + 'num': 144, + 'pause': 19, + 'break': 19, + 'insert': 45, + 'home': 36, + 'delete': 46, + 'end': 35, + 'pageup': 33, + 'page_up': 33, + 'pu': 33, + 'pagedown': 34, + 'page_down': 34, + 'pd': 34, + 'left': 37, + 'up': 38, + 'right': 39, + 'down': 40, + 'f1': 112, + 'f2': 113, + 'f3': 114, + 'f4': 115, + 'f5': 116, + 'f6': 117, + 'f7': 118, + 'f8': 119, + 'f9': 120, + 'f10': 121, + 'f11': 122, + 'f12': 123 + }; + var isMac = navigator.platform.toUpperCase().indexOf('MAC') >= 0; + // The event handler for bound element events + function eventHandler(e) { + e = e || $window.event; + var code, k; + // Find out which key is pressed + if (e.keyCode) { + code = e.keyCode; + } else if (e.which) { + code = e.which; + } + var character = String.fromCharCode(code).toLowerCase(); + if (code === 188) { + character = ','; + } + // If the user presses , when the type is onkeydown + if (code === 190) { + character = '.'; + } + // If the user presses , when the type is onkeydown + var propagate = true; + //Now we need to determine which shortcut this event is for, we'll do this by iterating over each + //registered shortcut to find the match. We use Find here so that the loop exits as soon + //as we've found the one we're looking for + _.find(_.keys(keyboardManagerService.keyboardEvent), function (key) { + var shortcutLabel = key; + var shortcutVal = keyboardManagerService.keyboardEvent[key]; + // Key Pressed - counts the number of valid keypresses - if it is same as the number of keys, the shortcut function is invoked + var kp = 0; + // Some modifiers key + var modifiers = { + shift: { + wanted: false, + pressed: e.shiftKey ? true : false + }, + ctrl: { + wanted: false, + pressed: e.ctrlKey ? true : false + }, + alt: { + wanted: false, + pressed: e.altKey ? true : false + }, + meta: { + //Meta is Mac specific + wanted: false, + pressed: e.metaKey ? true : false + } + }; + var keys = shortcutLabel.split('+'); + var opt = shortcutVal.opt; + var callback = shortcutVal.callback; + // Foreach keys in label (split on +) + var l = keys.length; + for (var i = 0; i < l; i++) { + var k = keys[i]; + switch (k) { + case 'ctrl': + case 'control': + kp++; + modifiers.ctrl.wanted = true; + break; + case 'shift': + case 'alt': + case 'meta': + kp++; + modifiers[k].wanted = true; + break; + } + if (k.length > 1) { + // If it is a special key + if (special_keys[k] === code) { + kp++; + } + } else if (opt['keyCode']) { + // If a specific key is set into the config + if (opt['keyCode'] === code) { + kp++; + } + } else { + // The special keys did not match + if (character === k) { + kp++; + } else { + if (shift_nums[character] && e.shiftKey) { + // Stupid Shift key bug created by using lowercase + character = shift_nums[character]; + if (character === k) { + kp++; + } + } + } + } + } + //for end + if (kp === keys.length && modifiers.ctrl.pressed === modifiers.ctrl.wanted && modifiers.shift.pressed === modifiers.shift.wanted && modifiers.alt.pressed === modifiers.alt.wanted && modifiers.meta.pressed === modifiers.meta.wanted) { + //found the right callback! + // Disable event handler when focus input and textarea + if (opt['inputDisabled']) { + var elt; + if (e.target) { + elt = e.target; + } else if (e.srcElement) { + elt = e.srcElement; + } + if (elt.nodeType === 3) { + elt = elt.parentNode; + } + if (elt.tagName === 'INPUT' || elt.tagName === 'TEXTAREA') { + //This exits the Find loop + return true; + } + } + $timeout(function () { + callback(e); + }, 1); + if (!opt['propagate']) { + // Stop the event + propagate = false; + } + //This exits the Find loop + return true; + } + //we haven't found one so continue looking + return false; + }); + // Stop the event if required + if (!propagate) { + // e.cancelBubble is supported by IE - this will kill the bubbling process. + e.cancelBubble = true; + e.returnValue = false; + // e.stopPropagation works in Firefox. + if (e.stopPropagation) { + e.stopPropagation(); + e.preventDefault(); + } + return false; + } + } + // Store all keyboard combination shortcuts + keyboardManagerService.keyboardEvent = {}; + // Add a new keyboard combination shortcut + keyboardManagerService.bind = function (label, callback, opt) { + //replace ctrl key with meta key + if (isMac && label !== 'ctrl+space') { + label = label.replace('ctrl', 'meta'); + } + var elt; + // Initialize opt object + opt = angular.extend({}, defaultOpt, opt); + label = label.toLowerCase(); + elt = opt.target; + if (typeof opt.target === 'string') { + elt = document.getElementById(opt.target); + } + //Ensure we aren't double binding to the same element + type otherwise we'll end up multi-binding + // and raising events for now reason. So here we'll check if the event is already registered for the element + var boundValues = _.values(keyboardManagerService.keyboardEvent); + var found = _.find(boundValues, function (i) { + return i.target === elt && i.event === opt['type']; + }); + // Store shortcut + keyboardManagerService.keyboardEvent[label] = { + 'callback': callback, + 'target': elt, + 'opt': opt + }; + if (!found) { + //Attach the function with the event + if (elt.addEventListener) { + elt.addEventListener(opt['type'], eventHandler, false); + } else if (elt.attachEvent) { + elt.attachEvent('on' + opt['type'], eventHandler); + } else { + elt['on' + opt['type']] = eventHandler; + } + } + }; + // Remove the shortcut - just specify the shortcut and I will remove the binding + keyboardManagerService.unbind = function (label) { + label = label.toLowerCase(); + var binding = keyboardManagerService.keyboardEvent[label]; + delete keyboardManagerService.keyboardEvent[label]; + if (!binding) { + return; + } + var type = binding['event'], elt = binding['target'], callback = binding['callback']; + if (elt.detachEvent) { + elt.detachEvent('on' + type, callback); + } else if (elt.removeEventListener) { + elt.removeEventListener(type, callback, false); + } else { + elt['on' + type] = false; + } + }; + // + return keyboardManagerService; + } + angular.module('umbraco.services').factory('keyboardService', [ + '$window', + '$timeout', + keyboardService + ]); + /** @ngdoc service * @name umbraco.services.listViewHelper * @@ -3600,15 +3563,12 @@ function keyboardService($window, $timeout) { * }); * */ -(function () { - 'use strict'; - - function listViewHelper(localStorageService) { - - var firstSelectedIndex = 0; - var localStorageKey = "umblistViewLayout"; - - /** + (function () { + 'use strict'; + function listViewHelper(localStorageService) { + var firstSelectedIndex = 0; + var localStorageKey = 'umblistViewLayout'; + /** * @ngdoc method * @name umbraco.services.listViewHelper#getLayout * @methodOf umbraco.services.listViewHelper @@ -3620,30 +3580,22 @@ function keyboardService($window, $timeout) { * @param {Number} nodeId The id of the current node displayed in the content editor * @param {Array} availableLayouts Array of all allowed layouts, available from $scope.model.config.layouts */ - - function getLayout(nodeId, availableLayouts) { - - var storedLayouts = []; - - if (localStorageService.get(localStorageKey)) { - storedLayouts = localStorageService.get(localStorageKey); - } - - if (storedLayouts && storedLayouts.length > 0) { - for (var i = 0; storedLayouts.length > i; i++) { - var layout = storedLayouts[i]; - if (layout.nodeId === nodeId) { - return setLayout(nodeId, layout, availableLayouts); + function getLayout(nodeId, availableLayouts) { + var storedLayouts = []; + if (localStorageService.get(localStorageKey)) { + storedLayouts = localStorageService.get(localStorageKey); + } + if (storedLayouts && storedLayouts.length > 0) { + for (var i = 0; storedLayouts.length > i; i++) { + var layout = storedLayouts[i]; + if (layout.nodeId === nodeId) { + return setLayout(nodeId, layout, availableLayouts); + } } } - + return getFirstAllowedLayout(availableLayouts); } - - return getFirstAllowedLayout(availableLayouts); - - } - - /** + /** * @ngdoc method * @name umbraco.services.listViewHelper#setLayout * @methodOf umbraco.services.listViewHelper @@ -3655,34 +3607,26 @@ function keyboardService($window, $timeout) { * @param {Object} selectedLayout Layout selected as the layout to set as the current layout * @param {Array} availableLayouts Array of all allowed layouts, available from $scope.model.config.layouts */ - - function setLayout(nodeId, selectedLayout, availableLayouts) { - - var activeLayout = {}; - var layoutFound = false; - - for (var i = 0; availableLayouts.length > i; i++) { - var layout = availableLayouts[i]; - if (layout.path === selectedLayout.path) { - activeLayout = layout; - layout.active = true; - layoutFound = true; - } else { - layout.active = false; + function setLayout(nodeId, selectedLayout, availableLayouts) { + var activeLayout = {}; + var layoutFound = false; + for (var i = 0; availableLayouts.length > i; i++) { + var layout = availableLayouts[i]; + if (layout.path === selectedLayout.path) { + activeLayout = layout; + layout.active = true; + layoutFound = true; + } else { + layout.active = false; + } } + if (!layoutFound) { + activeLayout = getFirstAllowedLayout(availableLayouts); + } + saveLayoutInLocalStorage(nodeId, activeLayout); + return activeLayout; } - - if (!layoutFound) { - activeLayout = getFirstAllowedLayout(availableLayouts); - } - - saveLayoutInLocalStorage(nodeId, activeLayout); - - return activeLayout; - - } - - /** + /** * @ngdoc method * @name umbraco.services.listViewHelper#saveLayoutInLocalStorage * @methodOf umbraco.services.listViewHelper @@ -3693,38 +3637,31 @@ function keyboardService($window, $timeout) { * @param {Number} nodeId Id of the current node displayed in the content editor * @param {Object} selectedLayout Layout selected as the layout to set as the current layout */ - - function saveLayoutInLocalStorage(nodeId, selectedLayout) { - var layoutFound = false; - var storedLayouts = []; - - if (localStorageService.get(localStorageKey)) { - storedLayouts = localStorageService.get(localStorageKey); - } - - if (storedLayouts.length > 0) { - for (var i = 0; storedLayouts.length > i; i++) { - var layout = storedLayouts[i]; - if (layout.nodeId === nodeId) { - layout.path = selectedLayout.path; - layoutFound = true; + function saveLayoutInLocalStorage(nodeId, selectedLayout) { + var layoutFound = false; + var storedLayouts = []; + if (localStorageService.get(localStorageKey)) { + storedLayouts = localStorageService.get(localStorageKey); + } + if (storedLayouts.length > 0) { + for (var i = 0; storedLayouts.length > i; i++) { + var layout = storedLayouts[i]; + if (layout.nodeId === nodeId) { + layout.path = selectedLayout.path; + layoutFound = true; + } } } + if (!layoutFound) { + var storageObject = { + 'nodeId': nodeId, + 'path': selectedLayout.path + }; + storedLayouts.push(storageObject); + } + localStorageService.set(localStorageKey, storedLayouts); } - - if (!layoutFound) { - var storageObject = { - "nodeId": nodeId, - "path": selectedLayout.path - }; - storedLayouts.push(storageObject); - } - - localStorageService.set(localStorageKey, storedLayouts); - - } - - /** + /** * @ngdoc method * @name umbraco.services.listViewHelper#getFirstAllowedLayout * @methodOf umbraco.services.listViewHelper @@ -3734,23 +3671,18 @@ function keyboardService($window, $timeout) { * * @param {Array} layouts Array of all allowed layouts, available from $scope.model.config.layouts */ - - function getFirstAllowedLayout(layouts) { - - var firstAllowedLayout = {}; - - for (var i = 0; layouts.length > i; i++) { - var layout = layouts[i]; - if (layout.selected === true) { - firstAllowedLayout = layout; - break; + function getFirstAllowedLayout(layouts) { + var firstAllowedLayout = {}; + for (var i = 0; layouts.length > i; i++) { + var layout = layouts[i]; + if (layout.selected === true) { + firstAllowedLayout = layout; + break; + } } + return firstAllowedLayout; } - - return firstAllowedLayout; - } - - /** + /** * @ngdoc method * @name umbraco.services.listViewHelper#selectHandler * @methodOf umbraco.services.listViewHelper @@ -3766,52 +3698,36 @@ function keyboardService($window, $timeout) { * @param {Array} selection All selected items in the current listview, available as $scope.selection * @param {Event} $event Event triggered by the checkbox being checked to select / deselect an item */ - - function selectHandler(selectedItem, selectedIndex, items, selection, $event) { - - var start = 0; - var end = 0; - var item = null; - - if ($event.shiftKey === true) { - - if (selectedIndex > firstSelectedIndex) { - - start = firstSelectedIndex; - end = selectedIndex; - - for (; end >= start; start++) { - item = items[start]; - selectItem(item, selection); + function selectHandler(selectedItem, selectedIndex, items, selection, $event) { + var start = 0; + var end = 0; + var item = null; + if ($event.shiftKey === true) { + if (selectedIndex > firstSelectedIndex) { + start = firstSelectedIndex; + end = selectedIndex; + for (; end >= start; start++) { + item = items[start]; + selectItem(item, selection); + } + } else { + start = firstSelectedIndex; + end = selectedIndex; + for (; end <= start; start--) { + item = items[start]; + selectItem(item, selection); + } } - } else { - - start = firstSelectedIndex; - end = selectedIndex; - - for (; end <= start; start--) { - item = items[start]; - selectItem(item, selection); + if (selectedItem.selected) { + deselectItem(selectedItem, selection); + } else { + selectItem(selectedItem, selection); } - + firstSelectedIndex = selectedIndex; } - - } else { - - if (selectedItem.selected) { - deselectItem(selectedItem, selection); - } else { - selectItem(selectedItem, selection); - } - - firstSelectedIndex = selectedIndex; - } - - } - - /** + /** * @ngdoc method * @name umbraco.services.listViewHelper#selectItem * @methodOf umbraco.services.listViewHelper @@ -3822,22 +3738,25 @@ function keyboardService($window, $timeout) { * @param {Object} item Item to select * @param {Array} selection Listview selection, available as $scope.selection */ - - function selectItem(item, selection) { - var isSelected = false; - for (var i = 0; selection.length > i; i++) { - var selectedItem = selection[i]; - if (item.id === selectedItem.id) { - isSelected = true; + function selectItem(item, selection) { + var isSelected = false; + for (var i = 0; selection.length > i; i++) { + var selectedItem = selection[i]; + // if item.id is 2147483647 (int.MaxValue) use item.key + if (item.id !== 2147483647 && item.id === selectedItem.id || item.key && item.key === selectedItem.key) { + isSelected = true; + } + } + if (!isSelected) { + var obj = { id: item.id }; + if (item.key) { + obj.key = item.key; + } + selection.push(obj); + item.selected = true; } } - if (!isSelected) { - selection.push({ id: item.id }); - item.selected = true; - } - } - - /** + /** * @ngdoc method * @name umbraco.services.listViewHelper#deselectItem * @methodOf umbraco.services.listViewHelper @@ -3848,18 +3767,17 @@ function keyboardService($window, $timeout) { * @param {Object} item Item to deselect * @param {Array} selection Listview selection, available as $scope.selection */ - - function deselectItem(item, selection) { - for (var i = 0; selection.length > i; i++) { - var selectedItem = selection[i]; - if (item.id === selectedItem.id) { - selection.splice(i, 1); - item.selected = false; + function deselectItem(item, selection) { + for (var i = 0; selection.length > i; i++) { + var selectedItem = selection[i]; + // if item.id is 2147483647 (int.MaxValue) use item.key + if (item.id !== 2147483647 && item.id === selectedItem.id || item.key && item.key === selectedItem.key) { + selection.splice(i, 1); + item.selected = false; + } } } - } - - /** + /** * @ngdoc method * @name umbraco.services.listViewHelper#clearSelection * @methodOf umbraco.services.listViewHelper @@ -3872,29 +3790,23 @@ function keyboardService($window, $timeout) { * @param {Array} folders Folders to remove, can be null * @param {Array} selection Listview selection, available as $scope.selection */ - - function clearSelection(items, folders, selection) { - - var i = 0; - - selection.length = 0; - - if (angular.isArray(items)) { - for (i = 0; items.length > i; i++) { - var item = items[i]; - item.selected = false; + function clearSelection(items, folders, selection) { + var i = 0; + selection.length = 0; + if (angular.isArray(items)) { + for (i = 0; items.length > i; i++) { + var item = items[i]; + item.selected = false; + } + } + if (angular.isArray(folders)) { + for (i = 0; folders.length > i; i++) { + var folder = folders[i]; + folder.selected = false; + } } } - - if(angular.isArray(folders)) { - for (i = 0; folders.length > i; i++) { - var folder = folders[i]; - folder.selected = false; - } - } - } - - /** + /** * @ngdoc method * @name umbraco.services.listViewHelper#selectAllItems * @methodOf umbraco.services.listViewHelper @@ -3907,39 +3819,31 @@ function keyboardService($window, $timeout) { * @param {Array} selection Listview selection, available as $scope.selection * @param {$event} $event Event passed from the checkbox being toggled */ - - function selectAllItems(items, selection, $event) { - - var checkbox = $event.target; - var clearSelection = false; - - if (!angular.isArray(items)) { - return; - } - - selection.length = 0; - - for (var i = 0; i < items.length; i++) { - - var item = items[i]; - - if (checkbox.checked) { - selection.push({ id: item.id }); - } else { - clearSelection = true; + function selectAllItems(items, selection, $event) { + var checkbox = $event.target; + var clearSelection = false; + if (!angular.isArray(items)) { + return; } - - item.selected = checkbox.checked; - - } - - if (clearSelection) { selection.length = 0; + for (var i = 0; i < items.length; i++) { + var item = items[i]; + var obj = { id: item.id }; + if (item.key) { + obj.key = item.key; + } + if (checkbox.checked) { + selection.push(obj); + } else { + clearSelection = true; + } + item.selected = checkbox.checked; + } + if (clearSelection) { + selection.length = 0; + } } - - } - - /** + /** * @ngdoc method * @name umbraco.services.listViewHelper#isSelectedAll * @methodOf umbraco.services.listViewHelper @@ -3952,31 +3856,23 @@ function keyboardService($window, $timeout) { * @param {Array} selection Listview selection, available as $scope.selection * @returns {Boolean} boolean indicate if all items in the listview have been selected */ - - function isSelectedAll(items, selection) { - - var numberOfSelectedItem = 0; - - for (var itemIndex = 0; items.length > itemIndex; itemIndex++) { - var item = items[itemIndex]; - - for (var selectedIndex = 0; selection.length > selectedIndex; selectedIndex++) { - var selectedItem = selection[selectedIndex]; - - if (item.id === selectedItem.id) { - numberOfSelectedItem++; + function isSelectedAll(items, selection) { + var numberOfSelectedItem = 0; + for (var itemIndex = 0; items.length > itemIndex; itemIndex++) { + var item = items[itemIndex]; + for (var selectedIndex = 0; selection.length > selectedIndex; selectedIndex++) { + var selectedItem = selection[selectedIndex]; + // if item.id is 2147483647 (int.MaxValue) use item.key + if (item.id !== 2147483647 && item.id === selectedItem.id || item.key && item.key === selectedItem.key) { + numberOfSelectedItem++; + } } } - + if (numberOfSelectedItem === items.length) { + return true; + } } - - if (numberOfSelectedItem === items.length) { - return true; - } - - } - - /** + /** * @ngdoc method * @name umbraco.services.listViewHelper#setSortingDirection * @methodOf umbraco.services.listViewHelper @@ -3987,12 +3883,10 @@ function keyboardService($window, $timeout) { * @param {String} direction Order direction `asc` or `desc` * @param {Object} options object passed from the parent listview available as $scope.options */ - - function setSortingDirection(col, direction, options) { - return options.orderBy.toUpperCase() === col.toUpperCase() && options.orderDirection === direction; - } - - /** + function setSortingDirection(col, direction, options) { + return options.orderBy.toUpperCase() === col.toUpperCase() && options.orderDirection === direction; + } + /** * @ngdoc method * @name umbraco.services.listViewHelper#setSorting * @methodOf umbraco.services.listViewHelper @@ -4004,81 +3898,69 @@ function keyboardService($window, $timeout) { * @param {Boolean} allow Determines if the user is allowed to set this field, normally true * @param {Object} options Options object passed from the parent listview available as $scope.options */ - - function setSorting(field, allow, options) { - if (allow) { - if (options.orderBy === field && options.orderDirection === 'asc') { - options.orderDirection = "desc"; - } else { - options.orderDirection = "asc"; + function setSorting(field, allow, options) { + if (allow) { + if (options.orderBy === field && options.orderDirection === 'asc') { + options.orderDirection = 'desc'; + } else { + options.orderDirection = 'asc'; + } + options.orderBy = field; } - options.orderBy = field; } - } - - //This takes in a dictionary of Ids with Permissions and determines - // the intersect of all permissions to return an object representing the - // listview button permissions - function getButtonPermissions(unmergedPermissions, currentIdsWithPermissions) { - - if (currentIdsWithPermissions == null) { - currentIdsWithPermissions = {}; + //This takes in a dictionary of Ids with Permissions and determines + // the intersect of all permissions to return an object representing the + // listview button permissions + function getButtonPermissions(unmergedPermissions, currentIdsWithPermissions) { + if (currentIdsWithPermissions == null) { + currentIdsWithPermissions = {}; + } + //merge the newly retrieved permissions to the main dictionary + _.each(unmergedPermissions, function (value, key, list) { + currentIdsWithPermissions[key] = value; + }); + //get the intersect permissions + var arr = []; + _.each(currentIdsWithPermissions, function (value, key, list) { + arr.push(value); + }); + //we need to use 'apply' to call intersection with an array of arrays, + //see: https://stackoverflow.com/a/16229480/694494 + var intersectPermissions = _.intersection.apply(_, arr); + return { + canCopy: _.contains(intersectPermissions, 'O'), + //Magic Char = O + canCreate: _.contains(intersectPermissions, 'C'), + //Magic Char = C + canDelete: _.contains(intersectPermissions, 'D'), + //Magic Char = D + canMove: _.contains(intersectPermissions, 'M'), + //Magic Char = M + canPublish: _.contains(intersectPermissions, 'U'), + //Magic Char = U + canUnpublish: _.contains(intersectPermissions, 'U') //Magic Char = Z (however UI says it can't be set, so if we can publish 'U' we can unpublish) + }; } - - //merge the newly retrieved permissions to the main dictionary - _.each(unmergedPermissions, function (value, key, list) { - currentIdsWithPermissions[key] = value; - }); - - //get the intersect permissions - var arr = []; - _.each(currentIdsWithPermissions, function (value, key, list) { - arr.push(value); - }); - - //we need to use 'apply' to call intersection with an array of arrays, - //see: http://stackoverflow.com/a/16229480/694494 - var intersectPermissions = _.intersection.apply(_, arr); - - return { - canCopy: _.contains(intersectPermissions, 'O'), //Magic Char = O - canCreate: _.contains(intersectPermissions, 'C'), //Magic Char = C - canDelete: _.contains(intersectPermissions, 'D'), //Magic Char = D - canMove: _.contains(intersectPermissions, 'M'), //Magic Char = M - canPublish: _.contains(intersectPermissions, 'U'), //Magic Char = U - canUnpublish: _.contains(intersectPermissions, 'U'), //Magic Char = Z (however UI says it can't be set, so if we can publish 'U' we can unpublish) + var service = { + getLayout: getLayout, + getFirstAllowedLayout: getFirstAllowedLayout, + setLayout: setLayout, + saveLayoutInLocalStorage: saveLayoutInLocalStorage, + selectHandler: selectHandler, + selectItem: selectItem, + deselectItem: deselectItem, + clearSelection: clearSelection, + selectAllItems: selectAllItems, + isSelectedAll: isSelectedAll, + setSortingDirection: setSortingDirection, + setSorting: setSorting, + getButtonPermissions: getButtonPermissions }; + return service; } - - var service = { - - getLayout: getLayout, - getFirstAllowedLayout: getFirstAllowedLayout, - setLayout: setLayout, - saveLayoutInLocalStorage: saveLayoutInLocalStorage, - selectHandler: selectHandler, - selectItem: selectItem, - deselectItem: deselectItem, - clearSelection: clearSelection, - selectAllItems: selectAllItems, - isSelectedAll: isSelectedAll, - setSortingDirection: setSortingDirection, - setSorting: setSorting, - getButtonPermissions: getButtonPermissions - - }; - - return service; - - } - - - angular.module('umbraco.services').factory('listViewHelper', listViewHelper); - - -})(); - -/** + angular.module('umbraco.services').factory('listViewHelper', listViewHelper); + }()); + /** @ngdoc service * @name umbraco.services.listViewPrevalueHelper * @@ -4086,14 +3968,11 @@ function keyboardService($window, $timeout) { * @description * Service for accessing the prevalues of a list view being edited in the inline list view editor in the doctype editor */ -(function () { - 'use strict'; - - function listViewPrevalueHelper() { - - var prevalues = []; - - /** + (function () { + 'use strict'; + function listViewPrevalueHelper() { + var prevalues = []; + /** * @ngdoc method * @name umbraco.services.listViewPrevalueHelper#getPrevalues * @methodOf umbraco.services.listViewPrevalueHelper @@ -4101,12 +3980,10 @@ function keyboardService($window, $timeout) { * @description * Set the collection of prevalues */ - - function getPrevalues() { - return prevalues; - } - - /** + function getPrevalues() { + return prevalues; + } + /** * @ngdoc method * @name umbraco.services.listViewPrevalueHelper#setPrevalues * @methodOf umbraco.services.listViewPrevalueHelper @@ -4116,410 +3993,458 @@ function keyboardService($window, $timeout) { * * @param {Array} values Array of prevalues */ - - function setPrevalues(values) { - prevalues = values; + function setPrevalues(values) { + prevalues = values; + } + var service = { + getPrevalues: getPrevalues, + setPrevalues: setPrevalues + }; + return service; + } + angular.module('umbraco.services').factory('listViewPrevalueHelper', listViewPrevalueHelper); + }()); + /** + * @ngdoc service + * @name umbraco.services.localizationService + * + * @requires $http + * @requires $q + * @requires $window + * @requires $filter + * + * @description + * Application-wide service for handling localization + * + * ##usage + * To use, simply inject the localizationService into any controller that needs it, and make + * sure the umbraco.services module is accesible - which it should be by default. + * + *
    + *    localizationService.localize("area_key").then(function(value){
    + *        element.html(value);
    + *    });
    + * 
    + */ + angular.module('umbraco.services').factory('localizationService', function ($http, $q, eventsService, $window, $filter, userService) { + //TODO: This should be injected as server vars + var url = 'LocalizedText'; + var resourceFileLoadStatus = 'none'; + var resourceLoadingPromise = []; + function _lookup(value, tokens, dictionary) { + //strip the key identifier if its there + if (value && value[0] === '@') { + value = value.substring(1); + } + //if no area specified, add general_ + if (value && value.indexOf('_') < 0) { + value = 'general_' + value; + } + var entry = dictionary[value]; + if (entry) { + if (tokens) { + for (var i = 0; i < tokens.length; i++) { + entry = entry.replace('%' + i + '%', tokens[i]); + } + } + return entry; + } + return '[' + value + ']'; } - - - var service = { - - getPrevalues: getPrevalues, - setPrevalues: setPrevalues - + // array to hold the localized resource string entries + dictionary: [], + // loads the language resource file from the server + initLocalizedResources: function () { + var deferred = $q.defer(); + if (resourceFileLoadStatus === 'loaded') { + deferred.resolve(service.dictionary); + return deferred.promise; + } + //if the resource is already loading, we don't want to force it to load another one in tandem, we'd rather + // wait for that initial http promise to finish and then return this one with the dictionary loaded + if (resourceFileLoadStatus === 'loading') { + //add to the list of promises waiting + resourceLoadingPromise.push(deferred); + //exit now it's already loading + return deferred.promise; + } + resourceFileLoadStatus = 'loading'; + // build the url to retrieve the localized resource file + $http({ + method: 'GET', + url: url, + cache: false + }).then(function (response) { + resourceFileLoadStatus = 'loaded'; + service.dictionary = response.data; + eventsService.emit('localizationService.updated', response.data); + deferred.resolve(response.data); + //ensure all other queued promises are resolved + for (var p in resourceLoadingPromise) { + resourceLoadingPromise[p].resolve(response.data); + } + }, function (err) { + deferred.reject('Something broke'); + //ensure all other queued promises are resolved + for (var p in resourceLoadingPromise) { + resourceLoadingPromise[p].reject('Something broke'); + } + }); + return deferred.promise; + }, + /** + * @ngdoc method + * @name umbraco.services.localizationService#tokenize + * @methodOf umbraco.services.localizationService + * + * @description + * Helper to tokenize and compile a localization string + * @param {String} value the value to tokenize + * @param {Object} scope the $scope object + * @returns {String} tokenized resource string + */ + tokenize: function (value, scope) { + if (value) { + var localizer = value.split(':'); + var retval = { + tokens: undefined, + key: localizer[0].substring(0) + }; + if (localizer.length > 1) { + retval.tokens = localizer[1].split(','); + for (var x = 0; x < retval.tokens.length; x++) { + retval.tokens[x] = scope.$eval(retval.tokens[x]); + } + } + return retval; + } + return value; + }, + /** + * @ngdoc method + * @name umbraco.services.localizationService#localize + * @methodOf umbraco.services.localizationService + * + * @description + * Checks the dictionary for a localized resource string + * @param {String} value the area/key to localize in the format of 'section_key' + * alternatively if no section is set such as 'key' then we assume the key is to be looked in + * the 'general' section + * + * @param {Array} tokens if specified this array will be sent as parameter values + * This replaces %0% and %1% etc in the dictionary key value with the passed in strings + * + * @returns {String} localized resource string + */ + localize: function (value, tokens) { + return service.initLocalizedResources().then(function (dic) { + var val = _lookup(value, tokens, dic); + return val; + }); + }, + /** + * @ngdoc method + * @name umbraco.services.localizationService#localizeMany + * @methodOf umbraco.services.localizationService + * + * @description + * Checks the dictionary for multipe localized resource strings at once, preventing the need for nested promises + * with localizationService.localize + * + * ##Usage + *
    +         * localizationService.localizeMany(["speechBubbles_templateErrorHeader", "speechBubbles_templateErrorText"]).then(function(data){
    +         *      var header = data[0];
    +         *      var message = data[1];
    +         *      notificationService.error(header, message);
    +         * });
    +         * 
    + * + * @param {Array} keys is an array of strings of the area/key to localize in the format of 'section_key' + * alternatively if no section is set such as 'key' then we assume the key is to be looked in + * the 'general' section + * + * @returns {Array} An array of localized resource string in the same order + */ + localizeMany: function (keys) { + if (keys) { + //The LocalizationService.localize promises we want to resolve + var promises = []; + for (var i = 0; i < keys.length; i++) { + promises.push(service.localize(keys[i], undefined)); + } + return $q.all(promises).then(function (localizedValues) { + return localizedValues; + }); + } + }, + /** + * @ngdoc method + * @name umbraco.services.localizationService#concat + * @methodOf umbraco.services.localizationService + * + * @description + * Checks the dictionary for multipe localized resource strings at once & concats them to a single string + * Which was not possible with localizationSerivce.localize() due to returning a promise + * + * ##Usage + *
    +         * localizationService.concat(["speechBubbles_templateErrorHeader", "speechBubbles_templateErrorText"]).then(function(data){
    +         *      var combinedText = data;
    +         * });
    +         * 
    + * + * @param {Array} keys is an array of strings of the area/key to localize in the format of 'section_key' + * alternatively if no section is set such as 'key' then we assume the key is to be looked in + * the 'general' section + * + * @returns {String} An concatenated string of localized resource string passed into the function in the same order + */ + concat: function (keys) { + if (keys) { + //The LocalizationService.localize promises we want to resolve + var promises = []; + for (var i = 0; i < keys.length; i++) { + promises.push(service.localize(keys[i], undefined)); + } + return $q.all(promises).then(function (localizedValues) { + //Build a concat string by looping over the array of resolved promises/translations + var returnValue = ''; + for (var i = 0; i < localizedValues.length; i++) { + returnValue += localizedValues[i]; + } + return returnValue; + }); + } + }, + /** + * @ngdoc method + * @name umbraco.services.localizationService#format + * @methodOf umbraco.services.localizationService + * + * @description + * Checks the dictionary for multipe localized resource strings at once & formats a tokenized message + * Which was not possible with localizationSerivce.localize() due to returning a promise + * + * ##Usage + *
    +         * localizationService.format(["template_insert", "template_insertSections"], "%0% %1%").then(function(data){
    +         *      //Will return 'Insert Sections'
    +         *      var formattedResult = data;
    +         * });
    +         * 
    + * + * @param {Array} keys is an array of strings of the area/key to localize in the format of 'section_key' + * alternatively if no section is set such as 'key' then we assume the key is to be looked in + * the 'general' section + * + * @param {String} message is the string you wish to replace containing tokens in the format of %0% and %1% + * with the localized resource strings + * + * @returns {String} An concatenated string of localized resource string passed into the function in the same order + */ + format: function (keys, message) { + if (keys) { + //The LocalizationService.localize promises we want to resolve + var promises = []; + for (var i = 0; i < keys.length; i++) { + promises.push(service.localize(keys[i], undefined)); + } + return $q.all(promises).then(function (localizedValues) { + //Replace {0} and {1} etc in message with the localized values + for (var i = 0; i < localizedValues.length; i++) { + var token = '%' + i + '%'; + var regex = new RegExp(token, 'g'); + message = message.replace(regex, localizedValues[i]); + } + return message; + }); + } + } }; - + //This happens after login / auth and assets loading + eventsService.on('app.authenticated', function () { + resourceFileLoadStatus = 'none'; + resourceLoadingPromise = []; + }); + // return the local instance when called return service; - + }); + /** + * @ngdoc service + * @name umbraco.services.macroService + * + * + * @description + * A service to return macro information such as generating syntax to insert a macro into an editor + */ + function macroService() { + return { + /** parses the special macro syntax like and returns an object with the macro alias and it's parameters */ + parseMacroSyntax: function (syntax) { + //This regex will match an alias of anything except characters that are quotes or new lines (for legacy reasons, when new macros are created + // their aliases are cleaned an invalid chars are stripped) + var expression = /(<\?UMBRACO_MACRO (?:.+?)?macroAlias=["']([^\"\'\n\r]+?)["'][\s\S]+?)(\/>|>.*?<\/\?UMBRACO_MACRO>)/i; + var match = expression.exec(syntax); + if (!match || match.length < 3) { + return null; + } + var alias = match[2]; + //this will leave us with just the parameters + var paramsChunk = match[1].trim().replace(new RegExp('UMBRACO_MACRO macroAlias=["\']' + alias + '["\']'), '').trim(); + var paramExpression = /(\w+?)=['\"]([\s\S]*?)['\"]/g; + var paramMatch; + var returnVal = { + macroAlias: alias, + macroParamsDictionary: {} + }; + while (paramMatch = paramExpression.exec(paramsChunk)) { + returnVal.macroParamsDictionary[paramMatch[1]] = paramMatch[2]; + } + return returnVal; + }, + /** + * @ngdoc function + * @name umbraco.services.macroService#generateWebFormsSyntax + * @methodOf umbraco.services.macroService + * @function + * + * @description + * generates the syntax for inserting a macro into a rich text editor - this is the very old umbraco style syntax + * + * @param {object} args an object containing the macro alias and it's parameter values + */ + generateMacroSyntax: function (args) { + // + var macroString = ''; + return macroString; + }, + /** + * @ngdoc function + * @name umbraco.services.macroService#generateWebFormsSyntax + * @methodOf umbraco.services.macroService + * @function + * + * @description + * generates the syntax for inserting a macro into a webforms templates + * + * @param {object} args an object containing the macro alias and it's parameter values + */ + generateWebFormsSyntax: function (args) { + var macroString = ''; + return macroString; + }, + /** + * @ngdoc function + * @name umbraco.services.macroService#generateMvcSyntax + * @methodOf umbraco.services.macroService + * @function + * + * @description + * generates the syntax for inserting a macro into an mvc template + * + * @param {object} args an object containing the macro alias and it's parameter values + */ + generateMvcSyntax: function (args) { + var macroString = '@Umbraco.RenderMacro("' + args.macroAlias + '"'; + var hasParams = false; + var paramString; + if (args.macroParamsDictionary) { + paramString = ', new {'; + _.each(args.macroParamsDictionary, function (val, key) { + hasParams = true; + var keyVal = key + '="' + (val ? val : '') + '", '; + paramString += keyVal; + }); + //remove the last , + paramString = paramString.trimEnd(', '); + paramString += '}'; + } + if (hasParams) { + macroString += paramString; + } + macroString += ')'; + return macroString; + }, + collectValueData: function (macro, macroParams, renderingEngine) { + var paramDictionary = {}; + var macroAlias = macro.alias; + var syntax; + _.each(macroParams, function (item) { + var val = item.value; + if (item.value !== null && item.value !== undefined && !_.isString(item.value)) { + try { + val = angular.toJson(val); + } catch (e) { + } + } + //each value needs to be xml escaped!! since the value get's stored as an xml attribute + paramDictionary[item.alias] = _.escape(val); + }); + //get the syntax based on the rendering engine + if (renderingEngine && renderingEngine === 'WebForms') { + syntax = this.generateWebFormsSyntax({ + macroAlias: macroAlias, + macroParamsDictionary: paramDictionary + }); + } else if (renderingEngine && renderingEngine === 'Mvc') { + syntax = this.generateMvcSyntax({ + macroAlias: macroAlias, + macroParamsDictionary: paramDictionary + }); + } else { + syntax = this.generateMacroSyntax({ + macroAlias: macroAlias, + macroParamsDictionary: paramDictionary + }); + } + var macroObject = { + 'macroParamsDictionary': paramDictionary, + 'macroAlias': macroAlias, + 'syntax': syntax + }; + return macroObject; + } + }; } - - - angular.module('umbraco.services').factory('listViewPrevalueHelper', listViewPrevalueHelper); - - -})(); - -/** - * @ngdoc service - * @name umbraco.services.localizationService - * - * @requires $http - * @requires $q - * @requires $window - * @requires $filter - * - * @description - * Application-wide service for handling localization - * - * ##usage - * To use, simply inject the localizationService into any controller that needs it, and make - * sure the umbraco.services module is accesible - which it should be by default. - * - *
    - *    localizationService.localize("area_key").then(function(value){
    - *        element.html(value);
    - *    });
    - * 
    - */ - -angular.module('umbraco.services') -.factory('localizationService', function ($http, $q, eventsService, $window, $filter, userService) { - - //TODO: This should be injected as server vars - var url = "LocalizedText"; - var resourceFileLoadStatus = "none"; - var resourceLoadingPromise = []; - - function _lookup(value, tokens, dictionary) { - - //strip the key identifier if its there - if (value && value[0] === "@") { - value = value.substring(1); - } - - //if no area specified, add general_ - if (value && value.indexOf("_") < 0) { - value = "general_" + value; - } - - var entry = dictionary[value]; - if (entry) { - if (tokens) { - for (var i = 0; i < tokens.length; i++) { - entry = entry.replace("%" + i + "%", tokens[i]); - } - } - return entry; - } - return "[" + value + "]"; - } - - var service = { - // array to hold the localized resource string entries - dictionary: [], - - // loads the language resource file from the server - initLocalizedResources: function () { - var deferred = $q.defer(); - - if (resourceFileLoadStatus === "loaded") { - deferred.resolve(service.dictionary); - return deferred.promise; - } - - //if the resource is already loading, we don't want to force it to load another one in tandem, we'd rather - // wait for that initial http promise to finish and then return this one with the dictionary loaded - if (resourceFileLoadStatus === "loading") { - //add to the list of promises waiting - resourceLoadingPromise.push(deferred); - - //exit now it's already loading - return deferred.promise; - } - - resourceFileLoadStatus = "loading"; - - // build the url to retrieve the localized resource file - $http({ method: "GET", url: url, cache: false }) - .then(function (response) { - resourceFileLoadStatus = "loaded"; - service.dictionary = response.data; - - eventsService.emit("localizationService.updated", response.data); - - deferred.resolve(response.data); - //ensure all other queued promises are resolved - for (var p in resourceLoadingPromise) { - resourceLoadingPromise[p].resolve(response.data); - } - }, function (err) { - deferred.reject("Something broke"); - //ensure all other queued promises are resolved - for (var p in resourceLoadingPromise) { - resourceLoadingPromise[p].reject("Something broke"); - } - }); - return deferred.promise; - }, - - /** - * @ngdoc method - * @name umbraco.services.localizationService#tokenize - * @methodOf umbraco.services.localizationService - * - * @description - * Helper to tokenize and compile a localization string - * @param {String} value the value to tokenize - * @param {Object} scope the $scope object - * @returns {String} tokenized resource string - */ - tokenize: function (value, scope) { - if (value) { - var localizer = value.split(':'); - var retval = { tokens: undefined, key: localizer[0].substring(0) }; - if (localizer.length > 1) { - retval.tokens = localizer[1].split(','); - for (var x = 0; x < retval.tokens.length; x++) { - retval.tokens[x] = scope.$eval(retval.tokens[x]); - } - } - - return retval; - } - return value; - }, - - /** - * @ngdoc method - * @name umbraco.services.localizationService#localize - * @methodOf umbraco.services.localizationService - * - * @description - * Checks the dictionary for a localized resource string - * @param {String} value the area/key to localize - * @param {Array} tokens if specified this array will be sent as parameter values - * @returns {String} localized resource string - */ - localize: function (value, tokens) { - return service.initLocalizedResources().then(function (dic) { - var val = _lookup(value, tokens, dic); - return val; - }); - }, - - }; - - //This happens after login / auth and assets loading - eventsService.on("app.authenticated", function () { - resourceFileLoadStatus = "none"; - resourceLoadingPromise = []; - }); - - // return the local instance when called - return service; -}); - -/** - * @ngdoc service - * @name umbraco.services.macroService - * - * - * @description - * A service to return macro information such as generating syntax to insert a macro into an editor - */ -function macroService() { - - return { - - /** parses the special macro syntax like and returns an object with the macro alias and it's parameters */ - parseMacroSyntax: function (syntax) { - - //This regex will match an alias of anything except characters that are quotes or new lines (for legacy reasons, when new macros are created - // their aliases are cleaned an invalid chars are stripped) - var expression = /(<\?UMBRACO_MACRO (?:.+?)?macroAlias=["']([^\"\'\n\r]+?)["'][\s\S]+?)(\/>|>.*?<\/\?UMBRACO_MACRO>)/i; - var match = expression.exec(syntax); - if (!match || match.length < 3) { - return null; - } - var alias = match[2]; - - //this will leave us with just the parameters - var paramsChunk = match[1].trim().replace(new RegExp("UMBRACO_MACRO macroAlias=[\"']" + alias + "[\"']"), "").trim(); - - var paramExpression = /(\w+?)=['\"]([\s\S]*?)['\"]/g; - - var paramMatch; - var returnVal = { - macroAlias: alias, - macroParamsDictionary: {} - }; - while (paramMatch = paramExpression.exec(paramsChunk)) { - returnVal.macroParamsDictionary[paramMatch[1]] = paramMatch[2]; - } - return returnVal; - }, - - /** - * @ngdoc function - * @name umbraco.services.macroService#generateWebFormsSyntax - * @methodOf umbraco.services.macroService - * @function - * - * @description - * generates the syntax for inserting a macro into a rich text editor - this is the very old umbraco style syntax - * - * @param {object} args an object containing the macro alias and it's parameter values - */ - generateMacroSyntax: function (args) { - - // - - var macroString = '"; - - return macroString; - }, - - /** - * @ngdoc function - * @name umbraco.services.macroService#generateWebFormsSyntax - * @methodOf umbraco.services.macroService - * @function - * - * @description - * generates the syntax for inserting a macro into a webforms templates - * - * @param {object} args an object containing the macro alias and it's parameter values - */ - generateWebFormsSyntax: function(args) { - - var macroString = '"; - - return macroString; - }, - - /** - * @ngdoc function - * @name umbraco.services.macroService#generateMvcSyntax - * @methodOf umbraco.services.macroService - * @function - * - * @description - * generates the syntax for inserting a macro into an mvc template - * - * @param {object} args an object containing the macro alias and it's parameter values - */ - generateMvcSyntax: function (args) { - - var macroString = "@Umbraco.RenderMacro(\"" + args.macroAlias + "\""; - - var hasParams = false; - var paramString; - if (args.macroParamsDictionary) { - - paramString = ", new {"; - - _.each(args.macroParamsDictionary, function(val, key) { - - hasParams = true; - - var keyVal = key + "=\"" + (val ? val : "") + "\", "; - - paramString += keyVal; - }); - - //remove the last , - paramString = paramString.trimEnd(", "); - - paramString += "}"; - } - if (hasParams) { - macroString += paramString; - } - - macroString += ")"; - return macroString; - }, - - collectValueData: function(macro, macroParams, renderingEngine) { - - var paramDictionary = {}; - var macroAlias = macro.alias; - var syntax; - - _.each(macroParams, function (item) { - - var val = item.value; - - if (item.value !== null && item.value !== undefined && !_.isString(item.value)) { - try { - val = angular.toJson(val); - } - catch (e) { - // not json - } - } - - //each value needs to be xml escaped!! since the value get's stored as an xml attribute - paramDictionary[item.alias] = _.escape(val); - - }); - - //get the syntax based on the rendering engine - if (renderingEngine && renderingEngine === "WebForms") { - syntax = this.generateWebFormsSyntax({ macroAlias: macroAlias, macroParamsDictionary: paramDictionary }); - } - else if (renderingEngine && renderingEngine === "Mvc") { - syntax = this.generateMvcSyntax({ macroAlias: macroAlias, macroParamsDictionary: paramDictionary }); - } - else { - syntax = this.generateMacroSyntax({ macroAlias: macroAlias, macroParamsDictionary: paramDictionary }); - } - - var macroObject = { - "macroParamsDictionary": paramDictionary, - "macroAlias": macroAlias, - "syntax": syntax - }; - - return macroObject; - - } - - }; - -} - -angular.module('umbraco.services').factory('macroService', macroService); - -/** + angular.module('umbraco.services').factory('macroService', macroService); + /** * @ngdoc service * @name umbraco.services.mediaHelper * @description A helper object used for dealing with media items **/ -function mediaHelper(umbRequestHelper) { - - //container of fileresolvers - var _mediaFileResolvers = {}; - - return { - /** + function mediaHelper(umbRequestHelper) { + //container of fileresolvers + var _mediaFileResolvers = {}; + return { + /** * @ngdoc function * @name umbraco.services.mediaHelper#getImagePropertyValue * @methodOf umbraco.services.mediaHelper @@ -4532,61 +4457,54 @@ function mediaHelper(umbRequestHelper) { * @param {object} options.mediaModel The media object to retrieve the image path from * @param {object} options.imageOnly Optional, if true then will only return a path if the media item is an image */ - getMediaPropertyValue: function (options) { - if (!options || !options.mediaModel) { - throw "The options objet does not contain the required parameters: mediaModel"; - } - - //combine all props, TODO: we really need a better way then this - var props = []; - if (options.mediaModel.properties) { - props = options.mediaModel.properties; - } else { - $(options.mediaModel.tabs).each(function (i, tab) { - props = props.concat(tab.properties); + getMediaPropertyValue: function (options) { + if (!options || !options.mediaModel) { + throw 'The options objet does not contain the required parameters: mediaModel'; + } + //combine all props, TODO: we really need a better way then this + var props = []; + if (options.mediaModel.properties) { + props = options.mediaModel.properties; + } else { + $(options.mediaModel.tabs).each(function (i, tab) { + props = props.concat(tab.properties); + }); + } + var mediaRoot = Umbraco.Sys.ServerVariables.umbracoSettings.mediaPath; + var imageProp = _.find(props, function (item) { + if (item.alias === 'umbracoFile') { + return true; + } + //this performs a simple check to see if we have a media file as value + //it doesnt catch everything, but better then nothing + if (angular.isString(item.value) && item.value.indexOf(mediaRoot) === 0) { + return true; + } + return false; }); - } - - var mediaRoot = Umbraco.Sys.ServerVariables.umbracoSettings.mediaPath; - var imageProp = _.find(props, function (item) { - if (item.alias === "umbracoFile") { - return true; + if (!imageProp) { + return ''; } - - //this performs a simple check to see if we have a media file as value - //it doesnt catch everything, but better then nothing - if (angular.isString(item.value) && item.value.indexOf(mediaRoot) === 0) { - return true; + var mediaVal; + //our default images might store one or many images (as csv) + var split = imageProp.value.split(','); + var self = this; + mediaVal = _.map(split, function (item) { + return { + file: item, + isImage: self.detectIfImageByExtension(item) + }; + }); + //for now we'll just return the first image in the collection. + //TODO: we should enable returning many to be displayed in the picker if the uploader supports many. + if (mediaVal.length && mediaVal.length > 0) { + if (!options.imageOnly || options.imageOnly === true && mediaVal[0].isImage) { + return mediaVal[0].file; + } } - - return false; - }); - - if (!imageProp) { - return ""; - } - - var mediaVal; - - //our default images might store one or many images (as csv) - var split = imageProp.value.split(','); - var self = this; - mediaVal = _.map(split, function (item) { - return { file: item, isImage: self.detectIfImageByExtension(item) }; - }); - - //for now we'll just return the first image in the collection. - //TODO: we should enable returning many to be displayed in the picker if the uploader supports many. - if (mediaVal.length && mediaVal.length > 0) { - if (!options.imageOnly || (options.imageOnly === true && mediaVal[0].isImage)) { - return mediaVal[0].file; - } - } - - return ""; - }, - - /** + return ''; + }, + /** * @ngdoc function * @name umbraco.services.mediaHelper#getImagePropertyValue * @methodOf umbraco.services.mediaHelper @@ -4598,19 +4516,16 @@ function mediaHelper(umbRequestHelper) { * @param {object} options Options object * @param {object} options.imageModel The media object to retrieve the image path from */ - getImagePropertyValue: function (options) { - if (!options || (!options.imageModel && !options.mediaModel)) { - throw "The options objet does not contain the required parameters: imageModel"; - } - - //required to support backwards compatibility. - options.mediaModel = options.imageModel ? options.imageModel : options.mediaModel; - - options.imageOnly = true; - - return this.getMediaPropertyValue(options); - }, - /** + getImagePropertyValue: function (options) { + if (!options || !options.imageModel && !options.mediaModel) { + throw 'The options objet does not contain the required parameters: imageModel'; + } + //required to support backwards compatibility. + options.mediaModel = options.imageModel ? options.imageModel : options.mediaModel; + options.imageOnly = true; + return this.getMediaPropertyValue(options); + }, + /** * @ngdoc function * @name umbraco.services.mediaHelper#getThumbnail * @methodOf umbraco.services.mediaHelper @@ -4622,24 +4537,20 @@ function mediaHelper(umbRequestHelper) { * @param {object} options Options object * @param {object} options.imageModel The media object to retrieve the image path from */ - getThumbnail: function (options) { - - if (!options || !options.imageModel) { - throw "The options objet does not contain the required parameters: imageModel"; - } - - var imagePropVal = this.getImagePropertyValue(options); - if (imagePropVal !== "") { - return this.getThumbnailFromPath(imagePropVal); - } - return ""; - }, - - registerFileResolver: function(propertyEditorAlias, func){ - _mediaFileResolvers[propertyEditorAlias] = func; - }, - - /** + getThumbnail: function (options) { + if (!options || !options.imageModel) { + throw 'The options objet does not contain the required parameters: imageModel'; + } + var imagePropVal = this.getImagePropertyValue(options); + if (imagePropVal !== '') { + return this.getThumbnailFromPath(imagePropVal); + } + return ''; + }, + registerFileResolver: function (propertyEditorAlias, func) { + _mediaFileResolvers[propertyEditorAlias] = func; + }, + /** * @ngdoc function * @name umbraco.services.mediaHelper#resolveFileFromEntity * @methodOf umbraco.services.mediaHelper @@ -4651,32 +4562,27 @@ function mediaHelper(umbRequestHelper) { * @param {object} mediaEntity A media Entity returned from the entityResource * @param {boolean} thumbnail Whether to return the thumbnail url or normal url */ - resolveFileFromEntity : function(mediaEntity, thumbnail) { - - if (!angular.isObject(mediaEntity.metaData)) { - throw "Cannot resolve the file url from the mediaEntity, it does not contain the required metaData"; - } - - var values = _.values(mediaEntity.metaData); - for (var i = 0; i < values.length; i++) { - var val = values[i]; - if (angular.isObject(val) && val.PropertyEditorAlias) { - for (var resolver in _mediaFileResolvers) { - if (val.PropertyEditorAlias === resolver) { - //we need to format a property variable that coincides with how the property would be structured - // if it came from the mediaResource just to keep things slightly easier for the file resolvers. - var property = { value: val.Value }; - - return _mediaFileResolvers[resolver](property, mediaEntity, thumbnail); + resolveFileFromEntity: function (mediaEntity, thumbnail) { + if (!angular.isObject(mediaEntity.metaData)) { + throw 'Cannot resolve the file url from the mediaEntity, it does not contain the required metaData'; + } + var values = _.values(mediaEntity.metaData); + for (var i = 0; i < values.length; i++) { + var val = values[i]; + if (angular.isObject(val) && val.PropertyEditorAlias) { + for (var resolver in _mediaFileResolvers) { + if (val.PropertyEditorAlias === resolver) { + //we need to format a property variable that coincides with how the property would be structured + // if it came from the mediaResource just to keep things slightly easier for the file resolvers. + var property = { value: val.Value }; + return _mediaFileResolvers[resolver](property, mediaEntity, thumbnail); + } } } } - } - - return ""; - }, - - /** + return ''; + }, + /** * @ngdoc function * @name umbraco.services.mediaHelper#resolveFile * @methodOf umbraco.services.mediaHelper @@ -4688,71 +4594,69 @@ function mediaHelper(umbRequestHelper) { * @param {object} mediaEntity A media Entity returned from the entityResource * @param {boolean} thumbnail Whether to return the thumbnail url or normal url */ - /*jshint loopfunc: true */ - resolveFile : function(mediaItem, thumbnail){ - - function iterateProps(props){ - var res = null; - for(var resolver in _mediaFileResolvers) { - var property = _.find(props, function(prop){ return prop.editor === resolver; }); - if(property){ - res = _mediaFileResolvers[resolver](property, mediaItem, thumbnail); - break; - } - } - - return res; - } - - //we either have properties raw on the object, or spread out on tabs - var result = ""; - if(mediaItem.properties){ - result = iterateProps(mediaItem.properties); - }else if(mediaItem.tabs){ - for(var tab in mediaItem.tabs) { - if(mediaItem.tabs[tab].properties){ - result = iterateProps(mediaItem.tabs[tab].properties); - if(result){ + /*jshint loopfunc: true */ + resolveFile: function (mediaItem, thumbnail) { + function iterateProps(props) { + var res = null; + for (var resolver in _mediaFileResolvers) { + var property = _.find(props, function (prop) { + return prop.editor === resolver; + }); + if (property) { + res = _mediaFileResolvers[resolver](property, mediaItem, thumbnail); break; } } + return res; } - } - return result; - }, - - /*jshint loopfunc: true */ - hasFilePropertyType : function(mediaItem){ - function iterateProps(props){ - var res = false; - for(var resolver in _mediaFileResolvers) { - var property = _.find(props, function(prop){ return prop.editor === resolver; }); - if(property){ - res = true; - break; - } - } - return res; - } - - //we either have properties raw on the object, or spread out on tabs - var result = false; - if(mediaItem.properties){ - result = iterateProps(mediaItem.properties); - }else if(mediaItem.tabs){ - for(var tab in mediaItem.tabs) { - if(mediaItem.tabs[tab].properties){ - result = iterateProps(mediaItem.tabs[tab].properties); - if(result){ - break; - } - } - } - } - return result; - }, - - /** + //we either have properties raw on the object, or spread out on tabs + var result = ''; + if (mediaItem.properties) { + result = iterateProps(mediaItem.properties); + } else if (mediaItem.tabs) { + for (var tab in mediaItem.tabs) { + if (mediaItem.tabs[tab].properties) { + result = iterateProps(mediaItem.tabs[tab].properties); + if (result) { + break; + } + } + } + } + return result; + }, + /*jshint loopfunc: true */ + hasFilePropertyType: function (mediaItem) { + function iterateProps(props) { + var res = false; + for (var resolver in _mediaFileResolvers) { + var property = _.find(props, function (prop) { + return prop.editor === resolver; + }); + if (property) { + res = true; + break; + } + } + return res; + } + //we either have properties raw on the object, or spread out on tabs + var result = false; + if (mediaItem.properties) { + result = iterateProps(mediaItem.properties); + } else if (mediaItem.tabs) { + for (var tab in mediaItem.tabs) { + if (mediaItem.tabs[tab].properties) { + result = iterateProps(mediaItem.tabs[tab].properties); + if (result) { + break; + } + } + } + } + return result; + }, + /** * @ngdoc function * @name umbraco.services.mediaHelper#scaleToMaxSize * @methodOf umbraco.services.mediaHelper @@ -4765,37 +4669,38 @@ function mediaHelper(umbRequestHelper) { * @param {number} width Current width * @param {number} height Current height */ - scaleToMaxSize: function (maxSize, width, height) { - var retval = { width: width, height: height }; - - var maxWidth = maxSize; // Max width for the image - var maxHeight = maxSize; // Max height for the image - var ratio = 0; // Used for aspect ratio - - // Check if the current width is larger than the max - if (width > maxWidth) { - ratio = maxWidth / width; // get ratio for scaling image - - retval.width = maxWidth; - retval.height = height * ratio; - - height = height * ratio; // Reset height to match scaled image - width = width * ratio; // Reset width to match scaled image - } - - // Check if current height is larger than max - if (height > maxHeight) { - ratio = maxHeight / height; // get ratio for scaling image - - retval.height = maxHeight; - retval.width = width * ratio; - width = width * ratio; // Reset width to match scaled image - } - - return retval; - }, - - /** + scaleToMaxSize: function (maxSize, width, height) { + var retval = { + width: width, + height: height + }; + var maxWidth = maxSize; + // Max width for the image + var maxHeight = maxSize; + // Max height for the image + var ratio = 0; + // Used for aspect ratio + // Check if the current width is larger than the max + if (width > maxWidth) { + ratio = maxWidth / width; + // get ratio for scaling image + retval.width = maxWidth; + retval.height = height * ratio; + height = height * ratio; + // Reset height to match scaled image + width = width * ratio; // Reset width to match scaled image + } + // Check if current height is larger than max + if (height > maxHeight) { + ratio = maxHeight / height; + // get ratio for scaling image + retval.height = maxHeight; + retval.width = width * ratio; + width = width * ratio; // Reset width to match scaled image + } + return retval; + }, + /** * @ngdoc function * @name umbraco.services.mediaHelper#getThumbnailFromPath * @methodOf umbraco.services.mediaHelper @@ -4806,26 +4711,18 @@ function mediaHelper(umbRequestHelper) { * * @param {string} imagePath Image path, ex: /media/1234/my-image.jpg */ - getThumbnailFromPath: function (imagePath) { - - //If the path is not an image we cannot get a thumb - if (!this.detectIfImageByExtension(imagePath)) { - return null; - } - - //get the proxy url for big thumbnails (this ensures one is always generated) - var thumbnailUrl = umbRequestHelper.getApiUrl( - "imagesApiBaseUrl", - "GetBigThumbnail", - [{ originalImagePath: imagePath }]); - - //var ext = imagePath.substr(imagePath.lastIndexOf('.')); - //return imagePath.substr(0, imagePath.lastIndexOf('.')) + "_big-thumb" + ".jpg"; - - return thumbnailUrl; - }, - - /** + getThumbnailFromPath: function (imagePath) { + //If the path is not an image we cannot get a thumb + if (!this.detectIfImageByExtension(imagePath)) { + return null; + } + //get the proxy url for big thumbnails (this ensures one is always generated) + var thumbnailUrl = umbRequestHelper.getApiUrl('imagesApiBaseUrl', 'GetBigThumbnail', [{ originalImagePath: imagePath }]); + //var ext = imagePath.substr(imagePath.lastIndexOf('.')); + //return imagePath.substr(0, imagePath.lastIndexOf('.')) + "_big-thumb" + ".jpg"; + return thumbnailUrl; + }, + /** * @ngdoc function * @name umbraco.services.mediaHelper#detectIfImageByExtension * @methodOf umbraco.services.mediaHelper @@ -4836,18 +4733,15 @@ function mediaHelper(umbRequestHelper) { * * @param {string} imagePath Image path, ex: /media/1234/my-image.jpg */ - detectIfImageByExtension: function (imagePath) { - - if (!imagePath) { - return false; - } - - var lowered = imagePath.toLowerCase(); - var ext = lowered.substr(lowered.lastIndexOf(".") + 1); - return ("," + Umbraco.Sys.ServerVariables.umbracoSettings.imageFileTypes + ",").indexOf("," + ext + ",") !== -1; - }, - - /** + detectIfImageByExtension: function (imagePath) { + if (!imagePath) { + return false; + } + var lowered = imagePath.toLowerCase(); + var ext = lowered.substr(lowered.lastIndexOf('.') + 1); + return (',' + Umbraco.Sys.ServerVariables.umbracoSettings.imageFileTypes + ',').indexOf(',' + ext + ',') !== -1; + }, + /** * @ngdoc function * @name umbraco.services.mediaHelper#formatFileTypes * @methodOf umbraco.services.mediaHelper @@ -4858,165 +4752,246 @@ function mediaHelper(umbRequestHelper) { * * @param {string} file types, ex: jpg,png,tiff */ - formatFileTypes: function(fileTypes) { - - var fileTypesArray = fileTypes.split(','); - var newFileTypesArray = []; - - for (var i = 0; i < fileTypesArray.length; i++) { - var fileType = fileTypesArray[i]; - - if (fileType.indexOf(".") !== 0) { - fileType = ".".concat(fileType); - } - - newFileTypesArray.push(fileType); - } - - return newFileTypesArray.join(","); - - } - - }; -}angular.module('umbraco.services').factory('mediaHelper', mediaHelper); - -/** + formatFileTypes: function (fileTypes) { + var fileTypesArray = fileTypes.split(','); + var newFileTypesArray = []; + for (var i = 0; i < fileTypesArray.length; i++) { + var fileType = fileTypesArray[i]; + if (fileType.indexOf('.') !== 0) { + fileType = '.'.concat(fileType); + } + newFileTypesArray.push(fileType); + } + return newFileTypesArray.join(','); + }, + /** + * @ngdoc function + * @name umbraco.services.mediaHelper#getFileExtension + * @methodOf umbraco.services.mediaHelper + * @function + * + * @description + * Returns file extension + * + * @param {string} filePath File path, ex /media/1234/my-image.jpg + */ + getFileExtension: function (filePath) { + if (!filePath) { + return false; + } + var lowered = filePath.toLowerCase(); + var ext = lowered.substr(lowered.lastIndexOf('.') + 1); + return ext; + } + }; + } + angular.module('umbraco.services').factory('mediaHelper', mediaHelper); + /** * @ngdoc service * @name umbraco.services.mediaTypeHelper * @description A helper service for the media types **/ -function mediaTypeHelper(mediaTypeResource, $q) { - - var mediaTypeHelperService = { - - getAllowedImagetypes: function (mediaId){ - - // Get All allowedTypes - return mediaTypeResource.getAllowedTypes(mediaId) - .then(function(types){ - - var allowedQ = types.map(function(type){ + function mediaTypeHelper(mediaTypeResource, $q) { + var mediaTypeHelperService = { + isFolderType: function (mediaEntity) { + if (!mediaEntity) { + throw 'mediaEntity is null'; + } + if (!mediaEntity.contentTypeAlias) { + throw 'mediaEntity.contentTypeAlias is null'; + } + //if you create a media type, which has an alias that ends with ...Folder then its a folder: ex: "secureFolder", "bannerFolder", "Folder" + //this is the exact same logic that is performed in MediaController.GetChildFolders + return mediaEntity.contentTypeAlias.endsWith('Folder'); + }, + getAllowedImagetypes: function (mediaId) { + //TODO: This is horribly inneficient - why make one request per type!? + //This should make a call to c# to get exactly what it's looking for instead of returning every single media type and doing + //some filtering on the client side. + //This is also called multiple times when it's not needed! Example, when launching the media picker, this will be called twice + //which means we'll be making at least 6 REST calls to fetch each media type + // Get All allowedTypes + return mediaTypeResource.getAllowedTypes(mediaId).then(function (types) { + var allowedQ = types.map(function (type) { return mediaTypeResource.getById(type.id); }); - // Get full list - return $q.all(allowedQ).then(function(fullTypes){ - + return $q.all(allowedQ).then(function (fullTypes) { // Find all the media types with an Image Cropper property editor var filteredTypes = mediaTypeHelperService.getTypeWithEditor(fullTypes, ['Umbraco.ImageCropper']); - // If there is only one media type with an Image Cropper we will return this one - if(filteredTypes.length === 1) { - return filteredTypes; - // If there is more than one Image cropper, custom media types have been added, and we return all media types with and Image cropper or UploadField + if (filteredTypes.length === 1) { + return filteredTypes; // If there is more than one Image cropper, custom media types have been added, and we return all media types with and Image cropper or UploadField } else { - return mediaTypeHelperService.getTypeWithEditor(fullTypes, ['Umbraco.ImageCropper', 'Umbraco.UploadField']); + return mediaTypeHelperService.getTypeWithEditor(fullTypes, [ + 'Umbraco.ImageCropper', + 'Umbraco.UploadField' + ]); } - }); - }); - }, - - getTypeWithEditor: function (types, editors) { - - return types.filter(function (mediatype) { - for (var i = 0; i < mediatype.groups.length; i++) { - var group = mediatype.groups[i]; - for (var j = 0; j < group.properties.length; j++) { - var property = group.properties[j]; - if( editors.indexOf(property.editor) !== -1 ) { - return mediatype; + }); + }, + getTypeWithEditor: function (types, editors) { + return types.filter(function (mediatype) { + for (var i = 0; i < mediatype.groups.length; i++) { + var group = mediatype.groups[i]; + for (var j = 0; j < group.properties.length; j++) { + var property = group.properties[j]; + if (editors.indexOf(property.editor) !== -1) { + return mediatype; + } } } + }); + } + }; + return mediaTypeHelperService; + } + angular.module('umbraco.services').factory('mediaTypeHelper', mediaTypeHelper); + /** + * @ngdoc service + * @name umbraco.services.umbracoMenuActions + * + * @requires q + * @requires treeService + * + * @description + * Defines the methods that are called when menu items declare only an action to execute + */ + function umbracoMenuActions($q, treeService, $location, navigationService, appState, umbRequestHelper, notificationsService, localizationService) { + return { + 'ExportMember': function (args) { + var url = umbRequestHelper.getApiUrl('memberApiBaseUrl', 'ExportMemberData', [{ key: args.entity.id }]); + umbRequestHelper.downloadFile(url).then(function () { + localizationService.localize('speechBubbles_memberExportedSuccess').then(function (value) { + notificationsService.success(value); + }); + }, function (data) { + localizationService.localize('speechBubbles_memberExportedError').then(function (value) { + notificationsService.error(value); + }); + }); + }, + /** + * @ngdoc method + * @name umbraco.services.umbracoMenuActions#RefreshNode + * @methodOf umbraco.services.umbracoMenuActions + * @function + * + * @description + * Clears all node children and then gets it's up-to-date children from the server and re-assigns them + * @param {object} args An arguments object + * @param {object} args.entity The basic entity being acted upon + * @param {object} args.treeAlias The tree alias associated with this entity + * @param {object} args.section The current section + */ + 'RefreshNode': function (args) { + ////just in case clear any tree cache for this node/section + //treeService.clearCache({ + // cacheKey: "__" + args.section, //each item in the tree cache is cached by the section name + // childrenOf: args.entity.parentId //clear the children of the parent + //}); + //since we're dealing with an entity, we need to attempt to find it's tree node, in the main tree + // this action is purely a UI thing so if for whatever reason there is no loaded tree node in the UI + // we can safely ignore this process. + //to find a visible tree node, we'll go get the currently loaded root node from appState + var treeRoot = appState.getTreeState('currentRootNode'); + if (treeRoot && treeRoot.root) { + var treeNode = treeService.getDescendantNode(treeRoot.root, args.entity.id, args.treeAlias); + if (treeNode) { + treeService.loadNodeChildren({ + node: treeNode, + section: args.section + }); + } } - }); - + }, + /** + * @ngdoc method + * @name umbraco.services.umbracoMenuActions#CreateChildEntity + * @methodOf umbraco.services.umbracoMenuActions + * @function + * + * @description + * This will re-route to a route for creating a new entity as a child of the current node + * @param {object} args An arguments object + * @param {object} args.entity The basic entity being acted upon + * @param {object} args.treeAlias The tree alias associated with this entity + * @param {object} args.section The current section + */ + 'CreateChildEntity': function (args) { + navigationService.hideNavigation(); + var route = '/' + args.section + '/' + args.treeAlias + '/edit/' + args.entity.id; + //change to new path + $location.path(route).search({ create: true }); + } + }; + } + angular.module('umbraco.services').factory('umbracoMenuActions', umbracoMenuActions); + (function () { + 'use strict'; + function miniEditorHelper(dialogService, editorState, fileManager, contentEditingHelper, $q) { + var launched = false; + function launchMiniEditor(node) { + var deferred = $q.defer(); + launched = true; + //We need to store the current files selected in the file manager locally because the fileManager + // is a singleton and is shared globally. The mini dialog will also be referencing the fileManager + // and we don't want it to be sharing the same files as the main editor. So we'll store the current files locally here, + // clear them out and then launch the dialog. When the dialog closes, we'll reset the fileManager to it's previous state. + var currFiles = _.groupBy(fileManager.getFiles(), 'alias'); + fileManager.clearFiles(); + //We need to store the original editorState entity because it will need to change when the mini editor is loaded so that + // any property editors that are working with editorState get given the correct entity, otherwise strange things will + // start happening. + var currEditorState = editorState.getCurrent(); + dialogService.open({ + template: 'views/common/dialogs/content/edit.html', + id: node.id, + closeOnSave: true, + tabFilter: ['Generic properties'], + callback: function (data) { + //set the node name back + node.name = data.name; + //reset the fileManager to what it was + fileManager.clearFiles(); + _.each(currFiles, function (val, key) { + fileManager.setFiles(key, _.map(currFiles['upload'], function (i) { + return i.file; + })); + }); + //reset the editor state + editorState.set(currEditorState); + //Now we need to check if the content item that was edited was actually the same content item + // as the main content editor and if so, update all property data + if (data.id === currEditorState.id) { + var changed = contentEditingHelper.reBindChangedProperties(currEditorState, data); + } + launched = false; + deferred.resolve(data); + }, + closeCallback: function () { + //reset the fileManager to what it was + fileManager.clearFiles(); + _.each(currFiles, function (val, key) { + fileManager.setFiles(key, _.map(currFiles['upload'], function (i) { + return i.file; + })); + }); + //reset the editor state + editorState.set(currEditorState); + launched = false; + deferred.reject(); + } + }); + return deferred.promise; + } + var service = { launchMiniEditor: launchMiniEditor }; + return service; } - - }; - - return mediaTypeHelperService; -} -angular.module('umbraco.services').factory('mediaTypeHelper', mediaTypeHelper); - -/** - * @ngdoc service - * @name umbraco.services.umbracoMenuActions - * - * @requires q - * @requires treeService - * - * @description - * Defines the methods that are called when menu items declare only an action to execute - */ -function umbracoMenuActions($q, treeService, $location, navigationService, appState) { - - return { - - /** - * @ngdoc method - * @name umbraco.services.umbracoMenuActions#RefreshNode - * @methodOf umbraco.services.umbracoMenuActions - * @function - * - * @description - * Clears all node children and then gets it's up-to-date children from the server and re-assigns them - * @param {object} args An arguments object - * @param {object} args.entity The basic entity being acted upon - * @param {object} args.treeAlias The tree alias associated with this entity - * @param {object} args.section The current section - */ - "RefreshNode": function (args) { - - ////just in case clear any tree cache for this node/section - //treeService.clearCache({ - // cacheKey: "__" + args.section, //each item in the tree cache is cached by the section name - // childrenOf: args.entity.parentId //clear the children of the parent - //}); - - //since we're dealing with an entity, we need to attempt to find it's tree node, in the main tree - // this action is purely a UI thing so if for whatever reason there is no loaded tree node in the UI - // we can safely ignore this process. - - //to find a visible tree node, we'll go get the currently loaded root node from appState - var treeRoot = appState.getTreeState("currentRootNode"); - if (treeRoot && treeRoot.root) { - var treeNode = treeService.getDescendantNode(treeRoot.root, args.entity.id, args.treeAlias); - if (treeNode) { - treeService.loadNodeChildren({ node: treeNode, section: args.section }); - } - } - - - }, - - /** - * @ngdoc method - * @name umbraco.services.umbracoMenuActions#CreateChildEntity - * @methodOf umbraco.services.umbracoMenuActions - * @function - * - * @description - * This will re-route to a route for creating a new entity as a child of the current node - * @param {object} args An arguments object - * @param {object} args.entity The basic entity being acted upon - * @param {object} args.treeAlias The tree alias associated with this entity - * @param {object} args.section The current section - */ - "CreateChildEntity": function (args) { - - navigationService.hideNavigation(); - - var route = "/" + args.section + "/" + args.treeAlias + "/edit/" + args.entity.id; - //change to new path - $location.path(route).search({ create: true }); - - } - }; -} - -angular.module('umbraco.services').factory('umbracoMenuActions', umbracoMenuActions); -/** + angular.module('umbraco.services').factory('miniEditorHelper', miniEditorHelper); + }()); + /** * @ngdoc service * @name umbraco.services.navigationService * @@ -5033,90 +5008,75 @@ angular.module('umbraco.services').factory('umbracoMenuActions', umbracoMenuActi * Section navigation and search, and maintain their state for the entire application lifetime * */ -function navigationService($rootScope, $routeParams, $log, $location, $q, $timeout, $injector, dialogService, umbModelMapper, treeService, notificationsService, historyService, appState, angularHelper) { - - - //used to track the current dialog object - var currentDialog = null; - - //the main tree event handler, which gets assigned via the setupTreeEvents method - var mainTreeEventHandler = null; - //tracks the user profile dialog - var userDialog = null; - - function setMode(mode) { - switch (mode) { - case 'tree': - appState.setGlobalState("navMode", "tree"); - appState.setGlobalState("showNavigation", true); - appState.setMenuState("showMenu", false); - appState.setMenuState("showMenuDialog", false); - appState.setGlobalState("stickyNavigation", false); - appState.setGlobalState("showTray", false); - - //$("#search-form input").focus(); - break; - case 'menu': - appState.setGlobalState("navMode", "menu"); - appState.setGlobalState("showNavigation", true); - appState.setMenuState("showMenu", true); - appState.setMenuState("showMenuDialog", false); - appState.setGlobalState("stickyNavigation", true); - break; - case 'dialog': - appState.setGlobalState("navMode", "dialog"); - appState.setGlobalState("stickyNavigation", true); - appState.setGlobalState("showNavigation", true); - appState.setMenuState("showMenu", false); - appState.setMenuState("showMenuDialog", true); - break; - case 'search': - appState.setGlobalState("navMode", "search"); - appState.setGlobalState("stickyNavigation", false); - appState.setGlobalState("showNavigation", true); - appState.setMenuState("showMenu", false); - appState.setSectionState("showSearchResults", true); - appState.setMenuState("showMenuDialog", false); - - //TODO: This would be much better off in the search field controller listening to appState changes - $timeout(function() { - $("#search-field").focus(); - }); - - break; - default: - appState.setGlobalState("navMode", "default"); - appState.setMenuState("showMenu", false); - appState.setMenuState("showMenuDialog", false); - appState.setSectionState("showSearchResults", false); - appState.setGlobalState("stickyNavigation", false); - appState.setGlobalState("showTray", false); - - if (appState.getGlobalState("isTablet") === true) { - appState.setGlobalState("showNavigation", false); + function navigationService($rootScope, $routeParams, $log, $location, $q, $timeout, $injector, dialogService, umbModelMapper, treeService, notificationsService, historyService, appState, angularHelper) { + //used to track the current dialog object + var currentDialog = null; + //the main tree event handler, which gets assigned via the setupTreeEvents method + var mainTreeEventHandler = null; + //tracks the user profile dialog + var userDialog = null; + function setMode(mode) { + switch (mode) { + case 'tree': + appState.setGlobalState('navMode', 'tree'); + appState.setGlobalState('showNavigation', true); + appState.setMenuState('showMenu', false); + appState.setMenuState('showMenuDialog', false); + appState.setGlobalState('stickyNavigation', false); + appState.setGlobalState('showTray', false); + //$("#search-form input").focus(); + break; + case 'menu': + appState.setGlobalState('navMode', 'menu'); + appState.setGlobalState('showNavigation', true); + appState.setMenuState('showMenu', true); + appState.setMenuState('showMenuDialog', false); + appState.setGlobalState('stickyNavigation', true); + break; + case 'dialog': + appState.setGlobalState('navMode', 'dialog'); + appState.setGlobalState('stickyNavigation', true); + appState.setGlobalState('showNavigation', true); + appState.setMenuState('showMenu', false); + appState.setMenuState('showMenuDialog', true); + break; + case 'search': + appState.setGlobalState('navMode', 'search'); + appState.setGlobalState('stickyNavigation', false); + appState.setGlobalState('showNavigation', true); + appState.setMenuState('showMenu', false); + appState.setSectionState('showSearchResults', true); + appState.setMenuState('showMenuDialog', false); + //TODO: This would be much better off in the search field controller listening to appState changes + $timeout(function () { + $('#search-field').focus(); + }); + break; + default: + appState.setGlobalState('navMode', 'default'); + appState.setMenuState('showMenu', false); + appState.setMenuState('showMenuDialog', false); + appState.setSectionState('showSearchResults', false); + appState.setGlobalState('stickyNavigation', false); + appState.setGlobalState('showTray', false); + if (appState.getGlobalState('isTablet') === true) { + appState.setGlobalState('showNavigation', false); + } + break; } - - break; } - } - - var service = { - - /** initializes the navigation service */ - init: function() { - - //keep track of the current section - initially this will always be undefined so - // no point in setting it now until it changes. - $rootScope.$watch(function () { - return $routeParams.section; - }, function (newVal, oldVal) { - appState.setSectionState("currentSection", newVal); - }); - - - }, - - /** + var service = { + /** initializes the navigation service */ + init: function () { + //keep track of the current section - initially this will always be undefined so + // no point in setting it now until it changes. + $rootScope.$watch(function () { + return $routeParams.section; + }, function (newVal, oldVal) { + appState.setSectionState('currentSection', newVal); + }); + }, + /** * @ngdoc method * @name umbraco.services.navigationService#load * @methodOf umbraco.services.navigationService @@ -5125,11 +5085,10 @@ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeo * Shows the legacy iframe and loads in the content based on the source url * @param {String} source The URL to load into the iframe */ - loadLegacyIFrame: function (source) { - $location.path("/" + appState.getSectionState("currentSection") + "/framed/" + encodeURIComponent(source)); - }, - - /** + loadLegacyIFrame: function (source) { + $location.path('/' + appState.getSectionState('currentSection') + '/framed/' + encodeURIComponent(source)); + }, + /** * @ngdoc method * @name umbraco.services.navigationService#changeSection * @methodOf umbraco.services.navigationService @@ -5140,20 +5099,16 @@ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeo * and load the dashboard related to the section * @param {string} sectionAlias The alias of the section */ - changeSection: function(sectionAlias, force) { - setMode("default-opensection"); - - if (force && appState.getSectionState("currentSection") === sectionAlias) { - appState.setSectionState("currentSection", ""); - } - - appState.setSectionState("currentSection", sectionAlias); - this.showTree(sectionAlias); - - $location.path(sectionAlias); - }, - - /** + changeSection: function (sectionAlias, force) { + setMode('default-opensection'); + if (force && appState.getSectionState('currentSection') === sectionAlias) { + appState.setSectionState('currentSection', ''); + } + appState.setSectionState('currentSection', sectionAlias); + this.showTree(sectionAlias); + $location.path(sectionAlias); + }, + /** * @ngdoc method * @name umbraco.services.navigationService#showTree * @methodOf umbraco.services.navigationService @@ -5164,120 +5119,102 @@ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeo * @param {string} sectionAlias The alias of the section to load * @param {Object} syncArgs Optional object of arguments for syncing the tree for the section being shown */ - showTree: function (sectionAlias, syncArgs) { - if (sectionAlias !== appState.getSectionState("currentSection")) { - appState.setSectionState("currentSection", sectionAlias); - - if (syncArgs) { - this.syncTree(syncArgs); + showTree: function (sectionAlias, syncArgs) { + if (sectionAlias !== appState.getSectionState('currentSection')) { + appState.setSectionState('currentSection', sectionAlias); + if (syncArgs) { + this.syncTree(syncArgs); + } } - } - setMode("tree"); - }, - - showTray: function () { - appState.setGlobalState("showTray", true); - }, - - hideTray: function () { - appState.setGlobalState("showTray", false); - }, - - /** + setMode('tree'); + }, + showTray: function () { + appState.setGlobalState('showTray', true); + }, + hideTray: function () { + appState.setGlobalState('showTray', false); + }, + /** Called to assign the main tree event handler - this is called by the navigation controller. TODO: Potentially another dev could call this which would kind of mung the whole app so potentially there's a better way. */ - setupTreeEvents: function(treeEventHandler) { - mainTreeEventHandler = treeEventHandler; - - //when a tree is loaded into a section, we need to put it into appState - mainTreeEventHandler.bind("treeLoaded", function(ev, args) { - appState.setTreeState("currentRootNode", args.tree); - }); - - //when a tree node is synced this event will fire, this allows us to set the currentNode - mainTreeEventHandler.bind("treeSynced", function (ev, args) { - - if (args.activate === undefined || args.activate === true) { - //set the current selected node - appState.setTreeState("selectedNode", args.node); - //when a node is activated, this is the same as clicking it and we need to set the - //current menu item to be this node as well. - appState.setMenuState("currentNode", args.node); - } - }); - - //this reacts to the options item in the tree - mainTreeEventHandler.bind("treeOptionsClick", function(ev, args) { - ev.stopPropagation(); - ev.preventDefault(); - - //Set the current action node (this is not the same as the current selected node!) - appState.setMenuState("currentNode", args.node); - - if (args.event && args.event.altKey) { + setupTreeEvents: function (treeEventHandler) { + mainTreeEventHandler = treeEventHandler; + //when a tree is loaded into a section, we need to put it into appState + mainTreeEventHandler.bind('treeLoaded', function (ev, args) { + appState.setTreeState('currentRootNode', args.tree); + }); + //when a tree node is synced this event will fire, this allows us to set the currentNode + mainTreeEventHandler.bind('treeSynced', function (ev, args) { + if (args.activate === undefined || args.activate === true) { + //set the current selected node + appState.setTreeState('selectedNode', args.node); + //when a node is activated, this is the same as clicking it and we need to set the + //current menu item to be this node as well. + appState.setMenuState('currentNode', args.node); + } + }); + //this reacts to the options item in the tree + mainTreeEventHandler.bind('treeOptionsClick', function (ev, args) { + ev.stopPropagation(); + ev.preventDefault(); + //Set the current action node (this is not the same as the current selected node!) + appState.setMenuState('currentNode', args.node); + if (args.event && args.event.altKey) { + args.skipDefault = true; + } + service.showMenu(ev, args); + }); + mainTreeEventHandler.bind('treeNodeAltSelect', function (ev, args) { + ev.stopPropagation(); + ev.preventDefault(); args.skipDefault = true; - } - - service.showMenu(ev, args); - }); - - mainTreeEventHandler.bind("treeNodeAltSelect", function(ev, args) { - ev.stopPropagation(); - ev.preventDefault(); - - args.skipDefault = true; - service.showMenu(ev, args); - }); - - //this reacts to tree items themselves being clicked - //the tree directive should not contain any handling, simply just bubble events - mainTreeEventHandler.bind("treeNodeSelect", function (ev, args) { - var n = args.node; - ev.stopPropagation(); - ev.preventDefault(); - - if (n.metaData && n.metaData["jsClickCallback"] && angular.isString(n.metaData["jsClickCallback"]) && n.metaData["jsClickCallback"] !== "") { - //this is a legacy tree node! - var jsPrefix = "javascript:"; - var js; - if (n.metaData["jsClickCallback"].startsWith(jsPrefix)) { - js = n.metaData["jsClickCallback"].substr(jsPrefix.length); - } - else { - js = n.metaData["jsClickCallback"]; - } - try { - var func = eval(js); - //this is normally not necessary since the eval above should execute the method and will return nothing. - if (func != null && (typeof func === "function")) { - func.call(); + service.showMenu(ev, args); + }); + //this reacts to tree items themselves being clicked + //the tree directive should not contain any handling, simply just bubble events + mainTreeEventHandler.bind('treeNodeSelect', function (ev, args) { + var n = args.node; + ev.stopPropagation(); + ev.preventDefault(); + if (n.metaData && n.metaData['jsClickCallback'] && angular.isString(n.metaData['jsClickCallback']) && n.metaData['jsClickCallback'] !== '') { + //this is a legacy tree node! + var jsPrefix = 'javascript:'; + var js; + if (n.metaData['jsClickCallback'].startsWith(jsPrefix)) { + js = n.metaData['jsClickCallback'].substr(jsPrefix.length); + } else { + js = n.metaData['jsClickCallback']; } + try { + var func = eval(js); + //this is normally not necessary since the eval above should execute the method and will return nothing. + if (func != null && typeof func === 'function') { + func.call(); + } + } catch (ex) { + $log.error('Error evaluating js callback from legacy tree node: ' + ex); + } + } else if (n.routePath) { + //add action to the history service + historyService.add({ + name: n.name, + link: n.routePath, + icon: n.icon + }); + //put this node into the tree state + appState.setTreeState('selectedNode', args.node); + //when a node is clicked we also need to set the active menu node to this node + appState.setMenuState('currentNode', args.node); + //not legacy, lets just set the route value and clear the query string if there is one. + $location.path(n.routePath).search(''); + } else if (args.element.section) { + $location.path(args.element.section).search(''); } - catch(ex) { - $log.error("Error evaluating js callback from legacy tree node: " + ex); - } - } - else if (n.routePath) { - //add action to the history service - historyService.add({ name: n.name, link: n.routePath, icon: n.icon }); - - //put this node into the tree state - appState.setTreeState("selectedNode", args.node); - //when a node is clicked we also need to set the active menu node to this node - appState.setMenuState("currentNode", args.node); - - //not legacy, lets just set the route value and clear the query string if there is one. - $location.path(n.routePath).search(""); - } - else if (args.element.section) { - $location.path(args.element.section).search(""); - } - - service.hideNavigation(); - }); - }, - /** + service.hideNavigation(); + }); + }, + /** * @ngdoc method * @name umbraco.services.navigationService#syncTree * @methodOf umbraco.services.navigationService @@ -5295,62 +5232,60 @@ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeo * @param {Boolean} args.forceReload optional, specifies whether to force reload the node data from the server even if it already exists in the tree currently * @param {Boolean} args.activate optional, specifies whether to set the synced node to be the active node, this will default to true if not specified */ - syncTree: function (args) { - if (!args) { - throw "args cannot be null"; - } - if (!args.path) { - throw "args.path cannot be null"; - } - if (!args.tree) { - throw "args.tree cannot be null"; - } - - if (mainTreeEventHandler) { - //returns a promise - return mainTreeEventHandler.syncTree(args); - } - - //couldn't sync - return angularHelper.rejectedPromise(); - }, - - /** + syncTree: function (args) { + if (!args) { + throw 'args cannot be null'; + } + if (!args.path) { + throw 'args.path cannot be null'; + } + if (!args.tree) { + throw 'args.tree cannot be null'; + } + if (mainTreeEventHandler) { + if (mainTreeEventHandler.syncTree) { + //returns a promise, + return mainTreeEventHandler.syncTree(args); + } + } + //couldn't sync + return angularHelper.rejectedPromise(); + }, + /** Internal method that should ONLY be used by the legacy API wrapper, the legacy API used to have to set an active tree and then sync, the new API does this in one method by using syncTree */ - _syncPath: function(path, forceReload) { - if (mainTreeEventHandler) { - mainTreeEventHandler.syncTree({ path: path, forceReload: forceReload }); - } - }, - - //TODO: This should return a promise - reloadNode: function(node) { - if (mainTreeEventHandler) { - mainTreeEventHandler.reloadNode(node); - } - }, - - //TODO: This should return a promise - reloadSection: function(sectionAlias) { - if (mainTreeEventHandler) { - mainTreeEventHandler.clearCache({ section: sectionAlias }); - mainTreeEventHandler.load(sectionAlias); - } - }, - - /** + _syncPath: function (path, forceReload) { + if (mainTreeEventHandler) { + mainTreeEventHandler.syncTree({ + path: path, + forceReload: forceReload + }); + } + }, + //TODO: This should return a promise + reloadNode: function (node) { + if (mainTreeEventHandler) { + mainTreeEventHandler.reloadNode(node); + } + }, + //TODO: This should return a promise + reloadSection: function (sectionAlias) { + if (mainTreeEventHandler) { + mainTreeEventHandler.clearCache({ section: sectionAlias }); + mainTreeEventHandler.load(sectionAlias); + } + }, + /** Internal method that should ONLY be used by the legacy API wrapper, the legacy API used to have to set an active tree and then sync, the new API does this in one method by using syncTreePath */ - _setActiveTreeType: function (treeAlias, loadChildren) { - if (mainTreeEventHandler) { - mainTreeEventHandler._setActiveTreeType(treeAlias, loadChildren); - } - }, - - /** + _setActiveTreeType: function (treeAlias, loadChildren) { + if (mainTreeEventHandler) { + mainTreeEventHandler._setActiveTreeType(treeAlias, loadChildren); + } + }, + /** * @ngdoc method * @name umbraco.services.navigationService#hideTree * @methodOf umbraco.services.navigationService @@ -5358,17 +5293,14 @@ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeo * @description * Hides the tree by hiding the containing dom element */ - hideTree: function() { - - if (appState.getGlobalState("isTablet") === true && !appState.getGlobalState("stickyNavigation")) { - //reset it to whatever is in the url - appState.setSectionState("currentSection", $routeParams.section); - setMode("default-hidesectiontree"); - } - - }, - - /** + hideTree: function () { + if (appState.getGlobalState('isTablet') === true && !appState.getGlobalState('stickyNavigation')) { + //reset it to whatever is in the url + appState.setSectionState('currentSection', $routeParams.section); + setMode('default-hidesectiontree'); + } + }, + /** * @ngdoc method * @name umbraco.services.navigationService#showMenu * @methodOf umbraco.services.navigationService @@ -5379,61 +5311,45 @@ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeo * * @param {Event} event the click event triggering the method, passed from the DOM element */ - showMenu: function(event, args) { - - var deferred = $q.defer(); - var self = this; - - treeService.getMenu({ treeNode: args.node }) - .then(function(data) { - + showMenu: function (event, args) { + var deferred = $q.defer(); + var self = this; + treeService.getMenu({ treeNode: args.node }).then(function (data) { //check for a default //NOTE: event will be undefined when a call to hideDialog is made so it won't re-load the default again. // but perhaps there's a better way to deal with with an additional parameter in the args ? it works though. if (data.defaultAlias && !args.skipDefault) { - - var found = _.find(data.menuItems, function(item) { + var found = _.find(data.menuItems, function (item) { return item.alias = data.defaultAlias; }); - if (found) { - //NOTE: This is assigning the current action node - this is not the same as the currently selected node! - appState.setMenuState("currentNode", args.node); - + appState.setMenuState('currentNode', args.node); //ensure the current dialog is cleared before creating another! if (currentDialog) { dialogService.close(currentDialog); } - var dialog = self.showDialog({ node: args.node, action: found, - section: appState.getSectionState("currentSection") + section: appState.getSectionState('currentSection') }); - //return the dialog this is opening. deferred.resolve(dialog); return; } } - //there is no default or we couldn't find one so just continue showing the menu - - setMode("menu"); - - appState.setMenuState("currentNode", args.node); - appState.setMenuState("menuActions", data.menuItems); - appState.setMenuState("dialogTitle", args.node.name); - + setMode('menu'); + appState.setMenuState('currentNode', args.node); + appState.setMenuState('menuActions', data.menuItems); + appState.setMenuState('dialogTitle', args.node.name); //we're not opening a dialog, return null. deferred.resolve(null); }); - - return deferred.promise; - }, - - /** + return deferred.promise; + }, + /** * @ngdoc method * @name umbraco.services.navigationService#hideMenu * @methodOf umbraco.services.navigationService @@ -5441,139 +5357,91 @@ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeo * @description * Hides the menu by hiding the containing dom element */ - hideMenu: function() { - //SD: Would we ever want to access the last action'd node instead of clearing it here? - appState.setMenuState("currentNode", null); - appState.setMenuState("menuActions", []); - setMode("tree"); - }, - - /** Executes a given menu action */ - executeMenuAction: function (action, node, section) { - - if (!action) { - throw "action cannot be null"; - } - if (!node) { - throw "node cannot be null"; - } - if (!section) { - throw "section cannot be null"; - } - - if (action.metaData && action.metaData["actionRoute"] && angular.isString(action.metaData["actionRoute"])) { - //first check if the menu item simply navigates to a route - var parts = action.metaData["actionRoute"].split("?"); - $location.path(parts[0]).search(parts.length > 1 ? parts[1] : ""); - this.hideNavigation(); - return; - } - else if (action.metaData && action.metaData["jsAction"] && angular.isString(action.metaData["jsAction"])) { - - //we'll try to get the jsAction from the injector - var menuAction = action.metaData["jsAction"].split('.'); - if (menuAction.length !== 2) { - - //if it is not two parts long then this most likely means that it's a legacy action - var js = action.metaData["jsAction"].replace("javascript:", ""); - //there's not really a different way to acheive this except for eval - eval(js); + hideMenu: function () { + //SD: Would we ever want to access the last action'd node instead of clearing it here? + appState.setMenuState('currentNode', null); + appState.setMenuState('menuActions', []); + setMode('tree'); + }, + /** Executes a given menu action */ + executeMenuAction: function (action, node, section) { + if (!action) { + throw 'action cannot be null'; } - else { - var menuActionService = $injector.get(menuAction[0]); - if (!menuActionService) { - throw "The angular service " + menuAction[0] + " could not be found"; + if (!node) { + throw 'node cannot be null'; + } + if (!section) { + throw 'section cannot be null'; + } + if (action.metaData && action.metaData['actionRoute'] && angular.isString(action.metaData['actionRoute'])) { + //first check if the menu item simply navigates to a route + var parts = action.metaData['actionRoute'].split('?'); + $location.path(parts[0]).search(parts.length > 1 ? parts[1] : ''); + this.hideNavigation(); + return; + } else if (action.metaData && action.metaData['jsAction'] && angular.isString(action.metaData['jsAction'])) { + //we'll try to get the jsAction from the injector + var menuAction = action.metaData['jsAction'].split('.'); + if (menuAction.length !== 2) { + //if it is not two parts long then this most likely means that it's a legacy action + var js = action.metaData['jsAction'].replace('javascript:', ''); + //there's not really a different way to acheive this except for eval + eval(js); + } else { + var menuActionService = $injector.get(menuAction[0]); + if (!menuActionService) { + throw 'The angular service ' + menuAction[0] + ' could not be found'; + } + var method = menuActionService[menuAction[1]]; + if (!method) { + throw 'The method ' + menuAction[1] + ' on the angular service ' + menuAction[0] + ' could not be found'; + } + method.apply(this, [{ + //map our content object to a basic entity to pass in to the menu handlers, + //this is required for consistency since a menu item needs to be decoupled from a tree node since the menu can + //exist standalone in the editor for which it can only pass in an entity (not tree node). + entity: umbModelMapper.convertToEntityBasic(node), + action: action, + section: section, + treeAlias: treeService.getTreeAlias(node) + }]); } - - var method = menuActionService[menuAction[1]]; - - if (!method) { - throw "The method " + menuAction[1] + " on the angular service " + menuAction[0] + " could not be found"; - } - - method.apply(this, [{ - //map our content object to a basic entity to pass in to the menu handlers, - //this is required for consistency since a menu item needs to be decoupled from a tree node since the menu can - //exist standalone in the editor for which it can only pass in an entity (not tree node). - entity: umbModelMapper.convertToEntityBasic(node), + } else { + service.showDialog({ + node: node, action: action, - section: section, - treeAlias: treeService.getTreeAlias(node) - }]); + section: section + }); } - } - else { - service.showDialog({ - node: node, - action: action, - section: section + }, + /** + * @ngdoc method + * @name umbraco.services.navigationService#showUserDialog + * @methodOf umbraco.services.navigationService + * + * @description + * Opens the user dialog, next to the sections navigation + * template is located in views/common/dialogs/user.html + */ + showUserDialog: function () { + // hide tray and close help dialog + if (service.helpDialog) { + service.helpDialog.close(); + } + service.hideTray(); + if (service.userDialog) { + service.userDialog.close(); + service.userDialog = undefined; + } + service.userDialog = dialogService.open({ + template: 'views/common/dialogs/user.html', + modalClass: 'umb-modal-left', + show: true }); - } - }, - - /** - * @ngdoc method - * @name umbraco.services.navigationService#showUserDialog - * @methodOf umbraco.services.navigationService - * - * @description - * Opens the user dialog, next to the sections navigation - * template is located in views/common/dialogs/user.html - */ - showUserDialog: function () { - // hide tray and close help dialog - if (service.helpDialog) { - service.helpDialog.close(); - } - service.hideTray(); - - if (service.userDialog) { - service.userDialog.close(); - service.userDialog = undefined; - } - - service.userDialog = dialogService.open( - { - template: "views/common/dialogs/user.html", - modalClass: "umb-modal-left", - show: true - }); - - return service.userDialog; - }, - - /** - * @ngdoc method - * @name umbraco.services.navigationService#showUserDialog - * @methodOf umbraco.services.navigationService - * - * @description - * Opens the user dialog, next to the sections navigation - * template is located in views/common/dialogs/user.html - */ - showHelpDialog: function () { - // hide tray and close user dialog - service.hideTray(); - if (service.userDialog) { - service.userDialog.close(); - } - - if(service.helpDialog){ - service.helpDialog.close(); - service.helpDialog = undefined; - } - - service.helpDialog = dialogService.open( - { - template: "views/common/dialogs/help.html", - modalClass: "umb-modal-left", - show: true - }); - - return service.helpDialog; - }, - - /** + return service.userDialog; + }, + /** * @ngdoc method * @name umbraco.services.navigationService#showDialog * @methodOf umbraco.services.navigationService @@ -5593,88 +5461,66 @@ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeo * @param {Scope} args.scope current scope passed to the dialog * @param {Object} args.action the clicked action containing `name` and `alias` */ - showDialog: function(args) { - - if (!args) { - throw "showDialog is missing the args parameter"; - } - if (!args.action) { - throw "The args parameter must have an 'action' property as the clicked menu action object"; - } - if (!args.node) { - throw "The args parameter must have a 'node' as the active tree node"; - } - - //ensure the current dialog is cleared before creating another! - if (currentDialog) { - dialogService.close(currentDialog); - currentDialog = null; - } - - setMode("dialog"); - - //NOTE: Set up the scope object and assign properties, this is legacy functionality but we have to live with it now. - // we should be passing in currentNode and currentAction using 'dialogData' for the dialog, not attaching it to a scope. - // This scope instance will be destroyed by the dialog so it cannot be a scope that exists outside of the dialog. - // If a scope instance has been passed in, we'll have to create a child scope of it, otherwise a new root scope. - var dialogScope = args.scope ? args.scope.$new() : $rootScope.$new(); - dialogScope.currentNode = args.node; - dialogScope.currentAction = args.action; - - //the title might be in the meta data, check there first - if (args.action.metaData["dialogTitle"]) { - appState.setMenuState("dialogTitle", args.action.metaData["dialogTitle"]); - } - else { - appState.setMenuState("dialogTitle", args.action.name); - } - - var templateUrl; - var iframe; - - if (args.action.metaData["actionUrl"]) { - templateUrl = args.action.metaData["actionUrl"]; - iframe = true; - } - else if (args.action.metaData["actionView"]) { - templateUrl = args.action.metaData["actionView"]; - iframe = false; - } - else { - - //by convention we will look into the /views/{treetype}/{action}.html - // for example: /views/content/create.html - - //we will also check for a 'packageName' for the current tree, if it exists then the convention will be: - // for example: /App_Plugins/{mypackage}/backoffice/{treetype}/create.html - - var treeAlias = treeService.getTreeAlias(args.node); - var packageTreeFolder = treeService.getTreePackageFolder(treeAlias); - - if (!treeAlias) { - throw "Could not get tree alias for node " + args.node.id; + showDialog: function (args) { + if (!args) { + throw 'showDialog is missing the args parameter'; } - - if (packageTreeFolder) { - templateUrl = Umbraco.Sys.ServerVariables.umbracoSettings.appPluginsPath + - "/" + packageTreeFolder + - "/backoffice/" + treeAlias + "/" + args.action.alias + ".html"; + if (!args.action) { + throw 'The args parameter must have an \'action\' property as the clicked menu action object'; } - else { - templateUrl = "views/" + treeAlias + "/" + args.action.alias + ".html"; + if (!args.node) { + throw 'The args parameter must have a \'node\' as the active tree node'; } - - iframe = false; - } - - //TODO: some action's want to launch a new window like live editing, we support this in the menu item's metadata with - // a key called: "actionUrlMethod" which can be set to either: Dialog, BlankWindow. Normally this is always set to Dialog - // if a URL is specified in the "actionUrl" metadata. For now I'm not going to implement launching in a blank window, - // though would be v-easy, just not sure we want to ever support that? - - var dialog = dialogService.open( - { - container: $("#dialog div.umb-modalcolumn-body"), + //ensure the current dialog is cleared before creating another! + if (currentDialog) { + dialogService.close(currentDialog); + currentDialog = null; + } + setMode('dialog'); + //NOTE: Set up the scope object and assign properties, this is legacy functionality but we have to live with it now. + // we should be passing in currentNode and currentAction using 'dialogData' for the dialog, not attaching it to a scope. + // This scope instance will be destroyed by the dialog so it cannot be a scope that exists outside of the dialog. + // If a scope instance has been passed in, we'll have to create a child scope of it, otherwise a new root scope. + var dialogScope = args.scope ? args.scope.$new() : $rootScope.$new(); + dialogScope.currentNode = args.node; + dialogScope.currentAction = args.action; + //the title might be in the meta data, check there first + if (args.action.metaData['dialogTitle']) { + appState.setMenuState('dialogTitle', args.action.metaData['dialogTitle']); + } else { + appState.setMenuState('dialogTitle', args.action.name); + } + var templateUrl; + var iframe; + if (args.action.metaData['actionUrl']) { + templateUrl = args.action.metaData['actionUrl']; + iframe = true; + } else if (args.action.metaData['actionView']) { + templateUrl = args.action.metaData['actionView']; + iframe = false; + } else { + //by convention we will look into the /views/{treetype}/{action}.html + // for example: /views/content/create.html + //we will also check for a 'packageName' for the current tree, if it exists then the convention will be: + // for example: /App_Plugins/{mypackage}/backoffice/{treetype}/create.html + var treeAlias = treeService.getTreeAlias(args.node); + var packageTreeFolder = treeService.getTreePackageFolder(treeAlias); + if (!treeAlias) { + throw 'Could not get tree alias for node ' + args.node.id; + } + if (packageTreeFolder) { + templateUrl = Umbraco.Sys.ServerVariables.umbracoSettings.appPluginsPath + '/' + packageTreeFolder + '/backoffice/' + treeAlias + '/' + args.action.alias + '.html'; + } else { + templateUrl = 'views/' + treeAlias + '/' + args.action.alias + '.html'; + } + iframe = false; + } + //TODO: some action's want to launch a new window like live editing, we support this in the menu item's metadata with + // a key called: "actionUrlMethod" which can be set to either: Dialog, BlankWindow. Normally this is always set to Dialog + // if a URL is specified in the "actionUrl" metadata. For now I'm not going to implement launching in a blank window, + // though would be v-easy, just not sure we want to ever support that? + var dialog = dialogService.open({ + container: $('#dialog div.umb-modalcolumn-body'), //The ONLY reason we're passing in scope to the dialogService (which is legacy functionality) is // for backwards compatibility since many dialogs require $scope.currentNode or $scope.currentAction // to exist @@ -5682,20 +5528,17 @@ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeo inline: true, show: true, iframe: iframe, - modalClass: "umb-dialog", + modalClass: 'umb-dialog', template: templateUrl, - //These will show up on the dialog controller's $scope under dialogOptions currentNode: args.node, - currentAction: args.action, + currentAction: args.action }); - - //save the currently assigned dialog so it can be removed before a new one is created - currentDialog = dialog; - return dialog; - }, - - /** + //save the currently assigned dialog so it can be removed before a new one is created + currentDialog = dialog; + return dialog; + }, + /** * @ngdoc method * @name umbraco.services.navigationService#hideDialog * @methodOf umbraco.services.navigationService @@ -5703,15 +5546,16 @@ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeo * @description * hides the currently open dialog */ - hideDialog: function (showMenu) { - - setMode("default"); - - if(showMenu){ - this.showMenu(undefined, { skipDefault: true, node: appState.getMenuState("currentNode") }); - } - }, - /** + hideDialog: function (showMenu) { + setMode('default'); + if (showMenu) { + this.showMenu(undefined, { + skipDefault: true, + node: appState.getMenuState('currentNode') + }); + } + }, + /** * @ngdoc method * @name umbraco.services.navigationService#showSearch * @methodOf umbraco.services.navigationService @@ -5719,10 +5563,10 @@ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeo * @description * shows the search pane */ - showSearch: function() { - setMode("search"); - }, - /** + showSearch: function () { + setMode('search'); + }, + /** * @ngdoc method * @name umbraco.services.navigationService#hideSearch * @methodOf umbraco.services.navigationService @@ -5730,10 +5574,10 @@ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeo * @description * hides the search pane */ - hideSearch: function() { - setMode("default-hidesearch"); - }, - /** + hideSearch: function () { + setMode('default-hidesearch'); + }, + /** * @ngdoc method * @name umbraco.services.navigationService#hideNavigation * @methodOf umbraco.services.navigationService @@ -5741,3860 +5585,4453 @@ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeo * @description * hides any open navigation panes and resets the tree, actions and the currently selected node */ - hideNavigation: function() { - appState.setMenuState("menuActions", []); - setMode("default"); + hideNavigation: function () { + appState.setMenuState('menuActions', []); + setMode('default'); + } + }; + return service; + } + angular.module('umbraco.services').factory('navigationService', navigationService); + /** + * @ngdoc service + * @name umbraco.services.notificationsService + * + * @requires $rootScope + * @requires $timeout + * @requires angularHelper + * + * @description + * Application-wide service for handling notifications, the umbraco application + * maintains a single collection of notications, which the UI watches for changes. + * By default when a notication is added, it is automaticly removed 7 seconds after + * This can be changed on add() + * + * ##usage + * To use, simply inject the notificationsService into any controller that needs it, and make + * sure the umbraco.services module is accesible - which it should be by default. + * + *
    + *		notificationsService.success("Document Published", "hooraaaay for you!");
    + *      notificationsService.error("Document Failed", "booooh");
    + * 
    + */ + angular.module('umbraco.services').factory('notificationsService', function ($rootScope, $timeout, angularHelper) { + var nArray = []; + function setViewPath(view) { + if (view.indexOf('/') < 0) { + view = 'views/common/notifications/' + view; + } + if (view.indexOf('.html') < 0) { + view = view + '.html'; + } + return view; } - }; - - return service; -} - -angular.module('umbraco.services').factory('navigationService', navigationService); - -/** - * @ngdoc service - * @name umbraco.services.notificationsService - * - * @requires $rootScope - * @requires $timeout - * @requires angularHelper - * - * @description - * Application-wide service for handling notifications, the umbraco application - * maintains a single collection of notications, which the UI watches for changes. - * By default when a notication is added, it is automaticly removed 7 seconds after - * This can be changed on add() - * - * ##usage - * To use, simply inject the notificationsService into any controller that needs it, and make - * sure the umbraco.services module is accesible - which it should be by default. - * - *
    - *		notificationsService.success("Document Published", "hooraaaay for you!");
    - *      notificationsService.error("Document Failed", "booooh");
    - * 
    - */ -angular.module('umbraco.services') -.factory('notificationsService', function ($rootScope, $timeout, angularHelper) { - - var nArray = []; - function setViewPath(view){ - if(view.indexOf('/') < 0) - { - view = "views/common/notifications/" + view; - } - - if(view.indexOf('.html') < 0) - { - view = view + ".html"; - } - return view; - } - - var service = { - - /** - * @ngdoc method - * @name umbraco.services.notificationsService#add - * @methodOf umbraco.services.notificationsService - * - * @description - * Lower level api for adding notifcations, support more advanced options - * @param {Object} item The notification item - * @param {String} item.headline Short headline - * @param {String} item.message longer text for the notication, trimmed after 200 characters, which can then be exanded - * @param {String} item.type Notification type, can be: "success","warning","error" or "info" - * @param {String} item.url url to open when notification is clicked - * @param {String} item.view path to custom view to load into the notification box - * @param {Array} item.actions Collection of button actions to append (label, func, cssClass) - * @param {Boolean} item.sticky if set to true, the notification will not auto-close - * @returns {Object} args notification object - */ - - add: function(item) { - angularHelper.safeApply($rootScope, function () { - - if(item.view){ - item.view = setViewPath(item.view); - item.sticky = true; - item.type = "form"; - item.headline = null; - } - - - //add a colon after the headline if there is a message as well - if (item.message) { - item.headline += ": "; - if(item.message.length > 200) { - item.sticky = true; - } - } - - //we need to ID the item, going by index isn't good enough because people can remove at different indexes - // whenever they want. Plus once we remove one, then the next index will be different. The only way to - // effectively remove an item is by an Id. - item.id = String.CreateGuid(); - - nArray.push(item); - - if(!item.sticky) { - $timeout(function() { - var found = _.find(nArray, function(i) { - return i.id === item.id; - }); - - if (found) { - var index = nArray.indexOf(found); - nArray.splice(index, 1); - } - - }, 7000); - } - - return item; - }); - - }, - - hasView : function(view){ - if(!view){ - return _.find(nArray, function(notification){ return notification.view;}); - }else{ - view = setViewPath(view).toLowerCase(); - return _.find(nArray, function(notification){ return notification.view.toLowerCase() === view;}); - } - }, - addView: function(view, args){ - var item = { - args: args, - view: view - }; - - service.add(item); - }, - - /** - * @ngdoc method - * @name umbraco.services.notificationsService#showNotification - * @methodOf umbraco.services.notificationsService - * - * @description - * Shows a notification based on the object passed in, normally used to render notifications sent back from the server - * - * @returns {Object} args notification object - */ - showNotification: function(args) { - if (!args) { - throw "args cannot be null"; - } - if (args.type === undefined || args.type === null) { - throw "args.type cannot be null"; - } - if (!args.header) { - throw "args.header cannot be null"; - } - - switch(args.type) { - case 0: - //save - this.success(args.header, args.message); - break; - case 1: - //info - this.success(args.header, args.message); - break; - case 2: - //error - this.error(args.header, args.message); - break; - case 3: - //success - this.success(args.header, args.message); - break; - case 4: - //warning - this.warning(args.header, args.message); - break; - } - }, - - /** - * @ngdoc method - * @name umbraco.services.notificationsService#success - * @methodOf umbraco.services.notificationsService - * - * @description - * Adds a green success notication to the notications collection - * This should be used when an operations *completes* without errors - * - * @param {String} headline Headline of the notification - * @param {String} message longer text for the notication, trimmed after 200 characters, which can then be exanded - * @returns {Object} notification object - */ - success: function (headline, message) { - return service.add({ headline: headline, message: message, type: 'success', time: new Date() }); - }, - /** - * @ngdoc method - * @name umbraco.services.notificationsService#error - * @methodOf umbraco.services.notificationsService - * - * @description - * Adds a red error notication to the notications collection - * This should be used when an operations *fails* and could not complete - * - * @param {String} headline Headline of the notification - * @param {String} message longer text for the notication, trimmed after 200 characters, which can then be exanded - * @returns {Object} notification object - */ - error: function (headline, message) { - return service.add({ headline: headline, message: message, type: 'error', time: new Date() }); - }, - - /** - * @ngdoc method - * @name umbraco.services.notificationsService#warning - * @methodOf umbraco.services.notificationsService - * - * @description - * Adds a yellow warning notication to the notications collection - * This should be used when an operations *completes* but something was not as expected - * - * - * @param {String} headline Headline of the notification - * @param {String} message longer text for the notication, trimmed after 200 characters, which can then be exanded - * @returns {Object} notification object - */ - warning: function (headline, message) { - return service.add({ headline: headline, message: message, type: 'warning', time: new Date() }); - }, - - /** - * @ngdoc method - * @name umbraco.services.notificationsService#warning - * @methodOf umbraco.services.notificationsService - * - * @description - * Adds a yellow warning notication to the notications collection - * This should be used when an operations *completes* but something was not as expected - * - * - * @param {String} headline Headline of the notification - * @param {String} message longer text for the notication, trimmed after 200 characters, which can then be exanded - * @returns {Object} notification object - */ - info: function (headline, message) { - return service.add({ headline: headline, message: message, type: 'info', time: new Date() }); - }, - - /** - * @ngdoc method - * @name umbraco.services.notificationsService#remove - * @methodOf umbraco.services.notificationsService - * - * @description - * Removes a notification from the notifcations collection at a given index - * - * @param {Int} index index where the notication should be removed from - */ - remove: function (index) { - if(angular.isObject(index)){ - var i = nArray.indexOf(index); - angularHelper.safeApply($rootScope, function() { - nArray.splice(i, 1); - }); - }else{ - angularHelper.safeApply($rootScope, function() { - nArray.splice(index, 1); - }); - } - }, - - /** - * @ngdoc method - * @name umbraco.services.notificationsService#removeAll - * @methodOf umbraco.services.notificationsService - * - * @description - * Removes all notifications from the notifcations collection - */ - removeAll: function () { - angularHelper.safeApply($rootScope, function() { - nArray = []; - }); - }, - - /** - * @ngdoc property - * @name umbraco.services.notificationsService#current - * @propertyOf umbraco.services.notificationsService - * - * @description - * Returns an array of current notifications to display - * - * @returns {string} returns an array - */ - current: nArray, - - /** - * @ngdoc method - * @name umbraco.services.notificationsService#getCurrent - * @methodOf umbraco.services.notificationsService - * - * @description - * Method to return all notifications from the notifcations collection - */ - getCurrent: function(){ - return nArray; - } - }; - - return service; -}); -(function() { - 'use strict'; - - function overlayHelper() { - - var numberOfOverlays = 0; - - function registerOverlay() { - numberOfOverlays++; - return numberOfOverlays; - } - - function unregisterOverlay() { - numberOfOverlays--; - return numberOfOverlays; - } - - function getNumberOfOverlays() { - return numberOfOverlays; - } - - var service = { - numberOfOverlays: numberOfOverlays, - registerOverlay: registerOverlay, - unregisterOverlay: unregisterOverlay, - getNumberOfOverlays: getNumberOfOverlays - }; - - return service; - - } - - - angular.module('umbraco.services').factory('overlayHelper', overlayHelper); - - -})(); - -/** - * @ngdoc service - * @name umbraco.services.searchService - * - * - * @description - * Service for handling the main application search, can currently search content, media and members - * - * ##usage - * To use, simply inject the searchService into any controller that needs it, and make - * sure the umbraco.services module is accesible - which it should be by default. - * - *
    - *      searchService.searchMembers({term: 'bob'}).then(function(results){
    - *          angular.forEach(results, function(result){
    - *                  //returns:
    - *                  {name: "name", id: 1234, menuUrl: "url", editorPath: "url", metaData: {}, subtitle: "/path/etc" }
    - *           })          
    - *           var result = 
    - *       }) 
    - * 
    - */ -angular.module('umbraco.services') -.factory('searchService', function ($q, $log, entityResource, contentResource, umbRequestHelper) { - - function configureMemberResult(member) { - member.menuUrl = umbRequestHelper.getApiUrl("memberTreeBaseUrl", "GetMenu", [{ id: member.id }, { application: 'member' }]); - member.editorPath = "member/member/edit/" + (member.key ? member.key : member.id); - angular.extend(member.metaData, { treeAlias: "member" }); - member.subTitle = member.metaData.Email; - } - - function configureMediaResult(media) - { - media.menuUrl = umbRequestHelper.getApiUrl("mediaTreeBaseUrl", "GetMenu", [{ id: media.id }, { application: 'media' }]); - media.editorPath = "media/media/edit/" + media.id; - angular.extend(media.metaData, { treeAlias: "media" }); - } - - function configureContentResult(content) { - content.menuUrl = umbRequestHelper.getApiUrl("contentTreeBaseUrl", "GetMenu", [{ id: content.id }, { application: 'content' }]); - content.editorPath = "content/content/edit/" + content.id; - angular.extend(content.metaData, { treeAlias: "content" }); - content.subTitle = content.metaData.Url; - } - - return { - - /** - * @ngdoc method - * @name umbraco.services.searchService#searchMembers - * @methodOf umbraco.services.searchService - * - * @description - * Searches the default member search index - * @param {Object} args argument object - * @param {String} args.term seach term - * @returns {Promise} returns promise containing all matching members - */ - searchMembers: function(args) { - - if (!args.term) { - throw "args.term is required"; - } - - return entityResource.search(args.term, "Member", args.searchFrom).then(function (data) { - _.each(data, function(item) { - configureMemberResult(item); - }); - return data; - }); - }, - - /** - * @ngdoc method - * @name umbraco.services.searchService#searchContent - * @methodOf umbraco.services.searchService - * - * @description - * Searches the default internal content search index - * @param {Object} args argument object - * @param {String} args.term seach term - * @returns {Promise} returns promise containing all matching content items - */ - searchContent: function(args) { - - if (!args.term) { - throw "args.term is required"; - } - - return entityResource.search(args.term, "Document", args.searchFrom, args.canceler).then(function (data) { - _.each(data, function (item) { - configureContentResult(item); - }); - return data; - }); - }, - - /** - * @ngdoc method - * @name umbraco.services.searchService#searchMedia - * @methodOf umbraco.services.searchService - * - * @description - * Searches the default media search index - * @param {Object} args argument object - * @param {String} args.term seach term - * @returns {Promise} returns promise containing all matching media items - */ - searchMedia: function(args) { - - if (!args.term) { - throw "args.term is required"; - } - - return entityResource.search(args.term, "Media", args.searchFrom).then(function (data) { - _.each(data, function (item) { - configureMediaResult(item); - }); - return data; - }); - }, - - /** - * @ngdoc method - * @name umbraco.services.searchService#searchAll - * @methodOf umbraco.services.searchService - * - * @description - * Searches all available indexes and returns all results in one collection - * @param {Object} args argument object - * @param {String} args.term seach term - * @returns {Promise} returns promise containing all matching items - */ - searchAll: function (args) { - - if (!args.term) { - throw "args.term is required"; - } - - return entityResource.searchAll(args.term, args.canceler).then(function (data) { - - _.each(data, function(resultByType) { - switch(resultByType.type) { - case "Document": - _.each(resultByType.results, function (item) { - configureContentResult(item); - }); - break; - case "Media": - _.each(resultByType.results, function (item) { - configureMediaResult(item); - }); - break; - case "Member": - _.each(resultByType.results, function (item) { - configureMemberResult(item); - }); - break; - } - }); - - return data; - }); - - }, - - //TODO: This doesn't do anything! - setCurrent: function(sectionAlias) { - - var currentSection = sectionAlias; - } - }; -}); -/** - * @ngdoc service - * @name umbraco.services.serverValidationManager - * @function - * - * @description - * Used to handle server side validation and wires up the UI with the messages. There are 2 types of validation messages, one - * is for user defined properties (called Properties) and the other is for field properties which are attached to the native - * model objects (not user defined). The methods below are named according to these rules: Properties vs Fields. - */ -function serverValidationManager($timeout) { - - var callbacks = []; - - /** calls the callback specified with the errors specified, used internally */ - function executeCallback(self, errorsForCallback, callback) { - - callback.apply(self, [ - false, //pass in a value indicating it is invalid - errorsForCallback, //pass in the errors for this item - self.items]); //pass in all errors in total - } - - function getFieldErrors(self, fieldName) { - if (!angular.isString(fieldName)) { - throw "fieldName must be a string"; - } - - //find errors for this field name - return _.filter(self.items, function (item) { - return (item.propertyAlias === null && item.fieldName === fieldName); - }); - } - - function getPropertyErrors(self, propertyAlias, fieldName) { - if (!angular.isString(propertyAlias)) { - throw "propertyAlias must be a string"; - } - if (fieldName && !angular.isString(fieldName)) { - throw "fieldName must be a string"; - } - - //find all errors for this property - return _.filter(self.items, function (item) { - return (item.propertyAlias === propertyAlias && (item.fieldName === fieldName || (fieldName === undefined || fieldName === ""))); - }); - } - - return { - - /** - * @ngdoc function - * @name umbraco.services.serverValidationManager#subscribe - * @methodOf umbraco.services.serverValidationManager - * @function - * - * @description - * This method needs to be called once all field and property errors are wired up. - * - * In some scenarios where the error collection needs to be persisted over a route change - * (i.e. when a content item (or any item) is created and the route redirects to the editor) - * the controller should call this method once the data is bound to the scope - * so that any persisted validation errors are re-bound to their controls. Once they are re-binded this then clears the validation - * colleciton so that if another route change occurs, the previously persisted validation errors are not re-bound to the new item. - */ - executeAndClearAllSubscriptions: function() { - - var self = this; - - $timeout(function () { - - for (var cb in callbacks) { - if (callbacks[cb].propertyAlias === null) { - //its a field error callback - var fieldErrors = getFieldErrors(self, callbacks[cb].fieldName); - if (fieldErrors.length > 0) { - executeCallback(self, fieldErrors, callbacks[cb].callback); - } - } - else { - //its a property error - var propErrors = getPropertyErrors(self, callbacks[cb].propertyAlias, callbacks[cb].fieldName); - if (propErrors.length > 0) { - executeCallback(self, propErrors, callbacks[cb].callback); - } - } - } - //now that they are all executed, we're gonna clear all of the errors we have - self.clear(); - }); - }, - - /** - * @ngdoc function - * @name umbraco.services.serverValidationManager#subscribe - * @methodOf umbraco.services.serverValidationManager - * @function - * - * @description - * Adds a callback method that is executed whenever validation changes for the field name + property specified. - * This is generally used for server side validation in order to match up a server side validation error with - * a particular field, otherwise we can only pinpoint that there is an error for a content property, not the - * property's specific field. This is used with the val-server directive in which the directive specifies the - * field alias to listen for. - * If propertyAlias is null, then this subscription is for a field property (not a user defined property). - */ - subscribe: function (propertyAlias, fieldName, callback) { - if (!callback) { - return; - } - - if (propertyAlias === null) { - //don't add it if it already exists - var exists1 = _.find(callbacks, function (item) { - return item.propertyAlias === null && item.fieldName === fieldName; - }); - if (!exists1) { - callbacks.push({ propertyAlias: null, fieldName: fieldName, callback: callback }); - } - } - else if (propertyAlias !== undefined) { - //don't add it if it already exists - var exists2 = _.find(callbacks, function (item) { - return item.propertyAlias === propertyAlias && item.fieldName === fieldName; - }); - if (!exists2) { - callbacks.push({ propertyAlias: propertyAlias, fieldName: fieldName, callback: callback }); - } - } - }, - - unsubscribe: function (propertyAlias, fieldName) { - - if (propertyAlias === null) { - - //remove all callbacks for the content field - callbacks = _.reject(callbacks, function (item) { - return item.propertyAlias === null && item.fieldName === fieldName; - }); - - } - else if (propertyAlias !== undefined) { - - //remove all callbacks for the content property - callbacks = _.reject(callbacks, function (item) { - return item.propertyAlias === propertyAlias && - (item.fieldName === fieldName || - ((item.fieldName === undefined || item.fieldName === "") && (fieldName === undefined || fieldName === ""))); - }); - } - - - }, - - - /** - * @ngdoc function - * @name getPropertyCallbacks - * @methodOf umbraco.services.serverValidationManager - * @function - * - * @description - * Gets all callbacks that has been registered using the subscribe method for the propertyAlias + fieldName combo. - * This will always return any callbacks registered for just the property (i.e. field name is empty) and for ones with an - * explicit field name set. - */ - getPropertyCallbacks: function (propertyAlias, fieldName) { - var found = _.filter(callbacks, function (item) { - //returns any callback that have been registered directly against the field and for only the property - return (item.propertyAlias === propertyAlias && (item.fieldName === fieldName || (item.fieldName === undefined || item.fieldName === ""))); - }); - return found; - }, - - /** - * @ngdoc function - * @name getFieldCallbacks - * @methodOf umbraco.services.serverValidationManager - * @function - * - * @description - * Gets all callbacks that has been registered using the subscribe method for the field. - */ - getFieldCallbacks: function (fieldName) { - var found = _.filter(callbacks, function (item) { - //returns any callback that have been registered directly against the field - return (item.propertyAlias === null && item.fieldName === fieldName); - }); - return found; - }, - - /** - * @ngdoc function - * @name addFieldError - * @methodOf umbraco.services.serverValidationManager - * @function - * - * @description - * Adds an error message for a native content item field (not a user defined property, for Example, 'Name') - */ - addFieldError: function(fieldName, errorMsg) { - if (!fieldName) { - return; - } - - //only add the item if it doesn't exist - if (!this.hasFieldError(fieldName)) { - this.items.push({ - propertyAlias: null, - fieldName: fieldName, - errorMsg: errorMsg - }); - } - - //find all errors for this item - var errorsForCallback = getFieldErrors(this, fieldName); - //we should now call all of the call backs registered for this error - var cbs = this.getFieldCallbacks(fieldName); - //call each callback for this error - for (var cb in cbs) { - executeCallback(this, errorsForCallback, cbs[cb].callback); - } - }, - - /** - * @ngdoc function - * @name addPropertyError - * @methodOf umbraco.services.serverValidationManager - * @function - * - * @description - * Adds an error message for the content property - */ - addPropertyError: function (propertyAlias, fieldName, errorMsg) { - if (!propertyAlias) { - return; - } - - //only add the item if it doesn't exist - if (!this.hasPropertyError(propertyAlias, fieldName)) { - this.items.push({ - propertyAlias: propertyAlias, - fieldName: fieldName, - errorMsg: errorMsg - }); - } - - //find all errors for this item - var errorsForCallback = getPropertyErrors(this, propertyAlias, fieldName); - //we should now call all of the call backs registered for this error - var cbs = this.getPropertyCallbacks(propertyAlias, fieldName); - //call each callback for this error - for (var cb in cbs) { - executeCallback(this, errorsForCallback, cbs[cb].callback); - } - }, - - /** - * @ngdoc function - * @name removePropertyError - * @methodOf umbraco.services.serverValidationManager - * @function - * - * @description - * Removes an error message for the content property - */ - removePropertyError: function (propertyAlias, fieldName) { - - if (!propertyAlias) { - return; - } - //remove the item - this.items = _.reject(this.items, function (item) { - return (item.propertyAlias === propertyAlias && (item.fieldName === fieldName || (fieldName === undefined || fieldName === ""))); - }); - }, - - /** - * @ngdoc function - * @name reset - * @methodOf umbraco.services.serverValidationManager - * @function - * - * @description - * Clears all errors and notifies all callbacks that all server errros are now valid - used when submitting a form - */ - reset: function () { - this.clear(); - for (var cb in callbacks) { - callbacks[cb].callback.apply(this, [ - true, //pass in a value indicating it is VALID - [], //pass in empty collection - []]); //pass in empty collection - } - }, - - /** - * @ngdoc function - * @name clear - * @methodOf umbraco.services.serverValidationManager - * @function - * - * @description - * Clears all errors - */ - clear: function() { - this.items = []; - }, - - /** - * @ngdoc function - * @name getPropertyError - * @methodOf umbraco.services.serverValidationManager - * @function - * - * @description - * Gets the error message for the content property - */ - getPropertyError: function (propertyAlias, fieldName) { - var err = _.find(this.items, function (item) { - //return true if the property alias matches and if an empty field name is specified or the field name matches - return (item.propertyAlias === propertyAlias && (item.fieldName === fieldName || (fieldName === undefined || fieldName === ""))); - }); - return err; - }, - - /** - * @ngdoc function - * @name getFieldError - * @methodOf umbraco.services.serverValidationManager - * @function - * - * @description - * Gets the error message for a content field - */ - getFieldError: function (fieldName) { - var err = _.find(this.items, function (item) { - //return true if the property alias matches and if an empty field name is specified or the field name matches - return (item.propertyAlias === null && item.fieldName === fieldName); - }); - return err; - }, - - /** - * @ngdoc function - * @name hasPropertyError - * @methodOf umbraco.services.serverValidationManager - * @function - * - * @description - * Checks if the content property + field name combo has an error - */ - hasPropertyError: function (propertyAlias, fieldName) { - var err = _.find(this.items, function (item) { - //return true if the property alias matches and if an empty field name is specified or the field name matches - return (item.propertyAlias === propertyAlias && (item.fieldName === fieldName || (fieldName === undefined || fieldName === ""))); - }); - return err ? true : false; - }, - - /** - * @ngdoc function - * @name hasFieldError - * @methodOf umbraco.services.serverValidationManager - * @function - * - * @description - * Checks if a content field has an error - */ - hasFieldError: function (fieldName) { - var err = _.find(this.items, function (item) { - //return true if the property alias matches and if an empty field name is specified or the field name matches - return (item.propertyAlias === null && item.fieldName === fieldName); - }); - return err ? true : false; - }, - - /** The array of error messages */ - items: [] - }; -} - -angular.module('umbraco.services').factory('serverValidationManager', serverValidationManager); -/** - * @ngdoc service - * @name umbraco.services.tinyMceService - * - * - * @description - * A service containing all logic for all of the Umbraco TinyMCE plugins - */ -function tinyMceService(dialogService, $log, imageHelper, $http, $timeout, macroResource, macroService, $routeParams, umbRequestHelper, angularHelper, userService) { - return { - - /** - * @ngdoc method - * @name umbraco.services.tinyMceService#configuration - * @methodOf umbraco.services.tinyMceService - * - * @description - * Returns a collection of plugins available to the tinyMCE editor - * - */ - configuration: function () { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "rteApiBaseUrl", - "GetConfiguration"), { cache: true }), - 'Failed to retrieve tinymce configuration'); - }, - - /** - * @ngdoc method - * @name umbraco.services.tinyMceService#defaultPrevalues - * @methodOf umbraco.services.tinyMceService - * - * @description - * Returns a default configration to fallback on in case none is provided - * - */ - defaultPrevalues: function () { - var cfg = {}; - cfg.toolbar = ["code", "bold", "italic", "styleselect","alignleft", "aligncenter", "alignright", "bullist","numlist", "outdent", "indent", "link", "image", "umbmediapicker", "umbembeddialog", "umbmacro"]; - cfg.stylesheets = []; - cfg.dimensions = { height: 500 }; - cfg.maxImageSize = 500; - return cfg; - }, - - /** - * @ngdoc method - * @name umbraco.services.tinyMceService#createInsertEmbeddedMedia - * @methodOf umbraco.services.tinyMceService - * - * @description - * Creates the umbrco insert embedded media tinymce plugin - * - * @param {Object} editor the TinyMCE editor instance - * @param {Object} $scope the current controller scope - */ - createInsertEmbeddedMedia: function (editor, scope, callback) { - editor.addButton('umbembeddialog', { - icon: 'custom icon-tv', - tooltip: 'Embed', - onclick: function () { - if (callback) { - callback(); - } - } - }); - }, - - insertEmbeddedMediaInEditor: function(editor, preview) { - editor.insertContent(preview); - }, - - /** - * @ngdoc method - * @name umbraco.services.tinyMceService#createMediaPicker - * @methodOf umbraco.services.tinyMceService - * - * @description - * Creates the umbrco insert media tinymce plugin - * - * @param {Object} editor the TinyMCE editor instance - * @param {Object} $scope the current controller scope - */ - createMediaPicker: function (editor, scope, callback) { - editor.addButton('umbmediapicker', { - icon: 'custom icon-picture', - tooltip: 'Media Picker', - onclick: function () { - - var selectedElm = editor.selection.getNode(), - currentTarget; - - - if(selectedElm.nodeName === 'IMG'){ - var img = $(selectedElm); - currentTarget = { - altText: img.attr("alt"), - url: img.attr("src"), - id: img.attr("rel") - }; - } - - userService.getCurrentUser().then(function(userData) { - if(callback) { - callback(currentTarget, userData); - } - }); - - } - }); - }, - - insertMediaInEditor: function(editor, img) { - if(img) { - - var data = { - alt: img.altText || "", - src: (img.url) ? img.url : "nothing.jpg", - rel: img.id, - 'data-id': img.id, - id: '__mcenew' - }; - - editor.insertContent(editor.dom.createHTML('img', data)); - - $timeout(function () { - var imgElm = editor.dom.get('__mcenew'); - var size = editor.dom.getSize(imgElm); - - if (editor.settings.maxImageSize && editor.settings.maxImageSize !== 0) { - var newSize = imageHelper.scaleToMaxSize(editor.settings.maxImageSize, size.w, size.h); - - var s = "width: " + newSize.width + "px; height:" + newSize.height + "px;"; - editor.dom.setAttrib(imgElm, 'style', s); - editor.dom.setAttrib(imgElm, 'id', null); - - if (img.url) { - var src = img.url + "?width=" + newSize.width + "&height=" + newSize.height; - editor.dom.setAttrib(imgElm, 'data-mce-src', src); - } - } - }, 500); - } - }, - - /** - * @ngdoc method - * @name umbraco.services.tinyMceService#createUmbracoMacro - * @methodOf umbraco.services.tinyMceService - * - * @description - * Creates the insert umbrco macro tinymce plugin - * - * @param {Object} editor the TinyMCE editor instance - * @param {Object} $scope the current controller scope - */ - createInsertMacro: function (editor, $scope, callback) { - - var createInsertMacroScope = this; - - /** Adds custom rules for the macro plugin and custom serialization */ - editor.on('preInit', function (args) { - //this is requires so that we tell the serializer that a 'div' is actually allowed in the root, otherwise the cleanup will strip it out - editor.serializer.addRules('div'); - - /** This checks if the div is a macro container, if so, checks if its wrapped in a p tag and then unwraps it (removes p tag) */ - editor.serializer.addNodeFilter('div', function (nodes, name) { - for (var i = 0; i < nodes.length; i++) { - if (nodes[i].attr("class") === "umb-macro-holder" && nodes[i].parent && nodes[i].parent.name.toUpperCase() === "P") { - nodes[i].parent.unwrap(); - } - } - }); - - }); - - /** - * Because the macro gets wrapped in a P tag because of the way 'enter' works, this - * method will return the macro element if not wrapped in a p, or the p if the macro - * element is the only one inside of it even if we are deep inside an element inside the macro - */ - function getRealMacroElem(element) { - var e = $(element).closest(".umb-macro-holder"); - if (e.length > 0) { - if (e.get(0).parentNode.nodeName === "P") { - //now check if we're the only element - if (element.parentNode.childNodes.length === 1) { - return e.get(0).parentNode; - } - } - return e.get(0); - } - return null; - } - - /** Adds the button instance */ - editor.addButton('umbmacro', { - icon: 'custom icon-settings-alt', - tooltip: 'Insert macro', - onPostRender: function () { - - var ctrl = this; - var isOnMacroElement = false; - - /** - if the selection comes from a different element that is not the macro's - we need to check if the selection includes part of the macro, if so we'll force the selection - to clear to the next element since if people can select part of the macro markup they can then modify it. - */ - function handleSelectionChange() { - - if (!editor.selection.isCollapsed()) { - var endSelection = tinymce.activeEditor.selection.getEnd(); - var startSelection = tinymce.activeEditor.selection.getStart(); - //don't proceed if it's an entire element selected - if (endSelection !== startSelection) { - - //if the end selection is a macro then move the cursor - //NOTE: we don't have to handle when the selection comes from a previous parent because - // that is automatically taken care of with the normal onNodeChanged logic since the - // evt.element will be the macro once it becomes part of the selection. - var $testForMacro = $(endSelection).closest(".umb-macro-holder"); - if ($testForMacro.length > 0) { - - //it came from before so move after, if there is no after then select ourselves - var next = $testForMacro.next(); - if (next.length > 0) { - editor.selection.setCursorLocation($testForMacro.next().get(0)); - } - else { - selectMacroElement($testForMacro.get(0)); - } - - } - } - } - } - - /** helper method to select the macro element */ - function selectMacroElement(macroElement) { - - // move selection to top element to ensure we can't edit this - editor.selection.select(macroElement); - - // check if the current selection *is* the element (ie bug) - var currentSelection = editor.selection.getStart(); - if (tinymce.isIE) { - if (!editor.dom.hasClass(currentSelection, 'umb-macro-holder')) { - while (!editor.dom.hasClass(currentSelection, 'umb-macro-holder') && currentSelection.parentNode) { - currentSelection = currentSelection.parentNode; - } - editor.selection.select(currentSelection); - } - } - } - - /** - * Add a node change handler, test if we're editing a macro and select the whole thing, then set our isOnMacroElement flag. - * If we change the selection inside this method, then we end up in an infinite loop, so we have to remove ourselves - * from the event listener before changing selection, however, it seems that putting a break point in this method - * will always cause an 'infinite' loop as the caret keeps changing. - */ - function onNodeChanged(evt) { - - //set our macro button active when on a node of class umb-macro-holder - var $macroElement = $(evt.element).closest(".umb-macro-holder"); - - handleSelectionChange(); - - //set the button active - ctrl.active($macroElement.length !== 0); - - if ($macroElement.length > 0) { - var macroElement = $macroElement.get(0); - - //remove the event listener before re-selecting - editor.off('NodeChange', onNodeChanged); - - selectMacroElement(macroElement); - - //set the flag - isOnMacroElement = true; - - //re-add the event listener - editor.on('NodeChange', onNodeChanged); - } - else { - isOnMacroElement = false; - } - - } - - /** when the contents load we need to find any macros declared and load in their content */ - editor.on("LoadContent", function (o) { - - //get all macro divs and load their content - $(editor.dom.select(".umb-macro-holder.mceNonEditable")).each(function() { - createInsertMacroScope.loadMacroContent($(this), null, $scope); - }); - - }); - - /** This prevents any other commands from executing when the current element is the macro so the content cannot be edited */ - editor.on('BeforeExecCommand', function (o) { - if (isOnMacroElement) { - if (o.preventDefault) { - o.preventDefault(); - } - if (o.stopImmediatePropagation) { - o.stopImmediatePropagation(); - } - return; - } - }); - - /** This double checks and ensures you can't paste content into the rendered macro */ - editor.on("Paste", function (o) { - if (isOnMacroElement) { - if (o.preventDefault) { - o.preventDefault(); - } - if (o.stopImmediatePropagation) { - o.stopImmediatePropagation(); - } - return; - } - }); - - //set onNodeChanged event listener - editor.on('NodeChange', onNodeChanged); - - /** - * Listen for the keydown in the editor, we'll check if we are currently on a macro element, if so - * we'll check if the key down is a supported key which requires an action, otherwise we ignore the request - * so the macro cannot be edited. - */ - editor.on('KeyDown', function (e) { - if (isOnMacroElement) { - var macroElement = editor.selection.getNode(); - - //get the 'real' element (either p or the real one) - macroElement = getRealMacroElem(macroElement); - - //prevent editing - e.preventDefault(); - e.stopPropagation(); - - var moveSibling = function (element, isNext) { - var $e = $(element); - var $sibling = isNext ? $e.next() : $e.prev(); - if ($sibling.length > 0) { - editor.selection.select($sibling.get(0)); - editor.selection.collapse(true); - } - else { - //if we're moving previous and there is no sibling, then lets recurse and just select the next one - if (!isNext) { - moveSibling(element, true); - return; - } - - //if there is no sibling we'll generate a new p at the end and select it - editor.setContent(editor.getContent() + "

     

    "); - editor.selection.select($(editor.dom.getRoot()).children().last().get(0)); - editor.selection.collapse(true); - - } - }; - - //supported keys to move to the next or prev element (13-enter, 27-esc, 38-up, 40-down, 39-right, 37-left) - //supported keys to remove the macro (8-backspace, 46-delete) - //TODO: Should we make the enter key insert a line break before or leave it as moving to the next element? - if ($.inArray(e.keyCode, [13, 40, 39]) !== -1) { - //move to next element - moveSibling(macroElement, true); - } - else if ($.inArray(e.keyCode, [27, 38, 37]) !== -1) { - //move to prev element - moveSibling(macroElement, false); - } - else if ($.inArray(e.keyCode, [8, 46]) !== -1) { - //delete macro element - - //move first, then delete - moveSibling(macroElement, false); - editor.dom.remove(macroElement); - } - return ; - } - }); - - }, - - /** The insert macro button click event handler */ - onclick: function () { - - var dialogData = { - //flag for use in rte so we only show macros flagged for the editor - richTextEditor: true - }; - - //when we click we could have a macro already selected and in that case we'll want to edit the current parameters - //so we'll need to extract them and submit them to the dialog. - var macroElement = editor.selection.getNode(); - macroElement = getRealMacroElem(macroElement); - if (macroElement) { - //we have a macro selected so we'll need to parse it's alias and parameters - var contents = $(macroElement).contents(); - var comment = _.find(contents, function(item) { - return item.nodeType === 8; - }); - if (!comment) { - throw "Cannot parse the current macro, the syntax in the editor is invalid"; - } - var syntax = comment.textContent.trim(); - var parsed = macroService.parseMacroSyntax(syntax); - dialogData = { - macroData: parsed - }; - } - - if(callback) { - callback(dialogData); - } - - } - }); - }, - - insertMacroInEditor: function(editor, macroObject, $scope) { - - //put the macro syntax in comments, we will parse this out on the server side to be used - //for persisting. - var macroSyntaxComment = ""; - //create an id class for this element so we can re-select it after inserting - var uniqueId = "umb-macro-" + editor.dom.uniqueId(); - var macroDiv = editor.dom.create('div', - { - 'class': 'umb-macro-holder ' + macroObject.macroAlias + ' mceNonEditable ' + uniqueId - }, - macroSyntaxComment + 'Macro alias: ' + macroObject.macroAlias + ''); - - editor.selection.setNode(macroDiv); - - var $macroDiv = $(editor.dom.select("div.umb-macro-holder." + uniqueId)); - - //async load the macro content - this.loadMacroContent($macroDiv, macroObject, $scope); - - }, - - /** loads in the macro content async from the server */ - loadMacroContent: function($macroDiv, macroData, $scope) { - - //if we don't have the macroData, then we'll need to parse it from the macro div - if (!macroData) { - var contents = $macroDiv.contents(); - var comment = _.find(contents, function (item) { - return item.nodeType === 8; - }); - if (!comment) { - throw "Cannot parse the current macro, the syntax in the editor is invalid"; - } - var syntax = comment.textContent.trim(); - var parsed = macroService.parseMacroSyntax(syntax); - macroData = parsed; - } - - var $ins = $macroDiv.find("ins"); - - //show the throbber - $macroDiv.addClass("loading"); - - var contentId = $routeParams.id; - - //need to wrap in safe apply since this might be occuring outside of angular - angularHelper.safeApply($scope, function() { - macroResource.getMacroResultAsHtmlForEditor(macroData.macroAlias, contentId, macroData.macroParamsDictionary) - .then(function (htmlResult) { - - $macroDiv.removeClass("loading"); - htmlResult = htmlResult.trim(); - if (htmlResult !== "") { - $ins.html(htmlResult); - } - }); - }); - - }, - - createLinkPicker: function(editor, $scope, onClick) { - - function createLinkList(callback) { - return function() { - var linkList = editor.settings.link_list; - - if (typeof(linkList) === "string") { - tinymce.util.XHR.send({ - url: linkList, - success: function(text) { - callback(tinymce.util.JSON.parse(text)); - } - }); - } else { - callback(linkList); - } - }; - } - - function showDialog(linkList) { - var data = {}, selection = editor.selection, dom = editor.dom, selectedElm, anchorElm, initialText; - var win, linkListCtrl, relListCtrl, targetListCtrl; - - function linkListChangeHandler(e) { - var textCtrl = win.find('#text'); - - if (!textCtrl.value() || (e.lastControl && textCtrl.value() === e.lastControl.text())) { - textCtrl.value(e.control.text()); - } - - win.find('#href').value(e.control.value()); - } - - function buildLinkList() { - var linkListItems = [{ - text: 'None', - value: '' - }]; - - tinymce.each(linkList, function(link) { - linkListItems.push({ - text: link.text || link.title, - value: link.value || link.url, - menu: link.menu - }); - }); - - return linkListItems; - } - - function buildRelList(relValue) { - var relListItems = [{ - text: 'None', - value: '' - }]; - - tinymce.each(editor.settings.rel_list, function(rel) { - relListItems.push({ - text: rel.text || rel.title, - value: rel.value, - selected: relValue === rel.value - }); - }); - - return relListItems; - } - - function buildTargetList(targetValue) { - var targetListItems = [{ - text: 'None', - value: '' - }]; - - if (!editor.settings.target_list) { - targetListItems.push({ - text: 'New window', - value: '_blank' - }); - } - - tinymce.each(editor.settings.target_list, function(target) { - targetListItems.push({ - text: target.text || target.title, - value: target.value, - selected: targetValue === target.value - }); - }); - - return targetListItems; - } - - function buildAnchorListControl(url) { - var anchorList = []; - - tinymce.each(editor.dom.select('a:not([href])'), function(anchor) { - var id = anchor.name || anchor.id; - - if (id) { - anchorList.push({ - text: id, - value: '#' + id, - selected: url.indexOf('#' + id) !== -1 - }); - } - }); - - if (anchorList.length) { - anchorList.unshift({ - text: 'None', - value: '' - }); - - return { - name: 'anchor', - type: 'listbox', - label: 'Anchors', - values: anchorList, - onselect: linkListChangeHandler - }; - } - } - - function updateText() { - if (!initialText && data.text.length === 0) { - this.parent().parent().find('#text')[0].value(this.value()); - } - } - - selectedElm = selection.getNode(); - anchorElm = dom.getParent(selectedElm, 'a[href]'); - - data.text = initialText = anchorElm ? (anchorElm.innerText || anchorElm.textContent) : selection.getContent({format: 'text'}); - data.href = anchorElm ? dom.getAttrib(anchorElm, 'href') : ''; - data.target = anchorElm ? dom.getAttrib(anchorElm, 'target') : ''; - data.rel = anchorElm ? dom.getAttrib(anchorElm, 'rel') : ''; - - if (selectedElm.nodeName === "IMG") { - data.text = initialText = " "; - } - - if (linkList) { - linkListCtrl = { - type: 'listbox', - label: 'Link list', - values: buildLinkList(), - onselect: linkListChangeHandler - }; - } - - if (editor.settings.target_list !== false) { - targetListCtrl = { - name: 'target', - type: 'listbox', - label: 'Target', - values: buildTargetList(data.target) - }; - } - - if (editor.settings.rel_list) { - relListCtrl = { - name: 'rel', - type: 'listbox', - label: 'Rel', - values: buildRelList(data.rel) - }; - } - - var injector = angular.element(document.getElementById("umbracoMainPageBody")).injector(); - var dialogService = injector.get("dialogService"); - var currentTarget = null; - - //if we already have a link selected, we want to pass that data over to the dialog - if(anchorElm){ - var anchor = $(anchorElm); - currentTarget = { - name: anchor.attr("title"), - url: anchor.attr("href"), - target: anchor.attr("target") - }; - - //locallink detection, we do this here, to avoid poluting the dialogservice - //so the dialog service can just expect to get a node-like structure - if(currentTarget.url.indexOf("localLink:") > 0){ - currentTarget.id = currentTarget.url.substring(currentTarget.url.indexOf(":")+1,currentTarget.url.length-1); - } - } - - if(onClick) { - onClick(currentTarget, anchorElm); - } - - } - - editor.addButton('link', { - icon: 'link', - tooltip: 'Insert/edit link', - shortcut: 'Ctrl+K', - onclick: createLinkList(showDialog), - stateSelector: 'a[href]' - }); - - editor.addButton('unlink', { - icon: 'unlink', - tooltip: 'Remove link', - cmd: 'unlink', - stateSelector: 'a[href]' - }); - - editor.addShortcut('Ctrl+K', '', createLinkList(showDialog)); - this.showDialog = showDialog; - - editor.addMenuItem('link', { - icon: 'link', - text: 'Insert link', - shortcut: 'Ctrl+K', - onclick: createLinkList(showDialog), - stateSelector: 'a[href]', - context: 'insert', - prependToContext: true - }); - - }, - - insertLinkInEditor: function(editor, target, anchorElm) { - - var href = target.url; - - function insertLink() { - if (anchorElm) { - editor.dom.setAttribs(anchorElm, { - href: href, - title: target.name, - target: target.target ? target.target : null, - rel: target.rel ? target.rel : null, - 'data-id': target.id ? target.id : null - }); - - editor.selection.select(anchorElm); - editor.execCommand('mceEndTyping'); - } else { - editor.execCommand('mceInsertLink', false, { - href: href, - title: target.name, - target: target.target ? target.target : null, - rel: target.rel ? target.rel : null, - 'data-id': target.id ? target.id : null - }); - } - } - - if (!href) { - editor.execCommand('unlink'); - return; - } - - //if we have an id, it must be a locallink:id, aslong as the isMedia flag is not set - if(target.id && (angular.isUndefined(target.isMedia) || !target.isMedia)){ - href = "/{localLink:" + target.id + "}"; - insertLink(); - return; - } - - // Is email and not //user@domain.com - if (href.indexOf('@') > 0 && href.indexOf('//') === -1 && href.indexOf('mailto:') === -1) { - href = 'mailto:' + href; - insertLink(); - return; - } - - // Is www. prefixed - if (/^\s*www\./i.test(href)) { - href = 'http://' + href; - insertLink(); - return; - } - - insertLink(); - - } - - }; -} - -angular.module('umbraco.services').factory('tinyMceService', tinyMceService); - - -/** - * @ngdoc service - * @name umbraco.services.treeService - * @function - * - * @description - * The tree service factory, used internally by the umbTree and umbTreeItem directives - */ -function treeService($q, treeResource, iconHelper, notificationsService, eventsService) { - - //SD: Have looked at putting this in sessionStorage (not localStorage since that means you wouldn't be able to work - // in multiple tabs) - however our tree structure is cyclical, meaning a node has a reference to it's parent and it's children - // which you cannot serialize to sessionStorage. There's really no benefit of session storage except that you could refresh - // a tab and have the trees where they used to be - supposed that is kind of nice but would mean we'd have to store the parent - // as a nodeid reference instead of a variable with a getParent() method. - var treeCache = {}; - - var standardCssClass = 'icon umb-tree-icon sprTree'; - - function getCacheKey(args) { - //if there is no cache key they return null - it won't be cached. - if (!args || !args.cacheKey) { - return null; - } - - var cacheKey = args.cacheKey; - cacheKey += "_" + args.section; - return cacheKey; - } - - return { - - /** Internal method to return the tree cache */ - _getTreeCache: function() { - return treeCache; - }, - - /** Internal method that ensures there's a routePath, parent and level property on each tree node and adds some icon specific properties so that the nodes display properly */ - _formatNodeDataForUseInUI: function (parentNode, treeNodes, section, level) { - //if no level is set, then we make it 1 - var childLevel = (level ? level : 1); - //set the section if it's not already set - if (!parentNode.section) { - parentNode.section = section; - } - //create a method outside of the loop to return the parent - otherwise jshint blows up - var funcParent = function() { - return parentNode; - }; - for (var i = 0; i < treeNodes.length; i++) { - - treeNodes[i].level = childLevel; - - //create a function to get the parent node, we could assign the parent node but - // then we cannot serialize this entity because we have a cyclical reference. - // Instead we just make a function to return the parentNode. - treeNodes[i].parent = funcParent; - - //set the section for each tree node - this allows us to reference this easily when accessing tree nodes - treeNodes[i].section = section; - - //if there is not route path specified, then set it automatically, - //if this is a tree root node then we want to route to the section's dashboard - if (!treeNodes[i].routePath) { - - if (treeNodes[i].metaData && treeNodes[i].metaData["treeAlias"]) { - //this is a root node - treeNodes[i].routePath = section; - } - else { - var treeAlias = this.getTreeAlias(treeNodes[i]); - treeNodes[i].routePath = section + "/" + treeAlias + "/edit/" + treeNodes[i].id; - } - } - - //now, format the icon data - if (treeNodes[i].iconIsClass === undefined || treeNodes[i].iconIsClass) { - var converted = iconHelper.convertFromLegacyTreeNodeIcon(treeNodes[i]); - treeNodes[i].cssClass = standardCssClass + " " + converted; - if (converted.startsWith('.')) { - //its legacy so add some width/height - treeNodes[i].style = "height:16px;width:16px;"; - } - else { - treeNodes[i].style = ""; - } - } - else { - treeNodes[i].style = "background-image: url('" + treeNodes[i].iconFilePath + "');"; - //we need an 'icon-' class in there for certain styles to work so if it is image based we'll add this - treeNodes[i].cssClass = standardCssClass + " legacy-custom-file"; - } - } - }, - - /** - * @ngdoc method - * @name umbraco.services.treeService#getTreePackageFolder - * @methodOf umbraco.services.treeService - * @function - * - * @description - * Determines if the current tree is a plugin tree and if so returns the package folder it has declared - * so we know where to find it's views, otherwise it will just return undefined. - * - * @param {String} treeAlias The tree alias to check - */ - getTreePackageFolder: function(treeAlias) { - //we determine this based on the server variables - if (Umbraco.Sys.ServerVariables.umbracoPlugins && - Umbraco.Sys.ServerVariables.umbracoPlugins.trees && - angular.isArray(Umbraco.Sys.ServerVariables.umbracoPlugins.trees)) { - - var found = _.find(Umbraco.Sys.ServerVariables.umbracoPlugins.trees, function(item) { - return item.alias === treeAlias; - }); - - return found ? found.packageFolder : undefined; - } - return undefined; - }, - - /** - * @ngdoc method - * @name umbraco.services.treeService#clearCache - * @methodOf umbraco.services.treeService - * @function - * - * @description - * Clears the tree cache - with optional cacheKey, optional section or optional filter. - * - * @param {Object} args arguments - * @param {String} args.cacheKey optional cachekey - this is used to clear specific trees in dialogs - * @param {String} args.section optional section alias - clear tree for a given section - * @param {String} args.childrenOf optional parent ID - only clear the cache below a specific node - */ - clearCache: function (args) { - //clear all if not specified - if (!args) { - treeCache = {}; - } - else { - //if section and cache key specified just clear that cache - if (args.section && args.cacheKey) { - var cacheKey = getCacheKey(args); - if (cacheKey && treeCache && treeCache[cacheKey] != null) { - treeCache = _.omit(treeCache, cacheKey); - } - } - else if (args.childrenOf) { - //if childrenOf is supplied a cacheKey must be supplied as well - if (!args.cacheKey) { - throw "args.cacheKey is required if args.childrenOf is supplied"; - } - //this will clear out all children for the parentId passed in to this parameter, we'll - // do this by recursing and specifying a filter - var self = this; - this.clearCache({ - cacheKey: args.cacheKey, - filter: function(cc) { - //get the new parent node from the tree cache - var parent = self.getDescendantNode(cc.root, args.childrenOf); - if (parent) { - //clear it's children and set to not expanded - parent.children = null; - parent.expanded = false; - } - //return the cache to be saved - return cc; - } - }); - } - else if (args.filter && angular.isFunction(args.filter)) { - //if a filter is supplied a cacheKey must be supplied as well - if (!args.cacheKey) { - throw "args.cacheKey is required if args.filter is supplied"; - } - - //if a filter is supplied the function needs to return the data to keep - var byKey = treeCache[args.cacheKey]; - if (byKey) { - var result = args.filter(byKey); - - if (result) { - //set the result to the filtered data - treeCache[args.cacheKey] = result; - } - else { - //remove the cache - treeCache = _.omit(treeCache, args.cacheKey); - } - - } - - } - else if (args.cacheKey) { - //if only the cache key is specified, then clear all cache starting with that key - var allKeys1 = _.keys(treeCache); - var toRemove1 = _.filter(allKeys1, function (k) { - return k.startsWith(args.cacheKey + "_"); - }); - treeCache = _.omit(treeCache, toRemove1); - } - else if (args.section) { - //if only the section is specified then clear all cache regardless of cache key by that section - var allKeys2 = _.keys(treeCache); - var toRemove2 = _.filter(allKeys2, function (k) { - return k.endsWith("_" + args.section); - }); - treeCache = _.omit(treeCache, toRemove2); - } - } - }, - - /** - * @ngdoc method - * @name umbraco.services.treeService#loadNodeChildren - * @methodOf umbraco.services.treeService - * @function - * - * @description - * Clears all node children, gets it's up-to-date children from the server and re-assigns them and then - * returns them in a promise. - * @param {object} args An arguments object - * @param {object} args.node The tree node - * @param {object} args.section The current section - */ - loadNodeChildren: function(args) { - if (!args) { - throw "No args object defined for loadNodeChildren"; - } - if (!args.node) { - throw "No node defined on args object for loadNodeChildren"; - } - - this.removeChildNodes(args.node); - args.node.loading = true; - - return this.getChildren(args) - .then(function(data) { - - //set state to done and expand (only if there actually are children!) - args.node.loading = false; - args.node.children = data; - if (args.node.children && args.node.children.length > 0) { - args.node.expanded = true; - args.node.hasChildren = true; - } - return data; - - }, function(reason) { - - //in case of error, emit event - eventsService.emit("treeService.treeNodeLoadError", {error: reason } ); - - //stop show the loading indicator - args.node.loading = false; - - //tell notications about the error - notificationsService.error(reason); - - return reason; - }); - - }, - - /** - * @ngdoc method - * @name umbraco.services.treeService#removeNode - * @methodOf umbraco.services.treeService - * @function - * - * @description - * Removes a given node from the tree - * @param {object} treeNode the node to remove - */ - removeNode: function(treeNode) { - if (!angular.isFunction(treeNode.parent)) { - return; - } - - if (treeNode.parent() == null) { - throw "Cannot remove a node that doesn't have a parent"; - } - //remove the current item from it's siblings - treeNode.parent().children.splice(treeNode.parent().children.indexOf(treeNode), 1); - }, - - /** - * @ngdoc method - * @name umbraco.services.treeService#removeChildNodes - * @methodOf umbraco.services.treeService - * @function - * - * @description - * Removes all child nodes from a given tree node - * @param {object} treeNode the node to remove children from - */ - removeChildNodes : function(treeNode) { - treeNode.expanded = false; - treeNode.children = []; - treeNode.hasChildren = false; - }, - - /** - * @ngdoc method - * @name umbraco.services.treeService#getChildNode - * @methodOf umbraco.services.treeService - * @function - * - * @description - * Gets a child node with a given ID, from a specific treeNode - * @param {object} treeNode to retrive child node from - * @param {int} id id of child node - */ - getChildNode: function (treeNode, id) { - if (!treeNode.children) { - return null; - } - var found = _.find(treeNode.children, function (child) { - return String(child.id).toLowerCase() === String(id).toLowerCase(); - }); - return found === undefined ? null : found; - }, - - /** - * @ngdoc method - * @name umbraco.services.treeService#getDescendantNode - * @methodOf umbraco.services.treeService - * @function - * - * @description - * Gets a descendant node by id - * @param {object} treeNode to retrive descendant node from - * @param {int} id id of descendant node - * @param {string} treeAlias - optional tree alias, if fetching descendant node from a child of a listview document - */ - getDescendantNode: function(treeNode, id, treeAlias) { - - //validate if it is a section container since we'll need a treeAlias if it is one - if (treeNode.isContainer === true && !treeAlias) { - throw "Cannot get a descendant node from a section container node without a treeAlias specified"; - } - - //if it is a section container, we need to find the tree to be searched - if (treeNode.isContainer) { - var foundRoot = null; - for (var c = 0; c < treeNode.children.length; c++) { - if (this.getTreeAlias(treeNode.children[c]) === treeAlias) { - foundRoot = treeNode.children[c]; - break; - } - } - if (!foundRoot) { - throw "Could not find a tree in the current section with alias " + treeAlias; - } - treeNode = foundRoot; - } - - //check this node - if (treeNode.id === id) { - return treeNode; - } - - //check the first level - var found = this.getChildNode(treeNode, id); - if (found) { - return found; - } - - //check each child of this node - if (!treeNode.children) { - return null; - } - - for (var i = 0; i < treeNode.children.length; i++) { - if (treeNode.children[i].children && angular.isArray(treeNode.children[i].children) && treeNode.children[i].children.length > 0) { - //recurse - found = this.getDescendantNode(treeNode.children[i], id); - if (found) { - return found; - } - } - } - - //not found - return found === undefined ? null : found; - }, - - /** - * @ngdoc method - * @name umbraco.services.treeService#getTreeRoot - * @methodOf umbraco.services.treeService - * @function - * - * @description - * Gets the root node of the current tree type for a given tree node - * @param {object} treeNode to retrive tree root node from - */ - getTreeRoot: function (treeNode) { - if (!treeNode) { - throw "treeNode cannot be null"; - } - - //all root nodes have metadata key 'treeAlias' - var root = null; - var current = treeNode; - while (root === null && current) { - - if (current.metaData && current.metaData["treeAlias"]) { - root = current; - } - else if (angular.isFunction(current.parent)) { - //we can only continue if there is a parent() method which means this - // tree node was loaded in as part of a real tree, not just as a single tree - // node from the server. - current = current.parent(); - } - else { - current = null; - } - } - return root; - }, - - /** Gets the node's tree alias, this is done by looking up the meta-data of the current node's root node */ - /** - * @ngdoc method - * @name umbraco.services.treeService#getTreeAlias - * @methodOf umbraco.services.treeService - * @function - * - * @description - * Gets the node's tree alias, this is done by looking up the meta-data of the current node's root node - * @param {object} treeNode to retrive tree alias from - */ - getTreeAlias : function(treeNode) { - var root = this.getTreeRoot(treeNode); - if (root) { - return root.metaData["treeAlias"]; - } - return ""; - }, - - /** - * @ngdoc method - * @name umbraco.services.treeService#getTree - * @methodOf umbraco.services.treeService - * @function - * - * @description - * gets the tree, returns a promise - * @param {object} args Arguments - * @param {string} args.section Section alias - * @param {string} args.cacheKey Optional cachekey - */ - getTree: function (args) { - - var deferred = $q.defer(); - - //set defaults - if (!args) { - args = { section: 'content', cacheKey: null }; - } - else if (!args.section) { - args.section = 'content'; - } - - var cacheKey = getCacheKey(args); - - //return the cache if it exists - if (cacheKey && treeCache[cacheKey] !== undefined) { - deferred.resolve(treeCache[cacheKey]); - return deferred.promise; - } - - var self = this; - treeResource.loadApplication(args) - .then(function(data) { - //this will be called once the tree app data has loaded - var result = { - name: data.name, - alias: args.section, - root: data - }; - //we need to format/modify some of the node data to be used in our app. - self._formatNodeDataForUseInUI(result.root, result.root.children, args.section); - - //cache this result if a cache key is specified - generally a cache key should ONLY - // be specified for application trees, dialog trees should not be cached. - if (cacheKey) { - treeCache[cacheKey] = result; - deferred.resolve(treeCache[cacheKey]); - } - - //return un-cached - deferred.resolve(result); - }); - - return deferred.promise; - }, - - /** - * @ngdoc method - * @name umbraco.services.treeService#getMenu - * @methodOf umbraco.services.treeService - * @function - * - * @description - * Returns available menu actions for a given tree node - * @param {object} args Arguments - * @param {string} args.treeNode tree node object to retrieve the menu for - */ - getMenu: function (args) { - - if (!args) { - throw "args cannot be null"; - } - if (!args.treeNode) { - throw "args.treeNode cannot be null"; - } - - return treeResource.loadMenu(args.treeNode) - .then(function(data) { - //need to convert the icons to new ones - for (var i = 0; i < data.length; i++) { - data[i].cssclass = iconHelper.convertFromLegacyIcon(data[i].cssclass); - } - return data; - }); - }, - - /** - * @ngdoc method - * @name umbraco.services.treeService#getChildren - * @methodOf umbraco.services.treeService - * @function - * - * @description - * Gets the children from the server for a given node - * @param {object} args Arguments - * @param {object} args.node tree node object to retrieve the children for - * @param {string} args.section current section alias - */ - getChildren: function (args) { - - if (!args) { - throw "No args object defined for getChildren"; - } - if (!args.node) { - throw "No node defined on args object for getChildren"; - } - - var section = args.section || 'content'; - var treeItem = args.node; - - var self = this; - - return treeResource.loadNodes({ node: treeItem }) - .then(function (data) { - //now that we have the data, we need to add the level property to each item and the view - self._formatNodeDataForUseInUI(treeItem, data, section, treeItem.level + 1); - return data; - }); - }, - - /** - * @ngdoc method - * @name umbraco.services.treeService#reloadNode - * @methodOf umbraco.services.treeService - * @function - * - * @description - * Re-loads the single node from the server - * @param {object} node Tree node to reload - */ - reloadNode: function(node) { - if (!node) { - throw "node cannot be null"; - } - if (!node.parent()) { - throw "cannot reload a single node without a parent"; - } - if (!node.section) { - throw "cannot reload a single node without an assigned node.section"; - } - - var deferred = $q.defer(); - - //set the node to loading - node.loading = true; - - this.getChildren({ node: node.parent(), section: node.section }).then(function(data) { - - //ok, now that we have the children, find the node we're reloading - var found = _.find(data, function(item) { - return item.id === node.id; - }); - if (found) { - //now we need to find the node in the parent.children collection to replace - var index = _.indexOf(node.parent().children, node); - //the trick here is to not actually replace the node - this would cause the delete animations - //to fire, instead we're just going to replace all the properties of this node. - - //there should always be a method assigned but we'll check anyways - if (angular.isFunction(node.parent().children[index].updateNodeData)) { - node.parent().children[index].updateNodeData(found); - } - else { - //just update as per normal - this means styles, etc.. won't be applied - _.extend(node.parent().children[index], found); - } - - //set the node loading - node.parent().children[index].loading = false; - //return - deferred.resolve(node.parent().children[index]); - } - else { - deferred.reject(); - } - }, function() { - deferred.reject(); - }); - - return deferred.promise; - }, - - /** - * @ngdoc method - * @name umbraco.services.treeService#getPath - * @methodOf umbraco.services.treeService - * @function - * - * @description - * This will return the current node's path by walking up the tree - * @param {object} node Tree node to retrieve path for - */ - getPath: function(node) { - if (!node) { - throw "node cannot be null"; - } - if (!angular.isFunction(node.parent)) { - throw "node.parent is not a function, the path cannot be resolved"; - } - //all root nodes have metadata key 'treeAlias' - var reversePath = []; - var current = node; - while (current != null) { - reversePath.push(current.id); - if (current.metaData && current.metaData["treeAlias"]) { - current = null; - } - else { - current = current.parent(); - } - } - return reversePath.reverse(); - }, - - syncTree: function(args) { - - if (!args) { - throw "No args object defined for syncTree"; - } - if (!args.node) { - throw "No node defined on args object for syncTree"; - } - if (!args.path) { - throw "No path defined on args object for syncTree"; - } - if (!angular.isArray(args.path)) { - throw "Path must be an array"; - } - if (args.path.length < 1) { - //if there is no path, make -1 the path, and that should sync the tree root - args.path.push("-1"); - } - - var deferred = $q.defer(); - - //get the rootNode for the current node, we'll sync based on that - var root = this.getTreeRoot(args.node); - if (!root) { - throw "Could not get the root tree node based on the node passed in"; - } - - //now we want to loop through the ids in the path, first we'll check if the first part - //of the path is the root node, otherwise we'll search it's children. - var currPathIndex = 0; - //if the first id is the root node and there's only one... then consider it synced - if (String(args.path[currPathIndex]).toLowerCase() === String(args.node.id).toLowerCase()) { - if (args.path.length === 1) { - //return the root - deferred.resolve(root); - return deferred.promise; - } - else { - //move to the next path part and continue - currPathIndex = 1; - } - } - - //now that we have the first id to lookup, we can start the process - - var self = this; - var node = args.node; - - var doSync = function () { - //check if it exists in the already loaded children - var child = self.getChildNode(node, args.path[currPathIndex]); - if (child) { - if (args.path.length === (currPathIndex + 1)) { - //woot! synced the node - if (!args.forceReload) { - deferred.resolve(child); - } - else { - //even though we've found the node if forceReload is specified - //we want to go update this single node from the server - self.reloadNode(child).then(function (reloaded) { - deferred.resolve(reloaded); - }, function () { - deferred.reject(); - }); - } - } - else { - //now we need to recurse with the updated node/currPathIndex - currPathIndex++; - node = child; - //recurse - doSync(); - } - } - else { - //couldn't find it in the - self.loadNodeChildren({ node: node, section: node.section }).then(function () { - //ok, got the children, let's find it - var found = self.getChildNode(node, args.path[currPathIndex]); - if (found) { - if (args.path.length === (currPathIndex + 1)) { - //woot! synced the node - deferred.resolve(found); - } - else { - //now we need to recurse with the updated node/currPathIndex - currPathIndex++; - node = found; - //recurse - doSync(); - } - } - else { - //fail! - deferred.reject(); - } - }, function () { - //fail! - deferred.reject(); - }); - } - }; - - //start - doSync(); - - return deferred.promise; - - } - - }; -} - -angular.module('umbraco.services').factory('treeService', treeService); -/** -* @ngdoc service -* @name umbraco.services.umbRequestHelper -* @description A helper object used for sending requests to the server -**/ -function umbRequestHelper($http, $q, umbDataFormatter, angularHelper, dialogService, notificationsService, eventsService) { - return { - - /** - * @ngdoc method - * @name umbraco.services.umbRequestHelper#convertVirtualToAbsolutePath - * @methodOf umbraco.services.umbRequestHelper - * @function - * - * @description - * This will convert a virtual path (i.e. ~/App_Plugins/Blah/Test.html ) to an absolute path - * - * @param {string} a virtual path, if this is already an absolute path it will just be returned, if this is a relative path an exception will be thrown - */ - convertVirtualToAbsolutePath: function(virtualPath) { - if (virtualPath.startsWith("/")) { - return virtualPath; - } - if (!virtualPath.startsWith("~/")) { - throw "The path " + virtualPath + " is not a virtual path"; - } - if (!Umbraco.Sys.ServerVariables.application.applicationPath) { - throw "No applicationPath defined in Umbraco.ServerVariables.application.applicationPath"; - } - return Umbraco.Sys.ServerVariables.application.applicationPath + virtualPath.trimStart("~/"); - }, - - /** - * @ngdoc method - * @name umbraco.services.umbRequestHelper#dictionaryToQueryString - * @methodOf umbraco.services.umbRequestHelper - * @function - * - * @description - * This will turn an array of key/value pairs into a query string - * - * @param {Array} queryStrings An array of key/value pairs - */ - dictionaryToQueryString: function (queryStrings) { - - if (angular.isArray(queryStrings)) { - return _.map(queryStrings, function (item) { - var key = null; - var val = null; - for (var k in item) { - key = k; - val = item[k]; - break; - } - if (key === null || val === null) { - throw "The object in the array was not formatted as a key/value pair"; - } - return encodeURIComponent(key) + "=" + encodeURIComponent(val); - }).join("&"); - } - else if (angular.isObject(queryStrings)) { - - //this allows for a normal object to be passed in (ie. a dictionary) - return decodeURIComponent($.param(queryStrings)); - } - - throw "The queryString parameter is not an array or object of key value pairs"; - }, - - /** - * @ngdoc method - * @name umbraco.services.umbRequestHelper#getApiUrl - * @methodOf umbraco.services.umbRequestHelper - * @function - * - * @description - * This will return the webapi Url for the requested key based on the servervariables collection - * - * @param {string} apiName The webapi name that is found in the servervariables["umbracoUrls"] dictionary - * @param {string} actionName The webapi action name - * @param {object} queryStrings Can be either a string or an array containing key/value pairs - */ - getApiUrl: function (apiName, actionName, queryStrings) { - if (!Umbraco || !Umbraco.Sys || !Umbraco.Sys.ServerVariables || !Umbraco.Sys.ServerVariables["umbracoUrls"]) { - throw "No server variables defined!"; - } - - if (!Umbraco.Sys.ServerVariables["umbracoUrls"][apiName]) { - throw "No url found for api name " + apiName; - } - - return Umbraco.Sys.ServerVariables["umbracoUrls"][apiName] + actionName + - (!queryStrings ? "" : "?" + (angular.isString(queryStrings) ? queryStrings : this.dictionaryToQueryString(queryStrings))); - - }, - - /** - * @ngdoc function - * @name umbraco.services.umbRequestHelper#resourcePromise - * @methodOf umbraco.services.umbRequestHelper - * @function - * - * @description - * This returns a promise with an underlying http call, it is a helper method to reduce - * the amount of duplicate code needed to query http resources and automatically handle any - * Http errors. See /docs/source/using-promises-resources.md - * - * @param {object} opts A mixed object which can either be a string representing the error message to be - * returned OR an object containing either: - * { success: successCallback, errorMsg: errorMessage } - * OR - * { success: successCallback, error: errorCallback } - * In both of the above, the successCallback must accept these parameters: data, status, headers, config - * If using the errorCallback it must accept these parameters: data, status, headers, config - * The success callback must return the data which will be resolved by the deferred object. - * The error callback must return an object containing: {errorMsg: errorMessage, data: originalData, status: status } - */ - resourcePromise: function (httpPromise, opts) { - var deferred = $q.defer(); - - /** The default success callback used if one is not supplied in the opts */ - function defaultSuccess(data, status, headers, config) { - //when it's successful, just return the data - return data; - } - - /** The default error callback used if one is not supplied in the opts */ - function defaultError(data, status, headers, config) { - return { - //NOTE: the default error message here should never be used based on the above docs! - errorMsg: (angular.isString(opts) ? opts : 'An error occurred!'), - data: data, - status: status - }; - } - - //create the callbacs based on whats been passed in. - var callbacks = { - success: ((!opts || !opts.success) ? defaultSuccess : opts.success), - error: ((!opts || !opts.error) ? defaultError : opts.error) - }; - - httpPromise.success(function (data, status, headers, config) { - - //invoke the callback - var result = callbacks.success.apply(this, [data, status, headers, config]); - - //when it's successful, just return the data - deferred.resolve(result); - - }).error(function (data, status, headers, config) { - - //invoke the callback - var result = callbacks.error.apply(this, [data, status, headers, config]); - - //when there's a 500 (unhandled) error show a YSOD overlay if debugging is enabled. - if (status >= 500 && status < 600) { - - //show a ysod dialog - if (Umbraco.Sys.ServerVariables["isDebuggingEnabled"] === true) { - eventsService.emit('app.ysod', - { - errorMsg: 'An error occured', - data: data - }); - } - else { - //show a simple error notification - notificationsService.error("Server error", "Contact administrator, see log for full details.
    " + result.errorMsg + ""); - } - - } - - //return an error object including the error message for UI - deferred.reject({ - errorMsg: result.errorMsg, - data: result.data, - status: result.status - }); - - - }); - - return deferred.promise; - - }, - - /** Used for saving media/content specifically */ - postSaveContent: function (args) { - - if (!args.restApiUrl) { - throw "args.restApiUrl is a required argument"; - } - if (!args.content) { - throw "args.content is a required argument"; - } - if (!args.action) { - throw "args.action is a required argument"; - } - if (!args.files) { - throw "args.files is a required argument"; - } - if (!args.dataFormatter) { - throw "args.dataFormatter is a required argument"; - } - - - var deferred = $q.defer(); - - //save the active tab id so we can set it when the data is returned. - var activeTab = _.find(args.content.tabs, function (item) { - return item.active; - }); - var activeTabIndex = (activeTab === undefined ? 0 : _.indexOf(args.content.tabs, activeTab)); - - //save the data - this.postMultiPartRequest( - args.restApiUrl, - { key: "contentItem", value: args.dataFormatter(args.content, args.action) }, - function (data, formData) { - //now add all of the assigned files - for (var f in args.files) { - //each item has a property alias and the file object, we'll ensure that the alias is suffixed to the key - // so we know which property it belongs to on the server side - formData.append("file_" + args.files[f].alias, args.files[f].file); - } - - }, - function (data, status, headers, config) { - //success callback - - //reset the tabs and set the active one - _.each(data.tabs, function (item) { - item.active = false; - }); - data.tabs[activeTabIndex].active = true; - - //the data returned is the up-to-date data so the UI will refresh - deferred.resolve(data); - }, - function (data, status, headers, config) { - //failure callback - - //when there's a 500 (unhandled) error show a YSOD overlay if debugging is enabled. - if (status >= 500 && status < 600) { - - //This is a bit of a hack to check if the error is due to a file being uploaded that is too large, - // we have to just check for the existence of a string value but currently that is the best way to - // do this since it's very hacky/difficult to catch this on the server - if (typeof data !== "undefined" && typeof data.indexOf === "function" && data.indexOf("Maximum request length exceeded") >= 0) { - notificationsService.error("Server error", "The uploaded file was too large, check with your site administrator to adjust the maximum size allowed"); - } - else if (Umbraco.Sys.ServerVariables["isDebuggingEnabled"] === true) { - //show a ysod dialog - eventsService.emit('app.ysod', - { - errorMsg: 'An error occured', - data: data - }); - } - else { - //show a simple error notification - notificationsService.error("Server error", "Contact administrator, see log for full details.
    " + data.ExceptionMessage + ""); - } - - } - - //return an error object including the error message for UI - deferred.reject({ - errorMsg: 'An error occurred', - data: data, - status: status - }); - - - }); - - return deferred.promise; - }, - - /** Posts a multi-part mime request to the server */ - postMultiPartRequest: function (url, jsonData, transformCallback, successCallback, failureCallback) { - - //validate input, jsonData can be an array of key/value pairs or just one key/value pair. - if (!jsonData) { throw "jsonData cannot be null"; } - - if (angular.isArray(jsonData)) { - _.each(jsonData, function (item) { - if (!item.key || !item.value) { throw "jsonData array item must have both a key and a value property"; } - }); - } - else if (!jsonData.key || !jsonData.value) { throw "jsonData object must have both a key and a value property"; } - - - $http({ - method: 'POST', - url: url, - //IMPORTANT!!! You might think this should be set to 'multipart/form-data' but this is not true because when we are sending up files - // the request needs to include a 'boundary' parameter which identifies the boundary name between parts in this multi-part request - // and setting the Content-type manually will not set this boundary parameter. For whatever reason, setting the Content-type to 'false' - // will force the request to automatically populate the headers properly including the boundary parameter. - headers: { 'Content-Type': false }, - transformRequest: function (data) { - var formData = new FormData(); - //add the json data - if (angular.isArray(data)) { - _.each(data, function (item) { - formData.append(item.key, !angular.isString(item.value) ? angular.toJson(item.value) : item.value); - }); - } - else { - formData.append(data.key, !angular.isString(data.value) ? angular.toJson(data.value) : data.value); - } - - //call the callback - if (transformCallback) { - transformCallback.apply(this, [data, formData]); - } - - return formData; - }, - data: jsonData - }). - success(function (data, status, headers, config) { - if (successCallback) { - successCallback.apply(this, [data, status, headers, config]); - } - }). - error(function (data, status, headers, config) { - if (failureCallback) { - failureCallback.apply(this, [data, status, headers, config]); - } - }); - } - }; -} -angular.module('umbraco.services').factory('umbRequestHelper', umbRequestHelper); - -angular.module('umbraco.services') - .factory('userService', function ($rootScope, eventsService, $q, $location, $log, securityRetryQueue, authResource, dialogService, $timeout, angularHelper, $http) { - - var currentUser = null; - var lastUserId = null; - var loginDialog = null; - //this tracks the last date/time that the user's remainingAuthSeconds was updated from the server - // this is used so that we know when to go and get the user's remaining seconds directly. - var lastServerTimeoutSet = null; - - function openLoginDialog(isTimedOut) { - if (!loginDialog) { - loginDialog = dialogService.open({ - - //very special flag which means that global events cannot close this dialog - manualClose: true, - - template: 'views/common/dialogs/login.html', - modalClass: "login-overlay", - animation: "slide", - show: true, - callback: onLoginDialogClose, - dialogData: { - isTimedOut: isTimedOut - } - }); - } - } - - function onLoginDialogClose(success) { - loginDialog = null; - - if (success) { - securityRetryQueue.retryAll(currentUser.name); - } - else { - securityRetryQueue.cancelAll(); - $location.path('/'); - } - } - - /** - This methods will set the current user when it is resolved and - will then start the counter to count in-memory how many seconds they have - remaining on the auth session - */ - function setCurrentUser(usr) { - if (!usr.remainingAuthSeconds) { - throw "The user object is invalid, the remainingAuthSeconds is required."; - } - currentUser = usr; - lastServerTimeoutSet = new Date(); - //start the timer - countdownUserTimeout(); - } - - /** - Method to count down the current user's timeout seconds, - this will continually count down their current remaining seconds every 5 seconds until - there are no more seconds remaining. - */ - function countdownUserTimeout() { - - $timeout(function () { - - if (currentUser) { - //countdown by 5 seconds since that is how long our timer is for. - currentUser.remainingAuthSeconds -= 5; - - //if there are more than 30 remaining seconds, recurse! - if (currentUser.remainingAuthSeconds > 30) { - - //we need to check when the last time the timeout was set from the server, if - // it has been more than 30 seconds then we'll manually go and retrieve it from the - // server - this helps to keep our local countdown in check with the true timeout. - if (lastServerTimeoutSet != null) { - var now = new Date(); - var seconds = (now.getTime() - lastServerTimeoutSet.getTime()) / 1000; - - if (seconds > 30) { - - //first we'll set the lastServerTimeoutSet to null - this is so we don't get back in to this loop while we - // wait for a response from the server otherwise we'll be making double/triple/etc... calls while we wait. - lastServerTimeoutSet = null; - - //now go get it from the server - //NOTE: the safeApply because our timeout is set to not run digests (performance reasons) - angularHelper.safeApply($rootScope, function () { - authResource.getRemainingTimeoutSeconds().then(function (result) { - setUserTimeoutInternal(result); - }); - }); - } - } - - //recurse the countdown! - countdownUserTimeout(); - } - else { - - //we are either timed out or very close to timing out so we need to show the login dialog. - if (Umbraco.Sys.ServerVariables.umbracoSettings.keepUserLoggedIn !== true) { - //NOTE: the safeApply because our timeout is set to not run digests (performance reasons) - angularHelper.safeApply($rootScope, function () { - try { - //NOTE: We are calling this again so that the server can create a log that the timeout has expired, we - // don't actually care about this result. - authResource.getRemainingTimeoutSeconds(); - } - finally { - userAuthExpired(); - } - }); - } - else { - //we've got less than 30 seconds remaining so let's check the server - - if (lastServerTimeoutSet != null) { - //first we'll set the lastServerTimeoutSet to null - this is so we don't get back in to this loop while we - // wait for a response from the server otherwise we'll be making double/triple/etc... calls while we wait. - lastServerTimeoutSet = null; - - //now go get it from the server - //NOTE: the safeApply because our timeout is set to not run digests (performance reasons) - angularHelper.safeApply($rootScope, function () { - authResource.getRemainingTimeoutSeconds().then(function (result) { - setUserTimeoutInternal(result); - }); - }); - } - - //recurse the countdown! - countdownUserTimeout(); - - } - } - } - }, 5000, //every 5 seconds - false); //false = do NOT execute a digest for every iteration - } - - /** Called to update the current user's timeout */ - function setUserTimeoutInternal(newTimeout) { - - - var asNumber = parseFloat(newTimeout); - if (!isNaN(asNumber) && currentUser && angular.isNumber(asNumber)) { - currentUser.remainingAuthSeconds = newTimeout; - lastServerTimeoutSet = new Date(); - } - } - - /** resets all user data, broadcasts the notAuthenticated event and shows the login dialog */ - function userAuthExpired(isLogout) { - //store the last user id and clear the user - if (currentUser && currentUser.id !== undefined) { - lastUserId = currentUser.id; - } - - if (currentUser) { - currentUser.remainingAuthSeconds = 0; - } - - lastServerTimeoutSet = null; - currentUser = null; - - //broadcast a global event that the user is no longer logged in - eventsService.emit("app.notAuthenticated"); - - openLoginDialog(isLogout === undefined ? true : !isLogout); - } - - // Register a handler for when an item is added to the retry queue - securityRetryQueue.onItemAddedCallbacks.push(function (retryItem) { - if (securityRetryQueue.hasMore()) { - userAuthExpired(); - } - }); - - return { - - /** Internal method to display the login dialog */ - _showLoginDialog: function () { - openLoginDialog(); - }, - - /** Returns a promise, sends a request to the server to check if the current cookie is authorized */ - isAuthenticated: function () { - //if we've got a current user then just return true - if (currentUser) { - var deferred = $q.defer(); - deferred.resolve(true); - return deferred.promise; - } - return authResource.isAuthenticated(); - }, - - /** Returns a promise, sends a request to the server to validate the credentials */ - authenticate: function (login, password) { - - return authResource.performLogin(login, password) - .then(function (data) { - - //when it's successful, return the user data - setCurrentUser(data); - - var result = { user: data, authenticated: true, lastUserId: lastUserId }; - - //broadcast a global event - eventsService.emit("app.authenticated", result); - return result; - }); - }, - - /** Logs the user out - */ - logout: function () { - - return authResource.performLogout() - .then(function(data) { - userAuthExpired(); - //done! - return null; - }); - }, - - /** Returns the current user object in a promise */ - getCurrentUser: function (args) { - var deferred = $q.defer(); - - if (!currentUser) { - authResource.getCurrentUser() - .then(function (data) { - - var result = { user: data, authenticated: true, lastUserId: lastUserId }; - - //TODO: This is a mega backwards compatibility hack... These variables SHOULD NOT exist in the server variables - // since they are not supposed to be dynamic but I accidentally added them there in 7.1.5 IIRC so some people might - // now be relying on this :( - if (Umbraco && Umbraco.Sys && Umbraco.Sys.ServerVariables) { - Umbraco.Sys.ServerVariables["security"] = { - startContentId: data.startContentId, - startMediaId: data.startMediaId - }; - } - - if (args && args.broadcastEvent) { - //broadcast a global event, will inform listening controllers to load in the user specific data - eventsService.emit("app.authenticated", result); - } - - setCurrentUser(data); - - deferred.resolve(currentUser); - }); - - } - else { - deferred.resolve(currentUser); - } - - return deferred.promise; - }, - - /** Called whenever a server request is made that contains a x-umb-user-seconds response header for which we can update the user's remaining timeout seconds */ - setUserTimeout: function (newTimeout) { - setUserTimeoutInternal(newTimeout); - } - }; - - }); - -/*Contains multiple services for various helper tasks */ -function versionHelper() { - - return { - - //see: https://gist.github.com/TheDistantSea/8021359 - versionCompare: function(v1, v2, options) { - var lexicographical = options && options.lexicographical, - zeroExtend = options && options.zeroExtend, - v1parts = v1.split('.'), - v2parts = v2.split('.'); - - function isValidPart(x) { - return (lexicographical ? /^\d+[A-Za-z]*$/ : /^\d+$/).test(x); - } - - if (!v1parts.every(isValidPart) || !v2parts.every(isValidPart)) { - return NaN; - } - - if (zeroExtend) { - while (v1parts.length < v2parts.length) { - v1parts.push("0"); - } - while (v2parts.length < v1parts.length) { - v2parts.push("0"); - } - } - - if (!lexicographical) { - v1parts = v1parts.map(Number); - v2parts = v2parts.map(Number); - } - - for (var i = 0; i < v1parts.length; ++i) { - if (v2parts.length === i) { - return 1; - } - - if (v1parts[i] === v2parts[i]) { - continue; - } - else if (v1parts[i] > v2parts[i]) { - return 1; - } - else { - return -1; - } - } - - if (v1parts.length !== v2parts.length) { - return -1; - } - - return 0; - } - }; -} -angular.module('umbraco.services').factory('versionHelper', versionHelper); - -function dateHelper() { - - return { - - convertToServerStringTime: function(momentLocal, serverOffsetMinutes, format) { - - //get the formatted offset time in HH:mm (server time offset is in minutes) - var formattedOffset = (serverOffsetMinutes > 0 ? "+" : "-") + - moment() - .startOf('day') - .minutes(Math.abs(serverOffsetMinutes)) - .format('HH:mm'); - - var server = moment.utc(momentLocal).utcOffset(formattedOffset); - return server.format(format ? format : "YYYY-MM-DD HH:mm:ss"); - }, - - convertToLocalMomentTime: function (strVal, serverOffsetMinutes) { - - //get the formatted offset time in HH:mm (server time offset is in minutes) - var formattedOffset = (serverOffsetMinutes > 0 ? "+" : "-") + - moment() - .startOf('day') - .minutes(Math.abs(serverOffsetMinutes)) - .format('HH:mm'); - - //convert to the iso string format - var isoFormat = moment(strVal).format("YYYY-MM-DDTHH:mm:ss") + formattedOffset; - - //create a moment with the iso format which will include the offset with the correct time - // then convert it to local time - return moment.parseZone(isoFormat).local(); - } - - }; -} -angular.module('umbraco.services').factory('dateHelper', dateHelper); - -function packageHelper(assetsService, treeService, eventsService, $templateCache) { - - return { - - /** Called when a package is installed, this resets a bunch of data and ensures the new package assets are loaded in */ - packageInstalled: function () { - - //clears the tree - treeService.clearCache(); - - //clears the template cache - $templateCache.removeAll(); - - //emit event to notify anything else - eventsService.emit("app.reInitialize"); - } - - }; -} -angular.module('umbraco.services').factory('packageHelper', packageHelper); - -//TODO: I believe this is obsolete -function umbPhotoFolderHelper($compile, $log, $timeout, $filter, imageHelper, mediaHelper, umbRequestHelper) { - return { - /** sets the image's url, thumbnail and if its a folder */ - setImageData: function(img) { - - img.isFolder = !mediaHelper.hasFilePropertyType(img); - - if(!img.isFolder){ - img.thumbnail = mediaHelper.resolveFile(img, true); - img.image = mediaHelper.resolveFile(img, false); - } - }, - - /** sets the images original size properties - will check if it is a folder and if so will just make it square */ - setOriginalSize: function(img, maxHeight) { - //set to a square by default - img.originalWidth = maxHeight; - img.originalHeight = maxHeight; - - var widthProp = _.find(img.properties, function(v) { return (v.alias === "umbracoWidth"); }); - if (widthProp && widthProp.value) { - img.originalWidth = parseInt(widthProp.value, 10); - if (isNaN(img.originalWidth)) { - img.originalWidth = maxHeight; - } - } - var heightProp = _.find(img.properties, function(v) { return (v.alias === "umbracoHeight"); }); - if (heightProp && heightProp.value) { - img.originalHeight = parseInt(heightProp.value, 10); - if (isNaN(img.originalHeight)) { - img.originalHeight = maxHeight; - } - } - }, - - /** sets the image style which get's used in the angular markup */ - setImageStyle: function(img, width, height, rightMargin, bottomMargin) { - img.style = { width: width + "px", height: height + "px", "margin-right": rightMargin + "px", "margin-bottom": bottomMargin + "px" }; - img.thumbStyle = { - "background-image": "url('" + img.thumbnail + "')", - "background-repeat": "no-repeat", - "background-position": "center", - "background-size": Math.min(width, img.originalWidth) + "px " + Math.min(height, img.originalHeight) + "px" - }; - }, - - /** gets the image's scaled wdith based on the max row height */ - getScaledWidth: function(img, maxHeight) { - var scaled = img.originalWidth * maxHeight / img.originalHeight; - return scaled; - //round down, we don't want it too big even by half a pixel otherwise it'll drop to the next row - //return Math.floor(scaled); - }, - - /** returns the target row width taking into account how many images will be in the row and removing what the margin is */ - getTargetWidth: function(imgsPerRow, maxRowWidth, margin) { - //take into account the margin, we will have 1 less margin item than we have total images - return (maxRowWidth - ((imgsPerRow - 1) * margin)); - }, - - /** - This will determine the row/image height for the next collection of images which takes into account the - ideal image count per row. It will check if a row can be filled with this ideal count and if not - if there - are additional images available to fill the row it will keep calculating until they fit. - - It will return the calculated height and the number of images for the row. - - targetHeight = optional; - */ - getRowHeightForImages: function(imgs, maxRowHeight, minDisplayHeight, maxRowWidth, idealImgPerRow, margin, targetHeight) { - - var idealImages = imgs.slice(0, idealImgPerRow); - //get the target row width without margin - var targetRowWidth = this.getTargetWidth(idealImages.length, maxRowWidth, margin); - //this gets the image with the smallest height which equals the maximum we can scale up for this image block - var maxScaleableHeight = this.getMaxScaleableHeight(idealImages, maxRowHeight); - //if the max scale height is smaller than the min display height, we'll use the min display height - targetHeight = targetHeight !== undefined ? targetHeight : Math.max(maxScaleableHeight, minDisplayHeight); - - var attemptedRowHeight = this.performGetRowHeight(idealImages, targetRowWidth, minDisplayHeight, targetHeight); - - if (attemptedRowHeight != null) { - - //if this is smaller than the min display then we need to use the min display, - // which means we'll need to remove one from the row so we can scale up to fill the row - if (attemptedRowHeight < minDisplayHeight) { - - if (idealImages.length > 1) { - - //we'll generate a new targetHeight that is halfway between the max and the current and recurse, passing in a new targetHeight - targetHeight += Math.floor((maxRowHeight - targetHeight) / 2); - return this.getRowHeightForImages(imgs, maxRowHeight, minDisplayHeight, maxRowWidth, idealImgPerRow - 1, margin, targetHeight); - } - else { - //this will occur when we only have one image remaining in the row but it's still going to be too wide even when - // using the minimum display height specified. In this case we're going to have to just crop the image in it's center - // using the minimum display height and the full row width - return { height: minDisplayHeight, imgCount: 1 }; - } - } - else { - //success! - return { height: attemptedRowHeight, imgCount: idealImages.length }; - } - } - - //we know the width will fit in a row, but we now need to figure out if we can fill - // the entire row in the case that we have more images remaining than the idealImgPerRow. - - if (idealImages.length === imgs.length) { - //we have no more remaining images to fill the space, so we'll just use the calc height - return { height: targetHeight, imgCount: idealImages.length }; - } - else if (idealImages.length === 1) { - //this will occur when we only have one image remaining in the row to process but it's not really going to fit ideally - // in the row. - return { height: minDisplayHeight, imgCount: 1 }; - } - else if (idealImages.length === idealImgPerRow && targetHeight < maxRowHeight) { - - //if we're already dealing with the ideal images per row and it's not quite wide enough, we can scale up a little bit so - // long as the targetHeight is currently less than the maxRowHeight. The scale up will be half-way between our current - // target height and the maxRowHeight (we won't loop forever though - if there's a difference of 5 px we'll just quit) - - while (targetHeight < maxRowHeight && (maxRowHeight - targetHeight) > 5) { - targetHeight += Math.floor((maxRowHeight - targetHeight) / 2); - attemptedRowHeight = this.performGetRowHeight(idealImages, targetRowWidth, minDisplayHeight, targetHeight); - if (attemptedRowHeight != null) { - //success! - return { height: attemptedRowHeight, imgCount: idealImages.length }; - } - } - - //Ok, we couldn't actually scale it up with the ideal row count we'll just recurse with a lesser image count. - return this.getRowHeightForImages(imgs, maxRowHeight, minDisplayHeight, maxRowWidth, idealImgPerRow - 1, margin); - } - else if (targetHeight === maxRowHeight) { - - //This is going to happen when: - // * We can fit a list of images in a row, but they come up too short (based on minDisplayHeight) - // * Then we'll try to remove an image, but when we try to scale to fit, the width comes up too narrow but the images are already at their - // maximum height (maxRowHeight) - // * So we're stuck, we cannot precicely fit the current list of images, so we'll render a row that will be max height but won't be wide enough - // which is better than rendering a row that is shorter than the minimum since that could be quite small. - - return { height: targetHeight, imgCount: idealImages.length }; - } - else { - - //we have additional images so we'll recurse and add 1 to the idealImgPerRow until it fits - return this.getRowHeightForImages(imgs, maxRowHeight, minDisplayHeight, maxRowWidth, idealImgPerRow + 1, margin); - } - - }, - - performGetRowHeight: function(idealImages, targetRowWidth, minDisplayHeight, targetHeight) { - - var currRowWidth = 0; - - for (var i = 0; i < idealImages.length; i++) { - var scaledW = this.getScaledWidth(idealImages[i], targetHeight); - currRowWidth += scaledW; - } - - if (currRowWidth > targetRowWidth) { - //get the new scaled height to fit - var newHeight = targetRowWidth * targetHeight / currRowWidth; - - return newHeight; - } - else if (idealImages.length === 1 && (currRowWidth <= targetRowWidth) && !idealImages[0].isFolder) { - //if there is only one image, then return the target height - return targetHeight; - } - else if (currRowWidth / targetRowWidth > 0.90) { - //it's close enough, it's at least 90% of the width so we'll accept it with the target height - return targetHeight; - } - else { - //if it's not successful, return null - return null; - } - }, - - /** builds an image grid row */ - buildRow: function(imgs, maxRowHeight, minDisplayHeight, maxRowWidth, idealImgPerRow, margin, totalRemaining) { - var currRowWidth = 0; - var row = { images: [] }; - - var imageRowHeight = this.getRowHeightForImages(imgs, maxRowHeight, minDisplayHeight, maxRowWidth, idealImgPerRow, margin); - var targetWidth = this.getTargetWidth(imageRowHeight.imgCount, maxRowWidth, margin); - - var sizes = []; - //loop through the images we know fit into the height - for (var i = 0; i < imageRowHeight.imgCount; i++) { - //get the lower width to ensure it always fits - var scaledWidth = Math.floor(this.getScaledWidth(imgs[i], imageRowHeight.height)); - - if (currRowWidth + scaledWidth <= targetWidth) { - currRowWidth += scaledWidth; - sizes.push({ - width:scaledWidth, - //ensure that the height is rounded - height: Math.round(imageRowHeight.height) - }); - row.images.push(imgs[i]); - } - else if (imageRowHeight.imgCount === 1 && row.images.length === 0) { - //the image is simply too wide, we'll crop/center it - sizes.push({ - width: maxRowWidth, - //ensure that the height is rounded - height: Math.round(imageRowHeight.height) - }); - row.images.push(imgs[i]); - } - else { - //the max width has been reached - break; - } - } - - //loop through the images for the row and apply the styles - for (var j = 0; j < row.images.length; j++) { - var bottomMargin = margin; - //make the margin 0 for the last one - if (j === (row.images.length - 1)) { - margin = 0; - } - this.setImageStyle(row.images[j], sizes[j].width, sizes[j].height, margin, bottomMargin); - } - - if (row.images.length === 1 && totalRemaining > 1) { - //if there's only one image on the row and there are more images remaining, set the container to max width - row.images[0].style.width = maxRowWidth + "px"; - } - - - return row; - }, - - /** Returns the maximum image scaling height for the current image collection */ - getMaxScaleableHeight: function(imgs, maxRowHeight) { - - var smallestHeight = _.min(imgs, function(item) { return item.originalHeight; }).originalHeight; - - //adjust the smallestHeight if it is larger than the static max row height - if (smallestHeight > maxRowHeight) { - smallestHeight = maxRowHeight; - } - return smallestHeight; - }, - - /** Creates the image grid with calculated widths/heights for images to fill the grid nicely */ - buildGrid: function(images, maxRowWidth, maxRowHeight, startingIndex, minDisplayHeight, idealImgPerRow, margin,imagesOnly) { - - var rows = []; - var imagesProcessed = 0; - - //first fill in all of the original image sizes and URLs - for (var i = startingIndex; i < images.length; i++) { - var item = images[i]; - - this.setImageData(item); - this.setOriginalSize(item, maxRowHeight); - - if(imagesOnly && !item.isFolder && !item.thumbnail){ - images.splice(i, 1); - i--; - } - } - - while ((imagesProcessed + startingIndex) < images.length) { - //get the maxHeight for the current un-processed images - var currImgs = images.slice(imagesProcessed); - - //build the row - var remaining = images.length - imagesProcessed; - var row = this.buildRow(currImgs, maxRowHeight, minDisplayHeight, maxRowWidth, idealImgPerRow, margin, remaining); - if (row.images.length > 0) { - rows.push(row); - imagesProcessed += row.images.length; - } - else { - - if (currImgs.length > 0) { - throw "Could not fill grid with all images, images remaining: " + currImgs.length; - } - - //if there was nothing processed, exit - break; - } - } - - return rows; - } - }; -} -angular.module("umbraco.services").factory("umbPhotoFolderHelper", umbPhotoFolderHelper); - -/** - * @ngdoc function - * @name umbraco.services.umbModelMapper - * @function - * - * @description - * Utility class to map/convert models - */ -function umbModelMapper() { - - return { - - - /** - * @ngdoc function - * @name umbraco.services.umbModelMapper#convertToEntityBasic - * @methodOf umbraco.services.umbModelMapper - * @function - * - * @description - * Converts the source model to a basic entity model, it will throw an exception if there isn't enough data to create the model. - * @param {Object} source The source model - * @param {Number} source.id The node id of the model - * @param {String} source.name The node name - * @param {String} source.icon The models icon as a css class (.icon-doc) - * @param {Number} source.parentId The parentID, if no parent, set to -1 - * @param {path} source.path comma-separated string of ancestor IDs (-1,1234,1782,1234) - */ - - /** This converts the source model to a basic entity model, it will throw an exception if there isn't enough data to create the model */ - convertToEntityBasic: function (source) { - var required = ["id", "name", "icon", "parentId", "path"]; - _.each(required, function (k) { - if (!_.has(source, k)) { - throw "The source object does not contain the property " + k; - } - }); - var optional = ["metaData", "key", "alias"]; - //now get the basic object - var result = _.pick(source, required.concat(optional)); - return result; - } - - }; -} -angular.module('umbraco.services').factory('umbModelMapper', umbModelMapper); - -/** - * @ngdoc function - * @name umbraco.services.umbSessionStorage - * @function - * - * @description - * Used to get/set things in browser sessionStorage but always prefixes keys with "umb_" and converts json vals so there is no overlap - * with any sessionStorage created by a developer. - */ -function umbSessionStorage($window) { - - //gets the sessionStorage object if available, otherwise just uses a normal object - // - required for unit tests. - var storage = $window['sessionStorage'] ? $window['sessionStorage'] : {}; - - return { - - get: function (key) { - return angular.fromJson(storage["umb_" + key]); - }, - - set : function(key, value) { - storage["umb_" + key] = angular.toJson(value); - } - - }; -} -angular.module('umbraco.services').factory('umbSessionStorage', umbSessionStorage); - -/** - * @ngdoc function - * @name umbraco.services.updateChecker - * @function - * - * @description - * used to check for updates and display a notifcation - */ -function updateChecker($http, umbRequestHelper) { - return { - - /** - * @ngdoc function - * @name umbraco.services.updateChecker#check - * @methodOf umbraco.services.updateChecker - * @function - * - * @description - * Called to load in the legacy tree js which is required on startup if a user is logged in or - * after login, but cannot be called until they are authenticated which is why it needs to be lazy loaded. - */ - check: function() { - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "updateCheckApiBaseUrl", - "GetCheck")), - 'Failed to retrieve update status'); - } - }; -} -angular.module('umbraco.services').factory('updateChecker', updateChecker); - -/** -* @ngdoc service -* @name umbraco.services.umbPropertyEditorHelper -* @description A helper object used for property editors -**/ -function umbPropEditorHelper() { - return { - /** - * @ngdoc function - * @name getImagePropertyValue - * @methodOf umbraco.services.umbPropertyEditorHelper - * @function - * - * @description - * Returns the correct view path for a property editor, it will detect if it is a full virtual path but if not then default to the internal umbraco one - * - * @param {string} input the view path currently stored for the property editor - */ - getViewPath: function(input, isPreValue) { - var path = String(input); - - if (path.startsWith('/')) { - - //This is an absolute path, so just leave it - return path; - } else { - - if (path.indexOf("/") >= 0) { - //This is a relative path, so just leave it - return path; - } else { - if (!isPreValue) { - //i.e. views/propertyeditors/fileupload/fileupload.html - return "views/propertyeditors/" + path + "/" + path + ".html"; - } else { - //i.e. views/prevalueeditors/requiredfield.html - return "views/prevalueeditors/" + path + ".html"; - } - } - - } - } - }; -} -angular.module('umbraco.services').factory('umbPropEditorHelper', umbPropEditorHelper); - - -/** -* @ngdoc service -* @name umbraco.services.umbDataFormatter -* @description A helper object used to format/transform JSON Umbraco data, mostly used for persisting data to the server -**/ -function umbDataFormatter() { - return { - - formatContentTypePostData: function (displayModel, action) { - - //create the save model from the display model - var saveModel = _.pick(displayModel, - 'compositeContentTypes', 'isContainer', 'allowAsRoot', 'allowedTemplates', 'allowedContentTypes', - 'alias', 'description', 'thumbnail', 'name', 'id', 'icon', 'trashed', - 'key', 'parentId', 'alias', 'path'); - - //TODO: Map these - saveModel.allowedTemplates = _.map(displayModel.allowedTemplates, function (t) { return t.alias; }); - saveModel.defaultTemplate = displayModel.defaultTemplate ? displayModel.defaultTemplate.alias : null; - var realGroups = _.reject(displayModel.groups, function(g) { - //do not include these tabs - return g.tabState === "init"; - }); - saveModel.groups = _.map(realGroups, function (g) { - - var saveGroup = _.pick(g, 'inherited', 'id', 'sortOrder', 'name'); - - var realProperties = _.reject(g.properties, function (p) { - //do not include these properties - return p.propertyState === "init" || p.inherited === true; - }); - - var saveProperties = _.map(realProperties, function (p) { - var saveProperty = _.pick(p, 'id', 'alias', 'description', 'validation', 'label', 'sortOrder', 'dataTypeId', 'groupId', 'memberCanEdit', 'showOnMemberProfile'); - return saveProperty; - }); - - saveGroup.properties = saveProperties; - - //if this is an inherited group and there are not non-inherited properties on it, then don't send up the data - if (saveGroup.inherited === true && saveProperties.length === 0) { - return null; - } - - return saveGroup; - }); - - //we don't want any null groups - saveModel.groups = _.reject(saveModel.groups, function(g) { - return !g; - }); - - return saveModel; - }, - - /** formats the display model used to display the data type to the model used to save the data type */ - formatDataTypePostData: function(displayModel, preValues, action) { - var saveModel = { - parentId: displayModel.parentId, - id: displayModel.id, - name: displayModel.name, - selectedEditor: displayModel.selectedEditor, - //set the action on the save model - action: action, - preValues: [] - }; - for (var i = 0; i < preValues.length; i++) { - - saveModel.preValues.push({ - key: preValues[i].alias, - value: preValues[i].value - }); - } - return saveModel; - }, - - /** formats the display model used to display the member to the model used to save the member */ - formatMemberPostData: function(displayModel, action) { - //this is basically the same as for media but we need to explicitly add the username,email, password to the save model - - var saveModel = this.formatMediaPostData(displayModel, action); - - saveModel.key = displayModel.key; - - var genericTab = _.find(displayModel.tabs, function (item) { - return item.id === 0; - }); - - //map the member login, email, password and groups - var propLogin = _.find(genericTab.properties, function (item) { - return item.alias === "_umb_login"; - }); - var propEmail = _.find(genericTab.properties, function (item) { - return item.alias === "_umb_email"; - }); - var propPass = _.find(genericTab.properties, function (item) { - return item.alias === "_umb_password"; - }); - var propGroups = _.find(genericTab.properties, function (item) { - return item.alias === "_umb_membergroup"; - }); - saveModel.email = propEmail.value; - saveModel.username = propLogin.value; - saveModel.password = propPass.value; - - var selectedGroups = []; - for (var n in propGroups.value) { - if (propGroups.value[n] === true) { - selectedGroups.push(n); - } - } - saveModel.memberGroups = selectedGroups; - - //turn the dictionary into an array of pairs - var memberProviderPropAliases = _.pairs(displayModel.fieldConfig); - _.each(displayModel.tabs, function (tab) { - _.each(tab.properties, function (prop) { - var foundAlias = _.find(memberProviderPropAliases, function(item) { - return prop.alias === item[1]; - }); - if (foundAlias) { - //we know the current property matches an alias, now we need to determine which membership provider property it was for - // by looking at the key - switch (foundAlias[0]) { - case "umbracoMemberLockedOut": - saveModel.isLockedOut = prop.value.toString() === "1" ? true : false; - break; - case "umbracoMemberApproved": - saveModel.isApproved = prop.value.toString() === "1" ? true : false; - break; - case "umbracoMemberComments": - saveModel.comments = prop.value; - break; - } - } - }); - }); - - - - return saveModel; - }, - - /** formats the display model used to display the media to the model used to save the media */ - formatMediaPostData: function(displayModel, action) { - //NOTE: the display model inherits from the save model so we can in theory just post up the display model but - // we don't want to post all of the data as it is unecessary. - var saveModel = { - id: displayModel.id, - properties: [], - name: displayModel.name, - contentTypeAlias: displayModel.contentTypeAlias, - parentId: displayModel.parentId, - //set the action on the save model - action: action - }; - - _.each(displayModel.tabs, function (tab) { - - _.each(tab.properties, function (prop) { - - //don't include the custom generic tab properties - if (!prop.alias.startsWith("_umb_")) { - saveModel.properties.push({ - id: prop.id, - alias: prop.alias, - value: prop.value - }); - } - - }); - }); - - return saveModel; - }, - - /** formats the display model used to display the content to the model used to save the content */ - formatContentPostData: function (displayModel, action) { - - //this is basically the same as for media but we need to explicitly add some extra properties - var saveModel = this.formatMediaPostData(displayModel, action); - - var genericTab = _.find(displayModel.tabs, function (item) { - return item.id === 0; - }); - - var propExpireDate = _.find(genericTab.properties, function(item) { - return item.alias === "_umb_expiredate"; - }); - var propReleaseDate = _.find(genericTab.properties, function (item) { - return item.alias === "_umb_releasedate"; - }); - var propTemplate = _.find(genericTab.properties, function (item) { - return item.alias === "_umb_template"; - }); - saveModel.expireDate = propExpireDate.value; - saveModel.releaseDate = propReleaseDate.value; - saveModel.templateAlias = propTemplate.value; - - return saveModel; - } - }; -} -angular.module('umbraco.services').factory('umbDataFormatter', umbDataFormatter); - - - -/** + var service = { + /** + * @ngdoc method + * @name umbraco.services.notificationsService#add + * @methodOf umbraco.services.notificationsService + * + * @description + * Lower level api for adding notifcations, support more advanced options + * @param {Object} item The notification item + * @param {String} item.headline Short headline + * @param {String} item.message longer text for the notication, trimmed after 200 characters, which can then be exanded + * @param {String} item.type Notification type, can be: "success","warning","error" or "info" + * @param {String} item.url url to open when notification is clicked + * @param {String} item.view path to custom view to load into the notification box + * @param {Array} item.actions Collection of button actions to append (label, func, cssClass) + * @param {Boolean} item.sticky if set to true, the notification will not auto-close + * @returns {Object} args notification object + */ + add: function (item) { + angularHelper.safeApply($rootScope, function () { + if (item.view) { + item.view = setViewPath(item.view); + item.sticky = true; + item.type = 'form'; + item.headline = null; + } + //add a colon after the headline if there is a message as well + if (item.message) { + item.headline += ': '; + if (item.message.length > 200) { + item.sticky = true; + } + } + //we need to ID the item, going by index isn't good enough because people can remove at different indexes + // whenever they want. Plus once we remove one, then the next index will be different. The only way to + // effectively remove an item is by an Id. + item.id = String.CreateGuid(); + nArray.push(item); + if (!item.sticky) { + $timeout(function () { + var found = _.find(nArray, function (i) { + return i.id === item.id; + }); + if (found) { + var index = nArray.indexOf(found); + nArray.splice(index, 1); + } + }, 7000); + } + return item; + }); + }, + hasView: function (view) { + if (!view) { + return _.find(nArray, function (notification) { + return notification.view; + }); + } else { + view = setViewPath(view).toLowerCase(); + return _.find(nArray, function (notification) { + return notification.view.toLowerCase() === view; + }); + } + }, + addView: function (view, args) { + var item = { + args: args, + view: view + }; + service.add(item); + }, + /** + * @ngdoc method + * @name umbraco.services.notificationsService#showNotification + * @methodOf umbraco.services.notificationsService + * + * @description + * Shows a notification based on the object passed in, normally used to render notifications sent back from the server + * + * @returns {Object} args notification object + */ + showNotification: function (args) { + if (!args) { + throw 'args cannot be null'; + } + if (args.type === undefined || args.type === null) { + throw 'args.type cannot be null'; + } + if (!args.header) { + throw 'args.header cannot be null'; + } + switch (args.type) { + case 0: + //save + this.success(args.header, args.message); + break; + case 1: + //info + this.success(args.header, args.message); + break; + case 2: + //error + this.error(args.header, args.message); + break; + case 3: + //success + this.success(args.header, args.message); + break; + case 4: + //warning + this.warning(args.header, args.message); + break; + } + }, + /** + * @ngdoc method + * @name umbraco.services.notificationsService#success + * @methodOf umbraco.services.notificationsService + * + * @description + * Adds a green success notication to the notications collection + * This should be used when an operations *completes* without errors + * + * @param {String} headline Headline of the notification + * @param {String} message longer text for the notication, trimmed after 200 characters, which can then be exanded + * @returns {Object} notification object + */ + success: function (headline, message) { + return service.add({ + headline: headline, + message: message, + type: 'success', + time: new Date() + }); + }, + /** + * @ngdoc method + * @name umbraco.services.notificationsService#error + * @methodOf umbraco.services.notificationsService + * + * @description + * Adds a red error notication to the notications collection + * This should be used when an operations *fails* and could not complete + * + * @param {String} headline Headline of the notification + * @param {String} message longer text for the notication, trimmed after 200 characters, which can then be exanded + * @returns {Object} notification object + */ + error: function (headline, message) { + return service.add({ + headline: headline, + message: message, + type: 'error', + time: new Date() + }); + }, + /** + * @ngdoc method + * @name umbraco.services.notificationsService#warning + * @methodOf umbraco.services.notificationsService + * + * @description + * Adds a yellow warning notication to the notications collection + * This should be used when an operations *completes* but something was not as expected + * + * + * @param {String} headline Headline of the notification + * @param {String} message longer text for the notication, trimmed after 200 characters, which can then be exanded + * @returns {Object} notification object + */ + warning: function (headline, message) { + return service.add({ + headline: headline, + message: message, + type: 'warning', + time: new Date() + }); + }, + /** + * @ngdoc method + * @name umbraco.services.notificationsService#warning + * @methodOf umbraco.services.notificationsService + * + * @description + * Adds a yellow warning notication to the notications collection + * This should be used when an operations *completes* but something was not as expected + * + * + * @param {String} headline Headline of the notification + * @param {String} message longer text for the notication, trimmed after 200 characters, which can then be exanded + * @returns {Object} notification object + */ + info: function (headline, message) { + return service.add({ + headline: headline, + message: message, + type: 'info', + time: new Date() + }); + }, + /** + * @ngdoc method + * @name umbraco.services.notificationsService#remove + * @methodOf umbraco.services.notificationsService + * + * @description + * Removes a notification from the notifcations collection at a given index + * + * @param {Int} index index where the notication should be removed from + */ + remove: function (index) { + if (angular.isObject(index)) { + var i = nArray.indexOf(index); + angularHelper.safeApply($rootScope, function () { + nArray.splice(i, 1); + }); + } else { + angularHelper.safeApply($rootScope, function () { + nArray.splice(index, 1); + }); + } + }, + /** + * @ngdoc method + * @name umbraco.services.notificationsService#removeAll + * @methodOf umbraco.services.notificationsService + * + * @description + * Removes all notifications from the notifcations collection + */ + removeAll: function () { + angularHelper.safeApply($rootScope, function () { + nArray = []; + }); + }, + /** + * @ngdoc property + * @name umbraco.services.notificationsService#current + * @propertyOf umbraco.services.notificationsService + * + * @description + * Returns an array of current notifications to display + * + * @returns {string} returns an array + */ + current: nArray, + /** + * @ngdoc method + * @name umbraco.services.notificationsService#getCurrent + * @methodOf umbraco.services.notificationsService + * + * @description + * Method to return all notifications from the notifcations collection + */ + getCurrent: function () { + return nArray; + } + }; + return service; + }); + (function () { + 'use strict'; + function overlayHelper() { + var numberOfOverlays = 0; + function registerOverlay() { + numberOfOverlays++; + return numberOfOverlays; + } + function unregisterOverlay() { + numberOfOverlays--; + return numberOfOverlays; + } + function getNumberOfOverlays() { + return numberOfOverlays; + } + var service = { + numberOfOverlays: numberOfOverlays, + registerOverlay: registerOverlay, + unregisterOverlay: unregisterOverlay, + getNumberOfOverlays: getNumberOfOverlays + }; + return service; + } + angular.module('umbraco.services').factory('overlayHelper', overlayHelper); + }()); + (function () { + 'use strict'; + function platformService() { + function isMac() { + return navigator.platform.toUpperCase().indexOf('MAC') >= 0; + } + //////////// + var service = { isMac: isMac }; + return service; + } + angular.module('umbraco.services').factory('platformService', platformService); + }()); + /** + * @ngdoc service + * @name umbraco.services.searchService + * + * + * @description + * Service for handling the main application search, can currently search content, media and members + * + * ##usage + * To use, simply inject the searchService into any controller that needs it, and make + * sure the umbraco.services module is accesible - which it should be by default. + * + *
    + *      searchService.searchMembers({term: 'bob'}).then(function(results){
    + *          angular.forEach(results, function(result){
    + *                  //returns:
    + *                  {name: "name", id: 1234, menuUrl: "url", editorPath: "url", metaData: {}, subtitle: "/path/etc" }
    + *           })          
    + *           var result = 
    + *       }) 
    + * 
    + */ + angular.module('umbraco.services').factory('searchService', function ($q, $log, entityResource, contentResource, umbRequestHelper, $injector, searchResultFormatter) { + return { + /** + * @ngdoc method + * @name umbraco.services.searchService#searchMembers + * @methodOf umbraco.services.searchService + * + * @description + * Searches the default member search index + * @param {Object} args argument object + * @param {String} args.term seach term + * @returns {Promise} returns promise containing all matching members + */ + searchMembers: function (args) { + if (!args.term) { + throw 'args.term is required'; + } + return entityResource.search(args.term, 'Member', args.searchFrom).then(function (data) { + _.each(data, function (item) { + searchResultFormatter.configureMemberResult(item); + }); + return data; + }); + }, + /** + * @ngdoc method + * @name umbraco.services.searchService#searchContent + * @methodOf umbraco.services.searchService + * + * @description + * Searches the default internal content search index + * @param {Object} args argument object + * @param {String} args.term seach term + * @returns {Promise} returns promise containing all matching content items + */ + searchContent: function (args) { + if (!args.term) { + throw 'args.term is required'; + } + return entityResource.search(args.term, 'Document', args.searchFrom, args.canceler).then(function (data) { + _.each(data, function (item) { + searchResultFormatter.configureContentResult(item); + }); + return data; + }); + }, + /** + * @ngdoc method + * @name umbraco.services.searchService#searchMedia + * @methodOf umbraco.services.searchService + * + * @description + * Searches the default media search index + * @param {Object} args argument object + * @param {String} args.term seach term + * @returns {Promise} returns promise containing all matching media items + */ + searchMedia: function (args) { + if (!args.term) { + throw 'args.term is required'; + } + return entityResource.search(args.term, 'Media', args.searchFrom).then(function (data) { + _.each(data, function (item) { + searchResultFormatter.configureMediaResult(item); + }); + return data; + }); + }, + /** + * @ngdoc method + * @name umbraco.services.searchService#searchAll + * @methodOf umbraco.services.searchService + * + * @description + * Searches all available indexes and returns all results in one collection + * @param {Object} args argument object + * @param {String} args.term seach term + * @returns {Promise} returns promise containing all matching items + */ + searchAll: function (args) { + if (!args.term) { + throw 'args.term is required'; + } + return entityResource.searchAll(args.term, args.canceler).then(function (data) { + _.each(data, function (resultByType) { + //we need to format the search result data to include things like the subtitle, urls, etc... + // this is done with registered angular services as part of the SearchableTreeAttribute, if that + // is not found, than we format with the default formatter + var formatterMethod = searchResultFormatter.configureDefaultResult; + //check if a custom formatter is specified... + if (resultByType.jsSvc) { + var searchFormatterService = $injector.get(resultByType.jsSvc); + if (searchFormatterService) { + if (!resultByType.jsMethod) { + resultByType.jsMethod = 'format'; + } + formatterMethod = searchFormatterService[resultByType.jsMethod]; + if (!formatterMethod) { + throw 'The method ' + resultByType.jsMethod + ' on the angular service ' + resultByType.jsSvc + ' could not be found'; + } + } + } + //now apply the formatter for each result + _.each(resultByType.results, function (item) { + formatterMethod.apply(this, [ + item, + resultByType.treeAlias, + resultByType.appAlias + ]); + }); + }); + return data; + }); + }, + //TODO: This doesn't do anything! + setCurrent: function (sectionAlias) { + var currentSection = sectionAlias; + } + }; + }); + function searchResultFormatter(umbRequestHelper) { + function configureDefaultResult(content, treeAlias, appAlias) { + content.editorPath = appAlias + '/' + treeAlias + '/edit/' + content.id; + angular.extend(content.metaData, { treeAlias: treeAlias }); + } + function configureContentResult(content, treeAlias, appAlias) { + content.menuUrl = umbRequestHelper.getApiUrl('contentTreeBaseUrl', 'GetMenu', [ + { id: content.id }, + { application: appAlias } + ]); + content.editorPath = appAlias + '/' + treeAlias + '/edit/' + content.id; + angular.extend(content.metaData, { treeAlias: treeAlias }); + content.subTitle = content.metaData.Url; + } + function configureMemberResult(member, treeAlias, appAlias) { + member.menuUrl = umbRequestHelper.getApiUrl('memberTreeBaseUrl', 'GetMenu', [ + { id: member.id }, + { application: appAlias } + ]); + member.editorPath = appAlias + '/' + treeAlias + '/edit/' + (member.key ? member.key : member.id); + angular.extend(member.metaData, { treeAlias: treeAlias }); + member.subTitle = member.metaData.Email; + } + function configureMediaResult(media, treeAlias, appAlias) { + media.menuUrl = umbRequestHelper.getApiUrl('mediaTreeBaseUrl', 'GetMenu', [ + { id: media.id }, + { application: appAlias } + ]); + media.editorPath = appAlias + '/' + treeAlias + '/edit/' + media.id; + angular.extend(media.metaData, { treeAlias: treeAlias }); + } + return { + configureContentResult: configureContentResult, + configureMemberResult: configureMemberResult, + configureMediaResult: configureMediaResult, + configureDefaultResult: configureDefaultResult + }; + } + angular.module('umbraco.services').factory('searchResultFormatter', searchResultFormatter); + /** + * @ngdoc service + * @name umbraco.services.sectionService + * + * + * @description + * A service to return the sections (applications) to be listed in the navigation which are contextual to the current user + */ + (function () { + 'use strict'; + function sectionService(userService, $q, sectionResource) { + function getSectionsForUser() { + var deferred = $q.defer(); + userService.getCurrentUser().then(function (u) { + //if they've already loaded, return them + if (u.sections) { + deferred.resolve(u.sections); + } else { + sectionResource.getSections().then(function (sections) { + //set these to the user (cached), then the user changes, these will be wiped + u.sections = sections; + deferred.resolve(u.sections); + }); + } + }); + return deferred.promise; + } + var service = { getSectionsForUser: getSectionsForUser }; + return service; + } + angular.module('umbraco.services').factory('sectionService', sectionService); + }()); + /** + * @ngdoc service + * @name umbraco.services.serverValidationManager + * @function + * + * @description + * Used to handle server side validation and wires up the UI with the messages. There are 2 types of validation messages, one + * is for user defined properties (called Properties) and the other is for field properties which are attached to the native + * model objects (not user defined). The methods below are named according to these rules: Properties vs Fields. + */ + function serverValidationManager($timeout) { + var callbacks = []; + /** calls the callback specified with the errors specified, used internally */ + function executeCallback(self, errorsForCallback, callback) { + callback.apply(self, [ + false, + //pass in a value indicating it is invalid + errorsForCallback, + //pass in the errors for this item + self.items + ]); //pass in all errors in total + } + function getFieldErrors(self, fieldName) { + if (!angular.isString(fieldName)) { + throw 'fieldName must be a string'; + } + //find errors for this field name + return _.filter(self.items, function (item) { + return item.propertyAlias === null && item.fieldName === fieldName; + }); + } + function getPropertyErrors(self, propertyAlias, fieldName) { + if (!angular.isString(propertyAlias)) { + throw 'propertyAlias must be a string'; + } + if (fieldName && !angular.isString(fieldName)) { + throw 'fieldName must be a string'; + } + //find all errors for this property + return _.filter(self.items, function (item) { + return item.propertyAlias === propertyAlias && (item.fieldName === fieldName || (fieldName === undefined || fieldName === '')); + }); + } + return { + /** + * @ngdoc function + * @name umbraco.services.serverValidationManager#subscribe + * @methodOf umbraco.services.serverValidationManager + * @function + * + * @description + * This method needs to be called once all field and property errors are wired up. + * + * In some scenarios where the error collection needs to be persisted over a route change + * (i.e. when a content item (or any item) is created and the route redirects to the editor) + * the controller should call this method once the data is bound to the scope + * so that any persisted validation errors are re-bound to their controls. Once they are re-binded this then clears the validation + * colleciton so that if another route change occurs, the previously persisted validation errors are not re-bound to the new item. + */ + executeAndClearAllSubscriptions: function () { + var self = this; + $timeout(function () { + for (var cb in callbacks) { + if (callbacks[cb].propertyAlias === null) { + //its a field error callback + var fieldErrors = getFieldErrors(self, callbacks[cb].fieldName); + if (fieldErrors.length > 0) { + executeCallback(self, fieldErrors, callbacks[cb].callback); + } + } else { + //its a property error + var propErrors = getPropertyErrors(self, callbacks[cb].propertyAlias, callbacks[cb].fieldName); + if (propErrors.length > 0) { + executeCallback(self, propErrors, callbacks[cb].callback); + } + } + } + //now that they are all executed, we're gonna clear all of the errors we have + self.clear(); + }); + }, + /** + * @ngdoc function + * @name umbraco.services.serverValidationManager#subscribe + * @methodOf umbraco.services.serverValidationManager + * @function + * + * @description + * Adds a callback method that is executed whenever validation changes for the field name + property specified. + * This is generally used for server side validation in order to match up a server side validation error with + * a particular field, otherwise we can only pinpoint that there is an error for a content property, not the + * property's specific field. This is used with the val-server directive in which the directive specifies the + * field alias to listen for. + * If propertyAlias is null, then this subscription is for a field property (not a user defined property). + */ + subscribe: function (propertyAlias, fieldName, callback) { + if (!callback) { + return; + } + if (propertyAlias === null) { + //don't add it if it already exists + var exists1 = _.find(callbacks, function (item) { + return item.propertyAlias === null && item.fieldName === fieldName; + }); + if (!exists1) { + callbacks.push({ + propertyAlias: null, + fieldName: fieldName, + callback: callback + }); + } + } else if (propertyAlias !== undefined) { + //don't add it if it already exists + var exists2 = _.find(callbacks, function (item) { + return item.propertyAlias === propertyAlias && item.fieldName === fieldName; + }); + if (!exists2) { + callbacks.push({ + propertyAlias: propertyAlias, + fieldName: fieldName, + callback: callback + }); + } + } + }, + unsubscribe: function (propertyAlias, fieldName) { + if (propertyAlias === null) { + //remove all callbacks for the content field + callbacks = _.reject(callbacks, function (item) { + return item.propertyAlias === null && item.fieldName === fieldName; + }); + } else if (propertyAlias !== undefined) { + //remove all callbacks for the content property + callbacks = _.reject(callbacks, function (item) { + return item.propertyAlias === propertyAlias && (item.fieldName === fieldName || (item.fieldName === undefined || item.fieldName === '') && (fieldName === undefined || fieldName === '')); + }); + } + }, + /** + * @ngdoc function + * @name getPropertyCallbacks + * @methodOf umbraco.services.serverValidationManager + * @function + * + * @description + * Gets all callbacks that has been registered using the subscribe method for the propertyAlias + fieldName combo. + * This will always return any callbacks registered for just the property (i.e. field name is empty) and for ones with an + * explicit field name set. + */ + getPropertyCallbacks: function (propertyAlias, fieldName) { + var found = _.filter(callbacks, function (item) { + //returns any callback that have been registered directly against the field and for only the property + return item.propertyAlias === propertyAlias && (item.fieldName === fieldName || (item.fieldName === undefined || item.fieldName === '')); + }); + return found; + }, + /** + * @ngdoc function + * @name getFieldCallbacks + * @methodOf umbraco.services.serverValidationManager + * @function + * + * @description + * Gets all callbacks that has been registered using the subscribe method for the field. + */ + getFieldCallbacks: function (fieldName) { + var found = _.filter(callbacks, function (item) { + //returns any callback that have been registered directly against the field + return item.propertyAlias === null && item.fieldName === fieldName; + }); + return found; + }, + /** + * @ngdoc function + * @name addFieldError + * @methodOf umbraco.services.serverValidationManager + * @function + * + * @description + * Adds an error message for a native content item field (not a user defined property, for Example, 'Name') + */ + addFieldError: function (fieldName, errorMsg) { + if (!fieldName) { + return; + } + //only add the item if it doesn't exist + if (!this.hasFieldError(fieldName)) { + this.items.push({ + propertyAlias: null, + fieldName: fieldName, + errorMsg: errorMsg + }); + } + //find all errors for this item + var errorsForCallback = getFieldErrors(this, fieldName); + //we should now call all of the call backs registered for this error + var cbs = this.getFieldCallbacks(fieldName); + //call each callback for this error + for (var cb in cbs) { + executeCallback(this, errorsForCallback, cbs[cb].callback); + } + }, + /** + * @ngdoc function + * @name addPropertyError + * @methodOf umbraco.services.serverValidationManager + * @function + * + * @description + * Adds an error message for the content property + */ + addPropertyError: function (propertyAlias, fieldName, errorMsg) { + if (!propertyAlias) { + return; + } + //only add the item if it doesn't exist + if (!this.hasPropertyError(propertyAlias, fieldName)) { + this.items.push({ + propertyAlias: propertyAlias, + fieldName: fieldName, + errorMsg: errorMsg + }); + } + //find all errors for this item + var errorsForCallback = getPropertyErrors(this, propertyAlias, fieldName); + //we should now call all of the call backs registered for this error + var cbs = this.getPropertyCallbacks(propertyAlias, fieldName); + //call each callback for this error + for (var cb in cbs) { + executeCallback(this, errorsForCallback, cbs[cb].callback); + } + }, + /** + * @ngdoc function + * @name removePropertyError + * @methodOf umbraco.services.serverValidationManager + * @function + * + * @description + * Removes an error message for the content property + */ + removePropertyError: function (propertyAlias, fieldName) { + if (!propertyAlias) { + return; + } + //remove the item + this.items = _.reject(this.items, function (item) { + return item.propertyAlias === propertyAlias && (item.fieldName === fieldName || (fieldName === undefined || fieldName === '')); + }); + }, + /** + * @ngdoc function + * @name reset + * @methodOf umbraco.services.serverValidationManager + * @function + * + * @description + * Clears all errors and notifies all callbacks that all server errros are now valid - used when submitting a form + */ + reset: function () { + this.clear(); + for (var cb in callbacks) { + callbacks[cb].callback.apply(this, [ + true, + //pass in a value indicating it is VALID + [], + //pass in empty collection + [] + ]); //pass in empty collection + } + }, + /** + * @ngdoc function + * @name clear + * @methodOf umbraco.services.serverValidationManager + * @function + * + * @description + * Clears all errors + */ + clear: function () { + this.items = []; + }, + /** + * @ngdoc function + * @name getPropertyError + * @methodOf umbraco.services.serverValidationManager + * @function + * + * @description + * Gets the error message for the content property + */ + getPropertyError: function (propertyAlias, fieldName) { + var err = _.find(this.items, function (item) { + //return true if the property alias matches and if an empty field name is specified or the field name matches + return item.propertyAlias === propertyAlias && (item.fieldName === fieldName || (fieldName === undefined || fieldName === '')); + }); + return err; + }, + /** + * @ngdoc function + * @name getFieldError + * @methodOf umbraco.services.serverValidationManager + * @function + * + * @description + * Gets the error message for a content field + */ + getFieldError: function (fieldName) { + var err = _.find(this.items, function (item) { + //return true if the property alias matches and if an empty field name is specified or the field name matches + return item.propertyAlias === null && item.fieldName === fieldName; + }); + return err; + }, + /** + * @ngdoc function + * @name hasPropertyError + * @methodOf umbraco.services.serverValidationManager + * @function + * + * @description + * Checks if the content property + field name combo has an error + */ + hasPropertyError: function (propertyAlias, fieldName) { + var err = _.find(this.items, function (item) { + //return true if the property alias matches and if an empty field name is specified or the field name matches + return item.propertyAlias === propertyAlias && (item.fieldName === fieldName || (fieldName === undefined || fieldName === '')); + }); + return err ? true : false; + }, + /** + * @ngdoc function + * @name hasFieldError + * @methodOf umbraco.services.serverValidationManager + * @function + * + * @description + * Checks if a content field has an error + */ + hasFieldError: function (fieldName) { + var err = _.find(this.items, function (item) { + //return true if the property alias matches and if an empty field name is specified or the field name matches + return item.propertyAlias === null && item.fieldName === fieldName; + }); + return err ? true : false; + }, + /** The array of error messages */ + items: [] + }; + } + angular.module('umbraco.services').factory('serverValidationManager', serverValidationManager); + (function () { + 'use strict'; + function templateHelperService(localizationService) { + //crappy hack due to dictionary items not in umbracoNode table + function getInsertDictionarySnippet(nodeName) { + return '@Umbraco.GetDictionaryValue("' + nodeName + '")'; + } + function getInsertPartialSnippet(parentId, nodeName) { + var partialViewName = nodeName.replace('.cshtml', ''); + if (parentId) { + partialViewName = parentId + '/' + partialViewName; + } + return '@Html.Partial("' + partialViewName + '")'; + } + function getQuerySnippet(queryExpression) { + var code = '\n@{\n' + '\tvar selection = ' + queryExpression + ';\n}\n'; + code += '
      \n' + '\t@foreach(var item in selection){\n' + '\t\t
    • \n' + '\t\t\t@item.Name\n' + '\t\t
    • \n' + '\t}\n' + '
    \n\n'; + return code; + } + function getRenderBodySnippet() { + return '@RenderBody()'; + } + function getRenderSectionSnippet(sectionName, mandatory) { + return '@RenderSection("' + sectionName + '", ' + mandatory + ')'; + } + function getAddSectionSnippet(sectionName) { + return '@section ' + sectionName + '\r\n{\r\n\r\n\t{0}\r\n\r\n}\r\n'; + } + function getGeneralShortcuts() { + return { + 'name': localizationService.localize('shortcuts_generalHeader'), + 'shortcuts': [ + { + 'description': localizationService.localize('buttons_undo'), + 'keys': [ + { 'key': 'ctrl' }, + { 'key': 'z' } + ] + }, + { + 'description': localizationService.localize('buttons_redo'), + 'keys': [ + { 'key': 'ctrl' }, + { 'key': 'y' } + ] + }, + { + 'description': localizationService.localize('buttons_save'), + 'keys': [ + { 'key': 'ctrl' }, + { 'key': 's' } + ] + } + ] + }; + } + function getEditorShortcuts() { + return { + 'name': localizationService.localize('shortcuts_editorHeader'), + 'shortcuts': [ + { + 'description': localizationService.localize('shortcuts_commentLine'), + 'keys': [ + { 'key': 'ctrl' }, + { 'key': '/' } + ] + }, + { + 'description': localizationService.localize('shortcuts_removeLine'), + 'keys': [ + { 'key': 'ctrl' }, + { 'key': 'd' } + ] + }, + { + 'description': localizationService.localize('shortcuts_copyLineUp'), + 'keys': { + 'win': [ + { 'key': 'alt' }, + { 'key': 'shift' }, + { 'key': 'up' } + ], + 'mac': [ + { 'key': 'cmd' }, + { 'key': 'alt' }, + { 'key': 'up' } + ] + } + }, + { + 'description': localizationService.localize('shortcuts_copyLineDown'), + 'keys': { + 'win': [ + { 'key': 'alt' }, + { 'key': 'shift' }, + { 'key': 'down' } + ], + 'mac': [ + { 'key': 'cmd' }, + { 'key': 'alt' }, + { 'key': 'down' } + ] + } + }, + { + 'description': localizationService.localize('shortcuts_moveLineUp'), + 'keys': [ + { 'key': 'alt' }, + { 'key': 'up' } + ] + }, + { + 'description': localizationService.localize('shortcuts_moveLineDown'), + 'keys': [ + { 'key': 'alt' }, + { 'key': 'down' } + ] + } + ] + }; + } + function getTemplateEditorShortcuts() { + return { + 'name': 'Umbraco', + //No need to localise Umbraco is the same in all languages :) + 'shortcuts': [ + { + 'description': localizationService.format([ + 'template_insert', + 'template_insertPageField' + ], '%0% %1%'), + 'keys': [ + { 'key': 'alt' }, + { 'key': 'shift' }, + { 'key': 'v' } + ] + }, + { + 'description': localizationService.format([ + 'template_insert', + 'template_insertPartialView' + ], '%0% %1%'), + 'keys': [ + { 'key': 'alt' }, + { 'key': 'shift' }, + { 'key': 'p' } + ] + }, + { + 'description': localizationService.format([ + 'template_insert', + 'template_insertDictionaryItem' + ], '%0% %1%'), + 'keys': [ + { 'key': 'alt' }, + { 'key': 'shift' }, + { 'key': 'd' } + ] + }, + { + 'description': localizationService.format([ + 'template_insert', + 'template_insertMacro' + ], '%0% %1%'), + 'keys': [ + { 'key': 'alt' }, + { 'key': 'shift' }, + { 'key': 'm' } + ] + }, + { + 'description': localizationService.localize('template_queryBuilder'), + 'keys': [ + { 'key': 'alt' }, + { 'key': 'shift' }, + { 'key': 'q' } + ] + }, + { + 'description': localizationService.format([ + 'template_insert', + 'template_insertSections' + ], '%0% %1%'), + 'keys': [ + { 'key': 'alt' }, + { 'key': 'shift' }, + { 'key': 's' } + ] + }, + { + 'description': localizationService.localize('template_mastertemplate'), + 'keys': [ + { 'key': 'alt' }, + { 'key': 'shift' }, + { 'key': 't' } + ] + } + ] + }; + } + function getPartialViewEditorShortcuts() { + return { + 'name': 'Umbraco', + //No need to localise Umbraco is the same in all languages :) + 'shortcuts': [ + { + 'description': localizationService.format([ + 'template_insert', + 'template_insertPageField' + ], '%0% %1%'), + 'keys': [ + { 'key': 'alt' }, + { 'key': 'shift' }, + { 'key': 'v' } + ] + }, + { + 'description': localizationService.format([ + 'template_insert', + 'template_insertDictionaryItem' + ], '%0% %1%'), + 'keys': [ + { 'key': 'alt' }, + { 'key': 'shift' }, + { 'key': 'd' } + ] + }, + { + 'description': localizationService.format([ + 'template_insert', + 'template_insertMacro' + ], '%0% %1%'), + 'keys': [ + { 'key': 'alt' }, + { 'key': 'shift' }, + { 'key': 'm' } + ] + }, + { + 'description': localizationService.localize('template_queryBuilder'), + 'keys': [ + { 'key': 'alt' }, + { 'key': 'shift' }, + { 'key': 'q' } + ] + } + ] + }; + } + //////////// + var service = { + getInsertDictionarySnippet: getInsertDictionarySnippet, + getInsertPartialSnippet: getInsertPartialSnippet, + getQuerySnippet: getQuerySnippet, + getRenderBodySnippet: getRenderBodySnippet, + getRenderSectionSnippet: getRenderSectionSnippet, + getAddSectionSnippet: getAddSectionSnippet, + getGeneralShortcuts: getGeneralShortcuts, + getEditorShortcuts: getEditorShortcuts, + getTemplateEditorShortcuts: getTemplateEditorShortcuts, + getPartialViewEditorShortcuts: getPartialViewEditorShortcuts + }; + return service; + } + angular.module('umbraco.services').factory('templateHelper', templateHelperService); + }()); + /** + * @ngdoc service + * @name umbraco.services.tinyMceService + * + * + * @description + * A service containing all logic for all of the Umbraco TinyMCE plugins + */ + function tinyMceService($log, imageHelper, $http, $timeout, macroResource, macroService, $routeParams, umbRequestHelper, angularHelper, userService) { + return { + /** + * @ngdoc method + * @name umbraco.services.tinyMceService#configuration + * @methodOf umbraco.services.tinyMceService + * + * @description + * Returns a collection of plugins available to the tinyMCE editor + * + */ + configuration: function () { + return umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('rteApiBaseUrl', 'GetConfiguration'), { cache: true }), 'Failed to retrieve tinymce configuration'); + }, + /** + * @ngdoc method + * @name umbraco.services.tinyMceService#defaultPrevalues + * @methodOf umbraco.services.tinyMceService + * + * @description + * Returns a default configration to fallback on in case none is provided + * + */ + defaultPrevalues: function () { + var cfg = {}; + cfg.toolbar = [ + 'code', + 'bold', + 'italic', + 'styleselect', + 'alignleft', + 'aligncenter', + 'alignright', + 'bullist', + 'numlist', + 'outdent', + 'indent', + 'link', + 'image', + 'umbmediapicker', + 'umbembeddialog', + 'umbmacro' + ]; + cfg.stylesheets = []; + cfg.dimensions = { height: 500 }; + cfg.maxImageSize = 500; + return cfg; + }, + /** + * @ngdoc method + * @name umbraco.services.tinyMceService#createInsertEmbeddedMedia + * @methodOf umbraco.services.tinyMceService + * + * @description + * Creates the umbrco insert embedded media tinymce plugin + * + * @param {Object} editor the TinyMCE editor instance + * @param {Object} $scope the current controller scope + */ + createInsertEmbeddedMedia: function (editor, scope, callback) { + editor.addButton('umbembeddialog', { + icon: 'custom icon-tv', + tooltip: 'Embed', + onclick: function () { + if (callback) { + callback(); + } + } + }); + }, + insertEmbeddedMediaInEditor: function (editor, preview) { + editor.insertContent(preview); + }, + /** + * @ngdoc method + * @name umbraco.services.tinyMceService#createMediaPicker + * @methodOf umbraco.services.tinyMceService + * + * @description + * Creates the umbrco insert media tinymce plugin + * + * @param {Object} editor the TinyMCE editor instance + * @param {Object} $scope the current controller scope + */ + createMediaPicker: function (editor, scope, callback) { + editor.addButton('umbmediapicker', { + icon: 'custom icon-picture', + tooltip: 'Media Picker', + stateSelector: 'img', + onclick: function () { + var selectedElm = editor.selection.getNode(), currentTarget; + if (selectedElm.nodeName === 'IMG') { + var img = $(selectedElm); + var hasUdi = img.attr('data-udi') ? true : false; + currentTarget = { + altText: img.attr('alt'), + url: img.attr('src') + }; + if (hasUdi) { + currentTarget['udi'] = img.attr('data-udi'); + } else { + currentTarget['id'] = img.attr('rel'); + } + } + userService.getCurrentUser().then(function (userData) { + if (callback) { + callback(currentTarget, userData); + } + }); + } + }); + }, + insertMediaInEditor: function (editor, img) { + if (img) { + var hasUdi = img.udi ? true : false; + var data = { + alt: img.altText || '', + src: img.url ? img.url : 'nothing.jpg', + id: '__mcenew' + }; + if (hasUdi) { + data['data-udi'] = img.udi; + } else { + //Considering these fixed because UDI will now be used and thus + // we have no need for rel http://issues.umbraco.org/issue/U4-6228, http://issues.umbraco.org/issue/U4-6595 + data['rel'] = img.id; + data['data-id'] = img.id; + } + editor.insertContent(editor.dom.createHTML('img', data)); + $timeout(function () { + var imgElm = editor.dom.get('__mcenew'); + var size = editor.dom.getSize(imgElm); + if (editor.settings.maxImageSize && editor.settings.maxImageSize !== 0) { + var newSize = imageHelper.scaleToMaxSize(editor.settings.maxImageSize, size.w, size.h); + var s = 'width: ' + newSize.width + 'px; height:' + newSize.height + 'px;'; + editor.dom.setAttrib(imgElm, 'style', s); + if (img.url) { + var src = img.url + '?width=' + newSize.width + '&height=' + newSize.height; + editor.dom.setAttrib(imgElm, 'data-mce-src', src); + } + } + editor.dom.setAttrib(imgElm, 'id', null); + }, 500); + } + }, + /** + * @ngdoc method + * @name umbraco.services.tinyMceService#createUmbracoMacro + * @methodOf umbraco.services.tinyMceService + * + * @description + * Creates the insert umbrco macro tinymce plugin + * + * @param {Object} editor the TinyMCE editor instance + * @param {Object} $scope the current controller scope + */ + createInsertMacro: function (editor, $scope, callback) { + var createInsertMacroScope = this; + /** Adds custom rules for the macro plugin and custom serialization */ + editor.on('preInit', function (args) { + //this is requires so that we tell the serializer that a 'div' is actually allowed in the root, otherwise the cleanup will strip it out + editor.serializer.addRules('div'); + /** This checks if the div is a macro container, if so, checks if its wrapped in a p tag and then unwraps it (removes p tag) */ + editor.serializer.addNodeFilter('div', function (nodes, name) { + for (var i = 0; i < nodes.length; i++) { + if (nodes[i].attr('class') === 'umb-macro-holder' && nodes[i].parent && nodes[i].parent.name.toUpperCase() === 'P') { + nodes[i].parent.unwrap(); + } + } + }); + }); + /** + * Because the macro gets wrapped in a P tag because of the way 'enter' works, this + * method will return the macro element if not wrapped in a p, or the p if the macro + * element is the only one inside of it even if we are deep inside an element inside the macro + */ + function getRealMacroElem(element) { + var e = $(element).closest('.umb-macro-holder'); + if (e.length > 0) { + if (e.get(0).parentNode.nodeName === 'P') { + //now check if we're the only element + if (element.parentNode.childNodes.length === 1) { + return e.get(0).parentNode; + } + } + return e.get(0); + } + return null; + } + /** Adds the button instance */ + editor.addButton('umbmacro', { + icon: 'custom icon-settings-alt', + tooltip: 'Insert macro', + onPostRender: function () { + var ctrl = this; + var isOnMacroElement = false; + /** + if the selection comes from a different element that is not the macro's + we need to check if the selection includes part of the macro, if so we'll force the selection + to clear to the next element since if people can select part of the macro markup they can then modify it. + */ + function handleSelectionChange() { + if (!editor.selection.isCollapsed()) { + var endSelection = tinymce.activeEditor.selection.getEnd(); + var startSelection = tinymce.activeEditor.selection.getStart(); + //don't proceed if it's an entire element selected + if (endSelection !== startSelection) { + //if the end selection is a macro then move the cursor + //NOTE: we don't have to handle when the selection comes from a previous parent because + // that is automatically taken care of with the normal onNodeChanged logic since the + // evt.element will be the macro once it becomes part of the selection. + var $testForMacro = $(endSelection).closest('.umb-macro-holder'); + if ($testForMacro.length > 0) { + //it came from before so move after, if there is no after then select ourselves + var next = $testForMacro.next(); + if (next.length > 0) { + editor.selection.setCursorLocation($testForMacro.next().get(0)); + } else { + selectMacroElement($testForMacro.get(0)); + } + } + } + } + } + /** helper method to select the macro element */ + function selectMacroElement(macroElement) { + // move selection to top element to ensure we can't edit this + editor.selection.select(macroElement); + // check if the current selection *is* the element (ie bug) + var currentSelection = editor.selection.getStart(); + if (tinymce.isIE) { + if (!editor.dom.hasClass(currentSelection, 'umb-macro-holder')) { + while (!editor.dom.hasClass(currentSelection, 'umb-macro-holder') && currentSelection.parentNode) { + currentSelection = currentSelection.parentNode; + } + editor.selection.select(currentSelection); + } + } + } + /** + * Add a node change handler, test if we're editing a macro and select the whole thing, then set our isOnMacroElement flag. + * If we change the selection inside this method, then we end up in an infinite loop, so we have to remove ourselves + * from the event listener before changing selection, however, it seems that putting a break point in this method + * will always cause an 'infinite' loop as the caret keeps changing. + */ + function onNodeChanged(evt) { + //set our macro button active when on a node of class umb-macro-holder + var $macroElement = $(evt.element).closest('.umb-macro-holder'); + handleSelectionChange(); + //set the button active + ctrl.active($macroElement.length !== 0); + if ($macroElement.length > 0) { + var macroElement = $macroElement.get(0); + //remove the event listener before re-selecting + editor.off('NodeChange', onNodeChanged); + selectMacroElement(macroElement); + //set the flag + isOnMacroElement = true; + //re-add the event listener + editor.on('NodeChange', onNodeChanged); + } else { + isOnMacroElement = false; + } + } + /** when the contents load we need to find any macros declared and load in their content */ + editor.on('LoadContent', function (o) { + //get all macro divs and load their content + $(editor.dom.select('.umb-macro-holder.mceNonEditable')).each(function () { + createInsertMacroScope.loadMacroContent($(this), null, $scope); + }); + }); + /** This prevents any other commands from executing when the current element is the macro so the content cannot be edited */ + editor.on('BeforeExecCommand', function (o) { + if (isOnMacroElement) { + if (o.preventDefault) { + o.preventDefault(); + } + if (o.stopImmediatePropagation) { + o.stopImmediatePropagation(); + } + return; + } + }); + /** This double checks and ensures you can't paste content into the rendered macro */ + editor.on('Paste', function (o) { + if (isOnMacroElement) { + if (o.preventDefault) { + o.preventDefault(); + } + if (o.stopImmediatePropagation) { + o.stopImmediatePropagation(); + } + return; + } + }); + //set onNodeChanged event listener + editor.on('NodeChange', onNodeChanged); + /** + * Listen for the keydown in the editor, we'll check if we are currently on a macro element, if so + * we'll check if the key down is a supported key which requires an action, otherwise we ignore the request + * so the macro cannot be edited. + */ + editor.on('KeyDown', function (e) { + if (isOnMacroElement) { + var macroElement = editor.selection.getNode(); + //get the 'real' element (either p or the real one) + macroElement = getRealMacroElem(macroElement); + //prevent editing + e.preventDefault(); + e.stopPropagation(); + var moveSibling = function (element, isNext) { + var $e = $(element); + var $sibling = isNext ? $e.next() : $e.prev(); + if ($sibling.length > 0) { + editor.selection.select($sibling.get(0)); + editor.selection.collapse(true); + } else { + //if we're moving previous and there is no sibling, then lets recurse and just select the next one + if (!isNext) { + moveSibling(element, true); + return; + } + //if there is no sibling we'll generate a new p at the end and select it + editor.setContent(editor.getContent() + '

     

    '); + editor.selection.select($(editor.dom.getRoot()).children().last().get(0)); + editor.selection.collapse(true); + } + }; + //supported keys to move to the next or prev element (13-enter, 27-esc, 38-up, 40-down, 39-right, 37-left) + //supported keys to remove the macro (8-backspace, 46-delete) + //TODO: Should we make the enter key insert a line break before or leave it as moving to the next element? + if ($.inArray(e.keyCode, [ + 13, + 40, + 39 + ]) !== -1) { + //move to next element + moveSibling(macroElement, true); + } else if ($.inArray(e.keyCode, [ + 27, + 38, + 37 + ]) !== -1) { + //move to prev element + moveSibling(macroElement, false); + } else if ($.inArray(e.keyCode, [ + 8, + 46 + ]) !== -1) { + //delete macro element + //move first, then delete + moveSibling(macroElement, false); + editor.dom.remove(macroElement); + } + return; + } + }); + }, + /** The insert macro button click event handler */ + onclick: function () { + var dialogData = { + //flag for use in rte so we only show macros flagged for the editor + richTextEditor: true + }; + //when we click we could have a macro already selected and in that case we'll want to edit the current parameters + //so we'll need to extract them and submit them to the dialog. + var macroElement = editor.selection.getNode(); + macroElement = getRealMacroElem(macroElement); + if (macroElement) { + //we have a macro selected so we'll need to parse it's alias and parameters + var contents = $(macroElement).contents(); + var comment = _.find(contents, function (item) { + return item.nodeType === 8; + }); + if (!comment) { + throw 'Cannot parse the current macro, the syntax in the editor is invalid'; + } + var syntax = comment.textContent.trim(); + var parsed = macroService.parseMacroSyntax(syntax); + dialogData = { macroData: parsed }; + } + if (callback) { + callback(dialogData); + } + } + }); + }, + insertMacroInEditor: function (editor, macroObject, $scope) { + //put the macro syntax in comments, we will parse this out on the server side to be used + //for persisting. + var macroSyntaxComment = ''; + //create an id class for this element so we can re-select it after inserting + var uniqueId = 'umb-macro-' + editor.dom.uniqueId(); + var macroDiv = editor.dom.create('div', { 'class': 'umb-macro-holder ' + macroObject.macroAlias + ' mceNonEditable ' + uniqueId }, macroSyntaxComment + 'Macro alias: ' + macroObject.macroAlias + ''); + editor.selection.setNode(macroDiv); + var $macroDiv = $(editor.dom.select('div.umb-macro-holder.' + uniqueId)); + //async load the macro content + this.loadMacroContent($macroDiv, macroObject, $scope); + }, + /** loads in the macro content async from the server */ + loadMacroContent: function ($macroDiv, macroData, $scope) { + //if we don't have the macroData, then we'll need to parse it from the macro div + if (!macroData) { + var contents = $macroDiv.contents(); + var comment = _.find(contents, function (item) { + return item.nodeType === 8; + }); + if (!comment) { + throw 'Cannot parse the current macro, the syntax in the editor is invalid'; + } + var syntax = comment.textContent.trim(); + var parsed = macroService.parseMacroSyntax(syntax); + macroData = parsed; + } + var $ins = $macroDiv.find('ins'); + //show the throbber + $macroDiv.addClass('loading'); + var contentId = $routeParams.id; + //need to wrap in safe apply since this might be occuring outside of angular + angularHelper.safeApply($scope, function () { + macroResource.getMacroResultAsHtmlForEditor(macroData.macroAlias, contentId, macroData.macroParamsDictionary).then(function (htmlResult) { + $macroDiv.removeClass('loading'); + htmlResult = htmlResult.trim(); + if (htmlResult !== '') { + $ins.html(htmlResult); + } + }); + }); + }, + createLinkPicker: function (editor, $scope, onClick) { + function createLinkList(callback) { + return function () { + var linkList = editor.settings.link_list; + if (typeof linkList === 'string') { + tinymce.util.XHR.send({ + url: linkList, + success: function (text) { + callback(tinymce.util.JSON.parse(text)); + } + }); + } else { + callback(linkList); + } + }; + } + function showDialog(linkList) { + var data = {}, selection = editor.selection, dom = editor.dom, selectedElm, anchorElm, initialText; + var win, linkListCtrl, relListCtrl, targetListCtrl; + function linkListChangeHandler(e) { + var textCtrl = win.find('#text'); + if (!textCtrl.value() || e.lastControl && textCtrl.value() === e.lastControl.text()) { + textCtrl.value(e.control.text()); + } + win.find('#href').value(e.control.value()); + } + function buildLinkList() { + var linkListItems = [{ + text: 'None', + value: '' + }]; + tinymce.each(linkList, function (link) { + linkListItems.push({ + text: link.text || link.title, + value: link.value || link.url, + menu: link.menu + }); + }); + return linkListItems; + } + function buildRelList(relValue) { + var relListItems = [{ + text: 'None', + value: '' + }]; + tinymce.each(editor.settings.rel_list, function (rel) { + relListItems.push({ + text: rel.text || rel.title, + value: rel.value, + selected: relValue === rel.value + }); + }); + return relListItems; + } + function buildTargetList(targetValue) { + var targetListItems = [{ + text: 'None', + value: '' + }]; + if (!editor.settings.target_list) { + targetListItems.push({ + text: 'New window', + value: '_blank' + }); + } + tinymce.each(editor.settings.target_list, function (target) { + targetListItems.push({ + text: target.text || target.title, + value: target.value, + selected: targetValue === target.value + }); + }); + return targetListItems; + } + function buildAnchorListControl(url) { + var anchorList = []; + tinymce.each(editor.dom.select('a:not([href])'), function (anchor) { + var id = anchor.name || anchor.id; + if (id) { + anchorList.push({ + text: id, + value: '#' + id, + selected: url.indexOf('#' + id) !== -1 + }); + } + }); + if (anchorList.length) { + anchorList.unshift({ + text: 'None', + value: '' + }); + return { + name: 'anchor', + type: 'listbox', + label: 'Anchors', + values: anchorList, + onselect: linkListChangeHandler + }; + } + } + function updateText() { + if (!initialText && data.text.length === 0) { + this.parent().parent().find('#text')[0].value(this.value()); + } + } + selectedElm = selection.getNode(); + anchorElm = dom.getParent(selectedElm, 'a[href]'); + data.text = initialText = anchorElm ? anchorElm.innerText || anchorElm.textContent : selection.getContent({ format: 'text' }); + data.href = anchorElm ? dom.getAttrib(anchorElm, 'href') : ''; + data.target = anchorElm ? dom.getAttrib(anchorElm, 'target') : ''; + data.rel = anchorElm ? dom.getAttrib(anchorElm, 'rel') : ''; + if (selectedElm.nodeName === 'IMG') { + data.text = initialText = ' '; + } + if (linkList) { + linkListCtrl = { + type: 'listbox', + label: 'Link list', + values: buildLinkList(), + onselect: linkListChangeHandler + }; + } + if (editor.settings.target_list !== false) { + targetListCtrl = { + name: 'target', + type: 'listbox', + label: 'Target', + values: buildTargetList(data.target) + }; + } + if (editor.settings.rel_list) { + relListCtrl = { + name: 'rel', + type: 'listbox', + label: 'Rel', + values: buildRelList(data.rel) + }; + } + var currentTarget = null; + //if we already have a link selected, we want to pass that data over to the dialog + if (anchorElm) { + var anchor = $(anchorElm); + currentTarget = { + name: anchor.attr('title'), + url: anchor.attr('href'), + target: anchor.attr('target') + }; + // drop the lead char from the anchor text, if it has a value + var anchorVal = anchor[0].dataset.anchor; + if (anchorVal) { + currentTarget.anchor = anchorVal.substring(1); + } + //locallink detection, we do this here, to avoid poluting the dialogservice + //so the dialog service can just expect to get a node-like structure + if (currentTarget.url.indexOf('localLink:') > 0) { + // if the current link has an anchor, it needs to be considered when getting the udi/id + // if an anchor exists, reduce the substring max by its length plus two to offset the removed prefix and trailing curly brace + var linkId = currentTarget.url.substring(currentTarget.url.indexOf(':') + 1, currentTarget.url.lastIndexOf('}')); + //we need to check if this is an INT or a UDI + var parsedIntId = parseInt(linkId, 10); + if (isNaN(parsedIntId)) { + //it's a UDI + currentTarget.udi = linkId; + } else { + currentTarget.id = linkId; + } + } + } + if (onClick) { + onClick(currentTarget, anchorElm); + } + } + editor.addButton('link', { + icon: 'link', + tooltip: 'Insert/edit link', + shortcut: 'Ctrl+K', + onclick: createLinkList(showDialog), + stateSelector: 'a[href]' + }); + editor.addButton('unlink', { + icon: 'unlink', + tooltip: 'Remove link', + cmd: 'unlink', + stateSelector: 'a[href]' + }); + editor.addShortcut('Ctrl+K', '', createLinkList(showDialog)); + this.showDialog = showDialog; + editor.addMenuItem('link', { + icon: 'link', + text: 'Insert link', + shortcut: 'Ctrl+K', + onclick: createLinkList(showDialog), + stateSelector: 'a[href]', + context: 'insert', + prependToContext: true + }); + }, + /** + * @ngdoc method + * @name umbraco.services.tinyMceService#getAnchorNames + * @methodOf umbraco.services.tinyMceService + * + * @description + * From the given string, generates a string array where each item is the id attribute value from a named anchor + * 'some string with a named anchor' returns ['anchor'] + * + * @param {string} input the string to parse + */ + getAnchorNames: function (input) { + if (!input) + return []; + var anchorPattern = //gi; + var matches = input.match(anchorPattern); + var anchors = []; + if (matches) { + anchors = matches.map(function (v) { + return v.substring(v.indexOf('"') + 1, v.lastIndexOf('\\')); + }); + } + return anchors.filter(function (val, i, self) { + return self.indexOf(val) === i; + }); + }, + insertLinkInEditor: function (editor, target, anchorElm) { + var href = target.url; + // We want to use the Udi. If it is set, we use it, else fallback to id, and finally to null + var hasUdi = target.udi ? true : false; + var id = hasUdi ? target.udi : target.id ? target.id : null; + // if an anchor exists, check that it is appropriately prefixed + if (target.anchor && target.anchor[0] !== '?' && target.anchor[0] !== '#') { + target.anchor = (target.anchor.indexOf('=') === -1 ? '#' : '?') + target.anchor; + } + // the href might be an external url, so check the value for an anchor/qs + // href has the anchor re-appended later, hence the reset here to avoid duplicating the anchor + if (!target.anchor) { + var urlParts = href.split(/(#|\?)/); + if (urlParts.length === 3) { + href = urlParts[0]; + target.anchor = urlParts[1] + urlParts[2]; + } + } + //Create a json obj used to create the attributes for the tag + function createElemAttributes() { + var a = { + href: href, + title: target.name, + target: target.target ? target.target : null, + rel: target.rel ? target.rel : null + }; + if (hasUdi) { + a['data-udi'] = target.udi; + } else if (target.id) { + a['data-id'] = target.id; + } + if (target.anchor) { + a['data-anchor'] = target.anchor; + a.href = a.href + target.anchor; + } else { + a['data-anchor'] = null; + } + return a; + } + function insertLink() { + if (anchorElm) { + editor.dom.setAttribs(anchorElm, createElemAttributes()); + editor.selection.select(anchorElm); + editor.execCommand('mceEndTyping'); + } else { + editor.execCommand('mceInsertLink', false, createElemAttributes()); + } + } + if (!href) { + editor.execCommand('unlink'); + return; + } + //if we have an id, it must be a locallink:id, aslong as the isMedia flag is not set + if (id && (angular.isUndefined(target.isMedia) || !target.isMedia)) { + href = '/{localLink:' + id + '}'; + insertLink(); + return; + } + // Is email and not //user@domain.com + if (href.indexOf('@') > 0 && href.indexOf('//') === -1 && href.indexOf('mailto:') === -1) { + href = 'mailto:' + href; + insertLink(); + return; + } + // Is www. prefixed + if (/^\s*www\./i.test(href)) { + href = 'http://' + href; + insertLink(); + return; + } + insertLink(); + } + }; + } + angular.module('umbraco.services').factory('tinyMceService', tinyMceService); + /** + @ngdoc service + * @name umbraco.services.tourService + * + * @description + * Added in Umbraco 7.8. Application-wide service for handling tours. + */ + (function () { + 'use strict'; + function tourService(eventsService, currentUserResource, $q, tourResource) { + var tours = []; + var currentTour = null; + /** + * Registers all tours from the server and returns a promise + */ + function registerAllTours() { + tours = []; + return tourResource.getTours().then(function (tourFiles) { + angular.forEach(tourFiles, function (tourFile) { + angular.forEach(tourFile.tours, function (newTour) { + validateTour(newTour); + validateTourRegistration(newTour); + tours.push(newTour); + }); + }); + eventsService.emit('appState.tour.updatedTours', tours); + }); + } + /** + * Method to return all of the tours as a new instance + */ + function getTours() { + return tours; + } + /** + * @ngdoc method + * @name umbraco.services.tourService#startTour + * @methodOf umbraco.services.tourService + * + * @description + * Raises an event to start a tour + * @param {Object} tour The tour which should be started + */ + function startTour(tour) { + validateTour(tour); + eventsService.emit('appState.tour.start', tour); + currentTour = tour; + } + /** + * @ngdoc method + * @name umbraco.services.tourService#endTour + * @methodOf umbraco.services.tourService + * + * @description + * Raises an event to end the current tour + */ + function endTour(tour) { + eventsService.emit('appState.tour.end', tour); + currentTour = null; + } + /** + * Disables a tour for the user, raises an event and returns a promise + * @param {any} tour + */ + function disableTour(tour) { + var deferred = $q.defer(); + tour.disabled = true; + currentUserResource.saveTourStatus({ + alias: tour.alias, + disabled: tour.disabled, + completed: tour.completed + }).then(function () { + eventsService.emit('appState.tour.end', tour); + currentTour = null; + deferred.resolve(tour); + }); + return deferred.promise; + } + /** + * @ngdoc method + * @name umbraco.services.tourService#completeTour + * @methodOf umbraco.services.tourService + * + * @description + * Completes a tour for the user, raises an event and returns a promise + * @param {Object} tour The tour which should be completed + */ + function completeTour(tour) { + var deferred = $q.defer(); + tour.completed = true; + currentUserResource.saveTourStatus({ + alias: tour.alias, + disabled: tour.disabled, + completed: tour.completed + }).then(function () { + eventsService.emit('appState.tour.complete', tour); + currentTour = null; + deferred.resolve(tour); + }); + return deferred.promise; + } + /** + * @ngdoc method + * @name umbraco.services.tourService#getCurrentTour + * @methodOf umbraco.services.tourService + * + * @description + * Returns the current tour + * @returns {Object} Returns the current tour + */ + function getCurrentTour() { + //TODO: This should be reset if a new user logs in + return currentTour; + } + /** + * @ngdoc method + * @name umbraco.services.tourService#getGroupedTours + * @methodOf umbraco.services.tourService + * + * @description + * Returns a promise of grouped tours with the current user statuses + * @returns {Array} All registered tours grouped by tour group + */ + function getGroupedTours() { + var deferred = $q.defer(); + var tours = getTours(); + setTourStatuses(tours).then(function () { + var groupedTours = []; + tours.forEach(function (item) { + 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; + 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; + } + newGroup.tours.push(item); + groupedTours.push(newGroup); + } + }); + deferred.resolve(groupedTours); + }); + return deferred.promise; + } + /** + * @ngdoc method + * @name umbraco.services.tourService#getTourByAlias + * @methodOf umbraco.services.tourService + * + * @description + * Returns a promise of the tour found by alias with the current user statuses + * @param {Object} tourAlias The tour alias of the tour which should be returned + * @returns {Object} Tour object + */ + function getTourByAlias(tourAlias) { + var deferred = $q.defer(); + var tours = getTours(); + setTourStatuses(tours).then(function () { + var tour = _.findWhere(tours, { alias: tourAlias }); + deferred.resolve(tour); + }); + return deferred.promise; + } + /////////// + /** + * Validates a tour object and makes sure it consists of the correct properties needed to start a tour + * @param {any} tour + */ + function validateTour(tour) { + if (!tour) { + throw 'A tour is not specified'; + } + if (!tour.alias) { + throw 'A tour alias is required'; + } + if (!tour.steps) { + throw 'Tour ' + tour.alias + ' is missing tour steps'; + } + if (tour.steps && tour.steps.length === 0) { + throw 'Tour ' + tour.alias + ' is missing tour steps'; + } + if (tour.requiredSections.length === 0) { + throw 'Tour ' + tour.alias + ' is missing the required sections'; + } + } + /** + * Validates a tour before it gets registered in the service + * @param {any} tour + */ + function validateTourRegistration(tour) { + // check for existing tours with the same alias + angular.forEach(tours, function (existingTour) { + if (existingTour.alias === tour.alias) { + throw 'A tour with the alias ' + tour.alias + ' is already registered'; + } + }); + } + /** + * Based on the tours given, this will set each of the tour statuses (disabled/completed) based on what is stored against the current user + * @param {any} tours + */ + function setTourStatuses(tours) { + var deferred = $q.defer(); + currentUserResource.getTours().then(function (storedTours) { + angular.forEach(storedTours, function (storedTour) { + if (storedTour.completed === true) { + angular.forEach(tours, function (tour) { + if (storedTour.alias === tour.alias) { + tour.completed = true; + } + }); + } + if (storedTour.disabled === true) { + angular.forEach(tours, function (tour) { + if (storedTour.alias === tour.alias) { + tour.disabled = true; + } + }); + } + }); + deferred.resolve(tours); + }); + return deferred.promise; + } + var service = { + registerAllTours: registerAllTours, + startTour: startTour, + endTour: endTour, + disableTour: disableTour, + completeTour: completeTour, + getCurrentTour: getCurrentTour, + getGroupedTours: getGroupedTours, + getTourByAlias: getTourByAlias + }; + return service; + } + angular.module('umbraco.services').factory('tourService', tourService); + }()); + /** + * @ngdoc service + * @name umbraco.services.treeService + * @function + * + * @description + * The tree service factory, used internally by the umbTree and umbTreeItem directives + */ + function treeService($q, treeResource, iconHelper, notificationsService, eventsService) { + //SD: Have looked at putting this in sessionStorage (not localStorage since that means you wouldn't be able to work + // in multiple tabs) - however our tree structure is cyclical, meaning a node has a reference to it's parent and it's children + // which you cannot serialize to sessionStorage. There's really no benefit of session storage except that you could refresh + // a tab and have the trees where they used to be - supposed that is kind of nice but would mean we'd have to store the parent + // as a nodeid reference instead of a variable with a getParent() method. + var treeCache = {}; + var standardCssClass = 'icon umb-tree-icon sprTree'; + function getCacheKey(args) { + //if there is no cache key they return null - it won't be cached. + if (!args || !args.cacheKey) { + return null; + } + var cacheKey = args.cacheKey; + cacheKey += '_' + args.section; + return cacheKey; + } + return { + /** Internal method to return the tree cache */ + _getTreeCache: function () { + return treeCache; + }, + /** Internal method that ensures there's a routePath, parent and level property on each tree node and adds some icon specific properties so that the nodes display properly */ + _formatNodeDataForUseInUI: function (parentNode, treeNodes, section, level) { + //if no level is set, then we make it 1 + var childLevel = level ? level : 1; + //set the section if it's not already set + if (!parentNode.section) { + parentNode.section = section; + } + if (parentNode.metaData && parentNode.metaData.noAccess === true) { + if (!parentNode.cssClasses) { + parentNode.cssClasses = []; + } + parentNode.cssClasses.push('no-access'); + } + //create a method outside of the loop to return the parent - otherwise jshint blows up + var funcParent = function () { + return parentNode; + }; + for (var i = 0; i < treeNodes.length; i++) { + var treeNode = treeNodes[i]; + treeNode.level = childLevel; + //create a function to get the parent node, we could assign the parent node but + // then we cannot serialize this entity because we have a cyclical reference. + // Instead we just make a function to return the parentNode. + treeNode.parent = funcParent; + //set the section for each tree node - this allows us to reference this easily when accessing tree nodes + treeNode.section = section; + //if there is not route path specified, then set it automatically, + //if this is a tree root node then we want to route to the section's dashboard + if (!treeNode.routePath) { + if (treeNode.metaData && treeNode.metaData['treeAlias']) { + //this is a root node + treeNode.routePath = section; + } else { + var treeAlias = this.getTreeAlias(treeNode); + treeNode.routePath = section + '/' + treeAlias + '/edit/' + treeNode.id; + } + } + //now, format the icon data + if (treeNode.iconIsClass === undefined || treeNode.iconIsClass) { + var converted = iconHelper.convertFromLegacyTreeNodeIcon(treeNode); + treeNode.cssClass = standardCssClass + ' ' + converted; + if (converted.startsWith('.')) { + //its legacy so add some width/height + treeNode.style = 'height:16px;width:16px;'; + } else { + treeNode.style = ''; + } + } else { + treeNode.style = 'background-image: url(\'' + treeNode.iconFilePath + '\');'; + //we need an 'icon-' class in there for certain styles to work so if it is image based we'll add this + treeNode.cssClass = standardCssClass + ' legacy-custom-file'; + } + if (treeNode.metaData && treeNode.metaData.noAccess === true) { + if (!treeNode.cssClasses) { + treeNode.cssClasses = []; + } + treeNode.cssClasses.push('no-access'); + } + } + }, + /** + * @ngdoc method + * @name umbraco.services.treeService#getTreePackageFolder + * @methodOf umbraco.services.treeService + * @function + * + * @description + * Determines if the current tree is a plugin tree and if so returns the package folder it has declared + * so we know where to find it's views, otherwise it will just return undefined. + * + * @param {String} treeAlias The tree alias to check + */ + getTreePackageFolder: function (treeAlias) { + //we determine this based on the server variables + if (Umbraco.Sys.ServerVariables.umbracoPlugins && Umbraco.Sys.ServerVariables.umbracoPlugins.trees && angular.isArray(Umbraco.Sys.ServerVariables.umbracoPlugins.trees)) { + var found = _.find(Umbraco.Sys.ServerVariables.umbracoPlugins.trees, function (item) { + return item.alias === treeAlias; + }); + return found ? found.packageFolder : undefined; + } + return undefined; + }, + /** + * @ngdoc method + * @name umbraco.services.treeService#clearCache + * @methodOf umbraco.services.treeService + * @function + * + * @description + * Clears the tree cache - with optional cacheKey, optional section or optional filter. + * + * @param {Object} args arguments + * @param {String} args.cacheKey optional cachekey - this is used to clear specific trees in dialogs + * @param {String} args.section optional section alias - clear tree for a given section + * @param {String} args.childrenOf optional parent ID - only clear the cache below a specific node + */ + clearCache: function (args) { + //clear all if not specified + if (!args) { + treeCache = {}; + } else { + //if section and cache key specified just clear that cache + if (args.section && args.cacheKey) { + var cacheKey = getCacheKey(args); + if (cacheKey && treeCache && treeCache[cacheKey] != null) { + treeCache = _.omit(treeCache, cacheKey); + } + } else if (args.childrenOf) { + //if childrenOf is supplied a cacheKey must be supplied as well + if (!args.cacheKey) { + throw 'args.cacheKey is required if args.childrenOf is supplied'; + } + //this will clear out all children for the parentId passed in to this parameter, we'll + // do this by recursing and specifying a filter + var self = this; + this.clearCache({ + cacheKey: args.cacheKey, + filter: function (cc) { + //get the new parent node from the tree cache + var parent = self.getDescendantNode(cc.root, args.childrenOf); + if (parent) { + //clear it's children and set to not expanded + parent.children = null; + parent.expanded = false; + } + //return the cache to be saved + return cc; + } + }); + } else if (args.filter && angular.isFunction(args.filter)) { + //if a filter is supplied a cacheKey must be supplied as well + if (!args.cacheKey) { + throw 'args.cacheKey is required if args.filter is supplied'; + } + //if a filter is supplied the function needs to return the data to keep + var byKey = treeCache[args.cacheKey]; + if (byKey) { + var result = args.filter(byKey); + if (result) { + //set the result to the filtered data + treeCache[args.cacheKey] = result; + } else { + //remove the cache + treeCache = _.omit(treeCache, args.cacheKey); + } + } + } else if (args.cacheKey) { + //if only the cache key is specified, then clear all cache starting with that key + var allKeys1 = _.keys(treeCache); + var toRemove1 = _.filter(allKeys1, function (k) { + return k.startsWith(args.cacheKey + '_'); + }); + treeCache = _.omit(treeCache, toRemove1); + } else if (args.section) { + //if only the section is specified then clear all cache regardless of cache key by that section + var allKeys2 = _.keys(treeCache); + var toRemove2 = _.filter(allKeys2, function (k) { + return k.endsWith('_' + args.section); + }); + treeCache = _.omit(treeCache, toRemove2); + } + } + }, + /** + * @ngdoc method + * @name umbraco.services.treeService#loadNodeChildren + * @methodOf umbraco.services.treeService + * @function + * + * @description + * Clears all node children, gets it's up-to-date children from the server and re-assigns them and then + * returns them in a promise. + * @param {object} args An arguments object + * @param {object} args.node The tree node + * @param {object} args.section The current section + */ + loadNodeChildren: function (args) { + if (!args) { + throw 'No args object defined for loadNodeChildren'; + } + if (!args.node) { + throw 'No node defined on args object for loadNodeChildren'; + } + this.removeChildNodes(args.node); + args.node.loading = true; + return this.getChildren(args).then(function (data) { + //set state to done and expand (only if there actually are children!) + args.node.loading = false; + args.node.children = data; + if (args.node.children && args.node.children.length > 0) { + args.node.expanded = true; + args.node.hasChildren = true; + if (angular.isFunction(args.node.updateNodeData)) { + args.node.updateNodeData(); + } + } + return data; + }, function (reason) { + //in case of error, emit event + eventsService.emit('treeService.treeNodeLoadError', { error: reason }); + //stop show the loading indicator + args.node.loading = false; + //tell notications about the error + notificationsService.error(reason); + return reason; + }); + }, + /** + * @ngdoc method + * @name umbraco.services.treeService#removeNode + * @methodOf umbraco.services.treeService + * @function + * + * @description + * Removes a given node from the tree + * @param {object} treeNode the node to remove + */ + removeNode: function (treeNode) { + if (!angular.isFunction(treeNode.parent)) { + return; + } + if (treeNode.parent() == null) { + throw 'Cannot remove a node that doesn\'t have a parent'; + } + //remove the current item from it's siblings + treeNode.parent().children.splice(treeNode.parent().children.indexOf(treeNode), 1); + }, + /** + * @ngdoc method + * @name umbraco.services.treeService#removeChildNodes + * @methodOf umbraco.services.treeService + * @function + * + * @description + * Removes all child nodes from a given tree node + * @param {object} treeNode the node to remove children from + */ + removeChildNodes: function (treeNode) { + treeNode.expanded = false; + treeNode.children = []; + treeNode.hasChildren = false; + }, + /** + * @ngdoc method + * @name umbraco.services.treeService#getChildNode + * @methodOf umbraco.services.treeService + * @function + * + * @description + * Gets a child node with a given ID, from a specific treeNode + * @param {object} treeNode to retrive child node from + * @param {int} id id of child node + */ + getChildNode: function (treeNode, id) { + if (!treeNode.children) { + return null; + } + var found = _.find(treeNode.children, function (child) { + return String(child.id).toLowerCase() === String(id).toLowerCase(); + }); + return found === undefined ? null : found; + }, + /** + * @ngdoc method + * @name umbraco.services.treeService#getDescendantNode + * @methodOf umbraco.services.treeService + * @function + * + * @description + * Gets a descendant node by id + * @param {object} treeNode to retrive descendant node from + * @param {int} id id of descendant node + * @param {string} treeAlias - optional tree alias, if fetching descendant node from a child of a listview document + */ + getDescendantNode: function (treeNode, id, treeAlias) { + //validate if it is a section container since we'll need a treeAlias if it is one + if (treeNode.isContainer === true && !treeAlias) { + throw 'Cannot get a descendant node from a section container node without a treeAlias specified'; + } + //if it is a section container, we need to find the tree to be searched + if (treeNode.isContainer) { + var foundRoot = null; + for (var c = 0; c < treeNode.children.length; c++) { + if (this.getTreeAlias(treeNode.children[c]) === treeAlias) { + foundRoot = treeNode.children[c]; + break; + } + } + if (!foundRoot) { + throw 'Could not find a tree in the current section with alias ' + treeAlias; + } + treeNode = foundRoot; + } + //check this node + if (treeNode.id === id) { + return treeNode; + } + //check the first level + var found = this.getChildNode(treeNode, id); + if (found) { + return found; + } + //check each child of this node + if (!treeNode.children) { + return null; + } + for (var i = 0; i < treeNode.children.length; i++) { + var child = treeNode.children[i]; + if (child.children && angular.isArray(child.children) && child.children.length > 0) { + //recurse + found = this.getDescendantNode(child, id); + if (found) { + return found; + } + } + } + //not found + return found === undefined ? null : found; + }, + /** + * @ngdoc method + * @name umbraco.services.treeService#getTreeRoot + * @methodOf umbraco.services.treeService + * @function + * + * @description + * Gets the root node of the current tree type for a given tree node + * @param {object} treeNode to retrive tree root node from + */ + getTreeRoot: function (treeNode) { + if (!treeNode) { + throw 'treeNode cannot be null'; + } + //all root nodes have metadata key 'treeAlias' + var root = null; + var current = treeNode; + while (root === null && current) { + if (current.metaData && current.metaData['treeAlias']) { + root = current; + } else if (angular.isFunction(current.parent)) { + //we can only continue if there is a parent() method which means this + // tree node was loaded in as part of a real tree, not just as a single tree + // node from the server. + current = current.parent(); + } else { + current = null; + } + } + return root; + }, + /** Gets the node's tree alias, this is done by looking up the meta-data of the current node's root node */ + /** + * @ngdoc method + * @name umbraco.services.treeService#getTreeAlias + * @methodOf umbraco.services.treeService + * @function + * + * @description + * Gets the node's tree alias, this is done by looking up the meta-data of the current node's root node + * @param {object} treeNode to retrive tree alias from + */ + getTreeAlias: function (treeNode) { + var root = this.getTreeRoot(treeNode); + if (root) { + return root.metaData['treeAlias']; + } + return ''; + }, + /** + * @ngdoc method + * @name umbraco.services.treeService#getTree + * @methodOf umbraco.services.treeService + * @function + * + * @description + * gets the tree, returns a promise + * @param {object} args Arguments + * @param {string} args.section Section alias + * @param {string} args.cacheKey Optional cachekey + */ + getTree: function (args) { + var deferred = $q.defer(); + //set defaults + if (!args) { + args = { + section: 'content', + cacheKey: null + }; + } else if (!args.section) { + args.section = 'content'; + } + var cacheKey = getCacheKey(args); + //return the cache if it exists + if (cacheKey && treeCache[cacheKey] !== undefined) { + deferred.resolve(treeCache[cacheKey]); + return deferred.promise; + } + var self = this; + treeResource.loadApplication(args).then(function (data) { + //this will be called once the tree app data has loaded + var result = { + name: data.name, + alias: args.section, + root: data + }; + //we need to format/modify some of the node data to be used in our app. + self._formatNodeDataForUseInUI(result.root, result.root.children, args.section); + //cache this result if a cache key is specified - generally a cache key should ONLY + // be specified for application trees, dialog trees should not be cached. + if (cacheKey) { + treeCache[cacheKey] = result; + deferred.resolve(treeCache[cacheKey]); + } + //return un-cached + deferred.resolve(result); + }); + return deferred.promise; + }, + /** + * @ngdoc method + * @name umbraco.services.treeService#getMenu + * @methodOf umbraco.services.treeService + * @function + * + * @description + * Returns available menu actions for a given tree node + * @param {object} args Arguments + * @param {string} args.treeNode tree node object to retrieve the menu for + */ + getMenu: function (args) { + if (!args) { + throw 'args cannot be null'; + } + if (!args.treeNode) { + throw 'args.treeNode cannot be null'; + } + return treeResource.loadMenu(args.treeNode).then(function (data) { + //need to convert the icons to new ones + for (var i = 0; i < data.length; i++) { + data[i].cssclass = iconHelper.convertFromLegacyIcon(data[i].cssclass); + } + return data; + }); + }, + /** + * @ngdoc method + * @name umbraco.services.treeService#getChildren + * @methodOf umbraco.services.treeService + * @function + * + * @description + * Gets the children from the server for a given node + * @param {object} args Arguments + * @param {object} args.node tree node object to retrieve the children for + * @param {string} args.section current section alias + */ + getChildren: function (args) { + if (!args) { + throw 'No args object defined for getChildren'; + } + if (!args.node) { + throw 'No node defined on args object for getChildren'; + } + var section = args.section || 'content'; + var treeItem = args.node; + var self = this; + return treeResource.loadNodes({ node: treeItem }).then(function (data) { + //now that we have the data, we need to add the level property to each item and the view + self._formatNodeDataForUseInUI(treeItem, data, section, treeItem.level + 1); + return data; + }); + }, + /** + * @ngdoc method + * @name umbraco.services.treeService#reloadNode + * @methodOf umbraco.services.treeService + * @function + * + * @description + * Re-loads the single node from the server + * @param {object} node Tree node to reload + */ + reloadNode: function (node) { + if (!node) { + throw 'node cannot be null'; + } + if (!node.parent()) { + throw 'cannot reload a single node without a parent'; + } + if (!node.section) { + throw 'cannot reload a single node without an assigned node.section'; + } + var deferred = $q.defer(); + //set the node to loading + node.loading = true; + this.getChildren({ + node: node.parent(), + section: node.section + }).then(function (data) { + //ok, now that we have the children, find the node we're reloading + var found = _.find(data, function (item) { + return item.id === node.id; + }); + if (found) { + //now we need to find the node in the parent.children collection to replace + var index = _.indexOf(node.parent().children, node); + //the trick here is to not actually replace the node - this would cause the delete animations + //to fire, instead we're just going to replace all the properties of this node. + //there should always be a method assigned but we'll check anyways + if (angular.isFunction(node.parent().children[index].updateNodeData)) { + node.parent().children[index].updateNodeData(found); + } else { + //just update as per normal - this means styles, etc.. won't be applied + _.extend(node.parent().children[index], found); + } + //set the node loading + node.parent().children[index].loading = false; + //return + deferred.resolve(node.parent().children[index]); + } else { + deferred.reject(); + } + }, function () { + deferred.reject(); + }); + return deferred.promise; + }, + /** + * @ngdoc method + * @name umbraco.services.treeService#getPath + * @methodOf umbraco.services.treeService + * @function + * + * @description + * This will return the current node's path by walking up the tree + * @param {object} node Tree node to retrieve path for + */ + getPath: function (node) { + if (!node) { + throw 'node cannot be null'; + } + if (!angular.isFunction(node.parent)) { + throw 'node.parent is not a function, the path cannot be resolved'; + } + //all root nodes have metadata key 'treeAlias' + var reversePath = []; + var current = node; + while (current != null) { + reversePath.push(current.id); + if (current.metaData && current.metaData['treeAlias']) { + current = null; + } else { + current = current.parent(); + } + } + return reversePath.reverse(); + }, + syncTree: function (args) { + if (!args) { + throw 'No args object defined for syncTree'; + } + if (!args.node) { + throw 'No node defined on args object for syncTree'; + } + if (!args.path) { + throw 'No path defined on args object for syncTree'; + } + if (!angular.isArray(args.path)) { + throw 'Path must be an array'; + } + if (args.path.length < 1) { + //if there is no path, make -1 the path, and that should sync the tree root + args.path.push('-1'); + } + var deferred = $q.defer(); + //get the rootNode for the current node, we'll sync based on that + var root = this.getTreeRoot(args.node); + if (!root) { + throw 'Could not get the root tree node based on the node passed in'; + } + //now we want to loop through the ids in the path, first we'll check if the first part + //of the path is the root node, otherwise we'll search it's children. + var currPathIndex = 0; + //if the first id is the root node and there's only one... then consider it synced + if (String(args.path[currPathIndex]).toLowerCase() === String(args.node.id).toLowerCase()) { + if (args.path.length === 1) { + //return the root + deferred.resolve(root); + return deferred.promise; + } else { + //move to the next path part and continue + currPathIndex = 1; + } + } + //now that we have the first id to lookup, we can start the process + var self = this; + var node = args.node; + var doSync = function () { + //check if it exists in the already loaded children + var child = self.getChildNode(node, args.path[currPathIndex]); + if (child) { + if (args.path.length === currPathIndex + 1) { + //woot! synced the node + if (!args.forceReload) { + deferred.resolve(child); + } else { + //even though we've found the node if forceReload is specified + //we want to go update this single node from the server + self.reloadNode(child).then(function (reloaded) { + deferred.resolve(reloaded); + }, function () { + deferred.reject(); + }); + } + } else { + //now we need to recurse with the updated node/currPathIndex + currPathIndex++; + node = child; + //recurse + doSync(); + } + } else { + //couldn't find it in the + self.loadNodeChildren({ + node: node, + section: node.section + }).then(function () { + //ok, got the children, let's find it + var found = self.getChildNode(node, args.path[currPathIndex]); + if (found) { + if (args.path.length === currPathIndex + 1) { + //woot! synced the node + deferred.resolve(found); + } else { + //now we need to recurse with the updated node/currPathIndex + currPathIndex++; + node = found; + //recurse + doSync(); + } + } else { + //fail! + deferred.reject(); + } + }, function () { + //fail! + deferred.reject(); + }); + } + }; + //start + doSync(); + return deferred.promise; + } + }; + } + angular.module('umbraco.services').factory('treeService', treeService); + (function () { + 'use strict'; + /** + * @ngdoc service + * @name umbraco.services.umbDataFormatter + * @description A helper object used to format/transform JSON Umbraco data, mostly used for persisting data to the server + **/ + function umbDataFormatter() { + return { + formatChangePasswordModel: function (model) { + if (!model) { + return null; + } + var trimmed = _.omit(model, [ + 'confirm', + 'generatedPassword' + ]); + //ensure that the pass value is null if all child properties are null + var allNull = true; + var vals = _.values(trimmed); + for (var k = 0; k < vals.length; k++) { + if (vals[k] !== null && vals[k] !== undefined) { + allNull = false; + } + } + if (allNull) { + return null; + } + return trimmed; + }, + formatContentTypePostData: function (displayModel, action) { + //create the save model from the display model + var saveModel = _.pick(displayModel, 'compositeContentTypes', 'isContainer', 'allowAsRoot', 'allowedTemplates', 'allowedContentTypes', 'alias', 'description', 'thumbnail', 'name', 'id', 'icon', 'trashed', 'key', 'parentId', 'alias', 'path'); + //TODO: Map these + saveModel.allowedTemplates = _.map(displayModel.allowedTemplates, function (t) { + return t.alias; + }); + saveModel.defaultTemplate = displayModel.defaultTemplate ? displayModel.defaultTemplate.alias : null; + var realGroups = _.reject(displayModel.groups, function (g) { + //do not include these tabs + return g.tabState === 'init'; + }); + saveModel.groups = _.map(realGroups, function (g) { + var saveGroup = _.pick(g, 'inherited', 'id', 'sortOrder', 'name'); + var realProperties = _.reject(g.properties, function (p) { + //do not include these properties + return p.propertyState === 'init' || p.inherited === true; + }); + var saveProperties = _.map(realProperties, function (p) { + var saveProperty = _.pick(p, 'id', 'alias', 'description', 'validation', 'label', 'sortOrder', 'dataTypeId', 'groupId', 'memberCanEdit', 'showOnMemberProfile', 'isSensitiveData'); + return saveProperty; + }); + saveGroup.properties = saveProperties; + //if this is an inherited group and there are not non-inherited properties on it, then don't send up the data + if (saveGroup.inherited === true && saveProperties.length === 0) { + return null; + } + return saveGroup; + }); + //we don't want any null groups + saveModel.groups = _.reject(saveModel.groups, function (g) { + return !g; + }); + return saveModel; + }, + /** formats the display model used to display the data type to the model used to save the data type */ + formatDataTypePostData: function (displayModel, preValues, action) { + var saveModel = { + parentId: displayModel.parentId, + id: displayModel.id, + name: displayModel.name, + selectedEditor: displayModel.selectedEditor, + //set the action on the save model + action: action, + preValues: [] + }; + for (var i = 0; i < preValues.length; i++) { + saveModel.preValues.push({ + key: preValues[i].alias, + value: preValues[i].value + }); + } + return saveModel; + }, + /** formats the display model used to display the dictionary to the model used to save the dictionary */ + formatDictionaryPostData: function (dictionary, nameIsDirty) { + var saveModel = { + parentId: dictionary.parentId, + id: dictionary.id, + name: dictionary.name, + nameIsDirty: nameIsDirty, + translations: [], + key: dictionary.key + }; + for (var i = 0; i < dictionary.translations.length; i++) { + saveModel.translations.push({ + isoCode: dictionary.translations[i].isoCode, + languageId: dictionary.translations[i].languageId, + translation: dictionary.translations[i].translation + }); + } + return saveModel; + }, + /** formats the display model used to display the user to the model used to save the user */ + formatUserPostData: function (displayModel) { + //create the save model from the display model + var saveModel = _.pick(displayModel, 'id', 'parentId', 'name', 'username', 'culture', 'email', 'startContentIds', 'startMediaIds', 'userGroups', 'message', 'changePassword'); + saveModel.changePassword = this.formatChangePasswordModel(saveModel.changePassword); + //make sure the userGroups are just a string array + var currGroups = saveModel.userGroups; + var formattedGroups = []; + for (var i = 0; i < currGroups.length; i++) { + if (!angular.isString(currGroups[i])) { + formattedGroups.push(currGroups[i].alias); + } else { + formattedGroups.push(currGroups[i]); + } + } + saveModel.userGroups = formattedGroups; + //make sure the startnodes are just a string array + var props = [ + 'startContentIds', + 'startMediaIds' + ]; + for (var m = 0; m < props.length; m++) { + var startIds = saveModel[props[m]]; + if (!startIds) { + continue; + } + var formattedIds = []; + for (var j = 0; j < startIds.length; j++) { + formattedIds.push(Number(startIds[j].id)); + } + saveModel[props[m]] = formattedIds; + } + return saveModel; + }, + /** formats the display model used to display the user group to the model used to save the user group*/ + formatUserGroupPostData: function (displayModel, action) { + //create the save model from the display model + var saveModel = _.pick(displayModel, 'id', 'alias', 'name', 'icon', 'sections', 'users', 'defaultPermissions', 'assignedPermissions'); + // the start nodes cannot be picked as the property name needs to change - assign manually + saveModel.startContentId = displayModel['contentStartNode']; + saveModel.startMediaId = displayModel['mediaStartNode']; + //set the action on the save model + saveModel.action = action; + if (!saveModel.id) { + saveModel.id = 0; + } + //the permissions need to just be the array of permission letters, currently it will be a dictionary of an array + var currDefaultPermissions = saveModel.defaultPermissions; + var saveDefaultPermissions = []; + _.each(currDefaultPermissions, function (value, key, list) { + _.each(value, function (element, index, list) { + if (element.checked) { + saveDefaultPermissions.push(element.permissionCode); + } + }); + }); + saveModel.defaultPermissions = saveDefaultPermissions; + //now format that assigned/content permissions + var currAssignedPermissions = saveModel.assignedPermissions; + var saveAssignedPermissions = {}; + _.each(currAssignedPermissions, function (nodePermissions, index) { + saveAssignedPermissions[nodePermissions.id] = []; + _.each(nodePermissions.allowedPermissions, function (permission, index) { + if (permission.checked) { + saveAssignedPermissions[nodePermissions.id].push(permission.permissionCode); + } + }); + }); + saveModel.assignedPermissions = saveAssignedPermissions; + //make sure the sections are just a string array + var currSections = saveModel.sections; + var formattedSections = []; + for (var i = 0; i < currSections.length; i++) { + if (!angular.isString(currSections[i])) { + formattedSections.push(currSections[i].alias); + } else { + formattedSections.push(currSections[i]); + } + } + saveModel.sections = formattedSections; + //make sure the user are just an int array + var currUsers = saveModel.users; + var formattedUsers = []; + for (var j = 0; j < currUsers.length; j++) { + if (!angular.isNumber(currUsers[j])) { + formattedUsers.push(currUsers[j].id); + } else { + formattedUsers.push(currUsers[j]); + } + } + saveModel.users = formattedUsers; + //make sure the startnodes are just an int if one is set + var props = [ + 'startContentId', + 'startMediaId' + ]; + for (var m = 0; m < props.length; m++) { + var startId = saveModel[props[m]]; + if (!startId) { + continue; + } + saveModel[props[m]] = startId.id; + } + saveModel.parentId = -1; + return saveModel; + }, + /** formats the display model used to display the member to the model used to save the member */ + formatMemberPostData: function (displayModel, action) { + //this is basically the same as for media but we need to explicitly add the username,email, password to the save model + var saveModel = this.formatMediaPostData(displayModel, action); + saveModel.key = displayModel.key; + var genericTab = _.find(displayModel.tabs, function (item) { + return item.id === 0; + }); + //map the member login, email, password and groups + var propLogin = _.find(genericTab.properties, function (item) { + return item.alias === '_umb_login'; + }); + var propEmail = _.find(genericTab.properties, function (item) { + return item.alias === '_umb_email'; + }); + var propPass = _.find(genericTab.properties, function (item) { + return item.alias === '_umb_password'; + }); + var propGroups = _.find(genericTab.properties, function (item) { + return item.alias === '_umb_membergroup'; + }); + saveModel.email = propEmail.value.trim(); + saveModel.username = propLogin.value.trim(); + saveModel.password = this.formatChangePasswordModel(propPass.value); + var selectedGroups = []; + for (var n in propGroups.value) { + if (propGroups.value[n] === true) { + selectedGroups.push(n); + } + } + saveModel.memberGroups = selectedGroups; + //turn the dictionary into an array of pairs + var memberProviderPropAliases = _.pairs(displayModel.fieldConfig); + _.each(displayModel.tabs, function (tab) { + _.each(tab.properties, function (prop) { + var foundAlias = _.find(memberProviderPropAliases, function (item) { + return prop.alias === item[1]; + }); + if (foundAlias) { + //we know the current property matches an alias, now we need to determine which membership provider property it was for + // by looking at the key + switch (foundAlias[0]) { + case 'umbracoMemberLockedOut': + saveModel.isLockedOut = prop.value ? prop.value.toString() === '1' ? true : false : false; + break; + case 'umbracoMemberApproved': + saveModel.isApproved = prop.value ? prop.value.toString() === '1' ? true : false : true; + break; + case 'umbracoMemberComments': + saveModel.comments = prop.value; + break; + } + } + }); + }); + return saveModel; + }, + /** formats the display model used to display the media to the model used to save the media */ + formatMediaPostData: function (displayModel, action) { + //NOTE: the display model inherits from the save model so we can in theory just post up the display model but + // we don't want to post all of the data as it is unecessary. + var saveModel = { + id: displayModel.id, + properties: [], + name: displayModel.name, + contentTypeAlias: displayModel.contentTypeAlias, + parentId: displayModel.parentId, + //set the action on the save model + action: action + }; + _.each(displayModel.tabs, function (tab) { + _.each(tab.properties, function (prop) { + //don't include the custom generic tab properties + //don't include a property that is marked readonly + if (!prop.alias.startsWith('_umb_') && !prop.readonly) { + saveModel.properties.push({ + id: prop.id, + alias: prop.alias, + value: prop.value + }); + } + }); + }); + return saveModel; + }, + /** formats the display model used to display the content to the model used to save the content */ + formatContentPostData: function (displayModel, action) { + //this is basically the same as for media but we need to explicitly add some extra properties + var saveModel = this.formatMediaPostData(displayModel, action); + var propExpireDate = displayModel.removeDate; + var propReleaseDate = displayModel.releaseDate; + var propTemplate = displayModel.template; + saveModel.expireDate = propExpireDate ? propExpireDate : null; + saveModel.releaseDate = propReleaseDate ? propReleaseDate : null; + saveModel.templateAlias = propTemplate ? propTemplate : null; + return saveModel; + } + }; + } + angular.module('umbraco.services').factory('umbDataFormatter', umbDataFormatter); + }()); + /** +* @ngdoc service +* @name umbraco.services.umbRequestHelper +* @description A helper object used for sending requests to the server +**/ + function umbRequestHelper($http, $q, umbDataFormatter, angularHelper, dialogService, notificationsService, eventsService) { + return { + /** + * @ngdoc method + * @name umbraco.services.umbRequestHelper#convertVirtualToAbsolutePath + * @methodOf umbraco.services.umbRequestHelper + * @function + * + * @description + * This will convert a virtual path (i.e. ~/App_Plugins/Blah/Test.html ) to an absolute path + * + * @param {string} a virtual path, if this is already an absolute path it will just be returned, if this is a relative path an exception will be thrown + */ + convertVirtualToAbsolutePath: function (virtualPath) { + if (virtualPath.startsWith('/')) { + return virtualPath; + } + if (!virtualPath.startsWith('~/')) { + throw 'The path ' + virtualPath + ' is not a virtual path'; + } + if (!Umbraco.Sys.ServerVariables.application.applicationPath) { + throw 'No applicationPath defined in Umbraco.ServerVariables.application.applicationPath'; + } + return Umbraco.Sys.ServerVariables.application.applicationPath + virtualPath.trimStart('~/'); + }, + /** + * @ngdoc method + * @name umbraco.services.umbRequestHelper#dictionaryToQueryString + * @methodOf umbraco.services.umbRequestHelper + * @function + * + * @description + * This will turn an array of key/value pairs or a standard dictionary into a query string + * + * @param {Array} queryStrings An array of key/value pairs + */ + dictionaryToQueryString: function (queryStrings) { + if (angular.isArray(queryStrings)) { + return _.map(queryStrings, function (item) { + var key = null; + var val = null; + for (var k in item) { + key = k; + val = item[k]; + break; + } + if (key === null || val === null) { + throw 'The object in the array was not formatted as a key/value pair'; + } + return encodeURIComponent(key) + '=' + encodeURIComponent(val); + }).join('&'); + } else if (angular.isObject(queryStrings)) { + //this allows for a normal object to be passed in (ie. a dictionary) + return decodeURIComponent($.param(queryStrings)); + } + throw 'The queryString parameter is not an array or object of key value pairs'; + }, + /** + * @ngdoc method + * @name umbraco.services.umbRequestHelper#getApiUrl + * @methodOf umbraco.services.umbRequestHelper + * @function + * + * @description + * This will return the webapi Url for the requested key based on the servervariables collection + * + * @param {string} apiName The webapi name that is found in the servervariables["umbracoUrls"] dictionary + * @param {string} actionName The webapi action name + * @param {object} queryStrings Can be either a string or an array containing key/value pairs + */ + getApiUrl: function (apiName, actionName, queryStrings) { + if (!Umbraco || !Umbraco.Sys || !Umbraco.Sys.ServerVariables || !Umbraco.Sys.ServerVariables['umbracoUrls']) { + throw 'No server variables defined!'; + } + if (!Umbraco.Sys.ServerVariables['umbracoUrls'][apiName]) { + throw 'No url found for api name ' + apiName; + } + return Umbraco.Sys.ServerVariables['umbracoUrls'][apiName] + actionName + (!queryStrings ? '' : '?' + (angular.isString(queryStrings) ? queryStrings : this.dictionaryToQueryString(queryStrings))); + }, + /** + * @ngdoc function + * @name umbraco.services.umbRequestHelper#resourcePromise + * @methodOf umbraco.services.umbRequestHelper + * @function + * + * @description + * This returns a promise with an underlying http call, it is a helper method to reduce + * the amount of duplicate code needed to query http resources and automatically handle any + * Http errors. See /docs/source/using-promises-resources.md + * + * @param {object} opts A mixed object which can either be a string representing the error message to be + * returned OR an object containing either: + * { success: successCallback, errorMsg: errorMessage } + * OR + * { success: successCallback, error: errorCallback } + * In both of the above, the successCallback must accept these parameters: data, status, headers, config + * If using the errorCallback it must accept these parameters: data, status, headers, config + * The success callback must return the data which will be resolved by the deferred object. + * The error callback must return an object containing: {errorMsg: errorMessage, data: originalData, status: status } + */ + resourcePromise: function (httpPromise, opts) { + var deferred = $q.defer(); + /** The default success callback used if one is not supplied in the opts */ + function defaultSuccess(data, status, headers, config) { + //when it's successful, just return the data + return data; + } + /** The default error callback used if one is not supplied in the opts */ + function defaultError(data, status, headers, config) { + return { + //NOTE: the default error message here should never be used based on the above docs! + errorMsg: angular.isString(opts) ? opts : 'An error occurred!', + data: data, + status: status + }; + } + //create the callbacs based on whats been passed in. + var callbacks = { + success: !opts || !opts.success ? defaultSuccess : opts.success, + error: !opts || !opts.error ? defaultError : opts.error + }; + httpPromise.success(function (data, status, headers, config) { + //invoke the callback + var result = callbacks.success.apply(this, [ + data, + status, + headers, + config + ]); + //when it's successful, just return the data + deferred.resolve(result); + }).error(function (data, status, headers, config) { + //invoke the callback + var result = callbacks.error.apply(this, [ + data, + status, + headers, + config + ]); + //when there's a 500 (unhandled) error show a YSOD overlay if debugging is enabled. + if (status >= 500 && status < 600) { + //show a ysod dialog + if (Umbraco.Sys.ServerVariables['isDebuggingEnabled'] === true) { + eventsService.emit('app.ysod', { + errorMsg: 'An error occured', + data: data + }); + } else { + //show a simple error notification + notificationsService.error('Server error', 'Contact administrator, see log for full details.
    ' + result.errorMsg + ''); + } + } + //return an error object including the error message for UI + deferred.reject({ + errorMsg: result.errorMsg, + data: result.data, + status: result.status + }); + }); + return deferred.promise; + }, + /** Used for saving media/content specifically */ + postSaveContent: function (args) { + if (!args.restApiUrl) { + throw 'args.restApiUrl is a required argument'; + } + if (!args.content) { + throw 'args.content is a required argument'; + } + if (!args.action) { + throw 'args.action is a required argument'; + } + if (!args.files) { + throw 'args.files is a required argument'; + } + if (!args.dataFormatter) { + throw 'args.dataFormatter is a required argument'; + } + var deferred = $q.defer(); + //save the active tab id so we can set it when the data is returned. + var activeTab = _.find(args.content.tabs, function (item) { + return item.active; + }); + var activeTabIndex = activeTab === undefined ? 0 : _.indexOf(args.content.tabs, activeTab); + //save the data + this.postMultiPartRequest(args.restApiUrl, { + key: 'contentItem', + value: args.dataFormatter(args.content, args.action) + }, function (data, formData) { + //now add all of the assigned files + for (var f in args.files) { + //each item has a property alias and the file object, we'll ensure that the alias is suffixed to the key + // so we know which property it belongs to on the server side + formData.append('file_' + args.files[f].alias, args.files[f].file); + } + }, function (data, status, headers, config) { + //success callback + //reset the tabs and set the active one + if (data.tabs && data.tabs.length > 0) { + _.each(data.tabs, function (item) { + item.active = false; + }); + data.tabs[activeTabIndex].active = true; + } + //the data returned is the up-to-date data so the UI will refresh + deferred.resolve(data); + }, function (data, status, headers, config) { + //failure callback + //when there's a 500 (unhandled) error show a YSOD overlay if debugging is enabled. + if (status >= 500 && status < 600) { + //This is a bit of a hack to check if the error is due to a file being uploaded that is too large, + // we have to just check for the existence of a string value but currently that is the best way to + // do this since it's very hacky/difficult to catch this on the server + if (typeof data !== 'undefined' && typeof data.indexOf === 'function' && data.indexOf('Maximum request length exceeded') >= 0) { + notificationsService.error('Server error', 'The uploaded file was too large, check with your site administrator to adjust the maximum size allowed'); + } else if (Umbraco.Sys.ServerVariables['isDebuggingEnabled'] === true) { + //show a ysod dialog + eventsService.emit('app.ysod', { + errorMsg: 'An error occured', + data: data + }); + } else { + //show a simple error notification + notificationsService.error('Server error', 'Contact administrator, see log for full details.
    ' + data.ExceptionMessage + ''); + } + } + //return an error object including the error message for UI + deferred.reject({ + errorMsg: 'An error occurred', + data: data, + status: status + }); + }); + return deferred.promise; + }, + /** Posts a multi-part mime request to the server */ + postMultiPartRequest: function (url, jsonData, transformCallback, successCallback, failureCallback) { + //validate input, jsonData can be an array of key/value pairs or just one key/value pair. + if (!jsonData) { + throw 'jsonData cannot be null'; + } + if (angular.isArray(jsonData)) { + _.each(jsonData, function (item) { + if (!item.key || !item.value) { + throw 'jsonData array item must have both a key and a value property'; + } + }); + } else if (!jsonData.key || !jsonData.value) { + throw 'jsonData object must have both a key and a value property'; + } + $http({ + method: 'POST', + url: url, + //IMPORTANT!!! You might think this should be set to 'multipart/form-data' but this is not true because when we are sending up files + // the request needs to include a 'boundary' parameter which identifies the boundary name between parts in this multi-part request + // and setting the Content-type manually will not set this boundary parameter. For whatever reason, setting the Content-type to 'false' + // will force the request to automatically populate the headers properly including the boundary parameter. + headers: { 'Content-Type': false }, + transformRequest: function (data) { + var formData = new FormData(); + //add the json data + if (angular.isArray(data)) { + _.each(data, function (item) { + formData.append(item.key, !angular.isString(item.value) ? angular.toJson(item.value) : item.value); + }); + } else { + formData.append(data.key, !angular.isString(data.value) ? angular.toJson(data.value) : data.value); + } + //call the callback + if (transformCallback) { + transformCallback.apply(this, [ + data, + formData + ]); + } + return formData; + }, + data: jsonData + }).success(function (data, status, headers, config) { + if (successCallback) { + successCallback.apply(this, [ + data, + status, + headers, + config + ]); + } + }).error(function (data, status, headers, config) { + if (failureCallback) { + failureCallback.apply(this, [ + data, + status, + headers, + config + ]); + } + }); + }, + /** + * Downloads a file to the client using AJAX/XHR + * Based on an implementation here: web.student.tuwien.ac.at/~e0427417/jsdownload.html + * See https://stackoverflow.com/a/24129082/694494 + */ + downloadFile: function (httpPath) { + var deferred = $q.defer(); + // Use an arraybuffer + $http.get(httpPath, { responseType: 'arraybuffer' }).success(function (data, status, headers) { + var octetStreamMime = 'application/octet-stream'; + var success = false; + // Get the headers + headers = headers(); + // Get the filename from the x-filename header or default to "download.bin" + var filename = headers['x-filename'] || 'download.bin'; + // Determine the content type from the header or default to "application/octet-stream" + var contentType = headers['content-type'] || octetStreamMime; + try { + // Try using msSaveBlob if supported + console.log('Trying saveBlob method ...'); + var blob = new Blob([data], { type: contentType }); + if (navigator.msSaveBlob) + navigator.msSaveBlob(blob, filename); + else { + // Try using other saveBlob implementations, if available + var saveBlob = navigator.webkitSaveBlob || navigator.mozSaveBlob || navigator.saveBlob; + if (saveBlob === undefined) + throw 'Not supported'; + saveBlob(blob, filename); + } + console.log('saveBlob succeeded'); + success = true; + } catch (ex) { + console.log('saveBlob method failed with the following exception:'); + console.log(ex); + } + if (!success) { + // Get the blob url creator + var urlCreator = window.URL || window.webkitURL || window.mozURL || window.msURL; + if (urlCreator) { + // Try to use a download link + var link = document.createElement('a'); + if ('download' in link) { + // Try to simulate a click + try { + // Prepare a blob URL + console.log('Trying download link method with simulated click ...'); + var blob = new Blob([data], { type: contentType }); + var url = urlCreator.createObjectURL(blob); + link.setAttribute('href', url); + // Set the download attribute (Supported in Chrome 14+ / Firefox 20+) + link.setAttribute('download', filename); + // Simulate clicking the download link + var event = document.createEvent('MouseEvents'); + event.initMouseEvent('click', true, true, window, 1, 0, 0, 0, 0, false, false, false, false, 0, null); + link.dispatchEvent(event); + console.log('Download link method with simulated click succeeded'); + success = true; + } catch (ex) { + console.log('Download link method with simulated click failed with the following exception:'); + console.log(ex); + } + } + if (!success) { + // Fallback to window.location method + try { + // Prepare a blob URL + // Use application/octet-stream when using window.location to force download + console.log('Trying download link method with window.location ...'); + var blob = new Blob([data], { type: octetStreamMime }); + var url = urlCreator.createObjectURL(blob); + window.location = url; + console.log('Download link method with window.location succeeded'); + success = true; + } catch (ex) { + console.log('Download link method with window.location failed with the following exception:'); + console.log(ex); + } + } + } + } + if (!success) { + // Fallback to window.open method + console.log('No methods worked for saving the arraybuffer, using last resort window.open'); + window.open(httpPath, '_blank', ''); + } + deferred.resolve(); + }).error(function (data, status) { + console.log('Request failed with status: ' + status); + deferred.reject({ + errorMsg: 'An error occurred downloading the file', + data: data, + status: status + }); + }); + return deferred.promise; + } + }; + } + angular.module('umbraco.services').factory('umbRequestHelper', umbRequestHelper); + angular.module('umbraco.services').factory('userService', function ($rootScope, eventsService, $q, $location, $log, securityRetryQueue, authResource, assetsService, dialogService, $timeout, angularHelper, $http, javascriptLibraryService) { + var currentUser = null; + var lastUserId = null; + var loginDialog = null; + //this tracks the last date/time that the user's remainingAuthSeconds was updated from the server + // this is used so that we know when to go and get the user's remaining seconds directly. + var lastServerTimeoutSet = null; + function openLoginDialog(isTimedOut) { + if (!loginDialog) { + loginDialog = dialogService.open({ + //very special flag which means that global events cannot close this dialog + manualClose: true, + template: 'views/common/dialogs/login.html', + modalClass: 'login-overlay', + animation: 'slide', + show: true, + callback: onLoginDialogClose, + dialogData: { isTimedOut: isTimedOut } + }); + } + } + function onLoginDialogClose(success) { + loginDialog = null; + if (success) { + securityRetryQueue.retryAll(currentUser.name); + } else { + securityRetryQueue.cancelAll(); + $location.path('/'); + } + } + /** + This methods will set the current user when it is resolved and + will then start the counter to count in-memory how many seconds they have + remaining on the auth session + */ + function setCurrentUser(usr) { + if (!usr.remainingAuthSeconds) { + throw 'The user object is invalid, the remainingAuthSeconds is required.'; + } + currentUser = usr; + lastServerTimeoutSet = new Date(); + //start the timer + countdownUserTimeout(); + } + /** + Method to count down the current user's timeout seconds, + this will continually count down their current remaining seconds every 5 seconds until + there are no more seconds remaining. + */ + function countdownUserTimeout() { + $timeout(function () { + if (currentUser) { + //countdown by 5 seconds since that is how long our timer is for. + currentUser.remainingAuthSeconds -= 5; + //if there are more than 30 remaining seconds, recurse! + if (currentUser.remainingAuthSeconds > 30) { + //we need to check when the last time the timeout was set from the server, if + // it has been more than 30 seconds then we'll manually go and retrieve it from the + // server - this helps to keep our local countdown in check with the true timeout. + if (lastServerTimeoutSet != null) { + var now = new Date(); + var seconds = (now.getTime() - lastServerTimeoutSet.getTime()) / 1000; + if (seconds > 30) { + //first we'll set the lastServerTimeoutSet to null - this is so we don't get back in to this loop while we + // wait for a response from the server otherwise we'll be making double/triple/etc... calls while we wait. + lastServerTimeoutSet = null; + //now go get it from the server + //NOTE: the safeApply because our timeout is set to not run digests (performance reasons) + angularHelper.safeApply($rootScope, function () { + authResource.getRemainingTimeoutSeconds().then(function (result) { + setUserTimeoutInternal(result); + }); + }); + } + } + //recurse the countdown! + countdownUserTimeout(); + } else { + //we are either timed out or very close to timing out so we need to show the login dialog. + if (Umbraco.Sys.ServerVariables.umbracoSettings.keepUserLoggedIn !== true) { + //NOTE: the safeApply because our timeout is set to not run digests (performance reasons) + angularHelper.safeApply($rootScope, function () { + try { + //NOTE: We are calling this again so that the server can create a log that the timeout has expired, we + // don't actually care about this result. + authResource.getRemainingTimeoutSeconds(); + } finally { + userAuthExpired(); + } + }); + } else { + //we've got less than 30 seconds remaining so let's check the server + if (lastServerTimeoutSet != null) { + //first we'll set the lastServerTimeoutSet to null - this is so we don't get back in to this loop while we + // wait for a response from the server otherwise we'll be making double/triple/etc... calls while we wait. + lastServerTimeoutSet = null; + //now go get it from the server + //NOTE: the safeApply because our timeout is set to not run digests (performance reasons) + angularHelper.safeApply($rootScope, function () { + authResource.getRemainingTimeoutSeconds().then(function (result) { + setUserTimeoutInternal(result); + }); + }); + } + //recurse the countdown! + countdownUserTimeout(); + } + } + } + }, 5000, //every 5 seconds + false); //false = do NOT execute a digest for every iteration + } + /** Called to update the current user's timeout */ + function setUserTimeoutInternal(newTimeout) { + var asNumber = parseFloat(newTimeout); + if (!isNaN(asNumber) && currentUser && angular.isNumber(asNumber)) { + currentUser.remainingAuthSeconds = newTimeout; + lastServerTimeoutSet = new Date(); + } + } + /** resets all user data, broadcasts the notAuthenticated event and shows the login dialog */ + function userAuthExpired(isLogout) { + //store the last user id and clear the user + if (currentUser && currentUser.id !== undefined) { + lastUserId = currentUser.id; + } + if (currentUser) { + currentUser.remainingAuthSeconds = 0; + } + lastServerTimeoutSet = null; + currentUser = null; + //broadcast a global event that the user is no longer logged in + eventsService.emit('app.notAuthenticated'); + openLoginDialog(isLogout === undefined ? true : !isLogout); + } + // Register a handler for when an item is added to the retry queue + securityRetryQueue.onItemAddedCallbacks.push(function (retryItem) { + if (securityRetryQueue.hasMore()) { + userAuthExpired(); + } + }); + return { + /** Internal method to display the login dialog */ + _showLoginDialog: function () { + openLoginDialog(); + }, + /** Returns a promise, sends a request to the server to check if the current cookie is authorized */ + isAuthenticated: function () { + //if we've got a current user then just return true + if (currentUser) { + var deferred = $q.defer(); + deferred.resolve(true); + return deferred.promise; + } + return authResource.isAuthenticated(); + }, + /** Returns a promise, sends a request to the server to validate the credentials */ + authenticate: function (login, password) { + return authResource.performLogin(login, password).then(this.setAuthenticationSuccessful); + }, + setAuthenticationSuccessful: function (data) { + //when it's successful, return the user data + setCurrentUser(data); + var result = { + user: data, + authenticated: true, + lastUserId: lastUserId, + loginType: 'credentials' + }; + //broadcast a global event + eventsService.emit('app.authenticated', result); + return result; + }, + /** Logs the user out + */ + logout: function () { + return authResource.performLogout().then(function (data) { + userAuthExpired(); + //done! + return null; + }); + }, + /** Refreshes the current user data with the data stored for the user on the server and returns it */ + refreshCurrentUser: function () { + var deferred = $q.defer(); + authResource.getCurrentUser().then(function (data) { + var result = { + user: data, + authenticated: true, + lastUserId: lastUserId, + loginType: 'implicit' + }; + setCurrentUser(data); + deferred.resolve(currentUser); + }, function () { + //it failed, so they are not logged in + deferred.reject(); + }); + return deferred.promise; + }, + /** Returns the current user object in a promise */ + getCurrentUser: function (args) { + var deferred = $q.defer(); + if (!currentUser) { + authResource.getCurrentUser().then(function (data) { + var result = { + user: data, + authenticated: true, + lastUserId: lastUserId, + loginType: 'implicit' + }; + if (args && args.broadcastEvent) { + //broadcast a global event, will inform listening controllers to load in the user specific data + eventsService.emit('app.authenticated', result); + } + setCurrentUser(data); + deferred.resolve(currentUser); + }, function () { + //it failed, so they are not logged in + deferred.reject(); + }); + } else { + deferred.resolve(currentUser); + } + return deferred.promise; + }, + /** Loads the Moment.js Locale for the current user. */ + loadMomentLocaleForCurrentUser: function () { + function loadLocales(currentUser, supportedLocales) { + var locale = currentUser.locale.toLowerCase(); + if (locale !== 'en-us') { + var localeUrls = []; + if (supportedLocales.indexOf(locale + '.js') > -1) { + localeUrls.push('lib/moment/' + locale + '.js'); + } + if (locale.indexOf('-') > -1) { + var majorLocale = locale.split('-')[0] + '.js'; + if (supportedLocales.indexOf(majorLocale) > -1) { + localeUrls.push('lib/moment/' + majorLocale); + } + } + return assetsService.load(localeUrls, $rootScope); + } else { + //return a noop promise + var deferred = $q.defer(); + var promise = deferred.promise; + deferred.resolve(true); + return promise; + } + } + var promises = { + currentUser: this.getCurrentUser(), + supportedLocales: javascriptLibraryService.getSupportedLocalesForMoment() + }; + return $q.all(promises).then(function (values) { + return loadLocales(values.currentUser, values.supportedLocales); + }); + }, + /** Called whenever a server request is made that contains a x-umb-user-seconds response header for which we can update the user's remaining timeout seconds */ + setUserTimeout: function (newTimeout) { + setUserTimeoutInternal(newTimeout); + } + }; + }); + (function () { + 'use strict'; + function usersHelperService(localizationService) { + var userStates = [ + { + 'name': 'All', + 'key': 'All' + }, + { + 'value': 0, + 'name': 'Active', + 'key': 'Active', + 'color': 'success' + }, + { + 'value': 1, + 'name': 'Disabled', + 'key': 'Disabled', + 'color': 'danger' + }, + { + 'value': 2, + 'name': 'Locked out', + 'key': 'LockedOut', + 'color': 'danger' + }, + { + 'value': 3, + 'name': 'Invited', + 'key': 'Invited', + 'color': 'warning' + }, + { + 'value': 4, + 'name': 'Inactive', + 'key': 'Inactive', + 'color': 'warning' + } + ]; + angular.forEach(userStates, function (userState) { + var key = 'user_state' + userState.key; + localizationService.localize(key).then(function (value) { + var reg = /^\[[\S\s]*]$/g; + var result = reg.test(value); + if (result === false) { + // Only translate if key exists + userState.name = value; + } + }); + }); + function getUserStateFromValue(value) { + var foundUserState; + angular.forEach(userStates, function (userState) { + if (userState.value === value) { + foundUserState = userState; + } + }); + return foundUserState; + } + function getUserStateByKey(key) { + var foundUserState; + angular.forEach(userStates, function (userState) { + if (userState.key === key) { + foundUserState = userState; + } + }); + return foundUserState; + } + function getUserStatesFilter(userStatesObject) { + var userStatesFilter = []; + for (var key in userStatesObject) { + if (userStatesObject.hasOwnProperty(key)) { + var userState = getUserStateByKey(key); + if (userState) { + userState.count = userStatesObject[key]; + userStatesFilter.push(userState); + } + } + } + return userStatesFilter; + } + //////////// + var service = { + getUserStateFromValue: getUserStateFromValue, + getUserStateByKey: getUserStateByKey, + getUserStatesFilter: getUserStatesFilter + }; + return service; + } + angular.module('umbraco.services').factory('usersHelper', usersHelperService); + }()); + /*Contains multiple services for various helper tasks */ + function versionHelper() { + return { + //see: https://gist.github.com/TheDistantSea/8021359 + versionCompare: function (v1, v2, options) { + var lexicographical = options && options.lexicographical, zeroExtend = options && options.zeroExtend, v1parts = v1.split('.'), v2parts = v2.split('.'); + function isValidPart(x) { + return (lexicographical ? /^\d+[A-Za-z]*$/ : /^\d+$/).test(x); + } + if (!v1parts.every(isValidPart) || !v2parts.every(isValidPart)) { + return NaN; + } + if (zeroExtend) { + while (v1parts.length < v2parts.length) { + v1parts.push('0'); + } + while (v2parts.length < v1parts.length) { + v2parts.push('0'); + } + } + if (!lexicographical) { + v1parts = v1parts.map(Number); + v2parts = v2parts.map(Number); + } + for (var i = 0; i < v1parts.length; ++i) { + if (v2parts.length === i) { + return 1; + } + if (v1parts[i] === v2parts[i]) { + continue; + } else if (v1parts[i] > v2parts[i]) { + return 1; + } else { + return -1; + } + } + if (v1parts.length !== v2parts.length) { + return -1; + } + return 0; + } + }; + } + angular.module('umbraco.services').factory('versionHelper', versionHelper); + function dateHelper() { + return { + convertToServerStringTime: function (momentLocal, serverOffsetMinutes, format) { + //get the formatted offset time in HH:mm (server time offset is in minutes) + var formattedOffset = (serverOffsetMinutes > 0 ? '+' : '-') + moment().startOf('day').minutes(Math.abs(serverOffsetMinutes)).format('HH:mm'); + var server = moment.utc(momentLocal).utcOffset(formattedOffset); + return server.format(format ? format : 'YYYY-MM-DD HH:mm:ss'); + }, + convertToLocalMomentTime: function (strVal, serverOffsetMinutes) { + //get the formatted offset time in HH:mm (server time offset is in minutes) + var formattedOffset = (serverOffsetMinutes > 0 ? '+' : '-') + moment().startOf('day').minutes(Math.abs(serverOffsetMinutes)).format('HH:mm'); + //if the string format already denotes that it's in "Roundtrip UTC" format (i.e. "2018-02-07T00:20:38.173Z") + //otherwise known as https://en.wikipedia.org/wiki/ISO_8601. This is the default format returned from the server + //since that is the default formatter for newtonsoft.json. When it is in this format, we need to tell moment + //to load the date as UTC so it's not changed, otherwise load it normally + var isoFormat; + if (strVal.indexOf('T') > -1 && strVal.endsWith('Z')) { + isoFormat = moment.utc(strVal).format('YYYY-MM-DDTHH:mm:ss') + formattedOffset; + } else { + isoFormat = moment(strVal).format('YYYY-MM-DDTHH:mm:ss') + formattedOffset; + } + //create a moment with the iso format which will include the offset with the correct time + // then convert it to local time + return moment.parseZone(isoFormat).local(); + }, + getLocalDate: function (date, culture, format) { + if (date) { + var dateVal; + var serverOffset = Umbraco.Sys.ServerVariables.application.serverTimeOffset; + var localOffset = new Date().getTimezoneOffset(); + var serverTimeNeedsOffsetting = -serverOffset !== localOffset; + if (serverTimeNeedsOffsetting) { + dateVal = this.convertToLocalMomentTime(date, serverOffset); + } else { + dateVal = moment(date, 'YYYY-MM-DD HH:mm:ss'); + } + return dateVal.locale(culture).format(format); + } + } + }; + } + angular.module('umbraco.services').factory('dateHelper', dateHelper); + function packageHelper(assetsService, treeService, eventsService, $templateCache) { + return { + /** Called when a package is installed, this resets a bunch of data and ensures the new package assets are loaded in */ + packageInstalled: function () { + //clears the tree + treeService.clearCache(); + //clears the template cache + $templateCache.removeAll(); + //emit event to notify anything else + eventsService.emit('app.reInitialize'); + } + }; + } + angular.module('umbraco.services').factory('packageHelper', packageHelper); + //TODO: I believe this is obsolete + function umbPhotoFolderHelper($compile, $log, $timeout, $filter, imageHelper, mediaHelper, umbRequestHelper) { + return { + /** sets the image's url, thumbnail and if its a folder */ + setImageData: function (img) { + img.isFolder = !mediaHelper.hasFilePropertyType(img); + if (!img.isFolder) { + img.thumbnail = mediaHelper.resolveFile(img, true); + img.image = mediaHelper.resolveFile(img, false); + } + }, + /** sets the images original size properties - will check if it is a folder and if so will just make it square */ + setOriginalSize: function (img, maxHeight) { + //set to a square by default + img.originalWidth = maxHeight; + img.originalHeight = maxHeight; + var widthProp = _.find(img.properties, function (v) { + return v.alias === 'umbracoWidth'; + }); + if (widthProp && widthProp.value) { + img.originalWidth = parseInt(widthProp.value, 10); + if (isNaN(img.originalWidth)) { + img.originalWidth = maxHeight; + } + } + var heightProp = _.find(img.properties, function (v) { + return v.alias === 'umbracoHeight'; + }); + if (heightProp && heightProp.value) { + img.originalHeight = parseInt(heightProp.value, 10); + if (isNaN(img.originalHeight)) { + img.originalHeight = maxHeight; + } + } + }, + /** sets the image style which get's used in the angular markup */ + setImageStyle: function (img, width, height, rightMargin, bottomMargin) { + img.style = { + width: width + 'px', + height: height + 'px', + 'margin-right': rightMargin + 'px', + 'margin-bottom': bottomMargin + 'px' + }; + img.thumbStyle = { + 'background-image': 'url(\'' + img.thumbnail + '\')', + 'background-repeat': 'no-repeat', + 'background-position': 'center', + 'background-size': Math.min(width, img.originalWidth) + 'px ' + Math.min(height, img.originalHeight) + 'px' + }; + }, + /** gets the image's scaled wdith based on the max row height */ + getScaledWidth: function (img, maxHeight) { + var scaled = img.originalWidth * maxHeight / img.originalHeight; + return scaled; //round down, we don't want it too big even by half a pixel otherwise it'll drop to the next row + //return Math.floor(scaled); + }, + /** returns the target row width taking into account how many images will be in the row and removing what the margin is */ + getTargetWidth: function (imgsPerRow, maxRowWidth, margin) { + //take into account the margin, we will have 1 less margin item than we have total images + return maxRowWidth - (imgsPerRow - 1) * margin; + }, + /** + This will determine the row/image height for the next collection of images which takes into account the + ideal image count per row. It will check if a row can be filled with this ideal count and if not - if there + are additional images available to fill the row it will keep calculating until they fit. + + It will return the calculated height and the number of images for the row. + + targetHeight = optional; + */ + getRowHeightForImages: function (imgs, maxRowHeight, minDisplayHeight, maxRowWidth, idealImgPerRow, margin, targetHeight) { + var idealImages = imgs.slice(0, idealImgPerRow); + //get the target row width without margin + var targetRowWidth = this.getTargetWidth(idealImages.length, maxRowWidth, margin); + //this gets the image with the smallest height which equals the maximum we can scale up for this image block + var maxScaleableHeight = this.getMaxScaleableHeight(idealImages, maxRowHeight); + //if the max scale height is smaller than the min display height, we'll use the min display height + targetHeight = targetHeight !== undefined ? targetHeight : Math.max(maxScaleableHeight, minDisplayHeight); + var attemptedRowHeight = this.performGetRowHeight(idealImages, targetRowWidth, minDisplayHeight, targetHeight); + if (attemptedRowHeight != null) { + //if this is smaller than the min display then we need to use the min display, + // which means we'll need to remove one from the row so we can scale up to fill the row + if (attemptedRowHeight < minDisplayHeight) { + if (idealImages.length > 1) { + //we'll generate a new targetHeight that is halfway between the max and the current and recurse, passing in a new targetHeight + targetHeight += Math.floor((maxRowHeight - targetHeight) / 2); + return this.getRowHeightForImages(imgs, maxRowHeight, minDisplayHeight, maxRowWidth, idealImgPerRow - 1, margin, targetHeight); + } else { + //this will occur when we only have one image remaining in the row but it's still going to be too wide even when + // using the minimum display height specified. In this case we're going to have to just crop the image in it's center + // using the minimum display height and the full row width + return { + height: minDisplayHeight, + imgCount: 1 + }; + } + } else { + //success! + return { + height: attemptedRowHeight, + imgCount: idealImages.length + }; + } + } + //we know the width will fit in a row, but we now need to figure out if we can fill + // the entire row in the case that we have more images remaining than the idealImgPerRow. + if (idealImages.length === imgs.length) { + //we have no more remaining images to fill the space, so we'll just use the calc height + return { + height: targetHeight, + imgCount: idealImages.length + }; + } else if (idealImages.length === 1) { + //this will occur when we only have one image remaining in the row to process but it's not really going to fit ideally + // in the row. + return { + height: minDisplayHeight, + imgCount: 1 + }; + } else if (idealImages.length === idealImgPerRow && targetHeight < maxRowHeight) { + //if we're already dealing with the ideal images per row and it's not quite wide enough, we can scale up a little bit so + // long as the targetHeight is currently less than the maxRowHeight. The scale up will be half-way between our current + // target height and the maxRowHeight (we won't loop forever though - if there's a difference of 5 px we'll just quit) + while (targetHeight < maxRowHeight && maxRowHeight - targetHeight > 5) { + targetHeight += Math.floor((maxRowHeight - targetHeight) / 2); + attemptedRowHeight = this.performGetRowHeight(idealImages, targetRowWidth, minDisplayHeight, targetHeight); + if (attemptedRowHeight != null) { + //success! + return { + height: attemptedRowHeight, + imgCount: idealImages.length + }; + } + } + //Ok, we couldn't actually scale it up with the ideal row count we'll just recurse with a lesser image count. + return this.getRowHeightForImages(imgs, maxRowHeight, minDisplayHeight, maxRowWidth, idealImgPerRow - 1, margin); + } else if (targetHeight === maxRowHeight) { + //This is going to happen when: + // * We can fit a list of images in a row, but they come up too short (based on minDisplayHeight) + // * Then we'll try to remove an image, but when we try to scale to fit, the width comes up too narrow but the images are already at their + // maximum height (maxRowHeight) + // * So we're stuck, we cannot precicely fit the current list of images, so we'll render a row that will be max height but won't be wide enough + // which is better than rendering a row that is shorter than the minimum since that could be quite small. + return { + height: targetHeight, + imgCount: idealImages.length + }; + } else { + //we have additional images so we'll recurse and add 1 to the idealImgPerRow until it fits + return this.getRowHeightForImages(imgs, maxRowHeight, minDisplayHeight, maxRowWidth, idealImgPerRow + 1, margin); + } + }, + performGetRowHeight: function (idealImages, targetRowWidth, minDisplayHeight, targetHeight) { + var currRowWidth = 0; + for (var i = 0; i < idealImages.length; i++) { + var scaledW = this.getScaledWidth(idealImages[i], targetHeight); + currRowWidth += scaledW; + } + if (currRowWidth > targetRowWidth) { + //get the new scaled height to fit + var newHeight = targetRowWidth * targetHeight / currRowWidth; + return newHeight; + } else if (idealImages.length === 1 && currRowWidth <= targetRowWidth && !idealImages[0].isFolder) { + //if there is only one image, then return the target height + return targetHeight; + } else if (currRowWidth / targetRowWidth > 0.9) { + //it's close enough, it's at least 90% of the width so we'll accept it with the target height + return targetHeight; + } else { + //if it's not successful, return null + return null; + } + }, + /** builds an image grid row */ + buildRow: function (imgs, maxRowHeight, minDisplayHeight, maxRowWidth, idealImgPerRow, margin, totalRemaining) { + var currRowWidth = 0; + var row = { images: [] }; + var imageRowHeight = this.getRowHeightForImages(imgs, maxRowHeight, minDisplayHeight, maxRowWidth, idealImgPerRow, margin); + var targetWidth = this.getTargetWidth(imageRowHeight.imgCount, maxRowWidth, margin); + var sizes = []; + //loop through the images we know fit into the height + for (var i = 0; i < imageRowHeight.imgCount; i++) { + //get the lower width to ensure it always fits + var scaledWidth = Math.floor(this.getScaledWidth(imgs[i], imageRowHeight.height)); + if (currRowWidth + scaledWidth <= targetWidth) { + currRowWidth += scaledWidth; + sizes.push({ + width: scaledWidth, + //ensure that the height is rounded + height: Math.round(imageRowHeight.height) + }); + row.images.push(imgs[i]); + } else if (imageRowHeight.imgCount === 1 && row.images.length === 0) { + //the image is simply too wide, we'll crop/center it + sizes.push({ + width: maxRowWidth, + //ensure that the height is rounded + height: Math.round(imageRowHeight.height) + }); + row.images.push(imgs[i]); + } else { + //the max width has been reached + break; + } + } + //loop through the images for the row and apply the styles + for (var j = 0; j < row.images.length; j++) { + var bottomMargin = margin; + //make the margin 0 for the last one + if (j === row.images.length - 1) { + margin = 0; + } + this.setImageStyle(row.images[j], sizes[j].width, sizes[j].height, margin, bottomMargin); + } + if (row.images.length === 1 && totalRemaining > 1) { + //if there's only one image on the row and there are more images remaining, set the container to max width + row.images[0].style.width = maxRowWidth + 'px'; + } + return row; + }, + /** Returns the maximum image scaling height for the current image collection */ + getMaxScaleableHeight: function (imgs, maxRowHeight) { + var smallestHeight = _.min(imgs, function (item) { + return item.originalHeight; + }).originalHeight; + //adjust the smallestHeight if it is larger than the static max row height + if (smallestHeight > maxRowHeight) { + smallestHeight = maxRowHeight; + } + return smallestHeight; + }, + /** Creates the image grid with calculated widths/heights for images to fill the grid nicely */ + buildGrid: function (images, maxRowWidth, maxRowHeight, startingIndex, minDisplayHeight, idealImgPerRow, margin, imagesOnly) { + var rows = []; + var imagesProcessed = 0; + //first fill in all of the original image sizes and URLs + for (var i = startingIndex; i < images.length; i++) { + var item = images[i]; + this.setImageData(item); + this.setOriginalSize(item, maxRowHeight); + if (imagesOnly && !item.isFolder && !item.thumbnail) { + images.splice(i, 1); + i--; + } + } + while (imagesProcessed + startingIndex < images.length) { + //get the maxHeight for the current un-processed images + var currImgs = images.slice(imagesProcessed); + //build the row + var remaining = images.length - imagesProcessed; + var row = this.buildRow(currImgs, maxRowHeight, minDisplayHeight, maxRowWidth, idealImgPerRow, margin, remaining); + if (row.images.length > 0) { + rows.push(row); + imagesProcessed += row.images.length; + } else { + if (currImgs.length > 0) { + throw 'Could not fill grid with all images, images remaining: ' + currImgs.length; + } + //if there was nothing processed, exit + break; + } + } + return rows; + } + }; + } + angular.module('umbraco.services').factory('umbPhotoFolderHelper', umbPhotoFolderHelper); + /** + * @ngdoc function + * @name umbraco.services.umbModelMapper + * @function + * + * @description + * Utility class to map/convert models + */ + function umbModelMapper() { + return { + /** + * @ngdoc function + * @name umbraco.services.umbModelMapper#convertToEntityBasic + * @methodOf umbraco.services.umbModelMapper + * @function + * + * @description + * Converts the source model to a basic entity model, it will throw an exception if there isn't enough data to create the model. + * @param {Object} source The source model + * @param {Number} source.id The node id of the model + * @param {String} source.name The node name + * @param {String} source.icon The models icon as a css class (.icon-doc) + * @param {Number} source.parentId The parentID, if no parent, set to -1 + * @param {path} source.path comma-separated string of ancestor IDs (-1,1234,1782,1234) + */ + /** This converts the source model to a basic entity model, it will throw an exception if there isn't enough data to create the model */ + convertToEntityBasic: function (source) { + var required = [ + 'id', + 'name', + 'icon', + 'parentId', + 'path' + ]; + _.each(required, function (k) { + if (!_.has(source, k)) { + throw 'The source object does not contain the property ' + k; + } + }); + var optional = [ + 'metaData', + 'key', + 'alias' + ]; + //now get the basic object + var result = _.pick(source, required.concat(optional)); + return result; + } + }; + } + angular.module('umbraco.services').factory('umbModelMapper', umbModelMapper); + /** + * @ngdoc function + * @name umbraco.services.umbSessionStorage + * @function + * + * @description + * Used to get/set things in browser sessionStorage but always prefixes keys with "umb_" and converts json vals so there is no overlap + * with any sessionStorage created by a developer. + */ + function umbSessionStorage($window) { + //gets the sessionStorage object if available, otherwise just uses a normal object + // - required for unit tests. + var storage = $window['sessionStorage'] ? $window['sessionStorage'] : {}; + return { + get: function (key) { + return angular.fromJson(storage['umb_' + key]); + }, + set: function (key, value) { + storage['umb_' + key] = angular.toJson(value); + } + }; + } + angular.module('umbraco.services').factory('umbSessionStorage', umbSessionStorage); + /** + * @ngdoc function + * @name umbraco.services.updateChecker + * @function + * + * @description + * used to check for updates and display a notifcation + */ + function updateChecker($http, umbRequestHelper) { + return { + /** + * @ngdoc function + * @name umbraco.services.updateChecker#check + * @methodOf umbraco.services.updateChecker + * @function + * + * @description + * Called to load in the legacy tree js which is required on startup if a user is logged in or + * after login, but cannot be called until they are authenticated which is why it needs to be lazy loaded. + */ + check: function () { + return umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('updateCheckApiBaseUrl', 'GetCheck')), 'Failed to retrieve update status'); + } + }; + } + angular.module('umbraco.services').factory('updateChecker', updateChecker); + /** +* @ngdoc service +* @name umbraco.services.umbPropertyEditorHelper +* @description A helper object used for property editors +**/ + function umbPropEditorHelper() { + return { + /** + * @ngdoc function + * @name getImagePropertyValue + * @methodOf umbraco.services.umbPropertyEditorHelper + * @function + * + * @description + * Returns the correct view path for a property editor, it will detect if it is a full virtual path but if not then default to the internal umbraco one + * + * @param {string} input the view path currently stored for the property editor + */ + getViewPath: function (input, isPreValue) { + var path = String(input); + if (path.startsWith('/')) { + //This is an absolute path, so just leave it + return path; + } else { + if (path.indexOf('/') >= 0) { + //This is a relative path, so just leave it + return path; + } else { + if (!isPreValue) { + //i.e. views/propertyeditors/fileupload/fileupload.html + return 'views/propertyeditors/' + path + '/' + path + '.html'; + } else { + //i.e. views/prevalueeditors/requiredfield.html + return 'views/prevalueeditors/' + path + '.html'; + } + } + } + } + }; + } + angular.module('umbraco.services').factory('umbPropEditorHelper', umbPropEditorHelper); + /** +* @ngdoc service +* @name umbraco.services.queryStrings +* @description A helper used to get query strings in the real URL (not the hash URL) +**/ + function queryStrings($window) { + var pl = /\+/g; + // Regex for replacing addition symbol with a space + var search = /([^&=]+)=?([^&]*)/g; + var decode = function (s) { + return decodeURIComponent(s.replace(pl, ' ')); + }; + return { + getParams: function () { + var match; + var query = $window.location.search.substring(1); + var urlParams = {}; + while (match = search.exec(query)) { + urlParams[decode(match[1])] = decode(match[2]); + } + return urlParams; + } + }; + } + angular.module('umbraco.services').factory('queryStrings', queryStrings); + /** * @ngdoc service * @name umbraco.services.windowResizeListener * @function @@ -9605,63 +10042,61 @@ angular.module('umbraco.services').factory('umbDataFormatter', umbDataFormatter) * a $rootScope.$apply when changed and notify all listeners * */ -function windowResizeListener($rootScope) { - - var WinReszier = (function () { - var registered = []; - var inited = false; - var resize = _.debounce(function(ev) { - notify(); - }, 100); - var notify = function () { - var h = $(window).height(); - var w = $(window).width(); - //execute all registrations inside of a digest - $rootScope.$apply(function() { - for (var i = 0, cnt = registered.length; i < cnt; i++) { - registered[i].apply($(window), [{ width: w, height: h }]); + function windowResizeListener($rootScope) { + var WinReszier = function () { + var registered = []; + var inited = false; + var resize = _.debounce(function (ev) { + notify(); + }, 100); + var notify = function () { + var h = $(window).height(); + var w = $(window).width(); + //execute all registrations inside of a digest + $rootScope.$apply(function () { + for (var i = 0, cnt = registered.length; i < cnt; i++) { + registered[i].apply($(window), [{ + width: w, + height: h + }]); + } + }); + }; + return { + register: function (fn) { + registered.push(fn); + if (inited === false) { + $(window).bind('resize', resize); + inited = true; + } + }, + unregister: function (fn) { + var index = registered.indexOf(fn); + if (index > -1) { + registered.splice(index, 1); + } } - }); - }; + }; + }(); return { - register: function (fn) { - registered.push(fn); - if (inited === false) { - $(window).bind('resize', resize); - inited = true; - } - }, - unregister: function (fn) { - var index = registered.indexOf(fn); - if (index > -1) { - registered.splice(index, 1); - } - } - }; - }()); - - return { - - /** + /** * Register a callback for resizing * @param {Function} cb */ - register: function (cb) { - WinReszier.register(cb); - }, - - /** + register: function (cb) { + WinReszier.register(cb); + }, + /** * Removes a registered callback * @param {Function} cb */ - unregister: function(cb) { - WinReszier.unregister(cb); - } - - }; -} -angular.module('umbraco.services').factory('windowResizeListener', windowResizeListener); -/** + unregister: function (cb) { + WinReszier.unregister(cb); + } + }; + } + angular.module('umbraco.services').factory('windowResizeListener', windowResizeListener); + /** * @ngdoc service * @name umbraco.services.xmlhelper * @function @@ -9669,8 +10104,8 @@ angular.module('umbraco.services').factory('windowResizeListener', windowResizeL * @description * Used to convert legacy xml data to json and back again */ -function xmlhelper($http) { - /* + function xmlhelper($http) { + /* Copyright 2011 Abdulla Abdurakhmanov Original sources are available at https://code.google.com/p/x2js/ @@ -9686,358 +10121,302 @@ function xmlhelper($http) { See the License for the specific language governing permissions and limitations under the License. */ - - function X2JS() { - var VERSION = "1.0.11"; - var escapeMode = false; - - var DOMNodeTypes = { - ELEMENT_NODE: 1, - TEXT_NODE: 3, - CDATA_SECTION_NODE: 4, - DOCUMENT_NODE: 9 - }; - - function getNodeLocalName(node) { - var nodeLocalName = node.localName; - if (nodeLocalName == null) { - nodeLocalName = node.baseName; - } // Yeah, this is IE!! - - if (nodeLocalName === null || nodeLocalName === "") { - nodeLocalName = node.nodeName; - } // =="" is IE too - - return nodeLocalName; - } - - function getNodePrefix(node) { - return node.prefix; - } - - function escapeXmlChars(str) { - if (typeof (str) === "string") { - return str.replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"').replace(/'/g, ''').replace(/\//g, '/'); - } else { - return str; + function X2JS() { + var VERSION = '1.0.11'; + var escapeMode = false; + var DOMNodeTypes = { + ELEMENT_NODE: 1, + TEXT_NODE: 3, + CDATA_SECTION_NODE: 4, + DOCUMENT_NODE: 9 + }; + function getNodeLocalName(node) { + var nodeLocalName = node.localName; + if (nodeLocalName == null) { + nodeLocalName = node.baseName; + } + // Yeah, this is IE!! + if (nodeLocalName === null || nodeLocalName === '') { + nodeLocalName = node.nodeName; + } + // =="" is IE too + return nodeLocalName; } - } - - function unescapeXmlChars(str) { - return str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, "'").replace(///g, '\/'); - } - - function parseDOMChildren(node) { - var result, child, childName; - - if (node.nodeType === DOMNodeTypes.DOCUMENT_NODE) { - result = {}; - child = node.firstChild; - childName = getNodeLocalName(child); - result[childName] = parseDOMChildren(child); + function getNodePrefix(node) { + return node.prefix; + } + function escapeXmlChars(str) { + if (typeof str === 'string') { + return str.replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"').replace(/'/g, ''').replace(/\//g, '/'); + } else { + return str; + } + } + function unescapeXmlChars(str) { + return str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, '\'').replace(///g, '/'); + } + function parseDOMChildren(node) { + var result, child, childName; + if (node.nodeType === DOMNodeTypes.DOCUMENT_NODE) { + result = {}; + child = node.firstChild; + childName = getNodeLocalName(child); + result[childName] = parseDOMChildren(child); + return result; + } else { + if (node.nodeType === DOMNodeTypes.ELEMENT_NODE) { + result = {}; + result.__cnt = 0; + var nodeChildren = node.childNodes; + // Children nodes + for (var cidx = 0; cidx < nodeChildren.length; cidx++) { + child = nodeChildren.item(cidx); + // nodeChildren[cidx]; + childName = getNodeLocalName(child); + result.__cnt++; + if (result[childName] === null) { + result[childName] = parseDOMChildren(child); + result[childName + '_asArray'] = new Array(1); + result[childName + '_asArray'][0] = result[childName]; + } else { + if (result[childName] !== null) { + if (!(result[childName] instanceof Array)) { + var tmpObj = result[childName]; + result[childName] = []; + result[childName][0] = tmpObj; + result[childName + '_asArray'] = result[childName]; + } + } + var aridx = 0; + while (result[childName][aridx] !== null) { + aridx++; + } + result[childName][aridx] = parseDOMChildren(child); + } + } + // Attributes + for (var aidx = 0; aidx < node.attributes.length; aidx++) { + var attr = node.attributes.item(aidx); + // [aidx]; + result.__cnt++; + result['_' + attr.name] = attr.value; + } + // Node namespace prefix + var nodePrefix = getNodePrefix(node); + if (nodePrefix !== null && nodePrefix !== '') { + result.__cnt++; + result.__prefix = nodePrefix; + } + if (result.__cnt === 1 && result['#text'] !== null) { + result = result['#text']; + } + if (result['#text'] !== null) { + result.__text = result['#text']; + if (escapeMode) { + result.__text = unescapeXmlChars(result.__text); + } + delete result['#text']; + delete result['#text_asArray']; + } + if (result['#cdata-section'] != null) { + result.__cdata = result['#cdata-section']; + delete result['#cdata-section']; + delete result['#cdata-section_asArray']; + } + if (result.__text != null || result.__cdata != null) { + result.toString = function () { + return (this.__text != null ? this.__text : '') + (this.__cdata != null ? this.__cdata : ''); + }; + } + return result; + } else { + if (node.nodeType === DOMNodeTypes.TEXT_NODE || node.nodeType === DOMNodeTypes.CDATA_SECTION_NODE) { + return node.nodeValue; + } + } + } + } + function startTag(jsonObj, element, attrList, closed) { + var resultStr = '<' + (jsonObj != null && jsonObj.__prefix != null ? jsonObj.__prefix + ':' : '') + element; + if (attrList != null) { + for (var aidx = 0; aidx < attrList.length; aidx++) { + var attrName = attrList[aidx]; + var attrVal = jsonObj[attrName]; + resultStr += ' ' + attrName.substr(1) + '=\'' + attrVal + '\''; + } + } + if (!closed) { + resultStr += '>'; + } else { + resultStr += '/>'; + } + return resultStr; + } + function endTag(jsonObj, elementName) { + return ''; + } + function endsWith(str, suffix) { + return str.indexOf(suffix, str.length - suffix.length) !== -1; + } + function jsonXmlSpecialElem(jsonObj, jsonObjField) { + if (endsWith(jsonObjField.toString(), '_asArray') || jsonObjField.toString().indexOf('_') === 0 || jsonObj[jsonObjField] instanceof Function) { + return true; + } else { + return false; + } + } + function jsonXmlElemCount(jsonObj) { + var elementsCnt = 0; + if (jsonObj instanceof Object) { + for (var it in jsonObj) { + if (jsonXmlSpecialElem(jsonObj, it)) { + continue; + } + elementsCnt++; + } + } + return elementsCnt; + } + function parseJSONAttributes(jsonObj) { + var attrList = []; + if (jsonObj instanceof Object) { + for (var ait in jsonObj) { + if (ait.toString().indexOf('__') === -1 && ait.toString().indexOf('_') === 0) { + attrList.push(ait); + } + } + } + return attrList; + } + function parseJSONTextAttrs(jsonTxtObj) { + var result = ''; + if (jsonTxtObj.__cdata != null) { + result += ''; + } + if (jsonTxtObj.__text != null) { + if (escapeMode) { + result += escapeXmlChars(jsonTxtObj.__text); + } else { + result += jsonTxtObj.__text; + } + } return result; } - else { - - if (node.nodeType === DOMNodeTypes.ELEMENT_NODE) { - result = {}; - result.__cnt = 0; - var nodeChildren = node.childNodes; - - // Children nodes - for (var cidx = 0; cidx < nodeChildren.length; cidx++) { - child = nodeChildren.item(cidx); // nodeChildren[cidx]; - childName = getNodeLocalName(child); - - result.__cnt++; - if (result[childName] === null) { - result[childName] = parseDOMChildren(child); - result[childName + "_asArray"] = new Array(1); - result[childName + "_asArray"][0] = result[childName]; - } - else { - if (result[childName] !== null) { - if (!(result[childName] instanceof Array)) { - var tmpObj = result[childName]; - result[childName] = []; - result[childName][0] = tmpObj; - - result[childName + "_asArray"] = result[childName]; - } - } - var aridx = 0; - while (result[childName][aridx] !== null) { - aridx++; - } - - (result[childName])[aridx] = parseDOMChildren(child); - } - } - - // Attributes - for (var aidx = 0; aidx < node.attributes.length; aidx++) { - var attr = node.attributes.item(aidx); // [aidx]; - result.__cnt++; - result["_" + attr.name] = attr.value; - } - - // Node namespace prefix - var nodePrefix = getNodePrefix(node); - if (nodePrefix !== null && nodePrefix !== "") { - result.__cnt++; - result.__prefix = nodePrefix; - } - - if (result.__cnt === 1 && result["#text"] !== null) { - result = result["#text"]; - } - - if (result["#text"] !== null) { - result.__text = result["#text"]; - if (escapeMode) { - result.__text = unescapeXmlChars(result.__text); - } - - delete result["#text"]; - delete result["#text_asArray"]; - } - if (result["#cdata-section"] != null) { - result.__cdata = result["#cdata-section"]; - delete result["#cdata-section"]; - delete result["#cdata-section_asArray"]; - } - - if (result.__text != null || result.__cdata != null) { - result.toString = function () { - return (this.__text != null ? this.__text : '') + (this.__cdata != null ? this.__cdata : ''); - }; - } - return result; - } - else { - if (node.nodeType === DOMNodeTypes.TEXT_NODE || node.nodeType === DOMNodeTypes.CDATA_SECTION_NODE) { - return node.nodeValue; - } - } - } - } - - function startTag(jsonObj, element, attrList, closed) { - var resultStr = "<" + ((jsonObj != null && jsonObj.__prefix != null) ? (jsonObj.__prefix + ":") : "") + element; - if (attrList != null) { - for (var aidx = 0; aidx < attrList.length; aidx++) { - var attrName = attrList[aidx]; - var attrVal = jsonObj[attrName]; - resultStr += " " + attrName.substr(1) + "='" + attrVal + "'"; - } - } - if (!closed) { - resultStr += ">"; - } else { - resultStr += "/>"; - } - - return resultStr; - } - - function endTag(jsonObj, elementName) { - return ""; - } - - function endsWith(str, suffix) { - return str.indexOf(suffix, str.length - suffix.length) !== -1; - } - - function jsonXmlSpecialElem(jsonObj, jsonObjField) { - if (endsWith(jsonObjField.toString(), ("_asArray")) || jsonObjField.toString().indexOf("_") === 0 || (jsonObj[jsonObjField] instanceof Function)) { - return true; - } else { - return false; - } - } - - function jsonXmlElemCount(jsonObj) { - var elementsCnt = 0; - if (jsonObj instanceof Object) { - for (var it in jsonObj) { - if (jsonXmlSpecialElem(jsonObj, it)) { - continue; - } - elementsCnt++; - } - } - return elementsCnt; - } - - function parseJSONAttributes(jsonObj) { - var attrList = []; - if (jsonObj instanceof Object) { - for (var ait in jsonObj) { - if (ait.toString().indexOf("__") === -1 && ait.toString().indexOf("_") === 0) { - attrList.push(ait); - } - } - } - - return attrList; - } - - function parseJSONTextAttrs(jsonTxtObj) { - var result = ""; - - if (jsonTxtObj.__cdata != null) { - result += ""; - } - - if (jsonTxtObj.__text != null) { - if (escapeMode) { - result += escapeXmlChars(jsonTxtObj.__text); + function parseJSONTextObject(jsonTxtObj) { + var result = ''; + if (jsonTxtObj instanceof Object) { + result += parseJSONTextAttrs(jsonTxtObj); } else { - result += jsonTxtObj.__text; - } - } - return result; - } - - function parseJSONTextObject(jsonTxtObj) { - var result = ""; - - if (jsonTxtObj instanceof Object) { - result += parseJSONTextAttrs(jsonTxtObj); - } - else { - if (jsonTxtObj != null) { - if (escapeMode) { - result += escapeXmlChars(jsonTxtObj); - } else { - result += jsonTxtObj; - } - } - } - - - return result; - } - - function parseJSONArray(jsonArrRoot, jsonArrObj, attrList) { - var result = ""; - if (jsonArrRoot.length === 0) { - result += startTag(jsonArrRoot, jsonArrObj, attrList, true); - } - else { - for (var arIdx = 0; arIdx < jsonArrRoot.length; arIdx++) { - result += startTag(jsonArrRoot[arIdx], jsonArrObj, parseJSONAttributes(jsonArrRoot[arIdx]), false); - result += parseJSONObject(jsonArrRoot[arIdx]); - result += endTag(jsonArrRoot[arIdx], jsonArrObj); - } - } - return result; - } - - function parseJSONObject(jsonObj) { - var result = ""; - - var elementsCnt = jsonXmlElemCount(jsonObj); - - if (elementsCnt > 0) { - for (var it in jsonObj) { - if (jsonXmlSpecialElem(jsonObj, it)) { - continue; - } - - var subObj = jsonObj[it]; - var attrList = parseJSONAttributes(subObj); - - if (subObj === null || subObj === undefined) { - result += startTag(subObj, it, attrList, true); - } else { - if (subObj instanceof Object) { - - if (subObj instanceof Array) { - result += parseJSONArray(subObj, it, attrList); - } else { - var subObjElementsCnt = jsonXmlElemCount(subObj); - if (subObjElementsCnt > 0 || subObj.__text !== null || subObj.__cdata !== null) { - result += startTag(subObj, it, attrList, false); - result += parseJSONObject(subObj); - result += endTag(subObj, it); - } else { - result += startTag(subObj, it, attrList, true); - } - } - + if (jsonTxtObj != null) { + if (escapeMode) { + result += escapeXmlChars(jsonTxtObj); } else { - result += startTag(subObj, it, attrList, false); - result += parseJSONTextObject(subObj); - result += endTag(subObj, it); + result += jsonTxtObj; } } } + return result; } - result += parseJSONTextObject(jsonObj); - - return result; - } - - this.parseXmlString = function (xmlDocStr) { - var xmlDoc; - if (window.DOMParser) { - var parser = new window.DOMParser(); - xmlDoc = parser.parseFromString(xmlDocStr, "text/xml"); - } - else { - // IE :( - if (xmlDocStr.indexOf("") + 2); + function parseJSONArray(jsonArrRoot, jsonArrObj, attrList) { + var result = ''; + if (jsonArrRoot.length === 0) { + result += startTag(jsonArrRoot, jsonArrObj, attrList, true); + } else { + for (var arIdx = 0; arIdx < jsonArrRoot.length; arIdx++) { + result += startTag(jsonArrRoot[arIdx], jsonArrObj, parseJSONAttributes(jsonArrRoot[arIdx]), false); + result += parseJSONObject(jsonArrRoot[arIdx]); + result += endTag(jsonArrRoot[arIdx], jsonArrObj); + } } - xmlDoc = new ActiveXObject("Microsoft.XMLDOM"); - xmlDoc.async = "false"; - xmlDoc.loadXML(xmlDocStr); + return result; + } + function parseJSONObject(jsonObj) { + var result = ''; + var elementsCnt = jsonXmlElemCount(jsonObj); + if (elementsCnt > 0) { + for (var it in jsonObj) { + if (jsonXmlSpecialElem(jsonObj, it)) { + continue; + } + var subObj = jsonObj[it]; + var attrList = parseJSONAttributes(subObj); + if (subObj === null || subObj === undefined) { + result += startTag(subObj, it, attrList, true); + } else { + if (subObj instanceof Object) { + if (subObj instanceof Array) { + result += parseJSONArray(subObj, it, attrList); + } else { + var subObjElementsCnt = jsonXmlElemCount(subObj); + if (subObjElementsCnt > 0 || subObj.__text !== null || subObj.__cdata !== null) { + result += startTag(subObj, it, attrList, false); + result += parseJSONObject(subObj); + result += endTag(subObj, it); + } else { + result += startTag(subObj, it, attrList, true); + } + } + } else { + result += startTag(subObj, it, attrList, false); + result += parseJSONTextObject(subObj); + result += endTag(subObj, it); + } + } + } + } + result += parseJSONTextObject(jsonObj); + return result; + } + this.parseXmlString = function (xmlDocStr) { + var xmlDoc; + if (window.DOMParser) { + var parser = new window.DOMParser(); + xmlDoc = parser.parseFromString(xmlDocStr, 'text/xml'); + } else { + // IE :( + if (xmlDocStr.indexOf('') + 2); + } + xmlDoc = new ActiveXObject('Microsoft.XMLDOM'); + xmlDoc.async = 'false'; + xmlDoc.loadXML(xmlDocStr); + } + return xmlDoc; + }; + this.xml2json = function (xmlDoc) { + return parseDOMChildren(xmlDoc); + }; + this.xml_str2json = function (xmlDocStr) { + var xmlDoc = this.parseXmlString(xmlDocStr); + return this.xml2json(xmlDoc); + }; + this.json2xml_str = function (jsonObj) { + return parseJSONObject(jsonObj); + }; + this.json2xml = function (jsonObj) { + var xmlDocStr = this.json2xml_str(jsonObj); + return this.parseXmlString(xmlDocStr); + }; + this.getVersion = function () { + return VERSION; + }; + this.escapeMode = function (enabled) { + escapeMode = enabled; + }; + } + var x2js = new X2JS(); + return { + /** Called to load in the legacy tree js which is required on startup if a user is logged in or + after login, but cannot be called until they are authenticated which is why it needs to be lazy loaded. */ + toJson: function (xml) { + var json = x2js.xml_str2json(xml); + return json; + }, + fromJson: function (json) { + var xml = x2js.json2xml_str(json); + return xml; } - return xmlDoc; - }; - - this.xml2json = function (xmlDoc) { - return parseDOMChildren(xmlDoc); - }; - - this.xml_str2json = function (xmlDocStr) { - var xmlDoc = this.parseXmlString(xmlDocStr); - return this.xml2json(xmlDoc); - }; - - this.json2xml_str = function (jsonObj) { - return parseJSONObject(jsonObj); - }; - - this.json2xml = function (jsonObj) { - var xmlDocStr = this.json2xml_str(jsonObj); - return this.parseXmlString(xmlDocStr); - }; - - this.getVersion = function () { - return VERSION; - }; - - this.escapeMode = function (enabled) { - escapeMode = enabled; }; } - - var x2js = new X2JS(); - return { - /** Called to load in the legacy tree js which is required on startup if a user is logged in or - after login, but cannot be called until they are authenticated which is why it needs to be lazy loaded. */ - toJson: function (xml) { - var json = x2js.xml_str2json(xml); - return json; - }, - fromJson: function (json) { - var xml = x2js.json2xml_str(json); - return xml; - } - }; -} -angular.module('umbraco.services').factory('xmlhelper', xmlhelper); - -})(); \ No newline at end of file + angular.module('umbraco.services').factory('xmlhelper', xmlhelper); +}()); \ No newline at end of file diff --git a/WebCms/Umbraco/Js/web.config b/WebCms/Umbraco/Js/web.config new file mode 100644 index 0000000..6903c39 --- /dev/null +++ b/WebCms/Umbraco/Js/web.config @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/WebCms/Umbraco/PartialViewMacros/Templates/Breadcrumb.cshtml b/WebCms/Umbraco/PartialViewMacros/Templates/Breadcrumb.cshtml index 8ba3dce..7be87c0 100644 --- a/WebCms/Umbraco/PartialViewMacros/Templates/Breadcrumb.cshtml +++ b/WebCms/Umbraco/PartialViewMacros/Templates/Breadcrumb.cshtml @@ -1,24 +1,25 @@ -@inherits Umbraco.Web.Macros.PartialViewMacroPage +@using Umbraco.Web +@inherits Umbraco.Web.Macros.PartialViewMacroPage @* - This snippet makes a breadcrumb of parents using an unordered html list. + This snippet makes a breadcrumb of parents using an unordered HTML list. How it works: - It uses the Ancestors() method to get all parents and then generates links so the visitor can go back - Finally it outputs the name of the current page (without a link) *@ -@{ var selection = CurrentPage.Ancestors(); } +@{ var selection = Model.Content.Ancestors().ToArray(); } -@if (selection.Any()) +@if (selection.Length > 0) {
    } \ No newline at end of file diff --git a/WebCms/Umbraco/PartialViewMacros/Templates/Gallery.cshtml b/WebCms/Umbraco/PartialViewMacros/Templates/Gallery.cshtml index 52d87c5..a34eab0 100644 --- a/WebCms/Umbraco/PartialViewMacros/Templates/Gallery.cshtml +++ b/WebCms/Umbraco/PartialViewMacros/Templates/Gallery.cshtml @@ -1,50 +1,47 @@ @inherits Umbraco.Web.Macros.PartialViewMacroPage @* - Macro to display a gallery of images from media the media section. + Macro to display a gallery of images from the Media section. Works with either a 'Single Media Picker' or a 'Multiple Media Picker' macro parameter (see below). How it works: - Confirm the macro parameter has been passed in with a value - - Loop through all the media Id's passed in (might be a single item, might be many) + - Loop through all the media Ids passed in (might be a single item, might be many) - Display any individual images, as well as any folders of images Macro Parameters To Create, for this macro to work: Alias:mediaIds Name:Select folders and/or images Type: Multiple Media Picker - Type: (note: you can use a Single Media Picker if that's more appropriate to your needs) + Type: (note: You can use a Single Media Picker if that's more appropriate to your needs) *@ -@{ var mediaIds = Model.MacroParameters["mediaIds"]; } +@{ var mediaIds = Model.MacroParameters["mediaIds"] as string; } + @if (mediaIds != null) { -
      - @foreach (var mediaId in mediaIds.ToString().Split(',')) +
      + @foreach (var mediaId in mediaIds.Split(',')) { - var media = Umbraco.Media(mediaId); + var media = Umbraco.TypedMedia(mediaId); @* a single image *@ if (media.DocumentTypeAlias == "Image") { - @Render(media); + @Render(media as Image); } @* a folder with images under it *@ - if (media.Children("Image").Any()) + foreach (var image in media.Children()) { - foreach (var image in media.Children("Image")) - { - @Render(image); - } + @Render(image); } - } -
    +
    } -@helper Render(dynamic item) +@helper Render(Image item) { -
  • - - @item.Name +
  • +
    } \ No newline at end of file diff --git a/WebCms/Umbraco/PartialViewMacros/Templates/ListAncestorsFromCurrentPage.cshtml b/WebCms/Umbraco/PartialViewMacros/Templates/ListAncestorsFromCurrentPage.cshtml index 9d1284b..f022aa5 100644 --- a/WebCms/Umbraco/PartialViewMacros/Templates/ListAncestorsFromCurrentPage.cshtml +++ b/WebCms/Umbraco/PartialViewMacros/Templates/ListAncestorsFromCurrentPage.cshtml @@ -1,24 +1,25 @@ +@using Umbraco.Web @inherits Umbraco.Web.Macros.PartialViewMacroPage @* - This snippet makes a list of links to the of parents of the current page using an unordered html list. + This snippet makes a list of links to the of parents of the current page using an unordered HTML list. How it works: - It uses the Ancestors() method to get all parents and then generates links so the visitor can go back - Finally it outputs the name of the current page (without a link) *@ -@{ var selection = CurrentPage.Ancestors(); } +@{ var selection = Model.Content.Ancestors().ToArray(); } -@if (selection.Any()) +@if (selection.Length > 0) {
      @* For each page in the ancestors collection which have been ordered by Level (so we start with the highest top node first) *@ - @foreach (var item in selection.OrderBy("Level")) + @foreach (var item in selection.OrderBy(x => x.Level)) {
    • @item.Name »
    • } @* Display the current page as the last item in the list *@ -
    • @CurrentPage.Name
    • +
    • @Model.Content.Name
    } diff --git a/WebCms/Umbraco/PartialViewMacros/Templates/ListChildPagesFromChangeableSource.cshtml b/WebCms/Umbraco/PartialViewMacros/Templates/ListChildPagesFromChangeableSource.cshtml index ea45c13..6fbe125 100644 --- a/WebCms/Umbraco/PartialViewMacros/Templates/ListChildPagesFromChangeableSource.cshtml +++ b/WebCms/Umbraco/PartialViewMacros/Templates/ListChildPagesFromChangeableSource.cshtml @@ -1,3 +1,4 @@ +@using Umbraco.Web @inherits Umbraco.Web.Macros.PartialViewMacroPage @* @@ -13,20 +14,19 @@ *@ @{ var startNodeId = Model.MacroParameters["startNodeId"]; } + @if (startNodeId != null) { @* Get the starting page *@ - var startNode = Umbraco.Content(startNodeId); - var selection = startNode.Children.Where("Visible"); + var startNode = Umbraco.TypedContent(startNodeId); + var selection = startNode.Children.Where(x => x.IsVisible()).ToArray(); - if (selection.Any()) + if (selection.Length > 0) { } diff --git a/WebCms/Umbraco/PartialViewMacros/Templates/ListChildPagesFromCurrentPage.cshtml b/WebCms/Umbraco/PartialViewMacros/Templates/ListChildPagesFromCurrentPage.cshtml index 78a3b0d..e6606d6 100644 --- a/WebCms/Umbraco/PartialViewMacros/Templates/ListChildPagesFromCurrentPage.cshtml +++ b/WebCms/Umbraco/PartialViewMacros/Templates/ListChildPagesFromCurrentPage.cshtml @@ -1,8 +1,17 @@ +@using Umbraco.Web @inherits Umbraco.Web.Macros.PartialViewMacroPage -@{ var selection = CurrentPage.Children.Where("Visible"); } +@* + This snippet makes a list of links to the of children of the current page using an unordered HTML list. -@if (selection.Any()) + How it works: + - It uses the Children method to get all child pages + - It then generates links so the visitor can go to each page +*@ + +@{ var selection = Model.Content.Children.Where(x => x.IsVisible()).ToArray(); } + +@if (selection.Length > 0) {
      @foreach (var item in selection) diff --git a/WebCms/Umbraco/PartialViewMacros/Templates/ListChildPagesOrderedByDate.cshtml b/WebCms/Umbraco/PartialViewMacros/Templates/ListChildPagesOrderedByDate.cshtml index 0a44941..2c2cc44 100644 --- a/WebCms/Umbraco/PartialViewMacros/Templates/ListChildPagesOrderedByDate.cshtml +++ b/WebCms/Umbraco/PartialViewMacros/Templates/ListChildPagesOrderedByDate.cshtml @@ -1,11 +1,23 @@ +@using Umbraco.Web @inherits Umbraco.Web.Macros.PartialViewMacroPage -@{ var selection = CurrentPage.Children.Where("Visible").OrderBy("CreateDate desc"); } -@* OrderBy() takes the property to sort by and optionally order desc/asc *@ +@* + This snippet makes a list of links to the of children of the current page using an unordered HTML list. -
        - @foreach (var item in selection) - { -
      • @item.Name
      • - } -
      + How it works: + - It uses the Children method to get all child pages + - It then uses the OrderByDescending() method, which takes the property to sort. In this case the page's creation date. + - It then generates links so the visitor can go to each page +*@ + +@{ var selection = Model.Content.Children.Where(x => x.IsVisible()).OrderByDescending(x => x.CreateDate).ToArray(); } + +@if (selection.Length > 0) +{ +
        + @foreach (var item in selection) + { +
      • @item.Name
      • + } +
      +} diff --git a/WebCms/Umbraco/PartialViewMacros/Templates/ListChildPagesOrderedByName.cshtml b/WebCms/Umbraco/PartialViewMacros/Templates/ListChildPagesOrderedByName.cshtml index 8f33316..dd5e00c 100644 --- a/WebCms/Umbraco/PartialViewMacros/Templates/ListChildPagesOrderedByName.cshtml +++ b/WebCms/Umbraco/PartialViewMacros/Templates/ListChildPagesOrderedByName.cshtml @@ -1,11 +1,23 @@ +@using Umbraco.Web @inherits Umbraco.Web.Mvc.UmbracoTemplatePage -@{ var selection = CurrentPage.Children.Where("Visible").OrderBy("Name"); } -@* OrderBy() takes the property to sort by *@ +@* + This snippet makes a list of links to the of children of the current page using an unordered HTML list. -
        - @foreach (var item in selection) - { -
      • @item.Name
      • - } -
      + How it works: + - It uses the Children method to get all child pages + - It then uses the OrderBy() method, which takes the property to sort. In this case, the page's name. + - It then generates links so the visitor can go to each page +*@ + +@{ var selection = Model.Content.Children.Where(x => x.IsVisible()).OrderBy(x => x.Name).ToArray(); } + +@if (selection.Length > 0) +{ +
        + @foreach (var item in selection) + { +
      • @item.Name
      • + } +
      +} \ No newline at end of file diff --git a/WebCms/Umbraco/PartialViewMacros/Templates/ListChildPagesOrderedByProperty.cshtml b/WebCms/Umbraco/PartialViewMacros/Templates/ListChildPagesOrderedByProperty.cshtml index b989e94..db0f91c 100644 --- a/WebCms/Umbraco/PartialViewMacros/Templates/ListChildPagesOrderedByProperty.cshtml +++ b/WebCms/Umbraco/PartialViewMacros/Templates/ListChildPagesOrderedByProperty.cshtml @@ -1,3 +1,4 @@ +@using Umbraco.Web @inherits Umbraco.Web.Macros.PartialViewMacroPage @* @@ -15,12 +16,15 @@ @{ var propertyAlias = Model.MacroParameters["propertyAlias"]; } @if (propertyAlias != null) { - var selection = CurrentPage.Children.Where("Visible").OrderBy(propertyAlias); + var selection = Model.Content.Children.Where(x => x.IsVisible()).OrderBy(x => x.GetPropertyValue(propertyAlias.ToString())).ToArray(); -
        - @foreach (var item in selection) - { -
      • @item.Name
      • - } -
      + if (selection.Length > 0) + { +
        + @foreach (var item in selection) + { +
      • @item.Name
      • + } +
      + } } diff --git a/WebCms/Umbraco/PartialViewMacros/Templates/ListChildPagesWithDoctype.cshtml b/WebCms/Umbraco/PartialViewMacros/Templates/ListChildPagesWithDoctype.cshtml index eb9812e..100d502 100644 --- a/WebCms/Umbraco/PartialViewMacros/Templates/ListChildPagesWithDoctype.cshtml +++ b/WebCms/Umbraco/PartialViewMacros/Templates/ListChildPagesWithDoctype.cshtml @@ -1,19 +1,17 @@ +@using Umbraco.Core.Models +@using Umbraco.Web @inherits Umbraco.Web.Macros.PartialViewMacroPage @* - This snippet shows how simple it is to fetch only children of a certain Document Type using Razor. - Be sure to change "DocumentTypeAlias" below to match your needs, such as "TextPage" or "NewsItems". + This snippet shows how simple it is to fetch only children of a certain Document Type. + + Be sure to change "IPublishedContent" below to match your needs, such as "TextPage" or "NewsItem". (You can find the alias of your Document Type by editing it in the Settings section) *@ -@{ var selection = CurrentPage.Children("DocumentTypeAlias").Where("Visible"); } -@* - As an example of more querying, if you have a true/false property with the alias of shouldBeFeatured: - var selection= CurrentPage.Children("DocumentTypeAlias").Where("shouldBeFeatured == true").Where("Visible"); -*@ +@{ var selection = Model.Content.Children().Where(x => x.IsVisible()).ToArray(); } - -@if (selection.Any()) +@if (selection.Length > 0) {
        @foreach (var item in selection) diff --git a/WebCms/Umbraco/PartialViewMacros/Templates/ListDescendantsFromCurrentPage.cshtml b/WebCms/Umbraco/PartialViewMacros/Templates/ListDescendantsFromCurrentPage.cshtml index 68f4b38..1e75282 100644 --- a/WebCms/Umbraco/PartialViewMacros/Templates/ListDescendantsFromCurrentPage.cshtml +++ b/WebCms/Umbraco/PartialViewMacros/Templates/ListDescendantsFromCurrentPage.cshtml @@ -1,31 +1,36 @@ @inherits Umbraco.Web.Mvc.UmbracoTemplatePage +@using Umbraco.Core.Models +@using Umbraco.Web @* This snippet creates links for every single page (no matter how deep) below - the page currently being viewed by the website visitor, displayed as nested unordered html lists. + the page currently being viewed by the website visitor, displayed as nested unordered HTML lists. *@ -@{ var selection = CurrentPage.Children.Where("Visible"); } +@{ var selection = Model.Content.Children.Where(x => x.IsVisible()).ToArray(); } @* Ensure that the Current Page has children *@ -@if (selection.Any()) +@if (selection.Length > 0) { @* Get the first page in the children, where the property umbracoNaviHide is not True *@ - var naviLevel = CurrentPage.FirstChild().Where("Visible").Level; + var naviLevel = selection[0].Level; @* Add in level for a CSS hook *@ -
          - @* For each child page where the property umbracoNaviHide is not True *@ +
            + @* Loop through the selection *@ @foreach (var item in selection) {
          • @item.Name @* if this child page has any children, where the property umbracoNaviHide is not True *@ - @if (item.Children.Where("Visible").Any()) - { - @* Call our helper to display the children *@ - @childPages(item.Children) + @{ + var children = item.Children.Where(x => x.IsVisible()).ToArray(); + if (children.Length > 0) + { + @* Call our helper to display the children *@ + @ChildPages(children) + } }
          • } @@ -33,26 +38,29 @@ } -@helper childPages(dynamic selection) +@helper ChildPages(IPublishedContent[] selection) { @* Ensure that we have a collection of pages *@ - if (selection.Any()) + if (selection.Length > 0) { - @* Get the first page in pages and get the level *@ - var naviLevel = selection.First().Level; + @* Get the first page in pages and get the level *@ + var naviLevel = selection[0].Level; - @* Add in level for a CSS hook *@ + @* Add in level for a CSS hook *@
              - @foreach (var item in selection.Where("Visible")) + @foreach (var item in selection) {
            • @item.Name - @* if the this page has any children, where the property umbracoNaviHide is not True *@ - @if (item.Children.Where("Visible").Any()) - { - @* Call our helper to display the children *@ - @childPages(item.Children) + @* if the page has any children, where the property umbracoNaviHide is not True *@ + @{ + var children = item.Children.Where(x => x.IsVisible()).ToArray(); + if (children.Length > 0) + { + @* Recurse and call our helper to display the children *@ + @ChildPages(children) + } }
            • } diff --git a/WebCms/Umbraco/PartialViewMacros/Templates/ListImagesFromMediaFolder.cshtml b/WebCms/Umbraco/PartialViewMacros/Templates/ListImagesFromMediaFolder.cshtml index 5d318aa..3303418 100644 --- a/WebCms/Umbraco/PartialViewMacros/Templates/ListImagesFromMediaFolder.cshtml +++ b/WebCms/Umbraco/PartialViewMacros/Templates/ListImagesFromMediaFolder.cshtml @@ -5,7 +5,7 @@ How it works: - Confirm the macro parameter has been passed in with a value - - Loop through all the media Id's passed in (might be a single item, might be many) + - Loop through all the media Ids passed in (might be a single item, might be many) - Display any individual images, as well as any folders of images Macro Parameters To Create, for this macro to work: @@ -15,17 +15,17 @@ @{ var mediaId = Model.MacroParameters["mediaId"]; } @if (mediaId != null) { - @* Get all the media item associated with the id passed in *@ - var media = Umbraco.Media(mediaId); - var selection = media.Children("Image"); + @* Get the media item associated with the id passed in *@ + var media = Umbraco.TypedMedia(mediaId); + var selection = media.Children().ToArray(); - if (selection.Any()) + if (selection.Length > 0) {
                @foreach (var item in selection) {
              • - @item.Name + @item.Name
              • }
              diff --git a/WebCms/Umbraco/PartialViewMacros/Templates/MultinodeTree-picker.cshtml b/WebCms/Umbraco/PartialViewMacros/Templates/MultinodeTree-picker.cshtml index ca79ad5..a8df4b9 100644 --- a/WebCms/Umbraco/PartialViewMacros/Templates/MultinodeTree-picker.cshtml +++ b/WebCms/Umbraco/PartialViewMacros/Templates/MultinodeTree-picker.cshtml @@ -1,21 +1,25 @@ +@using Umbraco.Core.Models +@using Umbraco.Web @inherits Umbraco.Web.Macros.PartialViewMacroPage @* - This snippet lists the items from a Multinode tree picker, using the pickers default settings. - Content Values stored as xml. + This snippet lists the items from a Multinode tree picker, using the picker's default settings. + Content Values stored as XML. To get it working with any site's data structure, set the selection equal to the property which has the multinode treepicker (so: replace "PropertyWithPicker" with the alias of your property). *@ -@{ var selection = CurrentPage.PropertyWithPicker.Split(','); } +@{ var selection = Model.Content.GetPropertyValue>("PropertyWithPicker").ToArray(); } -
                - @foreach (var id in selection) - { - var item = Umbraco.Content(id); -
              • - @item.Name -
              • - } -
              \ No newline at end of file +@if (selection.Length > 0) +{ +
                + @foreach (var item in selection) + { +
              • + @item.Name +
              • + } +
              +} \ No newline at end of file diff --git a/WebCms/Umbraco/PartialViewMacros/Templates/Navigation.cshtml b/WebCms/Umbraco/PartialViewMacros/Templates/Navigation.cshtml index 19c0199..9775c81 100644 --- a/WebCms/Umbraco/PartialViewMacros/Templates/Navigation.cshtml +++ b/WebCms/Umbraco/PartialViewMacros/Templates/Navigation.cshtml @@ -1,18 +1,22 @@ +@using Umbraco.Web @inherits Umbraco.Web.Macros.PartialViewMacroPage @* This snippet displays a list of links of the pages immediately under the top-most page in the content tree. This is the home page for a standard website. - It also highlights the current active page/section in the navigation with the css class "current". + It also highlights the current active page/section in the navigation with the CSS class "current". *@ -@{ var selection = CurrentPage.Site().Children.Where("Visible"); } +@{ var selection = Model.Content.Site().Children.Where(x => x.IsVisible()).ToArray(); } -
                - @foreach (var item in selection) - { -
              • - @item.Name -
              • - } -
              +@if (selection.Length > 0) +{ +
                + @foreach (var item in selection) + { +
              • + @item.Name +
              • + } +
              +} diff --git a/WebCms/Umbraco/PartialViewMacros/Templates/RegisterMember.cshtml b/WebCms/Umbraco/PartialViewMacros/Templates/RegisterMember.cshtml index 3b486e6..a3fb34d 100644 --- a/WebCms/Umbraco/PartialViewMacros/Templates/RegisterMember.cshtml +++ b/WebCms/Umbraco/PartialViewMacros/Templates/RegisterMember.cshtml @@ -47,7 +47,7 @@ @if (success) { @* This message will show if RedirectOnSucces is set to false (default) *@ -

              Registration succeeeded.

              +

              Registration succeeded.

              } else { @@ -101,4 +101,4 @@ else } -} \ No newline at end of file +} diff --git a/WebCms/Umbraco/PartialViewMacros/Templates/SiteMap.cshtml b/WebCms/Umbraco/PartialViewMacros/Templates/SiteMap.cshtml index 0fd074c..46ead8a 100644 --- a/WebCms/Umbraco/PartialViewMacros/Templates/SiteMap.cshtml +++ b/WebCms/Umbraco/PartialViewMacros/Templates/SiteMap.cshtml @@ -1,13 +1,15 @@ +@using Umbraco.Core.Models +@using Umbraco.Web @inherits Umbraco.Web.Macros.PartialViewMacroPage @* - This snippet makes a list of links of all visible pages of the site, as nested unordered html lists. + This snippet makes a list of links of all visible pages of the site, as nested unordered HTML lists. How it works: - It uses a custom Razor helper called Traverse() to select and display the markup and links. *@ -@{ var selection = CurrentPage.Site(); } +@{ var selection = Model.Content.Site(); }
              @* Render the sitemap by passing the root node to the traverse helper, below *@ @@ -15,17 +17,17 @@
              -@* Helper method to travers through all descendants *@ -@helper Traverse(dynamic node) +@* Helper method to traverse through all descendants *@ +@helper Traverse(IPublishedContent node) { @* Update the level to reflect how deep you want the sitemap to go *@ - var maxLevelForSitemap = 4; + const int maxLevelForSitemap = 4; @* Select visible children *@ - var selection = node.Children.Where("Visible").Where("Level <= " + maxLevelForSitemap); + var selection = node.Children.Where(x => x.IsVisible() && x.Level <= maxLevelForSitemap).ToArray(); @* If any items are returned, render a list *@ - if (selection.Any()) + if (selection.Length > 0) {
                @foreach (var item in selection) diff --git a/WebCms/Umbraco/Translation/default.aspx b/WebCms/Umbraco/Translation/default.aspx index d966259..b89c28c 100644 --- a/WebCms/Umbraco/Translation/default.aspx +++ b/WebCms/Umbraco/Translation/default.aspx @@ -16,7 +16,7 @@

                - When you have completed the translation. Upload the edited XML file here. The related translation tasks will automaticly be closed when a file is uploaded. + When you have completed the translation. Upload the edited XML file here. The related translation tasks will automatically be closed when a file is uploaded.

                diff --git a/WebCms/Umbraco/Views/Default.cshtml b/WebCms/Umbraco/Views/Default.cshtml index 6fd0a23..6a22113 100644 --- a/WebCms/Umbraco/Views/Default.cshtml +++ b/WebCms/Umbraco/Views/Default.cshtml @@ -1,19 +1,10 @@ -@using System.Collections -@using System.Net.Http -@using System.Web.Mvc.Html -@using Umbraco.Core +@using Umbraco.Core @using ClientDependency.Core @using ClientDependency.Core.Mvc -@using Microsoft.Owin.Security -@using Newtonsoft.Json -@using Newtonsoft.Json.Linq @using Umbraco.Core.IO @using Umbraco.Web -@using Umbraco.Web.Editors @using umbraco - @inherits System.Web.Mvc.WebViewPage - @{ var isDebug = false; if (Request.RawUrl.IndexOf('?') >= 0) @@ -32,9 +23,7 @@ .RequiresCss("lib/bootstrap-social/bootstrap-social.css", "Umbraco") .RequiresCss("lib/font-awesome/css/font-awesome.min.css", "Umbraco"); } - - @@ -43,49 +32,55 @@ + Umbraco @Html.RenderCssHere( new BasicPath("Umbraco", IOHelper.ResolveUrl(SystemDirectories.Umbraco)), new BasicPath("UmbracoClient", IOHelper.ResolveUrl(SystemDirectories.UmbracoClient))) - - - - -
                + - +
                -
                -
                -
                -
                + + - +
                + + + +
                +
                + +
                +
                + + + + + + +
                - @Html.BareMinimumServerVariablesScript(Url, Url.Action("ExternalLogin", "BackOffice", new { area = ViewBag.UmbracoPath })) - - - - - @*And finally we can load in our angular app*@ - - - - @if (isDebug) - { - @Html.RenderProfiler() - } + + + @Html.BareMinimumServerVariablesScript(Url, ApplicationContext.Current, Url.Action("ExternalLogin", "BackOffice", new { area = ViewBag.UmbracoPath })) + + + + + + + @if (isDebug) + { + + @Html.RenderProfiler() + } diff --git a/WebCms/Umbraco/Views/Preview/Background.cshtml b/WebCms/Umbraco/Views/Preview/Background.cshtml new file mode 100644 index 0000000..6e3793d --- /dev/null +++ b/WebCms/Umbraco/Views/Preview/Background.cshtml @@ -0,0 +1,57 @@ +@inherits System.Web.Mvc.WebViewPage +
                + +
                +
                +
                + +
                +
                + + +
                +
                + +
                + + diff --git a/WebCms/Umbraco/Views/Preview/Border.cshtml b/WebCms/Umbraco/Views/Preview/Border.cshtml new file mode 100644 index 0000000..40d46a0 --- /dev/null +++ b/WebCms/Umbraco/Views/Preview/Border.cshtml @@ -0,0 +1,20 @@ +@inherits System.Web.Mvc.WebViewPage +
                + +
                +
                  +
                • +
                +
                + +
                +
                + + +
                + +
                +
                +
                + +
                diff --git a/WebCms/Umbraco/Views/Preview/Color.cshtml b/WebCms/Umbraco/Views/Preview/Color.cshtml new file mode 100644 index 0000000..02213f2 --- /dev/null +++ b/WebCms/Umbraco/Views/Preview/Color.cshtml @@ -0,0 +1,4 @@ +@inherits System.Web.Mvc.WebViewPage +
                +
                +
                diff --git a/WebCms/Umbraco/Views/Preview/Googlefontpicker.cshtml b/WebCms/Umbraco/Views/Preview/Googlefontpicker.cshtml new file mode 100644 index 0000000..e1e61e3 --- /dev/null +++ b/WebCms/Umbraco/Views/Preview/Googlefontpicker.cshtml @@ -0,0 +1,34 @@ +@inherits System.Web.Mvc.WebViewPage +
                + +
                +
                + Aa + {{ item.values.fontFamily }} + +
                +
                + +
                + + diff --git a/WebCms/Umbraco/Views/Preview/GridRow.cshtml b/WebCms/Umbraco/Views/Preview/GridRow.cshtml new file mode 100644 index 0000000..52f24cb --- /dev/null +++ b/WebCms/Umbraco/Views/Preview/GridRow.cshtml @@ -0,0 +1,8 @@ +@inherits System.Web.Mvc.WebViewPage +
                + +
                + +
                + +
                diff --git a/WebCms/Umbraco/Views/Preview/Index.cshtml b/WebCms/Umbraco/Views/Preview/Index.cshtml new file mode 100644 index 0000000..0220906 --- /dev/null +++ b/WebCms/Umbraco/Views/Preview/Index.cshtml @@ -0,0 +1,151 @@ +@using System.Web.Mvc.Html +@inherits System.Web.Mvc.WebViewPage +@{ + var disableDevicePreview = Model.DisableDevicePreview.ToString().ToLowerInvariant(); +} + + + + Umbraco Canvas Designer + + + + + + +
                + + @if (string.IsNullOrWhiteSpace(Model.PreviewExtendedHeaderView) == false) + { + @Html.Partial(Model.PreviewExtendedHeaderView) + } + +
                + +
                +
                +
                +
                + +
                + + +
                + +
                +
                +
                +

                Select

                +
                +
                +
                  +
                • + {{configItem.name}} +
                • +
                +
                +
                +
                +
                +

                {{configItem.name}}

                +
                +
                +
                +

                + {{category}} + + +

                +
                +
                +
                {{item.name}}
                +
                +
                +
                +
                +
                +
                + +
                +
                +
                +
                +

                Styles saved and published

                +
                + + + + diff --git a/WebCms/Umbraco/Views/Preview/Layout.cshtml b/WebCms/Umbraco/Views/Preview/Layout.cshtml new file mode 100644 index 0000000..3df51ae --- /dev/null +++ b/WebCms/Umbraco/Views/Preview/Layout.cshtml @@ -0,0 +1,10 @@ +@inherits System.Web.Mvc.WebViewPage +
                + +
                + Box + Wide + Full +
                + +
                diff --git a/WebCms/Umbraco/Views/Preview/Margin.cshtml b/WebCms/Umbraco/Views/Preview/Margin.cshtml new file mode 100644 index 0000000..f14e592 --- /dev/null +++ b/WebCms/Umbraco/Views/Preview/Margin.cshtml @@ -0,0 +1,14 @@ +@inherits System.Web.Mvc.WebViewPage +
                + +
                +
                  +
                • +
                +
                + +
                +
                +
                + +
                diff --git a/WebCms/Umbraco/Views/Preview/Padding.cshtml b/WebCms/Umbraco/Views/Preview/Padding.cshtml new file mode 100644 index 0000000..3f2c440 --- /dev/null +++ b/WebCms/Umbraco/Views/Preview/Padding.cshtml @@ -0,0 +1,14 @@ +@inherits System.Web.Mvc.WebViewPage +
                + +
                +
                  +
                • +
                +
                + +
                +
                +
                + +
                diff --git a/WebCms/Umbraco/Views/Preview/Radius.cshtml b/WebCms/Umbraco/Views/Preview/Radius.cshtml new file mode 100644 index 0000000..1e8a96b --- /dev/null +++ b/WebCms/Umbraco/Views/Preview/Radius.cshtml @@ -0,0 +1,21 @@ +@inherits System.Web.Mvc.WebViewPage +
                + +
                +
                  + +
                • + + + + +
                • + +
                +
                + +
                +
                +
                + +
                diff --git a/WebCms/Umbraco/Views/Preview/Shadow.cshtml b/WebCms/Umbraco/Views/Preview/Shadow.cshtml new file mode 100644 index 0000000..6b9d476 --- /dev/null +++ b/WebCms/Umbraco/Views/Preview/Shadow.cshtml @@ -0,0 +1,8 @@ +@inherits System.Web.Mvc.WebViewPage +
                + +
                +
                +
                + +
                diff --git a/WebCms/Umbraco/Views/Preview/Slider.cshtml b/WebCms/Umbraco/Views/Preview/Slider.cshtml new file mode 100644 index 0000000..414159d --- /dev/null +++ b/WebCms/Umbraco/Views/Preview/Slider.cshtml @@ -0,0 +1,8 @@ +@inherits System.Web.Mvc.WebViewPage +
                + +
                +
                +
                + +
                diff --git a/WebCms/Umbraco/Views/Preview/web.config b/WebCms/Umbraco/Views/Preview/web.config new file mode 100644 index 0000000..5ab8bbc --- /dev/null +++ b/WebCms/Umbraco/Views/Preview/web.config @@ -0,0 +1,41 @@ + + + + + +
                +
                + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/WebCms/Umbraco/Views/common/dialogs/content/edit.html b/WebCms/Umbraco/Views/common/dialogs/content/edit.html index 55e0cad..eef28ae 100644 --- a/WebCms/Umbraco/Views/common/dialogs/content/edit.html +++ b/WebCms/Umbraco/Views/common/dialogs/content/edit.html @@ -2,7 +2,8 @@ ng-controller="Umbraco.Dialogs.Content.EditController" ng-show="loaded" ng-submit="save()" - val-form-manager> + val-form-manager + class="umb-mini-editor">
                @@ -12,15 +13,33 @@
                diff --git a/WebCms/Umbraco/Views/common/dialogs/help.html b/WebCms/Umbraco/Views/common/dialogs/help.html index b9428fb..be98565 100644 --- a/WebCms/Umbraco/Views/common/dialogs/help.html +++ b/WebCms/Umbraco/Views/common/dialogs/help.html @@ -22,8 +22,8 @@
                - \ No newline at end of file + diff --git a/WebCms/Umbraco/Views/common/dialogs/iconpicker.html b/WebCms/Umbraco/Views/common/dialogs/iconpicker.html index 96ab990..f21fdf0 100644 --- a/WebCms/Umbraco/Views/common/dialogs/iconpicker.html +++ b/WebCms/Umbraco/Views/common/dialogs/iconpicker.html @@ -1,57 +1,57 @@
                -
                -
                - +
                - + + No icons were found. + +
                - +
                diff --git a/WebCms/Umbraco/Views/common/dialogs/insertmacro.html b/WebCms/Umbraco/Views/common/dialogs/insertmacro.html index 590b569..f1db7bb 100644 --- a/WebCms/Umbraco/Views/common/dialogs/insertmacro.html +++ b/WebCms/Umbraco/Views/common/dialogs/insertmacro.html @@ -1,4 +1,4 @@ -
                +
                diff --git a/WebCms/Umbraco/Views/common/dialogs/legacydelete.html b/WebCms/Umbraco/Views/common/dialogs/legacydelete.html index 5545dac..b6026e3 100644 --- a/WebCms/Umbraco/Views/common/dialogs/legacydelete.html +++ b/WebCms/Umbraco/Views/common/dialogs/legacydelete.html @@ -3,7 +3,7 @@

                - Are you sure you want to delete {{currentNode.name}} ? + Are you sure you want to delete {{currentNode.name}} ?

                diff --git a/WebCms/Umbraco/Views/common/dialogs/linkpicker.html b/WebCms/Umbraco/Views/common/dialogs/linkpicker.html index a1728f3..ceb88b9 100644 --- a/WebCms/Umbraco/Views/common/dialogs/linkpicker.html +++ b/WebCms/Umbraco/Views/common/dialogs/linkpicker.html @@ -1,17 +1,29 @@
                - \ No newline at end of file + diff --git a/WebCms/Umbraco/Views/common/dialogs/login.html b/WebCms/Umbraco/Views/common/dialogs/login.html index 4e0268e..c7a9767 100644 --- a/WebCms/Umbraco/Views/common/dialogs/login.html +++ b/WebCms/Umbraco/Views/common/dialogs/login.html @@ -1,135 +1,253 @@ -
                +
                +
                -
                -

                {{greeting}}

                -
                + -

                - Log in below. - Log in below +

                + + \ No newline at end of file +
                diff --git a/WebCms/Umbraco/Views/common/dialogs/mediapicker.html b/WebCms/Umbraco/Views/common/dialogs/mediapicker.html index 0c1dbac..0ba7848 100644 --- a/WebCms/Umbraco/Views/common/dialogs/mediapicker.html +++ b/WebCms/Umbraco/Views/common/dialogs/mediapicker.html @@ -61,21 +61,23 @@
                + on-drag-leave="dragLeave()" + on-drag-end="dragLeave()" + on-drag-enter="dragEnter()" + ng-hide="target"> + -
                -
              - + @@ -158,4 +158,4 @@ - + diff --git a/WebCms/Umbraco/Views/common/dialogs/membergrouppicker.html b/WebCms/Umbraco/Views/common/dialogs/membergrouppicker.html index ec614d6..c41ff27 100644 --- a/WebCms/Umbraco/Views/common/dialogs/membergrouppicker.html +++ b/WebCms/Umbraco/Views/common/dialogs/membergrouppicker.html @@ -1,7 +1,7 @@
              - + \ No newline at end of file diff --git a/WebCms/Umbraco/Views/common/dialogs/rteembed.html b/WebCms/Umbraco/Views/common/dialogs/rteembed.html index 36127e4..ee14268 100644 --- a/WebCms/Umbraco/Views/common/dialogs/rteembed.html +++ b/WebCms/Umbraco/Views/common/dialogs/rteembed.html @@ -1,17 +1,17 @@ -
              +
              - \ No newline at end of file + diff --git a/WebCms/Umbraco/Views/common/dialogs/template/snippet.html b/WebCms/Umbraco/Views/common/dialogs/template/snippet.html index 7b51873..0d6c050 100644 --- a/WebCms/Umbraco/Views/common/dialogs/template/snippet.html +++ b/WebCms/Umbraco/Views/common/dialogs/template/snippet.html @@ -29,7 +29,7 @@ Close - Insert + Insert diff --git a/WebCms/Umbraco/Views/common/dialogs/user.html b/WebCms/Umbraco/Views/common/dialogs/user.html index 20dc41d..0344f28 100644 --- a/WebCms/Umbraco/Views/common/dialogs/user.html +++ b/WebCms/Umbraco/Views/common/dialogs/user.html @@ -24,7 +24,7 @@
              - @@ -42,7 +42,7 @@
              -
              External login providers
              +
              External login providers
              @@ -55,7 +55,7 @@ onclick="document.forms.oauthloginform.submit();"> - Link your {{login.caption}} account + Link your {{login.caption}} account @@ -67,7 +67,7 @@ name="provider" value="{{login.authType}}"> - Un-link your {{login.caption}} account + Un-link your {{login.caption}} account
              diff --git a/WebCms/Umbraco/Views/common/dialogs/ysod.html b/WebCms/Umbraco/Views/common/dialogs/ysod.html index 1734867..56f11c3 100644 --- a/WebCms/Umbraco/Views/common/dialogs/ysod.html +++ b/WebCms/Umbraco/Views/common/dialogs/ysod.html @@ -1,4 +1,4 @@ -
              +
              diff --git a/WebCms/Umbraco/Views/common/notifications/confirmunpublish.html b/WebCms/Umbraco/Views/common/notifications/confirmunpublish.html new file mode 100644 index 0000000..4bd3e2b --- /dev/null +++ b/WebCms/Umbraco/Views/common/notifications/confirmunpublish.html @@ -0,0 +1,6 @@ +
              +

              Are you sure?

              +

              Unpublishing will remove this page and all its descendants from the site

              + + +
              \ No newline at end of file diff --git a/WebCms/Umbraco/Views/common/overlays/contentpicker/contentpicker.html b/WebCms/Umbraco/Views/common/overlays/contentpicker/contentpicker.html index 8e70ad1..43eab53 100644 --- a/WebCms/Umbraco/Views/common/overlays/contentpicker/contentpicker.html +++ b/WebCms/Umbraco/Views/common/overlays/contentpicker/contentpicker.html @@ -1,34 +1,48 @@
              -
              - - -
              +
              - - +
              + + +
              -
              - - -
              + + -
              +
              + + +
              + +
              + + + + +
              \ No newline at end of file diff --git a/WebCms/Umbraco/Views/common/overlays/contenttypeeditor/compositions/compositions.html b/WebCms/Umbraco/Views/common/overlays/contenttypeeditor/compositions/compositions.html index 6554a03..f2d8902 100644 --- a/WebCms/Umbraco/Views/common/overlays/contenttypeeditor/compositions/compositions.html +++ b/WebCms/Umbraco/Views/common/overlays/contenttypeeditor/compositions/compositions.html @@ -7,30 +7,33 @@ style="width: 100%" ng-model="searchTerm" class="umb-search-field search-query input-block-level" - localize="placeholder" - placeholder="@placeholders_filter" - umb-auto-focus> + localize="placeholder" + placeholder="@placeholders_filter" + umb-auto-focus + no-dirty-check />
              - +
              - - + + - - + + - +
              • @@ -41,7 +44,7 @@ checklist-model="model.compositeContentTypes" checklist-value="compositeContentType.contentType.alias" ng-change="model.selectCompositeContentType(compositeContentType.contentType)" - ng-disabled="compositeContentType.allowed===false || compositeContentType.inherited"/> + ng-disabled="compositeContentType.allowed===false || compositeContentType.inherited" />
              - \ No newline at end of file + diff --git a/WebCms/Umbraco/Views/common/overlays/contenttypeeditor/editorpicker/editorpicker.html b/WebCms/Umbraco/Views/common/overlays/contenttypeeditor/editorpicker/editorpicker.html index 392d45e..1f2f3a8 100644 --- a/WebCms/Umbraco/Views/common/overlays/contenttypeeditor/editorpicker/editorpicker.html +++ b/WebCms/Umbraco/Views/common/overlays/contenttypeeditor/editorpicker/editorpicker.html @@ -1,117 +1,103 @@
              - - -
              - -
              - - - - -
              - - - - - - - -
              -
              -
              {{key}}
              - -
              -
              - -
              -
              -
              {{key}}
              - -
              -
              - -
              - -
              - -
              - - -
              - -
              -
              -
              -
              {{key}}
              - -
              -
              - -
              -
              -
              -
              {{key}}
              - -
              -
              - -
              - - - - + +
              + +
              + + +
              + + + +
              +
              +
              {{key}}
              + +
              +
              +
              +
              +
              {{key}}
              + +
              +
              +
              +
              +
              + +
              +
              +
              +
              +
              {{key}}
              + +
              +
              +
              +
              +
              +
              {{key}}
              + +
              +
              +
              + +
              diff --git a/WebCms/Umbraco/Views/common/overlays/contenttypeeditor/editorsettings/editorsettings.html b/WebCms/Umbraco/Views/common/overlays/contenttypeeditor/editorsettings/editorsettings.html index 0ea012b..d1833a5 100644 --- a/WebCms/Umbraco/Views/common/overlays/contenttypeeditor/editorsettings/editorsettings.html +++ b/WebCms/Umbraco/Views/common/overlays/contenttypeeditor/editorsettings/editorsettings.html @@ -1,21 +1,22 @@ -
              - - using this editor will get updated with the new settings. +
              + +
              + + using this editor will get updated with the new settings. +
              + +
              +
              +
              + +
              +
              +
              + +
              + + + + +
              - -
              -
              - -
              - -
              -
              -
              - -
              - - - - diff --git a/WebCms/Umbraco/Views/common/overlays/contenttypeeditor/propertysettings/propertysettings.html b/WebCms/Umbraco/Views/common/overlays/contenttypeeditor/propertysettings/propertysettings.html index 7c457ac..9df9c80 100644 --- a/WebCms/Umbraco/Views/common/overlays/contenttypeeditor/propertysettings/propertysettings.html +++ b/WebCms/Umbraco/Views/common/overlays/contenttypeeditor/propertysettings/propertysettings.html @@ -1,8 +1,9 @@
              -
              +
              -
              +
              - + @@ -59,7 +61,7 @@
              -
              +
              @@ -69,12 +71,13 @@ + + + + + + + + + + + + + + + +
              diff --git a/WebCms/Umbraco/Views/dictionary/list.html b/WebCms/Umbraco/Views/dictionary/list.html new file mode 100644 index 0000000..5dd6edb --- /dev/null +++ b/WebCms/Umbraco/Views/dictionary/list.html @@ -0,0 +1,46 @@ +
              + + + + + + + +
              +
              +
              +
              +
              + Name +
              +
              + + + +
              +
              +
              +
              +
              +
              +
              + + +
              +
              + +
              +
              +
              +
              +
              +
              +
              diff --git a/WebCms/Umbraco/Views/documenttypes/copy.html b/WebCms/Umbraco/Views/documenttypes/copy.html index db1a0db..2976f65 100644 --- a/WebCms/Umbraco/Views/documenttypes/copy.html +++ b/WebCms/Umbraco/Views/documenttypes/copy.html @@ -1,4 +1,4 @@ -
              +
              diff --git a/WebCms/Umbraco/Views/documenttypes/create.html b/WebCms/Umbraco/Views/documenttypes/create.html index 2a99fb7..549ad04 100644 --- a/WebCms/Umbraco/Views/documenttypes/create.html +++ b/WebCms/Umbraco/Views/documenttypes/create.html @@ -1,10 +1,10 @@
            diff --git a/WebCms/Umbraco/Views/install/upgrade.html b/WebCms/Umbraco/Views/install/upgrade.html index 5242fa8..df2748a 100644 --- a/WebCms/Umbraco/Views/install/upgrade.html +++ b/WebCms/Umbraco/Views/install/upgrade.html @@ -1,14 +1,14 @@ -
            +

            Upgrading Umbraco

            Welcome to the Umbraco installer. You see this screen because your Umbraco installation needs a quick upgrade of its database and files, which will ensure your website is kept as fast, secure and up to date as possible.

            - To read a report of changes between your current version {{installer.current.model.currentVersion}} and this version your upgrading to {{installer.current.model.newVersion}} + To read a report of changes between your current version {{installer.current.model.currentVersion}} and this version you're upgrading to {{installer.current.model.newVersion}}

            - View Report + View Report

            diff --git a/WebCms/Umbraco/Views/install/user.html b/WebCms/Umbraco/Views/install/user.html index 7be0d23..52a2fce 100644 --- a/WebCms/Umbraco/Views/install/user.html +++ b/WebCms/Umbraco/Views/install/user.html @@ -18,44 +18,48 @@

            - + Your email will be used as your login
            -
            - - + + - At least {{installer.current.model.minCharLength}} characters long - - - At least {{installer.current.model.minNonAlphaNumericLength}} symbol{{installer.current.model.minNonAlphaNumericLength > 1 ? 's' : ''}} - -
            + At least {{installer.current.model.minCharLength}} characters long + + + At least {{installer.current.model.minNonAlphaNumericLength}} symbol{{installer.current.model.minNonAlphaNumericLength > 1 ? 's' : ''}} + +
            - +
            diff --git a/WebCms/Umbraco/Views/install/version7upgradereport.html b/WebCms/Umbraco/Views/install/version7upgradereport.html index df1e58d..bef913c 100644 --- a/WebCms/Umbraco/Views/install/version7upgradereport.html +++ b/WebCms/Umbraco/Views/install/version7upgradereport.html @@ -1,4 +1,4 @@ -
            +

            Major version upgrade from {{installer.current.model.currentVersion}} to {{installer.current.model.newVersion}}

            There were {{installer.current.model.errors.length}} issues detected

            diff --git a/WebCms/Umbraco/Views/media/create.html b/WebCms/Umbraco/Views/media/create.html index db1810f..4144f60 100644 --- a/WebCms/Umbraco/Views/media/create.html +++ b/WebCms/Umbraco/Views/media/create.html @@ -11,31 +11,28 @@

          @@ -44,7 +41,7 @@ diff --git a/WebCms/Umbraco/Views/media/edit.html b/WebCms/Umbraco/Views/media/edit.html index 8b478ef..046e329 100644 --- a/WebCms/Umbraco/Views/media/edit.html +++ b/WebCms/Umbraco/Views/media/edit.html @@ -1,4 +1,4 @@ -
          +
          @@ -22,10 +22,21 @@ - - - - + +
          + + + +
          + + +
          + + +
          +
          @@ -47,6 +58,7 @@ -
          -
          +
          +
          -

          - Choose where to move - {{currentNode.name}} - to in the tree structure below -

          +
          +
          +
          {{error.errorMsg}}
          +
          {{error.data.message}}
          +
          +
          -
          -

          {{error.errorMsg}}

          -

          {{error.data.Message}}

          -
          +
          +
          + {{currentNode.name}} was moved underneath {{target.name}} +
          + +
          -
          -

          {{currentNode.name}} was moved underneath - {{target.name}}

          +

          + Choose where to move + {{currentNode.name}} + to in the tree structure below +

          - -
          +
          + +
          + + +
          -
          - - -
          -
          -
          - - - + + + +
          +
          +
          + +
          diff --git a/WebCms/Umbraco/Views/media/restore.html b/WebCms/Umbraco/Views/media/restore.html new file mode 100644 index 0000000..17fff15 --- /dev/null +++ b/WebCms/Umbraco/Views/media/restore.html @@ -0,0 +1,26 @@ +
          +
          + + +

          + Restore {{currentNode.name}} under {{target.name}}? +

          + +
          +
          {{error.errorMsg}}
          +
          {{error.data.Message}}
          +
          + +
          +

          {{currentNode.name}} was moved underneath {{target.name}}

          + +
          + +
          +
          + + +
          diff --git a/WebCms/Umbraco/Views/mediatypes/copy.html b/WebCms/Umbraco/Views/mediatypes/copy.html index 319e59c..fcc26b9 100644 --- a/WebCms/Umbraco/Views/mediatypes/copy.html +++ b/WebCms/Umbraco/Views/mediatypes/copy.html @@ -1,4 +1,4 @@ -
          +
          diff --git a/WebCms/Umbraco/Views/mediatypes/create.html b/WebCms/Umbraco/Views/mediatypes/create.html index ab83dae..afcb1ef 100644 --- a/WebCms/Umbraco/Views/mediatypes/create.html +++ b/WebCms/Umbraco/Views/mediatypes/create.html @@ -5,15 +5,12 @@
          diff --git a/WebCms/Umbraco/Views/mediatypes/edit.html b/WebCms/Umbraco/Views/mediatypes/edit.html index 5bde09f..5124c97 100644 --- a/WebCms/Umbraco/Views/mediatypes/edit.html +++ b/WebCms/Umbraco/Views/mediatypes/edit.html @@ -1,4 +1,4 @@ -
          +
          @@ -9,6 +9,7 @@ diff --git a/WebCms/Umbraco/Views/mediatypes/move.html b/WebCms/Umbraco/Views/mediatypes/move.html index 59172eb..18f7aee 100644 --- a/WebCms/Umbraco/Views/mediatypes/move.html +++ b/WebCms/Umbraco/Views/mediatypes/move.html @@ -1,4 +1,4 @@ -
          +
          diff --git a/WebCms/Umbraco/Views/mediatypes/rename.html b/WebCms/Umbraco/Views/mediatypes/rename.html new file mode 100644 index 0000000..f1d3b4b --- /dev/null +++ b/WebCms/Umbraco/Views/mediatypes/rename.html @@ -0,0 +1,21 @@ + diff --git a/WebCms/Umbraco/Views/mediatypes/views/permissions/permissions.html b/WebCms/Umbraco/Views/mediatypes/views/permissions/permissions.html index 7c9811e..d2cd50b 100644 --- a/WebCms/Umbraco/Views/mediatypes/views/permissions/permissions.html +++ b/WebCms/Umbraco/Views/mediatypes/views/permissions/permissions.html @@ -7,10 +7,12 @@
          - + +
          diff --git a/WebCms/Umbraco/Views/member/create.html b/WebCms/Umbraco/Views/member/create.html index c5693ad..eeadc03 100644 --- a/WebCms/Umbraco/Views/member/create.html +++ b/WebCms/Umbraco/Views/member/create.html @@ -6,10 +6,9 @@
        • - - - - {{docType.name}} + + + {{docType.name}} {{docType.description}} @@ -20,8 +19,7 @@
        • - diff --git a/WebCms/Umbraco/Views/member/edit.html b/WebCms/Umbraco/Views/member/edit.html index 3b96c2d..d3d5929 100644 --- a/WebCms/Umbraco/Views/member/edit.html +++ b/WebCms/Umbraco/Views/member/edit.html @@ -1,4 +1,4 @@ -
          +
          @@ -20,13 +20,13 @@ - + - - - + + + - + diff --git a/WebCms/Umbraco/Views/membertypes/create.html b/WebCms/Umbraco/Views/membertypes/create.html index f63cc48..cd9de7a 100644 --- a/WebCms/Umbraco/Views/membertypes/create.html +++ b/WebCms/Umbraco/Views/membertypes/create.html @@ -41,7 +41,7 @@
          diff --git a/WebCms/Umbraco/Views/membertypes/edit.html b/WebCms/Umbraco/Views/membertypes/edit.html index eb34aae..c2c2ebf 100644 --- a/WebCms/Umbraco/Views/membertypes/edit.html +++ b/WebCms/Umbraco/Views/membertypes/edit.html @@ -1,4 +1,4 @@ -
          +
          diff --git a/WebCms/Umbraco/Views/packager/overview.html b/WebCms/Umbraco/Views/packager/overview.html index b9857ac..43f08a9 100644 --- a/WebCms/Umbraco/Views/packager/overview.html +++ b/WebCms/Umbraco/Views/packager/overview.html @@ -1,4 +1,4 @@ -
          +
          diff --git a/WebCms/Umbraco/Views/packager/views/install-local.html b/WebCms/Umbraco/Views/packager/views/install-local.html index 6bb7c6f..f548a29 100644 --- a/WebCms/Umbraco/Views/packager/views/install-local.html +++ b/WebCms/Umbraco/Views/packager/views/install-local.html @@ -24,7 +24,7 @@ - Drop to upload + Drop to upload
          - - or click here to choose files + - or click here to choose files
          @@ -42,9 +42,9 @@
          -

          Upload package

          +

          Upload package

          - Install a local package by selecting it from your machine. Only install packages from sources you know and trust. + Install a local package by selecting it from your machine. Only install packages from sources you know and trust.

          @@ -55,7 +55,7 @@ - ← Upload another package + Upload another package @@ -68,7 +68,7 @@
          -

          Uploading package

          +

          Uploading package...

          @@ -90,7 +90,7 @@ - ← Cancel and upload another package + Cancel and upload another package @@ -111,22 +111,22 @@

          {{ vm.localPackage.name }}

          - Author + Author {{ vm.localPackage.author }}
          - Version + Version {{ vm.localPackage.version }}
          - License + License {{ vm.localPackage.license }}
          - Read me + Read me
          @@ -134,14 +134,14 @@
          @@ -153,7 +153,7 @@
          - This package cannot be installed, it requires a minimum Umbraco version of {{vm.localPackage.umbracoVersion}} + This package cannot be installed, it requires a minimum Umbraco version of {{vm.localPackage.umbracoVersion}}

          {{vm.installState.status}}

          @@ -168,7 +168,7 @@ class="btn btn-success flex-inline mt3" ng-click="vm.reloadPage()"> - Finish + Finish
          diff --git a/WebCms/Umbraco/Views/packager/views/installed.html b/WebCms/Umbraco/Views/packager/views/installed.html index 5da3c33..ca4f129 100644 --- a/WebCms/Umbraco/Views/packager/views/installed.html +++ b/WebCms/Umbraco/Views/packager/views/installed.html @@ -5,15 +5,15 @@
          -
          Installed packages
          +
          Installed packages
          - - + +
          @@ -24,7 +24,7 @@
          - + Uninstall
          @@ -36,8 +36,8 @@ -

          You don’t have any packages installed.

          -

          You don’t have any packages installed. Either install a local package by selecting it from your machine, or browse through available packages using the "Package" icon in the top right of your screen."

          +

          You don’t have any packages installed.

          +

          You don’t have any packages installed. Either install a local package by selecting it from your machine, or browse through available packages using the "Packages" icon in the top right of your screen.

          @@ -47,7 +47,7 @@ - ← Take me back + Back @@ -66,22 +66,22 @@

          {{ vm.package.name }}

          - Author + Author {{ vm.package.author }}
          - Version + Version {{ vm.package.version }}
          - License + License {{ vm.package.license }}
          - Read me + Read me
          {{ vm.package.readme }}
          @@ -89,14 +89,14 @@
          diff --git a/WebCms/Umbraco/Views/packager/views/repo.html b/WebCms/Umbraco/Views/packager/views/repo.html index e7b1418..e4cb589 100644 --- a/WebCms/Umbraco/Views/packager/views/repo.html +++ b/WebCms/Umbraco/Views/packager/views/repo.html @@ -7,7 +7,7 @@
          @@ -26,7 +26,7 @@
          -

          Popular

          +

          Popular

          @@ -58,8 +58,8 @@
          -

          New Releases

          -

          Results for '{{ vm.searchQuery }}'

          +

          New Releases

          +

          Results for '{{ vm.searchQuery }}'

          @@ -105,8 +105,8 @@ -

          We couldn't find anything for '{{ vm.searchQuery }}'

          -

          Please try searching for another package or browse through the categories.

          +

          We couldn't find anything for '{{ vm.searchQuery }}'

          +

          Please try searching for another package or browse through the categories.

          @@ -114,12 +114,12 @@
          - +
          - ← Take me back + Back @@ -153,9 +153,9 @@
          - + ng-click="vm.downloadPackage(vm.package)">Install package
          -
          Information
          +
          Information
          -
          Owner:
          +
          Owner:
          {{vm.package.ownerInfo.owner}}
          -
          Contributors:
          +
          Contributors:
          {{ contributor }}
          -
          Created:
          +
          Created:
          {{vm.package.created | date:'yyyy-MM-dd HH:mm:ss'}}
          -
          Current version:
          +
          Current version:
          {{vm.package.latestVersion}}
          -
          .Net Version:
          +
          .NET Version:
          {{vm.package.information.netVersion}}
          -
          License:
          +
          License:
          {{vm.package.licenseName}}
          -
          Downloads:
          +
          Downloads:
          {{vm.package.downloads}}
          -
          Likes:
          +
          Likes:
          {{vm.package.likes}}
          @@ -231,8 +231,8 @@
          -
          Compatibility
          -
          This package is compatible with the following versions of Umbraco, as reported by community members. Full compatability cannot be gauranteed for versions reported below 100%
          +
          Compatibility
          +
          This package is compatible with the following versions of Umbraco, as reported by community members. Full compatability cannot be gauranteed for versions reported below 100%
          {{compatibility.version}} @@ -247,7 +247,7 @@
          -
          External sources
          +
          External sources
          @@ -269,7 +269,7 @@
          - ← Take me back + Back @@ -295,17 +295,17 @@
          - Version + Version {{ vm.localPackage.version }}
          - License + License {{ vm.localPackage.license }}
          - Read me + Read me
          @@ -313,15 +313,15 @@
          @@ -333,7 +333,7 @@
          - This package cannot be installed, it requires a minimum Umbraco version of {{vm.localPackage.umbracoVersion}} + This package cannot be installed, it requires a minimum Umbraco version of {{vm.localPackage.umbracoVersion}}
          @@ -346,7 +346,7 @@
          diff --git a/WebCms/Umbraco/Views/partialviewmacros/create.html b/WebCms/Umbraco/Views/partialviewmacros/create.html new file mode 100644 index 0000000..febaaab --- /dev/null +++ b/WebCms/Umbraco/Views/partialviewmacros/create.html @@ -0,0 +1,85 @@ +
          + + + + + + +
          diff --git a/WebCms/Umbraco/Views/partialviewmacros/delete.html b/WebCms/Umbraco/Views/partialviewmacros/delete.html new file mode 100644 index 0000000..6b66a48 --- /dev/null +++ b/WebCms/Umbraco/Views/partialviewmacros/delete.html @@ -0,0 +1,12 @@ +
          +
          + +

          + Are you sure you want to delete {{currentNode.name}} ? +

          + + + + +
          +
          diff --git a/WebCms/Umbraco/Views/partialviewmacros/edit.html b/WebCms/Umbraco/Views/partialviewmacros/edit.html new file mode 100644 index 0000000..8f093a5 --- /dev/null +++ b/WebCms/Umbraco/Views/partialviewmacros/edit.html @@ -0,0 +1,123 @@ +
          + + + +
          + + + + + + + + +
          + +
          + + + + +
          + +
          + +
          +
          + + +
          + + + + + + + + + + + + +
          + + + + + + + + + + + + + + + + + +
          diff --git a/WebCms/Umbraco/Views/partialviews/create.html b/WebCms/Umbraco/Views/partialviews/create.html new file mode 100644 index 0000000..7dc4268 --- /dev/null +++ b/WebCms/Umbraco/Views/partialviews/create.html @@ -0,0 +1,75 @@ +
          + + + + + + +
          diff --git a/WebCms/Umbraco/Views/partialviews/delete.html b/WebCms/Umbraco/Views/partialviews/delete.html new file mode 100644 index 0000000..0f75e85 --- /dev/null +++ b/WebCms/Umbraco/Views/partialviews/delete.html @@ -0,0 +1,12 @@ +
          +
          + +

          + Are you sure you want to delete {{currentNode.name}} ? +

          + + + + +
          +
          diff --git a/WebCms/Umbraco/Views/partialviews/edit.html b/WebCms/Umbraco/Views/partialviews/edit.html new file mode 100644 index 0000000..6ecc2b2 --- /dev/null +++ b/WebCms/Umbraco/Views/partialviews/edit.html @@ -0,0 +1,128 @@ +
          + + + +
          + + + + + + + + +
          + +
          + + + + +
          + +
          + +
          +
          + + +
          + + + + + + + + + + + + + + + +
          + + + + + + + + + + + + + + + + + +
          diff --git a/WebCms/Umbraco/Views/prevalueeditors/boolean.html b/WebCms/Umbraco/Views/prevalueeditors/boolean.html index 8af17a1..2aeb122 100644 --- a/WebCms/Umbraco/Views/prevalueeditors/boolean.html +++ b/WebCms/Umbraco/Views/prevalueeditors/boolean.html @@ -1 +1,6 @@ - \ No newline at end of file +
          + + +
          diff --git a/WebCms/Umbraco/Views/prevalueeditors/imagepicker.html b/WebCms/Umbraco/Views/prevalueeditors/imagepicker.html index d9d988d..1a37a62 100644 --- a/WebCms/Umbraco/Views/prevalueeditors/imagepicker.html +++ b/WebCms/Umbraco/Views/prevalueeditors/imagepicker.html @@ -1,19 +1,28 @@
          -
            -
          • - - + + - + diff --git a/WebCms/Umbraco/Views/prevalueeditors/mediapicker.html b/WebCms/Umbraco/Views/prevalueeditors/mediapicker.html index 978ae63..f8d780d 100644 --- a/WebCms/Umbraco/Views/prevalueeditors/mediapicker.html +++ b/WebCms/Umbraco/Views/prevalueeditors/mediapicker.html @@ -1,30 +1,33 @@
            - - - - - - + +
            diff --git a/WebCms/Umbraco/Views/prevalueeditors/multivalues.html b/WebCms/Umbraco/Views/prevalueeditors/multivalues.html index f89efcf..3434a7e 100644 --- a/WebCms/Umbraco/Views/prevalueeditors/multivalues.html +++ b/WebCms/Umbraco/Views/prevalueeditors/multivalues.html @@ -1,13 +1,21 @@ -
            -
            - - +
            +
            +
            + +
            +
            + +
            -
            +
            - - +
            + +
            +
            + Remove +
            diff --git a/WebCms/Umbraco/Views/prevalueeditors/radiobuttonlist.html b/WebCms/Umbraco/Views/prevalueeditors/radiobuttonlist.html index b82eb88..973d287 100644 --- a/WebCms/Umbraco/Views/prevalueeditors/radiobuttonlist.html +++ b/WebCms/Umbraco/Views/prevalueeditors/radiobuttonlist.html @@ -1,7 +1,15 @@ -
              -
            • - -
            • -
            \ No newline at end of file +
              +
            • + +
            • +
            + +
              +
            • + +
            • +
            diff --git a/WebCms/Umbraco/Views/prevalueeditors/requiredfield.html b/WebCms/Umbraco/Views/prevalueeditors/requiredfield.html index 2d24c35..806459d 100644 --- a/WebCms/Umbraco/Views/prevalueeditors/requiredfield.html +++ b/WebCms/Umbraco/Views/prevalueeditors/requiredfield.html @@ -1,4 +1,4 @@ -
            +
            \ No newline at end of file + \ No newline at end of file diff --git a/WebCms/Umbraco/Views/prevalueeditors/textstringlimited.html b/WebCms/Umbraco/Views/prevalueeditors/textstringlimited.html new file mode 100644 index 0000000..d91013a --- /dev/null +++ b/WebCms/Umbraco/Views/prevalueeditors/textstringlimited.html @@ -0,0 +1,12 @@ +
            + + + Not a number + {{propertyForm.requiredField.errorMsg}} +
            diff --git a/WebCms/Umbraco/Views/prevalueeditors/treepicker.html b/WebCms/Umbraco/Views/prevalueeditors/treepicker.html index 8420ee0..e92cbae 100644 --- a/WebCms/Umbraco/Views/prevalueeditors/treepicker.html +++ b/WebCms/Umbraco/Views/prevalueeditors/treepicker.html @@ -1,24 +1,27 @@
            - - - + + Add +
            • - + Choose a root node... diff --git a/WebCms/Umbraco/Views/propertyeditors/boolean/boolean.html b/WebCms/Umbraco/Views/propertyeditors/boolean/boolean.html index 2d302d0..36b3d75 100644 --- a/WebCms/Umbraco/Views/propertyeditors/boolean/boolean.html +++ b/WebCms/Umbraco/Views/propertyeditors/boolean/boolean.html @@ -1,3 +1,6 @@
              - -
              \ No newline at end of file + + +
            diff --git a/WebCms/Umbraco/Views/propertyeditors/changepassword/changepassword.html b/WebCms/Umbraco/Views/propertyeditors/changepassword/changepassword.html index aee4211..df99fbd 100644 --- a/WebCms/Umbraco/Views/propertyeditors/changepassword/changepassword.html +++ b/WebCms/Umbraco/Views/propertyeditors/changepassword/changepassword.html @@ -1,62 +1,6 @@ -
            -
            - Password has been reset to: -
            - {{model.value.generatedPassword}} -
            -
            - -
            - - - - - - - - - - Required - - - - - - Required - Minimum {{$parent.model.config.minPasswordLength}} characters - - - - - - - - The confirmed password doesn't match the new password! - - - - - Cancel - -
            -
            +
            + +
            diff --git a/WebCms/Umbraco/Views/propertyeditors/checkboxlist/checkboxlist.html b/WebCms/Umbraco/Views/propertyeditors/checkboxlist/checkboxlist.html index e6407bd..f59e7f8 100644 --- a/WebCms/Umbraco/Views/propertyeditors/checkboxlist/checkboxlist.html +++ b/WebCms/Umbraco/Views/propertyeditors/checkboxlist/checkboxlist.html @@ -1,4 +1,4 @@ -
            +
            -
          @@ -95,26 +95,23 @@
          - - + + - +
          - + - + - + - +
          diff --git a/WebCms/Umbraco/Views/propertyeditors/imagecropper/imagecropper.prevalues.html b/WebCms/Umbraco/Views/propertyeditors/imagecropper/imagecropper.prevalues.html index e8c0eaf..62a5565 100644 --- a/WebCms/Umbraco/Views/propertyeditors/imagecropper/imagecropper.prevalues.html +++ b/WebCms/Umbraco/Views/propertyeditors/imagecropper/imagecropper.prevalues.html @@ -16,34 +16,34 @@
          -

          Define crop

          +

          Define crop

          - Give the crop an alias and it's default width and height. + Give the crop an alias and it's default width and height.

          - +
          - - + + × - +
          - - Cancel + + Cancel
          - +
          diff --git a/WebCms/Umbraco/Views/propertyeditors/integer/integer.html b/WebCms/Umbraco/Views/propertyeditors/integer/integer.html index 80c6a1e..c1c1212 100644 --- a/WebCms/Umbraco/Views/propertyeditors/integer/integer.html +++ b/WebCms/Umbraco/Views/propertyeditors/integer/integer.html @@ -1,4 +1,4 @@ -
          +
          - Not a number + Not a number {{propertyForm.requiredField.errorMsg}} -
          \ No newline at end of file +
          diff --git a/WebCms/Umbraco/Views/propertyeditors/listview/bulkActionPermissions.prevalues.html b/WebCms/Umbraco/Views/propertyeditors/listview/bulkActionPermissions.prevalues.html index 8a5ca61..a872e7e 100644 --- a/WebCms/Umbraco/Views/propertyeditors/listview/bulkActionPermissions.prevalues.html +++ b/WebCms/Umbraco/Views/propertyeditors/listview/bulkActionPermissions.prevalues.html @@ -1,4 +1,4 @@ -
          +
    Set the title of the overlay.
    model.subTitlemodel.subtitle String Set the subtitle of the overlay.
    @@ -20,7 +21,7 @@ - + '; - } else { - gridHtml += ''; - } + var register = function (editor) { + editor.addCommand('mceShowCharmap', function () { + $_amnajs9hjh8lpug3.open(editor); + }); + }; + var $_da46gc9gjh8lpug1 = { register: register }; - gridHtml += '
    diff --git a/WebCms/Umbraco/Views/propertyeditors/listview/layouts/grid/grid.html b/WebCms/Umbraco/Views/propertyeditors/listview/layouts/grid/grid.html index 16c5efe..cf1c3f6 100644 --- a/WebCms/Umbraco/Views/propertyeditors/listview/layouts/grid/grid.html +++ b/WebCms/Umbraco/Views/propertyeditors/listview/layouts/grid/grid.html @@ -14,8 +14,8 @@ -
    No content has been added
    -
    No members have been added
    +
    No content has been added
    +
    No members have been added
    @@ -76,7 +76,7 @@ diff --git a/WebCms/Umbraco/Views/propertyeditors/listview/layouts/list/list.html b/WebCms/Umbraco/Views/propertyeditors/listview/layouts/list/list.html index ca1c212..45f51e7 100644 --- a/WebCms/Umbraco/Views/propertyeditors/listview/layouts/list/list.html +++ b/WebCms/Umbraco/Views/propertyeditors/listview/layouts/list/list.html @@ -53,8 +53,8 @@ -
    No content has been added
    -
    No members have been added
    +
    No content has been added
    +
    No members have been added
    diff --git a/WebCms/Umbraco/Views/propertyeditors/listview/listview.html b/WebCms/Umbraco/Views/propertyeditors/listview/listview.html index 706c7e9..4c543b7 100644 --- a/WebCms/Umbraco/Views/propertyeditors/listview/listview.html +++ b/WebCms/Umbraco/Views/propertyeditors/listview/listview.html @@ -10,14 +10,14 @@ - +
    Create
    ' + - (chr ? String.fromCharCode(parseInt(chr[0], 10)) : ' ') + '
    '; - } - } + var getParentTd = function (elm) { + while (elm) { + if (elm.nodeName === 'TD') { + return elm; + } + elm = elm.parentNode; + } + }; + var open = function (editor) { + var win; + var charMapPanel = { + type: 'container', + html: $_5f2keu9ijh8lpug5.getHtml($_frpqx49djh8lpufs.getCharMap(editor)), + onclick: function (e) { + var target = e.target; + if (/^(TD|DIV)$/.test(target.nodeName)) { + var charDiv = getParentTd(target).firstChild; + if (charDiv && charDiv.hasAttribute('data-chr')) { + var charCodeString = charDiv.getAttribute('data-chr'); + var charCode = parseInt(charCodeString, 10); + if (!isNaN(charCode)) { + $_1atl5z9bjh8lpufq.insertChar(editor, String.fromCharCode(charCode)); + } + if (!e.ctrlKey) { + win.close(); + } + } + } + }, + onmouseover: function (e) { + var td = getParentTd(e.target); + if (td && td.firstChild) { + win.find('#preview').text(td.firstChild.firstChild.data); + win.find('#previewTitle').text(td.title); + } else { + win.find('#preview').text(' '); + win.find('#previewTitle').text(' '); + } + } + }; + win = editor.windowManager.open({ + title: 'Special character', + spacing: 10, + padding: 10, + items: [ + charMapPanel, + { + type: 'container', + layout: 'flex', + direction: 'column', + align: 'center', + spacing: 5, + minWidth: 160, + minHeight: 160, + items: [ + { + type: 'label', + name: 'preview', + text: ' ', + style: 'font-size: 40px; text-align: center', + border: 1, + minWidth: 140, + minHeight: 80 + }, + { + type: 'spacer', + minHeight: 20 + }, + { + type: 'label', + name: 'previewTitle', + text: ' ', + style: 'white-space: pre-wrap;', + border: 1, + minWidth: 140 + } + ] + } + ], + buttons: [{ + text: 'Close', + onclick: function () { + win.close(); + } + }] + }); + }; + var $_amnajs9hjh8lpug3 = { open: open }; - gridHtml += '
    '; + var register$1 = function (editor) { + editor.addButton('charmap', { + icon: 'charmap', + tooltip: 'Special character', + cmd: 'mceShowCharmap' + }); + editor.addMenuItem('charmap', { + icon: 'charmap', + text: 'Special character', + cmd: 'mceShowCharmap', + context: 'insert' + }); + }; + var $_2aewt09jjh8lpug7 = { register: register$1 }; - var charMapPanel = { - type: 'container', - html: gridHtml, - onclick: function(e) { - var target = e.target; - if (/^(TD|DIV)$/.test(target.nodeName)) { - if (getParentTd(target).firstChild) { - editor.execCommand('mceInsertContent', false, tinymce.trim(target.innerText || target.textContent)); + global.add('charmap', function (editor) { + $_da46gc9gjh8lpug1.register(editor); + $_2aewt09jjh8lpug7.register(editor); + return $_afbe299ajh8lpufp.get(editor); + }); + function Plugin () { + } - if (!e.ctrlKey) { - win.close(); - } - } - } - }, - onmouseover: function(e) { - var td = getParentTd(e.target); + return Plugin; - if (td && td.firstChild) { - win.find('#preview').text(td.firstChild.firstChild.data); - win.find('#previewTitle').text(td.title); - } else { - win.find('#preview').text(' '); - win.find('#previewTitle').text(' '); - } - } - }; - - win = editor.windowManager.open({ - title: "Special character", - spacing: 10, - padding: 10, - items: [ - charMapPanel, - { - type: 'container', - layout: 'flex', - direction: 'column', - align: 'center', - spacing: 5, - minWidth: 160, - minHeight: 160, - items: [ - { - type: 'label', - name: 'preview', - text: ' ', - style: 'font-size: 40px; text-align: center', - border: 1, - minWidth: 140, - minHeight: 80 - }, - { - type: 'label', - name: 'previewTitle', - text: ' ', - style: 'text-align: center', - border: 1, - minWidth: 140, - minHeight: 80 - } - ] - } - ], - buttons: [ - {text: "Close", onclick: function() { - win.close(); - }} - ] - }); - } - - editor.addButton('charmap', { - icon: 'charmap', - tooltip: 'Special character', - onclick: showDialog - }); - - editor.addMenuItem('charmap', { - icon: 'charmap', - text: 'Special character', - onclick: showDialog, - context: 'insert' - }); -}); \ No newline at end of file +}()); +})(); diff --git a/WebCms/Umbraco/lib/tinymce/plugins/charmap/plugin.min.js b/WebCms/Umbraco/lib/tinymce/plugins/charmap/plugin.min.js index 3f414ad..9ea3f75 100644 --- a/WebCms/Umbraco/lib/tinymce/plugins/charmap/plugin.min.js +++ b/WebCms/Umbraco/lib/tinymce/plugins/charmap/plugin.min.js @@ -1 +1 @@ -tinymce.PluginManager.add("charmap",function(a){function b(){function b(a){for(;a;){if("TD"==a.nodeName)return a;a=a.parentNode}}var d,e,f,g;d='';var h=25,i=Math.ceil(c.length/h);for(f=0;i>f;f++){for(d+="",e=0;h>e;e++){var j=f*h+e;if(j
    '+(k?String.fromCharCode(parseInt(k[0],10)):" ")+"
    "}else d+="
    "}d+="";var l={type:"container",html:d,onclick:function(c){var d=c.target;/^(TD|DIV)$/.test(d.nodeName)&&b(d).firstChild&&(a.execCommand("mceInsertContent",!1,tinymce.trim(d.innerText||d.textContent)),c.ctrlKey||g.close())},onmouseover:function(a){var c=b(a.target);c&&c.firstChild?(g.find("#preview").text(c.firstChild.firstChild.data),g.find("#previewTitle").text(c.title)):(g.find("#preview").text(" "),g.find("#previewTitle").text(" "))}};g=a.windowManager.open({title:"Special character",spacing:10,padding:10,items:[l,{type:"container",layout:"flex",direction:"column",align:"center",spacing:5,minWidth:160,minHeight:160,items:[{type:"label",name:"preview",text:" ",style:"font-size: 40px; text-align: center",border:1,minWidth:140,minHeight:80},{type:"label",name:"previewTitle",text:" ",style:"text-align: center",border:1,minWidth:140,minHeight:80}]}],buttons:[{text:"Close",onclick:function(){g.close()}}]})}var c=[["160","no-break space"],["173","soft hyphen"],["34","quotation mark"],["162","cent sign"],["8364","euro sign"],["163","pound sign"],["165","yen sign"],["169","copyright sign"],["174","registered sign"],["8482","trade mark sign"],["8240","per mille sign"],["181","micro sign"],["183","middle dot"],["8226","bullet"],["8230","three dot leader"],["8242","minutes / feet"],["8243","seconds / inches"],["167","section sign"],["182","paragraph sign"],["223","sharp s / ess-zed"],["8249","single left-pointing angle quotation mark"],["8250","single right-pointing angle quotation mark"],["171","left pointing guillemet"],["187","right pointing guillemet"],["8216","left single quotation mark"],["8217","right single quotation mark"],["8220","left double quotation mark"],["8221","right double quotation mark"],["8218","single low-9 quotation mark"],["8222","double low-9 quotation mark"],["60","less-than sign"],["62","greater-than sign"],["8804","less-than or equal to"],["8805","greater-than or equal to"],["8211","en dash"],["8212","em dash"],["175","macron"],["8254","overline"],["164","currency sign"],["166","broken bar"],["168","diaeresis"],["161","inverted exclamation mark"],["191","turned question mark"],["710","circumflex accent"],["732","small tilde"],["176","degree sign"],["8722","minus sign"],["177","plus-minus sign"],["247","division sign"],["8260","fraction slash"],["215","multiplication sign"],["185","superscript one"],["178","superscript two"],["179","superscript three"],["188","fraction one quarter"],["189","fraction one half"],["190","fraction three quarters"],["402","function / florin"],["8747","integral"],["8721","n-ary sumation"],["8734","infinity"],["8730","square root"],["8764","similar to"],["8773","approximately equal to"],["8776","almost equal to"],["8800","not equal to"],["8801","identical to"],["8712","element of"],["8713","not an element of"],["8715","contains as member"],["8719","n-ary product"],["8743","logical and"],["8744","logical or"],["172","not sign"],["8745","intersection"],["8746","union"],["8706","partial differential"],["8704","for all"],["8707","there exists"],["8709","diameter"],["8711","backward difference"],["8727","asterisk operator"],["8733","proportional to"],["8736","angle"],["180","acute accent"],["184","cedilla"],["170","feminine ordinal indicator"],["186","masculine ordinal indicator"],["8224","dagger"],["8225","double dagger"],["192","A - grave"],["193","A - acute"],["194","A - circumflex"],["195","A - tilde"],["196","A - diaeresis"],["197","A - ring above"],["198","ligature AE"],["199","C - cedilla"],["200","E - grave"],["201","E - acute"],["202","E - circumflex"],["203","E - diaeresis"],["204","I - grave"],["205","I - acute"],["206","I - circumflex"],["207","I - diaeresis"],["208","ETH"],["209","N - tilde"],["210","O - grave"],["211","O - acute"],["212","O - circumflex"],["213","O - tilde"],["214","O - diaeresis"],["216","O - slash"],["338","ligature OE"],["352","S - caron"],["217","U - grave"],["218","U - acute"],["219","U - circumflex"],["220","U - diaeresis"],["221","Y - acute"],["376","Y - diaeresis"],["222","THORN"],["224","a - grave"],["225","a - acute"],["226","a - circumflex"],["227","a - tilde"],["228","a - diaeresis"],["229","a - ring above"],["230","ligature ae"],["231","c - cedilla"],["232","e - grave"],["233","e - acute"],["234","e - circumflex"],["235","e - diaeresis"],["236","i - grave"],["237","i - acute"],["238","i - circumflex"],["239","i - diaeresis"],["240","eth"],["241","n - tilde"],["242","o - grave"],["243","o - acute"],["244","o - circumflex"],["245","o - tilde"],["246","o - diaeresis"],["248","o slash"],["339","ligature oe"],["353","s - caron"],["249","u - grave"],["250","u - acute"],["251","u - circumflex"],["252","u - diaeresis"],["253","y - acute"],["254","thorn"],["255","y - diaeresis"],["913","Alpha"],["914","Beta"],["915","Gamma"],["916","Delta"],["917","Epsilon"],["918","Zeta"],["919","Eta"],["920","Theta"],["921","Iota"],["922","Kappa"],["923","Lambda"],["924","Mu"],["925","Nu"],["926","Xi"],["927","Omicron"],["928","Pi"],["929","Rho"],["931","Sigma"],["932","Tau"],["933","Upsilon"],["934","Phi"],["935","Chi"],["936","Psi"],["937","Omega"],["945","alpha"],["946","beta"],["947","gamma"],["948","delta"],["949","epsilon"],["950","zeta"],["951","eta"],["952","theta"],["953","iota"],["954","kappa"],["955","lambda"],["956","mu"],["957","nu"],["958","xi"],["959","omicron"],["960","pi"],["961","rho"],["962","final sigma"],["963","sigma"],["964","tau"],["965","upsilon"],["966","phi"],["967","chi"],["968","psi"],["969","omega"],["8501","alef symbol"],["982","pi symbol"],["8476","real part symbol"],["978","upsilon - hook symbol"],["8472","Weierstrass p"],["8465","imaginary part"],["8592","leftwards arrow"],["8593","upwards arrow"],["8594","rightwards arrow"],["8595","downwards arrow"],["8596","left right arrow"],["8629","carriage return"],["8656","leftwards double arrow"],["8657","upwards double arrow"],["8658","rightwards double arrow"],["8659","downwards double arrow"],["8660","left right double arrow"],["8756","therefore"],["8834","subset of"],["8835","superset of"],["8836","not a subset of"],["8838","subset of or equal to"],["8839","superset of or equal to"],["8853","circled plus"],["8855","circled times"],["8869","perpendicular"],["8901","dot operator"],["8968","left ceiling"],["8969","right ceiling"],["8970","left floor"],["8971","right floor"],["9001","left-pointing angle bracket"],["9002","right-pointing angle bracket"],["9674","lozenge"],["9824","black spade suit"],["9827","black club suit"],["9829","black heart suit"],["9830","black diamond suit"],["8194","en space"],["8195","em space"],["8201","thin space"],["8204","zero width non-joiner"],["8205","zero width joiner"],["8206","left-to-right mark"],["8207","right-to-left mark"]];a.addButton("charmap",{icon:"charmap",tooltip:"Special character",onclick:b}),a.addMenuItem("charmap",{icon:"charmap",text:"Special character",onclick:b,context:"insert"})}); \ No newline at end of file +!function(){"use strict";var e=tinymce.util.Tools.resolve("tinymce.PluginManager"),i=function(e,t){return e.fire("insertCustomChar",{chr:t})},l=function(e,t){var a=i(e,t).chr;e.execCommand("mceInsertContent",!1,a)},a=tinymce.util.Tools.resolve("tinymce.util.Tools"),r=function(e){return e.settings.charmap},n=function(e){return e.settings.charmap_append},o=a.isArray,c=function(e){return o(e)?[].concat((t=e,a.grep(t,function(e){return o(e)&&2===e.length}))):"function"==typeof e?e():[];var t},s=function(e){return function(e,t){var a=r(e);a&&(t=c(a));var i=n(e);return i?[].concat(t).concat(c(i)):t}(e,[["160","no-break space"],["173","soft hyphen"],["34","quotation mark"],["162","cent sign"],["8364","euro sign"],["163","pound sign"],["165","yen sign"],["169","copyright sign"],["174","registered sign"],["8482","trade mark sign"],["8240","per mille sign"],["181","micro sign"],["183","middle dot"],["8226","bullet"],["8230","three dot leader"],["8242","minutes / feet"],["8243","seconds / inches"],["167","section sign"],["182","paragraph sign"],["223","sharp s / ess-zed"],["8249","single left-pointing angle quotation mark"],["8250","single right-pointing angle quotation mark"],["171","left pointing guillemet"],["187","right pointing guillemet"],["8216","left single quotation mark"],["8217","right single quotation mark"],["8220","left double quotation mark"],["8221","right double quotation mark"],["8218","single low-9 quotation mark"],["8222","double low-9 quotation mark"],["60","less-than sign"],["62","greater-than sign"],["8804","less-than or equal to"],["8805","greater-than or equal to"],["8211","en dash"],["8212","em dash"],["175","macron"],["8254","overline"],["164","currency sign"],["166","broken bar"],["168","diaeresis"],["161","inverted exclamation mark"],["191","turned question mark"],["710","circumflex accent"],["732","small tilde"],["176","degree sign"],["8722","minus sign"],["177","plus-minus sign"],["247","division sign"],["8260","fraction slash"],["215","multiplication sign"],["185","superscript one"],["178","superscript two"],["179","superscript three"],["188","fraction one quarter"],["189","fraction one half"],["190","fraction three quarters"],["402","function / florin"],["8747","integral"],["8721","n-ary sumation"],["8734","infinity"],["8730","square root"],["8764","similar to"],["8773","approximately equal to"],["8776","almost equal to"],["8800","not equal to"],["8801","identical to"],["8712","element of"],["8713","not an element of"],["8715","contains as member"],["8719","n-ary product"],["8743","logical and"],["8744","logical or"],["172","not sign"],["8745","intersection"],["8746","union"],["8706","partial differential"],["8704","for all"],["8707","there exists"],["8709","diameter"],["8711","backward difference"],["8727","asterisk operator"],["8733","proportional to"],["8736","angle"],["180","acute accent"],["184","cedilla"],["170","feminine ordinal indicator"],["186","masculine ordinal indicator"],["8224","dagger"],["8225","double dagger"],["192","A - grave"],["193","A - acute"],["194","A - circumflex"],["195","A - tilde"],["196","A - diaeresis"],["197","A - ring above"],["256","A - macron"],["198","ligature AE"],["199","C - cedilla"],["200","E - grave"],["201","E - acute"],["202","E - circumflex"],["203","E - diaeresis"],["274","E - macron"],["204","I - grave"],["205","I - acute"],["206","I - circumflex"],["207","I - diaeresis"],["298","I - macron"],["208","ETH"],["209","N - tilde"],["210","O - grave"],["211","O - acute"],["212","O - circumflex"],["213","O - tilde"],["214","O - diaeresis"],["216","O - slash"],["332","O - macron"],["338","ligature OE"],["352","S - caron"],["217","U - grave"],["218","U - acute"],["219","U - circumflex"],["220","U - diaeresis"],["362","U - macron"],["221","Y - acute"],["376","Y - diaeresis"],["562","Y - macron"],["222","THORN"],["224","a - grave"],["225","a - acute"],["226","a - circumflex"],["227","a - tilde"],["228","a - diaeresis"],["229","a - ring above"],["257","a - macron"],["230","ligature ae"],["231","c - cedilla"],["232","e - grave"],["233","e - acute"],["234","e - circumflex"],["235","e - diaeresis"],["275","e - macron"],["236","i - grave"],["237","i - acute"],["238","i - circumflex"],["239","i - diaeresis"],["299","i - macron"],["240","eth"],["241","n - tilde"],["242","o - grave"],["243","o - acute"],["244","o - circumflex"],["245","o - tilde"],["246","o - diaeresis"],["248","o slash"],["333","o macron"],["339","ligature oe"],["353","s - caron"],["249","u - grave"],["250","u - acute"],["251","u - circumflex"],["252","u - diaeresis"],["363","u - macron"],["253","y - acute"],["254","thorn"],["255","y - diaeresis"],["563","y - macron"],["913","Alpha"],["914","Beta"],["915","Gamma"],["916","Delta"],["917","Epsilon"],["918","Zeta"],["919","Eta"],["920","Theta"],["921","Iota"],["922","Kappa"],["923","Lambda"],["924","Mu"],["925","Nu"],["926","Xi"],["927","Omicron"],["928","Pi"],["929","Rho"],["931","Sigma"],["932","Tau"],["933","Upsilon"],["934","Phi"],["935","Chi"],["936","Psi"],["937","Omega"],["945","alpha"],["946","beta"],["947","gamma"],["948","delta"],["949","epsilon"],["950","zeta"],["951","eta"],["952","theta"],["953","iota"],["954","kappa"],["955","lambda"],["956","mu"],["957","nu"],["958","xi"],["959","omicron"],["960","pi"],["961","rho"],["962","final sigma"],["963","sigma"],["964","tau"],["965","upsilon"],["966","phi"],["967","chi"],["968","psi"],["969","omega"],["8501","alef symbol"],["982","pi symbol"],["8476","real part symbol"],["978","upsilon - hook symbol"],["8472","Weierstrass p"],["8465","imaginary part"],["8592","leftwards arrow"],["8593","upwards arrow"],["8594","rightwards arrow"],["8595","downwards arrow"],["8596","left right arrow"],["8629","carriage return"],["8656","leftwards double arrow"],["8657","upwards double arrow"],["8658","rightwards double arrow"],["8659","downwards double arrow"],["8660","left right double arrow"],["8756","therefore"],["8834","subset of"],["8835","superset of"],["8836","not a subset of"],["8838","subset of or equal to"],["8839","superset of or equal to"],["8853","circled plus"],["8855","circled times"],["8869","perpendicular"],["8901","dot operator"],["8968","left ceiling"],["8969","right ceiling"],["8970","left floor"],["8971","right floor"],["9001","left-pointing angle bracket"],["9002","right-pointing angle bracket"],["9674","lozenge"],["9824","black spade suit"],["9827","black club suit"],["9829","black heart suit"],["9830","black diamond suit"],["8194","en space"],["8195","em space"],["8201","thin space"],["8204","zero width non-joiner"],["8205","zero width joiner"],["8206","left-to-right mark"],["8207","right-to-left mark"]])},t=function(t){return{getCharMap:function(){return s(t)},insertChar:function(e){l(t,e)}}},u=function(e){var t,a,i,r=Math.min(e.length,25),n=Math.ceil(e.length/r);for(t='',i=0;i",a=0;a
    '+s+"
    "}else t+="
    "}return t+=""},d=function(e){for(;e;){if("TD"===e.nodeName)return e;e=e.parentNode}},m=function(n){var o,e={type:"container",html:u(s(n)),onclick:function(e){var t=e.target;if(/^(TD|DIV)$/.test(t.nodeName)){var a=d(t).firstChild;if(a&&a.hasAttribute("data-chr")){var i=a.getAttribute("data-chr"),r=parseInt(i,10);isNaN(r)||l(n,String.fromCharCode(r)),e.ctrlKey||o.close()}}},onmouseover:function(e){var t=d(e.target);t&&t.firstChild?(o.find("#preview").text(t.firstChild.firstChild.data),o.find("#previewTitle").text(t.title)):(o.find("#preview").text(" "),o.find("#previewTitle").text(" "))}};o=n.windowManager.open({title:"Special character",spacing:10,padding:10,items:[e,{type:"container",layout:"flex",direction:"column",align:"center",spacing:5,minWidth:160,minHeight:160,items:[{type:"label",name:"preview",text:" ",style:"font-size: 40px; text-align: center",border:1,minWidth:140,minHeight:80},{type:"spacer",minHeight:20},{type:"label",name:"previewTitle",text:" ",style:"white-space: pre-wrap;",border:1,minWidth:140}]}],buttons:[{text:"Close",onclick:function(){o.close()}}]})},g=function(e){e.addCommand("mceShowCharmap",function(){m(e)})},p=function(e){e.addButton("charmap",{icon:"charmap",tooltip:"Special character",cmd:"mceShowCharmap"}),e.addMenuItem("charmap",{icon:"charmap",text:"Special character",cmd:"mceShowCharmap",context:"insert"})};e.add("charmap",function(e){return g(e),p(e),t(e)})}(); \ No newline at end of file diff --git a/WebCms/Umbraco/lib/tinymce/plugins/code/index.js b/WebCms/Umbraco/lib/tinymce/plugins/code/index.js new file mode 100644 index 0000000..1e412f3 --- /dev/null +++ b/WebCms/Umbraco/lib/tinymce/plugins/code/index.js @@ -0,0 +1,7 @@ +// Exports the "code" plugin for usage with module loaders +// Usage: +// CommonJS: +// require('tinymce/plugins/code') +// ES2015: +// import 'tinymce/plugins/code' +require('./plugin.js'); \ No newline at end of file diff --git a/WebCms/Umbraco/lib/tinymce/plugins/code/plugin.js b/WebCms/Umbraco/lib/tinymce/plugins/code/plugin.js index 25c27d9..41a1355 100644 --- a/WebCms/Umbraco/lib/tinymce/plugins/code/plugin.js +++ b/WebCms/Umbraco/lib/tinymce/plugins/code/plugin.js @@ -1,60 +1,94 @@ -/** - * plugin.js - * - * Copyright, Moxiecode Systems AB - * Released under LGPL License. - * - * License: http://www.tinymce.com/license - * Contributing: http://www.tinymce.com/contributing - */ +(function () { +var code = (function () { + 'use strict'; -/*global tinymce:true */ + var global = tinymce.util.Tools.resolve('tinymce.PluginManager'); -tinymce.PluginManager.add('code', function(editor) { - function showDialog() { - var win = editor.windowManager.open({ - title: "Source code", - body: { - type: 'textbox', - name: 'code', - multiline: true, - minWidth: editor.getParam("code_dialog_width", 600), - minHeight: editor.getParam("code_dialog_height", Math.min(tinymce.DOM.getViewPort().h - 200, 500)), - spellcheck: false, - style: 'direction: ltr; text-align: left' - }, - onSubmit: function(e) { - // We get a lovely "Wrong document" error in IE 11 if we - // don't move the focus to the editor before creating an undo - // transation since it tries to make a bookmark for the current selection - editor.focus(); + var global$1 = tinymce.util.Tools.resolve('tinymce.dom.DOMUtils'); - editor.undoManager.transact(function() { - editor.setContent(e.data.code); - }); + var getMinWidth = function (editor) { + return editor.getParam('code_dialog_width', 600); + }; + var getMinHeight = function (editor) { + return editor.getParam('code_dialog_height', Math.min(global$1.DOM.getViewPort().h - 200, 500)); + }; + var $_7a5bps9ojh8lpugv = { + getMinWidth: getMinWidth, + getMinHeight: getMinHeight + }; - editor.selection.setCursorLocation(); - editor.nodeChanged(); - } - }); + var setContent = function (editor, html) { + editor.focus(); + editor.undoManager.transact(function () { + editor.setContent(html); + }); + editor.selection.setCursorLocation(); + editor.nodeChanged(); + }; + var getContent = function (editor) { + return editor.getContent({ source_view: true }); + }; + var $_4f0hos9qjh8lpugw = { + setContent: setContent, + getContent: getContent + }; - // Gecko has a major performance issue with textarea - // contents so we need to set it when all reflows are done - win.find('#code').value(editor.getContent({source_view: true})); - } + var open = function (editor) { + var minWidth = $_7a5bps9ojh8lpugv.getMinWidth(editor); + var minHeight = $_7a5bps9ojh8lpugv.getMinHeight(editor); + var win = editor.windowManager.open({ + title: 'Source code', + body: { + type: 'textbox', + name: 'code', + multiline: true, + minWidth: minWidth, + minHeight: minHeight, + spellcheck: false, + style: 'direction: ltr; text-align: left' + }, + onSubmit: function (e) { + $_4f0hos9qjh8lpugw.setContent(editor, e.data.code); + } + }); + win.find('#code').value($_4f0hos9qjh8lpugw.getContent(editor)); + }; + var $_8t2ji69njh8lpugu = { open: open }; - editor.addCommand("mceCodeEditor", showDialog); + var register = function (editor) { + editor.addCommand('mceCodeEditor', function () { + $_8t2ji69njh8lpugu.open(editor); + }); + }; + var $_1cb0ek9mjh8lpugt = { register: register }; - editor.addButton('code', { - icon: 'code', - tooltip: 'Source code', - onclick: showDialog - }); + var register$1 = function (editor) { + editor.addButton('code', { + icon: 'code', + tooltip: 'Source code', + onclick: function () { + $_8t2ji69njh8lpugu.open(editor); + } + }); + editor.addMenuItem('code', { + icon: 'code', + text: 'Source code', + onclick: function () { + $_8t2ji69njh8lpugu.open(editor); + } + }); + }; + var $_aziuou9rjh8lpugx = { register: register$1 }; - editor.addMenuItem('code', { - icon: 'code', - text: 'Source code', - context: 'tools', - onclick: showDialog - }); -}); \ No newline at end of file + global.add('code', function (editor) { + $_1cb0ek9mjh8lpugt.register(editor); + $_aziuou9rjh8lpugx.register(editor); + return {}; + }); + function Plugin () { + } + + return Plugin; + +}()); +})(); diff --git a/WebCms/Umbraco/lib/tinymce/plugins/code/plugin.min.js b/WebCms/Umbraco/lib/tinymce/plugins/code/plugin.min.js index d6331f8..7afcca6 100644 --- a/WebCms/Umbraco/lib/tinymce/plugins/code/plugin.min.js +++ b/WebCms/Umbraco/lib/tinymce/plugins/code/plugin.min.js @@ -1 +1 @@ -tinymce.PluginManager.add("code",function(a){function b(){var b=a.windowManager.open({title:"Source code",body:{type:"textbox",name:"code",multiline:!0,minWidth:a.getParam("code_dialog_width",600),minHeight:a.getParam("code_dialog_height",Math.min(tinymce.DOM.getViewPort().h-200,500)),spellcheck:!1,style:"direction: ltr; text-align: left"},onSubmit:function(b){a.focus(),a.undoManager.transact(function(){a.setContent(b.data.code)}),a.selection.setCursorLocation(),a.nodeChanged()}});b.find("#code").value(a.getContent({source_view:!0}))}a.addCommand("mceCodeEditor",b),a.addButton("code",{icon:"code",tooltip:"Source code",onclick:b}),a.addMenuItem("code",{icon:"code",text:"Source code",context:"tools",onclick:b})}); \ No newline at end of file +!function(){"use strict";var t=tinymce.util.Tools.resolve("tinymce.PluginManager"),n=tinymce.util.Tools.resolve("tinymce.dom.DOMUtils"),o=function(t){return t.getParam("code_dialog_width",600)},i=function(t){return t.getParam("code_dialog_height",Math.min(n.DOM.getViewPort().h-200,500))},c=function(t,n){t.focus(),t.undoManager.transact(function(){t.setContent(n)}),t.selection.setCursorLocation(),t.nodeChanged()},d=function(t){return t.getContent({source_view:!0})},e=function(n){var t=o(n),e=i(n);n.windowManager.open({title:"Source code",body:{type:"textbox",name:"code",multiline:!0,minWidth:t,minHeight:e,spellcheck:!1,style:"direction: ltr; text-align: left"},onSubmit:function(t){c(n,t.data.code)}}).find("#code").value(d(n))},u=function(t){t.addCommand("mceCodeEditor",function(){e(t)})},a=function(t){t.addButton("code",{icon:"code",tooltip:"Source code",onclick:function(){e(t)}}),t.addMenuItem("code",{icon:"code",text:"Source code",onclick:function(){e(t)}})};t.add("code",function(t){return u(t),a(t),{}})}(); \ No newline at end of file diff --git a/WebCms/Umbraco/lib/tinymce/plugins/codemirror/langs/da.js b/WebCms/Umbraco/lib/tinymce/plugins/codemirror/langs/da.js new file mode 100644 index 0000000..d5e0bbd --- /dev/null +++ b/WebCms/Umbraco/lib/tinymce/plugins/codemirror/langs/da.js @@ -0,0 +1,8 @@ +tinymce.addI18n('en',{ + 'HTML source code': 'HTML source code', + 'Start search': 'Start search', + 'Find next': 'Find next', + 'Find previous': 'Find previous', + 'Replace': 'Replace', + 'Replace all': 'Replace all' +}); diff --git a/WebCms/Umbraco/lib/tinymce/plugins/codemirror/langs/de.js b/WebCms/Umbraco/lib/tinymce/plugins/codemirror/langs/de.js new file mode 100644 index 0000000..d5e0bbd --- /dev/null +++ b/WebCms/Umbraco/lib/tinymce/plugins/codemirror/langs/de.js @@ -0,0 +1,8 @@ +tinymce.addI18n('en',{ + 'HTML source code': 'HTML source code', + 'Start search': 'Start search', + 'Find next': 'Find next', + 'Find previous': 'Find previous', + 'Replace': 'Replace', + 'Replace all': 'Replace all' +}); diff --git a/WebCms/Umbraco/lib/tinymce/plugins/codemirror/langs/en_us.js b/WebCms/Umbraco/lib/tinymce/plugins/codemirror/langs/en_us.js new file mode 100644 index 0000000..d5e0bbd --- /dev/null +++ b/WebCms/Umbraco/lib/tinymce/plugins/codemirror/langs/en_us.js @@ -0,0 +1,8 @@ +tinymce.addI18n('en',{ + 'HTML source code': 'HTML source code', + 'Start search': 'Start search', + 'Find next': 'Find next', + 'Find previous': 'Find previous', + 'Replace': 'Replace', + 'Replace all': 'Replace all' +}); diff --git a/WebCms/Umbraco/lib/tinymce/plugins/codemirror/langs/fi.js b/WebCms/Umbraco/lib/tinymce/plugins/codemirror/langs/fi.js new file mode 100644 index 0000000..d5e0bbd --- /dev/null +++ b/WebCms/Umbraco/lib/tinymce/plugins/codemirror/langs/fi.js @@ -0,0 +1,8 @@ +tinymce.addI18n('en',{ + 'HTML source code': 'HTML source code', + 'Start search': 'Start search', + 'Find next': 'Find next', + 'Find previous': 'Find previous', + 'Replace': 'Replace', + 'Replace all': 'Replace all' +}); diff --git a/WebCms/Umbraco/lib/tinymce/plugins/codemirror/langs/fr.js b/WebCms/Umbraco/lib/tinymce/plugins/codemirror/langs/fr.js new file mode 100644 index 0000000..d5e0bbd --- /dev/null +++ b/WebCms/Umbraco/lib/tinymce/plugins/codemirror/langs/fr.js @@ -0,0 +1,8 @@ +tinymce.addI18n('en',{ + 'HTML source code': 'HTML source code', + 'Start search': 'Start search', + 'Find next': 'Find next', + 'Find previous': 'Find previous', + 'Replace': 'Replace', + 'Replace all': 'Replace all' +}); diff --git a/WebCms/Umbraco/lib/tinymce/plugins/codemirror/langs/he.js b/WebCms/Umbraco/lib/tinymce/plugins/codemirror/langs/he.js new file mode 100644 index 0000000..d5e0bbd --- /dev/null +++ b/WebCms/Umbraco/lib/tinymce/plugins/codemirror/langs/he.js @@ -0,0 +1,8 @@ +tinymce.addI18n('en',{ + 'HTML source code': 'HTML source code', + 'Start search': 'Start search', + 'Find next': 'Find next', + 'Find previous': 'Find previous', + 'Replace': 'Replace', + 'Replace all': 'Replace all' +}); diff --git a/WebCms/Umbraco/lib/tinymce/plugins/codemirror/langs/it.js b/WebCms/Umbraco/lib/tinymce/plugins/codemirror/langs/it.js new file mode 100644 index 0000000..d5e0bbd --- /dev/null +++ b/WebCms/Umbraco/lib/tinymce/plugins/codemirror/langs/it.js @@ -0,0 +1,8 @@ +tinymce.addI18n('en',{ + 'HTML source code': 'HTML source code', + 'Start search': 'Start search', + 'Find next': 'Find next', + 'Find previous': 'Find previous', + 'Replace': 'Replace', + 'Replace all': 'Replace all' +}); diff --git a/WebCms/Umbraco/lib/tinymce/plugins/codemirror/langs/ja.js b/WebCms/Umbraco/lib/tinymce/plugins/codemirror/langs/ja.js new file mode 100644 index 0000000..d5e0bbd --- /dev/null +++ b/WebCms/Umbraco/lib/tinymce/plugins/codemirror/langs/ja.js @@ -0,0 +1,8 @@ +tinymce.addI18n('en',{ + 'HTML source code': 'HTML source code', + 'Start search': 'Start search', + 'Find next': 'Find next', + 'Find previous': 'Find previous', + 'Replace': 'Replace', + 'Replace all': 'Replace all' +}); diff --git a/WebCms/Umbraco/lib/tinymce/plugins/codemirror/langs/nl.js b/WebCms/Umbraco/lib/tinymce/plugins/codemirror/langs/nl.js new file mode 100644 index 0000000..d5e0bbd --- /dev/null +++ b/WebCms/Umbraco/lib/tinymce/plugins/codemirror/langs/nl.js @@ -0,0 +1,8 @@ +tinymce.addI18n('en',{ + 'HTML source code': 'HTML source code', + 'Start search': 'Start search', + 'Find next': 'Find next', + 'Find previous': 'Find previous', + 'Replace': 'Replace', + 'Replace all': 'Replace all' +}); diff --git a/WebCms/Umbraco/lib/tinymce/plugins/codemirror/langs/no.js b/WebCms/Umbraco/lib/tinymce/plugins/codemirror/langs/no.js new file mode 100644 index 0000000..d5e0bbd --- /dev/null +++ b/WebCms/Umbraco/lib/tinymce/plugins/codemirror/langs/no.js @@ -0,0 +1,8 @@ +tinymce.addI18n('en',{ + 'HTML source code': 'HTML source code', + 'Start search': 'Start search', + 'Find next': 'Find next', + 'Find previous': 'Find previous', + 'Replace': 'Replace', + 'Replace all': 'Replace all' +}); diff --git a/WebCms/Umbraco/lib/tinymce/plugins/codemirror/langs/pl.js b/WebCms/Umbraco/lib/tinymce/plugins/codemirror/langs/pl.js new file mode 100644 index 0000000..d5e0bbd --- /dev/null +++ b/WebCms/Umbraco/lib/tinymce/plugins/codemirror/langs/pl.js @@ -0,0 +1,8 @@ +tinymce.addI18n('en',{ + 'HTML source code': 'HTML source code', + 'Start search': 'Start search', + 'Find next': 'Find next', + 'Find previous': 'Find previous', + 'Replace': 'Replace', + 'Replace all': 'Replace all' +}); diff --git a/WebCms/Umbraco/lib/tinymce/plugins/codemirror/langs/pt.js b/WebCms/Umbraco/lib/tinymce/plugins/codemirror/langs/pt.js new file mode 100644 index 0000000..d5e0bbd --- /dev/null +++ b/WebCms/Umbraco/lib/tinymce/plugins/codemirror/langs/pt.js @@ -0,0 +1,8 @@ +tinymce.addI18n('en',{ + 'HTML source code': 'HTML source code', + 'Start search': 'Start search', + 'Find next': 'Find next', + 'Find previous': 'Find previous', + 'Replace': 'Replace', + 'Replace all': 'Replace all' +}); diff --git a/WebCms/Umbraco/lib/tinymce/plugins/codemirror/langs/ru.js b/WebCms/Umbraco/lib/tinymce/plugins/codemirror/langs/ru.js new file mode 100644 index 0000000..d5e0bbd --- /dev/null +++ b/WebCms/Umbraco/lib/tinymce/plugins/codemirror/langs/ru.js @@ -0,0 +1,8 @@ +tinymce.addI18n('en',{ + 'HTML source code': 'HTML source code', + 'Start search': 'Start search', + 'Find next': 'Find next', + 'Find previous': 'Find previous', + 'Replace': 'Replace', + 'Replace all': 'Replace all' +}); diff --git a/WebCms/Umbraco/lib/tinymce/plugins/codemirror/langs/sv.js b/WebCms/Umbraco/lib/tinymce/plugins/codemirror/langs/sv.js new file mode 100644 index 0000000..d5e0bbd --- /dev/null +++ b/WebCms/Umbraco/lib/tinymce/plugins/codemirror/langs/sv.js @@ -0,0 +1,8 @@ +tinymce.addI18n('en',{ + 'HTML source code': 'HTML source code', + 'Start search': 'Start search', + 'Find next': 'Find next', + 'Find previous': 'Find previous', + 'Replace': 'Replace', + 'Replace all': 'Replace all' +}); diff --git a/WebCms/Umbraco/lib/tinymce/plugins/codemirror/langs/zh.js b/WebCms/Umbraco/lib/tinymce/plugins/codemirror/langs/zh.js new file mode 100644 index 0000000..d5e0bbd --- /dev/null +++ b/WebCms/Umbraco/lib/tinymce/plugins/codemirror/langs/zh.js @@ -0,0 +1,8 @@ +tinymce.addI18n('en',{ + 'HTML source code': 'HTML source code', + 'Start search': 'Start search', + 'Find next': 'Find next', + 'Find previous': 'Find previous', + 'Replace': 'Replace', + 'Replace all': 'Replace all' +}); diff --git a/WebCms/Umbraco/lib/tinymce/plugins/codesample/css/prism.css b/WebCms/Umbraco/lib/tinymce/plugins/codesample/css/prism.css new file mode 100644 index 0000000..128237f --- /dev/null +++ b/WebCms/Umbraco/lib/tinymce/plugins/codesample/css/prism.css @@ -0,0 +1,138 @@ +/* http://prismjs.com/download.html?themes=prism&languages=markup+css+clike+javascript */ +/** + * prism.js default theme for JavaScript, CSS and HTML + * Based on dabblet (http://dabblet.com) + * @author Lea Verou + */ + +code[class*="language-"], +pre[class*="language-"] { + color: black; + text-shadow: 0 1px white; + font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; + direction: ltr; + text-align: left; + white-space: pre; + word-spacing: normal; + word-break: normal; + word-wrap: normal; + line-height: 1.5; + + -moz-tab-size: 4; + -o-tab-size: 4; + tab-size: 4; + + -webkit-hyphens: none; + -moz-hyphens: none; + -ms-hyphens: none; + hyphens: none; +} + +pre[class*="language-"]::-moz-selection, pre[class*="language-"] ::-moz-selection, +code[class*="language-"]::-moz-selection, code[class*="language-"] ::-moz-selection { + text-shadow: none; + background: #b3d4fc; +} + +pre[class*="language-"]::selection, pre[class*="language-"] ::selection, +code[class*="language-"]::selection, code[class*="language-"] ::selection { + text-shadow: none; + background: #b3d4fc; +} + +@media print { + code[class*="language-"], + pre[class*="language-"] { + text-shadow: none; + } +} + +/* Code blocks */ +pre[class*="language-"] { + padding: 1em; + margin: .5em 0; + overflow: auto; +} + +:not(pre) > code[class*="language-"], +pre[class*="language-"] { + background: #f5f2f0; +} + +/* Inline code */ +:not(pre) > code[class*="language-"] { + padding: .1em; + border-radius: .3em; +} + +.token.comment, +.token.prolog, +.token.doctype, +.token.cdata { + color: slategray; +} + +.token.punctuation { + color: #999; +} + +.namespace { + opacity: .7; +} + +.token.property, +.token.tag, +.token.boolean, +.token.number, +.token.constant, +.token.symbol, +.token.deleted { + color: #905; +} + +.token.selector, +.token.attr-name, +.token.string, +.token.char, +.token.builtin, +.token.inserted { + color: #690; +} + +.token.operator, +.token.entity, +.token.url, +.language-css .token.string, +.style .token.string { + color: #a67f59; + background: hsla(0, 0%, 100%, .5); +} + +.token.atrule, +.token.attr-value, +.token.keyword { + color: #07a; +} + +.token.function { + color: #DD4A68; +} + +.token.regex, +.token.important, +.token.variable { + color: #e90; +} + +.token.important, +.token.bold { + font-weight: bold; +} +.token.italic { + font-style: italic; +} + +.token.entity { + cursor: help; +} + diff --git a/WebCms/Umbraco/lib/tinymce/plugins/codesample/index.js b/WebCms/Umbraco/lib/tinymce/plugins/codesample/index.js new file mode 100644 index 0000000..c400ec3 --- /dev/null +++ b/WebCms/Umbraco/lib/tinymce/plugins/codesample/index.js @@ -0,0 +1,7 @@ +// Exports the "codesample" plugin for usage with module loaders +// Usage: +// CommonJS: +// require('tinymce/plugins/codesample') +// ES2015: +// import 'tinymce/plugins/codesample' +require('./plugin.js'); \ No newline at end of file diff --git a/WebCms/Umbraco/lib/tinymce/plugins/codesample/plugin.js b/WebCms/Umbraco/lib/tinymce/plugins/codesample/plugin.js new file mode 100644 index 0000000..bb6853f --- /dev/null +++ b/WebCms/Umbraco/lib/tinymce/plugins/codesample/plugin.js @@ -0,0 +1,967 @@ +(function () { +var codesample = (function () { + 'use strict'; + + var Cell = function (initial) { + var value = initial; + var get = function () { + return value; + }; + var set = function (v) { + value = v; + }; + var clone = function () { + return Cell(get()); + }; + return { + get: get, + set: set, + clone: clone + }; + }; + + var global = tinymce.util.Tools.resolve('tinymce.PluginManager'); + + var global$1 = tinymce.util.Tools.resolve('tinymce.dom.DOMUtils'); + + var getContentCss = function (editor) { + return editor.settings.codesample_content_css; + }; + var getLanguages = function (editor) { + return editor.settings.codesample_languages; + }; + var getDialogMinWidth = function (editor) { + return Math.min(global$1.DOM.getViewPort().w, editor.getParam('codesample_dialog_width', 800)); + }; + var getDialogMinHeight = function (editor) { + return Math.min(global$1.DOM.getViewPort().w, editor.getParam('codesample_dialog_height', 650)); + }; + var $_3ew0z79xjh8lpuhi = { + getContentCss: getContentCss, + getLanguages: getLanguages, + getDialogMinWidth: getDialogMinWidth, + getDialogMinHeight: getDialogMinHeight + }; + + var window = {}; + var global$2 = window; + var _self = typeof window !== 'undefined' ? window : typeof WorkerGlobalScope !== 'undefined' && self instanceof WorkerGlobalScope ? self : {}; + var Prism = function () { + var lang = /\blang(?:uage)?-(?!\*)(\w+)\b/i; + var _ = _self.Prism = { + util: { + encode: function (tokens) { + if (tokens instanceof Token) { + return new Token(tokens.type, _.util.encode(tokens.content), tokens.alias); + } else if (_.util.type(tokens) === 'Array') { + return tokens.map(_.util.encode); + } else { + return tokens.replace(/&/g, '&').replace(/ text.length) { + break tokenloop; + } + if (str instanceof Token) { + continue; + } + pattern.lastIndex = 0; + var match = pattern.exec(str); + if (match) { + if (lookbehind) { + lookbehindLength = match[1].length; + } + var from = match.index - 1 + lookbehindLength; + match = match[0].slice(lookbehindLength); + var len = match.length, to = from + len, before = str.slice(0, from + 1), after = str.slice(to + 1); + var args = [ + i, + 1 + ]; + if (before) { + args.push(before); + } + var wrapped = new Token(token, inside ? _.tokenize(match, inside) : match, alias); + args.push(wrapped); + if (after) { + args.push(after); + } + Array.prototype.splice.apply(strarr, args); + } + } + } + } + return strarr; + }, + hooks: { + all: {}, + add: function (name, callback) { + var hooks = _.hooks.all; + hooks[name] = hooks[name] || []; + hooks[name].push(callback); + }, + run: function (name, env) { + var callbacks = _.hooks.all[name]; + if (!callbacks || !callbacks.length) { + return; + } + for (var i = 0, callback = void 0; callback = callbacks[i++];) { + callback(env); + } + } + } + }; + var Token = _.Token = function (type, content, alias) { + this.type = type; + this.content = content; + this.alias = alias; + }; + Token.stringify = function (o, language, parent) { + if (typeof o === 'string') { + return o; + } + if (_.util.type(o) === 'Array') { + return o.map(function (element) { + return Token.stringify(element, language, o); + }).join(''); + } + var env = { + type: o.type, + content: Token.stringify(o.content, language, parent), + tag: 'span', + classes: [ + 'token', + o.type + ], + attributes: {}, + language: language, + parent: parent + }; + if (env.type === 'comment') { + env.attributes.spellcheck = 'true'; + } + if (o.alias) { + var aliases = _.util.type(o.alias) === 'Array' ? o.alias : [o.alias]; + Array.prototype.push.apply(env.classes, aliases); + } + _.hooks.run('wrap', env); + var attributes = ''; + for (var name_1 in env.attributes) { + attributes += (attributes ? ' ' : '') + name_1 + '="' + (env.attributes[name_1] || '') + '"'; + } + return '<' + env.tag + ' class="' + env.classes.join(' ') + '" ' + attributes + '>' + env.content + ''; + }; + if (!_self.document) { + if (!_self.addEventListener) { + return _self.Prism; + } + _self.addEventListener('message', function (evt) { + var message = JSON.parse(evt.data), lang = message.language, code = message.code, immediateClose = message.immediateClose; + _self.postMessage(_.highlight(code, _.languages[lang], lang)); + if (immediateClose) { + _self.close(); + } + }, false); + return _self.Prism; + } + }(); + if (typeof global$2 !== 'undefined') { + global$2.Prism = Prism; + } + Prism.languages.markup = { + comment: //, + prolog: /<\?[\w\W]+?\?>/, + doctype: //, + cdata: //i, + tag: { + pattern: /<\/?[^\s>\/=.]+(?:\s+[^\s>\/=]+(?:=(?:("|')(?:\\\1|\\?(?!\1)[\w\W])*\1|[^\s'">=]+))?)*\s*\/?>/i, + inside: { + 'tag': { + pattern: /^<\/?[^\s>\/]+/i, + inside: { + punctuation: /^<\/?/, + namespace: /^[^\s>\/:]+:/ + } + }, + 'attr-value': { + pattern: /=(?:('|")[\w\W]*?(\1)|[^\s>]+)/i, + inside: { punctuation: /[=>"']/ } + }, + 'punctuation': /\/?>/, + 'attr-name': { + pattern: /[^\s>\/]+/, + inside: { namespace: /^[^\s>\/:]+:/ } + } + } + }, + entity: /&#?[\da-z]{1,8};/i + }; + Prism.hooks.add('wrap', function (env) { + if (env.type === 'entity') { + env.attributes.title = env.content.replace(/&/, '&'); + } + }); + Prism.languages.xml = Prism.languages.markup; + Prism.languages.html = Prism.languages.markup; + Prism.languages.mathml = Prism.languages.markup; + Prism.languages.svg = Prism.languages.markup; + Prism.languages.css = { + comment: /\/\*[\w\W]*?\*\//, + atrule: { + pattern: /@[\w-]+?.*?(;|(?=\s*\{))/i, + inside: { rule: /@[\w-]+/ } + }, + url: /url\((?:(["'])(\\(?:\r\n|[\w\W])|(?!\1)[^\\\r\n])*\1|.*?)\)/i, + selector: /[^\{\}\s][^\{\};]*?(?=\s*\{)/, + string: /("|')(\\(?:\r\n|[\w\W])|(?!\1)[^\\\r\n])*\1/, + property: /(\b|\B)[\w-]+(?=\s*:)/i, + important: /\B!important\b/i, + function: /[-a-z0-9]+(?=\()/i, + punctuation: /[(){};:]/ + }; + Prism.languages.css.atrule.inside.rest = Prism.util.clone(Prism.languages.css); + if (Prism.languages.markup) { + Prism.languages.insertBefore('markup', 'tag', { + style: { + pattern: /[\w\W]*?<\/style>/i, + inside: { + tag: { + pattern: /|<\/style>/i, + inside: Prism.languages.markup.tag.inside + }, + rest: Prism.languages.css + }, + alias: 'language-css' + } + }); + Prism.languages.insertBefore('inside', 'attr-value', { + 'style-attr': { + pattern: /\s*style=("|').*?\1/i, + inside: { + 'attr-name': { + pattern: /^\s*style/i, + inside: Prism.languages.markup.tag.inside + }, + 'punctuation': /^\s*=\s*['"]|['"]\s*$/, + 'attr-value': { + pattern: /.+/i, + inside: Prism.languages.css + } + }, + alias: 'language-css' + } + }, Prism.languages.markup.tag); + } + Prism.languages.clike = { + 'comment': [ + { + pattern: /(^|[^\\])\/\*[\w\W]*?\*\//, + lookbehind: true + }, + { + pattern: /(^|[^\\:])\/\/.*/, + lookbehind: true + } + ], + 'string': /(["'])(\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/, + 'class-name': { + pattern: /((?:\b(?:class|interface|extends|implements|trait|instanceof|new)\s+)|(?:catch\s+\())[a-z0-9_\.\\]+/i, + lookbehind: true, + inside: { punctuation: /(\.|\\)/ } + }, + 'keyword': /\b(if|else|while|do|for|return|in|instanceof|function|new|try|throw|catch|finally|null|break|continue)\b/, + 'boolean': /\b(true|false)\b/, + 'function': /[a-z0-9_]+(?=\()/i, + 'number': /\b-?(?:0x[\da-f]+|\d*\.?\d+(?:e[+-]?\d+)?)\b/i, + 'operator': /--?|\+\+?|!=?=?|<=?|>=?|==?=?|&&?|\|\|?|\?|\*|\/|~|\^|%/, + 'punctuation': /[{}[\];(),.:]/ + }; + Prism.languages.javascript = Prism.languages.extend('clike', { + keyword: /\b(as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|false|finally|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|true|try|typeof|var|void|while|with|yield)\b/, + number: /\b-?(0x[\dA-Fa-f]+|0b[01]+|0o[0-7]+|\d*\.?\d+([Ee][+-]?\d+)?|NaN|Infinity)\b/, + function: /[_$a-zA-Z\xA0-\uFFFF][_$a-zA-Z0-9\xA0-\uFFFF]*(?=\()/i + }); + Prism.languages.insertBefore('javascript', 'keyword', { + regex: { + pattern: /(^|[^/])\/(?!\/)(\[.+?]|\\.|[^/\\\r\n])+\/[gimyu]{0,5}(?=\s*($|[\r\n,.;})]))/, + lookbehind: true + } + }); + Prism.languages.insertBefore('javascript', 'class-name', { + 'template-string': { + pattern: /`(?:\\`|\\?[^`])*`/, + inside: { + interpolation: { + pattern: /\$\{[^}]+\}/, + inside: { + 'interpolation-punctuation': { + pattern: /^\$\{|\}$/, + alias: 'punctuation' + }, + 'rest': Prism.languages.javascript + } + }, + string: /[\s\S]+/ + } + } + }); + if (Prism.languages.markup) { + Prism.languages.insertBefore('markup', 'tag', { + script: { + pattern: /[\w\W]*?<\/script>/i, + inside: { + tag: { + pattern: /|<\/script>/i, + inside: Prism.languages.markup.tag.inside + }, + rest: Prism.languages.javascript + }, + alias: 'language-javascript' + } + }); + } + Prism.languages.js = Prism.languages.javascript; + Prism.languages.c = Prism.languages.extend('clike', { + keyword: /\b(asm|typeof|inline|auto|break|case|char|const|continue|default|do|double|else|enum|extern|float|for|goto|if|int|long|register|return|short|signed|sizeof|static|struct|switch|typedef|union|unsigned|void|volatile|while)\b/, + operator: /\-[>-]?|\+\+?|!=?|<>?=?|==?|&&?|\|?\||[~^%?*\/]/, + number: /\b-?(?:0x[\da-f]+|\d*\.?\d+(?:e[+-]?\d+)?)[ful]*\b/i + }); + Prism.languages.insertBefore('c', 'string', { + macro: { + pattern: /(^\s*)#\s*[a-z]+([^\r\n\\]|\\.|\\(?:\r\n?|\n))*/im, + lookbehind: true, + alias: 'property', + inside: { + string: { + pattern: /(#\s*include\s*)(<.+?>|("|')(\\?.)+?\3)/, + lookbehind: true + } + } + } + }); + delete Prism.languages.c['class-name']; + delete Prism.languages.c.boolean; + Prism.languages.csharp = Prism.languages.extend('clike', { + keyword: /\b(abstract|as|async|await|base|bool|break|byte|case|catch|char|checked|class|const|continue|decimal|default|delegate|do|double|else|enum|event|explicit|extern|false|finally|fixed|float|for|foreach|goto|if|implicit|in|int|interface|internal|is|lock|long|namespace|new|null|object|operator|out|override|params|private|protected|public|readonly|ref|return|sbyte|sealed|short|sizeof|stackalloc|static|string|struct|switch|this|throw|true|try|typeof|uint|ulong|unchecked|unsafe|ushort|using|virtual|void|volatile|while|add|alias|ascending|async|await|descending|dynamic|from|get|global|group|into|join|let|orderby|partial|remove|select|set|value|var|where|yield)\b/, + string: [ + /@("|')(\1\1|\\\1|\\?(?!\1)[\s\S])*\1/, + /("|')(\\?.)*?\1/ + ], + number: /\b-?(0x[\da-f]+|\d*\.?\d+)\b/i + }); + Prism.languages.insertBefore('csharp', 'keyword', { + preprocessor: { + pattern: /(^\s*)#.*/m, + lookbehind: true + } + }); + Prism.languages.cpp = Prism.languages.extend('c', { + keyword: /\b(alignas|alignof|asm|auto|bool|break|case|catch|char|char16_t|char32_t|class|compl|const|constexpr|const_cast|continue|decltype|default|delete|do|double|dynamic_cast|else|enum|explicit|export|extern|float|for|friend|goto|if|inline|int|long|mutable|namespace|new|noexcept|nullptr|operator|private|protected|public|register|reinterpret_cast|return|short|signed|sizeof|static|static_assert|static_cast|struct|switch|template|this|thread_local|throw|try|typedef|typeid|typename|union|unsigned|using|virtual|void|volatile|wchar_t|while)\b/, + boolean: /\b(true|false)\b/, + operator: /[-+]{1,2}|!=?|<{1,2}=?|>{1,2}=?|\->|:{1,2}|={1,2}|\^|~|%|&{1,2}|\|?\||\?|\*|\/|\b(and|and_eq|bitand|bitor|not|not_eq|or|or_eq|xor|xor_eq)\b/ + }); + Prism.languages.insertBefore('cpp', 'keyword', { + 'class-name': { + pattern: /(class\s+)[a-z0-9_]+/i, + lookbehind: true + } + }); + Prism.languages.java = Prism.languages.extend('clike', { + keyword: /\b(abstract|continue|for|new|switch|assert|default|goto|package|synchronized|boolean|do|if|private|this|break|double|implements|protected|throw|byte|else|import|public|throws|case|enum|instanceof|return|transient|catch|extends|int|short|try|char|final|interface|static|void|class|finally|long|strictfp|volatile|const|float|native|super|while)\b/, + number: /\b0b[01]+\b|\b0x[\da-f]*\.?[\da-fp\-]+\b|\b\d*\.?\d+(?:e[+-]?\d+)?[df]?\b/i, + operator: { + pattern: /(^|[^.])(?:\+[+=]?|-[-=]?|!=?|<>?>?=?|==?|&[&=]?|\|[|=]?|\*=?|\/=?|%=?|\^=?|[?:~])/m, + lookbehind: true + } + }); + Prism.languages.php = Prism.languages.extend('clike', { + keyword: /\b(and|or|xor|array|as|break|case|cfunction|class|const|continue|declare|default|die|do|else|elseif|enddeclare|endfor|endforeach|endif|endswitch|endwhile|extends|for|foreach|function|include|include_once|global|if|new|return|static|switch|use|require|require_once|var|while|abstract|interface|public|implements|private|protected|parent|throw|null|echo|print|trait|namespace|final|yield|goto|instanceof|finally|try|catch)\b/i, + constant: /\b[A-Z0-9_]{2,}\b/, + comment: { + pattern: /(^|[^\\])(?:\/\*[\w\W]*?\*\/|\/\/.*)/, + lookbehind: true + } + }); + Prism.languages.insertBefore('php', 'class-name', { + 'shell-comment': { + pattern: /(^|[^\\])#.*/, + lookbehind: true, + alias: 'comment' + } + }); + Prism.languages.insertBefore('php', 'keyword', { + delimiter: /\?>|<\?(?:php)?/i, + variable: /\$\w+\b/i, + package: { + pattern: /(\\|namespace\s+|use\s+)[\w\\]+/, + lookbehind: true, + inside: { punctuation: /\\/ } + } + }); + Prism.languages.insertBefore('php', 'operator', { + property: { + pattern: /(->)[\w]+/, + lookbehind: true + } + }); + if (Prism.languages.markup) { + Prism.hooks.add('before-highlight', function (env) { + if (env.language !== 'php') { + return; + } + env.tokenStack = []; + env.backupCode = env.code; + env.code = env.code.replace(/(?:<\?php|<\?)[\w\W]*?(?:\?>)/ig, function (match) { + env.tokenStack.push(match); + return '{{{PHP' + env.tokenStack.length + '}}}'; + }); + }); + Prism.hooks.add('before-insert', function (env) { + if (env.language === 'php') { + env.code = env.backupCode; + delete env.backupCode; + } + }); + Prism.hooks.add('after-highlight', function (env) { + if (env.language !== 'php') { + return; + } + for (var i = 0, t = void 0; t = env.tokenStack[i]; i++) { + env.highlightedCode = env.highlightedCode.replace('{{{PHP' + (i + 1) + '}}}', Prism.highlight(t, env.grammar, 'php').replace(/\$/g, '$$$$')); + } + env.element.innerHTML = env.highlightedCode; + }); + Prism.hooks.add('wrap', function (env) { + if (env.language === 'php' && env.type === 'markup') { + env.content = env.content.replace(/(\{\{\{PHP[0-9]+\}\}\})/g, '$1'); + } + }); + Prism.languages.insertBefore('php', 'comment', { + markup: { + pattern: /<[^?]\/?(.*?)>/, + inside: Prism.languages.markup + }, + php: /\{\{\{PHP[0-9]+\}\}\}/ + }); + } + Prism.languages.python = { + 'comment': { + pattern: /(^|[^\\])#.*/, + lookbehind: true + }, + 'string': /"""[\s\S]+?"""|'''[\s\S]+?'''|("|')(?:\\?.)*?\1/, + 'function': { + pattern: /((?:^|\s)def[ \t]+)[a-zA-Z_][a-zA-Z0-9_]*(?=\()/g, + lookbehind: true + }, + 'class-name': { + pattern: /(\bclass\s+)[a-z0-9_]+/i, + lookbehind: true + }, + 'keyword': /\b(?:as|assert|async|await|break|class|continue|def|del|elif|else|except|exec|finally|for|from|global|if|import|in|is|lambda|pass|print|raise|return|try|while|with|yield)\b/, + 'boolean': /\b(?:True|False)\b/, + 'number': /\b-?(?:0[bo])?(?:(?:\d|0x[\da-f])[\da-f]*\.?\d*|\.\d+)(?:e[+-]?\d+)?j?\b/i, + 'operator': /[-+%=]=?|!=|\*\*?=?|\/\/?=?|<[<=>]?|>[=>]?|[&|^~]|\b(?:or|and|not)\b/, + 'punctuation': /[{}[\];(),.:]/ + }; + (function (Prism) { + Prism.languages.ruby = Prism.languages.extend('clike', { + comment: /#(?!\{[^\r\n]*?\}).*/, + keyword: /\b(alias|and|BEGIN|begin|break|case|class|def|define_method|defined|do|each|else|elsif|END|end|ensure|false|for|if|in|module|new|next|nil|not|or|raise|redo|require|rescue|retry|return|self|super|then|throw|true|undef|unless|until|when|while|yield)\b/ + }); + var interpolation = { + pattern: /#\{[^}]+\}/, + inside: { + delimiter: { + pattern: /^#\{|\}$/, + alias: 'tag' + }, + rest: Prism.util.clone(Prism.languages.ruby) + } + }; + Prism.languages.insertBefore('ruby', 'keyword', { + regex: [ + { + pattern: /%r([^a-zA-Z0-9\s\{\(\[<])(?:[^\\]|\\[\s\S])*?\1[gim]{0,3}/, + inside: { interpolation: interpolation } + }, + { + pattern: /%r\((?:[^()\\]|\\[\s\S])*\)[gim]{0,3}/, + inside: { interpolation: interpolation } + }, + { + pattern: /%r\{(?:[^#{}\\]|#(?:\{[^}]+\})?|\\[\s\S])*\}[gim]{0,3}/, + inside: { interpolation: interpolation } + }, + { + pattern: /%r\[(?:[^\[\]\\]|\\[\s\S])*\][gim]{0,3}/, + inside: { interpolation: interpolation } + }, + { + pattern: /%r<(?:[^<>\\]|\\[\s\S])*>[gim]{0,3}/, + inside: { interpolation: interpolation } + }, + { + pattern: /(^|[^/])\/(?!\/)(\[.+?]|\\.|[^/\r\n])+\/[gim]{0,3}(?=\s*($|[\r\n,.;})]))/, + lookbehind: true + } + ], + variable: /[@$]+[a-zA-Z_][a-zA-Z_0-9]*(?:[?!]|\b)/, + symbol: /:[a-zA-Z_][a-zA-Z_0-9]*(?:[?!]|\b)/ + }); + Prism.languages.insertBefore('ruby', 'number', { + builtin: /\b(Array|Bignum|Binding|Class|Continuation|Dir|Exception|FalseClass|File|Stat|File|Fixnum|Fload|Hash|Integer|IO|MatchData|Method|Module|NilClass|Numeric|Object|Proc|Range|Regexp|String|Struct|TMS|Symbol|ThreadGroup|Thread|Time|TrueClass)\b/, + constant: /\b[A-Z][a-zA-Z_0-9]*(?:[?!]|\b)/ + }); + Prism.languages.ruby.string = [ + { + pattern: /%[qQiIwWxs]?([^a-zA-Z0-9\s\{\(\[<])(?:[^\\]|\\[\s\S])*?\1/, + inside: { interpolation: interpolation } + }, + { + pattern: /%[qQiIwWxs]?\((?:[^()\\]|\\[\s\S])*\)/, + inside: { interpolation: interpolation } + }, + { + pattern: /%[qQiIwWxs]?\{(?:[^#{}\\]|#(?:\{[^}]+\})?|\\[\s\S])*\}/, + inside: { interpolation: interpolation } + }, + { + pattern: /%[qQiIwWxs]?\[(?:[^\[\]\\]|\\[\s\S])*\]/, + inside: { interpolation: interpolation } + }, + { + pattern: /%[qQiIwWxs]?<(?:[^<>\\]|\\[\s\S])*>/, + inside: { interpolation: interpolation } + }, + { + pattern: /("|')(#\{[^}]+\}|\\(?:\r?\n|\r)|\\?.)*?\1/, + inside: { interpolation: interpolation } + } + ]; + }(Prism)); + + function isCodeSample(elm) { + return elm && elm.nodeName === 'PRE' && elm.className.indexOf('language-') !== -1; + } + function trimArg(predicateFn) { + return function (arg1, arg2) { + return predicateFn(arg2); + }; + } + var $_ed862ja1jh8lpuih = { + isCodeSample: isCodeSample, + trimArg: trimArg + }; + + var getSelectedCodeSample = function (editor) { + var node = editor.selection.getNode(); + if ($_ed862ja1jh8lpuih.isCodeSample(node)) { + return node; + } + return null; + }; + var insertCodeSample = function (editor, language, code) { + editor.undoManager.transact(function () { + var node = getSelectedCodeSample(editor); + code = global$1.DOM.encode(code); + if (node) { + editor.dom.setAttrib(node, 'class', 'language-' + language); + node.innerHTML = code; + Prism.highlightElement(node); + editor.selection.select(node); + } else { + editor.insertContent('
    ' + code + '
    '); + editor.selection.select(editor.$('#__new').removeAttr('id')[0]); + } + }); + }; + var getCurrentCode = function (editor) { + var node = getSelectedCodeSample(editor); + if (node) { + return node.textContent; + } + return ''; + }; + var $_2bh0wp9zjh8lpuhk = { + getSelectedCodeSample: getSelectedCodeSample, + insertCodeSample: insertCodeSample, + getCurrentCode: getCurrentCode + }; + + var getLanguages$1 = function (editor) { + var defaultLanguages = [ + { + text: 'HTML/XML', + value: 'markup' + }, + { + text: 'JavaScript', + value: 'javascript' + }, + { + text: 'CSS', + value: 'css' + }, + { + text: 'PHP', + value: 'php' + }, + { + text: 'Ruby', + value: 'ruby' + }, + { + text: 'Python', + value: 'python' + }, + { + text: 'Java', + value: 'java' + }, + { + text: 'C', + value: 'c' + }, + { + text: 'C#', + value: 'csharp' + }, + { + text: 'C++', + value: 'cpp' + } + ]; + var customLanguages = $_3ew0z79xjh8lpuhi.getLanguages(editor); + return customLanguages ? customLanguages : defaultLanguages; + }; + var getCurrentLanguage = function (editor) { + var matches; + var node = $_2bh0wp9zjh8lpuhk.getSelectedCodeSample(editor); + if (node) { + matches = node.className.match(/language-(\w+)/); + return matches ? matches[1] : ''; + } + return ''; + }; + var $_dqyreda2jh8lpuii = { + getLanguages: getLanguages$1, + getCurrentLanguage: getCurrentLanguage + }; + + var $_17ew2k9wjh8lpuhh = { + open: function (editor) { + var minWidth = $_3ew0z79xjh8lpuhi.getDialogMinWidth(editor); + var minHeight = $_3ew0z79xjh8lpuhi.getDialogMinHeight(editor); + var currentLanguage = $_dqyreda2jh8lpuii.getCurrentLanguage(editor); + var currentLanguages = $_dqyreda2jh8lpuii.getLanguages(editor); + var currentCode = $_2bh0wp9zjh8lpuhk.getCurrentCode(editor); + editor.windowManager.open({ + title: 'Insert/Edit code sample', + minWidth: minWidth, + minHeight: minHeight, + layout: 'flex', + direction: 'column', + align: 'stretch', + body: [ + { + type: 'listbox', + name: 'language', + label: 'Language', + maxWidth: 200, + value: currentLanguage, + values: currentLanguages + }, + { + type: 'textbox', + name: 'code', + multiline: true, + spellcheck: false, + ariaLabel: 'Code view', + flex: 1, + style: 'direction: ltr; text-align: left', + classes: 'monospace', + value: currentCode, + autofocus: true + } + ], + onSubmit: function (e) { + $_2bh0wp9zjh8lpuhk.insertCodeSample(editor, e.data.language, e.data.code); + } + }); + } + }; + + var register = function (editor) { + editor.addCommand('codesample', function () { + var node = editor.selection.getNode(); + if (editor.selection.isCollapsed() || $_ed862ja1jh8lpuih.isCodeSample(node)) { + $_17ew2k9wjh8lpuhh.open(editor); + } else { + editor.formatter.toggle('code'); + } + }); + }; + var $_g7hv619vjh8lpuhg = { register: register }; + + var setup = function (editor) { + var $ = editor.$; + editor.on('PreProcess', function (e) { + $('pre[contenteditable=false]', e.node).filter($_ed862ja1jh8lpuih.trimArg($_ed862ja1jh8lpuih.isCodeSample)).each(function (idx, elm) { + var $elm = $(elm), code = elm.textContent; + $elm.attr('class', $.trim($elm.attr('class'))); + $elm.removeAttr('contentEditable'); + $elm.empty().append($('').each(function () { + this.textContent = code; + })); + }); + }); + editor.on('SetContent', function () { + var unprocessedCodeSamples = $('pre').filter($_ed862ja1jh8lpuih.trimArg($_ed862ja1jh8lpuih.isCodeSample)).filter(function (idx, elm) { + return elm.contentEditable !== 'false'; + }); + if (unprocessedCodeSamples.length) { + editor.undoManager.transact(function () { + unprocessedCodeSamples.each(function (idx, elm) { + $(elm).find('br').each(function (idx, elm) { + elm.parentNode.replaceChild(editor.getDoc().createTextNode('\n'), elm); + }); + elm.contentEditable = false; + elm.innerHTML = editor.dom.encode(elm.textContent); + Prism.highlightElement(elm); + elm.className = $.trim(elm.className); + }); + }); + } + }); + }; + var $_bhr6mpa3jh8lpuik = { setup: setup }; + + var loadCss = function (editor, pluginUrl, addedInlineCss, addedCss) { + var linkElm; + var contentCss = $_3ew0z79xjh8lpuhi.getContentCss(editor); + if (editor.inline && addedInlineCss.get()) { + return; + } + if (!editor.inline && addedCss.get()) { + return; + } + if (editor.inline) { + addedInlineCss.set(true); + } else { + addedCss.set(true); + } + if (contentCss !== false) { + linkElm = editor.dom.create('link', { + rel: 'stylesheet', + href: contentCss ? contentCss : pluginUrl + '/css/prism.css' + }); + editor.getDoc().getElementsByTagName('head')[0].appendChild(linkElm); + } + }; + var $_9m8czaa4jh8lpuim = { loadCss: loadCss }; + + var register$1 = function (editor) { + editor.addButton('codesample', { + cmd: 'codesample', + title: 'Insert/Edit code sample' + }); + editor.addMenuItem('codesample', { + cmd: 'codesample', + text: 'Code sample', + icon: 'codesample' + }); + }; + var $_7n1bf0a5jh8lpuin = { register: register$1 }; + + var addedInlineCss = Cell(false); + global.add('codesample', function (editor, pluginUrl) { + var addedCss = Cell(false); + $_bhr6mpa3jh8lpuik.setup(editor); + $_7n1bf0a5jh8lpuin.register(editor); + $_g7hv619vjh8lpuhg.register(editor); + editor.on('init', function () { + $_9m8czaa4jh8lpuim.loadCss(editor, pluginUrl, addedInlineCss, addedCss); + }); + editor.on('dblclick', function (ev) { + if ($_ed862ja1jh8lpuih.isCodeSample(ev.target)) { + $_17ew2k9wjh8lpuhh.open(editor); + } + }); + }); + function Plugin () { + } + + return Plugin; + +}()); +})(); diff --git a/WebCms/Umbraco/lib/tinymce/plugins/codesample/plugin.min.js b/WebCms/Umbraco/lib/tinymce/plugins/codesample/plugin.min.js new file mode 100644 index 0000000..8f7373c --- /dev/null +++ b/WebCms/Umbraco/lib/tinymce/plugins/codesample/plugin.min.js @@ -0,0 +1 @@ +!function(){"use strict";var n=function(e){var t=e,a=function(){return t};return{get:a,set:function(e){t=e},clone:function(){return n(a())}}},e=tinymce.util.Tools.resolve("tinymce.PluginManager"),i=tinymce.util.Tools.resolve("tinymce.dom.DOMUtils"),s=function(e){return e.settings.codesample_content_css},a=function(e){return e.settings.codesample_languages},o=function(e){return Math.min(i.DOM.getViewPort().w,e.getParam("codesample_dialog_width",800))},l=function(e){return Math.min(i.DOM.getViewPort().w,e.getParam("codesample_dialog_height",650))},t={},r=t,u=void 0!==t?t:"undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?self:{},c=function(){var c=/\blang(?:uage)?-(?!\*)(\w+)\b/i,S=u.Prism={util:{encode:function(e){return e instanceof o?new o(e.type,S.util.encode(e.content),e.alias):"Array"===S.util.type(e)?e.map(S.util.encode):e.replace(/&/g,"&").replace(/e.length)break e;if(!(h instanceof n)){c.lastIndex=0;var m=c.exec(h);if(m){g&&(d=m[1].length);var b=m.index-1+d,y=b+(m=m[0].slice(d)).length,v=h.slice(0,b+1),k=h.slice(y+1),w=[f,1];v&&w.push(v);var x=new n(s,u?S.tokenize(m,u):m,p);w.push(x),k&&w.push(k),Array.prototype.splice.apply(i,w)}}}}}return i},hooks:{all:{},add:function(e,t){var a=S.hooks.all;a[e]=a[e]||[],a[e].push(t)},run:function(e,t){var a=S.hooks.all[e];if(a&&a.length)for(var n=0,i=void 0;i=a[n++];)i(t)}}},o=S.Token=function(e,t,a){this.type=e,this.content=t,this.alias=a};if(o.stringify=function(t,a,e){if("string"==typeof t)return t;if("Array"===S.util.type(t))return t.map(function(e){return o.stringify(e,a,t)}).join("");var n={type:t.type,content:o.stringify(t.content,a,e),tag:"span",classes:["token",t.type],attributes:{},language:a,parent:e};if("comment"===n.type&&(n.attributes.spellcheck="true"),t.alias){var i="Array"===S.util.type(t.alias)?t.alias:[t.alias];Array.prototype.push.apply(n.classes,i)}S.hooks.run("wrap",n);var r="";for(var s in n.attributes)r+=(r?" ":"")+s+'="'+(n.attributes[s]||"")+'"';return"<"+n.tag+' class="'+n.classes.join(" ")+'" '+r+">"+n.content+""},!u.document)return u.addEventListener&&u.addEventListener("message",function(e){var t=JSON.parse(e.data),a=t.language,n=t.code,i=t.immediateClose;u.postMessage(S.highlight(n,S.languages[a],a)),i&&u.close()},!1),u.Prism}();void 0!==r&&(r.Prism=c),c.languages.markup={comment://,prolog:/<\?[\w\W]+?\?>/,doctype://,cdata://i,tag:{pattern:/<\/?[^\s>\/=.]+(?:\s+[^\s>\/=]+(?:=(?:("|')(?:\\\1|\\?(?!\1)[\w\W])*\1|[^\s'">=]+))?)*\s*\/?>/i,inside:{tag:{pattern:/^<\/?[^\s>\/]+/i,inside:{punctuation:/^<\/?/,namespace:/^[^\s>\/:]+:/}},"attr-value":{pattern:/=(?:('|")[\w\W]*?(\1)|[^\s>]+)/i,inside:{punctuation:/[=>"']/}},punctuation:/\/?>/,"attr-name":{pattern:/[^\s>\/]+/,inside:{namespace:/^[^\s>\/:]+:/}}}},entity:/&#?[\da-z]{1,8};/i},c.hooks.add("wrap",function(e){"entity"===e.type&&(e.attributes.title=e.content.replace(/&/,"&"))}),c.languages.xml=c.languages.markup,c.languages.html=c.languages.markup,c.languages.mathml=c.languages.markup,c.languages.svg=c.languages.markup,c.languages.css={comment:/\/\*[\w\W]*?\*\//,atrule:{pattern:/@[\w-]+?.*?(;|(?=\s*\{))/i,inside:{rule:/@[\w-]+/}},url:/url\((?:(["'])(\\(?:\r\n|[\w\W])|(?!\1)[^\\\r\n])*\1|.*?)\)/i,selector:/[^\{\}\s][^\{\};]*?(?=\s*\{)/,string:/("|')(\\(?:\r\n|[\w\W])|(?!\1)[^\\\r\n])*\1/,property:/(\b|\B)[\w-]+(?=\s*:)/i,important:/\B!important\b/i,"function":/[-a-z0-9]+(?=\()/i,punctuation:/[(){};:]/},c.languages.css.atrule.inside.rest=c.util.clone(c.languages.css),c.languages.markup&&(c.languages.insertBefore("markup","tag",{style:{pattern:/[\w\W]*?<\/style>/i,inside:{tag:{pattern:/|<\/style>/i,inside:c.languages.markup.tag.inside},rest:c.languages.css},alias:"language-css"}}),c.languages.insertBefore("inside","attr-value",{"style-attr":{pattern:/\s*style=("|').*?\1/i,inside:{"attr-name":{pattern:/^\s*style/i,inside:c.languages.markup.tag.inside},punctuation:/^\s*=\s*['"]|['"]\s*$/,"attr-value":{pattern:/.+/i,inside:c.languages.css}},alias:"language-css"}},c.languages.markup.tag)),c.languages.clike={comment:[{pattern:/(^|[^\\])\/\*[\w\W]*?\*\//,lookbehind:!0},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0}],string:/(["'])(\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,"class-name":{pattern:/((?:\b(?:class|interface|extends|implements|trait|instanceof|new)\s+)|(?:catch\s+\())[a-z0-9_\.\\]+/i,lookbehind:!0,inside:{punctuation:/(\.|\\)/}},keyword:/\b(if|else|while|do|for|return|in|instanceof|function|new|try|throw|catch|finally|null|break|continue)\b/,"boolean":/\b(true|false)\b/,"function":/[a-z0-9_]+(?=\()/i,number:/\b-?(?:0x[\da-f]+|\d*\.?\d+(?:e[+-]?\d+)?)\b/i,operator:/--?|\+\+?|!=?=?|<=?|>=?|==?=?|&&?|\|\|?|\?|\*|\/|~|\^|%/,punctuation:/[{}[\];(),.:]/},c.languages.javascript=c.languages.extend("clike",{keyword:/\b(as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|false|finally|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|true|try|typeof|var|void|while|with|yield)\b/,number:/\b-?(0x[\dA-Fa-f]+|0b[01]+|0o[0-7]+|\d*\.?\d+([Ee][+-]?\d+)?|NaN|Infinity)\b/,"function":/[_$a-zA-Z\xA0-\uFFFF][_$a-zA-Z0-9\xA0-\uFFFF]*(?=\()/i}),c.languages.insertBefore("javascript","keyword",{regex:{pattern:/(^|[^/])\/(?!\/)(\[.+?]|\\.|[^/\\\r\n])+\/[gimyu]{0,5}(?=\s*($|[\r\n,.;})]))/,lookbehind:!0}}),c.languages.insertBefore("javascript","class-name",{"template-string":{pattern:/`(?:\\`|\\?[^`])*`/,inside:{interpolation:{pattern:/\$\{[^}]+\}/,inside:{"interpolation-punctuation":{pattern:/^\$\{|\}$/,alias:"punctuation"},rest:c.languages.javascript}},string:/[\s\S]+/}}}),c.languages.markup&&c.languages.insertBefore("markup","tag",{script:{pattern:/[\w\W]*?<\/script>/i,inside:{tag:{pattern:/|<\/script>/i,inside:c.languages.markup.tag.inside},rest:c.languages.javascript},alias:"language-javascript"}}),c.languages.js=c.languages.javascript,c.languages.c=c.languages.extend("clike",{keyword:/\b(asm|typeof|inline|auto|break|case|char|const|continue|default|do|double|else|enum|extern|float|for|goto|if|int|long|register|return|short|signed|sizeof|static|struct|switch|typedef|union|unsigned|void|volatile|while)\b/,operator:/\-[>-]?|\+\+?|!=?|<>?=?|==?|&&?|\|?\||[~^%?*\/]/,number:/\b-?(?:0x[\da-f]+|\d*\.?\d+(?:e[+-]?\d+)?)[ful]*\b/i}),c.languages.insertBefore("c","string",{macro:{pattern:/(^\s*)#\s*[a-z]+([^\r\n\\]|\\.|\\(?:\r\n?|\n))*/im,lookbehind:!0,alias:"property",inside:{string:{pattern:/(#\s*include\s*)(<.+?>|("|')(\\?.)+?\3)/,lookbehind:!0}}}}),delete c.languages.c["class-name"],delete c.languages.c["boolean"],c.languages.csharp=c.languages.extend("clike",{keyword:/\b(abstract|as|async|await|base|bool|break|byte|case|catch|char|checked|class|const|continue|decimal|default|delegate|do|double|else|enum|event|explicit|extern|false|finally|fixed|float|for|foreach|goto|if|implicit|in|int|interface|internal|is|lock|long|namespace|new|null|object|operator|out|override|params|private|protected|public|readonly|ref|return|sbyte|sealed|short|sizeof|stackalloc|static|string|struct|switch|this|throw|true|try|typeof|uint|ulong|unchecked|unsafe|ushort|using|virtual|void|volatile|while|add|alias|ascending|async|await|descending|dynamic|from|get|global|group|into|join|let|orderby|partial|remove|select|set|value|var|where|yield)\b/,string:[/@("|')(\1\1|\\\1|\\?(?!\1)[\s\S])*\1/,/("|')(\\?.)*?\1/],number:/\b-?(0x[\da-f]+|\d*\.?\d+)\b/i}),c.languages.insertBefore("csharp","keyword",{preprocessor:{pattern:/(^\s*)#.*/m,lookbehind:!0}}),c.languages.cpp=c.languages.extend("c",{keyword:/\b(alignas|alignof|asm|auto|bool|break|case|catch|char|char16_t|char32_t|class|compl|const|constexpr|const_cast|continue|decltype|default|delete|do|double|dynamic_cast|else|enum|explicit|export|extern|float|for|friend|goto|if|inline|int|long|mutable|namespace|new|noexcept|nullptr|operator|private|protected|public|register|reinterpret_cast|return|short|signed|sizeof|static|static_assert|static_cast|struct|switch|template|this|thread_local|throw|try|typedef|typeid|typename|union|unsigned|using|virtual|void|volatile|wchar_t|while)\b/,"boolean":/\b(true|false)\b/,operator:/[-+]{1,2}|!=?|<{1,2}=?|>{1,2}=?|\->|:{1,2}|={1,2}|\^|~|%|&{1,2}|\|?\||\?|\*|\/|\b(and|and_eq|bitand|bitor|not|not_eq|or|or_eq|xor|xor_eq)\b/}),c.languages.insertBefore("cpp","keyword",{"class-name":{pattern:/(class\s+)[a-z0-9_]+/i,lookbehind:!0}}),c.languages.java=c.languages.extend("clike",{keyword:/\b(abstract|continue|for|new|switch|assert|default|goto|package|synchronized|boolean|do|if|private|this|break|double|implements|protected|throw|byte|else|import|public|throws|case|enum|instanceof|return|transient|catch|extends|int|short|try|char|final|interface|static|void|class|finally|long|strictfp|volatile|const|float|native|super|while)\b/,number:/\b0b[01]+\b|\b0x[\da-f]*\.?[\da-fp\-]+\b|\b\d*\.?\d+(?:e[+-]?\d+)?[df]?\b/i,operator:{pattern:/(^|[^.])(?:\+[+=]?|-[-=]?|!=?|<>?>?=?|==?|&[&=]?|\|[|=]?|\*=?|\/=?|%=?|\^=?|[?:~])/m,lookbehind:!0}}),c.languages.php=c.languages.extend("clike",{keyword:/\b(and|or|xor|array|as|break|case|cfunction|class|const|continue|declare|default|die|do|else|elseif|enddeclare|endfor|endforeach|endif|endswitch|endwhile|extends|for|foreach|function|include|include_once|global|if|new|return|static|switch|use|require|require_once|var|while|abstract|interface|public|implements|private|protected|parent|throw|null|echo|print|trait|namespace|final|yield|goto|instanceof|finally|try|catch)\b/i,constant:/\b[A-Z0-9_]{2,}\b/,comment:{pattern:/(^|[^\\])(?:\/\*[\w\W]*?\*\/|\/\/.*)/,lookbehind:!0}}),c.languages.insertBefore("php","class-name",{"shell-comment":{pattern:/(^|[^\\])#.*/,lookbehind:!0,alias:"comment"}}),c.languages.insertBefore("php","keyword",{delimiter:/\?>|<\?(?:php)?/i,variable:/\$\w+\b/i,"package":{pattern:/(\\|namespace\s+|use\s+)[\w\\]+/,lookbehind:!0,inside:{punctuation:/\\/}}}),c.languages.insertBefore("php","operator",{property:{pattern:/(->)[\w]+/,lookbehind:!0}}),c.languages.markup&&(c.hooks.add("before-highlight",function(t){"php"===t.language&&(t.tokenStack=[],t.backupCode=t.code,t.code=t.code.replace(/(?:<\?php|<\?)[\w\W]*?(?:\?>)/gi,function(e){return t.tokenStack.push(e),"{{{PHP"+t.tokenStack.length+"}}}"}))}),c.hooks.add("before-insert",function(e){"php"===e.language&&(e.code=e.backupCode,delete e.backupCode)}),c.hooks.add("after-highlight",function(e){if("php"===e.language){for(var t=0,a=void 0;a=e.tokenStack[t];t++)e.highlightedCode=e.highlightedCode.replace("{{{PHP"+(t+1)+"}}}",c.highlight(a,e.grammar,"php").replace(/\$/g,"$$$$"));e.element.innerHTML=e.highlightedCode}}),c.hooks.add("wrap",function(e){"php"===e.language&&"markup"===e.type&&(e.content=e.content.replace(/(\{\{\{PHP[0-9]+\}\}\})/g,'$1'))}),c.languages.insertBefore("php","comment",{markup:{pattern:/<[^?]\/?(.*?)>/,inside:c.languages.markup},php:/\{\{\{PHP[0-9]+\}\}\}/})),c.languages.python={comment:{pattern:/(^|[^\\])#.*/,lookbehind:!0},string:/"""[\s\S]+?"""|'''[\s\S]+?'''|("|')(?:\\?.)*?\1/,"function":{pattern:/((?:^|\s)def[ \t]+)[a-zA-Z_][a-zA-Z0-9_]*(?=\()/g,lookbehind:!0},"class-name":{pattern:/(\bclass\s+)[a-z0-9_]+/i,lookbehind:!0},keyword:/\b(?:as|assert|async|await|break|class|continue|def|del|elif|else|except|exec|finally|for|from|global|if|import|in|is|lambda|pass|print|raise|return|try|while|with|yield)\b/,"boolean":/\b(?:True|False)\b/,number:/\b-?(?:0[bo])?(?:(?:\d|0x[\da-f])[\da-f]*\.?\d*|\.\d+)(?:e[+-]?\d+)?j?\b/i,operator:/[-+%=]=?|!=|\*\*?=?|\/\/?=?|<[<=>]?|>[=>]?|[&|^~]|\b(?:or|and|not)\b/,punctuation:/[{}[\];(),.:]/},function(e){e.languages.ruby=e.languages.extend("clike",{comment:/#(?!\{[^\r\n]*?\}).*/,keyword:/\b(alias|and|BEGIN|begin|break|case|class|def|define_method|defined|do|each|else|elsif|END|end|ensure|false|for|if|in|module|new|next|nil|not|or|raise|redo|require|rescue|retry|return|self|super|then|throw|true|undef|unless|until|when|while|yield)\b/});var t={pattern:/#\{[^}]+\}/,inside:{delimiter:{pattern:/^#\{|\}$/,alias:"tag"},rest:e.util.clone(e.languages.ruby)}};e.languages.insertBefore("ruby","keyword",{regex:[{pattern:/%r([^a-zA-Z0-9\s\{\(\[<])(?:[^\\]|\\[\s\S])*?\1[gim]{0,3}/,inside:{interpolation:t}},{pattern:/%r\((?:[^()\\]|\\[\s\S])*\)[gim]{0,3}/,inside:{interpolation:t}},{pattern:/%r\{(?:[^#{}\\]|#(?:\{[^}]+\})?|\\[\s\S])*\}[gim]{0,3}/,inside:{interpolation:t}},{pattern:/%r\[(?:[^\[\]\\]|\\[\s\S])*\][gim]{0,3}/,inside:{interpolation:t}},{pattern:/%r<(?:[^<>\\]|\\[\s\S])*>[gim]{0,3}/,inside:{interpolation:t}},{pattern:/(^|[^/])\/(?!\/)(\[.+?]|\\.|[^/\r\n])+\/[gim]{0,3}(?=\s*($|[\r\n,.;})]))/,lookbehind:!0}],variable:/[@$]+[a-zA-Z_][a-zA-Z_0-9]*(?:[?!]|\b)/,symbol:/:[a-zA-Z_][a-zA-Z_0-9]*(?:[?!]|\b)/}),e.languages.insertBefore("ruby","number",{builtin:/\b(Array|Bignum|Binding|Class|Continuation|Dir|Exception|FalseClass|File|Stat|File|Fixnum|Fload|Hash|Integer|IO|MatchData|Method|Module|NilClass|Numeric|Object|Proc|Range|Regexp|String|Struct|TMS|Symbol|ThreadGroup|Thread|Time|TrueClass)\b/,constant:/\b[A-Z][a-zA-Z_0-9]*(?:[?!]|\b)/}),e.languages.ruby.string=[{pattern:/%[qQiIwWxs]?([^a-zA-Z0-9\s\{\(\[<])(?:[^\\]|\\[\s\S])*?\1/,inside:{interpolation:t}},{pattern:/%[qQiIwWxs]?\((?:[^()\\]|\\[\s\S])*\)/,inside:{interpolation:t}},{pattern:/%[qQiIwWxs]?\{(?:[^#{}\\]|#(?:\{[^}]+\})?|\\[\s\S])*\}/,inside:{interpolation:t}},{pattern:/%[qQiIwWxs]?\[(?:[^\[\]\\]|\\[\s\S])*\]/,inside:{interpolation:t}},{pattern:/%[qQiIwWxs]?<(?:[^<>\\]|\\[\s\S])*>/,inside:{interpolation:t}},{pattern:/("|')(#\{[^}]+\}|\\(?:\r?\n|\r)|\\?.)*?\1/,inside:{interpolation:t}}]}(c);var g={isCodeSample:function(e){return e&&"PRE"===e.nodeName&&-1!==e.className.indexOf("language-")},trimArg:function(a){return function(e,t){return a(t)}}},d=function(e){var t=e.selection.getNode();return g.isCodeSample(t)?t:null},p=d,f=function(t,a,n){t.undoManager.transact(function(){var e=d(t);n=i.DOM.encode(n),e?(t.dom.setAttrib(e,"class","language-"+a),e.innerHTML=n,c.highlightElement(e),t.selection.select(e)):(t.insertContent('
    '+n+"
    "),t.selection.select(t.$("#__new").removeAttr("id")[0]))})},h=function(e){var t=d(e);return t?t.textContent:""},m=function(e){var t=a(e);return t||[{text:"HTML/XML",value:"markup"},{text:"JavaScript",value:"javascript"},{text:"CSS",value:"css"},{text:"PHP",value:"php"},{text:"Ruby",value:"ruby"},{text:"Python",value:"python"},{text:"Java",value:"java"},{text:"C",value:"c"},{text:"C#",value:"csharp"},{text:"C++",value:"cpp"}]},b=function(e){var t,a=p(e);return a&&(t=a.className.match(/language-(\w+)/))?t[1]:""},y=function(t){var e=o(t),a=l(t),n=b(t),i=m(t),r=h(t);t.windowManager.open({title:"Insert/Edit code sample",minWidth:e,minHeight:a,layout:"flex",direction:"column",align:"stretch",body:[{type:"listbox",name:"language",label:"Language",maxWidth:200,value:n,values:i},{type:"textbox",name:"code",multiline:!0,spellcheck:!1,ariaLabel:"Code view",flex:1,style:"direction: ltr; text-align: left",classes:"monospace",value:r,autofocus:!0}],onSubmit:function(e){f(t,e.data.language,e.data.code)}})},v=function(t){t.addCommand("codesample",function(){var e=t.selection.getNode();t.selection.isCollapsed()||g.isCodeSample(e)?y(t):t.formatter.toggle("code")})},k=function(a){var i=a.$;a.on("PreProcess",function(e){i("pre[contenteditable=false]",e.node).filter(g.trimArg(g.isCodeSample)).each(function(e,t){var a=i(t),n=t.textContent;a.attr("class",i.trim(a.attr("class"))),a.removeAttr("contentEditable"),a.empty().append(i("").each(function(){this.textContent=n}))})}),a.on("SetContent",function(){var e=i("pre").filter(g.trimArg(g.isCodeSample)).filter(function(e,t){return"false"!==t.contentEditable});e.length&&a.undoManager.transact(function(){e.each(function(e,t){i(t).find("br").each(function(e,t){t.parentNode.replaceChild(a.getDoc().createTextNode("\n"),t)}),t.contentEditable=!1,t.innerHTML=a.dom.encode(t.textContent),c.highlightElement(t),t.className=i.trim(t.className)})})})},w=function(e,t,a,n){var i,r=s(e);e.inline&&a.get()||!e.inline&&n.get()||(e.inline?a.set(!0):n.set(!0),!1!==r&&(i=e.dom.create("link",{rel:"stylesheet",href:r||t+"/css/prism.css"}),e.getDoc().getElementsByTagName("head")[0].appendChild(i)))},x=function(e){e.addButton("codesample",{cmd:"codesample",title:"Insert/Edit code sample"}),e.addMenuItem("codesample",{cmd:"codesample",text:"Code sample",icon:"codesample"})},S=n(!1);e.add("codesample",function(t,e){var a=n(!1);k(t),x(t),v(t),t.on("init",function(){w(t,e,S,a)}),t.on("dblclick",function(e){g.isCodeSample(e.target)&&y(t)})})}(); \ No newline at end of file diff --git a/WebCms/Umbraco/lib/tinymce/plugins/colorpicker/index.js b/WebCms/Umbraco/lib/tinymce/plugins/colorpicker/index.js new file mode 100644 index 0000000..1af1ae6 --- /dev/null +++ b/WebCms/Umbraco/lib/tinymce/plugins/colorpicker/index.js @@ -0,0 +1,7 @@ +// Exports the "colorpicker" plugin for usage with module loaders +// Usage: +// CommonJS: +// require('tinymce/plugins/colorpicker') +// ES2015: +// import 'tinymce/plugins/colorpicker' +require('./plugin.js'); \ No newline at end of file diff --git a/WebCms/Umbraco/lib/tinymce/plugins/colorpicker/plugin.js b/WebCms/Umbraco/lib/tinymce/plugins/colorpicker/plugin.js index 11d0af3..135ba62 100644 --- a/WebCms/Umbraco/lib/tinymce/plugins/colorpicker/plugin.js +++ b/WebCms/Umbraco/lib/tinymce/plugins/colorpicker/plugin.js @@ -1,112 +1,126 @@ -/** - * plugin.js - * - * Copyright, Moxiecode Systems AB - * Released under LGPL License. - * - * License: http://www.tinymce.com/license - * Contributing: http://www.tinymce.com/contributing - */ +(function () { +var colorpicker = (function () { + 'use strict'; -/*global tinymce:true */ + var global = tinymce.util.Tools.resolve('tinymce.PluginManager'); -tinymce.PluginManager.add('colorpicker', function(editor) { - function colorPickerCallback(callback, value) { - function setColor(value) { - var color = new tinymce.util.Color(value), rgb = color.toRgb(); + var global$1 = tinymce.util.Tools.resolve('tinymce.util.Color'); - win.fromJSON({ - r: rgb.r, - g: rgb.g, - b: rgb.b, - hex: color.toHex().substr(1) - }); + var showPreview = function (win, hexColor) { + win.find('#preview')[0].getEl().style.background = hexColor; + }; + var setColor = function (win, value) { + var color = global$1(value), rgb = color.toRgb(); + win.fromJSON({ + r: rgb.r, + g: rgb.g, + b: rgb.b, + hex: color.toHex().substr(1) + }); + showPreview(win, color.toHex()); + }; + var open = function (editor, callback, value) { + var win = editor.windowManager.open({ + title: 'Color', + items: { + type: 'container', + layout: 'flex', + direction: 'row', + align: 'stretch', + padding: 5, + spacing: 10, + items: [ + { + type: 'colorpicker', + value: value, + onchange: function () { + var rgb = this.rgb(); + if (win) { + win.find('#r').value(rgb.r); + win.find('#g').value(rgb.g); + win.find('#b').value(rgb.b); + win.find('#hex').value(this.value().substr(1)); + showPreview(win, this.value()); + } + } + }, + { + type: 'form', + padding: 0, + labelGap: 5, + defaults: { + type: 'textbox', + size: 7, + value: '0', + flex: 1, + spellcheck: false, + onchange: function () { + var colorPickerCtrl = win.find('colorpicker')[0]; + var name, value; + name = this.name(); + value = this.value(); + if (name === 'hex') { + value = '#' + value; + setColor(win, value); + colorPickerCtrl.value(value); + return; + } + value = { + r: win.find('#r').value(), + g: win.find('#g').value(), + b: win.find('#b').value() + }; + colorPickerCtrl.value(value); + setColor(win, value); + } + }, + items: [ + { + name: 'r', + label: 'R', + autofocus: 1 + }, + { + name: 'g', + label: 'G' + }, + { + name: 'b', + label: 'B' + }, + { + name: 'hex', + label: '#', + value: '000000' + }, + { + name: 'preview', + type: 'container', + border: 1 + } + ] + } + ] + }, + onSubmit: function () { + callback('#' + win.toJSON().hex); + } + }); + setColor(win, value); + }; + var $_5qw23qa8jh8lpujn = { open: open }; - showPreview(color.toHex()); - } + global.add('colorpicker', function (editor) { + if (!editor.settings.color_picker_callback) { + editor.settings.color_picker_callback = function (callback, value) { + $_5qw23qa8jh8lpujn.open(editor, callback, value); + }; + } + }); + function Plugin () { + } - function showPreview(hexColor) { - win.find('#preview')[0].getEl().style.background = hexColor; - } + return Plugin; - var win = editor.windowManager.open({ - title: 'Color', - items: { - type: 'container', - layout: 'flex', - direction: 'row', - align: 'stretch', - padding: 5, - spacing: 10, - items: [ - { - type: 'colorpicker', - value: value, - onchange: function() { - var rgb = this.rgb(); - - if (win) { - win.find('#r').value(rgb.r); - win.find('#g').value(rgb.g); - win.find('#b').value(rgb.b); - win.find('#hex').value(this.value().substr(1)); - showPreview(this.value()); - } - } - }, - { - type: 'form', - padding: 0, - labelGap: 5, - defaults: { - type: 'textbox', - size: 7, - value: '0', - flex: 1, - spellcheck: false, - onchange: function() { - var colorPickerCtrl = win.find('colorpicker')[0]; - var name, value; - - name = this.name(); - value = this.value(); - - if (name == "hex") { - value = '#' + value; - setColor(value); - colorPickerCtrl.value(value); - return; - } - - value = { - r: win.find('#r').value(), - g: win.find('#g').value(), - b: win.find('#b').value() - }; - - colorPickerCtrl.value(value); - setColor(value); - } - }, - items: [ - {name: 'r', label: 'R', autofocus: 1}, - {name: 'g', label: 'G'}, - {name: 'b', label: 'B'}, - {name: 'hex', label: '#', value: '000000'}, - {name: 'preview', type: 'container', border: 1} - ] - } - ] - }, - onSubmit: function() { - callback('#' + this.toJSON().hex); - } - }); - - setColor(value); - } - - if (!editor.settings.color_picker_callback) { - editor.settings.color_picker_callback = colorPickerCallback; - } -}); \ No newline at end of file +}()); +})(); diff --git a/WebCms/Umbraco/lib/tinymce/plugins/colorpicker/plugin.min.js b/WebCms/Umbraco/lib/tinymce/plugins/colorpicker/plugin.min.js index 66ea69c..10317a5 100644 --- a/WebCms/Umbraco/lib/tinymce/plugins/colorpicker/plugin.min.js +++ b/WebCms/Umbraco/lib/tinymce/plugins/colorpicker/plugin.min.js @@ -1 +1 @@ -tinymce.PluginManager.add("colorpicker",function(a){function b(b,c){function d(a){var b=new tinymce.util.Color(a),c=b.toRgb();f.fromJSON({r:c.r,g:c.g,b:c.b,hex:b.toHex().substr(1)}),e(b.toHex())}function e(a){f.find("#preview")[0].getEl().style.background=a}var f=a.windowManager.open({title:"Color",items:{type:"container",layout:"flex",direction:"row",align:"stretch",padding:5,spacing:10,items:[{type:"colorpicker",value:c,onchange:function(){var a=this.rgb();f&&(f.find("#r").value(a.r),f.find("#g").value(a.g),f.find("#b").value(a.b),f.find("#hex").value(this.value().substr(1)),e(this.value()))}},{type:"form",padding:0,labelGap:5,defaults:{type:"textbox",size:7,value:"0",flex:1,spellcheck:!1,onchange:function(){var a,b,c=f.find("colorpicker")[0];return a=this.name(),b=this.value(),"hex"==a?(b="#"+b,d(b),void c.value(b)):(b={r:f.find("#r").value(),g:f.find("#g").value(),b:f.find("#b").value()},c.value(b),void d(b))}},items:[{name:"r",label:"R",autofocus:1},{name:"g",label:"G"},{name:"b",label:"B"},{name:"hex",label:"#",value:"000000"},{name:"preview",type:"container",border:1}]}]},onSubmit:function(){b("#"+this.toJSON().hex)}});d(c)}a.settings.color_picker_callback||(a.settings.color_picker_callback=b)}); \ No newline at end of file +!function(){"use strict";var e=tinymce.util.Tools.resolve("tinymce.PluginManager"),l=tinymce.util.Tools.resolve("tinymce.util.Color"),a=function(e,n){e.find("#preview")[0].getEl().style.background=n},o=function(e,n){var i=l(n),t=i.toRgb();e.fromJSON({r:t.r,g:t.g,b:t.b,hex:i.toHex().substr(1)}),a(e,i.toHex())},t=function(e,n,i){var t=e.windowManager.open({title:"Color",items:{type:"container",layout:"flex",direction:"row",align:"stretch",padding:5,spacing:10,items:[{type:"colorpicker",value:i,onchange:function(){var e=this.rgb();t&&(t.find("#r").value(e.r),t.find("#g").value(e.g),t.find("#b").value(e.b),t.find("#hex").value(this.value().substr(1)),a(t,this.value()))}},{type:"form",padding:0,labelGap:5,defaults:{type:"textbox",size:7,value:"0",flex:1,spellcheck:!1,onchange:function(){var e,n,i=t.find("colorpicker")[0];if(e=this.name(),n=this.value(),"hex"===e)return o(t,n="#"+n),void i.value(n);n={r:t.find("#r").value(),g:t.find("#g").value(),b:t.find("#b").value()},i.value(n),o(t,n)}},items:[{name:"r",label:"R",autofocus:1},{name:"g",label:"G"},{name:"b",label:"B"},{name:"hex",label:"#",value:"000000"},{name:"preview",type:"container",border:1}]}]},onSubmit:function(){n("#"+t.toJSON().hex)}});o(t,i)};e.add("colorpicker",function(i){i.settings.color_picker_callback||(i.settings.color_picker_callback=function(e,n){t(i,e,n)})})}(); \ No newline at end of file diff --git a/WebCms/Umbraco/lib/tinymce/plugins/contextmenu/index.js b/WebCms/Umbraco/lib/tinymce/plugins/contextmenu/index.js new file mode 100644 index 0000000..ae837e2 --- /dev/null +++ b/WebCms/Umbraco/lib/tinymce/plugins/contextmenu/index.js @@ -0,0 +1,7 @@ +// Exports the "contextmenu" plugin for usage with module loaders +// Usage: +// CommonJS: +// require('tinymce/plugins/contextmenu') +// ES2015: +// import 'tinymce/plugins/contextmenu' +require('./plugin.js'); \ No newline at end of file diff --git a/WebCms/Umbraco/lib/tinymce/plugins/contextmenu/plugin.js b/WebCms/Umbraco/lib/tinymce/plugins/contextmenu/plugin.js index 7010da5..a35bb5f 100644 --- a/WebCms/Umbraco/lib/tinymce/plugins/contextmenu/plugin.js +++ b/WebCms/Umbraco/lib/tinymce/plugins/contextmenu/plugin.js @@ -1,87 +1,168 @@ -/** - * plugin.js - * - * Copyright, Moxiecode Systems AB - * Released under LGPL License. - * - * License: http://www.tinymce.com/license - * Contributing: http://www.tinymce.com/contributing - */ +(function () { +var contextmenu = (function () { + 'use strict'; -/*global tinymce:true */ + var Cell = function (initial) { + var value = initial; + var get = function () { + return value; + }; + var set = function (v) { + value = v; + }; + var clone = function () { + return Cell(get()); + }; + return { + get: get, + set: set, + clone: clone + }; + }; -tinymce.PluginManager.add('contextmenu', function(editor) { - var menu, contextmenuNeverUseNative = editor.settings.contextmenu_never_use_native; + var global = tinymce.util.Tools.resolve('tinymce.PluginManager'); - editor.on('contextmenu', function(e) { - var contextmenu, doc = editor.getDoc(); + var get = function (visibleState) { + var isContextMenuVisible = function () { + return visibleState.get(); + }; + return { isContextMenuVisible: isContextMenuVisible }; + }; + var $_1plr87adjh8lpuk3 = { get: get }; - // Block TinyMCE menu on ctrlKey - if (e.ctrlKey && !contextmenuNeverUseNative) { - return; - } + var shouldNeverUseNative = function (editor) { + return editor.settings.contextmenu_never_use_native; + }; + var getContextMenu = function (editor) { + return editor.getParam('contextmenu', 'link openlink image inserttable | cell row column deletetable'); + }; + var $_aqpwplafjh8lpuk5 = { + shouldNeverUseNative: shouldNeverUseNative, + getContextMenu: getContextMenu + }; - e.preventDefault(); + var global$1 = tinymce.util.Tools.resolve('tinymce.dom.DOMUtils'); - /** - * WebKit/Blink on Mac has the odd behavior of selecting the target word or line this causes - * issues when for example inserting images see: #7022 - */ - if (tinymce.Env.mac && tinymce.Env.webkit) { - if (e.button == 2 && doc.caretRangeFromPoint) { - editor.selection.setRng(doc.caretRangeFromPoint(e.x, e.y)); - } - } + var getUiContainer = function (editor) { + return global$1.DOM.select(editor.settings.ui_container)[0]; + }; - contextmenu = editor.settings.contextmenu || 'link image inserttable | cell row column deletetable'; + var nu = function (x, y) { + return { + x: x, + y: y + }; + }; + var transpose = function (pos, dx, dy) { + return nu(pos.x + dx, pos.y + dy); + }; + var fromPageXY = function (e) { + return nu(e.pageX, e.pageY); + }; + var fromClientXY = function (e) { + return nu(e.clientX, e.clientY); + }; + var transposeUiContainer = function (element, pos) { + if (element && global$1.DOM.getStyle(element, 'position', true) !== 'static') { + var containerPos = global$1.DOM.getPos(element); + var dx = containerPos.x - element.scrollLeft; + var dy = containerPos.y - element.scrollTop; + return transpose(pos, -dx, -dy); + } else { + return transpose(pos, 0, 0); + } + }; + var transposeContentAreaContainer = function (element, pos) { + var containerPos = global$1.DOM.getPos(element); + return transpose(pos, containerPos.x, containerPos.y); + }; + var getPos = function (editor, e) { + if (editor.inline) { + return transposeUiContainer(getUiContainer(editor), fromPageXY(e)); + } else { + var iframePos = transposeContentAreaContainer(editor.getContentAreaContainer(), fromClientXY(e)); + return transposeUiContainer(getUiContainer(editor), iframePos); + } + }; + var $_chvtdxagjh8lpuk6 = { getPos: getPos }; - // Render menu - if (!menu) { - var items = []; + var global$2 = tinymce.util.Tools.resolve('tinymce.ui.Factory'); - tinymce.each(contextmenu.split(/[ ,]/), function(name) { - var item = editor.menuItems[name]; + var global$3 = tinymce.util.Tools.resolve('tinymce.util.Tools'); - if (name == '|') { - item = {text: name}; - } + var renderMenu = function (editor, visibleState) { + var menu, contextmenu; + var items = []; + contextmenu = $_aqpwplafjh8lpuk5.getContextMenu(editor); + global$3.each(contextmenu.split(/[ ,]/), function (name) { + var item = editor.menuItems[name]; + if (name === '|') { + item = { text: name }; + } + if (item) { + item.shortcut = ''; + items.push(item); + } + }); + for (var i = 0; i < items.length; i++) { + if (items[i].text === '|') { + if (i === 0 || i === items.length - 1) { + items.splice(i, 1); + } + } + } + menu = global$2.create('menu', { + items: items, + context: 'contextmenu', + classes: 'contextmenu' + }); + menu.uiContainer = getUiContainer(editor); + menu.renderTo(getUiContainer(editor)); + menu.on('hide', function (e) { + if (e.control === this) { + visibleState.set(false); + } + }); + editor.on('remove', function () { + menu.remove(); + menu = null; + }); + return menu; + }; + var show = function (editor, pos, visibleState, menu) { + if (menu.get() === null) { + menu.set(renderMenu(editor, visibleState)); + } else { + menu.get().show(); + } + menu.get().moveTo(pos.x, pos.y); + visibleState.set(true); + }; + var $_5cxb8ajjh8lpukb = { show: show }; - if (item) { - item.shortcut = ''; // Hide shortcuts - items.push(item); - } - }); + var isNativeOverrideKeyEvent = function (editor, e) { + return e.ctrlKey && !$_aqpwplafjh8lpuk5.shouldNeverUseNative(editor); + }; + var setup = function (editor, visibleState, menu) { + editor.on('contextmenu', function (e) { + if (isNativeOverrideKeyEvent(editor, e)) { + return; + } + e.preventDefault(); + $_5cxb8ajjh8lpukb.show(editor, $_chvtdxagjh8lpuk6.getPos(editor, e), visibleState, menu); + }); + }; + var $_fqo42laejh8lpuk4 = { setup: setup }; - for (var i = 0; i < items.length; i++) { - if (items[i].text == '|') { - if (i === 0 || i == items.length - 1) { - items.splice(i, 1); - } - } - } + global.add('contextmenu', function (editor) { + var menu = Cell(null), visibleState = Cell(false); + $_fqo42laejh8lpuk4.setup(editor, visibleState, menu); + return $_1plr87adjh8lpuk3.get(visibleState); + }); + function Plugin () { + } - menu = new tinymce.ui.Menu({ - items: items, - context: 'contextmenu' - }).addClass('contextmenu').renderTo(); + return Plugin; - editor.on('remove', function() { - menu.remove(); - menu = null; - }); - } else { - menu.show(); - } - - // Position menu - var pos = {x: e.pageX, y: e.pageY}; - - if (!editor.inline) { - pos = tinymce.DOM.getPos(editor.getContentAreaContainer()); - pos.x += e.clientX; - pos.y += e.clientY; - } - - menu.moveTo(pos.x, pos.y); - }); -}); \ No newline at end of file +}()); +})(); diff --git a/WebCms/Umbraco/lib/tinymce/plugins/contextmenu/plugin.min.js b/WebCms/Umbraco/lib/tinymce/plugins/contextmenu/plugin.min.js index a8ed37d..a2e2c75 100644 --- a/WebCms/Umbraco/lib/tinymce/plugins/contextmenu/plugin.min.js +++ b/WebCms/Umbraco/lib/tinymce/plugins/contextmenu/plugin.min.js @@ -1 +1 @@ -tinymce.PluginManager.add("contextmenu",function(a){var b,c=a.settings.contextmenu_never_use_native;a.on("contextmenu",function(d){var e,f=a.getDoc();if(!d.ctrlKey||c){if(d.preventDefault(),tinymce.Env.mac&&tinymce.Env.webkit&&2==d.button&&f.caretRangeFromPoint&&a.selection.setRng(f.caretRangeFromPoint(d.x,d.y)),e=a.settings.contextmenu||"link image inserttable | cell row column deletetable",b)b.show();else{var g=[];tinymce.each(e.split(/[ ,]/),function(b){var c=a.menuItems[b];"|"==b&&(c={text:b}),c&&(c.shortcut="",g.push(c))});for(var h=0;h'; + }); + emoticonsHtml += ''; + }); + emoticonsHtml += ''; + return emoticonsHtml; + }; + var $_ty332avjh8lpul9 = { getHtml: getHtml }; - emoticonsHtml = ''; + var insertEmoticon = function (editor, src, alt) { + editor.insertContent(editor.dom.createHTML('img', { + src: src, + alt: alt + })); + }; + var register = function (editor, pluginUrl) { + var panelHtml = $_ty332avjh8lpul9.getHtml(pluginUrl); + editor.addButton('emoticons', { + type: 'panelbutton', + panel: { + role: 'application', + autohide: true, + html: panelHtml, + onclick: function (e) { + var linkElm = editor.dom.getParent(e.target, 'a'); + if (linkElm) { + insertEmoticon(editor, linkElm.getAttribute('data-mce-url'), linkElm.getAttribute('data-mce-alt')); + this.hide(); + } + } + }, + tooltip: 'Emoticons' + }); + }; + var $_5iodwqaujh8lpul7 = { register: register }; - tinymce.each(emoticons, function(row) { - emoticonsHtml += ''; + global.add('emoticons', function (editor, pluginUrl) { + $_5iodwqaujh8lpul7.register(editor, pluginUrl); + }); + function Plugin () { + } - tinymce.each(row, function(icon) { - var emoticonUrl = url + '/img/smiley-' + icon + '.gif'; + return Plugin; - emoticonsHtml += ''; - }); - - emoticonsHtml += ''; - }); - - emoticonsHtml += '
    '; - - return emoticonsHtml; - } - - editor.addButton('emoticons', { - type: 'panelbutton', - panel: { - role: 'application', - autohide: true, - html: getHtml, - onclick: function(e) { - var linkElm = editor.dom.getParent(e.target, 'a'); - - if (linkElm) { - editor.insertContent( - '' + linkElm.getAttribute('data-mce-alt') + '' - ); - - this.hide(); - } - } - }, - tooltip: 'Emoticons' - }); -}); +}()); +})(); diff --git a/WebCms/Umbraco/lib/tinymce/plugins/emoticons/plugin.min.js b/WebCms/Umbraco/lib/tinymce/plugins/emoticons/plugin.min.js index b28e172..4e3cd25 100644 --- a/WebCms/Umbraco/lib/tinymce/plugins/emoticons/plugin.min.js +++ b/WebCms/Umbraco/lib/tinymce/plugins/emoticons/plugin.min.js @@ -1 +1 @@ -tinymce.PluginManager.add("emoticons",function(a,b){function c(){var a;return a='',tinymce.each(d,function(c){a+="",tinymce.each(c,function(c){var d=b+"/img/smiley-"+c+".gif";a+=''}),a+=""}),a+="
    "}var d=[["cool","cry","embarassed","foot-in-mouth"],["frown","innocent","kiss","laughing"],["money-mouth","sealed","smile","surprised"],["tongue-out","undecided","wink","yell"]];a.addButton("emoticons",{type:"panelbutton",panel:{role:"application",autohide:!0,html:c,onclick:function(b){var c=a.dom.getParent(b.target,"a");c&&(a.insertContent(''+c.getAttribute('),this.hide())}},tooltip:"Emoticons"})}); \ No newline at end of file +!function(){"use strict";var t=tinymce.util.Tools.resolve("tinymce.PluginManager"),e=tinymce.util.Tools.resolve("tinymce.util.Tools"),n=[["cool","cry","embarassed","foot-in-mouth"],["frown","innocent","kiss","laughing"],["money-mouth","sealed","smile","surprised"],["tongue-out","undecided","wink","yell"]],i=function(i){var o;return o='',e.each(n,function(t){o+="",e.each(t,function(t){var e=i+"/img/smiley-"+t+".gif";o+=''}),o+=""}),o+="
    "},o=function(a,t){var e=i(t);a.addButton("emoticons",{type:"panelbutton",panel:{role:"application",autohide:!0,html:e,onclick:function(t){var e,i,o,n=a.dom.getParent(t.target,"a");n&&(e=a,i=n.getAttribute("data-mce-url"),o=n.getAttribute("data-mce-alt"),e.insertContent(e.dom.createHTML("img",{src:i,alt:o})),this.hide())}},tooltip:"Emoticons"})};t.add("emoticons",function(t,e){o(t,e)})}(); \ No newline at end of file diff --git a/WebCms/Umbraco/lib/tinymce/plugins/fullpage/index.js b/WebCms/Umbraco/lib/tinymce/plugins/fullpage/index.js new file mode 100644 index 0000000..c327d6a --- /dev/null +++ b/WebCms/Umbraco/lib/tinymce/plugins/fullpage/index.js @@ -0,0 +1,7 @@ +// Exports the "fullpage" plugin for usage with module loaders +// Usage: +// CommonJS: +// require('tinymce/plugins/fullpage') +// ES2015: +// import 'tinymce/plugins/fullpage' +require('./plugin.js'); \ No newline at end of file diff --git a/WebCms/Umbraco/lib/tinymce/plugins/fullpage/plugin.js b/WebCms/Umbraco/lib/tinymce/plugins/fullpage/plugin.js index 94465ad..2a6bb6f 100644 --- a/WebCms/Umbraco/lib/tinymce/plugins/fullpage/plugin.js +++ b/WebCms/Umbraco/lib/tinymce/plugins/fullpage/plugin.js @@ -1,490 +1,519 @@ -/** - * plugin.js - * - * Copyright, Moxiecode Systems AB - * Released under LGPL License. - * - * License: http://www.tinymce.com/license - * Contributing: http://www.tinymce.com/contributing - */ - -/*global tinymce:true */ - -tinymce.PluginManager.add('fullpage', function(editor) { - var each = tinymce.each, Node = tinymce.html.Node; - var head, foot; - - function showDialog() { - var data = htmlToData(); - - editor.windowManager.open({ - title: 'Document properties', - data: data, - defaults: {type: 'textbox', size: 40}, - body: [ - {name: 'title', label: 'Title'}, - {name: 'keywords', label: 'Keywords'}, - {name: 'description', label: 'Description'}, - {name: 'robots', label: 'Robots'}, - {name: 'author', label: 'Author'}, - {name: 'docencoding', label: 'Encoding'} - ], - onSubmit: function(e) { - dataToHtml(tinymce.extend(data, e.data)); - } - }); - } - - function htmlToData() { - var headerFragment = parseHeader(), data = {}, elm, matches; - - function getAttr(elm, name) { - var value = elm.attr(name); - - return value || ''; - } - - // Default some values - data.fontface = editor.getParam("fullpage_default_fontface", ""); - data.fontsize = editor.getParam("fullpage_default_fontsize", ""); - - // Parse XML PI - elm = headerFragment.firstChild; - if (elm.type == 7) { - data.xml_pi = true; - matches = /encoding="([^"]+)"/.exec(elm.value); - if (matches) { - data.docencoding = matches[1]; - } - } - - // Parse doctype - elm = headerFragment.getAll('#doctype')[0]; - if (elm) { - data.doctype = '"; - } - - // Parse title element - elm = headerFragment.getAll('title')[0]; - if (elm && elm.firstChild) { - data.title = elm.firstChild.value; - } - - // Parse meta elements - each(headerFragment.getAll('meta'), function(meta) { - var name = meta.attr('name'), httpEquiv = meta.attr('http-equiv'), matches; - - if (name) { - data[name.toLowerCase()] = meta.attr('content'); - } else if (httpEquiv == "Content-Type") { - matches = /charset\s*=\s*(.*)\s*/gi.exec(meta.attr('content')); - - if (matches) { - data.docencoding = matches[1]; - } - } - }); - - // Parse html attribs - elm = headerFragment.getAll('html')[0]; - if (elm) { - data.langcode = getAttr(elm, 'lang') || getAttr(elm, 'xml:lang'); - } - - // Parse stylesheets - data.stylesheets = []; - tinymce.each(headerFragment.getAll('link'), function(link) { - if (link.attr('rel') == 'stylesheet') { - data.stylesheets.push(link.attr('href')); - } - }); - - // Parse body parts - elm = headerFragment.getAll('body')[0]; - if (elm) { - data.langdir = getAttr(elm, 'dir'); - data.style = getAttr(elm, 'style'); - data.visited_color = getAttr(elm, 'vlink'); - data.link_color = getAttr(elm, 'link'); - data.active_color = getAttr(elm, 'alink'); - } - - return data; - } - - function dataToHtml(data) { - var headerFragment, headElement, html, elm, value, dom = editor.dom; - - function setAttr(elm, name, value) { - elm.attr(name, value ? value : undefined); - } - - function addHeadNode(node) { - if (headElement.firstChild) { - headElement.insert(node, headElement.firstChild); - } else { - headElement.append(node); - } - } - - headerFragment = parseHeader(); - headElement = headerFragment.getAll('head')[0]; - if (!headElement) { - elm = headerFragment.getAll('html')[0]; - headElement = new Node('head', 1); - - if (elm.firstChild) { - elm.insert(headElement, elm.firstChild, true); - } else { - elm.append(headElement); - } - } - - // Add/update/remove XML-PI - elm = headerFragment.firstChild; - if (data.xml_pi) { - value = 'version="1.0"'; - - if (data.docencoding) { - value += ' encoding="' + data.docencoding + '"'; - } - - if (elm.type != 7) { - elm = new Node('xml', 7); - headerFragment.insert(elm, headerFragment.firstChild, true); - } - - elm.value = value; - } else if (elm && elm.type == 7) { - elm.remove(); - } - - // Add/update/remove doctype - elm = headerFragment.getAll('#doctype')[0]; - if (data.doctype) { - if (!elm) { - elm = new Node('#doctype', 10); - - if (data.xml_pi) { - headerFragment.insert(elm, headerFragment.firstChild); - } else { - addHeadNode(elm); - } - } - - elm.value = data.doctype.substring(9, data.doctype.length - 1); - } else if (elm) { - elm.remove(); - } - - // Add meta encoding - elm = null; - each(headerFragment.getAll('meta'), function(meta) { - if (meta.attr('http-equiv') == 'Content-Type') { - elm = meta; - } - }); - - if (data.docencoding) { - if (!elm) { - elm = new Node('meta', 1); - elm.attr('http-equiv', 'Content-Type'); - elm.shortEnded = true; - addHeadNode(elm); - } - - elm.attr('content', 'text/html; charset=' + data.docencoding); - } else if (elm) { - elm.remove(); - } - - // Add/update/remove title - elm = headerFragment.getAll('title')[0]; - if (data.title) { - if (!elm) { - elm = new Node('title', 1); - addHeadNode(elm); - } else { - elm.empty(); - } - - elm.append(new Node('#text', 3)).value = data.title; - } else if (elm) { - elm.remove(); - } - - // Add/update/remove meta - each('keywords,description,author,copyright,robots'.split(','), function(name) { - var nodes = headerFragment.getAll('meta'), i, meta, value = data[name]; - - for (i = 0; i < nodes.length; i++) { - meta = nodes[i]; - - if (meta.attr('name') == name) { - if (value) { - meta.attr('content', value); - } else { - meta.remove(); - } - - return; - } - } - - if (value) { - elm = new Node('meta', 1); - elm.attr('name', name); - elm.attr('content', value); - elm.shortEnded = true; - - addHeadNode(elm); - } - }); - - var currentStyleSheetsMap = {}; - tinymce.each(headerFragment.getAll('link'), function(stylesheet) { - if (stylesheet.attr('rel') == 'stylesheet') { - currentStyleSheetsMap[stylesheet.attr('href')] = stylesheet; - } - }); - - // Add new - tinymce.each(data.stylesheets, function(stylesheet) { - if (!currentStyleSheetsMap[stylesheet]) { - elm = new Node('link', 1); - elm.attr({ - rel: 'stylesheet', - text: 'text/css', - href: stylesheet - }); - elm.shortEnded = true; - addHeadNode(elm); - } - - delete currentStyleSheetsMap[stylesheet]; - }); - - // Delete old - tinymce.each(currentStyleSheetsMap, function(stylesheet) { - stylesheet.remove(); - }); - - // Update body attributes - elm = headerFragment.getAll('body')[0]; - if (elm) { - setAttr(elm, 'dir', data.langdir); - setAttr(elm, 'style', data.style); - setAttr(elm, 'vlink', data.visited_color); - setAttr(elm, 'link', data.link_color); - setAttr(elm, 'alink', data.active_color); - - // Update iframe body as well - dom.setAttribs(editor.getBody(), { - style: data.style, - dir: data.dir, - vLink: data.visited_color, - link: data.link_color, - aLink: data.active_color - }); - } - - // Set html attributes - elm = headerFragment.getAll('html')[0]; - if (elm) { - setAttr(elm, 'lang', data.langcode); - setAttr(elm, 'xml:lang', data.langcode); - } - - // No need for a head element - if (!headElement.firstChild) { - headElement.remove(); - } - - // Serialize header fragment and crop away body part - html = new tinymce.html.Serializer({ - validate: false, - indent: true, - apply_source_formatting: true, - indent_before: 'head,html,body,meta,title,script,link,style', - indent_after: 'head,html,body,meta,title,script,link,style' - }).serialize(headerFragment); - - head = html.substring(0, html.indexOf('')); - } - - function parseHeader() { - // Parse the contents with a DOM parser - return new tinymce.html.DomParser({ - validate: false, - root_name: '#document' - }).parse(head); - } - - function setContent(evt) { - var startPos, endPos, content = evt.content, headerFragment, styles = '', dom = editor.dom, elm; - - if (evt.selection) { - return; - } - - function low(s) { - return s.replace(/<\/?[A-Z]+/g, function(a) { - return a.toLowerCase(); - }); - } - - // Ignore raw updated if we already have a head, this will fix issues with undo/redo keeping the head/foot separate - if (evt.format == 'raw' && head) { - return; - } - - if (evt.source_view && editor.getParam('fullpage_hide_in_source_view')) { - return; - } - - // Fixed so new document/setContent('') doesn't remove existing header/footer except when it's in source code view - if (content.length === 0 && !evt.source_view) { - content = tinymce.trim(head) + '\n' + tinymce.trim(content) + '\n' + tinymce.trim(foot); - } - - // Parse out head, body and footer - content = content.replace(/<(\/?)BODY/gi, '<$1body'); - startPos = content.indexOf('', startPos); - head = low(content.substring(0, startPos + 1)); - - endPos = content.indexOf('\n'; - } - - header += editor.getParam('fullpage_default_doctype', ''); - header += '\n\n\n'; - - if ((value = editor.getParam('fullpage_default_title'))) { - header += '' + value + '\n'; - } - - if ((value = editor.getParam('fullpage_default_encoding'))) { - header += '\n'; - } - - if ((value = editor.getParam('fullpage_default_font_family'))) { - styles += 'font-family: ' + value + ';'; - } - - if ((value = editor.getParam('fullpage_default_font_size'))) { - styles += 'font-size: ' + value + ';'; - } - - if ((value = editor.getParam('fullpage_default_text_color'))) { - styles += 'color: ' + value + ';'; - } - - header += '\n\n'; - - return header; - } - - function getContent(evt) { - if (!evt.selection && (!evt.source_view || !editor.getParam('fullpage_hide_in_source_view'))) { - evt.content = tinymce.trim(head) + '\n' + tinymce.trim(evt.content) + '\n' + tinymce.trim(foot); - } - } - - editor.addCommand('mceFullPageProperties', showDialog); - - editor.addButton('fullpage', { - title: 'Document properties', - cmd: 'mceFullPageProperties' - }); - - editor.addMenuItem('fullpage', { - text: 'Document properties', - cmd: 'mceFullPageProperties', - context: 'file' - }); - - editor.on('BeforeSetContent', setContent); - editor.on('GetContent', getContent); -}); +(function () { +var fullpage = (function () { + 'use strict'; + + var Cell = function (initial) { + var value = initial; + var get = function () { + return value; + }; + var set = function (v) { + value = v; + }; + var clone = function () { + return Cell(get()); + }; + return { + get: get, + set: set, + clone: clone + }; + }; + + var global = tinymce.util.Tools.resolve('tinymce.PluginManager'); + + var global$1 = tinymce.util.Tools.resolve('tinymce.util.Tools'); + + var global$2 = tinymce.util.Tools.resolve('tinymce.html.DomParser'); + + var global$3 = tinymce.util.Tools.resolve('tinymce.html.Node'); + + var global$4 = tinymce.util.Tools.resolve('tinymce.html.Serializer'); + + var shouldHideInSourceView = function (editor) { + return editor.getParam('fullpage_hide_in_source_view'); + }; + var getDefaultXmlPi = function (editor) { + return editor.getParam('fullpage_default_xml_pi'); + }; + var getDefaultEncoding = function (editor) { + return editor.getParam('fullpage_default_encoding'); + }; + var getDefaultFontFamily = function (editor) { + return editor.getParam('fullpage_default_font_family'); + }; + var getDefaultFontSize = function (editor) { + return editor.getParam('fullpage_default_font_size'); + }; + var getDefaultTextColor = function (editor) { + return editor.getParam('fullpage_default_text_color'); + }; + var getDefaultTitle = function (editor) { + return editor.getParam('fullpage_default_title'); + }; + var getDefaultDocType = function (editor) { + return editor.getParam('fullpage_default_doctype', ''); + }; + var $_3e4zambsjh8lpuos = { + shouldHideInSourceView: shouldHideInSourceView, + getDefaultXmlPi: getDefaultXmlPi, + getDefaultEncoding: getDefaultEncoding, + getDefaultFontFamily: getDefaultFontFamily, + getDefaultFontSize: getDefaultFontSize, + getDefaultTextColor: getDefaultTextColor, + getDefaultTitle: getDefaultTitle, + getDefaultDocType: getDefaultDocType + }; + + var parseHeader = function (head) { + return global$2({ + validate: false, + root_name: '#document' + }).parse(head); + }; + var htmlToData = function (editor, head) { + var headerFragment = parseHeader(head); + var data = {}; + var elm, matches; + function getAttr(elm, name) { + var value = elm.attr(name); + return value || ''; + } + data.fontface = $_3e4zambsjh8lpuos.getDefaultFontFamily(editor); + data.fontsize = $_3e4zambsjh8lpuos.getDefaultFontSize(editor); + elm = headerFragment.firstChild; + if (elm.type === 7) { + data.xml_pi = true; + matches = /encoding="([^"]+)"/.exec(elm.value); + if (matches) { + data.docencoding = matches[1]; + } + } + elm = headerFragment.getAll('#doctype')[0]; + if (elm) { + data.doctype = ''; + } + elm = headerFragment.getAll('title')[0]; + if (elm && elm.firstChild) { + data.title = elm.firstChild.value; + } + global$1.each(headerFragment.getAll('meta'), function (meta) { + var name = meta.attr('name'); + var httpEquiv = meta.attr('http-equiv'); + var matches; + if (name) { + data[name.toLowerCase()] = meta.attr('content'); + } else if (httpEquiv === 'Content-Type') { + matches = /charset\s*=\s*(.*)\s*/gi.exec(meta.attr('content')); + if (matches) { + data.docencoding = matches[1]; + } + } + }); + elm = headerFragment.getAll('html')[0]; + if (elm) { + data.langcode = getAttr(elm, 'lang') || getAttr(elm, 'xml:lang'); + } + data.stylesheets = []; + global$1.each(headerFragment.getAll('link'), function (link) { + if (link.attr('rel') === 'stylesheet') { + data.stylesheets.push(link.attr('href')); + } + }); + elm = headerFragment.getAll('body')[0]; + if (elm) { + data.langdir = getAttr(elm, 'dir'); + data.style = getAttr(elm, 'style'); + data.visited_color = getAttr(elm, 'vlink'); + data.link_color = getAttr(elm, 'link'); + data.active_color = getAttr(elm, 'alink'); + } + return data; + }; + var dataToHtml = function (editor, data, head) { + var headerFragment, headElement, html, elm, value; + var dom = editor.dom; + function setAttr(elm, name, value) { + elm.attr(name, value ? value : undefined); + } + function addHeadNode(node) { + if (headElement.firstChild) { + headElement.insert(node, headElement.firstChild); + } else { + headElement.append(node); + } + } + headerFragment = parseHeader(head); + headElement = headerFragment.getAll('head')[0]; + if (!headElement) { + elm = headerFragment.getAll('html')[0]; + headElement = new global$3('head', 1); + if (elm.firstChild) { + elm.insert(headElement, elm.firstChild, true); + } else { + elm.append(headElement); + } + } + elm = headerFragment.firstChild; + if (data.xml_pi) { + value = 'version="1.0"'; + if (data.docencoding) { + value += ' encoding="' + data.docencoding + '"'; + } + if (elm.type !== 7) { + elm = new global$3('xml', 7); + headerFragment.insert(elm, headerFragment.firstChild, true); + } + elm.value = value; + } else if (elm && elm.type === 7) { + elm.remove(); + } + elm = headerFragment.getAll('#doctype')[0]; + if (data.doctype) { + if (!elm) { + elm = new global$3('#doctype', 10); + if (data.xml_pi) { + headerFragment.insert(elm, headerFragment.firstChild); + } else { + addHeadNode(elm); + } + } + elm.value = data.doctype.substring(9, data.doctype.length - 1); + } else if (elm) { + elm.remove(); + } + elm = null; + global$1.each(headerFragment.getAll('meta'), function (meta) { + if (meta.attr('http-equiv') === 'Content-Type') { + elm = meta; + } + }); + if (data.docencoding) { + if (!elm) { + elm = new global$3('meta', 1); + elm.attr('http-equiv', 'Content-Type'); + elm.shortEnded = true; + addHeadNode(elm); + } + elm.attr('content', 'text/html; charset=' + data.docencoding); + } else if (elm) { + elm.remove(); + } + elm = headerFragment.getAll('title')[0]; + if (data.title) { + if (!elm) { + elm = new global$3('title', 1); + addHeadNode(elm); + } else { + elm.empty(); + } + elm.append(new global$3('#text', 3)).value = data.title; + } else if (elm) { + elm.remove(); + } + global$1.each('keywords,description,author,copyright,robots'.split(','), function (name) { + var nodes = headerFragment.getAll('meta'); + var i, meta; + var value = data[name]; + for (i = 0; i < nodes.length; i++) { + meta = nodes[i]; + if (meta.attr('name') === name) { + if (value) { + meta.attr('content', value); + } else { + meta.remove(); + } + return; + } + } + if (value) { + elm = new global$3('meta', 1); + elm.attr('name', name); + elm.attr('content', value); + elm.shortEnded = true; + addHeadNode(elm); + } + }); + var currentStyleSheetsMap = {}; + global$1.each(headerFragment.getAll('link'), function (stylesheet) { + if (stylesheet.attr('rel') === 'stylesheet') { + currentStyleSheetsMap[stylesheet.attr('href')] = stylesheet; + } + }); + global$1.each(data.stylesheets, function (stylesheet) { + if (!currentStyleSheetsMap[stylesheet]) { + elm = new global$3('link', 1); + elm.attr({ + rel: 'stylesheet', + text: 'text/css', + href: stylesheet + }); + elm.shortEnded = true; + addHeadNode(elm); + } + delete currentStyleSheetsMap[stylesheet]; + }); + global$1.each(currentStyleSheetsMap, function (stylesheet) { + stylesheet.remove(); + }); + elm = headerFragment.getAll('body')[0]; + if (elm) { + setAttr(elm, 'dir', data.langdir); + setAttr(elm, 'style', data.style); + setAttr(elm, 'vlink', data.visited_color); + setAttr(elm, 'link', data.link_color); + setAttr(elm, 'alink', data.active_color); + dom.setAttribs(editor.getBody(), { + style: data.style, + dir: data.dir, + vLink: data.visited_color, + link: data.link_color, + aLink: data.active_color + }); + } + elm = headerFragment.getAll('html')[0]; + if (elm) { + setAttr(elm, 'lang', data.langcode); + setAttr(elm, 'xml:lang', data.langcode); + } + if (!headElement.firstChild) { + headElement.remove(); + } + html = global$4({ + validate: false, + indent: true, + apply_source_formatting: true, + indent_before: 'head,html,body,meta,title,script,link,style', + indent_after: 'head,html,body,meta,title,script,link,style' + }).serialize(headerFragment); + return html.substring(0, html.indexOf('')); + }; + var $_bbimm2bojh8lpuol = { + parseHeader: parseHeader, + htmlToData: htmlToData, + dataToHtml: dataToHtml + }; + + var open = function (editor, headState) { + var data = $_bbimm2bojh8lpuol.htmlToData(editor, headState.get()); + editor.windowManager.open({ + title: 'Document properties', + data: data, + defaults: { + type: 'textbox', + size: 40 + }, + body: [ + { + name: 'title', + label: 'Title' + }, + { + name: 'keywords', + label: 'Keywords' + }, + { + name: 'description', + label: 'Description' + }, + { + name: 'robots', + label: 'Robots' + }, + { + name: 'author', + label: 'Author' + }, + { + name: 'docencoding', + label: 'Encoding' + } + ], + onSubmit: function (e) { + var headHtml = $_bbimm2bojh8lpuol.dataToHtml(editor, global$1.extend(data, e.data), headState.get()); + headState.set(headHtml); + } + }); + }; + var $_93djdgbmjh8lpuoi = { open: open }; + + var register = function (editor, headState) { + editor.addCommand('mceFullPageProperties', function () { + $_93djdgbmjh8lpuoi.open(editor, headState); + }); + }; + var $_42xhtgbljh8lpuoh = { register: register }; + + var protectHtml = function (protect, html) { + global$1.each(protect, function (pattern) { + html = html.replace(pattern, function (str) { + return ''; + }); + }); + return html; + }; + var unprotectHtml = function (html) { + return html.replace(//g, function (a, m) { + return unescape(m); + }); + }; + var $_8s3veybujh8lpuox = { + protectHtml: protectHtml, + unprotectHtml: unprotectHtml + }; + + var each = global$1.each; + var low = function (s) { + return s.replace(/<\/?[A-Z]+/g, function (a) { + return a.toLowerCase(); + }); + }; + var handleSetContent = function (editor, headState, footState, evt) { + var startPos, endPos, content, headerFragment, styles = ''; + var dom = editor.dom; + var elm; + if (evt.selection) { + return; + } + content = $_8s3veybujh8lpuox.protectHtml(editor.settings.protect, evt.content); + if (evt.format === 'raw' && headState.get()) { + return; + } + if (evt.source_view && $_3e4zambsjh8lpuos.shouldHideInSourceView(editor)) { + return; + } + if (content.length === 0 && !evt.source_view) { + content = global$1.trim(headState.get()) + '\n' + global$1.trim(content) + '\n' + global$1.trim(footState.get()); + } + content = content.replace(/<(\/?)BODY/gi, '<$1body'); + startPos = content.indexOf('', startPos); + headState.set(low(content.substring(0, startPos + 1))); + endPos = content.indexOf('\n'); + } + headerFragment = $_bbimm2bojh8lpuol.parseHeader(headState.get()); + each(headerFragment.getAll('style'), function (node) { + if (node.firstChild) { + styles += node.firstChild.value; + } + }); + elm = headerFragment.getAll('body')[0]; + if (elm) { + dom.setAttribs(editor.getBody(), { + style: elm.attr('style') || '', + dir: elm.attr('dir') || '', + vLink: elm.attr('vlink') || '', + link: elm.attr('link') || '', + aLink: elm.attr('alink') || '' + }); + } + dom.remove('fullpage_styles'); + var headElm = editor.getDoc().getElementsByTagName('head')[0]; + if (styles) { + dom.add(headElm, 'style', { id: 'fullpage_styles' }, styles); + elm = dom.get('fullpage_styles'); + if (elm.styleSheet) { + elm.styleSheet.cssText = styles; + } + } + var currentStyleSheetsMap = {}; + global$1.each(headElm.getElementsByTagName('link'), function (stylesheet) { + if (stylesheet.rel === 'stylesheet' && stylesheet.getAttribute('data-mce-fullpage')) { + currentStyleSheetsMap[stylesheet.href] = stylesheet; + } + }); + global$1.each(headerFragment.getAll('link'), function (stylesheet) { + var href = stylesheet.attr('href'); + if (!href) { + return true; + } + if (!currentStyleSheetsMap[href] && stylesheet.attr('rel') === 'stylesheet') { + dom.add(headElm, 'link', { + 'rel': 'stylesheet', + 'text': 'text/css', + 'href': href, + 'data-mce-fullpage': '1' + }); + } + delete currentStyleSheetsMap[href]; + }); + global$1.each(currentStyleSheetsMap, function (stylesheet) { + stylesheet.parentNode.removeChild(stylesheet); + }); + }; + var getDefaultHeader = function (editor) { + var header = '', value, styles = ''; + if ($_3e4zambsjh8lpuos.getDefaultXmlPi(editor)) { + var piEncoding = $_3e4zambsjh8lpuos.getDefaultEncoding(editor); + header += '\n'; + } + header += $_3e4zambsjh8lpuos.getDefaultDocType(editor); + header += '\n\n\n'; + if (value = $_3e4zambsjh8lpuos.getDefaultTitle(editor)) { + header += '' + value + '\n'; + } + if (value = $_3e4zambsjh8lpuos.getDefaultEncoding(editor)) { + header += '\n'; + } + if (value = $_3e4zambsjh8lpuos.getDefaultFontFamily(editor)) { + styles += 'font-family: ' + value + ';'; + } + if (value = $_3e4zambsjh8lpuos.getDefaultFontSize(editor)) { + styles += 'font-size: ' + value + ';'; + } + if (value = $_3e4zambsjh8lpuos.getDefaultTextColor(editor)) { + styles += 'color: ' + value + ';'; + } + header += '\n\n'; + return header; + }; + var handleGetContent = function (editor, head, foot, evt) { + if (!evt.selection && (!evt.source_view || !$_3e4zambsjh8lpuos.shouldHideInSourceView(editor))) { + evt.content = $_8s3veybujh8lpuox.unprotectHtml(global$1.trim(head) + '\n' + global$1.trim(evt.content) + '\n' + global$1.trim(foot)); + } + }; + var setup = function (editor, headState, footState) { + editor.on('BeforeSetContent', function (evt) { + handleSetContent(editor, headState, footState, evt); + }); + editor.on('GetContent', function (evt) { + handleGetContent(editor, headState.get(), footState.get(), evt); + }); + }; + var $_cxt53pbtjh8lpuou = { setup: setup }; + + var register$1 = function (editor) { + editor.addButton('fullpage', { + title: 'Document properties', + cmd: 'mceFullPageProperties' + }); + editor.addMenuItem('fullpage', { + text: 'Document properties', + cmd: 'mceFullPageProperties', + context: 'file' + }); + }; + var $_dow6j4bvjh8lpuoy = { register: register$1 }; + + global.add('fullpage', function (editor) { + var headState = Cell(''), footState = Cell(''); + $_42xhtgbljh8lpuoh.register(editor, headState); + $_dow6j4bvjh8lpuoy.register(editor); + $_cxt53pbtjh8lpuou.setup(editor, headState, footState); + }); + function Plugin () { + } + + return Plugin; + +}()); +})(); diff --git a/WebCms/Umbraco/lib/tinymce/plugins/fullpage/plugin.min.js b/WebCms/Umbraco/lib/tinymce/plugins/fullpage/plugin.min.js index 1ea5c36..de5221a 100644 --- a/WebCms/Umbraco/lib/tinymce/plugins/fullpage/plugin.min.js +++ b/WebCms/Umbraco/lib/tinymce/plugins/fullpage/plugin.min.js @@ -1 +1 @@ -tinymce.PluginManager.add("fullpage",function(a){function b(){var b=c();a.windowManager.open({title:"Document properties",data:b,defaults:{type:"textbox",size:40},body:[{name:"title",label:"Title"},{name:"keywords",label:"Keywords"},{name:"description",label:"Description"},{name:"robots",label:"Robots"},{name:"author",label:"Author"},{name:"docencoding",label:"Encoding"}],onSubmit:function(a){d(tinymce.extend(b,a.data))}})}function c(){function b(a,b){var c=a.attr(b);return c||""}var c,d,f=e(),g={};return g.fontface=a.getParam("fullpage_default_fontface",""),g.fontsize=a.getParam("fullpage_default_fontsize",""),c=f.firstChild,7==c.type&&(g.xml_pi=!0,d=/encoding="([^"]+)"/.exec(c.value),d&&(g.docencoding=d[1])),c=f.getAll("#doctype")[0],c&&(g.doctype=""),c=f.getAll("title")[0],c&&c.firstChild&&(g.title=c.firstChild.value),k(f.getAll("meta"),function(a){var b,c=a.attr("name"),d=a.attr("http-equiv");c?g[c.toLowerCase()]=a.attr("content"):"Content-Type"==d&&(b=/charset\s*=\s*(.*)\s*/gi.exec(a.attr("content")),b&&(g.docencoding=b[1]))}),c=f.getAll("html")[0],c&&(g.langcode=b(c,"lang")||b(c,"xml:lang")),g.stylesheets=[],tinymce.each(f.getAll("link"),function(a){"stylesheet"==a.attr("rel")&&g.stylesheets.push(a.attr("href"))}),c=f.getAll("body")[0],c&&(g.langdir=b(c,"dir"),g.style=b(c,"style"),g.visited_color=b(c,"vlink"),g.link_color=b(c,"link"),g.active_color=b(c,"alink")),g}function d(b){function c(a,b,c){a.attr(b,c?c:void 0)}function d(a){g.firstChild?g.insert(a,g.firstChild):g.append(a)}var f,g,h,j,m,n=a.dom;f=e(),g=f.getAll("head")[0],g||(j=f.getAll("html")[0],g=new l("head",1),j.firstChild?j.insert(g,j.firstChild,!0):j.append(g)),j=f.firstChild,b.xml_pi?(m='version="1.0"',b.docencoding&&(m+=' encoding="'+b.docencoding+'"'),7!=j.type&&(j=new l("xml",7),f.insert(j,f.firstChild,!0)),j.value=m):j&&7==j.type&&j.remove(),j=f.getAll("#doctype")[0],b.doctype?(j||(j=new l("#doctype",10),b.xml_pi?f.insert(j,f.firstChild):d(j)),j.value=b.doctype.substring(9,b.doctype.length-1)):j&&j.remove(),j=null,k(f.getAll("meta"),function(a){"Content-Type"==a.attr("http-equiv")&&(j=a)}),b.docencoding?(j||(j=new l("meta",1),j.attr("http-equiv","Content-Type"),j.shortEnded=!0,d(j)),j.attr("content","text/html; charset="+b.docencoding)):j&&j.remove(),j=f.getAll("title")[0],b.title?(j?j.empty():(j=new l("title",1),d(j)),j.append(new l("#text",3)).value=b.title):j&&j.remove(),k("keywords,description,author,copyright,robots".split(","),function(a){var c,e,g=f.getAll("meta"),h=b[a];for(c=0;c"))}function e(){return new tinymce.html.DomParser({validate:!1,root_name:"#document"}).parse(i)}function f(b){function c(a){return a.replace(/<\/?[A-Z]+/g,function(a){return a.toLowerCase()})}var d,f,h,l,m=b.content,n="",o=a.dom;if(!b.selection&&!("raw"==b.format&&i||b.source_view&&a.getParam("fullpage_hide_in_source_view"))){0!==m.length||b.source_view||(m=tinymce.trim(i)+"\n"+tinymce.trim(m)+"\n"+tinymce.trim(j)),m=m.replace(/<(\/?)BODY/gi,"<$1body"),d=m.indexOf("",d),i=c(m.substring(0,d+1)),f=m.indexOf("\n"),h=e(),k(h.getAll("style"),function(a){a.firstChild&&(n+=a.firstChild.value)}),l=h.getAll("body")[0],l&&o.setAttribs(a.getBody(),{style:l.attr("style")||"",dir:l.attr("dir")||"",vLink:l.attr("vlink")||"",link:l.attr("link")||"",aLink:l.attr("alink")||""}),o.remove("fullpage_styles");var p=a.getDoc().getElementsByTagName("head")[0];n&&(o.add(p,"style",{id:"fullpage_styles"},n),l=o.get("fullpage_styles"),l.styleSheet&&(l.styleSheet.cssText=n));var q={};tinymce.each(p.getElementsByTagName("link"),function(a){"stylesheet"==a.rel&&a.getAttribute("data-mce-fullpage")&&(q[a.href]=a)}),tinymce.each(h.getAll("link"),function(a){var b=a.attr("href");q[b]||"stylesheet"!=a.attr("rel")||o.add(p,"link",{rel:"stylesheet",text:"text/css",href:b,"data-mce-fullpage":"1"}),delete q[b]}),tinymce.each(q,function(a){a.parentNode.removeChild(a)})}}function g(){var b,c="",d="";return a.getParam("fullpage_default_xml_pi")&&(c+='\n'),c+=a.getParam("fullpage_default_doctype",""),c+="\n\n\n",(b=a.getParam("fullpage_default_title"))&&(c+=""+b+"\n"),(b=a.getParam("fullpage_default_encoding"))&&(c+='\n'),(b=a.getParam("fullpage_default_font_family"))&&(d+="font-family: "+b+";"),(b=a.getParam("fullpage_default_font_size"))&&(d+="font-size: "+b+";"),(b=a.getParam("fullpage_default_text_color"))&&(d+="color: "+b+";"),c+="\n\n"}function h(b){b.selection||b.source_view&&a.getParam("fullpage_hide_in_source_view")||(b.content=tinymce.trim(i)+"\n"+tinymce.trim(b.content)+"\n"+tinymce.trim(j))}var i,j,k=tinymce.each,l=tinymce.html.Node;a.addCommand("mceFullPageProperties",b),a.addButton("fullpage",{title:"Document properties",cmd:"mceFullPageProperties"}),a.addMenuItem("fullpage",{text:"Document properties",cmd:"mceFullPageProperties",context:"file"}),a.on("BeforeSetContent",f),a.on("GetContent",h)}); \ No newline at end of file +!function(){"use strict";var l=function(e){var t=e,n=function(){return t};return{get:n,set:function(e){t=e},clone:function(){return l(n())}}},e=tinymce.util.Tools.resolve("tinymce.PluginManager"),g=tinymce.util.Tools.resolve("tinymce.util.Tools"),t=tinymce.util.Tools.resolve("tinymce.html.DomParser"),f=tinymce.util.Tools.resolve("tinymce.html.Node"),m=tinymce.util.Tools.resolve("tinymce.html.Serializer"),h=function(e){return e.getParam("fullpage_hide_in_source_view")},r=function(e){return e.getParam("fullpage_default_xml_pi")},o=function(e){return e.getParam("fullpage_default_encoding")},a=function(e){return e.getParam("fullpage_default_font_family")},c=function(e){return e.getParam("fullpage_default_font_size")},s=function(e){return e.getParam("fullpage_default_text_color")},u=function(e){return e.getParam("fullpage_default_title")},d=function(e){return e.getParam("fullpage_default_doctype","")},p=function(e){return t({validate:!1,root_name:"#document"}).parse(e)},y=p,v=function(e,t){var n,l,i=p(t),r={};function o(e,t){return e.attr(t)||""}return r.fontface=a(e),r.fontsize=c(e),7===(n=i.firstChild).type&&(r.xml_pi=!0,(l=/encoding="([^"]+)"/.exec(n.value))&&(r.docencoding=l[1])),(n=i.getAll("#doctype")[0])&&(r.doctype=""),(n=i.getAll("title")[0])&&n.firstChild&&(r.title=n.firstChild.value),g.each(i.getAll("meta"),function(e){var t,n=e.attr("name"),l=e.attr("http-equiv");n?r[n.toLowerCase()]=e.attr("content"):"Content-Type"===l&&(t=/charset\s*=\s*(.*)\s*/gi.exec(e.attr("content")))&&(r.docencoding=t[1])}),(n=i.getAll("html")[0])&&(r.langcode=o(n,"lang")||o(n,"xml:lang")),r.stylesheets=[],g.each(i.getAll("link"),function(e){"stylesheet"===e.attr("rel")&&r.stylesheets.push(e.attr("href"))}),(n=i.getAll("body")[0])&&(r.langdir=o(n,"dir"),r.style=o(n,"style"),r.visited_color=o(n,"vlink"),r.link_color=o(n,"link"),r.active_color=o(n,"alink")),r},_=function(e,r,t){var o,n,l,a,i,c=e.dom;function s(e,t,n){e.attr(t,n||undefined)}function u(e){n.firstChild?n.insert(e,n.firstChild):n.append(e)}o=p(t),(n=o.getAll("head")[0])||(a=o.getAll("html")[0],n=new f("head",1),a.firstChild?a.insert(n,a.firstChild,!0):a.append(n)),a=o.firstChild,r.xml_pi?(i='version="1.0"',r.docencoding&&(i+=' encoding="'+r.docencoding+'"'),7!==a.type&&(a=new f("xml",7),o.insert(a,o.firstChild,!0)),a.value=i):a&&7===a.type&&a.remove(),a=o.getAll("#doctype")[0],r.doctype?(a||(a=new f("#doctype",10),r.xml_pi?o.insert(a,o.firstChild):u(a)),a.value=r.doctype.substring(9,r.doctype.length-1)):a&&a.remove(),a=null,g.each(o.getAll("meta"),function(e){"Content-Type"===e.attr("http-equiv")&&(a=e)}),r.docencoding?(a||((a=new f("meta",1)).attr("http-equiv","Content-Type"),a.shortEnded=!0,u(a)),a.attr("content","text/html; charset="+r.docencoding)):a&&a.remove(),a=o.getAll("title")[0],r.title?(a?a.empty():u(a=new f("title",1)),a.append(new f("#text",3)).value=r.title):a&&a.remove(),g.each("keywords,description,author,copyright,robots".split(","),function(e){var t,n,l=o.getAll("meta"),i=r[e];for(t=0;t"))},n=function(n,l){var i=v(n,l.get());n.windowManager.open({title:"Document properties",data:i,defaults:{type:"textbox",size:40},body:[{name:"title",label:"Title"},{name:"keywords",label:"Keywords"},{name:"description",label:"Description"},{name:"robots",label:"Robots"},{name:"author",label:"Author"},{name:"docencoding",label:"Encoding"}],onSubmit:function(e){var t=_(n,g.extend(i,e.data),l.get());l.set(t)}})},i=function(e,t){e.addCommand("mceFullPageProperties",function(){n(e,t)})},b=function(e,t){return g.each(e,function(e){t=t.replace(e,function(e){return"\x3c!--mce:protected "+escape(e)+"--\x3e"})}),t},x=function(e){return e.replace(//g,function(e,t){return unescape(t)})},k=g.each,C=function(e){return e.replace(/<\/?[A-Z]+/g,function(e){return e.toLowerCase()})},A=function(e){var t,n="",l="";if(r(e)){var i=o(e);n+='\n'}return n+=d(e),n+="\n\n\n",(t=u(e))&&(n+=""+t+"\n"),(t=o(e))&&(n+='\n'),(t=a(e))&&(l+="font-family: "+t+";"),(t=c(e))&&(l+="font-size: "+t+";"),(t=s(e))&&(l+="color: "+t+";"),n+="\n\n"},w=function(r,o,a){r.on("BeforeSetContent",function(e){!function(e,t,n,l){var i,r,o,a,c,s="",u=e.dom;if(!(l.selection||(o=b(e.settings.protect,l.content),"raw"===l.format&&t.get()||l.source_view&&h(e)))){0!==o.length||l.source_view||(o=g.trim(t.get())+"\n"+g.trim(o)+"\n"+g.trim(n.get())),-1!==(i=(o=o.replace(/<(\/?)BODY/gi,"<$1body")).indexOf("",i),t.set(C(o.substring(0,i+1))),-1===(r=o.indexOf("\n")),a=y(t.get()),k(a.getAll("style"),function(e){e.firstChild&&(s+=e.firstChild.value)}),(c=a.getAll("body")[0])&&u.setAttribs(e.getBody(),{style:c.attr("style")||"",dir:c.attr("dir")||"",vLink:c.attr("vlink")||"",link:c.attr("link")||"",aLink:c.attr("alink")||""}),u.remove("fullpage_styles");var d=e.getDoc().getElementsByTagName("head")[0];s&&(u.add(d,"style",{id:"fullpage_styles"},s),(c=u.get("fullpage_styles")).styleSheet&&(c.styleSheet.cssText=s));var f={};g.each(d.getElementsByTagName("link"),function(e){"stylesheet"===e.rel&&e.getAttribute("data-mce-fullpage")&&(f[e.href]=e)}),g.each(a.getAll("link"),function(e){var t=e.attr("href");if(!t)return!0;f[t]||"stylesheet"!==e.attr("rel")||u.add(d,"link",{rel:"stylesheet",text:"text/css",href:t,"data-mce-fullpage":"1"}),delete f[t]}),g.each(f,function(e){e.parentNode.removeChild(e)})}}(r,o,a,e)}),r.on("GetContent",function(e){var t,n,l,i;t=r,n=o.get(),l=a.get(),(i=e).selection||i.source_view&&h(t)||(i.content=x(g.trim(n)+"\n"+g.trim(i.content)+"\n"+g.trim(l)))})},P=function(e){e.addButton("fullpage",{title:"Document properties",cmd:"mceFullPageProperties"}),e.addMenuItem("fullpage",{text:"Document properties",cmd:"mceFullPageProperties",context:"file"})};e.add("fullpage",function(e){var t=l(""),n=l("");i(e,t),P(e),w(e,t,n)})}(); \ No newline at end of file diff --git a/WebCms/Umbraco/lib/tinymce/plugins/fullscreen/index.js b/WebCms/Umbraco/lib/tinymce/plugins/fullscreen/index.js new file mode 100644 index 0000000..6b4e263 --- /dev/null +++ b/WebCms/Umbraco/lib/tinymce/plugins/fullscreen/index.js @@ -0,0 +1,7 @@ +// Exports the "fullscreen" plugin for usage with module loaders +// Usage: +// CommonJS: +// require('tinymce/plugins/fullscreen') +// ES2015: +// import 'tinymce/plugins/fullscreen' +require('./plugin.js'); \ No newline at end of file diff --git a/WebCms/Umbraco/lib/tinymce/plugins/fullscreen/plugin.js b/WebCms/Umbraco/lib/tinymce/plugins/fullscreen/plugin.js index 2a3cf20..627afab 100644 --- a/WebCms/Umbraco/lib/tinymce/plugins/fullscreen/plugin.js +++ b/WebCms/Umbraco/lib/tinymce/plugins/fullscreen/plugin.js @@ -1,136 +1,177 @@ -/** - * plugin.js - * - * Copyright, Moxiecode Systems AB - * Released under LGPL License. - * - * License: http://www.tinymce.com/license - * Contributing: http://www.tinymce.com/contributing - */ +(function () { +var fullscreen = (function () { + 'use strict'; -/*global tinymce:true */ + var Cell = function (initial) { + var value = initial; + var get = function () { + return value; + }; + var set = function (v) { + value = v; + }; + var clone = function () { + return Cell(get()); + }; + return { + get: get, + set: set, + clone: clone + }; + }; -tinymce.PluginManager.add('fullscreen', function(editor) { - var fullscreenState = false, DOM = tinymce.DOM, iframeWidth, iframeHeight, resizeHandler; - var containerWidth, containerHeight; + var global = tinymce.util.Tools.resolve('tinymce.PluginManager'); - if (editor.settings.inline) { - return; - } + var get = function (fullscreenState) { + return { + isFullscreen: function () { + return fullscreenState.get() !== null; + } + }; + }; + var $_id7tbbzjh8lpupt = { get: get }; - function getWindowSize() { - var w, h, win = window, doc = document; - var body = doc.body; + var global$1 = tinymce.util.Tools.resolve('tinymce.dom.DOMUtils'); - // Old IE - if (body.offsetWidth) { - w = body.offsetWidth; - h = body.offsetHeight; - } + var fireFullscreenStateChanged = function (editor, state) { + editor.fire('FullscreenStateChanged', { state: state }); + }; + var $_i7kzec3jh8lpupz = { fireFullscreenStateChanged: fireFullscreenStateChanged }; - // Modern browsers - if (win.innerWidth && win.innerHeight) { - w = win.innerWidth; - h = win.innerHeight; - } + var DOM = global$1.DOM; + var getWindowSize = function () { + var w; + var h; + var win = window; + var doc = document; + var body = doc.body; + if (body.offsetWidth) { + w = body.offsetWidth; + h = body.offsetHeight; + } + if (win.innerWidth && win.innerHeight) { + w = win.innerWidth; + h = win.innerHeight; + } + return { + w: w, + h: h + }; + }; + var getScrollPos = function () { + var vp = DOM.getViewPort(); + return { + x: vp.x, + y: vp.y + }; + }; + var setScrollPos = function (pos) { + window.scrollTo(pos.x, pos.y); + }; + var toggleFullscreen = function (editor, fullscreenState) { + var body = document.body; + var documentElement = document.documentElement; + var editorContainerStyle; + var editorContainer, iframe, iframeStyle; + var fullscreenInfo = fullscreenState.get(); + var resize = function () { + DOM.setStyle(iframe, 'height', getWindowSize().h - (editorContainer.clientHeight - iframe.clientHeight)); + }; + var removeResize = function () { + DOM.unbind(window, 'resize', resize); + }; + editorContainer = editor.getContainer(); + editorContainerStyle = editorContainer.style; + iframe = editor.getContentAreaContainer().firstChild; + iframeStyle = iframe.style; + if (!fullscreenInfo) { + var newFullScreenInfo = { + scrollPos: getScrollPos(), + containerWidth: editorContainerStyle.width, + containerHeight: editorContainerStyle.height, + iframeWidth: iframeStyle.width, + iframeHeight: iframeStyle.height, + resizeHandler: resize, + removeHandler: removeResize + }; + iframeStyle.width = iframeStyle.height = '100%'; + editorContainerStyle.width = editorContainerStyle.height = ''; + DOM.addClass(body, 'mce-fullscreen'); + DOM.addClass(documentElement, 'mce-fullscreen'); + DOM.addClass(editorContainer, 'mce-fullscreen'); + DOM.bind(window, 'resize', resize); + editor.on('remove', removeResize); + resize(); + fullscreenState.set(newFullScreenInfo); + $_i7kzec3jh8lpupz.fireFullscreenStateChanged(editor, true); + } else { + iframeStyle.width = fullscreenInfo.iframeWidth; + iframeStyle.height = fullscreenInfo.iframeHeight; + if (fullscreenInfo.containerWidth) { + editorContainerStyle.width = fullscreenInfo.containerWidth; + } + if (fullscreenInfo.containerHeight) { + editorContainerStyle.height = fullscreenInfo.containerHeight; + } + DOM.removeClass(body, 'mce-fullscreen'); + DOM.removeClass(documentElement, 'mce-fullscreen'); + DOM.removeClass(editorContainer, 'mce-fullscreen'); + setScrollPos(fullscreenInfo.scrollPos); + DOM.unbind(window, 'resize', fullscreenInfo.resizeHandler); + editor.off('remove', fullscreenInfo.removeHandler); + fullscreenState.set(null); + $_i7kzec3jh8lpupz.fireFullscreenStateChanged(editor, false); + } + }; + var $_971qctc1jh8lpupv = { toggleFullscreen: toggleFullscreen }; - return {w: w, h: h}; - } + var register = function (editor, fullscreenState) { + editor.addCommand('mceFullScreen', function () { + $_971qctc1jh8lpupv.toggleFullscreen(editor, fullscreenState); + }); + }; + var $_7y4syjc0jh8lpupt = { register: register }; - function toggleFullscreen() { - var body = document.body, documentElement = document.documentElement, editorContainerStyle; - var editorContainer, iframe, iframeStyle; + var postRender = function (editor) { + return function (e) { + var ctrl = e.control; + editor.on('FullscreenStateChanged', function (e) { + ctrl.active(e.state); + }); + }; + }; + var register$1 = function (editor) { + editor.addMenuItem('fullscreen', { + text: 'Fullscreen', + shortcut: 'Ctrl+Shift+F', + selectable: true, + cmd: 'mceFullScreen', + onPostRender: postRender(editor), + context: 'view' + }); + editor.addButton('fullscreen', { + active: false, + tooltip: 'Fullscreen', + cmd: 'mceFullScreen', + onPostRender: postRender(editor) + }); + }; + var $_492blpc4jh8lpuq0 = { register: register$1 }; - function resize() { - DOM.setStyle(iframe, 'height', getWindowSize().h - (editorContainer.clientHeight - iframe.clientHeight)); - } + global.add('fullscreen', function (editor) { + var fullscreenState = Cell(null); + if (editor.settings.inline) { + return $_id7tbbzjh8lpupt.get(fullscreenState); + } + $_7y4syjc0jh8lpupt.register(editor, fullscreenState); + $_492blpc4jh8lpuq0.register(editor); + editor.addShortcut('Ctrl+Shift+F', '', 'mceFullScreen'); + return $_id7tbbzjh8lpupt.get(fullscreenState); + }); + function Plugin () { + } - fullscreenState = !fullscreenState; + return Plugin; - editorContainer = editor.getContainer(); - editorContainerStyle = editorContainer.style; - iframe = editor.getContentAreaContainer().firstChild; - iframeStyle = iframe.style; - - if (fullscreenState) { - iframeWidth = iframeStyle.width; - iframeHeight = iframeStyle.height; - iframeStyle.width = iframeStyle.height = '100%'; - containerWidth = editorContainerStyle.width; - containerHeight = editorContainerStyle.height; - editorContainerStyle.width = editorContainerStyle.height = ''; - - DOM.addClass(body, 'mce-fullscreen'); - DOM.addClass(documentElement, 'mce-fullscreen'); - DOM.addClass(editorContainer, 'mce-fullscreen'); - - DOM.bind(window, 'resize', resize); - resize(); - resizeHandler = resize; - } else { - iframeStyle.width = iframeWidth; - iframeStyle.height = iframeHeight; - - if (containerWidth) { - editorContainerStyle.width = containerWidth; - } - - if (containerHeight) { - editorContainerStyle.height = containerHeight; - } - - DOM.removeClass(body, 'mce-fullscreen'); - DOM.removeClass(documentElement, 'mce-fullscreen'); - DOM.removeClass(editorContainer, 'mce-fullscreen'); - DOM.unbind(window, 'resize', resizeHandler); - } - - editor.fire('FullscreenStateChanged', {state: fullscreenState}); - } - - editor.on('init', function() { - editor.addShortcut('Meta+Alt+F', '', toggleFullscreen); - }); - - editor.on('remove', function() { - if (resizeHandler) { - DOM.unbind(window, 'resize', resizeHandler); - } - }); - - editor.addCommand('mceFullScreen', toggleFullscreen); - - editor.addMenuItem('fullscreen', { - text: 'Fullscreen', - shortcut: 'Meta+Alt+F', - selectable: true, - onClick: toggleFullscreen, - onPostRender: function() { - var self = this; - - editor.on('FullscreenStateChanged', function(e) { - self.active(e.state); - }); - }, - context: 'view' - }); - - editor.addButton('fullscreen', { - tooltip: 'Fullscreen', - shortcut: 'Meta+Alt+F', - onClick: toggleFullscreen, - onPostRender: function() { - var self = this; - - editor.on('FullscreenStateChanged', function(e) { - self.active(e.state); - }); - } - }); - - return { - isFullscreen: function() { - return fullscreenState; - } - }; -}); \ No newline at end of file +}()); +})(); diff --git a/WebCms/Umbraco/lib/tinymce/plugins/fullscreen/plugin.min.js b/WebCms/Umbraco/lib/tinymce/plugins/fullscreen/plugin.min.js index d275e05..cd4eb5e 100644 --- a/WebCms/Umbraco/lib/tinymce/plugins/fullscreen/plugin.min.js +++ b/WebCms/Umbraco/lib/tinymce/plugins/fullscreen/plugin.min.js @@ -1 +1 @@ -tinymce.PluginManager.add("fullscreen",function(a){function b(){var a,b,c=window,d=document,e=d.body;return e.offsetWidth&&(a=e.offsetWidth,b=e.offsetHeight),c.innerWidth&&c.innerHeight&&(a=c.innerWidth,b=c.innerHeight),{w:a,h:b}}function c(){function c(){j.setStyle(m,"height",b().h-(l.clientHeight-m.clientHeight))}var k,l,m,n,o=document.body,p=document.documentElement;i=!i,l=a.getContainer(),k=l.style,m=a.getContentAreaContainer().firstChild,n=m.style,i?(d=n.width,e=n.height,n.width=n.height="100%",g=k.width,h=k.height,k.width=k.height="",j.addClass(o,"mce-fullscreen"),j.addClass(p,"mce-fullscreen"),j.addClass(l,"mce-fullscreen"),j.bind(window,"resize",c),c(),f=c):(n.width=d,n.height=e,g&&(k.width=g),h&&(k.height=h),j.removeClass(o,"mce-fullscreen"),j.removeClass(p,"mce-fullscreen"),j.removeClass(l,"mce-fullscreen"),j.unbind(window,"resize",f)),a.fire("FullscreenStateChanged",{state:i})}var d,e,f,g,h,i=!1,j=tinymce.DOM;return a.settings.inline?void 0:(a.on("init",function(){a.addShortcut("Meta+Alt+F","",c)}),a.on("remove",function(){f&&j.unbind(window,"resize",f)}),a.addCommand("mceFullScreen",c),a.addMenuItem("fullscreen",{text:"Fullscreen",shortcut:"Meta+Alt+F",selectable:!0,onClick:c,onPostRender:function(){var b=this;a.on("FullscreenStateChanged",function(a){b.active(a.state)})},context:"view"}),a.addButton("fullscreen",{tooltip:"Fullscreen",shortcut:"Meta+Alt+F",onClick:c,onPostRender:function(){var b=this;a.on("FullscreenStateChanged",function(a){b.active(a.state)})}}),{isFullscreen:function(){return i}})}); \ No newline at end of file +!function(){"use strict";var i=function(e){var n=e,t=function(){return n};return{get:t,set:function(e){n=e},clone:function(){return i(t())}}},e=tinymce.util.Tools.resolve("tinymce.PluginManager"),t=function(e){return{isFullscreen:function(){return null!==e.get()}}},n=tinymce.util.Tools.resolve("tinymce.dom.DOMUtils"),m=function(e,n){e.fire("FullscreenStateChanged",{state:n})},g=n.DOM,r=function(e,n){var t,r,l,i,o,c,s=document.body,u=document.documentElement,d=n.get(),a=function(){var e,n,t,i;g.setStyle(l,"height",(t=window,i=document.body,i.offsetWidth&&(e=i.offsetWidth,n=i.offsetHeight),t.innerWidth&&t.innerHeight&&(e=t.innerWidth,n=t.innerHeight),{w:e,h:n}).h-(r.clientHeight-l.clientHeight))},h=function(){g.unbind(window,"resize",a)};if(t=(r=e.getContainer()).style,i=(l=e.getContentAreaContainer().firstChild).style,d)i.width=d.iframeWidth,i.height=d.iframeHeight,d.containerWidth&&(t.width=d.containerWidth),d.containerHeight&&(t.height=d.containerHeight),g.removeClass(s,"mce-fullscreen"),g.removeClass(u,"mce-fullscreen"),g.removeClass(r,"mce-fullscreen"),o=d.scrollPos,window.scrollTo(o.x,o.y),g.unbind(window,"resize",d.resizeHandler),e.off("remove",d.removeHandler),n.set(null),m(e,!1);else{var f={scrollPos:(c=g.getViewPort(),{x:c.x,y:c.y}),containerWidth:t.width,containerHeight:t.height,iframeWidth:i.width,iframeHeight:i.height,resizeHandler:a,removeHandler:h};i.width=i.height="100%",t.width=t.height="",g.addClass(s,"mce-fullscreen"),g.addClass(u,"mce-fullscreen"),g.addClass(r,"mce-fullscreen"),g.bind(window,"resize",a),e.on("remove",h),a(),n.set(f),m(e,!0)}},l=function(e,n){e.addCommand("mceFullScreen",function(){r(e,n)})},o=function(t){return function(e){var n=e.control;t.on("FullscreenStateChanged",function(e){n.active(e.state)})}},c=function(e){e.addMenuItem("fullscreen",{text:"Fullscreen",shortcut:"Ctrl+Shift+F",selectable:!0,cmd:"mceFullScreen",onPostRender:o(e),context:"view"}),e.addButton("fullscreen",{active:!1,tooltip:"Fullscreen",cmd:"mceFullScreen",onPostRender:o(e)})};e.add("fullscreen",function(e){var n=i(null);return e.settings.inline||(l(e,n),c(e),e.addShortcut("Ctrl+Shift+F","","mceFullScreen")),t(n)})}(); \ No newline at end of file diff --git a/WebCms/Umbraco/lib/tinymce/plugins/help/img/logo.png b/WebCms/Umbraco/lib/tinymce/plugins/help/img/logo.png new file mode 100644 index 0000000..ebd7eb1 Binary files /dev/null and b/WebCms/Umbraco/lib/tinymce/plugins/help/img/logo.png differ diff --git a/WebCms/Umbraco/lib/tinymce/plugins/help/index.js b/WebCms/Umbraco/lib/tinymce/plugins/help/index.js new file mode 100644 index 0000000..7f4bfe0 --- /dev/null +++ b/WebCms/Umbraco/lib/tinymce/plugins/help/index.js @@ -0,0 +1,7 @@ +// Exports the "help" plugin for usage with module loaders +// Usage: +// CommonJS: +// require('tinymce/plugins/help') +// ES2015: +// import 'tinymce/plugins/help' +require('./plugin.js'); \ No newline at end of file diff --git a/WebCms/Umbraco/lib/tinymce/plugins/help/plugin.js b/WebCms/Umbraco/lib/tinymce/plugins/help/plugin.js new file mode 100644 index 0000000..1eae6fd --- /dev/null +++ b/WebCms/Umbraco/lib/tinymce/plugins/help/plugin.js @@ -0,0 +1,1111 @@ +(function () { +var help = (function () { + 'use strict'; + + var global = tinymce.util.Tools.resolve('tinymce.PluginManager'); + + var noop = function () { + var x = []; + for (var _i = 0; _i < arguments.length; _i++) { + x[_i] = arguments[_i]; + } + }; + var noarg = function (f) { + return function () { + var x = []; + for (var _i = 0; _i < arguments.length; _i++) { + x[_i] = arguments[_i]; + } + return f(); + }; + }; + var compose = function (fa, fb) { + return function () { + var x = []; + for (var _i = 0; _i < arguments.length; _i++) { + x[_i] = arguments[_i]; + } + return fa(fb.apply(null, arguments)); + }; + }; + var constant = function (value) { + return function () { + return value; + }; + }; + var identity = function (x) { + return x; + }; + var tripleEquals = function (a, b) { + return a === b; + }; + var curry = function (f) { + var x = []; + for (var _i = 1; _i < arguments.length; _i++) { + x[_i - 1] = arguments[_i]; + } + var args = new Array(arguments.length - 1); + for (var i = 1; i < arguments.length; i++) + args[i - 1] = arguments[i]; + return function () { + var x = []; + for (var _i = 0; _i < arguments.length; _i++) { + x[_i] = arguments[_i]; + } + var newArgs = new Array(arguments.length); + for (var j = 0; j < newArgs.length; j++) + newArgs[j] = arguments[j]; + var all = args.concat(newArgs); + return f.apply(null, all); + }; + }; + var not = function (f) { + return function () { + var x = []; + for (var _i = 0; _i < arguments.length; _i++) { + x[_i] = arguments[_i]; + } + return !f.apply(null, arguments); + }; + }; + var die = function (msg) { + return function () { + throw new Error(msg); + }; + }; + var apply = function (f) { + return f(); + }; + var call = function (f) { + f(); + }; + var never = constant(false); + var always = constant(true); + var $_7j7nicb4jh8lpulz = { + noop: noop, + noarg: noarg, + compose: compose, + constant: constant, + identity: identity, + tripleEquals: tripleEquals, + curry: curry, + not: not, + die: die, + apply: apply, + call: call, + never: never, + always: always + }; + + var never$1 = $_7j7nicb4jh8lpulz.never; + var always$1 = $_7j7nicb4jh8lpulz.always; + var none = function () { + return NONE; + }; + var NONE = function () { + var eq = function (o) { + return o.isNone(); + }; + var call = function (thunk) { + return thunk(); + }; + var id = function (n) { + return n; + }; + var noop = function () { + }; + var me = { + fold: function (n, s) { + return n(); + }, + is: never$1, + isSome: never$1, + isNone: always$1, + getOr: id, + getOrThunk: call, + getOrDie: function (msg) { + throw new Error(msg || 'error: getOrDie called on none.'); + }, + or: id, + orThunk: call, + map: none, + ap: none, + each: noop, + bind: none, + flatten: none, + exists: never$1, + forall: always$1, + filter: none, + equals: eq, + equals_: eq, + toArray: function () { + return []; + }, + toString: $_7j7nicb4jh8lpulz.constant('none()') + }; + if (Object.freeze) + Object.freeze(me); + return me; + }(); + var some = function (a) { + var constant_a = function () { + return a; + }; + var self = function () { + return me; + }; + var map = function (f) { + return some(f(a)); + }; + var bind = function (f) { + return f(a); + }; + var me = { + fold: function (n, s) { + return s(a); + }, + is: function (v) { + return a === v; + }, + isSome: always$1, + isNone: never$1, + getOr: constant_a, + getOrThunk: constant_a, + getOrDie: constant_a, + or: self, + orThunk: self, + map: map, + ap: function (optfab) { + return optfab.fold(none, function (fab) { + return some(fab(a)); + }); + }, + each: function (f) { + f(a); + }, + bind: bind, + flatten: constant_a, + exists: bind, + forall: bind, + filter: function (f) { + return f(a) ? me : NONE; + }, + equals: function (o) { + return o.is(a); + }, + equals_: function (o, elementEq) { + return o.fold(never$1, function (b) { + return elementEq(a, b); + }); + }, + toArray: function () { + return [a]; + }, + toString: function () { + return 'some(' + a + ')'; + } + }; + return me; + }; + var from = function (value) { + return value === null || value === undefined ? NONE : some(value); + }; + var Option = { + some: some, + none: none, + from: from + }; + + var typeOf = function (x) { + if (x === null) + return 'null'; + var t = typeof x; + if (t === 'object' && Array.prototype.isPrototypeOf(x)) + return 'array'; + if (t === 'object' && String.prototype.isPrototypeOf(x)) + return 'string'; + return t; + }; + var isType = function (type) { + return function (value) { + return typeOf(value) === type; + }; + }; + var $_7r9fwpb5jh8lpum2 = { + isString: isType('string'), + isObject: isType('object'), + isArray: isType('array'), + isNull: isType('null'), + isBoolean: isType('boolean'), + isUndefined: isType('undefined'), + isFunction: isType('function'), + isNumber: isType('number') + }; + + var rawIndexOf = function () { + var pIndexOf = Array.prototype.indexOf; + var fastIndex = function (xs, x) { + return pIndexOf.call(xs, x); + }; + var slowIndex = function (xs, x) { + return slowIndexOf(xs, x); + }; + return pIndexOf === undefined ? slowIndex : fastIndex; + }(); + var indexOf = function (xs, x) { + var r = rawIndexOf(xs, x); + return r === -1 ? Option.none() : Option.some(r); + }; + var contains = function (xs, x) { + return rawIndexOf(xs, x) > -1; + }; + var exists = function (xs, pred) { + return findIndex(xs, pred).isSome(); + }; + var range = function (num, f) { + var r = []; + for (var i = 0; i < num; i++) { + r.push(f(i)); + } + return r; + }; + var chunk = function (array, size) { + var r = []; + for (var i = 0; i < array.length; i += size) { + var s = array.slice(i, i + size); + r.push(s); + } + return r; + }; + var map = function (xs, f) { + var len = xs.length; + var r = new Array(len); + for (var i = 0; i < len; i++) { + var x = xs[i]; + r[i] = f(x, i, xs); + } + return r; + }; + var each = function (xs, f) { + for (var i = 0, len = xs.length; i < len; i++) { + var x = xs[i]; + f(x, i, xs); + } + }; + var eachr = function (xs, f) { + for (var i = xs.length - 1; i >= 0; i--) { + var x = xs[i]; + f(x, i, xs); + } + }; + var partition = function (xs, pred) { + var pass = []; + var fail = []; + for (var i = 0, len = xs.length; i < len; i++) { + var x = xs[i]; + var arr = pred(x, i, xs) ? pass : fail; + arr.push(x); + } + return { + pass: pass, + fail: fail + }; + }; + var filter = function (xs, pred) { + var r = []; + for (var i = 0, len = xs.length; i < len; i++) { + var x = xs[i]; + if (pred(x, i, xs)) { + r.push(x); + } + } + return r; + }; + var groupBy = function (xs, f) { + if (xs.length === 0) { + return []; + } else { + var wasType = f(xs[0]); + var r = []; + var group = []; + for (var i = 0, len = xs.length; i < len; i++) { + var x = xs[i]; + var type = f(x); + if (type !== wasType) { + r.push(group); + group = []; + } + wasType = type; + group.push(x); + } + if (group.length !== 0) { + r.push(group); + } + return r; + } + }; + var foldr = function (xs, f, acc) { + eachr(xs, function (x) { + acc = f(acc, x); + }); + return acc; + }; + var foldl = function (xs, f, acc) { + each(xs, function (x) { + acc = f(acc, x); + }); + return acc; + }; + var find = function (xs, pred) { + for (var i = 0, len = xs.length; i < len; i++) { + var x = xs[i]; + if (pred(x, i, xs)) { + return Option.some(x); + } + } + return Option.none(); + }; + var findIndex = function (xs, pred) { + for (var i = 0, len = xs.length; i < len; i++) { + var x = xs[i]; + if (pred(x, i, xs)) { + return Option.some(i); + } + } + return Option.none(); + }; + var slowIndexOf = function (xs, x) { + for (var i = 0, len = xs.length; i < len; ++i) { + if (xs[i] === x) { + return i; + } + } + return -1; + }; + var push = Array.prototype.push; + var flatten = function (xs) { + var r = []; + for (var i = 0, len = xs.length; i < len; ++i) { + if (!Array.prototype.isPrototypeOf(xs[i])) + throw new Error('Arr.flatten item ' + i + ' was not an array, input: ' + xs); + push.apply(r, xs[i]); + } + return r; + }; + var bind = function (xs, f) { + var output = map(xs, f); + return flatten(output); + }; + var forall = function (xs, pred) { + for (var i = 0, len = xs.length; i < len; ++i) { + var x = xs[i]; + if (pred(x, i, xs) !== true) { + return false; + } + } + return true; + }; + var equal = function (a1, a2) { + return a1.length === a2.length && forall(a1, function (x, i) { + return x === a2[i]; + }); + }; + var slice = Array.prototype.slice; + var reverse = function (xs) { + var r = slice.call(xs, 0); + r.reverse(); + return r; + }; + var difference = function (a1, a2) { + return filter(a1, function (x) { + return !contains(a2, x); + }); + }; + var mapToObject = function (xs, f) { + var r = {}; + for (var i = 0, len = xs.length; i < len; i++) { + var x = xs[i]; + r[String(x)] = f(x, i); + } + return r; + }; + var pure = function (x) { + return [x]; + }; + var sort = function (xs, comparator) { + var copy = slice.call(xs, 0); + copy.sort(comparator); + return copy; + }; + var head = function (xs) { + return xs.length === 0 ? Option.none() : Option.some(xs[0]); + }; + var last = function (xs) { + return xs.length === 0 ? Option.none() : Option.some(xs[xs.length - 1]); + }; + var from$1 = $_7r9fwpb5jh8lpum2.isFunction(Array.from) ? Array.from : function (x) { + return slice.call(x); + }; + var $_cfartob2jh8lpulr = { + map: map, + each: each, + eachr: eachr, + partition: partition, + filter: filter, + groupBy: groupBy, + indexOf: indexOf, + foldr: foldr, + foldl: foldl, + find: find, + findIndex: findIndex, + flatten: flatten, + bind: bind, + forall: forall, + exists: exists, + contains: contains, + equal: equal, + reverse: reverse, + chunk: chunk, + difference: difference, + mapToObject: mapToObject, + pure: pure, + sort: sort, + range: range, + head: head, + last: last, + from: from$1 + }; + + var global$1 = tinymce.util.Tools.resolve('tinymce.util.I18n'); + + var global$2 = tinymce.util.Tools.resolve('tinymce.Env'); + + var meta = global$2.mac ? '\u2318' : 'Ctrl'; + var access = global$2.mac ? 'Ctrl + Alt' : 'Shift + Alt'; + var shortcuts = [ + { + shortcut: meta + ' + B', + action: 'Bold' + }, + { + shortcut: meta + ' + I', + action: 'Italic' + }, + { + shortcut: meta + ' + U', + action: 'Underline' + }, + { + shortcut: meta + ' + A', + action: 'Select all' + }, + { + shortcut: meta + ' + Y or ' + meta + ' + Shift + Z', + action: 'Redo' + }, + { + shortcut: meta + ' + Z', + action: 'Undo' + }, + { + shortcut: access + ' + 1', + action: 'Header 1' + }, + { + shortcut: access + ' + 2', + action: 'Header 2' + }, + { + shortcut: access + ' + 3', + action: 'Header 3' + }, + { + shortcut: access + ' + 4', + action: 'Header 4' + }, + { + shortcut: access + ' + 5', + action: 'Header 5' + }, + { + shortcut: access + ' + 6', + action: 'Header 6' + }, + { + shortcut: access + ' + 7', + action: 'Paragraph' + }, + { + shortcut: access + ' + 8', + action: 'Div' + }, + { + shortcut: access + ' + 9', + action: 'Address' + }, + { + shortcut: 'Alt + F9', + action: 'Focus to menubar' + }, + { + shortcut: 'Alt + F10', + action: 'Focus to toolbar' + }, + { + shortcut: 'Alt + F11', + action: 'Focus to element path' + }, + { + shortcut: 'Ctrl + Shift + P > Ctrl + Shift + P', + action: 'Focus to contextual toolbar' + }, + { + shortcut: meta + ' + K', + action: 'Insert link (if link plugin activated)' + }, + { + shortcut: meta + ' + S', + action: 'Save (if save plugin activated)' + }, + { + shortcut: meta + ' + F', + action: 'Find (if searchreplace plugin activated)' + } + ]; + var $_arffvhb7jh8lpum6 = { shortcuts: shortcuts }; + + var makeTab = function () { + var makeAriaLabel = function (shortcut) { + return 'aria-label="Action: ' + shortcut.action + ', Shortcut: ' + shortcut.shortcut.replace(/Ctrl/g, 'Control') + '"'; + }; + var shortcutLisString = $_cfartob2jh8lpulr.map($_arffvhb7jh8lpum6.shortcuts, function (shortcut) { + return '' + '' + global$1.translate(shortcut.action) + '' + '' + shortcut.shortcut + '' + ''; + }).join(''); + return { + title: 'Handy Shortcuts', + type: 'container', + style: 'overflow-y: auto; overflow-x: hidden; max-height: 250px', + items: [{ + type: 'container', + html: '
    ' + '' + '' + '' + '' + '' + shortcutLisString + '
    ' + global$1.translate('Action') + '' + global$1.translate('Shortcut') + '
    ' + '
    ' + }] + }; + }; + var $_100z0pb1jh8lpulk = { makeTab: makeTab }; + + var keys = function () { + var fastKeys = Object.keys; + var slowKeys = function (o) { + var r = []; + for (var i in o) { + if (o.hasOwnProperty(i)) { + r.push(i); + } + } + return r; + }; + return fastKeys === undefined ? slowKeys : fastKeys; + }(); + var each$1 = function (obj, f) { + var props = keys(obj); + for (var k = 0, len = props.length; k < len; k++) { + var i = props[k]; + var x = obj[i]; + f(x, i, obj); + } + }; + var objectMap = function (obj, f) { + return tupleMap(obj, function (x, i, obj) { + return { + k: i, + v: f(x, i, obj) + }; + }); + }; + var tupleMap = function (obj, f) { + var r = {}; + each$1(obj, function (x, i) { + var tuple = f(x, i, obj); + r[tuple.k] = tuple.v; + }); + return r; + }; + var bifilter = function (obj, pred) { + var t = {}; + var f = {}; + each$1(obj, function (x, i) { + var branch = pred(x, i) ? t : f; + branch[i] = x; + }); + return { + t: t, + f: f + }; + }; + var mapToArray = function (obj, f) { + var r = []; + each$1(obj, function (value, name) { + r.push(f(value, name)); + }); + return r; + }; + var find$1 = function (obj, pred) { + var props = keys(obj); + for (var k = 0, len = props.length; k < len; k++) { + var i = props[k]; + var x = obj[i]; + if (pred(x, i, obj)) { + return Option.some(x); + } + } + return Option.none(); + }; + var values = function (obj) { + return mapToArray(obj, function (v) { + return v; + }); + }; + var size = function (obj) { + return values(obj).length; + }; + var $_2ql8qybajh8lpumh = { + bifilter: bifilter, + each: each$1, + map: objectMap, + mapToArray: mapToArray, + tupleMap: tupleMap, + find: find$1, + keys: keys, + values: values, + size: size + }; + + var addToStart = function (str, prefix) { + return prefix + str; + }; + var addToEnd = function (str, suffix) { + return str + suffix; + }; + var removeFromStart = function (str, numChars) { + return str.substring(numChars); + }; + var removeFromEnd = function (str, numChars) { + return str.substring(0, str.length - numChars); + }; + var $_egnyhkbcjh8lpumq = { + addToStart: addToStart, + addToEnd: addToEnd, + removeFromStart: removeFromStart, + removeFromEnd: removeFromEnd + }; + + var first = function (str, count) { + return str.substr(0, count); + }; + var last$1 = function (str, count) { + return str.substr(str.length - count, str.length); + }; + var head$1 = function (str) { + return str === '' ? Option.none() : Option.some(str.substr(0, 1)); + }; + var tail = function (str) { + return str === '' ? Option.none() : Option.some(str.substring(1)); + }; + var $_en8nfdbdjh8lpums = { + first: first, + last: last$1, + head: head$1, + tail: tail + }; + + var checkRange = function (str, substr, start) { + if (substr === '') + return true; + if (str.length < substr.length) + return false; + var x = str.substr(start, start + substr.length); + return x === substr; + }; + var supplant = function (str, obj) { + var isStringOrNumber = function (a) { + var t = typeof a; + return t === 'string' || t === 'number'; + }; + return str.replace(/\${([^{}]*)}/g, function (a, b) { + var value = obj[b]; + return isStringOrNumber(value) ? value : a; + }); + }; + var removeLeading = function (str, prefix) { + return startsWith(str, prefix) ? $_egnyhkbcjh8lpumq.removeFromStart(str, prefix.length) : str; + }; + var removeTrailing = function (str, prefix) { + return endsWith(str, prefix) ? $_egnyhkbcjh8lpumq.removeFromEnd(str, prefix.length) : str; + }; + var ensureLeading = function (str, prefix) { + return startsWith(str, prefix) ? str : $_egnyhkbcjh8lpumq.addToStart(str, prefix); + }; + var ensureTrailing = function (str, prefix) { + return endsWith(str, prefix) ? str : $_egnyhkbcjh8lpumq.addToEnd(str, prefix); + }; + var contains$1 = function (str, substr) { + return str.indexOf(substr) !== -1; + }; + var capitalize = function (str) { + return $_en8nfdbdjh8lpums.head(str).bind(function (head) { + return $_en8nfdbdjh8lpums.tail(str).map(function (tail) { + return head.toUpperCase() + tail; + }); + }).getOr(str); + }; + var startsWith = function (str, prefix) { + return checkRange(str, prefix, 0); + }; + var endsWith = function (str, suffix) { + return checkRange(str, suffix, str.length - suffix.length); + }; + var trim = function (str) { + return str.replace(/^\s+|\s+$/g, ''); + }; + var lTrim = function (str) { + return str.replace(/^\s+/g, ''); + }; + var rTrim = function (str) { + return str.replace(/\s+$/g, ''); + }; + var $_a6l1r3bbjh8lpumm = { + supplant: supplant, + startsWith: startsWith, + removeLeading: removeLeading, + removeTrailing: removeTrailing, + ensureLeading: ensureLeading, + ensureTrailing: ensureTrailing, + endsWith: endsWith, + contains: contains$1, + trim: trim, + lTrim: lTrim, + rTrim: rTrim, + capitalize: capitalize + }; + + var urls = [ + { + key: 'advlist', + name: 'Advanced List' + }, + { + key: 'anchor', + name: 'Anchor' + }, + { + key: 'autolink', + name: 'Autolink' + }, + { + key: 'autoresize', + name: 'Autoresize' + }, + { + key: 'autosave', + name: 'Autosave' + }, + { + key: 'bbcode', + name: 'BBCode' + }, + { + key: 'charmap', + name: 'Character Map' + }, + { + key: 'code', + name: 'Code' + }, + { + key: 'codesample', + name: 'Code Sample' + }, + { + key: 'colorpicker', + name: 'Color Picker' + }, + { + key: 'compat3x', + name: '3.x Compatibility' + }, + { + key: 'contextmenu', + name: 'Context Menu' + }, + { + key: 'directionality', + name: 'Directionality' + }, + { + key: 'emoticons', + name: 'Emoticons' + }, + { + key: 'fullpage', + name: 'Full Page' + }, + { + key: 'fullscreen', + name: 'Full Screen' + }, + { + key: 'help', + name: 'Help' + }, + { + key: 'hr', + name: 'Horizontal Rule' + }, + { + key: 'image', + name: 'Image' + }, + { + key: 'imagetools', + name: 'Image Tools' + }, + { + key: 'importcss', + name: 'Import CSS' + }, + { + key: 'insertdatetime', + name: 'Insert Date/Time' + }, + { + key: 'legacyoutput', + name: 'Legacy Output' + }, + { + key: 'link', + name: 'Link' + }, + { + key: 'lists', + name: 'Lists' + }, + { + key: 'media', + name: 'Media' + }, + { + key: 'nonbreaking', + name: 'Nonbreaking' + }, + { + key: 'noneditable', + name: 'Noneditable' + }, + { + key: 'pagebreak', + name: 'Page Break' + }, + { + key: 'paste', + name: 'Paste' + }, + { + key: 'preview', + name: 'Preview' + }, + { + key: 'print', + name: 'Print' + }, + { + key: 'save', + name: 'Save' + }, + { + key: 'searchreplace', + name: 'Search and Replace' + }, + { + key: 'spellchecker', + name: 'Spell Checker' + }, + { + key: 'tabfocus', + name: 'Tab Focus' + }, + { + key: 'table', + name: 'Table' + }, + { + key: 'template', + name: 'Template' + }, + { + key: 'textcolor', + name: 'Text Color' + }, + { + key: 'textpattern', + name: 'Text Pattern' + }, + { + key: 'toc', + name: 'Table of Contents' + }, + { + key: 'visualblocks', + name: 'Visual Blocks' + }, + { + key: 'visualchars', + name: 'Visual Characters' + }, + { + key: 'wordcount', + name: 'Word Count' + } + ]; + var $_dr9dsabejh8lpumt = { urls: urls }; + + var makeLink = $_7j7nicb4jh8lpulz.curry($_a6l1r3bbjh8lpumm.supplant, '${name}'); + var maybeUrlize = function (editor, key) { + return $_cfartob2jh8lpulr.find($_dr9dsabejh8lpumt.urls, function (x) { + return x.key === key; + }).fold(function () { + var getMetadata = editor.plugins[key].getMetadata; + return typeof getMetadata === 'function' ? makeLink(getMetadata()) : key; + }, function (x) { + return makeLink({ + name: x.name, + url: 'https://www.tinymce.com/docs/plugins/' + x.key + }); + }); + }; + var getPluginKeys = function (editor) { + var keys = $_2ql8qybajh8lpumh.keys(editor.plugins); + return editor.settings.forced_plugins === undefined ? keys : $_cfartob2jh8lpulr.filter(keys, $_7j7nicb4jh8lpulz.not($_7j7nicb4jh8lpulz.curry($_cfartob2jh8lpulr.contains, editor.settings.forced_plugins))); + }; + var pluginLister = function (editor) { + var pluginKeys = getPluginKeys(editor); + var pluginLis = $_cfartob2jh8lpulr.map(pluginKeys, function (key) { + return '
  • ' + maybeUrlize(editor, key) + '
  • '; + }); + var count = pluginLis.length; + var pluginsString = pluginLis.join(''); + return '

    ' + global$1.translate([ + 'Plugins installed ({0}):', + count + ]) + '

    ' + '
      ' + pluginsString + '
    '; + }; + var installedPlugins = function (editor) { + return { + type: 'container', + html: '
    ' + pluginLister(editor) + '
    ', + flex: 1 + }; + }; + var availablePlugins = function () { + return { + type: 'container', + html: '
    ' + '

    ' + global$1.translate('Premium plugins:') + '

    ' + '
      ' + '
    • PowerPaste
    • ' + '
    • Spell Checker Pro
    • ' + '
    • Accessibility Checker
    • ' + '
    • Advanced Code Editor
    • ' + '
    • Enhanced Media Embed
    • ' + '
    • Link Checker
    • ' + '

    ' + '

    ' + global$1.translate('Learn more...') + '

    ' + '
    ', + flex: 1 + }; + }; + var makeTab$1 = function (editor) { + return { + title: 'Plugins', + type: 'container', + style: 'overflow-y: auto; overflow-x: hidden;', + layout: 'flex', + padding: 10, + spacing: 10, + items: [ + installedPlugins(editor), + availablePlugins() + ] + }; + }; + var $_ek4okeb9jh8lpum8 = { makeTab: makeTab$1 }; + + var global$3 = tinymce.util.Tools.resolve('tinymce.EditorManager'); + + var getVersion = function (major, minor) { + return major.indexOf('@') === 0 ? 'X.X.X' : major + '.' + minor; + }; + var makeRow = function () { + var version = getVersion(global$3.majorVersion, global$3.minorVersion); + var changeLogLink = 'TinyMCE ' + version + ''; + return [ + { + type: 'label', + html: global$1.translate([ + 'You are using {0}', + changeLogLink + ]) + }, + { + type: 'spacer', + flex: 1 + }, + { + text: 'Close', + onclick: function () { + this.parent().parent().close(); + } + } + ]; + }; + var $_eh04q5bfjh8lpumv = { makeRow: makeRow }; + + var open = function (editor, pluginUrl) { + return function () { + editor.windowManager.open({ + title: 'Help', + bodyType: 'tabpanel', + layout: 'flex', + body: [ + $_100z0pb1jh8lpulk.makeTab(), + $_ek4okeb9jh8lpum8.makeTab(editor) + ], + buttons: $_eh04q5bfjh8lpumv.makeRow(), + onPostRender: function () { + var title = this.getEl('title'); + title.innerHTML = 'TinyMCE Logo'; + } + }); + }; + }; + var $_bqxgr4b0jh8lpulj = { open: open }; + + var register = function (editor, pluginUrl) { + editor.addCommand('mceHelp', $_bqxgr4b0jh8lpulj.open(editor, pluginUrl)); + }; + var $_7dw64wazjh8lpuli = { register: register }; + + var register$1 = function (editor, pluginUrl) { + editor.addButton('help', { + icon: 'help', + onclick: $_bqxgr4b0jh8lpulj.open(editor, pluginUrl) + }); + editor.addMenuItem('help', { + text: 'Help', + icon: 'help', + context: 'help', + onclick: $_bqxgr4b0jh8lpulj.open(editor, pluginUrl) + }); + }; + var $_f8wi76bhjh8lpumw = { register: register$1 }; + + global.add('help', function (editor, pluginUrl) { + $_f8wi76bhjh8lpumw.register(editor, pluginUrl); + $_7dw64wazjh8lpuli.register(editor, pluginUrl); + editor.shortcuts.add('Alt+0', 'Open help dialog', 'mceHelp'); + }); + function Plugin () { + } + + return Plugin; + +}()); +})(); diff --git a/WebCms/Umbraco/lib/tinymce/plugins/help/plugin.min.js b/WebCms/Umbraco/lib/tinymce/plugins/help/plugin.min.js new file mode 100644 index 0000000..95c071b --- /dev/null +++ b/WebCms/Umbraco/lib/tinymce/plugins/help/plugin.min.js @@ -0,0 +1 @@ +!function(){"use strict";var e,t,n,r,o,a,i=tinymce.util.Tools.resolve("tinymce.PluginManager"),u=function(e){return function(){return e}},c=u(!1),l=u(!0),s={noop:function(){for(var e=[],t=0;t Ctrl + Shift + P",action:"Focus to contextual toolbar"},{shortcut:F+" + K",action:"Insert link (if link plugin activated)"},{shortcut:F+" + S",action:"Save (if save plugin activated)"},{shortcut:F+" + F",action:"Find (if searchreplace plugin activated)"}]},E=function(){var e=C(M.shortcuts,function(e){return''+O.translate(e.action)+""+e.shortcut+"";var t}).join("");return{title:"Handy Shortcuts",type:"container",style:"overflow-y: auto; overflow-x: hidden; max-height: 250px",items:[{type:"container",html:'
    "+e+"
    '+O.translate("Action")+""+O.translate("Shortcut")+"
    "}]}},j=(a=Object.keys)===undefined?function(e){var t=[];for(var n in e)e.hasOwnProperty(n)&&t.push(n);return t}:a,I=function(e,t){for(var n=j(e),r=0,o=n.length;r${name}'),R=function(r){var e,t,n=(e=r,t=z.keys(e.plugins),e.settings.forced_plugins===undefined?t:S(t,s.not(s.curry(T,e.settings.forced_plugins)))),o=C(n,function(e){return"
  • "+(t=r,n=e,P(D,function(e){return e.key===n}).fold(function(){var e=t.plugins[n].getMetadata;return"function"==typeof e?q(e()):n},function(e){return q({name:e.name,url:"https://www.tinymce.com/docs/plugins/"+e.key})}))+"
  • ";var t,n}),a=o.length,i=o.join("");return"

    "+O.translate(["Plugins installed ({0}):",a])+"

      "+i+"
    "},U=function(e){return{title:"Plugins",type:"container",style:"overflow-y: auto; overflow-x: hidden;",layout:"flex",padding:10,spacing:10,items:[(t=e,{type:"container",html:'
    '+R(t)+"
    ",flex:1}),{type:"container",html:'

    '+O.translate("Premium plugins:")+'

    • PowerPaste
    • Spell Checker Pro
    • Accessibility Checker
    • Advanced Code Editor
    • Enhanced Media Embed
    • Link Checker

    '+O.translate("Learn more...")+"

    ",flex:1}]};var t},V=tinymce.util.Tools.resolve("tinymce.EditorManager"),X=function(){var e,t,n='TinyMCE '+(e=V.majorVersion,t=V.minorVersion,0===e.indexOf("@")?"X.X.X":e+"."+t)+"";return[{type:"label",html:O.translate(["You are using {0}",n])},{type:"spacer",flex:1},{text:"Close",onclick:function(){this.parent().parent().close()}}]},$=function(e,t){return function(){e.windowManager.open({title:"Help",bodyType:"tabpanel",layout:"flex",body:[E(),U(e)],buttons:X(),onPostRender:function(){this.getEl("title").innerHTML='TinyMCE Logo'}})}},Y=function(e,t){e.addCommand("mceHelp",$(e,t))},Z=function(e,t){e.addButton("help",{icon:"help",onclick:$(e,t)}),e.addMenuItem("help",{text:"Help",icon:"help",context:"help",onclick:$(e,t)})};i.add("help",function(e,t){Z(e,t),Y(e,t),e.shortcuts.add("Alt+0","Open help dialog","mceHelp")})}(); \ No newline at end of file diff --git a/WebCms/Umbraco/lib/tinymce/plugins/hr/index.js b/WebCms/Umbraco/lib/tinymce/plugins/hr/index.js new file mode 100644 index 0000000..e4c56e5 --- /dev/null +++ b/WebCms/Umbraco/lib/tinymce/plugins/hr/index.js @@ -0,0 +1,7 @@ +// Exports the "hr" plugin for usage with module loaders +// Usage: +// CommonJS: +// require('tinymce/plugins/hr') +// ES2015: +// import 'tinymce/plugins/hr' +require('./plugin.js'); \ No newline at end of file diff --git a/WebCms/Umbraco/lib/tinymce/plugins/hr/plugin.js b/WebCms/Umbraco/lib/tinymce/plugins/hr/plugin.js index 915a563..1a4a1dc 100644 --- a/WebCms/Umbraco/lib/tinymce/plugins/hr/plugin.js +++ b/WebCms/Umbraco/lib/tinymce/plugins/hr/plugin.js @@ -1,30 +1,39 @@ -/** - * plugin.js - * - * Copyright, Moxiecode Systems AB - * Released under LGPL License. - * - * License: http://www.tinymce.com/license - * Contributing: http://www.tinymce.com/contributing - */ +(function () { +var hr = (function () { + 'use strict'; -/*global tinymce:true */ + var global = tinymce.util.Tools.resolve('tinymce.PluginManager'); -tinymce.PluginManager.add('hr', function(editor) { - editor.addCommand('InsertHorizontalRule', function() { - editor.execCommand('mceInsertContent', false, '
    '); - }); + var register = function (editor) { + editor.addCommand('InsertHorizontalRule', function () { + editor.execCommand('mceInsertContent', false, '
    '); + }); + }; + var $_fvts64c7jh8lpuqd = { register: register }; - editor.addButton('hr', { - icon: 'hr', - tooltip: 'Horizontal line', - cmd: 'InsertHorizontalRule' - }); + var register$1 = function (editor) { + editor.addButton('hr', { + icon: 'hr', + tooltip: 'Horizontal line', + cmd: 'InsertHorizontalRule' + }); + editor.addMenuItem('hr', { + icon: 'hr', + text: 'Horizontal line', + cmd: 'InsertHorizontalRule', + context: 'insert' + }); + }; + var $_fzoakwc8jh8lpuqe = { register: register$1 }; - editor.addMenuItem('hr', { - icon: 'hr', - text: 'Horizontal line', - cmd: 'InsertHorizontalRule', - context: 'insert' - }); -}); + global.add('hr', function (editor) { + $_fvts64c7jh8lpuqd.register(editor); + $_fzoakwc8jh8lpuqe.register(editor); + }); + function Plugin () { + } + + return Plugin; + +}()); +})(); diff --git a/WebCms/Umbraco/lib/tinymce/plugins/hr/plugin.min.js b/WebCms/Umbraco/lib/tinymce/plugins/hr/plugin.min.js index 25abb0c..72bc2ca 100644 --- a/WebCms/Umbraco/lib/tinymce/plugins/hr/plugin.min.js +++ b/WebCms/Umbraco/lib/tinymce/plugins/hr/plugin.min.js @@ -1 +1 @@ -tinymce.PluginManager.add("hr",function(a){a.addCommand("InsertHorizontalRule",function(){a.execCommand("mceInsertContent",!1,"
    ")}),a.addButton("hr",{icon:"hr",tooltip:"Horizontal line",cmd:"InsertHorizontalRule"}),a.addMenuItem("hr",{icon:"hr",text:"Horizontal line",cmd:"InsertHorizontalRule",context:"insert"})}); \ No newline at end of file +!function(){"use strict";var n=tinymce.util.Tools.resolve("tinymce.PluginManager"),t=function(n){n.addCommand("InsertHorizontalRule",function(){n.execCommand("mceInsertContent",!1,"
    ")})},o=function(n){n.addButton("hr",{icon:"hr",tooltip:"Horizontal line",cmd:"InsertHorizontalRule"}),n.addMenuItem("hr",{icon:"hr",text:"Horizontal line",cmd:"InsertHorizontalRule",context:"insert"})};n.add("hr",function(n){t(n),o(n)})}(); \ No newline at end of file diff --git a/WebCms/Umbraco/lib/tinymce/plugins/image/index.js b/WebCms/Umbraco/lib/tinymce/plugins/image/index.js new file mode 100644 index 0000000..092c73a --- /dev/null +++ b/WebCms/Umbraco/lib/tinymce/plugins/image/index.js @@ -0,0 +1,7 @@ +// Exports the "image" plugin for usage with module loaders +// Usage: +// CommonJS: +// require('tinymce/plugins/image') +// ES2015: +// import 'tinymce/plugins/image' +require('./plugin.js'); \ No newline at end of file diff --git a/WebCms/Umbraco/lib/tinymce/plugins/image/plugin.js b/WebCms/Umbraco/lib/tinymce/plugins/image/plugin.js index d82e0ec..ae9772b 100644 --- a/WebCms/Umbraco/lib/tinymce/plugins/image/plugin.js +++ b/WebCms/Umbraco/lib/tinymce/plugins/image/plugin.js @@ -1,548 +1,1337 @@ -/** - * plugin.js - * - * Copyright, Moxiecode Systems AB - * Released under LGPL License. - * - * License: http://www.tinymce.com/license - * Contributing: http://www.tinymce.com/contributing - */ - -/*global tinymce:true */ - -tinymce.PluginManager.add('image', function(editor) { - function getImageSize(url, callback) { - var img = document.createElement('img'); - - function done(width, height) { - if (img.parentNode) { - img.parentNode.removeChild(img); - } - - callback({width: width, height: height}); - } - - img.onload = function() { - done(img.clientWidth, img.clientHeight); - }; - - img.onerror = function() { - done(); - }; - - var style = img.style; - style.visibility = 'hidden'; - style.position = 'fixed'; - style.bottom = style.left = 0; - style.width = style.height = 'auto'; - - document.body.appendChild(img); - img.src = url; - } - - function buildListItems(inputList, itemCallback, startItems) { - function appendItems(values, output) { - output = output || []; - - tinymce.each(values, function(item) { - var menuItem = {text: item.text || item.title}; - - if (item.menu) { - menuItem.menu = appendItems(item.menu); - } else { - menuItem.value = item.value; - itemCallback(menuItem); - } - - output.push(menuItem); - }); - - return output; - } - - return appendItems(inputList, startItems || []); - } - - function createImageList(callback) { - return function() { - var imageList = editor.settings.image_list; - - if (typeof imageList == "string") { - tinymce.util.XHR.send({ - url: imageList, - success: function(text) { - callback(tinymce.util.JSON.parse(text)); - } - }); - } else if (typeof imageList == "function") { - imageList(callback); - } else { - callback(imageList); - } - }; - } - - function showDialog(imageList) { - var win, data = {}, dom = editor.dom, imgElm = editor.selection.getNode(); - var width, height, imageListCtrl, classListCtrl, imageDimensions = editor.settings.image_dimensions !== false; - - function recalcSize() { - var widthCtrl, heightCtrl, newWidth, newHeight; - - widthCtrl = win.find('#width')[0]; - heightCtrl = win.find('#height')[0]; - - if (!widthCtrl || !heightCtrl) { - return; - } - - newWidth = widthCtrl.value(); - newHeight = heightCtrl.value(); - - if (win.find('#constrain')[0].checked() && width && height && newWidth && newHeight) { - if (width != newWidth) { - newHeight = Math.round((newWidth / width) * newHeight); - - if (!isNaN(newHeight)) { - heightCtrl.value(newHeight); - } - } else { - newWidth = Math.round((newHeight / height) * newWidth); - - if (!isNaN(newWidth)) { - widthCtrl.value(newWidth); - } - } - } - - width = newWidth; - height = newHeight; - } - - function onSubmitForm() { - function waitLoad(imgElm) { - function selectImage() { - imgElm.onload = imgElm.onerror = null; - - if (editor.selection) { - editor.selection.select(imgElm); - editor.nodeChanged(); - } - } - - imgElm.onload = function() { - if (!data.width && !data.height && imageDimensions) { - dom.setAttribs(imgElm, { - width: imgElm.clientWidth, - height: imgElm.clientHeight - }); - } - - selectImage(); - }; - - imgElm.onerror = selectImage; - } - - updateStyle(); - recalcSize(); - - data = tinymce.extend(data, win.toJSON()); - - if (!data.alt) { - data.alt = ''; - } - - if (!data.title) { - data.title = ''; - } - - if (data.width === '') { - data.width = null; - } - - if (data.height === '') { - data.height = null; - } - - if (!data.style) { - data.style = null; - } - - // Setup new data excluding style properties - /*eslint dot-notation: 0*/ - data = { - src: data.src, - alt: data.alt, - title: data.title, - width: data.width, - height: data.height, - style: data.style, - "class": data["class"] - }; - - editor.undoManager.transact(function() { - if (!data.src) { - if (imgElm) { - dom.remove(imgElm); - editor.focus(); - editor.nodeChanged(); - } - - return; - } - - if (data.title === "") { - data.title = null; - } - - if (!imgElm) { - data.id = '__mcenew'; - editor.focus(); - editor.selection.setContent(dom.createHTML('img', data)); - imgElm = dom.get('__mcenew'); - dom.setAttrib(imgElm, 'id', null); - } else { - dom.setAttribs(imgElm, data); - } - - waitLoad(imgElm); - }); - } - - function removePixelSuffix(value) { - if (value) { - value = value.replace(/px$/, ''); - } - - return value; - } - - function srcChange(e) { - var srcURL, prependURL, absoluteURLPattern, meta = e.meta || {}; - - if (imageListCtrl) { - imageListCtrl.value(editor.convertURL(this.value(), 'src')); - } - - tinymce.each(meta, function(value, key) { - win.find('#' + key).value(value); - }); - - if (!meta.width && !meta.height) { - srcURL = editor.convertURL(this.value(), 'src'); - - // Pattern test the src url and make sure we haven't already prepended the url - prependURL = editor.settings.image_prepend_url; - absoluteURLPattern = new RegExp('^(?:[a-z]+:)?//', 'i'); - if (prependURL && !absoluteURLPattern.test(srcURL) && srcURL.substring(0, prependURL.length) !== prependURL) { - srcURL = prependURL + srcURL; - } - - this.value(srcURL); - - getImageSize(editor.documentBaseURI.toAbsolute(this.value()), function(data) { - if (data.width && data.height && imageDimensions) { - width = data.width; - height = data.height; - - win.find('#width').value(width); - win.find('#height').value(height); - } - }); - } - } - - width = dom.getAttrib(imgElm, 'width'); - height = dom.getAttrib(imgElm, 'height'); - - if (imgElm.nodeName == 'IMG' && !imgElm.getAttribute('data-mce-object') && !imgElm.getAttribute('data-mce-placeholder')) { - data = { - src: dom.getAttrib(imgElm, 'src'), - alt: dom.getAttrib(imgElm, 'alt'), - title: dom.getAttrib(imgElm, 'title'), - "class": dom.getAttrib(imgElm, 'class'), - width: width, - height: height - }; - } else { - imgElm = null; - } - - if (imageList) { - imageListCtrl = { - type: 'listbox', - label: 'Image list', - values: buildListItems( - imageList, - function(item) { - item.value = editor.convertURL(item.value || item.url, 'src'); - }, - [{text: 'None', value: ''}] - ), - value: data.src && editor.convertURL(data.src, 'src'), - onselect: function(e) { - var altCtrl = win.find('#alt'); - - if (!altCtrl.value() || (e.lastControl && altCtrl.value() == e.lastControl.text())) { - altCtrl.value(e.control.text()); - } - - win.find('#src').value(e.control.value()).fire('change'); - }, - onPostRender: function() { - imageListCtrl = this; - } - }; - } - - if (editor.settings.image_class_list) { - classListCtrl = { - name: 'class', - type: 'listbox', - label: 'Class', - values: buildListItems( - editor.settings.image_class_list, - function(item) { - if (item.value) { - item.textStyle = function() { - return editor.formatter.getCssText({inline: 'img', classes: [item.value]}); - }; - } - } - ) - }; - } - - // General settings shared between simple and advanced dialogs - var generalFormItems = [ - { - name: 'src', - type: 'filepicker', - filetype: 'image', - label: 'Source', - autofocus: true, - onchange: srcChange - }, - imageListCtrl - ]; - - if (editor.settings.image_description !== false) { - generalFormItems.push({name: 'alt', type: 'textbox', label: 'Image description'}); - } - - if (editor.settings.image_title) { - generalFormItems.push({name: 'title', type: 'textbox', label: 'Image Title'}); - } - - if (imageDimensions) { - generalFormItems.push({ - type: 'container', - label: 'Dimensions', - layout: 'flex', - direction: 'row', - align: 'center', - spacing: 5, - items: [ - {name: 'width', type: 'textbox', maxLength: 5, size: 3, onchange: recalcSize, ariaLabel: 'Width'}, - {type: 'label', text: 'x'}, - {name: 'height', type: 'textbox', maxLength: 5, size: 3, onchange: recalcSize, ariaLabel: 'Height'}, - {name: 'constrain', type: 'checkbox', checked: true, text: 'Constrain proportions'} - ] - }); - } - - generalFormItems.push(classListCtrl); - - function mergeMargins(css) { - if (css.margin) { - - var splitMargin = css.margin.split(" "); - - switch (splitMargin.length) { - case 1: //margin: toprightbottomleft; - css['margin-top'] = css['margin-top'] || splitMargin[0]; - css['margin-right'] = css['margin-right'] || splitMargin[0]; - css['margin-bottom'] = css['margin-bottom'] || splitMargin[0]; - css['margin-left'] = css['margin-left'] || splitMargin[0]; - break; - case 2: //margin: topbottom rightleft; - css['margin-top'] = css['margin-top'] || splitMargin[0]; - css['margin-right'] = css['margin-right'] || splitMargin[1]; - css['margin-bottom'] = css['margin-bottom'] || splitMargin[0]; - css['margin-left'] = css['margin-left'] || splitMargin[1]; - break; - case 3: //margin: top rightleft bottom; - css['margin-top'] = css['margin-top'] || splitMargin[0]; - css['margin-right'] = css['margin-right'] || splitMargin[1]; - css['margin-bottom'] = css['margin-bottom'] || splitMargin[2]; - css['margin-left'] = css['margin-left'] || splitMargin[1]; - break; - case 4: //margin: top right bottom left; - css['margin-top'] = css['margin-top'] || splitMargin[0]; - css['margin-right'] = css['margin-right'] || splitMargin[1]; - css['margin-bottom'] = css['margin-bottom'] || splitMargin[2]; - css['margin-left'] = css['margin-left'] || splitMargin[3]; - } - delete css.margin; - } - return css; - } - - function updateStyle() { - function addPixelSuffix(value) { - if (value.length > 0 && /^[0-9]+$/.test(value)) { - value += 'px'; - } - - return value; - } - - if (!editor.settings.image_advtab) { - return; - } - - var data = win.toJSON(), - css = dom.parseStyle(data.style); - - css = mergeMargins(css); - - if (data.vspace) { - css['margin-top'] = css['margin-bottom'] = addPixelSuffix(data.vspace); - } - if (data.hspace) { - css['margin-left'] = css['margin-right'] = addPixelSuffix(data.hspace); - } - if (data.border) { - css['border-width'] = addPixelSuffix(data.border); - } - - win.find('#style').value(dom.serializeStyle(dom.parseStyle(dom.serializeStyle(css)))); - } - - function updateVSpaceHSpaceBorder() { - if (!editor.settings.image_advtab) { - return; - } - - var data = win.toJSON(), - css = dom.parseStyle(data.style); - - win.find('#vspace').value(""); - win.find('#hspace').value(""); - - css = mergeMargins(css); - - //Move opposite equal margins to vspace/hspace field - if ((css['margin-top'] && css['margin-bottom']) || (css['margin-right'] && css['margin-left'])) { - if (css['margin-top'] === css['margin-bottom']) { - win.find('#vspace').value(removePixelSuffix(css['margin-top'])); - } else { - win.find('#vspace').value(''); - } - if (css['margin-right'] === css['margin-left']) { - win.find('#hspace').value(removePixelSuffix(css['margin-right'])); - } else { - win.find('#hspace').value(''); - } - } - - //Move border-width - if (css['border-width']) { - win.find('#border').value(removePixelSuffix(css['border-width'])); - } - - win.find('#style').value(dom.serializeStyle(dom.parseStyle(dom.serializeStyle(css)))); - - } - - if (editor.settings.image_advtab) { - // Parse styles from img - if (imgElm) { - if (imgElm.style.marginLeft && imgElm.style.marginRight && imgElm.style.marginLeft === imgElm.style.marginRight) { - data.hspace = removePixelSuffix(imgElm.style.marginLeft); - } - if (imgElm.style.marginTop && imgElm.style.marginBottom && imgElm.style.marginTop === imgElm.style.marginBottom) { - data.vspace = removePixelSuffix(imgElm.style.marginTop); - } - if (imgElm.style.borderWidth) { - data.border = removePixelSuffix(imgElm.style.borderWidth); - } - - data.style = editor.dom.serializeStyle(editor.dom.parseStyle(editor.dom.getAttrib(imgElm, 'style'))); - } - - // Advanced dialog shows general+advanced tabs - win = editor.windowManager.open({ - title: 'Insert/edit image', - data: data, - bodyType: 'tabpanel', - body: [ - { - title: 'General', - type: 'form', - items: generalFormItems - }, - - { - title: 'Advanced', - type: 'form', - pack: 'start', - items: [ - { - label: 'Style', - name: 'style', - type: 'textbox', - onchange: updateVSpaceHSpaceBorder - }, - { - type: 'form', - layout: 'grid', - packV: 'start', - columns: 2, - padding: 0, - alignH: ['left', 'right'], - defaults: { - type: 'textbox', - maxWidth: 50, - onchange: updateStyle - }, - items: [ - {label: 'Vertical space', name: 'vspace'}, - {label: 'Horizontal space', name: 'hspace'}, - {label: 'Border', name: 'border'} - ] - } - ] - } - ], - onSubmit: onSubmitForm - }); - } else { - // Simple default dialog - win = editor.windowManager.open({ - title: 'Insert/edit image', - data: data, - body: generalFormItems, - onSubmit: onSubmitForm - }); - } - } - - editor.addButton('image', { - icon: 'image', - tooltip: 'Insert/edit image', - onclick: createImageList(showDialog), - stateSelector: 'img:not([data-mce-object],[data-mce-placeholder])' - }); - - editor.addMenuItem('image', { - icon: 'image', - text: 'Insert/edit image', - onclick: createImageList(showDialog), - context: 'insert', - prependToContext: true - }); - - editor.addCommand('mceImage', createImageList(showDialog)); -}); +(function () { +var image = (function () { + 'use strict'; + + var global = tinymce.util.Tools.resolve('tinymce.PluginManager'); + + var hasDimensions = function (editor) { + return editor.settings.image_dimensions === false ? false : true; + }; + var hasAdvTab = function (editor) { + return editor.settings.image_advtab === true ? true : false; + }; + var getPrependUrl = function (editor) { + return editor.getParam('image_prepend_url', ''); + }; + var getClassList = function (editor) { + return editor.getParam('image_class_list'); + }; + var hasDescription = function (editor) { + return editor.settings.image_description === false ? false : true; + }; + var hasImageTitle = function (editor) { + return editor.settings.image_title === true ? true : false; + }; + var hasImageCaption = function (editor) { + return editor.settings.image_caption === true ? true : false; + }; + var getImageList = function (editor) { + return editor.getParam('image_list', false); + }; + var hasUploadUrl = function (editor) { + return editor.getParam('images_upload_url', false); + }; + var hasUploadHandler = function (editor) { + return editor.getParam('images_upload_handler', false); + }; + var getUploadUrl = function (editor) { + return editor.getParam('images_upload_url'); + }; + var getUploadHandler = function (editor) { + return editor.getParam('images_upload_handler'); + }; + var getUploadBasePath = function (editor) { + return editor.getParam('images_upload_base_path'); + }; + var getUploadCredentials = function (editor) { + return editor.getParam('images_upload_credentials'); + }; + var $_5qak3fcdjh8lpuqt = { + hasDimensions: hasDimensions, + hasAdvTab: hasAdvTab, + getPrependUrl: getPrependUrl, + getClassList: getClassList, + hasDescription: hasDescription, + hasImageTitle: hasImageTitle, + hasImageCaption: hasImageCaption, + getImageList: getImageList, + hasUploadUrl: hasUploadUrl, + hasUploadHandler: hasUploadHandler, + getUploadUrl: getUploadUrl, + getUploadHandler: getUploadHandler, + getUploadBasePath: getUploadBasePath, + getUploadCredentials: getUploadCredentials + }; + + var global$1 = typeof window !== 'undefined' ? window : Function('return this;')(); + + var path = function (parts, scope) { + var o = scope !== undefined && scope !== null ? scope : global$1; + for (var i = 0; i < parts.length && o !== undefined && o !== null; ++i) + o = o[parts[i]]; + return o; + }; + var resolve = function (p, scope) { + var parts = p.split('.'); + return path(parts, scope); + }; + var step = function (o, part) { + if (o[part] === undefined || o[part] === null) + o[part] = {}; + return o[part]; + }; + var forge = function (parts, target) { + var o = target !== undefined ? target : global$1; + for (var i = 0; i < parts.length; ++i) + o = step(o, parts[i]); + return o; + }; + var namespace = function (name, target) { + var parts = name.split('.'); + return forge(parts, target); + }; + var $_4dgtl5chjh8lpurb = { + path: path, + resolve: resolve, + forge: forge, + namespace: namespace + }; + + var unsafe = function (name, scope) { + return $_4dgtl5chjh8lpurb.resolve(name, scope); + }; + var getOrDie = function (name, scope) { + var actual = unsafe(name, scope); + if (actual === undefined || actual === null) + throw name + ' not available on this browser'; + return actual; + }; + var $_gakuxdcgjh8lpur7 = { getOrDie: getOrDie }; + + function FileReader () { + var f = $_gakuxdcgjh8lpur7.getOrDie('FileReader'); + return new f(); + } + + var global$2 = tinymce.util.Tools.resolve('tinymce.util.Promise'); + + var global$3 = tinymce.util.Tools.resolve('tinymce.util.Tools'); + + var global$4 = tinymce.util.Tools.resolve('tinymce.util.XHR'); + + var parseIntAndGetMax = function (val1, val2) { + return Math.max(parseInt(val1, 10), parseInt(val2, 10)); + }; + var getImageSize = function (url, callback) { + var img = document.createElement('img'); + function done(width, height) { + if (img.parentNode) { + img.parentNode.removeChild(img); + } + callback({ + width: width, + height: height + }); + } + img.onload = function () { + var width = parseIntAndGetMax(img.width, img.clientWidth); + var height = parseIntAndGetMax(img.height, img.clientHeight); + done(width, height); + }; + img.onerror = function () { + done(0, 0); + }; + var style = img.style; + style.visibility = 'hidden'; + style.position = 'fixed'; + style.bottom = style.left = '0px'; + style.width = style.height = 'auto'; + document.body.appendChild(img); + img.src = url; + }; + var buildListItems = function (inputList, itemCallback, startItems) { + function appendItems(values, output) { + output = output || []; + global$3.each(values, function (item) { + var menuItem = { text: item.text || item.title }; + if (item.menu) { + menuItem.menu = appendItems(item.menu); + } else { + menuItem.value = item.value; + itemCallback(menuItem); + } + output.push(menuItem); + }); + return output; + } + return appendItems(inputList, startItems || []); + }; + var removePixelSuffix = function (value) { + if (value) { + value = value.replace(/px$/, ''); + } + return value; + }; + var addPixelSuffix = function (value) { + if (value.length > 0 && /^[0-9]+$/.test(value)) { + value += 'px'; + } + return value; + }; + var mergeMargins = function (css) { + if (css.margin) { + var splitMargin = css.margin.split(' '); + switch (splitMargin.length) { + case 1: + css['margin-top'] = css['margin-top'] || splitMargin[0]; + css['margin-right'] = css['margin-right'] || splitMargin[0]; + css['margin-bottom'] = css['margin-bottom'] || splitMargin[0]; + css['margin-left'] = css['margin-left'] || splitMargin[0]; + break; + case 2: + css['margin-top'] = css['margin-top'] || splitMargin[0]; + css['margin-right'] = css['margin-right'] || splitMargin[1]; + css['margin-bottom'] = css['margin-bottom'] || splitMargin[0]; + css['margin-left'] = css['margin-left'] || splitMargin[1]; + break; + case 3: + css['margin-top'] = css['margin-top'] || splitMargin[0]; + css['margin-right'] = css['margin-right'] || splitMargin[1]; + css['margin-bottom'] = css['margin-bottom'] || splitMargin[2]; + css['margin-left'] = css['margin-left'] || splitMargin[1]; + break; + case 4: + css['margin-top'] = css['margin-top'] || splitMargin[0]; + css['margin-right'] = css['margin-right'] || splitMargin[1]; + css['margin-bottom'] = css['margin-bottom'] || splitMargin[2]; + css['margin-left'] = css['margin-left'] || splitMargin[3]; + } + delete css.margin; + } + return css; + }; + var createImageList = function (editor, callback) { + var imageList = $_5qak3fcdjh8lpuqt.getImageList(editor); + if (typeof imageList === 'string') { + global$4.send({ + url: imageList, + success: function (text) { + callback(JSON.parse(text)); + } + }); + } else if (typeof imageList === 'function') { + imageList(callback); + } else { + callback(imageList); + } + }; + var waitLoadImage = function (editor, data, imgElm) { + function selectImage() { + imgElm.onload = imgElm.onerror = null; + if (editor.selection) { + editor.selection.select(imgElm); + editor.nodeChanged(); + } + } + imgElm.onload = function () { + if (!data.width && !data.height && $_5qak3fcdjh8lpuqt.hasDimensions(editor)) { + editor.dom.setAttribs(imgElm, { + width: imgElm.clientWidth, + height: imgElm.clientHeight + }); + } + selectImage(); + }; + imgElm.onerror = selectImage; + }; + var blobToDataUri = function (blob) { + return new global$2(function (resolve, reject) { + var reader = new FileReader(); + reader.onload = function () { + resolve(reader.result); + }; + reader.onerror = function () { + reject(FileReader.error.message); + }; + reader.readAsDataURL(blob); + }); + }; + var $_745c6tcejh8lpuqy = { + getImageSize: getImageSize, + buildListItems: buildListItems, + removePixelSuffix: removePixelSuffix, + addPixelSuffix: addPixelSuffix, + mergeMargins: mergeMargins, + createImageList: createImageList, + waitLoadImage: waitLoadImage, + blobToDataUri: blobToDataUri + }; + + var global$5 = tinymce.util.Tools.resolve('tinymce.dom.DOMUtils'); + + var typeOf = function (x) { + if (x === null) + return 'null'; + var t = typeof x; + if (t === 'object' && Array.prototype.isPrototypeOf(x)) + return 'array'; + if (t === 'object' && String.prototype.isPrototypeOf(x)) + return 'string'; + return t; + }; + var isType = function (type) { + return function (value) { + return typeOf(value) === type; + }; + }; + var $_4jah87crjh8lpury = { + isString: isType('string'), + isObject: isType('object'), + isArray: isType('array'), + isNull: isType('null'), + isBoolean: isType('boolean'), + isUndefined: isType('undefined'), + isFunction: isType('function'), + isNumber: isType('number') + }; + + var shallow = function (old, nu) { + return nu; + }; + var deep = function (old, nu) { + var bothObjects = $_4jah87crjh8lpury.isObject(old) && $_4jah87crjh8lpury.isObject(nu); + return bothObjects ? deepMerge(old, nu) : nu; + }; + var baseMerge = function (merger) { + return function () { + var objects = new Array(arguments.length); + for (var i = 0; i < objects.length; i++) + objects[i] = arguments[i]; + if (objects.length === 0) + throw new Error('Can\'t merge zero objects'); + var ret = {}; + for (var j = 0; j < objects.length; j++) { + var curObject = objects[j]; + for (var key in curObject) + if (curObject.hasOwnProperty(key)) { + ret[key] = merger(ret[key], curObject[key]); + } + } + return ret; + }; + }; + var deepMerge = baseMerge(deep); + var merge = baseMerge(shallow); + var $_bdq2jqcqjh8lpurw = { + deepMerge: deepMerge, + merge: merge + }; + + var DOM = global$5.DOM; + var getHspace = function (image) { + if (image.style.marginLeft && image.style.marginRight && image.style.marginLeft === image.style.marginRight) { + return $_745c6tcejh8lpuqy.removePixelSuffix(image.style.marginLeft); + } else { + return ''; + } + }; + var getVspace = function (image) { + if (image.style.marginTop && image.style.marginBottom && image.style.marginTop === image.style.marginBottom) { + return $_745c6tcejh8lpuqy.removePixelSuffix(image.style.marginTop); + } else { + return ''; + } + }; + var getBorder = function (image) { + if (image.style.borderWidth) { + return $_745c6tcejh8lpuqy.removePixelSuffix(image.style.borderWidth); + } else { + return ''; + } + }; + var getAttrib = function (image, name) { + if (image.hasAttribute(name)) { + return image.getAttribute(name); + } else { + return ''; + } + }; + var getStyle = function (image, name) { + return image.style[name] ? image.style[name] : ''; + }; + var hasCaption = function (image) { + return image.parentNode !== null && image.parentNode.nodeName === 'FIGURE'; + }; + var setAttrib = function (image, name, value) { + image.setAttribute(name, value); + }; + var wrapInFigure = function (image) { + var figureElm = DOM.create('figure', { class: 'image' }); + DOM.insertAfter(figureElm, image); + figureElm.appendChild(image); + figureElm.appendChild(DOM.create('figcaption', { contentEditable: true }, 'Caption')); + figureElm.contentEditable = 'false'; + }; + var removeFigure = function (image) { + var figureElm = image.parentNode; + DOM.insertAfter(image, figureElm); + DOM.remove(figureElm); + }; + var toggleCaption = function (image) { + if (hasCaption(image)) { + removeFigure(image); + } else { + wrapInFigure(image); + } + }; + var normalizeStyle = function (image, normalizeCss) { + var attrValue = image.getAttribute('style'); + var value = normalizeCss(attrValue !== null ? attrValue : ''); + if (value.length > 0) { + image.setAttribute('style', value); + image.setAttribute('data-mce-style', value); + } else { + image.removeAttribute('style'); + } + }; + var setSize = function (name, normalizeCss) { + return function (image, name, value) { + if (image.style[name]) { + image.style[name] = $_745c6tcejh8lpuqy.addPixelSuffix(value); + normalizeStyle(image, normalizeCss); + } else { + setAttrib(image, name, value); + } + }; + }; + var getSize = function (image, name) { + if (image.style[name]) { + return $_745c6tcejh8lpuqy.removePixelSuffix(image.style[name]); + } else { + return getAttrib(image, name); + } + }; + var setHspace = function (image, value) { + var pxValue = $_745c6tcejh8lpuqy.addPixelSuffix(value); + image.style.marginLeft = pxValue; + image.style.marginRight = pxValue; + }; + var setVspace = function (image, value) { + var pxValue = $_745c6tcejh8lpuqy.addPixelSuffix(value); + image.style.marginTop = pxValue; + image.style.marginBottom = pxValue; + }; + var setBorder = function (image, value) { + var pxValue = $_745c6tcejh8lpuqy.addPixelSuffix(value); + image.style.borderWidth = pxValue; + }; + var setBorderStyle = function (image, value) { + image.style.borderStyle = value; + }; + var getBorderStyle = function (image) { + return getStyle(image, 'borderStyle'); + }; + var isFigure = function (elm) { + return elm.nodeName === 'FIGURE'; + }; + var defaultData = function () { + return { + src: '', + alt: '', + title: '', + width: '', + height: '', + class: '', + style: '', + caption: false, + hspace: '', + vspace: '', + border: '', + borderStyle: '' + }; + }; + var getStyleValue = function (normalizeCss, data) { + var image = document.createElement('img'); + setAttrib(image, 'style', data.style); + if (getHspace(image) || data.hspace !== '') { + setHspace(image, data.hspace); + } + if (getVspace(image) || data.vspace !== '') { + setVspace(image, data.vspace); + } + if (getBorder(image) || data.border !== '') { + setBorder(image, data.border); + } + if (getBorderStyle(image) || data.borderStyle !== '') { + setBorderStyle(image, data.borderStyle); + } + return normalizeCss(image.getAttribute('style')); + }; + var create = function (normalizeCss, data) { + var image = document.createElement('img'); + write(normalizeCss, $_bdq2jqcqjh8lpurw.merge(data, { caption: false }), image); + setAttrib(image, 'alt', data.alt); + if (data.caption) { + var figure = DOM.create('figure', { class: 'image' }); + figure.appendChild(image); + figure.appendChild(DOM.create('figcaption', { contentEditable: true }, 'Caption')); + figure.contentEditable = 'false'; + return figure; + } else { + return image; + } + }; + var read = function (normalizeCss, image) { + return { + src: getAttrib(image, 'src'), + alt: getAttrib(image, 'alt'), + title: getAttrib(image, 'title'), + width: getSize(image, 'width'), + height: getSize(image, 'height'), + class: getAttrib(image, 'class'), + style: normalizeCss(getAttrib(image, 'style')), + caption: hasCaption(image), + hspace: getHspace(image), + vspace: getVspace(image), + border: getBorder(image), + borderStyle: getStyle(image, 'borderStyle') + }; + }; + var updateProp = function (image, oldData, newData, name, set) { + if (newData[name] !== oldData[name]) { + set(image, name, newData[name]); + } + }; + var normalized = function (set, normalizeCss) { + return function (image, name, value) { + set(image, value); + normalizeStyle(image, normalizeCss); + }; + }; + var write = function (normalizeCss, newData, image) { + var oldData = read(normalizeCss, image); + updateProp(image, oldData, newData, 'caption', function (image, _name, _value) { + return toggleCaption(image); + }); + updateProp(image, oldData, newData, 'src', setAttrib); + updateProp(image, oldData, newData, 'alt', setAttrib); + updateProp(image, oldData, newData, 'title', setAttrib); + updateProp(image, oldData, newData, 'width', setSize('width', normalizeCss)); + updateProp(image, oldData, newData, 'height', setSize('height', normalizeCss)); + updateProp(image, oldData, newData, 'class', setAttrib); + updateProp(image, oldData, newData, 'style', normalized(function (image, value) { + return setAttrib(image, 'style', value); + }, normalizeCss)); + updateProp(image, oldData, newData, 'hspace', normalized(setHspace, normalizeCss)); + updateProp(image, oldData, newData, 'vspace', normalized(setVspace, normalizeCss)); + updateProp(image, oldData, newData, 'border', normalized(setBorder, normalizeCss)); + updateProp(image, oldData, newData, 'borderStyle', normalized(setBorderStyle, normalizeCss)); + }; + + var normalizeCss = function (editor, cssText) { + var css = editor.dom.styles.parse(cssText); + var mergedCss = $_745c6tcejh8lpuqy.mergeMargins(css); + var compressed = editor.dom.styles.parse(editor.dom.styles.serialize(mergedCss)); + return editor.dom.styles.serialize(compressed); + }; + var getSelectedImage = function (editor) { + var imgElm = editor.selection.getNode(); + var figureElm = editor.dom.getParent(imgElm, 'figure.image'); + if (figureElm) { + return editor.dom.select('img', figureElm)[0]; + } + if (imgElm && (imgElm.nodeName !== 'IMG' || imgElm.getAttribute('data-mce-object') || imgElm.getAttribute('data-mce-placeholder'))) { + return null; + } + return imgElm; + }; + var splitTextBlock = function (editor, figure) { + var dom = editor.dom; + var textBlock = dom.getParent(figure.parentNode, function (node) { + return editor.schema.getTextBlockElements()[node.nodeName]; + }); + if (textBlock) { + return dom.split(textBlock, figure); + } else { + return figure; + } + }; + var readImageDataFromSelection = function (editor) { + var image = getSelectedImage(editor); + return image ? read(function (css) { + return normalizeCss(editor, css); + }, image) : defaultData(); + }; + var insertImageAtCaret = function (editor, data) { + var elm = create(function (css) { + return normalizeCss(editor, css); + }, data); + editor.dom.setAttrib(elm, 'data-mce-id', '__mcenew'); + editor.focus(); + editor.selection.setContent(elm.outerHTML); + var insertedElm = editor.dom.select('*[data-mce-id="__mcenew"]')[0]; + editor.dom.setAttrib(insertedElm, 'data-mce-id', null); + if (isFigure(insertedElm)) { + var figure = splitTextBlock(editor, insertedElm); + editor.selection.select(figure); + } else { + editor.selection.select(insertedElm); + } + }; + var syncSrcAttr = function (editor, image) { + editor.dom.setAttrib(image, 'src', image.getAttribute('src')); + }; + var deleteImage = function (editor, image) { + if (image) { + var elm = editor.dom.is(image.parentNode, 'figure.image') ? image.parentNode : image; + editor.dom.remove(elm); + editor.focus(); + editor.nodeChanged(); + if (editor.dom.isEmpty(editor.getBody())) { + editor.setContent(''); + editor.selection.setCursorLocation(); + } + } + }; + var writeImageDataToSelection = function (editor, data) { + var image = getSelectedImage(editor); + write(function (css) { + return normalizeCss(editor, css); + }, data, image); + syncSrcAttr(editor, image); + if (isFigure(image.parentNode)) { + var figure = image.parentNode; + splitTextBlock(editor, figure); + editor.selection.select(image.parentNode); + } else { + editor.selection.select(image); + $_745c6tcejh8lpuqy.waitLoadImage(editor, data, image); + } + }; + var insertOrUpdateImage = function (editor, data) { + var image = getSelectedImage(editor); + if (image) { + if (data.src) { + writeImageDataToSelection(editor, data); + } else { + deleteImage(editor, image); + } + } else if (data.src) { + insertImageAtCaret(editor, data); + } + }; + + var updateVSpaceHSpaceBorder = function (editor) { + return function (evt) { + var dom = editor.dom; + var rootControl = evt.control.rootControl; + if (!$_5qak3fcdjh8lpuqt.hasAdvTab(editor)) { + return; + } + var data = rootControl.toJSON(); + var css = dom.parseStyle(data.style); + rootControl.find('#vspace').value(''); + rootControl.find('#hspace').value(''); + css = $_745c6tcejh8lpuqy.mergeMargins(css); + if (css['margin-top'] && css['margin-bottom'] || css['margin-right'] && css['margin-left']) { + if (css['margin-top'] === css['margin-bottom']) { + rootControl.find('#vspace').value($_745c6tcejh8lpuqy.removePixelSuffix(css['margin-top'])); + } else { + rootControl.find('#vspace').value(''); + } + if (css['margin-right'] === css['margin-left']) { + rootControl.find('#hspace').value($_745c6tcejh8lpuqy.removePixelSuffix(css['margin-right'])); + } else { + rootControl.find('#hspace').value(''); + } + } + if (css['border-width']) { + rootControl.find('#border').value($_745c6tcejh8lpuqy.removePixelSuffix(css['border-width'])); + } else { + rootControl.find('#border').value(''); + } + if (css['border-style']) { + rootControl.find('#borderStyle').value(css['border-style']); + } else { + rootControl.find('#borderStyle').value(''); + } + rootControl.find('#style').value(dom.serializeStyle(dom.parseStyle(dom.serializeStyle(css)))); + }; + }; + var updateStyle = function (editor, win) { + win.find('#style').each(function (ctrl) { + var value = getStyleValue(function (css) { + return normalizeCss(editor, css); + }, $_bdq2jqcqjh8lpurw.merge(defaultData(), win.toJSON())); + ctrl.value(value); + }); + }; + var makeTab = function (editor) { + return { + title: 'Advanced', + type: 'form', + pack: 'start', + items: [ + { + label: 'Style', + name: 'style', + type: 'textbox', + onchange: updateVSpaceHSpaceBorder(editor) + }, + { + type: 'form', + layout: 'grid', + packV: 'start', + columns: 2, + padding: 0, + defaults: { + type: 'textbox', + maxWidth: 50, + onchange: function (evt) { + updateStyle(editor, evt.control.rootControl); + } + }, + items: [ + { + label: 'Vertical space', + name: 'vspace' + }, + { + label: 'Border width', + name: 'border' + }, + { + label: 'Horizontal space', + name: 'hspace' + }, + { + label: 'Border style', + type: 'listbox', + name: 'borderStyle', + width: 90, + maxWidth: 90, + onselect: function (evt) { + updateStyle(editor, evt.control.rootControl); + }, + values: [ + { + text: 'Select...', + value: '' + }, + { + text: 'Solid', + value: 'solid' + }, + { + text: 'Dotted', + value: 'dotted' + }, + { + text: 'Dashed', + value: 'dashed' + }, + { + text: 'Double', + value: 'double' + }, + { + text: 'Groove', + value: 'groove' + }, + { + text: 'Ridge', + value: 'ridge' + }, + { + text: 'Inset', + value: 'inset' + }, + { + text: 'Outset', + value: 'outset' + }, + { + text: 'None', + value: 'none' + }, + { + text: 'Hidden', + value: 'hidden' + } + ] + } + ] + } + ] + }; + }; + var $_5gwoxocmjh8lpurf = { makeTab: makeTab }; + + var doSyncSize = function (widthCtrl, heightCtrl) { + widthCtrl.state.set('oldVal', widthCtrl.value()); + heightCtrl.state.set('oldVal', heightCtrl.value()); + }; + var doSizeControls = function (win, f) { + var widthCtrl = win.find('#width')[0]; + var heightCtrl = win.find('#height')[0]; + var constrained = win.find('#constrain')[0]; + if (widthCtrl && heightCtrl && constrained) { + f(widthCtrl, heightCtrl, constrained.checked()); + } + }; + var doUpdateSize = function (widthCtrl, heightCtrl, isContrained) { + var oldWidth = widthCtrl.state.get('oldVal'); + var oldHeight = heightCtrl.state.get('oldVal'); + var newWidth = widthCtrl.value(); + var newHeight = heightCtrl.value(); + if (isContrained && oldWidth && oldHeight && newWidth && newHeight) { + if (newWidth !== oldWidth) { + newHeight = Math.round(newWidth / oldWidth * newHeight); + if (!isNaN(newHeight)) { + heightCtrl.value(newHeight); + } + } else { + newWidth = Math.round(newHeight / oldHeight * newWidth); + if (!isNaN(newWidth)) { + widthCtrl.value(newWidth); + } + } + } + doSyncSize(widthCtrl, heightCtrl); + }; + var syncSize = function (win) { + doSizeControls(win, doSyncSize); + }; + var updateSize = function (win) { + doSizeControls(win, doUpdateSize); + }; + var createUi = function () { + var recalcSize = function (evt) { + updateSize(evt.control.rootControl); + }; + return { + type: 'container', + label: 'Dimensions', + layout: 'flex', + align: 'center', + spacing: 5, + items: [ + { + name: 'width', + type: 'textbox', + maxLength: 5, + size: 5, + onchange: recalcSize, + ariaLabel: 'Width' + }, + { + type: 'label', + text: 'x' + }, + { + name: 'height', + type: 'textbox', + maxLength: 5, + size: 5, + onchange: recalcSize, + ariaLabel: 'Height' + }, + { + name: 'constrain', + type: 'checkbox', + checked: true, + text: 'Constrain proportions' + } + ] + }; + }; + var $_b6478bctjh8lpus3 = { + createUi: createUi, + syncSize: syncSize, + updateSize: updateSize + }; + + var onSrcChange = function (evt, editor) { + var srcURL, prependURL, absoluteURLPattern; + var meta = evt.meta || {}; + var control = evt.control; + var rootControl = control.rootControl; + var imageListCtrl = rootControl.find('#image-list')[0]; + if (imageListCtrl) { + imageListCtrl.value(editor.convertURL(control.value(), 'src')); + } + global$3.each(meta, function (value, key) { + rootControl.find('#' + key).value(value); + }); + if (!meta.width && !meta.height) { + srcURL = editor.convertURL(control.value(), 'src'); + prependURL = $_5qak3fcdjh8lpuqt.getPrependUrl(editor); + absoluteURLPattern = new RegExp('^(?:[a-z]+:)?//', 'i'); + if (prependURL && !absoluteURLPattern.test(srcURL) && srcURL.substring(0, prependURL.length) !== prependURL) { + srcURL = prependURL + srcURL; + } + control.value(srcURL); + $_745c6tcejh8lpuqy.getImageSize(editor.documentBaseURI.toAbsolute(control.value()), function (data) { + if (data.width && data.height && $_5qak3fcdjh8lpuqt.hasDimensions(editor)) { + rootControl.find('#width').value(data.width); + rootControl.find('#height').value(data.height); + $_b6478bctjh8lpus3.syncSize(rootControl); + } + }); + } + }; + var onBeforeCall = function (evt) { + evt.meta = evt.control.rootControl.toJSON(); + }; + var getGeneralItems = function (editor, imageListCtrl) { + var generalFormItems = [ + { + name: 'src', + type: 'filepicker', + filetype: 'image', + label: 'Source', + autofocus: true, + onchange: function (evt) { + onSrcChange(evt, editor); + }, + onbeforecall: onBeforeCall + }, + imageListCtrl + ]; + if ($_5qak3fcdjh8lpuqt.hasDescription(editor)) { + generalFormItems.push({ + name: 'alt', + type: 'textbox', + label: 'Image description' + }); + } + if ($_5qak3fcdjh8lpuqt.hasImageTitle(editor)) { + generalFormItems.push({ + name: 'title', + type: 'textbox', + label: 'Image Title' + }); + } + if ($_5qak3fcdjh8lpuqt.hasDimensions(editor)) { + generalFormItems.push($_b6478bctjh8lpus3.createUi()); + } + if ($_5qak3fcdjh8lpuqt.getClassList(editor)) { + generalFormItems.push({ + name: 'class', + type: 'listbox', + label: 'Class', + values: $_745c6tcejh8lpuqy.buildListItems($_5qak3fcdjh8lpuqt.getClassList(editor), function (item) { + if (item.value) { + item.textStyle = function () { + return editor.formatter.getCssText({ + inline: 'img', + classes: [item.value] + }); + }; + } + }) + }); + } + if ($_5qak3fcdjh8lpuqt.hasImageCaption(editor)) { + generalFormItems.push({ + name: 'caption', + type: 'checkbox', + label: 'Caption' + }); + } + return generalFormItems; + }; + var makeTab$1 = function (editor, imageListCtrl) { + return { + title: 'General', + type: 'form', + items: getGeneralItems(editor, imageListCtrl) + }; + }; + var $_7ef2l1csjh8lpurz = { + makeTab: makeTab$1, + getGeneralItems: getGeneralItems + }; + + var url = function () { + return $_gakuxdcgjh8lpur7.getOrDie('URL'); + }; + var createObjectURL = function (blob) { + return url().createObjectURL(blob); + }; + var revokeObjectURL = function (u) { + url().revokeObjectURL(u); + }; + var $_5as8p6cvjh8lpus8 = { + createObjectURL: createObjectURL, + revokeObjectURL: revokeObjectURL + }; + + var global$6 = tinymce.util.Tools.resolve('tinymce.ui.Factory'); + + function XMLHttpRequest () { + var f = $_gakuxdcgjh8lpur7.getOrDie('XMLHttpRequest'); + return new f(); + } + + var noop = function () { + }; + var pathJoin = function (path1, path2) { + if (path1) { + return path1.replace(/\/$/, '') + '/' + path2.replace(/^\//, ''); + } + return path2; + }; + function Uploader (settings) { + var defaultHandler = function (blobInfo, success, failure, progress) { + var xhr, formData; + xhr = new XMLHttpRequest(); + xhr.open('POST', settings.url); + xhr.withCredentials = settings.credentials; + xhr.upload.onprogress = function (e) { + progress(e.loaded / e.total * 100); + }; + xhr.onerror = function () { + failure('Image upload failed due to a XHR Transport error. Code: ' + xhr.status); + }; + xhr.onload = function () { + var json; + if (xhr.status < 200 || xhr.status >= 300) { + failure('HTTP Error: ' + xhr.status); + return; + } + json = JSON.parse(xhr.responseText); + if (!json || typeof json.location !== 'string') { + failure('Invalid JSON: ' + xhr.responseText); + return; + } + success(pathJoin(settings.basePath, json.location)); + }; + formData = new FormData(); + formData.append('file', blobInfo.blob(), blobInfo.filename()); + xhr.send(formData); + }; + var uploadBlob = function (blobInfo, handler) { + return new global$2(function (resolve, reject) { + try { + handler(blobInfo, resolve, reject, noop); + } catch (ex) { + reject(ex.message); + } + }); + }; + var isDefaultHandler = function (handler) { + return handler === defaultHandler; + }; + var upload = function (blobInfo) { + return !settings.url && isDefaultHandler(settings.handler) ? global$2.reject('Upload url missing from the settings.') : uploadBlob(blobInfo, settings.handler); + }; + settings = global$3.extend({ + credentials: false, + handler: defaultHandler + }, settings); + return { upload: upload }; + } + + var onFileInput = function (editor) { + return function (evt) { + var Throbber = global$6.get('Throbber'); + var rootControl = evt.control.rootControl; + var throbber = new Throbber(rootControl.getEl()); + var file = evt.control.value(); + var blobUri = $_5as8p6cvjh8lpus8.createObjectURL(file); + var uploader = Uploader({ + url: $_5qak3fcdjh8lpuqt.getUploadUrl(editor), + basePath: $_5qak3fcdjh8lpuqt.getUploadBasePath(editor), + credentials: $_5qak3fcdjh8lpuqt.getUploadCredentials(editor), + handler: $_5qak3fcdjh8lpuqt.getUploadHandler(editor) + }); + var finalize = function () { + throbber.hide(); + $_5as8p6cvjh8lpus8.revokeObjectURL(blobUri); + }; + throbber.show(); + return $_745c6tcejh8lpuqy.blobToDataUri(file).then(function (dataUrl) { + var blobInfo = editor.editorUpload.blobCache.create({ + blob: file, + blobUri: blobUri, + name: file.name ? file.name.replace(/\.[^\.]+$/, '') : null, + base64: dataUrl.split(',')[1] + }); + return uploader.upload(blobInfo).then(function (url) { + var src = rootControl.find('#src'); + src.value(url); + rootControl.find('tabpanel')[0].activateTab(0); + src.fire('change'); + finalize(); + return url; + }); + }).catch(function (err) { + editor.windowManager.alert(err); + finalize(); + }); + }; + }; + var acceptExts = '.jpg,.jpeg,.png,.gif'; + var makeTab$2 = function (editor) { + return { + title: 'Upload', + type: 'form', + layout: 'flex', + direction: 'column', + align: 'stretch', + padding: '20 20 20 20', + items: [ + { + type: 'container', + layout: 'flex', + direction: 'column', + align: 'center', + spacing: 10, + items: [ + { + text: 'Browse for an image', + type: 'browsebutton', + accept: acceptExts, + onchange: onFileInput(editor) + }, + { + text: 'OR', + type: 'label' + } + ] + }, + { + text: 'Drop an image here', + type: 'dropzone', + accept: acceptExts, + height: 100, + onchange: onFileInput(editor) + } + ] + }; + }; + var $_fhqdtncujh8lpus5 = { makeTab: makeTab$2 }; + + var noop$1 = function () { + var x = []; + for (var _i = 0; _i < arguments.length; _i++) { + x[_i] = arguments[_i]; + } + }; + var noarg = function (f) { + return function () { + var x = []; + for (var _i = 0; _i < arguments.length; _i++) { + x[_i] = arguments[_i]; + } + return f(); + }; + }; + var compose = function (fa, fb) { + return function () { + var x = []; + for (var _i = 0; _i < arguments.length; _i++) { + x[_i] = arguments[_i]; + } + return fa(fb.apply(null, arguments)); + }; + }; + var constant = function (value) { + return function () { + return value; + }; + }; + var identity = function (x) { + return x; + }; + var tripleEquals = function (a, b) { + return a === b; + }; + var curry = function (f) { + var x = []; + for (var _i = 1; _i < arguments.length; _i++) { + x[_i - 1] = arguments[_i]; + } + var args = new Array(arguments.length - 1); + for (var i = 1; i < arguments.length; i++) + args[i - 1] = arguments[i]; + return function () { + var x = []; + for (var _i = 0; _i < arguments.length; _i++) { + x[_i] = arguments[_i]; + } + var newArgs = new Array(arguments.length); + for (var j = 0; j < newArgs.length; j++) + newArgs[j] = arguments[j]; + var all = args.concat(newArgs); + return f.apply(null, all); + }; + }; + var not = function (f) { + return function () { + var x = []; + for (var _i = 0; _i < arguments.length; _i++) { + x[_i] = arguments[_i]; + } + return !f.apply(null, arguments); + }; + }; + var die = function (msg) { + return function () { + throw new Error(msg); + }; + }; + var apply = function (f) { + return f(); + }; + var call = function (f) { + f(); + }; + var never = constant(false); + var always = constant(true); + var $_17awtsczjh8lpusf = { + noop: noop$1, + noarg: noarg, + compose: compose, + constant: constant, + identity: identity, + tripleEquals: tripleEquals, + curry: curry, + not: not, + die: die, + apply: apply, + call: call, + never: never, + always: always + }; + + var submitForm = function (editor, evt) { + var win = evt.control.getRoot(); + $_b6478bctjh8lpus3.updateSize(win); + editor.undoManager.transact(function () { + var data = $_bdq2jqcqjh8lpurw.merge(readImageDataFromSelection(editor), win.toJSON()); + insertOrUpdateImage(editor, data); + }); + editor.editorUpload.uploadImagesAuto(); + }; + function Dialog (editor) { + function showDialog(imageList) { + var data = readImageDataFromSelection(editor); + var win, imageListCtrl; + if (imageList) { + imageListCtrl = { + type: 'listbox', + label: 'Image list', + name: 'image-list', + values: $_745c6tcejh8lpuqy.buildListItems(imageList, function (item) { + item.value = editor.convertURL(item.value || item.url, 'src'); + }, [{ + text: 'None', + value: '' + }]), + value: data.src && editor.convertURL(data.src, 'src'), + onselect: function (e) { + var altCtrl = win.find('#alt'); + if (!altCtrl.value() || e.lastControl && altCtrl.value() === e.lastControl.text()) { + altCtrl.value(e.control.text()); + } + win.find('#src').value(e.control.value()).fire('change'); + }, + onPostRender: function () { + imageListCtrl = this; + } + }; + } + if ($_5qak3fcdjh8lpuqt.hasAdvTab(editor) || $_5qak3fcdjh8lpuqt.hasUploadUrl(editor) || $_5qak3fcdjh8lpuqt.hasUploadHandler(editor)) { + var body = [$_7ef2l1csjh8lpurz.makeTab(editor, imageListCtrl)]; + if ($_5qak3fcdjh8lpuqt.hasAdvTab(editor)) { + body.push($_5gwoxocmjh8lpurf.makeTab(editor)); + } + if ($_5qak3fcdjh8lpuqt.hasUploadUrl(editor) || $_5qak3fcdjh8lpuqt.hasUploadHandler(editor)) { + body.push($_fhqdtncujh8lpus5.makeTab(editor)); + } + win = editor.windowManager.open({ + title: 'Insert/edit image', + data: data, + bodyType: 'tabpanel', + body: body, + onSubmit: $_17awtsczjh8lpusf.curry(submitForm, editor) + }); + } else { + win = editor.windowManager.open({ + title: 'Insert/edit image', + data: data, + body: $_7ef2l1csjh8lpurz.getGeneralItems(editor, imageListCtrl), + onSubmit: $_17awtsczjh8lpusf.curry(submitForm, editor) + }); + } + $_b6478bctjh8lpus3.syncSize(win); + } + function open() { + $_745c6tcejh8lpuqy.createImageList(editor, showDialog); + } + return { open: open }; + } + + var register = function (editor) { + editor.addCommand('mceImage', Dialog(editor).open); + }; + var $_a5a4z8cbjh8lpuql = { register: register }; + + var hasImageClass = function (node) { + var className = node.attr('class'); + return className && /\bimage\b/.test(className); + }; + var toggleContentEditableState = function (state) { + return function (nodes) { + var i = nodes.length, node; + var toggleContentEditable = function (node) { + node.attr('contenteditable', state ? 'true' : null); + }; + while (i--) { + node = nodes[i]; + if (hasImageClass(node)) { + node.attr('contenteditable', state ? 'false' : null); + global$3.each(node.getAll('figcaption'), toggleContentEditable); + } + } + }; + }; + var setup = function (editor) { + editor.on('preInit', function () { + editor.parser.addNodeFilter('figure', toggleContentEditableState(true)); + editor.serializer.addNodeFilter('figure', toggleContentEditableState(false)); + }); + }; + var $_xzhiyd0jh8lpush = { setup: setup }; + + var register$1 = function (editor) { + editor.addButton('image', { + icon: 'image', + tooltip: 'Insert/edit image', + onclick: Dialog(editor).open, + stateSelector: 'img:not([data-mce-object],[data-mce-placeholder]),figure.image' + }); + editor.addMenuItem('image', { + icon: 'image', + text: 'Image', + onclick: Dialog(editor).open, + context: 'insert', + prependToContext: true + }); + }; + var $_3ibf2rd1jh8lpusi = { register: register$1 }; + + global.add('image', function (editor) { + $_xzhiyd0jh8lpush.setup(editor); + $_3ibf2rd1jh8lpusi.register(editor); + $_a5a4z8cbjh8lpuql.register(editor); + }); + function Plugin () { + } + + return Plugin; + +}()); +})(); diff --git a/WebCms/Umbraco/lib/tinymce/plugins/image/plugin.min.js b/WebCms/Umbraco/lib/tinymce/plugins/image/plugin.min.js index dfce1ab..eae3a15 100644 --- a/WebCms/Umbraco/lib/tinymce/plugins/image/plugin.min.js +++ b/WebCms/Umbraco/lib/tinymce/plugins/image/plugin.min.js @@ -1 +1 @@ -tinymce.PluginManager.add("image",function(a){function b(a,b){function c(a,c){d.parentNode&&d.parentNode.removeChild(d),b({width:a,height:c})}var d=document.createElement("img");d.onload=function(){c(d.clientWidth,d.clientHeight)},d.onerror=function(){c()};var e=d.style;e.visibility="hidden",e.position="fixed",e.bottom=e.left=0,e.width=e.height="auto",document.body.appendChild(d),d.src=a}function c(a,b,c){function d(a,c){return c=c||[],tinymce.each(a,function(a){var e={text:a.text||a.title};a.menu?e.menu=d(a.menu):(e.value=a.value,b(e)),c.push(e)}),c}return d(a,c||[])}function d(b){return function(){var c=a.settings.image_list;"string"==typeof c?tinymce.util.XHR.send({url:c,success:function(a){b(tinymce.util.JSON.parse(a))}}):"function"==typeof c?c(b):b(c)}}function e(d){function e(){var a,b,c,d;a=l.find("#width")[0],b=l.find("#height")[0],a&&b&&(c=a.value(),d=b.value(),l.find("#constrain")[0].checked()&&m&&n&&c&&d&&(m!=c?(d=Math.round(c/m*d),isNaN(d)||b.value(d)):(c=Math.round(d/n*c),isNaN(c)||a.value(c))),m=c,n=d)}function f(){function b(b){function c(){b.onload=b.onerror=null,a.selection&&(a.selection.select(b),a.nodeChanged())}b.onload=function(){q.width||q.height||!t||r.setAttribs(b,{width:b.clientWidth,height:b.clientHeight}),c()},b.onerror=c}j(),e(),q=tinymce.extend(q,l.toJSON()),q.alt||(q.alt=""),q.title||(q.title=""),""===q.width&&(q.width=null),""===q.height&&(q.height=null),q.style||(q.style=null),q={src:q.src,alt:q.alt,title:q.title,width:q.width,height:q.height,style:q.style,"class":q["class"]},a.undoManager.transact(function(){return q.src?(""===q.title&&(q.title=null),s?r.setAttribs(s,q):(q.id="__mcenew",a.focus(),a.selection.setContent(r.createHTML("img",q)),s=r.get("__mcenew"),r.setAttrib(s,"id",null)),void b(s)):void(s&&(r.remove(s),a.focus(),a.nodeChanged()))})}function g(a){return a&&(a=a.replace(/px$/,"")),a}function h(c){var d,e,f,g=c.meta||{};o&&o.value(a.convertURL(this.value(),"src")),tinymce.each(g,function(a,b){l.find("#"+b).value(a)}),g.width||g.height||(d=a.convertURL(this.value(),"src"),e=a.settings.image_prepend_url,f=new RegExp("^(?:[a-z]+:)?//","i"),e&&!f.test(d)&&d.substring(0,e.length)!==e&&(d=e+d),this.value(d),b(a.documentBaseURI.toAbsolute(this.value()),function(a){a.width&&a.height&&t&&(m=a.width,n=a.height,l.find("#width").value(m),l.find("#height").value(n))}))}function i(a){if(a.margin){var b=a.margin.split(" ");switch(b.length){case 1:a["margin-top"]=a["margin-top"]||b[0],a["margin-right"]=a["margin-right"]||b[0],a["margin-bottom"]=a["margin-bottom"]||b[0],a["margin-left"]=a["margin-left"]||b[0];break;case 2:a["margin-top"]=a["margin-top"]||b[0],a["margin-right"]=a["margin-right"]||b[1],a["margin-bottom"]=a["margin-bottom"]||b[0],a["margin-left"]=a["margin-left"]||b[1];break;case 3:a["margin-top"]=a["margin-top"]||b[0],a["margin-right"]=a["margin-right"]||b[1],a["margin-bottom"]=a["margin-bottom"]||b[2],a["margin-left"]=a["margin-left"]||b[1];break;case 4:a["margin-top"]=a["margin-top"]||b[0],a["margin-right"]=a["margin-right"]||b[1],a["margin-bottom"]=a["margin-bottom"]||b[2],a["margin-left"]=a["margin-left"]||b[3]}delete a.margin}return a}function j(){function b(a){return a.length>0&&/^[0-9]+$/.test(a)&&(a+="px"),a}if(a.settings.image_advtab){var c=l.toJSON(),d=r.parseStyle(c.style);d=i(d),c.vspace&&(d["margin-top"]=d["margin-bottom"]=b(c.vspace)),c.hspace&&(d["margin-left"]=d["margin-right"]=b(c.hspace)),c.border&&(d["border-width"]=b(c.border)),l.find("#style").value(r.serializeStyle(r.parseStyle(r.serializeStyle(d))))}}function k(){if(a.settings.image_advtab){var b=l.toJSON(),c=r.parseStyle(b.style);l.find("#vspace").value(""),l.find("#hspace").value(""),c=i(c),(c["margin-top"]&&c["margin-bottom"]||c["margin-right"]&&c["margin-left"])&&(l.find("#vspace").value(c["margin-top"]===c["margin-bottom"]?g(c["margin-top"]):""),l.find("#hspace").value(c["margin-right"]===c["margin-left"]?g(c["margin-right"]):"")),c["border-width"]&&l.find("#border").value(g(c["border-width"])),l.find("#style").value(r.serializeStyle(r.parseStyle(r.serializeStyle(c))))}}var l,m,n,o,p,q={},r=a.dom,s=a.selection.getNode(),t=a.settings.image_dimensions!==!1;m=r.getAttrib(s,"width"),n=r.getAttrib(s,"height"),"IMG"!=s.nodeName||s.getAttribute("data-mce-object")||s.getAttribute("data-mce-placeholder")?s=null:q={src:r.getAttrib(s,"src"),alt:r.getAttrib(s,"alt"),title:r.getAttrib(s,"title"),"class":r.getAttrib(s,"class"),width:m,height:n},d&&(o={type:"listbox",label:"Image list",values:c(d,function(b){b.value=a.convertURL(b.value||b.url,"src")},[{text:"None",value:""}]),value:q.src&&a.convertURL(q.src,"src"),onselect:function(a){var b=l.find("#alt");(!b.value()||a.lastControl&&b.value()==a.lastControl.text())&&b.value(a.control.text()),l.find("#src").value(a.control.value()).fire("change")},onPostRender:function(){o=this}}),a.settings.image_class_list&&(p={name:"class",type:"listbox",label:"Class",values:c(a.settings.image_class_list,function(b){b.value&&(b.textStyle=function(){return a.formatter.getCssText({inline:"img",classes:[b.value]})})})});var u=[{name:"src",type:"filepicker",filetype:"image",label:"Source",autofocus:!0,onchange:h},o];a.settings.image_description!==!1&&u.push({name:"alt",type:"textbox",label:"Image description"}),a.settings.image_title&&u.push({name:"title",type:"textbox",label:"Image Title"}),t&&u.push({type:"container",label:"Dimensions",layout:"flex",direction:"row",align:"center",spacing:5,items:[{name:"width",type:"textbox",maxLength:5,size:3,onchange:e,ariaLabel:"Width"},{type:"label",text:"x"},{name:"height",type:"textbox",maxLength:5,size:3,onchange:e,ariaLabel:"Height"},{name:"constrain",type:"checkbox",checked:!0,text:"Constrain proportions"}]}),u.push(p),a.settings.image_advtab?(s&&(s.style.marginLeft&&s.style.marginRight&&s.style.marginLeft===s.style.marginRight&&(q.hspace=g(s.style.marginLeft)),s.style.marginTop&&s.style.marginBottom&&s.style.marginTop===s.style.marginBottom&&(q.vspace=g(s.style.marginTop)),s.style.borderWidth&&(q.border=g(s.style.borderWidth)),q.style=a.dom.serializeStyle(a.dom.parseStyle(a.dom.getAttrib(s,"style")))),l=a.windowManager.open({title:"Insert/edit image",data:q,bodyType:"tabpanel",body:[{title:"General",type:"form",items:u},{title:"Advanced",type:"form",pack:"start",items:[{label:"Style",name:"style",type:"textbox",onchange:k},{type:"form",layout:"grid",packV:"start",columns:2,padding:0,alignH:["left","right"],defaults:{type:"textbox",maxWidth:50,onchange:j},items:[{label:"Vertical space",name:"vspace"},{label:"Horizontal space",name:"hspace"},{label:"Border",name:"border"}]}]}],onSubmit:f})):l=a.windowManager.open({title:"Insert/edit image",data:q,body:u,onSubmit:f})}a.addButton("image",{icon:"image",tooltip:"Insert/edit image",onclick:d(e),stateSelector:"img:not([data-mce-object],[data-mce-placeholder])"}),a.addMenuItem("image",{icon:"image",text:"Insert/edit image",onclick:d(e),context:"insert",prependToContext:!0}),a.addCommand("mceImage",d(e))}); \ No newline at end of file +!function(){"use strict";var e=tinymce.util.Tools.resolve("tinymce.PluginManager"),d=function(e){return!1!==e.settings.image_dimensions},a=function(e){return!0===e.settings.image_advtab},f=function(e){return e.getParam("image_prepend_url","")},n=function(e){return e.getParam("image_class_list")},r=function(e){return!1!==e.settings.image_description},i=function(e){return!0===e.settings.image_title},o=function(e){return!0===e.settings.image_caption},l=function(e){return e.getParam("image_list",!1)},u=function(e){return e.getParam("images_upload_url",!1)},c=function(e){return e.getParam("images_upload_handler",!1)},s=function(e){return e.getParam("images_upload_url")},g=function(e){return e.getParam("images_upload_handler")},m=function(e){return e.getParam("images_upload_base_path")},p=function(e){return e.getParam("images_upload_credentials")},h="undefined"!=typeof window?window:Function("return this;")(),v=function(e,t){for(var n=t!==undefined&&null!==t?t:h,r=0;r max) { + value = max; + } else if (value < min) { + value = min; + } + return value; + } + function identity$1() { + return [ + 1, + 0, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 0, + 1 + ]; + } + var DELTA_INDEX = [ + 0, + 0.01, + 0.02, + 0.04, + 0.05, + 0.06, + 0.07, + 0.08, + 0.1, + 0.11, + 0.12, + 0.14, + 0.15, + 0.16, + 0.17, + 0.18, + 0.2, + 0.21, + 0.22, + 0.24, + 0.25, + 0.27, + 0.28, + 0.3, + 0.32, + 0.34, + 0.36, + 0.38, + 0.4, + 0.42, + 0.44, + 0.46, + 0.48, + 0.5, + 0.53, + 0.56, + 0.59, + 0.62, + 0.65, + 0.68, + 0.71, + 0.74, + 0.77, + 0.8, + 0.83, + 0.86, + 0.89, + 0.92, + 0.95, + 0.98, + 1, + 1.06, + 1.12, + 1.18, + 1.24, + 1.3, + 1.36, + 1.42, + 1.48, + 1.54, + 1.6, + 1.66, + 1.72, + 1.78, + 1.84, + 1.9, + 1.96, + 2, + 2.12, + 2.25, + 2.37, + 2.5, + 2.62, + 2.75, + 2.87, + 3, + 3.2, + 3.4, + 3.6, + 3.8, + 4, + 4.3, + 4.7, + 4.9, + 5, + 5.5, + 6, + 6.5, + 6.8, + 7, + 7.3, + 7.5, + 7.8, + 8, + 8.4, + 8.7, + 9, + 9.4, + 9.6, + 9.8, + 10 + ]; + function multiply(matrix1, matrix2) { + var i, j, k, val, col = [], out = new Array(10); + for (i = 0; i < 5; i++) { + for (j = 0; j < 5; j++) { + col[j] = matrix2[j + i * 5]; + } + for (j = 0; j < 5; j++) { + val = 0; + for (k = 0; k < 5; k++) { + val += matrix1[j + k * 5] * col[k]; + } + out[j + i * 5] = val; + } + } + return out; + } + function adjust(matrix, adjustValue) { + adjustValue = clamp(adjustValue, 0, 1); + return matrix.map(function (value, index) { + if (index % 6 === 0) { + value = 1 - (1 - value) * adjustValue; + } else { + value *= adjustValue; + } + return clamp(value, 0, 1); + }); + } + function adjustContrast(matrix, value) { + var x; + value = clamp(value, -1, 1); + value *= 100; + if (value < 0) { + x = 127 + value / 100 * 127; + } else { + x = value % 1; + if (x === 0) { + x = DELTA_INDEX[value]; + } else { + x = DELTA_INDEX[Math.floor(value)] * (1 - x) + DELTA_INDEX[Math.floor(value) + 1] * x; + } + x = x * 127 + 127; + } + return multiply(matrix, [ + x / 127, + 0, + 0, + 0, + 0.5 * (127 - x), + 0, + x / 127, + 0, + 0, + 0.5 * (127 - x), + 0, + 0, + x / 127, + 0, + 0.5 * (127 - x), + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 0, + 1 + ]); + } + function adjustSaturation(matrix, value) { + var x, lumR, lumG, lumB; + value = clamp(value, -1, 1); + x = 1 + (value > 0 ? 3 * value : value); + lumR = 0.3086; + lumG = 0.6094; + lumB = 0.082; + return multiply(matrix, [ + lumR * (1 - x) + x, + lumG * (1 - x), + lumB * (1 - x), + 0, + 0, + lumR * (1 - x), + lumG * (1 - x) + x, + lumB * (1 - x), + 0, + 0, + lumR * (1 - x), + lumG * (1 - x), + lumB * (1 - x) + x, + 0, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 0, + 1 + ]); + } + function adjustHue(matrix, angle) { + var cosVal, sinVal, lumR, lumG, lumB; + angle = clamp(angle, -180, 180) / 180 * Math.PI; + cosVal = Math.cos(angle); + sinVal = Math.sin(angle); + lumR = 0.213; + lumG = 0.715; + lumB = 0.072; + return multiply(matrix, [ + lumR + cosVal * (1 - lumR) + sinVal * -lumR, + lumG + cosVal * -lumG + sinVal * -lumG, + lumB + cosVal * -lumB + sinVal * (1 - lumB), + 0, + 0, + lumR + cosVal * -lumR + sinVal * 0.143, + lumG + cosVal * (1 - lumG) + sinVal * 0.14, + lumB + cosVal * -lumB + sinVal * -0.283, + 0, + 0, + lumR + cosVal * -lumR + sinVal * -(1 - lumR), + lumG + cosVal * -lumG + sinVal * lumG, + lumB + cosVal * (1 - lumB) + sinVal * lumB, + 0, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 0, + 1 + ]); + } + function adjustBrightness(matrix, value) { + value = clamp(255 * value, -255, 255); + return multiply(matrix, [ + 1, + 0, + 0, + 0, + value, + 0, + 1, + 0, + 0, + value, + 0, + 0, + 1, + 0, + value, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 0, + 1 + ]); + } + function adjustColors(matrix, adjustR, adjustG, adjustB) { + adjustR = clamp(adjustR, 0, 2); + adjustG = clamp(adjustG, 0, 2); + adjustB = clamp(adjustB, 0, 2); + return multiply(matrix, [ + adjustR, + 0, + 0, + 0, + 0, + 0, + adjustG, + 0, + 0, + 0, + 0, + 0, + adjustB, + 0, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 0, + 1 + ]); + } + function adjustSepia(matrix, value) { + value = clamp(value, 0, 1); + return multiply(matrix, adjust([ + 0.393, + 0.769, + 0.189, + 0, + 0, + 0.349, + 0.686, + 0.168, + 0, + 0, + 0.272, + 0.534, + 0.131, + 0, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 0, + 1 + ], value)); + } + function adjustGrayscale(matrix, value) { + value = clamp(value, 0, 1); + return multiply(matrix, adjust([ + 0.33, + 0.34, + 0.33, + 0, + 0, + 0.33, + 0.34, + 0.33, + 0, + 0, + 0.33, + 0.34, + 0.33, + 0, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 0, + 1 + ], value)); + } + var $_b6gk9ndpjh8lpuwv = { + identity: identity$1, + adjust: adjust, + multiply: multiply, + adjustContrast: adjustContrast, + adjustBrightness: adjustBrightness, + adjustSaturation: adjustSaturation, + adjustHue: adjustHue, + adjustColors: adjustColors, + adjustSepia: adjustSepia, + adjustGrayscale: adjustGrayscale + }; + + function colorFilter(ir, matrix) { + return ir.toCanvas().then(function (canvas) { + return applyColorFilter(canvas, ir.getType(), matrix); + }); + } + function applyColorFilter(canvas, type, matrix) { + var context = $_4vc6osdajh8lpuvr.get2dContext(canvas); + var pixels; + function applyMatrix(pixels, m) { + var d = pixels.data, r, g, b, a, i, m0 = m[0], m1 = m[1], m2 = m[2], m3 = m[3], m4 = m[4], m5 = m[5], m6 = m[6], m7 = m[7], m8 = m[8], m9 = m[9], m10 = m[10], m11 = m[11], m12 = m[12], m13 = m[13], m14 = m[14], m15 = m[15], m16 = m[16], m17 = m[17], m18 = m[18], m19 = m[19]; + for (i = 0; i < d.length; i += 4) { + r = d[i]; + g = d[i + 1]; + b = d[i + 2]; + a = d[i + 3]; + d[i] = r * m0 + g * m1 + b * m2 + a * m3 + m4; + d[i + 1] = r * m5 + g * m6 + b * m7 + a * m8 + m9; + d[i + 2] = r * m10 + g * m11 + b * m12 + a * m13 + m14; + d[i + 3] = r * m15 + g * m16 + b * m17 + a * m18 + m19; + } + return pixels; + } + pixels = applyMatrix(context.getImageData(0, 0, canvas.width, canvas.height), matrix); + context.putImageData(pixels, 0, 0); + return $_dhco8idojh8lpuwp.fromCanvas(canvas, type); + } + function convoluteFilter(ir, matrix) { + return ir.toCanvas().then(function (canvas) { + return applyConvoluteFilter(canvas, ir.getType(), matrix); + }); + } + function applyConvoluteFilter(canvas, type, matrix) { + var context = $_4vc6osdajh8lpuvr.get2dContext(canvas); + var pixelsIn, pixelsOut; + function applyMatrix(pixelsIn, pixelsOut, matrix) { + var rgba, drgba, side, halfSide, x, y, r, g, b, cx, cy, scx, scy, offset, wt, w, h; + function clamp(value, min, max) { + if (value > max) { + value = max; + } else if (value < min) { + value = min; + } + return value; + } + side = Math.round(Math.sqrt(matrix.length)); + halfSide = Math.floor(side / 2); + rgba = pixelsIn.data; + drgba = pixelsOut.data; + w = pixelsIn.width; + h = pixelsIn.height; + for (y = 0; y < h; y++) { + for (x = 0; x < w; x++) { + r = g = b = 0; + for (cy = 0; cy < side; cy++) { + for (cx = 0; cx < side; cx++) { + scx = clamp(x + cx - halfSide, 0, w - 1); + scy = clamp(y + cy - halfSide, 0, h - 1); + offset = (scy * w + scx) * 4; + wt = matrix[cy * side + cx]; + r += rgba[offset] * wt; + g += rgba[offset + 1] * wt; + b += rgba[offset + 2] * wt; + } + } + offset = (y * w + x) * 4; + drgba[offset] = clamp(r, 0, 255); + drgba[offset + 1] = clamp(g, 0, 255); + drgba[offset + 2] = clamp(b, 0, 255); + } + } + return pixelsOut; + } + pixelsIn = context.getImageData(0, 0, canvas.width, canvas.height); + pixelsOut = context.getImageData(0, 0, canvas.width, canvas.height); + pixelsOut = applyMatrix(pixelsIn, pixelsOut, matrix); + context.putImageData(pixelsOut, 0, 0); + return $_dhco8idojh8lpuwp.fromCanvas(canvas, type); + } + function functionColorFilter(colorFn) { + var filterImpl = function (canvas, type, value) { + var context = $_4vc6osdajh8lpuvr.get2dContext(canvas); + var pixels, i, lookup = new Array(256); + function applyLookup(pixels, lookup) { + var d = pixels.data, i; + for (i = 0; i < d.length; i += 4) { + d[i] = lookup[d[i]]; + d[i + 1] = lookup[d[i + 1]]; + d[i + 2] = lookup[d[i + 2]]; + } + return pixels; + } + for (i = 0; i < lookup.length; i++) { + lookup[i] = colorFn(i, value); + } + pixels = applyLookup(context.getImageData(0, 0, canvas.width, canvas.height), lookup); + context.putImageData(pixels, 0, 0); + return $_dhco8idojh8lpuwp.fromCanvas(canvas, type); + }; + return function (ir, value) { + return ir.toCanvas().then(function (canvas) { + return filterImpl(canvas, ir.getType(), value); + }); + }; + } + function complexAdjustableColorFilter(matrixAdjustFn) { + return function (ir, adjust) { + return colorFilter(ir, matrixAdjustFn($_b6gk9ndpjh8lpuwv.identity(), adjust)); + }; + } + function basicColorFilter(matrix) { + return function (ir) { + return colorFilter(ir, matrix); + }; + } + function basicConvolutionFilter(kernel) { + return function (ir) { + return convoluteFilter(ir, kernel); + }; + } + var $_e9vkk4dnjh8lpuwk = { + invert: basicColorFilter([ + -1, + 0, + 0, + 0, + 255, + 0, + -1, + 0, + 0, + 255, + 0, + 0, + -1, + 0, + 255, + 0, + 0, + 0, + 1, + 0 + ]), + brightness: complexAdjustableColorFilter($_b6gk9ndpjh8lpuwv.adjustBrightness), + hue: complexAdjustableColorFilter($_b6gk9ndpjh8lpuwv.adjustHue), + saturate: complexAdjustableColorFilter($_b6gk9ndpjh8lpuwv.adjustSaturation), + contrast: complexAdjustableColorFilter($_b6gk9ndpjh8lpuwv.adjustContrast), + grayscale: complexAdjustableColorFilter($_b6gk9ndpjh8lpuwv.adjustGrayscale), + sepia: complexAdjustableColorFilter($_b6gk9ndpjh8lpuwv.adjustSepia), + colorize: function (ir, adjustR, adjustG, adjustB) { + return colorFilter(ir, $_b6gk9ndpjh8lpuwv.adjustColors($_b6gk9ndpjh8lpuwv.identity(), adjustR, adjustG, adjustB)); + }, + sharpen: basicConvolutionFilter([ + 0, + -1, + 0, + -1, + 5, + -1, + 0, + -1, + 0 + ]), + emboss: basicConvolutionFilter([ + -2, + -1, + 0, + -1, + 1, + 1, + 0, + 1, + 2 + ]), + gamma: functionColorFilter(function (color, value) { + return Math.pow(color / 255, 1 - value) * 255; + }), + exposure: functionColorFilter(function (color, value) { + return 255 * (1 - Math.exp(-(color / 255) * value)); + }), + colorFilter: colorFilter, + convoluteFilter: convoluteFilter + }; + + function scale(image, dW, dH) { + var sW = $_7rqxocdbjh8lpuvt.getWidth(image); + var sH = $_7rqxocdbjh8lpuvt.getHeight(image); + var wRatio = dW / sW; + var hRatio = dH / sH; + var scaleCapped = false; + if (wRatio < 0.5 || wRatio > 2) { + wRatio = wRatio < 0.5 ? 0.5 : 2; + scaleCapped = true; + } + if (hRatio < 0.5 || hRatio > 2) { + hRatio = hRatio < 0.5 ? 0.5 : 2; + scaleCapped = true; + } + var scaled = _scale(image, wRatio, hRatio); + return !scaleCapped ? scaled : scaled.then(function (tCanvas) { + return scale(tCanvas, dW, dH); + }); + } + function _scale(image, wRatio, hRatio) { + return new Promise(function (resolve) { + var sW = $_7rqxocdbjh8lpuvt.getWidth(image); + var sH = $_7rqxocdbjh8lpuvt.getHeight(image); + var dW = Math.floor(sW * wRatio); + var dH = Math.floor(sH * hRatio); + var canvas = $_4vc6osdajh8lpuvr.create(dW, dH); + var context = $_4vc6osdajh8lpuvr.get2dContext(canvas); + context.drawImage(image, 0, 0, sW, sH, 0, 0, dW, dH); + resolve(canvas); + }); + } + var $_6kcnpsdrjh8lpux7 = { scale: scale }; + + function rotate(ir, angle) { + return ir.toCanvas().then(function (canvas) { + return applyRotate(canvas, ir.getType(), angle); + }); + } + function applyRotate(image, type, angle) { + var canvas = $_4vc6osdajh8lpuvr.create(image.width, image.height); + var context = $_4vc6osdajh8lpuvr.get2dContext(canvas); + var translateX = 0, translateY = 0; + angle = angle < 0 ? 360 + angle : angle; + if (angle == 90 || angle == 270) { + $_4vc6osdajh8lpuvr.resize(canvas, canvas.height, canvas.width); + } + if (angle == 90 || angle == 180) { + translateX = canvas.width; + } + if (angle == 270 || angle == 180) { + translateY = canvas.height; + } + context.translate(translateX, translateY); + context.rotate(angle * Math.PI / 180); + context.drawImage(image, 0, 0); + return $_dhco8idojh8lpuwp.fromCanvas(canvas, type); + } + function flip(ir, axis) { + return ir.toCanvas().then(function (canvas) { + return applyFlip(canvas, ir.getType(), axis); + }); + } + function applyFlip(image, type, axis) { + var canvas = $_4vc6osdajh8lpuvr.create(image.width, image.height); + var context = $_4vc6osdajh8lpuvr.get2dContext(canvas); + if (axis == 'v') { + context.scale(1, -1); + context.drawImage(image, 0, -canvas.height); + } else { + context.scale(-1, 1); + context.drawImage(image, -canvas.width, 0); + } + return $_dhco8idojh8lpuwp.fromCanvas(canvas, type); + } + function crop(ir, x, y, w, h) { + return ir.toCanvas().then(function (canvas) { + return applyCrop(canvas, ir.getType(), x, y, w, h); + }); + } + function applyCrop(image, type, x, y, w, h) { + var canvas = $_4vc6osdajh8lpuvr.create(w, h); + var context = $_4vc6osdajh8lpuvr.get2dContext(canvas); + context.drawImage(image, -x, -y); + return $_dhco8idojh8lpuwp.fromCanvas(canvas, type); + } + function resize$1(ir, w, h) { + return ir.toCanvas().then(function (canvas) { + return $_6kcnpsdrjh8lpux7.scale(canvas, w, h).then(function (newCanvas) { + return $_dhco8idojh8lpuwp.fromCanvas(newCanvas, ir.getType()); + }); + }); + } + var $_9v2i8fdqjh8lpux3 = { + rotate: rotate, + flip: flip, + crop: crop, + resize: resize$1 + }; + + var invert = function (ir) { + return $_e9vkk4dnjh8lpuwk.invert(ir); + }; + var sharpen = function (ir) { + return $_e9vkk4dnjh8lpuwk.sharpen(ir); + }; + var emboss = function (ir) { + return $_e9vkk4dnjh8lpuwk.emboss(ir); + }; + var gamma = function (ir, value) { + return $_e9vkk4dnjh8lpuwk.gamma(ir, value); + }; + var exposure = function (ir, value) { + return $_e9vkk4dnjh8lpuwk.exposure(ir, value); + }; + var colorize = function (ir, adjustR, adjustG, adjustB) { + return $_e9vkk4dnjh8lpuwk.colorize(ir, adjustR, adjustG, adjustB); + }; + var brightness = function (ir, adjust) { + return $_e9vkk4dnjh8lpuwk.brightness(ir, adjust); + }; + var hue = function (ir, adjust) { + return $_e9vkk4dnjh8lpuwk.hue(ir, adjust); + }; + var saturate = function (ir, adjust) { + return $_e9vkk4dnjh8lpuwk.saturate(ir, adjust); + }; + var contrast = function (ir, adjust) { + return $_e9vkk4dnjh8lpuwk.contrast(ir, adjust); + }; + var grayscale = function (ir, adjust) { + return $_e9vkk4dnjh8lpuwk.grayscale(ir, adjust); + }; + var sepia = function (ir, adjust) { + return $_e9vkk4dnjh8lpuwk.sepia(ir, adjust); + }; + var flip$1 = function (ir, axis) { + return $_9v2i8fdqjh8lpux3.flip(ir, axis); + }; + var crop$1 = function (ir, x, y, w, h) { + return $_9v2i8fdqjh8lpux3.crop(ir, x, y, w, h); + }; + var resize$2 = function (ir, w, h) { + return $_9v2i8fdqjh8lpux3.resize(ir, w, h); + }; + var rotate$1 = function (ir, angle) { + return $_9v2i8fdqjh8lpux3.rotate(ir, angle); + }; + var $_c3vl36dmjh8lpuwe = { + invert: invert, + sharpen: sharpen, + emboss: emboss, + brightness: brightness, + hue: hue, + saturate: saturate, + contrast: contrast, + grayscale: grayscale, + sepia: sepia, + colorize: colorize, + gamma: gamma, + exposure: exposure, + flip: flip$1, + crop: crop$1, + resize: resize$2, + rotate: rotate$1 + }; + + var blobToImageResult = function (blob) { + return $_dhco8idojh8lpuwp.fromBlob(blob); + }; + var fromBlobAndUrlSync$1 = function (blob, uri) { + return $_dhco8idojh8lpuwp.fromBlobAndUrlSync(blob, uri); + }; + var imageToImageResult = function (image) { + return $_dhco8idojh8lpuwp.fromImage(image); + }; + var imageResultToBlob = function (ir, type, quality) { + if (type === undefined && quality === undefined) { + return imageResultToOriginalBlob(ir); + } else { + return ir.toAdjustedBlob(type, quality); + } + }; + var imageResultToOriginalBlob = function (ir) { + return ir.toBlob(); + }; + var imageResultToDataURL = function (ir) { + return ir.toDataURL(); + }; + var $_b87414dsjh8lpuxa = { + blobToImageResult: blobToImageResult, + fromBlobAndUrlSync: fromBlobAndUrlSync$1, + imageToImageResult: imageToImageResult, + imageResultToBlob: imageResultToBlob, + imageResultToOriginalBlob: imageResultToOriginalBlob, + imageResultToDataURL: imageResultToDataURL + }; + + var url = function () { + return $_9n34kadgjh8lpuw6.getOrDie('URL'); + }; + var createObjectURL = function (blob) { + return url().createObjectURL(blob); + }; + var revokeObjectURL = function (u) { + url().revokeObjectURL(u); + }; + var $_8zh2u6dtjh8lpuxb = { + createObjectURL: createObjectURL, + revokeObjectURL: revokeObjectURL + }; + + var global$3 = tinymce.util.Tools.resolve('tinymce.util.Delay'); + + var global$4 = tinymce.util.Tools.resolve('tinymce.util.Promise'); + + var global$5 = tinymce.util.Tools.resolve('tinymce.util.URI'); + + var getToolbarItems = function (editor) { + return editor.getParam('imagetools_toolbar', 'rotateleft rotateright | flipv fliph | crop editimage imageoptions'); + }; + var getProxyUrl = function (editor) { + return editor.getParam('imagetools_proxy'); + }; + var getCorsHosts = function (editor) { + return editor.getParam('imagetools_cors_hosts', [], 'string[]'); + }; + var getCredentialsHosts = function (editor) { + return editor.getParam('imagetools_credentials_hosts', [], 'string[]'); + }; + var getApiKey = function (editor) { + return editor.getParam('api_key', editor.getParam('imagetools_api_key', '', 'string'), 'string'); + }; + var getUploadTimeout = function (editor) { + return editor.getParam('images_upload_timeout', 30000, 'number'); + }; + var shouldReuseFilename = function (editor) { + return editor.getParam('images_reuse_filename', false, 'boolean'); + }; + + var global$6 = tinymce.util.Tools.resolve('tinymce.dom.DOMUtils'); + + var global$7 = tinymce.util.Tools.resolve('tinymce.ui.Factory'); + + function UndoStack () { + var data = []; + var index = -1; + function add(state) { + var removed; + removed = data.splice(++index); + data.push(state); + return { + state: state, + removed: removed + }; + } + function undo() { + if (canUndo()) { + return data[--index]; + } + } + function redo() { + if (canRedo()) { + return data[++index]; + } + } + function canUndo() { + return index > 0; + } + function canRedo() { + return index !== -1 && index < data.length - 1; + } + return { + data: data, + add: add, + undo: undo, + redo: redo, + canUndo: canUndo, + canRedo: canRedo + }; + } + + var global$8 = tinymce.util.Tools.resolve('tinymce.geom.Rect'); + + var loadImage$1 = function (image) { + return new global$4(function (resolve) { + var loaded = function () { + image.removeEventListener('load', loaded); + resolve(image); + }; + if (image.complete) { + resolve(image); + } else { + image.addEventListener('load', loaded); + } + }); + }; + var $_7riwd1e4jh8lpuy1 = { loadImage: loadImage$1 }; + + var global$9 = tinymce.util.Tools.resolve('tinymce.dom.DomQuery'); + + var global$10 = tinymce.util.Tools.resolve('tinymce.util.Observable'); + + var global$11 = tinymce.util.Tools.resolve('tinymce.util.VK'); + + var count = 0; + function CropRect (currentRect, viewPortRect, clampRect, containerElm, action) { + var instance; + var handles; + var dragHelpers; + var blockers; + var prefix = 'mce-'; + var id = prefix + 'crid-' + count++; + handles = [ + { + name: 'move', + xMul: 0, + yMul: 0, + deltaX: 1, + deltaY: 1, + deltaW: 0, + deltaH: 0, + label: 'Crop Mask' + }, + { + name: 'nw', + xMul: 0, + yMul: 0, + deltaX: 1, + deltaY: 1, + deltaW: -1, + deltaH: -1, + label: 'Top Left Crop Handle' + }, + { + name: 'ne', + xMul: 1, + yMul: 0, + deltaX: 0, + deltaY: 1, + deltaW: 1, + deltaH: -1, + label: 'Top Right Crop Handle' + }, + { + name: 'sw', + xMul: 0, + yMul: 1, + deltaX: 1, + deltaY: 0, + deltaW: -1, + deltaH: 1, + label: 'Bottom Left Crop Handle' + }, + { + name: 'se', + xMul: 1, + yMul: 1, + deltaX: 0, + deltaY: 0, + deltaW: 1, + deltaH: 1, + label: 'Bottom Right Crop Handle' + } + ]; + blockers = [ + 'top', + 'right', + 'bottom', + 'left' + ]; + function getAbsoluteRect(outerRect, relativeRect) { + return { + x: relativeRect.x + outerRect.x, + y: relativeRect.y + outerRect.y, + w: relativeRect.w, + h: relativeRect.h + }; + } + function getRelativeRect(outerRect, innerRect) { + return { + x: innerRect.x - outerRect.x, + y: innerRect.y - outerRect.y, + w: innerRect.w, + h: innerRect.h + }; + } + function getInnerRect() { + return getRelativeRect(clampRect, currentRect); + } + function moveRect(handle, startRect, deltaX, deltaY) { + var x, y, w, h, rect; + x = startRect.x; + y = startRect.y; + w = startRect.w; + h = startRect.h; + x += deltaX * handle.deltaX; + y += deltaY * handle.deltaY; + w += deltaX * handle.deltaW; + h += deltaY * handle.deltaH; + if (w < 20) { + w = 20; + } + if (h < 20) { + h = 20; + } + rect = currentRect = global$8.clamp({ + x: x, + y: y, + w: w, + h: h + }, clampRect, handle.name === 'move'); + rect = getRelativeRect(clampRect, rect); + instance.fire('updateRect', { rect: rect }); + setInnerRect(rect); + } + function render() { + function createDragHelper(handle) { + var startRect; + var DragHelper = global$7.get('DragHelper'); + return new DragHelper(id, { + document: containerElm.ownerDocument, + handle: id + '-' + handle.name, + start: function () { + startRect = currentRect; + }, + drag: function (e) { + moveRect(handle, startRect, e.deltaX, e.deltaY); + } + }); + } + global$9('
    ').appendTo(containerElm); + global$1.each(blockers, function (blocker) { + global$9('#' + id, containerElm).append('