Port v7@2aa0dfb2c5 - WIP
This commit is contained in:
+12
-5
@@ -1,10 +1,17 @@
|
||||
root=true
|
||||
# editorconfig.org
|
||||
|
||||
# top-most EditorConfig file
|
||||
root = true
|
||||
|
||||
# Default settings:
|
||||
# A newline ending every file
|
||||
# Use 4 spaces as indentation
|
||||
[*]
|
||||
end_of_line = crlf
|
||||
insert_final_newline = true
|
||||
end_of_line = lf
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.{cs,cshtml,csx,vb,vbx,vbhtml,fs,fsx,txt,ps1,sql}]
|
||||
indent_size = 4
|
||||
# Trim trailing whitespace, limited support.
|
||||
# https://github.com/editorconfig/editorconfig/wiki/Property-research:-Trim-trailing-spaces
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
*.doc diff=astextplain
|
||||
*.DOC diff=astextplain
|
||||
*.docx diff=astextplain
|
||||
*.DOCX diff=astextplain
|
||||
*.dot diff=astextplain
|
||||
*.DOT diff=astextplain
|
||||
*.pdf diff=astextplain
|
||||
*.PDF diff=astextplain
|
||||
*.rtf diff=astextplain
|
||||
*.RTF diff=astextplain
|
||||
|
||||
*.jpg binary
|
||||
*.png binary
|
||||
*.gif binary
|
||||
|
||||
*.cs text=auto diff=csharp
|
||||
*.vb text=auto
|
||||
*.c text=auto
|
||||
*.cpp text=auto
|
||||
*.cxx text=auto
|
||||
*.h text=auto
|
||||
*.hxx text=auto
|
||||
*.py text=auto
|
||||
*.rb text=auto
|
||||
*.java text=auto
|
||||
*.html text=auto
|
||||
*.htm text=auto
|
||||
*.css text=auto
|
||||
*.scss text=auto
|
||||
*.sass text=auto
|
||||
*.less text=auto
|
||||
*.js text=auto
|
||||
*.lisp text=auto
|
||||
*.clj text=auto
|
||||
*.sql text=auto
|
||||
*.php text=auto
|
||||
*.lua text=auto
|
||||
*.m text=auto
|
||||
*.asm text=auto
|
||||
*.erl text=auto
|
||||
*.fs text=auto
|
||||
*.fsx text=auto
|
||||
*.hs text=auto
|
||||
|
||||
*.csproj text=auto merge=union
|
||||
*.vbproj text=auto merge=union
|
||||
*.fsproj text=auto merge=union
|
||||
*.dbproj text=auto merge=union
|
||||
*.sln text=auto eol=crlf merge=union
|
||||
+2
-1
@@ -136,6 +136,7 @@ src/Umbraco.Tests/media
|
||||
tools/docfx/*
|
||||
apidocs/_site/*
|
||||
src/*/project.lock.json
|
||||
src/.idea/*
|
||||
|
||||
apidocs/api/*
|
||||
build/docs.zip
|
||||
@@ -149,4 +150,4 @@ src/PrecompiledWeb/*
|
||||
build.out/
|
||||
build.tmp/
|
||||
build/hooks/
|
||||
build/temp/
|
||||
build/temp/
|
||||
|
||||
@@ -14,6 +14,43 @@ By default, this builds the current version. It is possible to specify a differe
|
||||
|
||||
Valid version strings are defined in the `Set-UmbracoVersion` documentation below.
|
||||
|
||||
## PowerShell Quirks
|
||||
|
||||
There is a good chance that running `build.ps1` ends up in error, with messages such as
|
||||
|
||||
>The file ...\build\build.ps1 is not digitally signed. You cannot run this script on the current system. For more information about running scripts and setting execution policy, see about_Execution_Policies.
|
||||
|
||||
PowerShell has *Execution Policies* that may prevent the script from running. You can check the current policies with:
|
||||
|
||||
PS> Get-ExecutionPolicy -List
|
||||
|
||||
Scope ExecutionPolicy
|
||||
----- ---------------
|
||||
MachinePolicy Undefined
|
||||
UserPolicy Undefined
|
||||
Process Undefined
|
||||
CurrentUser Undefined
|
||||
LocalMachine RemoteSigned
|
||||
|
||||
Policies can be `Restricted`, `AllSigned`, `RemoteSigned`, `Unrestricted` and `Bypass`. Scopes can be `MachinePolicy`, `UserPolicy`, `Process`, `CurrentUser`, `LocalMachine`. You need the current policy to be `RemoteSigned`—as long as it is `Undefined`, the script cannot run. You can change the current user policy with:
|
||||
|
||||
PS> Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned
|
||||
|
||||
Alternatively, you can do it at machine level, from within an elevated PowerShell session:
|
||||
|
||||
PS> Set-ExecutionPolicy -Scope LocalMachine -ExecutionPolicy RemoteSigned
|
||||
|
||||
And *then* the script should run. It *might* however still complain about executing scripts, with messages such as:
|
||||
|
||||
>Security warning - Run only scripts that you trust. While scripts from the internet can be useful, this script can potentially harm your computer. If you trust this script, use the Unblock-File cmdlet to allow the script to run without this warning message. Do you want to run ...\build\build.ps1?
|
||||
[D] Do not run [R] Run once [S] Suspend [?] Help (default is "D"):
|
||||
|
||||
This is usually caused by the scripts being *blocked*. And that usually happens when the source code has been downloaded as a Zip file. When Windows downloads Zip files, they are marked as *blocked* (technically, they have a Zone.Identifier alternate data stream, with a value of "3" to indicate that they were downloaded from the Internet). And when such a Zip file is un-zipped, each and every single file is also marked as blocked.
|
||||
|
||||
The best solution is to unblock the Zip file before un-zipping: right-click the files, open *Properties*, and there should be a *Unblock* checkbox at the bottom of the dialog. If, however, the Zip file has already been un-zipped, it is possible to recursively unblock all files from PowerShell with:
|
||||
|
||||
PS> Get-ChildItem -Recurse *.* | Unblock-File
|
||||
|
||||
## Notes
|
||||
|
||||
Git might have issues dealing with long file paths during build. You may want/need to enable `core.longpaths` support (see [this page](https://github.com/msysgit/msysgit/wiki/Git-cannot-create-a-file-or-directory-with-a-long-path) for details).
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
# Code of Conduct
|
||||
|
||||
## 1. Purpose
|
||||
|
||||
A primary goal of Umbraco CMS is to be inclusive to the largest number of contributors, with the most varied and diverse backgrounds possible. As such, we are committed to providing a friendly, safe and welcoming environment for all, regardless of gender, sexual orientation, ability, ethnicity, socioeconomic status, and religion (or lack thereof).
|
||||
|
||||
This code of conduct outlines our expectations for all those who participate in our community, as well as the consequences for unacceptable behavior.
|
||||
|
||||
We invite all those who participate in Umbraco CMS to help us create safe and positive experiences for everyone.
|
||||
|
||||
## 2. Open Source Citizenship
|
||||
|
||||
A supplemental goal of this Code of Conduct is to increase open source citizenship by encouraging participants to recognize and strengthen the relationships between our actions and their effects on our community.
|
||||
|
||||
Communities mirror the societies in which they exist and positive action is essential to counteract the many forms of inequality and abuses of power that exist in society.
|
||||
|
||||
If you see someone who is making an extra effort to ensure our community is welcoming, friendly, and encourages all participants to contribute to the fullest extent, we want to know.
|
||||
|
||||
## 3. Expected Behavior
|
||||
|
||||
The following behaviors are expected and requested of all community members:
|
||||
|
||||
* Participate in an authentic and active way. In doing so, you contribute to the health and longevity of this community.
|
||||
* Exercise consideration and respect in your speech and actions.
|
||||
* Attempt collaboration before conflict.
|
||||
* Refrain from demeaning, discriminatory, or harassing behavior and speech.
|
||||
* Be mindful of your surroundings and of your fellow participants. Alert community leaders if you notice a dangerous situation, someone in distress, or violations of this Code of Conduct, even if they seem inconsequential.
|
||||
* Remember that community event venues may be shared with members of the public; please be respectful to all patrons of these locations.
|
||||
|
||||
## 4. Unacceptable Behavior
|
||||
|
||||
The following behaviors are considered harassment and are unacceptable within our community:
|
||||
|
||||
* Violence, threats of violence or violent language directed against another person.
|
||||
* Sexist, racist, homophobic, transphobic, ableist or otherwise discriminatory jokes and language.
|
||||
* Posting or displaying sexually explicit or violent material.
|
||||
* Posting or threatening to post other people’s personally identifying information ("doxing").
|
||||
* Personal insults, particularly those related to gender, sexual orientation, race, religion, or disability.
|
||||
* Inappropriate photography or recording.
|
||||
* Inappropriate physical contact. You should have someone’s consent before touching them.
|
||||
* Unwelcome sexual attention. This includes, sexualized comments or jokes; inappropriate touching, groping, and unwelcomed sexual advances.
|
||||
* Deliberate intimidation, stalking or following (online or in person).
|
||||
* Advocating for, or encouraging, any of the above behavior.
|
||||
* Sustained disruption of community events, including talks and presentations.
|
||||
|
||||
## 5. Consequences of Unacceptable Behavior
|
||||
|
||||
Unacceptable behavior from any community member, including sponsors and those with decision-making authority, will not be tolerated.
|
||||
|
||||
Anyone asked to stop unacceptable behavior is expected to comply immediately.
|
||||
|
||||
If a community member engages in unacceptable behavior, the community organizers may take any action they deem appropriate, up to and including a temporary ban or permanent expulsion from the community without warning (and without refund in the case of a paid event).
|
||||
|
||||
## 6. Reporting Guidelines
|
||||
|
||||
If you are subject to or witness unacceptable behavior, or have any other concerns, please notify a community organizer as soon as possible. Please contact Sebastiaan Janssen - [sj@umbraco.dk](mailto:sj@umbraco.dk).
|
||||
|
||||
Additionally, community organizers are available to help community members engage with local law enforcement or to otherwise help those experiencing unacceptable behavior feel safe. In the context of in-person events, organizers will also provide escorts as desired by the person experiencing distress.
|
||||
|
||||
## 7. Addressing Grievances
|
||||
|
||||
If you feel you have been falsely or unfairly accused of violating this Code of Conduct, you should notify Umbraco with a concise description of your grievance. Your grievance will be handled in accordance with our existing governing policies.
|
||||
|
||||
## 8. Scope
|
||||
|
||||
We expect all community participants (contributors, paid or otherwise; sponsors; and other guests) to abide by this Code of Conduct in all community venues–online and in-person–as well as in all one-on-one communications pertaining to community business.
|
||||
|
||||
This code of conduct and its related procedures also applies to unacceptable behavior occurring outside the scope of community activities when such behavior has the potential to adversely affect the safety and well-being of community members.
|
||||
|
||||
## 9. Contact info
|
||||
|
||||
Sebastiaan Janssen - [sj@umbraco.dk](mailto:sj@umbraco.dk)
|
||||
|
||||
## 10. License and attribution
|
||||
|
||||
This Code of Conduct is distributed under a [Creative Commons Attribution-ShareAlike license](http://creativecommons.org/licenses/by-sa/3.0/).
|
||||
|
||||
Portions of text derived from the [Django Code of Conduct](https://www.djangoproject.com/conduct/) and the [Geek Feminism Anti-Harassment Policy](http://geekfeminism.wikia.com/wiki/Conference_anti-harassment/Policy).
|
||||
|
||||
Retrieved on November 22, 2016 from [http://citizencodeofconduct.org/](http://citizencodeofconduct.org/)
|
||||
+207
@@ -0,0 +1,207 @@
|
||||
# Contributing to Umbraco CMS
|
||||
|
||||
👍🎉 First off, thanks for taking the time to contribute! 🎉👍
|
||||
|
||||
The following is a set of guidelines for contributing to Umbraco CMS.
|
||||
|
||||
These are mostly guidelines, not rules. Use your best judgment, and feel free to propose changes to this document in a pull request.
|
||||
|
||||
Remember, we're a friendly bunch and are happy with whatever contribution you might provide. Below are guidelines for success that we've gathered over the years. If you choose to ignore them then we still love you 💖.
|
||||
|
||||
#### Table Of Contents
|
||||
|
||||
[Code of Conduct](#code-of-conduct)
|
||||
|
||||
[How Can I Contribute?](#how-can-i-contribute)
|
||||
* [Reporting Bugs](#reporting-bugs)
|
||||
* [Suggesting Enhancements](#suggesting-enhancements)
|
||||
* [Your First Code Contribution](#your-first-code-contribution)
|
||||
* [Pull Requests](#pull-requests)
|
||||
|
||||
[Styleguides](#styleguides)
|
||||
|
||||
[What should I know before I get started?](#what-should-i-know-before-i-get-started)
|
||||
* [Working with the source code](#working-with-the-source-code)
|
||||
* [What branch should I target for my contributions?](#what-branch-should-i-target-for-my-contributions)
|
||||
* [Building Umbraco from source code](#building-umbraco-from-source-code)
|
||||
* [Keeping your Umbraco fork in sync with the main repository](#keeping-your-umbraco-fork-in-sync-with-the-main-repository)
|
||||
|
||||
[How do I even begin?](#how-do-i-even-begin)
|
||||
|
||||
[Problems?](#problems)
|
||||
|
||||
[Credits](#credits)
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
This project and everyone participating in it is governed by the [our Code of Conduct](CODE_OF_CONDUCT.md). By participating, you are expected to uphold this code. Please report unacceptable behavior to [Sebastiaan Janssen - sj@umbraco.dk](mailto:sj@umbraco.dk).
|
||||
|
||||
## How Can I Contribute?
|
||||
|
||||
### Reporting Bugs
|
||||
This section guides you through submitting a bug report for Umbraco CMS. Following these guidelines helps maintainers and the community understand your report 📝, reproduce the behavior 💻 💻, and find related reports 🔎.
|
||||
|
||||
Before creating bug reports, please check [this list](#before-submitting-a-bug-report) as you might find out that you don't need to create one. When you are creating a bug report, please [include as many details as possible](#how-do-i-submit-a-good-bug-report). Fill out [the required template](ISSUE_TEMPLATE.md), the information it asks for helps us resolve issues faster.
|
||||
|
||||
> **Note:** If you find a **Closed** issue that seems like it is the same thing that you're experiencing, open a new issue and include a link to the original issue in the body of your new one.
|
||||
|
||||
##### Before Submitting A Bug Report
|
||||
|
||||
* Most importantly, check **if you can reproduce the problem** in the [latest version of Umbraco](https://our.umbraco.org/download/). We might have already fixed your particular problem.
|
||||
* It also helps tremendously to check if the issue you're experiencing is present in **a clean install** of the Umbraco version you're currently using. Custom code can have side-effects that don't occur in a clean install.
|
||||
* **Use the Google**. Whatever you're experiencing, Google it plus "Umbraco" - usually you can get some pretty good hints from the search results, including open issues and further troubleshooting hints.
|
||||
* If you do find and existing issue has **and the issue is still open**, add a comment to the existing issue if you have additional information. If you have the same problem and no new info to add, just "star" the issue.
|
||||
|
||||
Explain the problem and include additional details to help maintainers reproduce the problem. The following is a long description which we've boiled down into a few very simple question in the issue tracker when you create a new issue. We're listing the following hints to indicate that the most successful reports usually have a lot of this ground covered:
|
||||
|
||||
* **Use a clear and descriptive title** for the issue to identify the problem.
|
||||
* **Describe the exact steps which reproduce the problem** in as many details as possible. For example, start by explaining which steps you took in the backoffice to get to a certain undesireable result, e.g. you created a document type, inherting 3 levels deep, added a certain datatype, tried to save it and you got an error.
|
||||
* **Provide specific examples to demonstrate the steps**. If you wrote some code, try to provide a code sample as specific as possible to be able to reproduce the behavior.
|
||||
* **Describe the behavior you observed after following the steps** and point out what exactly is the problem with that behavior.
|
||||
* **Explain which behavior you expected to see instead and why.**
|
||||
|
||||
Provide more context by answering these questions:
|
||||
|
||||
* **Can you reproduce the problem** when `debug="false"` in your `web.config` file?
|
||||
* **Did the problem start happening recently** (e.g. after updating to a new version of Umbraco) or was this always a problem?
|
||||
* **Can you reliably reproduce the issue?** If not, provide details about how often the problem happens and under which conditions it normally happens.
|
||||
|
||||
Include details about your configuration and environment:
|
||||
|
||||
* **Which version of Umbraco are you using?**
|
||||
* **What is the environment you're using Umbraco in?** Is this a problem on your local machine or on a server. Tell us about your configuration: Windows version, IIS/IISExpress, database type, etc.
|
||||
* **Which packages do you have installed?**
|
||||
|
||||
### Suggesting Enhancements
|
||||
|
||||
This section guides you through submitting an enhancement suggestion for Atom, including completely new features and minor improvements to existing functionality. Following these guidelines helps maintainers and the community understand your suggestion 📝 and find related suggestions 🔎.
|
||||
|
||||
Most of the suggestions in the [reporting bugs](#reporting-bugs) section also count for suggesting enhancements.
|
||||
|
||||
Some additional hints that may be helpful:
|
||||
|
||||
* **Include screenshots and animated GIFs** which help you demonstrate the steps or point out the part of Umbraco which the suggestion is related to.
|
||||
* **Explain why this enhancement would be useful to most Umbraco users** and isn't something that can or should be implemented as a [community package](https://our.umbraco.org/projects/).
|
||||
|
||||
### Your First Code Contribution
|
||||
|
||||
Unsure where to begin contributing to Umbraco? You can start by looking through [these `Up for grabs` and issues](http://issues.umbraco.org/issues/U4?q=%28project%3A+%7BU4%7D+Difficulty%3A+%7BVery+Easy%7D+%23Easy+%23Unresolved+Priority%3A+Normal+%23Major+%23Show-stopper+State%3A+-%7BIn+Progress%7D+sort+by%3A+votes+Affected+versions%3A+-6.*+Affected+versions%3A+-4.*%29+OR+%28tag%3A+%7BUp+For+Grabs%7D+%23Unresolved+%29).
|
||||
|
||||
The issue list is sorted by total number of upvotes. While not perfect, number of upvotes is a reasonable proxy for impact a given change will have.
|
||||
|
||||
### Pull Requests
|
||||
|
||||
The most successful pull requests usually look a like this:
|
||||
|
||||
* Fill in the required template
|
||||
* Include screenshots and animated GIFs in your pull request whenever possible.
|
||||
* Unit tests, while optional are awesome, thank you!
|
||||
* New code is commented with documentation from which [the reference documentation](https://our.umbraco.org/documentation/Reference/) is generated
|
||||
|
||||
Again, these are guidelines, not strict requirements.
|
||||
|
||||
## Styleguides
|
||||
|
||||
To be honest, we don't like rules very much. We trust you have the best of intentions and we encourage you to create working code. If it doesn't look perfect then we'll happily help clean it up.
|
||||
|
||||
That said, the Umbraco development team likes to follow the hints that ReSharper gives us (no problem if you don't have this installed) and we've added a `.editorconfig` file so that Visual Studio knows what to do with whitespace, line endings, etc.
|
||||
|
||||
## What should I know before I get started?
|
||||
|
||||
### Working with the source code
|
||||
|
||||
Some parts of our source code is over 10 years old now. And when we say "old", we mean "mature" of course!
|
||||
|
||||
There's two big areas that you should know about:
|
||||
|
||||
1. The Umbraco backoffice is a extensible AngularJS app and requires you to run a `gulp dev` command while you're working with it, so changes are copied over to the appropriate directories and you can refresh your browser to view the results of your changes.
|
||||
You may need to run the following commands to set up gulp properly:
|
||||
```
|
||||
npm cache clean
|
||||
npm install -g bower
|
||||
npm install -g gulp
|
||||
npm install -g gulp-cli
|
||||
npm install
|
||||
gulp build
|
||||
```
|
||||
2. "The rest" is a C# based codebase, with some traces of our WebForms past but mostly ASP.NET MVC based these days. You can make changes, build them in Visual Studio, and hit `F5` to see the result.
|
||||
|
||||
To find the general areas of something you're looking to fix or improve, have a look at the following two parts of the API documentation.
|
||||
|
||||
* [The AngularJS based backoffice files](https://our.umbraco.org/apidocs/ui/#/api) (to be found in `src\Umbraco.Web.UI.Client\src`)
|
||||
* [The rest](https://our.umbraco.org/apidocs/csharp/)
|
||||
|
||||
### What branch should I target for my contributions?
|
||||
|
||||
We like to use [Gitflow as much as possible](https://jeffkreeftmeijer.com/git-flow/), don't worry if you are not familiar with it. The most important thing you need to know is that when you fork the Umbraco repository, the default branch is set to something, usually `dev-v7`. Whatever the default is, that's where we'd like you to target your contributions.
|
||||
|
||||

|
||||
|
||||
### Building Umbraco from source code
|
||||
|
||||
The easiest way to get started is to run `build.bat` which will build both the backoffice (also known as "Belle") and the Umbraco core. You can then easily start debugging from Visual Studio, or if you need to debug Belle you can run `gulp dev` in `src\Umbraco.Web.UI.Client`. See [this page](BUILD.md) for more details.
|
||||
|
||||
Alternatively, you can open `src\umbraco.sln` in Visual Studio 2017 ([the community edition is free](https://www.visualstudio.com/thank-you-downloading-visual-studio/?sku=Community&rel=15) for you to use to contribute to Open Source projects). In Visual Studio, find the Task Runner Explorer (in the View menu under Other Windows) and run the build task under the gulpfile.
|
||||
|
||||

|
||||
|
||||
After this build completes, you should be able to hit `F5` in Visual Studio to build and run the project. A IISExpress webserver will start and the Umbraco installer will pop up in your browser, follow the directions there to get a working Umbraco install up and running.
|
||||
|
||||
### Keeping your Umbraco fork in sync with the main repository
|
||||
|
||||
We recommend you sync with our repository before you submit your pull request. That way, you can fix any potential merge conflicts and make our lives a little bit easier.
|
||||
|
||||
Also, if you've submitted a pull request three weeks ago and want to work on something new, you'll want to get the latest code to build against of course.
|
||||
|
||||
To sync your fork with this original one, you'll have to add the upstream url, you only have to do this once:
|
||||
|
||||
```
|
||||
git remote add upstream https://github.com/umbraco/Umbraco-CMS.git
|
||||
```
|
||||
|
||||
Then when you want to get the changes from the main repository:
|
||||
|
||||
```
|
||||
git fetch upstream
|
||||
git rebase upstream/dev-v7
|
||||
```
|
||||
|
||||
In this command we're syncing with the `dev-v7` branch, but you can of course choose another one if needed.
|
||||
|
||||
(More info on how this works: [http://robots.thoughtbot.com/post/5133345960/keeping-a-git-fork-updated](http://robots.thoughtbot.com/post/5133345960/keeping-a-git-fork-updated))
|
||||
|
||||
## How do I even begin?
|
||||
|
||||
Great question! The short version goes like this:
|
||||
|
||||
* **Fork** - create a fork of [`Umbraco-CMS` on GitHub](https://github.com/umbraco/Umbraco-CMS)
|
||||
|
||||

|
||||
|
||||
* **Clone** - when GitHub has created your fork, you can clone it in your favorite Git tool
|
||||
|
||||

|
||||
|
||||
* **Build** - build your fork of Umbraco locally as described in [building Umbraco from source code](#building-umbraco-from-source-code)
|
||||
* **Change** - make your changes, experiment, have fun, explore and learn, and don't be afraid. We welcome all contributions and will happily give feedback
|
||||
* **Commit** - done? Yay! 🎉 It is recommended to create a new branch now and name it after the issue you're fixing, we usually follow the format: `temp-U4-12345`. This means it's a temporary branch for the particular issue you're working on, in this case `U4-12345`
|
||||
* **Push** - great, now you can push the changes up to your fork on GitHub
|
||||
* **Create pull request** - exciting! You're ready to show us your changes (or not quite ready, you just need some feedback to progress). GitHub has picked up on the new branch you've pushed and will offer to create a Pull Request. Click that green button and away you go.
|
||||
|
||||

|
||||
|
||||
The Umbraco development team can now start reviewing your proposed changes and give you feedback on them. If it's not perfect, we'll either fix up what we need or we can request you to make some additional changes.
|
||||
|
||||
If you make the corrections we ask for in the same branch and push them to your fork again, the pull request automatically updates with the additional commit(s) so we can review it again. If all is well, we'll merge the code and your commits are forever part of Umbraco!
|
||||
|
||||
Not all changes are wanted so on occassion we might close a PR without merging it. We will give you feedback why we can't accept your changes at this and we'll be nice about it, thanking you for spending your valueable time.
|
||||
|
||||
Remember, if an issue is in the `Up for grabs` list or you've asked for some feedback before you send us a PR, your PR will not be closed as unwanted.
|
||||
|
||||
## Problems?
|
||||
|
||||
Did something not work as expected? Try leaving a note in the ["Contributing to Umbraco"](https://our.umbraco.org/forum/contributing-to-umbraco-cms/) forum, the team monitors that one closely!
|
||||
|
||||
## Credits
|
||||
|
||||
This contribution guide borrows heavily from the excellent work on [the Atom contribution guide](https://github.com/atom/atom/blob/master/CONTRIBUTING.md). A big [#h5yr](http://h5yr.com/) to them!
|
||||
@@ -0,0 +1,11 @@
|
||||
### Prerequisites
|
||||
|
||||
- [ ] I have written a descriptive pull-request title
|
||||
- [ ] I have linked this PR to an issue on the tracker at http://issues.umbraco.org
|
||||
|
||||
### Description
|
||||
<!-- A description of the changes proposed in the pull-request -->
|
||||
|
||||
|
||||
|
||||
<!-- Thanks for contributing to Umbraco CMS! -->
|
||||
@@ -1,49 +1,44 @@
|
||||
Umbraco CMS
|
||||
===========
|
||||
The friendliest, most flexible and fastest growing ASP.NET CMS used by more than 350,000 websites worldwide: [https://umbraco.com](https://umbraco.com)
|
||||
The friendliest, most flexible and fastest growing ASP.NET CMS used by more than 443,000 websites worldwide: [https://umbraco.com](https://umbraco.com)
|
||||
|
||||
[](https://vimeo.com/172382998/)
|
||||
|
||||
## Umbraco CMS ##
|
||||
## Umbraco CMS
|
||||
Umbraco is a free open source Content Management System built on the ASP.NET platform. Our mission is to help you deliver delightful digital experiences by making Umbraco friendly, simpler and social.
|
||||
|
||||
|
||||
## Building Umbraco from source ##
|
||||
|
||||
The easiest way to get started is to run `build/build.bat` which will build both the backoffice (also known as "Belle") and the Umbraco core. You can then easily start debugging from Visual Studio, or if you need to debug Belle you can run `gulp dev` in `src\Umbraco.Web.UI.Client`.
|
||||
|
||||
Note that you can always [download a nightly build](http://nightly.umbraco.org/?container=umbraco-750) so you don't have to build the code yourself.
|
||||
|
||||
## Watch an introduction video ##
|
||||
## Watch an introduction video
|
||||
|
||||
[](https://umbraco.tv/videos/umbraco-v7/content-editor/basics/introduction/cms-explanation/)
|
||||
|
||||
## Umbraco - The Friendly CMS ##
|
||||
## Umbraco - The Friendly CMS
|
||||
|
||||
For the first time on the Microsoft platform, there is a free user and developer friendly CMS that makes it quick and easy to create websites - or a breeze to build complex web applications. Umbraco has award-winning integration capabilities and supports ASP.NET MVC or Web Forms, including User and Custom Controls, out of the box.
|
||||
|
||||
Umbraco is not only loved by developers, but is a content editors dream. Enjoy intuitive editing tools, media management, responsive views and approval workflows to send your content live.
|
||||
|
||||
Used by more than 350,000 active websites including Carlsberg, Segway, Amazon and Heinz and **The Official ASP.NET and IIS.NET website from Microsoft** ([https://asp.net](https://asp.net) / [https://iis.net](https://iis.net)), you can be sure that the technology is proven, stable and scales. Backed by the team at Umbraco HQ, and supported by a dedicated community of over 200,000 craftspeople globally, you can trust that Umbraco is a safe choice and is here to stay.
|
||||
Used by more than 443,000 active websites including Carlsberg, Segway, Amazon and Heinz and **The Official ASP.NET and IIS.NET website from Microsoft** ([https://asp.net](https://asp.net) / [https://iis.net](https://iis.net)), you can be sure that the technology is proven, stable and scales. Backed by the team at Umbraco HQ, and supported by a dedicated community of over 220,000 craftspeople globally, you can trust that Umbraco is a safe choice and is here to stay.
|
||||
|
||||
To view more examples, please visit [https://umbraco.com/why-umbraco/#caseStudies](https://umbraco.com/why-umbraco/#caseStudies)
|
||||
|
||||
## Why Open Source? ##
|
||||
## Why Open Source?
|
||||
As an Open Source platform, Umbraco is more than just a CMS. We are transparent with our roadmap for future versions, our incremental sprint planning notes are publicly accessible and community contributions and packages are available for all to use.
|
||||
|
||||
## Downloading ##
|
||||
## Trying out Umbraco CMS
|
||||
|
||||
The downloadable Umbraco releases live at [https://our.umbraco.org/download](https://our.umbraco.org/download).
|
||||
[Umbraco Cloud](https://umbraco.com) is the easiest and fastest way to use Umbraco yet with full support for all your custom .NET code and intergrations. You're up and running in less than a minute and your life will be made easier with automated upgrades and a built-in deployment engine. We offer a free 14 day trial, no credit card needed.
|
||||
|
||||
## Forums ##
|
||||
If you want to DIY you can [download Umbraco](https://our.umbraco.org/download) either as a ZIP file or via NuGet. It's the same version of Umbraco CMS that powers Umbraco Xloud, but you'll need to find a place to host yourself and handling deployments and upgrades is all down to you.
|
||||
|
||||
Peer-to-peer support is available 24/7 at the community forum on [https://our.umbraco.org](https://our.umbraco.org).
|
||||
## Community
|
||||
|
||||
## Contribute to Umbraco ##
|
||||
Our friendly community is available 24/7 at the community hub we call ["Our Umbraco"](https://our.umbraco.org). Our Umbraco feature forums for questions and answers, documentation, downloadable plugins for Umbraco and a rich collection of community resources.
|
||||
|
||||
Umbraco is contribution focused and community driven. If you want to contribute back to Umbraco please check out our [guide to contributing](https://our.umbraco.org/contribute).
|
||||
## Contribute to Umbraco
|
||||
|
||||
## Found a bug? ##
|
||||
Umbraco is contribution focused and community driven. If you want to contribute back to Umbraco please check out our [guide to contributing](CONTRIBUTING.md).
|
||||
|
||||
## Found a bug?
|
||||
|
||||
Another way you can contribute to Umbraco is by providing issue reports. For information on how to submit an issue report refer to our [online guide for reporting issues](https://our.umbraco.org/contribute/report-an-issue-or-request-a-feature).
|
||||
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
Param(
|
||||
[string]$GitHubPersonalAccessToken,
|
||||
[string]$Directory
|
||||
)
|
||||
$workingDirectory = $Directory
|
||||
CD $workingDirectory
|
||||
|
||||
# Clone repo
|
||||
$fullGitUrl = "https://$env:GIT_URL/$env:GIT_REPOSITORYNAME.git"
|
||||
git clone $fullGitUrl 2>&1 | % { $_.ToString() }
|
||||
|
||||
# Remove everything so that unzipping the release later will update everything
|
||||
# Don't remove the readme file nor the git directory
|
||||
Write-Host "Cleaning up git directory before adding new version"
|
||||
Remove-Item -Recurse $workingDirectory\$env:GIT_REPOSITORYNAME\* -Exclude README.md,.git
|
||||
|
||||
# Find release zip
|
||||
$zipsDir = "$workingDirectory\$env:BUILD_DEFINITIONNAME\zips"
|
||||
$pattern = "UmbracoCms.([0-9]{1,2}.[0-9]{1,3}.[0-9]{1,3}).zip"
|
||||
Write-Host "Searching for Umbraco release files in $workingDirectory\$zipsDir for a file with pattern $pattern"
|
||||
$file = (Get-ChildItem $zipsDir | Where-Object { $_.Name -match "$pattern" })
|
||||
|
||||
if($file)
|
||||
{
|
||||
# Get release name
|
||||
$version = [regex]::Match($file.Name, $pattern).captures.groups[1].value
|
||||
$releaseName = "Umbraco $version"
|
||||
Write-Host "Found $releaseName"
|
||||
|
||||
# Unzip into repository to update release
|
||||
Add-Type -AssemblyName System.IO.Compression.FileSystem
|
||||
Write-Host "Unzipping $($file.FullName) to $workingDirectory\$env:GIT_REPOSITORYNAME"
|
||||
[System.IO.Compression.ZipFile]::ExtractToDirectory("$($file.FullName)", "$workingDirectory\$env:GIT_REPOSITORYNAME")
|
||||
|
||||
# Telling git who we are
|
||||
git config --global user.email "coffee@umbraco.com" 2>&1 | % { $_.ToString() }
|
||||
git config --global user.name "Umbraco HQ" 2>&1 | % { $_.ToString() }
|
||||
|
||||
# Commit
|
||||
CD $env:GIT_REPOSITORYNAME
|
||||
Write-Host "Committing Umbraco $version Release from Build Output"
|
||||
|
||||
git add . 2>&1 | % { $_.ToString() }
|
||||
git commit -m " Release $releaseName from Build Output" 2>&1 | % { $_.ToString() }
|
||||
|
||||
# Tag the release
|
||||
git tag -a "v$version" -m "v$version"
|
||||
|
||||
# Push release to master
|
||||
$fullGitAuthUrl = "https://$($env:GIT_USERNAME):$GitHubPersonalAccessToken@$env:GIT_URL/$env:GIT_REPOSITORYNAME.git"
|
||||
git push $fullGitAuthUrl 2>&1 | % { $_.ToString() }
|
||||
|
||||
#Push tag to master
|
||||
git push $fullGitAuthUrl --tags 2>&1 | % { $_.ToString() }
|
||||
}
|
||||
else
|
||||
{
|
||||
Write-Error "Umbraco release file not found, searched in $workingDirectory\$zipsDir for a file with pattern $pattern - cancelling"
|
||||
}
|
||||
@@ -24,10 +24,8 @@
|
||||
<dependency id="MiniProfiler" version="[3.2.0.157, 4.0.0)" />
|
||||
<dependency id="HtmlAgilityPack" version="[1.5.1, 2.0.0)" />
|
||||
<dependency id="Lucene.Net" version="[3.0.3, 4.0.0.0)" />
|
||||
<dependency id="SharpZipLib" version="[0.86.0, 1.0.0)" />
|
||||
<dependency id="MySql.Data" version="[6.9.9, 7.0.0)" />
|
||||
<dependency id="xmlrpcnet" version="[3.0.0.266, 4.0.0)" />
|
||||
<dependency id="ClientDependency" version="[1.9.2, 2.0.0)" />
|
||||
<dependency id="ClientDependency" version="[1.9.6, 2.0.0)" />
|
||||
<dependency id="ClientDependency-Mvc5" version="[1.8.0.0, 2.0.0)" />
|
||||
<dependency id="AutoMapper" version="[6.1.1, 7.0.0)" />
|
||||
<dependency id="LightInject" version="[5.0.3, 6.0.0)" />
|
||||
@@ -36,8 +34,8 @@
|
||||
<dependency id="LightInject.WebApi" version="[2.0.0, 3.0.0)" />
|
||||
<dependency id="Newtonsoft.Json" version="[10.0.3, 11.0.0)" />
|
||||
<dependency id="Examine" version="[2.0.0-beta2, 3.0.0)" />
|
||||
<dependency id="ImageProcessor" version="[2.5.4, 3.0.0)" />
|
||||
<dependency id="ImageProcessor.Web" version="[4.8.4, 5.0.0)" />
|
||||
<dependency id="ImageProcessor" version="[2.5.6, 3.0.0)" />
|
||||
<dependency id="ImageProcessor.Web" version="[4.8.7, 5.0.0)" />
|
||||
<dependency id="semver" version="[2.0.4, 3.0.0)" />
|
||||
<dependency id="Markdown" version="[2.2.1, 3.0.0)" />
|
||||
<dependency id="log4net" version="[2.0.8, 3.0.0)" />
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
<file src="$BuildTmp$\WebApp\bin\amd64\**" target="UmbracoFiles\bin\amd64" />
|
||||
<file src="$BuildTmp$\WebApp\bin\x86\**" target="UmbracoFiles\bin\x86" />
|
||||
<file src="$BuildTmp$\WebApp\config\splashes\**" target="UmbracoFiles\Config\splashes" />
|
||||
<file src="$BuildTmp$\WebApp\config\BackOfficeTours\**" target="Content\Config\BackOfficeTours" />
|
||||
<file src="$BuildTmp$\WebApp\umbraco\**" target="UmbracoFiles\umbraco" />
|
||||
<file src="$BuildTmp$\WebApp\umbraco_client\**" target="UmbracoFiles\umbraco_client" />
|
||||
<file src="$BuildTmp$\WebApp\Media\Web.config" target="Content\Media\Web.config" />
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
----------------------------------------------------
|
||||
|
||||
*** IMPORTANT NOTICE FOR 7.7 UPGRADES ***
|
||||
*** IMPORTANT NOTICE FOR UPGRADES FROM VERSIONS BELOW 7.7.0 ***
|
||||
|
||||
Be sure to read the version specific upgrade information before proceeding:
|
||||
https://our.umbraco.org/documentation/Getting-Started/Setup/Upgrading/version-specific#version-7-7-0
|
||||
|
||||
@@ -98,10 +98,51 @@ if ($project) {
|
||||
$umbracoUIXMLDestination = Join-Path $projectPath "Umbraco\Config\Create\UI.xml"
|
||||
Copy-Item $umbracoUIXMLSource $umbracoUIXMLDestination -Force
|
||||
} else {
|
||||
# This part only runs for upgrades
|
||||
|
||||
$upgradeViewSource = Join-Path $umbracoFolderSource "Views\install\*"
|
||||
$upgradeView = Join-Path $umbracoFolder "Views\install\"
|
||||
Write-Host "Copying2 ${upgradeViewSource} to ${upgradeView}"
|
||||
Copy-Item $upgradeViewSource $upgradeView -Force
|
||||
|
||||
Try
|
||||
{
|
||||
# Disable tours for upgrades, presumably Umbraco experience is already available
|
||||
$umbracoSettingsConfigPath = Join-Path $configFolder "umbracoSettings.config"
|
||||
$content = (Get-Content $umbracoSettingsConfigPath).Replace('<tours enable="true">','<tours enable="false">')
|
||||
# Saves with UTF-8 encoding without BOM which makes sure Umbraco can still read it
|
||||
# Reference: https://stackoverflow.com/a/32951824/5018
|
||||
[IO.File]::WriteAllLines($umbracoSettingsConfigPath, $content)
|
||||
}
|
||||
Catch
|
||||
{
|
||||
# Not a big problem if this fails, let it go
|
||||
}
|
||||
|
||||
Try
|
||||
{
|
||||
$uiXmlConfigPath = Join-Path $umbracoFolder -ChildPath "Config" | Join-Path -ChildPath "create" | Join-Path -ChildPath "UI.xml"
|
||||
$uiXmlFile = Join-Path $umbracoFolder -ChildPath "Config" | Join-Path -ChildPath "create" | Join-Path -ChildPath "UI.xml"
|
||||
|
||||
$uiXml = New-Object System.Xml.XmlDocument
|
||||
$uiXml.PreserveWhitespace = $true
|
||||
|
||||
$uiXml.Load($uiXmlFile)
|
||||
$createExists = $uiXml.SelectNodes("//nodeType[@alias='macros']/tasks/create")
|
||||
|
||||
if($createExists.Count -eq 0)
|
||||
{
|
||||
$macrosTasksNode = $uiXml.SelectNodes("//nodeType[@alias='macros']/tasks")
|
||||
|
||||
#Creating: <create assembly="umbraco" type="macroTasks" />
|
||||
$createNode = $uiXml.CreateElement("create")
|
||||
$createNode.SetAttribute("assembly", "umbraco")
|
||||
$createNode.SetAttribute("type", "macroTasks")
|
||||
$macrosTasksNode.AppendChild($createNode)
|
||||
$uiXml.Save($uiXmlFile)
|
||||
}
|
||||
}
|
||||
Catch { }
|
||||
}
|
||||
|
||||
$installFolder = Join-Path $projectPath "Install"
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
xdt:Locator="Match(application,alias)"
|
||||
xdt:Transform="InsertIfMissing" />
|
||||
|
||||
<add application="settings" alias="scripts" title="Scripts" type="umbraco.loadScripts, umbraco" iconClosed="icon-folder" iconOpen="icon-folder" sortOrder="4"
|
||||
<add application="settings" alias="scripts" title="Scripts" type="Umbraco.Web.Trees.ScriptTreeController, umbraco" iconClosed="icon-folder" iconOpen="icon-folder" sortOrder="4"
|
||||
xdt:Locator="Match(application,alias)"
|
||||
xdt:Transform="SetAttributes()" />
|
||||
<add application="settings" alias="dictionary" title="Dictionary" type="umbraco.loadDictionary, umbraco" iconClosed="icon-folder" iconOpen="icon-folder" sortOrder="6"
|
||||
@@ -79,14 +79,14 @@
|
||||
<add sortOrder="1" alias="dataTypes" application="developer"
|
||||
xdt:Locator="Match(application,alias)"
|
||||
xdt:Transform="SetAttributes(sortOrder)" />
|
||||
|
||||
<add application="developer" alias="macros" title="Macros" type="umbraco.loadMacros, umbraco" iconClosed="icon-folder" iconOpen="icon-folder" sortOrder="2"
|
||||
|
||||
<add initialize="true" sortOrder="2" alias="macros" application="developer" iconClosed="icon-folder" iconOpen="icon-folder-open" type="Umbraco.Web.Trees.MacroTreeController, umbraco"
|
||||
xdt:Locator="Match(application,alias)"
|
||||
xdt:Transform="SetAttributes()" />
|
||||
<add application="developer" alias="relationTypes" title="Relation Types" type="umbraco.loadRelationTypes, umbraco" iconClosed="icon-folder" iconOpen="icon-folder" sortOrder="4"
|
||||
xdt:Locator="Match(application,alias)"
|
||||
xdt:Transform="SetAttributes()" />
|
||||
<add application="developer" alias="xslt" title="XSLT Files" type="umbraco.loadXslt, umbraco" iconClosed="icon-folder" iconOpen="icon-folder" sortOrder="5"
|
||||
<add initialize="true" sortOrder="5" alias="xslt" application="developer" iconClosed="icon-folder" iconOpen="icon-folder-open" type="Umbraco.Web.Trees.XsltTreeController, umbraco"
|
||||
xdt:Locator="Match(application,alias)"
|
||||
xdt:Transform="SetAttributes()" />
|
||||
|
||||
|
||||
+10
-22
@@ -328,15 +328,7 @@
|
||||
$this.CopyFiles("$src\Umbraco.Web.UI\umbraco\js", "*", "$tmp\WebApp\umbraco\js")
|
||||
$this.CopyFiles("$src\Umbraco.Web.UI\umbraco\lib", "*", "$tmp\WebApp\umbraco\lib")
|
||||
$this.CopyFiles("$src\Umbraco.Web.UI\umbraco\views", "*", "$tmp\WebApp\umbraco\views")
|
||||
$this.CopyFiles("$src\Umbraco.Web.UI\umbraco\preview", "*", "$tmp\WebApp\umbraco\preview")
|
||||
|
||||
# prepare WebPI
|
||||
Write-Host "Prepare WebPI"
|
||||
$this.RemoveDirectory("$tmp\WebPi")
|
||||
mkdir "$tmp\WebPi" > $null
|
||||
mkdir "$tmp\WebPi\umbraco" > $null
|
||||
$this.CopyFiles("$tmp\WebApp", "*", "$tmp\WebPi\umbraco")
|
||||
$this.CopyFiles("$src\WebPi", "*", "$tmp\WebPi")
|
||||
$this.CopyFiles("$src\Umbraco.Web.UI\umbraco\preview", "*", "$tmp\WebApp\umbraco\preview")
|
||||
})
|
||||
|
||||
$ubuild.DefineMethod("PackageZip",
|
||||
@@ -359,19 +351,7 @@
|
||||
"$tmp\WebApp\*" `
|
||||
"-x!dotless.Core.*" "-x!Content_Types.xml" "-x!*.pdb" "-x!Umbraco.Compat7.*" `
|
||||
> $null
|
||||
if (-not $?) { throw "Failed to zip UmbracoCms." }
|
||||
|
||||
Write-Host "Zip WebPI"
|
||||
&$this.BuildEnv.Zip a -r "$out\UmbracoCms.WebPI.$($this.Version.Semver).zip" "-x!*.pdb" `
|
||||
"$tmp\WebPi\*" `
|
||||
"-x!dotless.Core.*" "-x!Umbraco.Compat7.*" `
|
||||
> $null
|
||||
if (-not $?) { throw "Failed to zip UmbracoCms.WebPI." }
|
||||
|
||||
# hash the webpi file
|
||||
Write-Host "Hash WebPI"
|
||||
$hash = $this.GetFileHash("$out\UmbracoCms.WebPI.$($this.Version.Semver).zip")
|
||||
Write-Output $hash | out-file "$out\webpihash.txt" -encoding ascii
|
||||
if (-not $?) { throw "Failed to zip UmbracoCms." }
|
||||
})
|
||||
|
||||
$ubuild.DefineMethod("PrepareBuild",
|
||||
@@ -450,6 +430,12 @@
|
||||
if ($this.OnError()) { return }
|
||||
})
|
||||
|
||||
$ubuild.DefineMethod("PrepareAzureGallery",
|
||||
{
|
||||
Write-Host "Prepare Azure Gallery"
|
||||
$this.CopyFile("$($this.SolutionRoot)\build\Azure\azuregalleryrelease.ps1", $this.BuildOutput)
|
||||
})
|
||||
|
||||
$ubuild.DefineMethod("Build",
|
||||
{
|
||||
$this.PrepareBuild()
|
||||
@@ -475,6 +461,8 @@
|
||||
if ($this.OnError()) { return }
|
||||
$this.PackageNuGet()
|
||||
if ($this.OnError()) { return }
|
||||
$this.PrepareAzureGallery()
|
||||
if ($this.OnError()) { return }
|
||||
})
|
||||
|
||||
# ################################################################
|
||||
|
||||
+1
-1
@@ -2,6 +2,6 @@
|
||||
<configuration>
|
||||
<packageSources>
|
||||
<add key="nuget.org" value="https://www.nuget.org/api/v2/" />
|
||||
<add key="umbracocore" value="http://www.myget.org/f/umbracocore/" />
|
||||
<add key="umbracocore" value="https://www.myget.org/F/umbracocore/api/v3/index.json" />
|
||||
</packageSources>
|
||||
</configuration>
|
||||
@@ -26,63 +26,48 @@
|
||||
"jquery-migrate": "1.4.0",
|
||||
"angular-dynamic-locale": "0.1.28",
|
||||
"ng-file-upload": "~7.3.8",
|
||||
"tinymce": "~4.5.3",
|
||||
"tinymce": "~4.7.1",
|
||||
"codemirror": "~5.3.0",
|
||||
"angular-local-storage": "~0.2.3",
|
||||
"moment": "~2.10.3",
|
||||
"ace-builds": "^1.2.3",
|
||||
"font-awesome": "~4.2",
|
||||
"clipboard": "1.7.1"
|
||||
"clipboard": "1.7.1",
|
||||
"font-awesome": "~4.2"
|
||||
},
|
||||
|
||||
"install": {
|
||||
|
||||
"path": "lib-bower",
|
||||
|
||||
"ignore": [
|
||||
"font-awesome",
|
||||
"angular",
|
||||
"bootstrap",
|
||||
"codemirror"
|
||||
],
|
||||
|
||||
"sources": {
|
||||
"moment": "bower_components/moment/min/moment-with-locales.js",
|
||||
|
||||
"underscore": [
|
||||
"bower_components/underscore/underscore-min.js",
|
||||
"bower_components/underscore/underscore-min.map"
|
||||
],
|
||||
|
||||
"jquery": [
|
||||
"bower_components/jquery/dist/jquery.min.js",
|
||||
"bower_components/jquery/dist/jquery.min.map"
|
||||
],
|
||||
|
||||
"angular-dynamic-locale": [
|
||||
"bower_components/angular-dynamic-locale/tmhDynamicLocale.min.js",
|
||||
"bower_components/angular-dynamic-locale/tmhDynamicLocale.min.js.map"
|
||||
],
|
||||
|
||||
"angular-local-storage": [
|
||||
"bower_components/angular-local-storage/dist/angular-local-storage.min.js",
|
||||
"bower_components/angular-local-storage/dist/angular-local-storage.min.js.map"
|
||||
],
|
||||
|
||||
"tinymce": [
|
||||
"bower_components/tinymce/tinymce.min.js"
|
||||
],
|
||||
|
||||
"typeahead.js": "bower_components/typeahead.js/dist/typeahead.bundle.min.js",
|
||||
|
||||
"rgrove-lazyload":"bower_components/rgrove-lazyload/lazyload.js",
|
||||
|
||||
"ng-file-upload":"bower_components/ng-file-upload/ng-file-upload.min.js",
|
||||
|
||||
"jquery-ui":"bower_components/jquery-ui/jquery-ui.min.js",
|
||||
|
||||
"jquery-migrate":"bower_components/jquery-migrate/jquery-migrate.min.js",
|
||||
|
||||
"rgrove-lazyload": "bower_components/rgrove-lazyload/lazyload.js",
|
||||
"ng-file-upload": "bower_components/ng-file-upload/ng-file-upload.min.js",
|
||||
"jquery-ui": "bower_components/jquery-ui/jquery-ui.min.js",
|
||||
"jquery-migrate": "bower_components/jquery-migrate/jquery-migrate.min.js",
|
||||
"clipboard": "bower_components/clipboard/dist/clipboard.min.js"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,15 +31,17 @@
|
||||
display: block;
|
||||
padding: 4px;
|
||||
line-height: @baseLineHeight;
|
||||
border: 1px solid #ddd;
|
||||
border: 1px solid @gray-8;
|
||||
.border-radius(@baseBorderRadius);
|
||||
.box-shadow(0 1px 3px rgba(0,0,0,.055));
|
||||
.transition(all .2s ease-in-out);
|
||||
}
|
||||
// Add a hover/focus state for linked versions only
|
||||
a.thumbnail:hover,
|
||||
a.thumbnail:focus {
|
||||
border-color: @linkColor;
|
||||
// Add a hover/focus state for linked versions only.
|
||||
a.thumbnail:hover,
|
||||
a.thumbnail:focus,
|
||||
a div.thumbnail:hover,
|
||||
a div.thumbnail:focus {
|
||||
border-color: @turquoise;
|
||||
.box-shadow(0 1px 4px rgba(0,105,214,.25));
|
||||
}
|
||||
|
||||
|
||||
@@ -350,11 +350,29 @@ Umbraco.Sys.registerNamespace("Umbraco.Application");
|
||||
rootScope : function(){
|
||||
return getRootScope();
|
||||
},
|
||||
|
||||
reloadLocation: function() {
|
||||
var injector = getRootInjector();
|
||||
var $route = injector.get("$route");
|
||||
$route.reload();
|
||||
|
||||
/**
|
||||
This will reload the content frame based on it's current route, if pathToMatch is specified it will only reload it if the current
|
||||
location matches the path
|
||||
*/
|
||||
reloadLocation: function(pathToMatch) {
|
||||
|
||||
var injector = getRootInjector();
|
||||
var doChange = true;
|
||||
if (pathToMatch) {
|
||||
var $location = injector.get("$location");
|
||||
var path = $location.path();
|
||||
if (path != pathToMatch) {
|
||||
doChange = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (doChange) {
|
||||
var $route = injector.get("$route");
|
||||
$route.reload();
|
||||
var $rootScope = injector.get("$rootScope");
|
||||
$rootScope.$apply();
|
||||
}
|
||||
},
|
||||
|
||||
closeModalWindow: function(rVal) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -7,12 +7,22 @@ var app = angular.module("Umbraco.canvasdesigner", ['colorpicker', 'ui.slider',
|
||||
|
||||
.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 = {};
|
||||
$scope.pageId = $location.search().id;
|
||||
$scope.pageUrl = "../dialogs/Preview.aspx?id=" + $location.search().id;
|
||||
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" },
|
||||
|
||||
+116
@@ -0,0 +1,116 @@
|
||||
(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("umbBackdrop", BackdropDirective);
|
||||
|
||||
})();
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
/**
|
||||
@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.
|
||||
|
||||
<h3>Markup example - how to open the drawer</h3>
|
||||
<pre>
|
||||
<div ng-controller="My.DrawerController as vm">
|
||||
|
||||
<umb-button
|
||||
type="button"
|
||||
label="Toggle drawer"
|
||||
action="vm.toggleDrawer()">
|
||||
</umb-button>
|
||||
|
||||
</div>
|
||||
</pre>
|
||||
|
||||
<h3>Controller example - how to open the drawer</h3>
|
||||
<pre>
|
||||
(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);
|
||||
|
||||
})();
|
||||
</pre>
|
||||
|
||||
<h3>Use the following components in the custom drawer to render the content</h3>
|
||||
<ul>
|
||||
<li>{@link umbraco.directives.directive:umbDrawerView umbDrawerView}</li>
|
||||
<li>{@link umbraco.directives.directive:umbDrawerHeader umbDrawerHeader}</li>
|
||||
<li>{@link umbraco.directives.directive:umbDrawerView umbDrawerContent}</li>
|
||||
<li>{@link umbraco.directives.directive:umbDrawerFooter umbDrawerFooter}</li>
|
||||
</ul>
|
||||
|
||||
@param {string} view (<code>binding</code>): Set the drawer view
|
||||
@param {string} model (<code>binding</code>): 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);
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
@ngdoc directive
|
||||
@name umbraco.directives.directive:umbDrawerContent
|
||||
@restrict E
|
||||
@scope
|
||||
|
||||
@description
|
||||
Use this directive to render drawer content
|
||||
|
||||
<h3>Markup example</h3>
|
||||
<pre>
|
||||
<umb-drawer-view>
|
||||
|
||||
<umb-drawer-header
|
||||
title="Drawer Title"
|
||||
description="Drawer description">
|
||||
</umb-drawer-header>
|
||||
|
||||
<umb-drawer-content>
|
||||
<!-- Your content here -->
|
||||
<pre>{{ model | json }}</pre>
|
||||
</umb-drawer-content>
|
||||
|
||||
<umb-drawer-footer>
|
||||
<!-- Your content here -->
|
||||
</umb-drawer-footer>
|
||||
|
||||
</umb-drawer-view>
|
||||
</pre>
|
||||
|
||||
|
||||
<h3>Use in combination with</h3>
|
||||
<ul>
|
||||
<li>{@link umbraco.directives.directive:umbDrawerView umbDrawerView}</li>
|
||||
<li>{@link umbraco.directives.directive:umbDrawerHeader umbDrawerHeader}</li>
|
||||
<li>{@link umbraco.directives.directive:umbDrawerFooter umbDrawerFooter}</li>
|
||||
</ul>
|
||||
|
||||
**/
|
||||
|
||||
(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);
|
||||
|
||||
})();
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
@ngdoc directive
|
||||
@name umbraco.directives.directive:umbDrawerFooter
|
||||
@restrict E
|
||||
@scope
|
||||
|
||||
@description
|
||||
Use this directive to render a drawer footer
|
||||
|
||||
<h3>Markup example</h3>
|
||||
<pre>
|
||||
<umb-drawer-view>
|
||||
|
||||
<umb-drawer-header
|
||||
title="Drawer Title"
|
||||
description="Drawer description">
|
||||
</umb-drawer-header>
|
||||
|
||||
<umb-drawer-content>
|
||||
<!-- Your content here -->
|
||||
<pre>{{ model | json }}</pre>
|
||||
</umb-drawer-content>
|
||||
|
||||
<umb-drawer-footer>
|
||||
<!-- Your content here -->
|
||||
</umb-drawer-footer>
|
||||
|
||||
</umb-drawer-view>
|
||||
</pre>
|
||||
|
||||
<h3>Use in combination with</h3>
|
||||
<ul>
|
||||
<li>{@link umbraco.directives.directive:umbDrawerView umbDrawerView}</li>
|
||||
<li>{@link umbraco.directives.directive:umbDrawerHeader umbDrawerHeader}</li>
|
||||
<li>{@link umbraco.directives.directive:umbDrawerContent umbDrawerContent}</li>
|
||||
</ul>
|
||||
|
||||
**/
|
||||
|
||||
(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);
|
||||
|
||||
})();
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
/**
|
||||
@ngdoc directive
|
||||
@name umbraco.directives.directive:umbDrawerHeader
|
||||
@restrict E
|
||||
@scope
|
||||
|
||||
@description
|
||||
Use this directive to render a drawer header
|
||||
|
||||
<h3>Markup example</h3>
|
||||
<pre>
|
||||
<umb-drawer-view>
|
||||
|
||||
<umb-drawer-header
|
||||
title="Drawer Title"
|
||||
description="Drawer description">
|
||||
</umb-drawer-header>
|
||||
|
||||
<umb-drawer-content>
|
||||
<!-- Your content here -->
|
||||
<pre>{{ model | json }}</pre>
|
||||
</umb-drawer-content>
|
||||
|
||||
<umb-drawer-footer>
|
||||
<!-- Your content here -->
|
||||
</umb-drawer-footer>
|
||||
|
||||
</umb-drawer-view>
|
||||
</pre>
|
||||
|
||||
<h3>Use in combination with</h3>
|
||||
<ul>
|
||||
<li>{@link umbraco.directives.directive:umbDrawerView umbDrawerView}</li>
|
||||
<li>{@link umbraco.directives.directive:umbDrawerContent umbDrawerContent}</li>
|
||||
<li>{@link umbraco.directives.directive:umbDrawerFooter umbDrawerFooter}</li>
|
||||
</ul>
|
||||
|
||||
@param {string} title (<code>attribute</code>): Set a drawer title.
|
||||
@param {string} description (<code>attribute</code>): 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);
|
||||
|
||||
})();
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
@ngdoc directive
|
||||
@name umbraco.directives.directive:umbDrawerView
|
||||
@restrict E
|
||||
@scope
|
||||
|
||||
@description
|
||||
Use this directive to render drawer view
|
||||
|
||||
<h3>Markup example</h3>
|
||||
<pre>
|
||||
<umb-drawer-view>
|
||||
|
||||
<umb-drawer-header
|
||||
title="Drawer Title"
|
||||
description="Drawer description">
|
||||
</umb-drawer-header>
|
||||
|
||||
<umb-drawer-content>
|
||||
<!-- Your content here -->
|
||||
<pre>{{ model | json }}</pre>
|
||||
</umb-drawer-content>
|
||||
|
||||
<umb-drawer-footer>
|
||||
<!-- Your content here -->
|
||||
</umb-drawer-footer>
|
||||
|
||||
</umb-drawer-view>
|
||||
</pre>
|
||||
|
||||
<h3>Use in combination with</h3>
|
||||
<ul>
|
||||
<li>{@link umbraco.directives.directive:umbDrawerHeader umbDrawerHeader}</li>
|
||||
<li>{@link umbraco.directives.directive:umbDrawerContent umbDrawerContent}</li>
|
||||
<li>{@link umbraco.directives.directive:umbDrawerFooter umbDrawerFooter}</li>
|
||||
</ul>
|
||||
|
||||
**/
|
||||
|
||||
(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);
|
||||
|
||||
})();
|
||||
+13
-30
@@ -3,7 +3,7 @@
|
||||
* @name umbraco.directives.directive:umbSections
|
||||
* @restrict E
|
||||
**/
|
||||
function sectionsDirective($timeout, $window, navigationService, treeService, sectionService, appState, eventsService, $location) {
|
||||
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
|
||||
@@ -111,30 +111,13 @@ function sectionsDirective($timeout, $window, navigationService, treeService, se
|
||||
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;
|
||||
}
|
||||
//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) {
|
||||
|
||||
@@ -149,19 +132,19 @@ function sectionsDirective($timeout, $window, navigationService, treeService, se
|
||||
if (scope.userDialog) {
|
||||
closeUserDialog();
|
||||
}
|
||||
if (scope.helpDialog) {
|
||||
closeHelpDialog();
|
||||
}
|
||||
|
||||
|
||||
navigationService.hideSearch();
|
||||
navigationService.showTree(section.alias);
|
||||
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 {
|
||||
$location.path(section.alias).search('');
|
||||
var lastAccessed = historyService.getLastAccessedItemForSection(section.alias);
|
||||
var path = lastAccessed != null ? lastAccessed.link : section.alias;
|
||||
$location.path(path).search('');
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
+552
@@ -0,0 +1,552 @@
|
||||
/**
|
||||
@ngdoc directive
|
||||
@name umbraco.directives.directive:umbTour
|
||||
@restrict E
|
||||
@scope
|
||||
|
||||
@description
|
||||
<b>Added in Umbraco 7.8</b>. 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 <a href="https://our.umbraco.org/projects/starter-kits/the-starter-kit/">The Starter Kit</a> in Umbraco 7.8
|
||||
|
||||
<h1><b>Extending the help drawer with custom tours</b></h1>
|
||||
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 <i>App_Plugins/{MyPackage}/backoffice/tours/{my-tour}.json</i> and it will automatically be
|
||||
picked up by Umbraco and shown in the Help-drawer.
|
||||
|
||||
<h3><b>The tour object</b></h3>
|
||||
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.
|
||||
<pre>
|
||||
// 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
|
||||
"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
|
||||
}
|
||||
</pre>
|
||||
<pre>
|
||||
// A tour step object
|
||||
{
|
||||
"title": "Title",
|
||||
"content": "<p>Step content</p>",
|
||||
"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
|
||||
}
|
||||
</pre>
|
||||
|
||||
<h1><b>Adding tours to other parts of the Umbraco backoffice</b></h1>
|
||||
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.
|
||||
|
||||
<h1><b>Using the tour service</b></h1>
|
||||
<h3>Markup example - show custom tour</h3>
|
||||
<pre>
|
||||
<div ng-controller="My.TourController as vm">
|
||||
|
||||
<div>{{vm.tour.name}}</div>
|
||||
<button type="button" ng-click="vm.startTour()">Start tour</button>
|
||||
|
||||
<!-- This button will be clicked in the tour -->
|
||||
<button data-element="my-tour-button" type="button">Click me</button>
|
||||
|
||||
</div>
|
||||
</pre>
|
||||
|
||||
<h3>Controller example - show custom tour</h3>
|
||||
<pre>
|
||||
(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);
|
||||
|
||||
})();
|
||||
</pre>
|
||||
|
||||
<h1><b>Custom step views</b></h1>
|
||||
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.
|
||||
|
||||
<h3>Markup example - custom step view</h3>
|
||||
<pre>
|
||||
<div ng-controller="My.TourStep as vm">
|
||||
|
||||
<umb-tour-step on-close="model.endTour()">
|
||||
|
||||
<umb-tour-step-header
|
||||
title="model.currentStep.title">
|
||||
</umb-tour-step-header>
|
||||
|
||||
<umb-tour-step-content
|
||||
content="model.currentStep.content">
|
||||
|
||||
<!-- Add any custom content here -->
|
||||
|
||||
</umb-tour-step-content>
|
||||
|
||||
<umb-tour-step-footer class="flex justify-between items-center">
|
||||
|
||||
<umb-tour-step-counter
|
||||
current-step="model.currentStepIndex + 1"
|
||||
total-steps="model.steps.length">
|
||||
</umb-tour-step-counter>
|
||||
|
||||
<div>
|
||||
<umb-button
|
||||
size="xs"
|
||||
button-style="success"
|
||||
type="button"
|
||||
action="vm.initNextStep()"
|
||||
label="Next">
|
||||
</umb-button>
|
||||
</div>
|
||||
|
||||
</umb-tour-step-footer>
|
||||
|
||||
</umb-tour-step>
|
||||
|
||||
</div>
|
||||
</pre>
|
||||
|
||||
<h3>Controller example - custom step view</h3>
|
||||
<pre>
|
||||
(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);
|
||||
|
||||
})();
|
||||
</pre>
|
||||
|
||||
|
||||
<h3>Related services</h3>
|
||||
<ul>
|
||||
<li>{@link umbraco.services.tourService tourService}</li>
|
||||
</ul>
|
||||
|
||||
@param {string} model (<code>binding</code>): 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 () {
|
||||
|
||||
// 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);
|
||||
|
||||
})();
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
(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);
|
||||
|
||||
})();
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
(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);
|
||||
|
||||
})();
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
(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);
|
||||
|
||||
})();
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
(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);
|
||||
|
||||
})();
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
(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);
|
||||
|
||||
})();
|
||||
+2
-1
@@ -149,7 +149,8 @@ Use this directive to render an umbraco button. The directive can be used to gen
|
||||
labelKey: "@?",
|
||||
icon: "@?",
|
||||
disabled: "=",
|
||||
size: "@?"
|
||||
size: "@?",
|
||||
alias: "@?"
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
+157
-44
@@ -1,7 +1,9 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
function ContentEditController($rootScope, $scope, $routeParams, $q, $timeout, $window, appState, contentResource, entityResource, navigationService, notificationsService, angularHelper, serverValidationManager, contentEditingHelper, treeService, fileManager, formHelper, umbRequestHelper, keyboardService, umbModelMapper, editorState, $http) {
|
||||
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;
|
||||
@@ -14,22 +16,13 @@
|
||||
$scope.page.menu.currentSection = appState.getSectionState("currentSection");
|
||||
$scope.page.listViewPath = null;
|
||||
$scope.page.isNew = $scope.isNew ? true : false;
|
||||
$scope.page.buttonGroupState = "init";
|
||||
$scope.page.buttonGroupState = "init";
|
||||
$scope.allowOpen = true;
|
||||
|
||||
|
||||
function init(content) {
|
||||
|
||||
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;
|
||||
createButtons(content);
|
||||
|
||||
editorState.set($scope.content);
|
||||
|
||||
@@ -42,6 +35,70 @@
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
$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 */
|
||||
@@ -130,35 +187,8 @@
|
||||
}
|
||||
else {
|
||||
|
||||
$scope.page.loading = true;
|
||||
getNode();
|
||||
|
||||
//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);
|
||||
|
||||
$scope.page.loading = false;
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -185,6 +215,8 @@
|
||||
|
||||
$scope.page.buttonGroupState = "success";
|
||||
|
||||
}, function(err) {
|
||||
$scope.page.buttonGroupState = 'error';
|
||||
});
|
||||
}
|
||||
|
||||
@@ -208,9 +240,9 @@
|
||||
if (!$scope.busy) {
|
||||
|
||||
// Chromes popup blocker will kick in if a window is opened
|
||||
// outwith the initial scoped request. This trick will fix that.
|
||||
// without the initial scoped request. This trick will fix that.
|
||||
//
|
||||
var previewWindow = $window.open('preview/?id=' + content.id, 'umbpreview');
|
||||
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;
|
||||
@@ -230,6 +262,87 @@
|
||||
|
||||
};
|
||||
|
||||
$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() {
|
||||
|
||||
+293
@@ -0,0 +1,293 @@
|
||||
(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 = {};
|
||||
|
||||
function onInit() {
|
||||
|
||||
scope.allowOpen = true;
|
||||
|
||||
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();
|
||||
|
||||
setNodePublishStatus(scope.node);
|
||||
|
||||
}
|
||||
|
||||
scope.auditTrailPageChange = function (pageNumber) {
|
||||
scope.auditTrailOptions.pageNumber = pageNumber;
|
||||
loadAuditTrail();
|
||||
};
|
||||
|
||||
scope.openDocumentType = function (documentType) {
|
||||
var url = "/settings/documenttypes/edit/" + documentType.id;
|
||||
$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);
|
||||
|
||||
})();
|
||||
+1
-1
@@ -156,7 +156,7 @@ angular.module('umbraco.directives')
|
||||
if(els.indexOf(el) >= 0){return;}
|
||||
|
||||
// ignore clicks on new overlay
|
||||
var parents = $(event.target).parents("a,button,.umb-overlay");
|
||||
var parents = $(event.target).parents("a,button,.umb-overlay,.umb-tour");
|
||||
if(parents.length > 0){
|
||||
return;
|
||||
}
|
||||
|
||||
+6
-1
@@ -115,7 +115,9 @@ 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
|
||||
};
|
||||
|
||||
|
||||
@@ -366,6 +368,9 @@ angular.module("umbraco.directives")
|
||||
// element might still be there even after the modal has been hidden.
|
||||
scope.$on('$destroy', function () {
|
||||
unsubscribe();
|
||||
if (tinyMceEditor !== undefined && tinyMceEditor != null) {
|
||||
tinyMceEditor.destroy()
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
function MediaNodeInfoDirective($timeout, $location, eventsService, userService, dateHelper) {
|
||||
|
||||
function link(scope, element, attrs, ctrl) {
|
||||
|
||||
var evts = [];
|
||||
|
||||
function onInit() {
|
||||
scope.allowOpenMediaType = true;
|
||||
// get document type details
|
||||
scope.mediaType = scope.node.contentType;
|
||||
// get node url
|
||||
scope.nodeUrl = scope.node.mediaLink;
|
||||
// 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');
|
||||
});
|
||||
}
|
||||
|
||||
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; }
|
||||
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);
|
||||
|
||||
})();
|
||||
+1
-2
@@ -59,7 +59,6 @@
|
||||
</pre>
|
||||
|
||||
<h1>General Options</h1>
|
||||
Lorem ipsum dolor sit amet..
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
@@ -74,7 +73,7 @@ Lorem ipsum dolor sit amet..
|
||||
<td>Set the title of the overlay.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>model.subTitle</td>
|
||||
<td>model.subtitle</td>
|
||||
<td>String</td>
|
||||
<td>Set the subtitle of the overlay.</td>
|
||||
</tr>
|
||||
|
||||
+11
-8
@@ -4,29 +4,32 @@
|
||||
* @restrict E
|
||||
**/
|
||||
angular.module("umbraco.directives")
|
||||
.directive('umbProperty', function (umbPropEditorHelper) {
|
||||
.directive('umbProperty', function (umbPropEditorHelper, userService) {
|
||||
return {
|
||||
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;
|
||||
};
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
+9
-2
@@ -8,7 +8,7 @@ angular.module("umbraco.directives")
|
||||
.directive('umbTabs', function () {
|
||||
return {
|
||||
restrict: 'A',
|
||||
controller: function ($scope, $element, $attrs) {
|
||||
controller: function ($scope, $element, $attrs, eventsService) {
|
||||
|
||||
var callbacks = [];
|
||||
this.onTabShown = function(cb) {
|
||||
@@ -19,14 +19,21 @@ angular.module("umbraco.directives")
|
||||
|
||||
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
|
||||
|
||||
+8
-8
@@ -31,10 +31,10 @@ function umbTreeDirective($compile, $log, $q, $rootScope, treeService, notificat
|
||||
//var showheader = (attrs.showheader !== 'false');
|
||||
var hideoptions = (attrs.hideoptions === 'true') ? "hide-options" : "";
|
||||
var template = '<ul class="umb-tree ' + hideoptions + '"><li class="root">';
|
||||
template += '<div ng-class="getNodeCssClass(tree.root)" ng-hide="hideheader" on-right-click="altSelect(tree.root, $event)">' +
|
||||
template += '<div data-element="tree-root" ng-class="getNodeCssClass(tree.root)" ng-hide="hideheader" on-right-click="altSelect(tree.root, $event)">' +
|
||||
'<h5>' +
|
||||
'<a href="#/{{section}}" ng-click="select(tree.root, $event)" class="root-link"><i ng-if="enablecheckboxes == \'true\'" ng-class="selectEnabledNodeClass(tree.root)"></i> {{tree.name}}</a></h5>' +
|
||||
'<a class="umb-options" ng-hide="tree.root.isContainer || !tree.root.menuUrl" ng-click="options(tree.root, $event)" ng-swipe-right="options(tree.root, $event)"><i></i><i></i><i></i></a>' +
|
||||
'<a data-element="tree-item-options" class="umb-options" ng-hide="tree.root.isContainer || !tree.root.menuUrl" ng-click="options(tree.root, $event)" ng-swipe-right="options(tree.root, $event)"><i></i><i></i><i></i></a>' +
|
||||
'</div>';
|
||||
template += '<ul>' +
|
||||
'<umb-tree-item ng-repeat="child in tree.root.children" enablelistviewexpand="{{enablelistviewexpand}}" eventhandler="eventhandler" node="child" current-node="currentNode" tree="this" section="{{section}}" ng-animate="animation()"></umb-tree-item>' +
|
||||
@@ -148,13 +148,13 @@ function umbTreeDirective($compile, $log, $q, $rootScope, treeService, notificat
|
||||
|
||||
userService.getCurrentUser().then(function (userData) {
|
||||
|
||||
var startNodes = [];
|
||||
var startNodes = [];
|
||||
for (var i = 0; i < userData.startContentIds; i++) {
|
||||
startNodes.push(userData.startContentIds[i]);
|
||||
}
|
||||
for (var j = 0; j < userData.startMediaIds; j++) {
|
||||
startNodes.push(userData.startMediaIds[j]);
|
||||
}
|
||||
}
|
||||
|
||||
_.each(startNodes, function (i) {
|
||||
var found = _.find(args.path, function (p) {
|
||||
@@ -317,17 +317,17 @@ function umbTreeDirective($compile, $log, $q, $rootScope, treeService, notificat
|
||||
}
|
||||
|
||||
//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.
|
||||
// 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 ?
|
||||
@@ -406,7 +406,7 @@ function umbTreeDirective($compile, $log, $q, $rootScope, treeService, notificat
|
||||
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
|
||||
|
||||
+12
-4
@@ -35,15 +35,15 @@ angular.module("umbraco.directives")
|
||||
//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: '<li ng-class="{\'current\': (node == currentNode), \'has-children\': node.hasChildren}" on-right-click="altSelect(node, $event)">' +
|
||||
'<div ng-class="getNodeCssClass(node)" ng-swipe-right="options(node, $event)" >' +
|
||||
template: '<li data-element="tree-item-{{node.dataElement}}" ng-class="{\'current\': (node == currentNode), \'has-children\': node.hasChildren}" on-right-click="altSelect(node, $event)">' +
|
||||
'<div ng-class="getNodeCssClass(node)" ng-swipe-right="options(node, $event)" ng-dblclick="load(node)" >' +
|
||||
//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
|
||||
//'<ins ng-if="tree.enablelistviewsearch && node.metaData.isContainer" class="umb-tree-node-search icon-search" ng-click="searchNode(node, $event)" alt="searchAltText"></ins>' +
|
||||
'<ins ng-class="{\'icon-navigation-right\': !node.expanded || node.metaData.isContainer, \'icon-navigation-down\': node.expanded && !node.metaData.isContainer}" ng-click="load(node)"> </ins>' +
|
||||
'<ins data-element="tree-item-expand" ng-class="{\'icon-navigation-right\': !node.expanded || node.metaData.isContainer, \'icon-navigation-down\': node.expanded && !node.metaData.isContainer}" ng-click="load(node)"> </ins>' +
|
||||
'<i class="icon umb-tree-icon sprTree" ng-click="select(node, $event)"></i>' +
|
||||
'<a class="umb-tree-item__label" href="#/{{node.routePath}}" ng-click="select(node, $event)"></a>' +
|
||||
//NOTE: These are the 'option' elipses
|
||||
'<a class="umb-options" ng-click="options(node, $event)"><i></i><i></i><i></i></a>' +
|
||||
'<a data-element="tree-item-options" class="umb-options" ng-click="options(node, $event)"><i></i><i></i><i></i></a>' +
|
||||
'<div ng-show="node.loading" class="l"><div></div></div>' +
|
||||
'</div>' +
|
||||
'</li>',
|
||||
@@ -96,6 +96,14 @@ angular.module("umbraco.directives")
|
||||
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
|
||||
|
||||
@@ -66,9 +66,13 @@ Use this directive to render an avatar.
|
||||
|
||||
function getNameInitials(name) {
|
||||
if(name) {
|
||||
var initials = name.match(/\b\w/g) || [];
|
||||
initials = ((initials.shift() || '') + (initials.pop() || '')).toUpperCase();
|
||||
return initials;
|
||||
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;
|
||||
}
|
||||
|
||||
+8
-1
@@ -84,7 +84,13 @@ Use this directive to render a date time picker
|
||||
|
||||
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');
|
||||
|
||||
@@ -148,7 +154,7 @@ Use this directive to render a date time picker
|
||||
.on("dp.show", onShow)
|
||||
.on("dp.change", onChange)
|
||||
.on("dp.error", onError)
|
||||
.on("dp.update", onUpdate);
|
||||
.on("dp.update", onUpdate);
|
||||
}
|
||||
|
||||
onInit();
|
||||
@@ -158,6 +164,7 @@ Use this directive to render a date time picker
|
||||
var directive = {
|
||||
restrict: 'E',
|
||||
replace: true,
|
||||
transclude: true,
|
||||
templateUrl: 'views/components/umb-date-time-picker.html',
|
||||
scope: {
|
||||
options: "=",
|
||||
|
||||
+2
-1
@@ -489,7 +489,7 @@
|
||||
|
||||
scope.editPropertyTypeSettings = function(property, group) {
|
||||
|
||||
if (!property.inherited && !property.locked) {
|
||||
if (!property.inherited) {
|
||||
|
||||
scope.propertySettingsDialogModel = {};
|
||||
scope.propertySettingsDialogModel.title = "Property settings";
|
||||
@@ -547,6 +547,7 @@
|
||||
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
|
||||
|
||||
+1
-1
@@ -110,7 +110,7 @@ Use this directive to generate a thumbnail grid of media items.
|
||||
itemMinWidth = scope.itemMinWidth;
|
||||
}
|
||||
|
||||
if (scope.itemMinWidth) {
|
||||
if (scope.itemMinHeight) {
|
||||
itemMinHeight = scope.itemMinHeight;
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -77,7 +77,7 @@
|
||||
|
||||
@param {string} icon (<code>binding</code>): The node icon.
|
||||
@param {string} name (<code>binding</code>): The node name.
|
||||
@param {boolean} published (<code>binding</code>): The node pusblished state.
|
||||
@param {boolean} published (<code>binding</code>): The node published state.
|
||||
@param {string} description (<code>binding</code>): A short description.
|
||||
@param {boolean} sortable (<code>binding</code>): Will add a move cursor on the node preview. Can used in combination with ui-sortable.
|
||||
@param {boolean} allowRemove (<code>binding</code>): Show/Hide the remove button.
|
||||
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
@ngdoc directive
|
||||
@name umbraco.directives.directive:umbPasswordToggle
|
||||
@restrict E
|
||||
@scope
|
||||
|
||||
@description
|
||||
<strong>Added in Umbraco v. 7.7.4:</strong> Use this directive to render a password toggle.
|
||||
|
||||
**/
|
||||
|
||||
(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("<a data-ng-click=\"tgl()\">Toggle</a>");
|
||||
$compile(lnk)(scope);
|
||||
elem.wrap("<div class=\"password-toggle\"/>").after(lnk);
|
||||
}
|
||||
};
|
||||
|
||||
return directive;
|
||||
|
||||
}
|
||||
|
||||
angular.module('umbraco.directives').directive('umbPasswordToggle', PasswordToggleDirective);
|
||||
|
||||
})();
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
/**
|
||||
@ngdoc directive
|
||||
@name umbraco.directives.directive:umbProgressCircle
|
||||
@restrict E
|
||||
@scope
|
||||
|
||||
@description
|
||||
Use this directive to render a circular progressbar.
|
||||
|
||||
<h3>Markup example</h3>
|
||||
<pre>
|
||||
<div>
|
||||
|
||||
<umb-progress-circle
|
||||
percentage="80"
|
||||
size="60"
|
||||
color="secondary">
|
||||
</umb-progress-circle>
|
||||
|
||||
</div>
|
||||
</pre>
|
||||
|
||||
@param {string} size (<code>attribute</code>): This parameter defines the width and the height of the circle in pixels.
|
||||
@param {string} percentage (<code>attribute</code>): Takes a number between 0 and 100 and applies it to the circle's highlight length.
|
||||
@param {string} color (<code>attribute</code>): 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();
|
||||
}
|
||||
|
||||
|
||||
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);
|
||||
|
||||
})();
|
||||
@@ -1,3 +1,114 @@
|
||||
/**
|
||||
@ngdoc directive
|
||||
@name umbraco.directives.directive:umbTable
|
||||
@restrict E
|
||||
@scope
|
||||
|
||||
@description
|
||||
<strong>Added in Umbraco v. 7.4:</strong> Use this directive to render a data table.
|
||||
|
||||
<h3>Markup example</h3>
|
||||
<pre>
|
||||
<div ng-controller="My.TableController as vm">
|
||||
|
||||
<umb-table
|
||||
ng-if="items"
|
||||
items="vm.items"
|
||||
item-properties="vm.options.includeProperties"
|
||||
allow-select-all="vm.allowSelectAll"
|
||||
on-select="vm.selectItem"
|
||||
on-click="vm.clickItem"
|
||||
on-select-all="vm.selectAll"
|
||||
on-selected-all="vm.isSelectedAll"
|
||||
on-sorting-direction="vm.isSortDirection"
|
||||
on-sort="vm.sort">
|
||||
</umb-table>
|
||||
|
||||
</div>
|
||||
</pre>
|
||||
|
||||
<h3>Controller example</h3>
|
||||
<pre>
|
||||
(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"
|
||||
}
|
||||
];
|
||||
|
||||
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 selectAll($event) {
|
||||
alert("select all");
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
})();
|
||||
</pre>
|
||||
|
||||
@param {string} icon (<code>binding</code>): The node icon.
|
||||
@param {string} name (<code>binding</code>): The node name.
|
||||
@param {string} published (<code>binding</code>): The node published state.
|
||||
@param {function} onSelect (<code>expression</code>): Callback function when the row is selected.
|
||||
@param {function} onClick (<code>expression</code>): Callback function when the "Name" column link is clicked.
|
||||
@param {function} onSelectAll (<code>expression</code>): Callback function when selecting all items.
|
||||
@param {function} onSelectedAll (<code>expression</code>): Callback function when all items are selected.
|
||||
@param {function} onSortingDirection (<code>expression</code>): Callback function when sorting direction is changed.
|
||||
@param {function} onSort (<code>expression</code>): Callback function when sorting items.
|
||||
**/
|
||||
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
|
||||
+8
-3
@@ -9,9 +9,14 @@ function noDirtyCheck() {
|
||||
restrict: 'A',
|
||||
require: 'ngModel',
|
||||
link: function (scope, elm, attrs, ctrl) {
|
||||
elm.focus(function () {
|
||||
ctrl.$pristine = false;
|
||||
});
|
||||
|
||||
var alwaysFalse = {
|
||||
get: function () { return false; },
|
||||
set: function () { }
|
||||
};
|
||||
Object.defineProperty(ctrl, '$pristine', alwaysFalse);
|
||||
Object.defineProperty(ctrl, '$dirty', alwaysFalse);
|
||||
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -47,8 +47,8 @@ function valEmail(valEmailExpression) {
|
||||
angular.module('umbraco.directives.validation')
|
||||
.directive("valEmail", valEmail)
|
||||
.factory('valEmailExpression', function () {
|
||||
//NOTE: This is the fixed regex which is part of the newer angular
|
||||
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: /^[a-z0-9!#$%&'*+\/=?^_`{|}~.-]+@[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i
|
||||
EMAIL_REGEXP: emailRegex
|
||||
};
|
||||
});
|
||||
@@ -10,6 +10,30 @@ 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) {
|
||||
|
||||
@@ -41,7 +41,7 @@ function entityResource($q, $http, umbRequestHelper) {
|
||||
if (!value) {
|
||||
return "";
|
||||
}
|
||||
|
||||
value = value.replace("#", "");
|
||||
return umbRequestHelper.resourcePromise(
|
||||
$http.get(
|
||||
umbRequestHelper.getApiUrl(
|
||||
|
||||
@@ -9,7 +9,73 @@ 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
|
||||
|
||||
@@ -493,7 +493,7 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) {
|
||||
* @methodOf umbraco.resources.mediaResource
|
||||
*
|
||||
* @description
|
||||
* Empties the media recycle bin
|
||||
* Paginated search for media items starting on the supplied nodeId
|
||||
*
|
||||
* ##usage
|
||||
* <pre>
|
||||
@@ -506,7 +506,7 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) {
|
||||
* @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 Id to search from
|
||||
* @param {int} searchFrom NodeId to search from (-1 for root)
|
||||
* @returns {Promise} resourcePromise object.
|
||||
*
|
||||
*/
|
||||
|
||||
@@ -10,8 +10,8 @@ function memberResource($q, $http, umbDataFormatter, umbRequestHelper) {
|
||||
|
||||
return umbRequestHelper.postSaveContent({
|
||||
restApiUrl: umbRequestHelper.getApiUrl(
|
||||
"memberApiBaseUrl",
|
||||
"PostSave"),
|
||||
"memberApiBaseUrl",
|
||||
"PostSave"),
|
||||
content: content,
|
||||
action: action,
|
||||
files: files,
|
||||
@@ -22,8 +22,7 @@ function memberResource($q, $http, umbDataFormatter, umbRequestHelper) {
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
getPagedResults: function (memberTypeAlias, options) {
|
||||
getPagedResults: function(memberTypeAlias, options) {
|
||||
|
||||
if (memberTypeAlias === 'all-members') {
|
||||
memberTypeAlias = null;
|
||||
@@ -67,35 +66,35 @@ function memberResource($q, $http, umbDataFormatter, umbRequestHelper) {
|
||||
}
|
||||
|
||||
var params = [
|
||||
{ pageNumber: options.pageNumber },
|
||||
{ pageSize: options.pageSize },
|
||||
{ orderBy: options.orderBy },
|
||||
{ orderDirection: options.orderDirection },
|
||||
{ orderBySystemField: toBool(options.orderBySystemField) },
|
||||
{ filter: options.filter }
|
||||
{ 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');
|
||||
$http.get(
|
||||
umbRequestHelper.getApiUrl(
|
||||
"memberApiBaseUrl",
|
||||
"GetPagedResults",
|
||||
params)),
|
||||
'Failed to retrieve member paged result');
|
||||
},
|
||||
|
||||
getListNode: function (listName) {
|
||||
getListNode: function(listName) {
|
||||
|
||||
return umbRequestHelper.resourcePromise(
|
||||
$http.get(
|
||||
umbRequestHelper.getApiUrl(
|
||||
"memberApiBaseUrl",
|
||||
"GetListNodeDisplay",
|
||||
[{ listName: listName }])),
|
||||
'Failed to retrieve data for member list ' + listName);
|
||||
$http.get(
|
||||
umbRequestHelper.getApiUrl(
|
||||
"memberApiBaseUrl",
|
||||
"GetListNodeDisplay",
|
||||
[{ listName: listName }])),
|
||||
'Failed to retrieve data for member list ' + listName);
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -119,15 +118,15 @@ function memberResource($q, $http, umbDataFormatter, umbRequestHelper) {
|
||||
* @returns {Promise} resourcePromise object containing the member item.
|
||||
*
|
||||
*/
|
||||
getByKey: function (key) {
|
||||
getByKey: function(key) {
|
||||
|
||||
return umbRequestHelper.resourcePromise(
|
||||
$http.get(
|
||||
umbRequestHelper.getApiUrl(
|
||||
"memberApiBaseUrl",
|
||||
"GetByKey",
|
||||
[{ key: key }])),
|
||||
'Failed to retrieve data for member id ' + key);
|
||||
$http.get(
|
||||
umbRequestHelper.getApiUrl(
|
||||
"memberApiBaseUrl",
|
||||
"GetByKey",
|
||||
[{ key: key }])),
|
||||
'Failed to retrieve data for member id ' + key);
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -150,14 +149,14 @@ function memberResource($q, $http, umbDataFormatter, umbRequestHelper) {
|
||||
* @returns {Promise} resourcePromise object.
|
||||
*
|
||||
*/
|
||||
deleteByKey: function (key) {
|
||||
deleteByKey: function(key) {
|
||||
return umbRequestHelper.resourcePromise(
|
||||
$http.post(
|
||||
umbRequestHelper.getApiUrl(
|
||||
"memberApiBaseUrl",
|
||||
"DeleteByKey",
|
||||
[{ key: key }])),
|
||||
'Failed to delete item ' + key);
|
||||
$http.post(
|
||||
umbRequestHelper.getApiUrl(
|
||||
"memberApiBaseUrl",
|
||||
"DeleteByKey",
|
||||
[{ key: key }])),
|
||||
'Failed to delete item ' + key);
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -190,24 +189,24 @@ function memberResource($q, $http, umbDataFormatter, umbRequestHelper) {
|
||||
* @returns {Promise} resourcePromise object containing the member scaffold.
|
||||
*
|
||||
*/
|
||||
getScaffold: function (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);
|
||||
$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);
|
||||
$http.get(
|
||||
umbRequestHelper.getApiUrl(
|
||||
"memberApiBaseUrl",
|
||||
"GetEmpty")),
|
||||
'Failed to retrieve data for empty member item type ' + alias);
|
||||
}
|
||||
|
||||
},
|
||||
@@ -240,7 +239,7 @@ function memberResource($q, $http, umbDataFormatter, umbRequestHelper) {
|
||||
* @returns {Promise} resourcePromise object containing the saved media item.
|
||||
*
|
||||
*/
|
||||
save: function (member, isNew, files) {
|
||||
save: function(member, isNew, files) {
|
||||
return saveMember(member, "save" + (isNew ? "New" : ""), files);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* @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);
|
||||
|
||||
})();
|
||||
@@ -74,6 +74,15 @@ function appState(eventsService) {
|
||||
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)) {
|
||||
@@ -212,6 +221,35 @@ function appState(eventsService) {
|
||||
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);
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
* @ngdoc service
|
||||
* @name umbraco.services.assetsService
|
||||
*
|
||||
* @requires $q
|
||||
* @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.
|
||||
@@ -18,7 +18,7 @@
|
||||
* //this code executes when the dependencies are done loading
|
||||
* });
|
||||
* });
|
||||
* </pre>
|
||||
* </pre>
|
||||
*
|
||||
* You can also load individual files, which gives you greater control over what attibutes are passed to the file, as well as timeout
|
||||
*
|
||||
@@ -38,13 +38,14 @@
|
||||
* //loadcss cannot determine when the css is done loading, so this will trigger instantly
|
||||
* });
|
||||
* });
|
||||
* </pre>
|
||||
* </pre>
|
||||
*/
|
||||
angular.module('umbraco.services')
|
||||
.factory('assetsService', function ($q, $log, angularHelper, umbRequestHelper, $rootScope, $http) {
|
||||
|
||||
var initAssetsLoaded = false;
|
||||
var appendRnd = function (url) {
|
||||
|
||||
function appendRnd (url) {
|
||||
//if we don't have a global umbraco obj yet, the app is bootstrapping
|
||||
if (!Umbraco.Sys.ServerVariables.application) {
|
||||
return url;
|
||||
@@ -77,7 +78,8 @@ angular.module('umbraco.services')
|
||||
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.
|
||||
*/
|
||||
@@ -108,10 +110,10 @@ angular.module('umbraco.services')
|
||||
*
|
||||
* @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 {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
|
||||
*/
|
||||
@@ -149,10 +151,10 @@ angular.module('umbraco.services')
|
||||
*
|
||||
* @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 {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
|
||||
*/
|
||||
@@ -192,8 +194,8 @@ angular.module('umbraco.services')
|
||||
* @methodOf umbraco.services.assetsService
|
||||
*
|
||||
* @description
|
||||
* Injects a collection of files, this can be ONLY js files
|
||||
*
|
||||
* 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
|
||||
@@ -206,61 +208,72 @@ angular.module('umbraco.services')
|
||||
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 === "";
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
//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
|
||||
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) {
|
||||
asset.deferred.resolve(true);
|
||||
return;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
/**
|
||||
@ngdoc service
|
||||
* @name umbraco.services.backdropService
|
||||
*
|
||||
* @description
|
||||
* <b>Added in Umbraco 7.8</b>. 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);
|
||||
|
||||
})();
|
||||
@@ -5,7 +5,7 @@
|
||||
* @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 contentEditingHelper(fileManager, $q, $location, $routeParams, notificationsService, localizationService, serverValidationManager, dialogService, formHelper, appState) {
|
||||
|
||||
function isValidIdentifier(id){
|
||||
//empty id <= 0
|
||||
@@ -28,7 +28,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica
|
||||
|
||||
return {
|
||||
|
||||
/** Used by the content editor and mini content editor to perform saving operations */
|
||||
/** 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)) {
|
||||
@@ -100,7 +100,35 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica
|
||||
|
||||
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
|
||||
@@ -134,7 +162,8 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica
|
||||
labelKey: "buttons_saveAndPublish",
|
||||
handler: args.methods.saveAndPublish,
|
||||
hotKey: "ctrl+p",
|
||||
hotKeyWhenHidden: true
|
||||
hotKeyWhenHidden: true,
|
||||
alias: "saveAndPublish"
|
||||
};
|
||||
case "H":
|
||||
//send to publish
|
||||
@@ -143,7 +172,8 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica
|
||||
labelKey: "buttons_saveToPublish",
|
||||
handler: args.methods.sendToPublish,
|
||||
hotKey: "ctrl+p",
|
||||
hotKeyWhenHidden: true
|
||||
hotKeyWhenHidden: true,
|
||||
alias: "sendToPublish"
|
||||
};
|
||||
case "A":
|
||||
//save
|
||||
@@ -152,7 +182,8 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica
|
||||
labelKey: "buttons_save",
|
||||
handler: args.methods.save,
|
||||
hotKey: "ctrl+s",
|
||||
hotKeyWhenHidden: true
|
||||
hotKeyWhenHidden: true,
|
||||
alias: "save"
|
||||
};
|
||||
case "Z":
|
||||
//unpublish
|
||||
@@ -161,7 +192,8 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica
|
||||
labelKey: "content_unPublish",
|
||||
handler: args.methods.unPublish,
|
||||
hotKey: "ctrl+u",
|
||||
hotKeyWhenHidden: true
|
||||
hotKeyWhenHidden: true,
|
||||
alias: "unpublish"
|
||||
};
|
||||
default:
|
||||
return null;
|
||||
@@ -176,10 +208,10 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica
|
||||
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
|
||||
//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) {
|
||||
@@ -187,9 +219,9 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica
|
||||
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
|
||||
}
|
||||
//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");
|
||||
}
|
||||
@@ -220,6 +252,40 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
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;
|
||||
},
|
||||
|
||||
|
||||
@@ -16,52 +16,16 @@ function eventsService($q, $rootScope) {
|
||||
|
||||
return {
|
||||
|
||||
/** raise an event with a given name, returns an array of promises for each listener */
|
||||
emit: function (name, args) {
|
||||
/** raise an event with a given name */
|
||||
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 */
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
angular.module('umbraco.services')
|
||||
.factory('helpService', function ($http, $q){
|
||||
.factory('helpService', function ($http, $q, umbRequestHelper) {
|
||||
var helpTopics = {};
|
||||
|
||||
var defaultUrl = "http://our.umbraco.org/rss/help";
|
||||
@@ -47,8 +47,6 @@ angular.module('umbraco.services')
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
|
||||
|
||||
var service = {
|
||||
findHelp: function (args) {
|
||||
var url = service.getUrl(defaultUrl, args);
|
||||
@@ -60,6 +58,26 @@ angular.module('umbraco.services')
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -115,6 +115,27 @@ angular.module('umbraco.services')
|
||||
*/
|
||||
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;
|
||||
}
|
||||
};
|
||||
});
|
||||
@@ -269,7 +269,8 @@
|
||||
var isSelected = false;
|
||||
for (var i = 0; selection.length > i; i++) {
|
||||
var selectedItem = selection[i];
|
||||
if (item.id === selectedItem.id || item.key === selectedItem.key) {
|
||||
// if item.id is 2147483647 (int.MaxValue) use item.key
|
||||
if ((item.id !== 2147483647 && item.id === selectedItem.id) || item.key === selectedItem.key) {
|
||||
isSelected = true;
|
||||
}
|
||||
}
|
||||
@@ -294,7 +295,8 @@
|
||||
function deselectItem(item, selection) {
|
||||
for (var i = 0; selection.length > i; i++) {
|
||||
var selectedItem = selection[i];
|
||||
if (item.id === selectedItem.id) {
|
||||
// if item.id is 2147483647 (int.MaxValue) use item.key
|
||||
if ((item.id !== 2147483647 && item.id === selectedItem.id) || item.key === selectedItem.key) {
|
||||
selection.splice(i, 1);
|
||||
item.selected = false;
|
||||
}
|
||||
@@ -366,7 +368,7 @@
|
||||
var item = items[i];
|
||||
|
||||
if (checkbox.checked) {
|
||||
selection.push({ id: item.id });
|
||||
selection.push({ id: item.id, key: item.key });
|
||||
} else {
|
||||
clearSelection = true;
|
||||
}
|
||||
@@ -405,7 +407,8 @@
|
||||
for (var selectedIndex = 0; selection.length > selectedIndex; selectedIndex++) {
|
||||
var selectedItem = selection[selectedIndex];
|
||||
|
||||
if (item.id === selectedItem.id) {
|
||||
// if item.id is 2147483647 (int.MaxValue) use item.key
|
||||
if ((item.id !== 2147483647 && item.id === selectedItem.id) || item.key === selectedItem.key) {
|
||||
numberOfSelectedItem++;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,9 +8,27 @@
|
||||
* @description
|
||||
* Defines the methods that are called when menu items declare only an action to execute
|
||||
*/
|
||||
function umbracoMenuActions($q, treeService, $location, navigationService, appState, localizationService, userResource) {
|
||||
function umbracoMenuActions($q, treeService, $location, navigationService, appState, localizationService, userResource, umbRequestHelper, notificationsService) {
|
||||
|
||||
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);
|
||||
})
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
"DisableUser": function(args) {
|
||||
localizationService.localize("defaultdialogs_confirmdisable").then(function (txtConfirmDisable) {
|
||||
|
||||
@@ -524,37 +524,6 @@ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeo
|
||||
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;
|
||||
},
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name umbraco.services.navigationService#showDialog
|
||||
|
||||
@@ -0,0 +1,281 @@
|
||||
/**
|
||||
@ngdoc service
|
||||
* @name umbraco.services.tourService
|
||||
*
|
||||
* @description
|
||||
* <b>Added in Umbraco 7.8</b>. 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);
|
||||
|
||||
})();
|
||||
@@ -14,7 +14,7 @@
|
||||
if (!model) {
|
||||
return null;
|
||||
}
|
||||
var trimmed = _.omit(model, ["confirm", "generatedPassword"])
|
||||
var trimmed = _.omit(model, ["confirm", "generatedPassword"]);
|
||||
|
||||
//ensure that the pass value is null if all child properties are null
|
||||
var allNull = true;
|
||||
@@ -56,7 +56,7 @@
|
||||
});
|
||||
|
||||
var saveProperties = _.map(realProperties, function (p) {
|
||||
var saveProperty = _.pick(p, 'id', 'alias', 'description', 'validation', 'label', 'sortOrder', 'dataTypeId', 'groupId', 'memberCanEdit', 'showOnMemberProfile');
|
||||
var saveProperty = _.pick(p, 'id', 'alias', 'description', 'validation', 'label', 'sortOrder', 'dataTypeId', 'groupId', 'memberCanEdit', 'showOnMemberProfile', 'isSensitiveData');
|
||||
return saveProperty;
|
||||
});
|
||||
|
||||
@@ -242,8 +242,8 @@
|
||||
var propGroups = _.find(genericTab.properties, function (item) {
|
||||
return item.alias === "_umb_membergroup";
|
||||
});
|
||||
saveModel.email = propEmail.value;
|
||||
saveModel.username = propLogin.value;
|
||||
saveModel.email = propEmail.value.trim();
|
||||
saveModel.username = propLogin.value.trim();
|
||||
|
||||
saveModel.password = this.formatChangePasswordModel(propPass.value);
|
||||
|
||||
@@ -267,10 +267,10 @@
|
||||
// by looking at the key
|
||||
switch (foundAlias[0]) {
|
||||
case "umbracoMemberLockedOut":
|
||||
saveModel.isLockedOut = prop.value.toString() === "1" ? true : false;
|
||||
saveModel.isLockedOut = prop.value ? (prop.value.toString() === "1" ? true : false) : false;
|
||||
break;
|
||||
case "umbracoMemberApproved":
|
||||
saveModel.isApproved = prop.value.toString() === "1" ? true : false;
|
||||
saveModel.isApproved = prop.value ? (prop.value.toString() === "1" ? true : false) : true;
|
||||
break;
|
||||
case "umbracoMemberComments":
|
||||
saveModel.comments = prop.value;
|
||||
@@ -304,14 +304,14 @@
|
||||
_.each(tab.properties, function (prop) {
|
||||
|
||||
//don't include the custom generic tab properties
|
||||
if (!prop.alias.startsWith("_umb_")) {
|
||||
//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
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
@@ -324,22 +324,13 @@
|
||||
//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 ? propExpireDate.value : null;
|
||||
saveModel.releaseDate = propReleaseDate ? propReleaseDate.value : null;
|
||||
saveModel.templateAlias = propTemplate ? propTemplate.value : null;
|
||||
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;
|
||||
}
|
||||
@@ -347,4 +338,4 @@
|
||||
}
|
||||
angular.module('umbraco.services').factory('umbDataFormatter', umbDataFormatter);
|
||||
|
||||
})();
|
||||
})();
|
||||
|
||||
@@ -230,10 +230,12 @@ function umbRequestHelper($http, $q, umbDataFormatter, angularHelper, dialogServ
|
||||
//success callback
|
||||
|
||||
//reset the tabs and set the active one
|
||||
_.each(data.tabs, function (item) {
|
||||
item.active = false;
|
||||
});
|
||||
data.tabs[activeTabIndex].active = true;
|
||||
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);
|
||||
@@ -331,6 +333,122 @@ function umbRequestHelper($http, $q, umbDataFormatter, angularHelper, dialogServ
|
||||
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;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -11,6 +11,18 @@
|
||||
{ "value": 3, "name": "Invited", "key": "Invited", "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) {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -8,7 +8,7 @@
|
||||
* The main application controller
|
||||
*
|
||||
*/
|
||||
function MainController($scope, $rootScope, $location, $routeParams, $timeout, $http, $log, appState, treeService, notificationsService, userService, navigationService, historyService, updateChecker, assetsService, eventsService, umbRequestHelper, tmhDynamicLocale, localStorageService) {
|
||||
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
|
||||
@@ -136,6 +136,40 @@ function MainController($scope, $rootScope, $location, $routeParams, $timeout, $
|
||||
};
|
||||
}));
|
||||
|
||||
// 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) {
|
||||
|
||||
@@ -14,7 +14,6 @@ function SearchController($scope, searchService, $log, $location, navigationServ
|
||||
$scope.isSearching = false;
|
||||
$scope.selectedResult = -1;
|
||||
|
||||
|
||||
$scope.navigateResults = function (ev) {
|
||||
//38: up 40: down, 13: enter
|
||||
|
||||
@@ -34,24 +33,37 @@ function SearchController($scope, searchService, $log, $location, navigationServ
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
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) {
|
||||
group = $scope.groups[0];
|
||||
|
||||
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($scope.groups.length - 1, true);
|
||||
gotoGroup(Object.keys($scope.groups).length - 1, true);
|
||||
} else {
|
||||
gotoGroup(groupIndex - 1, true);
|
||||
}
|
||||
@@ -62,7 +74,7 @@ function SearchController($scope, searchService, $log, $location, navigationServ
|
||||
if (itemIndex < group.results.length - 1) {
|
||||
gotoItem(itemIndex + 1);
|
||||
} else {
|
||||
if (groupIndex === $scope.groups.length - 1) {
|
||||
if (groupIndex === Object.keys($scope.groups).length - 1) {
|
||||
gotoGroup(0);
|
||||
} else {
|
||||
gotoGroup(groupIndex + 1);
|
||||
@@ -73,7 +85,7 @@ function SearchController($scope, searchService, $log, $location, navigationServ
|
||||
|
||||
function gotoGroup(index, up) {
|
||||
groupIndex = index;
|
||||
group = $scope.groups[groupIndex];
|
||||
group = $scope.groups[groupNames[groupIndex]];
|
||||
|
||||
if (up) {
|
||||
gotoItem(group.results.length - 1);
|
||||
@@ -95,6 +107,13 @@ function SearchController($scope, searchService, $log, $location, navigationServ
|
||||
$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;
|
||||
@@ -114,7 +133,7 @@ function SearchController($scope, searchService, $log, $location, navigationServ
|
||||
var filtered = {};
|
||||
_.each(result, function (value, key) {
|
||||
if (value.results.length > 0) {
|
||||
filtered[key] = value;
|
||||
filtered[key] = value;
|
||||
}
|
||||
});
|
||||
$scope.groups = filtered;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/** Executed when the application starts, binds to events and set global state */
|
||||
app.run(['userService', '$log', '$rootScope', '$location', 'queryStrings', 'navigationService', 'appState', 'editorState', 'fileManager', 'assetsService', 'eventsService', '$cookies', '$templateCache', 'localStorageService',
|
||||
function (userService, $log, $rootScope, $location, queryStrings, navigationService, appState, editorState, fileManager, assetsService, eventsService, $cookies, $templateCache, localStorageService) {
|
||||
|
||||
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
|
||||
@@ -18,14 +18,34 @@ app.run(['userService', '$log', '$rootScope', '$location', 'queryStrings', 'navi
|
||||
eventsService.on("app.authenticated", function(evt, data) {
|
||||
|
||||
assetsService._loadInitAssets().then(function() {
|
||||
appState.setGlobalState("isReady", true);
|
||||
|
||||
//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);
|
||||
});
|
||||
|
||||
//send the ready event with the included returnToPath,returnToSearch data
|
||||
eventsService.emit("app.ready", data);
|
||||
returnToPath = null, returnToSearch = null;
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
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) {
|
||||
|
||||
@@ -96,4 +116,5 @@ app.run(['userService', '$log', '$rootScope', '$location', 'queryStrings', 'navi
|
||||
//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);
|
||||
|
||||
}]);
|
||||
|
||||
@@ -17,7 +17,7 @@ angular.module("umbraco.install").factory('installerService', function($rootScop
|
||||
|
||||
//add to umbraco installer facts here
|
||||
var facts = ['Umbraco helped millions of people watch a man jump from the edge of space',
|
||||
'Over 420 000 websites are currently powered by Umbraco',
|
||||
'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',
|
||||
'<a target="_blank" href="https://umbraco.tv">umbraco.tv</a> is the premier source of Umbraco video tutorials to get you started',
|
||||
@@ -31,10 +31,10 @@ angular.module("umbraco.install").factory('installerService', function($rootScop
|
||||
"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 550 people from all over the world meet each year in Denmark in June for our annual conference <a target='_blank' href='https://umbra.co/codegarden'>CodeGarden</a>",
|
||||
"More than 600 people from all over the world meet each year in Denmark in June for our annual conference <a target='_blank' href='https://umbra.co/codegarden'>CodeGarden</a>",
|
||||
"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"
|
||||
"Umbraco has been installed in more than 198 countries"
|
||||
];
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
angular.module("umbraco.install").controller("Umbraco.Install.UserController", function($scope, installerService) {
|
||||
|
||||
$scope.passwordPattern = /.*/;
|
||||
$scope.installer.current.model.subscribeToNewsLetter = true;
|
||||
$scope.installer.current.model.subscribeToNewsLetter = false;
|
||||
|
||||
if ($scope.installer.current.model.minNonAlphaNumericLength > 0) {
|
||||
var exp = "";
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="email">Email</label>
|
||||
<div class="controls">
|
||||
<input type="text" id="email" name="email" placeholder="you@example.com" required ng-model="installer.current.model.email" val-email />
|
||||
<input type="email" id="email" name="email" placeholder="you@example.com" val-email required ng-model="installer.current.model.email" />
|
||||
<small class="inline-help">Your email will be used as your login</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -40,11 +40,15 @@ body {
|
||||
}
|
||||
|
||||
#mainwrapper {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
top: 0; left: 0; right: 0; bottom: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
body.umb-drawer-is-visible #mainwrapper{
|
||||
left: @drawerWidth;
|
||||
}
|
||||
|
||||
#contentwrapper, #contentcolumn {
|
||||
position: absolute;
|
||||
top: 0px; bottom: 0px; right: 0px; left: 80px;
|
||||
@@ -83,7 +87,8 @@ body {
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
position: absolute;
|
||||
text-align: center
|
||||
text-align: center;
|
||||
box-shadow: -10px 0px 25px rgba(0, 0, 0, 0.3)
|
||||
}
|
||||
|
||||
#applications-tray {
|
||||
|
||||
@@ -81,6 +81,10 @@
|
||||
@import "forms/umb-validation-label.less";
|
||||
|
||||
// Umbraco Components
|
||||
@import "components/application/umb-tour.less";
|
||||
@import "components/application/umb-backdrop.less";
|
||||
@import "components/application/umb-drawer.less";
|
||||
|
||||
@import "components/editor.less";
|
||||
@import "components/overlays.less";
|
||||
@import "components/card.less";
|
||||
@@ -125,6 +129,8 @@
|
||||
@import "components/umb-checkmark.less";
|
||||
@import "components/umb-list.less";
|
||||
@import "components/umb-box.less";
|
||||
@import "components/umb-number-badge.less";
|
||||
@import "components/umb-progress-circle.less";
|
||||
|
||||
@import "components/buttons/umb-button.less";
|
||||
@import "components/buttons/umb-button-group.less";
|
||||
@@ -167,3 +173,6 @@
|
||||
@import "hacks.less";
|
||||
|
||||
@import "healthcheck.less";
|
||||
|
||||
// cleanup properties.less when it is done
|
||||
@import "properties.less";
|
||||
@@ -261,6 +261,9 @@ input[type="submit"].btn {
|
||||
*padding-top: 1px;
|
||||
*padding-bottom: 1px;
|
||||
}
|
||||
|
||||
// Safari defaults to 1px for input. Ref U4-7721.
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
.umb-backdrop {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
position: fixed;
|
||||
z-index: 9999;
|
||||
top: 0;
|
||||
left: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.umb-backdrop__backdrop {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.umb-backdrop__rect {
|
||||
position: absolute;
|
||||
pointer-events: all;
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: @black;
|
||||
opacity: 0.4;
|
||||
transition: 200ms opacity ease-in-out;
|
||||
}
|
||||
|
||||
.umb-backdrop__highlight-prevent-click {
|
||||
position: absolute;
|
||||
pointer-events: all;
|
||||
}
|
||||
@@ -0,0 +1,194 @@
|
||||
.umb-drawer {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 10;
|
||||
width: @drawerWidth;
|
||||
box-shadow: 0 0 20px rgba(0,0,0,0.19), 0 0 6px rgba(0,0,0,0.23);
|
||||
background: @gray-9;
|
||||
}
|
||||
|
||||
.umb-drawer-view {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Header */
|
||||
|
||||
.umb-drawer-header {
|
||||
flex: 0 0 100px;
|
||||
padding: 20px 30px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.umb-drawer-header__title {
|
||||
font-size: @fontSizeLarge;
|
||||
font-weight: bold;
|
||||
margin-top: 7px;
|
||||
margin-bottom: 7px;
|
||||
}
|
||||
|
||||
.umb-drawer-header__subtitle {
|
||||
font-size: @fontSizeSmall;
|
||||
}
|
||||
|
||||
/* Content */
|
||||
|
||||
.umb-drawer-content {
|
||||
flex: 1 1 auto;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
padding: 0 30px 20px 30px;
|
||||
}
|
||||
|
||||
/* Footer */
|
||||
.umb-drawer-footer {
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
flex-basis: 31px;
|
||||
padding: 15px 30px;
|
||||
}
|
||||
|
||||
/* Our badge - should be moved */
|
||||
|
||||
.umb-help-badge {
|
||||
padding: 10px 20px 10px 35px;
|
||||
background: @white;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
border-radius: 3px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.umb-help-badge:hover,
|
||||
.umb-help-badge:active,
|
||||
.umb-help-badge:focus {
|
||||
text-decoration: none;
|
||||
|
||||
.umb-help-badge__title {
|
||||
text-decoration: underline !important;
|
||||
}
|
||||
}
|
||||
|
||||
.umb-help-badge__icon {
|
||||
font-size: 40px;
|
||||
transform: translate(0,-50%);
|
||||
position: absolute;
|
||||
left: -15px;
|
||||
top: 50%;
|
||||
color: @red-l3;
|
||||
}
|
||||
|
||||
.umb-help-badge__title {
|
||||
font-size: 15px;
|
||||
font-weight: bold;
|
||||
color: @black;
|
||||
}
|
||||
|
||||
/* Help article */
|
||||
|
||||
.umb-help-article {
|
||||
background: @white;
|
||||
padding: 20px;
|
||||
line-height: 1.4em;
|
||||
}
|
||||
|
||||
/* Make sure typography looks good */
|
||||
.umb-help-article h1,
|
||||
.umb-help-article h2,
|
||||
.umb-help-article h3,
|
||||
.umb-help-article h4 {
|
||||
line-height: 1.3em;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.umb-help-article h1 { font-size: 20px; }
|
||||
.umb-help-article h2 { font-size: 16px; margin-top: 20px; }
|
||||
.umb-help-article h3 { font-size: 15px; }
|
||||
.umb-help-article h4 { font-size: 14px; }
|
||||
|
||||
.umb-help-article ol li,
|
||||
.umb-help-article ul li {
|
||||
line-height: 1.4em;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.umb-help-article code {
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.umb-help-article-navigation {
|
||||
margin-top: 25px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
|
||||
/* Help list */
|
||||
|
||||
.umb-help-list {
|
||||
list-style: none;
|
||||
margin-left: 0;
|
||||
margin-bottom: 0;
|
||||
background: @white;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.umb-help-list:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.umb-help-list-item {
|
||||
margin-bottom: 1px;
|
||||
border-radius: 0;
|
||||
border-bottom: 1px solid @gray-9;
|
||||
}
|
||||
|
||||
.umb-help-list-item > a,
|
||||
.umb-help-list-item__content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 10px 20px;
|
||||
}
|
||||
|
||||
.umb-help-list-item > a:hover,
|
||||
.umb-help-list-item > a:focus,
|
||||
.umb-help-list-item > a:active {
|
||||
text-decoration: none;
|
||||
|
||||
.umb-help-list-item__title {
|
||||
text-decoration: underline !important;
|
||||
}
|
||||
}
|
||||
|
||||
.umb-help-list-item__title {
|
||||
font-size: 14px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.umb-help-list-item__description {
|
||||
margin-top: 5px;
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.umb-help-list-item__icon {
|
||||
margin-right: 8px;
|
||||
color: @gray-4;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.umb-help-list-item__open-icon {
|
||||
font-size: 14px;
|
||||
color: @gray-6;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.umb-help-list-item:hover .umb-help-list-item__group-title {
|
||||
text-decoration: underline;
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
.umb-tour__loader {
|
||||
background: @white;
|
||||
z-index: 10000;
|
||||
position: fixed;
|
||||
height: 5px;
|
||||
}
|
||||
|
||||
.umb-tour__pulse {
|
||||
position: fixed;
|
||||
z-index: 10000;
|
||||
display: none;
|
||||
background: transparent;
|
||||
box-shadow: 0 0 0 @green inset;
|
||||
animation: pulse 2s infinite;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0% {
|
||||
box-shadow: 0 0 0 0 @green inset;
|
||||
}
|
||||
70% {
|
||||
box-shadow: 0 0 0 5px fade(@green, 80%) inset;
|
||||
}
|
||||
100% {
|
||||
box-shadow: 0 0 0 0 @green inset;
|
||||
}
|
||||
}
|
||||
|
||||
.umb-tour__popover {
|
||||
position: fixed;
|
||||
background: @white;
|
||||
border-radius: @baseBorderRadius;
|
||||
z-index: 10000;
|
||||
width: 320px;
|
||||
max-width: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: 15px;
|
||||
|
||||
h1, h2, h3, h4, h5 {
|
||||
font-weight: bold;
|
||||
color: @black;
|
||||
}
|
||||
}
|
||||
|
||||
.umb-tour__popover--l {
|
||||
padding: 30px;
|
||||
width: 500px;
|
||||
|
||||
.umb-tour-step__header {
|
||||
margin-bottom: 30px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.umb-tour-step__title {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.umb-tour-step__content {
|
||||
margin-bottom: 25px;
|
||||
font-size: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
.umb-tour-step__counter {
|
||||
font-size: 13px;
|
||||
color: @gray-5;
|
||||
}
|
||||
|
||||
.umb-tour-step__close {
|
||||
position: absolute;
|
||||
top: 15px;
|
||||
right: 15px;
|
||||
font-size: 19px;
|
||||
color: @gray-7;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.umb-tour-step__close:hover,
|
||||
.umb-tour-step__close:active {
|
||||
color: @gray-4;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.umb-tour-step__header {
|
||||
margin-bottom: 10px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.umb-tour-step__title {
|
||||
font-weight: bold;
|
||||
color: @black;
|
||||
font-size: 15px;
|
||||
line-height: 1.3em;
|
||||
width: calc(~"100% - 35px");
|
||||
}
|
||||
|
||||
.umb-tour-step__content {
|
||||
margin-bottom: 15px;
|
||||
font-size: 14px;
|
||||
line-height: 1.6em;
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
.umb-button-group__toggle {
|
||||
padding-left: 8px;
|
||||
padding-right: 8px;
|
||||
float: none;
|
||||
}
|
||||
|
||||
.umb-button-group__sub-buttons.-align-right {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
.umb-button {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
display: inline;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.umb-button__button:focus {
|
||||
@@ -100,6 +99,11 @@
|
||||
}
|
||||
|
||||
/* Sizes */
|
||||
.umb-button--xxs {
|
||||
padding: 2px 10px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.umb-button--xs {
|
||||
padding: 5px 16px;
|
||||
font-size: 14px;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
position: fixed;
|
||||
overflow: hidden;
|
||||
background: @white;
|
||||
z-index: 7500;
|
||||
z-index: @zindexUmbOverlay;
|
||||
animation: fadeIn 0.2s;
|
||||
box-shadow: 0 10px 20px rgba(0,0,0,0.19), 0 6px 6px rgba(0,0,0,0.23);
|
||||
}
|
||||
@@ -136,6 +136,11 @@
|
||||
margin-left: 81px;
|
||||
}
|
||||
|
||||
// push left overlay when drawer is open
|
||||
.umb-drawer-is-visible .umb-overlay.umb-overlay-left {
|
||||
left: @drawerWidth;
|
||||
}
|
||||
|
||||
.umb-overlay.umb-overlay-left .umb-overlay-header {
|
||||
flex-basis: 100px;
|
||||
padding: 20px;
|
||||
|
||||
@@ -3,8 +3,12 @@
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(255, 255, 255, 0.50);
|
||||
z-index: 2000;
|
||||
z-index: @zindexOverlayBackdrop;
|
||||
top: 0;
|
||||
left: 0;
|
||||
animation: fadeIn 0.2s;
|
||||
}
|
||||
|
||||
.umb-drawer-is-visible .umb-overlay-backdrop {
|
||||
left: @drawerWidth;
|
||||
}
|
||||
@@ -6,6 +6,7 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: 0 auto;
|
||||
color: @black;
|
||||
font-weight: bold;
|
||||
font-size: 16px;
|
||||
|
||||
@@ -17,11 +17,16 @@
|
||||
border-color: @turquoise;
|
||||
}
|
||||
|
||||
.umb-badge--seconday {
|
||||
.umb-badge--secondary {
|
||||
background-color: @purple-washed;
|
||||
border-color: @purple;
|
||||
}
|
||||
|
||||
.umb-badge--gray {
|
||||
background-color: @gray-10;
|
||||
border-color: @gray-8;
|
||||
}
|
||||
|
||||
.umb-badge--danger {
|
||||
background-color: @red-washed;
|
||||
border-color: @red;
|
||||
|
||||
@@ -35,3 +35,10 @@
|
||||
margin-right: 5px;
|
||||
color: @gray-7;
|
||||
}
|
||||
|
||||
input.umb-breadcrumbs__add-ancestor {
|
||||
height: 25px;
|
||||
margin-top: -2px;
|
||||
margin-left: 3px;
|
||||
width: 100px;
|
||||
}
|
||||
@@ -6,9 +6,10 @@
|
||||
|
||||
.umb-sub-views-nav-item {
|
||||
text-align: center;
|
||||
margin-left: 20px;
|
||||
//margin-left: 20px;
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
padding: 5px 10px;
|
||||
}
|
||||
|
||||
.umb-sub-views-nav-item:focus {
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
}
|
||||
|
||||
.umb-empty-state.-small {
|
||||
font-size: @fontSizeSmall;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.umb-empty-state.-large {
|
||||
|
||||
@@ -280,7 +280,6 @@
|
||||
.umb-grid .umb-control {
|
||||
position: relative;
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
margin-left: 10px;
|
||||
margin-right: 10px;
|
||||
margin-bottom: 10px;
|
||||
@@ -535,7 +534,7 @@
|
||||
color: @gray-3;
|
||||
}
|
||||
|
||||
.umb-grid .umb-cell-rte textarea {
|
||||
.umb-grid .umb-cell-rte textarea.mceNoEditor {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
@@ -636,9 +635,19 @@
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.umb-grid .mce-btn button {
|
||||
padding: 8px 6px;
|
||||
line-height: inherit;
|
||||
.umb-grid .mce-btn {
|
||||
button {
|
||||
padding-top: 8px;
|
||||
padding-bottom: 8px;
|
||||
padding-left: 6px;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
&:not(.mce-menubtn) {
|
||||
button {
|
||||
padding-right: 6px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.umb-grid .mce-toolbar {
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
margin: 50px 0 0 0;
|
||||
border: 2px solid @gray-7;
|
||||
border-radius: 0 10px 10px 10px;
|
||||
position: relative;
|
||||
padding: 10px 10px 5px 10px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
@@ -34,6 +33,7 @@
|
||||
border: 1px dashed @gray-8;
|
||||
color: @turquoise-d1;
|
||||
font-weight: bold;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.umb-group-builder__group.-sortable {
|
||||
@@ -67,16 +67,18 @@
|
||||
}
|
||||
|
||||
.umb-group-builder__group-title-wrapper {
|
||||
position: absolute;
|
||||
left: -2px;
|
||||
top: -45px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-left: -12px;
|
||||
margin-top: -55px;
|
||||
}
|
||||
|
||||
.umb-group-builder__group-title-wrapper.-placeholder {
|
||||
position: absolute;
|
||||
left: -1px;
|
||||
top: -44px;
|
||||
margin-left: 0;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.umb-group-builder__group-title {
|
||||
@@ -424,103 +426,114 @@ input.umb-group-builder__group-title-input {
|
||||
|
||||
.content-type-editor-dialog.edit-property-settings {
|
||||
|
||||
.validation-wrapper {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.validation-label {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: 0;
|
||||
font-size: 12px;
|
||||
color: @red;
|
||||
transform: translate(0, -50%);
|
||||
}
|
||||
|
||||
textarea.editor-label {
|
||||
border-color:transparent;
|
||||
box-shadow: none;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
margin-bottom: 0;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
resize: none;
|
||||
line-height: 1.5em;
|
||||
padding-left: 0;
|
||||
border: none;
|
||||
&:focus {
|
||||
outline: none;
|
||||
box-shadow: none !important;
|
||||
.validation-wrapper {
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
|
||||
.editor-placeholder {
|
||||
border: 1px dashed @gray-8;
|
||||
width: 100%;
|
||||
height: 80px;
|
||||
line-height: 80px;
|
||||
text-align: center;
|
||||
display: block;
|
||||
border-radius: 5px;
|
||||
color: @gray-3;
|
||||
font-weight: bold;
|
||||
font-size: 14px;
|
||||
color: @turquoise-d1;
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
.editor {
|
||||
margin-bottom: 10px;
|
||||
.editor-icon-wrapper {
|
||||
border: 1px solid @gray-8;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
text-align: center;
|
||||
line-height: 60px;
|
||||
border-radius: 5px;
|
||||
float: left;
|
||||
margin-right: 20px;
|
||||
.icon {
|
||||
font-size: 26px;
|
||||
}
|
||||
}
|
||||
.editor-details {
|
||||
float: left;
|
||||
margin-top: 10px;
|
||||
.editor-name {
|
||||
display: block;
|
||||
font-weight: bold;
|
||||
}
|
||||
.editor-editor {
|
||||
display: block;
|
||||
.validation-label {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: 0;
|
||||
font-size: 12px;
|
||||
}
|
||||
color: @red;
|
||||
transform: translate(0, -50%);
|
||||
}
|
||||
.editor-settings-icon {
|
||||
font-size: 18px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.checkbox {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
textarea.editor-label {
|
||||
border-color: transparent;
|
||||
box-shadow: none;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
margin-bottom: 0;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
resize: none;
|
||||
line-height: 1.5em;
|
||||
padding-left: 0;
|
||||
border: none;
|
||||
|
||||
.editor-description,
|
||||
.editor-validation-pattern {
|
||||
min-width: 100%;
|
||||
min-height: 25px;
|
||||
resize: none;
|
||||
box-sizing: border-box;
|
||||
border: none;
|
||||
overflow: hidden;
|
||||
}
|
||||
&:focus {
|
||||
outline: none;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
.umb-dropdown {
|
||||
width: 100%;
|
||||
}
|
||||
.editor-placeholder {
|
||||
border: 1px dashed @gray-8;
|
||||
width: 100%;
|
||||
height: 80px;
|
||||
line-height: 80px;
|
||||
text-align: center;
|
||||
display: block;
|
||||
border-radius: 5px;
|
||||
color: @gray-3;
|
||||
font-weight: bold;
|
||||
font-size: 14px;
|
||||
color: @turquoise-d1;
|
||||
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
.editor {
|
||||
margin-bottom: 10px;
|
||||
|
||||
.editor-icon-wrapper {
|
||||
border: 1px solid @gray-8;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
text-align: center;
|
||||
line-height: 60px;
|
||||
border-radius: 5px;
|
||||
float: left;
|
||||
margin-right: 20px;
|
||||
|
||||
.icon {
|
||||
font-size: 26px;
|
||||
}
|
||||
}
|
||||
|
||||
.editor-details {
|
||||
float: left;
|
||||
margin-top: 10px;
|
||||
|
||||
.editor-name {
|
||||
display: block;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.editor-editor {
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.editor-settings-icon {
|
||||
font-size: 18px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.checkbox {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.editor-description,
|
||||
.editor-validation-pattern {
|
||||
min-width: 100%;
|
||||
min-height: 25px;
|
||||
resize: none;
|
||||
box-sizing: border-box;
|
||||
border: none;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.umb-dropdown {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
label.checkbox.no-indent {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,11 @@
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.umb-drawer-is-visible .umb-lightbox {
|
||||
width: calc(~'100%' - ~'@{drawerWidth}');
|
||||
left: @drawerWidth;
|
||||
}
|
||||
|
||||
.umb-lightbox__backdrop {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
@@ -40,16 +45,13 @@
|
||||
.umb-lightbox__images {
|
||||
position: relative;
|
||||
z-index: 1000;
|
||||
max-width: calc(~'100%' - 200px); // subtract the width of the two arrow buttons
|
||||
}
|
||||
|
||||
.umb-lightbox__image {
|
||||
background: @white;
|
||||
border-radius: 3px;
|
||||
padding: 10px;
|
||||
img {
|
||||
max-width: 80vw;
|
||||
max-height: 80vh;
|
||||
}
|
||||
}
|
||||
|
||||
.umb-lightbox__control {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user