diff --git a/.editorconfig b/.editorconfig index 5a35b71ce6..ca85827de8 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,40 +1,43 @@ -# editorconfig.org - -# top-most EditorConfig file -root = true - -# Default settings: -# A newline ending every file -# Use 4 spaces as indentation -[*] -insert_final_newline = true -end_of_line = crlf -indent_style = space -indent_size = 4 - -# Trim trailing whitespace, limited support. -# https://github.com/editorconfig/editorconfig/wiki/Property-research:-Trim-trailing-spaces -trim_trailing_whitespace = true - -[*.{cs,vb}] -dotnet_style_predefined_type_for_locals_parameters_members = true:error - -dotnet_naming_rule.private_members_with_underscore.symbols = private_fields -dotnet_naming_rule.private_members_with_underscore.style = prefix_underscore -dotnet_naming_rule.private_members_with_underscore.severity = suggestion - -dotnet_naming_symbols.private_fields.applicable_kinds = field -dotnet_naming_symbols.private_fields.applicable_accessibilities = private - -dotnet_naming_style.prefix_underscore.capitalization = camel_case -dotnet_naming_style.prefix_underscore.required_prefix = _ - -# https://github.com/MicrosoftDocs/visualstudio-docs/blob/master/docs/ide/editorconfig-code-style-settings-reference.md -[*.cs] -csharp_style_var_for_built_in_types = true:suggestion -csharp_style_var_when_type_is_apparent = true:suggestion -csharp_style_var_elsewhere = true:suggestion +# editorconfig.org + +# top-most EditorConfig file +root = true + +# Default settings: +# A newline ending every file +# Use 4 spaces as indentation +[*] +insert_final_newline = true +end_of_line = crlf +indent_style = space +indent_size = 4 + +# Trim trailing whitespace, limited support. +# https://github.com/editorconfig/editorconfig/wiki/Property-research:-Trim-trailing-spaces +trim_trailing_whitespace = true + +[*.{cs,vb}] +dotnet_style_predefined_type_for_locals_parameters_members = true:error + +dotnet_naming_rule.private_members_with_underscore.symbols = private_fields +dotnet_naming_rule.private_members_with_underscore.style = prefix_underscore +dotnet_naming_rule.private_members_with_underscore.severity = suggestion + +dotnet_naming_symbols.private_fields.applicable_kinds = field +dotnet_naming_symbols.private_fields.applicable_accessibilities = private + +dotnet_naming_style.prefix_underscore.capitalization = camel_case +dotnet_naming_style.prefix_underscore.required_prefix = _ + +# https://github.com/MicrosoftDocs/visualstudio-docs/blob/master/docs/ide/editorconfig-code-style-settings-reference.md +[*.cs] +csharp_style_var_for_built_in_types = true:suggestion +csharp_style_var_when_type_is_apparent = true:suggestion +csharp_style_var_elsewhere = true:suggestion csharp_prefer_braces = false : none - -[*.{js,less}] + +[*.js] +trim_trailing_whitespace = true + +[*.less] trim_trailing_whitespace = false diff --git a/.github/BUILD.md b/.github/BUILD.md index ad33872423..5f962a8911 100644 --- a/.github/BUILD.md +++ b/.github/BUILD.md @@ -39,7 +39,7 @@ To build Umbraco, fire up PowerShell and move to Umbraco's repository root (the build/build.ps1 -If you only see a build.bat-file, you're probably on the wrong branch. If you switch to the correct branch (dev-v8) the file will appear and you can build it. +If you only see a build.bat-file, you're probably on the wrong branch. If you switch to the correct branch (v8/contrib) the file will appear and you can build it. You might run into [Powershell quirks](#powershell-quirks). diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md index 1526c54656..e97b03e7e3 100644 --- a/.github/CODE_OF_CONDUCT.md +++ b/.github/CODE_OF_CONDUCT.md @@ -1,32 +1,92 @@ -# Code Of Conduct - -Our informal code of conduct concentrates on the values we, as Umbraco HQ, have set for ourselves and for our community. We expect you to be a friend. -Instead of listing out all the exact "do's" and "don't's" we want to challenge you to think about our values and apply them: +# Umbraco Code of Conduct -If there's a need to talk to Umbraco HQ about anything, please make sure to send a mail to [Sebastiaan Janssen - sj@umbraco.dk](mailto:sj@umbraco.dk). +## Preamble -## Be a Friend +We are the friendly CMS. And our friendliness stems from our values. That's why we have set for ourselves, Umbraco HQ, and the community, five values to guide us in everything we do: -We welcome and thank you for registering at Our Umbraco. Find below the values that govern Umbraco and which you accept by using Our Umbraco. +* Trust - We believe in and empower people +* Respect - We treat others as we would like to be treated +* Open - We share our thoughts and knowledge +* Hungry - We want to do things better, best is next +* Friendly - We want to build long-lasting relationships -## Trust +With these values in mind, we want to offer the Umbraco community a code of conduct that specifies a baseline standard of behavior so that people with different social values and communication styles can work together. -Assume positive intent and try to understand before being understood. +This code of conduct is based on the widely used Contributor Covenant, as described in [https://www.contributor-covenant.org/](https://www.contributor-covenant.org/) -## Respect +## Our Pledge -Treat others as you would like to be treated. +We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. -This also goes for treating the HQ with respect. For example: don’t promote products on [our.umbraco.com](https://our.umbraco.com) that directly compete with our commercial offerings which enables us to work for a sustainable Umbraco. +We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. -## Open +## Our Standards +Examples of behavior that contributes to a positive environment for our community include: -Be honest and straightforward. Tell it as it is. Share thoughts and knowledge and engage in collaboration. +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience +* Focusing on what is best not just for us as individuals, but for the overall community -## Hungry +Examples of unacceptable behavior include: -Don't rest on your laurels and never accept the status quo. Contribute and give back to fellow Umbracians. +* The use of sexualized language or imagery, and sexual attention or advances of any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email address, without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a professional setting -## Friendly +## Enforcement Responsibilities -Don’t judge upon mistakes made but rather upon the speed and quality with which mistakes are corrected. Friendly posts and contributions generate smiles and build long lasting relationships. \ No newline at end of file +Community leaders (e.g. Meetup & festival organizers, moderators, maintainers, ...) are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate. + +Specific enforcement steps are listed in the [Code of Conduct Enforcement Guidelines](https://github.com/umbraco/Umbraco-CMS/blob/v8/contrib/.github/CODE_OF_CONDUCT_ENFORCEMENT.md) document which is an appendix of this document, updated and maintained by the Code of Conduct Team. + +## Scope +This Code of Conduct applies within all community spaces and events supported by Umbraco HQ or using the Umbraco name. It also applies when an individual is officially representing the community in public spaces. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior, may be reported at [conduct@umbraco.com](mailto:conduct@umbraco.com). All complaints will be reviewed and investigated promptly and fairly. + +Or alternatively, you can reach out directly to any of the team members behind the address above: + +* Sebastiaan Janssen (He, Him - Languages spoken: English, Dutch, Danish(Read)) [sebastiaan@umbraco.com](mailto:sebastiaan@umbraco.com) +* Ilham Boulghallat (She, Her - Languages spoken: English, French, Arabic) [ilham@umbraco.com](mailto:ilham@umbraco.com) +* Arnold Visser (He, Him - Languages spoken: English, Dutch) [arnold@umbraco.com](mailto:arnold@umbraco.com) + +The review process is done with full respect for the privacy and security of the reporter of any incident. + +People with a conflict of interest should exclude themselves or if necessary be excluded by the other team members. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct: + +**1. Correction** +Community Impact: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community. + +Consequence: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested. + +**2. Warning** +Community Impact: A violation through a single incident or series of actions. + +Consequence: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban. + +**3. Temporary Ban** +Community Impact: A serious violation of community standards, including sustained inappropriate behavior. + +Consequence: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban. + +**4. Permanent Ban** +Community Impact: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. + +Consequence: A permanent ban from any sort of public interaction within the community. + +## Attribution +This Code of Conduct is adapted from the Contributor Covenant, version 2.0, available at [https://www.contributor-covenant.org/version/2/0/code_of_conduct.html](https://www.contributor-covenant.org/version/2/0/code_of_conduct.html). + +This Code of Conduct will be maintained and reviewed by the team listed above. \ No newline at end of file diff --git a/.github/CODE_OF_CONDUCT_ENFORCEMENT.md b/.github/CODE_OF_CONDUCT_ENFORCEMENT.md new file mode 100644 index 0000000000..2bb45644c2 --- /dev/null +++ b/.github/CODE_OF_CONDUCT_ENFORCEMENT.md @@ -0,0 +1,57 @@ +# Umbraco Code of Conduct Enforcement guidelines - Consequence Ladder + +These are the steps followed by the [Umbraco Code of Conduct Team](https://github.com/umbraco/Umbraco-CMS/blob/v8/contrib/.github/CODE_OF_CONDUCT.md) when we respond to an issue or incident brought to our attention by a community member. + +This is an appendix to the Code of Conduct and is updated and maintained by the Code of Conduct Team. + +To make sure that all reports will be reviewed and investigated promptly and fairly, as highlighted in the Umbraco Code of Conduct, we are following [Mozilla’s Consequence Ladder approach](https://github.com/mozilla/inclusion/blob/master/code-of-conduct-enforcement/consequence-ladder.md). + +This approach helps the Team enforce the Code of Conduct in a structured manner and can be used as a way of communicating escalation. Each time the Team takes an action (warning, ban) the individual is made aware of future consequences. The Team can either follow the order of the levels in the ladder or decide to jump levels. When needed, the team can go directly to a permanent ban. + +**Level 0: No Action** +Recommendations do not indicate a violation of the Code of Conduct. + +**Level 1: Simple Warning Issued** +A private, written warning from the Code of Conduct Team, with clarity of violation, consequences of continued behavior. + +**Level 2: Warning** +A private, written warning from the Code of Conduct Team, with clarity of violation, consequences of continued behavior. Additionally: + +* Communication of next-level consequences if behaviors are repeated (according to this ladder). + +**Level 3: Warning + Mandatory Cooling Off Period (Access Retained)** +A private warning from the Code of Conduct Team, with clarity of violation, consequences of continued behavior. Additionally: + +* Request to avoid interaction on community messaging platforms (public forums, Our, commenting on issues). + * This includes avoiding any interactions in any Umbraco channels, spaces/offices, as well as external channels like social media (e.g. Twitter, Facebook, LinkedIn). For example, 'following/liking/retweeting' would be considered a violation of these terms, and consequence would escalate according to this ladder. +* Require they do not interact with others in the report, or those who they suspect are involved in the report. +* Suggestions for 'out of office' type of message on platforms, to reduce curiosity, or suspicion among those not involved. + +**Level 4: Temporary Ban (Access Revoked)** +Private communication of ban from the Code of Conduct Team, with clarity of violation, consequences of continued behavior. Additionally: + +* 3-6 months imposed break. +* All accounts deactivated, or blocked during this time (Our, HQ Slack if applicable). +* Require to avoid interaction on community messaging platforms (public forums, Our, commenting on issues). + * This includes avoiding any interactions in any Umbraco channels, spaces/offices, as well as external channels like social media (e.g. Twitter, Facebook, LinkedIn). For example, 'following/liking/retweeting' would be considered a violation of these terms, and consequence would escalate according to this ladder. +* All community leadership roles (e.g. Community Teams, Meetup/festival organizer, Commit right on Github..) suspended. (onboarding/reapplication required outside of this process) +* No attendance at Umbraco events during the ban period. +* Not allowed to enter Umbraco HQ offices during the ban period. +* Permission to use the MVP title, if applicable, is revoked during this ban period. +* The community leaders running events and other initiatives are informed of the ban. + +**Level 5: Permanent Ban** +Private communication of ban from the Code of Conduct Team, with clarity of violation, consequences of continued behavior. Additionally: + +* All accounts deactivated permanently. +* No attendance at Umbraco events going forward. +* Not allowed to enter Umbraco HQ offices permanently. +* All community leadership roles (e.g. Community Teams, Meetup/festival organizer, Commit right on Github..) permanently suspended. +* Permission to use the MVP title, if applicable, revoked. +* The community leaders running events and other initiatives are informed of the ban. + + +Sources: +* [Mozilla Code of Conduct - Enforcement Consequence Ladder](https://github.com/mozilla/inclusion/blob/master/code-of-conduct-enforcement/consequence-ladder.md) +* [Drupal Conflict Resolution Policy and Process](https://www.drupal.org/conflict-resolution) +* [Django Code of Conduct - Enforcement Manual](https://www.djangoproject.com/conduct/enforcement-manual/) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 5537a46ef8..f4e237a1f2 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -102,10 +102,11 @@ That said, the Umbraco development team likes to follow the hints that ReSharper The Core Contributors team consists of one member of Umbraco HQ, [Sebastiaan](https://github.com/nul800sebastiaan), who gets assistance from the following community members who have comitted to volunteering their free time: -- [Anders Bjerner](https://github.com/abjerner) -- [Emma Burstow](https://github.com/emmaburstow) -- [Poornima Nayar](https://github.com/poornimanayar) -- [Kenn Jacobsen](https://twitter.com/KennJacobsen_DK) +- [Nathan Woulfe](https://github.com/nathanwoulfe) +- [Joe Glombek](https://github.com/glombek) +- [Laura Weatherhead](https://github.com/lssweatherhead) +- [Michael Latouche](https://github.com/mikecp) +- [Owain Williams](https://github.com/OwainWilliams) These wonderful people aim to provide you with a first reply to your PR, review and test out your changes and on occasions, they might ask more questions. If they are happy with your work, they'll let Umbraco HQ know by approving the PR. Hq will have final sign-off and will check the work again before it is merged. @@ -123,12 +124,12 @@ You can get in touch with [the core contributors team](#the-core-contributors-te In order to build the Umbraco source code locally, first make sure you have the following installed. - * [Visual Studio 2017 v15.9.7+](https://visualstudio.microsoft.com/vs/) + * [Visual Studio 2019 v16.3+ (with .NET Core 3.0)](https://visualstudio.microsoft.com/vs/) * [Node.js v10+](https://nodejs.org/en/download/) * npm v6.4.1+ (installed with Node.js) * [Git command line](https://git-scm.com/download/) -The easiest way to get started is to open `src\umbraco.sln` in Visual Studio 2017 (version 15.9.7 or higher, [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. +The easiest way to get started is to open `src\umbraco.sln` in Visual Studio 2019 (version 16.3 or higher, [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. Alternatively, you can run `build.ps1` from the Powershell command line, 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. diff --git a/.github/ISSUE_TEMPLATE/3_BugNetCore.md b/.github/ISSUE_TEMPLATE/3_BugNetCore.md new file mode 100644 index 0000000000..989904d4d8 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/3_BugNetCore.md @@ -0,0 +1,65 @@ +--- +name: 🌟 .Net Core Bug Report +about: For bugs specifically for the upcoming .NET Core release of Umbraco, don't use this if you're working with Umbraco version 7 or 8 +labels: project/net-core +--- + +ℹ️ If this bug **also** appears on the current version 8 of Umbraco then please [report it as a regular bug](https://github.com/umbraco/Umbraco-CMS/issues/new?template=1_Bug.md), fixes in version 8 will be merged to the .NET Core version. + +A brief description of the issue goes here. + + + + +Reproduction +------------ + +If you're filing a bug, please describe how to reproduce it. Include as much +relevant information as possible, such as: + +### Bug summary + + + +### Specifics + + + +### Steps to reproduce + + + +### Expected result + + + +### Actual result + + diff --git a/.github/ISSUE_TEMPLATE/3_Support_question.md b/.github/ISSUE_TEMPLATE/3_Support_question.md deleted file mode 100644 index 829df982f9..0000000000 --- a/.github/ISSUE_TEMPLATE/3_Support_question.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -name: ⁉️ Support Question -about: Having trouble with Umbraco? -> https://our.umbraco.com ---- - -This issue tracker is NOT meant for support questions. If you have a question, -please join us on the forum at https://our.umbraco.com. - -Thanks! diff --git a/.github/ISSUE_TEMPLATE/4_Documentation_issue.md b/.github/ISSUE_TEMPLATE/4_Documentation_issue.md deleted file mode 100644 index 8893647aa8..0000000000 --- a/.github/ISSUE_TEMPLATE/4_Documentation_issue.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -name: 📖 Documentation Issue -about: See https://github.com/umbraco/UmbracoDocs/issues for documentation issues ---- - -The Umbraco documentation has its own dedicated repository. Please open your -documentation-related issue at https://github.com/umbraco/UmbracoDocs/issues - -Thanks! diff --git a/.github/ISSUE_TEMPLATE/5_Security_issue.md b/.github/ISSUE_TEMPLATE/5_Security_issue.md deleted file mode 100644 index 84c5f5989c..0000000000 --- a/.github/ISSUE_TEMPLATE/5_Security_issue.md +++ /dev/null @@ -1,31 +0,0 @@ ---- -name: 🔐 Security Issue -about: Discovered a Security Issue in Umbraco? ---- - -⚠️ PLEASE DON'T DISCLOSE SECURITY-RELATED ISSUES PUBLICLY, SEE BELOW. - -If you have found a security issue in Umbraco, please send the details to -security@umbraco.com and don't disclose it publicly until we can provide a fix for -it. If you wish, we'll credit you for finding verified issues, when we release -the patched version. - -❗ Please read more about how to report security issues on https://umbraco.com/security - -A note on "Self XSS" --------------------- - -Umbraco is a CMS, that allows users to edit content on a website. As such, -all _authenticated users_ can: - - - Edit content, and (depending on the field types) insert HTML and CSS in that - content, with a variety of allowed attributes. - - Depending on the user level: Edit template files, and insert C#, HTML, CSS and - javascript in so on. - - Upload files to the site, which will become publicly available. - -We see these functionalities as _features_, and not as security issues. Please -report the mentioned items only if they can be performed by non-authorized -users, or other exploitable vulnerabilities. - -Thanks! diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000000..37d1be9158 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,11 @@ +blank_issues_enabled: true +contact_links: + - name: ⁉️ Support Question + url: https://our.umbraco.com + about: This issue tracker is NOT meant for support questions. If you have a question, please join us on the forum. + - name: 📖 Documentation Issue + url: https://github.com/umbraco/UmbracoDocs/issues + about: Documentation issues should be reported on the Umbraco documentation repository. + - name: 🔐 Security Issue + url: https://umbraco.com/about-us/trust-center/security-and-umbraco/how-to-report-a-vulnerability-in-umbraco/ + about: Discovered a Security Issue in Umbraco? \ No newline at end of file diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 0000000000..4dfa5a83b9 --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,61 @@ +name: "Code scanning - action" + +on: + push: + branches: [v8/contrib,v8/dev,v8/bug,v8/feature] + pull_request: + # The branches below must be a subset of the branches above + schedule: + - cron: '0 7 * * 2' + +jobs: + CodeQL-Build: + + runs-on: windows-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + with: + # We must fetch at least the immediate parents so that if this is + # a pull request then we can checkout the head. + fetch-depth: 2 + + # If this run was triggered by a pull request event, then checkout + # the head of the pull request instead of the merge commit. + - run: git checkout HEAD^2 + if: ${{ github.event_name == 'pull_request' }} + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v1 + # Override language selection by uncommenting this and choosing your languages + # with: + # languages: go, javascript, csharp, python, cpp, java + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + + # ℹ️ Command-line programs to run using the OS shell. + # 📚 https://git.io/JvXDl + + # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines + # and modify them (or add more) to build your code if your project + # uses a compiled language + + - name: configure Pagefile + uses: al-cheb/configure-pagefile-action@v1.2 + with: + minimum-size: 8GB + maximum-size: 32GB + + - run: | + echo "Run Umbraco-CMS build" + pwsh -command .\build\build.ps1 + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v1 + with: + config-file: ./.github/codeql-config.yml + + diff --git a/.github/workflows/codeql-config.yml b/.github/workflows/codeql-config.yml new file mode 100644 index 0000000000..59b55e48ec --- /dev/null +++ b/.github/workflows/codeql-config.yml @@ -0,0 +1,13 @@ +name: "CodeQL config" +on: + push: + branches: [v8/contrib,v8/dev] +paths-ignore: + - node_modules + - Umbraco.TestData + - Umbraco.Tests + - Umbraco.Tests.AcceptanceTest + - Umbraco.Tests.Benchmarks + - bin +paths: + - src \ No newline at end of file diff --git a/.gitignore b/.gitignore index 927c2ef570..ebbfe93576 100644 --- a/.gitignore +++ b/.gitignore @@ -167,6 +167,7 @@ build/temp/ # eof /src/Umbraco.Web.UI.Client/TESTS-*.xml /src/ApiDocs/api/* +/src/Umbraco.Web.UI.Client/package-lock.json # Acceptance tests cypress.env.json @@ -174,3 +175,6 @@ cypress.env.json /src/Umbraco.Tests.AcceptanceTest/package-lock.json /src/Umbraco.Tests.AcceptanceTest/cypress/videos/ /src/Umbraco.Tests.AcceptanceTest/cypress/screenshots/ +src/Umbraco.Web.UI/Umbraco/telemetrics-id.umb + +/src/Umbraco.Web.UI/config/umbracoSettings.config diff --git a/build/NuSpecs/UmbracoCms.Web.nuspec b/build/NuSpecs/UmbracoCms.Web.nuspec index 72619db02e..48f5f32222 100644 --- a/build/NuSpecs/UmbracoCms.Web.nuspec +++ b/build/NuSpecs/UmbracoCms.Web.nuspec @@ -42,6 +42,8 @@ + + diff --git a/build/NuSpecs/tools/ReadmeUpgrade.txt b/build/NuSpecs/tools/ReadmeUpgrade.txt index 91d49d896c..fd1b2174e2 100644 --- a/build/NuSpecs/tools/ReadmeUpgrade.txt +++ b/build/NuSpecs/tools/ReadmeUpgrade.txt @@ -11,7 +11,7 @@ Y88 88Y 888 888 888 888 d88P 888 888 888 Y88b. Y88..88P Don't forget to build! -We've done our best to transform your configuration files but in case something is not quite right: we recommmend you look in source control for the previous version so you can find the original files before they were transformed. +We've done our best to transform your configuration files but in case something is not quite right: we recommend you look in source control for the previous version so you can find the original files before they were transformed. This NuGet package includes build targets that extend the creation of a deploy package, which is generated by Publishing from Visual Studio. The targets will only work once Publishing is configured, so if you don't use diff --git a/build/NuSpecs/tools/Web.config.install.xdt b/build/NuSpecs/tools/Web.config.install.xdt index 2b79f95c70..e215bdbf29 100644 --- a/build/NuSpecs/tools/Web.config.install.xdt +++ b/build/NuSpecs/tools/Web.config.install.xdt @@ -1,139 +1,136 @@ - - - - - -
-
-
- - + + + + + + +
+
+
+ + - - - - - - + + + + + + - - - - - + + + + + + - - - - - - - + + + + + + + + - - - - - - + - - - > - - + + + + + + + + + + + + - + + + + + + + + + - - - - - - - - + + + + + - - - - - + + + + + + + + + + + + + + + - - - - - - - + + + + + + + + + + + - - - - - - - - + + + + - - - - - - - - - - - + + + + + + + - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + @@ -177,20 +174,21 @@ - + - - - - - - - - - - - - - + + + + + + + + + + + + + + diff --git a/build/build-bootstrap.ps1 b/build/build-bootstrap.ps1 index 82c789ff22..9a31a57acf 100644 --- a/build/build-bootstrap.ps1 +++ b/build/build-bootstrap.ps1 @@ -34,6 +34,7 @@ if (-not (test-path $nuget)) { Write-Host "Download NuGet..." + [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 Invoke-WebRequest $source -OutFile $nuget if (-not $?) { throw "Failed to download NuGet." } } diff --git a/build/build.ps1 b/build/build.ps1 index c2c5bdd232..15d455b976 100644 --- a/build/build.ps1 +++ b/build/build.ps1 @@ -125,7 +125,23 @@ $error.Clear() Write-Output "### gulp build for version $($this.Version.Release)" >> $log 2>&1 - npx gulp build --buildversion=$this.Version.Release >> $log 2>&1 + npm run build --buildversion=$this.Version.Release >> $log 2>&1 + + # We can ignore this warning, we need to update to node 12 at some point - https://github.com/jsdom/jsdom/issues/2939 + $indexes = [System.Collections.ArrayList]::new() + $index = 0; + $error | ForEach-Object { + # Find which of the errors is the ExperimentalWarning + if($_.ToString().Contains("ExperimentalWarning: The fs.promises API is experimental")) { + [void]$indexes.Add($index) + } + $index++ + } + $indexes | ForEach-Object { + # Loop through the list of indexes and remove the errors that we expect and feel confident we can ignore + $error.Remove($error[$_]) + } + if (-not $?) { throw "Failed to build" } # that one is expected to work } finally { Pop-Location diff --git a/src/SolutionInfo.cs b/src/SolutionInfo.cs index 5587979a6c..3ecfd20f03 100644 --- a/src/SolutionInfo.cs +++ b/src/SolutionInfo.cs @@ -2,7 +2,7 @@ using System.Resources; [assembly: AssemblyCompany("Umbraco")] -[assembly: AssemblyCopyright("Copyright © Umbraco 2020")] +[assembly: AssemblyCopyright("Copyright © Umbraco 2021")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] @@ -18,5 +18,5 @@ using System.Resources; [assembly: AssemblyVersion("8.0.0")] // these are FYI and changed automatically -[assembly: AssemblyFileVersion("8.7.0")] -[assembly: AssemblyInformationalVersion("8.7.0")] +[assembly: AssemblyFileVersion("8.11.1")] +[assembly: AssemblyInformationalVersion("8.11.1")] diff --git a/src/Umbraco.Core/Collections/EventClearingObservableCollection.cs b/src/Umbraco.Core/Collections/EventClearingObservableCollection.cs new file mode 100644 index 0000000000..af25cc1b4a --- /dev/null +++ b/src/Umbraco.Core/Collections/EventClearingObservableCollection.cs @@ -0,0 +1,41 @@ +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Collections.Specialized; + +namespace Umbraco.Core.Collections +{ + /// + /// Allows clearing all event handlers + /// + /// + public class EventClearingObservableCollection : ObservableCollection, INotifyCollectionChanged + { + public EventClearingObservableCollection() + { + } + + public EventClearingObservableCollection(List list) : base(list) + { + } + + public EventClearingObservableCollection(IEnumerable collection) : base(collection) + { + } + + // need to explicitly implement with event accessor syntax in order to override in order to to clear + // c# events are weird, they do not behave the same way as other c# things that are 'virtual', + // a good article is here: https://medium.com/@unicorn_dev/virtual-events-in-c-something-went-wrong-c6f6f5fbe252 + // and https://stackoverflow.com/questions/2268065/c-sharp-language-design-explicit-interface-implementation-of-an-event + private NotifyCollectionChangedEventHandler _changed; + event NotifyCollectionChangedEventHandler INotifyCollectionChanged.CollectionChanged + { + add { _changed += value; } + remove { _changed -= value; } + } + + /// + /// Clears all event handlers for the event + /// + public void ClearCollectionChangedEvents() => _changed = null; + } +} diff --git a/src/Umbraco.Core/Collections/ObservableDictionary.cs b/src/Umbraco.Core/Collections/ObservableDictionary.cs index c6aedab377..fd9e469f07 100644 --- a/src/Umbraco.Core/Collections/ObservableDictionary.cs +++ b/src/Umbraco.Core/Collections/ObservableDictionary.cs @@ -1,11 +1,14 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.Collections.Specialized; +using System.ComponentModel; using System.Linq; using System.Runtime.Serialization; namespace Umbraco.Core.Collections { + /// /// An ObservableDictionary /// @@ -15,7 +18,7 @@ namespace Umbraco.Core.Collections /// /// The type of elements contained in the BindableCollection /// The type of the indexing key - public class ObservableDictionary : ObservableCollection, IReadOnlyDictionary, IDictionary + public class ObservableDictionary : ObservableCollection, IReadOnlyDictionary, IDictionary, INotifyCollectionChanged { protected Dictionary Indecies { get; } protected Func KeySelector { get; } @@ -74,6 +77,22 @@ namespace Umbraco.Core.Collections #endregion + // need to explicitly implement with event accessor syntax in order to override in order to to clear + // c# events are weird, they do not behave the same way as other c# things that are 'virtual', + // a good article is here: https://medium.com/@unicorn_dev/virtual-events-in-c-something-went-wrong-c6f6f5fbe252 + // and https://stackoverflow.com/questions/2268065/c-sharp-language-design-explicit-interface-implementation-of-an-event + private NotifyCollectionChangedEventHandler _changed; + event NotifyCollectionChangedEventHandler INotifyCollectionChanged.CollectionChanged + { + add { _changed += value; } + remove { _changed -= value; } + } + + /// + /// Clears all event handlers + /// + public void ClearCollectionChangedEvents() => _changed = null; + public bool ContainsKey(TKey key) { return Indecies.ContainsKey(key); diff --git a/src/Umbraco.Core/Compose/AuditEventsComponent.cs b/src/Umbraco.Core/Compose/AuditEventsComponent.cs index 033b46c13f..62610ca9c7 100644 --- a/src/Umbraco.Core/Compose/AuditEventsComponent.cs +++ b/src/Umbraco.Core/Compose/AuditEventsComponent.cs @@ -42,7 +42,19 @@ namespace Umbraco.Core.Compose } public void Terminate() - { } + { + UserService.SavedUserGroup -= OnSavedUserGroupWithUsers; + + UserService.SavedUser -= OnSavedUser; + UserService.DeletedUser -= OnDeletedUser; + UserService.UserGroupPermissionsAssigned -= UserGroupPermissionAssigned; + + MemberService.Saved -= OnSavedMember; + MemberService.Deleted -= OnDeletedMember; + MemberService.AssignedRoles -= OnAssignedRoles; + MemberService.RemovedRoles -= OnRemovedRoles; + MemberService.Exported -= OnMemberExported; + } internal static IUser UnknownUser => new User { Id = Constants.Security.UnknownUserId, Name = Constants.Security.UnknownUserName, Email = "" }; diff --git a/src/Umbraco.Core/Compose/RelateOnCopyComponent.cs b/src/Umbraco.Core/Compose/RelateOnCopyComponent.cs index b56ff8b87e..a9f9ed1ee0 100644 --- a/src/Umbraco.Core/Compose/RelateOnCopyComponent.cs +++ b/src/Umbraco.Core/Compose/RelateOnCopyComponent.cs @@ -14,7 +14,9 @@ namespace Umbraco.Core.Compose } public void Terminate() - { } + { + ContentService.Copied -= ContentServiceCopied; + } private static void ContentServiceCopied(IContentService sender, Events.CopyEventArgs e) { diff --git a/src/Umbraco.Core/Compose/RelateOnTrashComponent.cs b/src/Umbraco.Core/Compose/RelateOnTrashComponent.cs index 4e01c50fc6..a216a584ba 100644 --- a/src/Umbraco.Core/Compose/RelateOnTrashComponent.cs +++ b/src/Umbraco.Core/Compose/RelateOnTrashComponent.cs @@ -18,7 +18,12 @@ namespace Umbraco.Core.Compose } public void Terminate() - { } + { + ContentService.Moved -= ContentService_Moved; + ContentService.Trashed -= ContentService_Trashed; + MediaService.Moved -= MediaService_Moved; + MediaService.Trashed -= MediaService_Trashed; + } private static void ContentService_Moved(IContentService sender, MoveEventArgs e) { diff --git a/src/Umbraco.Core/Composing/Current.cs b/src/Umbraco.Core/Composing/Current.cs index a06f09baf6..a4ccd59f9d 100644 --- a/src/Umbraco.Core/Composing/Current.cs +++ b/src/Umbraco.Core/Composing/Current.cs @@ -30,6 +30,7 @@ namespace Umbraco.Core.Composing public static class Current { private static IFactory _factory; + private static IRuntimeState _state; // TODO: get rid of these oddities // we don't want Umbraco tests to die because the container has not been properly initialized, @@ -125,7 +126,17 @@ namespace Umbraco.Core.Composing ?? new ProfilingLogger(Logger, Profiler); public static IRuntimeState RuntimeState - => Factory.GetInstance(); + { + get + { + return _state ?? Factory.GetInstance(); + } + internal set + { + // this is only used when the boot entirely fails, we need to manually set this so we can report + _state = value; + } + } public static TypeLoader TypeLoader => Factory.GetInstance(); diff --git a/src/Umbraco.Core/Composing/TypeFinder.cs b/src/Umbraco.Core/Composing/TypeFinder.cs index 5ad1e43580..394d9480ae 100644 --- a/src/Umbraco.Core/Composing/TypeFinder.cs +++ b/src/Umbraco.Core/Composing/TypeFinder.cs @@ -35,7 +35,7 @@ namespace Umbraco.Core.Composing var s = ConfigurationManager.AppSettings[Constants.AppSettings.AssembliesAcceptingLoadExceptions]; return _assembliesAcceptingLoadExceptions = string.IsNullOrWhiteSpace(s) ? Array.Empty() - : s.Split(',').Select(x => x.Trim()).ToArray(); + : s.Split(Constants.CharArrays.Comma).Select(x => x.Trim()).ToArray(); } } diff --git a/src/Umbraco.Core/CompositionExtensions.cs b/src/Umbraco.Core/CompositionExtensions.cs index ced9a9386a..2fcf446d6b 100644 --- a/src/Umbraco.Core/CompositionExtensions.cs +++ b/src/Umbraco.Core/CompositionExtensions.cs @@ -64,7 +64,7 @@ namespace Umbraco.Core => composition.WithCollectionBuilder(); /// - /// Gets the url segment providers collection builder. + /// Gets the URL segment providers collection builder. /// /// The composition. public static UrlSegmentProviderCollectionBuilder UrlSegmentProviders(this Composition composition) diff --git a/src/Umbraco.Core/Configuration/GlobalSettings.cs b/src/Umbraco.Core/Configuration/GlobalSettings.cs index a888e3c42b..1d1ccaf7b4 100644 --- a/src/Umbraco.Core/Configuration/GlobalSettings.cs +++ b/src/Umbraco.Core/Configuration/GlobalSettings.cs @@ -68,9 +68,9 @@ namespace Umbraco.Core.Configuration internal static bool? HasSmtpServer { get; set; } /// - /// Gets the reserved urls from web.config. + /// Gets the reserved URLs from web.config. /// - /// The reserved urls. + /// The reserved URLs. public string ReservedUrls { get @@ -145,6 +145,20 @@ namespace Umbraco.Core.Configuration } } + /// + /// Gets the path to folder containing the icons used in the umbraco backoffice (/umbraco/assets/icons by default). + /// + /// The icons path. + public string IconsPath + { + get + { + return ConfigurationManager.AppSettings.ContainsKey(Constants.AppSettings.IconsPath) + ? IOHelper.ResolveUrl(Constants.AppSettings.IconsPath) + : $"{Path}/assets/icons"; + } + } + /// /// Gets or sets the configuration status. This will return the version number of the currently installed umbraco instance. /// @@ -340,10 +354,10 @@ namespace Umbraco.Core.Configuration } /// - /// Gets a value indicating whether umbraco should hide top level nodes from generated urls. + /// Gets a value indicating whether umbraco should hide top level nodes from generated URLs. /// /// - /// true if umbraco hides top level nodes from urls; otherwise, false. + /// true if umbraco hides top level nodes from URLs; otherwise, false. /// public bool HideTopLevelNodeFromPath { @@ -377,5 +391,28 @@ namespace Umbraco.Core.Configuration } } } + + + /// + /// An int value representing the time in milliseconds to lock the database for a write operation + /// + /// + /// The default value is 1800 milliseconds + /// + /// The timeout in milliseconds. + public int SqlWriteLockTimeOut + { + get + { + try + { + return int.Parse(ConfigurationManager.AppSettings[Constants.AppSettings.SqlWriteLockTimeOut]); + } + catch + { + return 1800; + } + } + } } } diff --git a/src/Umbraco.Core/Configuration/GlobalSettingsExtensions.cs b/src/Umbraco.Core/Configuration/GlobalSettingsExtensions.cs index bc76caacee..e8fea7f27d 100644 --- a/src/Umbraco.Core/Configuration/GlobalSettingsExtensions.cs +++ b/src/Umbraco.Core/Configuration/GlobalSettingsExtensions.cs @@ -44,7 +44,7 @@ namespace Umbraco.Core.Configuration var path = globalSettings.Path; if (path.StartsWith(SystemDirectories.Root)) // beware of TrimStart, see U4-2518 path = path.Substring(SystemDirectories.Root.Length); - return path.TrimStart('~').TrimStart('/').Replace('/', '-').Trim().ToLower(); + return path.TrimStart(Constants.CharArrays.Tilde).TrimStart(Constants.CharArrays.ForwardSlash).Replace('/', '-').Trim().ToLower(); } } diff --git a/src/Umbraco.Core/Configuration/IGlobalSettings.cs b/src/Umbraco.Core/Configuration/IGlobalSettings.cs index b96434c30c..483829f85f 100644 --- a/src/Umbraco.Core/Configuration/IGlobalSettings.cs +++ b/src/Umbraco.Core/Configuration/IGlobalSettings.cs @@ -1,14 +1,14 @@ namespace Umbraco.Core.Configuration { /// - /// Contains general settings information for the entire Umbraco instance based on information from web.config appsettings + /// Contains general settings information for the entire Umbraco instance based on information from web.config appsettings /// public interface IGlobalSettings { /// - /// Gets the reserved urls from web.config. + /// Gets the reserved URLs from web.config. /// - /// The reserved urls. + /// The reserved URLs. string ReservedUrls { get; } /// @@ -21,7 +21,12 @@ /// Gets the path to umbraco's root directory (/umbraco by default). /// string Path { get; } - + + /// + /// Gets the path to umbraco's icons directory (/umbraco/assets/icons by default). + /// + string IconsPath { get; } + /// /// Gets or sets the configuration status. This will return the version number of the currently installed umbraco instance. /// @@ -40,10 +45,10 @@ string DefaultUILanguage { get; } /// - /// Gets a value indicating whether umbraco should hide top level nodes from generated urls. + /// Gets a value indicating whether umbraco should hide top level nodes from generated URLs. /// /// - /// true if umbraco hides top level nodes from urls; otherwise, false. + /// true if umbraco hides top level nodes from URLs; otherwise, false. /// bool HideTopLevelNodeFromPath { get; } @@ -67,5 +72,10 @@ /// Gets the location of temporary files. /// string LocalTempPath { get; } + + /// + /// Gets the write lock timeout. + /// + int SqlWriteLockTimeOut { get; } } } diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/BackOfficeElement.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/BackOfficeElement.cs index 79bff51d05..a84ae3d31c 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/BackOfficeElement.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/BackOfficeElement.cs @@ -8,5 +8,10 @@ namespace Umbraco.Core.Configuration.UmbracoSettings internal TourConfigElement Tours => (TourConfigElement)this["tours"]; ITourSection IBackOfficeSection.Tours => Tours; + + [ConfigurationProperty("id", DefaultValue = "")] + internal string Id => (string)base["id"]; + + string IBackOfficeSection.Id => (string)base["id"]; } } diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/ContentElement.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/ContentElement.cs index 77ad7df0dc..3ebb632882 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/ContentElement.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/ContentElement.cs @@ -40,6 +40,9 @@ namespace Umbraco.Core.Configuration.UmbracoSettings [ConfigurationProperty("loginBackgroundImage")] internal InnerTextConfigurationElement LoginBackgroundImage => GetOptionalTextElement("loginBackgroundImage", string.Empty); + [ConfigurationProperty("loginLogoImage")] + internal InnerTextConfigurationElement LoginLogoImage => GetOptionalTextElement("loginLogoImage", "assets/img/application/umbraco_logo_white.svg"); + string IContentSection.NotificationEmailAddress => Notifications.NotificationEmailAddress; bool IContentSection.DisableHtmlEmail => Notifications.DisableHtmlEmail; @@ -61,5 +64,7 @@ namespace Umbraco.Core.Configuration.UmbracoSettings bool IContentSection.ShowDeprecatedPropertyEditors => ShowDeprecatedPropertyEditors; string IContentSection.LoginBackgroundImage => LoginBackgroundImage; + + string IContentSection.LoginLogoImage => LoginLogoImage; } } diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/ContentSectionExtensions.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/ContentSectionExtensions.cs index 82cc5928cf..b3980d236a 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/ContentSectionExtensions.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/ContentSectionExtensions.cs @@ -15,7 +15,7 @@ namespace Umbraco.Core.Configuration.UmbracoSettings { if (contentConfig == null) throw new ArgumentNullException(nameof(contentConfig)); if (extension == null) return false; - extension = extension.TrimStart('.'); + extension = extension.TrimStart(Constants.CharArrays.Period); return contentConfig.ImageFileTypes.InvariantContains(extension); } diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/IBackOfficeSection.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/IBackOfficeSection.cs index 36dd6a22ed..32e8e4e3ec 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/IBackOfficeSection.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/IBackOfficeSection.cs @@ -1,7 +1,10 @@ -namespace Umbraco.Core.Configuration.UmbracoSettings +using System; + +namespace Umbraco.Core.Configuration.UmbracoSettings { public interface IBackOfficeSection { ITourSection Tours { get; } + string Id { get; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/IContentSection.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/IContentSection.cs index 228b0923dc..0f52adf02e 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/IContentSection.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/IContentSection.cs @@ -32,5 +32,7 @@ namespace Umbraco.Core.Configuration.UmbracoSettings bool ShowDeprecatedPropertyEditors { get; } string LoginBackgroundImage { get; } + + string LoginLogoImage { get; } } } diff --git a/src/Umbraco.Core/Constants-AppSettings.cs b/src/Umbraco.Core/Constants-AppSettings.cs index 704617d90c..0182034011 100644 --- a/src/Umbraco.Core/Constants-AppSettings.cs +++ b/src/Umbraco.Core/Constants-AppSettings.cs @@ -43,6 +43,11 @@ namespace Umbraco.Core /// public const string Path = "Umbraco.Core.Path"; + /// + /// Gets the path to umbraco's icons directory (/umbraco/assets/icons by default). + /// + public const string IconsPath = "Umbraco.Icons.Path"; + /// /// Gets the path to the css directory (/css by default). /// @@ -59,7 +64,7 @@ namespace Umbraco.Core public const string MediaPath = "umbracoMediaPath"; /// - /// The reserved urls from web.config. + /// The reserved URLs from web.config. /// public const string ReservedUrls = "Umbraco.Core.ReservedUrls"; @@ -96,7 +101,7 @@ namespace Umbraco.Core public const string DefaultUILanguage = "Umbraco.Core.DefaultUILanguage"; /// - /// A true/false value indicating whether umbraco should hide top level nodes from generated urls. + /// A true/false value indicating whether umbraco should hide top level nodes from generated URLs. /// public const string HideTopLevelNodeFromPath = "Umbraco.Core.HideTopLevelNodeFromPath"; @@ -135,6 +140,14 @@ namespace Umbraco.Core /// public const string DatabaseFactoryServerVersion = "Umbraco.Core.Debug.DatabaseFactoryServerVersion"; } + + /// + /// An int value representing the time in milliseconds to lock the database for a write operation + /// + /// + /// The default value is 1800 milliseconds + /// + public const string SqlWriteLockTimeOut = "Umbraco.Core.SqlWriteLockTimeOut"; } } } diff --git a/src/Umbraco.Core/Constants-CharArrays.cs b/src/Umbraco.Core/Constants-CharArrays.cs new file mode 100644 index 0000000000..2f8292b4a4 --- /dev/null +++ b/src/Umbraco.Core/Constants-CharArrays.cs @@ -0,0 +1,138 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Umbraco.Core +{ + public static partial class Constants + { + /// + /// Char Arrays to avoid allocations + /// + public static class CharArrays + { + /// + /// Char array containing only / + /// + public static readonly char[] ForwardSlash = new char[] { '/' }; + + /// + /// Char array containing only \ + /// + public static readonly char[] Backslash = new char[] { '\\' }; + + /// + /// Char array containing only ' + /// + public static readonly char[] SingleQuote = new char[] { '\'' }; + + /// + /// Char array containing only " + /// + public static readonly char[] DoubleQuote = new char[] { '\"' }; + + + /// + /// Char array containing ' " + /// + public static readonly char[] DoubleQuoteSingleQuote = new char[] { '\"', '\'' }; + + /// + /// Char array containing only _ + /// + public static readonly char[] Underscore = new char[] { '_' }; + + /// + /// Char array containing \n \r + /// + public static readonly char[] LineFeedCarriageReturn = new char[] { '\n', '\r' }; + + + /// + /// Char array containing \n + /// + public static readonly char[] LineFeed = new char[] { '\n' }; + + /// + /// Char array containing only , + /// + public static readonly char[] Comma = new char[] { ',' }; + + /// + /// Char array containing only & + /// + public static readonly char[] Ampersand = new char[] { '&' }; + + /// + /// Char array containing only \0 + /// + public static readonly char[] NullTerminator = new char[] { '\0' }; + + /// + /// Char array containing only . + /// + public static readonly char[] Period = new char[] { '.' }; + + /// + /// Char array containing only ~ + /// + public static readonly char[] Tilde = new char[] { '~' }; + /// + /// Char array containing ~ / + /// + public static readonly char[] TildeForwardSlash = new char[] { '~', '/' }; + + /// + /// Char array containing only ? + /// + public static readonly char[] QuestionMark = new char[] { '?' }; + + /// + /// Char array containing ? & + /// + public static readonly char[] QuestionMarkAmpersand = new char[] { '?', '&' }; + + /// + /// Char array containing XML 1.1 whitespace chars + /// + public static readonly char[] XmlWhitespaceChars = new char[] { ' ', '\t', '\r', '\n' }; + + /// + /// Char array containing only the Space char + /// + public static readonly char[] Space = new char[] { ' ' }; + + /// + /// Char array containing only ; + /// + public static readonly char[] Semicolon = new char[] { ';' }; + + /// + /// Char array containing a comma and a space + /// + public static readonly char[] CommaSpace = new char[] { ',', ' ' }; + + /// + /// Char array containing _ - + /// + public static readonly char[] UnderscoreDash = new char[] { '_', '-' }; + + /// + /// Char array containing = + /// + public static readonly char[] EqualsChar = new char[] { '=' }; + + /// + /// Char array containing > + /// + public static readonly char[] GreaterThan = new char[] { '>' }; + + /// + /// Char array containing | + /// + public static readonly char[] VerticalTab = new char[] { '|' }; + } + } +} diff --git a/src/Umbraco.Core/Constants-Icons.cs b/src/Umbraco.Core/Constants-Icons.cs index 05213ed1c4..d5cc37c9a5 100644 --- a/src/Umbraco.Core/Constants-Icons.cs +++ b/src/Umbraco.Core/Constants-Icons.cs @@ -24,6 +24,26 @@ /// public const string DataType = "icon-autofill"; + /// + /// System dictionary icon + /// + public const string Dictionary = "icon-book-alt"; + + /// + /// System generic folder icon + /// + public const string Folder = "icon-folder"; + + /// + /// System language icon + /// + public const string Language = "icon-globe"; + + /// + /// System logviewer icon + /// + public const string LogViewer = "icon-box-alt"; + /// /// System list view icon /// @@ -69,6 +89,11 @@ /// public const string MemberType = "icon-users"; + /// + /// System packages icon + /// + public const string Packages = "icon-box"; + /// /// System property editor icon /// diff --git a/src/Umbraco.Core/Constants-PropertyEditors.cs b/src/Umbraco.Core/Constants-PropertyEditors.cs index eb2b3525a7..90f5fbd0d0 100644 --- a/src/Umbraco.Core/Constants-PropertyEditors.cs +++ b/src/Umbraco.Core/Constants-PropertyEditors.cs @@ -36,6 +36,11 @@ namespace Umbraco.Core /// public static class Aliases { + /// + /// Block List. + /// + public const string BlockList = "Umbraco.BlockList"; + /// /// CheckBox List. /// @@ -187,7 +192,7 @@ namespace Umbraco.Core public const string NestedContent = "Umbraco.NestedContent"; /// - /// Alias for the multi url picker editor. + /// Alias for the multi URL picker editor. /// public const string MultiUrlPicker = "Umbraco.MultiUrlPicker"; } diff --git a/src/Umbraco.Core/Constants-Security.cs b/src/Umbraco.Core/Constants-Security.cs index 43c989805b..f900288ef5 100644 --- a/src/Umbraco.Core/Constants-Security.cs +++ b/src/Umbraco.Core/Constants-Security.cs @@ -56,6 +56,8 @@ namespace Umbraco.Core public const string AllowedApplicationsClaimType = "http://umbraco.org/2015/02/identity/claims/backoffice/allowedapp"; public const string SessionIdClaimType = "http://umbraco.org/2015/02/identity/claims/backoffice/sessionid"; + public const string BackOfficeExternalLoginOptionsProperty = "UmbracoBackOfficeExternalLoginOptions"; + } } } diff --git a/src/Umbraco.Core/Constants-SqlTemplates.cs b/src/Umbraco.Core/Constants-SqlTemplates.cs index 984bc495b0..8529e6bfbc 100644 --- a/src/Umbraco.Core/Constants-SqlTemplates.cs +++ b/src/Umbraco.Core/Constants-SqlTemplates.cs @@ -14,7 +14,30 @@ public const string GetParentNode = "Umbraco.Core.VersionableRepository.GetParentNode"; public const string GetReservedId = "Umbraco.Core.VersionableRepository.GetReservedId"; } + public static class RelationRepository + { + public const string DeleteByParentAll = "Umbraco.Core.RelationRepository.DeleteByParent"; + public const string DeleteByParentIn = "Umbraco.Core.RelationRepository.DeleteByParentIn"; + } + public static class DataTypeRepository + { + public const string EnsureUniqueNodeName = "Umbraco.Core.DataTypeDefinitionRepository.EnsureUniqueNodeName"; + } + + internal static class NuCacheDatabaseDataSource + { + public const string WhereNodeId = "Umbraco.Web.PublishedCache.NuCache.DataSource.WhereNodeId"; + public const string WhereNodeIdX = "Umbraco.Web.PublishedCache.NuCache.DataSource.WhereNodeIdX"; + public const string SourcesSelectUmbracoNodeJoin = "Umbraco.Web.PublishedCache.NuCache.DataSource.SourcesSelectUmbracoNodeJoin"; + public const string ContentSourcesSelect = "Umbraco.Web.PublishedCache.NuCache.DataSource.ContentSourcesSelect"; + public const string ContentSourcesCount = "Umbraco.Web.PublishedCache.NuCache.DataSource.ContentSourcesCount"; + public const string MediaSourcesSelect = "Umbraco.Web.PublishedCache.NuCache.DataSource.MediaSourcesSelect"; + public const string MediaSourcesCount = "Umbraco.Web.PublishedCache.NuCache.DataSource.MediaSourcesCount"; + public const string ObjectTypeNotTrashedFilter = "Umbraco.Web.PublishedCache.NuCache.DataSource.ObjectTypeNotTrashedFilter"; + public const string OrderByLevelIdSortOrder = "Umbraco.Web.PublishedCache.NuCache.DataSource.OrderByLevelIdSortOrder"; + + } } } } diff --git a/src/Umbraco.Core/Constants-SvgSanitizer.cs b/src/Umbraco.Core/Constants-SvgSanitizer.cs new file mode 100644 index 0000000000..c92b9f56c7 --- /dev/null +++ b/src/Umbraco.Core/Constants-SvgSanitizer.cs @@ -0,0 +1,23 @@ +using System.Collections.Generic; + +namespace Umbraco.Core +{ + public static partial class Constants + { + /// + /// Defines the alias identifiers for Umbraco's core application sections. + /// + public static class SvgSanitizer + { + /// + /// Allowlist for SVG attributes. + /// + public static readonly IList Attributes = new [] { "accent-height", "accumulate", "additive", "alignment-baseline", "allowReorder", "alphabetic", "amplitude", "arabic-form", "ascent", "attributeName", "attributeType", "autoReverse", "azimuth", "baseFrequency", "baseline-shift", "baseProfile", "bbox", "begin", "bias", "by", "calcMode", "cap-height", "class", "clip", "clipPathUnits", "clip-path", "clip-rule", "color", "color-interpolation", "color-interpolation-filters", "color-profile", "color-rendering", "contentScriptType", "contentStyleType", "cursor", "cx", "cy", "d", "decelerate", "descent", "diffuseConstant", "direction", "display", "divisor", "dominant-baseline", "dur", "dx", "dy", "edgeMode", "elevation", "enable-background", "end", "exponent", "externalResourcesRequired", "Section", "fill", "fill-opacity", "fill-rule", "filter", "filterRes", "filterUnits", "flood-color", "flood-opacity", "font-family", "font-size", "font-size-adjust", "font-stretch", "font-style", "font-variant", "font-weight", "format", "from", "fr", "fx", "fy", "g1", "g2", "glyph-name", "glyph-orientation-horizontal", "glyph-orientation-vertical", "glyphRef", "gradientTransform", "gradientUnits", "hanging", "height", "href", "hreflang", "horiz-adv-x", "horiz-origin-x", "ISection", "id", "ideographic", "image-rendering", "in", "in2", "intercept", "k", "k1", "k2", "k3", "k4", "kernelMatrix", "kernelUnitLength", "kerning", "keyPoints", "keySplines", "keyTimes", "lang", "lengthAdjust", "letter-spacing", "lighting-color", "limitingConeAngle", "local", "MSection", "marker-end", "marker-mid", "marker-start", "markerHeight", "markerUnits", "markerWidth", "mask", "maskContentUnits", "maskUnits", "mathematical", "max", "media", "method", "min", "mode", "NSection", "name", "numOctaves", "offset", "opacity", "operator", "order", "orient", "orientation", "origin", "overflow", "overline-position", "overline-thickness", "panose-1", "paint-order", "path", "pathLength", "patternContentUnits", "patternTransform", "patternUnits", "ping", "pointer-events", "points", "pointsAtX", "pointsAtY", "pointsAtZ", "preserveAlpha", "preserveAspectRatio", "primitiveUnits", "r", "radius", "referrerPolicy", "refX", "refY", "rel", "rendering-intent", "repeatCount", "repeatDur", "requiredExtensions", "requiredFeatures", "restart", "result", "rotate", "rx", "ry", "scale", "seed", "shape-rendering", "slope", "spacing", "specularConstant", "specularExponent", "speed", "spreadMethod", "startOffset", "stdDeviation", "stemh", "stemv", "stitchTiles", "stop-color", "stop-opacity", "strikethrough-position", "strikethrough-thickness", "string", "stroke", "stroke-dasharray", "stroke-dashoffset", "stroke-linecap", "stroke-linejoin", "stroke-miterlimit", "stroke-opacity", "stroke-width", "style", "surfaceScale", "systemLanguage", "tabindex", "tableValues", "target", "targetX", "targetY", "text-anchor", "text-decoration", "text-rendering", "textLength", "to", "transform", "type", "u1", "u2", "underline-position", "underline-thickness", "unicode", "unicode-bidi", "unicode-range", "units-per-em", "v-alphabetic", "v-hanging", "v-ideographic", "v-mathematical", "values", "vector-effect", "version", "vert-adv-y", "vert-origin-x", "vert-origin-y", "viewBox", "viewTarget", "visibility", "width", "widths", "word-spacing", "writing-mode", "x", "x-height", "x1", "x2", "xChannelSelector", "xlink:actuate", "xlink:arcrole", "xlink:href", "xlink:role", "xlink:show", "xlink:title", "xlink:type", "xml:base", "xml:lang", "xml:space", "y", "y1", "y2", "yChannelSelector", "z", "zoomAndPan" }; + + /// + /// Allowlist for SVG tabs. + /// + public static readonly IList Tags = new [] { "a", "altGlyph", "altGlyphDef", "altGlyphItem", "animate", "animateColor", "animateMotion", "animateTransform", "circle", "clipPath", "color-profile", "cursor", "defs", "desc", "discard", "ellipse", "feBlend", "feColorMatrix", "feComponentTransfer", "feComposite", "feConvolveMatrix", "feDiffuseLighting", "feDisplacementMap", "feDistantLight", "feDropShadow", "feFlood", "feFuncA", "feFuncB", "feFuncG", "feFuncR", "feGaussianBlur", "feImage", "feMerge", "feMergeNode", "feMorphology", "feOffset", "fePointLight", "feSpecularLighting", "feSpotLight", "feTile", "feTurbulence", "filter", "font", "font-face", "font-face-format", "font-face-name", "font-face-src", "font-face-uri", "foreignObject", "g", "glyph", "glyphRef", "hatch", "hatchpath", "hkern", "image", "line", "linearGradient", "marker", "mask", "mesh", "meshgradient", "meshpatch", "meshrow", "metadata", "missing-glyph", "mpath", "path", "pattern", "polygon", "polyline", "radialGradient", "rect", "set", "solidcolor", "stop", "svg", "switch", "symbol", "text", "textPath", "title", "tref", "tspan", "unknown", "use", "view", "vkern" }; + } + } +} diff --git a/src/Umbraco.Core/Constants-Web.cs b/src/Umbraco.Core/Constants-Web.cs index 64216ba571..80906a0663 100644 --- a/src/Umbraco.Core/Constants-Web.cs +++ b/src/Umbraco.Core/Constants-Web.cs @@ -17,6 +17,11 @@ /// The preview cookie name /// public const string PreviewCookieName = "UMB_PREVIEW"; + /// + + /// Client-side cookie that determines whether the user has accepted to be in Preview Mode when visiting the website. + /// + public const string AcceptPreviewCookieName = "UMB-WEBSITE-PREVIEW-ACCEPT"; public const string InstallerCookieName = "umb_installId"; } diff --git a/src/Umbraco.Core/Contants-UdiEntityType.cs b/src/Umbraco.Core/Contants-UdiEntityType.cs index 75a137bd2e..1ed862f328 100644 --- a/src/Umbraco.Core/Contants-UdiEntityType.cs +++ b/src/Umbraco.Core/Contants-UdiEntityType.cs @@ -25,6 +25,7 @@ namespace Umbraco.Core { Unknown, UdiType.Unknown }, { AnyGuid, UdiType.GuidUdi }, + { Element, UdiType.GuidUdi }, { Document, UdiType.GuidUdi }, { DocumentBlueprint, UdiType.GuidUdi }, { Media, UdiType.GuidUdi }, @@ -64,6 +65,8 @@ namespace Umbraco.Core public const string AnyGuid = "any-guid"; // that one is for tests + public const string Element = "element"; + public const string Document = "document"; public const string DocumentBlueprint = "document-blueprint"; diff --git a/src/Umbraco.Core/ContentExtensions.cs b/src/Umbraco.Core/ContentExtensions.cs index 7bce23e98e..eb6339741a 100644 --- a/src/Umbraco.Core/ContentExtensions.cs +++ b/src/Umbraco.Core/ContentExtensions.cs @@ -119,6 +119,15 @@ namespace Umbraco.Core return false; } + /// + /// Returns all properties based on the editorAlias + /// + /// + /// + /// + public static IEnumerable GetPropertiesByEditor(this IContentBase content, string editorAlias) + => content.Properties.Where(x => x.PropertyType.PropertyEditorAlias == editorAlias); + /// /// Returns properties that do not belong to a group /// diff --git a/src/Umbraco.Core/DictionaryExtensions.cs b/src/Umbraco.Core/DictionaryExtensions.cs index d9e998dbd1..88a042dbd5 100644 --- a/src/Umbraco.Core/DictionaryExtensions.cs +++ b/src/Umbraco.Core/DictionaryExtensions.cs @@ -253,7 +253,7 @@ namespace Umbraco.Core { builder.Append(String.Format("{0}={1}&", HttpUtility.UrlEncode(i.Key), i.Value == null ? string.Empty : HttpUtility.UrlEncode(i.Value.ToString()))); } - return builder.ToString().TrimEnd('&'); + return builder.ToString().TrimEnd(Constants.CharArrays.Ampersand); } /// The get entry ignore case. diff --git a/src/Umbraco.Core/Exceptions/UnattendedInstallException.cs b/src/Umbraco.Core/Exceptions/UnattendedInstallException.cs new file mode 100644 index 0000000000..6f672d17cd --- /dev/null +++ b/src/Umbraco.Core/Exceptions/UnattendedInstallException.cs @@ -0,0 +1,46 @@ +using System; +using System.Runtime.Serialization; + +namespace Umbraco.Core.Exceptions +{ + /// + /// An exception that is thrown if an unattended installation occurs. + /// + [Serializable] + public class UnattendedInstallException : Exception + { + /// + /// Initializes a new instance of the class. + /// + public UnattendedInstallException() + { + } + + /// + /// Initializes a new instance of the class with a specified error message. + /// + /// The message that describes the error. + public UnattendedInstallException(string message) : base(message) + { + } + + /// + /// Initializes a new instance of the class with a specified error message + /// and a reference to the inner exception which is the cause of this exception. + /// + /// The message that describes the error. + /// The inner exception, or null. + public UnattendedInstallException(string message, Exception innerException) : base(message, innerException) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The that holds the serialized object data about the exception being thrown. + /// The that contains contextual information about the source or destination. + protected UnattendedInstallException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } + } +} diff --git a/src/Umbraco.Core/GuidUdi.cs b/src/Umbraco.Core/GuidUdi.cs index d78a33a435..3743f6f108 100644 --- a/src/Umbraco.Core/GuidUdi.cs +++ b/src/Umbraco.Core/GuidUdi.cs @@ -33,8 +33,8 @@ namespace Umbraco.Core : base(uriValue) { Guid guid; - if (Guid.TryParse(uriValue.AbsolutePath.TrimStart('/'), out guid) == false) - throw new FormatException("Url \"" + uriValue + "\" is not a guid entity id."); + if (Guid.TryParse(uriValue.AbsolutePath.TrimStart(Constants.CharArrays.ForwardSlash), out guid) == false) + throw new FormatException("URI \"" + uriValue + "\" is not a GUID entity ID."); Guid = guid; } @@ -48,7 +48,7 @@ namespace Umbraco.Core { var udi = Udi.Parse(s); if (udi is GuidUdi == false) - throw new FormatException("String \"" + s + "\" is not a guid entity id."); + throw new FormatException("String \"" + s + "\" is not a GUID entity id."); return (GuidUdi) udi; } diff --git a/src/Umbraco.Core/HttpContextExtensions.cs b/src/Umbraco.Core/HttpContextExtensions.cs index 22eb4d1917..e9ac1aa861 100644 --- a/src/Umbraco.Core/HttpContextExtensions.cs +++ b/src/Umbraco.Core/HttpContextExtensions.cs @@ -50,7 +50,7 @@ namespace Umbraco.Core if (string.IsNullOrEmpty(ipAddress)) return request.UserHostAddress; - var addresses = ipAddress.Split(','); + var addresses = ipAddress.Split(Constants.CharArrays.Comma); if (addresses.Length != 0) return addresses[0]; diff --git a/src/Umbraco.Core/IO/IFileSystem.cs b/src/Umbraco.Core/IO/IFileSystem.cs index 0405c5925a..9de1ea40f2 100644 --- a/src/Umbraco.Core/IO/IFileSystem.cs +++ b/src/Umbraco.Core/IO/IFileSystem.cs @@ -102,7 +102,7 @@ namespace Umbraco.Core.IO /// /// Returns the application relative path to the file. /// - /// The full path or url. + /// The full path or URL. /// /// The representing the relative path. /// @@ -118,11 +118,11 @@ namespace Umbraco.Core.IO string GetFullPath(string path); /// - /// Returns the application relative url to the file. + /// Returns the application relative URL to the file. /// - /// The path to return the url for. + /// The path to return the URL for. /// - /// representing the relative url. + /// representing the relative URL. /// string GetUrl(string path); diff --git a/src/Umbraco.Core/IO/IOHelper.cs b/src/Umbraco.Core/IO/IOHelper.cs index 53aa5a8179..8661f73fb1 100644 --- a/src/Umbraco.Core/IO/IOHelper.cs +++ b/src/Umbraco.Core/IO/IOHelper.cs @@ -40,7 +40,7 @@ namespace Umbraco.Core.IO retval = virtualPath.Replace("~", SystemDirectories.Root); if (virtualPath.StartsWith("/") && virtualPath.StartsWith(SystemDirectories.Root) == false) - retval = SystemDirectories.Root + "/" + virtualPath.TrimStart('/'); + retval = SystemDirectories.Root + "/" + virtualPath.TrimStart(Constants.CharArrays.ForwardSlash); return retval; } @@ -98,11 +98,11 @@ namespace Umbraco.Core.IO if (String.IsNullOrEmpty(path) == false && (path.StartsWith("~") || path.StartsWith(SystemDirectories.Root))) return HostingEnvironment.MapPath(path); else - return HostingEnvironment.MapPath("~/" + path.TrimStart('/')); + return HostingEnvironment.MapPath("~/" + path.TrimStart(Constants.CharArrays.ForwardSlash)); } var root = GetRootDirectorySafe(); - var newPath = path.TrimStart('~', '/').Replace('/', IOHelper.DirSepChar); + var newPath = path.TrimStart(Constants.CharArrays.TildeForwardSlash).Replace('/', IOHelper.DirSepChar); var retval = root + IOHelper.DirSepChar.ToString(CultureInfo.InvariantCulture) + newPath; return retval; @@ -121,7 +121,7 @@ namespace Umbraco.Core.IO if (string.IsNullOrEmpty(retval)) retval = standardPath; - return retval.TrimEnd('/'); + return retval.TrimEnd(Constants.CharArrays.ForwardSlash); } internal static string ReturnPath(string settingsKey, string standardPath) @@ -188,7 +188,7 @@ namespace Umbraco.Core.IO internal static bool VerifyFileExtension(string filePath, IEnumerable validFileExtensions) { var ext = Path.GetExtension(filePath); - return ext != null && validFileExtensions.Contains(ext.TrimStart('.')); + return ext != null && validFileExtensions.Contains(ext.TrimStart(Constants.CharArrays.Period)); } public static bool PathStartsWith(string path, string root, char separator) diff --git a/src/Umbraco.Core/IO/PhysicalFileSystem.cs b/src/Umbraco.Core/IO/PhysicalFileSystem.cs index 96aaf7ca27..a833ba43af 100644 --- a/src/Umbraco.Core/IO/PhysicalFileSystem.cs +++ b/src/Umbraco.Core/IO/PhysicalFileSystem.cs @@ -19,7 +19,7 @@ namespace Umbraco.Core.IO // (is used in GetRelativePath) private readonly string _rootPathFwd; - // the relative url, using url separator chars, NOT ending with a separator + // the relative URL, using URL separator chars, NOT ending with a separator // eg "" or "/Views" or "/Media" or "//Media" in case of a virtual path private readonly string _rootUrl; @@ -33,7 +33,7 @@ namespace Umbraco.Core.IO _rootPath = EnsureDirectorySeparatorChar(IOHelper.MapPath(virtualRoot)).TrimEnd(Path.DirectorySeparatorChar); _rootPathFwd = EnsureUrlSeparatorChar(_rootPath); - _rootUrl = EnsureUrlSeparatorChar(IOHelper.ResolveUrl(virtualRoot)).TrimEnd('/'); + _rootUrl = EnsureUrlSeparatorChar(IOHelper.ResolveUrl(virtualRoot)).TrimEnd(Constants.CharArrays.ForwardSlash); } public PhysicalFileSystem(string rootPath, string rootUrl) @@ -54,7 +54,7 @@ namespace Umbraco.Core.IO _rootPath = EnsureDirectorySeparatorChar(rootPath).TrimEnd(Path.DirectorySeparatorChar); _rootPathFwd = EnsureUrlSeparatorChar(_rootPath); - _rootUrl = EnsureUrlSeparatorChar(rootUrl).TrimEnd('/'); + _rootUrl = EnsureUrlSeparatorChar(rootUrl).TrimEnd(Constants.CharArrays.ForwardSlash); } /// @@ -243,9 +243,9 @@ namespace Umbraco.Core.IO } /// - /// Gets the filesystem-relative path of a full path or of an url. + /// Gets the filesystem-relative path of a full path or of an URL. /// - /// The full path or url. + /// The full path or URL. /// The path, relative to this filesystem's root. /// /// The relative path is relative to this filesystem's root, not starting with any @@ -253,18 +253,18 @@ namespace Umbraco.Core.IO /// public string GetRelativePath(string fullPathOrUrl) { - // test url - var path = fullPathOrUrl.Replace('\\', '/'); // ensure url separator char + // test URL + var path = fullPathOrUrl.Replace('\\', '/'); // ensure URL separator char - // if it starts with the root url, strip it and trim the starting slash to make it relative + // if it starts with the root URL, strip it and trim the starting slash to make it relative // eg "/Media/1234/img.jpg" => "1234/img.jpg" if (IOHelper.PathStartsWith(path, _rootUrl, '/')) - return path.Substring(_rootUrl.Length).TrimStart('/'); + return path.Substring(_rootUrl.Length).TrimStart(Constants.CharArrays.ForwardSlash); // if it starts with the root path, strip it and trim the starting slash to make it relative // eg "c:/websites/test/root/Media/1234/img.jpg" => "1234/img.jpg" if (IOHelper.PathStartsWith(path, _rootPathFwd, '/')) - return path.Substring(_rootPathFwd.Length).TrimStart('/'); + return path.Substring(_rootPathFwd.Length).TrimStart(Constants.CharArrays.ForwardSlash); // unchanged - what else? return path; @@ -319,14 +319,14 @@ namespace Umbraco.Core.IO } /// - /// Gets the url. + /// Gets the URL. /// /// The filesystem-relative path. - /// The url. + /// The URL. /// All separators are forward-slashes. public string GetUrl(string path) { - path = EnsureUrlSeparatorChar(path).Trim('/'); + path = EnsureUrlSeparatorChar(path).Trim(Constants.CharArrays.ForwardSlash); return _rootUrl + "/" + path; } diff --git a/src/Umbraco.Core/IO/ShadowFileSystem.cs b/src/Umbraco.Core/IO/ShadowFileSystem.cs index 84ff1b428b..0e9390d13f 100644 --- a/src/Umbraco.Core/IO/ShadowFileSystem.cs +++ b/src/Umbraco.Core/IO/ShadowFileSystem.cs @@ -182,7 +182,7 @@ namespace Umbraco.Core.IO if (Nodes.TryGetValue(normPath, out sf) && sf.IsExist && (sf.IsDir || overrideIfExists == false)) throw new InvalidOperationException(string.Format("A file at path '{0}' already exists", path)); - var parts = normPath.Split('/'); + var parts = normPath.Split(Constants.CharArrays.ForwardSlash); for (var i = 0; i < parts.Length - 1; i++) { var dirPath = string.Join("/", parts.Take(i + 1)); @@ -297,7 +297,7 @@ namespace Umbraco.Core.IO if (Nodes.TryGetValue(normPath, out sf) && sf.IsExist && (sf.IsDir || overrideIfExists == false)) throw new InvalidOperationException(string.Format("A file at path '{0}' already exists", path)); - var parts = normPath.Split('/'); + var parts = normPath.Split(Constants.CharArrays.ForwardSlash); for (var i = 0; i < parts.Length - 1; i++) { var dirPath = string.Join("/", parts.Take(i + 1)); diff --git a/src/Umbraco.Core/IO/SystemDirectories.cs b/src/Umbraco.Core/IO/SystemDirectories.cs index b4688d2e9f..485fd7f965 100644 --- a/src/Umbraco.Core/IO/SystemDirectories.cs +++ b/src/Umbraco.Core/IO/SystemDirectories.cs @@ -11,6 +11,8 @@ namespace Umbraco.Core.IO public static string Data => "~/App_Data"; + public static string LogFiles => Data + "/Logs"; + public static string TempData => Data + "/TEMP"; public static string TempFileUploads => TempData + "/FileUploads"; diff --git a/src/Umbraco.Core/IO/SystemFiles.cs b/src/Umbraco.Core/IO/SystemFiles.cs index 12e3f57d99..337c97d081 100644 --- a/src/Umbraco.Core/IO/SystemFiles.cs +++ b/src/Umbraco.Core/IO/SystemFiles.cs @@ -7,6 +7,8 @@ namespace Umbraco.Core.IO { public static string TinyMceConfig => SystemDirectories.Config + "/tinyMceConfig.config"; + public static string UmbracoSettings => SystemDirectories.Config + "/UmbracoSettings.config"; + // TODO: Kill this off we don't have umbraco.config XML cache we now have NuCache public static string GetContentCacheXml(IGlobalSettings globalSettings) { diff --git a/src/Umbraco.Core/IRuntimeState.cs b/src/Umbraco.Core/IRuntimeState.cs index 30c768ab01..9f188b5308 100644 --- a/src/Umbraco.Core/IRuntimeState.cs +++ b/src/Umbraco.Core/IRuntimeState.cs @@ -41,7 +41,7 @@ namespace Umbraco.Core ServerRole ServerRole { get; } /// - /// Gets the Umbraco application url. + /// Gets the Umbraco application URL. /// /// This is eg "http://www.example.com". Uri ApplicationUrl { get; } diff --git a/src/Umbraco.Core/Logging/LoggingTaskExtension.cs b/src/Umbraco.Core/Logging/LoggingTaskExtension.cs index 1f742133c3..2e3aa0a883 100644 --- a/src/Umbraco.Core/Logging/LoggingTaskExtension.cs +++ b/src/Umbraco.Core/Logging/LoggingTaskExtension.cs @@ -1,4 +1,5 @@ using System; +using System.Threading; using System.Threading.Tasks; namespace Umbraco.Core.Logging @@ -14,7 +15,12 @@ namespace Umbraco.Core.Logging /// public static Task LogErrors(this Task task, Action logMethod) { - return task.ContinueWith(t => LogErrorsInner(t, logMethod), TaskContinuationOptions.OnlyOnFaulted); + return task.ContinueWith( + t => LogErrorsInner(t, logMethod), + CancellationToken.None, + TaskContinuationOptions.OnlyOnFaulted, + // Must explicitly specify this, see https://blog.stephencleary.com/2013/10/continuewith-is-dangerous-too.html + TaskScheduler.Default); } /// @@ -26,7 +32,10 @@ namespace Umbraco.Core.Logging /// public static Task LogErrorsWaitable(this Task task, Action logMethod) { - return task.ContinueWith(t => LogErrorsInner(t, logMethod)); + return task.ContinueWith( + t => LogErrorsInner(t, logMethod), + // Must explicitly specify this, see https://blog.stephencleary.com/2013/10/continuewith-is-dangerous-too.html + TaskScheduler.Default); } private static void LogErrorsInner(Task task, Action logAction) diff --git a/src/Umbraco.Core/Logging/Serilog/LoggerConfigExtensions.cs b/src/Umbraco.Core/Logging/Serilog/LoggerConfigExtensions.cs index 951d6f8787..6f8c32cf0c 100644 --- a/src/Umbraco.Core/Logging/Serilog/LoggerConfigExtensions.cs +++ b/src/Umbraco.Core/Logging/Serilog/LoggerConfigExtensions.cs @@ -7,6 +7,7 @@ using Serilog.Core; using Serilog.Events; using Serilog.Formatting; using Serilog.Formatting.Compact; +using Umbraco.Core.IO; using Umbraco.Core.Logging.Serilog.Enrichers; namespace Umbraco.Core.Logging.Serilog @@ -26,7 +27,7 @@ namespace Umbraco.Core.Logging.Serilog //Set this environment variable - so that it can be used in external config file //add key="serilog:write-to:RollingFile.pathFormat" value="%BASEDIR%\logs\log.txt" /> - Environment.SetEnvironmentVariable("BASEDIR", AppDomain.CurrentDomain.BaseDirectory, EnvironmentVariableTarget.Process); + Environment.SetEnvironmentVariable("BASEDIR", IOHelper.MapPath("/").TrimEnd("\\"), EnvironmentVariableTarget.Process); Environment.SetEnvironmentVariable("MACHINENAME", Environment.MachineName, EnvironmentVariableTarget.Process); logConfig.MinimumLevel.Verbose() //Set to highest level of logging (as any sinks may want to restrict it to Errors only) @@ -54,7 +55,7 @@ namespace Umbraco.Core.Logging.Serilog { //Main .txt logfile - in similar format to older Log4Net output //Ends with ..txt as Date is inserted before file extension substring - logConfig.WriteTo.File($@"{AppDomain.CurrentDomain.BaseDirectory}\App_Data\Logs\UmbracoTraceLog.{Environment.MachineName}..txt", + logConfig.WriteTo.File(IOHelper.MapPath(SystemDirectories.LogFiles + $"/UmbracoTraceLog.{Environment.MachineName}..txt"), shared: true, rollingInterval: RollingInterval.Day, restrictedToMinimumLevel: minimumLevel, @@ -110,7 +111,8 @@ namespace Umbraco.Core.Logging.Serilog { //.clef format (Compact log event format, that can be imported into local SEQ & will make searching/filtering logs easier) //Ends with ..txt as Date is inserted before file extension substring - logConfig.WriteTo.File(new CompactJsonFormatter(), $@"{AppDomain.CurrentDomain.BaseDirectory}\App_Data\Logs\UmbracoTraceLog.{Environment.MachineName}..json", + logConfig.WriteTo.File(new CompactJsonFormatter(), + IOHelper.MapPath(SystemDirectories.LogFiles + $"/UmbracoTraceLog.{Environment.MachineName}..json"), shared: true, rollingInterval: RollingInterval.Day, //Create a new JSON file every day retainedFileCountLimit: retainedFileCount, //Setting to null means we keep all files - default is 31 days @@ -127,7 +129,7 @@ namespace Umbraco.Core.Logging.Serilog public static LoggerConfiguration ReadFromConfigFile(this LoggerConfiguration logConfig) { //Read from main serilog.config file - logConfig.ReadFrom.AppSettings(filePath: AppDomain.CurrentDomain.BaseDirectory + @"\config\serilog.config"); + logConfig.ReadFrom.AppSettings(filePath: IOHelper.MapPath(SystemDirectories.Config + "/serilog.config")); return logConfig; } @@ -141,7 +143,7 @@ namespace Umbraco.Core.Logging.Serilog { //A nested logger - where any user configured sinks via config can not effect the main 'umbraco' logger above logConfig.WriteTo.Logger(cfg => - cfg.ReadFrom.AppSettings(filePath: AppDomain.CurrentDomain.BaseDirectory + @"\config\serilog.user.config")); + cfg.ReadFrom.AppSettings(filePath: IOHelper.MapPath(SystemDirectories.Config + "/serilog.user.config"))); return logConfig; } diff --git a/src/Umbraco.Core/Logging/Viewer/JsonLogViewer.cs b/src/Umbraco.Core/Logging/Viewer/JsonLogViewer.cs index bbe2f3704d..533c9847ce 100644 --- a/src/Umbraco.Core/Logging/Viewer/JsonLogViewer.cs +++ b/src/Umbraco.Core/Logging/Viewer/JsonLogViewer.cs @@ -5,6 +5,7 @@ using System.Linq; using Newtonsoft.Json; using Serilog.Events; using Serilog.Formatting.Compact.Reader; +using Umbraco.Core.IO; namespace Umbraco.Core.Logging.Viewer { @@ -16,7 +17,7 @@ namespace Umbraco.Core.Logging.Viewer public JsonLogViewer(ILogger logger, string logsPath = "", string searchPath = "") : base(searchPath) { if (string.IsNullOrEmpty(logsPath)) - logsPath = $@"{AppDomain.CurrentDomain.BaseDirectory}\App_Data\Logs\"; + logsPath = IOHelper.MapPath(SystemDirectories.LogFiles); _logsPath = logsPath; _logger = logger; @@ -62,7 +63,7 @@ namespace Umbraco.Core.Logging.Viewer var logs = new List(); //Log Directory - var logDirectory = $@"{AppDomain.CurrentDomain.BaseDirectory}\App_Data\Logs\"; + var logDirectory = _logsPath; var count = 0; diff --git a/src/Umbraco.Core/Media/IEmbedProvider.cs b/src/Umbraco.Core/Media/IEmbedProvider.cs index 99b162e0b7..39da6fae0d 100644 --- a/src/Umbraco.Core/Media/IEmbedProvider.cs +++ b/src/Umbraco.Core/Media/IEmbedProvider.cs @@ -15,7 +15,7 @@ namespace Umbraco.Core.Media string[] UrlSchemeRegex { get; } /// - /// A collection of querystring request parameters to append to the API Url + /// A collection of querystring request parameters to append to the API URL /// /// ?key=value&key2=value2 Dictionary RequestParams { get; } diff --git a/src/Umbraco.Core/Migrations/Install/DatabaseBuilder.cs b/src/Umbraco.Core/Migrations/Install/DatabaseBuilder.cs index d5d8bbab6f..fb9b8af46d 100644 --- a/src/Umbraco.Core/Migrations/Install/DatabaseBuilder.cs +++ b/src/Umbraco.Core/Migrations/Install/DatabaseBuilder.cs @@ -112,6 +112,14 @@ namespace Umbraco.Core.Migrations.Install } } + internal bool IsUmbracoInstalled() + { + using (var scope = _scopeProvider.CreateScope(autoComplete: true)) + { + return scope.Database.IsUmbracoInstalled(_logger); + } + } + #endregion #region Configure Connection String @@ -346,7 +354,7 @@ namespace Umbraco.Core.Migrations.Install var sqlCeDatabaseExists = false; if (dbIsSqlCe) { - var parts = databaseSettings.ConnectionString.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries); + var parts = databaseSettings.ConnectionString.Split(Constants.CharArrays.Semicolon, StringSplitOptions.RemoveEmptyEntries); var dataSourcePart = parts.FirstOrDefault(x => x.InvariantStartsWith("Data Source=")); if (dataSourcePart != null) { @@ -391,15 +399,15 @@ namespace Umbraco.Core.Migrations.Install private DatabaseSchemaResult ValidateSchema(IScope scope) { if (_databaseFactory.Initialized == false) - return new DatabaseSchemaResult(_databaseFactory.SqlContext.SqlSyntax); + return new DatabaseSchemaResult(); if (_databaseSchemaValidationResult != null) return _databaseSchemaValidationResult; - var database = scope.Database; - var dbSchema = new DatabaseSchemaCreator(database, _logger); - _databaseSchemaValidationResult = dbSchema.ValidateSchema(); + _databaseSchemaValidationResult = scope.Database.ValidateSchema(_logger); + scope.Complete(); + return _databaseSchemaValidationResult; } @@ -438,11 +446,9 @@ namespace Umbraco.Core.Migrations.Install var schemaResult = ValidateSchema(); var hasInstalledVersion = schemaResult.DetermineHasInstalledVersion(); - //var installedSchemaVersion = schemaResult.DetermineInstalledVersion(); - //var hasInstalledVersion = !installedSchemaVersion.Equals(new Version(0, 0, 0)); - //If Configuration Status is empty and the determined version is "empty" its a new install - otherwise upgrade the existing - if (string.IsNullOrEmpty(_globalSettings.ConfigurationStatus) && !hasInstalledVersion) + //If the determined version is "empty" its a new install - otherwise upgrade the existing + if (!hasInstalledVersion) { if (_runtime.Level == RuntimeLevel.Run) throw new Exception("Umbraco is already configured!"); diff --git a/src/Umbraco.Core/Migrations/Install/DatabaseSchemaCreator.cs b/src/Umbraco.Core/Migrations/Install/DatabaseSchemaCreator.cs index eab7afe308..e9580da74a 100644 --- a/src/Umbraco.Core/Migrations/Install/DatabaseSchemaCreator.cs +++ b/src/Umbraco.Core/Migrations/Install/DatabaseSchemaCreator.cs @@ -21,8 +21,13 @@ namespace Umbraco.Core.Migrations.Install public DatabaseSchemaCreator(IUmbracoDatabase database, ILogger logger) { - _database = database; - _logger = logger; + _database = database ?? throw new ArgumentNullException(nameof(database)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + + if (_database?.SqlContext?.SqlSyntax == null) + { + throw new InvalidOperationException("No SqlContext has been assigned to the database"); + } } private ISqlSyntaxProvider SqlSyntax => _database.SqlContext.SqlSyntax; @@ -143,7 +148,7 @@ namespace Umbraco.Core.Migrations.Install internal DatabaseSchemaResult ValidateSchema(IEnumerable orderedTables) { - var result = new DatabaseSchemaResult(SqlSyntax); + var result = new DatabaseSchemaResult(); result.IndexDefinitions.AddRange(SqlSyntax.GetDefinedIndexes(_database) .Select(x => new DbIndexDefinition(x))); @@ -445,14 +450,14 @@ namespace Umbraco.Core.Migrations.Install } //Execute the Create Table sql - var created = _database.Execute(new Sql(createSql)); - _logger.Info("Create Table {TableName} ({Created}): \n {Sql}", tableName, created, createSql); + _database.Execute(new Sql(createSql)); + _logger.Info("Create Table {TableName}: \n {Sql}", tableName, createSql); //If any statements exists for the primary key execute them here if (string.IsNullOrEmpty(createPrimaryKeySql) == false) { - var createdPk = _database.Execute(new Sql(createPrimaryKeySql)); - _logger.Info("Create Primary Key ({CreatedPk}):\n {Sql}", createdPk, createPrimaryKeySql); + _database.Execute(new Sql(createPrimaryKeySql)); + _logger.Info("Create Primary Key:\n {Sql}", createPrimaryKeySql); } if (SqlSyntax.SupportsIdentityInsert() && tableDefinition.Columns.Any(x => x.IsIdentity)) @@ -469,24 +474,24 @@ namespace Umbraco.Core.Migrations.Install //Loop through index statements and execute sql foreach (var sql in indexSql) { - var createdIndex = _database.Execute(new Sql(sql)); - _logger.Info("Create Index ({CreatedIndex}):\n {Sql}", createdIndex, sql); + _database.Execute(new Sql(sql)); + _logger.Info("Create Index:\n {Sql}", sql); } //Loop through foreignkey statements and execute sql foreach (var sql in foreignSql) { - var createdFk = _database.Execute(new Sql(sql)); - _logger.Info("Create Foreign Key ({CreatedFk}):\n {Sql}", createdFk, sql); + _database.Execute(new Sql(sql)); + _logger.Info("Create Foreign Key:\n {Sql}", sql); } if (overwrite) { - _logger.Info("Table {TableName} was recreated", tableName); + _logger.Info("Table {TableName} was recreated", tableName); } else { - _logger.Info("New table {TableName} was created", tableName); + _logger.Info("New table {TableName} was created", tableName); } } diff --git a/src/Umbraco.Core/Migrations/Install/DatabaseSchemaResult.cs b/src/Umbraco.Core/Migrations/Install/DatabaseSchemaResult.cs index f21216fde3..c0d8eff311 100644 --- a/src/Umbraco.Core/Migrations/Install/DatabaseSchemaResult.cs +++ b/src/Umbraco.Core/Migrations/Install/DatabaseSchemaResult.cs @@ -2,8 +2,8 @@ using System.Collections.Generic; using System.Linq; using System.Text; +using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.DatabaseModelDefinitions; -using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Core.Migrations.Install { @@ -12,7 +12,7 @@ namespace Umbraco.Core.Migrations.Install /// internal class DatabaseSchemaResult { - public DatabaseSchemaResult(ISqlSyntaxProvider sqlSyntax) + public DatabaseSchemaResult() { Errors = new List>(); TableDefinitions = new List(); diff --git a/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs b/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs index f0cfc08281..2a24c800b5 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs @@ -7,6 +7,9 @@ using Umbraco.Core.Migrations.Upgrade.V_8_0_0; using Umbraco.Core.Migrations.Upgrade.V_8_0_1; using Umbraco.Core.Migrations.Upgrade.V_8_1_0; using Umbraco.Core.Migrations.Upgrade.V_8_6_0; +using Umbraco.Core.Migrations.Upgrade.V_8_9_0; +using Umbraco.Core.Migrations.Upgrade.V_8_10_0; +using Umbraco.Core.Migrations.Upgrade.V_8_12_0; namespace Umbraco.Core.Migrations.Upgrade { @@ -175,25 +178,35 @@ namespace Umbraco.Core.Migrations.Upgrade To("{78BAF571-90D0-4D28-8175-EF96316DA789}"); // release-8.0.0 - // to 8.0.1... + // to 8.0.1 To("{80C0A0CB-0DD5-4573-B000-C4B7C313C70D}"); // release-8.0.1 - // to 8.1.0... + // to 8.1.0 To("{B69B6E8C-A769-4044-A27E-4A4E18D1645A}"); To("{0372A42B-DECF-498D-B4D1-6379E907EB94}"); To("{5B1E0D93-F5A3-449B-84BA-65366B84E2D4}"); - // to 8.6.0... + // to 8.6.0 To("{4759A294-9860-46BC-99F9-B4C975CAE580}"); To("{0BC866BC-0665-487A-9913-0290BD0169AD}"); To("{3D67D2C8-5E65-47D0-A9E1-DC2EE0779D6B}"); To("{EE288A91-531B-4995-8179-1D62D9AA3E2E}"); To("{2AB29964-02A1-474D-BD6B-72148D2A53A2}"); - + // to 8.7.0 To("{a78e3369-8ea3-40ec-ad3f-5f76929d2b20}"); + // to 8.9.0 + To("{B5838FF5-1D22-4F6C-BCEB-F83ACB14B575}"); + + // to 8.10.0 + To("{D6A8D863-38EC-44FB-91EC-ACD6A668BD18}"); + + // to 8.12.0... + To("{8DDDCD0B-D7D5-4C97-BD6A-6B38CA65752F}"); + To("{4695D0C9-0729-4976-985B-048D503665D8}"); + //FINAL } } diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/PropertyEditorsMigrationBase.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/PropertyEditorsMigrationBase.cs index 605f8a9eed..423ba71401 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/PropertyEditorsMigrationBase.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/PropertyEditorsMigrationBase.cs @@ -30,7 +30,7 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 protected int[] ConvertStringValues(string val) { - var splitVals = val.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + var splitVals = val.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries); var intVals = splitVals .Select(x => int.TryParse(x, out var i) ? i : int.MinValue) diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_10_0/AddPropertyTypeLabelOnTopColumn.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_10_0/AddPropertyTypeLabelOnTopColumn.cs new file mode 100644 index 0000000000..206ea2be02 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_10_0/AddPropertyTypeLabelOnTopColumn.cs @@ -0,0 +1,20 @@ +using System.Linq; +using Umbraco.Core.Persistence.Dtos; + +namespace Umbraco.Core.Migrations.Upgrade.V_8_10_0 +{ + + public class AddPropertyTypeLabelOnTopColumn : MigrationBase + { + public AddPropertyTypeLabelOnTopColumn(IMigrationContext context) + : base(context) + { } + + public override void Migrate() + { + var columns = SqlSyntax.GetColumnsInSchema(Context.Database).ToList(); + + AddColumnIfNotExists(columns, "labelOnTop"); + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_12_0/AddCmsContentNuByteColumn.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_12_0/AddCmsContentNuByteColumn.cs new file mode 100644 index 0000000000..7c793688ec --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_12_0/AddCmsContentNuByteColumn.cs @@ -0,0 +1,21 @@ +using System.Linq; +using Umbraco.Core.Persistence.Dtos; + +namespace Umbraco.Core.Migrations.Upgrade.V_8_12_0 +{ + public class AddCmsContentNuByteColumn : MigrationBase + { + public AddCmsContentNuByteColumn(IMigrationContext context) + : base(context) + { + + } + + public override void Migrate() + { + var columns = SqlSyntax.GetColumnsInSchema(Context.Database).ToList(); + + AddColumnIfNotExists(columns, "dataRaw"); + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_12_0/UpgradedIncludeIndexes.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_12_0/UpgradedIncludeIndexes.cs new file mode 100644 index 0000000000..d88abdef75 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_12_0/UpgradedIncludeIndexes.cs @@ -0,0 +1,65 @@ +using System.Linq; +using Umbraco.Core.Migrations.Expressions.Execute.Expressions; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; +using Umbraco.Core.Persistence.Dtos; + +namespace Umbraco.Core.Migrations.Upgrade.V_8_12_0 +{ + public class UpgradedIncludeIndexes : MigrationBase + { + public UpgradedIncludeIndexes(IMigrationContext context) + : base(context) + { + + } + + public override void Migrate() + { + // Need to drop the FK for the redirect table before modifying the unique id index + Delete.ForeignKey() + .FromTable(Constants.DatabaseSchema.Tables.RedirectUrl) + .ForeignColumn("contentKey") + .ToTable(NodeDto.TableName) + .PrimaryColumn("uniqueID") + .Do(); + var nodeDtoIndexes = new[] { $"IX_{NodeDto.TableName}_UniqueId", $"IX_{NodeDto.TableName}_ObjectType", $"IX_{NodeDto.TableName}_Level" }; + DeleteIndexes(nodeDtoIndexes); // delete existing ones + CreateIndexes(nodeDtoIndexes); // update/add + // Now re-create the FK for the redirect table + Create.ForeignKey() + .FromTable(Constants.DatabaseSchema.Tables.RedirectUrl) + .ForeignColumn("contentKey") + .ToTable(NodeDto.TableName) + .PrimaryColumn("uniqueID") + .Do(); + + + var contentVersionIndexes = new[] { $"IX_{ContentVersionDto.TableName}_NodeId", $"IX_{ContentVersionDto.TableName}_Current" }; + DeleteIndexes(contentVersionIndexes); // delete existing ones + CreateIndexes(contentVersionIndexes); // update/add + } + + private void DeleteIndexes(params string[] toDelete) + { + var tableDef = DefinitionFactory.GetTableDefinition(typeof(T), Context.SqlContext.SqlSyntax); + + foreach (var i in toDelete) + if (IndexExists(i)) + Delete.Index(i).OnTable(tableDef.Name).Do(); + + } + + private void CreateIndexes(params string[] toCreate) + { + var tableDef = DefinitionFactory.GetTableDefinition(typeof(T), Context.SqlContext.SqlSyntax); + + foreach (var c in toCreate) + { + // get the definition by name + var index = tableDef.Indexes.First(x => x.Name == c); + new ExecuteSqlStatementExpression(Context) { SqlStatement = Context.SqlContext.SqlSyntax.Format(index) }.Execute(); + } + + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_9_0/ExternalLoginTableUserData.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_9_0/ExternalLoginTableUserData.cs new file mode 100644 index 0000000000..45bc1c620b --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_9_0/ExternalLoginTableUserData.cs @@ -0,0 +1,20 @@ +using Umbraco.Core.Persistence.Dtos; + +namespace Umbraco.Core.Migrations.Upgrade.V_8_9_0 +{ + public class ExternalLoginTableUserData : MigrationBase + { + public ExternalLoginTableUserData(IMigrationContext context) + : base(context) + { + } + + /// + /// Adds new column to the External Login table + /// + public override void Migrate() + { + AddColumn(Constants.DatabaseSchema.Tables.ExternalLogin, "userData"); + } + } +} diff --git a/src/Umbraco.Core/Models/Blocks/BlockEditorData.cs b/src/Umbraco.Core/Models/Blocks/BlockEditorData.cs new file mode 100644 index 0000000000..d8186f0dfa --- /dev/null +++ b/src/Umbraco.Core/Models/Blocks/BlockEditorData.cs @@ -0,0 +1,45 @@ +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; + +namespace Umbraco.Core.Models.Blocks +{ + + /// + /// Convertable block data from json + /// + public class BlockEditorData + { + private readonly string _propertyEditorAlias; + + public static BlockEditorData Empty { get; } = new BlockEditorData(); + + private BlockEditorData() + { + BlockValue = new BlockValue(); + } + + public BlockEditorData(string propertyEditorAlias, + IEnumerable references, + BlockValue blockValue) + { + if (string.IsNullOrWhiteSpace(propertyEditorAlias)) + throw new ArgumentException($"'{nameof(propertyEditorAlias)}' cannot be null or whitespace", nameof(propertyEditorAlias)); + _propertyEditorAlias = propertyEditorAlias; + BlockValue = blockValue ?? throw new ArgumentNullException(nameof(blockValue)); + References = references != null ? new List(references) : throw new ArgumentNullException(nameof(references)); + } + + /// + /// Returns the layout for this specific property editor + /// + public JToken Layout => BlockValue.Layout.TryGetValue(_propertyEditorAlias, out var layout) ? layout : null; + + /// + /// Returns the reference to the original BlockValue + /// + public BlockValue BlockValue { get; } + + public List References { get; } = new List(); + } +} diff --git a/src/Umbraco.Core/Models/Blocks/BlockEditorDataConverter.cs b/src/Umbraco.Core/Models/Blocks/BlockEditorDataConverter.cs new file mode 100644 index 0000000000..802e8c2ee3 --- /dev/null +++ b/src/Umbraco.Core/Models/Blocks/BlockEditorDataConverter.cs @@ -0,0 +1,68 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System.Linq; +using System.Collections.Generic; + +namespace Umbraco.Core.Models.Blocks +{ + + /// + /// Converts the block json data into objects + /// + public abstract class BlockEditorDataConverter + { + private readonly string _propertyEditorAlias; + + protected BlockEditorDataConverter(string propertyEditorAlias) + { + _propertyEditorAlias = propertyEditorAlias; + } + + public BlockEditorData ConvertFrom(JToken json) + { + var value = json.ToObject(); + return Convert(value); + } + + public bool TryDeserialize(string json, out BlockEditorData blockEditorData) + { + try + { + var value = JsonConvert.DeserializeObject(json); + blockEditorData = Convert(value); + return true; + } + catch (System.Exception) + { + blockEditorData = null; + return false; + } + } + + public BlockEditorData Deserialize(string json) + { + var value = JsonConvert.DeserializeObject(json); + return Convert(value); + } + + private BlockEditorData Convert(BlockValue value) + { + if (value.Layout == null) + return BlockEditorData.Empty; + + var references = value.Layout.TryGetValue(_propertyEditorAlias, out var layout) + ? GetBlockReferences(layout) + : Enumerable.Empty(); + + return new BlockEditorData(_propertyEditorAlias, references, value); + } + + /// + /// Return the collection of from the block editor's Layout (which could be an array or an object depending on the editor) + /// + /// + /// + protected abstract IEnumerable GetBlockReferences(JToken jsonLayout); + + } +} diff --git a/src/Umbraco.Core/Models/Blocks/BlockItemData.cs b/src/Umbraco.Core/Models/Blocks/BlockItemData.cs new file mode 100644 index 0000000000..02432766b0 --- /dev/null +++ b/src/Umbraco.Core/Models/Blocks/BlockItemData.cs @@ -0,0 +1,62 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using Umbraco.Core.Serialization; + +namespace Umbraco.Core.Models.Blocks +{ + /// + /// Represents a single block's data in raw form + /// + public class BlockItemData + { + [JsonProperty("contentTypeKey")] + public Guid ContentTypeKey { get; set; } + + /// + /// not serialized, manually set and used during internally + /// + [JsonIgnore] + public string ContentTypeAlias { get; set; } + + [JsonProperty("udi")] + [JsonConverter(typeof(UdiJsonConverter))] + public Udi Udi { get; set; } + + [JsonIgnore] + public Guid Key => Udi != null ? ((GuidUdi)Udi).Guid : throw new InvalidOperationException("No Udi assigned"); + + /// + /// The remaining properties will be serialized to a dictionary + /// + /// + /// The JsonExtensionDataAttribute is used to put the non-typed properties into a bucket + /// http://www.newtonsoft.com/json/help/html/DeserializeExtensionData.htm + /// NestedContent serializes to string, int, whatever eg + /// "stringValue":"Some String","numericValue":125,"otherNumeric":null + /// + [JsonExtensionData] + public Dictionary RawPropertyValues { get; set; } = new Dictionary(); + + /// + /// Used during deserialization to convert the raw property data into data with a property type context + /// + [JsonIgnore] + public IDictionary PropertyValues { get; set; } = new Dictionary(); + + /// + /// Used during deserialization to populate the property value/property type of a block item content property + /// + public class BlockPropertyValue + { + public BlockPropertyValue(object value, PropertyType propertyType) + { + Value = value; + PropertyType = propertyType ?? throw new ArgumentNullException(nameof(propertyType)); + } + + public object Value { get; } + public PropertyType PropertyType { get; } + } + } +} diff --git a/src/Umbraco.Core/Models/Blocks/BlockListEditorDataConverter.cs b/src/Umbraco.Core/Models/Blocks/BlockListEditorDataConverter.cs new file mode 100644 index 0000000000..23f69922d9 --- /dev/null +++ b/src/Umbraco.Core/Models/Blocks/BlockListEditorDataConverter.cs @@ -0,0 +1,22 @@ +using Newtonsoft.Json.Linq; +using System.Linq; +using System.Collections.Generic; + +namespace Umbraco.Core.Models.Blocks +{ + /// + /// Data converter for the block list property editor + /// + public class BlockListEditorDataConverter : BlockEditorDataConverter + { + public BlockListEditorDataConverter() : base(Constants.PropertyEditors.Aliases.BlockList) + { + } + + protected override IEnumerable GetBlockReferences(JToken jsonLayout) + { + var blockListLayout = jsonLayout.ToObject>(); + return blockListLayout.Select(x => new ContentAndSettingsReference(x.ContentUdi, x.SettingsUdi)).ToList(); + } + } +} diff --git a/src/Umbraco.Core/Models/Blocks/BlockListItem.cs b/src/Umbraco.Core/Models/Blocks/BlockListItem.cs new file mode 100644 index 0000000000..620c3d9fe0 --- /dev/null +++ b/src/Umbraco.Core/Models/Blocks/BlockListItem.cs @@ -0,0 +1,130 @@ +using System; +using System.Runtime.Serialization; +using Umbraco.Core.Models.PublishedContent; + +namespace Umbraco.Core.Models.Blocks +{ + /// + /// Represents a layout item for the Block List editor. + /// + /// + [DataContract(Name = "block", Namespace = "")] + public class BlockListItem : IBlockReference + { + /// + /// Initializes a new instance of the class. + /// + /// The content UDI. + /// The content. + /// The settings UDI. + /// The settings. + /// contentUdi + /// or + /// content + public BlockListItem(Udi contentUdi, IPublishedElement content, Udi settingsUdi, IPublishedElement settings) + { + ContentUdi = contentUdi ?? throw new ArgumentNullException(nameof(contentUdi)); + Content = content ?? throw new ArgumentNullException(nameof(content)); + SettingsUdi = settingsUdi; + Settings = settings; + } + + /// + /// Gets the content UDI. + /// + /// + /// The content UDI. + /// + [DataMember(Name = "contentUdi")] + public Udi ContentUdi { get; } + + /// + /// Gets the content. + /// + /// + /// The content. + /// + [DataMember(Name = "content")] + public IPublishedElement Content { get; } + + /// + /// Gets the settings UDI. + /// + /// + /// The settings UDI. + /// + [DataMember(Name = "settingsUdi")] + public Udi SettingsUdi { get; } + + /// + /// Gets the settings. + /// + /// + /// The settings. + /// + [DataMember(Name = "settings")] + public IPublishedElement Settings { get; } + } + + /// + /// Represents a layout item with a generic content type for the Block List editor. + /// + /// The type of the content. + /// + public class BlockListItem : BlockListItem + where T : IPublishedElement + { + /// + /// Initializes a new instance of the class. + /// + /// The content UDI. + /// The content. + /// The settings UDI. + /// The settings. + public BlockListItem(Udi contentUdi, T content, Udi settingsUdi, IPublishedElement settings) + : base(contentUdi, content, settingsUdi, settings) + { + Content = content; + } + + /// + /// Gets the content. + /// + /// + /// The content. + /// + public new T Content { get; } + } + + /// + /// Represents a layout item with generic content and settings types for the Block List editor. + /// + /// The type of the content. + /// The type of the settings. + /// + public class BlockListItem : BlockListItem + where TContent : IPublishedElement + where TSettings : IPublishedElement + { + /// + /// Initializes a new instance of the class. + /// + /// The content udi. + /// The content. + /// The settings udi. + /// The settings. + public BlockListItem(Udi contentUdi, TContent content, Udi settingsUdi, TSettings settings) + : base(contentUdi, content, settingsUdi, settings) + { + Settings = settings; + } + + /// + /// Gets the settings. + /// + /// + /// The settings. + /// + public new TSettings Settings { get; } + } +} diff --git a/src/Umbraco.Core/Models/Blocks/BlockListLayoutItem.cs b/src/Umbraco.Core/Models/Blocks/BlockListLayoutItem.cs new file mode 100644 index 0000000000..5de44e16c1 --- /dev/null +++ b/src/Umbraco.Core/Models/Blocks/BlockListLayoutItem.cs @@ -0,0 +1,19 @@ +using Newtonsoft.Json; +using Umbraco.Core.Serialization; + +namespace Umbraco.Core.Models.Blocks +{ + /// + /// Used for deserializing the block list layout + /// + public class BlockListLayoutItem + { + [JsonProperty("contentUdi", Required = Required.Always)] + [JsonConverter(typeof(UdiJsonConverter))] + public Udi ContentUdi { get; set; } + + [JsonProperty("settingsUdi", NullValueHandling = NullValueHandling.Ignore)] + [JsonConverter(typeof(UdiJsonConverter))] + public Udi SettingsUdi { get; set; } + } +} diff --git a/src/Umbraco.Core/Models/Blocks/BlockListModel.cs b/src/Umbraco.Core/Models/Blocks/BlockListModel.cs new file mode 100644 index 0000000000..9a3a26ab30 --- /dev/null +++ b/src/Umbraco.Core/Models/Blocks/BlockListModel.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Runtime.Serialization; + +namespace Umbraco.Core.Models.Blocks +{ + /// + /// The strongly typed model for the Block List editor. + /// + /// + [DataContract(Name = "blockList", Namespace = "")] + public class BlockListModel : ReadOnlyCollection + { + /// + /// Gets the empty . + /// + /// + /// The empty . + /// + public static BlockListModel Empty { get; } = new BlockListModel(); + + /// + /// Prevents a default instance of the class from being created. + /// + private BlockListModel() + : this(new List()) + { } + + /// + /// Initializes a new instance of the class. + /// + /// The list to wrap. + public BlockListModel(IList list) + : base(list) + { } + + /// + /// Gets the with the specified content key. + /// + /// + /// The . + /// + /// The content key. + /// + /// The with the specified content key. + /// + public BlockListItem this[Guid contentKey] => this.FirstOrDefault(x => x.Content.Key == contentKey); + + /// + /// Gets the with the specified content UDI. + /// + /// + /// The . + /// + /// The content UDI. + /// + /// The with the specified content UDI. + /// + public BlockListItem this[Udi contentUdi] => contentUdi is GuidUdi guidUdi ? this.FirstOrDefault(x => x.Content.Key == guidUdi.Guid) : null; + } +} diff --git a/src/Umbraco.Core/Models/Blocks/BlockValue.cs b/src/Umbraco.Core/Models/Blocks/BlockValue.cs new file mode 100644 index 0000000000..4700ddfd3b --- /dev/null +++ b/src/Umbraco.Core/Models/Blocks/BlockValue.cs @@ -0,0 +1,18 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System.Collections.Generic; + +namespace Umbraco.Core.Models.Blocks +{ + public class BlockValue + { + [JsonProperty("layout")] + public IDictionary Layout { get; set; } + + [JsonProperty("contentData")] + public List ContentData { get; set; } = new List(); + + [JsonProperty("settingsData")] + public List SettingsData { get; set; } = new List(); + } +} diff --git a/src/Umbraco.Core/Models/Blocks/ContentAndSettingsReference.cs b/src/Umbraco.Core/Models/Blocks/ContentAndSettingsReference.cs new file mode 100644 index 0000000000..f7222fe140 --- /dev/null +++ b/src/Umbraco.Core/Models/Blocks/ContentAndSettingsReference.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; + +namespace Umbraco.Core.Models.Blocks +{ + public struct ContentAndSettingsReference : IEquatable + { + public ContentAndSettingsReference(Udi contentUdi, Udi settingsUdi) + { + ContentUdi = contentUdi ?? throw new ArgumentNullException(nameof(contentUdi)); + SettingsUdi = settingsUdi; + } + + public Udi ContentUdi { get; } + + public Udi SettingsUdi { get; } + + public override bool Equals(object obj) => obj is ContentAndSettingsReference reference && Equals(reference); + + public bool Equals(ContentAndSettingsReference other) => other != null + && EqualityComparer.Default.Equals(ContentUdi, other.ContentUdi) + && EqualityComparer.Default.Equals(SettingsUdi, other.SettingsUdi); + + public override int GetHashCode() => (ContentUdi, SettingsUdi).GetHashCode(); + + public static bool operator ==(ContentAndSettingsReference left, ContentAndSettingsReference right) + { + return left.Equals(right); + } + + public static bool operator !=(ContentAndSettingsReference left, ContentAndSettingsReference right) + { + return !(left == right); + } + } +} diff --git a/src/Umbraco.Core/Models/Blocks/IBlockReference.cs b/src/Umbraco.Core/Models/Blocks/IBlockReference.cs new file mode 100644 index 0000000000..7f5c835b3c --- /dev/null +++ b/src/Umbraco.Core/Models/Blocks/IBlockReference.cs @@ -0,0 +1,37 @@ +namespace Umbraco.Core.Models.Blocks +{ + /// + /// Represents a data item reference for a Block Editor implementation. + /// + /// + /// See: https://github.com/umbraco/rfcs/blob/907f3758cf59a7b6781296a60d57d537b3b60b8c/cms/0011-block-data-structure.md#strongly-typed + /// + public interface IBlockReference + { + /// + /// Gets the content UDI. + /// + /// + /// The content UDI. + /// + Udi ContentUdi { get; } + } + + /// + /// Represents a data item reference with settings for a Block editor implementation. + /// + /// The type of the settings. + /// + /// See: https://github.com/umbraco/rfcs/blob/907f3758cf59a7b6781296a60d57d537b3b60b8c/cms/0011-block-data-structure.md#strongly-typed + /// + public interface IBlockReference : IBlockReference + { + /// + /// Gets the settings. + /// + /// + /// The settings. + /// + TSettings Settings { get; } + } +} diff --git a/src/Umbraco.Core/Models/Content.cs b/src/Umbraco.Core/Models/Content.cs index c5930bf998..04f49e704e 100644 --- a/src/Umbraco.Core/Models/Content.cs +++ b/src/Umbraco.Core/Models/Content.cs @@ -98,10 +98,15 @@ namespace Umbraco.Core.Models set { if (_schedule != null) - _schedule.CollectionChanged -= ScheduleCollectionChanged; + { + _schedule.ClearCollectionChangedEvents(); + } + SetPropertyValueAndDetectChanges(value, ref _schedule, nameof(ContentSchedule)); if (_schedule != null) + { _schedule.CollectionChanged += ScheduleCollectionChanged; + } } } @@ -223,10 +228,16 @@ namespace Umbraco.Core.Models } set { - if (_publishInfos != null) _publishInfos.CollectionChanged -= PublishNamesCollectionChanged; + if (_publishInfos != null) + { + _publishInfos.ClearCollectionChangedEvents(); + } + _publishInfos = value; if (_publishInfos != null) + { _publishInfos.CollectionChanged += PublishNamesCollectionChanged; + } } } @@ -321,7 +332,7 @@ namespace Umbraco.Core.Models else Properties.EnsurePropertyTypes(contentType.CompositionPropertyTypes); - Properties.CollectionChanged -= PropertiesChanged; // be sure not to double add + Properties.ClearCollectionChangedEvents(); // be sure not to double add Properties.CollectionChanged += PropertiesChanged; } @@ -438,7 +449,7 @@ namespace Umbraco.Core.Models //if culture infos exist then deal with event bindings if (clonedContent._publishInfos != null) { - clonedContent._publishInfos.CollectionChanged -= PublishNamesCollectionChanged; //clear this event handler if any + clonedContent._publishInfos.ClearCollectionChangedEvents(); //clear this event handler if any clonedContent._publishInfos = (ContentCultureInfosCollection)_publishInfos.DeepClone(); //manually deep clone clonedContent._publishInfos.CollectionChanged += clonedContent.PublishNamesCollectionChanged; //re-assign correct event handler } @@ -446,7 +457,7 @@ namespace Umbraco.Core.Models //if properties exist then deal with event bindings if (clonedContent._schedule != null) { - clonedContent._schedule.CollectionChanged -= ScheduleCollectionChanged; //clear this event handler if any + clonedContent._schedule.ClearCollectionChangedEvents(); //clear this event handler if any clonedContent._schedule = (ContentScheduleCollection)_schedule.DeepClone(); //manually deep clone clonedContent._schedule.CollectionChanged += clonedContent.ScheduleCollectionChanged; //re-assign correct event handler } diff --git a/src/Umbraco.Core/Models/ContentBase.cs b/src/Umbraco.Core/Models/ContentBase.cs index d02ea82012..cbdee479fe 100644 --- a/src/Umbraco.Core/Models/ContentBase.cs +++ b/src/Umbraco.Core/Models/ContentBase.cs @@ -138,7 +138,11 @@ namespace Umbraco.Core.Models get => _properties; set { - if (_properties != null) _properties.CollectionChanged -= PropertiesChanged; + if (_properties != null) + { + _properties.ClearCollectionChangedEvents(); + } + _properties = value; _properties.CollectionChanged += PropertiesChanged; } @@ -173,10 +177,15 @@ namespace Umbraco.Core.Models } set { - if (_cultureInfos != null) _cultureInfos.CollectionChanged -= CultureInfosCollectionChanged; + if (_cultureInfos != null) + { + _cultureInfos.ClearCollectionChangedEvents(); + } _cultureInfos = value; if (_cultureInfos != null) + { _cultureInfos.CollectionChanged += CultureInfosCollectionChanged; + } } } @@ -479,7 +488,7 @@ namespace Umbraco.Core.Models //if culture infos exist then deal with event bindings if (clonedContent._cultureInfos != null) { - clonedContent._cultureInfos.CollectionChanged -= CultureInfosCollectionChanged; //clear this event handler if any + clonedContent._cultureInfos.ClearCollectionChangedEvents(); //clear this event handler if any clonedContent._cultureInfos = (ContentCultureInfosCollection)_cultureInfos.DeepClone(); //manually deep clone clonedContent._cultureInfos.CollectionChanged += clonedContent.CultureInfosCollectionChanged; //re-assign correct event handler } @@ -487,7 +496,7 @@ namespace Umbraco.Core.Models //if properties exist then deal with event bindings if (clonedContent._properties != null) { - clonedContent._properties.CollectionChanged -= PropertiesChanged; //clear this event handler if any + clonedContent._properties.ClearCollectionChangedEvents(); //clear this event handler if any clonedContent._properties = (PropertyCollection)_properties.DeepClone(); //manually deep clone clonedContent._properties.CollectionChanged += clonedContent.PropertiesChanged; //re-assign correct event handler } diff --git a/src/Umbraco.Core/Models/ContentCultureInfosCollection.cs b/src/Umbraco.Core/Models/ContentCultureInfosCollection.cs index 9bc78fc56d..07ee661497 100644 --- a/src/Umbraco.Core/Models/ContentCultureInfosCollection.cs +++ b/src/Umbraco.Core/Models/ContentCultureInfosCollection.cs @@ -15,7 +15,7 @@ namespace Umbraco.Core.Models public ContentCultureInfosCollection() : base(x => x.Culture, StringComparer.InvariantCultureIgnoreCase) { } - + /// /// Adds or updates a instance. /// diff --git a/src/Umbraco.Core/Models/ContentScheduleCollection.cs b/src/Umbraco.Core/Models/ContentScheduleCollection.cs index 6c7dd79312..0ebcc0fe4b 100644 --- a/src/Umbraco.Core/Models/ContentScheduleCollection.cs +++ b/src/Umbraco.Core/Models/ContentScheduleCollection.cs @@ -14,6 +14,11 @@ namespace Umbraco.Core.Models public event NotifyCollectionChangedEventHandler CollectionChanged; + /// + /// Clears all event handlers + /// + public void ClearCollectionChangedEvents() => CollectionChanged = null; + private void OnCollectionChanged(NotifyCollectionChangedEventArgs args) { CollectionChanged?.Invoke(this, args); diff --git a/src/Umbraco.Core/Models/ContentTypeBase.cs b/src/Umbraco.Core/Models/ContentTypeBase.cs index 04bcb7424a..4865db428a 100644 --- a/src/Umbraco.Core/Models/ContentTypeBase.cs +++ b/src/Umbraco.Core/Models/ContentTypeBase.cs @@ -262,7 +262,10 @@ namespace Umbraco.Core.Models set { if (_noGroupPropertyTypes != null) - _noGroupPropertyTypes.CollectionChanged -= PropertyTypesChanged; + { + _noGroupPropertyTypes.ClearCollectionChangedEvents(); + } + _noGroupPropertyTypes = new PropertyTypeCollection(SupportsPublishing, value); _noGroupPropertyTypes.CollectionChanged += PropertyTypesChanged; PropertyTypesChanged(_noGroupPropertyTypes, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); @@ -480,14 +483,14 @@ namespace Umbraco.Core.Models // its ignored from the auto-clone process because its return values are unions, not raw and // we end up with duplicates, see: http://issues.umbraco.org/issue/U4-4842 - clonedEntity._noGroupPropertyTypes.CollectionChanged -= PropertyTypesChanged; //clear this event handler if any + clonedEntity._noGroupPropertyTypes.ClearCollectionChangedEvents(); //clear this event handler if any clonedEntity._noGroupPropertyTypes = (PropertyTypeCollection) _noGroupPropertyTypes.DeepClone(); //manually deep clone clonedEntity._noGroupPropertyTypes.CollectionChanged += clonedEntity.PropertyTypesChanged; //re-assign correct event handler } if (clonedEntity._propertyGroups != null) { - clonedEntity._propertyGroups.CollectionChanged -= PropertyGroupsChanged; //clear this event handler if any + clonedEntity._propertyGroups.ClearCollectionChangedEvents(); //clear this event handler if any clonedEntity._propertyGroups = (PropertyGroupCollection) _propertyGroups.DeepClone(); //manually deep clone clonedEntity._propertyGroups.CollectionChanged += clonedEntity.PropertyGroupsChanged; //re-assign correct event handler } diff --git a/src/Umbraco.Core/Models/Entities/IMediaEntitySlim.cs b/src/Umbraco.Core/Models/Entities/IMediaEntitySlim.cs index 9440000146..f7daf79ec9 100644 --- a/src/Umbraco.Core/Models/Entities/IMediaEntitySlim.cs +++ b/src/Umbraco.Core/Models/Entities/IMediaEntitySlim.cs @@ -7,7 +7,7 @@ { /// - /// The media file's path/url + /// The media file's path/URL /// string MediaPath { get; } } diff --git a/src/Umbraco.Core/Models/IContentBase.cs b/src/Umbraco.Core/Models/IContentBase.cs index 0f660181fb..f4fae44ffc 100644 --- a/src/Umbraco.Core/Models/IContentBase.cs +++ b/src/Umbraco.Core/Models/IContentBase.cs @@ -4,6 +4,7 @@ using Umbraco.Core.Models.Entities; namespace Umbraco.Core.Models { + /// /// Provides a base class for content items. /// diff --git a/src/Umbraco.Core/Models/IReadOnlyContentBase.cs b/src/Umbraco.Core/Models/IReadOnlyContentBase.cs new file mode 100644 index 0000000000..3ed39b63ab --- /dev/null +++ b/src/Umbraco.Core/Models/IReadOnlyContentBase.cs @@ -0,0 +1,72 @@ +using System; + +namespace Umbraco.Core.Models +{ + public interface IReadOnlyContentBase + { + /// + /// Gets the integer identifier of the entity. + /// + int Id { get; } + + /// + /// Gets the Guid unique identifier of the entity. + /// + Guid Key { get; } + + /// + /// Gets the creation date. + /// + DateTime CreateDate { get; } + + /// + /// Gets the last update date. + /// + DateTime UpdateDate { get; } + + /// + /// Gets the name of the entity. + /// + string Name { get; } + + /// + /// Gets the identifier of the user who created this entity. + /// + int CreatorId { get; } + + /// + /// Gets the identifier of the parent entity. + /// + int ParentId { get; } + + /// + /// Gets the level of the entity. + /// + int Level { get; } + + /// + /// Gets the path to the entity. + /// + string Path { get; } + + /// + /// Gets the sort order of the entity. + /// + int SortOrder { get; } + + /// + /// Gets the content type id + /// + int ContentTypeId { get; } + + /// + /// Gets the identifier of the writer. + /// + int WriterId { get; } + + /// + /// Gets the version identifier. + /// + int VersionId { get; } + } +} diff --git a/src/Umbraco.Core/Models/IRedirectUrl.cs b/src/Umbraco.Core/Models/IRedirectUrl.cs index e066881645..527dad57da 100644 --- a/src/Umbraco.Core/Models/IRedirectUrl.cs +++ b/src/Umbraco.Core/Models/IRedirectUrl.cs @@ -5,7 +5,7 @@ using Umbraco.Core.Models.Entities; namespace Umbraco.Core.Models { /// - /// Represents a redirect url. + /// Represents a redirect URL. /// public interface IRedirectUrl : IEntity, IRememberBeingDirty { @@ -22,7 +22,7 @@ namespace Umbraco.Core.Models Guid ContentKey { get; set; } /// - /// Gets or sets the redirect url creation date. + /// Gets or sets the redirect URL creation date. /// [DataMember] DateTime CreateDateUtc { get; set; } @@ -34,7 +34,7 @@ namespace Umbraco.Core.Models string Culture { get; set; } /// - /// Gets or sets the redirect url route. + /// Gets or sets the redirect URL route. /// /// Is a proper Umbraco route eg /path/to/foo or 123/path/tofoo. [DataMember] diff --git a/src/Umbraco.Core/Models/IconModel.cs b/src/Umbraco.Core/Models/IconModel.cs new file mode 100644 index 0000000000..0de44301bb --- /dev/null +++ b/src/Umbraco.Core/Models/IconModel.cs @@ -0,0 +1,8 @@ +namespace Umbraco.Core.Models +{ + public class IconModel + { + public string Name { get; set; } + public string SvgString { get; set; } + } +} diff --git a/src/Umbraco.Core/Models/Identity/BackOfficeIdentityUser.cs b/src/Umbraco.Core/Models/Identity/BackOfficeIdentityUser.cs index 8dc056d555..d2899caccc 100644 --- a/src/Umbraco.Core/Models/Identity/BackOfficeIdentityUser.cs +++ b/src/Umbraco.Core/Models/Identity/BackOfficeIdentityUser.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; using System.Linq; +using Umbraco.Core.Collections; using Umbraco.Core.Composing; using Umbraco.Core.Models.Entities; using Umbraco.Core.Models.Membership; @@ -62,7 +63,7 @@ namespace Umbraco.Core.Models.Identity _culture = Current.Configs.Global().DefaultUILanguage; // TODO: inject // must initialize before setting groups - _roles = new ObservableCollection>(); + _roles = new EventClearingObservableCollection>(); _roles.CollectionChanged += _roles_CollectionChanged; // use the property setters - they do more than just setting a field @@ -223,7 +224,7 @@ namespace Umbraco.Core.Models.Identity _groups = value; //now clear all roles and re-add them - _roles.CollectionChanged -= _roles_CollectionChanged; + _roles.ClearCollectionChangedEvents(); _roles.Clear(); foreach (var identityUserRole in _groups.Select(x => new IdentityUserRole { @@ -275,16 +276,23 @@ namespace Umbraco.Core.Models.Identity { get { - if (_getLogins != null && _getLogins.IsValueCreated == false) + // return if it exists + if (_logins != null) return _logins; + + _logins = new ObservableCollection(); + + // if the callback is there and hasn't been created yet then execute it and populate the logins + if (_getLogins != null && !_getLogins.IsValueCreated) { - _logins = new ObservableCollection(); foreach (var l in _getLogins.Value) { _logins.Add(l); } - //now assign events - _logins.CollectionChanged += Logins_CollectionChanged; } + + //now assign events + _logins.CollectionChanged += Logins_CollectionChanged; + return _logins; } } @@ -299,7 +307,7 @@ namespace Umbraco.Core.Models.Identity _beingDirty.OnPropertyChanged(nameof(Roles)); } - private readonly ObservableCollection> _roles; + private readonly EventClearingObservableCollection> _roles; /// /// helper method to easily add a role without having to deal with IdentityUserRole{T} diff --git a/src/Umbraco.Core/Models/Identity/ExternalLogin.cs b/src/Umbraco.Core/Models/Identity/ExternalLogin.cs new file mode 100644 index 0000000000..6e4abf2906 --- /dev/null +++ b/src/Umbraco.Core/Models/Identity/ExternalLogin.cs @@ -0,0 +1,24 @@ +using System; + +namespace Umbraco.Core.Models.Identity +{ + /// + public class ExternalLogin : IExternalLogin + { + public ExternalLogin(string loginProvider, string providerKey, string userData = null) + { + LoginProvider = loginProvider ?? throw new ArgumentNullException(nameof(loginProvider)); + ProviderKey = providerKey ?? throw new ArgumentNullException(nameof(providerKey)); + UserData = userData; + } + + /// + public string LoginProvider { get; } + + /// + public string ProviderKey { get; } + + /// + public string UserData { get; } + } +} diff --git a/src/Umbraco.Core/Models/Identity/IExternalLogin.cs b/src/Umbraco.Core/Models/Identity/IExternalLogin.cs new file mode 100644 index 0000000000..68f66a5cee --- /dev/null +++ b/src/Umbraco.Core/Models/Identity/IExternalLogin.cs @@ -0,0 +1,12 @@ +namespace Umbraco.Core.Models.Identity +{ + /// + /// Used to persist external login data for a user + /// + public interface IExternalLogin + { + string LoginProvider { get; } + string ProviderKey { get; } + string UserData { get; } + } +} diff --git a/src/Umbraco.Core/Models/Identity/IIdentityUserLogin.cs b/src/Umbraco.Core/Models/Identity/IIdentityUserLogin.cs index 276f601771..feb8af24f3 100644 --- a/src/Umbraco.Core/Models/Identity/IIdentityUserLogin.cs +++ b/src/Umbraco.Core/Models/Identity/IIdentityUserLogin.cs @@ -2,23 +2,30 @@ namespace Umbraco.Core.Models.Identity { + // TODO: Merge these in v8! This is here purely for backward compat + + public interface IIdentityUserLoginExtended : IIdentityUserLogin + { + /// + /// Used to store any arbitrary data for the user and external provider - like user tokens returned from the provider + /// + string UserData { get; set; } + } + public interface IIdentityUserLogin : IEntity, IRememberBeingDirty { /// /// The login provider for the login (i.e. Facebook, Google) - /// /// string LoginProvider { get; set; } /// /// Key representing the login for the provider - /// /// string ProviderKey { get; set; } /// /// User Id for the user who owns this login - /// /// int UserId { get; set; } } diff --git a/src/Umbraco.Core/Models/Identity/IdentityUserLogin.cs b/src/Umbraco.Core/Models/Identity/IdentityUserLogin.cs index 5876f420b4..66911b08ac 100644 --- a/src/Umbraco.Core/Models/Identity/IdentityUserLogin.cs +++ b/src/Umbraco.Core/Models/Identity/IdentityUserLogin.cs @@ -3,11 +3,11 @@ using Umbraco.Core.Models.Entities; namespace Umbraco.Core.Models.Identity { + /// /// Entity type for a user's login (i.e. Facebook, Google) - /// /// - public class IdentityUserLogin : EntityBase, IIdentityUserLogin + public class IdentityUserLogin : EntityBase, IIdentityUserLoginExtended { public IdentityUserLogin(string loginProvider, string providerKey, int userId) { @@ -25,22 +25,16 @@ namespace Umbraco.Core.Models.Identity CreateDate = createDate; } - /// - /// The login provider for the login (i.e. Facebook, Google) - /// - /// + /// public string LoginProvider { get; set; } - /// - /// Key representing the login for the provider - /// - /// + /// public string ProviderKey { get; set; } - /// - /// User Id for the user who owns this login - /// - /// + /// public int UserId { get; set; } + + /// + public string UserData { get; set; } } } diff --git a/src/Umbraco.Core/Models/Media.cs b/src/Umbraco.Core/Models/Media.cs index 002611c09c..87ec9196d6 100644 --- a/src/Umbraco.Core/Models/Media.cs +++ b/src/Umbraco.Core/Models/Media.cs @@ -77,7 +77,7 @@ namespace Umbraco.Core.Models else Properties.EnsurePropertyTypes(contentType.CompositionPropertyTypes); - Properties.CollectionChanged -= PropertiesChanged; // be sure not to double add + Properties.ClearCollectionChangedEvents(); // be sure not to double add Properties.CollectionChanged += PropertiesChanged; } } diff --git a/src/Umbraco.Core/Models/MediaExtensions.cs b/src/Umbraco.Core/Models/MediaExtensions.cs index 96f183b6e6..c985ace8b0 100644 --- a/src/Umbraco.Core/Models/MediaExtensions.cs +++ b/src/Umbraco.Core/Models/MediaExtensions.cs @@ -9,7 +9,7 @@ namespace Umbraco.Core.Models public static class MediaExtensions { /// - /// Gets the url of a media item. + /// Gets the URL of a media item. /// public static string GetUrl(this IMedia media, string propertyAlias, ILogger logger) { @@ -29,7 +29,7 @@ namespace Umbraco.Core.Models } /// - /// Gets the urls of a media item. + /// Gets the URLs of a media item. /// public static string[] GetUrls(this IMedia media, IContentSection contentSection, ILogger logger) { diff --git a/src/Umbraco.Core/Models/PathValidationExtensions.cs b/src/Umbraco.Core/Models/PathValidationExtensions.cs index 2db954b316..4ade88f621 100644 --- a/src/Umbraco.Core/Models/PathValidationExtensions.cs +++ b/src/Umbraco.Core/Models/PathValidationExtensions.cs @@ -25,7 +25,7 @@ namespace Umbraco.Core.Models if (entity.Path.IsNullOrWhiteSpace()) throw new InvalidDataException($"The content item {entity.NodeId} has an empty path: {entity.Path} with parentID: {entity.ParentId}"); - var pathParts = entity.Path.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + var pathParts = entity.Path.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries); if (pathParts.Length < 2) { //a path cannot be less than 2 parts, at a minimum it must be root (-1) and it's own id @@ -53,7 +53,7 @@ namespace Umbraco.Core.Models if (entity.Path.IsNullOrWhiteSpace()) return false; - var pathParts = entity.Path.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + var pathParts = entity.Path.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries); if (pathParts.Length < 2) { //a path cannot be less than 2 parts, at a minimum it must be root (-1) and it's own id diff --git a/src/Umbraco.Core/Models/PropertyCollection.cs b/src/Umbraco.Core/Models/PropertyCollection.cs index c587a45424..a73df6a095 100644 --- a/src/Umbraco.Core/Models/PropertyCollection.cs +++ b/src/Umbraco.Core/Models/PropertyCollection.cs @@ -7,6 +7,7 @@ using System.Runtime.Serialization; namespace Umbraco.Core.Models { + /// /// Represents a collection of property values. /// @@ -168,6 +169,8 @@ namespace Umbraco.Core.Models /// public event NotifyCollectionChangedEventHandler CollectionChanged; + public void ClearCollectionChangedEvents() => CollectionChanged = null; + protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs args) { CollectionChanged?.Invoke(this, args); diff --git a/src/Umbraco.Core/Models/PropertyGroup.cs b/src/Umbraco.Core/Models/PropertyGroup.cs index 2e6da5d837..022fb6ed03 100644 --- a/src/Umbraco.Core/Models/PropertyGroup.cs +++ b/src/Umbraco.Core/Models/PropertyGroup.cs @@ -66,7 +66,10 @@ namespace Umbraco.Core.Models set { if (_propertyTypes != null) - _propertyTypes.CollectionChanged -= PropertyTypesChanged; + { + _propertyTypes.ClearCollectionChangedEvents(); + } + _propertyTypes = value; // since we're adding this collection to this group, @@ -100,7 +103,7 @@ namespace Umbraco.Core.Models if (clonedEntity._propertyTypes != null) { - clonedEntity._propertyTypes.CollectionChanged -= PropertyTypesChanged; //clear this event handler if any + clonedEntity._propertyTypes.ClearCollectionChangedEvents(); //clear this event handler if any clonedEntity._propertyTypes = (PropertyTypeCollection) _propertyTypes.DeepClone(); //manually deep clone clonedEntity._propertyTypes.CollectionChanged += clonedEntity.PropertyTypesChanged; //re-assign correct event handler } diff --git a/src/Umbraco.Core/Models/PropertyGroupCollection.cs b/src/Umbraco.Core/Models/PropertyGroupCollection.cs index 5422dfb792..973147b3fb 100644 --- a/src/Umbraco.Core/Models/PropertyGroupCollection.cs +++ b/src/Umbraco.Core/Models/PropertyGroupCollection.cs @@ -160,6 +160,11 @@ namespace Umbraco.Core.Models public event NotifyCollectionChangedEventHandler CollectionChanged; + /// + /// Clears all event handlers + /// + public void ClearCollectionChangedEvents() => CollectionChanged = null; + protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs args) { CollectionChanged?.Invoke(this, args); diff --git a/src/Umbraco.Core/Models/PropertyType.cs b/src/Umbraco.Core/Models/PropertyType.cs index 75c2a7cc00..abdbbfd8db 100644 --- a/src/Umbraco.Core/Models/PropertyType.cs +++ b/src/Umbraco.Core/Models/PropertyType.cs @@ -31,6 +31,7 @@ namespace Umbraco.Core.Models private string _validationRegExp; private string _validationRegExpMessage; private ContentVariation _variations; + private bool _labelOnTop; /// /// Initializes a new instance of the class. @@ -205,6 +206,16 @@ namespace Umbraco.Core.Models set => SetPropertyValueAndDetectChanges(value, ref _mandatoryMessage, nameof(MandatoryMessage)); } + /// + /// Gets or sets a value indicating whether the label of this property type should be displayed on top. + /// + [DataMember] + public bool LabelOnTop + { + get => _labelOnTop; + set => SetPropertyValueAndDetectChanges(value, ref _labelOnTop, nameof(LabelOnTop)); + } + /// /// Gets of sets the sort order of the property type. /// @@ -438,7 +449,7 @@ namespace Umbraco.Core.Models base.PerformDeepClone(clone); var clonedEntity = (PropertyType)clone; - + //need to manually assign the Lazy value as it will not be automatically mapped if (PropertyGroupId != null) { diff --git a/src/Umbraco.Core/Models/PropertyTypeCollection.cs b/src/Umbraco.Core/Models/PropertyTypeCollection.cs index 6e41f0d12b..cc9d00fa5d 100644 --- a/src/Umbraco.Core/Models/PropertyTypeCollection.cs +++ b/src/Umbraco.Core/Models/PropertyTypeCollection.cs @@ -162,7 +162,11 @@ namespace Umbraco.Core.Models } public event NotifyCollectionChangedEventHandler CollectionChanged; - + + /// + /// Clears all event handlers + /// + public void ClearCollectionChangedEvents() => CollectionChanged = null; protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs args) { CollectionChanged?.Invoke(this, args); diff --git a/src/Umbraco.Core/Models/PublicAccessEntry.cs b/src/Umbraco.Core/Models/PublicAccessEntry.cs index cfb30de147..cc86b797cb 100644 --- a/src/Umbraco.Core/Models/PublicAccessEntry.cs +++ b/src/Umbraco.Core/Models/PublicAccessEntry.cs @@ -4,6 +4,7 @@ using System.Collections.ObjectModel; using System.Collections.Specialized; using System.Linq; using System.Runtime.Serialization; +using Umbraco.Core.Collections; using Umbraco.Core.Models.Entities; namespace Umbraco.Core.Models @@ -12,7 +13,7 @@ namespace Umbraco.Core.Models [DataContract(IsReference = true)] public class PublicAccessEntry : EntityBase { - private readonly ObservableCollection _ruleCollection; + private readonly EventClearingObservableCollection _ruleCollection; private int _protectedNodeId; private int _noAccessNodeId; private int _loginNodeId; @@ -28,7 +29,7 @@ namespace Umbraco.Core.Models NoAccessNodeId = noAccessNode.Id; _protectedNodeId = protectedNode.Id; - _ruleCollection = new ObservableCollection(ruleCollection); + _ruleCollection = new EventClearingObservableCollection(ruleCollection); _ruleCollection.CollectionChanged += _ruleCollection_CollectionChanged; foreach (var rule in _ruleCollection) @@ -44,7 +45,7 @@ namespace Umbraco.Core.Models NoAccessNodeId = noAccessNodeId; _protectedNodeId = protectedNodeId; - _ruleCollection = new ObservableCollection(ruleCollection); + _ruleCollection = new EventClearingObservableCollection(ruleCollection); _ruleCollection.CollectionChanged += _ruleCollection_CollectionChanged; foreach (var rule in _ruleCollection) @@ -148,7 +149,7 @@ namespace Umbraco.Core.Models if (cloneEntity._ruleCollection != null) { - cloneEntity._ruleCollection.CollectionChanged -= _ruleCollection_CollectionChanged; //clear this event handler if any + cloneEntity._ruleCollection.ClearCollectionChangedEvents(); //clear this event handler if any cloneEntity._ruleCollection.CollectionChanged += cloneEntity._ruleCollection_CollectionChanged; //re-assign correct event handler } } diff --git a/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs b/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs index 4e154bf514..23861dfa9d 100644 --- a/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs +++ b/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs @@ -31,7 +31,7 @@ namespace Umbraco.Core.Models.PublishedContent string Name { get; } /// - /// Gets the url segment of the content item for the current culture. + /// Gets the URL segment of the content item for the current culture. /// string UrlSegment { get; } @@ -93,7 +93,7 @@ namespace Umbraco.Core.Models.PublishedContent DateTime UpdateDate { get; } /// - /// Gets the url of the content item for the current culture. + /// Gets the URL of the content item for the current culture. /// /// /// The value of this property is contextual. It depends on the 'current' request uri, diff --git a/src/Umbraco.Core/Models/PublishedContent/IPublishedContentType.cs b/src/Umbraco.Core/Models/PublishedContent/IPublishedContentType.cs index ab6920377c..cfc789324a 100644 --- a/src/Umbraco.Core/Models/PublishedContent/IPublishedContentType.cs +++ b/src/Umbraco.Core/Models/PublishedContent/IPublishedContentType.cs @@ -1,7 +1,21 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; namespace Umbraco.Core.Models.PublishedContent { + /// + /// Represents an type. + /// + /// Instances implementing the interface should be + /// immutable, ie if the content type changes, then a new instance needs to be created. + public interface IPublishedContentType2 : IPublishedContentType + { + /// + /// Gets the unique key for the content type. + /// + Guid Key { get; } + } + /// /// Represents an type. /// diff --git a/src/Umbraco.Core/Models/PublishedContent/IPublishedContentTypeFactory.cs b/src/Umbraco.Core/Models/PublishedContent/IPublishedContentTypeFactory.cs index 89009ac7b8..1a29f970d9 100644 --- a/src/Umbraco.Core/Models/PublishedContent/IPublishedContentTypeFactory.cs +++ b/src/Umbraco.Core/Models/PublishedContent/IPublishedContentTypeFactory.cs @@ -1,5 +1,6 @@ namespace Umbraco. Core.Models.PublishedContent { + /// /// Creates published content types. /// diff --git a/src/Umbraco.Core/Models/PublishedContent/IPublishedModelFactory.cs b/src/Umbraco.Core/Models/PublishedContent/IPublishedModelFactory.cs index ae4caf352e..60fa0fe603 100644 --- a/src/Umbraco.Core/Models/PublishedContent/IPublishedModelFactory.cs +++ b/src/Umbraco.Core/Models/PublishedContent/IPublishedModelFactory.cs @@ -3,6 +3,7 @@ using System.Collections; namespace Umbraco.Core.Models.PublishedContent { + /// /// Provides the published model creation service. /// diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentExtensionsForModels.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentExtensionsForModels.cs index bfc65b70d6..033396e4a1 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedContentExtensionsForModels.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentExtensionsForModels.cs @@ -3,6 +3,7 @@ using Umbraco.Core.Composing; namespace Umbraco.Core.Models.PublishedContent { + /// /// Provides strongly typed published content models services. /// diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs index 458b63ade3..7aa9b0dfd9 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs @@ -9,7 +9,7 @@ namespace Umbraco.Core.Models.PublishedContent /// /// Instances of the class are immutable, ie /// if the content type changes, then a new class needs to be created. - public class PublishedContentType : IPublishedContentType + public class PublishedContentType : IPublishedContentType2 { private readonly IPublishedPropertyType[] _propertyTypes; @@ -20,7 +20,7 @@ namespace Umbraco.Core.Models.PublishedContent /// Initializes a new instance of the class with a content type. /// public PublishedContentType(IContentTypeComposition contentType, IPublishedContentTypeFactory factory) - : this(contentType.Id, contentType.Alias, contentType.GetItemType(), contentType.CompositionAliases(), contentType.Variations, contentType.IsElement) + : this(contentType.Key, contentType.Id, contentType.Alias, contentType.GetItemType(), contentType.CompositionAliases(), contentType.Variations, contentType.IsElement) { var propertyTypes = contentType.CompositionPropertyTypes .Select(x => factory.CreatePropertyType(this, x)) @@ -40,8 +40,20 @@ namespace Umbraco.Core.Models.PublishedContent /// /// Values are assumed to be consistent and are not checked. /// + public PublishedContentType(Guid key, int id, string alias, PublishedItemType itemType, IEnumerable compositionAliases, IEnumerable propertyTypes, ContentVariation variations, bool isElement = false) + : this(key, id, alias, itemType, compositionAliases, variations, isElement) + { + var propertyTypesA = propertyTypes.ToArray(); + foreach (var propertyType in propertyTypesA) + propertyType.ContentType = this; + _propertyTypes = propertyTypesA; + + InitializeIndexes(); + } + + [Obsolete("Use the overload specifying a key instead")] public PublishedContentType(int id, string alias, PublishedItemType itemType, IEnumerable compositionAliases, IEnumerable propertyTypes, ContentVariation variations, bool isElement = false) - : this (id, alias, itemType, compositionAliases, variations, isElement) + : this (Guid.Empty, id, alias, itemType, compositionAliases, variations, isElement) { var propertyTypesA = propertyTypes.ToArray(); foreach (var propertyType in propertyTypesA) @@ -57,16 +69,26 @@ namespace Umbraco.Core.Models.PublishedContent /// /// Values are assumed to be consistent and are not checked. /// + public PublishedContentType(Guid key, int id, string alias, PublishedItemType itemType, IEnumerable compositionAliases, Func> propertyTypes, ContentVariation variations, bool isElement = false) + : this(key, id, alias, itemType, compositionAliases, variations, isElement) + { + _propertyTypes = propertyTypes(this).ToArray(); + + InitializeIndexes(); + } + + [Obsolete("Use the overload specifying a key instead")] public PublishedContentType(int id, string alias, PublishedItemType itemType, IEnumerable compositionAliases, Func> propertyTypes, ContentVariation variations, bool isElement = false) - : this(id, alias, itemType, compositionAliases, variations, isElement) + : this(Guid.Empty, id, alias, itemType, compositionAliases, variations, isElement) { _propertyTypes = propertyTypes(this).ToArray(); InitializeIndexes(); } - private PublishedContentType(int id, string alias, PublishedItemType itemType, IEnumerable compositionAliases, ContentVariation variations, bool isElement) + private PublishedContentType(Guid key, int id, string alias, PublishedItemType itemType, IEnumerable compositionAliases, ContentVariation variations, bool isElement) { + Key = key; Id = id; Alias = alias; ItemType = itemType; @@ -116,6 +138,9 @@ namespace Umbraco.Core.Models.PublishedContent #region Content type + /// + public Guid Key { get; } + /// public int Id { get; } diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentTypeExtensions.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentTypeExtensions.cs new file mode 100644 index 0000000000..feab33c1d6 --- /dev/null +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentTypeExtensions.cs @@ -0,0 +1,24 @@ +using System; + +namespace Umbraco.Core.Models.PublishedContent +{ + public static class PublishedContentTypeExtensions + { + /// + /// Get the GUID key from an + /// + /// + /// + /// + public static bool TryGetKey(this IPublishedContentType publishedContentType, out Guid key) + { + if (publishedContentType is IPublishedContentType2 contentTypeWithKey) + { + key = contentTypeWithKey.Key; + return true; + } + key = Guid.Empty; + return false; + } + } +} diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentTypeFactory.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentTypeFactory.cs index 34094508c3..c1548d3c3d 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedContentTypeFactory.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentTypeFactory.cs @@ -35,18 +35,18 @@ namespace Umbraco.Core.Models.PublishedContent /// This method is for tests and is not intended to be used directly from application code. /// /// Values are assumed to be consisted and are not checked. - internal IPublishedContentType CreateContentType(int id, string alias, Func> propertyTypes, ContentVariation variations = ContentVariation.Nothing, bool isElement = false) + internal IPublishedContentType CreateContentType(Guid key, int id, string alias, Func> propertyTypes, ContentVariation variations = ContentVariation.Nothing, bool isElement = false) { - return new PublishedContentType(id, alias, PublishedItemType.Content, Enumerable.Empty(), propertyTypes, variations, isElement); + return new PublishedContentType(key, id, alias, PublishedItemType.Content, Enumerable.Empty(), propertyTypes, variations, isElement); } /// /// This method is for tests and is not intended to be used directly from application code. /// /// Values are assumed to be consisted and are not checked. - internal IPublishedContentType CreateContentType(int id, string alias, IEnumerable compositionAliases, Func> propertyTypes, ContentVariation variations = ContentVariation.Nothing, bool isElement = false) + internal IPublishedContentType CreateContentType(Guid key, int id, string alias, IEnumerable compositionAliases, Func> propertyTypes, ContentVariation variations = ContentVariation.Nothing, bool isElement = false) { - return new PublishedContentType(id, alias, PublishedItemType.Content, compositionAliases, propertyTypes, variations, isElement); + return new PublishedContentType(key, id, alias, PublishedItemType.Content, compositionAliases, propertyTypes, variations, isElement); } /// diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedCultureInfos.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedCultureInfos.cs index 908b97fc36..58241ade24 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedCultureInfos.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedCultureInfos.cs @@ -32,7 +32,7 @@ namespace Umbraco.Core.Models.PublishedContent internal string Name { get; } /// - /// Gets the url segment of the item. + /// Gets the URL segment of the item. /// internal string UrlSegment { get; } diff --git a/src/Umbraco.Core/Models/PublishedContent/UrlMode.cs b/src/Umbraco.Core/Models/PublishedContent/UrlMode.cs index 4cd6a680f4..d11459bb9e 100644 --- a/src/Umbraco.Core/Models/PublishedContent/UrlMode.cs +++ b/src/Umbraco.Core/Models/PublishedContent/UrlMode.cs @@ -1,27 +1,27 @@ namespace Umbraco.Core.Models.PublishedContent { /// - /// Specifies the type of urls that the url provider should produce, Auto is the default. + /// Specifies the type of URLs that the URL provider should produce, Auto is the default. /// public enum UrlMode { /// - /// Indicates that the url provider should do what it has been configured to do. + /// Indicates that the URL provider should do what it has been configured to do. /// Default = 0, /// - /// Indicates that the url provider should produce relative urls exclusively. + /// Indicates that the URL provider should produce relative URLs exclusively. /// Relative, /// - /// Indicates that the url provider should produce absolute urls exclusively. + /// Indicates that the URL provider should produce absolute URLs exclusively. /// Absolute, /// - /// Indicates that the url provider should determine automatically whether to return relative or absolute urls. + /// Indicates that the URL provider should determine automatically whether to return relative or absolute URLs. /// Auto } diff --git a/src/Umbraco.Core/Models/Range.cs b/src/Umbraco.Core/Models/Range.cs index 83afa11658..108d564665 100644 --- a/src/Umbraco.Core/Models/Range.cs +++ b/src/Umbraco.Core/Models/Range.cs @@ -1,52 +1,130 @@ using System; +using System.Globalization; + namespace Umbraco.Core.Models { - // The Range class. Adapted from http://stackoverflow.com/questions/5343006/is-there-a-c-sharp-type-for-representing-an-integer-range - /// Generic parameter. - public class Range where T : IComparable + /// + /// Represents a range with a minimum and maximum value. + /// + /// The type of the minimum and maximum values. + /// + public class Range : IEquatable> + where T : IComparable { - /// Minimum value of the range. + /// + /// Gets or sets the minimum value. + /// + /// + /// The minimum value. + /// public T Minimum { get; set; } - /// Maximum value of the range. + /// + /// Gets or sets the maximum value. + /// + /// + /// The maximum value. + /// public T Maximum { get; set; } - /// Presents the Range in readable format. - /// String representation of the Range - public override string ToString() - { - return string.Format("{0},{1}", this.Minimum, this.Maximum); - } + /// + /// Returns a that represents this instance. + /// + /// + /// A that represents this instance. + /// + public override string ToString() => this.ToString("{0},{1}", CultureInfo.InvariantCulture); - /// Determines if the range is valid. - /// True if range is valid, else false - public bool IsValid() - { - return this.Minimum.CompareTo(this.Maximum) <= 0; - } + /// + /// Returns a that represents this instance. + /// + /// A composite format string for a single value (minimum and maximum are equal). Use {0} for the minimum and {1} for the maximum value. + /// A composite format string for the range values. Use {0} for the minimum and {1} for the maximum value. + /// An object that supplies culture-specific formatting information. + /// + /// A that represents this instance. + /// + public string ToString(string format, string formatRange, IFormatProvider provider = null) => this.ToString(this.Minimum.CompareTo(this.Maximum) == 0 ? format : formatRange, provider); - /// Determines if the provided value is inside the range. - /// The value to test - /// True if the value is inside Range, else false - public bool ContainsValue(T value) - { - return (this.Minimum.CompareTo(value) <= 0) && (value.CompareTo(this.Maximum) <= 0); - } + /// + /// Returns a that represents this instance. + /// + /// A composite format string for the range values. Use {0} for the minimum and {1} for the maximum value. + /// An object that supplies culture-specific formatting information. + /// + /// A that represents this instance. + /// + public string ToString(string format, IFormatProvider provider = null) => string.Format(provider, format, this.Minimum, this.Maximum); - /// Determines if this Range is inside the bounds of another range. - /// The parent range to test on - /// True if range is inclusive, else false - public bool IsInsideRange(Range range) - { - return this.IsValid() && range.IsValid() && range.ContainsValue(this.Minimum) && range.ContainsValue(this.Maximum); - } + /// + /// Determines whether this range is valid (the minimum value is lower than or equal to the maximum value). + /// + /// + /// true if this range is valid; otherwise, false. + /// + public bool IsValid() => this.Minimum.CompareTo(this.Maximum) <= 0; - /// Determines if another range is inside the bounds of this range. - /// The child range to test - /// True if range is inside, else false - public bool ContainsRange(Range range) - { - return this.IsValid() && range.IsValid() && this.ContainsValue(range.Minimum) && this.ContainsValue(range.Maximum); - } + /// + /// Determines whether this range contains the specified value. + /// + /// The value. + /// + /// true if this range contains the specified value; otherwise, false. + /// + public bool ContainsValue(T value) => this.Minimum.CompareTo(value) <= 0 && value.CompareTo(this.Maximum) <= 0; + + /// + /// Determines whether this range is inside the specified range. + /// + /// The range. + /// + /// true if this range is inside the specified range; otherwise, false. + /// + public bool IsInsideRange(Range range) => this.IsValid() && range.IsValid() && range.ContainsValue(this.Minimum) && range.ContainsValue(this.Maximum); + + /// + /// Determines whether this range contains the specified range. + /// + /// The range. + /// + /// true if this range contains the specified range; otherwise, false. + /// + public bool ContainsRange(Range range) => this.IsValid() && range.IsValid() && this.ContainsValue(range.Minimum) && this.ContainsValue(range.Maximum); + + /// + /// Determines whether the specified , is equal to this instance. + /// + /// The to compare with this instance. + /// + /// true if the specified is equal to this instance; otherwise, false. + /// + public override bool Equals(object obj) => obj is Range other && this.Equals(other); + + /// + /// Indicates whether the current object is equal to another object of the same type. + /// + /// An object to compare with this object. + /// + /// if the current object is equal to the parameter; otherwise, . + /// + public bool Equals(Range other) => other != null && this.Equals(other.Minimum, other.Maximum); + + /// + /// Determines whether the specified and values are equal to this instance values. + /// + /// The minimum value. + /// The maximum value. + /// + /// true if the specified and values are equal to this instance values; otherwise, false. + /// + public bool Equals(T minimum, T maximum) => this.Minimum.CompareTo(minimum) == 0 && this.Maximum.CompareTo(maximum) == 0; + + /// + /// Returns a hash code for this instance. + /// + /// + /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. + /// + public override int GetHashCode() => (this.Minimum, this.Maximum).GetHashCode(); } } diff --git a/src/Umbraco.Core/Models/ReadOnlyContentBaseAdapter.cs b/src/Umbraco.Core/Models/ReadOnlyContentBaseAdapter.cs new file mode 100644 index 0000000000..f707d2ab1c --- /dev/null +++ b/src/Umbraco.Core/Models/ReadOnlyContentBaseAdapter.cs @@ -0,0 +1,42 @@ +using System; + +namespace Umbraco.Core.Models +{ + internal struct ReadOnlyContentBaseAdapter : IReadOnlyContentBase + { + private readonly IContentBase _content; + + private ReadOnlyContentBaseAdapter(IContentBase content) + { + _content = content ?? throw new ArgumentNullException(nameof(content)); + } + + public static ReadOnlyContentBaseAdapter Create(IContentBase content) => new ReadOnlyContentBaseAdapter(content); + + public int Id => _content.Id; + + public Guid Key => _content.Key; + + public DateTime CreateDate => _content.CreateDate; + + public DateTime UpdateDate => _content.UpdateDate; + + public string Name => _content.Name; + + public int CreatorId => _content.CreatorId; + + public int ParentId => _content.ParentId; + + public int Level => _content.Level; + + public string Path => _content.Path; + + public int SortOrder => _content.SortOrder; + + public int ContentTypeId => _content.ContentTypeId; + + public int WriterId => _content.WriterId; + + public int VersionId => _content.VersionId; + } +} diff --git a/src/Umbraco.Core/Models/ServerRegistration.cs b/src/Umbraco.Core/Models/ServerRegistration.cs index 7dae5d5393..a862b11c23 100644 --- a/src/Umbraco.Core/Models/ServerRegistration.cs +++ b/src/Umbraco.Core/Models/ServerRegistration.cs @@ -24,7 +24,7 @@ namespace Umbraco.Core.Models /// Initializes a new instance of the class. /// /// The unique id of the server registration. - /// The server url. + /// The server URL. /// The unique server identity. /// The date and time the registration was created. /// The date and time the registration was last accessed. @@ -45,7 +45,7 @@ namespace Umbraco.Core.Models /// /// Initializes a new instance of the class. /// - /// The server url. + /// The server URL. /// The unique server identity. /// The date and time the registration was created. public ServerRegistration(string serverAddress, string serverIdentity, DateTime registered) @@ -58,7 +58,7 @@ namespace Umbraco.Core.Models } /// - /// Gets or sets the server url. + /// Gets or sets the server URL. /// public string ServerAddress { diff --git a/src/Umbraco.Core/Models/SimpleContentType.cs b/src/Umbraco.Core/Models/SimpleContentType.cs index 5c81017ec8..45b9d0ecfc 100644 --- a/src/Umbraco.Core/Models/SimpleContentType.cs +++ b/src/Umbraco.Core/Models/SimpleContentType.cs @@ -37,6 +37,7 @@ namespace Umbraco.Core.Models if (contentType == null) throw new ArgumentNullException(nameof(contentType)); Id = contentType.Id; + Key = contentType.Key; Alias = contentType.Alias; Variations = contentType.Variations; Icon = contentType.Icon; @@ -51,6 +52,8 @@ namespace Umbraco.Core.Models public int Id { get; } + public Guid Key { get; } + /// public ITemplate DefaultTemplate { get; } @@ -109,13 +112,14 @@ namespace Umbraco.Core.Models string ITreeEntity.Name { get => this.Name; set => throw new NotImplementedException(); } int IEntity.Id { get => this.Id; set => throw new NotImplementedException(); } bool IEntity.HasIdentity => this.Id != default; + Guid IEntity.Key { get => this.Key; set => throw new NotImplementedException(); } + int ITreeEntity.CreatorId { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } int ITreeEntity.ParentId { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } int ITreeEntity.Level { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } string ITreeEntity.Path { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } int ITreeEntity.SortOrder { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } - bool ITreeEntity.Trashed => throw new NotImplementedException(); - Guid IEntity.Key { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } + bool ITreeEntity.Trashed => throw new NotImplementedException(); DateTime IEntity.CreateDate { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } DateTime IEntity.UpdateDate { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } DateTime? IEntity.DeleteDate { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } diff --git a/src/Umbraco.Core/Packaging/PackageDataInstallation.cs b/src/Umbraco.Core/Packaging/PackageDataInstallation.cs index 6a5acb0dc7..4d1f12baaa 100644 --- a/src/Umbraco.Core/Packaging/PackageDataInstallation.cs +++ b/src/Umbraco.Core/Packaging/PackageDataInstallation.cs @@ -522,7 +522,7 @@ namespace Umbraco.Core.Packaging && ((string)infoElement.Element("Master")).IsNullOrWhiteSpace()) { var alias = documentType.Element("Info").Element("Alias").Value; - var folders = foldersAttribute.Value.Split('/'); + var folders = foldersAttribute.Value.Split(Constants.CharArrays.ForwardSlash); var rootFolder = HttpUtility.UrlDecode(folders[0]); //level 1 = root level folders, there can only be one with the same name var current = _contentTypeService.GetContainers(rootFolder, 1).FirstOrDefault(); @@ -604,6 +604,8 @@ namespace Umbraco.Core.Packaging var defaultTemplateElement = infoElement.Element("DefaultTemplate"); contentType.Name = infoElement.Element("Name").Value; + if (infoElement.Element("Key") != null) + contentType.Key = new Guid(infoElement.Element("Key").Value); contentType.Icon = infoElement.Element("Icon").Value; contentType.Thumbnail = infoElement.Element("Thumbnail").Value; contentType.Description = infoElement.Element("Description").Value; @@ -798,8 +800,13 @@ namespace Umbraco.Core.Packaging SortOrder = sortOrder, Variations = property.Element("Variations") != null ? (ContentVariation)Enum.Parse(typeof(ContentVariation), property.Element("Variations").Value) - : ContentVariation.Nothing + : ContentVariation.Nothing, + LabelOnTop = property.Element("LabelOnTop") != null + ? property.Element("LabelOnTop").Value.ToLowerInvariant().Equals("true") + : false }; + if (property.Element("Key") != null) + propertyType.Key = new Guid(property.Element("Key").Value); var tab = (string)property.Element("Tab"); if (string.IsNullOrEmpty(tab)) @@ -935,7 +942,7 @@ namespace Umbraco.Core.Packaging if (foldersAttribute != null) { var name = datatypeElement.Attribute("Name").Value; - var folders = foldersAttribute.Value.Split('/'); + var folders = foldersAttribute.Value.Split(Constants.CharArrays.ForwardSlash); var rootFolder = HttpUtility.UrlDecode(folders[0]); //there will only be a single result by name for level 1 (root) containers var current = _dataTypeService.GetContainers(rootFolder, 1).FirstOrDefault(); diff --git a/src/Umbraco.Core/Packaging/PackageDefinitionXmlParser.cs b/src/Umbraco.Core/Packaging/PackageDefinitionXmlParser.cs index 50cb692530..1bba373218 100644 --- a/src/Umbraco.Core/Packaging/PackageDefinitionXmlParser.cs +++ b/src/Umbraco.Core/Packaging/PackageDefinitionXmlParser.cs @@ -44,13 +44,13 @@ namespace Umbraco.Core.Packaging Actions = xml.Element("actions")?.ToString(SaveOptions.None) ?? "", //take the entire outer xml value ContentNodeId = xml.Element("content")?.AttributeValue("nodeId") ?? string.Empty, ContentLoadChildNodes = xml.Element("content")?.AttributeValue("loadChildNodes") ?? false, - Macros = xml.Element("macros")?.Value.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList() ?? new List(), - Templates = xml.Element("templates")?.Value.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList() ?? new List(), - Stylesheets = xml.Element("stylesheets")?.Value.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList() ?? new List(), - DocumentTypes = xml.Element("documentTypes")?.Value.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList() ?? new List(), - Languages = xml.Element("languages")?.Value.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList() ?? new List(), - DictionaryItems = xml.Element("dictionaryitems")?.Value.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList() ?? new List(), - DataTypes = xml.Element("datatypes")?.Value.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList() ?? new List(), + Macros = xml.Element("macros")?.Value.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries).ToList() ?? new List(), + Templates = xml.Element("templates")?.Value.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries).ToList() ?? new List(), + Stylesheets = xml.Element("stylesheets")?.Value.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries).ToList() ?? new List(), + DocumentTypes = xml.Element("documentTypes")?.Value.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries).ToList() ?? new List(), + Languages = xml.Element("languages")?.Value.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries).ToList() ?? new List(), + DictionaryItems = xml.Element("dictionaryitems")?.Value.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries).ToList() ?? new List(), + DataTypes = xml.Element("datatypes")?.Value.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries).ToList() ?? new List(), Files = xml.Element("files")?.Elements("file").Select(x => x.Value).ToList() ?? new List() }; diff --git a/src/Umbraco.Core/Persistence/DatabaseAnnotations/IndexAttribute.cs b/src/Umbraco.Core/Persistence/DatabaseAnnotations/IndexAttribute.cs index 138dceff09..5aaabbfa6f 100644 --- a/src/Umbraco.Core/Persistence/DatabaseAnnotations/IndexAttribute.cs +++ b/src/Umbraco.Core/Persistence/DatabaseAnnotations/IndexAttribute.cs @@ -31,5 +31,10 @@ namespace Umbraco.Core.Persistence.DatabaseAnnotations /// Gets or sets the column name(s) for the current index /// public string ForColumns { get; set; } + + /// + /// Gets or sets the column name(s) for the columns to include in the index + /// + public string IncludeColumns { get; set; } } } diff --git a/src/Umbraco.Core/Persistence/DatabaseAnnotations/SpecialDbTypes.cs b/src/Umbraco.Core/Persistence/DatabaseAnnotations/SpecialDbTypes.cs index 4b606bc0f5..6d211ebbd9 100644 --- a/src/Umbraco.Core/Persistence/DatabaseAnnotations/SpecialDbTypes.cs +++ b/src/Umbraco.Core/Persistence/DatabaseAnnotations/SpecialDbTypes.cs @@ -7,6 +7,7 @@ public enum SpecialDbTypes { NTEXT, - NCHAR + NCHAR, + NVARCHARMAX } } diff --git a/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/DefinitionFactory.cs b/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/DefinitionFactory.cs index 5925e58afc..e6f06e4200 100644 --- a/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/DefinitionFactory.cs +++ b/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/DefinitionFactory.cs @@ -160,12 +160,20 @@ namespace Umbraco.Core.Persistence.DatabaseModelDefinitions if (string.IsNullOrEmpty(attribute.ForColumns) == false) { - var columns = attribute.ForColumns.Split(',').Select(p => p.Trim()); + var columns = attribute.ForColumns.Split(Constants.CharArrays.Comma).Select(p => p.Trim()); foreach (var column in columns) { definition.Columns.Add(new IndexColumnDefinition {Name = column, Direction = Direction.Ascending}); } } + if (string.IsNullOrEmpty(attribute.IncludeColumns) == false) + { + var columns = attribute.IncludeColumns.Split(',').Select(p => p.Trim()); + foreach (var column in columns) + { + definition.IncludeColumns.Add(new IndexColumnDefinition { Name = column, Direction = Direction.Ascending }); + } + } return definition; } } diff --git a/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/IndexDefinition.cs b/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/IndexDefinition.cs index 582f9a40f7..20f75d38c8 100644 --- a/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/IndexDefinition.cs +++ b/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/IndexDefinition.cs @@ -6,17 +6,13 @@ namespace Umbraco.Core.Persistence.DatabaseModelDefinitions { public class IndexDefinition { - public IndexDefinition() - { - Columns = new List(); - } - public virtual string Name { get; set; } public virtual string SchemaName { get; set; } public virtual string TableName { get; set; } public virtual string ColumnName { get; set; } - public virtual ICollection Columns { get; set; } + public virtual ICollection Columns { get; set; } = new List(); + public virtual ICollection IncludeColumns { get; set; } = new List(); public IndexTypes IndexType { get; set; } } } diff --git a/src/Umbraco.Core/Persistence/Dtos/ContentNuDto.cs b/src/Umbraco.Core/Persistence/Dtos/ContentNuDto.cs index c6269d5317..a2f36584e0 100644 --- a/src/Umbraco.Core/Persistence/Dtos/ContentNuDto.cs +++ b/src/Umbraco.Core/Persistence/Dtos/ContentNuDto.cs @@ -25,9 +25,16 @@ namespace Umbraco.Core.Persistence.Dtos /// [Column("data")] [SpecialDbType(SpecialDbTypes.NTEXT)] + [NullSetting(NullSetting = NullSettings.Null)] public string Data { get; set; } [Column("rv")] public long Rv { get; set; } + + [Column("dataRaw")] + [NullSetting(NullSetting = NullSettings.Null)] + public byte[] RawData { get; set; } + + } } diff --git a/src/Umbraco.Core/Persistence/Dtos/ContentVersionDto.cs b/src/Umbraco.Core/Persistence/Dtos/ContentVersionDto.cs index 4b203c128f..f9bf283be9 100644 --- a/src/Umbraco.Core/Persistence/Dtos/ContentVersionDto.cs +++ b/src/Umbraco.Core/Persistence/Dtos/ContentVersionDto.cs @@ -19,7 +19,7 @@ namespace Umbraco.Core.Persistence.Dtos [Column("nodeId")] [ForeignKey(typeof(ContentDto))] - [Index(IndexTypes.NonClustered, Name = "IX_" + TableName + "_NodeId", ForColumns = "nodeId,current")] + [Index(IndexTypes.NonClustered, Name = "IX_" + TableName + "_NodeId", ForColumns = "nodeId,current", IncludeColumns = "id,versionDate,text,userId")] public int NodeId { get; set; } [Column("versionDate")] // TODO: db rename to 'updateDate' @@ -32,6 +32,7 @@ namespace Umbraco.Core.Persistence.Dtos public int? UserId { get => _userId == 0 ? null : _userId; set => _userId = value; } //return null if zero [Column("current")] + [Index(IndexTypes.NonClustered, Name = "IX_" + TableName + "_Current", IncludeColumns = "nodeId")] public bool Current { get; set; } // about current: diff --git a/src/Umbraco.Core/Persistence/Dtos/ExternalLoginDto.cs b/src/Umbraco.Core/Persistence/Dtos/ExternalLoginDto.cs index 1b774854a6..0a56552000 100644 --- a/src/Umbraco.Core/Persistence/Dtos/ExternalLoginDto.cs +++ b/src/Umbraco.Core/Persistence/Dtos/ExternalLoginDto.cs @@ -14,9 +14,13 @@ namespace Umbraco.Core.Persistence.Dtos [PrimaryKeyColumn(Name = "PK_umbracoExternalLogin")] public int Id { get; set; } + // TODO: This is completely missing a FK!!? + [Column("userId")] public int UserId { get; set; } + // TODO: There should be an index on both LoginProvider and ProviderKey + [Column("loginProvider")] [Length(4000)] [NullSetting(NullSetting = NullSettings.NotNull)] @@ -30,5 +34,13 @@ namespace Umbraco.Core.Persistence.Dtos [Column("createDate")] [Constraint(Default = SystemMethods.CurrentDateTime)] public DateTime CreateDate { get; set; } + + /// + /// Used to store any arbitrary data for the user and external provider - like user tokens returned from the provider + /// + [Column("userData")] + [NullSetting(NullSetting = NullSettings.Null)] + [SpecialDbType(SpecialDbTypes.NTEXT)] + public string UserData { get; set; } } } diff --git a/src/Umbraco.Core/Persistence/Dtos/NodeDto.cs b/src/Umbraco.Core/Persistence/Dtos/NodeDto.cs index 5800efb97a..207195e594 100644 --- a/src/Umbraco.Core/Persistence/Dtos/NodeDto.cs +++ b/src/Umbraco.Core/Persistence/Dtos/NodeDto.cs @@ -20,7 +20,7 @@ namespace Umbraco.Core.Persistence.Dtos [Column("uniqueId")] [NullSetting(NullSetting = NullSettings.NotNull)] - [Index(IndexTypes.UniqueNonClustered, Name = "IX_" + TableName + "_UniqueId")] + [Index(IndexTypes.UniqueNonClustered, Name = "IX_" + TableName + "_UniqueId", IncludeColumns = "parentId,level,path,sortOrder,trashed,nodeUser,text,createDate")] [Constraint(Default = SystemMethods.NewGuid)] public Guid UniqueId { get; set; } @@ -29,7 +29,9 @@ namespace Umbraco.Core.Persistence.Dtos [Index(IndexTypes.NonClustered, Name = "IX_" + TableName + "_ParentId")] public int ParentId { get; set; } + // NOTE: This index is primarily for the nucache data lookup, see https://github.com/umbraco/Umbraco-CMS/pull/8365#issuecomment-673404177 [Column("level")] + [Index(IndexTypes.NonClustered, Name = "IX_" + TableName + "_Level", ForColumns = "level,parentId,sortOrder,nodeObjectType,trashed", IncludeColumns = "nodeUser,path,uniqueId,createDate")] public short Level { get; set; } [Column("path")] @@ -55,8 +57,8 @@ namespace Umbraco.Core.Persistence.Dtos public string Text { get; set; } [Column("nodeObjectType")] // TODO: db rename to 'objectType' - [NullSetting(NullSetting = NullSettings.Null)] - [Index(IndexTypes.NonClustered, Name = "IX_" + TableName + "_ObjectType")] + [NullSetting(NullSetting = NullSettings.Null)] + [Index(IndexTypes.NonClustered, Name = "IX_" + TableName + "_ObjectType", ForColumns = "nodeObjectType,trashed", IncludeColumns = "uniqueId,parentId,level,path,sortOrder,nodeUser,text,createDate")] public Guid? NodeObjectType { get; set; } [Column("createDate")] diff --git a/src/Umbraco.Core/Persistence/Dtos/PropertyTypeDto.cs b/src/Umbraco.Core/Persistence/Dtos/PropertyTypeDto.cs index 3e8d6e7496..572201c94a 100644 --- a/src/Umbraco.Core/Persistence/Dtos/PropertyTypeDto.cs +++ b/src/Umbraco.Core/Persistence/Dtos/PropertyTypeDto.cs @@ -62,6 +62,10 @@ namespace Umbraco.Core.Persistence.Dtos [Length(2000)] public string Description { get; set; } + [Column("labelOnTop")] + [Constraint(Default = "0")] + public bool LabelOnTop { get; set; } + [Column("variations")] [Constraint(Default = "1" /*ContentVariation.InvariantNeutral*/)] public byte Variations { get; set; } diff --git a/src/Umbraco.Core/Persistence/Dtos/PropertyTypeReadOnlyDto.cs b/src/Umbraco.Core/Persistence/Dtos/PropertyTypeReadOnlyDto.cs index 4c352a0134..d2001c00d5 100644 --- a/src/Umbraco.Core/Persistence/Dtos/PropertyTypeReadOnlyDto.cs +++ b/src/Umbraco.Core/Persistence/Dtos/PropertyTypeReadOnlyDto.cs @@ -44,6 +44,9 @@ namespace Umbraco.Core.Persistence.Dtos [Column("Description")] public string Description { get; set; } + [Column("labelOnTop")] + public bool LabelOnTop { get; set; } + /* cmsMemberType */ [Column("memberCanEdit")] public bool CanEdit { get; set; } diff --git a/src/Umbraco.Core/Persistence/Factories/ExternalLoginFactory.cs b/src/Umbraco.Core/Persistence/Factories/ExternalLoginFactory.cs index 4309fe511f..6c1af68acd 100644 --- a/src/Umbraco.Core/Persistence/Factories/ExternalLoginFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/ExternalLoginFactory.cs @@ -1,13 +1,17 @@ -using Umbraco.Core.Models.Identity; +using System; +using Umbraco.Core.Models.Identity; using Umbraco.Core.Persistence.Dtos; namespace Umbraco.Core.Persistence.Factories { internal static class ExternalLoginFactory { - public static IIdentityUserLogin BuildEntity(ExternalLoginDto dto) + public static IIdentityUserLoginExtended BuildEntity(ExternalLoginDto dto) { - var entity = new IdentityUserLogin(dto.Id, dto.LoginProvider, dto.ProviderKey, dto.UserId, dto.CreateDate); + var entity = new IdentityUserLogin(dto.Id, dto.LoginProvider, dto.ProviderKey, dto.UserId, dto.CreateDate) + { + UserData = dto.UserData + }; // reset dirty initial properties (U4-1946) entity.ResetDirtyProperties(false); @@ -16,13 +20,30 @@ namespace Umbraco.Core.Persistence.Factories public static ExternalLoginDto BuildDto(IIdentityUserLogin entity) { + var asExtended = entity as IIdentityUserLoginExtended; var dto = new ExternalLoginDto { Id = entity.Id, CreateDate = entity.CreateDate, LoginProvider = entity.LoginProvider, ProviderKey = entity.ProviderKey, - UserId = entity.UserId + UserId = entity.UserId, + UserData = asExtended?.UserData + }; + + return dto; + } + + public static ExternalLoginDto BuildDto(int userId, IExternalLogin entity, int? id = null) + { + var dto = new ExternalLoginDto + { + Id = id ?? default, + UserId = userId, + LoginProvider = entity.LoginProvider, + ProviderKey = entity.ProviderKey, + UserData = entity.UserData, + CreateDate = DateTime.Now }; return dto; diff --git a/src/Umbraco.Core/Persistence/Factories/PropertyGroupFactory.cs b/src/Umbraco.Core/Persistence/Factories/PropertyGroupFactory.cs index dc1629e8f7..f139619302 100644 --- a/src/Umbraco.Core/Persistence/Factories/PropertyGroupFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/PropertyGroupFactory.cs @@ -132,7 +132,8 @@ namespace Umbraco.Core.Persistence.Factories ValidationRegExp = propertyType.ValidationRegExp, ValidationRegExpMessage = propertyType.ValidationRegExpMessage, UniqueId = propertyType.Key, - Variations = (byte)propertyType.Variations + Variations = (byte)propertyType.Variations, + LabelOnTop = propertyType.LabelOnTop }; if (tabId != default) diff --git a/src/Umbraco.Core/Persistence/FaultHandling/Strategies/SqlAzureTransientErrorDetectionStrategy.cs b/src/Umbraco.Core/Persistence/FaultHandling/Strategies/SqlAzureTransientErrorDetectionStrategy.cs index 849fd35fad..f763594616 100644 --- a/src/Umbraco.Core/Persistence/FaultHandling/Strategies/SqlAzureTransientErrorDetectionStrategy.cs +++ b/src/Umbraco.Core/Persistence/FaultHandling/Strategies/SqlAzureTransientErrorDetectionStrategy.cs @@ -4,6 +4,10 @@ using System.Data.SqlClient; namespace Umbraco.Core.Persistence.FaultHandling.Strategies { + // See https://docs.microsoft.com/en-us/azure/azure-sql/database/troubleshoot-common-connectivity-issues + // Also we could just use the nuget package instead https://www.nuget.org/packages/EnterpriseLibrary.TransientFaultHandling/ ? + // but i guess that's not netcore so we'll just leave it. + /// /// Provides the transient error detection logic for transient faults that are specific to SQL Azure. /// @@ -71,7 +75,7 @@ namespace Umbraco.Core.Persistence.FaultHandling.Strategies /// Determines whether the specified exception represents a transient failure that can be compensated by a retry. /// /// The exception object to be verified. - /// True if the specified exception is considered as transient, otherwise false. + /// true if the specified exception is considered as transient; otherwise, false. public bool IsTransient(Exception ex) { if (ex != null) @@ -97,40 +101,50 @@ namespace Umbraco.Core.Persistence.FaultHandling.Strategies return true; - // SQL Error Code: 40197 - // The service has encountered an error processing your request. Please try again. - case 40197: + // SQL Error Code: 10928 + // Resource ID: %d. The %s limit for the database is %d and has been reached. + case 10928: + // SQL Error Code: 10929 + // Resource ID: %d. The %s minimum guarantee is %d, maximum limit is %d and the current usage for the database is %d. + // However, the server is currently too busy to support requests greater than %d for this database. + case 10929: // SQL Error Code: 10053 // A transport-level error has occurred when receiving results from the server. // An established connection was aborted by the software in your host machine. case 10053: // SQL Error Code: 10054 - // A transport-level error has occurred when sending the request to the server. + // A transport-level error has occurred when sending the request to the server. // (provider: TCP Provider, error: 0 - An existing connection was forcibly closed by the remote host.) case 10054: // SQL Error Code: 10060 - // A network-related or instance-specific error occurred while establishing a connection to SQL Server. - // The server was not found or was not accessible. Verify that the instance name is correct and that SQL Server - // is configured to allow remote connections. (provider: TCP Provider, error: 0 - A connection attempt failed - // because the connected party did not properly respond after a period of time, or established connection failed + // A network-related or instance-specific error occurred while establishing a connection to SQL Server. + // The server was not found or was not accessible. Verify that the instance name is correct and that SQL Server + // is configured to allow remote connections. (provider: TCP Provider, error: 0 - A connection attempt failed + // because the connected party did not properly respond after a period of time, or established connection failed // because connected host has failed to respond.)"} case 10060: + // SQL Error Code: 40197 + // The service has encountered an error processing your request. Please try again. + case 40197: + // SQL Error Code: 40540 + // The service has encountered an error processing your request. Please try again. + case 40540: // SQL Error Code: 40613 - // Database XXXX on server YYYY is not currently available. Please retry the connection later. If the problem persists, contact customer + // Database XXXX on server YYYY is not currently available. Please retry the connection later. If the problem persists, contact customer // support, and provide them the session tracing ID of ZZZZZ. case 40613: // SQL Error Code: 40143 // The service has encountered an error processing your request. Please try again. case 40143: // SQL Error Code: 233 - // The client was unable to establish a connection because of an error during connection initialization process before login. - // Possible causes include the following: the client tried to connect to an unsupported version of SQL Server; the server was too busy - // to accept new connections; or there was a resource limitation (insufficient memory or maximum allowed connections) on the server. + // The client was unable to establish a connection because of an error during connection initialization process before login. + // Possible causes include the following: the client tried to connect to an unsupported version of SQL Server; the server was too busy + // to accept new connections; or there was a resource limitation (insufficient memory or maximum allowed connections) on the server. // (provider: TCP Provider, error: 0 - An existing connection was forcibly closed by the remote host.) case 233: // SQL Error Code: 64 - // A connection was successfully established with the server, but then an error occurred during the login process. - // (provider: TCP Provider, error: 0 - The specified network name is no longer available.) + // A connection was successfully established with the server, but then an error occurred during the login process. + // (provider: TCP Provider, error: 0 - The specified network name is no longer available.) case 64: // DBNETLIB Error Code: 20 // The instance of SQL Server you attempted to connect to does not support encryption. diff --git a/src/Umbraco.Core/Persistence/Mappers/PropertyTypeMapper.cs b/src/Umbraco.Core/Persistence/Mappers/PropertyTypeMapper.cs index 6f22b61f9a..d44618cc96 100644 --- a/src/Umbraco.Core/Persistence/Mappers/PropertyTypeMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/PropertyTypeMapper.cs @@ -29,6 +29,7 @@ namespace Umbraco.Core.Persistence.Mappers DefineMap(nameof(PropertyType.SortOrder), nameof(PropertyTypeDto.SortOrder)); DefineMap(nameof(PropertyType.ValidationRegExp), nameof(PropertyTypeDto.ValidationRegExp)); DefineMap(nameof(PropertyType.ValidationRegExpMessage), nameof(PropertyTypeDto.ValidationRegExpMessage)); + DefineMap(nameof(PropertyType.LabelOnTop), nameof(PropertyTypeDto.LabelOnTop)); DefineMap(nameof(PropertyType.PropertyEditorAlias), nameof(DataTypeDto.EditorAlias)); DefineMap(nameof(PropertyType.ValueStorageType), nameof(DataTypeDto.DbType)); } diff --git a/src/Umbraco.Core/Persistence/NPocoDatabaseExtensions-Bulk.cs b/src/Umbraco.Core/Persistence/NPocoDatabaseExtensions-Bulk.cs index 10db1ca18e..bff682d095 100644 --- a/src/Umbraco.Core/Persistence/NPocoDatabaseExtensions-Bulk.cs +++ b/src/Umbraco.Core/Persistence/NPocoDatabaseExtensions-Bulk.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Data; using System.Data.SqlClient; using System.Data.SqlServerCe; +using System.Data.SqlTypes; using System.Linq; using NPoco; using Umbraco.Core.Persistence.SqlSyntax; @@ -210,7 +211,15 @@ namespace Umbraco.Core.Persistence if (IncludeColumn(pocoData, columns[i])) { var val = columns[i].Value.GetValue(record); - updatableRecord.SetValue(i, val); + if (val is byte[]) + { + var bytes = val as byte[]; + updatableRecord.SetSqlBinary(i, new SqlBinary(bytes)); + } + else + { + updatableRecord.SetValue(i, val); + } } } resultSet.Insert(updatableRecord); diff --git a/src/Umbraco.Core/Persistence/NPocoDatabaseExtensions.cs b/src/Umbraco.Core/Persistence/NPocoDatabaseExtensions.cs index 152dcbe6d3..c2100d97ad 100644 --- a/src/Umbraco.Core/Persistence/NPocoDatabaseExtensions.cs +++ b/src/Umbraco.Core/Persistence/NPocoDatabaseExtensions.cs @@ -27,12 +27,13 @@ namespace Umbraco.Core.Persistence /// The number of rows to load per page /// /// + /// Specify a custom Sql command to get the total count, if null is specified than the auto-generated sql count will be used /// /// /// NPoco's normal Page returns a List{T} but sometimes we don't want all that in memory and instead want to /// iterate over each row with a reader using Query vs Fetch. /// - internal static IEnumerable QueryPaged(this IDatabase database, long pageSize, Sql sql) + internal static IEnumerable QueryPaged(this IDatabase database, long pageSize, Sql sql, Sql sqlCount) { var sqlString = sql.SQL; var sqlArgs = sql.Arguments; @@ -42,12 +43,12 @@ namespace Umbraco.Core.Persistence do { // Get the paged queries - database.BuildPageQueries(pageIndex * pageSize, pageSize, sqlString, ref sqlArgs, out var sqlCount, out var sqlPage); + database.BuildPageQueries(pageIndex * pageSize, pageSize, sqlString, ref sqlArgs, out var generatedSqlCount, out var sqlPage); // get the item count once if (itemCount == null) { - itemCount = database.ExecuteScalar(sqlCount, sqlArgs); + itemCount = database.ExecuteScalar(sqlCount?.SQL ?? generatedSqlCount, sqlCount?.Arguments ?? sqlArgs); } pageIndex++; @@ -60,6 +61,22 @@ namespace Umbraco.Core.Persistence } while ((pageIndex * pageSize) < itemCount); } + /// + /// Iterates over the result of a paged data set with a db reader + /// + /// + /// + /// + /// The number of rows to load per page + /// + /// + /// + /// + /// NPoco's normal Page returns a List{T} but sometimes we don't want all that in memory and instead want to + /// iterate over each row with a reader using Query vs Fetch. + /// + internal static IEnumerable QueryPaged(this IDatabase database, long pageSize, Sql sql) => database.QueryPaged(pageSize, sql, null); + // NOTE // // proper way to do it with TSQL and SQLCE diff --git a/src/Umbraco.Core/Persistence/PocoDataDataReader.cs b/src/Umbraco.Core/Persistence/PocoDataDataReader.cs index 1d7d301b87..460a4d3d90 100644 --- a/src/Umbraco.Core/Persistence/PocoDataDataReader.cs +++ b/src/Umbraco.Core/Persistence/PocoDataDataReader.cs @@ -79,6 +79,9 @@ namespace Umbraco.Core.Persistence case SpecialDbTypes.NCHAR: sqlDbType = SqlDbType.NChar; break; + case SpecialDbTypes.NVARCHARMAX: + sqlDbType = SqlDbType.NVarChar; + break; default: throw new ArgumentOutOfRangeException(); } diff --git a/src/Umbraco.Core/Persistence/Querying/ExpressionVisitorBase.cs b/src/Umbraco.Core/Persistence/Querying/ExpressionVisitorBase.cs index d04930fa92..388286a79b 100644 --- a/src/Umbraco.Core/Persistence/Querying/ExpressionVisitorBase.cs +++ b/src/Umbraco.Core/Persistence/Querying/ExpressionVisitorBase.cs @@ -328,8 +328,11 @@ namespace Umbraco.Core.Persistence.Querying { case ExpressionType.MemberAccess: // false property , i.e. x => !Trashed - SqlParameters.Add(true); - return Visited ? string.Empty : $"NOT ({o} = @{SqlParameters.Count - 1})"; + // BUT we don't want to do a NOT SQL statement since this generally results in indexes not being used + // so we want to do an == false + SqlParameters.Add(false); + return Visited ? string.Empty : $"{o} = @{SqlParameters.Count - 1}"; + //return Visited ? string.Empty : $"NOT ({o} = @{SqlParameters.Count - 1})"; default: // could be anything else, such as: x => !x.Path.StartsWith("-20") return Visited ? string.Empty : string.Concat("NOT (", o, ")"); @@ -744,7 +747,9 @@ namespace Umbraco.Core.Persistence.Querying var c = exp[0]; return (c == '"' || c == '`' || c == '\'') && exp[exp.Length - 1] == c - ? exp.Substring(1, exp.Length - 2) + ? exp.Length == 1 + ? string.Empty + : exp.Substring(1, exp.Length - 2) : exp; } } diff --git a/src/Umbraco.Core/Persistence/Repositories/IDocumentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IDocumentRepository.cs index fc5382499f..0971b2047a 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IDocumentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IDocumentRepository.cs @@ -12,6 +12,11 @@ namespace Umbraco.Core.Persistence.Repositories /// void ClearSchedule(DateTime date); + void ClearSchedule(DateTime date, ContentScheduleAction action); + + bool HasContentForExpiration(DateTime date); + bool HasContentForRelease(DateTime date); + /// /// Gets objects having an expiration date before (lower than, or equal to) a specified date. /// diff --git a/src/Umbraco.Core/Persistence/Repositories/IExternalLoginRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IExternalLoginRepository.cs index 6d145e9961..a5a93449d3 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IExternalLoginRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IExternalLoginRepository.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using Microsoft.AspNet.Identity; using Umbraco.Core.Models.Identity; @@ -6,7 +7,9 @@ namespace Umbraco.Core.Persistence.Repositories { public interface IExternalLoginRepository : IReadWriteQueryRepository { + [Obsolete("Use the overload specifying IIdentityUserLoginExtended instead")] void SaveUserLogins(int memberId, IEnumerable logins); + void Save(int userId, IEnumerable logins); void DeleteUserLogins(int memberId); } } diff --git a/src/Umbraco.Core/Persistence/Repositories/IMemberGroupRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IMemberGroupRepository.cs index 9c75c051bd..d63b1bb9db 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IMemberGroupRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IMemberGroupRepository.cs @@ -1,10 +1,18 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using Umbraco.Core.Models; namespace Umbraco.Core.Persistence.Repositories { public interface IMemberGroupRepository : IReadWriteQueryRepository { + /// + /// Gets a member group by it's uniqueId + /// + /// + /// + IMemberGroup Get(Guid uniqueId); + /// /// Gets a member group by it's name /// diff --git a/src/Umbraco.Core/Persistence/Repositories/IMemberRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IMemberRepository.cs index 245c024356..c737c2bf66 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IMemberRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IMemberRepository.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using Umbraco.Core.Models; using Umbraco.Core.Persistence.Querying; @@ -6,6 +7,8 @@ namespace Umbraco.Core.Persistence.Repositories { public interface IMemberRepository : IContentRepository { + IMember GetByUsername(string username); + /// /// Finds members in a given role /// @@ -35,5 +38,17 @@ namespace Umbraco.Core.Persistence.Repositories /// /// int GetCountByQuery(IQuery query); + + /// + /// Sets a members last login date based on their username + /// + /// + /// + /// + /// This is a specialized method because whenever a member logs in, the membership provider requires us to set the 'online' which requires + /// updating their login date. This operation must be fast and cannot use database locks which is fine if we are only executing a single query + /// for this data since there won't be any other data contention issues. + /// + void SetLastLogin(string username, DateTime date); } } diff --git a/src/Umbraco.Core/Persistence/Repositories/IRedirectUrlRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IRedirectUrlRepository.cs index d05f4e007c..36f885520d 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IRedirectUrlRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IRedirectUrlRepository.cs @@ -10,72 +10,80 @@ namespace Umbraco.Core.Persistence.Repositories public interface IRedirectUrlRepository : IReadWriteQueryRepository { /// - /// Gets a redirect url. + /// Gets a redirect URL. /// - /// The Umbraco redirect url route. + /// The Umbraco redirect URL route. /// The content unique key. /// The culture. /// IRedirectUrl Get(string url, Guid contentKey, string culture); /// - /// Deletes a redirect url. + /// Deletes a redirect URL. /// - /// The redirect url identifier. + /// The redirect URL identifier. void Delete(Guid id); /// - /// Deletes all redirect urls. + /// Deletes all redirect URLs. /// void DeleteAll(); /// - /// Deletes all redirect urls for a given content. + /// Deletes all redirect URLs for a given content. /// /// The content unique key. void DeleteContentUrls(Guid contentKey); /// - /// Gets the most recent redirect url corresponding to an Umbraco redirect url route. + /// Gets the most recent redirect URL corresponding to an Umbraco redirect URL route. /// - /// The Umbraco redirect url route. - /// The most recent redirect url corresponding to the route. + /// The Umbraco redirect URL route. + /// The most recent redirect URL corresponding to the route. IRedirectUrl GetMostRecentUrl(string url); /// - /// Gets all redirect urls for a content item. + /// Gets the most recent redirect URL corresponding to an Umbraco redirect URL route. + /// + /// The Umbraco redirect URL route. + /// The culture the domain is associated with + /// The most recent redirect URL corresponding to the route. + IRedirectUrl GetMostRecentUrl(string url, string culture); + + /// + /// Gets all redirect URLs for a content item. /// /// The content unique key. - /// All redirect urls for the content item. + /// All redirect URLs for the content item. IEnumerable GetContentUrls(Guid contentKey); /// - /// Gets all redirect urls. + /// Gets all redirect URLs. /// /// The page index. /// The page size. - /// The total count of redirect urls. - /// The redirect urls. + /// The total count of redirect URLs. + /// The redirect URLs. IEnumerable GetAllUrls(long pageIndex, int pageSize, out long total); /// - /// Gets all redirect urls below a given content item. + /// Gets all redirect URLs below a given content item. /// /// The content unique identifier. /// The page index. /// The page size. - /// The total count of redirect urls. - /// The redirect urls. + /// The total count of redirect URLs. + /// The redirect URLs. IEnumerable GetAllUrls(int rootContentId, long pageIndex, int pageSize, out long total); /// - /// Searches for all redirect urls that contain a given search term in their URL property. + /// Searches for all redirect URLs that contain a given search term in their URL property. /// /// The term to search for. /// The page index. /// The page size. - /// The total count of redirect urls. - /// The redirect urls. + /// The total count of redirect URLs. + /// The redirect URLs. IEnumerable SearchUrls(string searchTerm, long pageIndex, int pageSize, out long total); } } diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs index d56724db9f..1aabb51d6c 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs @@ -514,7 +514,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement currentParentIds.Add(node.NodeId); // paths parts without the roots - var pathParts = node.Path.Split(',').Where(x => !rootIds.Contains(int.Parse(x))).ToArray(); + var pathParts = node.Path.Split(Constants.CharArrays.Comma).Where(x => !rootIds.Contains(int.Parse(x))).ToArray(); if (!prevParentIds.Contains(node.ParentId)) { @@ -897,7 +897,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement var template = SqlContext.Templates.Get(Constants.SqlTemplates.VersionableRepository.EnsureUniqueNodeName, tsql => tsql .Select(x => Alias(x.NodeId, "id"), x => Alias(x.Text, "name")) .From() - .Where(x => x.NodeObjectType == SqlTemplate.Arg("nodeObjectType") && x.ParentId == SqlTemplate.Arg("parentId"))); + .Where(x => x.NodeObjectType == SqlTemplate.Arg("nodeObjectType") && x.ParentId == SqlTemplate.Arg("parentId")) + ); var sql = template.Sql(NodeObjectTypeId, parentId); var names = Database.Fetch(sql); @@ -907,28 +908,43 @@ namespace Umbraco.Core.Persistence.Repositories.Implement protected virtual int GetNewChildSortOrder(int parentId, int first) { - var template = SqlContext.Templates.Get(Constants.SqlTemplates.VersionableRepository.GetSortOrder, tsql => - tsql.Select($"COALESCE(MAX(sortOrder),{first - 1})").From().Where(x => x.ParentId == SqlTemplate.Arg("parentId") && x.NodeObjectType == NodeObjectTypeId) + var template = SqlContext.Templates.Get(Constants.SqlTemplates.VersionableRepository.GetSortOrder, tsql => tsql + .Select("MAX(sortOrder)") + .From() + .Where(x => x.NodeObjectType == SqlTemplate.Arg("nodeObjectType") && x.ParentId == SqlTemplate.Arg("parentId")) ); - return Database.ExecuteScalar(template.Sql(new { parentId })) + 1; + var sql = template.Sql(NodeObjectTypeId, parentId); + var sortOrder = Database.ExecuteScalar(sql); + + return (sortOrder + 1) ?? first; } protected virtual NodeDto GetParentNodeDto(int parentId) { - var template = SqlContext.Templates.Get(Constants.SqlTemplates.VersionableRepository.GetParentNode, tsql => - tsql.Select().From().Where(x => x.NodeId == SqlTemplate.Arg("parentId")) + var template = SqlContext.Templates.Get(Constants.SqlTemplates.VersionableRepository.GetParentNode, tsql => tsql + .Select() + .From() + .Where(x => x.NodeId == SqlTemplate.Arg("parentId")) ); - return Database.Fetch(template.Sql(parentId)).First(); + var sql = template.Sql(parentId); + var nodeDto = Database.First(sql); + + return nodeDto; } protected virtual int GetReservedId(Guid uniqueId) { - var template = SqlContext.Templates.Get(Constants.SqlTemplates.VersionableRepository.GetReservedId, tsql => - tsql.Select(x => x.NodeId).From().Where(x => x.UniqueId == SqlTemplate.Arg("uniqueId") && x.NodeObjectType == Constants.ObjectTypes.IdReservation) + var template = SqlContext.Templates.Get(Constants.SqlTemplates.VersionableRepository.GetReservedId, tsql => tsql + .Select(x => x.NodeId) + .From() + .Where(x => x.UniqueId == SqlTemplate.Arg("uniqueId") && x.NodeObjectType == Constants.ObjectTypes.IdReservation) ); - var id = Database.ExecuteScalar(template.Sql(new { uniqueId = uniqueId })); + + var sql = template.Sql(new { uniqueId }); + var id = Database.ExecuteScalar(sql); + return id ?? 0; } @@ -987,6 +1003,81 @@ namespace Umbraco.Core.Persistence.Repositories.Implement } + /// + /// Inserts property values for the content entity + /// + /// + /// + /// + /// + /// + /// Used when creating a new entity + /// + protected void InsertPropertyValues(TEntity entity, int publishedVersionId, out bool edited, out HashSet editedCultures) + { + // persist the property data + var propertyDataDtos = PropertyFactory.BuildDtos(entity.ContentType.Variations, entity.VersionId, publishedVersionId, entity.Properties, LanguageRepository, out edited, out editedCultures); + foreach (var propertyDataDto in propertyDataDtos) + { + Database.Insert(propertyDataDto); + } + // TODO: we can speed this up: Use BulkInsert and then do one SELECT to re-retrieve the property data inserted with assigned IDs. + // This is a perfect thing to benchmark with Benchmark.NET to compare perf between Nuget releases. + } + + /// + /// Used to atomically replace the property values for the entity version specified + /// + /// + /// + /// + /// + /// + + protected void ReplacePropertyValues(TEntity entity, int versionId, int publishedVersionId, out bool edited, out HashSet editedCultures) + { + // Replace the property data. + // Lookup the data to update with a UPDLOCK (using ForUpdate()) this is because we need to be atomic + // and handle DB concurrency. Doing a clear and then re-insert is prone to concurrency issues. + + var propDataSql = SqlContext.Sql().Select("*").From().Where(x => x.VersionId == versionId).ForUpdate(); + var existingPropData = Database.Fetch(propDataSql); + var propertyTypeToPropertyData = new Dictionary<(int propertyTypeId, int versionId, int? languageId, string segment), PropertyDataDto>(); + var existingPropDataIds = new List(); + foreach (var p in existingPropData) + { + existingPropDataIds.Add(p.Id); + propertyTypeToPropertyData[(p.PropertyTypeId, p.VersionId, p.LanguageId, p.Segment)] = p; + } + var propertyDataDtos = PropertyFactory.BuildDtos(entity.ContentType.Variations, entity.VersionId, publishedVersionId, entity.Properties, LanguageRepository, out edited, out editedCultures); + + foreach (var propertyDataDto in propertyDataDtos) + { + + // Check if this already exists and update, else insert a new one + if (propertyTypeToPropertyData.TryGetValue((propertyDataDto.PropertyTypeId, propertyDataDto.VersionId, propertyDataDto.LanguageId, propertyDataDto.Segment), out var propData)) + { + propertyDataDto.Id = propData.Id; + Database.Update(propertyDataDto); + } + else + { + // TODO: we can speed this up: Use BulkInsert and then do one SELECT to re-retrieve the property data inserted with assigned IDs. + // This is a perfect thing to benchmark with Benchmark.NET to compare perf between Nuget releases. + Database.Insert(propertyDataDto); + } + + // track which ones have been processed + existingPropDataIds.Remove(propertyDataDto.Id); + } + // For any remaining that haven't been processed they need to be deleted + if (existingPropDataIds.Count > 0) + { + Database.Execute(SqlContext.Sql().Delete().WhereIn(x => x.Id, existingPropDataIds)); + } + + } + private class NodeIdKey { [Column("id")] diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeCommonRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeCommonRepository.cs index 7781e2e38a..90774e4c0b 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeCommonRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeCommonRepository.cs @@ -303,7 +303,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement SortOrder = dto.SortOrder, ValidationRegExp = dto.ValidationRegExp, ValidationRegExpMessage = dto.ValidationRegExpMessage, - Variations = (ContentVariation)dto.Variations + Variations = (ContentVariation)dto.Variations, + LabelOnTop = dto.LabelOnTop }; } } diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepository.cs index 359b967dab..483f2393af 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepository.cs @@ -134,7 +134,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement if (objectTypes.Any()) { - sql = sql.Where("umbracoNode.nodeObjectType IN (@objectTypes)", objectTypes); + sql = sql.Where("umbracoNode.nodeObjectType IN (@objectTypes)", new { objectTypes = objectTypes }); } return Database.Fetch(sql); diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs index 357798a8a9..ed0c288691 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs @@ -448,8 +448,14 @@ AND umbracoNode.id <> @id", // The composed property is only considered segment variant when the base content type is also segment variant. // Example: Culture variant content type with a Culture+Segment variant property type will become ContentVariation.Culture var target = newContentTypeVariation & composedPropertyType.Variations; + // Determine the previous variation + // We have to compare with the old content type variation because the composed property might already have changed + // Example: A property with variations in an element type with variations is used in a document without + // when you enable variations the property has already enabled variations from the element type, + // but it's still a change from nothing because the document did not have variations, but it does now. + var from = oldContentTypeVariation & composedPropertyType.Variations; - propertyTypeVariationChanges[composedPropertyType.Id] = (composedPropertyType.Variations, target); + propertyTypeVariationChanges[composedPropertyType.Id] = (from, target); } } @@ -1184,7 +1190,7 @@ AND umbracoNode.id <> @id", { // first clear dependencies Database.Delete("WHERE propertyTypeId = @Id", new { Id = propertyTypeId }); - Database.Delete("WHERE propertytypeid = @Id", new { Id = propertyTypeId }); + Database.Delete("WHERE propertyTypeId = @Id", new { Id = propertyTypeId }); // then delete the property type Database.Delete("WHERE contentTypeId = @Id AND id = @PropertyTypeId", @@ -1312,7 +1318,7 @@ WHERE cmsContentType." + aliasColumn + @" LIKE @pattern", /// public bool HasContainerInPath(string contentPath) { - var ids = contentPath.Split(',').Select(int.Parse).ToArray(); + var ids = contentPath.Split(Constants.CharArrays.Comma).Select(int.Parse).ToArray(); return HasContainerInPath(ids); } diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/DataTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/DataTypeRepository.cs index 9ccf6e9623..cba030e17a 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/DataTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/DataTypeRepository.cs @@ -303,7 +303,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement private string EnsureUniqueNodeName(string nodeName, int id = 0) { - var template = SqlContext.Templates.Get("Umbraco.Core.DataTypeDefinitionRepository.EnsureUniqueNodeName", tsql => tsql + var template = SqlContext.Templates.Get(Constants.SqlTemplates.DataTypeRepository.EnsureUniqueNodeName, tsql => tsql .Select(x => Alias(x.NodeId, "id"), x => Alias(x.Text, "name")) .From() .Where(x => x.NodeObjectType == SqlTemplate.Arg("nodeObjectType"))); diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs index db1e2b350d..09d41a49a0 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs @@ -610,17 +610,11 @@ namespace Umbraco.Core.Persistence.Repositories.Implement Database.Insert(documentVersionDto); } - // replace the property data (rather than updating) + // replace the property data (rather than updating) // only need to delete for the version that existed, the new version (if any) has no property data yet - var versionToDelete = publishing ? entity.PublishedVersionId : entity.VersionId; - var deletePropertyDataSql = Sql().Delete().Where(x => x.VersionId == versionToDelete); - Database.Execute(deletePropertyDataSql); - - // insert property data - var propertyDataDtos = PropertyFactory.BuildDtos(entity.ContentType.Variations, entity.VersionId, publishing ? entity.PublishedVersionId : 0, - entity.Properties, LanguageRepository, out var edited, out var editedCultures); - foreach (var propertyDataDto in propertyDataDtos) - Database.Insert(propertyDataDto); + var versionToDelete = publishing ? entity.PublishedVersionId : entity.VersionId; + // insert property data + ReplacePropertyValues(entity, versionToDelete, publishing ? entity.PublishedVersionId : 0, out var edited, out var editedCultures); // if !publishing, we may have a new name != current publish name, // also impacts 'edited' @@ -900,7 +894,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement if (content.ParentId == -1) return content.Published; - var ids = content.Path.Split(',').Skip(1).Select(int.Parse); + var ids = content.Path.Split(Constants.CharArrays.Comma).Skip(1).Select(int.Parse); var sql = SqlContext.Sql() .SelectCount(x => x.NodeId) @@ -1017,6 +1011,37 @@ namespace Umbraco.Core.Persistence.Repositories.Implement Database.Execute(sql); } + /// + public void ClearSchedule(DateTime date, ContentScheduleAction action) + { + var a = action.ToString(); + var sql = Sql().Delete().Where(x => x.Date <= date && x.Action == a); + Database.Execute(sql); + } + + private Sql GetSqlForHasScheduling(ContentScheduleAction action, DateTime date) + { + var template = SqlContext.Templates.Get("Umbraco.Core.DocumentRepository.GetSqlForHasScheduling", tsql => tsql + .SelectCount() + .From() + .Where(x => x.Action == SqlTemplate.Arg("action") && x.Date <= SqlTemplate.Arg("date"))); + + var sql = template.Sql(action.ToString(), date); + return sql; + } + + public bool HasContentForExpiration(DateTime date) + { + var sql = GetSqlForHasScheduling(ContentScheduleAction.Expire, date); + return Database.ExecuteScalar(sql) > 0; + } + + public bool HasContentForRelease(DateTime date) + { + var sql = GetSqlForHasScheduling(ContentScheduleAction.Release, date); + return Database.ExecuteScalar(sql) > 0; + } + /// public IEnumerable GetContentForRelease(DateTime date) { diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs index 24040bf393..a7502a338a 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs @@ -369,7 +369,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement if (isContent || isMedia || isMember) sql - .AndSelect(x => Alias(x.Id, "versionId")) + .AndSelect(x => Alias(x.Id, "versionId"), x=>x.VersionDate) .AndSelect(x => x.Alias, x => x.Icon, x => x.Thumbnail, x => x.IsContainer, x => x.Variations); if (isContent) @@ -490,7 +490,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement if (isContent || isMedia || isMember) sql - .AndBy(x => x.Id) + .AndBy(x => x.Id, x => x.VersionDate) .AndBy(x => x.Alias, x => x.Icon, x => x.Thumbnail, x => x.IsContainer, x => x.Variations); if (defaultSort) @@ -594,6 +594,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement public string Text { get; set; } public Guid NodeObjectType { get; set; } public DateTime CreateDate { get; set; } + public DateTime VersionDate { get; set; } public int Children { get; set; } public int VersionId { get; set; } public string Alias { get; set; } @@ -627,6 +628,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement { entity.Trashed = dto.Trashed; entity.CreateDate = dto.CreateDate; + entity.UpdateDate = dto.VersionDate; entity.CreatorId = dto.UserId ?? Constants.Security.UnknownUserId; entity.Id = dto.NodeId; entity.Key = dto.UniqueId; diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ExternalLoginRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ExternalLoginRepository.cs index f708590ea8..ad53a2d522 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/ExternalLoginRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ExternalLoginRepository.cs @@ -22,25 +22,65 @@ namespace Umbraco.Core.Persistence.Repositories.Implement public void DeleteUserLogins(int memberId) { - Database.Execute("DELETE FROM ExternalLogins WHERE UserId=@userId", new { userId = memberId }); + Database.Delete("WHERE userId=@userId", new { userId = memberId }); + } + + public void Save(int userId, IEnumerable logins) + { + var sql = Sql() + .Select() + .From() + .Where(x => x.UserId == userId) + .ForUpdate(); + + // deduplicate the logins + logins = logins.DistinctBy(x => x.ProviderKey + x.LoginProvider).ToList(); + + var toUpdate = new Dictionary(); + var toDelete = new List(); + var toInsert = new List(logins); + + var existingLogins = Database.Query(sql).OrderByDescending(x => x.CreateDate).ToList(); + // used to track duplicates so they can be removed + var keys = new HashSet<(string, string)>(); + + foreach (var existing in existingLogins) + { + if (!keys.Add((existing.ProviderKey, existing.LoginProvider))) + { + // if it already exists we need to remove this one + toDelete.Add(existing.Id); + } + else + { + var found = logins.FirstOrDefault(x => + x.LoginProvider.Equals(existing.LoginProvider, StringComparison.InvariantCultureIgnoreCase) + && x.ProviderKey.Equals(existing.ProviderKey, StringComparison.InvariantCultureIgnoreCase)); + + if (found != null) + { + toUpdate.Add(existing.Id, found); + // if it's an update then it's not an insert + toInsert.RemoveAll(x => x.ProviderKey == found.ProviderKey && x.LoginProvider == found.LoginProvider); + } + else + { + toDelete.Add(existing.Id); + } + } + } + + // do the deletes, updates and inserts + if (toDelete.Count > 0) + Database.DeleteMany().Where(x => toDelete.Contains(x.Id)).Execute(); + foreach (var u in toUpdate) + Database.Update(ExternalLoginFactory.BuildDto(userId, u.Value, u.Key)); + Database.InsertBulk(toInsert.Select(i => ExternalLoginFactory.BuildDto(userId, i))); } public void SaveUserLogins(int memberId, IEnumerable logins) { - //clear out logins for member - Database.Execute("DELETE FROM umbracoExternalLogin WHERE userId=@userId", new { userId = memberId }); - - //add them all - foreach (var l in logins) - { - Database.Insert(new ExternalLoginDto - { - LoginProvider = l.LoginProvider, - ProviderKey = l.ProviderKey, - UserId = memberId, - CreateDate = DateTime.Now - }); - } + Save(memberId, logins.Select(x => new ExternalLogin(x.LoginProvider, x.ProviderKey))); } protected override IIdentityUserLogin PerformGet(int id) @@ -67,7 +107,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement return PerformGetAllOnIds(ids); } - var sql = GetBaseQuery(false); + var sql = GetBaseQuery(false).OrderByDescending(x => x.CreateDate); return ConvertFromDtos(Database.Fetch(sql)) .ToArray();// we don't want to re-iterate again! @@ -103,7 +143,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement foreach (var dto in dtos) { - yield return Get(dto.Id); + yield return ExternalLoginFactory.BuildEntity(dto); } } diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/MediaRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/MediaRepository.cs index 242f21c749..02bef366cb 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/MediaRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/MediaRepository.cs @@ -186,7 +186,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement const string pattern = ".*[_][0-9]+[x][0-9]+[.].*"; var isResized = Regex.IsMatch(mediaPath, pattern); - // If the image has been resized we strip the "_403x328" of the original "/media/1024/koala_403x328.jpg" url. + // If the image has been resized we strip the "_403x328" of the original "/media/1024/koala_403x328.jpg" URL. if (isResized) { var underscoreIndex = mediaPath.LastIndexOf('_'); @@ -281,9 +281,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement Database.Insert(mediaVersionDto); // persist the property data - var propertyDataDtos = PropertyFactory.BuildDtos(entity.ContentType.Variations, entity.VersionId, 0, entity.Properties, LanguageRepository, out _, out _); - foreach (var propertyDataDto in propertyDataDtos) - Database.Insert(propertyDataDto); + InsertPropertyValues(entity, 0, out _, out _); // set tags SetEntityTags(entity, _tagRepository); @@ -346,11 +344,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement Database.Update(mediaVersionDto); // replace the property data - var deletePropertyDataSql = SqlContext.Sql().Delete().Where(x => x.VersionId == entity.VersionId); - Database.Execute(deletePropertyDataSql); - var propertyDataDtos = PropertyFactory.BuildDtos(entity.ContentType.Variations, entity.VersionId, 0, entity.Properties, LanguageRepository, out _, out _); - foreach (var propertyDataDto in propertyDataDtos) - Database.Insert(propertyDataDto); + ReplacePropertyValues(entity, entity.VersionId, 0, out _, out _); SetEntityTags(entity, _tagRepository); diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/MemberGroupRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/MemberGroupRepository.cs index c138550de5..edd6dc0ebb 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/MemberGroupRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/MemberGroupRepository.cs @@ -69,7 +69,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement protected override string GetBaseWhereClause() { - return "umbracoNode.id = @id"; + return $"{Constants.DatabaseSchema.Tables.Node}.id = @id"; } protected override IEnumerable GetDeleteClauses() @@ -115,6 +115,16 @@ namespace Umbraco.Core.Persistence.Repositories.Implement entity.ResetDirtyProperties(); } + public IMemberGroup Get(Guid uniqueId) + { + var sql = GetBaseQuery(false); + sql.Where("umbracoNode.uniqueId = @uniqueId", new { uniqueId }); + + var dto = Database.Fetch(SqlSyntax.SelectTop(sql, 1)).FirstOrDefault(); + + return dto == null ? null : MemberGroupFactory.BuildEntity(dto); + } + public IMemberGroup GetByName(string name) { return IsolatedCache.GetCacheItem( diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/MemberRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/MemberRepository.cs index 42e7d1c32f..78dbbe317a 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/MemberRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/MemberRepository.cs @@ -25,6 +25,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement private readonly IMemberTypeRepository _memberTypeRepository; private readonly ITagRepository _tagRepository; private readonly IMemberGroupRepository _memberGroupRepository; + private readonly IRepositoryCachePolicy _memberByUsernameCachePolicy; public MemberRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IMemberTypeRepository memberTypeRepository, IMemberGroupRepository memberGroupRepository, ITagRepository tagRepository, ILanguageRepository languageRepository, IRelationRepository relationRepository, IRelationTypeRepository relationTypeRepository, @@ -34,6 +35,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement _memberTypeRepository = memberTypeRepository ?? throw new ArgumentNullException(nameof(memberTypeRepository)); _tagRepository = tagRepository ?? throw new ArgumentNullException(nameof(tagRepository)); _memberGroupRepository = memberGroupRepository; + + _memberByUsernameCachePolicy = new DefaultRepositoryCachePolicy(GlobalIsolatedCache, ScopeAccessor, DefaultOptions); } protected override MemberRepository This => this; @@ -242,8 +245,6 @@ namespace Umbraco.Core.Persistence.Repositories.Implement } entity.AddingEntity(); - var member = (Member) entity; - // ensure that strings don't contain characters that are invalid in xml // TODO: do we really want to keep doing this here? entity.SanitizeEntityPropertiesForXmlStorage(); @@ -301,7 +302,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement contentVersionDto.NodeId = nodeDto.NodeId; contentVersionDto.Current = true; Database.Insert(contentVersionDto); - member.VersionId = contentVersionDto.Id; + entity.VersionId = contentVersionDto.Id; // persist the member dto dto.NodeId = nodeDto.NodeId; @@ -318,9 +319,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement Database.Insert(dto); // persist the property data - var propertyDataDtos = PropertyFactory.BuildDtos(member.ContentType.Variations, member.VersionId, 0, entity.Properties, LanguageRepository, out _, out _); - foreach (var propertyDataDto in propertyDataDtos) - Database.Insert(propertyDataDto); + InsertPropertyValues(entity, 0, out _, out _); SetEntityTags(entity, _tagRepository); @@ -332,11 +331,9 @@ namespace Umbraco.Core.Persistence.Repositories.Implement } protected override void PersistUpdatedItem(IMember entity) - { - var member = (Member) entity; - + { // update - member.UpdatingEntity(); + entity.UpdatingEntity(); // ensure that strings don't contain characters that are invalid in xml // TODO: do we really want to keep doing this here? @@ -382,12 +379,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement if (changedCols.Count > 0) Database.Update(dto, changedCols); - // replace the property data - var deletePropertyDataSql = SqlContext.Sql().Delete().Where(x => x.VersionId == member.VersionId); - Database.Execute(deletePropertyDataSql); - var propertyDataDtos = PropertyFactory.BuildDtos(member.ContentType.Variations, member.VersionId, 0, entity.Properties, LanguageRepository, out _, out _); - foreach (var propertyDataDto in propertyDataDtos) - Database.Insert(propertyDataDto); + ReplacePropertyValues(entity, entity.VersionId, 0, out _, out _); SetEntityTags(entity, _tagRepository); @@ -505,6 +497,56 @@ namespace Umbraco.Core.Persistence.Repositories.Implement return Database.ExecuteScalar(fullSql); } + /// + public void SetLastLogin(string username, DateTime date) + { + // Important - these queries are designed to execute without an exclusive WriteLock taken in our distributed lock + // table. However due to the data that we are updating which relies on version data we cannot update this data + // without taking some locks, otherwise we'll end up with strange situations because when a member is updated, that operation + // deletes and re-inserts all property data. So if there are concurrent transactions, one deleting and re-inserting and another trying + // to update there can be problems. This is only an issue for cmsPropertyData, not umbracoContentVersion because that table just + // maintains a single row and it isn't deleted/re-inserted. + // So the important part here is the ForUpdate() call on the select to fetch the property data to update. + + // Update the cms property value for the member + + var sqlSelectTemplateProperty = SqlContext.Templates.Get("Umbraco.Core.MemberRepository.SetLastLogin1", s => s + .Select(x => x.Id) + .From() + .InnerJoin().On((l, r) => l.Id == r.PropertyTypeId) + .InnerJoin().On((l, r) => l.Id == r.VersionId) + .InnerJoin().On((l, r) => l.NodeId == r.NodeId) + .InnerJoin().On((l, r) => l.NodeId == r.NodeId) + .Where(x => x.NodeObjectType == SqlTemplate.Arg("nodeObjectType")) + .Where(x => x.Alias == SqlTemplate.Arg("propertyTypeAlias")) + .Where(x => x.LoginName == SqlTemplate.Arg("username")) + .ForUpdate()); + var sqlSelectProperty = sqlSelectTemplateProperty.Sql(Constants.ObjectTypes.Member, Constants.Conventions.Member.LastLoginDate, username); + + var update = Sql() + .Update(u => u + .Set(x => x.DateValue, date)) + .WhereIn(x => x.Id, sqlSelectProperty); + + Database.Execute(update); + + // Update the umbracoContentVersion value for the member + + var sqlSelectTemplateVersion = SqlContext.Templates.Get("Umbraco.Core.MemberRepository.SetLastLogin2", s => s + .Select(x => x.Id) + .From() + .InnerJoin().On((l, r) => l.NodeId == r.NodeId) + .InnerJoin().On((l, r) => l.NodeId == r.NodeId) + .Where(x => x.NodeObjectType == SqlTemplate.Arg("nodeObjectType")) + .Where(x => x.LoginName == SqlTemplate.Arg("username"))); + var sqlSelectVersion = sqlSelectTemplateVersion.Sql(Constants.ObjectTypes.Member, username); + + Database.Execute(Sql() + .Update(u => u + .Set(x => x.VersionDate, date)) + .WhereIn(x => x.Id, sqlSelectVersion)); + } + /// /// Gets paged member results. /// @@ -528,20 +570,6 @@ namespace Umbraco.Core.Persistence.Repositories.Implement ordering); } - private string _pagedResultsByQueryWhere; - - private string GetPagedResultsByQueryWhere() - { - if (_pagedResultsByQueryWhere == null) - _pagedResultsByQueryWhere = " AND (" - + $"({SqlSyntax.GetQuotedTableName("umbracoNode")}.{SqlSyntax.GetQuotedColumnName("text")} LIKE @0)" - + " OR " - + $"({SqlSyntax.GetQuotedTableName("cmsMember")}.{SqlSyntax.GetQuotedColumnName("LoginName")} LIKE @0)" - + ")"; - - return _pagedResultsByQueryWhere; - } - protected override string ApplySystemOrdering(ref Sql sql, Ordering ordering) { if (ordering.OrderBy.InvariantEquals("email")) @@ -631,5 +659,22 @@ namespace Umbraco.Core.Persistence.Repositories.Implement member.ResetDirtyProperties(false); return member; } + + public IMember GetByUsername(string username) + { + return _memberByUsernameCachePolicy.Get(username, PerformGetByUsername, PerformGetAllByUsername); + } + + private IMember PerformGetByUsername(string username) + { + var query = Query().Where(x => x.Username.Equals(username)); + return PerformGetByQuery(query).FirstOrDefault(); + } + + private IEnumerable PerformGetAllByUsername(params string[] usernames) + { + var query = Query().WhereIn(x => x.Username, usernames); + return PerformGetByQuery(query); + } } } diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/RedirectUrlRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/RedirectUrlRepository.cs index acf6bb7df2..24c1e31c20 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/RedirectUrlRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/RedirectUrlRepository.cs @@ -167,6 +167,23 @@ JOIN umbracoNode ON umbracoRedirectUrl.contentKey=umbracoNode.uniqueID"); return dto == null ? null : Map(dto); } + public IRedirectUrl GetMostRecentUrl(string url, string culture) + { + if (string.IsNullOrWhiteSpace(culture)) return GetMostRecentUrl(url); + var urlHash = url.GenerateHash(); + var sql = GetBaseQuery(false) + .Where(x => x.Url == url && x.UrlHash == urlHash && + (x.Culture == culture.ToLower() || x.Culture == string.Empty)) + .OrderByDescending(x => x.CreateDateUtc); + var dtos = Database.Fetch(sql); + var dto = dtos.FirstOrDefault(f => f.Culture == culture.ToLower()); + + if (dto == null) + dto = dtos.FirstOrDefault(f => f.Culture == string.Empty); + + return dto == null ? null : Map(dto); + } + public IEnumerable GetContentUrls(Guid contentKey) { var sql = GetBaseQuery(false) diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/RelationRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/RelationRepository.cs index 56a6336f75..592e112be1 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/RelationRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/RelationRepository.cs @@ -283,20 +283,54 @@ namespace Umbraco.Core.Persistence.Repositories.Implement return result; } - + public void DeleteByParent(int parentId, params string[] relationTypeAliases) { - var subQuery = Sql().Select(x => x.Id) - .From() - .InnerJoin().On(x => x.RelationType, x => x.Id) - .Where(x => x.ParentId == parentId); - - if (relationTypeAliases.Length > 0) + if (Database.DatabaseType.IsSqlCe()) { - subQuery.WhereIn(x => x.Alias, relationTypeAliases); - } + var subQuery = Sql().Select(x => x.Id) + .From() + .InnerJoin().On(x => x.RelationType, x => x.Id) + .Where(x => x.ParentId == parentId); - Database.Execute(Sql().Delete().WhereIn(x => x.Id, subQuery)); + if (relationTypeAliases.Length > 0) + { + subQuery.WhereIn(x => x.Alias, relationTypeAliases); + } + + Database.Execute(Sql().Delete().WhereIn(x => x.Id, subQuery)); + + } + else + { + if (relationTypeAliases.Length > 0) + { + var template = SqlContext.Templates.Get( + Constants.SqlTemplates.RelationRepository.DeleteByParentIn, + tsql => Sql().Delete() + .From() + .InnerJoin().On(x => x.RelationType, x => x.Id) + .Where(x => x.ParentId == SqlTemplate.Arg("parentId")) + .WhereIn(x => x.Alias, SqlTemplate.ArgIn("relationTypeAliases"))); + + var sql = template.Sql(parentId, relationTypeAliases); + + Database.Execute(sql); + } + else + { + var template = SqlContext.Templates.Get( + Constants.SqlTemplates.RelationRepository.DeleteByParentAll, + tsql => Sql().Delete() + .From() + .InnerJoin().On(x => x.RelationType, x => x.Id) + .Where(x => x.ParentId == SqlTemplate.Arg("parentId"))); + + var sql = template.Sql(parentId); + + Database.Execute(sql); + } + } } /// diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/SimpleGetRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/SimpleGetRepository.cs index f7e59820c3..d327fba67f 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/SimpleGetRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/SimpleGetRepository.cs @@ -11,6 +11,8 @@ using Umbraco.Core.Scoping; namespace Umbraco.Core.Persistence.Repositories.Implement { + // TODO: Obsolete this, change all implementations of this like in Dictionary to just use custom Cache policies like in the member repository. + /// /// Simple abstract ReadOnly repository used to simply have PerformGet and PeformGetAll with an underlying cache /// diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/UserRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/UserRepository.cs index 3be5102b83..c9f85c343c 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/UserRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/UserRepository.cs @@ -151,7 +151,7 @@ SELECT '4CountOfLockedOut' AS colName, COUNT(id) AS num FROM umbracoUser WHERE u UNION SELECT '5CountOfInvited' AS colName, COUNT(id) AS num FROM umbracoUser WHERE lastLoginDate IS NULL AND userDisabled = 1 AND invitedDate IS NOT NULL UNION -SELECT '6CountOfDisabled' AS colName, COUNT(id) AS num FROM umbracoUser WHERE userDisabled = 0 AND userNoConsole = 0 AND lastLoginDate IS NULL +SELECT '6CountOfDisabled' AS colName, COUNT(id) AS num FROM umbracoUser WHERE userDisabled = 0 AND userNoConsole = 0 AND lastLoginDate IS NULL ORDER BY colName"; var result = Database.Fetch(sql); @@ -168,10 +168,7 @@ ORDER BY colName"; } public Guid CreateLoginSession(int userId, string requestingIpAddress, bool cleanStaleSessions = true) - { - // TODO: I know this doesn't follow the normal repository conventions which would require us to create a UserSessionRepository - //and also business logic models for these objects but that's just so overkill for what we are doing - //and now that everything is properly in a transaction (Scope) there doesn't seem to be much reason for using that anymore + { var now = DateTime.UtcNow; var dto = new UserLoginDto { @@ -201,13 +198,14 @@ ORDER BY colName"; // that query is going to run a *lot*, make it a template var t = SqlContext.Templates.Get("Umbraco.Core.UserRepository.ValidateLoginSession", s => s .Select() + .SelectTop(1) .From() .Where(x => x.SessionId == SqlTemplate.Arg("sessionId")) .ForUpdate()); var sql = t.Sql(sessionId); - var found = Database.Query(sql).FirstOrDefault(); + var found = Database.FirstOrDefault(sql); if (found == null || found.UserId != userId || found.LoggedOutUtc.HasValue) return false; @@ -562,9 +560,9 @@ ORDER BY colName"; { userDto.EmailConfirmedDate = null; userDto.SecurityStampToken = entity.SecurityStamp = Guid.NewGuid().ToString(); - + changedCols.Add("emailConfirmedDate"); - changedCols.Add("securityStampToken"); + changedCols.Add("securityStampToken"); } //only update the changed cols @@ -693,7 +691,13 @@ ORDER BY colName"; else sql.WhereNotIn(x => x.Id, inSql); - return ConvertFromDtos(Database.Fetch(sql)); + + var dtos = Database.Fetch(sql); + + //adds missing bits like content and media start nodes + PerformGetReferencedDtos(dtos); + + return ConvertFromDtos(dtos); } /// diff --git a/src/Umbraco.Core/Persistence/SqlCeImageMapper.cs b/src/Umbraco.Core/Persistence/SqlCeImageMapper.cs new file mode 100644 index 0000000000..33303c68f0 --- /dev/null +++ b/src/Umbraco.Core/Persistence/SqlCeImageMapper.cs @@ -0,0 +1,59 @@ +using System; +using System.Data; +using System.Data.Common; +using System.Data.SqlServerCe; +using System.Linq; +using System.Reflection; +using NPoco; +using Umbraco.Core.Composing; + +namespace Umbraco.Core.Persistence +{ + /// + /// Custom NPoco mapper for SqlCe + /// + /// + /// Work arounds to handle special columns + /// + internal class SqlCeImageMapper : DefaultMapper + { + public override Func GetToDbConverter(Type destType, MemberInfo sourceMemberInfo) + { + if (sourceMemberInfo.GetMemberInfoType() == typeof(byte[])) + { + return x => + { + var pd = Current.SqlContext.PocoDataFactory.ForType(sourceMemberInfo.DeclaringType); + if (pd == null) return null; + var col = pd.AllColumns.FirstOrDefault(x => x.MemberInfoData.MemberInfo == sourceMemberInfo); + if (col == null) return null; + + return new SqlCeParameter + { + SqlDbType = SqlDbType.Image, + Value = x ?? Array.Empty() + }; + }; + } + return base.GetToDbConverter(destType, sourceMemberInfo); + } + + public override Func GetParameterConverter(DbCommand dbCommand, Type sourceType) + { + if (sourceType == typeof(byte[])) + { + return x => + { + var param = new SqlCeParameter + { + SqlDbType = SqlDbType.Image, + Value = x + }; + return param; + }; + + } + return base.GetParameterConverter(dbCommand, sourceType); + } + } +} diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/MicrosoftSqlSyntaxProviderBase.cs b/src/Umbraco.Core/Persistence/SqlSyntax/MicrosoftSqlSyntaxProviderBase.cs index be7b2cf069..7d0659dba8 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/MicrosoftSqlSyntaxProviderBase.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/MicrosoftSqlSyntaxProviderBase.cs @@ -34,7 +34,7 @@ namespace Umbraco.Core.Persistence.SqlSyntax if (tableName.Contains(".") == false) return $"[{tableName}]"; - var tableNameParts = tableName.Split(new[] { '.' }, 2); + var tableNameParts = tableName.Split(Constants.CharArrays.Period, 2); return $"[{tableNameParts[0]}].[{tableNameParts[1]}]"; } diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/SqlCeSyntaxProvider.cs b/src/Umbraco.Core/Persistence/SqlSyntax/SqlCeSyntaxProvider.cs index 2ed0fb878c..9c5edf595b 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/SqlCeSyntaxProvider.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/SqlCeSyntaxProvider.cs @@ -1,9 +1,11 @@ using System; using System.Collections.Generic; +using System.Configuration; using System.Data; using System.Data.SqlServerCe; using System.Linq; using NPoco; +using Umbraco.Core.Composing; using Umbraco.Core.Persistence.DatabaseAnnotations; using Umbraco.Core.Persistence.DatabaseModelDefinitions; @@ -14,6 +16,14 @@ namespace Umbraco.Core.Persistence.SqlSyntax /// public class SqlCeSyntaxProvider : MicrosoftSqlSyntaxProviderBase { + public SqlCeSyntaxProvider() + { + BlobColumnDefinition = "IMAGE"; + // This is silly to have to do this but the way these inherited classes are structured it's the easiest + // way without an overhaul in type map initialization + DbTypeMap.Set(DbType.Binary, BlobColumnDefinition); + } + public override Sql SelectTop(Sql sql, int top) { return new Sql(sql.SqlContext, sql.SQL.Insert(sql.SQL.IndexOf(' '), " TOP " + top), sql.Arguments); @@ -83,7 +93,7 @@ namespace Umbraco.Core.Persistence.SqlSyntax string columns = string.IsNullOrEmpty(columnDefinition.PrimaryKeyColumns) ? GetQuotedColumnName(columnDefinition.Name) : string.Join(", ", columnDefinition.PrimaryKeyColumns - .Split(new[]{',', ' '}, StringSplitOptions.RemoveEmptyEntries) + .Split(Constants.CharArrays.CommaSpace, StringSplitOptions.RemoveEmptyEntries) .Select(GetQuotedColumnName)); return string.Format(CreateConstraint, @@ -163,7 +173,8 @@ where table_name=@0 and column_name=@1", tableName, columnName).FirstOrDefault() if (db.Transaction.IsolationLevel < IsolationLevel.RepeatableRead) throw new InvalidOperationException("A transaction with minimum RepeatableRead isolation level is required."); - db.Execute(@"SET LOCK_TIMEOUT 1800;"); + var timeOut = Current.Configs.Global().SqlWriteLockTimeOut; + db.Execute(@"SET LOCK_TIMEOUT " + timeOut + ";"); // *not* using a unique 'WHERE IN' query here because the *order* of lockIds is important to avoid deadlocks foreach (var lockId in lockIds) { @@ -227,9 +238,36 @@ where table_name=@0 and column_name=@1", tableName, columnName).FirstOrDefault() } } - - public override string DropIndex { get { return "DROP INDEX {1}.{0}"; } } + public override string CreateIndex => "CREATE {0}{1}INDEX {2} ON {3} ({4})"; + public override string Format(IndexDefinition index) + { + var name = string.IsNullOrEmpty(index.Name) + ? $"IX_{index.TableName}_{index.ColumnName}" + : index.Name; + var columns = index.Columns.Any() + ? string.Join(",", index.Columns.Select(x => GetQuotedColumnName(x.Name))) + : GetQuotedColumnName(index.ColumnName); + + + return string.Format(CreateIndex, GetIndexType(index.IndexType), " ", GetQuotedName(name), + GetQuotedTableName(index.TableName), columns); + } + + public override string GetSpecialDbType(SpecialDbTypes dbTypes) + { + if (dbTypes == SpecialDbTypes.NVARCHARMAX) // SqlCE does not have nvarchar(max) for now + return "NTEXT"; + return base.GetSpecialDbType(dbTypes); + } + public override SqlDbType GetSqlDbType(DbType dbType) + { + if (DbType.Binary == dbType) + { + return SqlDbType.Image; + } + return base.GetSqlDbType(dbType); + } } } diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs b/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs index bb50fa98a1..313b2352a9 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs @@ -1,10 +1,12 @@ using System; using System.Collections.Generic; +using System.Configuration; using System.Data; using System.Data.Common; using System.Data.SqlClient; using System.Linq; using NPoco; +using Umbraco.Core.Composing; using Umbraco.Core.Logging; using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Scoping; @@ -30,6 +32,7 @@ namespace Umbraco.Core.Persistence.SqlSyntax V2014 = 6, V2016 = 7, V2017 = 8, + V2019 = 9, Other = 99 } @@ -38,7 +41,7 @@ namespace Umbraco.Core.Persistence.SqlSyntax Unknown = 0, Desktop = 1, Standard = 2, - Enterprise = 3, + Enterprise = 3,// Also developer edition Express = 4, Azure = 5 } @@ -74,11 +77,13 @@ namespace Umbraco.Core.Persistence.SqlSyntax private static VersionName MapProductVersion(string productVersion) { - var firstPart = string.IsNullOrWhiteSpace(productVersion) ? "??" : productVersion.Split('.')[0]; + var firstPart = string.IsNullOrWhiteSpace(productVersion) ? "??" : productVersion.Split(Constants.CharArrays.Period)[0]; switch (firstPart) { case "??": return VersionName.Invalid; + case "15": + return VersionName.V2019; case "14": return VersionName.V2017; case "13": @@ -251,7 +256,8 @@ where tbl.[name]=@0 and col.[name]=@1;", tableName, columnName) public override void WriteLock(IDatabase db, params int[] lockIds) { - WriteLock(db, TimeSpan.FromMilliseconds(1800), lockIds); + var timeOut = Current.Configs.Global().SqlWriteLockTimeOut; + WriteLock(db, TimeSpan.FromMilliseconds(timeOut), lockIds); } public void WriteLock(IDatabase db, TimeSpan timeout, params int[] lockIds) @@ -336,5 +342,24 @@ where tbl.[name]=@0 and col.[name]=@1;", tableName, columnName) public override string DropIndex => "DROP INDEX {0} ON {1}"; public override string RenameColumn => "sp_rename '{0}.{1}', '{2}', 'COLUMN'"; + + public override string CreateIndex => "CREATE {0}{1}INDEX {2} ON {3} ({4}){5}"; + public override string Format(IndexDefinition index) + { + var name = string.IsNullOrEmpty(index.Name) + ? $"IX_{index.TableName}_{index.ColumnName}" + : index.Name; + + var columns = index.Columns.Any() + ? string.Join(",", index.Columns.Select(x => GetQuotedColumnName(x.Name))) + : GetQuotedColumnName(index.ColumnName); + + var includeColumns = index.IncludeColumns?.Any() ?? false + ? $" INCLUDE ({string.Join(",", index.IncludeColumns.Select(x => GetQuotedColumnName(x.Name)))})" + : string.Empty; + + return string.Format(CreateIndex, GetIndexType(index.IndexType), " ", GetQuotedName(name), + GetQuotedTableName(index.TableName), columns, includeColumns); + } } } diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs b/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs index b2e03df96e..8570c49f69 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs @@ -196,7 +196,13 @@ namespace Umbraco.Core.Persistence.SqlSyntax return "NCHAR"; } else if (dbTypes == SpecialDbTypes.NTEXT) + { return "NTEXT"; + } + else if (dbTypes == SpecialDbTypes.NVARCHARMAX) + { + return "NVARCHAR(MAX)"; + } return "NVARCHAR"; } @@ -397,7 +403,7 @@ namespace Umbraco.Core.Persistence.SqlSyntax var columns = string.IsNullOrEmpty(columnDefinition.PrimaryKeyColumns) ? GetQuotedColumnName(columnDefinition.Name) : string.Join(", ", columnDefinition.PrimaryKeyColumns - .Split(new[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries) + .Split(Constants.CharArrays.CommaSpace, StringSplitOptions.RemoveEmptyEntries) .Select(GetQuotedColumnName)); var primaryKeyPart = string.Concat("PRIMARY KEY", columnDefinition.IsIndexed ? " CLUSTERED" : " NONCLUSTERED"); diff --git a/src/Umbraco.Core/Persistence/UmbracoDatabase.cs b/src/Umbraco.Core/Persistence/UmbracoDatabase.cs index a95d95ea08..ec09db690f 100644 --- a/src/Umbraco.Core/Persistence/UmbracoDatabase.cs +++ b/src/Umbraco.Core/Persistence/UmbracoDatabase.cs @@ -10,6 +10,7 @@ using Umbraco.Core.Persistence.FaultHandling; namespace Umbraco.Core.Persistence { + /// /// Extends NPoco Database for Umbraco. /// @@ -38,14 +39,10 @@ namespace Umbraco.Core.Persistence : base(connectionString, sqlContext.DatabaseType, provider, sqlContext.SqlSyntax.DefaultIsolationLevel) { SqlContext = sqlContext; - _logger = logger; _connectionRetryPolicy = connectionRetryPolicy; _commandRetryPolicy = commandRetryPolicy; - - EnableSqlTrace = EnableSqlTraceDefault; - - NPocoDatabaseExtensions.ConfigureNPocoBulkExtensions(); + Init(); } /// @@ -57,10 +54,17 @@ namespace Umbraco.Core.Persistence { SqlContext = sqlContext; _logger = logger; + Init(); + } + private void Init() + { EnableSqlTrace = EnableSqlTraceDefault; - NPocoDatabaseExtensions.ConfigureNPocoBulkExtensions(); + if (SqlContext.DatabaseType == DatabaseType.SQLCe) + { + Mappers.Add(new SqlCeImageMapper()); + } } #endregion @@ -257,5 +261,6 @@ namespace Umbraco.Core.Persistence } #endregion + } } diff --git a/src/Umbraco.Core/Persistence/UmbracoDatabaseExtensions.cs b/src/Umbraco.Core/Persistence/UmbracoDatabaseExtensions.cs index 249dd3dc73..1fcaca6fa1 100644 --- a/src/Umbraco.Core/Persistence/UmbracoDatabaseExtensions.cs +++ b/src/Umbraco.Core/Persistence/UmbracoDatabaseExtensions.cs @@ -1,4 +1,7 @@ using System; +using System.Linq; +using Umbraco.Core.Logging; +using Umbraco.Core.Migrations.Install; namespace Umbraco.Core.Persistence { @@ -10,5 +13,63 @@ namespace Umbraco.Core.Persistence if (asDatabase == null) throw new Exception("oops: database."); return asDatabase; } + + /// + /// Returns true if the database contains the specified table + /// + /// + /// + /// + public static bool HasTable(this IUmbracoDatabase database, string tableName) + { + try + { + return database.SqlContext.SqlSyntax.GetTablesInSchema(database).Any(table => table.InvariantEquals(tableName)); + } + catch (Exception) + { + return false; // will occur if the database cannot connect + } + } + + /// + /// Returns true if the database contains no tables + /// + /// + /// + public static bool IsDatabaseEmpty(this IUmbracoDatabase database) + => database.SqlContext.SqlSyntax.GetTablesInSchema(database).Any() == false; + + /// + /// Returns the for the database + /// + /// + /// + /// + public static DatabaseSchemaResult ValidateSchema(this IUmbracoDatabase database, ILogger logger) + { + if (database is null) throw new ArgumentNullException(nameof(database)); + if (logger is null) throw new ArgumentNullException(nameof(logger)); + + var dbSchema = new DatabaseSchemaCreator(database, logger); + var databaseSchemaValidationResult = dbSchema.ValidateSchema(); + return databaseSchemaValidationResult; + } + + /// + /// Returns true if Umbraco database tables are detected to be installed + /// + /// + /// + /// + public static bool IsUmbracoInstalled(this IUmbracoDatabase database, ILogger logger) + { + if (database is null) throw new ArgumentNullException(nameof(database)); + if (logger is null) throw new ArgumentNullException(nameof(logger)); + + var databaseSchemaValidationResult = database.ValidateSchema(logger); + return databaseSchemaValidationResult.DetermineHasInstalledVersion(); + } + } } diff --git a/src/Umbraco.Core/Persistence/UmbracoDatabaseFactory.cs b/src/Umbraco.Core/Persistence/UmbracoDatabaseFactory.cs index c502abc87c..baab8c486e 100644 --- a/src/Umbraco.Core/Persistence/UmbracoDatabaseFactory.cs +++ b/src/Umbraco.Core/Persistence/UmbracoDatabaseFactory.cs @@ -156,6 +156,7 @@ namespace Umbraco.Core.Persistence case SqlServerSyntaxProvider.VersionName.V2014: case SqlServerSyntaxProvider.VersionName.V2016: case SqlServerSyntaxProvider.VersionName.V2017: + case SqlServerSyntaxProvider.VersionName.V2019: _databaseType = DatabaseType.SqlServer2012; break; // else leave unchanged diff --git a/src/Umbraco.Core/PropertyEditors/ComplexPropertyEditorContentEventHandler.cs b/src/Umbraco.Core/PropertyEditors/ComplexPropertyEditorContentEventHandler.cs new file mode 100644 index 0000000000..2b819d4555 --- /dev/null +++ b/src/Umbraco.Core/PropertyEditors/ComplexPropertyEditorContentEventHandler.cs @@ -0,0 +1,98 @@ +using System; +using System.Collections.Generic; +using Umbraco.Core.Events; +using Umbraco.Core.Models; +using Umbraco.Core.Services; +using Umbraco.Core.Services.Implement; + +namespace Umbraco.Core.PropertyEditors +{ + /// + /// Utility class for dealing with Copying/Saving events for complex editors + /// + internal class ComplexPropertyEditorContentEventHandler : IDisposable + { + private readonly string _editorAlias; + private readonly Func _formatPropertyValue; + private bool _disposedValue; + + public ComplexPropertyEditorContentEventHandler(string editorAlias, + Func formatPropertyValue) + { + _editorAlias = editorAlias; + _formatPropertyValue = formatPropertyValue; + ContentService.Copying += ContentService_Copying; + ContentService.Saving += ContentService_Saving; + } + + /// + /// Copying event handler + /// + /// + /// + private void ContentService_Copying(IContentService sender, CopyEventArgs e) + { + var props = e.Copy.GetPropertiesByEditor(_editorAlias); + UpdatePropertyValues(props, false); + } + + /// + /// Saving event handler + /// + /// + /// + private void ContentService_Saving(IContentService sender, ContentSavingEventArgs e) + { + foreach (var entity in e.SavedEntities) + { + var props = entity.GetPropertiesByEditor(_editorAlias); + UpdatePropertyValues(props, true); + } + } + + private void UpdatePropertyValues(IEnumerable props, bool onlyMissingKeys) + { + foreach (var prop in props) + { + // A Property may have one or more values due to cultures + var propVals = prop.Values; + foreach (var cultureVal in propVals) + { + // Remove keys from published value & any nested properties + var updatedPublishedVal = _formatPropertyValue(cultureVal.PublishedValue?.ToString(), onlyMissingKeys); + cultureVal.PublishedValue = updatedPublishedVal; + + // Remove keys from edited/draft value & any nested properties + var updatedEditedVal = _formatPropertyValue(cultureVal.EditedValue?.ToString(), onlyMissingKeys); + cultureVal.EditedValue = updatedEditedVal; + } + } + } + + /// + /// Unbinds from events + /// + /// + protected virtual void Dispose(bool disposing) + { + if (!_disposedValue) + { + if (disposing) + { + ContentService.Copying -= ContentService_Copying; + ContentService.Saving -= ContentService_Saving; + } + _disposedValue = true; + } + } + + /// + /// Unbinds from events + /// + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + } +} diff --git a/src/Umbraco.Core/PropertyEditors/DataEditorAttribute.cs b/src/Umbraco.Core/PropertyEditors/DataEditorAttribute.cs index 7b3be7ea5f..0375509797 100644 --- a/src/Umbraco.Core/PropertyEditors/DataEditorAttribute.cs +++ b/src/Umbraco.Core/PropertyEditors/DataEditorAttribute.cs @@ -2,6 +2,7 @@ namespace Umbraco.Core.PropertyEditors { + /// /// Marks a class that represents a data editor. /// diff --git a/src/Umbraco.Core/PropertyEditors/DataValueEditor.cs b/src/Umbraco.Core/PropertyEditors/DataValueEditor.cs index c4380f032c..eebe5f5722 100644 --- a/src/Umbraco.Core/PropertyEditors/DataValueEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/DataValueEditor.cs @@ -204,7 +204,7 @@ namespace Umbraco.Core.PropertyEditors /// /// /// By default this will attempt to automatically convert the string value to the value type supplied by ValueType. - /// + /// /// If overridden then the object returned must match the type supplied in the ValueType, otherwise persisting the /// value to the DB will fail when it tries to validate the value type. /// diff --git a/src/Umbraco.Core/PropertyEditors/IPropertyCacheCompression.cs b/src/Umbraco.Core/PropertyEditors/IPropertyCacheCompression.cs new file mode 100644 index 0000000000..96a559630b --- /dev/null +++ b/src/Umbraco.Core/PropertyEditors/IPropertyCacheCompression.cs @@ -0,0 +1,15 @@ +using Umbraco.Core.Models; + +namespace Umbraco.Core.PropertyEditors +{ + /// + /// Determines if a property type's value should be compressed in memory + /// + /// + /// + /// + public interface IPropertyCacheCompression + { + bool IsCompressed(IReadOnlyContentBase content, string propertyTypeAlias); + } +} diff --git a/src/Umbraco.Core/PropertyEditors/IPropertyCacheCompressionOptions.cs b/src/Umbraco.Core/PropertyEditors/IPropertyCacheCompressionOptions.cs new file mode 100644 index 0000000000..2fa0153f9e --- /dev/null +++ b/src/Umbraco.Core/PropertyEditors/IPropertyCacheCompressionOptions.cs @@ -0,0 +1,9 @@ +using Umbraco.Core.Models; + +namespace Umbraco.Core.PropertyEditors +{ + public interface IPropertyCacheCompressionOptions + { + bool IsCompressed(IReadOnlyContentBase content, PropertyType propertyType, IDataEditor dataEditor); + } +} diff --git a/src/Umbraco.Core/PropertyEditors/NoopPropertyCacheCompressionOptions.cs b/src/Umbraco.Core/PropertyEditors/NoopPropertyCacheCompressionOptions.cs new file mode 100644 index 0000000000..1f12d45769 --- /dev/null +++ b/src/Umbraco.Core/PropertyEditors/NoopPropertyCacheCompressionOptions.cs @@ -0,0 +1,12 @@ +using Umbraco.Core.Models; + +namespace Umbraco.Core.PropertyEditors +{ + /// + /// Default implementation for which does not compress any property data + /// + internal class NoopPropertyCacheCompressionOptions : IPropertyCacheCompressionOptions + { + public bool IsCompressed(IReadOnlyContentBase content, PropertyType propertyType, IDataEditor dataEditor) => false; + } +} diff --git a/src/Umbraco.Core/PropertyEditors/PropertyCacheCompression.cs b/src/Umbraco.Core/PropertyEditors/PropertyCacheCompression.cs new file mode 100644 index 0000000000..6be21fca7f --- /dev/null +++ b/src/Umbraco.Core/PropertyEditors/PropertyCacheCompression.cs @@ -0,0 +1,49 @@ +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core.Models; + +namespace Umbraco.Core.PropertyEditors +{ + + /// + /// Compresses property data based on config + /// + internal class PropertyCacheCompression : IPropertyCacheCompression + { + private readonly IPropertyCacheCompressionOptions _compressionOptions; + private readonly IReadOnlyDictionary _contentTypes; + private readonly PropertyEditorCollection _propertyEditors; + private readonly ConcurrentDictionary<(int contentTypeId, string propertyAlias), bool> _isCompressedCache; + + public PropertyCacheCompression( + IPropertyCacheCompressionOptions compressionOptions, + IReadOnlyDictionary contentTypes, + PropertyEditorCollection propertyEditors, + ConcurrentDictionary<(int, string), bool> compressedStoragePropertyEditorCache) + { + _compressionOptions = compressionOptions; + _contentTypes = contentTypes ?? throw new System.ArgumentNullException(nameof(contentTypes)); + _propertyEditors = propertyEditors ?? throw new System.ArgumentNullException(nameof(propertyEditors)); + _isCompressedCache = compressedStoragePropertyEditorCache; + } + + public bool IsCompressed(IReadOnlyContentBase content, string alias) + { + var compressedStorage = _isCompressedCache.GetOrAdd((content.ContentTypeId, alias), x => + { + if (!_contentTypes.TryGetValue(x.contentTypeId, out var ct)) + return false; + + var propertyType = ct.CompositionPropertyTypes.FirstOrDefault(x => x.Alias == alias); + if (propertyType == null) return false; + + if (!_propertyEditors.TryGet(propertyType.PropertyEditorAlias, out var propertyEditor)) return false; + + return _compressionOptions.IsCompressed(content, propertyType, propertyEditor); + }); + + return compressedStorage; + } + } +} diff --git a/src/Umbraco.Core/PropertyEditors/PropertyValueConverterBase.cs b/src/Umbraco.Core/PropertyEditors/PropertyValueConverterBase.cs index 3b6ebc610c..2ec0438328 100644 --- a/src/Umbraco.Core/PropertyEditors/PropertyValueConverterBase.cs +++ b/src/Umbraco.Core/PropertyEditors/PropertyValueConverterBase.cs @@ -4,44 +4,57 @@ using Umbraco.Core.Models.PublishedContent; namespace Umbraco.Core.PropertyEditors { /// - /// Provides a default overridable implementation for that does nothing. + /// Provides a default implementation for . /// + /// public abstract class PropertyValueConverterBase : IPropertyValueConverter { + /// public virtual bool IsConverter(IPublishedPropertyType propertyType) => false; + /// public virtual bool? IsValue(object value, PropertyValueLevel level) { switch (level) { case PropertyValueLevel.Source: - return value != null && (!(value is string) || string.IsNullOrWhiteSpace((string) value) == false); + // the default implementation uses the old magic null & string comparisons, + // other implementations may be more clever, and/or test the final converted object values + return value != null && (!(value is string stringValue) || !string.IsNullOrWhiteSpace(stringValue)); + case PropertyValueLevel.Inter: + return null; + case PropertyValueLevel.Object: + return null; default: throw new NotSupportedException($"Invalid level: {level}."); } } + [Obsolete("This method is not part of the IPropertyValueConverter contract, therefore not used and will be removed in future versions; use IsValue instead.")] public virtual bool HasValue(IPublishedProperty property, string culture, string segment) { - // the default implementation uses the old magic null & string comparisons, - // other implementations may be more clever, and/or test the final converted object values var value = property.GetSourceValue(culture, segment); - return value != null && (!(value is string) || string.IsNullOrWhiteSpace((string) value) == false); + return value != null && (!(value is string stringValue) || !string.IsNullOrWhiteSpace(stringValue)); } + /// public virtual Type GetPropertyValueType(IPublishedPropertyType propertyType) - => typeof (object); + => typeof(object); + /// public virtual PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) => PropertyCacheLevel.Snapshot; + /// public virtual object ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object source, bool preview) => source; + /// public virtual object ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) => inter; + /// public virtual object ConvertIntermediateToXPath(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) => inter?.ToString() ?? string.Empty; } diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/ImageCropperValue.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/ImageCropperValue.cs index ab217d3870..2c6ec9b8aa 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/ImageCropperValue.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/ImageCropperValue.cs @@ -80,13 +80,13 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters } /// - /// Gets the value image url for a specified crop. + /// Gets the value image URL for a specified crop. /// [Obsolete("Use the overload that takes an IImageUrlGenerator")] public string GetCropUrl(string alias, bool useCropDimensions = true, bool useFocalPoint = false, string cacheBusterValue = null) => GetCropUrl(alias, Current.ImageUrlGenerator, useCropDimensions, useFocalPoint, cacheBusterValue); /// - /// Gets the value image url for a specified crop. + /// Gets the value image URL for a specified crop. /// public string GetCropUrl(string alias, IImageUrlGenerator imageUrlGenerator, bool useCropDimensions = true, bool useFocalPoint = false, string cacheBusterValue = null) { @@ -110,13 +110,13 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters } /// - /// Gets the value image url for a specific width and height. + /// Gets the value image URL for a specific width and height. /// [Obsolete("Use the overload that takes an IImageUrlGenerator")] public string GetCropUrl(int width, int height, bool useFocalPoint = false, string cacheBusterValue = null) => GetCropUrl(width, height, Current.ImageUrlGenerator, useFocalPoint, cacheBusterValue); /// - /// Gets the value image url for a specific width and height. + /// Gets the value image URL for a specific width and height. /// public string GetCropUrl(int width, int height, IImageUrlGenerator imageUrlGenerator, bool useFocalPoint = false, string cacheBusterValue = null) { @@ -157,12 +157,11 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters // merge the crop values - the alias + width + height comes from // configuration, but each crop can store its own coordinates - if (Crops == null) return; - var configuredCrops = configuration?.Crops; if (configuredCrops == null) return; - var crops = Crops.ToList(); + //Use Crops if it's not null, otherwise create a new list + var crops = Crops?.ToList() ?? new List(); foreach (var configuredCrop in configuredCrops) { diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/ImageCropperValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/ImageCropperValueConverter.cs index 36ffddb863..8926174c03 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/ImageCropperValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/ImageCropperValueConverter.cs @@ -42,7 +42,7 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters } catch (Exception ex) { - // cannot deserialize, assume it may be a raw image url + // cannot deserialize, assume it may be a raw image URL Current.Logger.Error(ex, "Could not deserialize string '{JsonString}' into an image cropper value.", sourceString); value = new ImageCropperValue { Src = sourceString }; } diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/SliderValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/SliderValueConverter.cs index 11502687b7..dc2a039bfe 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/SliderValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/SliderValueConverter.cs @@ -34,7 +34,7 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters if (IsRangeDataType(propertyType.DataType.Id)) { - var rangeRawValues = source.ToString().Split(','); + var rangeRawValues = source.ToString().Split(Constants.CharArrays.Comma); var minimumAttempt = rangeRawValues[0].TryConvertTo(); var maximumAttempt = rangeRawValues[1].TryConvertTo(); diff --git a/src/Umbraco.Core/PublishedContentExtensions.cs b/src/Umbraco.Core/PublishedContentExtensions.cs index 921883b822..4aa3b77a7b 100644 --- a/src/Umbraco.Core/PublishedContentExtensions.cs +++ b/src/Umbraco.Core/PublishedContentExtensions.cs @@ -61,10 +61,10 @@ namespace Umbraco.Core /// - /// Gets the url segment of the content item. + /// Gets the URL segment of the content item. /// /// The content item. - /// The specific culture to get the url segment for. If null is used the current culture is used (Default is null). + /// The specific culture to get the URL segment for. If null is used the current culture is used (Default is null). public static string UrlSegment(this IPublishedContent content, string culture = null) { // invariant has invariant value (whatever the requested culture) @@ -104,7 +104,7 @@ namespace Umbraco.Core /// /// The content item. /// - /// The specific culture to get the url children for. Default is null which will use the current culture in + /// The specific culture to get the URL children for. Default is null which will use the current culture in /// /// /// Gets children that are available for the specified culture. diff --git a/src/Umbraco.Core/Runtime/CoreRuntime.cs b/src/Umbraco.Core/Runtime/CoreRuntime.cs index b852aff2ff..26221e5e19 100644 --- a/src/Umbraco.Core/Runtime/CoreRuntime.cs +++ b/src/Umbraco.Core/Runtime/CoreRuntime.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Linq; +using System.Threading; using System.Web; using System.Web.Hosting; using Umbraco.Core.Cache; @@ -10,6 +12,8 @@ using Umbraco.Core.Exceptions; using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Logging.Serilog; +using Umbraco.Core.Migrations.Install; +using Umbraco.Core.Migrations.Upgrade; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Mappers; using Umbraco.Core.Sync; @@ -149,7 +153,7 @@ namespace Umbraco.Core.Runtime { MainDom = new MainDom(Logger, new MainDomSemaphoreLock(Logger)); } - + // create the composition composition = new Composition(register, typeLoader, ProfilingLogger, _state, configs); @@ -158,6 +162,9 @@ namespace Umbraco.Core.Runtime // run handlers RuntimeOptions.DoRuntimeEssentials(composition, appCaches, typeLoader, databaseFactory); + // determines if unattended install is enabled and performs it if required + DoUnattendedInstall(databaseFactory); + // register runtime-level services // there should be none, really - this is here "just in case" Compose(composition); @@ -175,7 +182,7 @@ namespace Umbraco.Core.Runtime using (ProfilingLogger.DebugDuration("Scanning enable/disable composer attributes")) { enableDisableAttributes = typeLoader.GetAssemblyAttributes(typeof(EnableComposerAttribute), typeof(DisableComposerAttribute)); - } + } var composers = new Composers(composition, composerTypes, enableDisableAttributes, ProfilingLogger); composers.Compose(); @@ -183,6 +190,16 @@ namespace Umbraco.Core.Runtime // create the factory _factory = Current.Factory = composition.CreateFactory(); + // if level is Run and reason is UpgradeMigrations, that means we need to perform an unattended upgrade + if (_state.Reason == RuntimeLevelReason.UpgradeMigrations && _state.Level == RuntimeLevel.Run) + { + // do the upgrade + DoUnattendedUpgrade(_factory.GetInstance()); + + // upgrade is done, set reason to Run + _state.Reason = RuntimeLevelReason.Run; + } + // create & initialize the components _components = _factory.GetInstance(); _components.Initialize(); @@ -210,7 +227,13 @@ namespace Umbraco.Core.Runtime { _factory = Current.Factory = composition?.CreateFactory(); } - catch { /* yea */ } + catch + { + // In this case we are basically dead, we do not have a factory but we need + // to report on the state so we need to manually set that, this is the only time + // we ever do this. + Current.RuntimeState = _state; + } } Debugger.Break(); @@ -225,6 +248,74 @@ namespace Umbraco.Core.Runtime return _factory; } + private void DoUnattendedInstall(IUmbracoDatabaseFactory databaseFactory) + { + // unattended install is not enabled + if (RuntimeOptions.InstallUnattended == false) return; + + var localVersion = UmbracoVersion.LocalVersion; // the local, files, version + var codeVersion = _state.SemanticVersion; // the executing code version + + // local version and code version is not equal, an unattended install cannot be performed + if (localVersion != codeVersion) return; + + // no connection string set + if (databaseFactory.Configured == false) return; + + var tries = 5; + var connect = false; + for (var i = 0;;) + { + connect = databaseFactory.CanConnect; + if (connect || ++i == tries) break; + Logger.Debug("Could not immediately connect to database, trying again."); + Thread.Sleep(1000); + } + + // could not connect to the database + if (connect == false) return; + + using (var database = databaseFactory.CreateDatabase()) + { + var hasUmbracoTables = database.IsUmbracoInstalled(Logger); + + // database has umbraco tables, assume Umbraco is already installed + if (hasUmbracoTables) return; + + // all conditions fulfilled, do the install + Logger.Info("Starting unattended install."); + + try + { + database.BeginTransaction(); + var creator = new DatabaseSchemaCreator(database, Logger); + creator.InitializeDatabaseSchema(); + database.CompleteTransaction(); + Logger.Info("Unattended install completed."); + } + catch (Exception ex) + { + Logger.Error(ex, "Error during unattended install."); + database.AbortTransaction(); + + throw new UnattendedInstallException( + "The database configuration failed with the following message: " + ex.Message + + "\n Please check log file for additional information (can be found in '/App_Data/Logs/')"); + } + } + } + + private void DoUnattendedUpgrade(DatabaseBuilder databaseBuilder) + { + var plan = new UmbracoPlan(); + using (ProfilingLogger.TraceDuration("Starting unattended upgrade.", "Unattended upgrade completed.")) + { + var result = databaseBuilder.UpgradeSchemaAndData(plan); + if (result.Success == false) + throw new UnattendedInstallException("An error occurred while running the unattended upgrade.\n" + result.Message); + } + } + protected virtual void ConfigureUnhandledException() { //take care of unhandled exceptions - there is nothing we can do to @@ -265,14 +356,14 @@ namespace Umbraco.Core.Runtime } } - // internal for tests - internal void DetermineRuntimeLevel(IUmbracoDatabaseFactory databaseFactory, IProfilingLogger profilingLogger) + // internal/virtual for tests (i.e. hack, do not port to netcore) + internal virtual void DetermineRuntimeLevel(IUmbracoDatabaseFactory databaseFactory, IProfilingLogger profilingLogger) { using (var timer = profilingLogger.DebugDuration("Determining runtime level.", "Determined.")) { try { - _state.DetermineRuntimeLevel(databaseFactory, profilingLogger); + _state.DetermineRuntimeLevel(databaseFactory); profilingLogger.Debug("Runtime level: {RuntimeLevel} - {RuntimeLevelReason}", _state.Level, _state.Reason); diff --git a/src/Umbraco.Core/Runtime/MainDom.cs b/src/Umbraco.Core/Runtime/MainDom.cs index e6780ec876..71842905dd 100644 --- a/src/Umbraco.Core/Runtime/MainDom.cs +++ b/src/Umbraco.Core/Runtime/MainDom.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Security.Cryptography; using System.Threading; +using System.Threading.Tasks; using System.Web.Hosting; using Umbraco.Core; using Umbraco.Core.Logging; @@ -38,6 +39,9 @@ namespace Umbraco.Core.Runtime private const int LockTimeoutMilliseconds = 40000; // 40 seconds + private Task _listenTask; + private Task _listenCompleteTask; + #endregion #region Ctor @@ -144,8 +148,16 @@ namespace Umbraco.Core.Runtime _logger.Info("Acquiring."); - // Get the lock - var acquired = _mainDomLock.AcquireLockAsync(LockTimeoutMilliseconds).GetAwaiter().GetResult(); + // Get the lock + var acquired = false; + try + { + acquired = _mainDomLock.AcquireLockAsync(LockTimeoutMilliseconds).GetAwaiter().GetResult(); + } + catch (Exception ex) + { + _logger.Error(ex, "Error while acquiring"); + } if (!acquired) { @@ -164,7 +176,13 @@ namespace Umbraco.Core.Runtime try { // Listen for the signal from another AppDomain coming online to release the lock - _mainDomLock.ListenAsync().ContinueWith(_ => OnSignal("signal")); + _listenTask = _mainDomLock.ListenAsync(); + _listenCompleteTask = _listenTask.ContinueWith(t => + { + _logger.Debug("Listening task completed with {TaskStatus}", _listenTask.Status); + + OnSignal("signal"); + }, TaskScheduler.Default); // Must explicitly specify this, see https://blog.stephencleary.com/2013/10/continuewith-is-dangerous-too.html } catch (OperationCanceledException ex) { diff --git a/src/Umbraco.Core/Runtime/SqlMainDomLock.cs b/src/Umbraco.Core/Runtime/SqlMainDomLock.cs index 5f5d0d607f..f58b279a8d 100644 --- a/src/Umbraco.Core/Runtime/SqlMainDomLock.cs +++ b/src/Umbraco.Core/Runtime/SqlMainDomLock.cs @@ -1,4 +1,5 @@ -using System; +using NPoco; +using System; using System.Data; using System.Data.SqlClient; using System.Diagnostics; @@ -6,12 +7,12 @@ using System.Linq; using System.Security.Cryptography; using System.Threading; using System.Threading.Tasks; -using System.Web; using Umbraco.Core.Logging; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.Persistence.Mappers; using Umbraco.Core.Persistence.SqlSyntax; +using MapperCollection = Umbraco.Core.Persistence.Mappers.MapperCollection; namespace Umbraco.Core.Runtime { @@ -21,60 +22,71 @@ namespace Umbraco.Core.Runtime private const string MainDomKeyPrefix = "Umbraco.Core.Runtime.SqlMainDom"; private const string UpdatedSuffix = "_updated"; private readonly ILogger _logger; - private IUmbracoDatabase _db; private CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); private SqlServerSyntaxProvider _sqlServerSyntax = new SqlServerSyntaxProvider(); private bool _mainDomChanging = false; private readonly UmbracoDatabaseFactory _dbFactory; - private bool _hasError; + private bool _errorDuringAcquiring; private object _locker = new object(); + private bool _hasTable = false; public SqlMainDomLock(ILogger logger) { // unique id for our appdomain, this is more unique than the appdomain id which is just an INT counter to its safer _lockId = Guid.NewGuid().ToString(); _logger = logger; - + _dbFactory = new UmbracoDatabaseFactory( Constants.System.UmbracoConnectionName, _logger, - new Lazy(() => new Persistence.Mappers.MapperCollection(Enumerable.Empty()))); + new Lazy(() => new MapperCollection(Enumerable.Empty()))); } public async Task AcquireLockAsync(int millisecondsTimeout) { if (!_dbFactory.Configured) { - // if we aren't configured, then we're in an install state, in which case we have no choice but to assume we can acquire + // if we aren't configured then we're in an install state, in which case we have no choice but to assume we can acquire return true; } if (!(_dbFactory.SqlContext.SqlSyntax is SqlServerSyntaxProvider sqlServerSyntaxProvider)) + { throw new NotSupportedException("SqlMainDomLock is only supported for Sql Server"); + } _sqlServerSyntax = sqlServerSyntaxProvider; _logger.Debug("Acquiring lock..."); - var db = GetDatabase(); - var tempId = Guid.NewGuid().ToString(); + IUmbracoDatabase db = null; + try { + db = _dbFactory.CreateDatabase(); + + _hasTable = db.HasTable(Constants.DatabaseSchema.Tables.KeyValue); + if (!_hasTable) + { + // the Db does not contain the required table, we must be in an install state we have no choice but to assume we can acquire + return true; + } + db.BeginTransaction(IsolationLevel.ReadCommitted); try { // wait to get a write lock - _sqlServerSyntax.WriteLock(db, TimeSpan.FromMilliseconds(millisecondsTimeout), Constants.Locks.MainDom); + _sqlServerSyntax.WriteLock(db, TimeSpan.FromMilliseconds(millisecondsTimeout), Constants.Locks.MainDom); } - catch (Exception ex) + catch(SqlException ex) { if (IsLockTimeoutException(ex)) { _logger.Error(ex, "Sql timeout occurred, could not acquire MainDom."); - _hasError = true; + _errorDuringAcquiring = true; return false; } @@ -82,15 +94,12 @@ namespace Umbraco.Core.Runtime throw; } - var result = InsertLockRecord(tempId); //we change the row to a random Id to signal other MainDom to shutdown + var result = InsertLockRecord(tempId, db); //we change the row to a random Id to signal other MainDom to shutdown if (result == RecordPersistenceType.Insert) { // if we've inserted, then there was no MainDom so we can instantly acquire - // TODO: see the other TODO, could we just delete the row and that would indicate that we - // are MainDom? then we don't leave any orphan rows behind. - - InsertLockRecord(_lockId); // so update with our appdomain id + InsertLockRecord(_lockId, db); // so update with our appdomain id _logger.Debug("Acquired with ID {LockId}", _lockId); return true; } @@ -100,23 +109,24 @@ namespace Umbraco.Core.Runtime } catch (Exception ex) { - ResetDatabase(); // unexpected _logger.Error(ex, "Unexpected error, cannot acquire MainDom"); - _hasError = true; + _errorDuringAcquiring = true; return false; } finally { db?.CompleteTransaction(); + db?.Dispose(); } + return await WaitForExistingAsync(tempId, millisecondsTimeout); } public Task ListenAsync() { - if (_hasError) + if (_errorDuringAcquiring) { _logger.Warn("Could not acquire MainDom, listening is canceled."); return Task.CompletedTask; @@ -124,7 +134,12 @@ namespace Umbraco.Core.Runtime // Create a long running task (dedicated thread) // to poll to check if we are still the MainDom registered in the DB - return Task.Factory.StartNew(ListeningLoop, _cancellationTokenSource.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default); + return Task.Factory.StartNew( + ListeningLoop, + _cancellationTokenSource.Token, + TaskCreationOptions.LongRunning, + // Must explicitly specify this, see https://blog.stephencleary.com/2013/10/continuewith-is-dangerous-too.html + TaskScheduler.Default); } @@ -142,8 +157,9 @@ namespace Umbraco.Core.Runtime { while (true) { - // poll every 1 second - Thread.Sleep(1000); + // poll every couple of seconds + // local testing shows the actual query to be executed from client/server is approx 300ms but would change depending on environment/IO + Thread.Sleep(2000); if (!_dbFactory.Configured) { @@ -158,22 +174,33 @@ namespace Umbraco.Core.Runtime // the other MainDom is taking to startup. In this case the db row will just be deleted and the // new MainDom will just take over. if (_cancellationTokenSource.IsCancellationRequested) + { + _logger.Debug("Task canceled, exiting loop"); return; - - var db = GetDatabase(); + } + IUmbracoDatabase db = null; try { - db.BeginTransaction(IsolationLevel.ReadCommitted); + db = _dbFactory.CreateDatabase(); + if (!_hasTable) + { + // re-check if its still false, we don't want to re-query once we know its there since this + // loop needs to use minimal resources + _hasTable = db.HasTable(Constants.DatabaseSchema.Tables.KeyValue); + if (!_hasTable) + { + // the Db does not contain the required table, we just keep looping since we can't query the db + continue; + } + } + + db.BeginTransaction(IsolationLevel.ReadCommitted); // get a read lock _sqlServerSyntax.ReadLock(db, Constants.Locks.MainDom); - // TODO: We could in theory just check if the main dom row doesn't exist, that could indicate that - // we are still the maindom. An empty value might be better because then we won't have any orphan rows - // if the app is terminated. Could that work? - - if (!IsMainDomValue(_lockId)) + if (!IsMainDomValue(_lockId, db)) { // we are no longer main dom, another one has come online, exit _mainDomChanging = true; @@ -183,38 +210,26 @@ namespace Umbraco.Core.Runtime } catch (Exception ex) { - ResetDatabase(); - // unexpected - _logger.Error(ex, "Unexpected error, listening is canceled."); - _hasError = true; - return; + _logger.Error(ex, "Unexpected error during listening."); + + // We need to keep on listening unless we've been notified by our own AppDomain to shutdown since + // we don't want to shutdown resources controlled by MainDom inadvertently. We'll just keep listening otherwise. + if (_cancellationTokenSource.IsCancellationRequested) + { + _logger.Debug("Task canceled, exiting loop"); + return; + } } finally { db?.CompleteTransaction(); + db?.Dispose(); } } } } - private void ResetDatabase() - { - if (_db.InTransaction) - _db.AbortTransaction(); - _db.Dispose(); - _db = null; - } - - private IUmbracoDatabase GetDatabase() - { - if (_db != null) - return _db; - - _db = _dbFactory.CreateDatabase(); - return _db; - } - /// /// Wait for any existing MainDom to release so we can continue booting /// @@ -227,121 +242,151 @@ namespace Umbraco.Core.Runtime return Task.Run(() => { - var db = GetDatabase(); - var watch = new Stopwatch(); - watch.Start(); - while(true) + try { - // poll very often, we need to take over as fast as we can - Thread.Sleep(100); + using var db = _dbFactory.CreateDatabase(); - try + var watch = new Stopwatch(); + watch.Start(); + while (true) { - db.BeginTransaction(IsolationLevel.ReadCommitted); + // poll very often, we need to take over as fast as we can + // local testing shows the actual query to be executed from client/server is approx 300ms but would change depending on environment/IO + Thread.Sleep(1000); - // get a read lock - _sqlServerSyntax.ReadLock(db, Constants.Locks.MainDom); + var acquired = TryAcquire(db, tempId, updatedTempId); + if (acquired.HasValue) + return acquired.Value; - // the row - var mainDomRows = db.Fetch("SELECT * FROM umbracoKeyValue WHERE [key] = @key", new { key = MainDomKey }); - - if (mainDomRows.Count == 0 || mainDomRows[0].Value == updatedTempId) + if (watch.ElapsedMilliseconds >= millisecondsTimeout) { - // the other main dom has updated our record - // Or the other maindom shutdown super fast and just deleted the record - // which indicates that we - // can acquire it and it has shutdown. - - _sqlServerSyntax.WriteLock(db, Constants.Locks.MainDom); - - // so now we update the row with our appdomain id - InsertLockRecord(_lockId); - _logger.Debug("Acquired with ID {LockId}", _lockId); - return true; - } - else if (mainDomRows.Count == 1 && !mainDomRows[0].Value.StartsWith(tempId)) - { - // in this case, the prefixed ID is different which means - // another new AppDomain has come online and is wanting to take over. In that case, we will not - // acquire. - - _logger.Debug("Cannot acquire, another booting application detected."); - - return false; - } - } - catch (Exception ex) - { - ResetDatabase(); - - if (IsLockTimeoutException(ex)) - { - _logger.Error(ex, "Sql timeout occurred, waiting for existing MainDom is canceled."); - _hasError = true; - return false; - } - // unexpected - _logger.Error(ex, "Unexpected error, waiting for existing MainDom is canceled."); - _hasError = true; - return false; - } - finally - { - db?.CompleteTransaction(); - } - - if (watch.ElapsedMilliseconds >= millisecondsTimeout) - { - // if the timeout has elapsed, it either means that the other main dom is taking too long to shutdown, - // or it could mean that the previous appdomain was terminated and didn't clear out the main dom SQL row - // and it's just been left as an orphan row. - // There's really know way of knowing unless we are constantly updating the row for the current maindom - // which isn't ideal. - // So... we're going to 'just' take over, if the writelock works then we'll assume we're ok - - _logger.Debug("Timeout elapsed, assuming orphan row, acquiring MainDom."); - - try - { - db.BeginTransaction(IsolationLevel.ReadCommitted); - - _sqlServerSyntax.WriteLock(db, Constants.Locks.MainDom); - - // so now we update the row with our appdomain id - InsertLockRecord(_lockId); - _logger.Debug("Acquired with ID {LockId}", _lockId); - return true; - } - catch (Exception ex) - { - ResetDatabase(); - - if (IsLockTimeoutException(ex)) - { - // something is wrong, we cannot acquire, not much we can do - _logger.Error(ex, "Sql timeout occurred, could not forcibly acquire MainDom."); - _hasError = true; - return false; - } - _logger.Error(ex, "Unexpected error, could not forcibly acquire MainDom."); - _hasError = true; - return false; - } - finally - { - db?.CompleteTransaction(); + return AcquireWhenMaxWaitTimeElapsed(db); } } } + catch (Exception ex) + { + _logger.Error(ex, "An error occurred trying to acquire and waiting for existing SqlMainDomLock to shutdown"); + return false; + } + }, _cancellationTokenSource.Token); } - /// - /// Inserts or updates the key/value row - /// - private RecordPersistenceType InsertLockRecord(string id) + private bool? TryAcquire(IUmbracoDatabase db, string tempId, string updatedTempId) + { + // Creates a separate transaction to the DB instance so we aren't allocating tons of new DB instances for each transaction + // since this is executed in a tight loop + + ITransaction transaction = null; + + try + { + transaction = db.GetTransaction(IsolationLevel.ReadCommitted); + // get a read lock + _sqlServerSyntax.ReadLock(db, Constants.Locks.MainDom); + + // the row + var mainDomRows = db.Fetch("SELECT * FROM umbracoKeyValue WHERE [key] = @key", new { key = MainDomKey }); + + if (mainDomRows.Count == 0 || mainDomRows[0].Value == updatedTempId) + { + // the other main dom has updated our record + // Or the other maindom shutdown super fast and just deleted the record + // which indicates that we + // can acquire it and it has shutdown. + + _sqlServerSyntax.WriteLock(db, Constants.Locks.MainDom); + + // so now we update the row with our appdomain id + InsertLockRecord(_lockId, db); + _logger.Debug("Acquired with ID {LockId}", _lockId); + return true; + } + else if (mainDomRows.Count == 1 && !mainDomRows[0].Value.StartsWith(tempId)) + { + // in this case, the prefixed ID is different which means + // another new AppDomain has come online and is wanting to take over. In that case, we will not + // acquire. + + _logger.Debug("Cannot acquire, another booting application detected."); + return false; + } + } + catch (Exception ex) + { + if (IsLockTimeoutException(ex as SqlException)) + { + _logger.Error(ex, "Sql timeout occurred, waiting for existing MainDom is canceled."); + _errorDuringAcquiring = true; + return false; + } + // unexpected + _logger.Error(ex, "Unexpected error, waiting for existing MainDom is canceled."); + _errorDuringAcquiring = true; + return false; + } + finally + { + transaction?.Complete(); + transaction?.Dispose(); + } + + return null; // continue + } + + private bool AcquireWhenMaxWaitTimeElapsed(IUmbracoDatabase db) + { + // Creates a separate transaction to the DB instance so we aren't allocating tons of new DB instances for each transaction + // since this is executed in a tight loop + + // if the timeout has elapsed, it either means that the other main dom is taking too long to shutdown, + // or it could mean that the previous appdomain was terminated and didn't clear out the main dom SQL row + // and it's just been left as an orphan row. + // There's really know way of knowing unless we are constantly updating the row for the current maindom + // which isn't ideal. + // So... we're going to 'just' take over, if the writelock works then we'll assume we're ok + + _logger.Debug("Timeout elapsed, assuming orphan row, acquiring MainDom."); + + ITransaction transaction = null; + + try + { + transaction = db.GetTransaction(IsolationLevel.ReadCommitted); + + _sqlServerSyntax.WriteLock(db, Constants.Locks.MainDom); + + // so now we update the row with our appdomain id + InsertLockRecord(_lockId, db); + _logger.Debug("Acquired with ID {LockId}", _lockId); + return true; + } + catch (Exception ex) + { + if (IsLockTimeoutException(ex as SqlException)) + { + // something is wrong, we cannot acquire, not much we can do + _logger.Error(ex, "Sql timeout occurred, could not forcibly acquire MainDom."); + _errorDuringAcquiring = true; + return false; + } + _logger.Error(ex, "Unexpected error, could not forcibly acquire MainDom."); + _errorDuringAcquiring = true; + return false; + } + finally + { + transaction?.Complete(); + transaction?.Dispose(); + } + } + + /// + /// Inserts or updates the key/value row + /// + private RecordPersistenceType InsertLockRecord(string id, IUmbracoDatabase db) { - var db = GetDatabase(); return db.InsertOrUpdate(new KeyValueDto { Key = MainDomKey, @@ -354,9 +399,8 @@ namespace Umbraco.Core.Runtime /// Checks if the DB row value is equals the value /// /// - private bool IsMainDomValue(string val) + private bool IsMainDomValue(string val, IUmbracoDatabase db) { - var db = GetDatabase(); return db.ExecuteScalar("SELECT COUNT(*) FROM umbracoKeyValue WHERE [key] = @key AND [value] = @val", new { key = MainDomKey, val = val }) == 1; } @@ -366,7 +410,7 @@ namespace Umbraco.Core.Runtime /// /// /// - private bool IsLockTimeoutException(Exception exception) => exception is SqlException sqlException && sqlException.Number == 1222; + private bool IsLockTimeoutException(SqlException sqlException) => sqlException?.Number == 1222; #region IDisposable Support private bool _disposedValue = false; // To detect redundant calls @@ -379,15 +423,18 @@ namespace Umbraco.Core.Runtime { lock (_locker) { + _logger.Debug($"{nameof(SqlMainDomLock)} Disposing..."); + // immediately cancel all sub-tasks, we don't want them to keep querying _cancellationTokenSource.Cancel(); _cancellationTokenSource.Dispose(); - if (_dbFactory.Configured) + if (_dbFactory.Configured && _hasTable) { - var db = GetDatabase(); + IUmbracoDatabase db = null; try { + db = _dbFactory.CreateDatabase(); db.BeginTransaction(IsolationLevel.ReadCommitted); // get a write lock @@ -402,24 +449,29 @@ namespace Umbraco.Core.Runtime if (_mainDomChanging) { _logger.Debug("Releasing MainDom, updating row, new application is booting."); - db.Execute($"UPDATE umbracoKeyValue SET [value] = [value] + '{UpdatedSuffix}' WHERE [key] = @key", new { key = MainDomKey }); + var count = db.Execute($"UPDATE umbracoKeyValue SET [value] = [value] + '{UpdatedSuffix}' WHERE [key] = @key", new { key = MainDomKey }); } else { _logger.Debug("Releasing MainDom, deleting row, application is shutting down."); - db.Execute("DELETE FROM umbracoKeyValue WHERE [key] = @key", new { key = MainDomKey }); + var count = db.Execute("DELETE FROM umbracoKeyValue WHERE [key] = @key", new { key = MainDomKey }); } } catch (Exception ex) { - ResetDatabase(); _logger.Error(ex, "Unexpected error during dipsose."); - _hasError = true; } finally { - db?.CompleteTransaction(); - ResetDatabase(); + try + { + db?.CompleteTransaction(); + db?.Dispose(); + } + catch (Exception ex) + { + _logger.Error(ex, "Unexpected error during dispose when completing transaction."); + } } } } diff --git a/src/Umbraco.Core/RuntimeOptions.cs b/src/Umbraco.Core/RuntimeOptions.cs index c0bae23446..6183f62c1c 100644 --- a/src/Umbraco.Core/RuntimeOptions.cs +++ b/src/Umbraco.Core/RuntimeOptions.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.Configuration; using System.Runtime.CompilerServices; using Umbraco.Core.Cache; @@ -21,6 +22,8 @@ namespace Umbraco.Core private static List> _onEssentials; private static bool? _installMissingDatabase; private static bool? _installEmptyDatabase; + private static bool? _installUnattended; + private static bool? _upgradeUnattended; // reads a boolean appSetting private static bool BoolSetting(string key, bool missing) => ConfigurationManager.AppSettings[key]?.InvariantEquals("true") ?? missing; @@ -37,24 +40,40 @@ namespace Umbraco.Core /// public static bool InstallMissingDatabase { - get => _installEmptyDatabase ?? BoolSetting("Umbraco.Core.RuntimeState.InstallMissingDatabase", false); - set => _installEmptyDatabase = value; + get => _installMissingDatabase ?? BoolSetting("Umbraco.Core.RuntimeState.InstallMissingDatabase", false); + set => _installMissingDatabase = value; + } + + [Obsolete("This setting is no longer used and will be removed in future versions. If a database connection string is configured and the database is empty Umbraco will be installed during the installation sequence.")] + [EditorBrowsable(EditorBrowsableState.Never)] + public static bool InstallEmptyDatabase + { + get => _installEmptyDatabase ?? BoolSetting("Umbraco.Core.RuntimeState.InstallEmptyDatabase", true); + set => _installEmptyDatabase = value; } /// - /// Gets a value indicating whether the runtime should enter Install level when the database is empty. + /// Gets a value indicating whether unattended installs are enabled. /// /// /// By default, when a database connection string is configured and it is possible to connect to - /// the database, but the database is empty, the runtime enters the BootFailed level. If this options - /// is set to true, it enters the Install level instead. - /// It is then up to the implementor, that is setting this value, to take over the installation - /// sequence. + /// the database, but the database is empty, the runtime enters the Install level. + /// If this option is set to true an unattended install will be performed and the runtime enters + /// the Run level. /// - public static bool InstallEmptyDatabase + public static bool InstallUnattended { - get => _installMissingDatabase ?? BoolSetting("Umbraco.Core.RuntimeState.InstallEmptyDatabase", false); - set => _installMissingDatabase = value; + get => _installUnattended ?? BoolSetting("Umbraco.Core.RuntimeState.InstallUnattended", false); + set => _installUnattended = value; + } + + /// + /// Gets a value indicating whether unattended upgrade is enabled. + /// + public static bool UpgradeUnattended + { + get => _upgradeUnattended ?? BoolSetting("Umbraco.Core.RuntimeState.UpgradeUnattended", false); + set => _upgradeUnattended = value; } /// diff --git a/src/Umbraco.Core/RuntimeState.cs b/src/Umbraco.Core/RuntimeState.cs index 74ec70828b..c7a1a18d44 100644 --- a/src/Umbraco.Core/RuntimeState.cs +++ b/src/Umbraco.Core/RuntimeState.cs @@ -1,4 +1,7 @@ using System; +using System.Data.Common; +using System.Data.SqlClient; +using System.Data.SqlServerCe; using System.Threading; using System.Web; using Semver; @@ -102,7 +105,7 @@ namespace Umbraco.Core // about this is that this is here specifically for the slot swap scenario https://issues.umbraco.org/issue/U4-10626 - // see U4-10626 - in some cases we want to reset the application url + // see U4-10626 - in some cases we want to reset the application URL // (this is a simplified version of what was in 7.x) // note: should this be optional? is it expensive? var url = request == null ? null : ApplicationUrlHelper.GetApplicationUrlFromCurrentRequest(request, _globalSettings); @@ -123,16 +126,15 @@ namespace Umbraco.Core /// /// Determines the runtime level. /// - public void DetermineRuntimeLevel(IUmbracoDatabaseFactory databaseFactory, ILogger logger) + public void DetermineRuntimeLevel(IUmbracoDatabaseFactory databaseFactory) { var localVersion = UmbracoVersion.LocalVersion; // the local, files, version var codeVersion = SemanticVersion; // the executing code version - var connect = false; if (localVersion == null) { // there is no local version, we are not installed - logger.Debug("No local version, need to install Umbraco."); + _logger.Debug("No local version, need to install Umbraco."); Level = RuntimeLevel.Install; Reason = RuntimeLevelReason.InstallNoVersion; return; @@ -142,13 +144,13 @@ namespace Umbraco.Core { // there *is* a local version, but it does not match the code version // need to upgrade - logger.Debug("Local version '{LocalVersion}' < code version '{CodeVersion}', need to upgrade Umbraco.", localVersion, codeVersion); + _logger.Debug("Local version '{LocalVersion}' < code version '{CodeVersion}', need to upgrade Umbraco.", localVersion, codeVersion); Level = RuntimeLevel.Upgrade; Reason = RuntimeLevelReason.UpgradeOldVersion; } else if (localVersion > codeVersion) { - logger.Warn("Local version '{LocalVersion}' > code version '{CodeVersion}', downgrading is not supported.", localVersion, codeVersion); + _logger.Warn("Local version '{LocalVersion}' > code version '{CodeVersion}', downgrading is not supported.", localVersion, codeVersion); // in fact, this is bad enough that we want to throw Reason = RuntimeLevelReason.BootFailedCannotDowngrade; @@ -158,108 +160,139 @@ namespace Umbraco.Core { // local version *does* match code version, but the database is not configured // install - may happen with Deploy/Cloud/etc - logger.Debug("Database is not configured, need to install Umbraco."); + _logger.Debug("Database is not configured, need to install Umbraco."); Level = RuntimeLevel.Install; Reason = RuntimeLevelReason.InstallNoDatabase; return; } - // else, keep going, - // anything other than install wants a database - see if we can connect - // (since this is an already existing database, assume localdb is ready) - var tries = RuntimeOptions.InstallMissingDatabase ? 2 : 5; - for (var i = 0;;) + // Check the database state, whether we can connect or if it's in an upgrade or empty state, etc... + + switch (GetUmbracoDatabaseState(databaseFactory)) { - connect = databaseFactory.CanConnect; - if (connect || ++i == tries) break; - logger.Debug("Could not immediately connect to database, trying again."); - Thread.Sleep(1000); + case UmbracoDatabaseState.CannotConnect: + { + // cannot connect to configured database, this is bad, fail + _logger.Debug("Could not connect to database."); + + if (RuntimeOptions.InstallMissingDatabase) + { + // ok to install on a configured but missing database + Level = RuntimeLevel.Install; + Reason = RuntimeLevelReason.InstallMissingDatabase; + return; + } + + // else it is bad enough that we want to throw + Reason = RuntimeLevelReason.BootFailedCannotConnectToDatabase; + throw new BootFailedException("A connection string is configured but Umbraco could not connect to the database."); + } + case UmbracoDatabaseState.NotInstalled: + { + // ok to install on an empty database + Level = RuntimeLevel.Install; + Reason = RuntimeLevelReason.InstallEmptyDatabase; + return; + } + case UmbracoDatabaseState.NeedsUpgrade: + { + // the db version does not match... but we do have a migration table + // so, at least one valid table, so we quite probably are installed & need to upgrade + + // although the files version matches the code version, the database version does not + // which means the local files have been upgraded but not the database - need to upgrade + _logger.Debug("Has not reached the final upgrade step, need to upgrade Umbraco."); + Level = RuntimeOptions.UpgradeUnattended ? RuntimeLevel.Run : RuntimeLevel.Upgrade; + Reason = RuntimeLevelReason.UpgradeMigrations; + } + break; + case UmbracoDatabaseState.Ok: + default: + { + // if we already know we want to upgrade, exit here + if (Level == RuntimeLevel.Upgrade) + return; + + // the database version matches the code & files version, all clear, can run + Level = RuntimeLevel.Run; + Reason = RuntimeLevelReason.Run; + } + break; } + } - if (connect == false) - { - // cannot connect to configured database, this is bad, fail - logger.Debug("Could not connect to database."); + private enum UmbracoDatabaseState + { + Ok, + CannotConnect, + NotInstalled, + NeedsUpgrade + } - if (RuntimeOptions.InstallMissingDatabase) - { - // ok to install on a configured but missing database - Level = RuntimeLevel.Install; - Reason = RuntimeLevelReason.InstallMissingDatabase; - return; - } - - // else it is bad enough that we want to throw - Reason = RuntimeLevelReason.BootFailedCannotConnectToDatabase; - throw new BootFailedException("A connection string is configured but Umbraco could not connect to the database."); - } - - // if we already know we want to upgrade, - // still run EnsureUmbracoUpgradeState to get the states - // (v7 will just get a null state, that's ok) - - // else - // look for a matching migration entry - bypassing services entirely - they are not 'up' yet - bool noUpgrade; + private UmbracoDatabaseState GetUmbracoDatabaseState(IUmbracoDatabaseFactory databaseFactory) + { try { - noUpgrade = EnsureUmbracoUpgradeState(databaseFactory, logger); + if (!TryDbConnect(databaseFactory)) + { + return UmbracoDatabaseState.CannotConnect; + } + + // no scope, no service - just directly accessing the database + using (var database = databaseFactory.CreateDatabase()) + { + if (!database.IsUmbracoInstalled(_logger)) + { + return UmbracoDatabaseState.NotInstalled; + } + + if (DoesUmbracoRequireUpgrade(database)) + { + return UmbracoDatabaseState.NeedsUpgrade; + } + } + + return UmbracoDatabaseState.Ok; } catch (Exception e) { - // can connect to the database but cannot check the upgrade state... oops - logger.Warn(e, "Could not check the upgrade state."); - - if (RuntimeOptions.InstallEmptyDatabase) - { - // ok to install on an empty database - Level = RuntimeLevel.Install; - Reason = RuntimeLevelReason.InstallEmptyDatabase; - return; - } + // can connect to the database so cannot check the upgrade state... oops + _logger.Warn(e, "Could not check the upgrade state."); // else it is bad enough that we want to throw Reason = RuntimeLevelReason.BootFailedCannotCheckUpgradeState; throw new BootFailedException("Could not check the upgrade state.", e); } - - // if we already know we want to upgrade, exit here - if (Level == RuntimeLevel.Upgrade) - return; - - if (noUpgrade) - { - // the database version matches the code & files version, all clear, can run - Level = RuntimeLevel.Run; - Reason = RuntimeLevelReason.Run; - return; - } - - // the db version does not match... but we do have a migration table - // so, at least one valid table, so we quite probably are installed & need to upgrade - - // although the files version matches the code version, the database version does not - // which means the local files have been upgraded but not the database - need to upgrade - logger.Debug("Has not reached the final upgrade step, need to upgrade Umbraco."); - Level = RuntimeLevel.Upgrade; - Reason = RuntimeLevelReason.UpgradeMigrations; } - protected virtual bool EnsureUmbracoUpgradeState(IUmbracoDatabaseFactory databaseFactory, ILogger logger) + private bool TryDbConnect(IUmbracoDatabaseFactory databaseFactory) + { + // anything other than install wants a database - see if we can connect + // (since this is an already existing database, assume localdb is ready) + bool canConnect; + var tries = RuntimeOptions.InstallMissingDatabase ? 2 : 5; + for (var i = 0; ;) + { + canConnect = databaseFactory.CanConnect; + if (canConnect || ++i == tries) break; + _logger.Debug("Could not immediately connect to database, trying again."); + Thread.Sleep(1000); + } + + return canConnect; + } + + private bool DoesUmbracoRequireUpgrade(IUmbracoDatabase database) { var upgrader = new Upgrader(new UmbracoPlan()); var stateValueKey = upgrader.StateValueKey; - // no scope, no service - just directly accessing the database - using (var database = databaseFactory.CreateDatabase()) - { - CurrentMigrationState = KeyValueService.GetValue(database, stateValueKey); - FinalMigrationState = upgrader.Plan.FinalState; - } + CurrentMigrationState = KeyValueService.GetValue(database, stateValueKey); + FinalMigrationState = upgrader.Plan.FinalState; - logger.Debug("Final upgrade state is {FinalMigrationState}, database contains {DatabaseState}", FinalMigrationState, CurrentMigrationState ?? ""); + _logger.Debug("Final upgrade state is {FinalMigrationState}, database contains {DatabaseState}", FinalMigrationState, CurrentMigrationState ?? ""); - return CurrentMigrationState == FinalMigrationState; + return CurrentMigrationState != FinalMigrationState; } } } diff --git a/src/Umbraco.Core/Security/BackOfficeUserStore.cs b/src/Umbraco.Core/Security/BackOfficeUserStore.cs index dc271452e1..7df328b5b7 100644 --- a/src/Umbraco.Core/Security/BackOfficeUserStore.cs +++ b/src/Umbraco.Core/Security/BackOfficeUserStore.cs @@ -96,9 +96,10 @@ namespace Umbraco.Core.Security IsLockedOut = user.IsLockedOut, }; - UpdateMemberProperties(userEntity, user); + // we have to remember whether Logins property is dirty, since the UpdateMemberProperties will reset it. + var isLoginsPropertyDirty = user.IsPropertyDirty(nameof(BackOfficeIdentityUser.Logins)); - // TODO: We should deal with Roles --> User Groups here which we currently are not doing + UpdateMemberProperties(userEntity, user); _userService.Save(userEntity); @@ -107,6 +108,16 @@ namespace Umbraco.Core.Security //re-assign id user.Id = userEntity.Id; + if (isLoginsPropertyDirty) + { + _externalLoginService.Save( + user.Id, + user.Logins.Select(x => new ExternalLogin( + x.LoginProvider, + x.ProviderKey, + (x is IIdentityUserLoginExtended extended) ? extended.UserData : null))); + } + return Task.FromResult(0); } @@ -115,7 +126,7 @@ namespace Umbraco.Core.Security /// /// /// - public async Task UpdateAsync(BackOfficeIdentityUser user) + public Task UpdateAsync(BackOfficeIdentityUser user) { ThrowIfDisposed(); if (user == null) throw new ArgumentNullException(nameof(user)); @@ -126,11 +137,13 @@ namespace Umbraco.Core.Security throw new InvalidOperationException("The user id must be an integer to work with the Umbraco"); } + // TODO: Wrap this in a scope! + var found = _userService.GetUserById(asInt.Result); if (found != null) { // we have to remember whether Logins property is dirty, since the UpdateMemberProperties will reset it. - var isLoginsPropertyDirty = user.IsPropertyDirty("Logins"); + var isLoginsPropertyDirty = user.IsPropertyDirty(nameof(BackOfficeIdentityUser.Logins)); if (UpdateMemberProperties(found, user)) { @@ -139,10 +152,16 @@ namespace Umbraco.Core.Security if (isLoginsPropertyDirty) { - var logins = await GetLoginsAsync(user); - _externalLoginService.SaveUserLogins(found.Id, logins); + _externalLoginService.Save( + found.Id, + user.Logins.Select(x => new ExternalLogin( + x.LoginProvider, + x.ProviderKey, + (x is IIdentityUserLoginExtended extended) ? extended.UserData : null))); } } + + return Task.CompletedTask; } /// @@ -382,7 +401,7 @@ namespace Umbraco.Core.Security if (login == null) throw new ArgumentNullException(nameof(login)); //get all logins associated with the login id - var result = _externalLoginService.Find(login).ToArray(); + var result = _externalLoginService.Find(login.LoginProvider, login.ProviderKey).ToArray(); if (result.Any()) { //return the first user that matches the result @@ -633,7 +652,7 @@ namespace Umbraco.Core.Security //don't assign anything if nothing has changed as this will trigger the track changes of the model - if (identityUser.IsPropertyDirty("LastLoginDateUtc") + if (identityUser.IsPropertyDirty(nameof(BackOfficeIdentityUser.LastLoginDateUtc)) || (user.LastLoginDate != default(DateTime) && identityUser.LastLoginDateUtc.HasValue == false) || identityUser.LastLoginDateUtc.HasValue && user.LastLoginDate.ToUniversalTime() != identityUser.LastLoginDateUtc.Value) { @@ -642,33 +661,33 @@ namespace Umbraco.Core.Security var dt = identityUser.LastLoginDateUtc == DateTime.MinValue ? DateTime.MinValue : identityUser.LastLoginDateUtc.Value.ToLocalTime(); user.LastLoginDate = dt; } - if (identityUser.IsPropertyDirty("LastPasswordChangeDateUtc") + if (identityUser.IsPropertyDirty(nameof(BackOfficeIdentityUser.LastPasswordChangeDateUtc)) || (user.LastPasswordChangeDate != default(DateTime) && identityUser.LastPasswordChangeDateUtc.HasValue == false) || identityUser.LastPasswordChangeDateUtc.HasValue && user.LastPasswordChangeDate.ToUniversalTime() != identityUser.LastPasswordChangeDateUtc.Value) { anythingChanged = true; user.LastPasswordChangeDate = identityUser.LastPasswordChangeDateUtc.Value.ToLocalTime(); } - if (identityUser.IsPropertyDirty("EmailConfirmed") + if (identityUser.IsPropertyDirty(nameof(BackOfficeIdentityUser.EmailConfirmed)) || (user.EmailConfirmedDate.HasValue && user.EmailConfirmedDate.Value != default(DateTime) && identityUser.EmailConfirmed == false) || ((user.EmailConfirmedDate.HasValue == false || user.EmailConfirmedDate.Value == default(DateTime)) && identityUser.EmailConfirmed)) { anythingChanged = true; user.EmailConfirmedDate = identityUser.EmailConfirmed ? (DateTime?)DateTime.Now : null; } - if (identityUser.IsPropertyDirty("Name") + if (identityUser.IsPropertyDirty(nameof(BackOfficeIdentityUser.Name)) && user.Name != identityUser.Name && identityUser.Name.IsNullOrWhiteSpace() == false) { anythingChanged = true; user.Name = identityUser.Name; } - if (identityUser.IsPropertyDirty("Email") + if (identityUser.IsPropertyDirty(nameof(BackOfficeIdentityUser.Email)) && user.Email != identityUser.Email && identityUser.Email.IsNullOrWhiteSpace() == false) { anythingChanged = true; user.Email = identityUser.Email; } - if (identityUser.IsPropertyDirty("AccessFailedCount") + if (identityUser.IsPropertyDirty(nameof(BackOfficeIdentityUser.AccessFailedCount)) && user.FailedPasswordAttempts != identityUser.AccessFailedCount) { anythingChanged = true; @@ -686,32 +705,32 @@ namespace Umbraco.Core.Security } } - if (identityUser.IsPropertyDirty("UserName") + if (identityUser.IsPropertyDirty(nameof(BackOfficeIdentityUser.UserName)) && user.Username != identityUser.UserName && identityUser.UserName.IsNullOrWhiteSpace() == false) { anythingChanged = true; user.Username = identityUser.UserName; } - if (identityUser.IsPropertyDirty("PasswordHash") + if (identityUser.IsPropertyDirty(nameof(BackOfficeIdentityUser.PasswordHash)) && user.RawPasswordValue != identityUser.PasswordHash && identityUser.PasswordHash.IsNullOrWhiteSpace() == false) { anythingChanged = true; user.RawPasswordValue = identityUser.PasswordHash; } - if (identityUser.IsPropertyDirty("Culture") + if (identityUser.IsPropertyDirty(nameof(BackOfficeIdentityUser.Culture)) && user.Language != identityUser.Culture && identityUser.Culture.IsNullOrWhiteSpace() == false) { anythingChanged = true; user.Language = identityUser.Culture; } - if (identityUser.IsPropertyDirty("StartMediaIds") + if (identityUser.IsPropertyDirty(nameof(BackOfficeIdentityUser.StartMediaIds)) && user.StartMediaIds.UnsortedSequenceEqual(identityUser.StartMediaIds) == false) { anythingChanged = true; user.StartMediaIds = identityUser.StartMediaIds; } - if (identityUser.IsPropertyDirty("StartContentIds") + if (identityUser.IsPropertyDirty(nameof(BackOfficeIdentityUser.StartContentIds)) && user.StartContentIds.UnsortedSequenceEqual(identityUser.StartContentIds) == false) { anythingChanged = true; @@ -724,7 +743,7 @@ namespace Umbraco.Core.Security } // TODO: Fix this for Groups too - if (identityUser.IsPropertyDirty("Roles") || identityUser.IsPropertyDirty("Groups")) + if (identityUser.IsPropertyDirty(nameof(BackOfficeIdentityUser.Roles)) || identityUser.IsPropertyDirty(nameof(BackOfficeIdentityUser.Groups))) { var userGroupAliases = user.Groups.Select(x => x.Alias).ToArray(); diff --git a/src/Umbraco.Core/Security/MembershipProviderBase.cs b/src/Umbraco.Core/Security/MembershipProviderBase.cs index 633e12bcc1..0bc8de492a 100644 --- a/src/Umbraco.Core/Security/MembershipProviderBase.cs +++ b/src/Umbraco.Core/Security/MembershipProviderBase.cs @@ -31,7 +31,9 @@ namespace Umbraco.Core.Security public bool VerifyPassword(string password, string hashedPassword) { - if (string.IsNullOrWhiteSpace(hashedPassword)) throw new ArgumentException("Value cannot be null or whitespace.", "hashedPassword"); + if (string.IsNullOrWhiteSpace(hashedPassword)) + return false; + return CheckPassword(password, hashedPassword); } diff --git a/src/Umbraco.Core/Services/ContentServiceExtensions.cs b/src/Umbraco.Core/Services/ContentServiceExtensions.cs index dfe02ba690..1d980b036b 100644 --- a/src/Umbraco.Core/Services/ContentServiceExtensions.cs +++ b/src/Umbraco.Core/Services/ContentServiceExtensions.cs @@ -42,7 +42,7 @@ namespace Umbraco.Core.Services var matches = AnchorRegex.Matches(rteContent); foreach (Match match in matches) { - result.Add(match.Value.Split('\"')[1]); + result.Add(match.Value.Split(Constants.CharArrays.DoubleQuote)[1]); } return result; } diff --git a/src/Umbraco.Core/Services/ContentTypeServiceExtensions.cs b/src/Umbraco.Core/Services/ContentTypeServiceExtensions.cs index 37f1e5127f..06f1ae0c0e 100644 --- a/src/Umbraco.Core/Services/ContentTypeServiceExtensions.cs +++ b/src/Umbraco.Core/Services/ContentTypeServiceExtensions.cs @@ -37,7 +37,7 @@ namespace Umbraco.Core.Services /// This is required because in the case of creating/modifying a content type because new property types being added to it are not yet persisted so cannot /// be looked up via the db, they need to be passed in. /// - /// Wether the composite content types should be applicable for an element type + /// Whether the composite content types should be applicable for an element type /// internal static ContentTypeAvailableCompositionsResults GetAvailableCompositeContentTypes(this IContentTypeService ctService, IContentTypeComposition source, diff --git a/src/Umbraco.Core/Services/IExternalLoginService.cs b/src/Umbraco.Core/Services/IExternalLoginService.cs index a81543cf2d..2749ca7b8b 100644 --- a/src/Umbraco.Core/Services/IExternalLoginService.cs +++ b/src/Umbraco.Core/Services/IExternalLoginService.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using Microsoft.AspNet.Identity; using Umbraco.Core.Models.Identity; @@ -16,20 +17,38 @@ namespace Umbraco.Core.Services /// IEnumerable GetAll(int userId); + [Obsolete("Use the overload specifying loginProvider and providerKey instead")] + IEnumerable Find(UserLoginInfo login); + /// /// Returns all logins matching the login info - generally there should only be one but in some cases /// there might be more than one depending on if an administrator has been editing/removing members /// - /// + /// + /// /// - IEnumerable Find(UserLoginInfo login); + IEnumerable Find(string loginProvider, string providerKey); + + [Obsolete("Use the Save method instead")] + void SaveUserLogins(int userId, IEnumerable logins); /// - /// Save user logins + /// Saves the external logins associated with the user /// - /// + /// + /// The user associated with the logins + /// /// - void SaveUserLogins(int userId, IEnumerable logins); + /// + /// This will replace all external login provider information for the user + /// + void Save(int userId, IEnumerable logins); + + /// + /// Save a single external login record + /// + /// + void Save(IIdentityUserLoginExtended login); /// /// Deletes all user logins - normally used when a member is deleted diff --git a/src/Umbraco.Core/Services/IIconService.cs b/src/Umbraco.Core/Services/IIconService.cs new file mode 100644 index 0000000000..963edb22a5 --- /dev/null +++ b/src/Umbraco.Core/Services/IIconService.cs @@ -0,0 +1,21 @@ +using System.Collections.Generic; +using Umbraco.Core.Models; + +namespace Umbraco.Core.Services +{ + public interface IIconService + { + /// + /// Gets an IconModel containing the icon name and SvgString according to an icon name found at the global icons path + /// + /// + /// + IconModel GetIcon(string iconName); + + /// + /// Gets a list of all svg icons found at at the global icons path. + /// + /// + IList GetAllIcons(); + } +} diff --git a/src/Umbraco.Core/Services/IMemberGroupService.cs b/src/Umbraco.Core/Services/IMemberGroupService.cs index 6a554aad31..9261dcfdf6 100644 --- a/src/Umbraco.Core/Services/IMemberGroupService.cs +++ b/src/Umbraco.Core/Services/IMemberGroupService.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using Umbraco.Core.Models; namespace Umbraco.Core.Services @@ -7,6 +8,7 @@ namespace Umbraco.Core.Services { IEnumerable GetAll(); IMemberGroup GetById(int id); + IMemberGroup GetById(Guid id); IEnumerable GetByIds(IEnumerable ids); IMemberGroup GetByName(string name); void Save(IMemberGroup memberGroup, bool raiseEvents = true); diff --git a/src/Umbraco.Core/Services/IMembershipMemberService.cs b/src/Umbraco.Core/Services/IMembershipMemberService.cs index 448b0c761a..3c6b4f6672 100644 --- a/src/Umbraco.Core/Services/IMembershipMemberService.cs +++ b/src/Umbraco.Core/Services/IMembershipMemberService.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; using Umbraco.Core.Persistence.Querying; @@ -107,6 +108,18 @@ namespace Umbraco.Core.Services /// or to Delete void Delete(T membershipUser); + /// + /// Sets the last login date for the member if they are found by username + /// + /// + /// + /// + /// This is a specialized method because whenever a member logs in, the membership provider requires us to set the 'online' which requires + /// updating their login date. This operation must be fast and cannot use database locks which is fine if we are only executing a single query + /// for this data since there won't be any other data contention issues. + /// + void SetLastLogin(string username, DateTime date); + /// /// Saves an /// diff --git a/src/Umbraco.Core/Services/IRedirectUrlService.cs b/src/Umbraco.Core/Services/IRedirectUrlService.cs index 3bd9b6f2cf..d54c9994e1 100644 --- a/src/Umbraco.Core/Services/IRedirectUrlService.cs +++ b/src/Umbraco.Core/Services/IRedirectUrlService.cs @@ -10,78 +10,86 @@ namespace Umbraco.Core.Services public interface IRedirectUrlService : IService { /// - /// Registers a redirect url. + /// Registers a redirect URL. /// - /// The Umbraco url route. + /// The Umbraco URL route. /// The content unique key. /// The culture. /// Is a proper Umbraco route eg /path/to/foo or 123/path/tofoo. void Register(string url, Guid contentKey, string culture = null); /// - /// Deletes all redirect urls for a given content. + /// Deletes all redirect URLs for a given content. /// /// The content unique key. void DeleteContentRedirectUrls(Guid contentKey); /// - /// Deletes a redirect url. + /// Deletes a redirect URL. /// - /// The redirect url to delete. + /// The redirect URL to delete. void Delete(IRedirectUrl redirectUrl); /// - /// Deletes a redirect url. + /// Deletes a redirect URL. /// - /// The redirect url identifier. + /// The redirect URL identifier. void Delete(Guid id); /// - /// Deletes all redirect urls. + /// Deletes all redirect URLs. /// void DeleteAll(); /// - /// Gets the most recent redirect urls corresponding to an Umbraco redirect url route. + /// Gets the most recent redirect URLs corresponding to an Umbraco redirect URL route. /// - /// The Umbraco redirect url route. - /// The most recent redirect urls corresponding to the route. + /// The Umbraco redirect URL route. + /// The most recent redirect URLs corresponding to the route. IRedirectUrl GetMostRecentRedirectUrl(string url); /// - /// Gets all redirect urls for a content item. + /// Gets the most recent redirect URLs corresponding to an Umbraco redirect URL route. + /// + /// The Umbraco redirect URL route. + /// The culture of the request. + /// The most recent redirect URLs corresponding to the route. + IRedirectUrl GetMostRecentRedirectUrl(string url, string culture); + + /// + /// Gets all redirect URLs for a content item. /// /// The content unique key. - /// All redirect urls for the content item. + /// All redirect URLs for the content item. IEnumerable GetContentRedirectUrls(Guid contentKey); /// - /// Gets all redirect urls. + /// Gets all redirect URLs. /// /// The page index. /// The page size. - /// The total count of redirect urls. - /// The redirect urls. + /// The total count of redirect URLs. + /// The redirect URLs. IEnumerable GetAllRedirectUrls(long pageIndex, int pageSize, out long total); /// - /// Gets all redirect urls below a given content item. + /// Gets all redirect URLs below a given content item. /// /// The content unique identifier. /// The page index. /// The page size. - /// The total count of redirect urls. - /// The redirect urls. + /// The total count of redirect URLs. + /// The redirect URLs. IEnumerable GetAllRedirectUrls(int rootContentId, long pageIndex, int pageSize, out long total); /// - /// Searches for all redirect urls that contain a given search term in their URL property. + /// Searches for all redirect URLs that contain a given search term in their URL property. /// /// The term to search for. /// The page index. /// The page size. - /// The total count of redirect urls. - /// The redirect urls. + /// The total count of redirect URLs. + /// The redirect URLs. IEnumerable SearchRedirectUrls(string searchTerm, long pageIndex, int pageSize, out long total); } } diff --git a/src/Umbraco.Core/Services/IServerRegistrationService.cs b/src/Umbraco.Core/Services/IServerRegistrationService.cs index 47bf3838f2..62bb68eb14 100644 --- a/src/Umbraco.Core/Services/IServerRegistrationService.cs +++ b/src/Umbraco.Core/Services/IServerRegistrationService.cs @@ -10,7 +10,7 @@ namespace Umbraco.Core.Services /// /// Touches a server to mark it as active; deactivate stale servers. /// - /// The server url. + /// The server URL. /// The server unique identity. /// The time after which a server is considered stale. void TouchServer(string serverAddress, string serverIdentity, TimeSpan staleTimeout); diff --git a/src/Umbraco.Core/Services/Implement/ContentService.cs b/src/Umbraco.Core/Services/Implement/ContentService.cs index 068864a558..a809b83f23 100644 --- a/src/Umbraco.Core/Services/Implement/ContentService.cs +++ b/src/Umbraco.Core/Services/Implement/ContentService.cs @@ -30,7 +30,7 @@ namespace Umbraco.Core.Services.Implement private IQuery _queryNotTrashed; //TODO: The non-lazy object should be injected private readonly Lazy _propertyValidationService = new Lazy(() => new PropertyValidationService()); - + #region Constructors @@ -535,7 +535,7 @@ namespace Umbraco.Core.Services.Implement if (content.Path.IsNullOrWhiteSpace()) return Enumerable.Empty(); var rootId = Constants.System.RootString; - var ids = content.Path.Split(',') + var ids = content.Path.Split(Constants.CharArrays.Comma) .Where(x => x != rootId && x != content.Id.ToString(CultureInfo.InvariantCulture)).Select(int.Parse).ToArray(); if (ids.Any() == false) return new List(); @@ -879,7 +879,7 @@ namespace Umbraco.Core.Services.Implement throw new NotSupportedException($"Culture \"{culture}\" is not supported by invariant content types."); } - if(content.Name != null && content.Name.Length > 255) + if (content.Name != null && content.Name.Length > 255) { throw new InvalidOperationException("Name cannot be more than 255 characters in length."); } @@ -1247,7 +1247,7 @@ namespace Umbraco.Core.Services.Implement if (culturesUnpublishing != null) { // This will mean that that we unpublished a mandatory culture or we unpublished the last culture. - + var langs = string.Join(", ", allLangs .Where(x => culturesUnpublishing.InvariantContains(x.IsoCode)) .Select(x => x.CultureName)); @@ -1256,7 +1256,7 @@ namespace Umbraco.Core.Services.Implement if (publishResult == null) throw new PanicException("publishResult == null - should not happen"); - switch(publishResult.Result) + switch (publishResult.Result) { case PublishResultType.FailedPublishMandatoryCultureMissing: //occurs when a mandatory culture was unpublished (which means we tried publishing the document without a mandatory culture) @@ -1270,7 +1270,7 @@ namespace Umbraco.Core.Services.Implement Audit(AuditType.Unpublish, userId, content.Id, "Unpublished (last language unpublished)"); return new PublishResult(PublishResultType.SuccessUnpublishLastCulture, evtMsgs, content); } - + } Audit(AuditType.Unpublish, userId, content.Id); @@ -1290,7 +1290,7 @@ namespace Umbraco.Core.Services.Implement changeType = TreeChangeTypes.RefreshBranch; // whole branch else if (isNew == false && previouslyPublished) changeType = TreeChangeTypes.RefreshNode; // single node - + // invalidate the node/branch if (!branchOne) // for branches, handled by SaveAndPublishBranch @@ -1364,24 +1364,90 @@ namespace Umbraco.Core.Services.Implement /// public IEnumerable PerformScheduledPublish(DateTime date) - => PerformScheduledPublishInternal(date).ToList(); - - // beware! this method yields results, so the returned IEnumerable *must* be - // enumerated for anything to happen - dangerous, so private + exposed via - // the public method above, which forces ToList(). - private IEnumerable PerformScheduledPublishInternal(DateTime date) { + var allLangs = new Lazy>(() => _languageRepository.GetMany().ToList()); var evtMsgs = EventMessagesFactory.Get(); + var results = new List(); - using (var scope = ScopeProvider.CreateScope()) + PerformScheduledPublishingRelease(date, results, evtMsgs, allLangs); + PerformScheduledPublishingExpiration(date, results, evtMsgs, allLangs); + + return results; + } + + private void PerformScheduledPublishingExpiration(DateTime date, List results, EventMessages evtMsgs, Lazy> allLangs) + { + using var scope = ScopeProvider.CreateScope(); + + // do a fast read without any locks since this executes often to see if we even need to proceed + if (_documentRepository.HasContentForExpiration(date)) { + // now take a write lock since we'll be updating scope.WriteLock(Constants.Locks.ContentTree); - var allLangs = _languageRepository.GetMany().ToList(); + foreach (var d in _documentRepository.GetContentForExpiration(date)) + { + if (d.ContentType.VariesByCulture()) + { + //find which cultures have pending schedules + var pendingCultures = d.ContentSchedule.GetPending(ContentScheduleAction.Expire, date) + .Select(x => x.Culture) + .Distinct() + .ToList(); + + if (pendingCultures.Count == 0) + continue; //shouldn't happen but no point in processing this document if there's nothing there + + var saveEventArgs = new ContentSavingEventArgs(d, evtMsgs); + if (scope.Events.DispatchCancelable(Saving, this, saveEventArgs, nameof(Saving))) + { + results.Add(new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, d)); + continue; + } + + foreach (var c in pendingCultures) + { + //Clear this schedule for this culture + d.ContentSchedule.Clear(c, ContentScheduleAction.Expire, date); + //set the culture to be published + d.UnpublishCulture(c); + } + + var result = CommitDocumentChangesInternal(scope, d, saveEventArgs, allLangs.Value, d.WriterId); + if (result.Success == false) + Logger.Error(null, "Failed to publish document id={DocumentId}, reason={Reason}.", d.Id, result.Result); + results.Add(result); + + } + else + { + //Clear this schedule + d.ContentSchedule.Clear(ContentScheduleAction.Expire, date); + var result = Unpublish(d, userId: d.WriterId); + if (result.Success == false) + Logger.Error(null, "Failed to unpublish document id={DocumentId}, reason={Reason}.", d.Id, result.Result); + results.Add(result); + } + } + + _documentRepository.ClearSchedule(date, ContentScheduleAction.Expire); + } + + scope.Complete(); + } + + private void PerformScheduledPublishingRelease(DateTime date, List results, EventMessages evtMsgs, Lazy> allLangs) + { + using var scope = ScopeProvider.CreateScope(); + + // do a fast read without any locks since this executes often to see if we even need to proceed + if (_documentRepository.HasContentForRelease(date)) + { + // now take a write lock since we'll be updating + scope.WriteLock(Constants.Locks.ContentTree); foreach (var d in _documentRepository.GetContentForRelease(date)) { - PublishResult result; if (d.ContentType.VariesByCulture()) { //find which cultures have pending schedules @@ -1391,11 +1457,14 @@ namespace Umbraco.Core.Services.Implement .ToList(); if (pendingCultures.Count == 0) - break; //shouldn't happen but no point in continuing if there's nothing there + continue; //shouldn't happen but no point in processing this document if there's nothing there var saveEventArgs = new ContentSavingEventArgs(d, evtMsgs); if (scope.Events.DispatchCancelable(Saving, this, saveEventArgs, nameof(Saving))) - yield return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, d); + { + results.Add(new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, d)); + continue; // this document is canceled move next + } var publishing = true; foreach (var culture in pendingCultures) @@ -1407,94 +1476,51 @@ namespace Umbraco.Core.Services.Implement //publish the culture values and validate the property values, if validation fails, log the invalid properties so the develeper has an idea of what has failed Property[] invalidProperties = null; - var impact = CultureImpact.Explicit(culture, IsDefaultCulture(allLangs, culture)); + var impact = CultureImpact.Explicit(culture, IsDefaultCulture(allLangs.Value, culture)); var tryPublish = d.PublishCulture(impact) && _propertyValidationService.Value.IsPropertyDataValid(d, out invalidProperties, impact); if (invalidProperties != null && invalidProperties.Length > 0) Logger.Warn("Scheduled publishing will fail for document {DocumentId} and culture {Culture} because of invalid properties {InvalidProperties}", d.Id, culture, string.Join(",", invalidProperties.Select(x => x.Alias))); publishing &= tryPublish; //set the culture to be published - if (!publishing) break; // no point continuing + if (!publishing) continue; // move to next document } + PublishResult result; + if (d.Trashed) result = new PublishResult(PublishResultType.FailedPublishIsTrashed, evtMsgs, d); else if (!publishing) result = new PublishResult(PublishResultType.FailedPublishContentInvalid, evtMsgs, d); else - result = CommitDocumentChangesInternal(scope, d, saveEventArgs, allLangs, d.WriterId); - + result = CommitDocumentChangesInternal(scope, d, saveEventArgs, allLangs.Value, d.WriterId); if (result.Success == false) Logger.Error(null, "Failed to publish document id={DocumentId}, reason={Reason}.", d.Id, result.Result); - yield return result; + results.Add(result); } else { //Clear this schedule d.ContentSchedule.Clear(ContentScheduleAction.Release, date); - result = d.Trashed + var result = d.Trashed ? new PublishResult(PublishResultType.FailedPublishIsTrashed, evtMsgs, d) : SaveAndPublish(d, userId: d.WriterId); if (result.Success == false) Logger.Error(null, "Failed to publish document id={DocumentId}, reason={Reason}.", d.Id, result.Result); - yield return result; + results.Add(result); } } - foreach (var d in _documentRepository.GetContentForExpiration(date)) - { - PublishResult result; - if (d.ContentType.VariesByCulture()) - { - //find which cultures have pending schedules - var pendingCultures = d.ContentSchedule.GetPending(ContentScheduleAction.Expire, date) - .Select(x => x.Culture) - .Distinct() - .ToList(); + _documentRepository.ClearSchedule(date, ContentScheduleAction.Release); - if (pendingCultures.Count == 0) - break; //shouldn't happen but no point in continuing if there's nothing there - - var saveEventArgs = new ContentSavingEventArgs(d, evtMsgs); - if (scope.Events.DispatchCancelable(Saving, this, saveEventArgs, nameof(Saving))) - yield return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, d); - - foreach (var c in pendingCultures) - { - //Clear this schedule for this culture - d.ContentSchedule.Clear(c, ContentScheduleAction.Expire, date); - //set the culture to be published - d.UnpublishCulture(c); - } - - result = CommitDocumentChangesInternal(scope, d, saveEventArgs, allLangs, d.WriterId); - if (result.Success == false) - Logger.Error(null, "Failed to publish document id={DocumentId}, reason={Reason}.", d.Id, result.Result); - yield return result; - - } - else - { - //Clear this schedule - d.ContentSchedule.Clear(ContentScheduleAction.Expire, date); - result = Unpublish(d, userId: d.WriterId); - if (result.Success == false) - Logger.Error(null, "Failed to unpublish document id={DocumentId}, reason={Reason}.", d.Id, result.Result); - yield return result; - } - - - } - - _documentRepository.ClearSchedule(date); - - scope.Complete(); } + + scope.Complete(); } // utility 'PublishCultures' func used by SaveAndPublishBranch @@ -2650,7 +2676,7 @@ namespace Umbraco.Core.Services.Implement // there will be nothing to publish/unpublish. return new PublishResult(PublishResultType.FailedPublishNothingToPublish, evtMsgs, content); } - + // missing mandatory culture = cannot be published var mandatoryCultures = allLangs.Where(x => x.IsMandatory).Select(x => x.IsoCode); @@ -2985,6 +3011,8 @@ namespace Umbraco.Core.Services.Implement _documentBlueprintRepository.Save(content); + Audit(AuditType.Save, Constants.Security.SuperUserId, content.Id, $"Saved content template: {content.Name}"); + scope.Events.Dispatch(SavedBlueprint, this, new SaveEventArgs(content), "SavedBlueprint"); scope.Complete(); @@ -3163,6 +3191,6 @@ namespace Umbraco.Core.Services.Implement #endregion - + } } diff --git a/src/Umbraco.Core/Services/Implement/ContentTypeServiceBaseOfTRepositoryTItemTService.cs b/src/Umbraco.Core/Services/Implement/ContentTypeServiceBaseOfTRepositoryTItemTService.cs index 3d4c109bfb..480501212f 100644 --- a/src/Umbraco.Core/Services/Implement/ContentTypeServiceBaseOfTRepositoryTItemTService.cs +++ b/src/Umbraco.Core/Services/Implement/ContentTypeServiceBaseOfTRepositoryTItemTService.cs @@ -879,7 +879,7 @@ namespace Umbraco.Core.Services.Implement public IEnumerable GetContainers(TItem item) { - var ancestorIds = item.Path.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) + var ancestorIds = item.Path.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries) .Select(x => { var asInt = x.TryConvertTo(); diff --git a/src/Umbraco.Core/Services/Implement/DataTypeService.cs b/src/Umbraco.Core/Services/Implement/DataTypeService.cs index 5a93fb91b1..7102d0eeeb 100644 --- a/src/Umbraco.Core/Services/Implement/DataTypeService.cs +++ b/src/Umbraco.Core/Services/Implement/DataTypeService.cs @@ -99,7 +99,7 @@ namespace Umbraco.Core.Services.Implement public IEnumerable GetContainers(IDataType dataType) { - var ancestorIds = dataType.Path.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) + var ancestorIds = dataType.Path.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries) .Select(x => { var asInt = x.TryConvertTo(); diff --git a/src/Umbraco.Core/Services/Implement/EntityXmlSerializer.cs b/src/Umbraco.Core/Services/Implement/EntityXmlSerializer.cs index 5189b3422e..4d7694d1b8 100644 --- a/src/Umbraco.Core/Services/Implement/EntityXmlSerializer.cs +++ b/src/Umbraco.Core/Services/Implement/EntityXmlSerializer.cs @@ -177,7 +177,7 @@ namespace Umbraco.Core.Services.Implement var folderNames = string.Empty; if (dataType.Level != 1) { - //get url encoded folder names + //get URL encoded folder names var folders = _dataTypeService.GetContainers(dataType) .OrderBy(x => x.Level) .Select(x => HttpUtility.UrlEncode(x.Name)); @@ -363,6 +363,7 @@ namespace Umbraco.Core.Services.Implement new XElement("MandatoryMessage", propertyType.MandatoryMessage), new XElement("Validation", propertyType.ValidationRegExp), new XElement("ValidationRegExpMessage", propertyType.ValidationRegExpMessage), + new XElement("LabelOnTop", propertyType.LabelOnTop), new XElement("Description", new XCData(propertyType.Description))); genericProperties.Add(genericProperty); } @@ -434,6 +435,7 @@ namespace Umbraco.Core.Services.Implement var info = new XElement("Info", new XElement("Name", contentType.Name), new XElement("Alias", contentType.Alias), + new XElement("Key", contentType.Key), new XElement("Icon", contentType.Icon), new XElement("Thumbnail", contentType.Thumbnail), new XElement("Description", contentType.Description), @@ -484,11 +486,13 @@ namespace Umbraco.Core.Services.Implement var genericProperty = new XElement("GenericProperty", new XElement("Name", propertyType.Name), new XElement("Alias", propertyType.Alias), + new XElement("Key", propertyType.Key), new XElement("Type", propertyType.PropertyEditorAlias), - new XElement("Definition", definition.Key), + new XElement("Definition", definition.Key), new XElement("Tab", propertyGroup == null ? "" : propertyGroup.Name), new XElement("SortOrder", propertyType.SortOrder), new XElement("Mandatory", propertyType.Mandatory.ToString()), + new XElement("LabelOnTop", propertyType.LabelOnTop.ToString()), propertyType.MandatoryMessage != null ? new XElement("MandatoryMessage", propertyType.MandatoryMessage) : null, propertyType.ValidationRegExp != null ? new XElement("Validation", propertyType.ValidationRegExp) : null, propertyType.ValidationRegExpMessage != null ? new XElement("ValidationRegExpMessage", propertyType.ValidationRegExpMessage) : null, @@ -518,7 +522,7 @@ namespace Umbraco.Core.Services.Implement //don't add folders if this is a child doc type if (contentType.Level != 1 && masterContentType == null) { - //get url encoded folder names + //get URL encoded folder names var folders = _contentTypeService.GetContainers(contentType) .OrderBy(x => x.Level) .Select(x => HttpUtility.UrlEncode(x.Name)); diff --git a/src/Umbraco.Core/Services/Implement/ExternalLoginService.cs b/src/Umbraco.Core/Services/Implement/ExternalLoginService.cs index aedf3874dd..b0d9a80a65 100644 --- a/src/Umbraco.Core/Services/Implement/ExternalLoginService.cs +++ b/src/Umbraco.Core/Services/Implement/ExternalLoginService.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using Microsoft.AspNet.Identity; using Umbraco.Core.Events; @@ -20,53 +21,60 @@ namespace Umbraco.Core.Services.Implement _externalLoginRepository = externalLoginRepository; } - /// - /// Returns all user logins assigned - /// - /// - /// + /// public IEnumerable GetAll(int userId) { using (var scope = ScopeProvider.CreateScope(autoComplete: true)) { return _externalLoginRepository.Get(Query().Where(x => x.UserId == userId)) - .ToList(); // ToList is important here, must evaluate within uow! // ToList is important here, must evaluate within uow! + .ToList(); } } - /// - /// Returns all logins matching the login info - generally there should only be one but in some cases - /// there might be more than one depending on if an administrator has been editing/removing members - /// - /// - /// + [Obsolete("Use the overload specifying loginProvider and providerKey instead")] public IEnumerable Find(UserLoginInfo login) + { + return Find(login.LoginProvider, login.ProviderKey); + } + + /// + public IEnumerable Find(string loginProvider, string providerKey) { using (var scope = ScopeProvider.CreateScope(autoComplete: true)) { - return _externalLoginRepository.Get(Query().Where(x => x.ProviderKey == login.ProviderKey && x.LoginProvider == login.LoginProvider)) - .ToList(); // ToList is important here, must evaluate within uow! // ToList is important here, must evaluate within uow! + return _externalLoginRepository.Get(Query() + .Where(x => x.ProviderKey == providerKey && x.LoginProvider == loginProvider)) + .ToList(); } } - /// - /// Save user logins - /// - /// - /// + [Obsolete("Use the Save method instead")] public void SaveUserLogins(int userId, IEnumerable logins) + { + Save(userId, logins.Select(x => new ExternalLogin(x.LoginProvider, x.ProviderKey))); + } + + /// + public void Save(int userId, IEnumerable logins) { using (var scope = ScopeProvider.CreateScope()) { - _externalLoginRepository.SaveUserLogins(userId, logins); + _externalLoginRepository.Save(userId, logins); scope.Complete(); } } - /// - /// Deletes all user logins - normally used when a member is deleted - /// - /// + /// + public void Save(IIdentityUserLoginExtended login) + { + using (var scope = ScopeProvider.CreateScope()) + { + _externalLoginRepository.Save(login); + scope.Complete(); + } + } + + /// public void DeleteUserLogins(int userId) { using (var scope = ScopeProvider.CreateScope()) @@ -75,5 +83,7 @@ namespace Umbraco.Core.Services.Implement scope.Complete(); } } + + } } diff --git a/src/Umbraco.Core/Services/Implement/MediaService.cs b/src/Umbraco.Core/Services/Implement/MediaService.cs index ecd4cccc8d..ac9c83458d 100644 --- a/src/Umbraco.Core/Services/Implement/MediaService.cs +++ b/src/Umbraco.Core/Services/Implement/MediaService.cs @@ -477,7 +477,7 @@ namespace Umbraco.Core.Services.Implement if (media.Path.IsNullOrWhiteSpace()) return Enumerable.Empty(); var rootId = Constants.System.RootString; - var ids = media.Path.Split(',') + var ids = media.Path.Split(Constants.CharArrays.Comma) .Where(x => x != rootId && x != media.Id.ToString(CultureInfo.InvariantCulture)) .Select(int.Parse) .ToArray(); diff --git a/src/Umbraco.Core/Services/Implement/MemberGroupService.cs b/src/Umbraco.Core/Services/Implement/MemberGroupService.cs index c879a00ccb..308080bbf4 100644 --- a/src/Umbraco.Core/Services/Implement/MemberGroupService.cs +++ b/src/Umbraco.Core/Services/Implement/MemberGroupService.cs @@ -70,6 +70,14 @@ namespace Umbraco.Core.Services.Implement } } + public IMemberGroup GetById(Guid id) + { + using (var scope = ScopeProvider.CreateScope(autoComplete: true)) + { + return _memberGroupRepository.Get(id); + } + } + public IMemberGroup GetByName(string name) { using (var scope = ScopeProvider.CreateScope(autoComplete: true)) diff --git a/src/Umbraco.Core/Services/Implement/MemberService.cs b/src/Umbraco.Core/Services/Implement/MemberService.cs index 9b97c6b161..877bb5c9ce 100644 --- a/src/Umbraco.Core/Services/Implement/MemberService.cs +++ b/src/Umbraco.Core/Services/Implement/MemberService.cs @@ -447,15 +447,10 @@ namespace Umbraco.Core.Services.Implement /// public IMember GetByUsername(string username) { - // TODO: Somewhere in here, whether at this level or the repository level, we need to add - // a caching mechanism since this method is used by all the membership providers and could be - // called quite a bit when dealing with members. - using (var scope = ScopeProvider.CreateScope(autoComplete: true)) { - scope.ReadLock(Constants.Locks.MemberTree); - var query = Query().Where(x => x.Username.Equals(username)); - return _memberRepository.Get(query).FirstOrDefault(); + scope.ReadLock(Constants.Locks.MemberTree); + return _memberRepository.GetByUsername(username); } } @@ -806,12 +801,17 @@ namespace Umbraco.Core.Services.Implement #region Save - /// - /// Saves an - /// - /// to Save - /// Optional parameter to raise events. - /// Default is True otherwise set to False to not raise events + /// + public void SetLastLogin(string username, DateTime date) + { + using (var scope = ScopeProvider.CreateScope()) + { + _memberRepository.SetLastLogin(username, date); + scope.Complete(); + } + } + + /// public void Save(IMember member, bool raiseEvents = true) { //trimming username and email to make sure we have no trailing space @@ -847,12 +847,7 @@ namespace Umbraco.Core.Services.Implement } } - /// - /// Saves a list of objects - /// - /// to save - /// Optional parameter to raise events. - /// Default is True otherwise set to False to not raise events + /// public void Save(IEnumerable members, bool raiseEvents = true) { var membersA = members.ToArray(); diff --git a/src/Umbraco.Core/Services/Implement/NotificationService.cs b/src/Umbraco.Core/Services/Implement/NotificationService.cs index 2b21945ba8..3c5f91e932 100644 --- a/src/Umbraco.Core/Services/Implement/NotificationService.cs +++ b/src/Umbraco.Core/Services/Implement/NotificationService.cs @@ -78,7 +78,7 @@ namespace Umbraco.Core.Services.Implement if (entitiesL.Count == 0) return; //put all entity's paths into a list with the same indices - var paths = entitiesL.Select(x => x.Path.Split(',').Select(int.Parse).ToArray()).ToArray(); + var paths = entitiesL.Select(x => x.Path.Split(Constants.CharArrays.Comma).Select(int.Parse).ToArray()).ToArray(); // lazily get versions var prevVersionDictionary = new Dictionary(); @@ -176,7 +176,7 @@ namespace Umbraco.Core.Services.Implement /// public IEnumerable FilterUserNotificationsByPath(IEnumerable userNotifications, string path) { - var pathParts = path.Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries); + var pathParts = path.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries); return userNotifications.Where(r => pathParts.InvariantContains(r.EntityId.ToString(CultureInfo.InvariantCulture))).ToList(); } @@ -394,7 +394,7 @@ namespace Umbraco.Core.Services.Implement content.Id.ToString(CultureInfo.InvariantCulture), string.Format("{2}://{0}/{1}", string.Concat(siteUri.Authority), - // TODO: RE-enable this so we can have a nice url + // TODO: RE-enable this so we can have a nice URL /*umbraco.library.NiceUrl(documentObject.Id))*/ string.Concat(content.Id, ".aspx"), protocol), diff --git a/src/Umbraco.Core/Services/Implement/PublicAccessService.cs b/src/Umbraco.Core/Services/Implement/PublicAccessService.cs index ab9ea64292..4e3cd96012 100644 --- a/src/Umbraco.Core/Services/Implement/PublicAccessService.cs +++ b/src/Umbraco.Core/Services/Implement/PublicAccessService.cs @@ -54,7 +54,7 @@ namespace Umbraco.Core.Services.Implement { //Get all ids in the path for the content item and ensure they all // parse to ints that are not -1. - var ids = contentPath.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) + var ids = contentPath.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries) .Select(x => int.TryParse(x, out int val) ? val : -1) .Where(x => x != -1) .ToList(); diff --git a/src/Umbraco.Core/Services/Implement/RedirectUrlService.cs b/src/Umbraco.Core/Services/Implement/RedirectUrlService.cs index 80816961fc..497e9f7730 100644 --- a/src/Umbraco.Core/Services/Implement/RedirectUrlService.cs +++ b/src/Umbraco.Core/Services/Implement/RedirectUrlService.cs @@ -108,5 +108,15 @@ namespace Umbraco.Core.Services.Implement return _redirectUrlRepository.SearchUrls(searchTerm, pageIndex, pageSize, out total); } } + + public IRedirectUrl GetMostRecentRedirectUrl(string url, string culture) + { + if (string.IsNullOrWhiteSpace(culture)) return GetMostRecentRedirectUrl(url); + using (var scope = ScopeProvider.CreateScope(autoComplete: true)) + { + return _redirectUrlRepository.GetMostRecentUrl(url, culture); + } + + } } } diff --git a/src/Umbraco.Core/Services/Implement/RelationService.cs b/src/Umbraco.Core/Services/Implement/RelationService.cs index 4b53709de9..c629466edf 100644 --- a/src/Umbraco.Core/Services/Implement/RelationService.cs +++ b/src/Umbraco.Core/Services/Implement/RelationService.cs @@ -15,13 +15,15 @@ namespace Umbraco.Core.Services.Implement private readonly IEntityService _entityService; private readonly IRelationRepository _relationRepository; private readonly IRelationTypeRepository _relationTypeRepository; + private readonly IAuditRepository _auditRepository; public RelationService(IScopeProvider uowProvider, ILogger logger, IEventMessagesFactory eventMessagesFactory, IEntityService entityService, - IRelationRepository relationRepository, IRelationTypeRepository relationTypeRepository) + IRelationRepository relationRepository, IRelationTypeRepository relationTypeRepository, IAuditRepository auditRepository) : base(uowProvider, logger, eventMessagesFactory) { _relationRepository = relationRepository; _relationTypeRepository = relationTypeRepository; + _auditRepository = auditRepository; _entityService = entityService ?? throw new ArgumentNullException(nameof(entityService)); } @@ -476,6 +478,7 @@ namespace Umbraco.Core.Services.Implement } _relationTypeRepository.Save(relationType); + Audit(AuditType.Save, Constants.Security.SuperUserId, relationType.Id, $"Saved relation type: {relationType.Name}"); scope.Complete(); saveEventArgs.CanCancel = false; scope.Events.Dispatch(SavedRelationType, this, saveEventArgs); @@ -565,6 +568,11 @@ namespace Umbraco.Core.Services.Implement } return relations; } + + private void Audit(AuditType type, int userId, int objectId, string message = null) + { + _auditRepository.Save(new AuditItem(objectId, type, userId, ObjectTypes.GetName(UmbracoObjectTypes.RelationType), message)); + } #endregion #region Events Handlers diff --git a/src/Umbraco.Core/Services/Implement/ServerRegistrationService.cs b/src/Umbraco.Core/Services/Implement/ServerRegistrationService.cs index d9ce978274..056d4d9fd9 100644 --- a/src/Umbraco.Core/Services/Implement/ServerRegistrationService.cs +++ b/src/Umbraco.Core/Services/Implement/ServerRegistrationService.cs @@ -40,7 +40,7 @@ namespace Umbraco.Core.Services.Implement /// /// Touches a server to mark it as active; deactivate stale servers. /// - /// The server url. + /// The server URL. /// The server unique identity. /// The time after which a server is considered stale. public void TouchServer(string serverAddress, string serverIdentity, TimeSpan staleTimeout) diff --git a/src/Umbraco.Core/Services/Implement/UserService.cs b/src/Umbraco.Core/Services/Implement/UserService.cs index 6f5cbfca56..ca42d5878a 100644 --- a/src/Umbraco.Core/Services/Implement/UserService.cs +++ b/src/Umbraco.Core/Services/Implement/UserService.cs @@ -205,7 +205,7 @@ namespace Umbraco.Core.Services.Implement //NOTE: this will not be cached return _userRepository.GetByUsername(username, includeSecurityData: false); } - + throw; } } @@ -254,6 +254,13 @@ namespace Umbraco.Core.Services.Implement } } + // explicit implementation because we don't need it now but due to the way that the members membership provider is put together + // this method must exist in this service as an implementation (legacy) + void IMembershipMemberService.SetLastLogin(string username, DateTime date) + { + Logger.Warn("This method is not implemented. Using membership providers users is not advised, use ASP.NET Identity instead. See issue #9224 for more information."); + } + /// /// Saves an /// @@ -710,7 +717,7 @@ namespace Umbraco.Core.Services.Implement //NOTE: this will not be cached return _userRepository.Get(id, includeSecurityData: false); } - + throw; } } diff --git a/src/Umbraco.Core/Services/PropertyValidationService.cs b/src/Umbraco.Core/Services/PropertyValidationService.cs index a037a83920..d83628a316 100644 --- a/src/Umbraco.Core/Services/PropertyValidationService.cs +++ b/src/Umbraco.Core/Services/PropertyValidationService.cs @@ -1,9 +1,7 @@ using System; using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Umbraco.Core.Collections; using Umbraco.Core.Composing; using Umbraco.Core.Models; using Umbraco.Core.PropertyEditors; @@ -15,19 +13,73 @@ namespace Umbraco.Core.Services { private readonly PropertyEditorCollection _propertyEditors; private readonly IDataTypeService _dataTypeService; + private readonly ILocalizedTextService _textService; - public PropertyValidationService(PropertyEditorCollection propertyEditors, IDataTypeService dataTypeService) + public PropertyValidationService(PropertyEditorCollection propertyEditors, IDataTypeService dataTypeService, ILocalizedTextService textService) { _propertyEditors = propertyEditors; _dataTypeService = dataTypeService; + _textService = textService; } //TODO: Remove this method in favor of the overload specifying all dependencies public PropertyValidationService() - : this(Current.PropertyEditors, Current.Services.DataTypeService) + : this(Current.PropertyEditors, Current.Services.DataTypeService, Current.Services.TextService) { } + public IEnumerable ValidatePropertyValue( + PropertyType propertyType, + object postedValue) + { + if (propertyType is null) throw new ArgumentNullException(nameof(propertyType)); + var dataType = _dataTypeService.GetDataType(propertyType.DataTypeId); + if (dataType == null) throw new InvalidOperationException("No data type found by id " + propertyType.DataTypeId); + + var editor = _propertyEditors[propertyType.PropertyEditorAlias]; + if (editor == null) throw new InvalidOperationException("No property editor found by alias " + propertyType.PropertyEditorAlias); + + return ValidatePropertyValue(_textService, editor, dataType, postedValue, propertyType.Mandatory, propertyType.ValidationRegExp, propertyType.MandatoryMessage, propertyType.ValidationRegExpMessage); + } + + internal static IEnumerable ValidatePropertyValue( + ILocalizedTextService textService, + IDataEditor editor, + IDataType dataType, + object postedValue, + bool isRequired, + string validationRegExp, + string isRequiredMessage, + string validationRegExpMessage) + { + // Retrieve default messages used for required and regex validatation. We'll replace these + // if set with custom ones if they've been provided for a given property. + var requiredDefaultMessages = new[] + { + textService.Localize("validation", "invalidNull"), + textService.Localize("validation", "invalidEmpty") + }; + var formatDefaultMessages = new[] + { + textService.Localize("validation", "invalidPattern"), + }; + + var valueEditor = editor.GetValueEditor(dataType.Configuration); + foreach (var validationResult in valueEditor.Validate(postedValue, isRequired, validationRegExp)) + { + // If we've got custom error messages, we'll replace the default ones that will have been applied in the call to Validate(). + if (isRequired && !string.IsNullOrWhiteSpace(isRequiredMessage) && requiredDefaultMessages.Contains(validationResult.ErrorMessage, StringComparer.OrdinalIgnoreCase)) + { + validationResult.ErrorMessage = isRequiredMessage; + } + if (!string.IsNullOrWhiteSpace(validationRegExp) && !string.IsNullOrWhiteSpace(validationRegExpMessage) && formatDefaultMessages.Contains(validationResult.ErrorMessage, StringComparer.OrdinalIgnoreCase)) + { + validationResult.ErrorMessage = validationRegExpMessage; + } + yield return validationResult; + } + } + /// /// Validates the content item's properties pass validation rules /// diff --git a/src/Umbraco.Core/Services/UserServiceExtensions.cs b/src/Umbraco.Core/Services/UserServiceExtensions.cs index c365f1ccc2..e817d7925a 100644 --- a/src/Umbraco.Core/Services/UserServiceExtensions.cs +++ b/src/Umbraco.Core/Services/UserServiceExtensions.cs @@ -10,7 +10,7 @@ namespace Umbraco.Core.Services { public static EntityPermission GetPermissions(this IUserService userService, IUser user, string path) { - var ids = path.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) + var ids = path.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries) .Select(x => x.TryConvertTo()) .Where(x => x.Success) .Select(x => x.Result) diff --git a/src/Umbraco.Core/StringExtensions.cs b/src/Umbraco.Core/StringExtensions.cs index c37f8bdf35..80ef81f36d 100644 --- a/src/Umbraco.Core/StringExtensions.cs +++ b/src/Umbraco.Core/StringExtensions.cs @@ -41,7 +41,7 @@ namespace Umbraco.Core /// internal static int[] GetIdsFromPathReversed(this string path) { - var nodeIds = path.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) + var nodeIds = path.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries) .Select(x => x.TryConvertTo()) .Where(x => x.Success) .Select(x => x.Result) @@ -106,7 +106,7 @@ namespace Umbraco.Core /// internal static Attempt DetectIsJavaScriptPath(this string input) { - //validate that this is a url, if it is not, we'll assume that it is a text block and render it as a text + //validate that this is a URL, if it is not, we'll assume that it is a text block and render it as a text //block instead. var isValid = true; @@ -114,7 +114,7 @@ namespace Umbraco.Core { //ok it validates, but so does alert('hello'); ! so we need to do more checks - //here are the valid chars in a url without escaping + //here are the valid chars in a URL without escaping if (Regex.IsMatch(input, @"[^a-zA-Z0-9-._~:/?#\[\]@!$&'\(\)*\+,%;=]")) isValid = false; @@ -256,7 +256,7 @@ namespace Umbraco.Core //remove any prefixed '&' or '?' for (var i = 0; i < queryStrings.Length; i++) { - queryStrings[i] = queryStrings[i].TrimStart('?', '&').TrimEnd('&'); + queryStrings[i] = queryStrings[i].TrimStart(Constants.CharArrays.QuestionMarkAmpersand).TrimEnd(Constants.CharArrays.Ampersand); } var nonEmpty = queryStrings.Where(x => !x.IsNullOrWhiteSpace()).ToArray(); @@ -315,7 +315,7 @@ namespace Umbraco.Core if (value == null) return null; - string[] parts = value.Split('\n'); + string[] parts = value.Split(Constants.CharArrays.LineFeed); StringBuilder decryptedValue = new StringBuilder(); @@ -856,7 +856,7 @@ namespace Umbraco.Core var pos = str.IndexOf('='); if (pos < 0) pos = str.Length; - // replace chars that would cause problems in urls + // replace chars that would cause problems in URLs var chArray = new char[pos]; for (var i = 0; i < pos; i++) { @@ -1095,13 +1095,13 @@ namespace Umbraco.Core return Current.ShortStringHelper.CleanStringForSafeAlias(alias, culture); } - // the new methods to get a url segment + // the new methods to get a URL segment /// - /// Cleans a string to produce a string that can safely be used in an url segment. + /// Cleans a string to produce a string that can safely be used in an URL segment. /// /// The text to filter. - /// The safe url segment. + /// The safe URL segment. public static string ToUrlSegment(this string text) { if (text == null) throw new ArgumentNullException(nameof(text)); @@ -1111,11 +1111,11 @@ namespace Umbraco.Core } /// - /// Cleans a string, in the context of a specified culture, to produce a string that can safely be used in an url segment. + /// Cleans a string, in the context of a specified culture, to produce a string that can safely be used in an URL segment. /// /// The text to filter. /// The culture. - /// The safe url segment. + /// The safe URL segment. public static string ToUrlSegment(this string text, string culture) { if (text == null) throw new ArgumentNullException(nameof(text)); @@ -1124,7 +1124,7 @@ namespace Umbraco.Core return Current.ShortStringHelper.CleanStringForUrlSegment(text, culture); } - // the new methods to clean a string (to alias, url segment...) + // the new methods to clean a string (to alias, URL segment...) /// /// Cleans a string. @@ -1205,7 +1205,7 @@ namespace Umbraco.Core /// /// Cleans a string, in the context of the invariant culture, to produce a string that can safely be used as a filename, - /// both internally (on disk) and externally (as a url). + /// both internally (on disk) and externally (as a URL). /// /// The text to filter. /// The safe filename. @@ -1216,7 +1216,7 @@ namespace Umbraco.Core /// /// Cleans a string, in the context of the invariant culture, to produce a string that can safely be used as a filename, - /// both internally (on disk) and externally (as a url). + /// both internally (on disk) and externally (as a URL). /// /// The text to filter. /// The culture. @@ -1347,7 +1347,7 @@ namespace Umbraco.Core { return false; } - var idCheckList = csv.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries); + var idCheckList = csv.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries); return idCheckList.Contains(value); } @@ -1362,7 +1362,7 @@ namespace Umbraco.Core fileName = fileName.StripFileExtension(); // underscores and dashes to spaces - fileName = fileName.ReplaceMany(new[] { '_', '-' }, ' '); + fileName = fileName.ReplaceMany(Constants.CharArrays.UnderscoreDash, ' '); // any other conversions ? @@ -1370,7 +1370,7 @@ namespace Umbraco.Core fileName = CultureInfo.InvariantCulture.TextInfo.ToTitleCase(fileName); // Replace multiple consecutive spaces with a single space - fileName = string.Join(" ", fileName.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries)); + fileName = string.Join(" ", fileName.Split(Constants.CharArrays.Space, StringSplitOptions.RemoveEmptyEntries)); return fileName; } diff --git a/src/Umbraco.Core/StringUdi.cs b/src/Umbraco.Core/StringUdi.cs index 7b32399599..77c55e6692 100644 --- a/src/Umbraco.Core/StringUdi.cs +++ b/src/Umbraco.Core/StringUdi.cs @@ -33,7 +33,7 @@ namespace Umbraco.Core public StringUdi(Uri uriValue) : base(uriValue) { - Id = Uri.UnescapeDataString(uriValue.AbsolutePath.TrimStart('/')); + Id = Uri.UnescapeDataString(uriValue.AbsolutePath.TrimStart(Constants.CharArrays.ForwardSlash)); } private static string EscapeUriString(string s) @@ -46,7 +46,7 @@ namespace Umbraco.Core // we want to preserve the / and the unreserved // so... - return string.Join("/", s.Split('/').Select(Uri.EscapeDataString)); + return string.Join("/", s.Split(Constants.CharArrays.ForwardSlash).Select(Uri.EscapeDataString)); } /// diff --git a/src/Umbraco.Core/Strings/ContentBaseExtensions.cs b/src/Umbraco.Core/Strings/ContentBaseExtensions.cs index 43e3506a84..01560f3b8a 100644 --- a/src/Umbraco.Core/Strings/ContentBaseExtensions.cs +++ b/src/Umbraco.Core/Strings/ContentBaseExtensions.cs @@ -6,17 +6,17 @@ using Umbraco.Core.Models; namespace Umbraco.Core.Strings { /// - /// Provides extension methods to IContentBase to get url segments. + /// Provides extension methods to IContentBase to get URL segments. /// internal static class ContentBaseExtensions { /// - /// Gets the url segment for a specified content and culture. + /// Gets the URL segment for a specified content and culture. /// /// The content. /// The culture. /// - /// The url segment. + /// The URL segment. public static string GetUrlSegment(this IContentBase content, IEnumerable urlSegmentProviders, string culture = null) { if (content == null) throw new ArgumentNullException(nameof(content)); diff --git a/src/Umbraco.Core/Strings/Css/StylesheetRule.cs b/src/Umbraco.Core/Strings/Css/StylesheetRule.cs index 6f91906250..2c84e9adc5 100644 --- a/src/Umbraco.Core/Strings/Css/StylesheetRule.cs +++ b/src/Umbraco.Core/Strings/Css/StylesheetRule.cs @@ -28,7 +28,7 @@ namespace Umbraco.Core.Strings.Css { // since we already have a string builder in play here, we'll append to it the "hard" way // instead of using string interpolation (for increased performance) - foreach (var style in Styles.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries)) + foreach (var style in Styles.Split(Constants.CharArrays.Semicolon, StringSplitOptions.RemoveEmptyEntries)) { sb.Append("\t").Append(style.StripNewLines().Trim()).Append(";").Append(Environment.NewLine); } diff --git a/src/Umbraco.Core/Strings/DefaultShortStringHelper.cs b/src/Umbraco.Core/Strings/DefaultShortStringHelper.cs index 6361186604..15a87717d2 100644 --- a/src/Umbraco.Core/Strings/DefaultShortStringHelper.cs +++ b/src/Umbraco.Core/Strings/DefaultShortStringHelper.cs @@ -8,7 +8,7 @@ using Umbraco.Core.Configuration.UmbracoSettings; namespace Umbraco.Core.Strings { /// - /// New default implementation of string functions for short strings such as aliases or url segments. + /// New default implementation of string functions for short strings such as aliases or URL segments. /// /// /// Not optimized to work on large bodies of text. @@ -92,10 +92,10 @@ namespace Umbraco.Core.Strings } /// - /// Cleans a string to produce a string that can safely be used in an url segment. + /// Cleans a string to produce a string that can safely be used in an URL segment. /// /// The text to filter. - /// The safe url segment. + /// The safe URL segment. /// /// The string will be cleaned in the context of the default culture. /// Url segments are Ascii only (no accents...). @@ -106,11 +106,11 @@ namespace Umbraco.Core.Strings } /// - /// Cleans a string, in the context of a specified culture, to produce a string that can safely be used in an url segment. + /// Cleans a string, in the context of a specified culture, to produce a string that can safely be used in an URL segment. /// /// The text to filter. /// The culture. - /// The safe url segment. + /// The safe URL segment. /// /// Url segments are Ascii only (no accents...). /// @@ -121,7 +121,7 @@ namespace Umbraco.Core.Strings /// /// Cleans a string, in the context of the default culture, to produce a string that can safely be used as a filename, - /// both internally (on disk) and externally (as a url). + /// both internally (on disk) and externally (as a URL). /// /// The text to filter. /// The safe filename. @@ -133,7 +133,7 @@ namespace Umbraco.Core.Strings /// /// Cleans a string to produce a string that can safely be used as a filename, - /// both internally (on disk) and externally (as a url). + /// both internally (on disk) and externally (as a URL). /// /// The text to filter. /// The culture. diff --git a/src/Umbraco.Core/Strings/DefaultUrlSegmentProvider.cs b/src/Umbraco.Core/Strings/DefaultUrlSegmentProvider.cs index 9472ff4823..a0f8c73548 100644 --- a/src/Umbraco.Core/Strings/DefaultUrlSegmentProvider.cs +++ b/src/Umbraco.Core/Strings/DefaultUrlSegmentProvider.cs @@ -8,11 +8,11 @@ namespace Umbraco.Core.Strings public class DefaultUrlSegmentProvider : IUrlSegmentProvider { /// - /// Gets the url segment for a specified content and culture. + /// Gets the URL segment for a specified content and culture. /// /// The content. /// The culture. - /// The url segment. + /// The URL segment. public string GetUrlSegment(IContentBase content, string culture = null) { return GetUrlSegmentSource(content, culture).ToUrlSegment(culture); diff --git a/src/Umbraco.Core/Strings/Diff.cs b/src/Umbraco.Core/Strings/Diff.cs index 6cd4985ead..9aeeb487ef 100644 --- a/src/Umbraco.Core/Strings/Diff.cs +++ b/src/Umbraco.Core/Strings/Diff.cs @@ -232,7 +232,7 @@ namespace Umbraco.Core.Strings // strip off all cr, only use lf as text line separator. aText = aText.Replace("\r", ""); - var lines = aText.Split('\n'); + var lines = aText.Split(Constants.CharArrays.LineFeed); var codes = new int[lines.Length]; diff --git a/src/Umbraco.Core/Strings/IShortStringHelper.cs b/src/Umbraco.Core/Strings/IShortStringHelper.cs index fecbeaaee9..37873caded 100644 --- a/src/Umbraco.Core/Strings/IShortStringHelper.cs +++ b/src/Umbraco.Core/Strings/IShortStringHelper.cs @@ -1,7 +1,7 @@ namespace Umbraco.Core.Strings { /// - /// Provides string functions for short strings such as aliases or url segments. + /// Provides string functions for short strings such as aliases or URL segments. /// /// Not necessarily optimized to work on large bodies of text. public interface IShortStringHelper @@ -26,24 +26,24 @@ string CleanStringForSafeAlias(string text, string culture); /// - /// Cleans a string to produce a string that can safely be used in an url segment. + /// Cleans a string to produce a string that can safely be used in an URL segment. /// /// The text to filter. - /// The safe url segment. + /// The safe URL segment. /// The string will be cleaned in the context of the IShortStringHelper default culture. string CleanStringForUrlSegment(string text); /// - /// Cleans a string, in the context of a specified culture, to produce a string that can safely be used in an url segment. + /// Cleans a string, in the context of a specified culture, to produce a string that can safely be used in an URL segment. /// /// The text to filter. /// The culture. - /// The safe url segment. + /// The safe URL segment. string CleanStringForUrlSegment(string text, string culture); /// /// Cleans a string, in the context of the invariant culture, to produce a string that can safely be used as a filename, - /// both internally (on disk) and externally (as a url). + /// both internally (on disk) and externally (as a URL). /// /// The text to filter. /// The safe filename. @@ -52,7 +52,7 @@ /// /// Cleans a string, in the context of a specified culture, to produce a string that can safely be used as a filename, - /// both internally (on disk) and externally (as a url). + /// both internally (on disk) and externally (as a URL). /// /// The text to filter. /// The culture. diff --git a/src/Umbraco.Core/Strings/IUrlSegmentProvider.cs b/src/Umbraco.Core/Strings/IUrlSegmentProvider.cs index 12d2ef9a17..fddb87e716 100644 --- a/src/Umbraco.Core/Strings/IUrlSegmentProvider.cs +++ b/src/Umbraco.Core/Strings/IUrlSegmentProvider.cs @@ -4,20 +4,20 @@ using Umbraco.Core.Models; namespace Umbraco.Core.Strings { /// - /// Provides url segments for content. + /// Provides URL segments for content. /// /// Url segments should comply with IETF RFCs regarding content, encoding, etc. public interface IUrlSegmentProvider { /// - /// Gets the url segment for a specified content and culture. + /// Gets the URL segment for a specified content and culture. /// /// The content. /// The culture. - /// The url segment. - /// This is for when Umbraco is capable of managing more than one url + /// The URL segment. + /// This is for when Umbraco is capable of managing more than one URL /// per content, in 1-to-1 multilingual configurations. Then there would be one - /// url per culture. + /// URL per culture. string GetUrlSegment(IContentBase content, string culture = null); // TODO: For the 301 tracking, we need to add another extended interface to this so that diff --git a/src/Umbraco.Core/Sync/ApplicationUrlHelper.cs b/src/Umbraco.Core/Sync/ApplicationUrlHelper.cs index 2a558f85aa..52af734f1c 100644 --- a/src/Umbraco.Core/Sync/ApplicationUrlHelper.cs +++ b/src/Umbraco.Core/Sync/ApplicationUrlHelper.cs @@ -9,7 +9,7 @@ using Umbraco.Core.Logging; namespace Umbraco.Core.Sync { /// - /// A helper used to determine the current server umbraco application url. + /// A helper used to determine the current server umbraco application URL. /// public static class ApplicationUrlHelper { @@ -17,12 +17,12 @@ namespace Umbraco.Core.Sync private static readonly Type TypeOfApplicationUrlHelper = typeof(ApplicationUrlHelper); /// - /// Gets or sets a custom provider for the umbraco application url. + /// Gets or sets a custom provider for the umbraco application URL. /// /// /// Receives the current request as a parameter, and it may be null. Must return a properly - /// formatted url with scheme and umbraco dir and no trailing slash eg "http://www.mysite.com/umbraco", - /// or null. To be used in auto-load-balancing scenarios where the application url is not + /// formatted URL with scheme and umbraco dir and no trailing slash eg "http://www.mysite.com/umbraco", + /// or null. To be used in auto-load-balancing scenarios where the application URL is not /// in config files but is determined programmatically. /// Must be assigned before resolution is frozen. /// @@ -38,7 +38,7 @@ namespace Umbraco.Core.Sync umbracoApplicationUrl = ApplicationUrlProvider?.Invoke(request); if (string.IsNullOrWhiteSpace(umbracoApplicationUrl) == false) { - umbracoApplicationUrl = umbracoApplicationUrl.TrimEnd('/'); + umbracoApplicationUrl = umbracoApplicationUrl.TrimEnd(Constants.CharArrays.ForwardSlash); logger.Info(TypeOfApplicationUrlHelper, "ApplicationUrl: {UmbracoAppUrl} (provider)", umbracoApplicationUrl); return umbracoApplicationUrl; } @@ -61,13 +61,13 @@ namespace Umbraco.Core.Sync var url = settings.WebRouting.UmbracoApplicationUrl; if (url.IsNullOrWhiteSpace() == false) { - var umbracoApplicationUrl = url.TrimEnd('/'); + var umbracoApplicationUrl = url.TrimEnd(Constants.CharArrays.ForwardSlash); logger.Info(TypeOfApplicationUrlHelper, "ApplicationUrl: {UmbracoAppUrl} (using web.routing/@umbracoApplicationUrl)", umbracoApplicationUrl); return umbracoApplicationUrl; } // try the server registrar - // which is assumed to return a url that: + // which is assumed to return a URL that: // - end with SystemDirectories.Umbraco // - contain a scheme // - end or not with a slash, it will be taken care of @@ -75,7 +75,7 @@ namespace Umbraco.Core.Sync url = serverRegistrar.GetCurrentServerUmbracoApplicationUrl(); if (url.IsNullOrWhiteSpace() == false) { - var umbracoApplicationUrl = url.TrimEnd('/'); + var umbracoApplicationUrl = url.TrimEnd(Constants.CharArrays.ForwardSlash); logger.Info(TypeOfApplicationUrlHelper, "ApplicationUrl: {UmbracoAppUrl} (IServerRegistrar)", umbracoApplicationUrl); return umbracoApplicationUrl; } @@ -100,7 +100,7 @@ namespace Umbraco.Core.Sync var ssl = globalSettings.UseHttps ? "s" : ""; // force, whatever the first request var url = "http" + ssl + "://" + request.ServerVariables["SERVER_NAME"] + port + IOHelper.ResolveUrl(SystemDirectories.Umbraco); - return url.TrimEnd('/'); + return url.TrimEnd(Constants.CharArrays.ForwardSlash); } } } diff --git a/src/Umbraco.Core/Sync/DatabaseServerRegistrar.cs b/src/Umbraco.Core/Sync/DatabaseServerRegistrar.cs index 276ce6097c..a4315c33e9 100644 --- a/src/Umbraco.Core/Sync/DatabaseServerRegistrar.cs +++ b/src/Umbraco.Core/Sync/DatabaseServerRegistrar.cs @@ -46,11 +46,11 @@ namespace Umbraco.Core.Sync } /// - /// Gets the current umbraco application url. + /// Gets the current umbraco application URL. /// public string GetCurrentServerUmbracoApplicationUrl() { - // this registrar does not provide the umbraco application url + // this registrar does not provide the umbraco application URL return null; } diff --git a/src/Umbraco.Core/Sync/IServerRegistrar.cs b/src/Umbraco.Core/Sync/IServerRegistrar.cs index a4edb1d0e9..8bc8021eec 100644 --- a/src/Umbraco.Core/Sync/IServerRegistrar.cs +++ b/src/Umbraco.Core/Sync/IServerRegistrar.cs @@ -18,11 +18,11 @@ namespace Umbraco.Core.Sync ServerRole GetCurrentServerRole(); /// - /// Gets the current umbraco application url. + /// Gets the current umbraco application URL. /// /// - /// If the registrar does not provide the umbraco application url, should return null. - /// Must return null, or a url that ends with SystemDirectories.Umbraco, and contains a scheme, eg "http://www.mysite.com/umbraco". + /// If the registrar does not provide the umbraco application URL, should return null. + /// Must return null, or a URL that ends with SystemDirectories.Umbraco, and contains a scheme, eg "http://www.mysite.com/umbraco". /// string GetCurrentServerUmbracoApplicationUrl(); } diff --git a/src/Umbraco.Core/Udi.cs b/src/Umbraco.Core/Udi.cs index b11ce250ad..4fafbc0aeb 100644 --- a/src/Umbraco.Core/Udi.cs +++ b/src/Umbraco.Core/Udi.cs @@ -171,7 +171,7 @@ namespace Umbraco.Core throw new FormatException(string.Format("Unknown entity type \"{0}\".", entityType)); } - var path = uri.AbsolutePath.TrimStart('/'); + var path = uri.AbsolutePath.TrimStart(Constants.CharArrays.ForwardSlash); if (udiType == UdiType.GuidUdi) { diff --git a/src/Umbraco.Core/UdiGetterExtensions.cs b/src/Umbraco.Core/UdiGetterExtensions.cs index 5a5ccf5574..4eba1858a6 100644 --- a/src/Umbraco.Core/UdiGetterExtensions.cs +++ b/src/Umbraco.Core/UdiGetterExtensions.cs @@ -154,7 +154,7 @@ namespace Umbraco.Core public static StringUdi GetUdi(this Stylesheet entity) { if (entity == null) throw new ArgumentNullException("entity"); - return new StringUdi(Constants.UdiEntityType.Stylesheet, entity.Path.TrimStart('/')).EnsureClosed(); + return new StringUdi(Constants.UdiEntityType.Stylesheet, entity.Path.TrimStart(Constants.CharArrays.ForwardSlash)).EnsureClosed(); } /// @@ -165,7 +165,7 @@ namespace Umbraco.Core public static StringUdi GetUdi(this Script entity) { if (entity == null) throw new ArgumentNullException("entity"); - return new StringUdi(Constants.UdiEntityType.Script, entity.Path.TrimStart('/')).EnsureClosed(); + return new StringUdi(Constants.UdiEntityType.Script, entity.Path.TrimStart(Constants.CharArrays.ForwardSlash)).EnsureClosed(); } /// @@ -204,7 +204,7 @@ namespace Umbraco.Core ? Constants.UdiEntityType.PartialViewMacro : Constants.UdiEntityType.PartialView; - return new StringUdi(entityType, entity.Path.TrimStart('/')).EnsureClosed(); + return new StringUdi(entityType, entity.Path.TrimStart(Constants.CharArrays.ForwardSlash)).EnsureClosed(); } /// diff --git a/src/Umbraco.Core/UdiRange.cs b/src/Umbraco.Core/UdiRange.cs index b70cf43d18..857e818ce6 100644 --- a/src/Umbraco.Core/UdiRange.cs +++ b/src/Umbraco.Core/UdiRange.cs @@ -70,7 +70,7 @@ namespace Umbraco.Core } var udiUri = uri.Query == string.Empty ? uri : new UriBuilder(uri) { Query = string.Empty }.Uri; - return new UdiRange(Udi.Create(udiUri), uri.Query.TrimStart('?')); + return new UdiRange(Udi.Create(udiUri), uri.Query.TrimStart(Constants.CharArrays.QuestionMark)); } public override string ToString() diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index c5a953631a..eb51e20f5d 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -128,21 +128,52 @@ --> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -237,6 +268,7 @@ + diff --git a/src/Umbraco.Core/UriExtensions.cs b/src/Umbraco.Core/UriExtensions.cs index 1198b26e0f..60d9cd6ead 100644 --- a/src/Umbraco.Core/UriExtensions.cs +++ b/src/Umbraco.Core/UriExtensions.cs @@ -43,8 +43,8 @@ namespace Umbraco.Core { applicationPath = applicationPath ?? string.Empty; - var fullUrlPath = url.AbsolutePath.TrimStart(new[] {'/'}); - var appPath = applicationPath.TrimStart(new[] {'/'}); + var fullUrlPath = url.AbsolutePath.TrimStart(Constants.CharArrays.ForwardSlash); + var appPath = applicationPath.TrimStart(Constants.CharArrays.ForwardSlash); var urlPath = fullUrlPath.TrimStart(appPath).EnsureStartsWith('/'); //check if this is in the umbraco back office @@ -93,7 +93,7 @@ namespace Umbraco.Core // Umbraco/MYPLUGINAREA/MYCONTROLLERNAME/{action}/{id} // so if the path contains at a minimum 3 parts: Umbraco + MYPLUGINAREA + MYCONTROLLERNAME then we will have to assume it is a // plugin controller for the front-end. - if (urlPath.Split(new[] {'/'}, StringSplitOptions.RemoveEmptyEntries).Length >= 3) + if (urlPath.Split(Constants.CharArrays.ForwardSlash, StringSplitOptions.RemoveEmptyEntries).Length >= 3) { return false; } diff --git a/src/Umbraco.Core/Xml/XmlHelper.cs b/src/Umbraco.Core/Xml/XmlHelper.cs index d6461ec8c6..f070e08d4f 100644 --- a/src/Umbraco.Core/Xml/XmlHelper.cs +++ b/src/Umbraco.Core/Xml/XmlHelper.cs @@ -53,7 +53,7 @@ namespace Umbraco.Core.Xml public static bool IsXmlWhitespace(string s) { // as per xml 1.1 specs - anything else is significant whitespace - s = s.Trim(' ', '\t', '\r', '\n'); + s = s.Trim(Constants.CharArrays.XmlWhitespaceChars); return s.Length == 0; } diff --git a/src/Umbraco.Examine/ContentIndexPopulator.cs b/src/Umbraco.Examine/ContentIndexPopulator.cs index 99ff4d7f87..e10ad012c4 100644 --- a/src/Umbraco.Examine/ContentIndexPopulator.cs +++ b/src/Umbraco.Examine/ContentIndexPopulator.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Linq; using Examine; -using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Core.Services; using Umbraco.Core.Persistence; @@ -15,7 +14,7 @@ namespace Umbraco.Examine /// /// Performs the data lookups required to rebuild a content index /// - public class ContentIndexPopulator : IndexPopulator + public class ContentIndexPopulator : IndexPopulator { private readonly IContentService _contentService; private readonly IValueSetBuilder _contentValueSetBuilder; @@ -58,6 +57,12 @@ namespace Umbraco.Examine _parentId = parentId; } + public override bool IsRegistered(IUmbracoContentIndex2 index) + { + // check if it should populate based on published values + return _publishedValuesOnly == index.PublishedValuesOnly; + } + protected override void PopulateIndexes(IReadOnlyList indexes) { if (indexes.Count == 0) return; @@ -70,31 +75,89 @@ namespace Umbraco.Examine { contentParentId = _parentId.Value; } + + if (_publishedValuesOnly) + { + IndexPublishedContent(contentParentId, pageIndex, pageSize, indexes); + } + else + { + IndexAllContent(contentParentId, pageIndex, pageSize, indexes); + } + } + + protected void IndexAllContent(int contentParentId, int pageIndex, int pageSize, IReadOnlyList indexes) + { IContent[] content; do { - if (!_publishedValuesOnly) - { - content = _contentService.GetPagedDescendants(contentParentId, pageIndex, pageSize, out _).ToArray(); - } - else - { - //add the published filter - //note: We will filter for published variants in the validator - content = _contentService.GetPagedDescendants(contentParentId, pageIndex, pageSize, out _, - _publishedQuery, Ordering.By("Path", Direction.Ascending)).ToArray(); - } + content = _contentService.GetPagedDescendants(contentParentId, pageIndex, pageSize, out _).ToArray(); if (content.Length > 0) { + var valueSets = _contentValueSetBuilder.GetValueSets(content).ToList(); + // ReSharper disable once PossibleMultipleEnumeration foreach (var index in indexes) - index.IndexItems(_contentValueSetBuilder.GetValueSets(content)); + { + index.IndexItems(valueSets); + } + } + + pageIndex++; + } while (content.Length == pageSize); + } + + protected void IndexPublishedContent(int contentParentId, int pageIndex, int pageSize, + IReadOnlyList indexes) + { + IContent[] content; + + var publishedPages = new HashSet(); + + do + { + //add the published filter + //note: We will filter for published variants in the validator + content = _contentService.GetPagedDescendants(contentParentId, pageIndex, pageSize, out _, _publishedQuery, + Ordering.By("Path", Direction.Ascending)).ToArray(); + + + if (content.Length > 0) + { + var indexableContent = new List(); + + foreach (var item in content) + { + if (item.Level == 1) + { + // first level pages are always published so no need to filter them + indexableContent.Add(item); + publishedPages.Add(item.Id); + } + else + { + if (publishedPages.Contains(item.ParentId)) + { + // only index when parent is published + publishedPages.Add(item.Id); + indexableContent.Add(item); + } + } + } + + var valueSets = _contentValueSetBuilder.GetValueSets(indexableContent.ToArray()).ToList(); + + // ReSharper disable once PossibleMultipleEnumeration + foreach (var index in indexes) + index.IndexItems(valueSets); } pageIndex++; } while (content.Length == pageSize); } } + + } diff --git a/src/Umbraco.Examine/ContentValueSetBuilder.cs b/src/Umbraco.Examine/ContentValueSetBuilder.cs index b8477a9047..dfd64fcd47 100644 --- a/src/Umbraco.Examine/ContentValueSetBuilder.cs +++ b/src/Umbraco.Examine/ContentValueSetBuilder.cs @@ -60,6 +60,11 @@ namespace Umbraco.Examine scope.Complete(); } + return GetValueSetsEnumerable(content, creatorIds, writerIds); + } + + private IEnumerable GetValueSetsEnumerable(IContent[] content, Dictionary creatorIds, Dictionary writerIds) + { // TODO: There is a lot of boxing going on here and ultimately all values will be boxed by Lucene anyways // but I wonder if there's a way to reduce the boxing that we have to do or if it will matter in the end since // Lucene will do it no matter what? One idea was to create a `FieldValue` struct which would contain `object`, `object[]`, `ValueType` and `ValueType[]` diff --git a/src/Umbraco.Examine/IUmbracoContentIndex.cs b/src/Umbraco.Examine/IUmbracoContentIndex.cs index 3181ff663e..b718ec6bce 100644 --- a/src/Umbraco.Examine/IUmbracoContentIndex.cs +++ b/src/Umbraco.Examine/IUmbracoContentIndex.cs @@ -2,6 +2,20 @@ using Examine; namespace Umbraco.Examine { + /// + /// Marker interface for indexes of Umbraco content + /// + /// + /// This is a backwards compat change, in next major version remove the need for this and just have a single interface + /// + public interface IUmbracoContentIndex2 : IUmbracoContentIndex + { + bool PublishedValuesOnly { get; } + } + + /// + /// Marker interface for indexes of Umbraco content + /// public interface IUmbracoContentIndex : IIndex { diff --git a/src/Umbraco.Examine/IndexPopulator.cs b/src/Umbraco.Examine/IndexPopulator.cs index f9d4d85dc8..bfd757f9be 100644 --- a/src/Umbraco.Examine/IndexPopulator.cs +++ b/src/Umbraco.Examine/IndexPopulator.cs @@ -13,9 +13,16 @@ namespace Umbraco.Examine { public override bool IsRegistered(IIndex index) { - if (base.IsRegistered(index)) return true; - return index is TIndex; + if (base.IsRegistered(index)) + return true; + + if (!(index is TIndex casted)) + return false; + + return IsRegistered(casted); } + + public virtual bool IsRegistered(TIndex index) => true; } public abstract class IndexPopulator : IIndexPopulator diff --git a/src/Umbraco.Examine/IndexRebuilder.cs b/src/Umbraco.Examine/IndexRebuilder.cs index 786aecac71..b14ff25c57 100644 --- a/src/Umbraco.Examine/IndexRebuilder.cs +++ b/src/Umbraco.Examine/IndexRebuilder.cs @@ -3,6 +3,8 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Examine; +using Umbraco.Core.Composing; +using Umbraco.Core.Logging; namespace Umbraco.Examine { @@ -12,12 +14,20 @@ namespace Umbraco.Examine /// public class IndexRebuilder { + private readonly IProfilingLogger _logger; private readonly IEnumerable _populators; public IExamineManager ExamineManager { get; } + [Obsolete("Use constructor with all dependencies")] public IndexRebuilder(IExamineManager examineManager, IEnumerable populators) + : this(Current.ProfilingLogger, examineManager, populators) + { + } + + public IndexRebuilder(IProfilingLogger logger, IExamineManager examineManager, IEnumerable populators) { _populators = populators; + _logger = logger; ExamineManager = examineManager; } @@ -50,8 +60,18 @@ namespace Umbraco.Examine index.CreateIndex(); // clear the index } - //run the populators in parallel against all indexes - Parallel.ForEach(_populators, populator => populator.Populate(indexes)); + // run each populator over the indexes + foreach(var populator in _populators) + { + try + { + populator.Populate(indexes); + } + catch (Exception e) + { + _logger.Error(e, "Index populating failed for populator {Populator}", populator.GetType()); + } + } } } diff --git a/src/Umbraco.Examine/UmbracoContentIndex.cs b/src/Umbraco.Examine/UmbracoContentIndex.cs index e266ca789d..88033b1407 100644 --- a/src/Umbraco.Examine/UmbracoContentIndex.cs +++ b/src/Umbraco.Examine/UmbracoContentIndex.cs @@ -17,7 +17,7 @@ namespace Umbraco.Examine /// /// An indexer for Umbraco content and media /// - public class UmbracoContentIndex : UmbracoExamineIndex, IUmbracoContentIndex + public class UmbracoContentIndex : UmbracoExamineIndex, IUmbracoContentIndex2 { public const string VariesByCultureFieldName = SpecialFieldPrefix + "VariesByCulture"; protected ILocalizationService LanguageService { get; } @@ -62,7 +62,7 @@ namespace Umbraco.Examine /// protected override void PerformIndexItems(IEnumerable values, Action onComplete) { - //We don't want to re-enumerate this list, but we need to split it into 2x enumerables: invalid and valid items. + // We don't want to re-enumerate this list, but we need to split it into 2x enumerables: invalid and valid items. // The Invalid items will be deleted, these are items that have invalid paths (i.e. moved to the recycle bin, etc...) // Then we'll index the Value group all together. // We return 0 or 1 here so we can order the results and do the invalid first and then the valid. @@ -80,7 +80,7 @@ namespace Umbraco.Examine || !validator.ValidateProtectedContent(path, v.Category)) ? 0 : 1; - }); + }).ToList(); var hasDeletes = false; var hasUpdates = false; @@ -99,7 +99,7 @@ namespace Umbraco.Examine { hasUpdates = true; //these are the valid ones, so just index them all at once - base.PerformIndexItems(group, onComplete); + base.PerformIndexItems(group.ToList(), onComplete); } } diff --git a/src/Umbraco.ModelsBuilder.Embedded/BackOffice/ContentTypeModelValidatorBase.cs b/src/Umbraco.ModelsBuilder.Embedded/BackOffice/ContentTypeModelValidatorBase.cs index 15ca2cca24..4051bc3bd5 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/BackOffice/ContentTypeModelValidatorBase.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/BackOffice/ContentTypeModelValidatorBase.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; using Umbraco.Core; @@ -25,6 +26,13 @@ namespace Umbraco.ModelsBuilder.Embedded.BackOffice //don't do anything if we're not enabled if (!_config.Enable) yield break; + //list of reserved/disallowed aliases for content/media/member types - more can be added as the need arises + var reservedModelAliases = new[] { "system" }; + if(reservedModelAliases.Contains(model.Alias, StringComparer.OrdinalIgnoreCase)) + { + yield return new ValidationResult($"The model alias {model.Alias} is a reserved term and cannot be used", new[] { "Alias" }); + } + var properties = model.Groups.SelectMany(x => x.Properties) .Where(x => x.Inherited == false) .ToArray(); diff --git a/src/Umbraco.ModelsBuilder.Embedded/Building/TextBuilder.cs b/src/Umbraco.ModelsBuilder.Embedded/Building/TextBuilder.cs index 7ef8112d11..e59900bdc4 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/Building/TextBuilder.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/Building/TextBuilder.cs @@ -446,7 +446,7 @@ namespace Umbraco.ModelsBuilder.Embedded.Building { WriteNonGenericClrType(sb, type.Substring(0, p)); sb.Append("<"); - var args = type.Substring(p + 1).TrimEnd('>').Split(','); // fixme will NOT work with nested generic types + var args = type.Substring(p + 1).TrimEnd(Umbraco.Core.Constants.CharArrays.GreaterThan).Split(Umbraco.Core.Constants.CharArrays.Comma); // fixme will NOT work with nested generic types for (var i = 0; i < args.Length; i++) { if (i > 0) sb.Append(", "); diff --git a/src/Umbraco.ModelsBuilder.Embedded/Compose/DisabledModelsBuilderComponent.cs b/src/Umbraco.ModelsBuilder.Embedded/Compose/DisabledModelsBuilderComponent.cs index c599785711..826b392592 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/Compose/DisabledModelsBuilderComponent.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/Compose/DisabledModelsBuilderComponent.cs @@ -8,7 +8,7 @@ namespace Umbraco.ModelsBuilder.Embedded.Compose /// /// Special component used for when MB is disabled with the legacy MB is detected /// - internal class DisabledModelsBuilderComponent : IComponent + public sealed class DisabledModelsBuilderComponent : IComponent { private readonly UmbracoFeatures _features; diff --git a/src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderComponent.cs b/src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderComponent.cs index 0e41c9ac62..a475e4e1c3 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderComponent.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderComponent.cs @@ -50,32 +50,38 @@ namespace Umbraco.ModelsBuilder.Embedded.Compose } public void Terminate() - { } + { + ServerVariablesParser.Parsing -= ServerVariablesParser_Parsing; + ContentModelBinder.ModelBindingException -= ContentModelBinder_ModelBindingException; + FileService.SavingTemplate -= FileService_SavingTemplate; + } private void InstallServerVars() { - // register our url - for the backoffice api - ServerVariablesParser.Parsing += (sender, serverVars) => - { - if (!serverVars.ContainsKey("umbracoUrls")) - throw new ArgumentException("Missing umbracoUrls."); - var umbracoUrlsObject = serverVars["umbracoUrls"]; - if (umbracoUrlsObject == null) - throw new ArgumentException("Null umbracoUrls"); - if (!(umbracoUrlsObject is Dictionary umbracoUrls)) - throw new ArgumentException("Invalid umbracoUrls"); + // register our URL - for the backoffice API + ServerVariablesParser.Parsing += ServerVariablesParser_Parsing; + } - if (!serverVars.ContainsKey("umbracoPlugins")) - throw new ArgumentException("Missing umbracoPlugins."); - if (!(serverVars["umbracoPlugins"] is Dictionary umbracoPlugins)) - throw new ArgumentException("Invalid umbracoPlugins"); + private void ServerVariablesParser_Parsing(object sender, Dictionary serverVars) + { + if (!serverVars.ContainsKey("umbracoUrls")) + throw new ArgumentException("Missing umbracoUrls."); + var umbracoUrlsObject = serverVars["umbracoUrls"]; + if (umbracoUrlsObject == null) + throw new ArgumentException("Null umbracoUrls"); + if (!(umbracoUrlsObject is Dictionary umbracoUrls)) + throw new ArgumentException("Invalid umbracoUrls"); - if (HttpContext.Current == null) throw new InvalidOperationException("HttpContext is null"); - var urlHelper = new UrlHelper(new RequestContext(new HttpContextWrapper(HttpContext.Current), new RouteData())); + if (!serverVars.ContainsKey("umbracoPlugins")) + throw new ArgumentException("Missing umbracoPlugins."); + if (!(serverVars["umbracoPlugins"] is Dictionary umbracoPlugins)) + throw new ArgumentException("Invalid umbracoPlugins"); - umbracoUrls["modelsBuilderBaseUrl"] = urlHelper.GetUmbracoApiServiceBaseUrl(controller => controller.BuildModels()); - umbracoPlugins["modelsBuilder"] = GetModelsBuilderSettings(); - }; + if (HttpContext.Current == null) throw new InvalidOperationException("HttpContext is null"); + var urlHelper = new UrlHelper(new RequestContext(new HttpContextWrapper(HttpContext.Current), new RouteData())); + + umbracoUrls["modelsBuilderBaseUrl"] = urlHelper.GetUmbracoApiServiceBaseUrl(controller => controller.BuildModels()); + umbracoPlugins["modelsBuilder"] = GetModelsBuilderSettings(); } private Dictionary GetModelsBuilderSettings() diff --git a/src/Umbraco.ModelsBuilder.Embedded/PureLiveModelFactory.cs b/src/Umbraco.ModelsBuilder.Embedded/PureLiveModelFactory.cs index 0e125759c6..912d0e3363 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/PureLiveModelFactory.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/PureLiveModelFactory.cs @@ -542,8 +542,10 @@ namespace Umbraco.ModelsBuilder.Embedded if (modelInfos.TryGetValue(typeName, out var modelInfo)) throw new InvalidOperationException($"Both types {type.FullName} and {modelInfo.ModelType.FullName} want to be a model type for content type with alias \"{typeName}\"."); - // fixme use Core's ReflectionUtilities.EmitCtor !! + // TODO: use Core's ReflectionUtilities.EmitCtor !! // Yes .. DynamicMethod is uber slow + // TODO: But perhaps https://docs.microsoft.com/en-us/dotnet/api/system.reflection.emit.constructorbuilder?view=netcore-3.1 is better still? + // See CtorInvokeBenchmarks var meth = new DynamicMethod(string.Empty, typeof(IPublishedElement), ctorArgTypes, type.Module, true); var gen = meth.GetILGenerator(); gen.Emit(OpCodes.Ldarg_0); diff --git a/src/Umbraco.TestData/LoadTestController.cs b/src/Umbraco.TestData/LoadTestController.cs new file mode 100644 index 0000000000..97665dd084 --- /dev/null +++ b/src/Umbraco.TestData/LoadTestController.cs @@ -0,0 +1,371 @@ +using System; +using System.Threading; +using System.Linq; +using System.Web.Mvc; +using Umbraco.Core.Services; +using Umbraco.Core.Models; +using System.Web; +using System.Web.Hosting; +using System.Web.Routing; +using System.Diagnostics; +using Umbraco.Core.Composing; +using System.Configuration; + +// see https://github.com/Shazwazza/UmbracoScripts/tree/master/src/LoadTesting + +namespace Umbraco.TestData +{ + public class LoadTestController : Controller + { + public LoadTestController(ServiceContext serviceContext) + { + _serviceContext = serviceContext; + } + + private static readonly Random _random = new Random(); + private static readonly object _locko = new object(); + + private static volatile int _containerId = -1; + + private const string _containerAlias = "LoadTestContainer"; + private const string _contentAlias = "LoadTestContent"; + private const int _textboxDefinitionId = -88; + private const int _maxCreate = 1000; + + private static readonly string HeadHtml = @" + + LoadTest + + + +
+

LoadTest

+
" + System.Configuration.ConfigurationManager.AppSettings["umbracoConfigurationStatus"] + @"
+
+"; + + private const string FootHtml = @" +"; + + private static readonly string _containerTemplateText = @" +@inherits Umbraco.Web.Mvc.UmbracoViewPage +@{ + Layout = null; + var container = Umbraco.ContentAtRoot().OfTypes(""" + _containerAlias + @""").FirstOrDefault(); + var contents = container.Children().ToArray(); + var groups = contents.GroupBy(x => x.Value(""origin"")); + var id = contents.Length > 0 ? contents[0].Id : -1; + var wurl = Request.QueryString[""u""] == ""1""; + var missing = contents.Length > 0 && contents[contents.Length - 1].Id - contents[0].Id >= contents.Length; +} +" + HeadHtml + @" +
+@contents.Length items +
    +@foreach (var group in groups) +{ +
  • @group.Key: @group.Count()
  • +} +
+
+@foreach (var content in contents) +{ + while (content.Id > id) + { +
@id :: MISSING
+ id++; + } + if (wurl) + { +
@content.Id :: @content.Name :: @content.Url
+ } + else + { +
@content.Id :: @content.Name
+ } id++; +} +
+" + FootHtml; + private readonly ServiceContext _serviceContext; + + private ActionResult ContentHtml(string s) + { + return Content(HeadHtml + s + FootHtml); + } + + public ActionResult Index() + { + var res = EnsureInitialize(); + if (res != null) return res; + + var html = @"Welcome. You can: + +"; + + return ContentHtml(html); + } + + private ActionResult EnsureInitialize() + { + if (_containerId > 0) return null; + + lock (_locko) + { + if (_containerId > 0) return null; + + var contentTypeService = _serviceContext.ContentTypeService; + var contentType = contentTypeService.Get(_contentAlias); + if (contentType == null) + return ContentHtml("Not installed, first you must install."); + + var containerType = contentTypeService.Get(_containerAlias); + if (containerType == null) + return ContentHtml("Panic! Container type is missing."); + + var contentService = _serviceContext.ContentService; + var container = contentService.GetPagedOfType(containerType.Id, 0, 100, out _, null).FirstOrDefault(); + if (container == null) + return ContentHtml("Panic! Container is missing."); + + _containerId = container.Id; + return null; + } + } + + public ActionResult Install() + { + var dataTypeService = _serviceContext.DataTypeService; + + //var dataType = dataTypeService.GetAll(Constants.DataTypes.DefaultContentListView); + + + //if (!dict.ContainsKey("pageSize")) dict["pageSize"] = new PreValue("10"); + //dict["pageSize"].Value = "200"; + //dataTypeService.SavePreValues(dataType, dict); + + var contentTypeService = _serviceContext.ContentTypeService; + + var contentType = new ContentType(-1) + { + Alias = _contentAlias, + Name = "LoadTest Content", + Description = "Content for LoadTest", + Icon = "icon-document" + }; + var def = _serviceContext.DataTypeService.GetDataType(_textboxDefinitionId); + contentType.AddPropertyType(new PropertyType(def) + { + Name = "Origin", + Alias = "origin", + Description = "The origin of the content.", + }); + contentTypeService.Save(contentType); + + var containerTemplate = ImportTemplate(_serviceContext, + "LoadTestContainer", "LoadTestContainer", _containerTemplateText); + + var containerType = new ContentType(-1) + { + Alias = _containerAlias, + Name = "LoadTest Container", + Description = "Container for LoadTest content", + Icon = "icon-document", + AllowedAsRoot = true, + IsContainer = true + }; + containerType.AllowedContentTypes = containerType.AllowedContentTypes.Union(new[] + { + new ContentTypeSort(new Lazy(() => contentType.Id), 0, contentType.Alias), + }); + containerType.AllowedTemplates = containerType.AllowedTemplates.Union(new[] { containerTemplate }); + containerType.SetDefaultTemplate(containerTemplate); + contentTypeService.Save(containerType); + + var contentService = _serviceContext.ContentService; + var content = contentService.Create("LoadTestContainer", -1, _containerAlias); + contentService.SaveAndPublish(content); + + return ContentHtml("Installed."); + } + + public ActionResult Create(int n = 1, int r = 0, string o = null) + { + var res = EnsureInitialize(); + if (res != null) return res; + + if (r < 0) r = 0; + if (r > 100) r = 100; + var restart = GetRandom(0, 100) > (100 - r); + + var contentService = _serviceContext.ContentService; + + if (n < 1) n = 1; + if (n > _maxCreate) n = _maxCreate; + for (int i = 0; i < n; i++) + { + var name = Guid.NewGuid().ToString("N").ToUpper() + "-" + (restart ? "R" : "X") + "-" + o; + var content = contentService.Create(name, _containerId, _contentAlias); + content.SetValue("origin", o); + contentService.SaveAndPublish(content); + } + + if (restart) + DoRestart(); + + return ContentHtml("Created " + n + " content" + + (restart ? ", and restarted" : "") + + "."); + } + + private int GetRandom(int minValue, int maxValue) + { + lock (_locko) + { + return _random.Next(minValue, maxValue); + } + } + + public ActionResult Clear() + { + var res = EnsureInitialize(); + if (res != null) return res; + + var contentType = _serviceContext.ContentTypeService.Get(_contentAlias); + _serviceContext.ContentService.DeleteOfType(contentType.Id); + + return ContentHtml("Cleared."); + } + + private void DoRestart() + { + HttpContext.User = null; + System.Web.HttpContext.Current.User = null; + Thread.CurrentPrincipal = null; + HttpRuntime.UnloadAppDomain(); + } + + public ActionResult Restart() + { + DoRestart(); + + return ContentHtml("Restarted."); + } + + public ActionResult Die() + { + var timer = new System.Threading.Timer(_ => + { + throw new Exception("die!"); + }); + timer.Change(100, 0); + + return ContentHtml("Dying."); + } + + public ActionResult Domains() + { + var currentDomain = AppDomain.CurrentDomain; + var currentName = currentDomain.FriendlyName; + var pos = currentName.IndexOf('-'); + if (pos > 0) currentName = currentName.Substring(0, pos); + + var text = new System.Text.StringBuilder(); + text.Append("
Process ID: " + Process.GetCurrentProcess().Id + "
"); + text.Append("
"); + text.Append("
IIS Site: " + HostingEnvironment.ApplicationHost.GetSiteName() + "
"); + text.Append("
App ID: " + currentName + "
"); + //text.Append("
AppPool: " + Zbu.WebManagement.AppPoolHelper.GetCurrentApplicationPoolName() + "
"); + text.Append("
"); + + text.Append("
Domains:
    "); + text.Append("
  • Not implemented.
  • "); + /* + foreach (var domain in Zbu.WebManagement.AppDomainHelper.GetAppDomains().OrderBy(x => x.Id)) + { + var name = domain.FriendlyName; + pos = name.IndexOf('-'); + if (pos > 0) name = name.Substring(0, pos); + text.Append("
  • " + +"[" + domain.Id + "] " + name + + (domain.IsDefaultAppDomain() ? " (default)" : "") + + (domain.Id == currentDomain.Id ? " (current)" : "") + + "
  • "); + } + */ + text.Append("
"); + + return ContentHtml(text.ToString()); + } + + public ActionResult Recycle() + { + return ContentHtml("Not implemented—please use IIS console."); + } + + private static Template ImportTemplate(ServiceContext svces, string name, string alias, string text, ITemplate master = null) + { + var t = new Template(name, alias) { Content = text }; + if (master != null) + t.SetMasterTemplate(master); + svces.FileService.SaveTemplate(t); + return t; + } + } + + public class TestComponent : IComponent + { + public void Initialize() + { + if (ConfigurationManager.AppSettings["Umbraco.TestData.Enabled"] != "true") + return; + + RouteTable.Routes.MapRoute( + name: "LoadTest", + url: "LoadTest/{action}", + defaults: new + { + controller = "LoadTest", + action = "Index" + }, + namespaces: new[] { "Umbraco.TestData" } + ); + } + + public void Terminate() + { + } + } + + public class TestComposer : ComponentComposer, IUserComposer + { + public override void Compose(Composition composition) + { + base.Compose(composition); + + if (ConfigurationManager.AppSettings["Umbraco.TestData.Enabled"] != "true") + return; + + composition.Register(typeof(LoadTestController), Lifetime.Request); + } + } +} diff --git a/src/Umbraco.TestData/Umbraco.TestData.csproj b/src/Umbraco.TestData/Umbraco.TestData.csproj index d61321ebb8..a3753cc17b 100644 --- a/src/Umbraco.TestData/Umbraco.TestData.csproj +++ b/src/Umbraco.TestData/Umbraco.TestData.csproj @@ -41,6 +41,7 @@ + diff --git a/src/Umbraco.TestData/UmbracoTestDataController.cs b/src/Umbraco.TestData/UmbracoTestDataController.cs index 02949d5345..26f52da478 100644 --- a/src/Umbraco.TestData/UmbracoTestDataController.cs +++ b/src/Umbraco.TestData/UmbracoTestDataController.cs @@ -172,7 +172,7 @@ namespace Umbraco.TestData { var imageUrl = faker.Image.PicsumUrl(); - // we are appending a &ext=.jpg to the end of this for a reason. The result of this url will be something like: + // we are appending a &ext=.jpg to the end of this for a reason. The result of this URL will be something like: // https://picsum.photos/640/480/?image=106 // and due to the way that we detect images there must be an extension so we'll change it to // https://picsum.photos/640/480/?image=106&ext=.jpg @@ -208,7 +208,8 @@ namespace Umbraco.TestData var docType = GetOrCreateContentType(); var parent = Services.ContentService.Create(company, -1, docType.Alias); - parent.SetValue("review", faker.Rant.Review()); + // give it some reasonable data (100 reviews) + parent.SetValue("review", string.Join(" ", Enumerable.Range(0, 100).Select(x => faker.Rant.Review()))); parent.SetValue("desc", company); parent.SetValue("media", imageIds[random.Next(0, imageIds.Count - 1)]); Services.ContentService.Save(parent); @@ -218,7 +219,8 @@ namespace Umbraco.TestData return CreateHierarchy(parent, count, depth, currParent => { var content = Services.ContentService.Create(faker.Commerce.ProductName(), currParent, docType.Alias); - content.SetValue("review", faker.Rant.Review()); + // give it some reasonable data (100 reviews) + content.SetValue("review", string.Join(" ", Enumerable.Range(0, 100).Select(x => faker.Rant.Review()))); content.SetValue("desc", string.Join(", ", Enumerable.Range(0, 5).Select(x => faker.Commerce.ProductAdjective()))); content.SetValue("media", imageIds[random.Next(0, imageIds.Count - 1)]); diff --git a/src/Umbraco.Tests.AcceptanceTest/README.md b/src/Umbraco.Tests.AcceptanceTest/README.md index e8699b0733..f4edaa92ea 100644 --- a/src/Umbraco.Tests.AcceptanceTest/README.md +++ b/src/Umbraco.Tests.AcceptanceTest/README.md @@ -1,35 +1,36 @@ -# Umbraco Acceptance Tests - -### Prerequisite -- NodeJS 12+ -- A running installed Umbraco on url: [https://localhost:44331](https://localhost:44331) (Default development port) - - Install using a `SqlServer`/`LocalDb` as the tests execute too fast for `SqlCE` to handle. -- User information in `cypress.env.json` (See [Getting started](#getting-started)) - -### Getting started -The tests is located in the project/folder named `Umbraco.Tests.AcceptanceTests`. Ensur to run `npm install` in that folder, or let your IDE do that. - -Next, it is important you create a new file in the root of the project called `cypress.env.json`. -This file is already added to `.gitignore` and can contain values that is different for each developer machine. - -The file need the following content: -``` -{ - "username": "", - "password": "" -} -``` -Replace the `` and `` placeholders with correct info. - - - -### Executing tests - -There exists two npm scripts, that can be used to execute the test. - -1. `npm run test` - - Executes the tests headless. -1. `npm run ui` - - Executes the tests in a browser handled by a cypress application. - - In case of errors it is recommended to use the UI to debug. +# Umbraco Acceptance Tests + +### Prerequisites +- NodeJS 12+ +- A running installed Umbraco on url: [https://localhost:44331](https://localhost:44331) (Default development port) + - Install using a `SqlServer`/`LocalDb` as the tests execute too fast for `SqlCE` to handle. + +### Getting started +The tests are located in the project/folder as `Umbraco.Tests.AcceptanceTests`. Make sure you run `npm install` in that folder, or let your IDE do that. + +The script will ask you to enter the username and password for a superadmin user of your Umbraco CMS. + +### Executing tests +There are two npm scripts that can be used to execute the test: + +1. `npm run test` + - Executes the tests headless. +1. `npm run ui` + - Executes the tests in a browser handled by a cypress application. + + In case of errors it is recommended to use the UI to debug. + +### Enviroment Configuration + +The enviroment configuration is begin setup by the npm installation script. +This results in the creation of this file: `cypress.env.json`. +This file is already added to `.gitignore` and can contain values that are different for each developer machine. + +The file has the following content: +``` +{ + "username": "", + "password": "" +} +``` +You can change this if you like or run the config script to reset the values, type "npm run config" in your terminal. diff --git a/src/Umbraco.Tests.AcceptanceTest/config.js b/src/Umbraco.Tests.AcceptanceTest/config.js new file mode 100644 index 0000000000..5297cbccbc --- /dev/null +++ b/src/Umbraco.Tests.AcceptanceTest/config.js @@ -0,0 +1,49 @@ +const prompt = require('prompt'); +const fs = require('fs'); + +const properties = [ + { + description: 'Enter your superadmin username/email', + name: 'username', + required: true + }, + { + description: 'Enter your superadmin password', + name: 'password', + hidden: true, + required: true + }, + { + description: 'Enter CMS URL, or leave empty for default(https://localhost:44331)', + name: 'baseUrl' + } +]; + + +const configPath = './cypress.env.json' + +console.log("Configure your test enviroment") + +prompt.start(); + +prompt.get(properties, function (error, result) { + if (error) { return onError(error); } + +var fileContent = `{ + "username": "${result.username}", + "password": "${result.password}"${ + result.baseUrl && `, + "baseUrl": "${result.baseUrl}"` + } +}`; + + fs.writeFile(configPath, fileContent, function (error) { + if (error) return console.error(error); + console.log('Configuration saved'); + }); +}); + +function onError(error) { + console.error(error); + return true; +} diff --git a/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Content/content.ts b/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Content/content.ts new file mode 100644 index 0000000000..22f1f883d0 --- /dev/null +++ b/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Content/content.ts @@ -0,0 +1,599 @@ +/// +import { DocumentTypeBuilder, ContentBuilder, AliasHelper } from 'umbraco-cypress-testhelpers'; +context('Content', () => { + + beforeEach(() => { + cy.umbracoLogin(Cypress.env('username'), Cypress.env('password')); + }); + + function refreshContentTree(){ + // Refresh to update the tree + cy.get('li .umb-tree-root:contains("Content")').should("be.visible").rightclick(); + cy.umbracoContextMenuAction("action-refreshNode").click(); + // We have to wait in case the execution is slow, otherwise we'll try and click the item before it appears in the UI + cy.get('.umb-tree-item__inner').should('exist', {timeout: 10000}); + } + + it('Copy content', () => { + const rootDocTypeName = "Test document type"; + const childDocTypeName = "Child test document type"; + const nodeName = "1) Home"; + const childNodeName = "1) Child"; + const anotherNodeName = "2) Home"; + + const childDocType = new DocumentTypeBuilder() + .withName(childDocTypeName) + .build(); + + cy.deleteAllContent(); + cy.umbracoEnsureDocumentTypeNameNotExists(rootDocTypeName); + cy.umbracoEnsureDocumentTypeNameNotExists(childDocTypeName); + + cy.saveDocumentType(childDocType).then((generatedChildDocType) => { + const rootDocTypeAlias; + const createdChildDocType = generatedChildDocType; + + cy.get('li .umb-tree-root:contains("Content")').should("be.visible"); + + const rootDocType = new DocumentTypeBuilder() + .withName(rootDocTypeName) + .withAllowAsRoot(true) + .withAllowedContentTypes(createdChildDocType["id"]) + .build(); + + cy.saveDocumentType(rootDocType).then((generatedRootDocType) => { + rootDocTypeAlias = generatedRootDocType["alias"]; + + const rootContentNode = new ContentBuilder() + .withContentTypeAlias(rootDocTypeAlias) + .withAction("saveNew") + .addVariant() + .withName(nodeName) + .withSave(true) + .done() + .build(); + + cy.saveContent(rootContentNode).then((contentNode) => { + // Add an item under root node + const childContentNode = new ContentBuilder() + .withContentTypeAlias(createdChildDocType["alias"]) + .withAction("saveNew") + .withParent(contentNode["id"]) + .addVariant() + .withName(childNodeName) + .withSave(true) + .done() + .build(); + + cy.saveContent(childContentNode); + }); + + const anotherRootContentNode = new ContentBuilder() + .withContentTypeAlias(rootDocTypeAlias) + .withAction("saveNew") + .addVariant() + .withName(anotherNodeName) + .withSave(true) + .done() + .build(); + + cy.saveContent(anotherRootContentNode); + }); + }); + + // Refresh to update the tree + refreshContentTree(); + + // Copy node + cy.umbracoTreeItem("content", [nodeName, childNodeName]).rightclick({ force: true }); + cy.umbracoContextMenuAction("action-copy").click(); + cy.get('.umb-pane [data-element="tree-item-' + anotherNodeName + '"]').click(); + cy.get('.umb-dialog-footer > .btn-primary').click(); + + // Assert + cy.get('.alert-success').should('exist'); + + // Clean up (content is automatically deleted when document types are gone) + cy.umbracoEnsureDocumentTypeNameNotExists(rootDocTypeName); + cy.umbracoEnsureDocumentTypeNameNotExists(childDocTypeName); + }); + + it('Move content', () => { + const rootDocTypeName = "Test document type"; + const childDocTypeName = "Child test document type"; + const nodeName = "1) Home"; + const childNodeName = "1) Child"; + const anotherNodeName = "2) Home"; + + const childDocType = new DocumentTypeBuilder() + .withName(childDocTypeName) + .build(); + + cy.deleteAllContent(); + cy.umbracoEnsureDocumentTypeNameNotExists(rootDocTypeName); + cy.umbracoEnsureDocumentTypeNameNotExists(childDocTypeName); + + cy.saveDocumentType(childDocType).then((generatedChildDocType) => { + const rootDocTypeAlias; + const createdChildDocType = generatedChildDocType; + + cy.get('li .umb-tree-root:contains("Content")').should("be.visible"); + + const rootDocType = new DocumentTypeBuilder() + .withName(rootDocTypeName) + .withAllowAsRoot(true) + .withAllowedContentTypes(createdChildDocType["id"]) + .build(); + + cy.saveDocumentType(rootDocType).then((generatedRootDocType) => { + rootDocTypeAlias = generatedRootDocType["alias"]; + + const rootContentNode = new ContentBuilder() + .withContentTypeAlias(rootDocTypeAlias) + .withAction("saveNew") + .addVariant() + .withName(nodeName) + .withSave(true) + .done() + .build(); + + cy.saveContent(rootContentNode).then((contentNode) => { + // Add an item under root node + const childContentNode = new ContentBuilder() + .withContentTypeAlias(createdChildDocType["alias"]) + .withAction("saveNew") + .withParent(contentNode["id"]) + .addVariant() + .withName(childNodeName) + .withSave(true) + .done() + .build(); + + cy.saveContent(childContentNode); + }); + + const anotherRootContentNode = new ContentBuilder() + .withContentTypeAlias(rootDocTypeAlias) + .withAction("saveNew") + .addVariant() + .withName(anotherNodeName) + .withSave(true) + .done() + .build(); + + cy.saveContent(anotherRootContentNode); + }); + }); + + // Refresh to update the tree + refreshContentTree(); + + // Move node + cy.umbracoTreeItem("content", [nodeName, childNodeName]).rightclick({ force: true }); + cy.umbracoContextMenuAction("action-move").click(); + cy.get('.umb-pane [data-element="tree-item-' + anotherNodeName + '"]').click(); + cy.get('.umb-dialog-footer > .btn-primary').click(); + + // Assert + cy.get('.alert-success').should('exist'); + + // Clean up (content is automatically deleted when document types are gone) + cy.umbracoEnsureDocumentTypeNameNotExists(rootDocTypeName); + cy.umbracoEnsureDocumentTypeNameNotExists(childDocTypeName); + }); + + it('Sort content', () => { + const rootDocTypeName = "Test document type"; + const childDocTypeName = "Child test document type"; + const nodeName = "1) Home"; + const firstChildNodeName = "1) Child"; + const secondChildNodeName = "2) Child"; + + const childDocType = new DocumentTypeBuilder() + .withName(childDocTypeName) + .build(); + + cy.deleteAllContent(); + cy.umbracoEnsureDocumentTypeNameNotExists(rootDocTypeName); + cy.umbracoEnsureDocumentTypeNameNotExists(childDocTypeName); + + cy.saveDocumentType(childDocType).then((generatedChildDocType) => { + const createdChildDocType = generatedChildDocType; + + cy.get('li .umb-tree-root:contains("Content")').should("be.visible"); + + const rootDocType = new DocumentTypeBuilder() + .withName(rootDocTypeName) + .withAllowAsRoot(true) + .withAllowedContentTypes(createdChildDocType["id"]) + .build(); + + cy.saveDocumentType(rootDocType).then((generatedRootDocType) => { + const parentId; + + const rootContentNode = new ContentBuilder() + .withContentTypeAlias(generatedRootDocType["alias"]) + .withAction("saveNew") + .addVariant() + .withName(nodeName) + .withSave(true) + .done() + .build(); + + cy.saveContent(rootContentNode).then((contentNode) => { + parentId = contentNode["id"]; + + // Add an item under root node + const firstChildContentNode = new ContentBuilder() + .withContentTypeAlias(createdChildDocType["alias"]) + .withAction("saveNew") + .withParent(parentId) + .addVariant() + .withName(firstChildNodeName) + .withSave(true) + .done() + .build(); + + cy.saveContent(firstChildContentNode); + + // Add a second item under root node + const secondChildContentNode = new ContentBuilder() + .withContentTypeAlias(createdChildDocType["alias"]) + .withAction("saveNew") + .withParent(parentId) + .addVariant() + .withName(secondChildNodeName) + .withSave(true) + .done() + .build(); + + cy.saveContent(secondChildContentNode); + }); + }); + }); + + // Refresh to update the tree + refreshContentTree(); + + // Sort nodes + cy.umbracoTreeItem("content", [nodeName]).rightclick({ force: true }); + cy.umbracoContextMenuAction("action-sort").click(); + + //Drag and drop + cy.get('.ui-sortable .ui-sortable-handle :nth-child(2)').eq(0).trigger('mousedown', { which: 1 }) + cy.get('.ui-sortable .ui-sortable-handle :nth-child(2)').eq(1).trigger("mousemove").trigger("mouseup") + + // Save and close dialog + cy.get('.umb-modalcolumn .btn-success').click(); + cy.get('.umb-modalcolumn .btn-link').click(); + + // Assert + cy.get('.umb-tree-item [node="child"]').eq(0).should('contain.text', secondChildNodeName); + cy.get('.umb-tree-item [node="child"]').eq(1).should('contain.text', firstChildNodeName); + + // Clean up (content is automatically deleted when document types are gone) + cy.umbracoEnsureDocumentTypeNameNotExists(rootDocTypeName); + cy.umbracoEnsureDocumentTypeNameNotExists(childDocTypeName); + }); + + it('Rollback content', () => { + const rootDocTypeName = "Test document type"; + const initialNodeName = "Home node"; + const nodeName = "Home"; + + const rootDocType = new DocumentTypeBuilder() + .withName(rootDocTypeName) + .withAllowAsRoot(true) + .build(); + + cy.deleteAllContent(); + cy.umbracoEnsureDocumentTypeNameNotExists(rootDocTypeName); + + cy.saveDocumentType(rootDocType).then((generatedRootDocType) => { + const rootContentNode = new ContentBuilder() + .withContentTypeAlias(generatedRootDocType["alias"]) + .addVariant() + .withName(initialNodeName) + .withSave(true) + .done() + .build(); + + cy.saveContent(rootContentNode) + }); + + // Refresh to update the tree + refreshContentTree(); + + // Access node + cy.umbracoTreeItem("content", [initialNodeName]).click(); + + // Edit header + cy.get('#headerName').clear(); + cy.umbracoEditorHeaderName(nodeName); + + // Save and publish + cy.get('.btn-success').first().click(); + cy.umbracoSuccessNotification().should('be.visible'); + + // Rollback + cy.get('.umb-box-header :button').click(); + + cy.get('.umb-box-content > div > .input-block-level') + .find('option[label*=' + new Date().getDate() + ']') + .then(elements => { + const option = elements[elements.length - 1].getAttribute('value'); + cy.get('.umb-box-content > div > .input-block-level') + .select(option); + }); + + cy.get('.umb-editor-footer-content__right-side > [button-style="success"] > .umb-button > .btn-success').click(); + + cy.reload(); + + // Assert + cy.get('.history').find('.umb-badge').contains('Save').should('be.visible'); + cy.get('.history').find('.umb-badge').contains('Rollback').should('be.visible'); + cy.get('#headerName').should('have.value', initialNodeName); + + // Clean up (content is automatically deleted when document types are gone) + cy.umbracoEnsureDocumentTypeNameNotExists(rootDocTypeName); + }); + + it('View audit trail', () => { + const rootDocTypeName = "Test document type"; + const nodeName = "Home"; + const labelName = "Name"; + + const rootDocType = new DocumentTypeBuilder() + .withName(rootDocTypeName) + .withAllowAsRoot(true) + .addGroup() + .addTextBoxProperty() + .withLabel(labelName) + .done() + .done() + .build(); + + cy.deleteAllContent(); + cy.umbracoEnsureDocumentTypeNameNotExists(rootDocTypeName); + + cy.saveDocumentType(rootDocType).then((generatedRootDocType) => { + const rootContentNode = new ContentBuilder() + .withContentTypeAlias(generatedRootDocType["alias"]) + .addVariant() + .withName(nodeName) + .withSave(true) + .done() + .build(); + + cy.saveContent(rootContentNode) + }); + + // Refresh to update the tree + refreshContentTree(); + + // Access node + cy.umbracoTreeItem("content", [nodeName]).click(); + + // Navigate to Info app + cy.get(':nth-child(2) > [ng-show="navItem.alias !== \'more\'"]').click(); + + // Assert + cy.get('.history').should('exist'); + + // Clean up (content is automatically deleted when document types are gone) + cy.umbracoEnsureDocumentTypeNameNotExists(rootDocTypeName); + }); + + it('Save draft', () => { + const rootDocTypeName = "Test document type"; + const nodeName = "Home"; + + const rootDocType = new DocumentTypeBuilder() + .withName(rootDocTypeName) + .withAllowAsRoot(true) + .build(); + + cy.deleteAllContent(); + cy.umbracoEnsureDocumentTypeNameNotExists(rootDocTypeName); + + cy.saveDocumentType(rootDocType).then((generatedRootDocType) => { + const rootContentNode = new ContentBuilder() + .withContentTypeAlias(generatedRootDocType["alias"]) + .withAction("saveNew") + .addVariant() + .withName(nodeName) + .withSave(true) + .done() + .build(); + + cy.saveContent(rootContentNode) + }); + + // Refresh to update the tree + refreshContentTree(); + + // Access node + cy.umbracoTreeItem("content", [nodeName]).click(); + + // Assert + cy.get('[data-element="node-info-status"]').find('.umb-badge').should('contain.text', "Draft"); + + // Clean up (content is automatically deleted when document types are gone) + cy.umbracoEnsureDocumentTypeNameNotExists(rootDocTypeName); + }); + + it('Preview draft', () => { + const rootDocTypeName = "Test document type"; + const nodeName = "Home"; + + const rootDocType = new DocumentTypeBuilder() + .withName(rootDocTypeName) + .withAllowAsRoot(true) + .build(); + + cy.deleteAllContent(); + cy.umbracoEnsureDocumentTypeNameNotExists(rootDocTypeName); + + cy.saveDocumentType(rootDocType).then((generatedRootDocType) => { + const rootContentNode = new ContentBuilder() + .withContentTypeAlias(generatedRootDocType["alias"]) + .withAction("saveNew") + .addVariant() + .withName(nodeName) + .withSave(true) + .done() + .build(); + + cy.saveContent(rootContentNode) + }); + + // Refresh to update the tree + refreshContentTree(); + + // Access node + cy.umbracoTreeItem("content", [nodeName]).click(); + + // Preview + cy.get('[alias="preview"]').should('be.visible').click(); + + // Assert + cy.umbracoSuccessNotification({ multiple: true }).should('be.visible'); + + // Clean up (content is automatically deleted when document types are gone) + cy.umbracoEnsureDocumentTypeNameNotExists(rootDocTypeName); + }); + + it('Publish draft', () => { + const rootDocTypeName = "Test document type"; + const nodeName = "Home"; + + const rootDocType = new DocumentTypeBuilder() + .withName(rootDocTypeName) + .withAllowAsRoot(true) + .build(); + + cy.deleteAllContent(); + cy.umbracoEnsureDocumentTypeNameNotExists(rootDocTypeName); + + cy.saveDocumentType(rootDocType).then((generatedRootDocType) => { + const rootContentNode = new ContentBuilder() + .withContentTypeAlias(generatedRootDocType["alias"]) + .addVariant() + .withName(nodeName) + .withSave(true) + .done() + .build(); + + cy.saveContent(rootContentNode) + }); + + // Refresh to update the tree + refreshContentTree(); + + // Access node + cy.umbracoTreeItem("content", [nodeName]).click(); + + // Assert + cy.get('[data-element="node-info-status"]').find('.umb-badge').should('contain.text', "Published"); + + // Clean up (content is automatically deleted when document types are gone) + cy.umbracoEnsureDocumentTypeNameNotExists(rootDocTypeName); + }); + + it('Content with contentpicker', () => { + const pickerDocTypeName = 'Content picker doc type'; + const pickerDocTypeAlias = AliasHelper.toAlias(pickerDocTypeName); + const pickedDocTypeName = 'Picked content document type'; + + cy.deleteAllContent(); + cy.umbracoEnsureDocumentTypeNameNotExists(pickerDocTypeName); + cy.umbracoEnsureTemplateNameNotExists(pickerDocTypeName); + cy.umbracoEnsureDocumentTypeNameNotExists(pickedDocTypeName); + + // Create the content type and content we'll be picking from. + const pickedDocType = new DocumentTypeBuilder() + .withName(pickedDocTypeName) + .withAllowAsRoot(true) + .addGroup() + .addTextBoxProperty() + .withAlias('text') + .done() + .done() + .build(); + + cy.saveDocumentType(pickedDocType).then((generatedType) => { + const pickedContentNode = new ContentBuilder() + .withContentTypeAlias(generatedType["alias"]) + .withAction("publishNew") + .addVariant() + .withName('Content to pick') + .withSave(true) + .withPublish(true) + .addProperty() + .withAlias('text') + .withValue('Acceptance test') + .done() + .withSave(true) + .withPublish(true) + .done() + .build(); + cy.saveContent(pickedContentNode); + }); + + // Create the doctype with a the picker + const pickerDocType = new DocumentTypeBuilder() + .withName(pickerDocTypeName) + .withAlias(pickerDocTypeAlias) + .withAllowAsRoot(true) + .withDefaultTemplate(pickerDocTypeAlias) + .addGroup() + .withName('ContentPickerGroup') + .addContentPickerProperty() + .withAlias('picker') + .done() + .done() + .build(); + + cy.saveDocumentType(pickerDocType); + + // Edit it the template to allow us to verify the rendered view. + cy.editTemplate(pickerDocTypeName, `@inherits Umbraco.Web.Mvc.UmbracoViewPage + @using ContentModels = Umbraco.Web.PublishedModels; + @{ + Layout = null; + } + + @{ + IPublishedContent typedContentPicker = Model.Value("picker"); + if (typedContentPicker != null) + { +

@typedContentPicker.Value("text")

+ } + }`); + + // Create content with content picker + cy.get('.umb-tree-root-link').rightclick(); + cy.get('.-opens-dialog > .umb-action-link').click(); + cy.get('[data-element="action-create-' + pickerDocTypeAlias + '"] > .umb-action-link').click(); + // Fill out content + cy.umbracoEditorHeaderName('ContentPickerContent'); + cy.get('.umb-node-preview-add').click(); + // Should really try and find a better way to do this, but umbracoTreeItem tries to click the content pane in the background + cy.get('[ng-if="vm.treeReady"] > .umb-tree > [ng-if="!tree.root.containsGroups"] > .umb-animated > .umb-tree-item__inner').click(); + // We have to wait for the picked content to show up or it wont be added. + cy.get('.umb-node-preview__description').should('be.visible'); + //save and publish + cy.umbracoButtonByLabelKey('buttons_saveAndPublish').click(); + cy.umbracoSuccessNotification().should('be.visible'); + + // Assert + cy.log('Checking that content is rendered correctly.') + const expectedContent = '

Acceptance test

' + cy.umbracoVerifyRenderedViewContent('contentpickercontent', expectedContent, true).should('be.true'); + // clean + cy.umbracoEnsureDocumentTypeNameNotExists(pickerDocTypeName); + cy.umbracoEnsureTemplateNameNotExists(pickerDocTypeName); + cy.umbracoEnsureDocumentTypeNameNotExists(pickedDocTypeName); + }); +}); diff --git a/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Members/members.js b/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Members/members.js index aeb4576ccd..4d468a0cf0 100644 --- a/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Members/members.js +++ b/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Members/members.js @@ -8,7 +8,8 @@ context('Members', () => { it('Create member', () => { const name = "Alice Bobson"; const email = "alice-bobson@acceptancetest.umbraco"; - const password = "$AUlkoF*St0kgPiyyVEk5iU5JWdN*F7&@OSl5Y4pOofnidfifkBj5Ns2ONv%FzsTl36V1E924Gw97zcuSeT7UwK&qb5l&O9h!d!w"; + const password = "$AUlkoF*St0kgPiyyVEk5iU5JWdN*F7&"; + const passwordTimeout = 20000 cy.umbracoEnsureMemberEmailNotExists(email); cy.umbracoSection('member'); @@ -24,8 +25,8 @@ context('Members', () => { cy.get('input#_umb_login').clear().type(email); cy.get('input#_umb_email').clear().type(email); - cy.get('input#password').clear().type(password); - cy.get('input#confirmPassword').clear().type(password); + cy.get('input#password').clear().type(password, { timeout: passwordTimeout }); + cy.get('input#confirmPassword').clear().type(password, { timeout: passwordTimeout }); // Save cy.get('.btn-success').click(); diff --git a/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/dataTypes.ts b/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/dataTypes.ts index 5803810f54..53fd55a3fc 100644 --- a/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/dataTypes.ts +++ b/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/dataTypes.ts @@ -23,7 +23,7 @@ context('Data Types', () => { cy.umbracoEditorHeaderName(name); - cy.get('select[name="selectedEditor"]').select('Label'); + cy.get('select[name="selectedEditor"]', {timeout: 5000}).select('Label'); cy.get('.umb-property-editor select').select('Time'); diff --git a/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/documentTypes.ts b/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/documentTypes.ts index 5a5ba0b3e0..1a86e90852 100644 --- a/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/documentTypes.ts +++ b/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/documentTypes.ts @@ -10,6 +10,7 @@ context('Document Types', () => { const name = "Test document type"; cy.umbracoEnsureDocumentTypeNameNotExists(name); + cy.umbracoEnsureTemplateNameNotExists(name); cy.umbracoSection('settings'); cy.get('li .umb-tree-root:contains("Settings")').should("be.visible"); @@ -31,10 +32,10 @@ context('Document Types', () => { cy.get('[data-element="editor-add"]').click(); //Search for textstring - cy.get('.umb-search-field').type('Textstring'); + cy.get('#datatype-search').type('Textstring'); // Choose first item - cy.get('ul.umb-card-grid li a[title="Textstring"]').closest("li").click(); + cy.get('ul.umb-card-grid li [title="Textstring"]').closest("li").click(); // Save property cy.get('.btn-success').last().click(); @@ -44,6 +45,7 @@ context('Document Types', () => { //Assert cy.umbracoSuccessNotification().should('be.visible'); + cy.umbracoEnsureTemplateNameNotExists(name); //Clean up cy.umbracoEnsureDocumentTypeNameNotExists(name); @@ -68,7 +70,7 @@ context('Document Types', () => { cy.umbracoContextMenuAction("action-delete").click(); cy.get('label.checkbox').click(); - cy.umbracoButtonByLabelKey("general_ok").click(); + cy.umbracoButtonByLabelKey("delete").click(); cy.contains(name).should('not.exist'); diff --git a/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/mediaTypes.ts b/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/mediaTypes.ts index a963da754f..4064c1f41e 100644 --- a/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/mediaTypes.ts +++ b/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/mediaTypes.ts @@ -31,10 +31,10 @@ context('Media Types', () => { cy.get('[data-element="editor-add"]').click(); //Search for textstring - cy.get('.umb-search-field').type('Textstring'); + cy.get('#datatype-search').type('Textstring'); // Choose first item - cy.get('ul.umb-card-grid li a[title="Textstring"]').closest("li").click(); + cy.get('ul.umb-card-grid li [title="Textstring"]').closest("li").click(); // Save property cy.get('.btn-success').last().click(); diff --git a/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/memberTypes.ts b/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/memberTypes.ts index 53823f2a69..e84c29c0d8 100644 --- a/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/memberTypes.ts +++ b/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/memberTypes.ts @@ -29,10 +29,10 @@ context('Member Types', () => { cy.get('[data-element="editor-add"]').click(); //Search for textstring - cy.get('.umb-search-field').type('Textstring'); + cy.get('#datatype-search').type('Textstring'); // Choose first item - cy.get('ul.umb-card-grid li a[title="Textstring"]').closest("li").click(); + cy.get('ul.umb-card-grid li [title="Textstring"]').closest("li").click(); // Save property cy.get('.btn-success').last().click(); diff --git a/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/partialsViewMacroFiles.ts b/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/partialsViewMacroFiles.ts index f4c976de08..563ff77658 100644 --- a/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/partialsViewMacroFiles.ts +++ b/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/partialsViewMacroFiles.ts @@ -1,23 +1,34 @@ /// +import { PartialViewMacroBuilder } from "umbraco-cypress-testhelpers"; + context('Partial View Macro Files', () => { beforeEach(() => { cy.umbracoLogin(Cypress.env('username'), Cypress.env('password')); }); - it('Create new partial view macro', () => { - const name = "TestPartialViewMacro"; - const fileName = name + ".cshtml"; - - cy.umbracoEnsurePartialViewMacroFileNameNotExists(fileName); - cy.umbracoEnsureMacroNameNotExists(name); - + function openPartialViewMacroCreatePanel() { cy.umbracoSection('settings'); cy.get('li .umb-tree-root:contains("Settings")').should("be.visible"); cy.umbracoTreeItem("settings", ["Partial View Macro Files"]).rightclick(); - cy.umbracoContextMenuAction("action-create").click(); + } + + function cleanup(name, extension = ".cshtml") { + const fileName = name + extension; + + cy.umbracoEnsureMacroNameNotExists(name); + cy.umbracoEnsurePartialViewMacroFileNameNotExists(fileName); + } + + it('Create new partial view macro', () => { + const name = "TestPartialViewMacro"; + + cleanup(name); + + openPartialViewMacroCreatePanel(); + cy.get('.menu-label').first().click(); // TODO: Fucked we cant use something like cy.umbracoContextMenuAction("action-label").click(); //Type name @@ -28,10 +39,117 @@ context('Partial View Macro Files', () => { //Assert cy.umbracoSuccessNotification().should('be.visible'); + cy.umbracoMacroExists(name).then(exists => { expect(exists).to.be.true; }); //Clean up - cy.umbracoEnsurePartialViewMacroFileNameNotExists(fileName); - cy.umbracoEnsureMacroNameNotExists(name); - }); + cleanup(name); + }); + + it('Create new partial view macro without macro', () => { + const name = "TestPartialMacrolessMacro"; + + cleanup(name); + + openPartialViewMacroCreatePanel(); + + cy.get('.menu-label').eq(1).click(); + + // Type name + cy.umbracoEditorHeaderName(name); + + // Save + cy.get('.btn-success').click(); + + // Assert + cy.umbracoSuccessNotification().should('be.visible'); + cy.umbracoMacroExists(name).then(exists => { expect(exists).to.be.false; }); + + // Clean + cleanup(name); + }); + + it('Create new partial view macro from snippet', () => { + const name = "TestPartialFromSnippet"; + + cleanup(name); + + openPartialViewMacroCreatePanel(); + + cy.get('.menu-label').eq(2).click(); + + // Select snippet + cy.get('.menu-label').eq(1).click(); + + // Type name + cy.umbracoEditorHeaderName(name); + + // Save + cy.get('.btn-success').click(); + + // Assert + cy.umbracoSuccessNotification().should('be.visible'); + cy.umbracoMacroExists(name).then(exists => { expect(exists).to.be.true; }); + + // Clean + cleanup(name); + }); + + it('Delete partial view macro', () => { + const name = "TestDeletePartialViewMacro"; + const fullName = name + ".cshtml" + + cleanup(name); + + const partialViewMacro = new PartialViewMacroBuilder() + .withName(name) + .withContent("@inherits Umbraco.Web.Macros.PartialViewMacroPage") + .build(); + + cy.savePartialViewMacro(partialViewMacro); + + // Navigate to settings + cy.umbracoSection('settings'); + cy.get('li .umb-tree-root:contains("Settings")').should("be.visible"); + + // Delete partialViewMacro + cy.umbracoTreeItem("settings", ["Partial View Macro Files", fullName]).rightclick(); + cy.umbracoContextMenuAction("action-delete").click(); + cy.umbracoButtonByLabelKey("general_ok").click(); + + // Assert + cy.contains(fullName).should('not.exist'); + + // Clean + cleanup(name); + }); + + it('Edit partial view macro', () => { + const name = "TestPartialViewMacroEditable"; + const fullName = name + ".cshtml"; + + cleanup(name); + + const partialViewMacro = new PartialViewMacroBuilder() + .withName(name) + .withContent("@inherits Umbraco.Web.Macros.PartialViewMacroPage") + .build(); + + cy.savePartialViewMacro(partialViewMacro); + + // Navigate to settings + cy.umbracoSection('settings'); + cy.get('li .umb-tree-root:contains("Settings")').should("be.visible"); + cy.umbracoTreeItem("settings", ["Partial View Macro Files", fullName]).click(); + + // Type an edit + cy.get('.ace_text-input').type(" // test", {force:true} ); + // Save + cy.get('.btn-success').click(); + + // Assert + cy.umbracoSuccessNotification().should('be.visible'); + + cleanup(name); + }); }); diff --git a/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/partialsViews.ts b/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/partialsViews.ts index b644c6642b..068338f8fa 100644 --- a/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/partialsViews.ts +++ b/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/partialsViews.ts @@ -1,35 +1,145 @@ /// +import { PartialViewBuilder } from "umbraco-cypress-testhelpers"; + context('Partial Views', () => { - beforeEach(() => { - cy.umbracoLogin(Cypress.env('username'), Cypress.env('password')); - }); + beforeEach(() => { + cy.umbracoLogin(Cypress.env('username'), Cypress.env('password')); + }); - it('Create new empty partial view', () => { - const name = "TestPartialView"; - const fileName = name + ".cshtml"; + function navigateToSettings() { + cy.umbracoSection('settings'); + cy.get('li .umb-tree-root:contains("Settings")').should("be.visible"); + } - cy.umbracoEnsurePartialViewNameNotExists(fileName); + function openPartialViewsCreatePanel() { + navigateToSettings(); + cy.umbracoTreeItem("settings", ["Partial Views"]).rightclick(); + } - cy.umbracoSection('settings'); - cy.get('li .umb-tree-root:contains("Settings")').should("be.visible"); + it('Create new empty partial view', () => { + const name = "TestPartialView"; + const fileName = name + ".cshtml"; - cy.umbracoTreeItem("settings", ["Partial Views"]).rightclick(); + cy.umbracoEnsurePartialViewNameNotExists(fileName); - cy.umbracoContextMenuAction("action-create").click(); - cy.get('.menu-label').first().click(); // TODO: Fucked we cant use something like cy.umbracoContextMenuAction("action-mediaType").click(); + openPartialViewsCreatePanel(); - //Type name - cy.umbracoEditorHeaderName(name); + cy.umbracoContextMenuAction("action-create").click(); + cy.get('.menu-label').first().click(); // TODO: Fucked we cant use something like cy.umbracoContextMenuAction("action-mediaType").click(); - //Save - cy.get('.btn-success').click(); + //Type name + cy.umbracoEditorHeaderName(name); - //Assert - cy.umbracoSuccessNotification().should('be.visible'); + //Save + cy.get('.btn-success').click(); + + //Assert + cy.umbracoSuccessNotification().should('be.visible'); + cy.umbracoPartialViewExists(fileName).then(exists => { expect(exists).to.be.true; }); + + //Clean up + cy.umbracoEnsurePartialViewNameNotExists(fileName); + }); + + it('Create partial view from snippet', () => { + const name = "TestPartialViewFromSnippet"; + const fileName = name + ".cshtml"; + + cy.umbracoEnsurePartialViewNameNotExists(fileName); + + openPartialViewsCreatePanel(); + + cy.umbracoContextMenuAction("action-create").click(); + cy.get('.menu-label').eq(1).click(); + // Select snippet + cy.get('.menu-label').eq(2).click(); + + // Type name + cy.umbracoEditorHeaderName(name); + + // Save + cy.get('.btn-success').click(); + + // Assert + cy.umbracoSuccessNotification().should('be.visible'); + cy.umbracoPartialViewExists(fileName).then(exists => { expect(exists).to.be.true; }); + + // Clean up + cy.umbracoEnsurePartialViewNameNotExists(fileName); + }); + + it('Partial view with no name', () => { + openPartialViewsCreatePanel(); + + cy.umbracoContextMenuAction("action-create").click(); + cy.get('.menu-label').first().click(); + + // The test would fail intermittently, most likely because the editor didn't have time to load + // This should ensure that the editor is loaded and the test should no longer fail unexpectedly. + cy.get('.ace_content', {timeout: 5000}).should('exist'); + + // Click save + cy.get('.btn-success').click(); + + // Asserts + cy.umbracoErrorNotification().should('be.visible'); + }); + + it('Delete partial view', () => { + const name = "TestDeletePartialView"; + const fileName = name + ".cshtml"; + + cy.umbracoEnsurePartialViewNameNotExists(fileName); + + // Build and save partial view + const partialView = new PartialViewBuilder() + .withName(name) + .withContent("@inherits Umbraco.Web.Mvc.UmbracoViewPage") + .build(); + + cy.savePartialView(partialView); + + navigateToSettings(); + + // Delete partial view + cy.umbracoTreeItem("settings", ["Partial Views", fileName]).rightclick(); + cy.umbracoContextMenuAction("action-delete").click(); + cy.umbracoButtonByLabelKey("general_ok").click(); + + // Assert + cy.contains(fileName).should('not.exist'); + cy.umbracoPartialViewExists(fileName).then(exists => { expect(exists).to.be.false; }); + + // Clean + cy.umbracoEnsurePartialViewNameNotExists(fileName); + }); + + it('Edit partial view', () => { + const name = 'EditPartialView'; + const fileName = name + ".cshtml"; + + cy.umbracoEnsurePartialViewNameNotExists(fileName); + + const partialView = new PartialViewBuilder() + .withName(name) + .withContent("@inherits Umbraco.Web.Mvc.UmbracoViewPage\n") + .build(); + + cy.savePartialView(partialView); + + navigateToSettings(); + // Open partial view + cy.umbracoTreeItem("settings", ["Partial Views", fileName]).click(); + // Edit + cy.get('.ace_text-input').type("var num = 5;", {force:true} ); + cy.get('.btn-success').click(); + + // Assert + cy.umbracoSuccessNotification().should('be.visible'); + // Clean + cy.umbracoEnsurePartialViewNameNotExists(fileName); + }); - //Clean up - cy.umbracoEnsurePartialViewNameNotExists(fileName); - }); }); diff --git a/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/scripts.ts b/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/scripts.ts index 8cffd3e59b..430f8aa108 100644 --- a/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/scripts.ts +++ b/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/scripts.ts @@ -1,20 +1,26 @@ /// +import { ScriptBuilder } from "umbraco-cypress-testhelpers"; + context('Scripts', () => { beforeEach(() => { cy.umbracoLogin(Cypress.env('username'), Cypress.env('password')); }); + function navigateToSettings() { + cy.umbracoSection('settings'); + cy.get('li .umb-tree-root:contains("Settings")').should("be.visible"); + } + it('Create new JavaScript file', () => { const name = "TestScript"; const fileName = name + ".js"; - cy.umbracoEnsureScriptNameNotExists(fileName); + cy.umbracoEnsureScriptNameNotExists(fileName); - cy.umbracoSection('settings'); - cy.get('li .umb-tree-root:contains("Settings")').should("be.visible"); + navigateToSettings() - cy.umbracoTreeItem("settings", ["Stylesheets"]).rightclick(); + cy.umbracoTreeItem("settings", ["Scripts"]).rightclick(); cy.umbracoContextMenuAction("action-create").click(); cy.get('.menu-label').first().click(); // TODO: Fucked we cant use something like cy.umbracoContextMenuAction("action-mediaType").click(); @@ -27,9 +33,89 @@ context('Scripts', () => { //Assert cy.umbracoSuccessNotification().should('be.visible'); + cy.umbracoScriptExists(fileName).should('be.true'); + //Clean up cy.umbracoEnsureScriptNameNotExists(fileName); - }); + }); + it('Delete a JavaScript file', () => { + const name = "TestDeleteScriptFile"; + const fileName = name + ".js"; + + cy.umbracoEnsureScriptNameNotExists(fileName); + + const script = new ScriptBuilder() + .withName(name) + .withContent('alert("this is content");') + .build(); + + cy.saveScript(script); + + navigateToSettings() + + cy.umbracoTreeItem("settings", ["Scripts", fileName]).rightclick(); + cy.umbracoContextMenuAction("action-delete").click(); + cy.umbracoButtonByLabelKey("general_ok").click(); + + cy.contains(fileName).should('not.exist'); + cy.umbracoScriptExists(name).should('be.false'); + + cy.umbracoEnsureScriptNameNotExists(fileName); + }); + + it('Update JavaScript file', () => { + const name = "TestEditJavaScriptFile"; + const nameEdit = "Edited"; + let fileName = name + ".js"; + + const originalContent = 'console.log("A script);\n'; + const edit = 'alert("content");'; + const expected = originalContent + edit; + + cy.umbracoEnsureScriptNameNotExists(fileName); + + const script = new ScriptBuilder() + .withName(name) + .withContent(originalContent) + .build(); + cy.saveScript(script); + + navigateToSettings(); + cy.umbracoTreeItem("settings", ["Scripts", fileName]).click(); + + cy.get('.ace_text-input').type(edit, { force: true }); + + // Since scripts has no alias it should be safe to not use umbracoEditorHeaderName + // umbracoEditorHeaderName does not like {backspace} + cy.get('#headerName').type("{backspace}{backspace}{backspace}" + nameEdit).should('have.value', name+nameEdit); + fileName = name + nameEdit + ".js"; + cy.get('.btn-success').click(); + + cy.umbracoSuccessNotification().should('be.visible'); + cy.umbracoVerifyScriptContent(fileName, expected).should('be.true'); + + cy.umbracoEnsureScriptNameNotExists(fileName); + }); + + it('Can Delete folder', () => { + const folderName = "TestFolder"; + + // The way scripts and folders are fetched and deleted are identical + cy.umbracoEnsureScriptNameNotExists(folderName); + cy.saveFolder('scripts', folderName); + + navigateToSettings() + + cy.umbracoTreeItem("settings", ["Scripts", folderName]).rightclick(); + cy.umbracoContextMenuAction("action-delete").click(); + cy.umbracoButtonByLabelKey("general_ok").click(); + + cy.contains(folderName).should('not.exist'); + cy.umbracoScriptExists(folderName).should('be.false') + + // A script an a folder is the same thing in this case + cy.umbracoEnsureScriptNameNotExists(folderName); + }); }); diff --git a/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/templates.ts b/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/templates.ts index 6871db7ffe..c586384af7 100644 --- a/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/templates.ts +++ b/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/templates.ts @@ -1,57 +1,173 @@ /// -import {DocumentTypeBuilder, TemplateBuilder} from "umbraco-cypress-testhelpers"; +import { TemplateBuilder } from 'umbraco-cypress-testhelpers'; context('Templates', () => { - beforeEach(() => { - cy.umbracoLogin(Cypress.env('username'), Cypress.env('password')); - }); - - it('Create template', () => { - const name = "Test template"; - - cy.umbracoEnsureTemplateNameNotExists(name); + beforeEach(() => { + cy.umbracoLogin(Cypress.env('username'), Cypress.env('password')); + }); + function navigateToSettings() { cy.umbracoSection('settings'); cy.get('li .umb-tree-root:contains("Settings")').should("be.visible"); + } + function createTemplate() { + navigateToSettings(); cy.umbracoTreeItem("settings", ["Templates"]).rightclick(); - cy.umbracoContextMenuAction("action-create").click(); + } + it('Create template', () => { + const name = "Create template test"; + cy.umbracoEnsureTemplateNameNotExists(name); + + createTemplate(); //Type name cy.umbracoEditorHeaderName(name); - - //Save - cy.get("form[name='contentForm']").submit(); + // Save + // We must drop focus for the auto save event to occur. + cy.get('.btn-success').focus(); + // And then wait for the auto save event to finish by finding the page in the tree view. + // This is a bit of a roundabout way to find items in a treev view since we dont use umbracoTreeItem + // but we must be able to wait for the save evnent to finish, and we can't do that with umbracoTreeItem + cy.get('[data-element="tree-item-templates"] > :nth-child(2) > .umb-animated > .umb-tree-item__inner > .umb-tree-item__label') + .contains(name).should('be.visible', { timeout: 10000 }); + // Now that the auto save event has finished we can save + // and there wont be any duplicates or file in use errors. + cy.get('.btn-success').click(); //Assert cy.umbracoSuccessNotification().should('be.visible'); + // For some reason cy.umbracoErrorNotification tries to click the element which is not possible + // if it doesn't actually exist, making should('not.be.visible') impossible. + cy.get('.umb-notifications__notifications > .alert-error').should('not.exist'); //Clean up cy.umbracoEnsureTemplateNameNotExists(name); - }); + }); - it('Delete template', () => { - const name = "Test template"; + it('Unsaved changes stay', () => { + const name = "Templates Unsaved Changes Stay test"; + const edit = "var num = 5;"; cy.umbracoEnsureTemplateNameNotExists(name); const template = new TemplateBuilder() .withName(name) + .withContent('@inherits Umbraco.Web.Mvc.UmbracoViewPage\n') .build(); cy.saveTemplate(template); - cy.umbracoSection('settings'); - cy.get('li .umb-tree-root:contains("Settings")').should("be.visible"); + navigateToSettings(); - cy.umbracoTreeItem("settings", ["Templates", name]).rightclick(); - cy.umbracoContextMenuAction("action-delete").click(); + // Open partial view + cy.umbracoTreeItem("settings", ["Templates", name]).click(); + // Edit + cy.get('.ace_text-input').type(edit, {force:true} ); - cy.umbracoButtonByLabelKey("general_ok").click(); + // Navigate away + cy.umbracoSection('content'); + // Click stay button + cy.get('umb-button[label="Stay"] button:enabled').click(); - cy.contains(name).should('not.exist'); + // Assert + // That the same document is open + cy.get('#headerName').should('have.value', name); + cy.get('.ace_content').contains(edit); cy.umbracoEnsureTemplateNameNotExists(name); }); + + it('Discard unsaved changes', () => { + const name = "Discard changes test"; + const edit = "var num = 5;"; + + cy.umbracoEnsureTemplateNameNotExists(name); + + const template = new TemplateBuilder() + .withName(name) + .withContent('@inherits Umbraco.Web.Mvc.UmbracoViewPage\n') + .build(); + + cy.saveTemplate(template); + + navigateToSettings(); + + // Open partial view + cy.umbracoTreeItem("settings", ["Templates", name]).click(); + // Edit + cy.get('.ace_text-input').type(edit, {force:true} ); + + // Navigate away + cy.umbracoSection('content'); + // Click discard + cy.get('umb-button[label="Discard changes"] button:enabled').click(); + // Navigate back + cy.umbracoSection('settings'); + + // Asserts + cy.get('.ace_content').should('not.contain', edit); + // cy.umbracoPartialViewExists(fileName).then(exists => { expect(exists).to.be.false; }); TODO: Switch to template + cy.umbracoEnsureTemplateNameNotExists(name); + }); + + it('Insert macro', () => { + const name = 'InsertMacroTest'; + + cy.umbracoEnsureTemplateNameNotExists(name); + cy.umbracoEnsureMacroNameNotExists(name); + + const template = new TemplateBuilder() + .withName(name) + .withContent('') + .build(); + + cy.saveTemplate(template); + + cy.saveMacro(name); + + navigateToSettings(); + cy.umbracoTreeItem("settings", ["Templates", name]).click(); + // Insert macro + cy.umbracoButtonByLabelKey('general_insert').click(); + cy.get('.umb-insert-code-box__title').contains('Macro').click(); + cy.get('.umb-card-grid-item').contains(name).click(); + + // Assert + cy.get('.ace_content').contains('@Umbraco.RenderMacro("' + name + '")').should('exist'); + + // Clean + cy.umbracoEnsureTemplateNameNotExists(name); + cy.umbracoEnsureMacroNameNotExists(name); + }); + + it('Insert value', () => { + const name = 'Insert Value Test'; + + cy.umbracoEnsureTemplateNameNotExists(name); + + const partialView = new TemplateBuilder() + .withName(name) + .withContent('') + .build(); + + cy.saveTemplate(partialView); + + navigateToSettings(); + cy.umbracoTreeItem("settings", ["Templates", name]).click(); + + // Insert value + cy.umbracoButtonByLabelKey('general_insert').click(); + cy.get('.umb-insert-code-box__title').contains('Value').click(); + cy.get('select').select('umbracoBytes'); + cy.umbracoButtonByLabelKey('general_submit').click(); + + // assert + cy.get('.ace_content').contains('@Model.Value("umbracoBytes")').should('exist'); + + // Clean + cy.umbracoEnsureTemplateNameNotExists(name); + }); + }); diff --git a/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Tour/backofficeTour.ts b/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Tour/backofficeTour.ts new file mode 100644 index 0000000000..9bc1fff488 --- /dev/null +++ b/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Tour/backofficeTour.ts @@ -0,0 +1,103 @@ +/// + +context('Backoffice Tour', () => { + var timeout = 60000; + beforeEach(() => { + //arrange + cy.umbracoLogin(Cypress.env('username'), Cypress.env('password')); + resetTourData(); + }); + + it('Backoffice introduction tour should run', () => { + //act + cy.umbracoGlobalHelp().should("be.visible"); + cy.umbracoGlobalHelp().click(); + runBackOfficeIntroTour(0, 'Start'); + + //assert + cy.get('[data-element="help-tours"]').should("be.visible"); + cy.get('[data-element="help-tours"]').click(); + getPercentage(17, timeout); + }); + + it('Backoffice introduction tour should run then rerun', () => { + //act + cy.umbracoGlobalHelp().should("be.visible"); + cy.umbracoGlobalHelp().click(); + runBackOfficeIntroTour(0, 'Start', timeout); + runBackOfficeIntroTour(17, 'Rerun', timeout); + + //assert + cy.get('[data-element="help-tours"]').should("be.visible"); + cy.get('[data-element="help-tours"]').click(); + cy.umbracoGlobalHelp().should("be.visible"); + getPercentage(17, timeout); + }); + + afterEach(() => { + //cleanup + resetTourData(); + }); +}); + +function getPercentage(percentage, timeout) { + cy.get('[data-element="help-tours"] .umb-progress-circle', { timeout: timeout }).get('[percentage]').contains(percentage + '%'); +} + +function resetTourData() { + var tourStatus = + { + "alias": "umbIntroIntroduction", + "completed": false, + "disabled": false + }; + + cy.getCookie('UMB-XSRF-TOKEN', { log: false }).then((token) => { + cy.request({ + method: 'POST', + url: '/umbraco/backoffice/UmbracoApi/CurrentUser/PostSetUserTour', + followRedirect: false, + headers: { + ContentType: 'application/json', + 'X-UMB-XSRF-TOKEN': token.value, + }, + body: tourStatus, + }).then((resp) => { + return; + }); + }) +} + +function runBackOfficeIntroTour(percentageComplete, buttonText, timeout) { + cy.get('[data-element="help-tours"]').should("be.visible"); + cy.get('[data-element="help-tours"]').click(); + cy.get('[data-element="help-tours"] .umb-progress-circle', { timeout: timeout }).get('[percentage]').contains(percentageComplete + '%', { timeout: timeout }); + cy.get('[data-element="help-tours"]').click(); + cy.get('[data-element="tour-umbIntroIntroduction"] .umb-button').should("be.visible"); + cy.get('[data-element="tour-umbIntroIntroduction"] .umb-button').contains(buttonText); + cy.get('[data-element="tour-umbIntroIntroduction"] .umb-button').click(); + //act + cy.get('.umb-tour-step', { timeout: timeout }).should('be.visible'); + cy.get('.umb-tour-step__footer').should('be.visible'); + cy.get('.umb-tour-step__counter').should('be.visible'); + + for (let i = 1; i < 7; i++) { + cy.get('.umb-tour-step__counter').contains(i + '/12'); + cy.get('.umb-tour-step__footer .umb-button').should('be.visible').click(); + } + cy.umbracoGlobalUser().click() + cy.get('.umb-tour-step__counter', { timeout: timeout }).contains('8/12'); + cy.get('.umb-tour-step__footer .umb-button').should('be.visible').click(); + cy.get('.umb-tour-step__counter', { timeout: timeout }).contains('9/12'); + cy.get('.umb-overlay-drawer__align-right .umb-button').should('be.visible').click(); + cy.get('.umb-tour-step__counter', { timeout: timeout }).contains('10/12'); + cy.umbracoGlobalHelp().click() + + for (let i = 11; i < 13; i++) { + cy.get('.umb-tour-step__counter', { timeout: timeout }).contains(i + '/12'); + cy.get('.umb-tour-step__footer .umb-button').should('be.visible').click(); + } + cy.get('.umb-tour-step__footer .umb-button').should('be.visible').click(); + + cy.umbracoGlobalHelp().should("be.visible"); +} diff --git a/src/Umbraco.Tests.AcceptanceTest/cypress/plugins/index.js b/src/Umbraco.Tests.AcceptanceTest/cypress/plugins/index.js index aa9918d215..59283feec5 100644 --- a/src/Umbraco.Tests.AcceptanceTest/cypress/plugins/index.js +++ b/src/Umbraco.Tests.AcceptanceTest/cypress/plugins/index.js @@ -18,4 +18,11 @@ module.exports = (on, config) => { // `on` is used to hook into various events Cypress emits // `config` is the resolved Cypress config + const baseUrl = config.env.baseUrl || null; + + if (baseUrl) { + config.baseUrl = baseUrl; + } + + return config; } diff --git a/src/Umbraco.Tests.AcceptanceTest/package.json b/src/Umbraco.Tests.AcceptanceTest/package.json index 3b4177ce3f..378fe719fc 100644 --- a/src/Umbraco.Tests.AcceptanceTest/package.json +++ b/src/Umbraco.Tests.AcceptanceTest/package.json @@ -1,13 +1,16 @@ { "scripts": { + "postinstall": "node postinstall.js", + "config": "node config.js", "test": "npx cypress run", "ui": "npx cypress open" }, "devDependencies": { "cross-env": "^7.0.2", + "cypress": "^6.0.1", "ncp": "^2.0.0", - "cypress": "^4.9.0", - "umbraco-cypress-testhelpers": "1.0.0-beta-44" + "prompt": "^1.0.0", + "umbraco-cypress-testhelpers": "^1.0.0-beta-52" }, "dependencies": { "typescript": "^3.9.2" diff --git a/src/Umbraco.Tests.AcceptanceTest/postinstall.js b/src/Umbraco.Tests.AcceptanceTest/postinstall.js new file mode 100644 index 0000000000..6117ac84f0 --- /dev/null +++ b/src/Umbraco.Tests.AcceptanceTest/postinstall.js @@ -0,0 +1,14 @@ +const fs = require('fs'); + +const configPath = './cypress.env.json'; + +try { + if (fs.existsSync(configPath)) { + //file exists + console.log("Skips configuration as file already exists, run 'npm run config' to change your configuration."); + } else { + require('./config.js'); + } +} catch(err) { + console.error(err) +} diff --git a/src/Umbraco.Tests.Benchmarks/CtorInvokeBenchmarks.cs b/src/Umbraco.Tests.Benchmarks/CtorInvokeBenchmarks.cs index 8d15613791..37fe952851 100644 --- a/src/Umbraco.Tests.Benchmarks/CtorInvokeBenchmarks.cs +++ b/src/Umbraco.Tests.Benchmarks/CtorInvokeBenchmarks.cs @@ -16,6 +16,8 @@ namespace Umbraco.Tests.Benchmarks // - it's faster to get+invoke the ctor // - emitting the ctor is unless if invoked only 1 + // TODO: Check out https://docs.microsoft.com/en-us/dotnet/api/system.reflection.emit.constructorbuilder?view=netcore-3.1 ? + //[Config(typeof(Config))] [MemoryDiagnoser] public class CtorInvokeBenchmarks diff --git a/src/Umbraco.Tests.Benchmarks/Umbraco.Tests.Benchmarks.csproj b/src/Umbraco.Tests.Benchmarks/Umbraco.Tests.Benchmarks.csproj index 48d69cf757..df2af67573 100644 --- a/src/Umbraco.Tests.Benchmarks/Umbraco.Tests.Benchmarks.csproj +++ b/src/Umbraco.Tests.Benchmarks/Umbraco.Tests.Benchmarks.csproj @@ -93,4 +93,4 @@
- \ No newline at end of file + diff --git a/src/Umbraco.Tests/App.config b/src/Umbraco.Tests/App.config index 91047ba6a2..4af04827e3 100644 --- a/src/Umbraco.Tests/App.config +++ b/src/Umbraco.Tests/App.config @@ -1,59 +1,59 @@ - - + + - - - - - - - - - - - + + + + + + + + + + + - - - + + + - - - - - - + + + + + + - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + - - - + + + - - + + @@ -94,7 +94,11 @@ - - + + + + + + diff --git a/src/Umbraco.Tests/Cache/PublishedCache/PublishedMediaCacheTests.cs b/src/Umbraco.Tests/Cache/PublishedCache/PublishedMediaCacheTests.cs index f3d9f895ef..2346740ffb 100644 --- a/src/Umbraco.Tests/Cache/PublishedCache/PublishedMediaCacheTests.cs +++ b/src/Umbraco.Tests/Cache/PublishedCache/PublishedMediaCacheTests.cs @@ -42,9 +42,9 @@ namespace Umbraco.Tests.Cache.PublishedCache protected override void Initialize() { base.Initialize(); - var type = new AutoPublishedContentType(22, "myType", new PublishedPropertyType[] { }); - var image = new AutoPublishedContentType(23, "Image", new PublishedPropertyType[] { }); - var testMediaType = new AutoPublishedContentType(24, "TestMediaType", new PublishedPropertyType[] { }); + var type = new AutoPublishedContentType(Guid.NewGuid(), 22, "myType", new PublishedPropertyType[] { }); + var image = new AutoPublishedContentType(Guid.NewGuid(), 23, "Image", new PublishedPropertyType[] { }); + var testMediaType = new AutoPublishedContentType(Guid.NewGuid(), 24, "TestMediaType", new PublishedPropertyType[] { }); _mediaTypes = new Dictionary { { type.Alias, type }, diff --git a/src/Umbraco.Tests/Composing/TypeLoaderTests.cs b/src/Umbraco.Tests/Composing/TypeLoaderTests.cs index 7459ae848b..9cd4f39c17 100644 --- a/src/Umbraco.Tests/Composing/TypeLoaderTests.cs +++ b/src/Umbraco.Tests/Composing/TypeLoaderTests.cs @@ -268,7 +268,7 @@ AnotherContentFinder public void GetDataEditors() { var types = _typeLoader.GetDataEditors(); - Assert.AreEqual(38, types.Count()); + Assert.AreEqual(39, types.Count()); } /// diff --git a/src/Umbraco.Tests/IO/ShadowFileSystemTests.cs b/src/Umbraco.Tests/IO/ShadowFileSystemTests.cs index 343994b03a..43a120d154 100644 --- a/src/Umbraco.Tests/IO/ShadowFileSystemTests.cs +++ b/src/Umbraco.Tests/IO/ShadowFileSystemTests.cs @@ -887,12 +887,12 @@ namespace Umbraco.Tests.IO } /// - /// Ensure the url returned contains the path relative to the FS root, + /// Ensure the URL returned contains the path relative to the FS root, /// but including the rootUrl the FS was initialized with. /// /// /// This file stuff in this test is kinda irrelevant with the current implementation. - /// We do tests that the files are written to the correct places and the url is returned correct, + /// We do tests that the files are written to the correct places and the URL is returned correct, /// but GetUrl is currently really just string manipulation so files are not actually hit by the code. /// Leaving the file stuff in here for now in case the method becomes more clever at some point. /// diff --git a/src/Umbraco.Tests/Integration/ContentEventsTests.cs b/src/Umbraco.Tests/Integration/ContentEventsTests.cs index 7f103e13e4..af8ebe626e 100644 --- a/src/Umbraco.Tests/Integration/ContentEventsTests.cs +++ b/src/Umbraco.Tests/Integration/ContentEventsTests.cs @@ -2171,7 +2171,7 @@ namespace Umbraco.Tests.Integration [Test] public void HasInitialContent() { - Assert.AreEqual(4, ServiceContext.ContentService.Count()); + Assert.AreEqual(5, ServiceContext.ContentService.Count()); } #endregion diff --git a/src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedContentCache.cs b/src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedContentCache.cs index 8ce6b10983..893a170f5a 100644 --- a/src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedContentCache.cs +++ b/src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedContentCache.cs @@ -14,7 +14,7 @@ using Umbraco.Web.Routing; namespace Umbraco.Tests.LegacyXmlPublishedCache { - internal class PublishedContentCache : PublishedCacheBase, IPublishedContentCache + internal class PublishedContentCache : PublishedCacheBase, IPublishedContentCache2 { private readonly IAppCache _appCache; private readonly IGlobalSettings _globalSettings; @@ -96,7 +96,7 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache // so we have a route that maps to a content... say "1234/path/to/content" - however, there could be a // domain set on "to" and route "4567/content" would also map to the same content - and due to how - // urls computing work (by walking the tree up to the first domain we find) it is that second route + // URLs computing work (by walking the tree up to the first domain we find) it is that second route // that would be returned - the "deepest" route - and that is the route we want to cache, *not* the // longer one - so make sure we don't cache the wrong route @@ -257,7 +257,7 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache if (node == null) return null; // walk up from that node until we hit a node with a domain, - // or we reach the content root, collecting urls in the way + // or we reach the content root, collecting URLs in the way var pathParts = new List(); var n = node; var hasDomains = _domainCache.HasAssigned(n.Id); @@ -532,15 +532,11 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache #region Content types - public override IPublishedContentType GetContentType(int id) - { - return _contentTypeCache.Get(PublishedItemType.Content, id); - } + public override IPublishedContentType GetContentType(int id) => _contentTypeCache.Get(PublishedItemType.Content, id); - public override IPublishedContentType GetContentType(string alias) - { - return _contentTypeCache.Get(PublishedItemType.Content, alias); - } + public override IPublishedContentType GetContentType(string alias) => _contentTypeCache.Get(PublishedItemType.Content, alias); + + public override IPublishedContentType GetContentType(Guid key) => _contentTypeCache.Get(PublishedItemType.Content, key); #endregion } diff --git a/src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedMediaCache.cs b/src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedMediaCache.cs index 999d7f040d..56033e6b0a 100644 --- a/src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedMediaCache.cs +++ b/src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedMediaCache.cs @@ -28,7 +28,7 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache /// /// NOTE: In the future if we want to properly cache all media this class can be extended or replaced when these classes/interfaces are exposed publicly. /// - internal class PublishedMediaCache : PublishedCacheBase, IPublishedMediaCache + internal class PublishedMediaCache : PublishedCacheBase, IPublishedMediaCache2 { private readonly IMediaService _mediaService; private readonly IUserService _userService; @@ -612,15 +612,11 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache #region Content types - public override IPublishedContentType GetContentType(int id) - { - return _contentTypeCache.Get(PublishedItemType.Media, id); - } + public override IPublishedContentType GetContentType(int id) => _contentTypeCache.Get(PublishedItemType.Media, id); - public override IPublishedContentType GetContentType(string alias) - { - return _contentTypeCache.Get(PublishedItemType.Media, alias); - } + public override IPublishedContentType GetContentType(string alias) => _contentTypeCache.Get(PublishedItemType.Media, alias); + + public override IPublishedContentType GetContentType(Guid key) => _contentTypeCache.Get(PublishedItemType.Media, key); public override IEnumerable GetByContentType(IPublishedContentType contentType) { diff --git a/src/Umbraco.Tests/Misc/ApplicationUrlHelperTests.cs b/src/Umbraco.Tests/Misc/ApplicationUrlHelperTests.cs index 79878a59a1..163effb676 100644 --- a/src/Umbraco.Tests/Misc/ApplicationUrlHelperTests.cs +++ b/src/Umbraco.Tests/Misc/ApplicationUrlHelperTests.cs @@ -77,7 +77,7 @@ namespace Umbraco.Tests.Misc [Test] public void SetApplicationUrlWhenNoSettings() { - // no applicable settings, cannot set url + // no applicable settings, cannot set URL var settings = Mock.Of(section => section.WebRouting == Mock.Of(wrSection => wrSection.UmbracoApplicationUrl == (string) null)); diff --git a/src/Umbraco.Tests/Models/Mapping/ContentTypeModelMappingTests.cs b/src/Umbraco.Tests/Models/Mapping/ContentTypeModelMappingTests.cs index eee7446823..1480253958 100644 --- a/src/Umbraco.Tests/Models/Mapping/ContentTypeModelMappingTests.cs +++ b/src/Umbraco.Tests/Models/Mapping/ContentTypeModelMappingTests.cs @@ -215,6 +215,7 @@ namespace Umbraco.Tests.Models.Mapping { Assert.AreEqual(propTypes.ElementAt(j).Id, result.PropertyTypes.ElementAt(j).Id); Assert.AreEqual(propTypes.ElementAt(j).DataTypeId, result.PropertyTypes.ElementAt(j).DataTypeId); + Assert.AreEqual(propTypes.ElementAt(j).LabelOnTop, result.PropertyTypes.ElementAt(j).LabelOnTop); } } @@ -449,6 +450,7 @@ namespace Umbraco.Tests.Models.Mapping { Assert.AreEqual(propTypes[j].Id, result.Groups.ElementAt(i).Properties.ElementAt(j).Id); Assert.AreEqual(propTypes[j].DataTypeId, result.Groups.ElementAt(i).Properties.ElementAt(j).DataTypeId); + Assert.AreEqual(propTypes[j].LabelOnTop, result.Groups.ElementAt(i).Properties.ElementAt(j).LabelOnTop); } } @@ -639,6 +641,7 @@ namespace Umbraco.Tests.Models.Mapping Assert.AreEqual(basic.Validation.MandatoryMessage, result.MandatoryMessage); Assert.AreEqual(basic.Validation.Pattern, result.ValidationRegExp); Assert.AreEqual(basic.Validation.PatternMessage, result.ValidationRegExpMessage); + Assert.AreEqual(basic.LabelOnTop, result.LabelOnTop); } [Test] @@ -677,6 +680,7 @@ namespace Umbraco.Tests.Models.Mapping Assert.AreEqual(basic.Validation.MandatoryMessage, result.MandatoryMessage); Assert.AreEqual(basic.Validation.Pattern, result.ValidationRegExp); Assert.AreEqual(basic.Validation.PatternMessage, result.ValidationRegExpMessage); + Assert.AreEqual(basic.LabelOnTop, result.LabelOnTop); } [Test] @@ -1074,7 +1078,8 @@ namespace Umbraco.Tests.Models.Mapping Pattern = string.Empty }, SortOrder = 0, - DataTypeId = 555 + DataTypeId = 555, + LabelOnTop = true } } } @@ -1120,7 +1125,8 @@ namespace Umbraco.Tests.Models.Mapping Pattern = string.Empty }, SortOrder = 0, - DataTypeId = 555 + DataTypeId = 555, + LabelOnTop = true } } }, @@ -1144,7 +1150,8 @@ namespace Umbraco.Tests.Models.Mapping Pattern = string.Empty }, SortOrder = 0, - DataTypeId = 555 + DataTypeId = 555, + LabelOnTop = false } } @@ -1198,7 +1205,8 @@ namespace Umbraco.Tests.Models.Mapping Pattern = string.Empty }, SortOrder = 0, - DataTypeId = 555 + DataTypeId = 555, + LabelOnTop = true } } }, @@ -1222,7 +1230,8 @@ namespace Umbraco.Tests.Models.Mapping Pattern = string.Empty }, SortOrder = 0, - DataTypeId = 555 + DataTypeId = 555, + LabelOnTop = false } } diff --git a/src/Umbraco.Tests/Models/RangeTests.cs b/src/Umbraco.Tests/Models/RangeTests.cs new file mode 100644 index 0000000000..3fb4e8708b --- /dev/null +++ b/src/Umbraco.Tests/Models/RangeTests.cs @@ -0,0 +1,179 @@ +using System.Globalization; +using NUnit.Framework; +using Umbraco.Core.Models; + +namespace Umbraco.Tests.Models +{ + [TestFixture] + public class RangeTests + { + [TestCase(0, 0, "0,0")] + [TestCase(0, 1, "0,1")] + [TestCase(1, 1, "1,1")] + public void RangeInt32_ToString(int minimum, int maximum, string expected) + { + Assert.AreEqual(expected, new Range() + { + Minimum = minimum, + Maximum = maximum + }.ToString()); + } + + [TestCase(0, 0.5, "0,0.5")] + [TestCase(0.5, 0.5, "0.5,0.5")] + [TestCase(0.5, 1, "0.5,1")] + public void RangeDouble_ToString(double minimum, double maximum, string expected) + { + Assert.AreEqual(expected, new Range() + { + Minimum = minimum, + Maximum = maximum + }.ToString()); + } + + [TestCase(0, 0, "0")] + [TestCase(0, 1, "0,1")] + [TestCase(1, 1, "1")] + public void RangeInt32_ToStringFormatRange(int minimum, int maximum, string expected) + { + Assert.AreEqual(expected, new Range() + { + Minimum = minimum, + Maximum = maximum + }.ToString("{0}", "{0},{1}", CultureInfo.InvariantCulture)); + } + + [TestCase(0, 0.5, "0,0.5")] + [TestCase(0.5, 0.5, "0.5")] + [TestCase(0.5, 1, "0.5,1")] + public void RangeDouble_ToStringFormatRange(double minimum, double maximum, string expected) + { + Assert.AreEqual(expected, new Range() + { + Minimum = minimum, + Maximum = maximum + }.ToString("{0}", "{0},{1}", CultureInfo.InvariantCulture)); + } + + [TestCase(0, 0, "0,0")] + [TestCase(0, 1, "0,1")] + [TestCase(1, 1, "1,1")] + public void RangeInt32_ToStringFormat(int minimum, int maximum, string expected) + { + Assert.AreEqual(expected, new Range() + { + Minimum = minimum, + Maximum = maximum + }.ToString("{0},{1}", CultureInfo.InvariantCulture)); + } + + [TestCase(0, 0.5, "0,0.5")] + [TestCase(0.5, 0.5, "0.5,0.5")] + [TestCase(0.5, 1, "0.5,1")] + public void RangeDouble_ToStringFormat(double minimum, double maximum, string expected) + { + Assert.AreEqual(expected, new Range() + { + Minimum = minimum, + Maximum = maximum + }.ToString("{0},{1}", CultureInfo.InvariantCulture)); + } + + [TestCase(0, 0, true)] + [TestCase(0, 1, true)] + [TestCase(-1, 1, true)] + [TestCase(1, 0, false)] + [TestCase(0, -1, false)] + public void RangeInt32_IsValid(int minimum, int maximum, bool expected) + { + Assert.AreEqual(expected, new Range() + { + Minimum = minimum, + Maximum = maximum + }.IsValid()); + } + + [TestCase(0, 0, 0, true)] + [TestCase(0, 1, 0, true)] + [TestCase(0, 1, 1, true)] + [TestCase(-1, 1, 0, true)] + [TestCase(0, 0, 1, false)] + [TestCase(0, 0, -1, false)] + public void RangeInt32_ContainsValue(int minimum, int maximum, int value, bool expected) + { + Assert.AreEqual(expected, new Range() + { + Minimum = minimum, + Maximum = maximum + }.ContainsValue(value)); + } + + [TestCase(0, 0, 0, 0, true)] + [TestCase(0, 1, 0, 1, true)] + [TestCase(0, 1, 1, 1, false)] + [TestCase(-1, 1, 0, 0, false)] + [TestCase(0, 0, 1, 1, false)] + [TestCase(0, 0, -1, 1, true)] + public void RangeInt32_IsInsideRange(int minimum1, int maximum1, int minimum2, int maximum2, bool expected) + { + Assert.AreEqual(expected, new Range() + { + Minimum = minimum1, + Maximum = maximum1 + }.IsInsideRange(new Range() + { + Minimum = minimum2, + Maximum = maximum2 + })); + } + + [TestCase(0, 0, 0, 0, true)] + [TestCase(0, 1, 0, 1, true)] + [TestCase(0, 1, 1, 1, true)] + [TestCase(-1, 1, 0, 0, true)] + [TestCase(0, 0, 1, 1, false)] + [TestCase(0, 0, -1, 1, false)] + public void RangeInt32_ContainsRange(int minimum1, int maximum1, int minimum2, int maximum2, bool expected) + { + Assert.AreEqual(expected, new Range() + { + Minimum = minimum1, + Maximum = maximum1 + }.ContainsRange(new Range() + { + Minimum = minimum2, + Maximum = maximum2 + })); + } + + [TestCase(0, 0, 0, 0, true)] + [TestCase(0, 1, 0, 1, true)] + [TestCase(0, 1, 1, 1, false)] + [TestCase(0, 0, 1, 1, false)] + public void RangeInt32_Equals(int minimum1, int maximum1, int minimum2, int maximum2, bool expected) + { + Assert.AreEqual(expected, new Range() + { + Minimum = minimum1, + Maximum = maximum1 + }.Equals(new Range() + { + Minimum = minimum2, + Maximum = maximum2 + })); + } + + [TestCase(0, 0, 0, 0, true)] + [TestCase(0, 1, 0, 1, true)] + [TestCase(0, 1, 1, 1, false)] + [TestCase(0, 0, 1, 1, false)] + public void RangeInt32_EqualsValues(int minimum1, int maximum1, int minimum2, int maximum2, bool expected) + { + Assert.AreEqual(expected, new Range() + { + Minimum = minimum1, + Maximum = maximum1 + }.Equals(minimum2, maximum2)); + } + } +} diff --git a/src/Umbraco.Tests/Models/VariationTests.cs b/src/Umbraco.Tests/Models/VariationTests.cs index ab5f726894..b87ff499b6 100644 --- a/src/Umbraco.Tests/Models/VariationTests.cs +++ b/src/Umbraco.Tests/Models/VariationTests.cs @@ -442,7 +442,10 @@ namespace Umbraco.Tests.Models [Test] public void ContentPublishValuesWithMixedPropertyTypeVariations() { - var propertyValidationService = new PropertyValidationService(Current.Factory.GetInstance(), Current.Factory.GetInstance().DataTypeService); + var propertyValidationService = new PropertyValidationService( + Current.Factory.GetInstance(), + Current.Factory.GetInstance().DataTypeService, + Current.Factory.GetInstance().TextService); const string langFr = "fr-FR"; // content type varies by Culture @@ -574,7 +577,10 @@ namespace Umbraco.Tests.Models prop.SetValue("a"); Assert.AreEqual("a", prop.GetValue()); Assert.IsNull(prop.GetValue(published: true)); - var propertyValidationService = new PropertyValidationService(Current.Factory.GetInstance(), Current.Factory.GetInstance().DataTypeService); + var propertyValidationService = new PropertyValidationService( + Current.Factory.GetInstance(), + Current.Factory.GetInstance().DataTypeService, + Current.Factory.GetInstance().TextService); Assert.IsTrue(propertyValidationService.IsPropertyValid(prop)); diff --git a/src/Umbraco.Tests/Persistence/NPocoTests/NPocoSqlTests.cs b/src/Umbraco.Tests/Persistence/NPocoTests/NPocoSqlTests.cs index a04984eb64..b4210ee31a 100644 --- a/src/Umbraco.Tests/Persistence/NPocoTests/NPocoSqlTests.cs +++ b/src/Umbraco.Tests/Persistence/NPocoTests/NPocoSqlTests.cs @@ -82,9 +82,9 @@ namespace Umbraco.Tests.Persistence.NPocoTests var sql = Sql().SelectAll().From() .Where(x => x.Trashed == false); - Assert.AreEqual("SELECT * FROM [umbracoNode] WHERE (NOT ([umbracoNode].[trashed] = @0))", sql.SQL.Replace("\n", " ")); + Assert.AreEqual("SELECT * FROM [umbracoNode] WHERE ([umbracoNode].[trashed] = @0)", sql.SQL.Replace("\n", " ")); Assert.AreEqual(1, sql.Arguments.Length); - Assert.AreEqual(true, sql.Arguments[0]); + Assert.AreEqual(false, sql.Arguments[0]); } [Test] @@ -92,9 +92,9 @@ namespace Umbraco.Tests.Persistence.NPocoTests { var sql = Sql().SelectAll().From().Where(x => x.Trashed == false); - Assert.AreEqual("SELECT * FROM [umbracoNode] WHERE (NOT ([umbracoNode].[trashed] = @0))", sql.SQL.Replace("\n", " ")); + Assert.AreEqual("SELECT * FROM [umbracoNode] WHERE ([umbracoNode].[trashed] = @0)", sql.SQL.Replace("\n", " ")); Assert.AreEqual(1, sql.Arguments.Length); - Assert.AreEqual(true, sql.Arguments[0]); + Assert.AreEqual(false, sql.Arguments[0]); } [Test] diff --git a/src/Umbraco.Tests/Persistence/Querying/QueryBuilderTests.cs b/src/Umbraco.Tests/Persistence/Querying/QueryBuilderTests.cs index ca6b4cd5f0..2b43b13a79 100644 --- a/src/Umbraco.Tests/Persistence/Querying/QueryBuilderTests.cs +++ b/src/Umbraco.Tests/Persistence/Querying/QueryBuilderTests.cs @@ -110,7 +110,7 @@ namespace Umbraco.Tests.Persistence.Querying Assert.AreEqual("-1,1046,1076,1089%", result.Arguments[0]); Assert.AreEqual(1046, result.Arguments[1]); Assert.AreEqual(true, result.Arguments[2]); - Assert.AreEqual(true, result.Arguments[3]); + Assert.AreEqual(false, result.Arguments[3]); } } } diff --git a/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs index e592c5171a..93a6daecde 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs @@ -14,6 +14,7 @@ using Umbraco.Tests.TestHelpers; using Umbraco.Tests.TestHelpers.Entities; using Umbraco.Tests.Testing; using Umbraco.Web.Models.ContentEditing; +using Content = Umbraco.Core.Models.Content; namespace Umbraco.Tests.Persistence.Repositories { @@ -348,6 +349,7 @@ namespace Umbraco.Tests.Persistence.Repositories Assert.That(contentType.Path.Contains(","), Is.True); Assert.That(contentType.SortOrder, Is.GreaterThan(0)); + Assert.That(contentType.PropertyGroups.ElementAt(0).Name == "testGroup", Is.True); var groupId = contentType.PropertyGroups.ElementAt(0).Id; @@ -355,6 +357,7 @@ namespace Umbraco.Tests.Persistence.Repositories Assert.AreEqual("gen", propertyTypes[0].Alias); // just to be sure Assert.IsNull(propertyTypes[0].PropertyGroupId); Assert.IsTrue(propertyTypes.Skip(1).All((x => x.PropertyGroupId.Value == groupId))); + Assert.That(propertyTypes.Single(x=> x.Alias == "title").LabelOnTop, Is.True); } } @@ -377,7 +380,8 @@ namespace Umbraco.Tests.Persistence.Repositories Description = "Optional Subtitle", Mandatory = false, SortOrder = 1, - DataTypeId = -88 + DataTypeId = -88, + LabelOnTop = true }); repository.Save(contentType); @@ -389,6 +393,8 @@ namespace Umbraco.Tests.Persistence.Repositories Assert.That(dirty, Is.False); Assert.That(contentType.Thumbnail, Is.EqualTo("Doc2.png")); Assert.That(contentType.PropertyTypes.Any(x => x.Alias == "subtitle"), Is.True); + Assert.That(contentType.PropertyTypes.Single(x => x.Alias == "subtitle").LabelOnTop, Is.True); + } @@ -467,7 +473,8 @@ namespace Umbraco.Tests.Persistence.Repositories Pattern = "" }, SortOrder = 1, - DataTypeId = -88 + DataTypeId = -88, + LabelOnTop = true } }); @@ -476,6 +483,7 @@ namespace Umbraco.Tests.Persistence.Repositories // just making sure Assert.AreEqual(mapped.Thumbnail, "Doc2.png"); Assert.IsTrue(mapped.PropertyTypes.Any(x => x.Alias == "subtitle")); + Assert.IsTrue(mapped.PropertyTypes.Single(x => x.Alias == "subtitle").LabelOnTop); repository.Save(mapped); @@ -490,6 +498,9 @@ namespace Umbraco.Tests.Persistence.Repositories Assert.That(dirty, Is.False); Assert.That(contentType.Thumbnail, Is.EqualTo("Doc2.png")); Assert.That(contentType.PropertyTypes.Any(x => x.Alias == "subtitle"), Is.True); + + Assert.That(contentType.PropertyTypes.Single(x => x.Alias == "subtitle").LabelOnTop, Is.True); + foreach (var propertyType in contentType.PropertyTypes) { Assert.IsTrue(propertyType.HasIdentity); @@ -997,6 +1008,88 @@ namespace Umbraco.Tests.Persistence.Repositories } } + [Test] + public void Can_Update_Variation_Of_Element_Type_Property() + { + var provider = TestObjects.GetScopeProvider(Logger); + using (var scope = provider.CreateScope()) + { + ContentTypeRepository repository; + var contentRepository = CreateRepository((IScopeAccessor) provider, out repository); + + // Create elementType + var elementType = new ContentType(-1) + { + Alias = "elementType", + Name = "Element type", + Description = "Element type to use as compositions", + Icon = ".sprTreeDoc3", + Thumbnail = "doc.png", + SortOrder = 1, + CreatorId = 0, + Trashed = false, + IsElement = true, + Variations = ContentVariation.Nothing + }; + + var contentCollection = new PropertyTypeCollection(true); + contentCollection.Add(new PropertyType("test", ValueStorageType.Ntext) + { + Alias = "title", + Name = "Title", + Description = "", + Mandatory = false, + SortOrder = 1, + DataTypeId = Constants.DataTypes.Textbox, + LabelOnTop = true, + Variations = ContentVariation.Nothing + }); + elementType.PropertyGroups.Add(new PropertyGroup(contentCollection) {Name = "Content", SortOrder = 1}); + elementType.ResetDirtyProperties(false); + elementType.SetDefaultTemplate(new Template("ElementType", "elementType")); + repository.Save(elementType); + + // Create the basic "home" doc type that uses element type as comp + var docType = new ContentType(-1) + { + Alias = "home", + Name = "Home", + Description = "Home containing elementType", + Icon = ".sprTreeDoc3", + Thumbnail = "doc.png", + SortOrder = 1, + CreatorId = 0, + Trashed = false, + Variations = ContentVariation.Nothing + }; + var comp = new List(); + comp.Add(elementType); + docType.ContentTypeComposition = comp; + repository.Save(docType); + + // Create "home" content + var content = new Content("Home", -1, docType){Level = 1, SortOrder = 1, CreatorId = 0, WriterId = 0}; + object obj = new {title = "test title"}; + content.PropertyValues(obj); + content.ResetDirtyProperties(false); + contentRepository.Save(content); + + // Update variation on element type + elementType.Variations = ContentVariation.Culture; + elementType.PropertyTypes.First().Variations = ContentVariation.Culture; + repository.Save(elementType); + + // Update variation on doc type + docType.Variations = ContentVariation.Culture; + repository.Save(docType); + + // Re fetch renewedContent and make sure that the culture has been set. + var renewedContent = ServiceContext.ContentService.GetById(content.Id); + var hasCulture = renewedContent.Properties["title"].Values.First().Culture != null; + Assert.That(hasCulture, Is.True); + } + } + public void CreateTestData() { //Create and Save ContentType "umbTextpage" -> (NodeDto.NodeIdSeed) diff --git a/src/Umbraco.Tests/Persistence/Repositories/RedirectUrlRepositoryTests.cs b/src/Umbraco.Tests/Persistence/Repositories/RedirectUrlRepositoryTests.cs index 652209b65b..2fb305c97e 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/RedirectUrlRepositoryTests.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/RedirectUrlRepositoryTests.cs @@ -9,6 +9,7 @@ using Umbraco.Core.Scoping; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.TestHelpers.Entities; using Umbraco.Tests.Testing; +using Umbraco.Web.JavaScript; namespace Umbraco.Tests.Persistence.Repositories { @@ -53,6 +54,39 @@ namespace Umbraco.Tests.Persistence.Repositories } } + [Test] + public void CanSaveAndGetWithCulture() + { + var provider = TestObjects.GetScopeProvider(Logger); + var culture = "en"; + using (var scope = provider.CreateScope()) + { + var repo = CreateRepository(provider); + var rurl = new RedirectUrl + { + ContentKey = _textpage.Key, + Url = "blah", + Culture = culture + }; + repo.Save(rurl); + scope.Complete(); + + Assert.AreNotEqual(0, rurl.Id); + } + + using (var scope = provider.CreateScope()) + { + var repo = CreateRepository(provider); + var rurl = repo.GetMostRecentUrl("blah"); + scope.Complete(); + + Assert.IsNotNull(rurl); + Assert.AreEqual(_textpage.Id, rurl.ContentId); + Assert.AreEqual(culture, rurl.Culture); + } + } + + [Test] public void CanSaveAndGetMostRecent() { @@ -101,6 +135,59 @@ namespace Umbraco.Tests.Persistence.Repositories } } + [Test] + public void CanSaveAndGetMostRecentForCulture() + { + var provider = TestObjects.GetScopeProvider(Logger); + var cultureA = "en"; + var cultureB = "de"; + Assert.AreNotEqual(_textpage.Id, _otherpage.Id); + + using (var scope = provider.CreateScope()) + { + var repo = CreateRepository(provider); + var rurl = new RedirectUrl + { + ContentKey = _textpage.Key, + Url = "blah", + Culture = cultureA + }; + repo.Save(rurl); + scope.Complete(); + + Assert.AreNotEqual(0, rurl.Id); + + // FIXME: too fast = same date = key violation? + // and... can that happen in real life? + // we don't really *care* about the IX, only supposed to make things faster... + // BUT in realife we AddOrUpdate in a trx so it should be safe, always + + rurl = new RedirectUrl + { + ContentKey = _otherpage.Key, + Url = "blah", + CreateDateUtc = rurl.CreateDateUtc.AddSeconds(1), // ensure time difference + Culture = cultureB + }; + repo.Save(rurl); + scope.Complete(); + + Assert.AreNotEqual(0, rurl.Id); + } + + using (var scope = provider.CreateScope()) + { + var repo = CreateRepository(provider); + var rurl = repo.GetMostRecentUrl("blah", cultureA); + scope.Complete(); + + Assert.IsNotNull(rurl); + Assert.AreEqual(_textpage.Id, rurl.ContentId); + Assert.AreEqual(cultureA, rurl.Culture); + } + } + + [Test] public void CanSaveAndGetByContent() { diff --git a/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs index bbefb79f6b..b2efbd34b8 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs @@ -16,6 +16,7 @@ using Umbraco.Tests.Testing; using Umbraco.Core.Persistence; using Umbraco.Core.PropertyEditors; using System; +using Umbraco.Core.Persistence.Dtos; namespace Umbraco.Tests.Persistence.Repositories { @@ -76,12 +77,44 @@ namespace Umbraco.Tests.Persistence.Repositories return new UserGroupRepository(accessor, AppCaches.Disabled, Logger); } + [Test] + public void Validate_Login_Session() + { + // Arrange + var provider = TestObjects.GetScopeProvider(Logger); + var user = MockedUser.CreateUser(); + using (var scope = provider.CreateScope(autoComplete: true)) + { + var repository = CreateRepository(provider); + repository.Save(user); + } + + using (var scope = provider.CreateScope(autoComplete: true)) + { + var repository = CreateRepository(provider); + var sessionId = repository.CreateLoginSession(user.Id, "1.2.3.4"); + + // manually update this record to be in the past + scope.Database.Execute(SqlContext.Sql() + .Update(u => u.Set(x => x.LoggedOutUtc, DateTime.UtcNow.AddDays(-100))) + .Where(x => x.SessionId == sessionId)); + + var isValid = repository.ValidateLoginSession(user.Id, sessionId); + Assert.IsFalse(isValid); + + // create a new one + sessionId = repository.CreateLoginSession(user.Id, "1.2.3.4"); + isValid = repository.ValidateLoginSession(user.Id, sessionId); + Assert.IsTrue(isValid); + } + } + [Test] public void Can_Perform_Add_On_UserRepository() { // Arrange var provider = TestObjects.GetScopeProvider(Logger); - using (var scope = provider.CreateScope()) + using (var scope = provider.CreateScope(autoComplete: true)) { var repository = CreateRepository(provider); @@ -101,7 +134,7 @@ namespace Umbraco.Tests.Persistence.Repositories { // Arrange var provider = TestObjects.GetScopeProvider(Logger); - using (var scope = provider.CreateScope()) + using (var scope = provider.CreateScope(autoComplete: true)) { var repository = CreateRepository(provider); @@ -125,7 +158,7 @@ namespace Umbraco.Tests.Persistence.Repositories { // Arrange var provider = TestObjects.GetScopeProvider(Logger); - using (var scope = provider.CreateScope()) + using (var scope = provider.CreateScope(autoComplete: true)) { var repository = CreateRepository(provider); @@ -150,7 +183,7 @@ namespace Umbraco.Tests.Persistence.Repositories // Arrange var provider = TestObjects.GetScopeProvider(Logger); - using (var scope = provider.CreateScope()) + using (var scope = provider.CreateScope(autoComplete: true)) { var userRepository = CreateRepository(provider); var contentRepository = CreateContentRepository(provider, out var contentTypeRepo); @@ -209,7 +242,7 @@ namespace Umbraco.Tests.Persistence.Repositories { // Arrange var provider = TestObjects.GetScopeProvider(Logger); - using (var scope = provider.CreateScope()) + using (var scope = provider.CreateScope(autoComplete: true)) { var repository = CreateRepository(provider); @@ -237,7 +270,7 @@ namespace Umbraco.Tests.Persistence.Repositories { // Arrange var provider = TestObjects.GetScopeProvider(Logger); - using (var scope = provider.CreateScope()) + using (var scope = provider.CreateScope(autoComplete: true)) { var repository = CreateRepository(provider); var userGroupRepository = CreateUserGroupRepository(provider); @@ -260,7 +293,7 @@ namespace Umbraco.Tests.Persistence.Repositories { // Arrange var provider = TestObjects.GetScopeProvider(Logger); - using (var scope = provider.CreateScope()) + using (var scope = provider.CreateScope(autoComplete: true)) { var repository = CreateRepository(provider); @@ -280,7 +313,7 @@ namespace Umbraco.Tests.Persistence.Repositories { // Arrange var provider = TestObjects.GetScopeProvider(Logger); - using (var scope = provider.CreateScope()) + using (var scope = provider.CreateScope(autoComplete: true)) { var repository = CreateRepository(provider); @@ -301,7 +334,7 @@ namespace Umbraco.Tests.Persistence.Repositories { // Arrange var provider = TestObjects.GetScopeProvider(Logger); - using (var scope = provider.CreateScope()) + using (var scope = provider.CreateScope(autoComplete: true)) { var repository = CreateRepository(provider); @@ -322,7 +355,7 @@ namespace Umbraco.Tests.Persistence.Repositories { // Arrange var provider = TestObjects.GetScopeProvider(Logger); - using (var scope = provider.CreateScope()) + using (var scope = provider.CreateScope(autoComplete: true)) { var repository = CreateRepository(provider); @@ -341,7 +374,7 @@ namespace Umbraco.Tests.Persistence.Repositories { // Arrange var provider = TestObjects.GetScopeProvider(Logger); - using (var scope = provider.CreateScope()) + using (var scope = provider.CreateScope(autoComplete: true)) { var repository = CreateRepository(provider); @@ -360,7 +393,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Get_Paged_Results_By_Query_And_Filter_And_Groups() { var provider = TestObjects.GetScopeProvider(Logger); - using (var scope = provider.CreateScope()) + using (var scope = provider.CreateScope(autoComplete: true)) { var repository = CreateRepository(provider); @@ -393,7 +426,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Get_Paged_Results_With_Filter_And_Groups() { var provider = TestObjects.GetScopeProvider(Logger); - using (var scope = provider.CreateScope()) + using (var scope = provider.CreateScope(autoComplete: true)) { var repository = CreateRepository(provider); @@ -426,7 +459,7 @@ namespace Umbraco.Tests.Persistence.Repositories { // Arrange var provider = TestObjects.GetScopeProvider(Logger); - using (var scope = provider.CreateScope()) + using (var scope = provider.CreateScope(autoComplete: true)) { var repository = CreateRepository(provider); var userGroupRepository = CreateUserGroupRepository(provider); diff --git a/src/Umbraco.Tests/PropertyEditors/BlockEditorComponentTests.cs b/src/Umbraco.Tests/PropertyEditors/BlockEditorComponentTests.cs new file mode 100644 index 0000000000..bfd8b8c77b --- /dev/null +++ b/src/Umbraco.Tests/PropertyEditors/BlockEditorComponentTests.cs @@ -0,0 +1,261 @@ +using Newtonsoft.Json; +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core; +using Umbraco.Web.Compose; + +namespace Umbraco.Tests.PropertyEditors +{ + [TestFixture] + public class BlockEditorComponentTests + { + private readonly JsonSerializerSettings _serializerSettings = new JsonSerializerSettings + { + Formatting = Formatting.None, + NullValueHandling = NullValueHandling.Ignore, + + }; + + private const string _contentGuid1 = "036ce82586a64dfba2d523a99ed80f58"; + private const string _contentGuid2 = "48288c21a38a40ef82deb3eda90a58f6"; + private const string _settingsGuid1 = "ffd35c4e2eea4900abfa5611b67b2492"; + private const string _subContentGuid1 = "4c44ce6b3a5c4f5f8f15e3dc24819a9e"; + private const string _subContentGuid2 = "a062c06d6b0b44ac892b35d90309c7f8"; + private const string _subSettingsGuid1 = "4d998d980ffa4eee8afdc23c4abd6d29"; + + [Test] + public void Cannot_Have_Null_Udi() + { + var component = new BlockEditorComponent(); + var json = GetBlockListJson(null, string.Empty); + Assert.Throws(() => component.ReplaceBlockListUdis(json)); + } + + [Test] + public void No_Nesting() + { + var guids = Enumerable.Range(0, 3).Select(x => Guid.NewGuid()).ToList(); + var guidCounter = 0; + Func guidFactory = () => guids[guidCounter++]; + + var json = GetBlockListJson(null); + + var expected = ReplaceGuids(json, guids, _contentGuid1, _contentGuid2, _settingsGuid1); + + var component = new BlockEditorComponent(); + var result = component.ReplaceBlockListUdis(json, guidFactory); + + var expectedJson = JsonConvert.SerializeObject(JsonConvert.DeserializeObject(expected, _serializerSettings), _serializerSettings); + var resultJson = JsonConvert.SerializeObject(JsonConvert.DeserializeObject(result, _serializerSettings), _serializerSettings); + Console.WriteLine(expectedJson); + Console.WriteLine(resultJson); + Assert.AreEqual(expectedJson, resultJson); + } + + [Test] + public void One_Level_Nesting_Escaped() + { + var guids = Enumerable.Range(0, 6).Select(x => Guid.NewGuid()).ToList(); + + var guidCounter = 0; + Func guidFactory = () => guids[guidCounter++]; + + var innerJson = GetBlockListJson(null, _subContentGuid1, _subContentGuid2, _subSettingsGuid1); + + // we need to ensure the escaped json is consistent with how it will be re-escaped after parsing + // and this is how to do that, the result will also include quotes around it. + var innerJsonEscaped = JsonConvert.ToString(innerJson); + + // get the json with the subFeatures as escaped + var json = GetBlockListJson(innerJsonEscaped); + + var component = new BlockEditorComponent(); + var result = component.ReplaceBlockListUdis(json, guidFactory); + + // the expected result is that the subFeatures data is no longer escaped + var expected = ReplaceGuids(GetBlockListJson(innerJson), guids, + _contentGuid1, _contentGuid2, _settingsGuid1, + _subContentGuid1, _subContentGuid2, _subSettingsGuid1); + + var expectedJson = JsonConvert.SerializeObject(JsonConvert.DeserializeObject(expected, _serializerSettings), _serializerSettings); + var resultJson = JsonConvert.SerializeObject(JsonConvert.DeserializeObject(result, _serializerSettings), _serializerSettings); + Console.WriteLine(expectedJson); + Console.WriteLine(resultJson); + Assert.AreEqual(expectedJson, resultJson); + } + + [Test] + public void One_Level_Nesting_Unescaped() + { + var guids = Enumerable.Range(0, 6).Select(x => Guid.NewGuid()).ToList(); + var guidCounter = 0; + Func guidFactory = () => guids[guidCounter++]; + + // nested blocks without property value escaping used in the conversion + var innerJson = GetBlockListJson(null, _subContentGuid1, _subContentGuid2, _subSettingsGuid1); + + // get the json with the subFeatures as unescaped + var json = GetBlockListJson(innerJson); + + var expected = ReplaceGuids(GetBlockListJson(innerJson), guids, + _contentGuid1, _contentGuid2, _settingsGuid1, + _subContentGuid1, _subContentGuid2, _subSettingsGuid1); + + var component = new BlockEditorComponent(); + var result = component.ReplaceBlockListUdis(json, guidFactory); + + var expectedJson = JsonConvert.SerializeObject(JsonConvert.DeserializeObject(expected, _serializerSettings), _serializerSettings); + var resultJson = JsonConvert.SerializeObject(JsonConvert.DeserializeObject(result, _serializerSettings), _serializerSettings); + Console.WriteLine(expectedJson); + Console.WriteLine(resultJson); + Assert.AreEqual(expectedJson, resultJson); + } + + [Test] + public void Nested_In_Complex_Editor_Escaped() + { + var guids = Enumerable.Range(0, 6).Select(x => Guid.NewGuid()).ToList(); + var guidCounter = 0; + Func guidFactory = () => guids[guidCounter++]; + + var innerJson = GetBlockListJson(null, _subContentGuid1, _subContentGuid2, _subSettingsGuid1); + + // we need to ensure the escaped json is consistent with how it will be re-escaped after parsing + // and this is how to do that, the result will also include quotes around it. + var innerJsonEscaped = JsonConvert.ToString(innerJson); + + // Complex editor such as the grid + var complexEditorJsonEscaped = GetGridJson(innerJsonEscaped); + + var json = GetBlockListJson(complexEditorJsonEscaped); + + var component = new BlockEditorComponent(); + var result = component.ReplaceBlockListUdis(json, guidFactory); + + // the expected result is that the subFeatures data is no longer escaped + var expected = ReplaceGuids(GetBlockListJson(GetGridJson(innerJson)), guids, + _contentGuid1, _contentGuid2, _settingsGuid1, + _subContentGuid1, _subContentGuid2, _subSettingsGuid1); + + var expectedJson = JsonConvert.SerializeObject(JsonConvert.DeserializeObject(expected, _serializerSettings), _serializerSettings); + var resultJson = JsonConvert.SerializeObject(JsonConvert.DeserializeObject(result, _serializerSettings), _serializerSettings); + Console.WriteLine(expectedJson); + Console.WriteLine(resultJson); + Assert.AreEqual(expectedJson, resultJson); + } + + private string GetBlockListJson(string subFeatures, + string contentGuid1 = _contentGuid1, + string contentGuid2 = _contentGuid2, + string settingsGuid1 = _settingsGuid1) + { + return @"{ + ""layout"": + { + ""Umbraco.BlockList"": [ + { + ""contentUdi"": """ + (contentGuid1.IsNullOrWhiteSpace() ? string.Empty : GuidUdi.Create(Constants.UdiEntityType.Element, Guid.Parse(contentGuid1)).ToString()) + @""" + }, + { + ""contentUdi"": ""umb://element/" + contentGuid2 + @""", + ""settingsUdi"": ""umb://element/" + settingsGuid1 + @""" + } + ] + }, + ""contentData"": [ + { + ""contentTypeKey"": ""d6ce4a86-91a2-45b3-a99c-8691fc1fb020"", + ""udi"": """ + (contentGuid1.IsNullOrWhiteSpace() ? string.Empty : GuidUdi.Create(Constants.UdiEntityType.Element, Guid.Parse(contentGuid1)).ToString()) + @""", + ""featureName"": ""Hello"", + ""featureDetails"": ""World"" + }, + { + ""contentTypeKey"": ""d6ce4a86-91a2-45b3-a99c-8691fc1fb020"", + ""udi"": ""umb://element/" + contentGuid2 + @""", + ""featureName"": ""Another"", + ""featureDetails"": ""Feature""" + (subFeatures == null ? string.Empty : (@", ""subFeatures"": " + subFeatures)) + @" + } + ], + ""settingsData"": [ + { + ""contentTypeKey"": ""d6ce4a86-91a2-45b3-a99c-8691fc1fb020"", + ""udi"": ""umb://element/" + settingsGuid1 + @""", + ""featureName"": ""Setting 1"", + ""featureDetails"": ""Setting 2"" + }, + ] +}"; + } + + private string GetGridJson(string subBlockList) + { + return @"{ + ""name"": ""1 column layout"", + ""sections"": [ + { + ""grid"": ""12"", + ""rows"": [ + { + ""name"": ""Article"", + ""id"": ""b4f6f651-0de3-ef46-e66a-464f4aaa9c57"", + ""areas"": [ + { + ""grid"": ""4"", + ""controls"": [ + { + ""value"": ""I am quote"", + ""editor"": { + ""alias"": ""quote"", + ""view"": ""textstring"" + }, + ""styles"": null, + ""config"": null + }], + ""styles"": null, + ""config"": null + }, + { + ""grid"": ""8"", + ""controls"": [ + { + ""value"": ""Header"", + ""editor"": { + ""alias"": ""headline"", + ""view"": ""textstring"" + }, + ""styles"": null, + ""config"": null + }, + { + ""value"": " + subBlockList + @", + ""editor"": { + ""alias"": ""madeUpNestedContent"", + ""view"": ""madeUpNestedContentInGrid"" + }, + ""styles"": null, + ""config"": null + }], + ""styles"": null, + ""config"": null + }], + ""styles"": null, + ""config"": null + }] + }] +}"; + } + + private string ReplaceGuids(string json, List newGuids, params string[] oldGuids) + { + for (var i = 0; i < oldGuids.Length; i++) + { + var old = oldGuids[i]; + json = json.Replace(old, newGuids[i].ToString("N")); + } + return json; + } + + } +} diff --git a/src/Umbraco.Tests/PropertyEditors/BlockListPropertyValueConverterTests.cs b/src/Umbraco.Tests/PropertyEditors/BlockListPropertyValueConverterTests.cs new file mode 100644 index 0000000000..959e059d59 --- /dev/null +++ b/src/Umbraco.Tests/PropertyEditors/BlockListPropertyValueConverterTests.cs @@ -0,0 +1,436 @@ +using Moq; +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Umbraco.Core; +using Umbraco.Core.Logging; +using Umbraco.Core.Models.Blocks; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.PropertyEditors; +using Umbraco.Web.PropertyEditors; +using Umbraco.Web.PropertyEditors.ValueConverters; +using Umbraco.Web.PublishedCache; + +namespace Umbraco.Tests.PropertyEditors +{ + [TestFixture] + public class BlockListPropertyValueConverterTests + { + private readonly Guid ContentKey1 = Guid.NewGuid(); + private readonly Guid ContentKey2 = Guid.NewGuid(); + private const string ContentAlias1 = "Test1"; + private const string ContentAlias2 = "Test2"; + private readonly Guid SettingKey1 = Guid.NewGuid(); + private readonly Guid SettingKey2 = Guid.NewGuid(); + private const string SettingAlias1 = "Setting1"; + private const string SettingAlias2 = "Setting2"; + + /// + /// Setup mocks for IPublishedSnapshotAccessor + /// + /// + private IPublishedSnapshotAccessor GetPublishedSnapshotAccessor() + { + var test1ContentType = Mock.Of(x => + x.IsElement == true + && x.Key == ContentKey1 + && x.Alias == ContentAlias1); + var test2ContentType = Mock.Of(x => + x.IsElement == true + && x.Key == ContentKey2 + && x.Alias == ContentAlias2); + var test3ContentType = Mock.Of(x => + x.IsElement == true + && x.Key == SettingKey1 + && x.Alias == SettingAlias1); + var test4ContentType = Mock.Of(x => + x.IsElement == true + && x.Key == SettingKey2 + && x.Alias == SettingAlias2); + var contentCache = new Mock(); + contentCache.Setup(x => x.GetContentType(ContentKey1)).Returns(test1ContentType); + contentCache.Setup(x => x.GetContentType(ContentKey2)).Returns(test2ContentType); + contentCache.Setup(x => x.GetContentType(SettingKey1)).Returns(test3ContentType); + contentCache.Setup(x => x.GetContentType(SettingKey2)).Returns(test4ContentType); + var publishedSnapshot = Mock.Of(x => x.Content == contentCache.Object); + var publishedSnapshotAccessor = Mock.Of(x => x.PublishedSnapshot == publishedSnapshot); + return publishedSnapshotAccessor; + } + + private BlockListPropertyValueConverter CreateConverter() + { + var publishedSnapshotAccessor = GetPublishedSnapshotAccessor(); + var publishedModelFactory = new NoopPublishedModelFactory(); + var editor = new BlockListPropertyValueConverter( + Mock.Of(), + new BlockEditorConverter(publishedSnapshotAccessor, publishedModelFactory)); + return editor; + } + + private BlockListConfiguration ConfigForMany() => new BlockListConfiguration + { + Blocks = new[] { + new BlockListConfiguration.BlockConfiguration + { + ContentElementTypeKey = ContentKey1, + SettingsElementTypeKey = SettingKey2 + }, + new BlockListConfiguration.BlockConfiguration + { + ContentElementTypeKey = ContentKey2, + SettingsElementTypeKey = SettingKey1 + } + } + }; + + private BlockListConfiguration ConfigForSingle() => new BlockListConfiguration + { + Blocks = new[] { + new BlockListConfiguration.BlockConfiguration + { + ContentElementTypeKey = ContentKey1 + } + } + }; + + private IPublishedPropertyType GetPropertyType(BlockListConfiguration config) + { + var dataType = new PublishedDataType(1, "test", new Lazy(() => config)); + var propertyType = Mock.Of(x => + x.EditorAlias == Constants.PropertyEditors.Aliases.BlockList + && x.DataType == dataType); + return propertyType; + } + + [Test] + public void Is_Converter_For() + { + var editor = CreateConverter(); + Assert.IsTrue(editor.IsConverter(Mock.Of(x => x.EditorAlias == Constants.PropertyEditors.Aliases.BlockList))); + Assert.IsFalse(editor.IsConverter(Mock.Of(x => x.EditorAlias == Constants.PropertyEditors.Aliases.NestedContent))); + } + + [Test] + public void Get_Value_Type_Multiple() + { + var editor = CreateConverter(); + var config = ConfigForMany(); + + var dataType = new PublishedDataType(1, "test", new Lazy(() => config)); + var propType = Mock.Of(x => x.DataType == dataType); + + var valueType = editor.GetPropertyValueType(propType); + + // the result is always block list model + Assert.AreEqual(typeof(BlockListModel), valueType); + } + + [Test] + public void Get_Value_Type_Single() + { + var editor = CreateConverter(); + var config = ConfigForSingle(); + + var dataType = new PublishedDataType(1, "test", new Lazy(() => config)); + var propType = Mock.Of(x => x.DataType == dataType); + + var valueType = editor.GetPropertyValueType(propType); + + // the result is always block list model + Assert.AreEqual(typeof(BlockListModel), valueType); + } + + [Test] + public void Convert_Null_Empty() + { + var editor = CreateConverter(); + var config = ConfigForMany(); + var propertyType = GetPropertyType(config); + var publishedElement = Mock.Of(); + + string json = null; + var converted = editor.ConvertIntermediateToObject(publishedElement, propertyType, PropertyCacheLevel.None, json, false) as BlockListModel; + + Assert.IsNotNull(converted); + Assert.AreEqual(0, converted.Count); + + json = string.Empty; + converted = editor.ConvertIntermediateToObject(publishedElement, propertyType, PropertyCacheLevel.None, json, false) as BlockListModel; + + Assert.IsNotNull(converted); + Assert.AreEqual(0, converted.Count); + } + + [Test] + public void Convert_Valid_Empty_Json() + { + var editor = CreateConverter(); + var config = ConfigForMany(); + var propertyType = GetPropertyType(config); + var publishedElement = Mock.Of(); + + var json = "{}"; + var converted = editor.ConvertIntermediateToObject(publishedElement, propertyType, PropertyCacheLevel.None, json, false) as BlockListModel; + + Assert.IsNotNull(converted); + Assert.AreEqual(0, converted.Count); + + json = @"{ +layout: {}, +data: []}"; + converted = editor.ConvertIntermediateToObject(publishedElement, propertyType, PropertyCacheLevel.None, json, false) as BlockListModel; + + Assert.IsNotNull(converted); + Assert.AreEqual(0, converted.Count); + + // Even though there is a layout, there is no data, so the conversion will result in zero elements in total + json = @" +{ + layout: { + '" + Constants.PropertyEditors.Aliases.BlockList + @"': [ + { + 'contentUdi': 'umb://element/e7dba547615b4e9ab4ab2a7674845bc9' + } + ] + }, + contentData: [] +}"; + + converted = editor.ConvertIntermediateToObject(publishedElement, propertyType, PropertyCacheLevel.None, json, false) as BlockListModel; + + Assert.IsNotNull(converted); + Assert.AreEqual(0, converted.Count); + + // Even though there is a layout and data, the data is invalid (missing required keys) so the conversion will result in zero elements in total + json = @" +{ + layout: { + '" + Constants.PropertyEditors.Aliases.BlockList + @"': [ + { + 'contentUdi': 'umb://element/e7dba547615b4e9ab4ab2a7674845bc9' + } + ] + }, + contentData: [ + { + 'udi': 'umb://element/e7dba547615b4e9ab4ab2a7674845bc9' + } + ] +}"; + + converted = editor.ConvertIntermediateToObject(publishedElement, propertyType, PropertyCacheLevel.None, json, false) as BlockListModel; + + Assert.IsNotNull(converted); + Assert.AreEqual(0, converted.Count); + + // Everthing is ok except the udi reference in the layout doesn't match the data so it will be empty + json = @" +{ + layout: { + '" + Constants.PropertyEditors.Aliases.BlockList + @"': [ + { + 'contentUdi': 'umb://element/1304E1DDAC87439684FE8A399231CB3D' + } + ] + }, + contentData: [ + { + 'contentTypeKey': '" + ContentKey1 + @"', + 'key': '1304E1DD-0000-4396-84FE-8A399231CB3D' + } + ] +}"; + + converted = editor.ConvertIntermediateToObject(publishedElement, propertyType, PropertyCacheLevel.None, json, false) as BlockListModel; + + Assert.IsNotNull(converted); + Assert.AreEqual(0, converted.Count); + } + + [Test] + public void Convert_Valid_Json() + { + var editor = CreateConverter(); + var config = ConfigForMany(); + var propertyType = GetPropertyType(config); + var publishedElement = Mock.Of(); + + var json = @" +{ + layout: { + '" + Constants.PropertyEditors.Aliases.BlockList + @"': [ + { + 'contentUdi': 'umb://element/1304E1DDAC87439684FE8A399231CB3D' + } + ] + }, + contentData: [ + { + 'contentTypeKey': '" + ContentKey1 + @"', + 'udi': 'umb://element/1304E1DDAC87439684FE8A399231CB3D' + } + ] +}"; + var converted = editor.ConvertIntermediateToObject(publishedElement, propertyType, PropertyCacheLevel.None, json, false) as BlockListModel; + + Assert.IsNotNull(converted); + Assert.AreEqual(1, converted.Count); + var item0 = converted[0].Content; + Assert.AreEqual(Guid.Parse("1304E1DD-AC87-4396-84FE-8A399231CB3D"), item0.Key); + Assert.AreEqual("Test1", item0.ContentType.Alias); + Assert.IsNull(converted[0].Settings); + Assert.AreEqual(Udi.Parse("umb://element/1304E1DDAC87439684FE8A399231CB3D"), converted[0].ContentUdi); + } + + [Test] + public void Get_Data_From_Layout_Item() + { + var editor = CreateConverter(); + var config = ConfigForMany(); + var propertyType = GetPropertyType(config); + var publishedElement = Mock.Of(); + + var json = @" +{ + layout: { + '" + Constants.PropertyEditors.Aliases.BlockList + @"': [ + { + 'contentUdi': 'umb://element/1304E1DDAC87439684FE8A399231CB3D', + 'settingsUdi': 'umb://element/1F613E26CE274898908A561437AF5100' + }, + { + 'contentUdi': 'umb://element/0A4A416E547D464FABCC6F345C17809A', + 'settingsUdi': 'umb://element/63027539B0DB45E7B70459762D4E83DD' + } + ] + }, + contentData: [ + { + 'contentTypeKey': '" + ContentKey1 + @"', + 'udi': 'umb://element/1304E1DDAC87439684FE8A399231CB3D' + }, + { + 'contentTypeKey': '" + ContentKey2 + @"', + 'udi': 'umb://element/E05A034704424AB3A520E048E6197E79' + }, + { + 'contentTypeKey': '" + ContentKey2 + @"', + 'udi': 'umb://element/0A4A416E547D464FABCC6F345C17809A' + } + ], + settingsData: [ + { + 'contentTypeKey': '" + SettingKey1 + @"', + 'udi': 'umb://element/63027539B0DB45E7B70459762D4E83DD' + }, + { + 'contentTypeKey': '" + SettingKey2 + @"', + 'udi': 'umb://element/1F613E26CE274898908A561437AF5100' + }, + { + 'contentTypeKey': '" + SettingKey2 + @"', + 'udi': 'umb://element/BCF4BA3DA40C496C93EC58FAC85F18B9' + } + ], +}"; + + var converted = editor.ConvertIntermediateToObject(publishedElement, propertyType, PropertyCacheLevel.None, json, false) as BlockListModel; + + Assert.IsNotNull(converted); + Assert.AreEqual(2, converted.Count); + + var item0 = converted[0]; + Assert.AreEqual(Guid.Parse("1304E1DD-AC87-4396-84FE-8A399231CB3D"), item0.Content.Key); + Assert.AreEqual("Test1", item0.Content.ContentType.Alias); + Assert.AreEqual(Guid.Parse("1F613E26CE274898908A561437AF5100"), item0.Settings.Key); + Assert.AreEqual("Setting2", item0.Settings.ContentType.Alias); + + var item1 = converted[1]; + Assert.AreEqual(Guid.Parse("0A4A416E-547D-464F-ABCC-6F345C17809A"), item1.Content.Key); + Assert.AreEqual("Test2", item1.Content.ContentType.Alias); + Assert.AreEqual(Guid.Parse("63027539B0DB45E7B70459762D4E83DD"), item1.Settings.Key); + Assert.AreEqual("Setting1", item1.Settings.ContentType.Alias); + + } + + [Test] + public void Data_Item_Removed_If_Removed_From_Config() + { + var editor = CreateConverter(); + + // The data below expects that ContentKey1 + ContentKey2 + SettingsKey1 + SettingsKey2 exist but only ContentKey2 exists so + // the data should all be filtered. + var config = new BlockListConfiguration + { + Blocks = new[] { + new BlockListConfiguration.BlockConfiguration + { + ContentElementTypeKey = ContentKey2, + SettingsElementTypeKey = null + } + } + }; + + var propertyType = GetPropertyType(config); + var publishedElement = Mock.Of(); + + var json = @" +{ + layout: { + '" + Constants.PropertyEditors.Aliases.BlockList + @"': [ + { + 'contentUdi': 'umb://element/1304E1DDAC87439684FE8A399231CB3D', + 'settingsUdi': 'umb://element/1F613E26CE274898908A561437AF5100' + }, + { + 'contentUdi': 'umb://element/0A4A416E547D464FABCC6F345C17809A', + 'settingsUdi': 'umb://element/63027539B0DB45E7B70459762D4E83DD' + } + ] + }, + contentData: [ + { + 'contentTypeKey': '" + ContentKey1 + @"', + 'udi': 'umb://element/1304E1DDAC87439684FE8A399231CB3D' + }, + { + 'contentTypeKey': '" + ContentKey2 + @"', + 'udi': 'umb://element/E05A034704424AB3A520E048E6197E79' + }, + { + 'contentTypeKey': '" + ContentKey2 + @"', + 'udi': 'umb://element/0A4A416E547D464FABCC6F345C17809A' + } + ], + settingsData: [ + { + 'contentTypeKey': '" + SettingKey1 + @"', + 'udi': 'umb://element/63027539B0DB45E7B70459762D4E83DD' + }, + { + 'contentTypeKey': '" + SettingKey2 + @"', + 'udi': 'umb://element/1F613E26CE274898908A561437AF5100' + }, + { + 'contentTypeKey': '" + SettingKey2 + @"', + 'udi': 'umb://element/BCF4BA3DA40C496C93EC58FAC85F18B9' + } + ], +}"; + + var converted = editor.ConvertIntermediateToObject(publishedElement, propertyType, PropertyCacheLevel.None, json, false) as BlockListModel; + + Assert.IsNotNull(converted); + Assert.AreEqual(1, converted.Count); + + var item0 = converted[0]; + Assert.AreEqual(Guid.Parse("0A4A416E-547D-464F-ABCC-6F345C17809A"), item0.Content.Key); + Assert.AreEqual("Test2", item0.Content.ContentType.Alias); + Assert.IsNull(item0.Settings); + + } + + } +} diff --git a/src/Umbraco.Tests/PropertyEditors/NestedContentPropertyComponentTests.cs b/src/Umbraco.Tests/PropertyEditors/NestedContentPropertyComponentTests.cs index 1b83c048d2..5b7e220123 100644 --- a/src/Umbraco.Tests/PropertyEditors/NestedContentPropertyComponentTests.cs +++ b/src/Umbraco.Tests/PropertyEditors/NestedContentPropertyComponentTests.cs @@ -9,6 +9,7 @@ using Umbraco.Web.Compose; namespace Umbraco.Tests.PropertyEditors { + [TestFixture] public class NestedContentPropertyComponentTests { diff --git a/src/Umbraco.Tests/PropertyEditors/PropertyEditorValueConverterTests.cs b/src/Umbraco.Tests/PropertyEditors/PropertyEditorValueConverterTests.cs index 43c1a83d33..a9e3e8b9db 100644 --- a/src/Umbraco.Tests/PropertyEditors/PropertyEditorValueConverterTests.cs +++ b/src/Umbraco.Tests/PropertyEditors/PropertyEditorValueConverterTests.cs @@ -93,7 +93,7 @@ namespace Umbraco.Tests.PropertyEditors }))); var publishedPropType = new PublishedPropertyType( - new PublishedContentType(1234, "test", PublishedItemType.Content, Enumerable.Empty(), Enumerable.Empty(), ContentVariation.Nothing), + new PublishedContentType(Guid.NewGuid(), 1234, "test", PublishedItemType.Content, Enumerable.Empty(), Enumerable.Empty(), ContentVariation.Nothing), new PropertyType("test", ValueStorageType.Nvarchar) { DataTypeId = 123 }, new PropertyValueConverterCollection(Enumerable.Empty()), Mock.Of(), mockPublishedContentTypeFactory.Object); diff --git a/src/Umbraco.Tests/Published/ConvertersTests.cs b/src/Umbraco.Tests/Published/ConvertersTests.cs index 671129848c..3c60f4ddd0 100644 --- a/src/Umbraco.Tests/Published/ConvertersTests.cs +++ b/src/Umbraco.Tests/Published/ConvertersTests.cs @@ -40,7 +40,7 @@ namespace Umbraco.Tests.Published yield return contentTypeFactory.CreatePropertyType(contentType, "prop1", 1); } - var elementType1 = contentTypeFactory.CreateContentType(1000, "element1", CreatePropertyTypes); + var elementType1 = contentTypeFactory.CreateContentType(Guid.NewGuid(), 1000, "element1", CreatePropertyTypes); var element1 = new PublishedElement(elementType1, Guid.NewGuid(), new Dictionary { { "prop1", "1234" } }, false); @@ -74,7 +74,7 @@ namespace Umbraco.Tests.Published => propertyType.EditorAlias.InvariantEquals("Umbraco.Void"); public Type GetPropertyValueType(IPublishedPropertyType propertyType) - => typeof (int); + => typeof(int); public PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) => PropertyCacheLevel.Element; @@ -83,10 +83,10 @@ namespace Umbraco.Tests.Published => int.TryParse(source as string, out int i) ? i : 0; public object ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) - => (int) inter; + => (int)inter; public object ConvertIntermediateToXPath(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) - => ((int) inter).ToString(); + => ((int)inter).ToString(); } #endregion @@ -120,11 +120,11 @@ namespace Umbraco.Tests.Published yield return contentTypeFactory.CreatePropertyType(contentType, "prop1", 1); } - var elementType1 = contentTypeFactory.CreateContentType(1000, "element1", CreatePropertyTypes); + var elementType1 = contentTypeFactory.CreateContentType(Guid.NewGuid(), 1000, "element1", CreatePropertyTypes); var element1 = new PublishedElement(elementType1, Guid.NewGuid(), new Dictionary { { "prop1", "1234" } }, false); - var cntType1 = contentTypeFactory.CreateContentType(1001, "cnt1", t => Enumerable.Empty()); + var cntType1 = contentTypeFactory.CreateContentType(Guid.NewGuid(), 1001, "cnt1", t => Enumerable.Empty()); var cnt1 = new SolidPublishedContent(cntType1) { Id = 1234 }; cacheContent[cnt1.Id] = cnt1; @@ -143,7 +143,7 @@ namespace Umbraco.Tests.Published } public bool? IsValue(object value, PropertyValueLevel level) - => value != null && (!(value is string) || string.IsNullOrWhiteSpace((string) value) == false); + => value != null && (!(value is string) || string.IsNullOrWhiteSpace((string)value) == false); public bool IsConverter(IPublishedPropertyType propertyType) => propertyType.EditorAlias.InvariantEquals("Umbraco.Void"); @@ -162,10 +162,10 @@ namespace Umbraco.Tests.Published => int.TryParse(source as string, out int i) ? i : -1; public object ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) - => _publishedSnapshotAccessor.PublishedSnapshot.Content.GetById((int) inter); + => _publishedSnapshotAccessor.PublishedSnapshot.Content.GetById((int)inter); public object ConvertIntermediateToXPath(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) - => ((int) inter).ToString(); + => ((int)inter).ToString(); } #endregion @@ -215,10 +215,10 @@ namespace Umbraco.Tests.Published yield return contentTypeFactory.CreatePropertyType(contentType, "prop" + i, i); } - var elementType1 = contentTypeFactory.CreateContentType(1000, "element1", t => CreatePropertyTypes(t, 1)); - var elementType2 = contentTypeFactory.CreateContentType(1001, "element2", t => CreatePropertyTypes(t, 2)); - var contentType1 = contentTypeFactory.CreateContentType(1002, "content1", t => CreatePropertyTypes(t, 1)); - var contentType2 = contentTypeFactory.CreateContentType(1003, "content2", t => CreatePropertyTypes(t, 2)); + var elementType1 = contentTypeFactory.CreateContentType(Guid.NewGuid(), 1000, "element1", t => CreatePropertyTypes(t, 1)); + var elementType2 = contentTypeFactory.CreateContentType(Guid.NewGuid(), 1001, "element2", t => CreatePropertyTypes(t, 2)); + var contentType1 = contentTypeFactory.CreateContentType(Guid.NewGuid(), 1002, "content1", t => CreatePropertyTypes(t, 1)); + var contentType2 = contentTypeFactory.CreateContentType(Guid.NewGuid(), 1003, "content2", t => CreatePropertyTypes(t, 2)); var element1 = new PublishedElement(elementType1, Guid.NewGuid(), new Dictionary { { "prop1", "val1" } }, false); var element2 = new PublishedElement(elementType2, Guid.NewGuid(), new Dictionary { { "prop2", "1003" } }, false); @@ -239,22 +239,22 @@ namespace Umbraco.Tests.Published // can get the actual property Clr type // ie ModelType gets properly mapped by IPublishedContentModelFactory // must test ModelClrType with special equals 'cos they are not ref-equals - Assert.IsTrue(ModelType.Equals(typeof (IEnumerable<>).MakeGenericType(ModelType.For("content1")), contentType2.GetPropertyType("prop2").ModelClrType)); - Assert.AreEqual(typeof (IEnumerable), contentType2.GetPropertyType("prop2").ClrType); + Assert.IsTrue(ModelType.Equals(typeof(IEnumerable<>).MakeGenericType(ModelType.For("content1")), contentType2.GetPropertyType("prop2").ModelClrType)); + Assert.AreEqual(typeof(IEnumerable), contentType2.GetPropertyType("prop2").ClrType); // can create a model for an element var model1 = factory.CreateModel(element1); Assert.IsInstanceOf(model1); - Assert.AreEqual("val1", ((PublishedSnapshotTestObjects.TestElementModel1) model1).Prop1); + Assert.AreEqual("val1", ((PublishedSnapshotTestObjects.TestElementModel1)model1).Prop1); // can create a model for a published content var model2 = factory.CreateModel(element2); Assert.IsInstanceOf(model2); - var mmodel2 = (PublishedSnapshotTestObjects.TestElementModel2) model2; + var mmodel2 = (PublishedSnapshotTestObjects.TestElementModel2)model2; // and get direct property Assert.IsInstanceOf(model2.Value("prop2")); - Assert.AreEqual(1, ((PublishedSnapshotTestObjects.TestContentModel1[]) model2.Value("prop2")).Length); + Assert.AreEqual(1, ((PublishedSnapshotTestObjects.TestContentModel1[])model2.Value("prop2")).Length); // and get model property Assert.IsInstanceOf>(mmodel2.Prop2); @@ -271,7 +271,7 @@ namespace Umbraco.Tests.Published => propertyType.EditorAlias == "Umbraco.Void"; public override Type GetPropertyValueType(IPublishedPropertyType propertyType) - => typeof (string); + => typeof(string); public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) => PropertyCacheLevel.Element; @@ -290,7 +290,7 @@ namespace Umbraco.Tests.Published => propertyType.EditorAlias == "Umbraco.Void.2"; public override Type GetPropertyValueType(IPublishedPropertyType propertyType) - => typeof (IEnumerable<>).MakeGenericType(ModelType.For("content1")); + => typeof(IEnumerable<>).MakeGenericType(ModelType.For("content1")); public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) => PropertyCacheLevel.Elements; @@ -303,7 +303,7 @@ namespace Umbraco.Tests.Published public override object ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) { - return ((int[]) inter).Select(x => (PublishedSnapshotTestObjects.TestContentModel1) _publishedSnapshotAccessor.PublishedSnapshot.Content.GetById(x)).ToArray(); + return ((int[])inter).Select(x => (PublishedSnapshotTestObjects.TestContentModel1)_publishedSnapshotAccessor.PublishedSnapshot.Content.GetById(x)).ToArray(); } } diff --git a/src/Umbraco.Tests/Published/NestedContentTests.cs b/src/Umbraco.Tests/Published/NestedContentTests.cs index adfb9d3b6b..c5cee8843f 100644 --- a/src/Umbraco.Tests/Published/NestedContentTests.cs +++ b/src/Umbraco.Tests/Published/NestedContentTests.cs @@ -34,7 +34,7 @@ namespace Umbraco.Tests.Published var proflog = new ProfilingLogger(logger, profiler); PropertyEditorCollection editors = null; - var editor = new NestedContentPropertyEditor(logger, new Lazy(() => editors), Mock.Of(), Mock.Of()); + var editor = new NestedContentPropertyEditor(logger, new Lazy(() => editors), Mock.Of(), Mock.Of(), Mock.Of()); editors = new PropertyEditorCollection(new DataEditorCollection(new DataEditor[] { editor })); var dataType1 = new DataType(editor) @@ -100,8 +100,8 @@ namespace Umbraco.Tests.Published .Returns((string alias) => { return alias == "contentN1" - ? (IList) new List() - : (IList) new List(); + ? (IList)new List() + : (IList)new List(); }); var contentCache = new Mock(); @@ -142,9 +142,9 @@ namespace Umbraco.Tests.Published yield return factory.CreatePropertyType(contentType, "propertyN1", 3); } - var contentType1 = factory.CreateContentType(1, "content1", CreatePropertyTypes1); - var contentType2 = factory.CreateContentType(2, "content2", CreatePropertyTypes2); - var contentTypeN1 = factory.CreateContentType(2, "contentN1", CreatePropertyTypesN1, isElement: true); + var contentType1 = factory.CreateContentType(Guid.NewGuid(), 1, "content1", CreatePropertyTypes1); + var contentType2 = factory.CreateContentType(Guid.NewGuid(), 2, "content2", CreatePropertyTypes2); + var contentTypeN1 = factory.CreateContentType(Guid.NewGuid(), 2, "contentN1", CreatePropertyTypesN1, isElement: true); // mocked content cache returns content types contentCache @@ -164,7 +164,7 @@ namespace Umbraco.Tests.Published (var contentType1, _) = CreateContentTypes(); // nested single converter returns the proper value clr type TestModel, and cache level - Assert.AreEqual(typeof (TestElementModel), contentType1.GetPropertyType("property1").ClrType); + Assert.AreEqual(typeof(TestElementModel), contentType1.GetPropertyType("property1").ClrType); Assert.AreEqual(PropertyCacheLevel.Element, contentType1.GetPropertyType("property1").CacheLevel); var key = Guid.NewGuid(); @@ -172,7 +172,7 @@ namespace Umbraco.Tests.Published var content = new SolidPublishedContent(contentType1) { Key = key, - Properties = new [] + Properties = new[] { new TestPublishedProperty(contentType1.GetPropertyType("property1"), $@"[ {{ ""key"": ""{keyA}"", ""propertyN1"": ""foo"", ""ncContentTypeAlias"": ""contentN1"" }} @@ -183,7 +183,7 @@ namespace Umbraco.Tests.Published // nested single converter returns proper TestModel value Assert.IsInstanceOf(value); - var valueM = (TestElementModel) value; + var valueM = (TestElementModel)value; Assert.AreEqual("foo", valueM.PropValue); Assert.AreEqual(keyA, valueM.Key); } @@ -194,7 +194,7 @@ namespace Umbraco.Tests.Published (_, var contentType2) = CreateContentTypes(); // nested many converter returns the proper value clr type IEnumerable, and cache level - Assert.AreEqual(typeof (IEnumerable), contentType2.GetPropertyType("property2").ClrType); + Assert.AreEqual(typeof(IEnumerable), contentType2.GetPropertyType("property2").ClrType); Assert.AreEqual(PropertyCacheLevel.Element, contentType2.GetPropertyType("property2").CacheLevel); var key = Guid.NewGuid(); @@ -216,7 +216,7 @@ namespace Umbraco.Tests.Published // nested many converter returns proper IEnumerable value Assert.IsInstanceOf>(value); Assert.IsInstanceOf>(value); - var valueM = ((IEnumerable) value).ToArray(); + var valueM = ((IEnumerable)value).ToArray(); Assert.AreEqual("foo", valueM[0].PropValue); Assert.AreEqual(keyA, valueM[0].Key); Assert.AreEqual("bar", valueM[1].PropValue); diff --git a/src/Umbraco.Tests/Published/PropertyCacheLevelTests.cs b/src/Umbraco.Tests/Published/PropertyCacheLevelTests.cs index 9db539d142..a795ca433e 100644 --- a/src/Umbraco.Tests/Published/PropertyCacheLevelTests.cs +++ b/src/Umbraco.Tests/Published/PropertyCacheLevelTests.cs @@ -41,7 +41,7 @@ namespace Umbraco.Tests.Published yield return publishedContentTypeFactory.CreatePropertyType(contentType, "prop1", 1); } - var setType1 = publishedContentTypeFactory.CreateContentType(1000, "set1", CreatePropertyTypes); + var setType1 = publishedContentTypeFactory.CreateContentType(Guid.NewGuid(), 1000, "set1", CreatePropertyTypes); // PublishedElementPropertyBase.GetCacheLevels: // @@ -122,7 +122,7 @@ namespace Umbraco.Tests.Published yield return publishedContentTypeFactory.CreatePropertyType(contentType, "prop1", 1); } - var setType1 = publishedContentTypeFactory.CreateContentType(1000, "set1", CreatePropertyTypes); + var setType1 = publishedContentTypeFactory.CreateContentType(Guid.NewGuid(), 1000, "set1", CreatePropertyTypes); var elementsCache = new FastDictionaryAppCache(); var snapshotCache = new FastDictionaryAppCache(); @@ -199,7 +199,7 @@ namespace Umbraco.Tests.Published yield return publishedContentTypeFactory.CreatePropertyType(contentType, "prop1", 1); } - var setType1 = publishedContentTypeFactory.CreateContentType(1000, "set1", CreatePropertyTypes); + var setType1 = publishedContentTypeFactory.CreateContentType(Guid.NewGuid(), 1000, "set1", CreatePropertyTypes); Assert.Throws(() => { diff --git a/src/Umbraco.Tests/PublishedContent/ContentSerializationTests.cs b/src/Umbraco.Tests/PublishedContent/ContentSerializationTests.cs index debfddef98..b3543dad1a 100644 --- a/src/Umbraco.Tests/PublishedContent/ContentSerializationTests.cs +++ b/src/Umbraco.Tests/PublishedContent/ContentSerializationTests.cs @@ -1,10 +1,9 @@ -using NUnit.Framework; +using Moq; +using NUnit.Framework; using System; -using System.Collections; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using Umbraco.Core.Models; +using Umbraco.Core.PropertyEditors; using Umbraco.Web.PublishedCache.NuCache.DataSource; namespace Umbraco.Tests.PublishedContent @@ -13,13 +12,13 @@ namespace Umbraco.Tests.PublishedContent public class ContentSerializationTests { [Test] - public void Ensure_Same_Results() + public void GivenACacheModel_WhenItsSerializedAndDeserializedWithAnySerializer_TheResultsAreTheSame() { var jsonSerializer = new JsonContentNestedDataSerializer(); - var msgPackSerializer = new MsgPackContentNestedDataSerializer(); + var msgPackSerializer = new MsgPackContentNestedDataSerializer(Mock.Of()); var now = DateTime.Now; - var content = new ContentNestedData + var cacheModel = new ContentCacheDataModel { PropertyData = new Dictionary { @@ -55,14 +54,16 @@ namespace Umbraco.Tests.PublishedContent UrlSegment = "home" }; - var json = jsonSerializer.Serialize(content); - var msgPack = msgPackSerializer.Serialize(content); + var content = Mock.Of(x => x.ContentTypeId == 1); + + var json = jsonSerializer.Serialize(content, cacheModel).StringData; + var msgPack = msgPackSerializer.Serialize(content, cacheModel).ByteData; Console.WriteLine(json); Console.WriteLine(msgPackSerializer.ToJson(msgPack)); - var jsonContent = jsonSerializer.Deserialize(json); - var msgPackContent = msgPackSerializer.Deserialize(msgPack); + var jsonContent = jsonSerializer.Deserialize(content, json, null); + var msgPackContent = msgPackSerializer.Deserialize(content, null, msgPack); CollectionAssert.AreEqual(jsonContent.CultureData.Keys, msgPackContent.CultureData.Keys); diff --git a/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs b/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs index fef096498c..afba2dcc4f 100644 --- a/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs +++ b/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs @@ -37,7 +37,7 @@ namespace Umbraco.Tests.PublishedContent private ContentType _contentTypeInvariant; private ContentType _contentTypeVariant; private TestDataSource _source; - private IContentNestedDataSerializer _contentNestedDataSerializer; + private IContentCacheDataSerializerFactory _contentNestedDataSerializerFactory; [TearDown] public void Teardown() @@ -135,7 +135,7 @@ namespace Umbraco.Tests.PublishedContent // create a data source for NuCache _source = new TestDataSource(kits()); - _contentNestedDataSerializer = new JsonContentNestedDataSerializer(); + _contentNestedDataSerializerFactory = new JsonContentNestedDataSerializerFactory(); // at last, create the complete NuCache snapshot service! var options = new PublishedSnapshotServiceOptions { IgnoreLocalDb = true }; @@ -158,7 +158,7 @@ namespace Umbraco.Tests.PublishedContent Mock.Of(), Mock.Of(), new UrlSegmentProviderCollection(new[] { new DefaultUrlSegmentProvider() }), - _contentNestedDataSerializer); + _contentNestedDataSerializerFactory); // invariant is the current default _variationAccesor.VariationContext = new VariationContext(); diff --git a/src/Umbraco.Tests/PublishedContent/NuCacheTests.cs b/src/Umbraco.Tests/PublishedContent/NuCacheTests.cs index 792ccc8529..eee3500495 100644 --- a/src/Umbraco.Tests/PublishedContent/NuCacheTests.cs +++ b/src/Umbraco.Tests/PublishedContent/NuCacheTests.cs @@ -33,7 +33,7 @@ namespace Umbraco.Tests.PublishedContent { private IPublishedSnapshotService _snapshotService; private IVariationContextAccessor _variationAccesor; - private IContentNestedDataSerializer _contentNestedDataSerializer; + private IContentCacheDataSerializerFactory _contentNestedDataSerializerFactory; private ContentType _contentType; private PropertyType _propertyType; @@ -115,7 +115,7 @@ namespace Umbraco.Tests.PublishedContent // create a data source for NuCache var dataSource = new TestDataSource(kit); - _contentNestedDataSerializer = new JsonContentNestedDataSerializer(); + _contentNestedDataSerializerFactory = new JsonContentNestedDataSerializerFactory(); var runtime = Mock.Of(); Mock.Get(runtime).Setup(x => x.Level).Returns(RuntimeLevel.Run); @@ -204,7 +204,7 @@ namespace Umbraco.Tests.PublishedContent Mock.Of(), Mock.Of(), new UrlSegmentProviderCollection(new[] { new DefaultUrlSegmentProvider() }), - _contentNestedDataSerializer); + _contentNestedDataSerializerFactory); // invariant is the current default _variationAccesor.VariationContext = new VariationContext(); diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentDataTableTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentDataTableTests.cs index cc455b8e5d..7277f75be2 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentDataTableTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentDataTableTests.cs @@ -98,7 +98,7 @@ namespace Umbraco.Tests.PublishedContent var doc = GetContent(true, 1); //change a doc type alias var c = (SolidPublishedContent)doc.Children.ElementAt(0); - c.ContentType = new PublishedContentType(22, "DontMatch", PublishedItemType.Content, Enumerable.Empty(), Enumerable.Empty(), ContentVariation.Nothing); + c.ContentType = new PublishedContentType(Guid.NewGuid(), 22, "DontMatch", PublishedItemType.Content, Enumerable.Empty(), Enumerable.Empty(), ContentVariation.Nothing); var dt = doc.ChildrenAsTable(Current.Services, "Child"); @@ -129,7 +129,7 @@ namespace Umbraco.Tests.PublishedContent var factory = new PublishedContentTypeFactory(Mock.Of(), new PropertyValueConverterCollection(Array.Empty()), dataTypeService); var contentTypeAlias = createChildren ? "Parent" : "Child"; - var contentType = new PublishedContentType(22, contentTypeAlias, PublishedItemType.Content, Enumerable.Empty(), Enumerable.Empty(), ContentVariation.Nothing); + var contentType = new PublishedContentType(Guid.NewGuid(), 22, contentTypeAlias, PublishedItemType.Content, Enumerable.Empty(), Enumerable.Empty(), ContentVariation.Nothing); var d = new SolidPublishedContent(contentType) { CreateDate = DateTime.Now, diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentLanguageVariantTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentLanguageVariantTests.cs index 62447742ff..636f8502ed 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentLanguageVariantTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentLanguageVariantTests.cs @@ -73,14 +73,14 @@ namespace Umbraco.Tests.PublishedContent yield return factory.CreatePropertyType(contentType, "noprop", 1, variations: ContentVariation.Culture); } - var contentType1 = factory.CreateContentType(1, "ContentType1", Enumerable.Empty(), CreatePropertyTypes1); + var contentType1 = factory.CreateContentType(Guid.NewGuid(), 1, "ContentType1", Enumerable.Empty(), CreatePropertyTypes1); IEnumerable CreatePropertyTypes2(IPublishedContentType contentType) { yield return factory.CreatePropertyType(contentType, "prop3", 1, variations: ContentVariation.Culture); } - var contentType2 = factory.CreateContentType(2, "contentType2", Enumerable.Empty(), CreatePropertyTypes2); + var contentType2 = factory.CreateContentType(Guid.NewGuid(), 2, "contentType2", Enumerable.Empty(), CreatePropertyTypes2); var prop1 = new SolidPublishedPropertyWithLanguageVariants { diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentMoreTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentMoreTests.cs index 440474ae74..e2a1721d26 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentMoreTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentMoreTests.cs @@ -7,6 +7,7 @@ using Umbraco.Web; using Umbraco.Core; using Umbraco.Tests.Testing; using Umbraco.Web.Composing; +using System; namespace Umbraco.Tests.PublishedContent { @@ -21,9 +22,9 @@ namespace Umbraco.Tests.PublishedContent yield return factory.CreatePropertyType(contentType, "prop1", 1); } - var contentType1 = factory.CreateContentType(1, "ContentType1", Enumerable.Empty(), CreatePropertyTypes); - var contentType2 = factory.CreateContentType(2, "ContentType2", Enumerable.Empty(), CreatePropertyTypes); - var contentType2Sub = factory.CreateContentType(3, "ContentType2Sub", Enumerable.Empty(), CreatePropertyTypes); + var contentType1 = factory.CreateContentType(Guid.NewGuid(), 1, "ContentType1", Enumerable.Empty(), CreatePropertyTypes); + var contentType2 = factory.CreateContentType(Guid.NewGuid(), 2, "ContentType2", Enumerable.Empty(), CreatePropertyTypes); + var contentType2Sub = factory.CreateContentType(Guid.NewGuid(), 3, "ContentType2Sub", Enumerable.Empty(), CreatePropertyTypes); var content = new SolidPublishedContent(contentType1) { diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentTestBase.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentTestBase.cs index 59791fc645..7e0d0f332e 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentTestBase.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentTestBase.cs @@ -13,6 +13,7 @@ using Umbraco.Core.Services; using Umbraco.Web; using Umbraco.Web.Templates; using Umbraco.Web.Models; +using System; namespace Umbraco.Tests.PublishedContent { @@ -56,7 +57,7 @@ namespace Umbraco.Tests.PublishedContent yield return publishedContentTypeFactory.CreatePropertyType(contentType, "content", 1); } - var type = new AutoPublishedContentType(0, "anything", CreatePropertyTypes); + var type = new AutoPublishedContentType(Guid.NewGuid(), 0, "anything", CreatePropertyTypes); ContentTypesCache.GetPublishedContentTypeByAlias = alias => type; var umbracoContext = GetUmbracoContext("/test"); diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs index 998fc92380..cafda161f4 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs @@ -83,8 +83,8 @@ namespace Umbraco.Tests.PublishedContent } var compositionAliases = new[] { "MyCompositionAlias" }; - var anythingType = new AutoPublishedContentType(0, "anything", compositionAliases, CreatePropertyTypes); - var homeType = new AutoPublishedContentType(0, "home", compositionAliases, CreatePropertyTypes); + var anythingType = new AutoPublishedContentType(Guid.NewGuid(), 0, "anything", compositionAliases, CreatePropertyTypes); + var homeType = new AutoPublishedContentType(Guid.NewGuid(), 0, "home", compositionAliases, CreatePropertyTypes); ContentTypesCache.GetPublishedContentTypeByAlias = alias => alias.InvariantEquals("home") ? homeType : anythingType; } @@ -398,8 +398,8 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Children_GroupBy_DocumentTypeAlias() { - var home = new AutoPublishedContentType(22, "Home", new PublishedPropertyType[] { }); - var custom = new AutoPublishedContentType(23, "CustomDocument", new PublishedPropertyType[] { }); + var home = new AutoPublishedContentType(Guid.NewGuid(), 22, "Home", new PublishedPropertyType[] { }); + var custom = new AutoPublishedContentType(Guid.NewGuid(), 23, "CustomDocument", new PublishedPropertyType[] { }); var contentTypes = new Dictionary { { home.Alias, home }, @@ -419,8 +419,8 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Children_Where_DocumentTypeAlias() { - var home = new AutoPublishedContentType(22, "Home", new PublishedPropertyType[] { }); - var custom = new AutoPublishedContentType(23, "CustomDocument", new PublishedPropertyType[] { }); + var home = new AutoPublishedContentType(Guid.NewGuid(), 22, "Home", new PublishedPropertyType[] { }); + var custom = new AutoPublishedContentType(Guid.NewGuid(), 23, "CustomDocument", new PublishedPropertyType[] { }); var contentTypes = new Dictionary { { home.Alias, home }, @@ -903,7 +903,7 @@ namespace Umbraco.Tests.PublishedContent yield return factory.CreatePropertyType(contentType, "detached", 1003); } - var ct = factory.CreateContentType(0, "alias", CreatePropertyTypes); + var ct = factory.CreateContentType(Guid.NewGuid(), 0, "alias", CreatePropertyTypes); var pt = ct.GetPropertyType("detached"); var prop = new PublishedElementPropertyBase(pt, null, false, PropertyCacheLevel.None, 5548); Assert.IsInstanceOf(prop.GetValue()); @@ -935,7 +935,7 @@ namespace Umbraco.Tests.PublishedContent var guid = Guid.NewGuid(); - var ct = factory.CreateContentType(0, "alias", CreatePropertyTypes); + var ct = factory.CreateContentType(Guid.NewGuid(), 0, "alias", CreatePropertyTypes); var c = new ImageWithLegendModel(ct, guid, new Dictionary { diff --git a/src/Umbraco.Tests/PublishedContent/PublishedRouterTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedRouterTests.cs index d8dbabb569..4a93fadbdf 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedRouterTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedRouterTests.cs @@ -66,7 +66,7 @@ namespace Umbraco.Tests.PublishedContent pc.Setup(content => content.Path).Returns("-1,1"); pc.Setup(content => content.Parent).Returns(() => null); pc.Setup(content => content.Properties).Returns(new Collection()); - pc.Setup(content => content.ContentType).Returns(new PublishedContentType(22, "anything", PublishedItemType.Content, Enumerable.Empty(), Enumerable.Empty(), ContentVariation.Nothing)); + pc.Setup(content => content.ContentType).Returns(new PublishedContentType(Guid.NewGuid(), 22, "anything", PublishedItemType.Content, Enumerable.Empty(), Enumerable.Empty(), ContentVariation.Nothing)); return pc; } } diff --git a/src/Umbraco.Tests/PublishedContent/SolidPublishedSnapshot.cs b/src/Umbraco.Tests/PublishedContent/SolidPublishedSnapshot.cs index a7b6d3d18a..4a0af69999 100644 --- a/src/Umbraco.Tests/PublishedContent/SolidPublishedSnapshot.cs +++ b/src/Umbraco.Tests/PublishedContent/SolidPublishedSnapshot.cs @@ -44,7 +44,7 @@ namespace Umbraco.Tests.PublishedContent { } } - class SolidPublishedContentCache : PublishedCacheBase, IPublishedContentCache, IPublishedMediaCache + class SolidPublishedContentCache : PublishedCacheBase, IPublishedContentCache2, IPublishedMediaCache2 { private readonly Dictionary _content = new Dictionary(); @@ -150,6 +150,11 @@ namespace Umbraco.Tests.PublishedContent throw new NotImplementedException(); } + public override IPublishedContentType GetContentType(Guid key) + { + throw new NotImplementedException(); + } + public override IEnumerable GetByContentType(IPublishedContentType contentType) { throw new NotImplementedException(); @@ -378,7 +383,7 @@ namespace Umbraco.Tests.PublishedContent #endregion } - class PublishedContentStrong1 : PublishedContentModel + internal class PublishedContentStrong1 : PublishedContentModel { public PublishedContentStrong1(IPublishedContent content) : base(content) @@ -387,7 +392,7 @@ namespace Umbraco.Tests.PublishedContent public int StrongValue => this.Value("strongValue"); } - class PublishedContentStrong1Sub : PublishedContentStrong1 + internal class PublishedContentStrong1Sub : PublishedContentStrong1 { public PublishedContentStrong1Sub(IPublishedContent content) : base(content) @@ -396,7 +401,7 @@ namespace Umbraco.Tests.PublishedContent public int AnotherValue => this.Value("anotherValue"); } - class PublishedContentStrong2 : PublishedContentModel + internal class PublishedContentStrong2 : PublishedContentModel { public PublishedContentStrong2(IPublishedContent content) : base(content) @@ -405,7 +410,7 @@ namespace Umbraco.Tests.PublishedContent public int StrongValue => this.Value("strongValue"); } - class AutoPublishedContentType : PublishedContentType + internal class AutoPublishedContentType : PublishedContentType { private static readonly IPublishedPropertyType Default; @@ -418,20 +423,20 @@ namespace Umbraco.Tests.PublishedContent Default = factory.CreatePropertyType("*", 666); } - public AutoPublishedContentType(int id, string alias, IEnumerable propertyTypes) - : base(id, alias, PublishedItemType.Content, Enumerable.Empty(), propertyTypes, ContentVariation.Nothing) + public AutoPublishedContentType(Guid key, int id, string alias, IEnumerable propertyTypes) + : base(key, id, alias, PublishedItemType.Content, Enumerable.Empty(), propertyTypes, ContentVariation.Nothing) { } - public AutoPublishedContentType(int id, string alias, Func> propertyTypes) - : base(id, alias, PublishedItemType.Content, Enumerable.Empty(), propertyTypes, ContentVariation.Nothing) + public AutoPublishedContentType(Guid key, int id, string alias, Func> propertyTypes) + : base(key, id, alias, PublishedItemType.Content, Enumerable.Empty(), propertyTypes, ContentVariation.Nothing) { } - public AutoPublishedContentType(int id, string alias, IEnumerable compositionAliases, IEnumerable propertyTypes) - : base(id, alias, PublishedItemType.Content, compositionAliases, propertyTypes, ContentVariation.Nothing) + public AutoPublishedContentType(Guid key, int id, string alias, IEnumerable compositionAliases, IEnumerable propertyTypes) + : base(key, id, alias, PublishedItemType.Content, compositionAliases, propertyTypes, ContentVariation.Nothing) { } - public AutoPublishedContentType(int id, string alias, IEnumerable compositionAliases, Func> propertyTypes) - : base(id, alias, PublishedItemType.Content, compositionAliases, propertyTypes, ContentVariation.Nothing) + public AutoPublishedContentType(Guid key, int id, string alias, IEnumerable compositionAliases, Func> propertyTypes) + : base(key, id, alias, PublishedItemType.Content, compositionAliases, propertyTypes, ContentVariation.Nothing) { } public override IPublishedPropertyType GetPropertyType(string alias) diff --git a/src/Umbraco.Tests/Routing/ContentFinderByAliasTests.cs b/src/Umbraco.Tests/Routing/ContentFinderByAliasTests.cs index 2aa01916fb..25d3eda081 100644 --- a/src/Umbraco.Tests/Routing/ContentFinderByAliasTests.cs +++ b/src/Umbraco.Tests/Routing/ContentFinderByAliasTests.cs @@ -1,4 +1,5 @@ -using System.Linq; +using System; +using System.Linq; using Moq; using NUnit.Framework; using Umbraco.Core; @@ -27,7 +28,7 @@ namespace Umbraco.Tests.Routing Mock.Of(), Mock.Of()), }; - _publishedContentType = new PublishedContentType(0, "Doc", PublishedItemType.Content, Enumerable.Empty(), properties, ContentVariation.Nothing); + _publishedContentType = new PublishedContentType(Guid.NewGuid(), 0, "Doc", PublishedItemType.Content, Enumerable.Empty(), properties, ContentVariation.Nothing); } protected override PublishedContentType GetPublishedContentTypeByAlias(string alias) diff --git a/src/Umbraco.Tests/Routing/ContentFinderByAliasWithDomainsTests.cs b/src/Umbraco.Tests/Routing/ContentFinderByAliasWithDomainsTests.cs index 0c1f89f430..18fe268a9a 100644 --- a/src/Umbraco.Tests/Routing/ContentFinderByAliasWithDomainsTests.cs +++ b/src/Umbraco.Tests/Routing/ContentFinderByAliasWithDomainsTests.cs @@ -1,4 +1,5 @@ -using System.Linq; +using System; +using System.Linq; using Moq; using NUnit.Framework; using Umbraco.Core; @@ -25,7 +26,7 @@ namespace Umbraco.Tests.Routing Mock.Of(), Mock.Of()), }; - _publishedContentType = new PublishedContentType(0, "Doc", PublishedItemType.Content, Enumerable.Empty(), properties, ContentVariation.Nothing); + _publishedContentType = new PublishedContentType(Guid.NewGuid(), 0, "Doc", PublishedItemType.Content, Enumerable.Empty(), properties, ContentVariation.Nothing); } protected override PublishedContentType GetPublishedContentTypeByAlias(string alias) diff --git a/src/Umbraco.Tests/Routing/ContentFinderByUrlTests.cs b/src/Umbraco.Tests/Routing/ContentFinderByUrlTests.cs index 85168e4490..18bb6bdfb5 100644 --- a/src/Umbraco.Tests/Routing/ContentFinderByUrlTests.cs +++ b/src/Umbraco.Tests/Routing/ContentFinderByUrlTests.cs @@ -24,7 +24,7 @@ namespace Umbraco.Tests.Routing [TestCase("/home/sub1", -1)] // should fail // these two are special. getNiceUrl(1046) returns "/" but getNiceUrl(1172) cannot also return "/" so - // we've made it return "/test-page" => we have to support that url back in the lookup... + // we've made it return "/test-page" => we have to support that URL back in the lookup... [TestCase("/home", 1046)] [TestCase("/test-page", 1172)] public void Match_Document_By_Url_Hide_Top_Level(string urlString, int expectedId) diff --git a/src/Umbraco.Tests/Routing/ContentFinderByUrlWithDomainsTests.cs b/src/Umbraco.Tests/Routing/ContentFinderByUrlWithDomainsTests.cs index ac2e62b1ef..18eafe508e 100644 --- a/src/Umbraco.Tests/Routing/ContentFinderByUrlWithDomainsTests.cs +++ b/src/Umbraco.Tests/Routing/ContentFinderByUrlWithDomainsTests.cs @@ -134,7 +134,7 @@ namespace Umbraco.Tests.Routing var publishedRouter = CreatePublishedRouter(Factory); var frequest = publishedRouter.CreateRequest(umbracoContext); - // must lookup domain else lookup by url fails + // must lookup domain else lookup by URL fails publishedRouter.FindDomain(frequest); var lookup = new ContentFinderByUrl(Logger); @@ -176,7 +176,7 @@ namespace Umbraco.Tests.Routing var publishedRouter = CreatePublishedRouter(Factory); var frequest = publishedRouter.CreateRequest(umbracoContext); - // must lookup domain else lookup by url fails + // must lookup domain else lookup by URL fails publishedRouter.FindDomain(frequest); Assert.AreEqual(expectedCulture, frequest.Culture.Name); diff --git a/src/Umbraco.Tests/Routing/MediaUrlProviderTests.cs b/src/Umbraco.Tests/Routing/MediaUrlProviderTests.cs index 2f960d498d..3a1ff36a0a 100644 --- a/src/Umbraco.Tests/Routing/MediaUrlProviderTests.cs +++ b/src/Umbraco.Tests/Routing/MediaUrlProviderTests.cs @@ -139,7 +139,7 @@ namespace Umbraco.Tests.Routing property.SetSourceValue("en", enMediaUrl, true); property.SetSourceValue("da", daMediaUrl); - var contentType = new PublishedContentType(666, "alias", PublishedItemType.Content, Enumerable.Empty(), new [] { umbracoFilePropertyType }, ContentVariation.Culture); + var contentType = new PublishedContentType(Guid.NewGuid(), 666, "alias", PublishedItemType.Content, Enumerable.Empty(), new [] { umbracoFilePropertyType }, ContentVariation.Culture); var publishedContent = new SolidPublishedContent(contentType) {Properties = new[] {property}}; var resolvedUrl = umbracoContext.UrlProvider.GetMediaUrl(publishedContent, UrlMode.Auto, "da"); @@ -150,7 +150,7 @@ namespace Umbraco.Tests.Routing { var umbracoFilePropertyType = CreatePropertyType(propertyEditorAlias, dataTypeConfiguration, ContentVariation.Nothing); - var contentType = new PublishedContentType(666, "alias", PublishedItemType.Content, Enumerable.Empty(), + var contentType = new PublishedContentType(Guid.NewGuid(), 666, "alias", PublishedItemType.Content, Enumerable.Empty(), new[] {umbracoFilePropertyType}, ContentVariation.Nothing); return new SolidPublishedContent(contentType) diff --git a/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs b/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs index 135172460d..786eebea9f 100644 --- a/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs +++ b/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs @@ -140,7 +140,7 @@ namespace Umbraco.Tests.Routing frequest.TemplateModel = template; var umbracoContextAccessor = new TestUmbracoContextAccessor(umbracoContext); - var type = new AutoPublishedContentType(22, "CustomDocument", new PublishedPropertyType[] { }); + var type = new AutoPublishedContentType(Guid.NewGuid(), 22, "CustomDocument", new PublishedPropertyType[] { }); ContentTypesCache.GetPublishedContentTypeByAlias = alias => type; var handler = new RenderRouteHandler(umbracoContext, new TestControllerFactory(umbracoContextAccessor, Mock.Of(), context => diff --git a/src/Umbraco.Tests/Routing/RouteTestExtensions.cs b/src/Umbraco.Tests/Routing/RouteTestExtensions.cs index fae8e2a6fa..642488c256 100644 --- a/src/Umbraco.Tests/Routing/RouteTestExtensions.cs +++ b/src/Umbraco.Tests/Routing/RouteTestExtensions.cs @@ -9,7 +9,7 @@ namespace Umbraco.Tests.Routing { /// - /// Return the route data for the url based on a mocked context + /// Return the route data for the URL based on a mocked context /// /// /// @@ -21,7 +21,7 @@ namespace Umbraco.Tests.Routing } /// - /// Get the route data based on the url and http context + /// Get the route data based on the URL and HTTP context /// /// /// diff --git a/src/Umbraco.Tests/Routing/UmbracoModuleTests.cs b/src/Umbraco.Tests/Routing/UmbracoModuleTests.cs index e95fdfc87c..94660d40cf 100644 --- a/src/Umbraco.Tests/Routing/UmbracoModuleTests.cs +++ b/src/Umbraco.Tests/Routing/UmbracoModuleTests.cs @@ -100,7 +100,7 @@ namespace Umbraco.Tests.Routing [Test] public void Is_Client_Side_Request_InvalidPath_ReturnFalse() { - //This url is invalid. Default to false when the extension cannot be determined + //This URL is invalid. Default to false when the extension cannot be determined var uri = new Uri("http://test.com/installing-modules+foobar+\"yipee\""); var result = uri.IsClientSideRequest(); Assert.AreEqual(false, result); diff --git a/src/Umbraco.Tests/Routing/UrlProviderTests.cs b/src/Umbraco.Tests/Routing/UrlProviderTests.cs index 02aa95cd2e..d61517b41e 100644 --- a/src/Umbraco.Tests/Routing/UrlProviderTests.cs +++ b/src/Umbraco.Tests/Routing/UrlProviderTests.cs @@ -158,7 +158,7 @@ namespace Umbraco.Tests.Routing var umbracoSettings = Current.Configs.Settings(); - var contentType = new PublishedContentType(666, "alias", PublishedItemType.Content, Enumerable.Empty(), Enumerable.Empty(), ContentVariation.Culture); + var contentType = new PublishedContentType(Guid.NewGuid(), 666, "alias", PublishedItemType.Content, Enumerable.Empty(), Enumerable.Empty(), ContentVariation.Culture); var publishedContent = new SolidPublishedContent(contentType) { Id = 1234 }; var publishedContentCache = new Mock(); @@ -184,7 +184,7 @@ namespace Umbraco.Tests.Routing globalSettings: globalSettings.Object, snapshotService: snapshotService.Object); - //even though we are asking for a specific culture URL, there are no domains assigned so all that can be returned is a normal relative url. + //even though we are asking for a specific culture URL, there are no domains assigned so all that can be returned is a normal relative URL. var url = umbracoContext.UrlProvider.GetUrl(1234, culture: "fr-FR"); Assert.AreEqual("/home/test-fr/", url); @@ -203,7 +203,7 @@ namespace Umbraco.Tests.Routing var umbracoSettings = Current.Configs.Settings(); - var contentType = new PublishedContentType(666, "alias", PublishedItemType.Content, Enumerable.Empty(), Enumerable.Empty(), ContentVariation.Culture); + var contentType = new PublishedContentType(Guid.NewGuid(), 666, "alias", PublishedItemType.Content, Enumerable.Empty(), Enumerable.Empty(), ContentVariation.Culture); var publishedContent = new SolidPublishedContent(contentType) { Id = 1234 }; var publishedContentCache = new Mock(); @@ -257,7 +257,7 @@ namespace Umbraco.Tests.Routing var umbracoSettings = Current.Configs.Settings(); - var contentType = new PublishedContentType(666, "alias", PublishedItemType.Content, Enumerable.Empty(), Enumerable.Empty(), ContentVariation.Culture); + var contentType = new PublishedContentType(Guid.NewGuid(), 666, "alias", PublishedItemType.Content, Enumerable.Empty(), Enumerable.Empty(), ContentVariation.Culture); var publishedContent = new SolidPublishedContent(contentType) { Id = 1234 }; var publishedContentCache = new Mock(); diff --git a/src/Umbraco.Tests/Routing/UrlRoutesTests.cs b/src/Umbraco.Tests/Routing/UrlRoutesTests.cs index 4b8d708df6..724a263b54 100644 --- a/src/Umbraco.Tests/Routing/UrlRoutesTests.cs +++ b/src/Umbraco.Tests/Routing/UrlRoutesTests.cs @@ -12,7 +12,7 @@ using Umbraco.Tests.Testing; namespace Umbraco.Tests.Routing { // purpose: test the values returned by PublishedContentCache.GetRouteById - // and .GetByRoute (no caching at all, just routing nice urls) including all + // and .GetByRoute (no caching at all, just routing nice URLs) including all // the quirks due to hideTopLevelFromPath and backward compatibility. [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerFixture)] @@ -146,7 +146,7 @@ DetermineRouteById(id): node = node(id) - walk up from node to domain or root, assemble parts = url segments + walk up from node to domain or root, assemble parts = URL segments if !domain and global.hide: if id.parent: diff --git a/src/Umbraco.Tests/Routing/UrlsWithNestedDomains.cs b/src/Umbraco.Tests/Routing/UrlsWithNestedDomains.cs index 6587b2e4f6..363f87395d 100644 --- a/src/Umbraco.Tests/Routing/UrlsWithNestedDomains.cs +++ b/src/Umbraco.Tests/Routing/UrlsWithNestedDomains.cs @@ -16,7 +16,7 @@ namespace Umbraco.Tests.Routing [TestFixture] public class UrlsWithNestedDomains : UrlRoutingTestBase { - // in the case of nested domains more than 1 url may resolve to a document + // in the case of nested domains more than 1 URL may resolve to a document // but only one route can be cached - the 'canonical' route ie the route // using the closest domain to the node - here we test that if we request // a non-canonical route, it is not cached / the cache is not polluted @@ -40,7 +40,7 @@ namespace Umbraco.Tests.Routing const string url = "http://domain1.com/1001-1/1001-1-1"; - // get the nice url for 100111 + // get the nice URL for 100111 var umbracoContext = GetUmbracoContext(url, 9999, umbracoSettings: settings, urlProviders: new [] { new DefaultUrlProvider(settings.RequestHandler, Logger, globalSettings.Object, new SiteDomainHelper()) @@ -53,7 +53,7 @@ namespace Umbraco.Tests.Routing var cachedRoutes = cache.RoutesCache.GetCachedRoutes(); Assert.AreEqual("10011/1001-1-1", cachedRoutes[100111]); - // route a rogue url + // route a rogue URL var publishedRouter = CreatePublishedRouter(); var frequest = publishedRouter.CreateRequest(umbracoContext); @@ -71,7 +71,7 @@ namespace Umbraco.Tests.Routing Assert.AreEqual("10011/1001-1-1", cachedRoutes[100111]); // no //Assert.AreEqual("1001/1001-1/1001-1-1", cachedRoutes[100111]); // yes - // what's the nice url now? + // what's the nice URL now? Assert.AreEqual("http://domain2.com/1001-1-1/", umbracoContext.UrlProvider.GetUrl(100111)); // good //Assert.AreEqual("http://domain1.com/1001-1/1001-1-1", routingContext.NiceUrlProvider.GetNiceUrl(100111, true)); // bad } diff --git a/src/Umbraco.Tests/Runtimes/CoreRuntimeTests.cs b/src/Umbraco.Tests/Runtimes/CoreRuntimeTests.cs index 5850153100..3a99fbb0de 100644 --- a/src/Umbraco.Tests/Runtimes/CoreRuntimeTests.cs +++ b/src/Umbraco.Tests/Runtimes/CoreRuntimeTests.cs @@ -82,14 +82,18 @@ namespace Umbraco.Tests.Runtimes protected override IRuntime GetRuntime() { - return Runtime = new TestRuntime(); + var logger = new DebugDiagnosticsLogger(); + return Runtime = new TestRuntime(logger, new MainDom(logger, new MainDomSemaphoreLock(logger))); } } // test runtime public class TestRuntime : CoreRuntime { - protected override ILogger GetLogger() => new DebugDiagnosticsLogger(); + public TestRuntime(ILogger logger, IMainDom mainDom) : base(logger, mainDom) + { + } + protected override IProfiler GetProfiler() => new TestProfiler(); // must override the database factory @@ -102,6 +106,13 @@ namespace Umbraco.Tests.Runtimes return mock.Object; } + internal override void DetermineRuntimeLevel(IUmbracoDatabaseFactory databaseFactory, IProfilingLogger profilingLogger) + { + // mega hack to make it boot up without DB access. + var state = (RuntimeState)State; + state.Level = RuntimeLevel.Boot; + } + protected override Configs GetConfigs() { var configs = new Configs(); @@ -142,6 +153,8 @@ namespace Umbraco.Tests.Runtimes private IMainDom _mainDom; + + public override IFactory Boot(IRegister container) { var factory = base.Boot(container); diff --git a/src/Umbraco.Tests/Runtimes/StandaloneTests.cs b/src/Umbraco.Tests/Runtimes/StandaloneTests.cs index 9157a76773..b2ceabf905 100644 --- a/src/Umbraco.Tests/Runtimes/StandaloneTests.cs +++ b/src/Umbraco.Tests/Runtimes/StandaloneTests.cs @@ -75,7 +75,7 @@ namespace Umbraco.Tests.Runtimes coreRuntime.Compose(composition); // determine actual runtime level - runtimeState.DetermineRuntimeLevel(databaseFactory, logger); + runtimeState.DetermineRuntimeLevel(databaseFactory); Console.WriteLine(runtimeState.Level); // going to be Install BUT we want to force components to be there (nucache etc) runtimeState.Level = RuntimeLevel.Run; @@ -201,7 +201,7 @@ namespace Umbraco.Tests.Runtimes Assert.AreEqual("test", pcontent.Name()); Assert.IsTrue(pcontent.IsDraft()); - // no published url + // no published URL Assert.AreEqual("#", pcontent.Url()); // now publish the document + make some unpublished changes @@ -215,7 +215,7 @@ namespace Umbraco.Tests.Runtimes Assert.AreEqual("test", pcontent.Name()); Assert.IsFalse(pcontent.IsDraft()); - // but the url is the published one - no draft url + // but the URL is the published one - no draft URL Assert.AreEqual("/test/", pcontent.Url()); // and also an updated draft document @@ -224,7 +224,7 @@ namespace Umbraco.Tests.Runtimes Assert.AreEqual("testx", pcontent.Name()); Assert.IsTrue(pcontent.IsDraft()); - // and the published document has a url + // and the published document has a URL Assert.AreEqual("/test/", pcontent.Url()); umbracoContextReference.Dispose(); diff --git a/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs b/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs index 5f72947382..be10db3a9d 100644 --- a/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs +++ b/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs @@ -82,7 +82,7 @@ namespace Umbraco.Tests.Scoping var mediaRepository = Mock.Of(); var memberRepository = Mock.Of(); - var nestedContentDataSerializer = new JsonContentNestedDataSerializer(); + var nestedContentDataSerializerFactory = new JsonContentNestedDataSerializerFactory(); return new PublishedSnapshotService( options, null, @@ -96,12 +96,12 @@ namespace Umbraco.Tests.Scoping ScopeProvider, documentRepository, mediaRepository, memberRepository, DefaultCultureAccessor, - new DatabaseDataSource(nestedContentDataSerializer), + new DatabaseDataSource(nestedContentDataSerializerFactory), Factory.GetInstance(), Factory.GetInstance(), Mock.Of(), new UrlSegmentProviderCollection(new[] { new DefaultUrlSegmentProvider() }), - nestedContentDataSerializer); + nestedContentDataSerializerFactory); } protected UmbracoContext GetUmbracoContextNu(string url, int templateId = 1234, RouteData routeData = null, bool setSingleton = false, IUmbracoSettingsSection umbracoSettings = null, IEnumerable urlProviders = null) diff --git a/src/Umbraco.Tests/Serialization/AutoInterningStringConverterTests.cs b/src/Umbraco.Tests/Serialization/AutoInterningStringConverterTests.cs index bf99b68077..f83ea940c9 100644 --- a/src/Umbraco.Tests/Serialization/AutoInterningStringConverterTests.cs +++ b/src/Umbraco.Tests/Serialization/AutoInterningStringConverterTests.cs @@ -19,7 +19,7 @@ namespace Umbraco.Tests.Serialization var str1 = "Hello"; var obj = new Test { - Name = str1 + " " + "there" + Name = str1 + Guid.NewGuid() }; // ensure the raw value is not interned @@ -39,8 +39,8 @@ namespace Umbraco.Tests.Serialization { Values = new Dictionary { - [str1 + "1"] = 0, - [str1 + "2"] = 1 + [str1 + Guid.NewGuid()] = 0, + [str1 + Guid.NewGuid()] = 1 } }; diff --git a/src/Umbraco.Tests/Services/ContentServiceTests.cs b/src/Umbraco.Tests/Services/ContentServiceTests.cs index 553d1b97ba..008c24fcbf 100644 --- a/src/Umbraco.Tests/Services/ContentServiceTests.cs +++ b/src/Umbraco.Tests/Services/ContentServiceTests.cs @@ -343,7 +343,7 @@ namespace Umbraco.Tests.Services } // Assert - Assert.AreEqual(24, contentService.Count()); + Assert.AreEqual(25, contentService.Count()); } [Test] @@ -1197,7 +1197,7 @@ namespace Umbraco.Tests.Services Assert.IsFalse(content.HasIdentity); // content cannot publish values because they are invalid - var propertyValidationService = new PropertyValidationService(Factory.GetInstance(), ServiceContext.DataTypeService); + var propertyValidationService = new PropertyValidationService(Factory.GetInstance(), ServiceContext.DataTypeService, ServiceContext.TextService); var isValid = propertyValidationService.IsPropertyDataValid(content, out var invalidProperties, CultureImpact.Invariant); Assert.IsFalse(isValid); Assert.IsNotEmpty(invalidProperties); @@ -1415,7 +1415,7 @@ namespace Umbraco.Tests.Services { // Arrange var contentService = ServiceContext.ContentService; - var content = contentService.GetById(NodeDto.NodeIdSeed + 5); + var content = contentService.GetById(NodeDto.NodeIdSeed + 6); // Act var published = contentService.SaveAndPublish(content, userId: Constants.Security.SuperUserId); @@ -1537,6 +1537,53 @@ namespace Umbraco.Tests.Services Assert.That(content.HasIdentity, Is.True); } + [Test] + public void Can_Update_Content_Property_Values() + { + IContentType contentType = MockedContentTypes.CreateSimpleContentType(); + ServiceContext.ContentTypeService.Save(contentType); + IContent content = MockedContent.CreateSimpleContent(contentType, "hello"); + content.SetValue("title", "title of mine"); + content.SetValue("bodyText", "hello world"); + ServiceContext.ContentService.SaveAndPublish(content); + + // re-get + content = ServiceContext.ContentService.GetById(content.Id); + content.SetValue("title", "another title of mine"); // Change a value + content.SetValue("bodyText", null); // Clear a value + content.SetValue("author", "new author"); // Add a value + ServiceContext.ContentService.SaveAndPublish(content); + + // re-get + content = ServiceContext.ContentService.GetById(content.Id); + Assert.AreEqual("another title of mine", content.GetValue("title")); + Assert.IsNull(content.GetValue("bodyText")); + Assert.AreEqual("new author", content.GetValue("author")); + + content.SetValue("title", "new title"); + content.SetValue("bodyText", "new body text"); + content.SetValue("author", "new author text"); + ServiceContext.ContentService.Save(content); // new non-published version + + // re-get + content = ServiceContext.ContentService.GetById(content.Id); + content.SetValue("title", null); // Clear a value + content.SetValue("bodyText", null); // Clear a value + ServiceContext.ContentService.Save(content); // saving non-published version + + // re-get + content = ServiceContext.ContentService.GetById(content.Id); + Assert.IsNull(content.GetValue("title")); // Test clearing the value worked with the non-published version + Assert.IsNull(content.GetValue("bodyText")); + Assert.AreEqual("new author text", content.GetValue("author")); + + // make sure that the published version remained the same + var publishedContent = ServiceContext.ContentService.GetVersion(content.PublishedVersionId); + Assert.AreEqual("another title of mine", publishedContent.GetValue("title")); + Assert.IsNull(publishedContent.GetValue("bodyText")); + Assert.AreEqual("new author", publishedContent.GetValue("author")); + } + [Test] public void Can_Bulk_Save_Content() { @@ -1640,7 +1687,7 @@ namespace Umbraco.Tests.Services Assert.AreNotEqual(-20, content.ParentId); Assert.IsFalse(content.Trashed); - Assert.AreEqual(3, descendants.Count); + Assert.AreEqual(4, descendants.Count); Assert.IsFalse(descendants.Any(x => x.Path.StartsWith("-1,-20,"))); Assert.IsFalse(descendants.Any(x => x.Trashed)); @@ -1653,7 +1700,7 @@ namespace Umbraco.Tests.Services Assert.AreEqual(-20, content.ParentId); Assert.IsTrue(content.Trashed); - Assert.AreEqual(3, descendants.Count); + Assert.AreEqual(4, descendants.Count); Assert.IsTrue(descendants.All(x => x.Path.StartsWith("-1,-20,"))); Assert.True(descendants.All(x => x.Trashed)); @@ -1940,7 +1987,7 @@ namespace Umbraco.Tests.Services var contentService = ServiceContext.ContentService; var temp = contentService.GetById(NodeDto.NodeIdSeed + 2); Assert.AreEqual("Home", temp.Name); - Assert.AreEqual(2, contentService.CountChildren(temp.Id)); + Assert.AreEqual(3, contentService.CountChildren(temp.Id)); // Act var copy = contentService.Copy(temp, temp.ParentId, false, true, Constants.Security.SuperUserId); @@ -1950,7 +1997,7 @@ namespace Umbraco.Tests.Services Assert.That(copy, Is.Not.Null); Assert.That(copy.Id, Is.Not.EqualTo(content.Id)); Assert.AreNotSame(content, copy); - Assert.AreEqual(2, contentService.CountChildren(copy.Id)); + Assert.AreEqual(3, contentService.CountChildren(copy.Id)); var child = contentService.GetById(NodeDto.NodeIdSeed + 3); var childCopy = contentService.GetPagedChildren(copy.Id, 0, 500, out var total).First(); @@ -1966,7 +2013,7 @@ namespace Umbraco.Tests.Services var contentService = ServiceContext.ContentService; var temp = contentService.GetById(NodeDto.NodeIdSeed + 2); Assert.AreEqual("Home", temp.Name); - Assert.AreEqual(2, contentService.CountChildren(temp.Id)); + Assert.AreEqual(3, contentService.CountChildren(temp.Id)); // Act var copy = contentService.Copy(temp, temp.ParentId, false, false, Constants.Security.SuperUserId); diff --git a/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs b/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs index 938b14c3a9..aaad60f7e9 100644 --- a/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs +++ b/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs @@ -53,7 +53,7 @@ namespace Umbraco.Tests.Services var mediaRepository = Mock.Of(); var memberRepository = Mock.Of(); - var nestedContentDataSerializer = new JsonContentNestedDataSerializer(); + var nestedContentDataSerializerFactory = new JsonContentNestedDataSerializerFactory(); return new PublishedSnapshotService( options, @@ -68,12 +68,12 @@ namespace Umbraco.Tests.Services ScopeProvider, documentRepository, mediaRepository, memberRepository, DefaultCultureAccessor, - new DatabaseDataSource(nestedContentDataSerializer), + new DatabaseDataSource(nestedContentDataSerializerFactory), Factory.GetInstance(), Factory.GetInstance(), Mock.Of(), new UrlSegmentProviderCollection(new[] { new DefaultUrlSegmentProvider() }), - nestedContentDataSerializer); + nestedContentDataSerializerFactory); } public class LocalServerMessenger : ServerMessengerBase diff --git a/src/Umbraco.Tests/Services/EntityServiceTests.cs b/src/Umbraco.Tests/Services/EntityServiceTests.cs index 0598b8cea2..4b83407e63 100644 --- a/src/Umbraco.Tests/Services/EntityServiceTests.cs +++ b/src/Umbraco.Tests/Services/EntityServiceTests.cs @@ -8,12 +8,12 @@ using Umbraco.Core.Models; using Umbraco.Core.Models.Entities; using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Services; -using Umbraco.Tests.TestHelpers; using Umbraco.Tests.TestHelpers.Entities; using Umbraco.Tests.Testing; namespace Umbraco.Tests.Services { + /// /// Tests covering the EntityService /// @@ -477,7 +477,7 @@ namespace Umbraco.Tests.Services var entities = service.GetAll(UmbracoObjectTypes.Document).ToArray(); Assert.That(entities.Any(), Is.True); - Assert.That(entities.Length, Is.EqualTo(4)); + Assert.That(entities.Length, Is.EqualTo(5)); Assert.That(entities.Any(x => x.Trashed), Is.True); } @@ -490,7 +490,7 @@ namespace Umbraco.Tests.Services var entities = service.GetAll(objectTypeId).ToArray(); Assert.That(entities.Any(), Is.True); - Assert.That(entities.Length, Is.EqualTo(4)); + Assert.That(entities.Length, Is.EqualTo(5)); Assert.That(entities.Any(x => x.Trashed), Is.True); } @@ -502,7 +502,7 @@ namespace Umbraco.Tests.Services var entities = service.GetAll().ToArray(); Assert.That(entities.Any(), Is.True); - Assert.That(entities.Length, Is.EqualTo(4)); + Assert.That(entities.Length, Is.EqualTo(5)); Assert.That(entities.Any(x => x.Trashed), Is.True); } diff --git a/src/Umbraco.Tests/Services/ExternalLoginServiceTests.cs b/src/Umbraco.Tests/Services/ExternalLoginServiceTests.cs new file mode 100644 index 0000000000..8a31518ca0 --- /dev/null +++ b/src/Umbraco.Tests/Services/ExternalLoginServiceTests.cs @@ -0,0 +1,249 @@ +using System; +using System.Linq; +using System.Threading; +using Microsoft.AspNet.Identity; +using NUnit.Framework; +using Umbraco.Core.Models.Identity; +using Umbraco.Core.Models.Membership; +using Umbraco.Core.Persistence.Dtos; +using Umbraco.Tests.TestHelpers; +using Umbraco.Tests.Testing; + +namespace Umbraco.Tests.Services +{ + [TestFixture] + [Apartment(ApartmentState.STA)] + [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerFixture)] + public class ExternalLoginServiceTests : TestWithDatabaseBase + { + [Test] + public void Removes_Existing_Duplicates_On_Save() + { + var user = new User("Test", "test@test.com", "test", "helloworldtest"); + ServiceContext.UserService.Save(user); + + var providerKey = Guid.NewGuid().ToString("N"); + var latest = DateTime.Now.AddDays(-1); + var oldest = DateTime.Now.AddDays(-10); + + using (var scope = ScopeProvider.CreateScope()) + { + // insert duplicates manuall + scope.Database.Insert(new ExternalLoginDto + { + UserId = user.Id, + LoginProvider = "test1", + ProviderKey = providerKey, + CreateDate = latest + }); + scope.Database.Insert(new ExternalLoginDto + { + UserId = user.Id, + LoginProvider = "test1", + ProviderKey = providerKey, + CreateDate = oldest + }); + } + + // try to save 2 other duplicates + var externalLogins = new[] + { + new ExternalLogin("test2", providerKey), + new ExternalLogin("test2", providerKey), + new ExternalLogin("test1", providerKey) + }; + + ServiceContext.ExternalLoginService.Save(user.Id, externalLogins); + + var logins = ServiceContext.ExternalLoginService.GetAll(user.Id).ToList(); + + // duplicates will be removed, keeping the latest entries + Assert.AreEqual(2, logins.Count); + + var test1 = logins.Single(x => x.LoginProvider == "test1"); + Assert.Greater(test1.CreateDate, latest); + } + + [Test] + public void Does_Not_Persist_Duplicates() + { + var user = new User("Test", "test@test.com", "test", "helloworldtest"); + ServiceContext.UserService.Save(user); + + var providerKey = Guid.NewGuid().ToString("N"); + var externalLogins = new[] + { + new ExternalLogin("test1", providerKey), + new ExternalLogin("test1", providerKey) + }; + + ServiceContext.ExternalLoginService.Save(user.Id, externalLogins); + + var logins = ServiceContext.ExternalLoginService.GetAll(user.Id).ToList(); + Assert.AreEqual(1, logins.Count); + } + + [Test] + public void Single_Create() + { + var user = new User("Test", "test@test.com", "test", "helloworldtest"); + ServiceContext.UserService.Save(user); + + var extLogin = new IdentityUserLogin("test1", Guid.NewGuid().ToString("N"), user.Id) + { + UserData = "hello" + }; + ServiceContext.ExternalLoginService.Save(extLogin); + + var found = ServiceContext.ExternalLoginService.GetAll(user.Id); + + Assert.AreEqual(1, found.Count()); + Assert.IsTrue(extLogin.HasIdentity); + Assert.IsTrue(extLogin.Id > 0); + } + + [Test] + public void Single_Update() + { + var user = new User("Test", "test@test.com", "test", "helloworldtest"); + ServiceContext.UserService.Save(user); + + var extLogin = new IdentityUserLogin("test1", Guid.NewGuid().ToString("N"), user.Id) + { + UserData = "hello" + }; + ServiceContext.ExternalLoginService.Save(extLogin); + + extLogin.UserData = "world"; + ServiceContext.ExternalLoginService.Save(extLogin); + + var found = ServiceContext.ExternalLoginService.GetAll(user.Id).Cast().ToList(); + Assert.AreEqual(1, found.Count); + Assert.AreEqual("world", found[0].UserData); + } + + [Test] + public void Multiple_Update() + { + var user = new User("Test", "test@test.com", "test", "helloworldtest"); + ServiceContext.UserService.Save(user); + + var providerKey1 = Guid.NewGuid().ToString("N"); + var providerKey2 = Guid.NewGuid().ToString("N"); + var extLogins = new[] + { + new ExternalLogin("test1", providerKey1, "hello"), + new ExternalLogin("test2", providerKey2, "world") + }; + ServiceContext.ExternalLoginService.Save(user.Id, extLogins); + + extLogins = new[] + { + new ExternalLogin("test1", providerKey1, "123456"), + new ExternalLogin("test2", providerKey2, "987654") + }; + ServiceContext.ExternalLoginService.Save(user.Id, extLogins); + + var found = ServiceContext.ExternalLoginService.GetAll(user.Id).Cast().OrderBy(x => x.LoginProvider).ToList(); + Assert.AreEqual(2, found.Count); + Assert.AreEqual("123456", found[0].UserData); + Assert.AreEqual("987654", found[1].UserData); + } + + [Test] + public void Can_Find_As_Extended_Type() + { + var user = new User("Test", "test@test.com", "test", "helloworldtest"); + ServiceContext.UserService.Save(user); + + var providerKey1 = Guid.NewGuid().ToString("N"); + var providerKey2 = Guid.NewGuid().ToString("N"); + var extLogins = new[] + { + new ExternalLogin("test1", providerKey1, "hello"), + new ExternalLogin("test2", providerKey2, "world") + }; + ServiceContext.ExternalLoginService.Save(user.Id, extLogins); + + var found = ServiceContext.ExternalLoginService.Find("test2", providerKey2).ToList(); + Assert.AreEqual(1, found.Count); + var asExtended = found.Cast().ToList(); + Assert.AreEqual(1, found.Count); + + } + + [Test] + public void Add_Logins() + { + var user = new User("Test", "test@test.com", "test", "helloworldtest"); + ServiceContext.UserService.Save(user); + + var externalLogins = new[] + { + new ExternalLogin("test1", Guid.NewGuid().ToString("N")), + new ExternalLogin("test2", Guid.NewGuid().ToString("N")) + }; + + ServiceContext.ExternalLoginService.Save(user.Id, externalLogins); + + var logins = ServiceContext.ExternalLoginService.GetAll(user.Id).OrderBy(x => x.LoginProvider).ToList(); + Assert.AreEqual(2, logins.Count); + for (int i = 0; i < logins.Count; i++) + { + Assert.AreEqual(logins[i].ProviderKey, externalLogins[i].ProviderKey); + Assert.AreEqual(logins[i].LoginProvider, externalLogins[i].LoginProvider); + } + } + + [Test] + public void Add_Update_Delete_Logins() + { + var user = new User("Test", "test@test.com", "test", "helloworldtest"); + ServiceContext.UserService.Save(user); + + var externalLogins = new[] + { + new ExternalLogin("test1", Guid.NewGuid().ToString("N")), + new ExternalLogin("test2", Guid.NewGuid().ToString("N")), + new ExternalLogin("test3", Guid.NewGuid().ToString("N")), + new ExternalLogin("test4", Guid.NewGuid().ToString("N")) + }; + + ServiceContext.ExternalLoginService.Save(user.Id, externalLogins); + + var logins = ServiceContext.ExternalLoginService.GetAll(user.Id).OrderBy(x => x.LoginProvider).ToList(); + + logins.RemoveAt(0); // remove the first one + logins.Add(new IdentityUserLogin("test5", Guid.NewGuid().ToString("N"), user.Id)); // add a new one + + // save new list + ServiceContext.ExternalLoginService.Save(user.Id, logins.Select(x => new ExternalLogin(x.LoginProvider, x.ProviderKey))); + + var updatedLogins = ServiceContext.ExternalLoginService.GetAll(user.Id).OrderBy(x => x.LoginProvider).ToList(); + Assert.AreEqual(4, updatedLogins.Count); + for (int i = 0; i < updatedLogins.Count; i++) + { + Assert.AreEqual(logins[i].LoginProvider, updatedLogins[i].LoginProvider); + } + } + + [Test] + public void Add_Retrieve_User_Data() + { + var user = new User("Test", "test@test.com", "test", "helloworldtest"); + ServiceContext.UserService.Save(user); + + var externalLogins = new[] + { + new ExternalLogin("test1", Guid.NewGuid().ToString("N"), "hello world") + }; + + ServiceContext.ExternalLoginService.Save(user.Id, externalLogins); + + var logins = ServiceContext.ExternalLoginService.GetAll(user.Id).Cast().ToList(); + + Assert.AreEqual("hello world", logins[0].UserData); + + } + } +} diff --git a/src/Umbraco.Tests/Services/MediaServiceTests.cs b/src/Umbraco.Tests/Services/MediaServiceTests.cs index b3dc274c5e..52f26ecb4d 100644 --- a/src/Umbraco.Tests/Services/MediaServiceTests.cs +++ b/src/Umbraco.Tests/Services/MediaServiceTests.cs @@ -26,6 +26,30 @@ namespace Umbraco.Tests.Services [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest, PublishedRepositoryEvents = true)] public class MediaServiceTests : TestWithSomeContentBase { + [Test] + public void Can_Update_Media_Property_Values() + { + IMediaType mediaType = MockedContentTypes.CreateSimpleMediaType("test", "Test"); + ServiceContext.MediaTypeService.Save(mediaType); + IMedia media = MockedMedia.CreateSimpleMedia(mediaType, "hello", -1); + media.SetValue("title", "title of mine"); + media.SetValue("bodyText", "hello world"); + ServiceContext.MediaService.Save(media); + + // re-get + media = ServiceContext.MediaService.GetById(media.Id); + media.SetValue("title", "another title of mine"); // Change a value + media.SetValue("bodyText", null); // Clear a value + media.SetValue("author", "new author"); // Add a value + ServiceContext.MediaService.Save(media); + + // re-get + media = ServiceContext.MediaService.GetById(media.Id); + Assert.AreEqual("another title of mine", media.GetValue("title")); + Assert.IsNull(media.GetValue("bodyText")); + Assert.AreEqual("new author", media.GetValue("author")); + } + /// /// Used to list out all ambiguous events that will require dispatching with a name /// diff --git a/src/Umbraco.Tests/Services/MemberServiceTests.cs b/src/Umbraco.Tests/Services/MemberServiceTests.cs index 1385bbe0b3..61bc3cf1c1 100644 --- a/src/Umbraco.Tests/Services/MemberServiceTests.cs +++ b/src/Umbraco.Tests/Services/MemberServiceTests.cs @@ -48,6 +48,65 @@ namespace Umbraco.Tests.Services ((MemberService)ServiceContext.MemberService).MembershipProvider = provider; } + [Test] + public void Can_Update_Member_Property_Values() + { + IMemberType memberType = MockedContentTypes.CreateSimpleMemberType(); + ServiceContext.MemberTypeService.Save(memberType); + IMember member = MockedMember.CreateSimpleMember(memberType, "hello", "helloworld@test123.com", "hello", "hello"); + member.SetValue("title", "title of mine"); + member.SetValue("bodyText", "hello world"); + ServiceContext.MemberService.Save(member); + + // re-get + member = ServiceContext.MemberService.GetById(member.Id); + member.SetValue("title", "another title of mine"); // Change a value + member.SetValue("bodyText", null); // Clear a value + member.SetValue("author", "new author"); // Add a value + ServiceContext.MemberService.Save(member); + + // re-get + member = ServiceContext.MemberService.GetById(member.Id); + Assert.AreEqual("another title of mine", member.GetValue("title")); + Assert.IsNull(member.GetValue("bodyText")); + Assert.AreEqual("new author", member.GetValue("author")); + } + + [Test] + public void Can_Get_By_Username() + { + var memberType = ServiceContext.MemberTypeService.Get("member"); + IMember member = new Member("xname", "xemail", "xusername", "xrawpassword", memberType, true); + ServiceContext.MemberService.Save(member); + + var member2 = ServiceContext.MemberService.GetByUsername(member.Username); + + Assert.IsNotNull(member2); + Assert.AreEqual(member.Email, member2.Email); + } + + [Test] + public void Can_Set_Last_Login_Date() + { + var now = DateTime.Now; + var memberType = ServiceContext.MemberTypeService.Get("member"); + IMember member = new Member("xname", "xemail", "xusername", "xrawpassword", memberType, true) + { + LastLoginDate = now, + UpdateDate = now + }; + ServiceContext.MemberService.Save(member); + + var newDate = now.AddDays(10); + ServiceContext.MemberService.SetLastLogin(member.Username, newDate); + + //re-get + member = ServiceContext.MemberService.GetById(member.Id); + + Assert.That(member.LastLoginDate, Is.EqualTo(newDate).Within(1).Seconds); + Assert.That(member.UpdateDate, Is.EqualTo(newDate).Within(1).Seconds); + } + [Test] public void Can_Create_Member_With_Properties() { diff --git a/src/Umbraco.Tests/Services/PropertyValidationServiceTests.cs b/src/Umbraco.Tests/Services/PropertyValidationServiceTests.cs index e1e19918ce..28bdf59373 100644 --- a/src/Umbraco.Tests/Services/PropertyValidationServiceTests.cs +++ b/src/Umbraco.Tests/Services/PropertyValidationServiceTests.cs @@ -36,7 +36,7 @@ namespace Umbraco.Tests.Services var propEditors = new PropertyEditorCollection(new DataEditorCollection(new[] { dataEditor })); - validationService = new PropertyValidationService(propEditors, dataTypeService.Object); + validationService = new PropertyValidationService(propEditors, dataTypeService.Object, Mock.Of()); } [Test] diff --git a/src/Umbraco.Tests/Services/RedirectUrlServiceTests.cs b/src/Umbraco.Tests/Services/RedirectUrlServiceTests.cs new file mode 100644 index 0000000000..437de3ac5b --- /dev/null +++ b/src/Umbraco.Tests/Services/RedirectUrlServiceTests.cs @@ -0,0 +1,102 @@ +using Moq; +using NUnit.Framework; +using System.Linq; +using System.Threading; +using Umbraco.Core.Cache; +using Umbraco.Core.Logging; +using Umbraco.Core.Models; +using Umbraco.Core.Persistence.Repositories.Implement; +using Umbraco.Core.Scoping; +using Umbraco.Tests.Testing; + +namespace Umbraco.Tests.Services +{ + [TestFixture] + [Apartment(ApartmentState.STA)] + [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)] + public class RedirectUrlServiceTests : TestWithSomeContentBase + { + private IContent _firstSubPage; + private IContent _secondSubPage; + private IContent _thirdSubPage; + private readonly string _url = "blah"; + private readonly string _urlAlt = "alternativeUrl"; + private readonly string _cultureEnglish = "en"; + private readonly string _cultureGerman = "de"; + private readonly string _unusedCulture = "es"; + public override void CreateTestData() + { + base.CreateTestData(); + + var provider = TestObjects.GetScopeProvider(Logger); + using (var scope = provider.CreateScope()) + { + var repository = new RedirectUrlRepository((IScopeAccessor)provider, AppCaches.Disabled, Mock.Of()); + var rootContent = ServiceContext.ContentService.GetRootContent().FirstOrDefault(); + var subPages = ServiceContext.ContentService.GetPagedChildren(rootContent.Id, 0, 3, out _).ToList(); + _firstSubPage = subPages[0]; + _secondSubPage = subPages[1]; + _thirdSubPage = subPages[2]; + + + + repository.Save(new RedirectUrl + { + ContentKey = _firstSubPage.Key, + Url = _url, + Culture = _cultureEnglish + }); + Thread.Sleep(1000); //Added delay to ensure timestamp difference as sometimes they seem to have the same timestamp + repository.Save(new RedirectUrl + { + ContentKey = _secondSubPage.Key, + Url = _url, + Culture = _cultureGerman + }); + Thread.Sleep(1000); + repository.Save(new RedirectUrl + { + ContentKey = _thirdSubPage.Key, + Url = _urlAlt, + Culture = string.Empty + }); + + scope.Complete(); + } + } + + [TearDown] + public override void TearDown() + { + base.TearDown(); + } + + [Test] + public void Can_Get_Most_Recent_RedirectUrl() + { + var redirectUrlService = ServiceContext.RedirectUrlService; + var redirect = redirectUrlService.GetMostRecentRedirectUrl(_url); + Assert.AreEqual(redirect.ContentId, _secondSubPage.Id); + + } + + [Test] + public void Can_Get_Most_Recent_RedirectUrl_With_Culture() + { + var redirectUrlService = ServiceContext.RedirectUrlService; + var redirect = redirectUrlService.GetMostRecentRedirectUrl(_url, _cultureEnglish); + Assert.AreEqual(redirect.ContentId, _firstSubPage.Id); + + } + + [Test] + public void Can_Get_Most_Recent_RedirectUrl_With_Culture_When_No_CultureVariant_Exists() + { + var redirectUrlService = ServiceContext.RedirectUrlService; + var redirect = redirectUrlService.GetMostRecentRedirectUrl(_urlAlt, _unusedCulture); + Assert.AreEqual(redirect.ContentId, _thirdSubPage.Id); + + } + + } +} diff --git a/src/Umbraco.Tests/Services/TestWithSomeContentBase.cs b/src/Umbraco.Tests/Services/TestWithSomeContentBase.cs index 2b313afc5c..6daa16470b 100644 --- a/src/Umbraco.Tests/Services/TestWithSomeContentBase.cs +++ b/src/Umbraco.Tests/Services/TestWithSomeContentBase.cs @@ -41,6 +41,9 @@ namespace Umbraco.Tests.Services Content subpage2 = MockedContent.CreateSimpleContent(contentType, "Text Page 2", textpage.Id); ServiceContext.ContentService.Save(subpage2, 0); + Content subpage3 = MockedContent.CreateSimpleContent(contentType, "Text Page 3", textpage.Id); + ServiceContext.ContentService.Save(subpage3, 0); + //Create and Save Content "Text Page Deleted" based on "umbTextpage" -> 1064 Content trashed = MockedContent.CreateSimpleContent(contentType, "Text Page Deleted", -20); trashed.Trashed = true; diff --git a/src/Umbraco.Tests/Services/UserServiceTests.cs b/src/Umbraco.Tests/Services/UserServiceTests.cs index 016085c352..0bac25b297 100644 --- a/src/Umbraco.Tests/Services/UserServiceTests.cs +++ b/src/Umbraco.Tests/Services/UserServiceTests.cs @@ -7,6 +7,7 @@ using System.Threading; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Exceptions; +using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Tests.TestHelpers.Entities; @@ -968,16 +969,72 @@ namespace Umbraco.Tests.Services Assert.That(updatedItem.AllowedSections.Count(), Is.EqualTo(originalUser.AllowedSections.Count())); } + [Test] + public void Can_Get_Assigned_StartNodes_For_User() + { + var startContentItems = BuildContentItems(3); + + var testUserGroup = CreateTestUserGroup(); + + var userGroupId = testUserGroup.Id; + + CreateTestUsers(startContentItems.Select(x => x.Id).ToArray(), testUserGroup, 3); + + var usersInGroup = ServiceContext.UserService.GetAllInGroup(userGroupId); + + foreach (var user in usersInGroup) + Assert.AreEqual(user.StartContentIds.Length, startContentItems.Length); + } + + private Content[] BuildContentItems(int numberToCreate) + { + var contentType = MockedContentTypes.CreateSimpleContentType(); + + ServiceContext.ContentTypeService.Save(contentType); + + var startContentItems = new List(); + + for (var i = 0; i < numberToCreate; i++) + startContentItems.Add(MockedContent.CreateSimpleContent(contentType)); + + ServiceContext.ContentService.Save(startContentItems); + + return startContentItems.ToArray(); + } + private IUser CreateTestUser(out IUserGroup userGroup) { userGroup = CreateTestUserGroup(); var user = ServiceContext.UserService.CreateUserWithIdentity("test1", "test1@test.com"); + user.AddGroup(userGroup.ToReadOnlyGroup()); + ServiceContext.UserService.Save(user); + return user; } + private List CreateTestUsers(int[] startContentIds, IUserGroup userGroup, int numberToCreate) + { + var users = new List(); + + for (var i = 0; i < numberToCreate; i++) + { + var user = ServiceContext.UserService.CreateUserWithIdentity($"test{i}", $"test{i}@test.com"); + user.AddGroup(userGroup.ToReadOnlyGroup()); + + var updateable = (User)user; + updateable.StartContentIds = startContentIds; + + ServiceContext.UserService.Save(user); + + users.Add(user); + } + + return users; + } + private UserGroup CreateTestUserGroup(string alias = "testGroup", string name = "Test Group") { var userGroup = new UserGroup diff --git a/src/Umbraco.Tests/Strings/DefaultShortStringHelperTests.cs b/src/Umbraco.Tests/Strings/DefaultShortStringHelperTests.cs index 5fd5710a79..b93d79dcf0 100644 --- a/src/Umbraco.Tests/Strings/DefaultShortStringHelperTests.cs +++ b/src/Umbraco.Tests/Strings/DefaultShortStringHelperTests.cs @@ -338,7 +338,7 @@ namespace Umbraco.Tests.Strings Assert.AreEqual("house*2", helper.CleanString("house (2)", CleanStringType.Alias)); // FIXME: but for a filename we want to keep them! - // FIXME: and what about a url? + // FIXME: and what about a URL? } [Test] @@ -424,7 +424,7 @@ namespace Umbraco.Tests.Strings // lower-cased, utf8 filename, removing illegal filename chars, using dash-separator Assert.AreEqual("0123-中文测试-中文测试-léger-zôrg-2-a-x", filename, "filename"); - // lower-cased, utf8 url segment, only letters and digits, using dash-separator + // lower-cased, utf8 URL segment, only letters and digits, using dash-separator Assert.AreEqual("0123-中文测试-中文测试-léger-zôrg-2-a-x", segment, "segment"); } diff --git a/src/Umbraco.Tests/Templates/HtmlImageSourceParserTests.cs b/src/Umbraco.Tests/Templates/HtmlImageSourceParserTests.cs index bce9bd4155..59968024d1 100644 --- a/src/Umbraco.Tests/Templates/HtmlImageSourceParserTests.cs +++ b/src/Umbraco.Tests/Templates/HtmlImageSourceParserTests.cs @@ -63,9 +63,9 @@ namespace Umbraco.Tests.Templates [Test] public void Ensure_Image_Sources() { - //setup a mock url provider which we'll use for testing + //setup a mock URL provider which we'll use for testing - var mediaType = new PublishedContentType(777, "image", PublishedItemType.Media, Enumerable.Empty(), Enumerable.Empty(), ContentVariation.Nothing); + var mediaType = new PublishedContentType(Guid.NewGuid(), 777, "image", PublishedItemType.Media, Enumerable.Empty(), Enumerable.Empty(), ContentVariation.Nothing); var media = new Mock(); media.Setup(x => x.ContentType).Returns(mediaType); var mediaUrlProvider = new Mock(); diff --git a/src/Umbraco.Tests/Templates/HtmlLocalLinkParserTests.cs b/src/Umbraco.Tests/Templates/HtmlLocalLinkParserTests.cs index 7cd96a32ed..f9f82fd31b 100644 --- a/src/Umbraco.Tests/Templates/HtmlLocalLinkParserTests.cs +++ b/src/Umbraco.Tests/Templates/HtmlLocalLinkParserTests.cs @@ -49,17 +49,17 @@ namespace Umbraco.Tests.Templates [TestCase("hello href=\"{localLink:umb://document-type/9931BDE0-AAC3-4BAB-B838-909A7B47570E}\" world ", "hello href=\"#\" world ")] public void ParseLocalLinks(string input, string result) { - //setup a mock url provider which we'll use for testing + //setup a mock URL provider which we'll use for testing var contentUrlProvider = new Mock(); contentUrlProvider .Setup(x => x.GetUrl(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Returns(UrlInfo.Url("/my-test-url")); - var contentType = new PublishedContentType(666, "alias", PublishedItemType.Content, Enumerable.Empty(), Enumerable.Empty(), ContentVariation.Nothing); + var contentType = new PublishedContentType(Guid.NewGuid(), 666, "alias", PublishedItemType.Content, Enumerable.Empty(), Enumerable.Empty(), ContentVariation.Nothing); var publishedContent = new Mock(); publishedContent.Setup(x => x.Id).Returns(1234); publishedContent.Setup(x => x.ContentType).Returns(contentType); - var mediaType = new PublishedContentType(777, "image", PublishedItemType.Media, Enumerable.Empty(), Enumerable.Empty(), ContentVariation.Nothing); + var mediaType = new PublishedContentType(Guid.NewGuid(), 777, "image", PublishedItemType.Media, Enumerable.Empty(), Enumerable.Empty(), ContentVariation.Nothing); var media = new Mock(); media.Setup(x => x.ContentType).Returns(mediaType); var mediaUrlProvider = new Mock(); diff --git a/src/Umbraco.Tests/TestHelpers/BaseWebTest.cs b/src/Umbraco.Tests/TestHelpers/BaseWebTest.cs index f7e3744600..7d8cedc9c6 100644 --- a/src/Umbraco.Tests/TestHelpers/BaseWebTest.cs +++ b/src/Umbraco.Tests/TestHelpers/BaseWebTest.cs @@ -42,7 +42,7 @@ namespace Umbraco.Tests.TestHelpers new DataType(new VoidEditor(Mock.Of())) { Id = 1 }); var factory = new PublishedContentTypeFactory(Mock.Of(), new PropertyValueConverterCollection(Array.Empty()), dataTypeService); - var type = new AutoPublishedContentType(0, "anything", new PublishedPropertyType[] { }); + var type = new AutoPublishedContentType(Guid.NewGuid(), 0, "anything", new PublishedPropertyType[] { }); ContentTypesCache.GetPublishedContentTypeByAlias = alias => GetPublishedContentTypeByAlias(alias) ?? type; } diff --git a/src/Umbraco.Tests/TestHelpers/Entities/MockedContentTypes.cs b/src/Umbraco.Tests/TestHelpers/Entities/MockedContentTypes.cs index c55467431d..e3bb012dae 100644 --- a/src/Umbraco.Tests/TestHelpers/Entities/MockedContentTypes.cs +++ b/src/Umbraco.Tests/TestHelpers/Entities/MockedContentTypes.cs @@ -41,12 +41,12 @@ namespace Umbraco.Tests.TestHelpers.Entities }; var contentCollection = new PropertyTypeCollection(true); - contentCollection.Add(new PropertyType("test", ValueStorageType.Ntext) { Alias = "title", Name = "Title", Description = "", Mandatory = false, SortOrder = 1, DataTypeId = -88 }); - contentCollection.Add(new PropertyType("test", ValueStorageType.Ntext) { Alias = "bodyText", Name = "Body Text", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = -87 }); + contentCollection.Add(new PropertyType("test", ValueStorageType.Ntext) { Alias = "title", Name = "Title", Description = "", Mandatory = false, SortOrder = 1, DataTypeId = Constants.DataTypes.Textbox, LabelOnTop = true }); + contentCollection.Add(new PropertyType("test", ValueStorageType.Ntext) { Alias = "bodyText", Name = "Body Text", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = Constants.DataTypes.RichtextEditor, LabelOnTop = false }); var metaCollection = new PropertyTypeCollection(true); - metaCollection.Add(new PropertyType("test", ValueStorageType.Ntext) { Alias = "keywords", Name = "Meta Keywords", Description = "", Mandatory = false, SortOrder = 1, DataTypeId = -88 }); - metaCollection.Add(new PropertyType("test", ValueStorageType.Ntext) { Alias = "description", Name = "Meta Description", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = -89 }); + metaCollection.Add(new PropertyType("test", ValueStorageType.Ntext) { Alias = "keywords", Name = "Meta Keywords", Description = "", Mandatory = false, SortOrder = 1, DataTypeId = Constants.DataTypes.Textbox }); + metaCollection.Add(new PropertyType("test", ValueStorageType.Ntext) { Alias = "description", Name = "Meta Description", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = Constants.DataTypes.Textarea }); contentType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Content", SortOrder = 1 }); contentType.PropertyGroups.Add(new PropertyGroup(metaCollection) { Name = "Meta", SortOrder = 2 }); @@ -213,7 +213,7 @@ namespace Umbraco.Tests.TestHelpers.Entities contentType.Trashed = false; var contentCollection = new PropertyTypeCollection(true); - contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.TextBox, ValueStorageType.Ntext) { Alias = RandomAlias("title", randomizeAliases), Name = "Title", Description = "", Mandatory = false, SortOrder = 1, DataTypeId = -88 }); + contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.TextBox, ValueStorageType.Ntext) { Alias = RandomAlias("title", randomizeAliases), Name = "Title", Description = "", Mandatory = false, SortOrder = 1, DataTypeId = -88, LabelOnTop = true }); contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.TinyMce, ValueStorageType.Ntext) { Alias = RandomAlias("bodyText", randomizeAliases), Name = "Body Text", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = -87 }); contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.TextBox, ValueStorageType.Ntext) { Alias = RandomAlias("author", randomizeAliases) , Name = "Author", Description = "Name of the author", Mandatory = false, SortOrder = 3, DataTypeId = -88 }); diff --git a/src/Umbraco.Tests/TestHelpers/SettingsForTests.cs b/src/Umbraco.Tests/TestHelpers/SettingsForTests.cs index 572d572ab7..1121a48823 100644 --- a/src/Umbraco.Tests/TestHelpers/SettingsForTests.cs +++ b/src/Umbraco.Tests/TestHelpers/SettingsForTests.cs @@ -23,7 +23,8 @@ namespace Umbraco.Tests.TestHelpers settings.LocalTempStorageLocation == LocalTempStorage.Default && settings.LocalTempPath == IOHelper.MapPath("~/App_Data/TEMP") && settings.ReservedPaths == (GlobalSettings.StaticReservedPaths + "~/umbraco") && - settings.ReservedUrls == GlobalSettings.StaticReservedUrls); + settings.ReservedUrls == GlobalSettings.StaticReservedUrls && + settings.SqlWriteLockTimeOut == 1800); return config; } diff --git a/src/Umbraco.Tests/TestHelpers/TestObjects.cs b/src/Umbraco.Tests/TestHelpers/TestObjects.cs index 56ad22d414..cc1cfa6a1d 100644 --- a/src/Umbraco.Tests/TestHelpers/TestObjects.cs +++ b/src/Umbraco.Tests/TestHelpers/TestObjects.cs @@ -81,7 +81,7 @@ namespace Umbraco.Tests.TestHelpers /// /// /// An event messages factory. - /// Some url segment providers. + /// Some URL segment providers. /// /// A container. /// @@ -183,7 +183,7 @@ namespace Umbraco.Tests.TestHelpers compiledPackageXmlParser, Mock.Of(), new DirectoryInfo(IOHelper.GetRootDirectorySafe()))); }); - var relationService = GetLazyService(factory, c => new RelationService(scopeProvider, logger, eventMessagesFactory, entityService.Value, GetRepo(c), GetRepo(c))); + var relationService = GetLazyService(factory, c => new RelationService(scopeProvider, logger, eventMessagesFactory, entityService.Value, GetRepo(c), GetRepo(c), GetRepo(c))); var tagService = GetLazyService(factory, c => new TagService(scopeProvider, logger, eventMessagesFactory, GetRepo(c))); var redirectUrlService = GetLazyService(factory, c => new RedirectUrlService(scopeProvider, logger, eventMessagesFactory, GetRepo(c))); var consentService = GetLazyService(factory, c => new ConsentService(scopeProvider, logger, eventMessagesFactory, GetRepo(c))); diff --git a/src/Umbraco.Tests/Testing/TestingTests/MockTests.cs b/src/Umbraco.Tests/Testing/TestingTests/MockTests.cs index ee91427a63..f53b0bfff0 100644 --- a/src/Umbraco.Tests/Testing/TestingTests/MockTests.cs +++ b/src/Umbraco.Tests/Testing/TestingTests/MockTests.cs @@ -86,7 +86,7 @@ namespace Umbraco.Tests.Testing.TestingTests var theUrlProvider = new UrlProvider(umbracoContext, new [] { urlProvider }, Enumerable.Empty(), umbracoContext.VariationContextAccessor); - var contentType = new PublishedContentType(666, "alias", PublishedItemType.Content, Enumerable.Empty(), Enumerable.Empty(), ContentVariation.Nothing); + var contentType = new PublishedContentType(Guid.NewGuid(), 666, "alias", PublishedItemType.Content, Enumerable.Empty(), Enumerable.Empty(), ContentVariation.Nothing); var publishedContent = Mock.Of(); Mock.Get(publishedContent).Setup(x => x.ContentType).Returns(contentType); diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 5518fb5678..0ec48730ac 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -78,7 +78,7 @@ - + 1.8.14 @@ -100,7 +100,10 @@ - + + 4.14.5 + + @@ -109,7 +112,6 @@ - @@ -143,10 +145,13 @@ + + + @@ -167,9 +172,11 @@ + + @@ -268,7 +275,7 @@ - + @@ -515,6 +522,7 @@ + @@ -639,4 +647,4 @@ - \ No newline at end of file + diff --git a/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs b/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs index e9f18d8947..1a288b423c 100644 --- a/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs +++ b/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs @@ -45,7 +45,7 @@ namespace Umbraco.Tests.UmbracoExamine public static ContentIndexPopulator GetContentIndexRebuilder(PropertyEditorCollection propertyEditors, IContentService contentService, IScopeProvider scopeProvider, bool publishedValuesOnly) { var contentValueSetBuilder = GetContentValueSetBuilder(propertyEditors, scopeProvider, publishedValuesOnly); - var contentIndexDataSource = new ContentIndexPopulator(true, null, contentService, scopeProvider.SqlContext, contentValueSetBuilder); + var contentIndexDataSource = new ContentIndexPopulator(publishedValuesOnly, null, contentService, scopeProvider.SqlContext, contentValueSetBuilder); return contentIndexDataSource; } diff --git a/src/Umbraco.Tests/Web/Validation/ContentModelValidatorTests.cs b/src/Umbraco.Tests/Web/Validation/ContentModelValidatorTests.cs new file mode 100644 index 0000000000..fbb2ce9c80 --- /dev/null +++ b/src/Umbraco.Tests/Web/Validation/ContentModelValidatorTests.cs @@ -0,0 +1,369 @@ +using Moq; +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Umbraco.Core.Services; +using Umbraco.Core.Logging; +using Umbraco.Web; +using Umbraco.Web.Editors.Filters; +using Umbraco.Tests.TestHelpers.Entities; +using Umbraco.Core.Models; +using Umbraco.Web.Models.ContentEditing; +using Umbraco.Web.Editors.Binders; +using Umbraco.Core; +using Umbraco.Tests.Testing; +using Umbraco.Core.Mapping; +using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Composing; +using System.Web.Http.ModelBinding; +using Umbraco.Web.PropertyEditors; +using System.ComponentModel.DataAnnotations; +using Umbraco.Tests.TestHelpers; +using System.Globalization; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Umbraco.Web.PropertyEditors.Validation; + +namespace Umbraco.Tests.Web.Validation +{ + [UmbracoTest(Mapper = true, WithApplication = true, Logger = UmbracoTestOptions.Logger.Console)] + [TestFixture] + public class ContentModelValidatorTests : UmbracoTestBase + { + private const int ComplexDataTypeId = 9999; + private const string ContentTypeAlias = "textPage"; + private ContentType _contentType; + + public override void SetUp() + { + base.SetUp(); + + _contentType = MockedContentTypes.CreateTextPageContentType(ContentTypeAlias); + // add complex editor + _contentType.AddPropertyType( + new PropertyType("complexTest", ValueStorageType.Ntext) { Alias = "complex", Name = "Complex", Description = "", Mandatory = false, SortOrder = 1, DataTypeId = ComplexDataTypeId }, + "Content"); + + // make them all validate with a regex rule that will not pass + foreach (var prop in _contentType.PropertyTypes) + { + prop.ValidationRegExp = "^donotmatch$"; + prop.ValidationRegExpMessage = "Does not match!"; + } + } + + protected override void Compose() + { + base.Compose(); + + var complexEditorConfig = new NestedContentConfiguration + { + ContentTypes = new[] + { + new NestedContentConfiguration.ContentType { Alias = "feature" } + } + }; + var dataTypeService = new Mock(); + dataTypeService.Setup(x => x.GetDataType(It.IsAny())) + .Returns((int id) => id == ComplexDataTypeId + ? Mock.Of(x => x.Configuration == complexEditorConfig) + : Mock.Of()); + + var contentTypeService = new Mock(); + contentTypeService.Setup(x => x.GetAll(It.IsAny())) + .Returns(() => new List + { + _contentType + }); + + var textService = new Mock(); + textService.Setup(x => x.Localize("validation/invalidPattern", It.IsAny(), It.IsAny>())).Returns(() => "invalidPattern"); + textService.Setup(x => x.Localize("validation/invalidNull", It.IsAny(), It.IsAny>())).Returns("invalidNull"); + textService.Setup(x => x.Localize("validation/invalidEmpty", It.IsAny(), It.IsAny>())).Returns("invalidEmpty"); + + Composition.RegisterUnique(x => Mock.Of(x => x.GetDataType(It.IsAny()) == Mock.Of())); + Composition.RegisterUnique(x => dataTypeService.Object); + Composition.RegisterUnique(x => contentTypeService.Object); + Composition.RegisterUnique(x => textService.Object); + + Composition.WithCollectionBuilder() + .Add() + .Add(); + } + + [Test] + public void Test_Serializer() + { + var nestedLevel2 = new ComplexEditorValidationResult(); + var id1 = Guid.NewGuid(); + var addressInfoElementTypeResult = new ComplexEditorElementTypeValidationResult("addressInfo", id1); + var cityPropertyTypeResult = new ComplexEditorPropertyTypeValidationResult("city"); + cityPropertyTypeResult.AddValidationResult(new ValidationResult("City is invalid")); + cityPropertyTypeResult.AddValidationResult(new ValidationResult("City cannot be empty")); + cityPropertyTypeResult.AddValidationResult(new ValidationResult("City is not in Australia", new[] { "country" })); + cityPropertyTypeResult.AddValidationResult(new ValidationResult("Not a capital city", new[] { "capital" })); + addressInfoElementTypeResult.ValidationResults.Add(cityPropertyTypeResult); + nestedLevel2.ValidationResults.Add(addressInfoElementTypeResult); + + var nestedLevel1 = new ComplexEditorValidationResult(); + var id2 = Guid.NewGuid(); + var addressBookElementTypeResult = new ComplexEditorElementTypeValidationResult("addressBook", id2); + var addressesPropertyTypeResult = new ComplexEditorPropertyTypeValidationResult("addresses"); + addressesPropertyTypeResult.AddValidationResult(new ValidationResult("Must have at least 3 addresses", new[] { "counter" })); + addressesPropertyTypeResult.AddValidationResult(nestedLevel2); // This is a nested result within the level 1 + addressBookElementTypeResult.ValidationResults.Add(addressesPropertyTypeResult); + var bookNamePropertyTypeResult = new ComplexEditorPropertyTypeValidationResult("bookName"); + bookNamePropertyTypeResult.AddValidationResult(new ValidationResult("Invalid address book name", new[] { "book" })); + addressBookElementTypeResult.ValidationResults.Add(bookNamePropertyTypeResult); + nestedLevel1.ValidationResults.Add(addressBookElementTypeResult); + + var id3 = Guid.NewGuid(); + var addressBookElementTypeResult2 = new ComplexEditorElementTypeValidationResult("addressBook", id3); + var addressesPropertyTypeResult2 = new ComplexEditorPropertyTypeValidationResult("addresses"); + addressesPropertyTypeResult2.AddValidationResult(new ValidationResult("Must have at least 2 addresses", new[] { "counter" })); + addressBookElementTypeResult2.ValidationResults.Add(addressesPropertyTypeResult); + var bookNamePropertyTypeResult2 = new ComplexEditorPropertyTypeValidationResult("bookName"); + bookNamePropertyTypeResult2.AddValidationResult(new ValidationResult("Name is too long")); + addressBookElementTypeResult2.ValidationResults.Add(bookNamePropertyTypeResult2); + nestedLevel1.ValidationResults.Add(addressBookElementTypeResult2); + + // books is the outer most validation result and doesn't have it's own direct ValidationResult errors + var outerError = new ComplexEditorValidationResult(); + var id4 = Guid.NewGuid(); + var addressBookCollectionElementTypeResult = new ComplexEditorElementTypeValidationResult("addressBookCollection", id4); + var booksPropertyTypeResult= new ComplexEditorPropertyTypeValidationResult("books"); + booksPropertyTypeResult.AddValidationResult(nestedLevel1); // books is the outer most validation result + addressBookCollectionElementTypeResult.ValidationResults.Add(booksPropertyTypeResult); + outerError.ValidationResults.Add(addressBookCollectionElementTypeResult); + + var serialized = JsonConvert.SerializeObject(outerError, Formatting.Indented, new ValidationResultConverter()); + Console.WriteLine(serialized); + + var jsonError = JsonConvert.DeserializeObject(serialized); + + Assert.IsNotNull(jsonError.SelectToken("$[0]")); + Assert.AreEqual(id4.ToString(), jsonError.SelectToken("$[0].$id").Value()); + Assert.AreEqual("addressBookCollection", jsonError.SelectToken("$[0].$elementTypeAlias").Value()); + Assert.AreEqual(string.Empty, jsonError.SelectToken("$[0].ModelState['_Properties.books.invariant.null'][0]").Value()); + + var error0 = jsonError.SelectToken("$[0].books") as JArray; + Assert.IsNotNull(error0); + Assert.AreEqual(id2.ToString(), error0.SelectToken("$[0].$id").Value()); + Assert.AreEqual("addressBook", error0.SelectToken("$[0].$elementTypeAlias").Value()); + Assert.IsNotNull(error0.SelectToken("$[0].ModelState")); + Assert.AreEqual(string.Empty, error0.SelectToken("$[0].ModelState['_Properties.addresses.invariant.null'][0]").Value()); + var error1 = error0.SelectToken("$[0].ModelState['_Properties.addresses.invariant.null.counter']") as JArray; + Assert.IsNotNull(error1); + Assert.AreEqual(1, error1.Count); + var error2 = error0.SelectToken("$[0].ModelState['_Properties.bookName.invariant.null.book']") as JArray; + Assert.IsNotNull(error2); + Assert.AreEqual(1, error2.Count); + + Assert.AreEqual(id3.ToString(), error0.SelectToken("$[1].$id").Value()); + Assert.AreEqual("addressBook", error0.SelectToken("$[1].$elementTypeAlias").Value()); + Assert.IsNotNull(error0.SelectToken("$[1].ModelState")); + Assert.AreEqual(string.Empty, error0.SelectToken("$[1].ModelState['_Properties.addresses.invariant.null'][0]").Value()); + var error6 = error0.SelectToken("$[1].ModelState['_Properties.addresses.invariant.null.counter']") as JArray; + Assert.IsNotNull(error6); + Assert.AreEqual(1, error6.Count); + var error7 = error0.SelectToken("$[1].ModelState['_Properties.bookName.invariant.null']") as JArray; + Assert.IsNotNull(error7); + Assert.AreEqual(1, error7.Count); + + Assert.IsNotNull(error0.SelectToken("$[0].addresses")); + Assert.AreEqual(id1.ToString(), error0.SelectToken("$[0].addresses[0].$id").Value()); + Assert.AreEqual("addressInfo", error0.SelectToken("$[0].addresses[0].$elementTypeAlias").Value()); + Assert.IsNotNull(error0.SelectToken("$[0].addresses[0].ModelState")); + var error3 = error0.SelectToken("$[0].addresses[0].ModelState['_Properties.city.invariant.null.country']") as JArray; + Assert.IsNotNull(error3); + Assert.AreEqual(1, error3.Count); + var error4 = error0.SelectToken("$[0].addresses[0].ModelState['_Properties.city.invariant.null.capital']") as JArray; + Assert.IsNotNull(error4); + Assert.AreEqual(1, error4.Count); + var error5 = error0.SelectToken("$[0].addresses[0].ModelState['_Properties.city.invariant.null']") as JArray; + Assert.IsNotNull(error5); + Assert.AreEqual(2, error5.Count); + } + + [Test] + public void Validating_ContentItemSave() + { + var validator = new ContentSaveModelValidator( + Factory.GetInstance(), + Mock.Of(), + Factory.GetInstance()); + + var content = MockedContent.CreateTextpageContent(_contentType, "test", -1); + + var id1 = new Guid("c8df5136-d606-41f0-9134-dea6ae0c2fd9"); + var id2 = new Guid("f916104a-4082-48b2-a515-5c4bf2230f38"); + var id3 = new Guid("77E15DE9-1C79-47B2-BC60-4913BC4D4C6A"); + + // TODO: Ok now test with a 4th level complex nested editor + + var complexValue = @"[{ + ""key"": """ + id1.ToString() + @""", + ""name"": ""Hello world"", + ""ncContentTypeAlias"": """ + ContentTypeAlias + @""", + ""title"": ""Hello world"", + ""bodyText"": ""The world is round"" + }, { + ""key"": """ + id2.ToString() + @""", + ""name"": ""Super nested"", + ""ncContentTypeAlias"": """ + ContentTypeAlias + @""", + ""title"": ""Hi there!"", + ""bodyText"": ""Well hello there"", + ""complex"" : [{ + ""key"": """ + id3.ToString() + @""", + ""name"": ""I am a sub nested content"", + ""ncContentTypeAlias"": """ + ContentTypeAlias + @""", + ""title"": ""Hello up there :)"", + ""bodyText"": ""Hello way up there on a different level"" + }] + } + ]"; + content.SetValue("complex", complexValue); + + // map the persisted properties to a model representing properties to save + //var saveProperties = content.Properties.Select(x => Mapper.Map(x)).ToList(); + var saveProperties = content.Properties.Select(x => + { + return new ContentPropertyBasic + { + Alias = x.Alias, + Id = x.Id, + Value = x.GetValue() + }; + }).ToList(); + + var saveVariants = new List + { + new ContentVariantSave + { + Culture = string.Empty, + Segment = string.Empty, + Name = content.Name, + Save = true, + Properties = saveProperties + } + }; + + var save = new ContentItemSave + { + Id = content.Id, + Action = ContentSaveAction.Save, + ContentTypeAlias = _contentType.Alias, + ParentId = -1, + PersistedContent = content, + TemplateAlias = null, + Variants = saveVariants + }; + + // This will map the ContentItemSave.Variants.PropertyCollectionDto and then map the values in the saved model + // back onto the persisted IContent model. + ContentItemBinder.BindModel(save, content); + + var modelState = new ModelStateDictionary(); + var isValid = validator.ValidatePropertiesData(save, saveVariants[0], saveVariants[0].PropertyCollectionDto, modelState); + + // list results for debugging + foreach (var state in modelState) + { + Console.WriteLine(state.Key); + foreach (var error in state.Value.Errors) + { + Console.WriteLine("\t" + error.ErrorMessage); + } + } + + // assert + + Assert.IsFalse(isValid); + Assert.AreEqual(11, modelState.Keys.Count); + const string complexPropertyKey = "_Properties.complex.invariant.null"; + Assert.IsTrue(modelState.Keys.Contains(complexPropertyKey)); + foreach (var state in modelState.Where(x => x.Key != complexPropertyKey)) + { + foreach (var error in state.Value.Errors) + { + Assert.IsFalse(error.ErrorMessage.DetectIsJson()); // non complex is just an error message + } + } + var complexEditorErrors = modelState.Single(x => x.Key == complexPropertyKey).Value.Errors; + Assert.AreEqual(1, complexEditorErrors.Count); + var nestedError = complexEditorErrors[0]; + var jsonError = JsonConvert.DeserializeObject(nestedError.ErrorMessage); + + var modelStateKeys = new[] { "_Properties.title.invariant.null.innerFieldId", "_Properties.title.invariant.null.value", "_Properties.bodyText.invariant.null.innerFieldId", "_Properties.bodyText.invariant.null.value" }; + AssertNestedValidation(jsonError, 0, id1, modelStateKeys); + AssertNestedValidation(jsonError, 1, id2, modelStateKeys.Concat(new[] { "_Properties.complex.invariant.null.innerFieldId", "_Properties.complex.invariant.null.value" }).ToArray()); + var nestedJsonError = jsonError.SelectToken("$[1].complex") as JArray; + Assert.IsNotNull(nestedJsonError); + AssertNestedValidation(nestedJsonError, 0, id3, modelStateKeys); + } + + private void AssertNestedValidation(JArray jsonError, int index, Guid id, string[] modelStateKeys) + { + Assert.IsNotNull(jsonError.SelectToken("$[" + index + "]")); + Assert.AreEqual(id.ToString(), jsonError.SelectToken("$[" + index + "].$id").Value()); + Assert.AreEqual("textPage", jsonError.SelectToken("$[" + index + "].$elementTypeAlias").Value()); + Assert.IsNotNull(jsonError.SelectToken("$[" + index + "].ModelState")); + foreach (var key in modelStateKeys) + { + var error = jsonError.SelectToken("$[" + index + "].ModelState['" + key + "']") as JArray; + Assert.IsNotNull(error); + Assert.AreEqual(1, error.Count); + } + } + + [HideFromTypeFinder] + [DataEditor("complexTest", "test", "test")] + public class ComplexTestEditor : NestedContentPropertyEditor + { + public ComplexTestEditor(ILogger logger, Lazy propertyEditors, IDataTypeService dataTypeService, IContentTypeService contentTypeService) : base(logger, propertyEditors, dataTypeService, contentTypeService) + { + } + + protected override IDataValueEditor CreateValueEditor() + { + var editor = base.CreateValueEditor(); + editor.Validators.Add(new NeverValidateValidator()); + return editor; + } + } + + [HideFromTypeFinder] + [DataEditor("test", "test", "test")] // This alias aligns with the prop editor alias for all properties created from MockedContentTypes.CreateTextPageContentType + public class TestEditor : DataEditor + { + public TestEditor(ILogger logger) + : base(logger) + { + } + + protected override IDataValueEditor CreateValueEditor() => new TestValueEditor(Attribute); + + private class TestValueEditor : DataValueEditor + { + public TestValueEditor(DataEditorAttribute attribute) + : base(attribute) + { + Validators.Add(new NeverValidateValidator()); + } + + } + } + + public class NeverValidateValidator : IValueValidator + { + public IEnumerable Validate(object value, string valueType, object dataTypeConfiguration) + { + yield return new ValidationResult("WRONG!", new[] { "innerFieldId" }); + } + } + + } +} diff --git a/src/Umbraco.Tests/Web/ModelStateExtensionsTests.cs b/src/Umbraco.Tests/Web/Validation/ModelStateExtensionsTests.cs similarity index 99% rename from src/Umbraco.Tests/Web/ModelStateExtensionsTests.cs rename to src/Umbraco.Tests/Web/Validation/ModelStateExtensionsTests.cs index 7b25e60b5a..0355705378 100644 --- a/src/Umbraco.Tests/Web/ModelStateExtensionsTests.cs +++ b/src/Umbraco.Tests/Web/Validation/ModelStateExtensionsTests.cs @@ -6,7 +6,7 @@ using NUnit.Framework; using Umbraco.Core.Services; using Umbraco.Web; -namespace Umbraco.Tests.Web +namespace Umbraco.Tests.Web.Validation { [TestFixture] public class ModelStateExtensionsTests diff --git a/src/Umbraco.Web.UI.Client/gulp/config.js b/src/Umbraco.Web.UI.Client/gulp/config.js index 3c32832ba0..9eb335a62c 100755 --- a/src/Umbraco.Web.UI.Client/gulp/config.js +++ b/src/Umbraco.Web.UI.Client/gulp/config.js @@ -30,6 +30,7 @@ module.exports = { // js files for backoffice // processed in the js task js: { + websitepreview: { files: "./src/websitepreview/**/*.js", out: "umbraco.websitepreview.js" }, preview: { files: "./src/preview/**/*.js", out: "umbraco.preview.js" }, installer: { files: "./src/installer/**/*.js", out: "umbraco.installer.js" }, filters: { files: "./src/common/filters/**/*.js", out: "umbraco.filters.js" }, @@ -56,7 +57,7 @@ module.exports = { ], out: "umbraco.directives.js" } - + }, //selectors for copying all views into the build diff --git a/src/Umbraco.Web.UI.Client/gulp/tasks/dependencies.js b/src/Umbraco.Web.UI.Client/gulp/tasks/dependencies.js index def956ac9f..b5339b60c9 100644 --- a/src/Umbraco.Web.UI.Client/gulp/tasks/dependencies.js +++ b/src/Umbraco.Web.UI.Client/gulp/tasks/dependencies.js @@ -28,12 +28,18 @@ function dependencies() { "./node_modules/ace-builds/src-min-noconflict/snippets/text.js", "./node_modules/ace-builds/src-min-noconflict/snippets/javascript.js", "./node_modules/ace-builds/src-min-noconflict/snippets/css.js", + "./node_modules/ace-builds/src-min-noconflict/snippets/json.js", + "./node_modules/ace-builds/src-min-noconflict/snippets/xml.js", "./node_modules/ace-builds/src-min-noconflict/theme-chrome.js", "./node_modules/ace-builds/src-min-noconflict/mode-razor.js", "./node_modules/ace-builds/src-min-noconflict/mode-javascript.js", "./node_modules/ace-builds/src-min-noconflict/mode-css.js", + "./node_modules/ace-builds/src-min-noconflict/mode-json.js", + "./node_modules/ace-builds/src-min-noconflict/mode-xml.js", "./node_modules/ace-builds/src-min-noconflict/worker-javascript.js", - "./node_modules/ace-builds/src-min-noconflict/worker-css.js" + "./node_modules/ace-builds/src-min-noconflict/worker-css.js", + "./node_modules/ace-builds/src-min-noconflict/worker-json.js", + "./node_modules/ace-builds/src-min-noconflict/worker-xml.js" ], "base": "./node_modules/ace-builds" }, @@ -44,7 +50,8 @@ function dependencies() { }, { "name": "angular-aria", - "src": ["./node_modules/angular-aria/angular-aria.min.js"], + "src": ["./node_modules/angular-aria/angular-aria.min.js", + "./node_modules/angular-aria/angular-aria.min.js.map"], "base": "./node_modules/angular-aria" }, { @@ -213,10 +220,10 @@ function dependencies() { { "name": "spectrum", "src": [ - "./node_modules/spectrum-colorpicker/spectrum.js", - "./node_modules/spectrum-colorpicker/spectrum.css" + "./node_modules/spectrum-colorpicker2/dist/spectrum.js", + "./node_modules/spectrum-colorpicker2/dist/spectrum.css" ], - "base": "./node_modules/spectrum-colorpicker" + "base": "./node_modules/spectrum-colorpicker2/dist" }, { "name": "tinymce", @@ -237,6 +244,14 @@ function dependencies() { "name": "underscore", "src": ["node_modules/underscore/underscore-min.js"], "base": "./node_modules/underscore" + }, + { + "name": "wicg-inert", + "src": [ + "./node_modules/wicg-inert/dist/inert.min.js", + "./node_modules/wicg-inert/dist/inert.min.js.map" + ], + "base": "./node_modules/wicg-inert" } ]; diff --git a/src/Umbraco.Web.UI.Client/gulp/tasks/test.js b/src/Umbraco.Web.UI.Client/gulp/tasks/test.js index 255fe17435..b5239d35e7 100644 --- a/src/Umbraco.Web.UI.Client/gulp/tasks/test.js +++ b/src/Umbraco.Web.UI.Client/gulp/tasks/test.js @@ -23,7 +23,6 @@ function runUnitTestServer() { autoWatch: true, port: 9999, singleRun: false, - browsers: ['ChromeDebugging'], keepalive: true }) .start(); diff --git a/src/Umbraco.Web.UI.Client/gulpfile.js b/src/Umbraco.Web.UI.Client/gulpfile.js index d272c77397..547b77cc7b 100644 --- a/src/Umbraco.Web.UI.Client/gulpfile.js +++ b/src/Umbraco.Web.UI.Client/gulpfile.js @@ -6,11 +6,11 @@ * =========== * This is now using Gulp 4, each child task is now a child function in its own corresponding file. * - * To add a new task, simply add a new task file to gulp/tasks folder, add a require statement below to include the one or more methods + * To add a new task, simply add a new task file to gulp/tasks folder, add a require statement below to include the one or more methods * and then add the exports command to add the new item into the task menu. */ -const { src, dest, series, parallel, lastRun } = require('gulp'); +const { series, parallel } = require('gulp'); const config = require('./gulp/config'); const { setDevelopmentMode, setTestMode } = require('./gulp/modes'); @@ -28,9 +28,9 @@ config.compile.current = config.compile.build; // These Exports are the new way of defining Tasks in Gulp 4.x // *********************************************************** exports.build = series(parallel(dependencies, js, less, views), testUnit); -exports.dev = series(setDevelopmentMode, parallel(dependencies, js, less, views), watchTask); +exports.dev = series(setDevelopmentMode, parallel(dependencies, js, less, views), runUnitTestServer, watchTask); exports.watch = series(watchTask); -// +// exports.runTests = series(setTestMode, series(js, testUnit)); exports.runUnit = series(setTestMode, series(js, runUnitTestServer), watchTask); exports.testE2e = series(setTestMode, parallel(testE2e)); diff --git a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/pager.less b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/pager.less index 1476188297..718ade757e 100644 --- a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/pager.less +++ b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/pager.less @@ -13,6 +13,7 @@ display: inline; } .pager li > a, +.pager li > button, .pager li > span { display: inline-block; padding: 5px 14px; @@ -21,23 +22,30 @@ .border-radius(15px); } .pager li > a:hover, -.pager li > a:focus { - text-decoration: none; - background-color: #f5f5f5; +.pager li > a:focus, +.pager li > button:hover, +.pager li > button:focus { + text-decoration: none; + background-color: #f5f5f5; } .pager .next > a, +.pager .next > button, .pager .next > span { float: right; } .pager .previous > a, +.pager .previous > button, .pager .previous > span { - float: left; + float: left; } .pager .disabled > a, .pager .disabled > a:hover, .pager .disabled > a:focus, +.pager .disabled > button, +.pager .disabled > button:hover, +.pager .disabled > button:focus .pager .disabled > span { - color: @grayLight; - background-color: #fff; - cursor: default; -} \ No newline at end of file + color: @grayLight; + background-color: #fff; + cursor: default; +} diff --git a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/pagination.less b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/pagination.less index 6f6e6ac7de..ae10700eb3 100644 --- a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/pagination.less +++ b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/pagination.less @@ -21,6 +21,7 @@ display: inline; // Remove list-style and block-level defaults } .pagination ul > li > a, +.pagination ul > li > button, .pagination ul > li > span { float: left; // Collapse white-space padding: 4px 12px; @@ -32,29 +33,38 @@ } .pagination ul > li > a:hover, .pagination ul > li > a:focus, +.pagination ul > li > button:hover, +.pagination ul > li > button:focus, .pagination ul > .active > a, +.pagination ul > .active > button, .pagination ul > .active > span { - background-color: @paginationActiveBackground; + background-color: @paginationActiveBackground; } .pagination ul > .active > a, +.pagination ul > .active > button, .pagination ul > .active > span { - color: @grayLight; - cursor: default; + color: @grayLight; + cursor: default; } .pagination ul > .disabled > span, .pagination ul > .disabled > a, .pagination ul > .disabled > a:hover, -.pagination ul > .disabled > a:focus { - color: @grayLight; - background-color: transparent; - cursor: default; +.pagination ul > .disabled > a:focus, +.pagination ul > .disabled > button, +.pagination ul > .disabled > button:hover, +.pagination ul > .disabled > button:focus { + color: @grayLight; + background-color: transparent; + cursor: default; } .pagination ul > li:first-child > a, +.pagination ul > li:first-child > button, .pagination ul > li:first-child > span { border-left-width: 1px; .border-left-radius(@baseBorderRadius); } .pagination ul > li:last-child > a, +.pagination ul > li:last-child > button, .pagination ul > li:last-child > span { .border-right-radius(@baseBorderRadius); } @@ -77,15 +87,18 @@ // Large .pagination-large { ul > li > a, + ul > li > button, ul > li > span { padding: @paddingLarge; font-size: @fontSizeLarge; } ul > li:first-child > a, + ul > li:first-child > button, ul > li:first-child > span { .border-left-radius(@borderRadiusLarge); } ul > li:last-child > a, + ul > li:last-child > button, ul > li:last-child > span { .border-right-radius(@borderRadiusLarge); } @@ -95,10 +108,12 @@ .pagination-mini, .pagination-small { ul > li:first-child > a, + ul > li:first-child > button, ul > li:first-child > span { .border-left-radius(@borderRadiusSmall); } ul > li:last-child > a, + ul > li:last-child > button, ul > li:last-child > span { .border-right-radius(@borderRadiusSmall); } @@ -107,6 +122,7 @@ // Small .pagination-small { ul > li > a, + ul > li > button, ul > li > span { padding: @paddingSmall; font-size: @fontSizeSmall; @@ -115,6 +131,7 @@ // Mini .pagination-mini { ul > li > a, + ul > li > button, ul > li > span { padding: @paddingMini; font-size: @fontSizeMini; diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/cy.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/cy.js index d2fdd077f5..2863840abb 100644 --- a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/cy.js +++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/cy.js @@ -121,7 +121,7 @@ tinymce.addI18n('cy',{ "Date\/time": "Dyddiad\/amser", "Insert date\/time": "Mewnosod dyddiad\/amser", "Remove link": "Tynnu dolen", -"Url": "Url", +"Url": "URL", "Text to display": "Testun i'w ddangos", "Anchors": "Angorau", "Insert link": "Mewnosod dolen", @@ -227,4 +227,4 @@ tinymce.addI18n('cy',{ "View": "Dangos", "Table": "Tabl", "Format": "Fformat" -}); \ No newline at end of file +}); diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/da.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/da.js index 90e4009d92..a50f2f10ec 100644 --- a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/da.js +++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/da.js @@ -150,7 +150,7 @@ tinymce.addI18n('da',{ "Insert link": "Inds\u00e6t link", "Insert\/edit link": "Inds\u00e6t\/ret link", "Text to display": "Vis tekst", -"Url": "Url", +"Url": "URL", "Target": "Target", "None": "Ingen", "New window": "Nyt vindue", @@ -258,4 +258,4 @@ tinymce.addI18n('da',{ "Tools": "V\u00e6rkt\u00f8j", "Powered by {0}": "Drevet af {0}", "Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "Rich Text omr\u00e5de. Tryk ALT-F9 for menu. Tryk ALT-F10 for toolbar. Tryk ALT-0 for hj\u00e6lp" -}); \ No newline at end of file +}); diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/de_AT.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/de_AT.js index 2af071f5ce..0073810c1a 100644 --- a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/de_AT.js +++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/de_AT.js @@ -150,7 +150,7 @@ tinymce.addI18n('de_AT',{ "Insert link": "Link einf\u00fcgen", "Insert\/edit link": "Link einf\u00fcgen\/bearbeiten", "Text to display": "Angezeigter Text", -"Url": "Url", +"Url": "URL", "Target": "Ziel", "None": "Keine", "New window": "Neues Fenster", @@ -258,4 +258,4 @@ tinymce.addI18n('de_AT',{ "Tools": "Extras", "Powered by {0}": "Betrieben von {0}", "Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "Rich Text Area. Dr\u00fccken Sie ALT-F9 f\u00fcr das Men\u00fc. Dr\u00fccken Sie ALT-F10 f\u00fcr die Werkzeugleiste. Dr\u00fccken Sie ALT-0 f\u00fcr Hilfe" -}); \ No newline at end of file +}); diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/en_CA.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/en_CA.js index f32de01724..cc07ffd23e 100644 --- a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/en_CA.js +++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/en_CA.js @@ -150,7 +150,7 @@ tinymce.addI18n('en_CA',{ "Insert link": "Insert link", "Insert\/edit link": "Insert\/edit link", "Text to display": "Text to display", -"Url": "Url", +"Url": "URL", "Target": "Target", "None": "None", "New window": "New window", @@ -258,4 +258,4 @@ tinymce.addI18n('en_CA',{ "Tools": "Tools", "Powered by {0}": "Powered by {0}", "Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help" -}); \ No newline at end of file +}); diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/en_US.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/en_US.js index 90eae85800..0b50212fd9 100644 --- a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/en_US.js +++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/en_US.js @@ -150,7 +150,7 @@ tinymce.addI18n('en_US',{ "Insert link": "Insert link", "Insert\/edit link": "Insert\/edit link", "Text to display": "Text to display", -"Url": "Url", +"Url": "URL", "Target": "Target", "None": "None", "New window": "New window", @@ -258,4 +258,4 @@ tinymce.addI18n('en_US',{ "Tools": "Tools", "Powered by {0}": "Powered by {0}", "Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help" -}); \ No newline at end of file +}); diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/es_MX.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/es_MX.js index b0f2019ffe..688f14ba51 100644 --- a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/es_MX.js +++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/es_MX.js @@ -150,7 +150,7 @@ tinymce.addI18n('es_MX',{ "Insert link": "Insertar enlace", "Insert\/edit link": "Inserta\/editar enlace", "Text to display": "Texto a mostrar", -"Url": "Url", +"Url": "URL", "Target": "Objetivo", "None": "Ninguno", "New window": "Nueva ventana", @@ -258,4 +258,4 @@ tinymce.addI18n('es_MX',{ "Tools": "Herramientas", "Powered by {0}": "Creado con {0}", "Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "Presione dentro del \u00e1rea de texto ALT-F9 para invocar el men\u00fa, ALT-F10 para la barra de herramientas y ALT-0 para la ayuda." -}); \ No newline at end of file +}); diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/et.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/et.js index c6beeeb510..96b763506c 100644 --- a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/et.js +++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/et.js @@ -150,7 +150,7 @@ tinymce.addI18n('et',{ "Insert link": "Lisa link", "Insert\/edit link": "Lisa\/muuda link", "Text to display": "Kuvatav tekst", -"Url": "Viide (url)", +"Url": "Viide (URL)", "Target": "Sihtm\u00e4rk", "None": "Puudub", "New window": "Uus aken", @@ -258,4 +258,4 @@ tinymce.addI18n('et',{ "Tools": "T\u00f6\u00f6riistad", "Powered by {0}": "Kasutatud tarkvara {0}", "Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "Rikastatud teksti ala. Men\u00fc\u00fc jaoks vajuta ALT-F9. T\u00f6\u00f6riistariba jaoks vajuta ALT-F10. Abi saamiseks vajuta ALT-0." -}); \ No newline at end of file +}); diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/eu.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/eu.js index c4374394d4..44e62db3f8 100644 --- a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/eu.js +++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/eu.js @@ -150,7 +150,7 @@ tinymce.addI18n('eu',{ "Insert link": "Esteka txertatu", "Insert\/edit link": "Esteka txertatu\/editatu", "Text to display": "Bistaratzeko testua", -"Url": "Url", +"Url": "URL", "Target": "Target", "None": "Bat ere ez", "New window": "Lehio berria", @@ -258,4 +258,4 @@ tinymce.addI18n('eu',{ "Tools": "Tresnak", "Powered by {0}": "{0}rekin egina", "Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "Testu aberastuko area. Sakatu ALT-F9 menurako. Sakatu ALT-F10 tresna-barrarako. Sakatu ALT-0 laguntzarako" -}); \ No newline at end of file +}); diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/fr.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/fr.js index 2d074f8c6e..d61015efa0 100644 --- a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/fr.js +++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/fr.js @@ -150,7 +150,7 @@ tinymce.addI18n('fr_FR',{ "Insert\/Edit Link": "Ins\u00e9rer\/Modifier lien", "Insert\/edit link": "Ins\u00e9rer\/modifier un lien", "Text to display": "Texte \u00e0 afficher", -"Url": "Url", +"Url": "URL", "Open link in...": "Ouvrir le lien dans...", "Current window": "Fen\u00eatre active", "None": "n\/a", @@ -386,4 +386,4 @@ tinymce.addI18n('fr_FR',{ "Spellcheck": "V\u00e9rification orthographique", "Caption": "Titre", "Insert template": "Ajouter un th\u00e8me" -}); \ No newline at end of file +}); diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/fr_FR.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/fr_FR.js index 5c37164b2c..5ed177c9fc 100644 --- a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/fr_FR.js +++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/fr_FR.js @@ -150,7 +150,7 @@ tinymce.addI18n('fr_FR',{ "Insert link": "Ins\u00e9rer un lien", "Insert\/edit link": "Ins\u00e9rer\/modifier un lien", "Text to display": "Texte \u00e0 afficher", -"Url": "Url", +"Url": "URL", "Target": "Cible", "None": "n\/a", "New window": "Nouvelle fen\u00eatre", @@ -258,4 +258,4 @@ tinymce.addI18n('fr_FR',{ "Tools": "Outils", "Powered by {0}": "Propuls\u00e9 par {0}", "Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "Zone Texte Riche. Appuyer sur ALT-F9 pour le menu. Appuyer sur ALT-F10 pour la barre d'outils. Appuyer sur ALT-0 pour de l'aide." -}); \ No newline at end of file +}); diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/hr.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/hr.js index d52f861ce9..617e1f4823 100644 --- a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/hr.js +++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/hr.js @@ -144,7 +144,7 @@ tinymce.addI18n('hr',{ "Insert link": "Umetni poveznicu", "Insert\/edit link": "Umetni\/izmijeni poveznicu", "Text to display": "Tekst za prikaz", -"Url": "Url", +"Url": "URL", "Target": "Meta", "None": "Ni\u0161ta", "New window": "Novi prozor", @@ -250,4 +250,4 @@ tinymce.addI18n('hr',{ "Table": "Tablica", "Tools": "Alati", "Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "Pritisni ALT-F9 za izbornik. Pritisni ALT-F10 za alatnu traku. Pritisni ALT-0 za pomo\u0107" -}); \ No newline at end of file +}); diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/hu_HU.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/hu_HU.js index 3972dc2b3c..13bb4984a9 100644 --- a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/hu_HU.js +++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/hu_HU.js @@ -150,7 +150,7 @@ tinymce.addI18n('hu_HU',{ "Insert link": "Hivatkoz\u00e1s beilleszt\u00e9se", "Insert\/edit link": "Hivatkoz\u00e1s beilleszt\u00e9se\/szerkeszt\u00e9se", "Text to display": "Megjelen\u0151 sz\u00f6veg", -"Url": "Url", +"Url": "URL", "Target": "C\u00e9l", "None": "Nincs", "New window": "\u00daj ablak", @@ -258,4 +258,4 @@ tinymce.addI18n('hu_HU',{ "Tools": "Eszk\u00f6z\u00f6k", "Powered by {0}": "\u00dczemelteti: {0}", "Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "Rich Text ter\u00fclet. Nyomj ALT-F9-et a men\u00fch\u00f6z. Nyomj ALT-F10-et az eszk\u00f6zt\u00e1rhoz. Nyomj ALT-0-t a s\u00fag\u00f3hoz" -}); \ No newline at end of file +}); diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/it.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/it.js index 5ffc0c0f80..a97118da21 100644 --- a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/it.js +++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/it.js @@ -150,7 +150,7 @@ tinymce.addI18n('it',{ "Insert link": "Inserisci il Link", "Insert\/edit link": "Inserisci\/Modifica Link", "Text to display": "Testo da Visualizzare", -"Url": "Url", +"Url": "URL", "Target": "Target", "None": "No", "New window": "Nuova Finestra", @@ -258,4 +258,4 @@ tinymce.addI18n('it',{ "Tools": "Strumenti", "Powered by {0}": "Fornito da {0}", "Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "Rich Text Area. Premi ALT-F9 per il men\u00f9. Premi ALT-F10 per la barra degli strumenti. Premi ALT-0 per l'aiuto." -}); \ No newline at end of file +}); diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/ka_GE.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/ka_GE.js index 9bffb8040d..805a966489 100644 --- a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/ka_GE.js +++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/ka_GE.js @@ -121,7 +121,7 @@ tinymce.addI18n('ka_GE',{ "Date\/time": "\u10d7\u10d0\u10e0\u10d8\u10e6\u10d8\/\u10d3\u10e0\u10dd", "Insert date\/time": "\u10d7\u10d0\u10e0\u10d8\u10e6\u10d8\/\u10d3\u10e0\u10dd\u10d8\u10e1 \u10e9\u10d0\u10e1\u10db\u10d0", "Remove link": "\u10d1\u10db\u10e3\u10da\u10d8\u10e1 \u10ec\u10d0\u10e8\u10da\u10d0", -"Url": "Url", +"Url": "URL", "Text to display": "\u10e2\u10d4\u10e5\u10e1\u10e2\u10d8", "Anchors": "\u10e6\u10e3\u10d6\u10d0", "Insert link": "\u10d1\u10db\u10e3\u10da\u10d8\u10e1 \u10e9\u10d0\u10e1\u10db\u10d0", @@ -227,4 +227,4 @@ tinymce.addI18n('ka_GE',{ "View": "\u10dc\u10d0\u10ee\u10d5\u10d0", "Table": "\u10ea\u10ee\u10e0\u10d8\u10da\u10d8", "Format": "\u10e4\u10dd\u10e0\u10db\u10d0\u10e2\u10d8" -}); \ No newline at end of file +}); diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/kab.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/kab.js index b9f9bccf40..48f7d3bf55 100644 --- a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/kab.js +++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/kab.js @@ -150,7 +150,7 @@ tinymce.addI18n('kab',{ "Insert link": "Ger azday", "Insert\/edit link": "Ger\/\u1e93reg azday", "Text to display": "A\u1e0dris ara yettwabeqq\u1e0den", -"Url": "Url", +"Url": "URL", "Target": "Target", "None": "Ulac", "New window": "Asfaylu amaynut", @@ -258,4 +258,4 @@ tinymce.addI18n('kab',{ "Tools": "Ifecka", "Powered by {0}": "Iteddu s {0} ", "Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help" -}); \ No newline at end of file +}); diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/km_KH.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/km_KH.js index 5c4b055126..381d4c4a46 100644 --- a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/km_KH.js +++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/km_KH.js @@ -144,7 +144,7 @@ tinymce.addI18n('km_KH',{ "Insert link": "\u1794\u1789\u17d2\u1785\u17bc\u179b\u200b\u178f\u17c6\u178e", "Insert\/edit link": "\u1794\u1789\u17d2\u1785\u17bc\u179b\/\u1780\u17c2 \u178f\u17c6\u178e", "Text to display": "\u17a2\u1780\u17d2\u179f\u179a\u200b\u178f\u17d2\u179a\u17bc\u179c\u200b\u1794\u1784\u17d2\u17a0\u17b6\u1789", -"Url": "Url", +"Url": "URL", "Target": "\u1791\u17b7\u179f\u178a\u17c5", "None": "\u1798\u17b7\u1793\u200b\u1798\u17b6\u1793", "New window": "\u1795\u17d2\u1791\u17b6\u17c6\u1784\u200b\u179c\u17b8\u1793\u178a\u17bc\u200b\u1790\u17d2\u1798\u17b8", @@ -250,4 +250,4 @@ tinymce.addI18n('km_KH',{ "Table": "\u178f\u17b6\u179a\u17b6\u1784", "Tools": "\u17a7\u1794\u1780\u179a\u178e\u17cd", "Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "\u1791\u17b8\u178f\u17b6\u17c6\u1784\u200b\u17a2\u1780\u17d2\u179f\u179a\u200b\u179f\u17c6\u1794\u17bc\u179a\u1794\u17c2\u1794\u17d4 \u1785\u17bb\u1785 ALT-F9 \u179f\u1798\u17d2\u179a\u17b6\u1794\u17cb\u200b\u1798\u17c9\u17ba\u1793\u17bb\u1799\u17d4 \u1785\u17bb\u1785 ALT-F10 \u179f\u1798\u17d2\u179a\u17b6\u1794\u17cb\u200b\u179a\u1794\u17b6\u179a\u200b\u17a7\u1794\u1780\u179a\u178e\u17cd\u17d4 \u1785\u17bb\u1785 ALT-0 \u179f\u1798\u17d2\u179a\u17b6\u1794\u17cb\u200b\u1787\u17c6\u1793\u17bd\u1799\u17d4" -}); \ No newline at end of file +}); diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/nb_NO.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/nb_NO.js index 59233450b0..e6f1df8971 100644 --- a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/nb_NO.js +++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/nb_NO.js @@ -150,7 +150,7 @@ tinymce.addI18n('nb_NO',{ "Insert link": "Sett inn lenke", "Insert\/edit link": "Sett inn\/endre lenke", "Text to display": "Tekst som skal vises", -"Url": "Url", +"Url": "URL", "Target": "M\u00e5l", "None": "Ingen", "New window": "Nytt vindu", @@ -258,4 +258,4 @@ tinymce.addI18n('nb_NO',{ "Tools": "Verkt\u00f8y", "Powered by {0}": "Redigert med {0}", "Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "Tekstredigering. Tast ALT-F9 for meny. Tast ALT-F10 for verkt\u00f8ys-rader. Tast ALT-0 for hjelp." -}); \ No newline at end of file +}); diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/nl.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/nl.js index c80590b297..d8631c9ab6 100644 --- a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/nl.js +++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/nl.js @@ -150,7 +150,7 @@ tinymce.addI18n('nl',{ "Insert link": "Hyperlink invoegen", "Insert\/edit link": "Hyperlink invoegen\/bewerken", "Text to display": "Linktekst", -"Url": "Url", +"Url": "URL", "Target": "Doel", "None": "Geen", "New window": "Nieuw venster", @@ -258,4 +258,4 @@ tinymce.addI18n('nl',{ "Tools": "Gereedschap", "Powered by {0}": "Gemaakt door {0}", "Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "Rich Text Area. Druk ALT-F9 voor het menu. Druk ALT-F10 voor de toolbar. Druk ALT-0 voor help." -}); \ No newline at end of file +}); diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/pt_BR.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/pt_BR.js index 497043a9d4..2beccd413b 100644 --- a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/pt_BR.js +++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/pt_BR.js @@ -150,7 +150,7 @@ tinymce.addI18n('pt_BR',{ "Insert link": "Inserir link", "Insert\/edit link": "Inserir\/editar link", "Text to display": "Texto para mostrar", -"Url": "Url", +"Url": "URL", "Target": "Alvo", "None": "Nenhum", "New window": "Nova janela", @@ -258,4 +258,4 @@ tinymce.addI18n('pt_BR',{ "Tools": "Ferramentas", "Powered by {0}": "Distribu\u00eddo por {0}", "Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "\u00c1rea de texto formatado. Pressione ALT-F9 para exibir o menu, ALT-F10 para exibir a barra de ferramentas ou ALT-0 para exibir a ajuda" -}); \ No newline at end of file +}); diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/ro.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/ro.js index 0a6a2eadf0..a2f3caec80 100644 --- a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/ro.js +++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/ro.js @@ -121,7 +121,7 @@ tinymce.addI18n('ro',{ "Date\/time": "Data\/ora", "Insert date\/time": "Insereaz\u0103 data\/ora", "Remove link": "\u0218terge link-ul", -"Url": "Url", +"Url": "URL", "Text to display": "Text de afi\u0219at", "Anchors": "Ancor\u0103", "Insert link": "Inserare link", @@ -227,4 +227,4 @@ tinymce.addI18n('ro',{ "View": "Vezi", "Table": "Tabel\u0103", "Format": "Formateaz\u0103" -}); \ No newline at end of file +}); diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/sk.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/sk.js index 2362a6dbc6..5cc085abdc 100644 --- a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/sk.js +++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/sk.js @@ -144,7 +144,7 @@ tinymce.addI18n('sk',{ "Insert link": "Vlo\u017ei\u0165 odkaz", "Insert\/edit link": "Vlo\u017ei\u0165\/upravi\u0165 odkaz", "Text to display": "Zobrazen\u00fd text", -"Url": "Url", +"Url": "URL", "Target": "Cie\u013e", "None": "\u017diadne", "New window": "Nov\u00e9 okno", @@ -250,4 +250,4 @@ tinymce.addI18n('sk',{ "Table": "Tabu\u013eka", "Tools": "N\u00e1stroje", "Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "Textov\u00e9 pole. Stla\u010dte ALT-F9 pre zobrazenie menu, ALT-F10 pre zobrazenie panela n\u00e1strojov, ALT-0 pre n\u00e1povedu." -}); \ No newline at end of file +}); diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/sr.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/sr.js index 9150c2ed82..01bfad7303 100644 --- a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/sr.js +++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/sr.js @@ -150,7 +150,7 @@ tinymce.addI18n('sr',{ "Insert link": "Ubaci vezu", "Insert\/edit link": "Ubaci\/promeni vezu", "Text to display": "Tekst za prikaz", -"Url": "Url", +"Url": "URL", "Target": "Meta", "None": "Ni\u0161ta", "New window": "Novi prozor", @@ -258,4 +258,4 @@ tinymce.addI18n('sr',{ "Tools": "Alatke", "Powered by {0}": "Pokre\u0107e ga {0}", "Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "Oboga\u0107en tekst. Pritisni te ALT-F9 za meni.Pritisnite ALT-F10 za traku sa alatkama.Pritisnite ALT-0 za pomo\u0107" -}); \ No newline at end of file +}); diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/sv_SE.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/sv_SE.js index 83aaaef59d..b97ea68a18 100644 --- a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/sv_SE.js +++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/sv_SE.js @@ -150,7 +150,7 @@ tinymce.addI18n('sv_SE',{ "Insert link": "Infoga l\u00e4nk", "Insert\/edit link": "Infoga\/redigera l\u00e4nk", "Text to display": "Text att visa", -"Url": "Url", +"Url": "URL", "Target": "M\u00e5l", "None": "Ingen", "New window": "Nytt f\u00f6nster", @@ -158,8 +158,8 @@ tinymce.addI18n('sv_SE',{ "Anchors": "Bokm\u00e4rken", "Link": "L\u00e4nk", "Paste or type a link": "Klistra in eller skriv en l\u00e4nk", -"The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?": "Urlen du angav verkar vara en epost adress. Vill du l\u00e4gga till ett mailto: prefix?", -"The URL you entered seems to be an external link. Do you want to add the required http:\/\/ prefix?": "Urlen du angav verkar vara en extern l\u00e4nk. Vill du l\u00e4gga till http:\/\/ prefixet?", +"The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?": "URLen du angav verkar vara en epost adress. Vill du l\u00e4gga till ett mailto: prefix?", +"The URL you entered seems to be an external link. Do you want to add the required http:\/\/ prefix?": "URLen du angav verkar vara en extern l\u00e4nk. Vill du l\u00e4gga till http:\/\/ prefixet?", "Link list": "L\u00e4nklista", "Insert video": "Infoga video", "Insert\/edit video": "Infoga\/redigera video", @@ -258,4 +258,4 @@ tinymce.addI18n('sv_SE',{ "Tools": "Verktyg", "Powered by {0}": "Powered by {0}", "Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "Textredigerare. Tryck ALT-F9 f\u00f6r menyn. Tryck ALT-F10 f\u00f6r verktygsrader. Tryck ALT-0 f\u00f6r hj\u00e4lp." -}); \ No newline at end of file +}); diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/tr.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/tr.js index 7b69596402..3dd22ca25f 100644 --- a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/tr.js +++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/tr.js @@ -150,7 +150,7 @@ tinymce.addI18n('tr',{ "Insert link": "Ba\u011flant\u0131 ekle", "Insert\/edit link": "Ba\u011flant\u0131 ekle\/d\u00fczenle", "Text to display": "Yaz\u0131y\u0131 g\u00f6r\u00fcnt\u00fcle", -"Url": "Url", +"Url": "URL", "Target": "Hedef", "None": "Hi\u00e7biri", "New window": "Yeni pencere", @@ -258,4 +258,4 @@ tinymce.addI18n('tr',{ "Tools": "Ara\u00e7lar", "Powered by {0}": "Powered by {0}", "Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "Zengin Metin Alan\u0131. Men\u00fc i\u00e7in ALT-F9 tu\u015funa bas\u0131n\u0131z. Ara\u00e7 \u00e7ubu\u011fu i\u00e7in ALT-F10 tu\u015funa bas\u0131n\u0131z. Yard\u0131m i\u00e7in ALT-0 tu\u015funa bas\u0131n\u0131z." -}); \ No newline at end of file +}); diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/tr_TR.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/tr_TR.js index ea89bc44c3..496fe3dc21 100644 --- a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/tr_TR.js +++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/tr_TR.js @@ -150,7 +150,7 @@ tinymce.addI18n('tr_TR',{ "Insert link": "Ba\u011flant\u0131 ekle", "Insert\/edit link": "Ba\u011flant\u0131 ekle\/d\u00fczenle", "Text to display": "G\u00f6r\u00fcnen yaz\u0131", -"Url": "Url", +"Url": "URL", "Target": "Hedef", "None": "Hi\u00e7biri", "New window": "Yeni pencere", @@ -258,4 +258,4 @@ tinymce.addI18n('tr_TR',{ "Tools": "Ara\u00e7lar", "Powered by {0}": "{0} taraf\u0131ndan yap\u0131lm\u0131\u015ft\u0131r ", "Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "Zengin Metin Alan\u0131. Men\u00fc i\u00e7in ALT-F9 k\u0131sayolunu kullan\u0131n. Ara\u00e7 \u00e7ubu\u011fu i\u00e7in ALT-F10 k\u0131sayolunu kullan\u0131n. Yard\u0131m i\u00e7in ALT-0 k\u0131sayolunu kullan\u0131n." -}); \ No newline at end of file +}); diff --git a/src/Umbraco.Web.UI.Client/lib/umbraco/Extensions.js b/src/Umbraco.Web.UI.Client/lib/umbraco/Extensions.js index 823d3d526d..54fda13a0d 100644 --- a/src/Umbraco.Web.UI.Client/lib/umbraco/Extensions.js +++ b/src/Umbraco.Web.UI.Client/lib/umbraco/Extensions.js @@ -69,6 +69,18 @@ }; } + if (!String.prototype.trimStartSpecial) { + /** trimSpecial extension method for string */ + // Removes all non printable chars from beginning of a string + String.prototype.trimStartSpecial = function () { + var index = 0; + while (this.charCodeAt(index) <= 46) { + index++; + } + return this.substr(index); + }; + } + if (!String.prototype.startsWith) { /** startsWith extension method for string */ String.prototype.startsWith = function (str) { diff --git a/src/Umbraco.Web.UI.Client/package-lock.json b/src/Umbraco.Web.UI.Client/package-lock.json new file mode 100644 index 0000000000..1b28cfb029 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/package-lock.json @@ -0,0 +1,16456 @@ +{ + "requires": true, + "lockfileVersion": 1, + "dependencies": { + "@babel/code-frame": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.5.5.tgz", + "integrity": "sha512-27d4lZoomVyo51VegxI20xZPuSHusqbQag/ztrBC7wegWoQ1nLREPVSKSW8byhTlzTKyNE4ifaTA6lCp7JjpFw==", + "dev": true, + "requires": { + "@babel/highlight": "^7.0.0" + } + }, + "@babel/core": { + "version": "7.6.4", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.6.4.tgz", + "integrity": "sha512-Rm0HGw101GY8FTzpWSyRbki/jzq+/PkNQJ+nSulrdY6gFGOsNseCqD6KHRYe2E+EdzuBdr2pxCp6s4Uk6eJ+XQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.5.5", + "@babel/generator": "^7.6.4", + "@babel/helpers": "^7.6.2", + "@babel/parser": "^7.6.4", + "@babel/template": "^7.6.0", + "@babel/traverse": "^7.6.3", + "@babel/types": "^7.6.3", + "convert-source-map": "^1.1.0", + "debug": "^4.1.0", + "json5": "^2.1.0", + "lodash": "^4.17.13", + "resolve": "^1.3.2", + "semver": "^5.4.1", + "source-map": "^0.5.0" + }, + "dependencies": { + "@babel/generator": { + "version": "7.6.4", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.6.4.tgz", + "integrity": "sha512-jsBuXkFoZxk0yWLyGI9llT9oiQ2FeTASmRFE32U+aaDTfoE92t78eroO7PTpU/OrYq38hlcDM6vbfLDaOLy+7w==", + "dev": true, + "requires": { + "@babel/types": "^7.6.3", + "jsesc": "^2.5.1", + "lodash": "^4.17.13", + "source-map": "^0.5.0" + } + }, + "@babel/parser": { + "version": "7.6.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.6.4.tgz", + "integrity": "sha512-D8RHPW5qd0Vbyo3qb+YjO5nvUVRTXFLQ/FsDxJU2Nqz4uB5EnUN0ZQSEYpvTIbRuttig1XbHWU5oMeQwQSAA+A==", + "dev": true + }, + "@babel/traverse": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.6.3.tgz", + "integrity": "sha512-unn7P4LGsijIxaAJo/wpoU11zN+2IaClkQAxcJWBNCMS6cmVh802IyLHNkAjQ0iYnRS3nnxk5O3fuXW28IMxTw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.5.5", + "@babel/generator": "^7.6.3", + "@babel/helper-function-name": "^7.1.0", + "@babel/helper-split-export-declaration": "^7.4.4", + "@babel/parser": "^7.6.3", + "@babel/types": "^7.6.3", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.13" + } + }, + "@babel/types": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.6.3.tgz", + "integrity": "sha512-CqbcpTxMcpuQTMhjI37ZHVgjBkysg5icREQIEZ0eG1yCNwg3oy+5AaLiOKmjsCj6nqOsa6Hf0ObjRVwokb7srA==", + "dev": true, + "requires": { + "esutils": "^2.0.2", + "lodash": "^4.17.13", + "to-fast-properties": "^2.0.0" + } + } + } + }, + "@babel/generator": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.6.2.tgz", + "integrity": "sha512-j8iHaIW4gGPnViaIHI7e9t/Hl8qLjERI6DcV9kEpAIDJsAOrcnXqRS7t+QbhL76pwbtqP+QCQLL0z1CyVmtjjQ==", + "dev": true, + "requires": { + "@babel/types": "^7.6.0", + "jsesc": "^2.5.1", + "lodash": "^4.17.13", + "source-map": "^0.5.0" + }, + "dependencies": { + "lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", + "dev": true + } + } + }, + "@babel/helper-annotate-as-pure": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.0.0.tgz", + "integrity": "sha1-Mj053QtQ4Qx8Bsp9djjmhk2MXDI=", + "dev": true, + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-builder-binary-assignment-operator-visitor": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.1.0.tgz", + "integrity": "sha1-a2lijf5Ah3mODE7Zjj1Kay+9L18=", + "dev": true, + "requires": { + "@babel/helper-explode-assignable-expression": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-call-delegate": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/helper-call-delegate/-/helper-call-delegate-7.4.4.tgz", + "integrity": "sha512-l79boDFJ8S1c5hvQvG+rc+wHw6IuH7YldmRKsYtpbawsxURu/paVy57FZMomGK22/JckepaikOkY0MoAmdyOlQ==", + "dev": true, + "requires": { + "@babel/helper-hoist-variables": "^7.4.4", + "@babel/traverse": "^7.4.4", + "@babel/types": "^7.4.4" + } + }, + "@babel/helper-define-map": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.5.5.tgz", + "integrity": "sha512-fTfxx7i0B5NJqvUOBBGREnrqbTxRh7zinBANpZXAVDlsZxYdclDp467G1sQ8VZYMnAURY3RpBUAgOYT9GfzHBg==", + "dev": true, + "requires": { + "@babel/helper-function-name": "^7.1.0", + "@babel/types": "^7.5.5", + "lodash": "^4.17.13" + } + }, + "@babel/helper-explode-assignable-expression": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.1.0.tgz", + "integrity": "sha1-U3+hP28WdN90WwwA7I/k6ZaByPY=", + "dev": true, + "requires": { + "@babel/traverse": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-function-name": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.1.0.tgz", + "integrity": "sha1-oM6wFoX3M1XUNgwSR/WCv6/I/1M=", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.0.0", + "@babel/template": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0.tgz", + "integrity": "sha1-g1ctQyDipGVyY3NBE8QoaLZOScM=", + "dev": true, + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-hoist-variables": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.4.4.tgz", + "integrity": "sha512-VYk2/H/BnYbZDDg39hr3t2kKyifAm1W6zHRfhx8jGjIHpQEBv9dry7oQ2f3+J703TLu69nYdxsovl0XYfcnK4w==", + "dev": true, + "requires": { + "@babel/types": "^7.4.4" + } + }, + "@babel/helper-member-expression-to-functions": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.5.5.tgz", + "integrity": "sha512-5qZ3D1uMclSNqYcXqiHoA0meVdv+xUEex9em2fqMnrk/scphGlGgg66zjMrPJESPwrFJ6sbfFQYUSa0Mz7FabA==", + "dev": true, + "requires": { + "@babel/types": "^7.5.5" + } + }, + "@babel/helper-module-imports": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.0.0.tgz", + "integrity": "sha1-lggbcRHkhtpNLNlxrRpP4hbMLj0=", + "dev": true, + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-module-transforms": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.5.5.tgz", + "integrity": "sha512-jBeCvETKuJqeiaCdyaheF40aXnnU1+wkSiUs/IQg3tB85up1LyL8x77ClY8qJpuRJUcXQo+ZtdNESmZl4j56Pw==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.0.0", + "@babel/helper-simple-access": "^7.1.0", + "@babel/helper-split-export-declaration": "^7.4.4", + "@babel/template": "^7.4.4", + "@babel/types": "^7.5.5", + "lodash": "^4.17.13" + } + }, + "@babel/helper-optimise-call-expression": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.0.0.tgz", + "integrity": "sha1-opIMVwKwc8Fd5REGIAqoytIEl9U=", + "dev": true, + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-plugin-utils": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.0.0.tgz", + "integrity": "sha1-u7P77phmHFaQNCN8wDlnupm08lA=", + "dev": true + }, + "@babel/helper-regex": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/helper-regex/-/helper-regex-7.5.5.tgz", + "integrity": "sha512-CkCYQLkfkiugbRDO8eZn6lRuR8kzZoGXCg3149iTk5se7g6qykSpy3+hELSwquhu+TgHn8nkLiBwHvNX8Hofcw==", + "dev": true, + "requires": { + "lodash": "^4.17.13" + } + }, + "@babel/helper-remap-async-to-generator": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.1.0.tgz", + "integrity": "sha1-Nh2AghtvONp1vT8HheziCojF/n8=", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.0.0", + "@babel/helper-wrap-function": "^7.1.0", + "@babel/template": "^7.1.0", + "@babel/traverse": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-replace-supers": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.5.5.tgz", + "integrity": "sha512-XvRFWrNnlsow2u7jXDuH4jDDctkxbS7gXssrP4q2nUD606ukXHRvydj346wmNg+zAgpFx4MWf4+usfC93bElJg==", + "dev": true, + "requires": { + "@babel/helper-member-expression-to-functions": "^7.5.5", + "@babel/helper-optimise-call-expression": "^7.0.0", + "@babel/traverse": "^7.5.5", + "@babel/types": "^7.5.5" + } + }, + "@babel/helper-simple-access": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.1.0.tgz", + "integrity": "sha1-Ze65VMjCRb6qToWdphiPOdceWFw=", + "dev": true, + "requires": { + "@babel/template": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.4.4.tgz", + "integrity": "sha512-Ro/XkzLf3JFITkW6b+hNxzZ1n5OQ80NvIUdmHspih1XAhtN3vPTuUFT4eQnela+2MaZ5ulH+iyP513KJrxbN7Q==", + "dev": true, + "requires": { + "@babel/types": "^7.4.4" + } + }, + "@babel/helper-wrap-function": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.2.0.tgz", + "integrity": "sha512-o9fP1BZLLSrYlxYEYyl2aS+Flun5gtjTIG8iln+XuEzQTs0PLagAGSXUcqruJwD5fM48jzIEggCKpIfWTcR7pQ==", + "dev": true, + "requires": { + "@babel/helper-function-name": "^7.1.0", + "@babel/template": "^7.1.0", + "@babel/traverse": "^7.1.0", + "@babel/types": "^7.2.0" + } + }, + "@babel/helpers": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.6.2.tgz", + "integrity": "sha512-3/bAUL8zZxYs1cdX2ilEE0WobqbCmKWr/889lf2SS0PpDcpEIY8pb1CCyz0pEcX3pEb+MCbks1jIokz2xLtGTA==", + "dev": true, + "requires": { + "@babel/template": "^7.6.0", + "@babel/traverse": "^7.6.2", + "@babel/types": "^7.6.0" + } + }, + "@babel/highlight": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.5.0.tgz", + "integrity": "sha512-7dV4eu9gBxoM0dAnj/BCFDW9LFU0zvTrkq0ugM7pnHEgguOEeOz1so2ZghEdzviYzQEED0r4EAgpsBChKy1TRQ==", + "dev": true, + "requires": { + "chalk": "^2.0.0", + "esutils": "^2.0.2", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.6.2.tgz", + "integrity": "sha512-mdFqWrSPCmikBoaBYMuBulzTIKuXVPtEISFbRRVNwMWpCms/hmE2kRq0bblUHaNRKrjRlmVbx1sDHmjmRgD2Xg==", + "dev": true + }, + "@babel/plugin-proposal-async-generator-functions": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.2.0.tgz", + "integrity": "sha512-+Dfo/SCQqrwx48ptLVGLdE39YtWRuKc/Y9I5Fy0P1DDBB9lsAHpjcEJQt+4IifuSOSTLBKJObJqMvaO1pIE8LQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-remap-async-to-generator": "^7.1.0", + "@babel/plugin-syntax-async-generators": "^7.2.0" + } + }, + "@babel/plugin-proposal-dynamic-import": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.5.0.tgz", + "integrity": "sha512-x/iMjggsKTFHYC6g11PL7Qy58IK8H5zqfm9e6hu4z1iH2IRyAp9u9dL80zA6R76yFovETFLKz2VJIC2iIPBuFw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-syntax-dynamic-import": "^7.2.0" + } + }, + "@babel/plugin-proposal-json-strings": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.2.0.tgz", + "integrity": "sha512-MAFV1CA/YVmYwZG0fBQyXhmj0BHCB5egZHCKWIFVv/XCxAeVGIHfos3SwDck4LvCllENIAg7xMKOG5kH0dzyUg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-syntax-json-strings": "^7.2.0" + } + }, + "@babel/plugin-proposal-object-rest-spread": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.6.2.tgz", + "integrity": "sha512-LDBXlmADCsMZV1Y9OQwMc0MyGZ8Ta/zlD9N67BfQT8uYwkRswiu2hU6nJKrjrt/58aH/vqfQlR/9yId/7A2gWw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-syntax-object-rest-spread": "^7.2.0" + } + }, + "@babel/plugin-proposal-optional-catch-binding": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.2.0.tgz", + "integrity": "sha512-mgYj3jCcxug6KUcX4OBoOJz3CMrwRfQELPQ5560F70YQUBZB7uac9fqaWamKR1iWUzGiK2t0ygzjTScZnVz75g==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-syntax-optional-catch-binding": "^7.2.0" + } + }, + "@babel/plugin-proposal-unicode-property-regex": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.6.2.tgz", + "integrity": "sha512-NxHETdmpeSCtiatMRYWVJo7266rrvAC3DTeG5exQBIH/fMIUK7ejDNznBbn3HQl/o9peymRRg7Yqkx6PdUXmMw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-regex": "^7.4.4", + "regexpu-core": "^4.6.0" + } + }, + "@babel/plugin-syntax-async-generators": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.2.0.tgz", + "integrity": "sha512-1ZrIRBv2t0GSlcwVoQ6VgSLpLgiN/FVQUzt9znxo7v2Ov4jJrs8RY8tv0wvDmFN3qIdMKWrmMMW6yZ0G19MfGg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-syntax-dynamic-import": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.2.0.tgz", + "integrity": "sha512-mVxuJ0YroI/h/tbFTPGZR8cv6ai+STMKNBq0f8hFxsxWjl94qqhsb+wXbpNMDPU3cfR1TIsVFzU3nXyZMqyK4w==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-syntax-json-strings": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.2.0.tgz", + "integrity": "sha512-5UGYnMSLRE1dqqZwug+1LISpA403HzlSfsg6P9VXU6TBjcSHeNlw4DxDx7LgpF+iKZoOG/+uzqoRHTdcUpiZNg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-syntax-object-rest-spread": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.2.0.tgz", + "integrity": "sha512-t0JKGgqk2We+9may3t0xDdmneaXmyxq0xieYcKHxIsrJO64n1OiMWNUtc5gQK1PA0NpdCRrtZp4z+IUaKugrSA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-syntax-optional-catch-binding": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.2.0.tgz", + "integrity": "sha512-bDe4xKNhb0LI7IvZHiA13kff0KEfaGX/Hv4lMA9+7TEc63hMNvfKo6ZFpXhKuEp+II/q35Gc4NoMeDZyaUbj9w==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-arrow-functions": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.2.0.tgz", + "integrity": "sha512-ER77Cax1+8/8jCB9fo4Ud161OZzWN5qawi4GusDuRLcDbDG+bIGYY20zb2dfAFdTRGzrfq2xZPvF0R64EHnimg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-async-to-generator": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.5.0.tgz", + "integrity": "sha512-mqvkzwIGkq0bEF1zLRRiTdjfomZJDV33AH3oQzHVGkI2VzEmXLpKKOBvEVaFZBJdN0XTyH38s9j/Kiqr68dggg==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.0.0", + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-remap-async-to-generator": "^7.1.0" + } + }, + "@babel/plugin-transform-block-scoped-functions": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.2.0.tgz", + "integrity": "sha512-ntQPR6q1/NKuphly49+QiQiTN0O63uOwjdD6dhIjSWBI5xlrbUFh720TIpzBhpnrLfv2tNH/BXvLIab1+BAI0w==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-block-scoping": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.6.3.tgz", + "integrity": "sha512-7hvrg75dubcO3ZI2rjYTzUrEuh1E9IyDEhhB6qfcooxhDA33xx2MasuLVgdxzcP6R/lipAC6n9ub9maNW6RKdw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "lodash": "^4.17.13" + } + }, + "@babel/plugin-transform-classes": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.5.5.tgz", + "integrity": "sha512-U2htCNK/6e9K7jGyJ++1p5XRU+LJjrwtoiVn9SzRlDT2KubcZ11OOwy3s24TjHxPgxNwonCYP7U2K51uVYCMDg==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.0.0", + "@babel/helper-define-map": "^7.5.5", + "@babel/helper-function-name": "^7.1.0", + "@babel/helper-optimise-call-expression": "^7.0.0", + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-replace-supers": "^7.5.5", + "@babel/helper-split-export-declaration": "^7.4.4", + "globals": "^11.1.0" + } + }, + "@babel/plugin-transform-computed-properties": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.2.0.tgz", + "integrity": "sha512-kP/drqTxY6Xt3NNpKiMomfgkNn4o7+vKxK2DDKcBG9sHj51vHqMBGy8wbDS/J4lMxnqs153/T3+DmCEAkC5cpA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-destructuring": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.6.0.tgz", + "integrity": "sha512-2bGIS5P1v4+sWTCnKNDZDxbGvEqi0ijeqM/YqHtVGrvG2y0ySgnEEhXErvE9dA0bnIzY9bIzdFK0jFA46ASIIQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-dotall-regex": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.6.2.tgz", + "integrity": "sha512-KGKT9aqKV+9YMZSkowzYoYEiHqgaDhGmPNZlZxX6UeHC4z30nC1J9IrZuGqbYFB1jaIGdv91ujpze0exiVK8bA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-regex": "^7.4.4", + "regexpu-core": "^4.6.0" + } + }, + "@babel/plugin-transform-duplicate-keys": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.5.0.tgz", + "integrity": "sha512-igcziksHizyQPlX9gfSjHkE2wmoCH3evvD2qR5w29/Dk0SMKE/eOI7f1HhBdNhR/zxJDqrgpoDTq5YSLH/XMsQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-exponentiation-operator": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.2.0.tgz", + "integrity": "sha512-umh4hR6N7mu4Elq9GG8TOu9M0bakvlsREEC+ialrQN6ABS4oDQ69qJv1VtR3uxlKMCQMCvzk7vr17RHKcjx68A==", + "dev": true, + "requires": { + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.1.0", + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-for-of": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.4.4.tgz", + "integrity": "sha512-9T/5Dlr14Z9TIEXLXkt8T1DU7F24cbhwhMNUziN3hB1AXoZcdzPcTiKGRn/6iOymDqtTKWnr/BtRKN9JwbKtdQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-function-name": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.4.4.tgz", + "integrity": "sha512-iU9pv7U+2jC9ANQkKeNF6DrPy4GBa4NWQtl6dHB4Pb3izX2JOEvDTFarlNsBj/63ZEzNNIAMs3Qw4fNCcSOXJA==", + "dev": true, + "requires": { + "@babel/helper-function-name": "^7.1.0", + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-literals": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.2.0.tgz", + "integrity": "sha512-2ThDhm4lI4oV7fVQ6pNNK+sx+c/GM5/SaML0w/r4ZB7sAneD/piDJtwdKlNckXeyGK7wlwg2E2w33C/Hh+VFCg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-member-expression-literals": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.2.0.tgz", + "integrity": "sha512-HiU3zKkSU6scTidmnFJ0bMX8hz5ixC93b4MHMiYebmk2lUVNGOboPsqQvx5LzooihijUoLR/v7Nc1rbBtnc7FA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-modules-amd": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.5.0.tgz", + "integrity": "sha512-n20UsQMKnWrltocZZm24cRURxQnWIvsABPJlw/fvoy9c6AgHZzoelAIzajDHAQrDpuKFFPPcFGd7ChsYuIUMpg==", + "dev": true, + "requires": { + "@babel/helper-module-transforms": "^7.1.0", + "@babel/helper-plugin-utils": "^7.0.0", + "babel-plugin-dynamic-import-node": "^2.3.0" + } + }, + "@babel/plugin-transform-modules-commonjs": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.6.0.tgz", + "integrity": "sha512-Ma93Ix95PNSEngqomy5LSBMAQvYKVe3dy+JlVJSHEXZR5ASL9lQBedMiCyVtmTLraIDVRE3ZjTZvmXXD2Ozw3g==", + "dev": true, + "requires": { + "@babel/helper-module-transforms": "^7.4.4", + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-simple-access": "^7.1.0", + "babel-plugin-dynamic-import-node": "^2.3.0" + } + }, + "@babel/plugin-transform-modules-systemjs": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.5.0.tgz", + "integrity": "sha512-Q2m56tyoQWmuNGxEtUyeEkm6qJYFqs4c+XyXH5RAuYxObRNz9Zgj/1g2GMnjYp2EUyEy7YTrxliGCXzecl/vJg==", + "dev": true, + "requires": { + "@babel/helper-hoist-variables": "^7.4.4", + "@babel/helper-plugin-utils": "^7.0.0", + "babel-plugin-dynamic-import-node": "^2.3.0" + } + }, + "@babel/plugin-transform-modules-umd": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.2.0.tgz", + "integrity": "sha512-BV3bw6MyUH1iIsGhXlOK6sXhmSarZjtJ/vMiD9dNmpY8QXFFQTj+6v92pcfy1iqa8DeAfJFwoxcrS/TUZda6sw==", + "dev": true, + "requires": { + "@babel/helper-module-transforms": "^7.1.0", + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.6.3.tgz", + "integrity": "sha512-jTkk7/uE6H2s5w6VlMHeWuH+Pcy2lmdwFoeWCVnvIrDUnB5gQqTVI8WfmEAhF2CDEarGrknZcmSFg1+bkfCoSw==", + "dev": true, + "requires": { + "regexpu-core": "^4.6.0" + } + }, + "@babel/plugin-transform-new-target": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.4.4.tgz", + "integrity": "sha512-r1z3T2DNGQwwe2vPGZMBNjioT2scgWzK9BCnDEh+46z8EEwXBq24uRzd65I7pjtugzPSj921aM15RpESgzsSuA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-object-super": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.5.5.tgz", + "integrity": "sha512-un1zJQAhSosGFBduPgN/YFNvWVpRuHKU7IHBglLoLZsGmruJPOo6pbInneflUdmq7YvSVqhpPs5zdBvLnteltQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-replace-supers": "^7.5.5" + } + }, + "@babel/plugin-transform-parameters": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.4.4.tgz", + "integrity": "sha512-oMh5DUO1V63nZcu/ZVLQFqiihBGo4OpxJxR1otF50GMeCLiRx5nUdtokd+u9SuVJrvvuIh9OosRFPP4pIPnwmw==", + "dev": true, + "requires": { + "@babel/helper-call-delegate": "^7.4.4", + "@babel/helper-get-function-arity": "^7.0.0", + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-property-literals": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.2.0.tgz", + "integrity": "sha512-9q7Dbk4RhgcLp8ebduOpCbtjh7C0itoLYHXd9ueASKAG/is5PQtMR5VJGka9NKqGhYEGn5ITahd4h9QeBMylWQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-regenerator": { + "version": "7.4.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.4.5.tgz", + "integrity": "sha512-gBKRh5qAaCWntnd09S8QC7r3auLCqq5DI6O0DlfoyDjslSBVqBibrMdsqO+Uhmx3+BlOmE/Kw1HFxmGbv0N9dA==", + "dev": true, + "requires": { + "regenerator-transform": "^0.14.0" + } + }, + "@babel/plugin-transform-reserved-words": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.2.0.tgz", + "integrity": "sha512-fz43fqW8E1tAB3DKF19/vxbpib1fuyCwSPE418ge5ZxILnBhWyhtPgz8eh1RCGGJlwvksHkyxMxh0eenFi+kFw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-shorthand-properties": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.2.0.tgz", + "integrity": "sha512-QP4eUM83ha9zmYtpbnyjTLAGKQritA5XW/iG9cjtuOI8s1RuL/3V6a3DeSHfKutJQ+ayUfeZJPcnCYEQzaPQqg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-spread": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.6.2.tgz", + "integrity": "sha512-DpSvPFryKdK1x+EDJYCy28nmAaIMdxmhot62jAXF/o99iA33Zj2Lmcp3vDmz+MUh0LNYVPvfj5iC3feb3/+PFg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-sticky-regex": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.2.0.tgz", + "integrity": "sha512-KKYCoGaRAf+ckH8gEL3JHUaFVyNHKe3ASNsZ+AlktgHevvxGigoIttrEJb8iKN03Q7Eazlv1s6cx2B2cQ3Jabw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-regex": "^7.0.0" + } + }, + "@babel/plugin-transform-template-literals": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.4.4.tgz", + "integrity": "sha512-mQrEC4TWkhLN0z8ygIvEL9ZEToPhG5K7KDW3pzGqOfIGZ28Jb0POUkeWcoz8HnHvhFy6dwAT1j8OzqN8s804+g==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.0.0", + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-typeof-symbol": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.2.0.tgz", + "integrity": "sha512-2LNhETWYxiYysBtrBTqL8+La0jIoQQnIScUJc74OYvUGRmkskNY4EzLCnjHBzdmb38wqtTaixpo1NctEcvMDZw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-unicode-regex": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.6.2.tgz", + "integrity": "sha512-orZI6cWlR3nk2YmYdb0gImrgCUwb5cBUwjf6Ks6dvNVvXERkwtJWOQaEOjPiu0Gu1Tq6Yq/hruCZZOOi9F34Dw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-regex": "^7.4.4", + "regexpu-core": "^4.6.0" + } + }, + "@babel/preset-env": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.6.3.tgz", + "integrity": "sha512-CWQkn7EVnwzlOdR5NOm2+pfgSNEZmvGjOhlCHBDq0J8/EStr+G+FvPEiz9B56dR6MoiUFjXhfE4hjLoAKKJtIQ==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.0.0", + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-proposal-async-generator-functions": "^7.2.0", + "@babel/plugin-proposal-dynamic-import": "^7.5.0", + "@babel/plugin-proposal-json-strings": "^7.2.0", + "@babel/plugin-proposal-object-rest-spread": "^7.6.2", + "@babel/plugin-proposal-optional-catch-binding": "^7.2.0", + "@babel/plugin-proposal-unicode-property-regex": "^7.6.2", + "@babel/plugin-syntax-async-generators": "^7.2.0", + "@babel/plugin-syntax-dynamic-import": "^7.2.0", + "@babel/plugin-syntax-json-strings": "^7.2.0", + "@babel/plugin-syntax-object-rest-spread": "^7.2.0", + "@babel/plugin-syntax-optional-catch-binding": "^7.2.0", + "@babel/plugin-transform-arrow-functions": "^7.2.0", + "@babel/plugin-transform-async-to-generator": "^7.5.0", + "@babel/plugin-transform-block-scoped-functions": "^7.2.0", + "@babel/plugin-transform-block-scoping": "^7.6.3", + "@babel/plugin-transform-classes": "^7.5.5", + "@babel/plugin-transform-computed-properties": "^7.2.0", + "@babel/plugin-transform-destructuring": "^7.6.0", + "@babel/plugin-transform-dotall-regex": "^7.6.2", + "@babel/plugin-transform-duplicate-keys": "^7.5.0", + "@babel/plugin-transform-exponentiation-operator": "^7.2.0", + "@babel/plugin-transform-for-of": "^7.4.4", + "@babel/plugin-transform-function-name": "^7.4.4", + "@babel/plugin-transform-literals": "^7.2.0", + "@babel/plugin-transform-member-expression-literals": "^7.2.0", + "@babel/plugin-transform-modules-amd": "^7.5.0", + "@babel/plugin-transform-modules-commonjs": "^7.6.0", + "@babel/plugin-transform-modules-systemjs": "^7.5.0", + "@babel/plugin-transform-modules-umd": "^7.2.0", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.6.3", + "@babel/plugin-transform-new-target": "^7.4.4", + "@babel/plugin-transform-object-super": "^7.5.5", + "@babel/plugin-transform-parameters": "^7.4.4", + "@babel/plugin-transform-property-literals": "^7.2.0", + "@babel/plugin-transform-regenerator": "^7.4.5", + "@babel/plugin-transform-reserved-words": "^7.2.0", + "@babel/plugin-transform-shorthand-properties": "^7.2.0", + "@babel/plugin-transform-spread": "^7.6.2", + "@babel/plugin-transform-sticky-regex": "^7.2.0", + "@babel/plugin-transform-template-literals": "^7.4.4", + "@babel/plugin-transform-typeof-symbol": "^7.2.0", + "@babel/plugin-transform-unicode-regex": "^7.6.2", + "@babel/types": "^7.6.3", + "browserslist": "^4.6.0", + "core-js-compat": "^3.1.1", + "invariant": "^2.2.2", + "js-levenshtein": "^1.1.3", + "semver": "^5.5.0" + }, + "dependencies": { + "@babel/types": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.6.3.tgz", + "integrity": "sha512-CqbcpTxMcpuQTMhjI37ZHVgjBkysg5icREQIEZ0eG1yCNwg3oy+5AaLiOKmjsCj6nqOsa6Hf0ObjRVwokb7srA==", + "dev": true, + "requires": { + "esutils": "^2.0.2", + "lodash": "^4.17.13", + "to-fast-properties": "^2.0.0" + } + } + } + }, + "@babel/template": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.6.0.tgz", + "integrity": "sha512-5AEH2EXD8euCk446b7edmgFdub/qfH1SN6Nii3+fyXP807QRx9Q73A2N5hNwRRslC2H9sNzaFhsPubkS4L8oNQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/parser": "^7.6.0", + "@babel/types": "^7.6.0" + } + }, + "@babel/traverse": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.6.2.tgz", + "integrity": "sha512-8fRE76xNwNttVEF2TwxJDGBLWthUkHWSldmfuBzVRmEDWOtu4XdINTgN7TDWzuLg4bbeIMLvfMFD9we5YcWkRQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.5.5", + "@babel/generator": "^7.6.2", + "@babel/helper-function-name": "^7.1.0", + "@babel/helper-split-export-declaration": "^7.4.4", + "@babel/parser": "^7.6.2", + "@babel/types": "^7.6.0", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.13" + }, + "dependencies": { + "lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", + "dev": true + } + } + }, + "@babel/types": { + "version": "7.6.1", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.6.1.tgz", + "integrity": "sha512-X7gdiuaCmA0uRjCmRtYJNAVCc/q+5xSgsfKJHqMN4iNLILX39677fJE1O40arPMh0TTtS9ItH67yre6c7k6t0g==", + "dev": true, + "requires": { + "esutils": "^2.0.2", + "lodash": "^4.17.13", + "to-fast-properties": "^2.0.0" + }, + "dependencies": { + "lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", + "dev": true + } + } + }, + "@gulp-sourcemaps/identity-map": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@gulp-sourcemaps/identity-map/-/identity-map-1.0.2.tgz", + "integrity": "sha512-ciiioYMLdo16ShmfHBXJBOFm3xPC4AuwO4xeRpFeHz7WK9PYsWCmigagG2XyzZpubK4a3qNKoUBDhbzHfa50LQ==", + "dev": true, + "requires": { + "acorn": "^5.0.3", + "css": "^2.2.1", + "normalize-path": "^2.1.1", + "source-map": "^0.6.0", + "through2": "^2.0.3" + }, + "dependencies": { + "acorn": { + "version": "5.7.4", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.4.tgz", + "integrity": "sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "@gulp-sourcemaps/map-sources": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@gulp-sourcemaps/map-sources/-/map-sources-1.0.0.tgz", + "integrity": "sha1-iQrnxdjId/bThIYCFazp1+yUW9o=", + "dev": true, + "requires": { + "normalize-path": "^2.0.1", + "through2": "^2.0.3" + } + }, + "@nodelib/fs.scandir": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz", + "integrity": "sha512-eGmwYQn3gxo4r7jdQnkrrN6bY478C3P+a/y72IJukF8LjB6ZHeB3c+Ehacj3sYeSmUXGlnA67/PmbM9CVwL7Dw==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "2.0.3", + "run-parallel": "^1.1.9" + } + }, + "@nodelib/fs.stat": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.3.tgz", + "integrity": "sha512-bQBFruR2TAwoevBEd/NWMoAAtNGzTRgdrqnYCc7dhzfoNvqPzLyqlEQnzZ3kVnNrSp25iyxE00/3h2fqGAGArA==", + "dev": true + }, + "@nodelib/fs.walk": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.4.tgz", + "integrity": "sha512-1V9XOY4rDW0rehzbrcqAmHnz8e7SKvX27gh8Gt2WgB0+pdzdiLV83p72kZPU+jvMbS1qU5mauP2iOvO8rhmurQ==", + "dev": true, + "requires": { + "@nodelib/fs.scandir": "2.1.3", + "fastq": "^1.6.0" + } + }, + "@sindresorhus/is": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.7.0.tgz", + "integrity": "sha512-ONhaKPIufzzrlNbqtWFFd+jlnemX6lJAgq9ZeiZtS7I1PIf/la7CW4m83rTXRnVnsMbW2k56pGYu7AUFJD9Pow==", + "dev": true, + "optional": true + }, + "@types/angular": { + "version": "1.6.56", + "resolved": "https://registry.npmjs.org/@types/angular/-/angular-1.6.56.tgz", + "integrity": "sha512-HxtqilvklZ7i6XOaiP7uIJIrFXEVEhfbSY45nfv2DeBRngncI58Y4ZOUMiUkcT8sqgLL1ablmbfylChUg7A3GA==" + }, + "@types/events": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz", + "integrity": "sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==", + "dev": true + }, + "@types/glob": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.1.tgz", + "integrity": "sha512-1Bh06cbWJUHMC97acuD6UMG29nMt0Aqz1vF3guLfG+kHHJhy3AyohZFFxYk2f7Q1SQIrNwvncxAE0N/9s70F2w==", + "dev": true, + "requires": { + "@types/events": "*", + "@types/minimatch": "*", + "@types/node": "*" + } + }, + "@types/minimatch": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", + "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==", + "dev": true + }, + "@types/node": { + "version": "12.11.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.11.2.tgz", + "integrity": "sha512-dsfE4BHJkLQW+reOS6b17xhZ/6FB1rB8eRRvO08nn5o+voxf3i74tuyFWNH6djdfgX7Sm5s6LD8t6mJug4dpDw==", + "dev": true + }, + "@types/q": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.2.tgz", + "integrity": "sha512-ce5d3q03Ex0sy4R14722Rmt6MT07Ua+k4FwDfdcToYJcMKNtRVQvJ6JCAPdAmAnbRb6CsX6aYb9m96NGod9uTw==", + "dev": true + }, + "abab": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.5.tgz", + "integrity": "sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q==", + "dev": true + }, + "accepts": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", + "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "dev": true, + "requires": { + "mime-types": "~2.1.24", + "negotiator": "0.6.2" + } + }, + "accord": { + "version": "0.29.0", + "resolved": "https://registry.npmjs.org/accord/-/accord-0.29.0.tgz", + "integrity": "sha1-t0HBdtAENcWSnUZt/oz2vukzseQ=", + "dev": true, + "requires": { + "convert-source-map": "^1.5.0", + "glob": "^7.0.5", + "indx": "^0.2.3", + "lodash.clone": "^4.3.2", + "lodash.defaults": "^4.0.1", + "lodash.flatten": "^4.2.0", + "lodash.merge": "^4.4.0", + "lodash.partialright": "^4.1.4", + "lodash.pick": "^4.2.1", + "lodash.uniq": "^4.3.0", + "resolve": "^1.5.0", + "semver": "^5.3.0", + "uglify-js": "^2.8.22", + "when": "^3.7.8" + }, + "dependencies": { + "glob": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", + "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha1-UWbihkV/AzBgZL5Ul+jbsMPTIIM=", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + } + } + }, + "ace-builds": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/ace-builds/-/ace-builds-1.4.2.tgz", + "integrity": "sha1-avwuQ6e17/3ETYQHQ2EShSVo6A0=" + }, + "acorn": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.0.tgz", + "integrity": "sha512-kL5CuoXA/dgxlBbVrflsflzQ3PAas7RYZB52NOm/6839iVYJgKMJ3cQJD+t2i5+qFa8h3MDpEOJiS64E8JLnSQ==", + "dev": true + }, + "acorn-globals": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz", + "integrity": "sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==", + "dev": true, + "requires": { + "acorn": "^7.1.1", + "acorn-walk": "^7.1.1" + }, + "dependencies": { + "acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true + } + } + }, + "acorn-jsx": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.1.0.tgz", + "integrity": "sha512-tMUqwBWfLFbJbizRmEcWSLw6HnFzfdJs2sOJEOwwtVPMoH/0Ay+E703oZz78VSXZiiDcZrQ5XKjPIUQixhmgVw==", + "dev": true + }, + "acorn-walk": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", + "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", + "dev": true + }, + "after": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz", + "integrity": "sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=", + "dev": true + }, + "ajv": { + "version": "6.10.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz", + "integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==", + "dev": true, + "requires": { + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "align-text": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", + "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", + "dev": true, + "requires": { + "kind-of": "^3.0.2", + "longest": "^1.0.1", + "repeat-string": "^1.5.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "alphanum-sort": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/alphanum-sort/-/alphanum-sort-1.0.2.tgz", + "integrity": "sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM=", + "dev": true + }, + "amdefine": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", + "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", + "dev": true + }, + "angular": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/angular/-/angular-1.8.0.tgz", + "integrity": "sha512-VdaMx+Qk0Skla7B5gw77a8hzlcOakwF8mjlW13DpIWIDlfqwAbSSLfd8N/qZnzEmQF4jC4iofInd3gE7vL8ZZg==" + }, + "angular-animate": { + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/angular-animate/-/angular-animate-1.7.5.tgz", + "integrity": "sha1-H/xsKpze4ieiunnMbNj3HsRNtdw=" + }, + "angular-aria": { + "version": "1.7.9", + "resolved": "https://registry.npmjs.org/angular-aria/-/angular-aria-1.7.9.tgz", + "integrity": "sha512-luI3Jemd1AbOQW0krdzfEG3fM0IFtLY0bSSqIDEx3POE0XjKIC1MkrO8Csyq9PPgueLphyAPofzUwZ8YeZ88SA==" + }, + "angular-chart.js": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/angular-chart.js/-/angular-chart.js-1.1.1.tgz", + "integrity": "sha1-SfDhjQgXYrbUyXkeSHr/L7sw9a4=", + "requires": { + "angular": "1.x", + "chart.js": "2.3.x" + }, + "dependencies": { + "chart.js": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-2.3.0.tgz", + "integrity": "sha1-QEYOSOLEF8BfwzJc2E97AA3H19Y=", + "requires": { + "chartjs-color": "^2.0.0", + "moment": "^2.10.6" + } + } + } + }, + "angular-cookies": { + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/angular-cookies/-/angular-cookies-1.7.5.tgz", + "integrity": "sha1-HFqzwFzcQ/F3e+lQbmRYfLNUNjQ=" + }, + "angular-dynamic-locale": { + "version": "0.1.37", + "resolved": "https://registry.npmjs.org/angular-dynamic-locale/-/angular-dynamic-locale-0.1.37.tgz", + "integrity": "sha1-fon70uxFvdaryJ82zaiJODjkk1Q=", + "requires": { + "@types/angular": "^1.6.25" + } + }, + "angular-i18n": { + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/angular-i18n/-/angular-i18n-1.7.5.tgz", + "integrity": "sha1-Lie2Thl3qMa2sFHFHQF1xtTcglI=" + }, + "angular-local-storage": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/angular-local-storage/-/angular-local-storage-0.7.1.tgz", + "integrity": "sha1-+9JzB2PCn6mvVyXgGGx4BiHozdI=" + }, + "angular-messages": { + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/angular-messages/-/angular-messages-1.7.5.tgz", + "integrity": "sha1-fC/XgTFaQ6GYOLEX2gFCqYhFThQ=" + }, + "angular-mocks": { + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/angular-mocks/-/angular-mocks-1.7.5.tgz", + "integrity": "sha1-yLq6WgbtYLk0aXAmtJIWliavOEs=" + }, + "angular-route": { + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/angular-route/-/angular-route-1.7.5.tgz", + "integrity": "sha1-NKNkjEB6FKAw0HXPSFMY4zuiPw4=" + }, + "angular-sanitize": { + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/angular-sanitize/-/angular-sanitize-1.7.5.tgz", + "integrity": "sha1-ddSeFQccqccFgedtIJQPJjcuJNI=" + }, + "angular-touch": { + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/angular-touch/-/angular-touch-1.7.5.tgz", + "integrity": "sha1-7SYyKmhfApmyPLauqYNMEZQk2kY=" + }, + "angular-ui-sortable": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/angular-ui-sortable/-/angular-ui-sortable-0.19.0.tgz", + "integrity": "sha1-SsQ5H8TU3lcRDbS10xp8GY0xT9A=", + "requires": { + "angular": ">=1.2.x", + "jquery": ">=3.1.x", + "jquery-ui-dist": ">=1.12.x" + } + }, + "animejs": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/animejs/-/animejs-2.2.0.tgz", + "integrity": "sha1-Ne79/FNbgZScnLBvCz5gwC5v3IA=" + }, + "ansi-colors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz", + "integrity": "sha1-Y3S03V1HGP884npnGjscrQdxMqk=", + "dev": true, + "requires": { + "ansi-wrap": "^0.1.0" + } + }, + "ansi-cyan": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-cyan/-/ansi-cyan-0.1.1.tgz", + "integrity": "sha1-U4rlKK+JgvKK4w2G8vF0VtJgmHM=", + "dev": true, + "requires": { + "ansi-wrap": "0.1.0" + } + }, + "ansi-escapes": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", + "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", + "dev": true + }, + "ansi-gray": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-gray/-/ansi-gray-0.1.1.tgz", + "integrity": "sha1-KWLPVOyXksSFEKPetSRDaGHvclE=", + "dev": true, + "requires": { + "ansi-wrap": "0.1.0" + } + }, + "ansi-red": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-red/-/ansi-red-0.1.1.tgz", + "integrity": "sha1-jGOPnRCAgAo1PJwoyKgcpHBdlGw=", + "dev": true, + "requires": { + "ansi-wrap": "0.1.0" + } + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha1-QfuyAkPlCxK+DwS43tvwdSDOhB0=", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + }, + "dependencies": { + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + } + } + }, + "ansi-wrap": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/ansi-wrap/-/ansi-wrap-0.1.0.tgz", + "integrity": "sha1-qCJQ3bABXponyoLoLqYDu/pF768=", + "dev": true + }, + "anymatch": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-1.3.2.tgz", + "integrity": "sha1-VT3Lj5HjyImEXf26NMd3IbkLnXo=", + "dev": true, + "requires": { + "micromatch": "^2.1.5", + "normalize-path": "^2.0.0" + }, + "dependencies": { + "arr-diff": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", + "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", + "dev": true, + "requires": { + "arr-flatten": "^1.0.1" + } + }, + "array-unique": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", + "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=", + "dev": true + }, + "braces": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", + "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", + "dev": true, + "requires": { + "expand-range": "^1.8.1", + "preserve": "^0.2.0", + "repeat-element": "^1.1.2" + } + }, + "expand-brackets": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", + "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", + "dev": true, + "requires": { + "is-posix-bracket": "^0.1.0" + } + }, + "extglob": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", + "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", + "dev": true, + "requires": { + "is-extglob": "^1.0.0" + } + }, + "is-extglob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", + "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", + "dev": true + }, + "is-glob": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", + "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", + "dev": true, + "requires": { + "is-extglob": "^1.0.0" + } + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + }, + "micromatch": { + "version": "2.3.11", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", + "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", + "dev": true, + "requires": { + "arr-diff": "^2.0.0", + "array-unique": "^0.2.1", + "braces": "^1.8.2", + "expand-brackets": "^0.1.4", + "extglob": "^0.3.1", + "filename-regex": "^2.0.0", + "is-extglob": "^1.0.0", + "is-glob": "^2.0.1", + "kind-of": "^3.0.2", + "normalize-path": "^2.0.1", + "object.omit": "^2.0.0", + "parse-glob": "^3.0.4", + "regex-cache": "^0.4.2" + } + } + } + }, + "append-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/append-buffer/-/append-buffer-1.0.2.tgz", + "integrity": "sha1-2CIM9GYIFSXv6lBhTz3mUU36WPE=", + "dev": true, + "requires": { + "buffer-equal": "^1.0.0" + } + }, + "arch": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/arch/-/arch-2.1.1.tgz", + "integrity": "sha512-BLM56aPo9vLLFVa8+/+pJLnrZ7QGGTVHWsCwieAWT9o9K8UeGaQbzZbGoabWLOo2ksBCztoXdqBZBplqLDDCSg==", + "dev": true, + "optional": true + }, + "archive-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/archive-type/-/archive-type-4.0.0.tgz", + "integrity": "sha1-+S5yIzBW38aWlHJ0nCZ72wRrHXA=", + "dev": true, + "optional": true, + "requires": { + "file-type": "^4.2.0" + }, + "dependencies": { + "file-type": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-4.4.0.tgz", + "integrity": "sha1-G2AOX8ofvcboDApwxxyNul95BsU=", + "dev": true, + "optional": true + } + } + }, + "archy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", + "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=", + "dev": true + }, + "argh": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/argh/-/argh-0.1.4.tgz", + "integrity": "sha1-PrTWEpc/xrbcbvM49W91nyrFw6Y=", + "dev": true + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha1-vNZ5HqWuCXJeF+WtmIE0zUCz2RE=", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "dev": true + }, + "arr-filter": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/arr-filter/-/arr-filter-1.1.2.tgz", + "integrity": "sha1-Q/3d0JHo7xGqTEXZzcGOLf8XEe4=", + "dev": true, + "requires": { + "make-iterator": "^1.0.0" + } + }, + "arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha1-NgSLv/TntH4TZkQxbJlmnqWukfE=", + "dev": true + }, + "arr-map": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/arr-map/-/arr-map-2.0.2.tgz", + "integrity": "sha1-Onc0X/wc814qkYJWAfnljy4kysQ=", + "dev": true, + "requires": { + "make-iterator": "^1.0.0" + } + }, + "arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", + "dev": true + }, + "array-differ": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-1.0.0.tgz", + "integrity": "sha1-7/UuN1gknTO+QCuLuOVkuytdQDE=", + "dev": true + }, + "array-each": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-each/-/array-each-1.0.1.tgz", + "integrity": "sha1-p5SvDAWrF1KEbudTofIRoFugxE8=", + "dev": true + }, + "array-find-index": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", + "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=", + "dev": true, + "optional": true + }, + "array-initial": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/array-initial/-/array-initial-1.1.0.tgz", + "integrity": "sha1-L6dLJnOTccOUe9enrcc74zSz15U=", + "dev": true, + "requires": { + "array-slice": "^1.0.0", + "is-number": "^4.0.0" + }, + "dependencies": { + "is-number": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", + "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", + "dev": true + } + } + }, + "array-last": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/array-last/-/array-last-1.3.0.tgz", + "integrity": "sha512-eOCut5rXlI6aCOS7Z7kCplKRKyiFQ6dHFBem4PwlwKeNFk2/XxTrhRh5T9PyaEWGy/NHTZWbY+nsZlNFJu9rYg==", + "dev": true, + "requires": { + "is-number": "^4.0.0" + }, + "dependencies": { + "is-number": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", + "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", + "dev": true + } + } + }, + "array-slice": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-1.1.0.tgz", + "integrity": "sha1-42jqFfibxwaff/uJrsOmx9SsItQ=", + "dev": true + }, + "array-sort": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-sort/-/array-sort-1.0.0.tgz", + "integrity": "sha1-5MBTVkU/VvU1EqfR1hI/LFTAqIo=", + "dev": true, + "requires": { + "default-compare": "^1.0.0", + "get-value": "^2.0.6", + "kind-of": "^5.0.2" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha1-cpyR4thXt6QZofmqZWhcTDP1hF0=", + "dev": true + } + } + }, + "array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true + }, + "array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", + "dev": true + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "dev": true + }, + "arraybuffer.slice": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz", + "integrity": "sha1-O7xCdd1YTMGxCAm4nU6LY6aednU=", + "dev": true + }, + "asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=", + "dev": true, + "optional": true + }, + "asn1": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha1-jSR136tVO7M+d7VOWeiAu4ziMTY=", + "dev": true, + "requires": { + "safer-buffer": "~2.1.0" + } + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true + }, + "assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", + "dev": true + }, + "astral-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", + "integrity": "sha1-bIw/uCfdQ+45GPJ7gngqt2WKb9k=", + "dev": true + }, + "async": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", + "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", + "dev": true, + "requires": { + "lodash": "^4.17.14" + }, + "dependencies": { + "lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", + "dev": true + } + } + }, + "async-done": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/async-done/-/async-done-1.3.2.tgz", + "integrity": "sha512-uYkTP8dw2og1tu1nmza1n1CMW0qb8gWWlwqMmLb7MhBVs4BXrFziT6HXUd+/RlRA/i4H9AkofYloUbs1fwMqlw==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.2", + "process-nextick-args": "^2.0.0", + "stream-exhaust": "^1.0.1" + } + }, + "async-each": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz", + "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==", + "dev": true + }, + "async-limiter": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", + "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==", + "dev": true + }, + "async-settle": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-settle/-/async-settle-1.0.0.tgz", + "integrity": "sha1-HQqRS7Aldb7IqPOnTlCA9yssDGs=", + "dev": true, + "requires": { + "async-done": "^1.2.2" + } + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "dev": true + }, + "atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha1-bZUX654DDSQ2ZmZR6GvZ9vE1M8k=", + "dev": true + }, + "autoprefixer": { + "version": "9.6.5", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.6.5.tgz", + "integrity": "sha512-rGd50YV8LgwFQ2WQp4XzOTG69u1qQsXn0amww7tjqV5jJuNazgFKYEVItEBngyyvVITKOg20zr2V+9VsrXJQ2g==", + "dev": true, + "requires": { + "browserslist": "^4.7.0", + "caniuse-lite": "^1.0.30000999", + "chalk": "^2.4.2", + "normalize-range": "^0.1.2", + "num2fraction": "^1.2.2", + "postcss": "^7.0.18", + "postcss-value-parser": "^4.0.2" + }, + "dependencies": { + "postcss-value-parser": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.0.2.tgz", + "integrity": "sha512-LmeoohTpp/K4UiyQCwuGWlONxXamGzCMtFxLq4W1nZVGIQLYvMCJx3yAF9qyyuFpflABI9yVdtJAqbihOsCsJQ==", + "dev": true + } + } + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", + "dev": true + }, + "aws4": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", + "integrity": "sha1-8OAD2cqef1nHpQiUXXsu+aBKVC8=", + "dev": true + }, + "babel-plugin-dynamic-import-node": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.0.tgz", + "integrity": "sha512-o6qFkpeQEBxcqt0XYlWzAVxNCSCZdUgcR8IRlhD/8DylxjjO4foPcvTW0GGKa/cVt3rvxZ7o5ippJ+/0nvLhlQ==", + "dev": true, + "requires": { + "object.assign": "^4.1.0" + } + }, + "bach": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/bach/-/bach-1.2.0.tgz", + "integrity": "sha1-Szzpa/JxNPeaG0FKUcFONMO9mIA=", + "dev": true, + "requires": { + "arr-filter": "^1.1.1", + "arr-flatten": "^1.0.1", + "arr-map": "^2.0.0", + "array-each": "^1.0.0", + "array-initial": "^1.0.0", + "array-last": "^1.1.1", + "async-done": "^1.2.2", + "async-settle": "^1.0.0", + "now-and-later": "^2.0.0" + } + }, + "backo2": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", + "integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc=", + "dev": true + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "base": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", + "integrity": "sha1-e95c7RRbbVUakNuH+DxVi060io8=", + "dev": true, + "requires": { + "cache-base": "^1.0.1", + "class-utils": "^0.3.5", + "component-emitter": "^1.2.1", + "define-property": "^1.0.0", + "isobject": "^3.0.1", + "mixin-deep": "^1.2.0", + "pascalcase": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha1-FpwvbT3x+ZJhgHI2XJsOofaHhlY=", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha1-2Eh2Mh0Oet0DmQQGq7u9NrqSaMc=", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha1-OxWXRqZmBLBPjIFSS6NlxfFNhuw=", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "base64-arraybuffer": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz", + "integrity": "sha1-c5JncZI7Whl0etZmqlzUv5xunOg=", + "dev": true + }, + "base64-js": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", + "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==", + "dev": true, + "optional": true + }, + "base64id": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-1.0.0.tgz", + "integrity": "sha1-R2iMuZu2gE8OBtPnY7HDLlfY5rY=", + "dev": true + }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "dev": true, + "requires": { + "tweetnacl": "^0.14.3" + } + }, + "beeper": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/beeper/-/beeper-1.1.1.tgz", + "integrity": "sha1-5tXqjF2tABMEpwsiY4RH9pyy+Ak=", + "dev": true + }, + "better-assert": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/better-assert/-/better-assert-1.0.2.tgz", + "integrity": "sha1-QIZrnhueC1W0gYlDEeaPr/rrxSI=", + "dev": true, + "requires": { + "callsite": "1.0.0" + } + }, + "bin-build": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bin-build/-/bin-build-3.0.0.tgz", + "integrity": "sha512-jcUOof71/TNAI2uM5uoUaDq2ePcVBQ3R/qhxAz1rX7UfvduAL/RXD3jXzvn8cVcDJdGVkiR1shal3OH0ImpuhA==", + "dev": true, + "optional": true, + "requires": { + "decompress": "^4.0.0", + "download": "^6.2.2", + "execa": "^0.7.0", + "p-map-series": "^1.0.0", + "tempfile": "^2.0.0" + } + }, + "bin-check": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bin-check/-/bin-check-4.1.0.tgz", + "integrity": "sha512-b6weQyEUKsDGFlACWSIOfveEnImkJyK/FGW6FAG42loyoquvjdtOIqO6yBFzHyqyVVhNgNkQxxx09SFLK28YnA==", + "dev": true, + "optional": true, + "requires": { + "execa": "^0.7.0", + "executable": "^4.1.0" + } + }, + "bin-version": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bin-version/-/bin-version-3.1.0.tgz", + "integrity": "sha512-Mkfm4iE1VFt4xd4vH+gx+0/71esbfus2LsnCGe8Pi4mndSPyT+NGES/Eg99jx8/lUGWfu3z2yuB/bt5UB+iVbQ==", + "dev": true, + "optional": true, + "requires": { + "execa": "^1.0.0", + "find-versions": "^3.0.0" + }, + "dependencies": { + "execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "dev": true, + "optional": true, + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "optional": true, + "requires": { + "pump": "^3.0.0" + } + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "optional": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + } + } + }, + "bin-version-check": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/bin-version-check/-/bin-version-check-4.0.0.tgz", + "integrity": "sha512-sR631OrhC+1f8Cvs8WyVWOA33Y8tgwjETNPyyD/myRBXLkfS/vl74FmH/lFcRl9KY3zwGh7jFhvyk9vV3/3ilQ==", + "dev": true, + "optional": true, + "requires": { + "bin-version": "^3.0.0", + "semver": "^5.6.0", + "semver-truncate": "^1.1.2" + } + }, + "bin-wrapper": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bin-wrapper/-/bin-wrapper-4.1.0.tgz", + "integrity": "sha512-hfRmo7hWIXPkbpi0ZltboCMVrU+0ClXR/JgbCKKjlDjQf6igXa7OwdqNcFWQZPZTgiY7ZpzE3+LjjkLiTN2T7Q==", + "dev": true, + "optional": true, + "requires": { + "bin-check": "^4.1.0", + "bin-version-check": "^4.0.0", + "download": "^7.1.0", + "import-lazy": "^3.1.0", + "os-filter-obj": "^2.0.0", + "pify": "^4.0.1" + }, + "dependencies": { + "download": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/download/-/download-7.1.0.tgz", + "integrity": "sha512-xqnBTVd/E+GxJVrX5/eUJiLYjCGPwMpdL+jGhGU57BvtcA7wwhtHVbXBeUk51kOpW3S7Jn3BQbN9Q1R1Km2qDQ==", + "dev": true, + "optional": true, + "requires": { + "archive-type": "^4.0.0", + "caw": "^2.0.1", + "content-disposition": "^0.5.2", + "decompress": "^4.2.0", + "ext-name": "^5.0.0", + "file-type": "^8.1.0", + "filenamify": "^2.0.0", + "get-stream": "^3.0.0", + "got": "^8.3.1", + "make-dir": "^1.2.0", + "p-event": "^2.1.0", + "pify": "^3.0.0" + }, + "dependencies": { + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true, + "optional": true + } + } + }, + "file-type": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-8.1.0.tgz", + "integrity": "sha512-qyQ0pzAy78gVoJsmYeNgl8uH8yKhr1lVhW7JbzJmnlRi0I4R2eEDEJZVKG8agpDnLpacwNbDhLNG/LMdxHD2YQ==", + "dev": true, + "optional": true + }, + "got": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/got/-/got-8.3.2.tgz", + "integrity": "sha512-qjUJ5U/hawxosMryILofZCkm3C84PLJS/0grRIpjAwu+Lkxxj5cxeCU25BG0/3mDSpXKTyZr8oh8wIgLaH0QCw==", + "dev": true, + "optional": true, + "requires": { + "@sindresorhus/is": "^0.7.0", + "cacheable-request": "^2.1.1", + "decompress-response": "^3.3.0", + "duplexer3": "^0.1.4", + "get-stream": "^3.0.0", + "into-stream": "^3.1.0", + "is-retry-allowed": "^1.1.0", + "isurl": "^1.0.0-alpha5", + "lowercase-keys": "^1.0.0", + "mimic-response": "^1.0.0", + "p-cancelable": "^0.4.0", + "p-timeout": "^2.0.1", + "pify": "^3.0.0", + "safe-buffer": "^5.1.1", + "timed-out": "^4.0.1", + "url-parse-lax": "^3.0.0", + "url-to-options": "^1.0.1" + }, + "dependencies": { + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true, + "optional": true + } + } + }, + "make-dir": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", + "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", + "dev": true, + "optional": true, + "requires": { + "pify": "^3.0.0" + }, + "dependencies": { + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true, + "optional": true + } + } + }, + "p-cancelable": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-0.4.1.tgz", + "integrity": "sha512-HNa1A8LvB1kie7cERyy21VNeHb2CWJJYqyyC2o3klWFfMGlFmWv2Z7sFgZH8ZiaYL95ydToKTFVXgMV/Os0bBQ==", + "dev": true, + "optional": true + }, + "p-event": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/p-event/-/p-event-2.3.1.tgz", + "integrity": "sha512-NQCqOFhbpVTMX4qMe8PF8lbGtzZ+LCiN7pcNrb/413Na7+TRoe1xkKUzuWa/YEJdGQ0FvKtj35EEbDoVPO2kbA==", + "dev": true, + "optional": true, + "requires": { + "p-timeout": "^2.0.1" + } + }, + "p-timeout": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-2.0.1.tgz", + "integrity": "sha512-88em58dDVB/KzPEx1X0N3LwFfYZPyDc4B6eF38M1rk9VTZMbxXXgjugz8mmwpS9Ox4BDZ+t6t3QP5+/gazweIA==", + "dev": true, + "optional": true, + "requires": { + "p-finally": "^1.0.0" + } + }, + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true, + "optional": true + }, + "prepend-http": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", + "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=", + "dev": true, + "optional": true + }, + "url-parse-lax": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", + "integrity": "sha1-FrXK/Afb42dsGxmZF3gj1lA6yww=", + "dev": true, + "optional": true, + "requires": { + "prepend-http": "^2.0.0" + } + } + } + }, + "binary-extensions": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", + "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", + "dev": true + }, + "bl": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.2.tgz", + "integrity": "sha1-oWCRFxcQPAdBDO9j71Gzl8Alr5w=", + "dev": true, + "optional": true, + "requires": { + "readable-stream": "^2.3.5", + "safe-buffer": "^5.1.1" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true, + "optional": true + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha1-sRwn2IuP8fvgcGQ8+UsMea4bCq8=", + "dev": true, + "optional": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha1-nPFhG6YmhdcDCunkujQUnDrwP8g=", + "dev": true, + "optional": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "blob": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/blob/-/blob-0.0.5.tgz", + "integrity": "sha1-1oDu7yX4zZGtUz9bAe7UjmTK9oM=", + "dev": true + }, + "bluebird": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.1.tgz", + "integrity": "sha512-DdmyoGCleJnkbp3nkbxTLJ18rjDsE4yCggEwKNXkeV123sPNfOCYeDoeuOY+F2FrSjO1YXcTU+dsy96KMy+gcg==", + "dev": true + }, + "body-parser": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", + "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", + "dev": true, + "requires": { + "bytes": "3.1.0", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.2", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "on-finished": "~2.3.0", + "qs": "6.7.0", + "raw-body": "2.4.0", + "type-is": "~1.6.17" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha1-XRKFFd8TT/Mn6QpMk/Tgd6U2NB8=", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "qs": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", + "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==", + "dev": true + } + } + }, + "boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=", + "dev": true + }, + "bootstrap": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-3.4.1.tgz", + "integrity": "sha512-yN5oZVmRCwe5aKwzRj6736nSmKDX7pLYwsXiCj/EYmo16hODaBiT4En5btW/jhBF/seV+XMx3aYwukYC3A49DA==" + }, + "bootstrap-social": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/bootstrap-social/-/bootstrap-social-5.1.1.tgz", + "integrity": "sha1-dTDGeK31bPj60/qCwp1NPl0CdQE=", + "requires": { + "bootstrap": "~3", + "font-awesome": "~4.7" + } + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha1-PH/L9SnYcibz0vUrlm/1Jx60Qd0=", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha1-WXn9PxTNUxVl5fot8av/8d+u5yk=", + "dev": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "browser-process-hrtime": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", + "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==", + "dev": true + }, + "browserslist": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.7.0.tgz", + "integrity": "sha512-9rGNDtnj+HaahxiVV38Gn8n8Lr8REKsel68v1sPFfIGEK6uSXTY3h9acgiT1dZVtOOUtifo/Dn8daDQ5dUgVsA==", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30000989", + "electron-to-chromium": "^1.3.247", + "node-releases": "^1.1.29" + } + }, + "buffer": { + "version": "5.4.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.4.3.tgz", + "integrity": "sha512-zvj65TkFeIt3i6aj5bIvJDzjjQQGs4o/sNoezg1F1kYap9Nu2jcUdpwzRSJTHMMzG0H7bZkn4rNQpImhuxWX2A==", + "dev": true, + "optional": true, + "requires": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4" + } + }, + "buffer-alloc": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", + "integrity": "sha1-iQ3ZDZI6hz4I4Q5f1RpX5bfM4Ow=", + "dev": true, + "requires": { + "buffer-alloc-unsafe": "^1.1.0", + "buffer-fill": "^1.0.0" + } + }, + "buffer-alloc-unsafe": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz", + "integrity": "sha1-vX3CauKXLQ7aJTvgYdupkjScGfA=", + "dev": true + }, + "buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=", + "dev": true, + "optional": true + }, + "buffer-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-1.0.0.tgz", + "integrity": "sha1-WWFrSYME1Var1GaWayLu2j7KX74=", + "dev": true + }, + "buffer-fill": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", + "integrity": "sha1-+PeLdniYiO858gXNY39o5wISKyw=", + "dev": true + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha1-MnE7wCj3XAL9txDXx7zsHyxgcO8=", + "dev": true + }, + "bufferstreams": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/bufferstreams/-/bufferstreams-1.0.1.tgz", + "integrity": "sha1-z7GtlWjTujz+k1upq92VLeiKqyo=", + "dev": true, + "requires": { + "readable-stream": "^1.0.33" + } + }, + "bytes": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", + "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", + "dev": true + }, + "cache-base": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", + "integrity": "sha1-Cn9GQWgxyLZi7jb+TnxZ129marI=", + "dev": true, + "requires": { + "collection-visit": "^1.0.0", + "component-emitter": "^1.2.1", + "get-value": "^2.0.6", + "has-value": "^1.0.0", + "isobject": "^3.0.1", + "set-value": "^2.0.0", + "to-object-path": "^0.3.0", + "union-value": "^1.0.0", + "unset-value": "^1.0.0" + } + }, + "cacheable-request": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-2.1.4.tgz", + "integrity": "sha1-DYCIAbY0KtM8kd+dC0TcCbkeXD0=", + "dev": true, + "optional": true, + "requires": { + "clone-response": "1.0.2", + "get-stream": "3.0.0", + "http-cache-semantics": "3.8.1", + "keyv": "3.0.0", + "lowercase-keys": "1.0.0", + "normalize-url": "2.0.1", + "responselike": "1.0.2" + }, + "dependencies": { + "lowercase-keys": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.0.tgz", + "integrity": "sha1-TjNms55/VFfjXxMkvfb4jQv8cwY=", + "dev": true, + "optional": true + }, + "normalize-url": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-2.0.1.tgz", + "integrity": "sha512-D6MUW4K/VzoJ4rJ01JFKxDrtY1v9wrgzCX5f2qj/lzH1m/lW6MhUZFKerVsnyjOhOsYzI9Kqqak+10l4LvLpMw==", + "dev": true, + "optional": true, + "requires": { + "prepend-http": "^2.0.0", + "query-string": "^5.0.1", + "sort-keys": "^2.0.0" + } + }, + "prepend-http": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", + "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=", + "dev": true, + "optional": true + }, + "sort-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-2.0.0.tgz", + "integrity": "sha1-ZYU1WEhh7JfXMNbPQYIuH1ZoQSg=", + "dev": true, + "optional": true, + "requires": { + "is-plain-obj": "^1.0.0" + } + } + } + }, + "caller-callsite": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz", + "integrity": "sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ=", + "dev": true, + "requires": { + "callsites": "^2.0.0" + } + }, + "caller-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz", + "integrity": "sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ=", + "dev": true, + "requires": { + "caller-callsite": "^2.0.0" + } + }, + "callsite": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz", + "integrity": "sha1-KAOY5dZkvXQDi28JBRU+borxvCA=", + "dev": true + }, + "callsites": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", + "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=", + "dev": true + }, + "camelcase": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", + "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=", + "dev": true + }, + "camelcase-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", + "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", + "dev": true, + "optional": true, + "requires": { + "camelcase": "^2.0.0", + "map-obj": "^1.0.0" + }, + "dependencies": { + "camelcase": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", + "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=", + "dev": true, + "optional": true + } + } + }, + "caniuse-api": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", + "integrity": "sha1-Xk2Q4idJYdRikZl99Znj7QCO5MA=", + "dev": true, + "requires": { + "browserslist": "^4.0.0", + "caniuse-lite": "^1.0.0", + "lodash.memoize": "^4.1.2", + "lodash.uniq": "^4.5.0" + } + }, + "caniuse-lite": { + "version": "1.0.30001168", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001168.tgz", + "integrity": "sha512-P2zmX7swIXKu+GMMR01TWa4csIKELTNnZKc+f1CjebmZJQtTAEXmpQSoKVJVVcvPGAA0TEYTOUp3VehavZSFPQ==", + "dev": true + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", + "dev": true + }, + "caw": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/caw/-/caw-2.0.1.tgz", + "integrity": "sha512-Cg8/ZSBEa8ZVY9HspcGUYaK63d/bN7rqS3CYCzEGUxuYv6UlmcjzDUz2fCFFHyTvUW5Pk0I+3hkA3iXlIj6guA==", + "dev": true, + "optional": true, + "requires": { + "get-proxy": "^2.0.0", + "isurl": "^1.0.0-alpha5", + "tunnel-agent": "^0.6.0", + "url-to-options": "^1.0.1" + } + }, + "center-align": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", + "integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=", + "dev": true, + "requires": { + "align-text": "^0.1.3", + "lazy-cache": "^1.0.3" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha1-kAlISfCTfy7twkJdDSip5fDLrZ4=", + "dev": true + }, + "chart.js": { + "version": "2.9.4", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-2.9.4.tgz", + "integrity": "sha512-B07aAzxcrikjAPyV+01j7BmOpxtQETxTSlQ26BEYJ+3iUkbNKaOJ/nDbT6JjyqYxseM0ON12COHYdU2cTIjC7A==", + "requires": { + "chartjs-color": "^2.1.0", + "moment": "^2.10.2" + } + }, + "chartjs-color": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/chartjs-color/-/chartjs-color-2.3.0.tgz", + "integrity": "sha512-hEvVheqczsoHD+fZ+tfPUE+1+RbV6b+eksp2LwAhwRTVXEjCSEavvk+Hg3H6SZfGlPh/UfmWKGIvZbtobOEm3g==", + "requires": { + "chartjs-color-string": "^0.6.0", + "color-convert": "^0.5.3" + } + }, + "chartjs-color-string": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/chartjs-color-string/-/chartjs-color-string-0.6.0.tgz", + "integrity": "sha512-TIB5OKn1hPJvO7JcteW4WY/63v6KwEdt6udfnDE9iCAZgy+V4SrbSxoIbTw/xkUIapjEI4ExGtD0+6D3KyFd7A==", + "requires": { + "color-name": "^1.0.0" + } + }, + "chokidar": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", + "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", + "dev": true, + "requires": { + "anymatch": "^2.0.0", + "async-each": "^1.0.1", + "braces": "^2.3.2", + "fsevents": "^1.2.7", + "glob-parent": "^3.1.0", + "inherits": "^2.0.3", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "normalize-path": "^3.0.0", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.2.1", + "upath": "^1.1.1" + }, + "dependencies": { + "anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha1-vLJLTzeTTZqnrBe0ra+J58du8us=", + "dev": true, + "requires": { + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" + }, + "dependencies": { + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true, + "requires": { + "remove-trailing-separator": "^1.0.1" + } + } + } + }, + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + } + } + }, + "class-utils": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", + "integrity": "sha1-+TNprouafOAv1B+q0MqDAzGQxGM=", + "dev": true, + "requires": { + "arr-union": "^3.1.0", + "define-property": "^0.2.5", + "isobject": "^3.0.0", + "static-extend": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "clean-css": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.1.tgz", + "integrity": "sha1-LUEe92uFabbQyEBo2r6FsKpeXBc=", + "dev": true, + "requires": { + "source-map": "~0.6.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM=", + "dev": true + } + } + }, + "cli-color": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/cli-color/-/cli-color-1.4.0.tgz", + "integrity": "sha1-fRBzj0hSaCT4/n2lGFfLD1cv4B8=", + "dev": true, + "requires": { + "ansi-regex": "^2.1.1", + "d": "1", + "es5-ext": "^0.10.46", + "es6-iterator": "^2.0.3", + "memoizee": "^0.4.14", + "timers-ext": "^0.1.5" + } + }, + "cli-cursor": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", + "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", + "dev": true, + "requires": { + "restore-cursor": "^2.0.0" + } + }, + "cli-width": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", + "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", + "dev": true + }, + "clipboard": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.4.tgz", + "integrity": "sha1-g22v1mzw/qXXHOXVsL9ulYAJES0=", + "requires": { + "good-listener": "^1.2.2", + "select": "^1.1.2", + "tiny-emitter": "^2.0.0" + } + }, + "cliui": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", + "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", + "dev": true, + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wrap-ansi": "^2.0.0" + } + }, + "clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", + "dev": true + }, + "clone-buffer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/clone-buffer/-/clone-buffer-1.0.0.tgz", + "integrity": "sha1-4+JbIHrE5wGvch4staFnksrD3Fg=", + "dev": true + }, + "clone-response": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", + "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=", + "dev": true, + "optional": true, + "requires": { + "mimic-response": "^1.0.0" + } + }, + "clone-stats": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-0.0.1.tgz", + "integrity": "sha1-uI+UqCzzi4eR1YBG6kAprYjKmdE=", + "dev": true + }, + "cloneable-readable": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/cloneable-readable/-/cloneable-readable-1.1.3.tgz", + "integrity": "sha512-2EF8zTQOxYq70Y4XKtorQupqF0m49MBz2/yf5Bj+MHjvpG3Hy7sImifnqD6UA+TKYxeSV+u6qqQPawN5UvnpKQ==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "process-nextick-args": "^2.0.0", + "readable-stream": "^2.3.5" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha1-sRwn2IuP8fvgcGQ8+UsMea4bCq8=", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha1-nPFhG6YmhdcDCunkujQUnDrwP8g=", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "coa": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/coa/-/coa-2.0.2.tgz", + "integrity": "sha512-q5/jG+YQnSy4nRTV4F7lPepBJZ8qBNJJDBuJdoejDyLXgmL7IEo+Le2JDZudFTFt7mrCqIRaSjws4ygRCTCAXA==", + "dev": true, + "requires": { + "@types/q": "^1.5.1", + "chalk": "^2.4.1", + "q": "^1.1.2" + } + }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "dev": true + }, + "collection-map": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-map/-/collection-map-1.0.0.tgz", + "integrity": "sha1-rqDwb40mx4DCt1SUOFVEsiVa8Yw=", + "dev": true, + "requires": { + "arr-map": "^2.0.2", + "for-own": "^1.0.0", + "make-iterator": "^1.0.0" + } + }, + "collection-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", + "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", + "dev": true, + "requires": { + "map-visit": "^1.0.0", + "object-visit": "^1.0.0" + } + }, + "color": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/color/-/color-3.1.2.tgz", + "integrity": "sha512-vXTJhHebByxZn3lDvDJYw4lR5+uB3vuoHsuYA5AKuxRVn5wzzIfQKGLBmgdVRHKTJYeK5rvJcHnrd0Li49CFpg==", + "dev": true, + "requires": { + "color-convert": "^1.9.1", + "color-string": "^1.5.2" + }, + "dependencies": { + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + } + } + }, + "color-convert": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-0.5.3.tgz", + "integrity": "sha1-vbbGnOZg+t/+CwAHzER+G59ygr0=" + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "color-string": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.5.3.tgz", + "integrity": "sha1-ybvF8BtYtUkvPWhXRZy2WQziBMw=", + "dev": true, + "requires": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha1-k4NDeaHMmgxh+C9S8NBDIiUb1aI=", + "dev": true + }, + "colornames": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/colornames/-/colornames-1.1.1.tgz", + "integrity": "sha1-+IiQMGhcfE/54qVZ9Qd+t2qBb5Y=", + "dev": true + }, + "colors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", + "dev": true + }, + "colorspace": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.2.tgz", + "integrity": "sha512-vt+OoIP2d76xLhjwbBaucYlNSpPsrJWPlBTtwCpQKIu6/CSMutyzX93O/Do0qzpH3YoHEes8YEFXyZ797rEhzQ==", + "dev": true, + "requires": { + "color": "3.0.x", + "text-hex": "1.0.x" + }, + "dependencies": { + "color": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/color/-/color-3.0.0.tgz", + "integrity": "sha1-2SC0Mo1TSjrIKV1o971LpsQnvpo=", + "dev": true, + "requires": { + "color-convert": "^1.9.1", + "color-string": "^1.5.2" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + } + } + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "commander": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.8.1.tgz", + "integrity": "sha1-Br42f+v9oMMwqh4qBy09yXYkJdQ=", + "dev": true, + "optional": true, + "requires": { + "graceful-readlink": ">= 1.0.0" + } + }, + "component-bind": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz", + "integrity": "sha1-AMYIq33Nk4l8AAllGx06jh5zu9E=", + "dev": true + }, + "component-emitter": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", + "dev": true + }, + "component-inherit": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/component-inherit/-/component-inherit-0.0.3.tgz", + "integrity": "sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM=", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha1-kEvfGUzTEi/Gdcd/xKw9T/D9GjQ=", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha1-nPFhG6YmhdcDCunkujQUnDrwP8g=", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "concat-with-sourcemaps": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/concat-with-sourcemaps/-/concat-with-sourcemaps-1.1.0.tgz", + "integrity": "sha1-1OqT8FriV5CVG5nns7CeOQikCC4=", + "dev": true, + "requires": { + "source-map": "^0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM=", + "dev": true + } + } + }, + "config-chain": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.12.tgz", + "integrity": "sha512-a1eOIcu8+7lUInge4Rpf/n4Krkf3Dd9lqhljRzII1/Zno/kRtUWnznPO3jOKBmTEktkt3fkxisUcivoj0ebzoA==", + "dev": true, + "optional": true, + "requires": { + "ini": "^1.3.4", + "proto-list": "~1.2.1" + } + }, + "connect": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", + "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", + "dev": true, + "requires": { + "debug": "2.6.9", + "finalhandler": "1.1.2", + "parseurl": "~1.3.3", + "utils-merge": "1.0.1" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha1-XRKFFd8TT/Mn6QpMk/Tgd6U2NB8=", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "console-stream": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/console-stream/-/console-stream-0.1.1.tgz", + "integrity": "sha1-oJX+B7IEZZVfL6/Si11yvM2UnUQ=", + "dev": true, + "optional": true + }, + "consolidate": { + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/consolidate/-/consolidate-0.15.1.tgz", + "integrity": "sha512-DW46nrsMJgy9kqAbPt5rKaCr7uFtpo4mSUvLHIUbJEjm0vo+aY5QLwBUq3FK4tRnJr/X0Psc0C4jf/h+HtXSMw==", + "dev": true, + "requires": { + "bluebird": "^3.1.1" + } + }, + "content-disposition": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", + "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", + "dev": true, + "optional": true, + "requires": { + "safe-buffer": "5.1.2" + } + }, + "content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha1-4TjMdeBAxyexlm/l5fjJruJW/js=", + "dev": true + }, + "convert-source-map": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.6.0.tgz", + "integrity": "sha1-UbU3qMQ+DwTewZk7/83VBOdYrCA=", + "dev": true, + "requires": { + "safe-buffer": "~5.1.1" + } + }, + "cookie": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", + "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=", + "dev": true + }, + "copy-descriptor": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", + "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", + "dev": true + }, + "copy-props": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/copy-props/-/copy-props-2.0.4.tgz", + "integrity": "sha1-k7scrfr9MdpbuKnUtB9HHsOnLf4=", + "dev": true, + "requires": { + "each-props": "^1.3.0", + "is-plain-object": "^2.0.1" + } + }, + "core-js-compat": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.3.3.tgz", + "integrity": "sha512-GNZkENsx5pMnS7Inwv7ZO/s3B68a9WU5kIjxqrD/tkNR8mtfXJRk8fAKRlbvWZSGPc59/TkiOBDYl5Cb65pTVA==", + "dev": true, + "requires": { + "browserslist": "^4.7.1", + "semver": "^6.3.0" + }, + "dependencies": { + "browserslist": { + "version": "4.7.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.7.1.tgz", + "integrity": "sha512-QtULFqKIAtiyNx7NhZ/p4rB8m3xDozVo/pi5VgTlADLF2tNigz/QH+v0m5qhn7XfHT7u+607NcCNOnC0HZAlMg==", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30000999", + "electron-to-chromium": "^1.3.284", + "node-releases": "^1.1.36" + } + }, + "electron-to-chromium": { + "version": "1.3.292", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.292.tgz", + "integrity": "sha512-hqkem5ANpt6mxVXmhAmlbdG8iicuyM/jEYgmP1tiHPeOLyZoTyGUzrDmJS/xyrrZy9frkW1uQcubicu7f6DS5g==", + "dev": true + }, + "node-releases": { + "version": "1.1.37", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.37.tgz", + "integrity": "sha512-0EOsAEdn6S2vQdDGBWBpmClm5BCkXVkVOURdnhfg7//rxI2XbleRdKig87WuBrk+0PHZ4OhO58fRm9mzWW4jNw==", + "dev": true, + "requires": { + "semver": "^6.3.0" + } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + }, + "cosmiconfig": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", + "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==", + "dev": true, + "requires": { + "import-fresh": "^2.0.0", + "is-directory": "^0.3.1", + "js-yaml": "^3.13.1", + "parse-json": "^4.0.0" + } + }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha1-Sl7Hxk364iw6FBJNus3uhG2Ay8Q=", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "css": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/css/-/css-2.2.4.tgz", + "integrity": "sha512-oUnjmWpy0niI3x/mPL8dVEI1l7MnG3+HHyRPHf+YFSbK+svOhXpmSOcDURUh2aOCgl2grzrOPt1nHLuCVFULLw==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "source-map": "^0.6.1", + "source-map-resolve": "^0.5.2", + "urix": "^0.1.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "css-color-names": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/css-color-names/-/css-color-names-0.0.4.tgz", + "integrity": "sha1-gIrcLnnPhHOAabZGyyDsJ762KeA=", + "dev": true + }, + "css-declaration-sorter": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-4.0.1.tgz", + "integrity": "sha1-wZiUD2OnbX42wecQGLABchBUyyI=", + "dev": true, + "requires": { + "postcss": "^7.0.1", + "timsort": "^0.3.0" + } + }, + "css-select": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-2.0.2.tgz", + "integrity": "sha1-q0OGzsnh9miFVWSxfDcztDsqXt4=", + "dev": true, + "requires": { + "boolbase": "^1.0.0", + "css-what": "^2.1.2", + "domutils": "^1.7.0", + "nth-check": "^1.0.2" + } + }, + "css-select-base-adapter": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz", + "integrity": "sha1-Oy/0lyzDYquIVhUHqVQIoUMhNdc=", + "dev": true + }, + "css-tree": { + "version": "1.0.0-alpha.33", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.33.tgz", + "integrity": "sha512-SPt57bh5nQnpsTBsx/IXbO14sRc9xXu5MtMAVuo0BaQQmyf0NupNPPSoMaqiAF5tDFafYsTkfeH4Q/HCKXkg4w==", + "dev": true, + "requires": { + "mdn-data": "2.0.4", + "source-map": "^0.5.3" + } + }, + "css-unit-converter": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/css-unit-converter/-/css-unit-converter-1.1.1.tgz", + "integrity": "sha1-2bkoGtz9jO2TW9urqDeGiX9k6ZY=", + "dev": true + }, + "css-what": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.3.tgz", + "integrity": "sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg==", + "dev": true + }, + "cssesc": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-2.0.0.tgz", + "integrity": "sha1-OxO9G7HLNuG8taTc0n9UxdyzVwM=", + "dev": true + }, + "cssnano": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-4.1.10.tgz", + "integrity": "sha512-5wny+F6H4/8RgNlaqab4ktc3e0/blKutmq8yNlBFXA//nSFFAqAngjNVRzUvCgYROULmZZUoosL/KSoZo5aUaQ==", + "dev": true, + "requires": { + "cosmiconfig": "^5.0.0", + "cssnano-preset-default": "^4.0.7", + "is-resolvable": "^1.0.0", + "postcss": "^7.0.0" + } + }, + "cssnano-preset-default": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-4.0.7.tgz", + "integrity": "sha512-x0YHHx2h6p0fCl1zY9L9roD7rnlltugGu7zXSKQx6k2rYw0Hi3IqxcoAGF7u9Q5w1nt7vK0ulxV8Lo+EvllGsA==", + "dev": true, + "requires": { + "css-declaration-sorter": "^4.0.1", + "cssnano-util-raw-cache": "^4.0.1", + "postcss": "^7.0.0", + "postcss-calc": "^7.0.1", + "postcss-colormin": "^4.0.3", + "postcss-convert-values": "^4.0.1", + "postcss-discard-comments": "^4.0.2", + "postcss-discard-duplicates": "^4.0.2", + "postcss-discard-empty": "^4.0.1", + "postcss-discard-overridden": "^4.0.1", + "postcss-merge-longhand": "^4.0.11", + "postcss-merge-rules": "^4.0.3", + "postcss-minify-font-values": "^4.0.2", + "postcss-minify-gradients": "^4.0.2", + "postcss-minify-params": "^4.0.2", + "postcss-minify-selectors": "^4.0.2", + "postcss-normalize-charset": "^4.0.1", + "postcss-normalize-display-values": "^4.0.2", + "postcss-normalize-positions": "^4.0.2", + "postcss-normalize-repeat-style": "^4.0.2", + "postcss-normalize-string": "^4.0.2", + "postcss-normalize-timing-functions": "^4.0.2", + "postcss-normalize-unicode": "^4.0.1", + "postcss-normalize-url": "^4.0.1", + "postcss-normalize-whitespace": "^4.0.2", + "postcss-ordered-values": "^4.1.2", + "postcss-reduce-initial": "^4.0.3", + "postcss-reduce-transforms": "^4.0.2", + "postcss-svgo": "^4.0.2", + "postcss-unique-selectors": "^4.0.1" + } + }, + "cssnano-util-get-arguments": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cssnano-util-get-arguments/-/cssnano-util-get-arguments-4.0.0.tgz", + "integrity": "sha1-7ToIKZ8h11dBsg87gfGU7UnMFQ8=", + "dev": true + }, + "cssnano-util-get-match": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cssnano-util-get-match/-/cssnano-util-get-match-4.0.0.tgz", + "integrity": "sha1-wOTKB/U4a7F+xeUiULT1lhNlFW0=", + "dev": true + }, + "cssnano-util-raw-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/cssnano-util-raw-cache/-/cssnano-util-raw-cache-4.0.1.tgz", + "integrity": "sha1-sm1f1fcqEd/np4RvtMZyYPlr8oI=", + "dev": true, + "requires": { + "postcss": "^7.0.0" + } + }, + "cssnano-util-same-parent": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/cssnano-util-same-parent/-/cssnano-util-same-parent-4.0.1.tgz", + "integrity": "sha1-V0CC+yhZ0ttDOFWDXZqEVuoYu/M=", + "dev": true + }, + "csso": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/csso/-/csso-3.5.1.tgz", + "integrity": "sha1-e564vmFiiXPBsmHhadLwJACOdYs=", + "dev": true, + "requires": { + "css-tree": "1.0.0-alpha.29" + }, + "dependencies": { + "css-tree": { + "version": "1.0.0-alpha.29", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.29.tgz", + "integrity": "sha1-P6nU7zFCy9HDAedmTB81K9gvWjk=", + "dev": true, + "requires": { + "mdn-data": "~1.1.0", + "source-map": "^0.5.3" + } + }, + "mdn-data": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-1.1.4.tgz", + "integrity": "sha512-FSYbp3lyKjyj3E7fMl6rYvUdX0FBXaluGqlFoYESWQlyUTq8R+wp0rkFxoYFqZlHCvsUXGjyJmLQSnXToYhOSA==", + "dev": true + } + } + }, + "cssom": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz", + "integrity": "sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw==", + "dev": true + }, + "cssstyle": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", + "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", + "dev": true, + "requires": { + "cssom": "~0.3.6" + }, + "dependencies": { + "cssom": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", + "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", + "dev": true + } + } + }, + "currently-unhandled": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", + "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=", + "dev": true, + "optional": true, + "requires": { + "array-find-index": "^1.0.1" + } + }, + "custom-event": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz", + "integrity": "sha1-XQKkaFCt8bSjF5RqOSj8y1v9BCU=", + "dev": true + }, + "d": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", + "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", + "dev": true, + "requires": { + "es5-ext": "^0.10.50", + "type": "^1.0.1" + } + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "data-urls": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-2.0.0.tgz", + "integrity": "sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ==", + "dev": true, + "requires": { + "abab": "^2.0.3", + "whatwg-mimetype": "^2.3.0", + "whatwg-url": "^8.0.0" + } + }, + "date-format": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/date-format/-/date-format-2.1.0.tgz", + "integrity": "sha512-bYQuGLeFxhkxNOF3rcMtiZxvCBAquGzZm6oWA1oZ0g2THUzivaRhv8uOhdr19LmoobSOLoIAxeUK2RdbM8IFTA==", + "dev": true + }, + "dateformat": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-2.2.0.tgz", + "integrity": "sha1-QGXiATz5+5Ft39gu+1Bq1MZ2kGI=", + "dev": true + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "debug-fabulous": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/debug-fabulous/-/debug-fabulous-1.1.0.tgz", + "integrity": "sha512-GZqvGIgKNlUnHUPQhepnUZFIMoi3dgZKQBzKDeL2g7oJF9SNAji/AAu36dusFUas0O+pae74lNeoIPHqXWDkLg==", + "dev": true, + "requires": { + "debug": "3.X", + "memoizee": "0.4.X", + "object-assign": "4.X" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true + } + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true + }, + "decimal.js": { + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.2.1.tgz", + "integrity": "sha512-KaL7+6Fw6i5A2XSnsbhm/6B+NuEA7TZ4vqxnd5tXz9sbKtrN9Srj8ab4vKVdK8YAqZO9P1kg45Y6YLoduPf+kw==", + "dev": true + }, + "decode-uri-component": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", + "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", + "dev": true + }, + "decompress": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/decompress/-/decompress-4.2.0.tgz", + "integrity": "sha1-eu3YVCflqS2s/lVnSnxQXpbQH50=", + "dev": true, + "optional": true, + "requires": { + "decompress-tar": "^4.0.0", + "decompress-tarbz2": "^4.0.0", + "decompress-targz": "^4.0.0", + "decompress-unzip": "^4.0.1", + "graceful-fs": "^4.1.10", + "make-dir": "^1.0.0", + "pify": "^2.3.0", + "strip-dirs": "^2.0.0" + }, + "dependencies": { + "make-dir": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", + "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", + "dev": true, + "optional": true, + "requires": { + "pify": "^3.0.0" + }, + "dependencies": { + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true, + "optional": true + } + } + } + } + }, + "decompress-response": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", + "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=", + "dev": true, + "optional": true, + "requires": { + "mimic-response": "^1.0.0" + } + }, + "decompress-tar": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/decompress-tar/-/decompress-tar-4.1.1.tgz", + "integrity": "sha512-JdJMaCrGpB5fESVyxwpCx4Jdj2AagLmv3y58Qy4GE6HMVjWz1FeVQk1Ct4Kye7PftcdOo/7U7UKzYBJgqnGeUQ==", + "dev": true, + "optional": true, + "requires": { + "file-type": "^5.2.0", + "is-stream": "^1.1.0", + "tar-stream": "^1.5.2" + }, + "dependencies": { + "file-type": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-5.2.0.tgz", + "integrity": "sha1-LdvqfHP/42No365J3DOMBYwritY=", + "dev": true, + "optional": true + } + } + }, + "decompress-tarbz2": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/decompress-tarbz2/-/decompress-tarbz2-4.1.1.tgz", + "integrity": "sha512-s88xLzf1r81ICXLAVQVzaN6ZmX4A6U4z2nMbOwobxkLoIIfjVMBg7TeguTUXkKeXni795B6y5rnvDw7rxhAq9A==", + "dev": true, + "optional": true, + "requires": { + "decompress-tar": "^4.1.0", + "file-type": "^6.1.0", + "is-stream": "^1.1.0", + "seek-bzip": "^1.0.5", + "unbzip2-stream": "^1.0.9" + }, + "dependencies": { + "file-type": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-6.2.0.tgz", + "integrity": "sha512-YPcTBDV+2Tm0VqjybVd32MHdlEGAtuxS3VAYsumFokDSMG+ROT5wawGlnHDoz7bfMcMDt9hxuXvXwoKUx2fkOg==", + "dev": true, + "optional": true + } + } + }, + "decompress-targz": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/decompress-targz/-/decompress-targz-4.1.1.tgz", + "integrity": "sha512-4z81Znfr6chWnRDNfFNqLwPvm4db3WuZkqV+UgXQzSngG3CEKdBkw5jrv3axjjL96glyiiKjsxJG3X6WBZwX3w==", + "dev": true, + "optional": true, + "requires": { + "decompress-tar": "^4.1.1", + "file-type": "^5.2.0", + "is-stream": "^1.1.0" + }, + "dependencies": { + "file-type": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-5.2.0.tgz", + "integrity": "sha1-LdvqfHP/42No365J3DOMBYwritY=", + "dev": true, + "optional": true + } + } + }, + "decompress-unzip": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/decompress-unzip/-/decompress-unzip-4.0.1.tgz", + "integrity": "sha1-3qrM39FK6vhVePczroIQ+bSEj2k=", + "dev": true, + "optional": true, + "requires": { + "file-type": "^3.8.0", + "get-stream": "^2.2.0", + "pify": "^2.3.0", + "yauzl": "^2.4.2" + }, + "dependencies": { + "file-type": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz", + "integrity": "sha1-JXoHg4TR24CHvESdEH1SpSZyuek=", + "dev": true, + "optional": true + }, + "get-stream": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-2.3.1.tgz", + "integrity": "sha1-Xzj5PzRgCWZu4BUKBUFn+Rvdld4=", + "dev": true, + "optional": true, + "requires": { + "object-assign": "^4.0.1", + "pinkie-promise": "^2.0.0" + } + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true, + "optional": true + } + } + }, + "deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "dev": true + }, + "default-compare": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/default-compare/-/default-compare-1.0.0.tgz", + "integrity": "sha1-y2ETGESthNhHiPto/QFoHKd4Gi8=", + "dev": true, + "requires": { + "kind-of": "^5.0.2" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha1-cpyR4thXt6QZofmqZWhcTDP1hF0=", + "dev": true + } + } + }, + "default-resolution": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/default-resolution/-/default-resolution-2.0.0.tgz", + "integrity": "sha1-vLgrqnKtebQmp2cy8aga1t8m1oQ=", + "dev": true + }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha1-z4jabL7ib+bbcJT2HYcMvYTO6fE=", + "dev": true, + "requires": { + "object-keys": "^1.0.12" + } + }, + "define-property": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha1-1Flono1lS6d+AqgX+HENcCyxbp0=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" + }, + "dependencies": { + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha1-FpwvbT3x+ZJhgHI2XJsOofaHhlY=", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha1-2Eh2Mh0Oet0DmQQGq7u9NrqSaMc=", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha1-OxWXRqZmBLBPjIFSS6NlxfFNhuw=", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "dev": true + }, + "delegate": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/delegate/-/delegate-3.2.0.tgz", + "integrity": "sha1-tmtxwxWFIuirV0T3INjKDCr1kWY=" + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", + "dev": true + }, + "detect-file": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz", + "integrity": "sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=", + "dev": true + }, + "detect-newline": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-2.1.0.tgz", + "integrity": "sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I=", + "dev": true + }, + "di": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz", + "integrity": "sha1-gGZJMmzqp8qjMG112YXqJ0i6kTw=", + "dev": true + }, + "diagnostics": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/diagnostics/-/diagnostics-1.1.1.tgz", + "integrity": "sha1-yrasM99wydmnJ0kK5DrJladpsio=", + "dev": true, + "requires": { + "colorspace": "1.1.x", + "enabled": "1.0.x", + "kuler": "1.0.x" + } + }, + "diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha1-gAwN0eCov7yVg1wgKtIg/jF+WhI=" + }, + "dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "requires": { + "path-type": "^4.0.0" + }, + "dependencies": { + "path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true + } + } + }, + "doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "dom-serialize": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/dom-serialize/-/dom-serialize-2.2.1.tgz", + "integrity": "sha1-ViromZ9Evl6jB29UGdzVnrQ6yVs=", + "dev": true, + "requires": { + "custom-event": "~1.0.0", + "ent": "~2.2.0", + "extend": "^3.0.0", + "void-elements": "^2.0.0" + } + }, + "dom-serializer": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.1.tgz", + "integrity": "sha512-sK3ujri04WyjwQXVoK4PU3y8ula1stq10GJZpqHIUgoGZdsGzAGu65BnU3d08aTVSvO7mGPZUc0wTEDL+qGE0Q==", + "dev": true, + "requires": { + "domelementtype": "^2.0.1", + "entities": "^2.0.0" + }, + "dependencies": { + "domelementtype": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.0.1.tgz", + "integrity": "sha512-5HOHUDsYZWV8FGWN0Njbr/Rn7f/eWSQi1v7+HsUVwXgn8nWWlL64zKDkS0n8ZmQ3mlWOMuXOnR+7Nx/5tMO5AQ==", + "dev": true + } + } + }, + "domelementtype": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", + "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==", + "dev": true + }, + "domexception": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz", + "integrity": "sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg==", + "dev": true, + "requires": { + "webidl-conversions": "^5.0.0" + }, + "dependencies": { + "webidl-conversions": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz", + "integrity": "sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==", + "dev": true + } + } + }, + "domhandler": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz", + "integrity": "sha1-iAUJfpM9ZehVRvcm1g9euItE+AM=", + "dev": true, + "requires": { + "domelementtype": "1" + } + }, + "domutils": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz", + "integrity": "sha1-Vuo0HoNOBuZ0ivehyyXaZ+qfjCo=", + "dev": true, + "requires": { + "dom-serializer": "0", + "domelementtype": "1" + } + }, + "dot-prop": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.0.tgz", + "integrity": "sha1-HxngwuGqDjJ5fEl5nyg3rGr2nFc=", + "dev": true, + "requires": { + "is-obj": "^1.0.0" + } + }, + "download": { + "version": "6.2.5", + "resolved": "https://registry.npmjs.org/download/-/download-6.2.5.tgz", + "integrity": "sha512-DpO9K1sXAST8Cpzb7kmEhogJxymyVUd5qz/vCOSyvwtp2Klj2XcDt5YUuasgxka44SxF0q5RriKIwJmQHG2AuA==", + "dev": true, + "optional": true, + "requires": { + "caw": "^2.0.0", + "content-disposition": "^0.5.2", + "decompress": "^4.0.0", + "ext-name": "^5.0.0", + "file-type": "5.2.0", + "filenamify": "^2.0.0", + "get-stream": "^3.0.0", + "got": "^7.0.0", + "make-dir": "^1.0.0", + "p-event": "^1.0.0", + "pify": "^3.0.0" + }, + "dependencies": { + "file-type": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-5.2.0.tgz", + "integrity": "sha1-LdvqfHP/42No365J3DOMBYwritY=", + "dev": true, + "optional": true + }, + "make-dir": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", + "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", + "dev": true, + "optional": true, + "requires": { + "pify": "^3.0.0" + } + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true, + "optional": true + } + } + }, + "duplexer2": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.0.2.tgz", + "integrity": "sha1-xhTc9n4vsUmVqRcR5aYX6KYKMds=", + "dev": true, + "requires": { + "readable-stream": "~1.1.9" + } + }, + "duplexer3": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", + "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=", + "dev": true, + "optional": true + }, + "duplexify": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", + "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", + "dev": true, + "requires": { + "end-of-stream": "^1.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.0.0", + "stream-shift": "^1.0.0" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha1-sRwn2IuP8fvgcGQ8+UsMea4bCq8=", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha1-nPFhG6YmhdcDCunkujQUnDrwP8g=", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "each-props": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/each-props/-/each-props-1.3.2.tgz", + "integrity": "sha1-6kWkFNFt1c+kGbGoFyDVygaJIzM=", + "dev": true, + "requires": { + "is-plain-object": "^2.0.1", + "object.defaults": "^1.1.0" + } + }, + "ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "dev": true, + "requires": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", + "dev": true + }, + "electron-to-chromium": { + "version": "1.3.266", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.266.tgz", + "integrity": "sha512-UTuTZ4v8T0gLPHI7U75PXLQePWI65MTS3mckRrnLCkNljHvsutbYs+hn2Ua/RFul3Jt/L3Ht2rLP+dU/AlBfrQ==", + "dev": true + }, + "emits": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/emits/-/emits-3.0.0.tgz", + "integrity": "sha1-MnUrupXhcHshlWI4Srm7ix/WL3A=", + "dev": true + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "enabled": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/enabled/-/enabled-1.0.2.tgz", + "integrity": "sha1-ll9lE9LC0cX0ZStkouM5ZGf8L5M=", + "dev": true, + "requires": { + "env-variable": "0.0.x" + } + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", + "dev": true + }, + "end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, + "requires": { + "once": "^1.4.0" + }, + "dependencies": { + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + } + } + }, + "engine.io": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.2.1.tgz", + "integrity": "sha1-tgKBw1SEpw7gNR6g6/+D7IyVIqI=", + "dev": true, + "requires": { + "accepts": "~1.3.4", + "base64id": "1.0.0", + "cookie": "0.3.1", + "debug": "~3.1.0", + "engine.io-parser": "~2.1.0", + "ws": "~3.3.1" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha1-W7WgZyYotkFJVmuhaBnmFRjGcmE=", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "engine.io-client": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.2.1.tgz", + "integrity": "sha1-b1TAR13khxWKGnx30QF4cItq3TY=", + "dev": true, + "requires": { + "component-emitter": "1.2.1", + "component-inherit": "0.0.3", + "debug": "~3.1.0", + "engine.io-parser": "~2.1.1", + "has-cors": "1.1.0", + "indexof": "0.0.1", + "parseqs": "0.0.5", + "parseuri": "0.0.5", + "ws": "~3.3.1", + "xmlhttprequest-ssl": "~1.5.4", + "yeast": "0.1.2" + }, + "dependencies": { + "component-emitter": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", + "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", + "dev": true + }, + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "engine.io-parser": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-2.1.3.tgz", + "integrity": "sha1-dXq5cPvy37Mse3SwMyFtVznveaY=", + "dev": true, + "requires": { + "after": "0.8.2", + "arraybuffer.slice": "~0.0.7", + "base64-arraybuffer": "0.1.5", + "blob": "0.0.5", + "has-binary2": "~1.0.2" + } + }, + "ent": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", + "integrity": "sha1-6WQhkyWiHQX0RGai9obtbOX13R0=", + "dev": true + }, + "entities": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.0.0.tgz", + "integrity": "sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw==", + "dev": true + }, + "env-variable": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/env-variable/-/env-variable-0.0.5.tgz", + "integrity": "sha1-kT3YML7xHpagOcA41BMGBOujf4g=", + "dev": true + }, + "errno": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz", + "integrity": "sha1-RoTXF3mtOa8Xfj8AeZb3xnyFJhg=", + "dev": true, + "optional": true, + "requires": { + "prr": "~1.0.1" + } + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha1-tKxAZIEH/c3PriQvQovqihTU8b8=", + "dev": true, + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "es-abstract": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.15.0.tgz", + "integrity": "sha512-bhkEqWJ2t2lMeaJDuk7okMkJWI/yqgH/EoGwpcvv0XW9RWQsRspI4wt6xuyuvMvvQE3gg/D9HXppgk21w78GyQ==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.0", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.0", + "is-callable": "^1.1.4", + "is-regex": "^1.0.4", + "object-inspect": "^1.6.0", + "object-keys": "^1.1.1", + "string.prototype.trimleft": "^2.1.0", + "string.prototype.trimright": "^2.1.0" + } + }, + "es-to-primitive": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.0.tgz", + "integrity": "sha1-7fckeAM0VujdqO8J4ArZZQcH83c=", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "es5-ext": { + "version": "0.10.51", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.51.tgz", + "integrity": "sha512-oRpWzM2WcLHVKpnrcyB7OW8j/s67Ba04JCm0WnNv3RiABSvs7mrQlutB8DBv793gKcp0XENR8Il8WxGTlZ73gQ==", + "dev": true, + "requires": { + "es6-iterator": "~2.0.3", + "es6-symbol": "~3.1.1", + "next-tick": "^1.0.0" + } + }, + "es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, + "es6-promise": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", + "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==", + "dev": true + }, + "es6-symbol": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.2.tgz", + "integrity": "sha512-/ZypxQsArlv+KHpGvng52/Iz8by3EQPxhmbuz8yFG89N/caTFBSbcXONDw0aMjy827gQg26XAjP4uXFvnfINmQ==", + "dev": true, + "requires": { + "d": "^1.0.1", + "es5-ext": "^0.10.51" + } + }, + "es6-weak-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.3.tgz", + "integrity": "sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "^0.10.46", + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.1" + } + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "escodegen": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.12.0.tgz", + "integrity": "sha512-TuA+EhsanGcme5T3R0L80u4t8CpbXQjegRmf7+FPTJrtCTErXFeelblRgHQa1FofEzqYYJmJ/OqjTwREp9qgmg==", + "dev": true, + "requires": { + "esprima": "^3.1.3", + "estraverse": "^4.2.0", + "esutils": "^2.0.2", + "optionator": "^0.8.1", + "source-map": "~0.6.1" + }, + "dependencies": { + "esprima": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz", + "integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM=", + "dev": true, + "optional": true + } + } + }, + "eslint": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-6.5.1.tgz", + "integrity": "sha512-32h99BoLYStT1iq1v2P9uwpyznQ4M2jRiFB6acitKz52Gqn+vPaMDUTB1bYi1WN4Nquj2w+t+bimYUG83DC55A==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "ajv": "^6.10.0", + "chalk": "^2.1.0", + "cross-spawn": "^6.0.5", + "debug": "^4.0.1", + "doctrine": "^3.0.0", + "eslint-scope": "^5.0.0", + "eslint-utils": "^1.4.2", + "eslint-visitor-keys": "^1.1.0", + "espree": "^6.1.1", + "esquery": "^1.0.1", + "esutils": "^2.0.2", + "file-entry-cache": "^5.0.1", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^5.0.0", + "globals": "^11.7.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "inquirer": "^6.4.1", + "is-glob": "^4.0.0", + "js-yaml": "^3.13.1", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.3.0", + "lodash": "^4.17.14", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.1", + "natural-compare": "^1.4.0", + "optionator": "^0.8.2", + "progress": "^2.0.0", + "regexpp": "^2.0.1", + "semver": "^6.1.2", + "strip-ansi": "^5.2.0", + "strip-json-comments": "^3.0.1", + "table": "^5.2.3", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "glob-parent": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.0.tgz", + "integrity": "sha512-qjtRgnIVmOfnKUE3NJAQEdk+lKrxfw8t5ke7SXtfMTHcjsBfOfWXCQfdb30zfDoZQ2IRSIiidmjtbHZPZ++Ihw==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "import-fresh": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.1.0.tgz", + "integrity": "sha512-PpuksHKGt8rXfWEr9m9EHIpgyyaltBy8+eF6GJM0QCAxMgxCfucMF3mjecK2QsJr0amJW7gTqh5/wht0z2UhEQ==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + } + }, + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "eslint-scope": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.0.0.tgz", + "integrity": "sha512-oYrhJW7S0bxAFDvWqzvMPRm6pcgcnWc4QnofCAqRTRfQC0JcwenzGglTtsLyIuuWFfkqDG9vz67cnttSd53djw==", + "dev": true, + "requires": { + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + } + }, + "eslint-utils": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.3.tgz", + "integrity": "sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^1.1.0" + } + }, + "eslint-visitor-keys": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz", + "integrity": "sha512-8y9YjtM1JBJU/A9Kc+SbaOV4y29sSWckBwMHa+FGtVj5gN/sbnKDf6xJUl+8g7FAij9LVaP8C24DUiH/f/2Z9A==", + "dev": true + }, + "espree": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/espree/-/espree-6.1.2.tgz", + "integrity": "sha512-2iUPuuPP+yW1PZaMSDM9eyVf8D5P0Hi8h83YtZ5bPc/zHYjII5khoixIUTMO794NOY8F/ThF1Bo8ncZILarUTA==", + "dev": true, + "requires": { + "acorn": "^7.1.0", + "acorn-jsx": "^5.1.0", + "eslint-visitor-keys": "^1.1.0" + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha1-E7BM2z5sXRnfkatph6hpVhmwqnE=", + "dev": true + }, + "esquery": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz", + "integrity": "sha1-QGxRZYsfWZGl+bYrHcJbAOPlxwg=", + "dev": true, + "requires": { + "estraverse": "^4.0.0" + } + }, + "esrecurse": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", + "integrity": "sha1-AHo7n9vCs7uH5IeeoZyS/b05Qs8=", + "dev": true, + "requires": { + "estraverse": "^4.1.0" + } + }, + "estemplate": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/estemplate/-/estemplate-0.5.1.tgz", + "integrity": "sha1-FxSp1GGQc4rJWLyv1J4CnNpWo54=", + "dev": true, + "requires": { + "esprima": "^2.7.2", + "estraverse": "^4.1.1" + }, + "dependencies": { + "esprima": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", + "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=", + "dev": true + } + } + }, + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true + }, + "event-emitter": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", + "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "~0.10.14" + } + }, + "eventemitter3": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.0.tgz", + "integrity": "sha512-qerSRB0p+UDEssxTtm6EDKcE7W4OaoisfIMl4CngyEhjpYglocpNg6UEqCvemdGhosAsg4sO2dXJOdyBifPGCg==", + "dev": true + }, + "exec-buffer": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/exec-buffer/-/exec-buffer-3.2.0.tgz", + "integrity": "sha1-sWhtvZBMfPmC5lLB9aebHlVzCCs=", + "dev": true, + "optional": true, + "requires": { + "execa": "^0.7.0", + "p-finally": "^1.0.0", + "pify": "^3.0.0", + "rimraf": "^2.5.4", + "tempfile": "^2.0.0" + }, + "dependencies": { + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true, + "optional": true + } + } + }, + "execa": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", + "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", + "dev": true, + "optional": true, + "requires": { + "cross-spawn": "^5.0.1", + "get-stream": "^3.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + }, + "dependencies": { + "cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "dev": true, + "optional": true, + "requires": { + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + } + } + }, + "executable": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/executable/-/executable-4.1.1.tgz", + "integrity": "sha512-8iA79xD3uAch729dUG8xaaBBFGaEa0wdD2VkYLFHwlqosEj/jT66AzcreRDSgV7ehnNLBW2WR5jIXwGKjVdTLg==", + "dev": true, + "optional": true, + "requires": { + "pify": "^2.2.0" + } + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "dev": true, + "requires": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha1-XRKFFd8TT/Mn6QpMk/Tgd6U2NB8=", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "expand-range": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz", + "integrity": "sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=", + "dev": true, + "requires": { + "fill-range": "^2.1.0" + }, + "dependencies": { + "fill-range": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.4.tgz", + "integrity": "sha1-6x53OrsFbc2N8r/favWbizqTZWU=", + "dev": true, + "requires": { + "is-number": "^2.1.0", + "isobject": "^2.0.0", + "randomatic": "^3.0.0", + "repeat-element": "^1.1.2", + "repeat-string": "^1.5.2" + } + }, + "is-number": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", + "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + } + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "dev": true, + "requires": { + "isarray": "1.0.0" + } + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "expand-tilde": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", + "integrity": "sha1-l+gBqgUt8CRU3kawK/YhZCzchQI=", + "dev": true, + "requires": { + "homedir-polyfill": "^1.0.1" + } + }, + "ext-list": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/ext-list/-/ext-list-2.2.2.tgz", + "integrity": "sha512-u+SQgsubraE6zItfVA0tBuCBhfU9ogSRnsvygI7wht9TS510oLkBRXBsqopeUG/GBOIQyKZO9wjTqIu/sf5zFA==", + "dev": true, + "optional": true, + "requires": { + "mime-db": "^1.28.0" + } + }, + "ext-name": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ext-name/-/ext-name-5.0.0.tgz", + "integrity": "sha512-yblEwXAbGv1VQDmow7s38W77hzAgJAO50ztBLMcUyUBfxv1HC+LGwtiEN+Co6LtlqT/5uwVOxsD4TNIilWhwdQ==", + "dev": true, + "optional": true, + "requires": { + "ext-list": "^2.0.0", + "sort-keys-length": "^1.0.0" + } + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha1-+LETa0Bx+9jrFAr/hYsQGewpFfo=", + "dev": true + }, + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "dev": true, + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha1-p0cPnkJnM9gb2B4RVSZOOjUHyrQ=", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "external-editor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", + "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", + "dev": true, + "requires": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + } + }, + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha1-rQD+TcYSqSMuhxhxHcXLWrAoVUM=", + "dev": true, + "requires": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha1-FpwvbT3x+ZJhgHI2XJsOofaHhlY=", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha1-2Eh2Mh0Oet0DmQQGq7u9NrqSaMc=", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha1-OxWXRqZmBLBPjIFSS6NlxfFNhuw=", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", + "dev": true + }, + "fancy-log": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/fancy-log/-/fancy-log-1.3.3.tgz", + "integrity": "sha1-28GRVPVYaQFQojlToK29A1vkX8c=", + "dev": true, + "requires": { + "ansi-gray": "^0.1.1", + "color-support": "^1.1.3", + "parse-node-version": "^1.0.0", + "time-stamp": "^1.0.0" + } + }, + "fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", + "dev": true + }, + "fast-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.1.0.tgz", + "integrity": "sha512-TrUz3THiq2Vy3bjfQUB2wNyPdGBeGmdjbzzBLhfHN4YFurYptCKwGq/TfiRavbGywFRzY6U2CdmQ1zmsY5yYaw==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.0", + "merge2": "^1.3.0", + "micromatch": "^4.0.2" + }, + "dependencies": { + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "glob-parent": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.0.tgz", + "integrity": "sha512-qjtRgnIVmOfnKUE3NJAQEdk+lKrxfw8t5ke7SXtfMTHcjsBfOfWXCQfdb30zfDoZQ2IRSIiidmjtbHZPZ++Ihw==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "micromatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", + "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", + "dev": true, + "requires": { + "braces": "^3.0.1", + "picomatch": "^2.0.5" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + } + } + }, + "fast-json-stable-stringify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", + "dev": true + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, + "fastq": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.6.0.tgz", + "integrity": "sha512-jmxqQ3Z/nXoeyDmWAzF9kH1aGZSis6e/SbfPmJpUnyZ0ogr6iscHQaml4wsEepEWSdtmpy+eVXmCRIMpxaXqOA==", + "dev": true, + "requires": { + "reusify": "^1.0.0" + } + }, + "fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=", + "dev": true, + "optional": true, + "requires": { + "pend": "~1.2.0" + } + }, + "figures": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", + "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5" + } + }, + "file-entry-cache": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz", + "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==", + "dev": true, + "requires": { + "flat-cache": "^2.0.1" + } + }, + "file-type": { + "version": "12.3.1", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-12.3.1.tgz", + "integrity": "sha512-FXxY5h6vSYMjrRal4YqbtfuoKD/oE0AMjJ7E5Hm+BdaQECcFVD03B41RAWYJ7wyuLr/wRnCtFo7y37l+nh+TAA==", + "dev": true + }, + "filename-regex": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", + "integrity": "sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY=", + "dev": true + }, + "filename-reserved-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-2.0.0.tgz", + "integrity": "sha1-q/c9+rc10EVECr/qLZHzieu/oik=", + "dev": true, + "optional": true + }, + "filenamify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/filenamify/-/filenamify-2.1.0.tgz", + "integrity": "sha512-ICw7NTT6RsDp2rnYKVd8Fu4cr6ITzGy3+u4vUujPkabyaz+03F24NWEX7fs5fp+kBonlaqPH8fAO2NM+SXt/JA==", + "dev": true, + "optional": true, + "requires": { + "filename-reserved-regex": "^2.0.0", + "strip-outer": "^1.0.0", + "trim-repeated": "^1.0.0" + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "dev": true, + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha1-XRKFFd8TT/Mn6QpMk/Tgd6U2NB8=", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "find-up": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", + "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", + "dev": true, + "requires": { + "path-exists": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "find-versions": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/find-versions/-/find-versions-3.1.0.tgz", + "integrity": "sha512-NCTfNiVzeE/xL+roNDffGuRbrWI6atI18lTJ22vKp7rs2OhYzMK3W1dIdO2TUndH/QMcacM4d1uWwgcZcHK69Q==", + "dev": true, + "optional": true, + "requires": { + "array-uniq": "^2.1.0", + "semver-regex": "^2.0.0" + }, + "dependencies": { + "array-uniq": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-2.1.0.tgz", + "integrity": "sha512-bdHxtev7FN6+MXI1YFW0Q8mQ8dTJc2S8AMfju+ZR77pbg2yAdVyDlwkaUI7Har0LyOMRFPHrJ9lYdyjZZswdlQ==", + "dev": true, + "optional": true + } + } + }, + "findup-sync": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-3.0.0.tgz", + "integrity": "sha512-YbffarhcicEhOrm4CtrwdKBdCuz576RLdhJDsIfvNtxUuhdRet1qZcsMjqbePtAseKdAnDyM/IyXbu7PRPRLYg==", + "dev": true, + "requires": { + "detect-file": "^1.0.0", + "is-glob": "^4.0.0", + "micromatch": "^3.0.4", + "resolve-dir": "^1.0.1" + }, + "dependencies": { + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + } + } + }, + "fined": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fined/-/fined-1.2.0.tgz", + "integrity": "sha512-ZYDqPLGxDkDhDZBjZBb+oD1+j0rA4E0pXY50eplAAOPg2N/gUBSSk5IM1/QhPfyVo19lJ+CvXpqfvk+b2p/8Ng==", + "dev": true, + "requires": { + "expand-tilde": "^2.0.2", + "is-plain-object": "^2.0.3", + "object.defaults": "^1.1.0", + "object.pick": "^1.2.0", + "parse-filepath": "^1.0.1" + } + }, + "first-chunk-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/first-chunk-stream/-/first-chunk-stream-2.0.0.tgz", + "integrity": "sha1-G97NuOCDwGZLkZRVgVd6Q6nzHXA=", + "dev": true, + "requires": { + "readable-stream": "^2.0.2" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "flagged-respawn": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/flagged-respawn/-/flagged-respawn-1.0.1.tgz", + "integrity": "sha512-lNaHNVymajmk0OJMBn8fVUAU1BtDeKIqKoVhk4xAALB57aALg6b4W0MfJ/cUE0g9YBXy5XhSlPIpYIJ7HaY/3Q==", + "dev": true + }, + "flat-cache": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", + "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==", + "dev": true, + "requires": { + "flatted": "^2.0.0", + "rimraf": "2.6.3", + "write": "1.0.3" + } + }, + "flatpickr": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/flatpickr/-/flatpickr-4.5.2.tgz", + "integrity": "sha1-R8itRyoJbl+350uAmwcDU1OD8g0=" + }, + "flatted": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.1.tgz", + "integrity": "sha512-a1hQMktqW9Nmqr5aktAux3JMNqaucxGcjtjWnZLHX7yyPCmlSV3M54nGYbqT8K+0GhF3NBgmJCc3ma+WOgX8Jg==", + "dev": true + }, + "flush-write-stream": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz", + "integrity": "sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "readable-stream": "^2.3.6" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "follow-redirects": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.9.0.tgz", + "integrity": "sha512-CRcPzsSIbXyVDl0QI01muNDu69S8trU4jArW9LpOt2WtC6LyUJetcIrmfHsRBx7/Jb6GHJUiuqyYxPooFfNt6A==", + "dev": true, + "requires": { + "debug": "^3.0.0" + }, + "dependencies": { + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + } + } + }, + "font-awesome": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/font-awesome/-/font-awesome-4.7.0.tgz", + "integrity": "sha1-j6jPBBGhoxr9B7BtKQK7n8gVoTM=" + }, + "for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", + "dev": true + }, + "for-own": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz", + "integrity": "sha1-xjMy9BXO3EsE2/5wz4NklMU8tEs=", + "dev": true, + "requires": { + "for-in": "^1.0.1" + } + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", + "dev": true + }, + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha1-3M5SwF9kTymManq5Nr1yTO/786Y=", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, + "fragment-cache": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", + "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", + "dev": true, + "requires": { + "map-cache": "^0.2.2" + } + }, + "from2": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", + "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=", + "dev": true, + "optional": true, + "requires": { + "inherits": "^2.0.1", + "readable-stream": "^2.0.0" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true, + "optional": true + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "dev": true, + "optional": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "optional": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "fs": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/fs/-/fs-0.0.2.tgz", + "integrity": "sha1-4fJE7zkzwbKmS9R5kTYGDQ9ZFPg=", + "dev": true + }, + "fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha1-a+Dem+mYzhavivwkSXue6bfM2a0=", + "dev": true, + "optional": true + }, + "fs-mkdirp-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-mkdirp-stream/-/fs-mkdirp-stream-1.0.0.tgz", + "integrity": "sha1-C3gV/DIBxqaeFNuYzgmMFpNSWes=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.11", + "through2": "^2.0.3" + } + }, + "fs-readfile-promise": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/fs-readfile-promise/-/fs-readfile-promise-3.0.1.tgz", + "integrity": "sha512-LsSxMeaJdYH27XrW7Dmq0Gx63mioULCRel63B5VeELYLavi1wF5s0XfsIdKDFdCL9hsfQ2qBvXJszQtQJ9h17A==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.11" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "fsevents": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.9.tgz", + "integrity": "sha512-oeyj2H3EjjonWcFjD5NvZNE9Rqe4UW+nQBU2HNeKw0koVLEFIhtyETyAakeAM3de7Z/SW5kcA+fZUait9EApnw==", + "dev": true, + "optional": true, + "requires": { + "nan": "^2.12.1", + "node-pre-gyp": "^0.12.0" + }, + "dependencies": { + "abbrev": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "ansi-regex": { + "version": "2.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "aproba": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "optional": true + }, + "are-we-there-yet": { + "version": "1.1.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, + "balanced-match": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "brace-expansion": { + "version": "1.1.11", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "chownr": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "code-point-at": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "optional": true + }, + "concat-map": { + "version": "0.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "console-control-strings": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "optional": true + }, + "core-util-is": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "debug": { + "version": "4.1.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ms": "^2.1.1" + } + }, + "deep-extend": { + "version": "0.6.0", + "bundled": true, + "dev": true, + "optional": true + }, + "delegates": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "detect-libc": { + "version": "1.0.3", + "bundled": true, + "dev": true, + "optional": true + }, + "fs-minipass": { + "version": "1.2.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "fs.realpath": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "gauge": { + "version": "2.7.4", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + } + }, + "glob": { + "version": "7.1.3", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "has-unicode": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "iconv-lite": { + "version": "0.4.24", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ignore-walk": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minimatch": "^3.0.4" + } + }, + "inflight": { + "version": "1.0.6", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "bundled": true, + "dev": true, + "optional": true + }, + "ini": { + "version": "1.3.5", + "bundled": true, + "dev": true, + "optional": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "isarray": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "minimatch": { + "version": "3.0.4", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "bundled": true, + "dev": true, + "optional": true + }, + "minipass": { + "version": "2.3.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + } + }, + "minizlib": { + "version": "1.2.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "mkdirp": { + "version": "0.5.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minimist": "0.0.8" + } + }, + "ms": { + "version": "2.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "needle": { + "version": "2.3.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "debug": "^4.1.0", + "iconv-lite": "^0.4.4", + "sax": "^1.2.4" + } + }, + "node-pre-gyp": { + "version": "0.12.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "detect-libc": "^1.0.2", + "mkdirp": "^0.5.1", + "needle": "^2.2.1", + "nopt": "^4.0.1", + "npm-packlist": "^1.1.6", + "npmlog": "^4.0.2", + "rc": "^1.2.7", + "rimraf": "^2.6.1", + "semver": "^5.3.0", + "tar": "^4" + } + }, + "nopt": { + "version": "4.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "abbrev": "1", + "osenv": "^0.1.4" + } + }, + "npm-bundled": { + "version": "1.0.6", + "bundled": true, + "dev": true, + "optional": true + }, + "npm-packlist": { + "version": "1.4.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1" + } + }, + "npmlog": { + "version": "4.1.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "object-assign": { + "version": "4.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "once": { + "version": "1.4.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "wrappy": "1" + } + }, + "os-homedir": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "os-tmpdir": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "osenv": { + "version": "0.1.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "process-nextick-args": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "rc": { + "version": "1.2.8", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "readable-stream": { + "version": "2.3.6", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "rimraf": { + "version": "2.6.3", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "glob": "^7.1.3" + } + }, + "safe-buffer": { + "version": "5.1.2", + "bundled": true, + "dev": true, + "optional": true + }, + "safer-buffer": { + "version": "2.1.2", + "bundled": true, + "dev": true, + "optional": true + }, + "sax": { + "version": "1.2.4", + "bundled": true, + "dev": true, + "optional": true + }, + "semver": { + "version": "5.7.0", + "bundled": true, + "dev": true, + "optional": true + }, + "set-blocking": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "signal-exit": { + "version": "3.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "string-width": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "string_decoder": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "tar": { + "version": "4.4.8", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "chownr": "^1.1.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.3.4", + "minizlib": "^1.1.1", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.2", + "yallist": "^3.0.2" + } + }, + "util-deprecate": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "wide-align": { + "version": "1.1.3", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "string-width": "^1.0.2 || 2" + } + }, + "wrappy": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "yallist": { + "version": "3.0.3", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha1-pWiZ0+o8m6uHS7l3O3xe3pL0iV0=", + "dev": true + }, + "functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", + "dev": true + }, + "get-caller-file": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", + "integrity": "sha1-+Xj6TJDR3+f/LWvtoqUV5xO9z0o=", + "dev": true + }, + "get-proxy": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/get-proxy/-/get-proxy-2.1.0.tgz", + "integrity": "sha512-zmZIaQTWnNQb4R4fJUEp/FC51eZsc6EkErspy3xtIYStaq8EB/hDIWipxsal+E8rz0qD7f2sL/NA9Xee4RInJw==", + "dev": true, + "optional": true, + "requires": { + "npm-conf": "^1.1.0" + } + }, + "get-stdin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", + "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=", + "dev": true, + "optional": true + }, + "get-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", + "dev": true, + "optional": true + }, + "get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", + "dev": true + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "gifsicle": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/gifsicle/-/gifsicle-4.0.1.tgz", + "integrity": "sha512-A/kiCLfDdV+ERV/UB+2O41mifd+RxH8jlRG8DMxZO84Bma/Fw0htqZ+hY2iaalLRNyUu7tYZQslqUBJxBggxbg==", + "dev": true, + "optional": true, + "requires": { + "bin-build": "^3.0.0", + "bin-wrapper": "^4.0.0", + "execa": "^1.0.0", + "logalot": "^2.0.0" + }, + "dependencies": { + "execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "dev": true, + "optional": true, + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "optional": true, + "requires": { + "pump": "^3.0.0" + } + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "optional": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + } + } + }, + "glob": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", + "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-base": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz", + "integrity": "sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q=", + "dev": true, + "requires": { + "glob-parent": "^2.0.0", + "is-glob": "^2.0.0" + }, + "dependencies": { + "glob-parent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", + "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=", + "dev": true, + "requires": { + "is-glob": "^2.0.0" + } + }, + "is-extglob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", + "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", + "dev": true + }, + "is-glob": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", + "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", + "dev": true, + "requires": { + "is-extglob": "^1.0.0" + } + } + } + }, + "glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "dev": true, + "requires": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + } + }, + "glob-stream": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/glob-stream/-/glob-stream-6.1.0.tgz", + "integrity": "sha1-cEXJlBOz65SIjYOrRtC0BMx73eQ=", + "dev": true, + "requires": { + "extend": "^3.0.0", + "glob": "^7.1.1", + "glob-parent": "^3.1.0", + "is-negated-glob": "^1.0.0", + "ordered-read-streams": "^1.0.0", + "pumpify": "^1.3.5", + "readable-stream": "^2.1.5", + "remove-trailing-separator": "^1.0.1", + "to-absolute-glob": "^2.0.0", + "unique-stream": "^2.0.2" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "glob-watcher": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/glob-watcher/-/glob-watcher-5.0.3.tgz", + "integrity": "sha512-8tWsULNEPHKQ2MR4zXuzSmqbdyV5PtwwCaWSGQ1WwHsJ07ilNeN1JB8ntxhckbnpSHaf9dXFUHzIWvm1I13dsg==", + "dev": true, + "requires": { + "anymatch": "^2.0.0", + "async-done": "^1.2.0", + "chokidar": "^2.0.0", + "is-negated-glob": "^1.0.0", + "just-debounce": "^1.0.0", + "object.defaults": "^1.1.0" + }, + "dependencies": { + "anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "dev": true, + "requires": { + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" + } + } + } + }, + "global-modules": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", + "integrity": "sha1-bXcPDrUjrHgWTXK15xqIdyZcw+o=", + "dev": true, + "requires": { + "global-prefix": "^1.0.1", + "is-windows": "^1.0.1", + "resolve-dir": "^1.0.0" + } + }, + "global-prefix": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz", + "integrity": "sha1-2/dDxsFJklk8ZVVoy2btMsASLr4=", + "dev": true, + "requires": { + "expand-tilde": "^2.0.2", + "homedir-polyfill": "^1.0.1", + "ini": "^1.3.4", + "is-windows": "^1.0.1", + "which": "^1.2.14" + } + }, + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true + }, + "globby": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/globby/-/globby-10.0.1.tgz", + "integrity": "sha512-sSs4inE1FB2YQiymcmTv6NWENryABjUNPeWhOvmn4SjtKybglsyPZxFB3U1/+L1bYi0rNZDqCLlHyLYDl1Pq5A==", + "dev": true, + "requires": { + "@types/glob": "^7.1.1", + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.0.3", + "glob": "^7.1.3", + "ignore": "^5.1.1", + "merge2": "^1.2.3", + "slash": "^3.0.0" + }, + "dependencies": { + "ignore": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.4.tgz", + "integrity": "sha512-MzbUSahkTW1u7JpKKjY7LCARd1fU5W2rLdxlM4kdkayuCwZImjkpluF9CM1aLewYJguPDqewLam18Y6AU69A8A==", + "dev": true + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + } + } + }, + "glogg": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/glogg/-/glogg-1.0.2.tgz", + "integrity": "sha512-5mwUoSuBk44Y4EshyiqcH95ZntbDdTQqA3QYSrxmzj28Ai0vXBGMH1ApSANH14j2sIRtqCEyg6PfsuP7ElOEDA==", + "dev": true, + "requires": { + "sparkles": "^1.0.0" + } + }, + "good-listener": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/good-listener/-/good-listener-1.2.2.tgz", + "integrity": "sha1-1TswzfkxPf+33JoNR3CWqm0UXFA=", + "requires": { + "delegate": "^3.1.2" + } + }, + "got": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/got/-/got-7.1.0.tgz", + "integrity": "sha512-Y5WMo7xKKq1muPsxD+KmrR8DH5auG7fBdDVueZwETwV6VytKyU9OX/ddpq2/1hp1vIPvVb4T81dKQz3BivkNLw==", + "dev": true, + "optional": true, + "requires": { + "decompress-response": "^3.2.0", + "duplexer3": "^0.1.4", + "get-stream": "^3.0.0", + "is-plain-obj": "^1.1.0", + "is-retry-allowed": "^1.0.0", + "is-stream": "^1.0.0", + "isurl": "^1.0.0-alpha5", + "lowercase-keys": "^1.0.0", + "p-cancelable": "^0.3.0", + "p-timeout": "^1.1.1", + "safe-buffer": "^5.0.1", + "timed-out": "^4.0.0", + "url-parse-lax": "^1.0.0", + "url-to-options": "^1.0.1" + } + }, + "graceful-fs": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.2.tgz", + "integrity": "sha512-IItsdsea19BoLC7ELy13q1iJFNmd7ofZH5+X/pJr90/nRoPEX0DJo1dHDbgtYWOhJhcCgMDTOw84RZ72q6lB+Q==", + "dev": true + }, + "graceful-readlink": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz", + "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=", + "dev": true, + "optional": true + }, + "growly": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz", + "integrity": "sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=", + "dev": true + }, + "gulp": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/gulp/-/gulp-4.0.2.tgz", + "integrity": "sha512-dvEs27SCZt2ibF29xYgmnwwCYZxdxhQ/+LFWlbAW8y7jt68L/65402Lz3+CKy0Ov4rOs+NERmDq7YlZaDqUIfA==", + "dev": true, + "requires": { + "glob-watcher": "^5.0.3", + "gulp-cli": "^2.2.0", + "undertaker": "^1.2.1", + "vinyl-fs": "^3.0.0" + } + }, + "gulp-angular-embed-templates": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/gulp-angular-embed-templates/-/gulp-angular-embed-templates-2.3.0.tgz", + "integrity": "sha1-wBDv3VlN7pRRMoNFN9eSOt6EDXk=", + "dev": true, + "requires": { + "gulp-util": "^3.0.6", + "htmlparser2": "~3.9.1", + "minimize": "^2.0.0", + "object-assign": "4.1.0", + "through2": "^2.0.1" + }, + "dependencies": { + "object-assign": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.0.tgz", + "integrity": "sha1-ejs9DpgGPUP0wD8uiubNUahog6A=", + "dev": true + } + } + }, + "gulp-babel": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/gulp-babel/-/gulp-babel-8.0.0.tgz", + "integrity": "sha1-4NqW9PLsSojdOjAw9HbjirISbYc=", + "dev": true, + "requires": { + "plugin-error": "^1.0.1", + "replace-ext": "^1.0.0", + "through2": "^2.0.0", + "vinyl-sourcemaps-apply": "^0.2.0" + }, + "dependencies": { + "replace-ext": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.0.tgz", + "integrity": "sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs=", + "dev": true + } + } + }, + "gulp-clean-css": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/gulp-clean-css/-/gulp-clean-css-4.2.0.tgz", + "integrity": "sha512-r4zQsSOAK2UYUL/ipkAVCTRg/2CLZ2A+oPVORopBximRksJ6qy3EX1KGrIWT4ZrHxz3Hlobb1yyJtqiut7DNjA==", + "dev": true, + "requires": { + "clean-css": "4.2.1", + "plugin-error": "1.0.1", + "through2": "3.0.1", + "vinyl-sourcemaps-apply": "0.2.1" + }, + "dependencies": { + "readable-stream": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.4.0.tgz", + "integrity": "sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "safe-buffer": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz", + "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==", + "dev": true + }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "requires": { + "safe-buffer": "~5.2.0" + } + }, + "through2": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.1.tgz", + "integrity": "sha512-M96dvTalPT3YbYLaKaCuwu+j06D/8Jfib0o/PxbVt6Amhv3dUAtW6rTV1jPgJSBG83I/e04Y6xkVdVhSRhi0ww==", + "dev": true, + "requires": { + "readable-stream": "2 || 3" + } + } + } + }, + "gulp-cli": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/gulp-cli/-/gulp-cli-2.3.0.tgz", + "integrity": "sha512-zzGBl5fHo0EKSXsHzjspp3y5CONegCm8ErO5Qh0UzFzk2y4tMvzLWhoDokADbarfZRL2pGpRp7yt6gfJX4ph7A==", + "dev": true, + "requires": { + "ansi-colors": "^1.0.1", + "archy": "^1.0.0", + "array-sort": "^1.0.0", + "color-support": "^1.1.3", + "concat-stream": "^1.6.0", + "copy-props": "^2.0.1", + "fancy-log": "^1.3.2", + "gulplog": "^1.0.0", + "interpret": "^1.4.0", + "isobject": "^3.0.1", + "liftoff": "^3.1.0", + "matchdep": "^2.0.0", + "mute-stdout": "^1.0.0", + "pretty-hrtime": "^1.0.0", + "replace-homedir": "^1.0.0", + "semver-greatest-satisfied-range": "^1.1.0", + "v8flags": "^3.2.0", + "yargs": "^7.1.0" + } + }, + "gulp-concat": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/gulp-concat/-/gulp-concat-2.6.1.tgz", + "integrity": "sha1-Yz0WyV2IUEYorQJmVmPO5aR5M1M=", + "dev": true, + "requires": { + "concat-with-sourcemaps": "^1.0.0", + "through2": "^2.0.0", + "vinyl": "^2.0.0" + }, + "dependencies": { + "clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=", + "dev": true + }, + "clone-stats": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-1.0.0.tgz", + "integrity": "sha1-s3gt/4u1R04Yuba/D9/ngvh3doA=", + "dev": true + }, + "replace-ext": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.0.tgz", + "integrity": "sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs=", + "dev": true + }, + "vinyl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.0.tgz", + "integrity": "sha1-2FsH2pbkWNJbL/4Z/s6fLKoT7YY=", + "dev": true, + "requires": { + "clone": "^2.1.1", + "clone-buffer": "^1.0.0", + "clone-stats": "^1.0.0", + "cloneable-readable": "^1.0.0", + "remove-trailing-separator": "^1.0.1", + "replace-ext": "^1.0.0" + } + } + } + }, + "gulp-eslint": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/gulp-eslint/-/gulp-eslint-6.0.0.tgz", + "integrity": "sha512-dCVPSh1sA+UVhn7JSQt7KEb4An2sQNbOdB3PA8UCfxsoPlAKjJHxYHGXdXC7eb+V1FAnilSFFqslPrq037l1ig==", + "dev": true, + "requires": { + "eslint": "^6.0.0", + "fancy-log": "^1.3.2", + "plugin-error": "^1.0.1" + } + }, + "gulp-imagemin": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/gulp-imagemin/-/gulp-imagemin-6.1.1.tgz", + "integrity": "sha512-fqaSR8bMc5lhqa6HzRPuJaDY6lY7rcCipe6WtqQ5+hNYTCSPYjXic+1gvFG1+8X879gjVJmIxwmqIbfjuMqTpQ==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "fancy-log": "^1.3.2", + "imagemin": "^7.0.0", + "imagemin-gifsicle": "^6.0.1", + "imagemin-jpegtran": "^6.0.0", + "imagemin-optipng": "^7.0.0", + "imagemin-svgo": "^7.0.0", + "plugin-error": "^1.0.1", + "plur": "^3.0.1", + "pretty-bytes": "^5.3.0", + "through2-concurrent": "^2.0.0" + } + }, + "gulp-less": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/gulp-less/-/gulp-less-4.0.1.tgz", + "integrity": "sha1-NIwzpd3nogfFdxsdgmHRrBAhzu0=", + "dev": true, + "requires": { + "accord": "^0.29.0", + "less": "2.6.x || ^3.7.1", + "object-assign": "^4.0.1", + "plugin-error": "^0.1.2", + "replace-ext": "^1.0.0", + "through2": "^2.0.0", + "vinyl-sourcemaps-apply": "^0.2.0" + }, + "dependencies": { + "arr-diff": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-1.1.0.tgz", + "integrity": "sha1-aHwydYFjWI/vfeezb6vklesaOZo=", + "dev": true, + "requires": { + "arr-flatten": "^1.0.1", + "array-slice": "^0.2.3" + } + }, + "arr-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-2.1.0.tgz", + "integrity": "sha1-IPnqtexw9cfSFbEHexw5Fh0pLH0=", + "dev": true + }, + "array-slice": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-0.2.3.tgz", + "integrity": "sha1-3Tz7gO15c6dRF82sabC5nshhhvU=", + "dev": true + }, + "extend-shallow": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-1.1.4.tgz", + "integrity": "sha1-Gda/lN/AnXa6cR85uHLSH/TdkHE=", + "dev": true, + "requires": { + "kind-of": "^1.1.0" + } + }, + "kind-of": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-1.1.0.tgz", + "integrity": "sha1-FAo9LUGjbS78+pN3tiwk+ElaXEQ=", + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true + }, + "plugin-error": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-0.1.2.tgz", + "integrity": "sha1-O5uzM1zPAPQl4HQ34ZJ2ln2kes4=", + "dev": true, + "requires": { + "ansi-cyan": "^0.1.1", + "ansi-red": "^0.1.1", + "arr-diff": "^1.0.1", + "arr-union": "^2.0.1", + "extend-shallow": "^1.1.2" + } + }, + "replace-ext": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.0.tgz", + "integrity": "sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs=", + "dev": true + } + } + }, + "gulp-notify": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/gulp-notify/-/gulp-notify-3.2.0.tgz", + "integrity": "sha1-KugiUAnfiB7vWb5d1aLxM3OHdk4=", + "dev": true, + "requires": { + "ansi-colors": "^1.0.1", + "fancy-log": "^1.3.2", + "lodash.template": "^4.4.0", + "node-notifier": "^5.2.1", + "node.extend": "^2.0.0", + "plugin-error": "^0.1.2", + "through2": "^2.0.3" + }, + "dependencies": { + "arr-diff": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-1.1.0.tgz", + "integrity": "sha1-aHwydYFjWI/vfeezb6vklesaOZo=", + "dev": true, + "requires": { + "arr-flatten": "^1.0.1", + "array-slice": "^0.2.3" + } + }, + "arr-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-2.1.0.tgz", + "integrity": "sha1-IPnqtexw9cfSFbEHexw5Fh0pLH0=", + "dev": true + }, + "array-slice": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-0.2.3.tgz", + "integrity": "sha1-3Tz7gO15c6dRF82sabC5nshhhvU=", + "dev": true + }, + "extend-shallow": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-1.1.4.tgz", + "integrity": "sha1-Gda/lN/AnXa6cR85uHLSH/TdkHE=", + "dev": true, + "requires": { + "kind-of": "^1.1.0" + } + }, + "kind-of": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-1.1.0.tgz", + "integrity": "sha1-FAo9LUGjbS78+pN3tiwk+ElaXEQ=", + "dev": true + }, + "lodash.template": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-4.5.0.tgz", + "integrity": "sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A==", + "dev": true, + "requires": { + "lodash._reinterpolate": "^3.0.0", + "lodash.templatesettings": "^4.0.0" + } + }, + "lodash.templatesettings": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-4.2.0.tgz", + "integrity": "sha512-stgLz+i3Aa9mZgnjr/O+v9ruKZsPsndy7qPZOchbqk2cnTU1ZaldKK+v7m54WoKIyxiuMZTKT2H81F8BeAc3ZQ==", + "dev": true, + "requires": { + "lodash._reinterpolate": "^3.0.0" + } + }, + "plugin-error": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-0.1.2.tgz", + "integrity": "sha1-O5uzM1zPAPQl4HQ34ZJ2ln2kes4=", + "dev": true, + "requires": { + "ansi-cyan": "^0.1.1", + "ansi-red": "^0.1.1", + "arr-diff": "^1.0.1", + "arr-union": "^2.0.1", + "extend-shallow": "^1.1.2" + } + } + } + }, + "gulp-postcss": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/gulp-postcss/-/gulp-postcss-8.0.0.tgz", + "integrity": "sha1-jTdyzU0nvKVeyMtMjlduO95NxVA=", + "dev": true, + "requires": { + "fancy-log": "^1.3.2", + "plugin-error": "^1.0.1", + "postcss": "^7.0.2", + "postcss-load-config": "^2.0.0", + "vinyl-sourcemaps-apply": "^0.2.1" + } + }, + "gulp-rename": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/gulp-rename/-/gulp-rename-1.4.0.tgz", + "integrity": "sha1-3hxxjnxAla6GH3KW708ySGSCQL0=", + "dev": true + }, + "gulp-sort": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/gulp-sort/-/gulp-sort-2.0.0.tgz", + "integrity": "sha1-xnYqLx8N4KP8WVohWZ0/rI26Gso=", + "dev": true, + "requires": { + "through2": "^2.0.1" + } + }, + "gulp-sourcemaps": { + "version": "2.6.5", + "resolved": "https://registry.npmjs.org/gulp-sourcemaps/-/gulp-sourcemaps-2.6.5.tgz", + "integrity": "sha512-SYLBRzPTew8T5Suh2U8jCSDKY+4NARua4aqjj8HOysBh2tSgT9u4jc1FYirAdPx1akUxxDeK++fqw6Jg0LkQRg==", + "dev": true, + "requires": { + "@gulp-sourcemaps/identity-map": "1.X", + "@gulp-sourcemaps/map-sources": "1.X", + "acorn": "5.X", + "convert-source-map": "1.X", + "css": "2.X", + "debug-fabulous": "1.X", + "detect-newline": "2.X", + "graceful-fs": "4.X", + "source-map": "~0.6.0", + "strip-bom-string": "1.X", + "through2": "2.X" + }, + "dependencies": { + "acorn": { + "version": "5.7.4", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.4.tgz", + "integrity": "sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "gulp-util": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/gulp-util/-/gulp-util-3.0.8.tgz", + "integrity": "sha1-AFTh50RQLifATBh8PsxQXdVLu08=", + "dev": true, + "requires": { + "array-differ": "^1.0.0", + "array-uniq": "^1.0.2", + "beeper": "^1.0.0", + "chalk": "^1.0.0", + "dateformat": "^2.0.0", + "fancy-log": "^1.1.0", + "gulplog": "^1.0.0", + "has-gulplog": "^0.1.0", + "lodash._reescape": "^3.0.0", + "lodash._reevaluate": "^3.0.0", + "lodash._reinterpolate": "^3.0.0", + "lodash.template": "^3.0.0", + "minimist": "^1.1.0", + "multipipe": "^0.1.2", + "object-assign": "^3.0.0", + "replace-ext": "0.0.1", + "through2": "^2.0.0", + "vinyl": "^0.5.0" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + } + } + }, + "gulp-watch": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/gulp-watch/-/gulp-watch-5.0.1.tgz", + "integrity": "sha1-g9N4dS9b+0baAj5zwX7R2nBmIV0=", + "dev": true, + "requires": { + "ansi-colors": "1.1.0", + "anymatch": "^1.3.0", + "chokidar": "^2.0.0", + "fancy-log": "1.3.2", + "glob-parent": "^3.0.1", + "object-assign": "^4.1.0", + "path-is-absolute": "^1.0.1", + "plugin-error": "1.0.1", + "readable-stream": "^2.2.2", + "slash": "^1.0.0", + "vinyl": "^2.1.0", + "vinyl-file": "^2.0.0" + }, + "dependencies": { + "clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=", + "dev": true + }, + "clone-stats": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-1.0.0.tgz", + "integrity": "sha1-s3gt/4u1R04Yuba/D9/ngvh3doA=", + "dev": true + }, + "fancy-log": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fancy-log/-/fancy-log-1.3.2.tgz", + "integrity": "sha1-9BEl49hPLn2JpD0G2VjI94vha+E=", + "dev": true, + "requires": { + "ansi-gray": "^0.1.1", + "color-support": "^1.1.3", + "time-stamp": "^1.0.0" + } + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha1-sRwn2IuP8fvgcGQ8+UsMea4bCq8=", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "replace-ext": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.0.tgz", + "integrity": "sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs=", + "dev": true + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha1-nPFhG6YmhdcDCunkujQUnDrwP8g=", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "vinyl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.0.tgz", + "integrity": "sha1-2FsH2pbkWNJbL/4Z/s6fLKoT7YY=", + "dev": true, + "requires": { + "clone": "^2.1.1", + "clone-buffer": "^1.0.0", + "clone-stats": "^1.0.0", + "cloneable-readable": "^1.0.0", + "remove-trailing-separator": "^1.0.1", + "replace-ext": "^1.0.0" + } + } + } + }, + "gulp-wrap": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/gulp-wrap/-/gulp-wrap-0.15.0.tgz", + "integrity": "sha512-f17zkGObA+hE/FThlg55gfA0nsXbdmHK1WqzjjB2Ytq1TuhLR7JiCBJ3K4AlMzCyoFaCjfowos+VkToUNE0WTQ==", + "dev": true, + "requires": { + "consolidate": "^0.15.1", + "es6-promise": "^4.2.6", + "fs-readfile-promise": "^3.0.1", + "js-yaml": "^3.13.0", + "lodash": "^4.17.11", + "node.extend": "2.0.2", + "plugin-error": "^1.0.1", + "through2": "^3.0.1", + "tryit": "^1.0.1", + "vinyl-bufferstream": "^1.0.1" + }, + "dependencies": { + "readable-stream": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.4.0.tgz", + "integrity": "sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "safe-buffer": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz", + "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==", + "dev": true + }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "requires": { + "safe-buffer": "~5.2.0" + } + }, + "through2": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.1.tgz", + "integrity": "sha512-M96dvTalPT3YbYLaKaCuwu+j06D/8Jfib0o/PxbVt6Amhv3dUAtW6rTV1jPgJSBG83I/e04Y6xkVdVhSRhi0ww==", + "dev": true, + "requires": { + "readable-stream": "2 || 3" + } + } + } + }, + "gulp-wrap-js": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/gulp-wrap-js/-/gulp-wrap-js-0.4.1.tgz", + "integrity": "sha1-3uYqpISqupVHqT0f9c0MPQvtwDE=", + "dev": true, + "requires": { + "escodegen": "^1.6.1", + "esprima": "^2.3.0", + "estemplate": "*", + "gulp-util": "~3.0.5", + "through2": "*", + "vinyl-sourcemaps-apply": "^0.1.4" + }, + "dependencies": { + "esprima": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", + "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=", + "dev": true + }, + "source-map": { + "version": "0.1.43", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.43.tgz", + "integrity": "sha1-wkvBRspRfBRx9drL4lcbK3+eM0Y=", + "dev": true, + "requires": { + "amdefine": ">=0.0.4" + } + }, + "vinyl-sourcemaps-apply": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/vinyl-sourcemaps-apply/-/vinyl-sourcemaps-apply-0.1.4.tgz", + "integrity": "sha1-xfy9Q+LyOEI8LcmL3db3m3K8NFs=", + "dev": true, + "requires": { + "source-map": "^0.1.39" + } + } + } + }, + "gulplog": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/gulplog/-/gulplog-1.0.0.tgz", + "integrity": "sha1-4oxNRdBey77YGDY86PnFkmIp/+U=", + "dev": true, + "requires": { + "glogg": "^1.0.0" + } + }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", + "dev": true + }, + "har-validator": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", + "integrity": "sha1-HvievT5JllV2de7ZiTEQ3DUPoIA=", + "dev": true, + "requires": { + "ajv": "^6.5.5", + "har-schema": "^2.0.0" + } + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha1-ci18v8H2qoJB8W3YFOAR4fQeh5Y=", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "has-binary2": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-binary2/-/has-binary2-1.0.3.tgz", + "integrity": "sha1-d3asYn8+p3JQz8My2rfd9eT10R0=", + "dev": true, + "requires": { + "isarray": "2.0.1" + }, + "dependencies": { + "isarray": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", + "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=", + "dev": true + } + } + }, + "has-cors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz", + "integrity": "sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk=", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "has-gulplog": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/has-gulplog/-/has-gulplog-0.1.0.tgz", + "integrity": "sha1-ZBTIKRNpfaUVkDl9r7EvIpZ4Ec4=", + "dev": true, + "requires": { + "sparkles": "^1.0.0" + } + }, + "has-symbol-support-x": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/has-symbol-support-x/-/has-symbol-support-x-1.4.2.tgz", + "integrity": "sha512-3ToOva++HaW+eCpgqZrCfN51IPB+7bJNVT6CUATzueB5Heb8o6Nam0V3HG5dlDvZU1Gn5QLcbahiKw/XVk5JJw==", + "dev": true, + "optional": true + }, + "has-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz", + "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=", + "dev": true + }, + "has-to-string-tag-x": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/has-to-string-tag-x/-/has-to-string-tag-x-1.4.1.tgz", + "integrity": "sha512-vdbKfmw+3LoOYVr+mtxHaX5a96+0f3DljYd8JOqvOLsf5mw2Otda2qCDT9qRqLAhrjyQ0h7ual5nOiASpsGNFw==", + "dev": true, + "optional": true, + "requires": { + "has-symbol-support-x": "^1.4.1" + } + }, + "has-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", + "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", + "dev": true, + "requires": { + "get-value": "^2.0.6", + "has-values": "^1.0.0", + "isobject": "^3.0.0" + } + }, + "has-values": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", + "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "kind-of": "^4.0.0" + }, + "dependencies": { + "kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "hex-color-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/hex-color-regex/-/hex-color-regex-1.1.0.tgz", + "integrity": "sha1-TAb8y0YC/iYCs8k9+C1+fb8aio4=", + "dev": true + }, + "homedir-polyfill": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", + "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==", + "dev": true, + "requires": { + "parse-passwd": "^1.0.0" + } + }, + "hosted-git-info": { + "version": "2.8.4", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.4.tgz", + "integrity": "sha512-pzXIvANXEFrc5oFFXRMkbLPQ2rXRoDERwDLyrcUxGhaZhgP54BBSl9Oheh7Vv0T090cszWBxPjkQQ5Sq1PbBRQ==", + "dev": true + }, + "hsl-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/hsl-regex/-/hsl-regex-1.0.0.tgz", + "integrity": "sha1-1JMwx4ntgZ4nakwNJy3/owsY/m4=", + "dev": true + }, + "hsla-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/hsla-regex/-/hsla-regex-1.0.0.tgz", + "integrity": "sha1-wc56MWjIxmFAM6S194d/OyJfnDg=", + "dev": true + }, + "html-comment-regex": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/html-comment-regex/-/html-comment-regex-1.1.2.tgz", + "integrity": "sha1-l9RoiutcgYhqNk+qDK0d2hTUM6c=", + "dev": true + }, + "html-encoding-sniffer": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz", + "integrity": "sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ==", + "dev": true, + "requires": { + "whatwg-encoding": "^1.0.5" + } + }, + "htmlparser2": { + "version": "3.9.2", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.9.2.tgz", + "integrity": "sha1-G9+HrMoPP55T+k/M6w9LTLsAszg=", + "dev": true, + "requires": { + "domelementtype": "^1.3.0", + "domhandler": "^2.3.0", + "domutils": "^1.5.1", + "entities": "^1.1.1", + "inherits": "^2.0.1", + "readable-stream": "^2.0.2" + }, + "dependencies": { + "entities": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", + "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==", + "dev": true + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha1-sRwn2IuP8fvgcGQ8+UsMea4bCq8=", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha1-nPFhG6YmhdcDCunkujQUnDrwP8g=", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "http-cache-semantics": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz", + "integrity": "sha512-5ai2iksyV8ZXmnZhHH4rWPoxxistEexSi5936zIQ1bnNTW5VnA85B6P/VpXiRM017IgRvb2kKo1a//y+0wSp3w==", + "dev": true, + "optional": true + }, + "http-errors": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", + "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", + "dev": true, + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.1", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + }, + "dependencies": { + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + } + } + }, + "http-proxy": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.0.tgz", + "integrity": "sha512-84I2iJM/n1d4Hdgc6y2+qY5mDaz2PUVjlg9znE9byl+q0uC3DeByqBGReQu5tpLK0TAqTIXScRUV+dg7+bUPpQ==", + "dev": true, + "requires": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + } + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha1-ICK0sl+93CHS9SSXSkdKr+czkIs=", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ieee754": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", + "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==", + "dev": true, + "optional": true + }, + "ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha1-dQ49tYYgh7RzfrrIIH/9HvJ7Jfw=", + "dev": true + }, + "image-size": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz", + "integrity": "sha1-Cd/Uq50g4p6xw+gLiZA3jfnjy5w=", + "dev": true, + "optional": true + }, + "imagemin": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/imagemin/-/imagemin-7.0.0.tgz", + "integrity": "sha512-TXvCSSIYl4KQUASur9S0+E4olVECzvxvZABU9rNqsza7vzIrUQMRTjyczGf8OmtcgvZ9jOYyinXW3epOpd/04A==", + "dev": true, + "requires": { + "file-type": "^12.0.0", + "globby": "^10.0.0", + "junk": "^3.1.0", + "make-dir": "^3.0.0", + "p-pipe": "^3.0.0", + "replace-ext": "^1.0.0" + }, + "dependencies": { + "replace-ext": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.0.tgz", + "integrity": "sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs=", + "dev": true + } + } + }, + "imagemin-gifsicle": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/imagemin-gifsicle/-/imagemin-gifsicle-6.0.1.tgz", + "integrity": "sha512-kuu47c6iKDQ6R9J10xCwL0lgs0+sMz3LRHqRcJ2CRBWdcNmo3T5hUaM8hSZfksptZXJLGKk8heSAvwtSdB1Fng==", + "dev": true, + "optional": true, + "requires": { + "exec-buffer": "^3.0.0", + "gifsicle": "^4.0.0", + "is-gif": "^3.0.0" + } + }, + "imagemin-jpegtran": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/imagemin-jpegtran/-/imagemin-jpegtran-6.0.0.tgz", + "integrity": "sha512-Ih+NgThzqYfEWv9t58EItncaaXIHR0u9RuhKa8CtVBlMBvY0dCIxgQJQCfwImA4AV1PMfmUKlkyIHJjb7V4z1g==", + "dev": true, + "optional": true, + "requires": { + "exec-buffer": "^3.0.0", + "is-jpg": "^2.0.0", + "jpegtran-bin": "^4.0.0" + } + }, + "imagemin-optipng": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/imagemin-optipng/-/imagemin-optipng-7.1.0.tgz", + "integrity": "sha512-JNORTZ6j6untH7e5gF4aWdhDCxe3ODsSLKs/f7Grewy3ebZpl1ZsU+VUTPY4rzeHgaFA8GSWOoA8V2M3OixWZQ==", + "dev": true, + "optional": true, + "requires": { + "exec-buffer": "^3.0.0", + "is-png": "^2.0.0", + "optipng-bin": "^6.0.0" + } + }, + "imagemin-svgo": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/imagemin-svgo/-/imagemin-svgo-7.0.0.tgz", + "integrity": "sha512-+iGJFaPIMx8TjFW6zN+EkOhlqcemdL7F3N3Y0wODvV2kCUBuUtZK7DRZc1+Zfu4U2W/lTMUyx2G8YMOrZntIWg==", + "dev": true, + "optional": true, + "requires": { + "is-svg": "^3.0.0", + "svgo": "^1.0.5" + } + }, + "import-cwd": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/import-cwd/-/import-cwd-2.1.0.tgz", + "integrity": "sha1-qmzzbnInYShcs3HsZRn1PiQ1sKk=", + "dev": true, + "requires": { + "import-from": "^2.1.0" + } + }, + "import-fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", + "integrity": "sha1-2BNVwVYS04bGH53dOSLUMEgipUY=", + "dev": true, + "requires": { + "caller-path": "^2.0.0", + "resolve-from": "^3.0.0" + } + }, + "import-from": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/import-from/-/import-from-2.1.0.tgz", + "integrity": "sha1-M1238qev/VOqpHHUuAId7ja387E=", + "dev": true, + "requires": { + "resolve-from": "^3.0.0" + } + }, + "import-lazy": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-3.1.0.tgz", + "integrity": "sha512-8/gvXvX2JMn0F+CDlSC4l6kOmVaLOO3XLkksI7CI3Ud95KDYJuYur2b9P/PUt/i/pDAMd/DulQsNbbbmRRsDIQ==", + "dev": true, + "optional": true + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true + }, + "indent-string": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", + "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", + "dev": true, + "optional": true, + "requires": { + "repeating": "^2.0.0" + } + }, + "indexes-of": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/indexes-of/-/indexes-of-1.0.1.tgz", + "integrity": "sha1-8w9xbI4r00bHtn0985FVZqfAVgc=", + "dev": true + }, + "indexof": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", + "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=", + "dev": true + }, + "indx": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/indx/-/indx-0.2.3.tgz", + "integrity": "sha1-Fdz1bunPZcAjTFE8J/vVgOcPvFA=", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "ini": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", + "integrity": "sha1-7uJfVtscnsYIXgwid4CD9Zar+Sc=", + "dev": true + }, + "inquirer": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.5.2.tgz", + "integrity": "sha512-cntlB5ghuB0iuO65Ovoi8ogLHiWGs/5yNrtUcKjFhSSiVeAIVpD7koaSU9RM8mpXw5YDi9RdYXGQMaOURB7ycQ==", + "dev": true, + "requires": { + "ansi-escapes": "^3.2.0", + "chalk": "^2.4.2", + "cli-cursor": "^2.1.0", + "cli-width": "^2.0.0", + "external-editor": "^3.0.3", + "figures": "^2.0.0", + "lodash": "^4.17.12", + "mute-stream": "0.0.7", + "run-async": "^2.2.0", + "rxjs": "^6.4.0", + "string-width": "^2.1.0", + "strip-ansi": "^5.1.0", + "through": "^2.3.6" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + }, + "dependencies": { + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + } + } + } + } + }, + "interpret": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", + "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", + "dev": true + }, + "into-stream": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/into-stream/-/into-stream-3.1.0.tgz", + "integrity": "sha1-lvsKk2wSur1v8XUqF9BWFqvQlMY=", + "dev": true, + "optional": true, + "requires": { + "from2": "^2.1.1", + "p-is-promise": "^1.1.0" + } + }, + "invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha1-YQ88ksk1nOHbYW5TgAjSP/NRWOY=", + "dev": true, + "requires": { + "loose-envify": "^1.0.0" + } + }, + "invert-kv": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", + "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=", + "dev": true + }, + "ip-regex": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz", + "integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=", + "dev": true + }, + "irregular-plurals": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/irregular-plurals/-/irregular-plurals-2.0.0.tgz", + "integrity": "sha512-Y75zBYLkh0lJ9qxeHlMjQ7bSbyiSqNW/UOPWDmzC7cXskL1hekSITh1Oc6JV0XCWWZ9DE8VYSB71xocLk3gmGw==", + "dev": true + }, + "is": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/is/-/is-3.3.0.tgz", + "integrity": "sha512-nW24QBoPcFGGHJGUwnfpI7Yc5CdqWNdsyHQszVE/z2pKHXzh7FZ5GWhJqSyaQ9wMkQnsTx+kAI8bHlCX4tKdbg==", + "dev": true + }, + "is-absolute": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz", + "integrity": "sha1-OV4a6EsR8mrReV5zwXN45IowFXY=", + "dev": true, + "requires": { + "is-relative": "^1.0.0", + "is-windows": "^1.0.1" + } + }, + "is-absolute-url": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-2.1.0.tgz", + "integrity": "sha1-UFMN+4T8yap9vnhS6Do3uTufKqY=", + "dev": true + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + }, + "is-binary-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", + "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", + "dev": true, + "requires": { + "binary-extensions": "^1.0.0" + } + }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha1-76ouqdqg16suoTqXsritUf776L4=", + "dev": true + }, + "is-callable": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz", + "integrity": "sha1-HhrfIZ4e62hNaR+dagX/DTCiTXU=", + "dev": true + }, + "is-color-stop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-color-stop/-/is-color-stop-1.1.0.tgz", + "integrity": "sha1-z/9HGu5N1cnhWFmPvhKWe1za00U=", + "dev": true, + "requires": { + "css-color-names": "^0.0.4", + "hex-color-regex": "^1.1.0", + "hsl-regex": "^1.0.0", + "hsla-regex": "^1.0.0", + "rgb-regex": "^1.0.1", + "rgba-regex": "^1.0.0" + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-date-object": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", + "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=", + "dev": true + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha1-Nm2CQN3kh8pRgjsaufB6EKeCUco=", + "dev": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha1-cpyR4thXt6QZofmqZWhcTDP1hF0=", + "dev": true + } + } + }, + "is-directory": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz", + "integrity": "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=", + "dev": true + }, + "is-dotfile": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.3.tgz", + "integrity": "sha1-pqLzL/0t+wT1yiXs0Pa4PPeYoeE=", + "dev": true + }, + "is-equal-shallow": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz", + "integrity": "sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ=", + "dev": true, + "requires": { + "is-primitive": "^2.0.0" + } + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "dev": true + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-finite": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz", + "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=", + "dev": true, + "optional": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "is-gif": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-gif/-/is-gif-3.0.0.tgz", + "integrity": "sha512-IqJ/jlbw5WJSNfwQ/lHEDXF8rxhRgF6ythk2oiEvhpG29F704eX9NO6TvPfMiq9DrbwgcEDnETYNcZDPewQoVw==", + "dev": true, + "optional": true, + "requires": { + "file-type": "^10.4.0" + }, + "dependencies": { + "file-type": { + "version": "10.11.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-10.11.0.tgz", + "integrity": "sha512-uzk64HRpUZyTGZtVuvrjP0FYxzQrBf4rojot6J65YMEbwBLB0CWm0CLojVpwpmFmxcE/lkvYICgfcGozbBq6rw==", + "dev": true, + "optional": true + } + } + }, + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "dev": true, + "requires": { + "is-extglob": "^2.1.0" + } + }, + "is-jpg": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-jpg/-/is-jpg-2.0.0.tgz", + "integrity": "sha1-LhmX+m6RZuqsAkLarkQ0A+TvHZc=", + "dev": true, + "optional": true + }, + "is-natural-number": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-natural-number/-/is-natural-number-4.0.1.tgz", + "integrity": "sha1-q5124dtM7VHjXeDHLr7PCfc0zeg=", + "dev": true, + "optional": true + }, + "is-negated-glob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-negated-glob/-/is-negated-glob-1.0.0.tgz", + "integrity": "sha1-aRC8pdqMleeEtXUbl2z1oQ/uNtI=", + "dev": true + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", + "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=", + "dev": true + }, + "is-object": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-object/-/is-object-1.0.1.tgz", + "integrity": "sha1-iVJojF7C/9awPsyF52ngKQMINHA=", + "dev": true, + "optional": true + }, + "is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=", + "dev": true, + "optional": true + }, + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha1-LBY7P6+xtgbZ0Xko8FwqHDjgdnc=", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } + }, + "is-png": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-png/-/is-png-2.0.0.tgz", + "integrity": "sha512-4KPGizaVGj2LK7xwJIz8o5B2ubu1D/vcQsgOGFEDlpcvgZHto4gBnyd0ig7Ws+67ixmwKoNmu0hYnpo6AaKb5g==", + "dev": true, + "optional": true + }, + "is-posix-bracket": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz", + "integrity": "sha1-MzTceXdDaOkvAW5vvAqI9c1ua8Q=", + "dev": true + }, + "is-potential-custom-element-name": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.0.tgz", + "integrity": "sha1-DFLlS8yjkbssSUsh6GJtczbG45c=", + "dev": true + }, + "is-primitive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz", + "integrity": "sha1-IHurkWOEmcB7Kt8kCkGochADRXU=", + "dev": true + }, + "is-promise": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", + "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", + "dev": true + }, + "is-regex": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", + "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", + "dev": true, + "requires": { + "has": "^1.0.1" + } + }, + "is-relative": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz", + "integrity": "sha1-obtpNc6MXboei5dUubLcwCDiJg0=", + "dev": true, + "requires": { + "is-unc-path": "^1.0.0" + } + }, + "is-resolvable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", + "integrity": "sha1-+xj4fOH+uSUWnJpAfBkxijIG7Yg=", + "dev": true + }, + "is-retry-allowed": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz", + "integrity": "sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg==", + "dev": true, + "optional": true + }, + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", + "dev": true, + "optional": true + }, + "is-svg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-svg/-/is-svg-3.0.0.tgz", + "integrity": "sha1-kyHb0pwhLlypnE+peUxxS8r6L3U=", + "dev": true, + "requires": { + "html-comment-regex": "^1.1.0" + } + }, + "is-symbol": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz", + "integrity": "sha1-oFX2rlcZLK7jKeeoYBGLSXqVDzg=", + "dev": true, + "requires": { + "has-symbols": "^1.0.0" + } + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "dev": true + }, + "is-unc-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz", + "integrity": "sha1-1zHoiY7QkKEsNSrS6u1Qla0yLJ0=", + "dev": true, + "requires": { + "unc-path-regex": "^0.1.2" + } + }, + "is-utf8": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", + "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", + "dev": true + }, + "is-valid-glob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-valid-glob/-/is-valid-glob-1.0.0.tgz", + "integrity": "sha1-Kb8+/3Ab4tTTFdusw5vDn+j2Aao=", + "dev": true + }, + "is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha1-0YUOuXkezRjmGCzhKjDzlmNLsZ0=", + "dev": true + }, + "is-wsl": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", + "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=", + "dev": true + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "isbinaryfile": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-3.0.3.tgz", + "integrity": "sha1-XW3vPt6/boyoyunDAYOoBLX4voA=", + "dev": true, + "requires": { + "buffer-alloc": "^1.2.0" + } + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", + "dev": true + }, + "isurl": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isurl/-/isurl-1.0.0.tgz", + "integrity": "sha512-1P/yWsxPlDtn7QeRD+ULKQPaIaN6yF368GZ2vDfv0AL0NwpStafjWCDDdn0k8wgFMWpVAqG7oJhxHnlud42i9w==", + "dev": true, + "optional": true, + "requires": { + "has-to-string-tag-x": "^1.2.0", + "is-object": "^1.0.1" + } + }, + "jasmine-core": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-3.5.0.tgz", + "integrity": "sha512-nCeAiw37MIMA9w9IXso7bRaLl+c/ef3wnxsoSAlYrzS+Ot0zTG6nU8G/cIfGkqpkjX2wNaIW9RFG0TwIFnG6bA==", + "dev": true + }, + "jpegtran-bin": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jpegtran-bin/-/jpegtran-bin-4.0.0.tgz", + "integrity": "sha512-2cRl1ism+wJUoYAYFt6O/rLBfpXNWG2dUWbgcEkTt5WGMnqI46eEro8T4C5zGROxKRqyKpCBSdHPvt5UYCtxaQ==", + "dev": true, + "optional": true, + "requires": { + "bin-build": "^3.0.0", + "bin-wrapper": "^4.0.0", + "logalot": "^2.0.0" + } + }, + "jquery": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.5.1.tgz", + "integrity": "sha512-XwIBPqcMn57FxfT+Go5pzySnm4KWkT1Tv7gjrpT1srtf8Weynl6R273VJ5GjkRb51IzMp5nbaPjJXMWeju2MKg==" + }, + "jquery-ui-dist": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/jquery-ui-dist/-/jquery-ui-dist-1.12.1.tgz", + "integrity": "sha1-XAgV08xvkP9fqvWyaKbiO0ypBPo=" + }, + "jquery-ui-touch-punch": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/jquery-ui-touch-punch/-/jquery-ui-touch-punch-0.2.3.tgz", + "integrity": "sha1-7tgiQnM7okP0az6HwYQbMIGR2mg=" + }, + "js-levenshtein": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.6.tgz", + "integrity": "sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==", + "dev": true + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha1-GSA/tZmR35jjoocFDUZHzerzJJk=", + "dev": true + }, + "js-yaml": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "dev": true + }, + "jsdom": { + "version": "16.4.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.4.0.tgz", + "integrity": "sha512-lYMm3wYdgPhrl7pDcRmvzPhhrGVBeVhPIqeHjzeiHN3DFmD1RBpbExbi8vU7BJdH8VAZYovR8DMt0PNNDM7k8w==", + "dev": true, + "requires": { + "abab": "^2.0.3", + "acorn": "^7.1.1", + "acorn-globals": "^6.0.0", + "cssom": "^0.4.4", + "cssstyle": "^2.2.0", + "data-urls": "^2.0.0", + "decimal.js": "^10.2.0", + "domexception": "^2.0.1", + "escodegen": "^1.14.1", + "html-encoding-sniffer": "^2.0.1", + "is-potential-custom-element-name": "^1.0.0", + "nwsapi": "^2.2.0", + "parse5": "5.1.1", + "request": "^2.88.2", + "request-promise-native": "^1.0.8", + "saxes": "^5.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^3.0.1", + "w3c-hr-time": "^1.0.2", + "w3c-xmlserializer": "^2.0.0", + "webidl-conversions": "^6.1.0", + "whatwg-encoding": "^1.0.5", + "whatwg-mimetype": "^2.3.0", + "whatwg-url": "^8.0.0", + "ws": "^7.2.3", + "xml-name-validator": "^3.0.0" + }, + "dependencies": { + "acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true + }, + "escodegen": { + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz", + "integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==", + "dev": true, + "requires": { + "esprima": "^4.0.1", + "estraverse": "^4.2.0", + "esutils": "^2.0.2", + "optionator": "^0.8.1", + "source-map": "~0.6.1" + } + }, + "request": { + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", + "dev": true, + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + }, + "dependencies": { + "tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "dev": true, + "requires": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + } + } + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "optional": true + }, + "tough-cookie": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-3.0.1.tgz", + "integrity": "sha512-yQyJ0u4pZsv9D4clxO69OEjLWYw+jbgspjTue4lTQZLfV0c5l1VmK2y1JK8E9ahdpltPOaAThPcp5nKPUgSnsg==", + "dev": true, + "requires": { + "ip-regex": "^2.1.0", + "psl": "^1.1.28", + "punycode": "^2.1.1" + } + }, + "ws": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.1.tgz", + "integrity": "sha512-pTsP8UAfhy3sk1lSk/O/s4tjD0CRwvMnzvwr4OKGX7ZvqZtUyx4KIJB5JWbkykPoc55tixMGgTNoh3k4FkNGFQ==", + "dev": true + } + } + }, + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha1-gFZNLkg9rPbo7yCWUKZ98/DCg6Q=", + "dev": true + }, + "json-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", + "integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=", + "dev": true, + "optional": true + }, + "json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha1-u4Z8+zRQ5pEHwTHRxRS6s9yLyqk=", + "dev": true + }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", + "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha1-afaofZUTq4u4/mO9sJecRI5oRmA=", + "dev": true + }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "dev": true + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "dev": true + }, + "json5": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.1.tgz", + "integrity": "sha512-l+3HXD0GEI3huGq1njuqtzYK8OYJyXMkOLtQ53pjWh89tvWS2h6l+1zMkYWqlb57+SiQodKZyvMEFb2X+KrFhQ==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "dev": true, + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "junk": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/junk/-/junk-3.1.0.tgz", + "integrity": "sha512-pBxcB3LFc8QVgdggvZWyeys+hnrNWg4OcZIU/1X59k5jQdLBlCsYGRQaz234SqoRLTCgMH00fY0xRJH+F9METQ==", + "dev": true + }, + "just-debounce": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/just-debounce/-/just-debounce-1.0.0.tgz", + "integrity": "sha1-h/zPrv/AtozRnVX2cilD+SnqNeo=", + "dev": true + }, + "karma": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/karma/-/karma-4.4.1.tgz", + "integrity": "sha512-L5SIaXEYqzrh6b1wqYC42tNsFMx2PWuxky84pK9coK09MvmL7mxii3G3bZBh/0rvD27lqDd0le9jyhzvwif73A==", + "dev": true, + "requires": { + "bluebird": "^3.3.0", + "body-parser": "^1.16.1", + "braces": "^3.0.2", + "chokidar": "^3.0.0", + "colors": "^1.1.0", + "connect": "^3.6.0", + "di": "^0.0.1", + "dom-serialize": "^2.2.0", + "flatted": "^2.0.0", + "glob": "^7.1.1", + "graceful-fs": "^4.1.2", + "http-proxy": "^1.13.0", + "isbinaryfile": "^3.0.0", + "lodash": "^4.17.14", + "log4js": "^4.0.0", + "mime": "^2.3.1", + "minimatch": "^3.0.2", + "optimist": "^0.6.1", + "qjobs": "^1.1.4", + "range-parser": "^1.2.0", + "rimraf": "^2.6.0", + "safe-buffer": "^5.0.1", + "socket.io": "2.1.1", + "source-map": "^0.6.1", + "tmp": "0.0.33", + "useragent": "2.3.0" + }, + "dependencies": { + "anymatch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", + "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "binary-extensions": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.0.0.tgz", + "integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==", + "dev": true + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "chokidar": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.2.2.tgz", + "integrity": "sha512-bw3pm7kZ2Wa6+jQWYP/c7bAZy3i4GwiIiMO2EeRjrE48l8vBqC/WvFhSF0xyM8fQiPEGvwMY/5bqDG7sSEOuhg==", + "dev": true, + "requires": { + "anymatch": "~3.1.1", + "braces": "~3.0.2", + "fsevents": "~2.1.1", + "glob-parent": "~5.1.0", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.2.0" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "fsevents": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.1.tgz", + "integrity": "sha512-4FRPXWETxtigtJW/gxzEDsX1LVbPAM93VleB83kZB+ellqbHMkyt2aJfuzNLRvFPnGi6bcE5SvfxgbXPeKteJw==", + "dev": true, + "optional": true + }, + "glob-parent": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.0.tgz", + "integrity": "sha512-qjtRgnIVmOfnKUE3NJAQEdk+lKrxfw8t5ke7SXtfMTHcjsBfOfWXCQfdb30zfDoZQ2IRSIiidmjtbHZPZ++Ihw==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "mime": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.4.tgz", + "integrity": "sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA==", + "dev": true + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "readdirp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.2.0.tgz", + "integrity": "sha512-crk4Qu3pmXwgxdSgGhgA/eXiJAPQiX4GMOZZMXnqKxHX7TaoL+3gQVo/WeuAiogr07DpnfjIMpXXa+PAIvwPGQ==", + "dev": true, + "requires": { + "picomatch": "^2.0.4" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM=", + "dev": true + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + } + } + }, + "karma-jasmine": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/karma-jasmine/-/karma-jasmine-2.0.1.tgz", + "integrity": "sha1-JuPjHy+vJy3YDrsOGJiRTMOhl2M=", + "dev": true, + "requires": { + "jasmine-core": "^3.3" + } + }, + "karma-jsdom-launcher": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/karma-jsdom-launcher/-/karma-jsdom-launcher-8.0.2.tgz", + "integrity": "sha512-jxO+Nf9U/XNSnHXrNpxBbbMyeYuvJH1V++bRdZv20vJ9pvaLuQ6LFNIgn4hA1WAVmzMsvW9j0P2Q2hTLMWcSvw==", + "dev": true + }, + "karma-junit-reporter": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/karma-junit-reporter/-/karma-junit-reporter-2.0.1.tgz", + "integrity": "sha512-VtcGfE0JE4OE1wn0LK8xxDKaTP7slN8DO3I+4xg6gAi1IoAHAXOJ1V9G/y45Xg6sxdxPOR3THCFtDlAfBo9Afw==", + "dev": true, + "requires": { + "path-is-absolute": "^1.0.0", + "xmlbuilder": "12.0.0" + } + }, + "karma-spec-reporter": { + "version": "0.0.32", + "resolved": "https://registry.npmjs.org/karma-spec-reporter/-/karma-spec-reporter-0.0.32.tgz", + "integrity": "sha1-LpxyB+pyZ3EmAln4K+y1QyCeRAo=", + "dev": true, + "requires": { + "colors": "^1.1.2" + } + }, + "keyv": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.0.0.tgz", + "integrity": "sha512-eguHnq22OE3uVoSYG0LVWNP+4ppamWr9+zWBe1bsNcovIMy6huUJFPgy4mGwCd/rnl3vOLGW1MTlu4c57CT1xA==", + "dev": true, + "optional": true, + "requires": { + "json-buffer": "3.0.0" + } + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha1-ARRrNqYhjmTljzqNZt5df8b20FE=", + "dev": true + }, + "kuler": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/kuler/-/kuler-1.0.1.tgz", + "integrity": "sha1-73x4TzbJ+24W3TFQ0VJneysCKKY=", + "dev": true, + "requires": { + "colornames": "^1.1.1" + } + }, + "last-run": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/last-run/-/last-run-1.1.1.tgz", + "integrity": "sha1-RblpQsF7HHnHchmCWbqUO+v4yls=", + "dev": true, + "requires": { + "default-resolution": "^2.0.0", + "es6-weak-map": "^2.0.1" + } + }, + "lazy-cache": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", + "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=", + "dev": true + }, + "lazyload-js": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lazyload-js/-/lazyload-js-1.0.0.tgz", + "integrity": "sha1-jBA5sbaRec1J/cMkICOvSM4IOSU=" + }, + "lazystream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.0.tgz", + "integrity": "sha1-9plf4PggOS9hOWvolGJAe7dxaOQ=", + "dev": true, + "requires": { + "readable-stream": "^2.0.5" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha1-sRwn2IuP8fvgcGQ8+UsMea4bCq8=", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha1-nPFhG6YmhdcDCunkujQUnDrwP8g=", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "lcid": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", + "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", + "dev": true, + "requires": { + "invert-kv": "^1.0.0" + } + }, + "lead": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lead/-/lead-1.0.0.tgz", + "integrity": "sha1-bxT5mje+Op3XhPVJVpDlkDRm7kI=", + "dev": true, + "requires": { + "flush-write-stream": "^1.0.2" + } + }, + "less": { + "version": "3.10.3", + "resolved": "https://registry.npmjs.org/less/-/less-3.10.3.tgz", + "integrity": "sha512-vz32vqfgmoxF1h3K4J+yKCtajH0PWmjkIFgbs5d78E/c/e+UQTnI+lWK+1eQRE95PXM2mC3rJlLSSP9VQHnaow==", + "dev": true, + "requires": { + "clone": "^2.1.2", + "errno": "^0.1.1", + "graceful-fs": "^4.1.2", + "image-size": "~0.5.0", + "mime": "^1.4.1", + "mkdirp": "^0.5.0", + "promise": "^7.1.1", + "request": "^2.83.0", + "source-map": "~0.6.0" + }, + "dependencies": { + "clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM=", + "dev": true, + "optional": true + } + } + }, + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + } + }, + "liftoff": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/liftoff/-/liftoff-3.1.0.tgz", + "integrity": "sha512-DlIPlJUkCV0Ips2zf2pJP0unEoT1kwYhiiPUGF3s/jtxTCjziNLoiVVh+jqWOWeFi6mmwQ5fNxvAUyPad4Dfog==", + "dev": true, + "requires": { + "extend": "^3.0.0", + "findup-sync": "^3.0.0", + "fined": "^1.0.1", + "flagged-respawn": "^1.0.0", + "is-plain-object": "^2.0.4", + "object.map": "^1.0.0", + "rechoir": "^0.6.2", + "resolve": "^1.1.7" + } + }, + "load-json-file": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", + "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0", + "strip-bom": "^2.0.0" + }, + "dependencies": { + "graceful-fs": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.2.tgz", + "integrity": "sha512-IItsdsea19BoLC7ELy13q1iJFNmd7ofZH5+X/pJr90/nRoPEX0DJo1dHDbgtYWOhJhcCgMDTOw84RZ72q6lB+Q==", + "dev": true + }, + "parse-json": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", + "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", + "dev": true, + "requires": { + "error-ex": "^1.2.0" + } + }, + "strip-bom": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", + "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", + "dev": true, + "requires": { + "is-utf8": "^0.2.0" + } + } + } + }, + "lodash": { + "version": "4.17.19", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz", + "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==", + "dev": true + }, + "lodash._basecopy": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz", + "integrity": "sha1-jaDmqHbPNEwK2KVIghEd08XHyjY=", + "dev": true + }, + "lodash._basetostring": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash._basetostring/-/lodash._basetostring-3.0.1.tgz", + "integrity": "sha1-0YYdh3+CSlL2aYMtyvPuFVZqB9U=", + "dev": true + }, + "lodash._basevalues": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._basevalues/-/lodash._basevalues-3.0.0.tgz", + "integrity": "sha1-W3dXYoAr3j0yl1A+JjAIIP32Ybc=", + "dev": true + }, + "lodash._getnative": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", + "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=", + "dev": true + }, + "lodash._isiterateecall": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz", + "integrity": "sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw=", + "dev": true + }, + "lodash._reescape": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._reescape/-/lodash._reescape-3.0.0.tgz", + "integrity": "sha1-Kx1vXf4HyKNVdT5fJ/rH8c3hYWo=", + "dev": true + }, + "lodash._reevaluate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._reevaluate/-/lodash._reevaluate-3.0.0.tgz", + "integrity": "sha1-WLx0xAZklTrgsSTYBpltrKQx4u0=", + "dev": true + }, + "lodash._reinterpolate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz", + "integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=", + "dev": true + }, + "lodash._root": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash._root/-/lodash._root-3.0.1.tgz", + "integrity": "sha1-+6HEUkwZ7ppfgTa0YJ8BfPTe1pI=", + "dev": true + }, + "lodash.clone": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clone/-/lodash.clone-4.5.0.tgz", + "integrity": "sha1-GVhwRQ9aExkkeN9Lw9I9LeoZB7Y=", + "dev": true + }, + "lodash.defaults": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", + "integrity": "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=", + "dev": true + }, + "lodash.escape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-3.2.0.tgz", + "integrity": "sha1-mV7g3BjBtIzJLv+ucaEKq1tIdpg=", + "dev": true, + "requires": { + "lodash._root": "^3.0.0" + } + }, + "lodash.flatten": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", + "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=", + "dev": true + }, + "lodash.isarguments": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", + "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=", + "dev": true + }, + "lodash.isarray": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz", + "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=", + "dev": true + }, + "lodash.keys": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", + "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", + "dev": true, + "requires": { + "lodash._getnative": "^3.0.0", + "lodash.isarguments": "^3.0.0", + "lodash.isarray": "^3.0.0" + } + }, + "lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=", + "dev": true + }, + "lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "lodash.partialright": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/lodash.partialright/-/lodash.partialright-4.2.1.tgz", + "integrity": "sha1-ATDYDoM2MmTUAHTzKbij56ihzEs=", + "dev": true + }, + "lodash.pick": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.pick/-/lodash.pick-4.4.0.tgz", + "integrity": "sha1-UvBWEP/53tQiYRRB7R/BI6AwAbM=", + "dev": true + }, + "lodash.restparam": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/lodash.restparam/-/lodash.restparam-3.6.1.tgz", + "integrity": "sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU=", + "dev": true + }, + "lodash.sortby": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", + "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=", + "dev": true + }, + "lodash.template": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-3.6.2.tgz", + "integrity": "sha1-+M3sxhaaJVvpCYrosMU9N4kx0U8=", + "dev": true, + "requires": { + "lodash._basecopy": "^3.0.0", + "lodash._basetostring": "^3.0.0", + "lodash._basevalues": "^3.0.0", + "lodash._isiterateecall": "^3.0.0", + "lodash._reinterpolate": "^3.0.0", + "lodash.escape": "^3.0.0", + "lodash.keys": "^3.0.0", + "lodash.restparam": "^3.0.0", + "lodash.templatesettings": "^3.0.0" + } + }, + "lodash.templatesettings": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-3.1.1.tgz", + "integrity": "sha1-+zB4RHU7Zrnxr6VOJix0UwfbqOU=", + "dev": true, + "requires": { + "lodash._reinterpolate": "^3.0.0", + "lodash.escape": "^3.0.0" + } + }, + "lodash.uniq": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", + "integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=", + "dev": true + }, + "log4js": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/log4js/-/log4js-4.5.1.tgz", + "integrity": "sha512-EEEgFcE9bLgaYUKuozyFfytQM2wDHtXn4tAN41pkaxpNjAykv11GVdeI4tHtmPWW4Xrgh9R/2d7XYghDVjbKKw==", + "dev": true, + "requires": { + "date-format": "^2.0.0", + "debug": "^4.1.1", + "flatted": "^2.0.0", + "rfdc": "^1.1.4", + "streamroller": "^1.0.6" + } + }, + "logalot": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/logalot/-/logalot-2.1.0.tgz", + "integrity": "sha1-X46MkNME7fElMJUaVVSruMXj9VI=", + "dev": true, + "optional": true, + "requires": { + "figures": "^1.3.5", + "squeak": "^1.0.0" + }, + "dependencies": { + "figures": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", + "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", + "dev": true, + "optional": true, + "requires": { + "escape-string-regexp": "^1.0.5", + "object-assign": "^4.1.0" + } + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true, + "optional": true + } + } + }, + "longest": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", + "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=", + "dev": true + }, + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha1-ce5R+nvkyuwaY4OffmgtgTLTDK8=", + "dev": true, + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" + } + }, + "loud-rejection": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", + "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=", + "dev": true, + "optional": true, + "requires": { + "currently-unhandled": "^0.4.1", + "signal-exit": "^3.0.0" + } + }, + "lowercase-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", + "integrity": "sha1-b54wtHCE2XGnyCD/FabFFnt0wm8=", + "dev": true, + "optional": true + }, + "lpad-align": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/lpad-align/-/lpad-align-1.1.2.tgz", + "integrity": "sha1-IfYArBwwlcPG5JfuZyce4ISB/p4=", + "dev": true, + "optional": true, + "requires": { + "get-stdin": "^4.0.1", + "indent-string": "^2.1.0", + "longest": "^1.0.0", + "meow": "^3.3.0" + } + }, + "lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "dev": true, + "requires": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "lru-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/lru-queue/-/lru-queue-0.1.0.tgz", + "integrity": "sha1-Jzi9nw089PhEkMVzbEhpmsYyzaM=", + "dev": true, + "requires": { + "es5-ext": "~0.10.2" + } + }, + "make-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.0.0.tgz", + "integrity": "sha512-grNJDhb8b1Jm1qeqW5R/O63wUo4UXo2v2HMic6YT9i/HBlF93S8jkMgH7yugvY9ABDShH4VZMn8I+U8+fCNegw==", + "dev": true, + "requires": { + "semver": "^6.0.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "make-iterator": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/make-iterator/-/make-iterator-1.0.1.tgz", + "integrity": "sha1-KbM/MSqo9UfEpeSQ9Wr87JkTOtY=", + "dev": true, + "requires": { + "kind-of": "^6.0.2" + } + }, + "map-cache": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", + "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", + "dev": true + }, + "map-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", + "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", + "dev": true, + "optional": true + }, + "map-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", + "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", + "dev": true, + "requires": { + "object-visit": "^1.0.0" + } + }, + "marked": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-0.7.0.tgz", + "integrity": "sha512-c+yYdCZJQrsRjTPhUx7VKkApw9bwDkNbHUKo1ovgcfDjb2kc8rLuRbIFyXL5WOEUwzSSKo3IXpph2K6DqB/KZg==", + "dev": true + }, + "matchdep": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/matchdep/-/matchdep-2.0.0.tgz", + "integrity": "sha1-xvNINKDY28OzfCfui7yyfHd1WC4=", + "dev": true, + "requires": { + "findup-sync": "^2.0.0", + "micromatch": "^3.0.4", + "resolve": "^1.4.0", + "stack-trace": "0.0.10" + }, + "dependencies": { + "findup-sync": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-2.0.0.tgz", + "integrity": "sha1-kyaxSIwi0aYIhlCoaQGy2akKLLw=", + "dev": true, + "requires": { + "detect-file": "^1.0.0", + "is-glob": "^3.1.0", + "micromatch": "^3.0.4", + "resolve-dir": "^1.0.1" + } + } + } + }, + "math-random": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/math-random/-/math-random-1.0.4.tgz", + "integrity": "sha512-rUxjysqif/BZQH2yhd5Aaq7vXMSx9NdEsQcyA07uEzIvxgI7zIr33gGsh+RU0/XjmQpCW7RsVof1vlkvQVCK5A==", + "dev": true + }, + "mdn-data": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.4.tgz", + "integrity": "sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA==", + "dev": true + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", + "dev": true + }, + "memoizee": { + "version": "0.4.14", + "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.14.tgz", + "integrity": "sha1-B6APIEaZ+alcLZ53IYJxx81hDVc=", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "^0.10.45", + "es6-weak-map": "^2.0.2", + "event-emitter": "^0.3.5", + "is-promise": "^2.1", + "lru-queue": "0.1", + "next-tick": "1", + "timers-ext": "^0.1.5" + } + }, + "meow": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", + "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", + "dev": true, + "optional": true, + "requires": { + "camelcase-keys": "^2.0.0", + "decamelize": "^1.1.2", + "loud-rejection": "^1.0.0", + "map-obj": "^1.0.1", + "minimist": "^1.1.3", + "normalize-package-data": "^2.3.4", + "object-assign": "^4.0.1", + "read-pkg-up": "^1.0.1", + "redent": "^1.0.0", + "trim-newlines": "^1.0.0" + }, + "dependencies": { + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true, + "optional": true + } + } + }, + "merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "merge2": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.3.0.tgz", + "integrity": "sha512-2j4DAdlBOkiSZIsaXk4mTE3sRS02yBHAtfy127xRV3bQUFqXkjHCHLW6Scv7DwNRbIWNHH8zpnz9zMaKXIdvYw==", + "dev": true + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha1-cIWbyVyYQJUvNZoGij/En57PrCM=", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "optional": true + }, + "mime-db": { + "version": "1.40.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz", + "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==", + "dev": true + }, + "mime-types": { + "version": "2.1.24", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz", + "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==", + "dev": true, + "requires": { + "mime-db": "1.40.0" + } + }, + "mimic-fn": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", + "integrity": "sha1-ggyGo5M0ZA6ZUWkovQP8qIBX0CI=", + "dev": true + }, + "mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", + "dev": true, + "optional": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + }, + "minimize": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/minimize/-/minimize-2.2.0.tgz", + "integrity": "sha1-ixZ28wBR2FmNdDZGvRJpCwdNpMM=", + "dev": true, + "requires": { + "argh": "^0.1.4", + "async": "^2.1.5", + "cli-color": "^1.2.0", + "diagnostics": "^1.1.0", + "emits": "^3.0.0", + "htmlparser2": "^3.9.2", + "uuid": "^3.0.0" + } + }, + "mixin-deep": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", + "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", + "dev": true, + "requires": { + "for-in": "^1.0.2", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha1-p0cPnkJnM9gb2B4RVSZOOjUHyrQ=", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, + "requires": { + "minimist": "0.0.8" + }, + "dependencies": { + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + } + } + }, + "moment": { + "version": "2.22.2", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.22.2.tgz", + "integrity": "sha1-PCV/mDn8DpP/UxSWMiOeuQeD/2Y=" + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "multipipe": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/multipipe/-/multipipe-0.1.2.tgz", + "integrity": "sha1-Ko8t33Du1WTf8tV/HhoTfZ8FB4s=", + "dev": true, + "requires": { + "duplexer2": "0.0.2" + } + }, + "mute-stdout": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mute-stdout/-/mute-stdout-1.0.1.tgz", + "integrity": "sha1-rLAwDrTeI6fd7sAU4+lgRLNHIzE=", + "dev": true + }, + "mute-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", + "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=", + "dev": true + }, + "nan": { + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", + "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==", + "dev": true, + "optional": true + }, + "nanomatch": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", + "integrity": "sha1-uHqKpPwN6P5r6IiVs4mD/yZb0Rk=", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "fragment-cache": "^0.2.1", + "is-windows": "^1.0.2", + "kind-of": "^6.0.2", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + } + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, + "negotiator": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", + "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==", + "dev": true + }, + "next-tick": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", + "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=", + "dev": true + }, + "ng-file-upload": { + "version": "12.2.13", + "resolved": "https://registry.npmjs.org/ng-file-upload/-/ng-file-upload-12.2.13.tgz", + "integrity": "sha1-AYAPOHLlJvlTEPhHfpnk8S0NjRQ=" + }, + "nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha1-ozeKdpbOfSI+iPybdkvX7xCJ42Y=", + "dev": true + }, + "node-notifier": { + "version": "5.4.3", + "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-5.4.3.tgz", + "integrity": "sha512-M4UBGcs4jeOK9CjTsYwkvH6/MzuUmGCyTW+kCY7uO+1ZVr0+FHGdPdIf5CCLqAaxnRrWidyoQlNkMIIVwbKB8Q==", + "dev": true, + "requires": { + "growly": "^1.3.0", + "is-wsl": "^1.1.0", + "semver": "^5.5.0", + "shellwords": "^0.1.1", + "which": "^1.3.0" + } + }, + "node-releases": { + "version": "1.1.32", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.32.tgz", + "integrity": "sha512-VhVknkitq8dqtWoluagsGPn3dxTvN9fwgR59fV3D7sLBHe0JfDramsMI8n8mY//ccq/Kkrf8ZRHRpsyVZ3qw1A==", + "dev": true, + "requires": { + "semver": "^5.3.0" + } + }, + "node.extend": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/node.extend/-/node.extend-2.0.2.tgz", + "integrity": "sha512-pDT4Dchl94/+kkgdwyS2PauDFjZG0Hk0IcHIB+LkW27HLDtdoeMxHTxZh39DYbPP8UflWXWj9JcdDozF+YDOpQ==", + "dev": true, + "requires": { + "has": "^1.0.3", + "is": "^3.2.1" + } + }, + "normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "requires": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true, + "requires": { + "remove-trailing-separator": "^1.0.1" + } + }, + "normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=", + "dev": true + }, + "normalize-url": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-3.3.0.tgz", + "integrity": "sha1-suHE3E98bVd0PfczpPWXjRhlBVk=", + "dev": true + }, + "nouislider": { + "version": "14.6.2", + "resolved": "https://registry.npmjs.org/nouislider/-/nouislider-14.6.2.tgz", + "integrity": "sha512-/lJeqJBghNAZS3P2VYrHzm1RM6YJPvvC/1wNpGaHBRX+05wpzUDafrW/ohAYp4kjKhRH8+BJ0vkorCHiMmgTMQ==" + }, + "now-and-later": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/now-and-later/-/now-and-later-2.0.1.tgz", + "integrity": "sha512-KGvQ0cB70AQfg107Xvs/Fbu+dGmZoTRJp2TaPwcwQm3/7PteUyN2BCgk8KBMPGBUXZdVwyWS8fDCGFygBm19UQ==", + "dev": true, + "requires": { + "once": "^1.3.2" + } + }, + "npm": { + "version": "6.14.9", + "resolved": "https://registry.npmjs.org/npm/-/npm-6.14.9.tgz", + "integrity": "sha512-yHi1+i9LyAZF1gAmgyYtVk+HdABlLy94PMIDoK1TRKWvmFQAt5z3bodqVwKvzY0s6dLqQPVsRLiwhJfNtiHeCg==", + "requires": { + "JSONStream": "^1.3.5", + "abbrev": "~1.1.1", + "ansicolors": "~0.3.2", + "ansistyles": "~0.1.3", + "aproba": "^2.0.0", + "archy": "~1.0.0", + "bin-links": "^1.1.8", + "bluebird": "^3.5.5", + "byte-size": "^5.0.1", + "cacache": "^12.0.3", + "call-limit": "^1.1.1", + "chownr": "^1.1.4", + "ci-info": "^2.0.0", + "cli-columns": "^3.1.2", + "cli-table3": "^0.5.1", + "cmd-shim": "^3.0.3", + "columnify": "~1.5.4", + "config-chain": "^1.1.12", + "debuglog": "*", + "detect-indent": "~5.0.0", + "detect-newline": "^2.1.0", + "dezalgo": "~1.0.3", + "editor": "~1.0.0", + "figgy-pudding": "^3.5.1", + "find-npm-prefix": "^1.0.2", + "fs-vacuum": "~1.2.10", + "fs-write-stream-atomic": "~1.0.10", + "gentle-fs": "^2.3.1", + "glob": "^7.1.6", + "graceful-fs": "^4.2.4", + "has-unicode": "~2.0.1", + "hosted-git-info": "^2.8.8", + "iferr": "^1.0.2", + "imurmurhash": "*", + "infer-owner": "^1.0.4", + "inflight": "~1.0.6", + "inherits": "^2.0.4", + "ini": "^1.3.5", + "init-package-json": "^1.10.3", + "is-cidr": "^3.0.0", + "json-parse-better-errors": "^1.0.2", + "lazy-property": "~1.0.0", + "libcipm": "^4.0.8", + "libnpm": "^3.0.1", + "libnpmaccess": "^3.0.2", + "libnpmhook": "^5.0.3", + "libnpmorg": "^1.0.1", + "libnpmsearch": "^2.0.2", + "libnpmteam": "^1.0.2", + "libnpx": "^10.2.4", + "lock-verify": "^2.1.0", + "lockfile": "^1.0.4", + "lodash._baseindexof": "*", + "lodash._baseuniq": "~4.6.0", + "lodash._bindcallback": "*", + "lodash._cacheindexof": "*", + "lodash._createcache": "*", + "lodash._getnative": "*", + "lodash.clonedeep": "~4.5.0", + "lodash.restparam": "*", + "lodash.union": "~4.6.0", + "lodash.uniq": "~4.5.0", + "lodash.without": "~4.4.0", + "lru-cache": "^5.1.1", + "meant": "^1.0.2", + "mississippi": "^3.0.0", + "mkdirp": "^0.5.5", + "move-concurrently": "^1.0.1", + "node-gyp": "^5.1.0", + "nopt": "^4.0.3", + "normalize-package-data": "^2.5.0", + "npm-audit-report": "^1.3.3", + "npm-cache-filename": "~1.0.2", + "npm-install-checks": "^3.0.2", + "npm-lifecycle": "^3.1.5", + "npm-package-arg": "^6.1.1", + "npm-packlist": "^1.4.8", + "npm-pick-manifest": "^3.0.2", + "npm-profile": "^4.0.4", + "npm-registry-fetch": "^4.0.7", + "npm-user-validate": "^1.0.1", + "npmlog": "~4.1.2", + "once": "~1.4.0", + "opener": "^1.5.1", + "osenv": "^0.1.5", + "pacote": "^9.5.12", + "path-is-inside": "~1.0.2", + "promise-inflight": "~1.0.1", + "qrcode-terminal": "^0.12.0", + "query-string": "^6.8.2", + "qw": "~1.0.1", + "read": "~1.0.7", + "read-cmd-shim": "^1.0.5", + "read-installed": "~4.0.3", + "read-package-json": "^2.1.1", + "read-package-tree": "^5.3.1", + "readable-stream": "^3.6.0", + "readdir-scoped-modules": "^1.1.0", + "request": "^2.88.0", + "retry": "^0.12.0", + "rimraf": "^2.7.1", + "safe-buffer": "^5.1.2", + "semver": "^5.7.1", + "sha": "^3.0.0", + "slide": "~1.1.6", + "sorted-object": "~2.0.1", + "sorted-union-stream": "~2.1.3", + "ssri": "^6.0.1", + "stringify-package": "^1.0.1", + "tar": "^4.4.13", + "text-table": "~0.2.0", + "tiny-relative-date": "^1.3.0", + "uid-number": "0.0.6", + "umask": "~1.1.0", + "unique-filename": "^1.1.1", + "unpipe": "~1.0.0", + "update-notifier": "^2.5.0", + "uuid": "^3.3.3", + "validate-npm-package-license": "^3.0.4", + "validate-npm-package-name": "~3.0.0", + "which": "^1.3.1", + "worker-farm": "^1.7.0", + "write-file-atomic": "^2.4.3" + }, + "dependencies": { + "JSONStream": { + "version": "1.3.5", + "bundled": true, + "requires": { + "jsonparse": "^1.2.0", + "through": ">=2.2.7 <3" + } + }, + "abbrev": { + "version": "1.1.1", + "bundled": true + }, + "agent-base": { + "version": "4.3.0", + "bundled": true, + "requires": { + "es6-promisify": "^5.0.0" + } + }, + "agentkeepalive": { + "version": "3.5.2", + "bundled": true, + "requires": { + "humanize-ms": "^1.2.1" + } + }, + "ansi-align": { + "version": "2.0.0", + "bundled": true, + "requires": { + "string-width": "^2.0.0" + } + }, + "ansi-regex": { + "version": "2.1.1", + "bundled": true + }, + "ansi-styles": { + "version": "3.2.1", + "bundled": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "ansicolors": { + "version": "0.3.2", + "bundled": true + }, + "ansistyles": { + "version": "0.1.3", + "bundled": true + }, + "aproba": { + "version": "2.0.0", + "bundled": true + }, + "archy": { + "version": "1.0.0", + "bundled": true + }, + "are-we-there-yet": { + "version": "1.1.4", + "bundled": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.6", + "bundled": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "bundled": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "asap": { + "version": "2.0.6", + "bundled": true + }, + "asn1": { + "version": "0.2.4", + "bundled": true, + "requires": { + "safer-buffer": "~2.1.0" + } + }, + "assert-plus": { + "version": "1.0.0", + "bundled": true + }, + "asynckit": { + "version": "0.4.0", + "bundled": true + }, + "aws-sign2": { + "version": "0.7.0", + "bundled": true + }, + "aws4": { + "version": "1.8.0", + "bundled": true + }, + "balanced-match": { + "version": "1.0.0", + "bundled": true + }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "bundled": true, + "optional": true, + "requires": { + "tweetnacl": "^0.14.3" + } + }, + "bin-links": { + "version": "1.1.8", + "bundled": true, + "requires": { + "bluebird": "^3.5.3", + "cmd-shim": "^3.0.0", + "gentle-fs": "^2.3.0", + "graceful-fs": "^4.1.15", + "npm-normalize-package-bin": "^1.0.0", + "write-file-atomic": "^2.3.0" + } + }, + "bluebird": { + "version": "3.5.5", + "bundled": true + }, + "boxen": { + "version": "1.3.0", + "bundled": true, + "requires": { + "ansi-align": "^2.0.0", + "camelcase": "^4.0.0", + "chalk": "^2.0.1", + "cli-boxes": "^1.0.0", + "string-width": "^2.0.0", + "term-size": "^1.2.0", + "widest-line": "^2.0.0" + } + }, + "brace-expansion": { + "version": "1.1.11", + "bundled": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "buffer-from": { + "version": "1.0.0", + "bundled": true + }, + "builtins": { + "version": "1.0.3", + "bundled": true + }, + "byline": { + "version": "5.0.0", + "bundled": true + }, + "byte-size": { + "version": "5.0.1", + "bundled": true + }, + "cacache": { + "version": "12.0.3", + "bundled": true, + "requires": { + "bluebird": "^3.5.5", + "chownr": "^1.1.1", + "figgy-pudding": "^3.5.1", + "glob": "^7.1.4", + "graceful-fs": "^4.1.15", + "infer-owner": "^1.0.3", + "lru-cache": "^5.1.1", + "mississippi": "^3.0.0", + "mkdirp": "^0.5.1", + "move-concurrently": "^1.0.1", + "promise-inflight": "^1.0.1", + "rimraf": "^2.6.3", + "ssri": "^6.0.1", + "unique-filename": "^1.1.1", + "y18n": "^4.0.0" + } + }, + "call-limit": { + "version": "1.1.1", + "bundled": true + }, + "camelcase": { + "version": "4.1.0", + "bundled": true + }, + "capture-stack-trace": { + "version": "1.0.0", + "bundled": true + }, + "caseless": { + "version": "0.12.0", + "bundled": true + }, + "chalk": { + "version": "2.4.1", + "bundled": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "chownr": { + "version": "1.1.4", + "bundled": true + }, + "ci-info": { + "version": "2.0.0", + "bundled": true + }, + "cidr-regex": { + "version": "2.0.10", + "bundled": true, + "requires": { + "ip-regex": "^2.1.0" + } + }, + "cli-boxes": { + "version": "1.0.0", + "bundled": true + }, + "cli-columns": { + "version": "3.1.2", + "bundled": true, + "requires": { + "string-width": "^2.0.0", + "strip-ansi": "^3.0.1" + } + }, + "cli-table3": { + "version": "0.5.1", + "bundled": true, + "requires": { + "colors": "^1.1.2", + "object-assign": "^4.1.0", + "string-width": "^2.1.1" + } + }, + "cliui": { + "version": "5.0.0", + "bundled": true, + "requires": { + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "bundled": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "bundled": true + }, + "string-width": { + "version": "3.1.0", + "bundled": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "bundled": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "clone": { + "version": "1.0.4", + "bundled": true + }, + "cmd-shim": { + "version": "3.0.3", + "bundled": true, + "requires": { + "graceful-fs": "^4.1.2", + "mkdirp": "~0.5.0" + } + }, + "code-point-at": { + "version": "1.1.0", + "bundled": true + }, + "color-convert": { + "version": "1.9.1", + "bundled": true, + "requires": { + "color-name": "^1.1.1" + } + }, + "color-name": { + "version": "1.1.3", + "bundled": true + }, + "colors": { + "version": "1.3.3", + "bundled": true, + "optional": true + }, + "columnify": { + "version": "1.5.4", + "bundled": true, + "requires": { + "strip-ansi": "^3.0.0", + "wcwidth": "^1.0.0" + } + }, + "combined-stream": { + "version": "1.0.6", + "bundled": true, + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "concat-map": { + "version": "0.0.1", + "bundled": true + }, + "concat-stream": { + "version": "1.6.2", + "bundled": true, + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.6", + "bundled": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "bundled": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "config-chain": { + "version": "1.1.12", + "bundled": true, + "requires": { + "ini": "^1.3.4", + "proto-list": "~1.2.1" + } + }, + "configstore": { + "version": "3.1.5", + "bundled": true, + "requires": { + "dot-prop": "^4.2.1", + "graceful-fs": "^4.1.2", + "make-dir": "^1.0.0", + "unique-string": "^1.0.0", + "write-file-atomic": "^2.0.0", + "xdg-basedir": "^3.0.0" + } + }, + "console-control-strings": { + "version": "1.1.0", + "bundled": true + }, + "copy-concurrently": { + "version": "1.0.5", + "bundled": true, + "requires": { + "aproba": "^1.1.1", + "fs-write-stream-atomic": "^1.0.8", + "iferr": "^0.1.5", + "mkdirp": "^0.5.1", + "rimraf": "^2.5.4", + "run-queue": "^1.0.0" + }, + "dependencies": { + "aproba": { + "version": "1.2.0", + "bundled": true + }, + "iferr": { + "version": "0.1.5", + "bundled": true + } + } + }, + "core-util-is": { + "version": "1.0.2", + "bundled": true + }, + "create-error-class": { + "version": "3.0.2", + "bundled": true, + "requires": { + "capture-stack-trace": "^1.0.0" + } + }, + "cross-spawn": { + "version": "5.1.0", + "bundled": true, + "requires": { + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + }, + "dependencies": { + "lru-cache": { + "version": "4.1.5", + "bundled": true, + "requires": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "yallist": { + "version": "2.1.2", + "bundled": true + } + } + }, + "crypto-random-string": { + "version": "1.0.0", + "bundled": true + }, + "cyclist": { + "version": "0.2.2", + "bundled": true + }, + "dashdash": { + "version": "1.14.1", + "bundled": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "debug": { + "version": "3.1.0", + "bundled": true, + "requires": { + "ms": "2.0.0" + }, + "dependencies": { + "ms": { + "version": "2.0.0", + "bundled": true + } + } + }, + "debuglog": { + "version": "1.0.1", + "bundled": true + }, + "decamelize": { + "version": "1.2.0", + "bundled": true + }, + "decode-uri-component": { + "version": "0.2.0", + "bundled": true + }, + "deep-extend": { + "version": "0.6.0", + "bundled": true + }, + "defaults": { + "version": "1.0.3", + "bundled": true, + "requires": { + "clone": "^1.0.2" + } + }, + "define-properties": { + "version": "1.1.3", + "bundled": true, + "requires": { + "object-keys": "^1.0.12" + } + }, + "delayed-stream": { + "version": "1.0.0", + "bundled": true + }, + "delegates": { + "version": "1.0.0", + "bundled": true + }, + "detect-indent": { + "version": "5.0.0", + "bundled": true + }, + "detect-newline": { + "version": "2.1.0", + "bundled": true + }, + "dezalgo": { + "version": "1.0.3", + "bundled": true, + "requires": { + "asap": "^2.0.0", + "wrappy": "1" + } + }, + "dot-prop": { + "version": "4.2.1", + "bundled": true, + "requires": { + "is-obj": "^1.0.0" + } + }, + "dotenv": { + "version": "5.0.1", + "bundled": true + }, + "duplexer3": { + "version": "0.1.4", + "bundled": true + }, + "duplexify": { + "version": "3.6.0", + "bundled": true, + "requires": { + "end-of-stream": "^1.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.0.0", + "stream-shift": "^1.0.0" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.6", + "bundled": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "bundled": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "ecc-jsbn": { + "version": "0.1.2", + "bundled": true, + "optional": true, + "requires": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "editor": { + "version": "1.0.0", + "bundled": true + }, + "emoji-regex": { + "version": "7.0.3", + "bundled": true + }, + "encoding": { + "version": "0.1.12", + "bundled": true, + "requires": { + "iconv-lite": "~0.4.13" + } + }, + "end-of-stream": { + "version": "1.4.1", + "bundled": true, + "requires": { + "once": "^1.4.0" + } + }, + "env-paths": { + "version": "2.2.0", + "bundled": true + }, + "err-code": { + "version": "1.1.2", + "bundled": true + }, + "errno": { + "version": "0.1.7", + "bundled": true, + "requires": { + "prr": "~1.0.1" + } + }, + "es-abstract": { + "version": "1.12.0", + "bundled": true, + "requires": { + "es-to-primitive": "^1.1.1", + "function-bind": "^1.1.1", + "has": "^1.0.1", + "is-callable": "^1.1.3", + "is-regex": "^1.0.4" + } + }, + "es-to-primitive": { + "version": "1.2.0", + "bundled": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "es6-promise": { + "version": "4.2.8", + "bundled": true + }, + "es6-promisify": { + "version": "5.0.0", + "bundled": true, + "requires": { + "es6-promise": "^4.0.3" + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "bundled": true + }, + "execa": { + "version": "0.7.0", + "bundled": true, + "requires": { + "cross-spawn": "^5.0.1", + "get-stream": "^3.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + }, + "dependencies": { + "get-stream": { + "version": "3.0.0", + "bundled": true + } + } + }, + "extend": { + "version": "3.0.2", + "bundled": true + }, + "extsprintf": { + "version": "1.3.0", + "bundled": true + }, + "fast-json-stable-stringify": { + "version": "2.0.0", + "bundled": true + }, + "figgy-pudding": { + "version": "3.5.1", + "bundled": true + }, + "find-npm-prefix": { + "version": "1.0.2", + "bundled": true + }, + "flush-write-stream": { + "version": "1.0.3", + "bundled": true, + "requires": { + "inherits": "^2.0.1", + "readable-stream": "^2.0.4" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.6", + "bundled": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "bundled": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "forever-agent": { + "version": "0.6.1", + "bundled": true + }, + "form-data": { + "version": "2.3.2", + "bundled": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "1.0.6", + "mime-types": "^2.1.12" + } + }, + "from2": { + "version": "2.3.0", + "bundled": true, + "requires": { + "inherits": "^2.0.1", + "readable-stream": "^2.0.0" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.6", + "bundled": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "bundled": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "fs-minipass": { + "version": "1.2.7", + "bundled": true, + "requires": { + "minipass": "^2.6.0" + }, + "dependencies": { + "minipass": { + "version": "2.9.0", + "bundled": true, + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + } + } + } + }, + "fs-vacuum": { + "version": "1.2.10", + "bundled": true, + "requires": { + "graceful-fs": "^4.1.2", + "path-is-inside": "^1.0.1", + "rimraf": "^2.5.2" + } + }, + "fs-write-stream-atomic": { + "version": "1.0.10", + "bundled": true, + "requires": { + "graceful-fs": "^4.1.2", + "iferr": "^0.1.5", + "imurmurhash": "^0.1.4", + "readable-stream": "1 || 2" + }, + "dependencies": { + "iferr": { + "version": "0.1.5", + "bundled": true + }, + "readable-stream": { + "version": "2.3.6", + "bundled": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "bundled": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "fs.realpath": { + "version": "1.0.0", + "bundled": true + }, + "function-bind": { + "version": "1.1.1", + "bundled": true + }, + "gauge": { + "version": "2.7.4", + "bundled": true, + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + }, + "dependencies": { + "aproba": { + "version": "1.2.0", + "bundled": true + }, + "string-width": { + "version": "1.0.2", + "bundled": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + } + } + }, + "genfun": { + "version": "5.0.0", + "bundled": true + }, + "gentle-fs": { + "version": "2.3.1", + "bundled": true, + "requires": { + "aproba": "^1.1.2", + "chownr": "^1.1.2", + "cmd-shim": "^3.0.3", + "fs-vacuum": "^1.2.10", + "graceful-fs": "^4.1.11", + "iferr": "^0.1.5", + "infer-owner": "^1.0.4", + "mkdirp": "^0.5.1", + "path-is-inside": "^1.0.2", + "read-cmd-shim": "^1.0.1", + "slide": "^1.1.6" + }, + "dependencies": { + "aproba": { + "version": "1.2.0", + "bundled": true + }, + "iferr": { + "version": "0.1.5", + "bundled": true + } + } + }, + "get-caller-file": { + "version": "2.0.5", + "bundled": true + }, + "get-stream": { + "version": "4.1.0", + "bundled": true, + "requires": { + "pump": "^3.0.0" + } + }, + "getpass": { + "version": "0.1.7", + "bundled": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "glob": { + "version": "7.1.6", + "bundled": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "global-dirs": { + "version": "0.1.1", + "bundled": true, + "requires": { + "ini": "^1.3.4" + } + }, + "got": { + "version": "6.7.1", + "bundled": true, + "requires": { + "create-error-class": "^3.0.0", + "duplexer3": "^0.1.4", + "get-stream": "^3.0.0", + "is-redirect": "^1.0.0", + "is-retry-allowed": "^1.0.0", + "is-stream": "^1.0.0", + "lowercase-keys": "^1.0.0", + "safe-buffer": "^5.0.1", + "timed-out": "^4.0.0", + "unzip-response": "^2.0.1", + "url-parse-lax": "^1.0.0" + }, + "dependencies": { + "get-stream": { + "version": "3.0.0", + "bundled": true + } + } + }, + "graceful-fs": { + "version": "4.2.4", + "bundled": true + }, + "har-schema": { + "version": "2.0.0", + "bundled": true + }, + "har-validator": { + "version": "5.1.5", + "bundled": true, + "requires": { + "ajv": "^6.12.3", + "har-schema": "^2.0.0" + }, + "dependencies": { + "ajv": { + "version": "6.12.6", + "bundled": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "fast-deep-equal": { + "version": "3.1.3", + "bundled": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "bundled": true + } + } + }, + "has": { + "version": "1.0.3", + "bundled": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-flag": { + "version": "3.0.0", + "bundled": true + }, + "has-symbols": { + "version": "1.0.0", + "bundled": true + }, + "has-unicode": { + "version": "2.0.1", + "bundled": true + }, + "hosted-git-info": { + "version": "2.8.8", + "bundled": true + }, + "http-cache-semantics": { + "version": "3.8.1", + "bundled": true + }, + "http-proxy-agent": { + "version": "2.1.0", + "bundled": true, + "requires": { + "agent-base": "4", + "debug": "3.1.0" + } + }, + "http-signature": { + "version": "1.2.0", + "bundled": true, + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, + "https-proxy-agent": { + "version": "2.2.4", + "bundled": true, + "requires": { + "agent-base": "^4.3.0", + "debug": "^3.1.0" + } + }, + "humanize-ms": { + "version": "1.2.1", + "bundled": true, + "requires": { + "ms": "^2.0.0" + } + }, + "iconv-lite": { + "version": "0.4.23", + "bundled": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "iferr": { + "version": "1.0.2", + "bundled": true + }, + "ignore-walk": { + "version": "3.0.3", + "bundled": true, + "requires": { + "minimatch": "^3.0.4" + } + }, + "import-lazy": { + "version": "2.1.0", + "bundled": true + }, + "imurmurhash": { + "version": "0.1.4", + "bundled": true + }, + "infer-owner": { + "version": "1.0.4", + "bundled": true + }, + "inflight": { + "version": "1.0.6", + "bundled": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "bundled": true + }, + "ini": { + "version": "1.3.5", + "bundled": true + }, + "init-package-json": { + "version": "1.10.3", + "bundled": true, + "requires": { + "glob": "^7.1.1", + "npm-package-arg": "^4.0.0 || ^5.0.0 || ^6.0.0", + "promzard": "^0.3.0", + "read": "~1.0.1", + "read-package-json": "1 || 2", + "semver": "2.x || 3.x || 4 || 5", + "validate-npm-package-license": "^3.0.1", + "validate-npm-package-name": "^3.0.0" + } + }, + "ip": { + "version": "1.1.5", + "bundled": true + }, + "ip-regex": { + "version": "2.1.0", + "bundled": true + }, + "is-callable": { + "version": "1.1.4", + "bundled": true + }, + "is-ci": { + "version": "1.2.1", + "bundled": true, + "requires": { + "ci-info": "^1.5.0" + }, + "dependencies": { + "ci-info": { + "version": "1.6.0", + "bundled": true + } + } + }, + "is-cidr": { + "version": "3.0.0", + "bundled": true, + "requires": { + "cidr-regex": "^2.0.10" + } + }, + "is-date-object": { + "version": "1.0.1", + "bundled": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "bundled": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "is-installed-globally": { + "version": "0.1.0", + "bundled": true, + "requires": { + "global-dirs": "^0.1.0", + "is-path-inside": "^1.0.0" + } + }, + "is-npm": { + "version": "1.0.0", + "bundled": true + }, + "is-obj": { + "version": "1.0.1", + "bundled": true + }, + "is-path-inside": { + "version": "1.0.1", + "bundled": true, + "requires": { + "path-is-inside": "^1.0.1" + } + }, + "is-redirect": { + "version": "1.0.0", + "bundled": true + }, + "is-regex": { + "version": "1.0.4", + "bundled": true, + "requires": { + "has": "^1.0.1" + } + }, + "is-retry-allowed": { + "version": "1.2.0", + "bundled": true + }, + "is-stream": { + "version": "1.1.0", + "bundled": true + }, + "is-symbol": { + "version": "1.0.2", + "bundled": true, + "requires": { + "has-symbols": "^1.0.0" + } + }, + "is-typedarray": { + "version": "1.0.0", + "bundled": true + }, + "isarray": { + "version": "1.0.0", + "bundled": true + }, + "isexe": { + "version": "2.0.0", + "bundled": true + }, + "isstream": { + "version": "0.1.2", + "bundled": true + }, + "jsbn": { + "version": "0.1.1", + "bundled": true, + "optional": true + }, + "json-parse-better-errors": { + "version": "1.0.2", + "bundled": true + }, + "json-schema": { + "version": "0.2.3", + "bundled": true + }, + "json-stringify-safe": { + "version": "5.0.1", + "bundled": true + }, + "jsonparse": { + "version": "1.3.1", + "bundled": true + }, + "jsprim": { + "version": "1.4.1", + "bundled": true, + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "latest-version": { + "version": "3.1.0", + "bundled": true, + "requires": { + "package-json": "^4.0.0" + } + }, + "lazy-property": { + "version": "1.0.0", + "bundled": true + }, + "libcipm": { + "version": "4.0.8", + "bundled": true, + "requires": { + "bin-links": "^1.1.2", + "bluebird": "^3.5.1", + "figgy-pudding": "^3.5.1", + "find-npm-prefix": "^1.0.2", + "graceful-fs": "^4.1.11", + "ini": "^1.3.5", + "lock-verify": "^2.1.0", + "mkdirp": "^0.5.1", + "npm-lifecycle": "^3.0.0", + "npm-logical-tree": "^1.2.1", + "npm-package-arg": "^6.1.0", + "pacote": "^9.1.0", + "read-package-json": "^2.0.13", + "rimraf": "^2.6.2", + "worker-farm": "^1.6.0" + } + }, + "libnpm": { + "version": "3.0.1", + "bundled": true, + "requires": { + "bin-links": "^1.1.2", + "bluebird": "^3.5.3", + "find-npm-prefix": "^1.0.2", + "libnpmaccess": "^3.0.2", + "libnpmconfig": "^1.2.1", + "libnpmhook": "^5.0.3", + "libnpmorg": "^1.0.1", + "libnpmpublish": "^1.1.2", + "libnpmsearch": "^2.0.2", + "libnpmteam": "^1.0.2", + "lock-verify": "^2.0.2", + "npm-lifecycle": "^3.0.0", + "npm-logical-tree": "^1.2.1", + "npm-package-arg": "^6.1.0", + "npm-profile": "^4.0.2", + "npm-registry-fetch": "^4.0.0", + "npmlog": "^4.1.2", + "pacote": "^9.5.3", + "read-package-json": "^2.0.13", + "stringify-package": "^1.0.0" + } + }, + "libnpmaccess": { + "version": "3.0.2", + "bundled": true, + "requires": { + "aproba": "^2.0.0", + "get-stream": "^4.0.0", + "npm-package-arg": "^6.1.0", + "npm-registry-fetch": "^4.0.0" + } + }, + "libnpmconfig": { + "version": "1.2.1", + "bundled": true, + "requires": { + "figgy-pudding": "^3.5.1", + "find-up": "^3.0.0", + "ini": "^1.3.5" + }, + "dependencies": { + "find-up": { + "version": "3.0.0", + "bundled": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "bundled": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "2.2.0", + "bundled": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "bundled": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.2.0", + "bundled": true + } + } + }, + "libnpmhook": { + "version": "5.0.3", + "bundled": true, + "requires": { + "aproba": "^2.0.0", + "figgy-pudding": "^3.4.1", + "get-stream": "^4.0.0", + "npm-registry-fetch": "^4.0.0" + } + }, + "libnpmorg": { + "version": "1.0.1", + "bundled": true, + "requires": { + "aproba": "^2.0.0", + "figgy-pudding": "^3.4.1", + "get-stream": "^4.0.0", + "npm-registry-fetch": "^4.0.0" + } + }, + "libnpmpublish": { + "version": "1.1.2", + "bundled": true, + "requires": { + "aproba": "^2.0.0", + "figgy-pudding": "^3.5.1", + "get-stream": "^4.0.0", + "lodash.clonedeep": "^4.5.0", + "normalize-package-data": "^2.4.0", + "npm-package-arg": "^6.1.0", + "npm-registry-fetch": "^4.0.0", + "semver": "^5.5.1", + "ssri": "^6.0.1" + } + }, + "libnpmsearch": { + "version": "2.0.2", + "bundled": true, + "requires": { + "figgy-pudding": "^3.5.1", + "get-stream": "^4.0.0", + "npm-registry-fetch": "^4.0.0" + } + }, + "libnpmteam": { + "version": "1.0.2", + "bundled": true, + "requires": { + "aproba": "^2.0.0", + "figgy-pudding": "^3.4.1", + "get-stream": "^4.0.0", + "npm-registry-fetch": "^4.0.0" + } + }, + "libnpx": { + "version": "10.2.4", + "bundled": true, + "requires": { + "dotenv": "^5.0.1", + "npm-package-arg": "^6.0.0", + "rimraf": "^2.6.2", + "safe-buffer": "^5.1.0", + "update-notifier": "^2.3.0", + "which": "^1.3.0", + "y18n": "^4.0.0", + "yargs": "^14.2.3" + } + }, + "lock-verify": { + "version": "2.1.0", + "bundled": true, + "requires": { + "npm-package-arg": "^6.1.0", + "semver": "^5.4.1" + } + }, + "lockfile": { + "version": "1.0.4", + "bundled": true, + "requires": { + "signal-exit": "^3.0.2" + } + }, + "lodash._baseindexof": { + "version": "3.1.0", + "bundled": true + }, + "lodash._baseuniq": { + "version": "4.6.0", + "bundled": true, + "requires": { + "lodash._createset": "~4.0.0", + "lodash._root": "~3.0.0" + } + }, + "lodash._bindcallback": { + "version": "3.0.1", + "bundled": true + }, + "lodash._cacheindexof": { + "version": "3.0.2", + "bundled": true + }, + "lodash._createcache": { + "version": "3.1.2", + "bundled": true, + "requires": { + "lodash._getnative": "^3.0.0" + } + }, + "lodash._createset": { + "version": "4.0.3", + "bundled": true + }, + "lodash._getnative": { + "version": "3.9.1", + "bundled": true + }, + "lodash._root": { + "version": "3.0.1", + "bundled": true + }, + "lodash.clonedeep": { + "version": "4.5.0", + "bundled": true + }, + "lodash.restparam": { + "version": "3.6.1", + "bundled": true + }, + "lodash.union": { + "version": "4.6.0", + "bundled": true + }, + "lodash.uniq": { + "version": "4.5.0", + "bundled": true + }, + "lodash.without": { + "version": "4.4.0", + "bundled": true + }, + "lowercase-keys": { + "version": "1.0.1", + "bundled": true + }, + "lru-cache": { + "version": "5.1.1", + "bundled": true, + "requires": { + "yallist": "^3.0.2" + } + }, + "make-dir": { + "version": "1.3.0", + "bundled": true, + "requires": { + "pify": "^3.0.0" + } + }, + "make-fetch-happen": { + "version": "5.0.2", + "bundled": true, + "requires": { + "agentkeepalive": "^3.4.1", + "cacache": "^12.0.0", + "http-cache-semantics": "^3.8.1", + "http-proxy-agent": "^2.1.0", + "https-proxy-agent": "^2.2.3", + "lru-cache": "^5.1.1", + "mississippi": "^3.0.0", + "node-fetch-npm": "^2.0.2", + "promise-retry": "^1.1.1", + "socks-proxy-agent": "^4.0.0", + "ssri": "^6.0.0" + } + }, + "meant": { + "version": "1.0.2", + "bundled": true + }, + "mime-db": { + "version": "1.35.0", + "bundled": true + }, + "mime-types": { + "version": "2.1.19", + "bundled": true, + "requires": { + "mime-db": "~1.35.0" + } + }, + "minimatch": { + "version": "3.0.4", + "bundled": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.5", + "bundled": true + }, + "minizlib": { + "version": "1.3.3", + "bundled": true, + "requires": { + "minipass": "^2.9.0" + }, + "dependencies": { + "minipass": { + "version": "2.9.0", + "bundled": true, + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + } + } + } + }, + "mississippi": { + "version": "3.0.0", + "bundled": true, + "requires": { + "concat-stream": "^1.5.0", + "duplexify": "^3.4.2", + "end-of-stream": "^1.1.0", + "flush-write-stream": "^1.0.0", + "from2": "^2.1.0", + "parallel-transform": "^1.1.0", + "pump": "^3.0.0", + "pumpify": "^1.3.3", + "stream-each": "^1.1.0", + "through2": "^2.0.0" + } + }, + "mkdirp": { + "version": "0.5.5", + "bundled": true, + "requires": { + "minimist": "^1.2.5" + }, + "dependencies": { + "minimist": { + "version": "1.2.5", + "bundled": true + } + } + }, + "move-concurrently": { + "version": "1.0.1", + "bundled": true, + "requires": { + "aproba": "^1.1.1", + "copy-concurrently": "^1.0.0", + "fs-write-stream-atomic": "^1.0.8", + "mkdirp": "^0.5.1", + "rimraf": "^2.5.4", + "run-queue": "^1.0.3" + }, + "dependencies": { + "aproba": { + "version": "1.2.0", + "bundled": true + } + } + }, + "ms": { + "version": "2.1.1", + "bundled": true + }, + "mute-stream": { + "version": "0.0.7", + "bundled": true + }, + "node-fetch-npm": { + "version": "2.0.2", + "bundled": true, + "requires": { + "encoding": "^0.1.11", + "json-parse-better-errors": "^1.0.0", + "safe-buffer": "^5.1.1" + } + }, + "node-gyp": { + "version": "5.1.0", + "bundled": true, + "requires": { + "env-paths": "^2.2.0", + "glob": "^7.1.4", + "graceful-fs": "^4.2.2", + "mkdirp": "^0.5.1", + "nopt": "^4.0.1", + "npmlog": "^4.1.2", + "request": "^2.88.0", + "rimraf": "^2.6.3", + "semver": "^5.7.1", + "tar": "^4.4.12", + "which": "^1.3.1" + } + }, + "nopt": { + "version": "4.0.3", + "bundled": true, + "requires": { + "abbrev": "1", + "osenv": "^0.1.4" + } + }, + "normalize-package-data": { + "version": "2.5.0", + "bundled": true, + "requires": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + }, + "dependencies": { + "resolve": { + "version": "1.10.0", + "bundled": true, + "requires": { + "path-parse": "^1.0.6" + } + } + } + }, + "npm-audit-report": { + "version": "1.3.3", + "bundled": true, + "requires": { + "cli-table3": "^0.5.0", + "console-control-strings": "^1.1.0" + } + }, + "npm-bundled": { + "version": "1.1.1", + "bundled": true, + "requires": { + "npm-normalize-package-bin": "^1.0.1" + } + }, + "npm-cache-filename": { + "version": "1.0.2", + "bundled": true + }, + "npm-install-checks": { + "version": "3.0.2", + "bundled": true, + "requires": { + "semver": "^2.3.0 || 3.x || 4 || 5" + } + }, + "npm-lifecycle": { + "version": "3.1.5", + "bundled": true, + "requires": { + "byline": "^5.0.0", + "graceful-fs": "^4.1.15", + "node-gyp": "^5.0.2", + "resolve-from": "^4.0.0", + "slide": "^1.1.6", + "uid-number": "0.0.6", + "umask": "^1.1.0", + "which": "^1.3.1" + } + }, + "npm-logical-tree": { + "version": "1.2.1", + "bundled": true + }, + "npm-normalize-package-bin": { + "version": "1.0.1", + "bundled": true + }, + "npm-package-arg": { + "version": "6.1.1", + "bundled": true, + "requires": { + "hosted-git-info": "^2.7.1", + "osenv": "^0.1.5", + "semver": "^5.6.0", + "validate-npm-package-name": "^3.0.0" + } + }, + "npm-packlist": { + "version": "1.4.8", + "bundled": true, + "requires": { + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1", + "npm-normalize-package-bin": "^1.0.1" + } + }, + "npm-pick-manifest": { + "version": "3.0.2", + "bundled": true, + "requires": { + "figgy-pudding": "^3.5.1", + "npm-package-arg": "^6.0.0", + "semver": "^5.4.1" + } + }, + "npm-profile": { + "version": "4.0.4", + "bundled": true, + "requires": { + "aproba": "^1.1.2 || 2", + "figgy-pudding": "^3.4.1", + "npm-registry-fetch": "^4.0.0" + } + }, + "npm-registry-fetch": { + "version": "4.0.7", + "bundled": true, + "requires": { + "JSONStream": "^1.3.4", + "bluebird": "^3.5.1", + "figgy-pudding": "^3.4.1", + "lru-cache": "^5.1.1", + "make-fetch-happen": "^5.0.0", + "npm-package-arg": "^6.1.0", + "safe-buffer": "^5.2.0" + }, + "dependencies": { + "safe-buffer": { + "version": "5.2.1", + "bundled": true + } + } + }, + "npm-run-path": { + "version": "2.0.2", + "bundled": true, + "requires": { + "path-key": "^2.0.0" + } + }, + "npm-user-validate": { + "version": "1.0.1", + "bundled": true + }, + "npmlog": { + "version": "4.1.2", + "bundled": true, + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "bundled": true + }, + "oauth-sign": { + "version": "0.9.0", + "bundled": true + }, + "object-assign": { + "version": "4.1.1", + "bundled": true + }, + "object-keys": { + "version": "1.0.12", + "bundled": true + }, + "object.getownpropertydescriptors": { + "version": "2.0.3", + "bundled": true, + "requires": { + "define-properties": "^1.1.2", + "es-abstract": "^1.5.1" + } + }, + "once": { + "version": "1.4.0", + "bundled": true, + "requires": { + "wrappy": "1" + } + }, + "opener": { + "version": "1.5.1", + "bundled": true + }, + "os-homedir": { + "version": "1.0.2", + "bundled": true + }, + "os-tmpdir": { + "version": "1.0.2", + "bundled": true + }, + "osenv": { + "version": "0.1.5", + "bundled": true, + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, + "p-finally": { + "version": "1.0.0", + "bundled": true + }, + "package-json": { + "version": "4.0.1", + "bundled": true, + "requires": { + "got": "^6.7.1", + "registry-auth-token": "^3.0.1", + "registry-url": "^3.0.3", + "semver": "^5.1.0" + } + }, + "pacote": { + "version": "9.5.12", + "bundled": true, + "requires": { + "bluebird": "^3.5.3", + "cacache": "^12.0.2", + "chownr": "^1.1.2", + "figgy-pudding": "^3.5.1", + "get-stream": "^4.1.0", + "glob": "^7.1.3", + "infer-owner": "^1.0.4", + "lru-cache": "^5.1.1", + "make-fetch-happen": "^5.0.0", + "minimatch": "^3.0.4", + "minipass": "^2.3.5", + "mississippi": "^3.0.0", + "mkdirp": "^0.5.1", + "normalize-package-data": "^2.4.0", + "npm-normalize-package-bin": "^1.0.0", + "npm-package-arg": "^6.1.0", + "npm-packlist": "^1.1.12", + "npm-pick-manifest": "^3.0.0", + "npm-registry-fetch": "^4.0.0", + "osenv": "^0.1.5", + "promise-inflight": "^1.0.1", + "promise-retry": "^1.1.1", + "protoduck": "^5.0.1", + "rimraf": "^2.6.2", + "safe-buffer": "^5.1.2", + "semver": "^5.6.0", + "ssri": "^6.0.1", + "tar": "^4.4.10", + "unique-filename": "^1.1.1", + "which": "^1.3.1" + }, + "dependencies": { + "minipass": { + "version": "2.9.0", + "bundled": true, + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + } + } + } + }, + "parallel-transform": { + "version": "1.1.0", + "bundled": true, + "requires": { + "cyclist": "~0.2.2", + "inherits": "^2.0.3", + "readable-stream": "^2.1.5" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.6", + "bundled": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "bundled": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "path-exists": { + "version": "3.0.0", + "bundled": true + }, + "path-is-absolute": { + "version": "1.0.1", + "bundled": true + }, + "path-is-inside": { + "version": "1.0.2", + "bundled": true + }, + "path-key": { + "version": "2.0.1", + "bundled": true + }, + "path-parse": { + "version": "1.0.6", + "bundled": true + }, + "performance-now": { + "version": "2.1.0", + "bundled": true + }, + "pify": { + "version": "3.0.0", + "bundled": true + }, + "prepend-http": { + "version": "1.0.4", + "bundled": true + }, + "process-nextick-args": { + "version": "2.0.0", + "bundled": true + }, + "promise-inflight": { + "version": "1.0.1", + "bundled": true + }, + "promise-retry": { + "version": "1.1.1", + "bundled": true, + "requires": { + "err-code": "^1.0.0", + "retry": "^0.10.0" + }, + "dependencies": { + "retry": { + "version": "0.10.1", + "bundled": true + } + } + }, + "promzard": { + "version": "0.3.0", + "bundled": true, + "requires": { + "read": "1" + } + }, + "proto-list": { + "version": "1.2.4", + "bundled": true + }, + "protoduck": { + "version": "5.0.1", + "bundled": true, + "requires": { + "genfun": "^5.0.0" + } + }, + "prr": { + "version": "1.0.1", + "bundled": true + }, + "pseudomap": { + "version": "1.0.2", + "bundled": true + }, + "psl": { + "version": "1.1.29", + "bundled": true + }, + "pump": { + "version": "3.0.0", + "bundled": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "pumpify": { + "version": "1.5.1", + "bundled": true, + "requires": { + "duplexify": "^3.6.0", + "inherits": "^2.0.3", + "pump": "^2.0.0" + }, + "dependencies": { + "pump": { + "version": "2.0.1", + "bundled": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + } + } + }, + "punycode": { + "version": "1.4.1", + "bundled": true + }, + "qrcode-terminal": { + "version": "0.12.0", + "bundled": true + }, + "qs": { + "version": "6.5.2", + "bundled": true + }, + "query-string": { + "version": "6.8.2", + "bundled": true, + "requires": { + "decode-uri-component": "^0.2.0", + "split-on-first": "^1.0.0", + "strict-uri-encode": "^2.0.0" + } + }, + "qw": { + "version": "1.0.1", + "bundled": true + }, + "rc": { + "version": "1.2.8", + "bundled": true, + "requires": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + } + }, + "read": { + "version": "1.0.7", + "bundled": true, + "requires": { + "mute-stream": "~0.0.4" + } + }, + "read-cmd-shim": { + "version": "1.0.5", + "bundled": true, + "requires": { + "graceful-fs": "^4.1.2" + } + }, + "read-installed": { + "version": "4.0.3", + "bundled": true, + "requires": { + "debuglog": "^1.0.1", + "graceful-fs": "^4.1.2", + "read-package-json": "^2.0.0", + "readdir-scoped-modules": "^1.0.0", + "semver": "2 || 3 || 4 || 5", + "slide": "~1.1.3", + "util-extend": "^1.0.1" + } + }, + "read-package-json": { + "version": "2.1.1", + "bundled": true, + "requires": { + "glob": "^7.1.1", + "graceful-fs": "^4.1.2", + "json-parse-better-errors": "^1.0.1", + "normalize-package-data": "^2.0.0", + "npm-normalize-package-bin": "^1.0.0" + } + }, + "read-package-tree": { + "version": "5.3.1", + "bundled": true, + "requires": { + "read-package-json": "^2.0.0", + "readdir-scoped-modules": "^1.0.0", + "util-promisify": "^2.1.0" + } + }, + "readable-stream": { + "version": "3.6.0", + "bundled": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "readdir-scoped-modules": { + "version": "1.1.0", + "bundled": true, + "requires": { + "debuglog": "^1.0.1", + "dezalgo": "^1.0.0", + "graceful-fs": "^4.1.2", + "once": "^1.3.0" + } + }, + "registry-auth-token": { + "version": "3.4.0", + "bundled": true, + "requires": { + "rc": "^1.1.6", + "safe-buffer": "^5.0.1" + } + }, + "registry-url": { + "version": "3.1.0", + "bundled": true, + "requires": { + "rc": "^1.0.1" + } + }, + "request": { + "version": "2.88.0", + "bundled": true, + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.0", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.4.3", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + } + }, + "require-directory": { + "version": "2.1.1", + "bundled": true + }, + "require-main-filename": { + "version": "2.0.0", + "bundled": true + }, + "resolve-from": { + "version": "4.0.0", + "bundled": true + }, + "retry": { + "version": "0.12.0", + "bundled": true + }, + "rimraf": { + "version": "2.7.1", + "bundled": true, + "requires": { + "glob": "^7.1.3" + } + }, + "run-queue": { + "version": "1.0.3", + "bundled": true, + "requires": { + "aproba": "^1.1.1" + }, + "dependencies": { + "aproba": { + "version": "1.2.0", + "bundled": true + } + } + }, + "safe-buffer": { + "version": "5.1.2", + "bundled": true + }, + "safer-buffer": { + "version": "2.1.2", + "bundled": true + }, + "semver": { + "version": "5.7.1", + "bundled": true + }, + "semver-diff": { + "version": "2.1.0", + "bundled": true, + "requires": { + "semver": "^5.0.3" + } + }, + "set-blocking": { + "version": "2.0.0", + "bundled": true + }, + "sha": { + "version": "3.0.0", + "bundled": true, + "requires": { + "graceful-fs": "^4.1.2" + } + }, + "shebang-command": { + "version": "1.2.0", + "bundled": true, + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "bundled": true + }, + "signal-exit": { + "version": "3.0.2", + "bundled": true + }, + "slide": { + "version": "1.1.6", + "bundled": true + }, + "smart-buffer": { + "version": "4.1.0", + "bundled": true + }, + "socks": { + "version": "2.3.3", + "bundled": true, + "requires": { + "ip": "1.1.5", + "smart-buffer": "^4.1.0" + } + }, + "socks-proxy-agent": { + "version": "4.0.2", + "bundled": true, + "requires": { + "agent-base": "~4.2.1", + "socks": "~2.3.2" + }, + "dependencies": { + "agent-base": { + "version": "4.2.1", + "bundled": true, + "requires": { + "es6-promisify": "^5.0.0" + } + } + } + }, + "sorted-object": { + "version": "2.0.1", + "bundled": true + }, + "sorted-union-stream": { + "version": "2.1.3", + "bundled": true, + "requires": { + "from2": "^1.3.0", + "stream-iterate": "^1.1.0" + }, + "dependencies": { + "from2": { + "version": "1.3.0", + "bundled": true, + "requires": { + "inherits": "~2.0.1", + "readable-stream": "~1.1.10" + } + }, + "isarray": { + "version": "0.0.1", + "bundled": true + }, + "readable-stream": { + "version": "1.1.14", + "bundled": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "string_decoder": { + "version": "0.10.31", + "bundled": true + } + } + }, + "spdx-correct": { + "version": "3.0.0", + "bundled": true, + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.1.0", + "bundled": true + }, + "spdx-expression-parse": { + "version": "3.0.0", + "bundled": true, + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.5", + "bundled": true + }, + "split-on-first": { + "version": "1.1.0", + "bundled": true + }, + "sshpk": { + "version": "1.14.2", + "bundled": true, + "requires": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + } + }, + "ssri": { + "version": "6.0.1", + "bundled": true, + "requires": { + "figgy-pudding": "^3.5.1" + } + }, + "stream-each": { + "version": "1.2.2", + "bundled": true, + "requires": { + "end-of-stream": "^1.1.0", + "stream-shift": "^1.0.0" + } + }, + "stream-iterate": { + "version": "1.2.0", + "bundled": true, + "requires": { + "readable-stream": "^2.1.5", + "stream-shift": "^1.0.0" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.6", + "bundled": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "bundled": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "stream-shift": { + "version": "1.0.0", + "bundled": true + }, + "strict-uri-encode": { + "version": "2.0.0", + "bundled": true + }, + "string-width": { + "version": "2.1.1", + "bundled": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "bundled": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "bundled": true + }, + "strip-ansi": { + "version": "4.0.0", + "bundled": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "string_decoder": { + "version": "1.3.0", + "bundled": true, + "requires": { + "safe-buffer": "~5.2.0" + }, + "dependencies": { + "safe-buffer": { + "version": "5.2.0", + "bundled": true + } + } + }, + "stringify-package": { + "version": "1.0.1", + "bundled": true + }, + "strip-ansi": { + "version": "3.0.1", + "bundled": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-eof": { + "version": "1.0.0", + "bundled": true + }, + "strip-json-comments": { + "version": "2.0.1", + "bundled": true + }, + "supports-color": { + "version": "5.4.0", + "bundled": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "tar": { + "version": "4.4.13", + "bundled": true, + "requires": { + "chownr": "^1.1.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.8.6", + "minizlib": "^1.2.1", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.2", + "yallist": "^3.0.3" + }, + "dependencies": { + "minipass": { + "version": "2.9.0", + "bundled": true, + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + } + } + } + }, + "term-size": { + "version": "1.2.0", + "bundled": true, + "requires": { + "execa": "^0.7.0" + } + }, + "text-table": { + "version": "0.2.0", + "bundled": true + }, + "through": { + "version": "2.3.8", + "bundled": true + }, + "through2": { + "version": "2.0.3", + "bundled": true, + "requires": { + "readable-stream": "^2.1.5", + "xtend": "~4.0.1" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.6", + "bundled": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "bundled": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "timed-out": { + "version": "4.0.1", + "bundled": true + }, + "tiny-relative-date": { + "version": "1.3.0", + "bundled": true + }, + "tough-cookie": { + "version": "2.4.3", + "bundled": true, + "requires": { + "psl": "^1.1.24", + "punycode": "^1.4.1" + } + }, + "tunnel-agent": { + "version": "0.6.0", + "bundled": true, + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "bundled": true, + "optional": true + }, + "typedarray": { + "version": "0.0.6", + "bundled": true + }, + "uid-number": { + "version": "0.0.6", + "bundled": true + }, + "umask": { + "version": "1.1.0", + "bundled": true + }, + "unique-filename": { + "version": "1.1.1", + "bundled": true, + "requires": { + "unique-slug": "^2.0.0" + } + }, + "unique-slug": { + "version": "2.0.0", + "bundled": true, + "requires": { + "imurmurhash": "^0.1.4" + } + }, + "unique-string": { + "version": "1.0.0", + "bundled": true, + "requires": { + "crypto-random-string": "^1.0.0" + } + }, + "unpipe": { + "version": "1.0.0", + "bundled": true + }, + "unzip-response": { + "version": "2.0.1", + "bundled": true + }, + "update-notifier": { + "version": "2.5.0", + "bundled": true, + "requires": { + "boxen": "^1.2.1", + "chalk": "^2.0.1", + "configstore": "^3.0.0", + "import-lazy": "^2.1.0", + "is-ci": "^1.0.10", + "is-installed-globally": "^0.1.0", + "is-npm": "^1.0.0", + "latest-version": "^3.0.0", + "semver-diff": "^2.0.0", + "xdg-basedir": "^3.0.0" + } + }, + "uri-js": { + "version": "4.4.0", + "bundled": true, + "requires": { + "punycode": "^2.1.0" + }, + "dependencies": { + "punycode": { + "version": "2.1.1", + "bundled": true + } + } + }, + "url-parse-lax": { + "version": "1.0.0", + "bundled": true, + "requires": { + "prepend-http": "^1.0.1" + } + }, + "util-deprecate": { + "version": "1.0.2", + "bundled": true + }, + "util-extend": { + "version": "1.0.3", + "bundled": true + }, + "util-promisify": { + "version": "2.1.0", + "bundled": true, + "requires": { + "object.getownpropertydescriptors": "^2.0.3" + } + }, + "uuid": { + "version": "3.3.3", + "bundled": true + }, + "validate-npm-package-license": { + "version": "3.0.4", + "bundled": true, + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "validate-npm-package-name": { + "version": "3.0.0", + "bundled": true, + "requires": { + "builtins": "^1.0.3" + } + }, + "verror": { + "version": "1.10.0", + "bundled": true, + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "wcwidth": { + "version": "1.0.1", + "bundled": true, + "requires": { + "defaults": "^1.0.3" + } + }, + "which": { + "version": "1.3.1", + "bundled": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "which-module": { + "version": "2.0.0", + "bundled": true + }, + "wide-align": { + "version": "1.1.2", + "bundled": true, + "requires": { + "string-width": "^1.0.2" + }, + "dependencies": { + "string-width": { + "version": "1.0.2", + "bundled": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + } + } + }, + "widest-line": { + "version": "2.0.1", + "bundled": true, + "requires": { + "string-width": "^2.1.1" + } + }, + "worker-farm": { + "version": "1.7.0", + "bundled": true, + "requires": { + "errno": "~0.1.7" + } + }, + "wrap-ansi": { + "version": "5.1.0", + "bundled": true, + "requires": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "bundled": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "bundled": true + }, + "string-width": { + "version": "3.1.0", + "bundled": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "bundled": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "wrappy": { + "version": "1.0.2", + "bundled": true + }, + "write-file-atomic": { + "version": "2.4.3", + "bundled": true, + "requires": { + "graceful-fs": "^4.1.11", + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.2" + } + }, + "xdg-basedir": { + "version": "3.0.0", + "bundled": true + }, + "xtend": { + "version": "4.0.1", + "bundled": true + }, + "y18n": { + "version": "4.0.0", + "bundled": true + }, + "yallist": { + "version": "3.0.3", + "bundled": true + }, + "yargs": { + "version": "14.2.3", + "bundled": true, + "requires": { + "cliui": "^5.0.0", + "decamelize": "^1.2.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^15.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "bundled": true + }, + "find-up": { + "version": "3.0.0", + "bundled": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "bundled": true + }, + "locate-path": { + "version": "3.0.0", + "bundled": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "2.3.0", + "bundled": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "bundled": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.2.0", + "bundled": true + }, + "string-width": { + "version": "3.1.0", + "bundled": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "bundled": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "yargs-parser": { + "version": "15.0.1", + "bundled": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "dependencies": { + "camelcase": { + "version": "5.3.1", + "bundled": true + } + } + } + } + }, + "npm-conf": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/npm-conf/-/npm-conf-1.1.3.tgz", + "integrity": "sha512-Yic4bZHJOt9RCFbRP3GgpqhScOY4HH3V2P8yBj6CeYq118Qr+BLXqT2JvpJ00mryLESpgOxf5XlFv4ZjXxLScw==", + "dev": true, + "optional": true, + "requires": { + "config-chain": "^1.1.11", + "pify": "^3.0.0" + }, + "dependencies": { + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true, + "optional": true + } + } + }, + "npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "dev": true, + "optional": true, + "requires": { + "path-key": "^2.0.0" + } + }, + "nth-check": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", + "integrity": "sha1-sr0pXDfj3VijvwcAN2Zjuk2c8Fw=", + "dev": true, + "requires": { + "boolbase": "~1.0.0" + } + }, + "num2fraction": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/num2fraction/-/num2fraction-1.2.2.tgz", + "integrity": "sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4=", + "dev": true + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "dev": true + }, + "nwsapi": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz", + "integrity": "sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ==", + "dev": true + }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha1-R6ewFrqmi1+g7PPe4IqFxnmsZFU=", + "dev": true + }, + "object-assign": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-3.0.0.tgz", + "integrity": "sha1-m+3VygiXlJvKR+f/QIBi1Un1h/I=", + "dev": true + }, + "object-component": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/object-component/-/object-component-0.0.3.tgz", + "integrity": "sha1-8MaapQ78lbhmwYb0AKM3acsvEpE=", + "dev": true + }, + "object-copy": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", + "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", + "dev": true, + "requires": { + "copy-descriptor": "^0.1.0", + "define-property": "^0.2.5", + "kind-of": "^3.0.3" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "object-inspect": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.6.0.tgz", + "integrity": "sha512-GJzfBZ6DgDAmnuaM3104jR4s1Myxr3Y3zfIyN4z3UdqN69oSRacNK8UhnobDdC+7J2AHCjGwxQubNJfE70SXXQ==", + "dev": true + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true + }, + "object-visit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", + "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", + "dev": true, + "requires": { + "isobject": "^3.0.0" + } + }, + "object.assign": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", + "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.0", + "object-keys": "^1.0.11" + } + }, + "object.defaults": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/object.defaults/-/object.defaults-1.1.0.tgz", + "integrity": "sha1-On+GgzS0B96gbaFtiNXNKeQ1/s8=", + "dev": true, + "requires": { + "array-each": "^1.0.1", + "array-slice": "^1.0.0", + "for-own": "^1.0.0", + "isobject": "^3.0.0" + } + }, + "object.getownpropertydescriptors": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz", + "integrity": "sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY=", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "es-abstract": "^1.5.1" + } + }, + "object.map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object.map/-/object.map-1.0.1.tgz", + "integrity": "sha1-z4Plncj8wK1fQlDh94s7gb2AHTc=", + "dev": true, + "requires": { + "for-own": "^1.0.0", + "make-iterator": "^1.0.0" + } + }, + "object.omit": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz", + "integrity": "sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo=", + "dev": true, + "requires": { + "for-own": "^0.1.4", + "is-extendable": "^0.1.1" + }, + "dependencies": { + "for-own": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", + "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=", + "dev": true, + "requires": { + "for-in": "^1.0.1" + } + } + } + }, + "object.pick": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", + "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } + }, + "object.reduce": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object.reduce/-/object.reduce-1.0.1.tgz", + "integrity": "sha1-b+NI8qx/oPlcpiEiZZkJaCW7A60=", + "dev": true, + "requires": { + "for-own": "^1.0.0", + "make-iterator": "^1.0.0" + } + }, + "object.values": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.0.tgz", + "integrity": "sha512-8mf0nKLAoFX6VlNVdhGj31SVYpaNFtUnuoOXWyFEstsWRgU837AK+JYM0iAxwkSzGRbwn8cbFmgbyxj1j4VbXg==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.12.0", + "function-bind": "^1.1.1", + "has": "^1.0.3" + } + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "dev": true, + "requires": { + "ee-first": "1.1.1" + } + }, + "once": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/once/-/once-1.3.3.tgz", + "integrity": "sha1-suJhVXzkwxTsgwTz+oJmPkKXyiA=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "onetime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", + "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", + "dev": true, + "requires": { + "mimic-fn": "^1.0.0" + } + }, + "optimist": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", + "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", + "dev": true, + "requires": { + "minimist": "~0.0.1", + "wordwrap": "~0.0.2" + }, + "dependencies": { + "minimist": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", + "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=", + "dev": true + }, + "wordwrap": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", + "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", + "dev": true + } + } + }, + "optionator": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", + "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", + "dev": true, + "requires": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.4", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "wordwrap": "~1.0.0" + } + }, + "optipng-bin": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/optipng-bin/-/optipng-bin-6.0.0.tgz", + "integrity": "sha512-95bB4y8IaTsa/8x6QH4bLUuyvyOoGBCLDA7wOgDL8UFqJpSUh1Hob8JRJhit+wC1ZLN3tQ7mFt7KuBj0x8F2Wg==", + "dev": true, + "optional": true, + "requires": { + "bin-build": "^3.0.0", + "bin-wrapper": "^4.0.0", + "logalot": "^2.0.0" + } + }, + "ordered-read-streams": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ordered-read-streams/-/ordered-read-streams-1.0.1.tgz", + "integrity": "sha1-d8DLN8QVJdZBZtmQ/61+xqDhNj4=", + "dev": true, + "requires": { + "readable-stream": "^2.0.1" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "os-filter-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/os-filter-obj/-/os-filter-obj-2.0.0.tgz", + "integrity": "sha512-uksVLsqG3pVdzzPvmAHpBK0wKxYItuzZr7SziusRPoz67tGV8rL1szZ6IdeUrbqLjGDwApBtN29eEE3IqGHOjg==", + "dev": true, + "optional": true, + "requires": { + "arch": "^2.1.0" + } + }, + "os-locale": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", + "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", + "dev": true, + "requires": { + "lcid": "^1.0.0" + } + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "dev": true + }, + "p-cancelable": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-0.3.0.tgz", + "integrity": "sha512-RVbZPLso8+jFeq1MfNvgXtCRED2raz/dKpacfTNxsx6pLEpEomM7gah6VeHSYV3+vo0OAi4MkArtQcWWXuQoyw==", + "dev": true, + "optional": true + }, + "p-event": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-event/-/p-event-1.3.0.tgz", + "integrity": "sha1-jmtPT2XHK8W2/ii3XtqHT5akoIU=", + "dev": true, + "optional": true, + "requires": { + "p-timeout": "^1.1.1" + } + }, + "p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", + "dev": true, + "optional": true + }, + "p-is-promise": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-1.1.0.tgz", + "integrity": "sha1-nJRWmJ6fZYgBewQ01WCXZ1w9oF4=", + "dev": true, + "optional": true + }, + "p-map-series": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-map-series/-/p-map-series-1.0.0.tgz", + "integrity": "sha1-v5j+V1cFZYqeE1G++4WuTB8Hvco=", + "dev": true, + "optional": true, + "requires": { + "p-reduce": "^1.0.0" + } + }, + "p-pipe": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-pipe/-/p-pipe-3.0.0.tgz", + "integrity": "sha512-gwwdRFmaxsT3IU+Tl3vYKVRdjfhg8Bbdjw7B+E0y6F7Yz6l+eaQLn0BRmGMXIhcPDONPtOkMoNwx1etZh4zPJA==", + "dev": true + }, + "p-reduce": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-reduce/-/p-reduce-1.0.0.tgz", + "integrity": "sha1-GMKw3ZNqRpClKfgjH1ig/bakffo=", + "dev": true, + "optional": true + }, + "p-timeout": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-1.2.1.tgz", + "integrity": "sha1-XrOzU7f86Z8QGhA4iAuwVOu+o4Y=", + "dev": true, + "optional": true, + "requires": { + "p-finally": "^1.0.0" + } + }, + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "requires": { + "callsites": "^3.0.0" + }, + "dependencies": { + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true + } + } + }, + "parse-filepath": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/parse-filepath/-/parse-filepath-1.0.2.tgz", + "integrity": "sha1-pjISf1Oq89FYdvWHLz/6x2PWyJE=", + "dev": true, + "requires": { + "is-absolute": "^1.0.0", + "map-cache": "^0.2.0", + "path-root": "^0.1.1" + } + }, + "parse-glob": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz", + "integrity": "sha1-ssN2z7EfNVE7rdFz7wu246OIORw=", + "dev": true, + "requires": { + "glob-base": "^0.3.0", + "is-dotfile": "^1.0.0", + "is-extglob": "^1.0.0", + "is-glob": "^2.0.0" + }, + "dependencies": { + "is-extglob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", + "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", + "dev": true + }, + "is-glob": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", + "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", + "dev": true, + "requires": { + "is-extglob": "^1.0.0" + } + } + } + }, + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "dev": true, + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + }, + "parse-node-version": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.1.tgz", + "integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==", + "dev": true + }, + "parse-passwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", + "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=", + "dev": true + }, + "parse5": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", + "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==", + "dev": true + }, + "parseqs": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.5.tgz", + "integrity": "sha1-1SCKNzjkZ2bikbouoXNoSSGouJ0=", + "dev": true, + "requires": { + "better-assert": "~1.0.0" + } + }, + "parseuri": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.5.tgz", + "integrity": "sha1-gCBKUNTbt3m/3G6+J3jZDkvOMgo=", + "dev": true, + "requires": { + "better-assert": "~1.0.0" + } + }, + "parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true + }, + "pascalcase": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", + "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", + "dev": true + }, + "path-dirname": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", + "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=", + "dev": true + }, + "path-exists": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", + "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", + "dev": true, + "requires": { + "pinkie-promise": "^2.0.0" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true + }, + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha1-1i27VnlAXXLEc37FhgDp3c8G0kw=", + "dev": true + }, + "path-root": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/path-root/-/path-root-0.1.1.tgz", + "integrity": "sha1-mkpoFMrBwM1zNgqV8yCDyOpHRbc=", + "dev": true, + "requires": { + "path-root-regex": "^0.1.0" + } + }, + "path-root-regex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/path-root-regex/-/path-root-regex-0.1.2.tgz", + "integrity": "sha1-v8zcjfWxLcUsi0PsONGNcsBLqW0=", + "dev": true + }, + "path-type": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", + "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + }, + "dependencies": { + "graceful-fs": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.2.tgz", + "integrity": "sha512-IItsdsea19BoLC7ELy13q1iJFNmd7ofZH5+X/pJr90/nRoPEX0DJo1dHDbgtYWOhJhcCgMDTOw84RZ72q6lB+Q==", + "dev": true + } + } + }, + "pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=", + "dev": true, + "optional": true + }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", + "dev": true + }, + "picomatch": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.0.7.tgz", + "integrity": "sha512-oLHIdio3tZ0qH76NybpeneBhYVj0QFTfXEFTc/B3zKQspYfYYkWYgFsmzo+4kvId/bQRcNkVeguI3y+CD22BtA==", + "dev": true + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + }, + "pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", + "dev": true + }, + "pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "dev": true, + "requires": { + "pinkie": "^2.0.0" + } + }, + "plugin-error": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-1.0.1.tgz", + "integrity": "sha1-dwFr2JGdCsN3/c3QMiMolTyleBw=", + "dev": true, + "requires": { + "ansi-colors": "^1.0.1", + "arr-diff": "^4.0.0", + "arr-union": "^3.1.0", + "extend-shallow": "^3.0.2" + } + }, + "plur": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/plur/-/plur-3.1.1.tgz", + "integrity": "sha512-t1Ax8KUvV3FFII8ltczPn2tJdjqbd1sIzu6t4JL7nQ3EyeL/lTrj5PWKb06ic5/6XYDr65rQ4uzQEGN70/6X5w==", + "dev": true, + "requires": { + "irregular-plurals": "^2.0.0" + } + }, + "posix-character-classes": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", + "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", + "dev": true + }, + "postcss": { + "version": "7.0.18", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.18.tgz", + "integrity": "sha512-/7g1QXXgegpF+9GJj4iN7ChGF40sYuGYJ8WZu8DZWnmhQ/G36hfdk3q9LBJmoK+lZ+yzZ5KYpOoxq7LF1BxE8g==", + "dev": true, + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM=", + "dev": true + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-calc": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-7.0.1.tgz", + "integrity": "sha1-Ntd7qwI7Dsu5eJ2E3LI8SUEUVDY=", + "dev": true, + "requires": { + "css-unit-converter": "^1.1.1", + "postcss": "^7.0.5", + "postcss-selector-parser": "^5.0.0-rc.4", + "postcss-value-parser": "^3.3.1" + } + }, + "postcss-colormin": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-4.0.3.tgz", + "integrity": "sha512-WyQFAdDZpExQh32j0U0feWisZ0dmOtPl44qYmJKkq9xFWY3p+4qnRzCHeNrkeRhwPHz9bQ3mo0/yVkaply0MNw==", + "dev": true, + "requires": { + "browserslist": "^4.0.0", + "color": "^3.0.0", + "has": "^1.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + } + }, + "postcss-convert-values": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-4.0.1.tgz", + "integrity": "sha1-yjgT7U2g+BL51DcDWE5Enr4Ymn8=", + "dev": true, + "requires": { + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + } + }, + "postcss-discard-comments": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-4.0.2.tgz", + "integrity": "sha512-RJutN259iuRf3IW7GZyLM5Sw4GLTOH8FmsXBnv8Ab/Tc2k4SR4qbV4DNbyyY4+Sjo362SyDmW2DQ7lBSChrpkg==", + "dev": true, + "requires": { + "postcss": "^7.0.0" + } + }, + "postcss-discard-duplicates": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-4.0.2.tgz", + "integrity": "sha1-P+EzzTyCKC5VD8myORdqkge3hOs=", + "dev": true, + "requires": { + "postcss": "^7.0.0" + } + }, + "postcss-discard-empty": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-4.0.1.tgz", + "integrity": "sha1-yMlR6fc+2UKAGUWERKAq2Qu592U=", + "dev": true, + "requires": { + "postcss": "^7.0.0" + } + }, + "postcss-discard-overridden": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-4.0.1.tgz", + "integrity": "sha1-ZSrvipZybwKfXj4AFG7npOdV/1c=", + "dev": true, + "requires": { + "postcss": "^7.0.0" + } + }, + "postcss-load-config": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-2.1.0.tgz", + "integrity": "sha512-4pV3JJVPLd5+RueiVVB+gFOAa7GWc25XQcMp86Zexzke69mKf6Nx9LRcQywdz7yZI9n1udOxmLuAwTBypypF8Q==", + "dev": true, + "requires": { + "cosmiconfig": "^5.0.0", + "import-cwd": "^2.0.0" + } + }, + "postcss-merge-longhand": { + "version": "4.0.11", + "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-4.0.11.tgz", + "integrity": "sha512-alx/zmoeXvJjp7L4mxEMjh8lxVlDFX1gqWHzaaQewwMZiVhLo42TEClKaeHbRf6J7j82ZOdTJ808RtN0ZOZwvw==", + "dev": true, + "requires": { + "css-color-names": "0.0.4", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0", + "stylehacks": "^4.0.0" + } + }, + "postcss-merge-rules": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-4.0.3.tgz", + "integrity": "sha512-U7e3r1SbvYzO0Jr3UT/zKBVgYYyhAz0aitvGIYOYK5CPmkNih+WDSsS5tvPrJ8YMQYlEMvsZIiqmn7HdFUaeEQ==", + "dev": true, + "requires": { + "browserslist": "^4.0.0", + "caniuse-api": "^3.0.0", + "cssnano-util-same-parent": "^4.0.0", + "postcss": "^7.0.0", + "postcss-selector-parser": "^3.0.0", + "vendors": "^1.0.0" + }, + "dependencies": { + "postcss-selector-parser": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-3.1.1.tgz", + "integrity": "sha1-T4dfSvsMllc9XPTXQBGu4lCn6GU=", + "dev": true, + "requires": { + "dot-prop": "^4.1.1", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" + } + } + } + }, + "postcss-minify-font-values": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-4.0.2.tgz", + "integrity": "sha1-zUw0TM5HQ0P6xdgiBqssvLiv1aY=", + "dev": true, + "requires": { + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + } + }, + "postcss-minify-gradients": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-4.0.2.tgz", + "integrity": "sha512-qKPfwlONdcf/AndP1U8SJ/uzIJtowHlMaSioKzebAXSG4iJthlWC9iSWznQcX4f66gIWX44RSA841HTHj3wK+Q==", + "dev": true, + "requires": { + "cssnano-util-get-arguments": "^4.0.0", + "is-color-stop": "^1.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + } + }, + "postcss-minify-params": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-4.0.2.tgz", + "integrity": "sha512-G7eWyzEx0xL4/wiBBJxJOz48zAKV2WG3iZOqVhPet/9geefm/Px5uo1fzlHu+DOjT+m0Mmiz3jkQzVHe6wxAWg==", + "dev": true, + "requires": { + "alphanum-sort": "^1.0.0", + "browserslist": "^4.0.0", + "cssnano-util-get-arguments": "^4.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0", + "uniqs": "^2.0.0" + } + }, + "postcss-minify-selectors": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-4.0.2.tgz", + "integrity": "sha512-D5S1iViljXBj9kflQo4YutWnJmwm8VvIsU1GeXJGiG9j8CIg9zs4voPMdQDUmIxetUOh60VilsNzCiAFTOqu3g==", + "dev": true, + "requires": { + "alphanum-sort": "^1.0.0", + "has": "^1.0.0", + "postcss": "^7.0.0", + "postcss-selector-parser": "^3.0.0" + }, + "dependencies": { + "postcss-selector-parser": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-3.1.1.tgz", + "integrity": "sha1-T4dfSvsMllc9XPTXQBGu4lCn6GU=", + "dev": true, + "requires": { + "dot-prop": "^4.1.1", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" + } + } + } + }, + "postcss-normalize-charset": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-4.0.1.tgz", + "integrity": "sha1-izWt067oOhNrBHHg1ZvlilAoXdQ=", + "dev": true, + "requires": { + "postcss": "^7.0.0" + } + }, + "postcss-normalize-display-values": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-4.0.2.tgz", + "integrity": "sha512-3F2jcsaMW7+VtRMAqf/3m4cPFhPD3EFRgNs18u+k3lTJJlVe7d0YPO+bnwqo2xg8YiRpDXJI2u8A0wqJxMsQuQ==", + "dev": true, + "requires": { + "cssnano-util-get-match": "^4.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + } + }, + "postcss-normalize-positions": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-4.0.2.tgz", + "integrity": "sha512-Dlf3/9AxpxE+NF1fJxYDeggi5WwV35MXGFnnoccP/9qDtFrTArZ0D0R+iKcg5WsUd8nUYMIl8yXDCtcrT8JrdA==", + "dev": true, + "requires": { + "cssnano-util-get-arguments": "^4.0.0", + "has": "^1.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + } + }, + "postcss-normalize-repeat-style": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-4.0.2.tgz", + "integrity": "sha512-qvigdYYMpSuoFs3Is/f5nHdRLJN/ITA7huIoCyqqENJe9PvPmLhNLMu7QTjPdtnVf6OcYYO5SHonx4+fbJE1+Q==", + "dev": true, + "requires": { + "cssnano-util-get-arguments": "^4.0.0", + "cssnano-util-get-match": "^4.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + } + }, + "postcss-normalize-string": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-4.0.2.tgz", + "integrity": "sha512-RrERod97Dnwqq49WNz8qo66ps0swYZDSb6rM57kN2J+aoyEAJfZ6bMx0sx/F9TIEX0xthPGCmeyiam/jXif0eA==", + "dev": true, + "requires": { + "has": "^1.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + } + }, + "postcss-normalize-timing-functions": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-4.0.2.tgz", + "integrity": "sha512-acwJY95edP762e++00Ehq9L4sZCEcOPyaHwoaFOhIwWCDfik6YvqsYNxckee65JHLKzuNSSmAdxwD2Cud1Z54A==", + "dev": true, + "requires": { + "cssnano-util-get-match": "^4.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + } + }, + "postcss-normalize-unicode": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-4.0.1.tgz", + "integrity": "sha1-hBvUj9zzAZrUuqdJOj02O1KuHPs=", + "dev": true, + "requires": { + "browserslist": "^4.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + } + }, + "postcss-normalize-url": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-4.0.1.tgz", + "integrity": "sha1-EOQ3+GvHx+WPe5ZS7YeNqqlfquE=", + "dev": true, + "requires": { + "is-absolute-url": "^2.0.0", + "normalize-url": "^3.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + } + }, + "postcss-normalize-whitespace": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-4.0.2.tgz", + "integrity": "sha512-tO8QIgrsI3p95r8fyqKV+ufKlSHh9hMJqACqbv2XknufqEDhDvbguXGBBqxw9nsQoXWf0qOqppziKJKHMD4GtA==", + "dev": true, + "requires": { + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + } + }, + "postcss-ordered-values": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-4.1.2.tgz", + "integrity": "sha512-2fCObh5UanxvSxeXrtLtlwVThBvHn6MQcu4ksNT2tsaV2Fg76R2CV98W7wNSlX+5/pFwEyaDwKLLoEV7uRybAw==", + "dev": true, + "requires": { + "cssnano-util-get-arguments": "^4.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + } + }, + "postcss-reduce-initial": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-4.0.3.tgz", + "integrity": "sha512-gKWmR5aUulSjbzOfD9AlJiHCGH6AEVLaM0AV+aSioxUDd16qXP1PCh8d1/BGVvpdWn8k/HiK7n6TjeoXN1F7DA==", + "dev": true, + "requires": { + "browserslist": "^4.0.0", + "caniuse-api": "^3.0.0", + "has": "^1.0.0", + "postcss": "^7.0.0" + } + }, + "postcss-reduce-transforms": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-4.0.2.tgz", + "integrity": "sha512-EEVig1Q2QJ4ELpJXMZR8Vt5DQx8/mo+dGWSR7vWXqcob2gQLyQGsionYcGKATXvQzMPn6DSN1vTN7yFximdIAg==", + "dev": true, + "requires": { + "cssnano-util-get-match": "^4.0.0", + "has": "^1.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + } + }, + "postcss-selector-parser": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-5.0.0.tgz", + "integrity": "sha512-w+zLE5Jhg6Liz8+rQOWEAwtwkyqpfnmsinXjXg6cY7YIONZZtgvE0v2O0uhQBs0peNomOJwWRKt6JBfTdTd3OQ==", + "dev": true, + "requires": { + "cssesc": "^2.0.0", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" + } + }, + "postcss-svgo": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-4.0.2.tgz", + "integrity": "sha512-C6wyjo3VwFm0QgBy+Fu7gCYOkCmgmClghO+pjcxvrcBKtiKt0uCF+hvbMO1fyv5BMImRK90SMb+dwUnfbGd+jw==", + "dev": true, + "requires": { + "is-svg": "^3.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0", + "svgo": "^1.0.0" + } + }, + "postcss-unique-selectors": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-4.0.1.tgz", + "integrity": "sha1-lEaRHzKJv9ZMbWgPBzwDsfnuS6w=", + "dev": true, + "requires": { + "alphanum-sort": "^1.0.0", + "postcss": "^7.0.0", + "uniqs": "^2.0.0" + } + }, + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha1-n/giVH4okyE88cMO+lGsX9G6goE=", + "dev": true + }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "dev": true + }, + "prepend-http": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", + "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=", + "dev": true, + "optional": true + }, + "preserve": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz", + "integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=", + "dev": true + }, + "pretty-bytes": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.3.0.tgz", + "integrity": "sha512-hjGrh+P926p4R4WbaB6OckyRtO0F0/lQBiT+0gnxjV+5kjPBrfVBFCsCLbMqVQeydvIoouYTCmmEURiH3R1Bdg==", + "dev": true + }, + "pretty-hrtime": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", + "integrity": "sha1-t+PqQkNaTJsnWdmeDyAesZWALuE=", + "dev": true + }, + "private": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", + "integrity": "sha1-I4Hts2ifelPWUxkAYPz4ItLzaP8=", + "dev": true + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true + }, + "progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true + }, + "promise": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", + "integrity": "sha1-BktyYCsY+Q8pGSuLG8QY/9Hr078=", + "dev": true, + "optional": true, + "requires": { + "asap": "~2.0.3" + } + }, + "proto-list": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", + "integrity": "sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk=", + "dev": true, + "optional": true + }, + "prr": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", + "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=", + "dev": true, + "optional": true + }, + "pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", + "dev": true + }, + "psl": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.4.0.tgz", + "integrity": "sha512-HZzqCGPecFLyoRj5HLfuDSKYTJkAfB5thKBIkRHtGjWwY7p1dAyveIbXIq4tO0KYfDF2tHqPUgY9SDnGm00uFw==", + "dev": true + }, + "pump": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", + "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "pumpify": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz", + "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", + "dev": true, + "requires": { + "duplexify": "^3.6.0", + "inherits": "^2.0.3", + "pump": "^2.0.0" + } + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha1-tYsBCsQMIsVldhbI0sLALHv0eew=", + "dev": true + }, + "q": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", + "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=", + "dev": true + }, + "qjobs": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/qjobs/-/qjobs-1.2.0.tgz", + "integrity": "sha1-xF6cYYAL0IfviNfiVkI73Unl0HE=", + "dev": true + }, + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", + "dev": true + }, + "query-string": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-5.1.1.tgz", + "integrity": "sha512-gjWOsm2SoGlgLEdAGt7a6slVOk9mGiXmPFMqrEhLQ68rhQuBnpfs3+EmlvqKyxnCo9/PPlF+9MtY02S1aFg+Jw==", + "dev": true, + "optional": true, + "requires": { + "decode-uri-component": "^0.2.0", + "object-assign": "^4.1.0", + "strict-uri-encode": "^1.0.0" + }, + "dependencies": { + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true, + "optional": true + } + } + }, + "randomatic": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-3.1.1.tgz", + "integrity": "sha1-t3bvxZN1mE42xTey9RofCv8Noe0=", + "dev": true, + "requires": { + "is-number": "^4.0.0", + "kind-of": "^6.0.0", + "math-random": "^1.0.1" + }, + "dependencies": { + "is-number": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", + "integrity": "sha1-ACbjf1RU1z41bf5lZGmYZ8an8P8=", + "dev": true + } + } + }, + "range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "dev": true + }, + "raw-body": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", + "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", + "dev": true, + "requires": { + "bytes": "3.1.0", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + } + }, + "read-pkg": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", + "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", + "dev": true, + "requires": { + "load-json-file": "^1.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^1.0.0" + } + }, + "read-pkg-up": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", + "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", + "dev": true, + "requires": { + "find-up": "^1.0.0", + "read-pkg": "^1.0.0" + } + }, + "readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "readdirp": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", + "integrity": "sha1-DodiKjMlqjPokihcr4tOhGUppSU=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.11", + "micromatch": "^3.1.10", + "readable-stream": "^2.0.2" + }, + "dependencies": { + "graceful-fs": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.2.tgz", + "integrity": "sha512-IItsdsea19BoLC7ELy13q1iJFNmd7ofZH5+X/pJr90/nRoPEX0DJo1dHDbgtYWOhJhcCgMDTOw84RZ72q6lB+Q==", + "dev": true + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha1-sRwn2IuP8fvgcGQ8+UsMea4bCq8=", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha1-nPFhG6YmhdcDCunkujQUnDrwP8g=", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "rechoir": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", + "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", + "dev": true, + "requires": { + "resolve": "^1.1.6" + } + }, + "redent": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz", + "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=", + "dev": true, + "optional": true, + "requires": { + "indent-string": "^2.1.0", + "strip-indent": "^1.0.1" + } + }, + "regenerate": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz", + "integrity": "sha1-SoVuxLVuQHfFV1icroXnpMiGmhE=", + "dev": true + }, + "regenerate-unicode-properties": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-8.1.0.tgz", + "integrity": "sha512-LGZzkgtLY79GeXLm8Dp0BVLdQlWICzBnJz/ipWUgo59qBaZ+BHtq51P2q1uVZlppMuUAT37SDk39qUbjTWB7bA==", + "dev": true, + "requires": { + "regenerate": "^1.4.0" + } + }, + "regenerator-transform": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.1.tgz", + "integrity": "sha512-flVuee02C3FKRISbxhXl9mGzdbWUVHubl1SMaknjxkFB1/iqpJhArQUvRxOOPEc/9tAiX0BaQ28FJH10E4isSQ==", + "dev": true, + "requires": { + "private": "^0.1.6" + } + }, + "regex-cache": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.4.tgz", + "integrity": "sha1-db3FiioUls7EihKDW8VMjVYjNt0=", + "dev": true, + "requires": { + "is-equal-shallow": "^0.1.3" + } + }, + "regex-not": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", + "integrity": "sha1-H07OJ+ALC2XgJHpoEOaoXYOldSw=", + "dev": true, + "requires": { + "extend-shallow": "^3.0.2", + "safe-regex": "^1.1.0" + } + }, + "regexpp": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz", + "integrity": "sha1-jRnTHPYySCtYkEn4KB+T28uk0H8=", + "dev": true + }, + "regexpu-core": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.6.0.tgz", + "integrity": "sha512-YlVaefl8P5BnFYOITTNzDvan1ulLOiXJzCNZxduTIosN17b87h3bvG9yHMoHaRuo88H4mQ06Aodj5VtYGGGiTg==", + "dev": true, + "requires": { + "regenerate": "^1.4.0", + "regenerate-unicode-properties": "^8.1.0", + "regjsgen": "^0.5.0", + "regjsparser": "^0.6.0", + "unicode-match-property-ecmascript": "^1.0.4", + "unicode-match-property-value-ecmascript": "^1.1.0" + } + }, + "regjsgen": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.1.tgz", + "integrity": "sha512-5qxzGZjDs9w4tzT3TPhCJqWdCc3RLYwy9J2NB0nm5Lz+S273lvWcpjaTGHsT1dc6Hhfq41uSEOw8wBmxrKOuyg==", + "dev": true + }, + "regjsparser": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.0.tgz", + "integrity": "sha512-RQ7YyokLiQBomUJuUG8iGVvkgOLxwyZM8k6d3q5SAXpg4r5TZJZigKFvC6PpD+qQ98bCDC5YelPeA3EucDoNeQ==", + "dev": true, + "requires": { + "jsesc": "~0.5.0" + }, + "dependencies": { + "jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", + "dev": true + } + } + }, + "remove-bom-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/remove-bom-buffer/-/remove-bom-buffer-3.0.0.tgz", + "integrity": "sha512-8v2rWhaakv18qcvNeli2mZ/TMTL2nEyAKRvzo1WtnZBl15SHyEhrCu2/xKlJyUFKHiHgfXIyuY6g2dObJJycXQ==", + "dev": true, + "requires": { + "is-buffer": "^1.1.5", + "is-utf8": "^0.2.1" + } + }, + "remove-bom-stream": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/remove-bom-stream/-/remove-bom-stream-1.2.0.tgz", + "integrity": "sha1-BfGlk/FuQuH7kOv1nejlaVJflSM=", + "dev": true, + "requires": { + "remove-bom-buffer": "^3.0.0", + "safe-buffer": "^5.1.0", + "through2": "^2.0.3" + } + }, + "remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", + "dev": true + }, + "repeat-element": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", + "integrity": "sha1-eC4NglwMWjuzlzH4Tv7mt0Lmsc4=", + "dev": true + }, + "repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", + "dev": true + }, + "repeating": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", + "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", + "dev": true, + "optional": true, + "requires": { + "is-finite": "^1.0.0" + } + }, + "replace-ext": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-0.0.1.tgz", + "integrity": "sha1-KbvZIHinOfC8zitO5B6DeVNSKSQ=", + "dev": true + }, + "replace-homedir": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/replace-homedir/-/replace-homedir-1.0.0.tgz", + "integrity": "sha1-6H9tUTuSjd6AgmDBK+f+xv9ueYw=", + "dev": true, + "requires": { + "homedir-polyfill": "^1.0.1", + "is-absolute": "^1.0.0", + "remove-trailing-separator": "^1.1.0" + } + }, + "request": { + "version": "2.88.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", + "integrity": "sha1-nC/KT301tZLv5Xx/ClXoEFIST+8=", + "dev": true, + "optional": true, + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.0", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.4.3", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + }, + "dependencies": { + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "dev": true, + "optional": true, + "requires": { + "safe-buffer": "^5.0.1" + } + } + } + }, + "request-promise-core": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.4.tgz", + "integrity": "sha512-TTbAfBBRdWD7aNNOoVOBH4pN/KigV6LyapYNNlAPA8JwbovRti1E88m3sYAwsLi5ryhPKsE9APwnjFTgdUjTpw==", + "dev": true, + "requires": { + "lodash": "^4.17.19" + } + }, + "request-promise-native": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.9.tgz", + "integrity": "sha512-wcW+sIUiWnKgNY0dqCpOZkUbF/I+YPi+f09JZIDa39Ec+q82CpSYniDp+ISgTTbKmnpJWASeJBPZmoxH84wt3g==", + "dev": true, + "requires": { + "request-promise-core": "1.1.4", + "stealthy-require": "^1.1.1", + "tough-cookie": "^2.3.3" + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true + }, + "require-main-filename": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", + "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", + "dev": true + }, + "requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", + "dev": true + }, + "resolve": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.12.0.tgz", + "integrity": "sha512-B/dOmuoAik5bKcD6s6nXDCjzUKnaDvdkRyAk6rsmsKLipWj4797iothd7jmmUhWTfinVMU+wc56rYKsit2Qy4w==", + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } + }, + "resolve-dir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz", + "integrity": "sha1-eaQGRMNivoLybv/nOcm7U4IEb0M=", + "dev": true, + "requires": { + "expand-tilde": "^2.0.0", + "global-modules": "^1.0.0" + } + }, + "resolve-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", + "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", + "dev": true + }, + "resolve-options": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/resolve-options/-/resolve-options-1.1.0.tgz", + "integrity": "sha1-MrueOcBtZzONyTeMDW1gdFZq0TE=", + "dev": true, + "requires": { + "value-or-function": "^3.0.0" + } + }, + "resolve-url": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", + "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", + "dev": true + }, + "responselike": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", + "integrity": "sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec=", + "dev": true, + "optional": true, + "requires": { + "lowercase-keys": "^1.0.0" + } + }, + "restore-cursor": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", + "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", + "dev": true, + "requires": { + "onetime": "^2.0.0", + "signal-exit": "^3.0.2" + } + }, + "ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha1-uKSCXVvbH8P29Twrwz+BOIaBx7w=", + "dev": true + }, + "reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true + }, + "rfdc": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.1.4.tgz", + "integrity": "sha512-5C9HXdzK8EAqN7JDif30jqsBzavB7wLpaubisuQIGHWf2gUXSpzy6ArX/+Da8RjFpagWsCn+pIgxTMAmKw9Zug==", + "dev": true + }, + "rgb-regex": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/rgb-regex/-/rgb-regex-1.0.1.tgz", + "integrity": "sha1-wODWiC3w4jviVKR16O3UGRX+rrE=", + "dev": true + }, + "rgba-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/rgba-regex/-/rgba-regex-1.0.0.tgz", + "integrity": "sha1-QzdOLiyglosO8VI0YLfXMP8i7rM=", + "dev": true + }, + "right-align": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", + "integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=", + "dev": true, + "requires": { + "align-text": "^0.1.1" + } + }, + "rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "run-async": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", + "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=", + "dev": true, + "requires": { + "is-promise": "^2.1.0" + } + }, + "run-parallel": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.1.9.tgz", + "integrity": "sha512-DEqnSRTDw/Tc3FXf49zedI638Z9onwUotBMiUFKmrO2sdFKIbXamXGQ3Axd4qgphxKB4kw/qP1w5kTxnfU1B9Q==", + "dev": true + }, + "run-sequence": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/run-sequence/-/run-sequence-2.2.1.tgz", + "integrity": "sha1-HOZD2jb9jH6n4akynaM/wriJhJU=", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "fancy-log": "^1.3.2", + "plugin-error": "^0.1.2" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "arr-diff": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-1.1.0.tgz", + "integrity": "sha1-aHwydYFjWI/vfeezb6vklesaOZo=", + "dev": true, + "requires": { + "arr-flatten": "^1.0.1", + "array-slice": "^0.2.3" + } + }, + "arr-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-2.1.0.tgz", + "integrity": "sha1-IPnqtexw9cfSFbEHexw5Fh0pLH0=", + "dev": true + }, + "array-slice": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-0.2.3.tgz", + "integrity": "sha1-3Tz7gO15c6dRF82sabC5nshhhvU=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "extend-shallow": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-1.1.4.tgz", + "integrity": "sha1-Gda/lN/AnXa6cR85uHLSH/TdkHE=", + "dev": true, + "requires": { + "kind-of": "^1.1.0" + } + }, + "kind-of": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-1.1.0.tgz", + "integrity": "sha1-FAo9LUGjbS78+pN3tiwk+ElaXEQ=", + "dev": true + }, + "plugin-error": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-0.1.2.tgz", + "integrity": "sha1-O5uzM1zPAPQl4HQ34ZJ2ln2kes4=", + "dev": true, + "requires": { + "ansi-cyan": "^0.1.1", + "ansi-red": "^0.1.1", + "arr-diff": "^1.0.1", + "arr-union": "^2.0.1", + "extend-shallow": "^1.1.2" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + } + } + }, + "rxjs": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.3.tgz", + "integrity": "sha512-wuYsAYYFdWTAnAaPoKGNhfpWwKZbJW+HgAJ+mImp+Epl7BG8oNWBCTyRM8gba9k4lk8BgWdoYm21Mo/RYhhbgA==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha1-mR7GnSluAxN0fVm9/St0XDX4go0=", + "dev": true + }, + "safe-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", + "dev": true, + "requires": { + "ret": "~0.1.10" + } + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha1-RPoWGwGHuVSd2Eu5GAL5vYOFzWo=", + "dev": true + }, + "sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha1-KBYjTiN4vdxOU1T6tcqold9xANk=", + "dev": true + }, + "saxes": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", + "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", + "dev": true, + "requires": { + "xmlchars": "^2.2.0" + } + }, + "seek-bzip": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/seek-bzip/-/seek-bzip-1.0.5.tgz", + "integrity": "sha1-z+kXyz0nS8/6x5J1ivUxc+sfq9w=", + "dev": true, + "optional": true, + "requires": { + "commander": "~2.8.1" + } + }, + "select": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/select/-/select-1.1.2.tgz", + "integrity": "sha1-DnNQrN7ICxEIUoeG7B1EGNEbOW0=" + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "semver-greatest-satisfied-range": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/semver-greatest-satisfied-range/-/semver-greatest-satisfied-range-1.1.0.tgz", + "integrity": "sha1-E+jCZYq5aRywzXEJMkAoDTb3els=", + "dev": true, + "requires": { + "sver-compat": "^1.5.0" + } + }, + "semver-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/semver-regex/-/semver-regex-2.0.0.tgz", + "integrity": "sha512-mUdIBBvdn0PLOeP3TEkMH7HHeUP3GjsXCwKarjv/kGmUFOYg1VqEemKhoQpWMu6X2I8kHeuVdGibLGkVK+/5Qw==", + "dev": true, + "optional": true + }, + "semver-truncate": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/semver-truncate/-/semver-truncate-1.1.2.tgz", + "integrity": "sha1-V/Qd5pcHpicJp+AQS6IRcQnqR+g=", + "dev": true, + "optional": true, + "requires": { + "semver": "^5.3.0" + } + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true + }, + "set-value": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", + "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "setprototypeof": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", + "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==", + "dev": true + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true + }, + "shellwords": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz", + "integrity": "sha1-1rkYHBpI05cyTISHHvvPxz/AZUs=", + "dev": true + }, + "signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", + "dev": true + }, + "signalr": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/signalr/-/signalr-2.4.0.tgz", + "integrity": "sha1-kq8AjmtSetSzbpT7s0DhNQh6YNI=", + "requires": { + "jquery": ">=1.6.4" + } + }, + "simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo=", + "dev": true, + "requires": { + "is-arrayish": "^0.3.1" + }, + "dependencies": { + "is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha1-RXSirlb3qyBolvtDHq7tBm/fjwM=", + "dev": true + } + } + }, + "slash": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", + "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=", + "dev": true + }, + "slice-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", + "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.0", + "astral-regex": "^1.0.0", + "is-fullwidth-code-point": "^2.0.0" + }, + "dependencies": { + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + } + } + }, + "snapdragon": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", + "integrity": "sha1-ZJIufFZbDhQgS6GqfWlkJ40lGC0=", + "dev": true, + "requires": { + "base": "^0.11.1", + "debug": "^2.2.0", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "map-cache": "^0.2.2", + "source-map": "^0.5.6", + "source-map-resolve": "^0.5.0", + "use": "^3.1.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha1-XRKFFd8TT/Mn6QpMk/Tgd6U2NB8=", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "snapdragon-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", + "integrity": "sha1-bBdfhv8UvbByRWPo88GwIaKGhTs=", + "dev": true, + "requires": { + "define-property": "^1.0.0", + "isobject": "^3.0.0", + "snapdragon-util": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha1-FpwvbT3x+ZJhgHI2XJsOofaHhlY=", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha1-2Eh2Mh0Oet0DmQQGq7u9NrqSaMc=", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha1-OxWXRqZmBLBPjIFSS6NlxfFNhuw=", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "snapdragon-util": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", + "integrity": "sha1-+VZHlIbyrNeXAGk/b3uAXkWrVuI=", + "dev": true, + "requires": { + "kind-of": "^3.2.0" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "socket.io": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-2.1.1.tgz", + "integrity": "sha1-oGnF/qvuPmshSnW0DOBlLhz7mYA=", + "dev": true, + "requires": { + "debug": "~3.1.0", + "engine.io": "~3.2.0", + "has-binary2": "~1.0.2", + "socket.io-adapter": "~1.1.0", + "socket.io-client": "2.1.1", + "socket.io-parser": "~3.2.0" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha1-W7WgZyYotkFJVmuhaBnmFRjGcmE=", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "socket.io-adapter": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-1.1.1.tgz", + "integrity": "sha1-KoBeihTWNyEk3ZFZrUUC+MsH8Gs=", + "dev": true + }, + "socket.io-client": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-2.1.1.tgz", + "integrity": "sha1-3LOBA0NqtFeN2wJmOK4vIbYjZx8=", + "dev": true, + "requires": { + "backo2": "1.0.2", + "base64-arraybuffer": "0.1.5", + "component-bind": "1.0.0", + "component-emitter": "1.2.1", + "debug": "~3.1.0", + "engine.io-client": "~3.2.0", + "has-binary2": "~1.0.2", + "has-cors": "1.1.0", + "indexof": "0.0.1", + "object-component": "0.0.3", + "parseqs": "0.0.5", + "parseuri": "0.0.5", + "socket.io-parser": "~3.2.0", + "to-array": "0.1.4" + }, + "dependencies": { + "component-emitter": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", + "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", + "dev": true + }, + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha1-W7WgZyYotkFJVmuhaBnmFRjGcmE=", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "socket.io-parser": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.2.0.tgz", + "integrity": "sha1-58Yii2qh+BTmFIrqMltRqpSZ4Hc=", + "dev": true, + "requires": { + "component-emitter": "1.2.1", + "debug": "~3.1.0", + "isarray": "2.0.1" + }, + "dependencies": { + "component-emitter": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", + "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", + "dev": true + }, + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "isarray": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", + "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=", + "dev": true + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "sort-keys": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz", + "integrity": "sha1-RBttTTRnmPG05J6JIK37oOVD+a0=", + "dev": true, + "optional": true, + "requires": { + "is-plain-obj": "^1.0.0" + } + }, + "sort-keys-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/sort-keys-length/-/sort-keys-length-1.0.1.tgz", + "integrity": "sha1-nLb09OnkgVWmqgZx7dM2/xR5oYg=", + "dev": true, + "optional": true, + "requires": { + "sort-keys": "^1.0.0" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "source-map-resolve": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.2.tgz", + "integrity": "sha1-cuLMNAlVQ+Q7LGKyxMENSpBU8lk=", + "dev": true, + "requires": { + "atob": "^2.1.1", + "decode-uri-component": "^0.2.0", + "resolve-url": "^0.2.1", + "source-map-url": "^0.4.0", + "urix": "^0.1.0" + } + }, + "source-map-url": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", + "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", + "dev": true + }, + "sparkles": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/sparkles/-/sparkles-1.0.1.tgz", + "integrity": "sha1-AI22XtzmxQ7sDF4ijhlFBh3QQ3w=", + "dev": true + }, + "spdx-correct": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", + "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==", + "dev": true, + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz", + "integrity": "sha1-LqRQrudPKom/uUUZwH/Nb0EyKXc=", + "dev": true + }, + "spdx-expression-parse": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", + "integrity": "sha1-meEZt6XaAOBUkcn6M4t5BII7QdA=", + "dev": true, + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz", + "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==", + "dev": true + }, + "spectrum-colorpicker2": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/spectrum-colorpicker2/-/spectrum-colorpicker2-2.0.3.tgz", + "integrity": "sha512-uEmzdiPqSHgJ1sJvEiwsuYAt7ep/GltjWZ7yloMDFMPcr4qQmmwX4UqlFz7C2HxmmqA51jx51FfgiF65s7R3Pg==" + }, + "split-string": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", + "integrity": "sha1-fLCd2jqGWFcFxks5pkZgOGguj+I=", + "dev": true, + "requires": { + "extend-shallow": "^3.0.0" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "squeak": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/squeak/-/squeak-1.3.0.tgz", + "integrity": "sha1-MwRQN7ZDiLVnZ0uEMiplIQc5FsM=", + "dev": true, + "optional": true, + "requires": { + "chalk": "^1.0.0", + "console-stream": "^0.1.1", + "lpad-align": "^1.0.1" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true, + "optional": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "optional": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true, + "optional": true + } + } + }, + "sshpk": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", + "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", + "dev": true, + "requires": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + } + }, + "stable": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz", + "integrity": "sha1-g26zyDgv4pNv6vVEYxAXzn1Ho88=", + "dev": true + }, + "stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=", + "dev": true + }, + "static-extend": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", + "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", + "dev": true, + "requires": { + "define-property": "^0.2.5", + "object-copy": "^0.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", + "dev": true + }, + "stealthy-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", + "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=", + "dev": true + }, + "stream-exhaust": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/stream-exhaust/-/stream-exhaust-1.0.2.tgz", + "integrity": "sha512-b/qaq/GlBK5xaq1yrK9/zFcyRSTNxmcZwFLGSTG0mXgZl/4Z6GgiyYOXOvY7N3eEvFRAG1bkDRz5EPGSvPYQlw==", + "dev": true + }, + "stream-shift": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.0.tgz", + "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=", + "dev": true + }, + "streamroller": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-1.0.6.tgz", + "integrity": "sha512-3QC47Mhv3/aZNFpDDVO44qQb9gwB9QggMEE0sQmkTAwBVYdBRWISdsywlkfm5II1Q5y/pmrHflti/IgmIzdDBg==", + "dev": true, + "requires": { + "async": "^2.6.2", + "date-format": "^2.0.0", + "debug": "^3.2.6", + "fs-extra": "^7.0.1", + "lodash": "^4.17.14" + }, + "dependencies": { + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha1-6D0X3hbYp++3cX7b5fsQE17uYps=", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "fs-extra": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6" + } + } + } + }, + "strict-uri-encode": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz", + "integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=", + "dev": true, + "optional": true + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "string.prototype.trimleft": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.0.tgz", + "integrity": "sha512-FJ6b7EgdKxxbDxc79cOlok6Afd++TTs5szo+zJTUyow3ycrRfJVE2pq3vcN53XexvKZu/DJMDfeI/qMiZTrjTw==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "function-bind": "^1.1.1" + } + }, + "string.prototype.trimright": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.0.tgz", + "integrity": "sha512-fXZTSV55dNBwv16uw+hh5jkghxSnc5oHq+5K/gXgizHwAvMetdAJlHqqoFC1FSDVPYWLkAKl2cxpUT41sV7nSg==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "function-bind": "^1.1.1" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-bom": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", + "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", + "dev": true, + "requires": { + "is-utf8": "^0.2.0" + } + }, + "strip-bom-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-bom-stream/-/strip-bom-stream-2.0.0.tgz", + "integrity": "sha1-+H217yYT9paKpUWr/h7HKLaoKco=", + "dev": true, + "requires": { + "first-chunk-stream": "^2.0.0", + "strip-bom": "^2.0.0" + } + }, + "strip-bom-string": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz", + "integrity": "sha1-5SEekiQ2n7uB1jOi8ABE3IztrZI=", + "dev": true + }, + "strip-dirs": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/strip-dirs/-/strip-dirs-2.1.0.tgz", + "integrity": "sha512-JOCxOeKLm2CAS73y/U4ZeZPTkE+gNVCzKt7Eox84Iej1LT/2pTWYpZKJuxwQpvX1LiZb1xokNR7RLfuBAa7T3g==", + "dev": true, + "optional": true, + "requires": { + "is-natural-number": "^4.0.1" + } + }, + "strip-eof": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", + "dev": true, + "optional": true + }, + "strip-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz", + "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=", + "dev": true, + "optional": true, + "requires": { + "get-stdin": "^4.0.1" + } + }, + "strip-json-comments": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.0.1.tgz", + "integrity": "sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw==", + "dev": true + }, + "strip-outer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-1.0.1.tgz", + "integrity": "sha1-sv0qv2YEudHmATBXGV34Nrip1jE=", + "dev": true, + "optional": true, + "requires": { + "escape-string-regexp": "^1.0.2" + } + }, + "stylehacks": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-4.0.3.tgz", + "integrity": "sha512-7GlLk9JwlElY4Y6a/rmbH2MhVlTyVmiJd1PfTCqFaIBEGMYNsrO/v3SeGTdhBThLg4Z+NbOk/qFMwCa+J+3p/g==", + "dev": true, + "requires": { + "browserslist": "^4.0.0", + "postcss": "^7.0.0", + "postcss-selector-parser": "^3.0.0" + }, + "dependencies": { + "postcss-selector-parser": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-3.1.1.tgz", + "integrity": "sha1-T4dfSvsMllc9XPTXQBGu4lCn6GU=", + "dev": true, + "requires": { + "dot-prop": "^4.1.1", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" + } + } + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha1-4uaaRKyHcveKHsCzW2id9lMO/I8=", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "sver-compat": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/sver-compat/-/sver-compat-1.5.0.tgz", + "integrity": "sha1-PPh9/rTQe0o/FIJ7wYaz/QxkXNg=", + "dev": true, + "requires": { + "es6-iterator": "^2.0.1", + "es6-symbol": "^3.1.1" + } + }, + "svgo": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-1.3.0.tgz", + "integrity": "sha512-MLfUA6O+qauLDbym+mMZgtXCGRfIxyQoeH6IKVcFslyODEe/ElJNwr0FohQ3xG4C6HK6bk3KYPPXwHVJk3V5NQ==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "coa": "^2.0.2", + "css-select": "^2.0.0", + "css-select-base-adapter": "^0.1.1", + "css-tree": "1.0.0-alpha.33", + "csso": "^3.5.1", + "js-yaml": "^3.13.1", + "mkdirp": "~0.5.1", + "object.values": "^1.1.0", + "sax": "~1.2.4", + "stable": "^0.1.8", + "unquote": "~1.1.1", + "util.promisify": "~1.0.0" + } + }, + "symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true + }, + "table": { + "version": "5.4.6", + "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz", + "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==", + "dev": true, + "requires": { + "ajv": "^6.10.2", + "lodash": "^4.17.14", + "slice-ansi": "^2.1.0", + "string-width": "^3.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "tar-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.6.2.tgz", + "integrity": "sha1-jqVdqzeXIlPZqa+Q/c1VmuQ1xVU=", + "dev": true, + "optional": true, + "requires": { + "bl": "^1.0.0", + "buffer-alloc": "^1.2.0", + "end-of-stream": "^1.0.0", + "fs-constants": "^1.0.0", + "readable-stream": "^2.3.0", + "to-buffer": "^1.1.1", + "xtend": "^4.0.0" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true, + "optional": true + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha1-sRwn2IuP8fvgcGQ8+UsMea4bCq8=", + "dev": true, + "optional": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha1-nPFhG6YmhdcDCunkujQUnDrwP8g=", + "dev": true, + "optional": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "temp-dir": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-1.0.0.tgz", + "integrity": "sha1-CnwOom06Oa+n4OvqnB/AvE2qAR0=", + "dev": true, + "optional": true + }, + "tempfile": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tempfile/-/tempfile-2.0.0.tgz", + "integrity": "sha1-awRGhWqbERTRhW/8vlCczLCXcmU=", + "dev": true, + "optional": true, + "requires": { + "temp-dir": "^1.0.0", + "uuid": "^3.0.1" + } + }, + "text-hex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", + "integrity": "sha1-adycGxdEbueakr9biEu0uRJ1BvU=", + "dev": true + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true + }, + "through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha1-AcHjnrMdB8t9A6lqcIIyYLIxMs0=", + "dev": true, + "requires": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha1-sRwn2IuP8fvgcGQ8+UsMea4bCq8=", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha1-nPFhG6YmhdcDCunkujQUnDrwP8g=", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "through2-concurrent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/through2-concurrent/-/through2-concurrent-2.0.0.tgz", + "integrity": "sha512-R5/jLkfMvdmDD+seLwN7vB+mhbqzWop5fAjx5IX8/yQq7VhBhzDmhXgaHAOnhnWkCpRMM7gToYHycB0CS/pd+A==", + "dev": true, + "requires": { + "through2": "^2.0.0" + } + }, + "through2-filter": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/through2-filter/-/through2-filter-3.0.0.tgz", + "integrity": "sha512-jaRjI2WxN3W1V8/FMZ9HKIBXixtiqs3SQSX4/YGIiP3gL6djW48VoZq9tDqeCWs3MT8YY5wb/zli8VW8snY1CA==", + "dev": true, + "requires": { + "through2": "~2.0.0", + "xtend": "~4.0.0" + } + }, + "time-stamp": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/time-stamp/-/time-stamp-1.1.0.tgz", + "integrity": "sha1-dkpaEa9QVhkhsTPztE5hhofg9cM=", + "dev": true + }, + "timed-out": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz", + "integrity": "sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=", + "dev": true, + "optional": true + }, + "timers-ext": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.7.tgz", + "integrity": "sha1-b1ethXjgej+5+R2Th9ZWR1VeJcY=", + "dev": true, + "requires": { + "es5-ext": "~0.10.46", + "next-tick": "1" + } + }, + "timsort": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz", + "integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=", + "dev": true + }, + "tiny-emitter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz", + "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==" + }, + "tinymce": { + "version": "4.9.11", + "resolved": "https://registry.npmjs.org/tinymce/-/tinymce-4.9.11.tgz", + "integrity": "sha512-nkSLsax+VY5DBRjMFnHFqPwTnlLEGHCco82FwJF2JNH6W+5/ClvNC1P4uhD5lXPDNiDykSHR0XJdEh7w/ICHzA==" + }, + "tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha1-bTQzWIl2jSGyvNoKonfO07G/rfk=", + "dev": true, + "requires": { + "os-tmpdir": "~1.0.2" + } + }, + "to-absolute-glob": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/to-absolute-glob/-/to-absolute-glob-2.0.2.tgz", + "integrity": "sha1-GGX0PZ50sIItufFFt4z/fQ98hJs=", + "dev": true, + "requires": { + "is-absolute": "^1.0.0", + "is-negated-glob": "^1.0.0" + } + }, + "to-array": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz", + "integrity": "sha1-F+bBH3PdTz10zaek/zI46a2b+JA=", + "dev": true + }, + "to-buffer": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.1.1.tgz", + "integrity": "sha1-STvUj2LXxD/N7TE6A9ytsuEhOoA=", + "dev": true, + "optional": true + }, + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", + "dev": true + }, + "to-object-path": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", + "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "to-regex": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", + "integrity": "sha1-E8/dmzNlUvMLUfM6iuG0Knp1mc4=", + "dev": true, + "requires": { + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "regex-not": "^1.0.2", + "safe-regex": "^1.1.0" + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + } + }, + "to-through": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-through/-/to-through-2.0.0.tgz", + "integrity": "sha1-/JKtq6ByZHvAtn1rA2ZKoZUJOvY=", + "dev": true, + "requires": { + "through2": "^2.0.3" + } + }, + "toidentifier": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", + "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", + "dev": true + }, + "tough-cookie": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", + "integrity": "sha1-U/Nto/R3g7CSWvoG/587FlKA94E=", + "dev": true, + "requires": { + "psl": "^1.1.24", + "punycode": "^1.4.1" + }, + "dependencies": { + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "dev": true + } + } + }, + "tr46": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-2.0.2.tgz", + "integrity": "sha512-3n1qG+/5kg+jrbTzwAykB5yRYtQCTqOGKq5U5PE3b0a1/mzo6snDhjGS0zJVJunO0NrT3Dg1MLy5TjWP/UJppg==", + "dev": true, + "requires": { + "punycode": "^2.1.1" + } + }, + "trim-newlines": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz", + "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=", + "dev": true, + "optional": true + }, + "trim-repeated": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/trim-repeated/-/trim-repeated-1.0.0.tgz", + "integrity": "sha1-42RqLqTokTEr9+rObPsFOAvAHCE=", + "dev": true, + "optional": true, + "requires": { + "escape-string-regexp": "^1.0.2" + } + }, + "tryit": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tryit/-/tryit-1.0.3.tgz", + "integrity": "sha1-OTvnMKlEb9Hq1tpZoBQwjzbCics=", + "dev": true + }, + "tslib": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", + "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==", + "dev": true + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "dev": true, + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "dev": true + }, + "type": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", + "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==", + "dev": true + }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2" + } + }, + "type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dev": true, + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + } + }, + "typeahead.js": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/typeahead.js/-/typeahead.js-0.11.1.tgz", + "integrity": "sha1-TmTmcbIjEKhgb0rsgFkkuoSwFbg=", + "requires": { + "jquery": ">=1.7" + } + }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", + "dev": true + }, + "uglify-js": { + "version": "2.8.29", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz", + "integrity": "sha1-KcVzMUgFe7Th913zW3qcty5qWd0=", + "dev": true, + "requires": { + "source-map": "~0.5.1", + "uglify-to-browserify": "~1.0.0", + "yargs": "~3.10.0" + }, + "dependencies": { + "camelcase": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", + "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=", + "dev": true + }, + "cliui": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", + "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=", + "dev": true, + "requires": { + "center-align": "^0.1.1", + "right-align": "^0.1.1", + "wordwrap": "0.0.2" + } + }, + "wordwrap": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", + "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=", + "dev": true + }, + "yargs": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", + "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=", + "dev": true, + "requires": { + "camelcase": "^1.0.2", + "cliui": "^2.1.0", + "decamelize": "^1.0.0", + "window-size": "0.1.0" + } + } + } + }, + "uglify-to-browserify": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz", + "integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=", + "dev": true, + "optional": true + }, + "ultron": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz", + "integrity": "sha1-n+FTahCmZKZSZqHjzPhf02MCvJw=", + "dev": true + }, + "unbzip2-stream": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.3.3.tgz", + "integrity": "sha512-fUlAF7U9Ah1Q6EieQ4x4zLNejrRvDWUYmxXUpN3uziFYCHapjWFaCAnreY9bGgxzaMCFAPPpYNng57CypwJVhg==", + "dev": true, + "optional": true, + "requires": { + "buffer": "^5.2.1", + "through": "^2.3.8" + } + }, + "unc-path-regex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz", + "integrity": "sha1-5z3T17DXxe2G+6xrCufYxqadUPo=", + "dev": true + }, + "underscore": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.9.1.tgz", + "integrity": "sha1-BtzjSg5op7q8KbNluOdLiSUgOWE=" + }, + "undertaker": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/undertaker/-/undertaker-1.2.1.tgz", + "integrity": "sha512-71WxIzDkgYk9ZS+spIB8iZXchFhAdEo2YU8xYqBYJ39DIUIqziK78ftm26eecoIY49X0J2MLhG4hr18Yp6/CMA==", + "dev": true, + "requires": { + "arr-flatten": "^1.0.1", + "arr-map": "^2.0.0", + "bach": "^1.0.0", + "collection-map": "^1.0.0", + "es6-weak-map": "^2.0.1", + "last-run": "^1.1.0", + "object.defaults": "^1.0.0", + "object.reduce": "^1.0.0", + "undertaker-registry": "^1.0.0" + } + }, + "undertaker-registry": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/undertaker-registry/-/undertaker-registry-1.0.1.tgz", + "integrity": "sha1-XkvaMI5KiirlhPm5pDWaSZglzFA=", + "dev": true + }, + "unicode-canonical-property-names-ecmascript": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz", + "integrity": "sha1-JhmADEyCWADv3YNDr33Zkzy+KBg=", + "dev": true + }, + "unicode-match-property-ecmascript": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-1.0.4.tgz", + "integrity": "sha1-jtKjJWmWG86SJ9Cc0/+7j+1fAgw=", + "dev": true, + "requires": { + "unicode-canonical-property-names-ecmascript": "^1.0.4", + "unicode-property-aliases-ecmascript": "^1.0.4" + } + }, + "unicode-match-property-value-ecmascript": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.1.0.tgz", + "integrity": "sha512-hDTHvaBk3RmFzvSl0UVrUmC3PuW9wKVnpoUDYH0JDkSIovzw+J5viQmeYHxVSBptubnr7PbH2e0fnpDRQnQl5g==", + "dev": true + }, + "unicode-property-aliases-ecmascript": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.0.5.tgz", + "integrity": "sha512-L5RAqCfXqAwR3RriF8pM0lU0w4Ryf/GgzONwi6KnL1taJQa7x1TCxdJnILX59WIGOwR57IVxn7Nej0fz1Ny6fw==", + "dev": true + }, + "union-value": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", + "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", + "dev": true, + "requires": { + "arr-union": "^3.1.0", + "get-value": "^2.0.6", + "is-extendable": "^0.1.1", + "set-value": "^2.0.1" + } + }, + "uniq": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz", + "integrity": "sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8=", + "dev": true + }, + "uniqs": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/uniqs/-/uniqs-2.0.0.tgz", + "integrity": "sha1-/+3ks2slKQaW5uFl1KWe25mOawI=", + "dev": true + }, + "unique-stream": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/unique-stream/-/unique-stream-2.3.1.tgz", + "integrity": "sha512-2nY4TnBE70yoxHkDli7DMazpWiP7xMdCYqU2nBRO0UB+ZpEkGsSija7MvmvnZFUeC+mrgiUfcHSr3LmRFIg4+A==", + "dev": true, + "requires": { + "json-stable-stringify-without-jsonify": "^1.0.1", + "through2-filter": "^3.0.0" + } + }, + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", + "dev": true + }, + "unquote": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/unquote/-/unquote-1.1.1.tgz", + "integrity": "sha1-j97XMk7G6IoP+LkF58CYzcCG1UQ=", + "dev": true + }, + "unset-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", + "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", + "dev": true, + "requires": { + "has-value": "^0.3.1", + "isobject": "^3.0.0" + }, + "dependencies": { + "has-value": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", + "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", + "dev": true, + "requires": { + "get-value": "^2.0.3", + "has-values": "^0.1.4", + "isobject": "^2.0.0" + }, + "dependencies": { + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "dev": true, + "requires": { + "isarray": "1.0.0" + } + } + } + }, + "has-values": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", + "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", + "dev": true + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + } + } + }, + "upath": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", + "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==", + "dev": true + }, + "uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha1-lMVA4f93KVbiKZUHwBCupsiDjrA=", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "urix": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", + "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", + "dev": true + }, + "url-parse-lax": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz", + "integrity": "sha1-evjzA2Rem9eaJy56FKxovAYJ2nM=", + "dev": true, + "optional": true, + "requires": { + "prepend-http": "^1.0.1" + } + }, + "url-to-options": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/url-to-options/-/url-to-options-1.0.1.tgz", + "integrity": "sha1-FQWgOiiaSMvXpDTvuu7FBV9WM6k=", + "dev": true, + "optional": true + }, + "use": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", + "integrity": "sha1-1QyMrHmhn7wg8pEfVuuXP04QBw8=", + "dev": true + }, + "useragent": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/useragent/-/useragent-2.3.0.tgz", + "integrity": "sha512-4AoH4pxuSvHCjqLO04sU6U/uE65BYza8l/KKBS0b0hnUPWi+cQ2BpeTEwejCSx9SPV5/U03nniDTrWx5NrmKdw==", + "dev": true, + "requires": { + "lru-cache": "4.1.x", + "tmp": "0.0.x" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "util.promisify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.0.tgz", + "integrity": "sha1-RA9xZaRZyaFtwUXrjnLzVocJcDA=", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "object.getownpropertydescriptors": "^2.0.3" + } + }, + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", + "dev": true + }, + "uuid": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.3.tgz", + "integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ==", + "dev": true + }, + "v8-compile-cache": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz", + "integrity": "sha512-usZBT3PW+LOjM25wbqIlZwPeJV+3OSz3M1k1Ws8snlW39dZyYL9lOGC5FgPVHfk0jKmjiDV8Z0mIbVQPiwFs7g==", + "dev": true + }, + "v8flags": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.2.0.tgz", + "integrity": "sha512-mH8etigqMfiGWdeXpaaqGfs6BndypxusHHcv2qSHyZkGEznCd/qAXCWWRzeowtL54147cktFOC4P5y+kl8d8Jg==", + "dev": true, + "requires": { + "homedir-polyfill": "^1.0.1" + } + }, + "validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha1-/JH2uce6FchX9MssXe/uw51PQQo=", + "dev": true, + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "value-or-function": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/value-or-function/-/value-or-function-3.0.0.tgz", + "integrity": "sha1-HCQ6ULWVwb5Up1S/7OhWO5/42BM=", + "dev": true + }, + "vendors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/vendors/-/vendors-1.0.3.tgz", + "integrity": "sha512-fOi47nsJP5Wqefa43kyWSg80qF+Q3XA6MUkgi7Hp1HQaKDQW4cQrK2D0P7mmbFtsV1N89am55Yru/nyEwRubcw==", + "dev": true + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "vinyl": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.5.3.tgz", + "integrity": "sha1-sEVbOPxeDPMNQyUTLkYZcMIJHN4=", + "dev": true, + "requires": { + "clone": "^1.0.0", + "clone-stats": "^0.0.1", + "replace-ext": "0.0.1" + } + }, + "vinyl-bufferstream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/vinyl-bufferstream/-/vinyl-bufferstream-1.0.1.tgz", + "integrity": "sha1-BTeGn1gO/6TKRay0dXnkuf5jCBo=", + "dev": true, + "requires": { + "bufferstreams": "1.0.1" + } + }, + "vinyl-file": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/vinyl-file/-/vinyl-file-2.0.0.tgz", + "integrity": "sha1-p+v1/779obfRjRQPyweyI++2dRo=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "pify": "^2.3.0", + "pinkie-promise": "^2.0.0", + "strip-bom": "^2.0.0", + "strip-bom-stream": "^2.0.0", + "vinyl": "^1.1.0" + }, + "dependencies": { + "vinyl": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-1.2.0.tgz", + "integrity": "sha1-XIgDbPVl5d8FVYv8kR+GVt8hiIQ=", + "dev": true, + "requires": { + "clone": "^1.0.0", + "clone-stats": "^0.0.1", + "replace-ext": "0.0.1" + } + } + } + }, + "vinyl-fs": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/vinyl-fs/-/vinyl-fs-3.0.3.tgz", + "integrity": "sha512-vIu34EkyNyJxmP0jscNzWBSygh7VWhqun6RmqVfXePrOwi9lhvRs//dOaGOTRUQr4tx7/zd26Tk5WeSVZitgng==", + "dev": true, + "requires": { + "fs-mkdirp-stream": "^1.0.0", + "glob-stream": "^6.1.0", + "graceful-fs": "^4.0.0", + "is-valid-glob": "^1.0.0", + "lazystream": "^1.0.0", + "lead": "^1.0.0", + "object.assign": "^4.0.4", + "pumpify": "^1.3.5", + "readable-stream": "^2.3.3", + "remove-bom-buffer": "^3.0.0", + "remove-bom-stream": "^1.2.0", + "resolve-options": "^1.1.0", + "through2": "^2.0.0", + "to-through": "^2.0.0", + "value-or-function": "^3.0.0", + "vinyl": "^2.0.0", + "vinyl-sourcemap": "^1.1.0" + }, + "dependencies": { + "clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=", + "dev": true + }, + "clone-stats": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-1.0.0.tgz", + "integrity": "sha1-s3gt/4u1R04Yuba/D9/ngvh3doA=", + "dev": true + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "replace-ext": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.0.tgz", + "integrity": "sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs=", + "dev": true + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "vinyl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.0.tgz", + "integrity": "sha512-MBH+yP0kC/GQ5GwBqrTPTzEfiiLjta7hTtvQtbxBgTeSXsmKQRQecjibMbxIXzVT3Y9KJK+drOz1/k+vsu8Nkg==", + "dev": true, + "requires": { + "clone": "^2.1.1", + "clone-buffer": "^1.0.0", + "clone-stats": "^1.0.0", + "cloneable-readable": "^1.0.0", + "remove-trailing-separator": "^1.0.1", + "replace-ext": "^1.0.0" + } + } + } + }, + "vinyl-sourcemap": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/vinyl-sourcemap/-/vinyl-sourcemap-1.1.0.tgz", + "integrity": "sha1-kqgAWTo4cDqM2xHYswCtS+Y7PhY=", + "dev": true, + "requires": { + "append-buffer": "^1.0.2", + "convert-source-map": "^1.5.0", + "graceful-fs": "^4.1.6", + "normalize-path": "^2.1.1", + "now-and-later": "^2.0.0", + "remove-bom-buffer": "^3.0.0", + "vinyl": "^2.0.0" + }, + "dependencies": { + "clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=", + "dev": true + }, + "clone-stats": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-1.0.0.tgz", + "integrity": "sha1-s3gt/4u1R04Yuba/D9/ngvh3doA=", + "dev": true + }, + "replace-ext": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.0.tgz", + "integrity": "sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs=", + "dev": true + }, + "vinyl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.0.tgz", + "integrity": "sha512-MBH+yP0kC/GQ5GwBqrTPTzEfiiLjta7hTtvQtbxBgTeSXsmKQRQecjibMbxIXzVT3Y9KJK+drOz1/k+vsu8Nkg==", + "dev": true, + "requires": { + "clone": "^2.1.1", + "clone-buffer": "^1.0.0", + "clone-stats": "^1.0.0", + "cloneable-readable": "^1.0.0", + "remove-trailing-separator": "^1.0.1", + "replace-ext": "^1.0.0" + } + } + } + }, + "vinyl-sourcemaps-apply": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/vinyl-sourcemaps-apply/-/vinyl-sourcemaps-apply-0.2.1.tgz", + "integrity": "sha1-q2VJ1h0XLCsbh75cUI0jnI74dwU=", + "dev": true, + "requires": { + "source-map": "^0.5.1" + } + }, + "void-elements": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz", + "integrity": "sha1-wGavtYK7HLQSjWDqkjkulNXp2+w=", + "dev": true + }, + "w3c-hr-time": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", + "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", + "dev": true, + "requires": { + "browser-process-hrtime": "^1.0.0" + } + }, + "w3c-xmlserializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz", + "integrity": "sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA==", + "dev": true, + "requires": { + "xml-name-validator": "^3.0.0" + } + }, + "webidl-conversions": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz", + "integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==", + "dev": true + }, + "whatwg-encoding": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", + "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", + "dev": true, + "requires": { + "iconv-lite": "0.4.24" + } + }, + "whatwg-mimetype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", + "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==", + "dev": true + }, + "whatwg-url": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.4.0.tgz", + "integrity": "sha512-vwTUFf6V4zhcPkWp/4CQPr1TW9Ml6SF4lVyaIMBdJw5i6qUUJ1QWM4Z6YYVkfka0OUIzVo/0aNtGVGk256IKWw==", + "dev": true, + "requires": { + "lodash.sortby": "^4.7.0", + "tr46": "^2.0.2", + "webidl-conversions": "^6.1.0" + } + }, + "when": { + "version": "3.7.8", + "resolved": "https://registry.npmjs.org/when/-/when-3.7.8.tgz", + "integrity": "sha1-xxMLan6gRpPoQs3J56Hyqjmjn4I=", + "dev": true + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha1-pFBD1U9YBTFtqNYvn1CRjT2nCwo=", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "which-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz", + "integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=", + "dev": true + }, + "wicg-inert": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/wicg-inert/-/wicg-inert-3.1.0.tgz", + "integrity": "sha512-P0ZiWaN9SxOkJbYtF/PIwmIRO8UTqTJtyl33QTQlHfAb6h15T0Dp5m7WTJ8N6UWIoj+KU5M0a8EtfRZLlHiP0Q==" + }, + "window-size": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", + "integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=", + "dev": true + }, + "wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", + "dev": true + }, + "wrap-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", + "dev": true, + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "write": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz", + "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==", + "dev": true, + "requires": { + "mkdirp": "^0.5.1" + } + }, + "ws": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz", + "integrity": "sha1-8c+E/i1ekB686U767OeF8YeiKPI=", + "dev": true, + "requires": { + "async-limiter": "~1.0.0", + "safe-buffer": "~5.1.0", + "ultron": "~1.1.0" + } + }, + "xml-name-validator": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", + "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", + "dev": true + }, + "xmlbuilder": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-12.0.0.tgz", + "integrity": "sha512-lMo8DJ8u6JRWp0/Y4XLa/atVDr75H9litKlb2E5j3V3MesoL50EBgZDWoLT3F/LztVnG67GjPXLZpqcky/UMnQ==", + "dev": true + }, + "xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true + }, + "xmlhttprequest-ssl": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz", + "integrity": "sha1-wodrBhaKrcQOV9l+gRkayPQ5iz4=", + "dev": true + }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true + }, + "y18n": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", + "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=", + "dev": true + }, + "yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", + "dev": true + }, + "yargs": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-7.1.1.tgz", + "integrity": "sha512-huO4Fr1f9PmiJJdll5kwoS2e4GqzGSsMT3PPMpOwoVkOK8ckqAewMTZyA6LXVQWflleb/Z8oPBEvNsMft0XE+g==", + "dev": true, + "requires": { + "camelcase": "^3.0.0", + "cliui": "^3.2.0", + "decamelize": "^1.1.1", + "get-caller-file": "^1.0.1", + "os-locale": "^1.4.0", + "read-pkg-up": "^1.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^1.0.2", + "which-module": "^1.0.0", + "y18n": "^3.2.1", + "yargs-parser": "5.0.0-security.0" + } + }, + "yargs-parser": { + "version": "5.0.0-security.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-5.0.0-security.0.tgz", + "integrity": "sha512-T69y4Ps64LNesYxeYGYPvfoMTt/7y1XtfpIslUeK4um+9Hu7hlGoRtaDLvdXb7+/tfq4opVa2HRY5xGip022rQ==", + "dev": true, + "requires": { + "camelcase": "^3.0.0", + "object.assign": "^4.1.0" + } + }, + "yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=", + "dev": true, + "optional": true, + "requires": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, + "yeast": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz", + "integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk=", + "dev": true + } + } +} diff --git a/src/Umbraco.Web.UI.Client/package.json b/src/Umbraco.Web.UI.Client/package.json index 0d6db9118f..4d0c15204e 100644 --- a/src/Umbraco.Web.UI.Client/package.json +++ b/src/Umbraco.Web.UI.Client/package.json @@ -9,6 +9,9 @@ "fastdev": "gulp fastdev", "watch": "gulp watch" }, + "engines": { + "node": ">=10.00.0" + }, "dependencies": { "ace-builds": "1.4.2", "angular": "1.8.0", @@ -27,7 +30,7 @@ "angular-ui-sortable": "0.19.0", "animejs": "2.2.0", "bootstrap-social": "5.1.1", - "chart.js": "^2.8.0", + "chart.js": "^2.9.3", "clipboard": "2.0.4", "diff": "3.5.0", "flatpickr": "4.5.2", @@ -38,13 +41,14 @@ "lazyload-js": "1.0.0", "moment": "2.22.2", "ng-file-upload": "12.2.13", - "nouislider": "14.4.0", - "npm": "^6.14.0", + "nouislider": "14.6.2", + "npm": "^6.14.7", "signalr": "2.4.0", - "spectrum-colorpicker": "1.8.0", - "tinymce": "4.9.10", + "spectrum-colorpicker2": "2.0.3", + "tinymce": "4.9.11", "typeahead.js": "0.11.1", - "underscore": "1.9.1" + "underscore": "1.9.1", + "wicg-inert": "^3.0.2" }, "devDependencies": { "@babel/core": "7.6.4", @@ -57,7 +61,7 @@ "gulp-angular-embed-templates": "^2.3.0", "gulp-babel": "8.0.0", "gulp-clean-css": "4.2.0", - "gulp-cli": "^2.2.0", + "gulp-cli": "^2.3.0", "gulp-concat": "2.6.1", "gulp-eslint": "6.0.0", "gulp-imagemin": "6.1.1", @@ -71,15 +75,14 @@ "gulp-wrap": "0.15.0", "gulp-wrap-js": "0.4.1", "jasmine-core": "3.5.0", - "jasmine-promise-matchers": "^2.6.0", + "jsdom": "16.4.0", "karma": "4.4.1", - "karma-chrome-launcher": "^3.1.0", + "karma-jsdom-launcher": "^8.0.2", "karma-jasmine": "2.0.1", "karma-junit-reporter": "2.0.1", - "karma-phantomjs-launcher": "1.0.4", "karma-spec-reporter": "0.0.32", "less": "3.10.3", - "lodash": "4.17.15", + "lodash": "4.17.19", "marked": "^0.7.0", "merge-stream": "2.0.0", "run-sequence": "2.2.1" diff --git a/src/Umbraco.Web.UI.Client/src/app.js b/src/Umbraco.Web.UI.Client/src/app.js index 74a7008901..645296f0e0 100644 --- a/src/Umbraco.Web.UI.Client/src/app.js +++ b/src/Umbraco.Web.UI.Client/src/app.js @@ -91,6 +91,6 @@ angular.module("umbraco.viewcache", []) // be able to configure angular values in the Default.cshtml // view which is much easier to do that configuring values by injecting them in the back office controller // to follow through to the js initialization stuff -if (angular.isFunction(document.angularReady)) { +if (_.isFunction(document.angularReady)) { document.angularReady.apply(this, [app]); } diff --git a/src/Umbraco.Web.UI.Client/src/assets/fonts/lato/LatoLatin-Black.woff b/src/Umbraco.Web.UI.Client/src/assets/fonts/lato/LatoLatin-Black.woff deleted file mode 100644 index d1e2579bf8..0000000000 Binary files a/src/Umbraco.Web.UI.Client/src/assets/fonts/lato/LatoLatin-Black.woff and /dev/null differ diff --git a/src/Umbraco.Web.UI.Client/src/assets/fonts/lato/LatoLatin-BlackItalic.woff b/src/Umbraco.Web.UI.Client/src/assets/fonts/lato/LatoLatin-BlackItalic.woff deleted file mode 100644 index 142c1c9c48..0000000000 Binary files a/src/Umbraco.Web.UI.Client/src/assets/fonts/lato/LatoLatin-BlackItalic.woff and /dev/null differ diff --git a/src/Umbraco.Web.UI.Client/src/assets/fonts/lato/LatoLatin-Bold.woff b/src/Umbraco.Web.UI.Client/src/assets/fonts/lato/LatoLatin-Bold.woff deleted file mode 100644 index cdfcbe0fbc..0000000000 Binary files a/src/Umbraco.Web.UI.Client/src/assets/fonts/lato/LatoLatin-Bold.woff and /dev/null differ diff --git a/src/Umbraco.Web.UI.Client/src/assets/fonts/lato/LatoLatin-BoldItalic.woff b/src/Umbraco.Web.UI.Client/src/assets/fonts/lato/LatoLatin-BoldItalic.woff deleted file mode 100644 index 3e683fea7c..0000000000 Binary files a/src/Umbraco.Web.UI.Client/src/assets/fonts/lato/LatoLatin-BoldItalic.woff and /dev/null differ diff --git a/src/Umbraco.Web.UI.Client/src/assets/fonts/lato/LatoLatin-Italic.woff b/src/Umbraco.Web.UI.Client/src/assets/fonts/lato/LatoLatin-Italic.woff deleted file mode 100644 index d8cf84c8b9..0000000000 Binary files a/src/Umbraco.Web.UI.Client/src/assets/fonts/lato/LatoLatin-Italic.woff and /dev/null differ diff --git a/src/Umbraco.Web.UI.Client/src/assets/fonts/lato/LatoLatin-Light.woff b/src/Umbraco.Web.UI.Client/src/assets/fonts/lato/LatoLatin-Light.woff deleted file mode 100644 index e7d4278cce..0000000000 Binary files a/src/Umbraco.Web.UI.Client/src/assets/fonts/lato/LatoLatin-Light.woff and /dev/null differ diff --git a/src/Umbraco.Web.UI.Client/src/assets/fonts/lato/LatoLatin-LightItalic.woff b/src/Umbraco.Web.UI.Client/src/assets/fonts/lato/LatoLatin-LightItalic.woff deleted file mode 100644 index bb72fd2200..0000000000 Binary files a/src/Umbraco.Web.UI.Client/src/assets/fonts/lato/LatoLatin-LightItalic.woff and /dev/null differ diff --git a/src/Umbraco.Web.UI.Client/src/assets/fonts/lato/LatoLatin-Regular.woff b/src/Umbraco.Web.UI.Client/src/assets/fonts/lato/LatoLatin-Regular.woff deleted file mode 100644 index bf73a6d9f9..0000000000 Binary files a/src/Umbraco.Web.UI.Client/src/assets/fonts/lato/LatoLatin-Regular.woff and /dev/null differ diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-activity.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-activity.svg new file mode 100644 index 0000000000..84c1202f04 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-activity.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-add.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-add.svg new file mode 100644 index 0000000000..9abe4f9085 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-add.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-addressbook.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-addressbook.svg new file mode 100644 index 0000000000..1c6676a711 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-addressbook.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-alarm-clock.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-alarm-clock.svg new file mode 100644 index 0000000000..4aa9ce1485 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-alarm-clock.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-alert-alt.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-alert-alt.svg new file mode 100644 index 0000000000..dbb78a2188 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-alert-alt.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-alert.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-alert.svg new file mode 100644 index 0000000000..3a75464feb --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-alert.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-alt.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-alt.svg new file mode 100644 index 0000000000..1d4c0eb3e6 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-alt.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-anchor.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-anchor.svg new file mode 100644 index 0000000000..033467b283 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-anchor.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-app.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-app.svg new file mode 100644 index 0000000000..02b19fe989 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-app.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-application-error.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-application-error.svg new file mode 100644 index 0000000000..cd57ff336f --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-application-error.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-application-window-alt.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-application-window-alt.svg new file mode 100644 index 0000000000..e87dae6ec8 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-application-window-alt.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-application-window.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-application-window.svg new file mode 100644 index 0000000000..f99af0f195 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-application-window.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-arrivals.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-arrivals.svg new file mode 100644 index 0000000000..bfbe503d6b --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-arrivals.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-arrow-down.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-arrow-down.svg new file mode 100644 index 0000000000..fcdf4548ec --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-arrow-down.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-arrow-left.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-arrow-left.svg new file mode 100644 index 0000000000..2cf42ff7d6 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-arrow-left.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-arrow-right.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-arrow-right.svg new file mode 100644 index 0000000000..d1349b3b23 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-arrow-right.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-arrow-up.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-arrow-up.svg new file mode 100644 index 0000000000..f7ca5f8249 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-arrow-up.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-art-easel.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-art-easel.svg new file mode 100644 index 0000000000..c4df06598b --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-art-easel.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-article.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-article.svg new file mode 100644 index 0000000000..28b2e35c71 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-article.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-attachment.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-attachment.svg new file mode 100644 index 0000000000..a3c4f466c3 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-attachment.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-auction-hammer.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-auction-hammer.svg new file mode 100644 index 0000000000..50f8b8ef26 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-auction-hammer.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-autofill.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-autofill.svg new file mode 100644 index 0000000000..15cbe9fc35 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-autofill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-award.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-award.svg new file mode 100644 index 0000000000..18a82455f8 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-award.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-axis-rotation-2.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-axis-rotation-2.svg new file mode 100644 index 0000000000..d8ef136b72 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-axis-rotation-2.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-axis-rotation-3.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-axis-rotation-3.svg new file mode 100644 index 0000000000..77a56e9984 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-axis-rotation-3.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-axis-rotation.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-axis-rotation.svg new file mode 100644 index 0000000000..5f56a648d1 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-axis-rotation.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-baby-stroller.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-baby-stroller.svg new file mode 100644 index 0000000000..d8edc730c1 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-baby-stroller.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-backspace.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-backspace.svg new file mode 100644 index 0000000000..6c84aa5cbc --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-backspace.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-badge-add.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-badge-add.svg new file mode 100644 index 0000000000..0c6be8a34b --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-badge-add.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-badge-count.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-badge-count.svg new file mode 100644 index 0000000000..e309c884f9 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-badge-count.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-badge-remove.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-badge-remove.svg new file mode 100644 index 0000000000..a6317092b8 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-badge-remove.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-badge-restricted.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-badge-restricted.svg new file mode 100644 index 0000000000..c7ad647e22 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-badge-restricted.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-ball.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-ball.svg new file mode 100644 index 0000000000..5416815534 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-ball.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-band-aid.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-band-aid.svg new file mode 100644 index 0000000000..8232ea9b23 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-band-aid.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-bar-chart.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-bar-chart.svg new file mode 100644 index 0000000000..c2fc1a2e2f --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-bar-chart.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-barcode.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-barcode.svg new file mode 100644 index 0000000000..7b7e4e151e --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-barcode.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-bars.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-bars.svg new file mode 100644 index 0000000000..2199f4fb11 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-bars.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-battery-full.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-battery-full.svg new file mode 100644 index 0000000000..5aff9d10df --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-battery-full.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-battery-low.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-battery-low.svg new file mode 100644 index 0000000000..72909d1d84 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-battery-low.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-beer-glass.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-beer-glass.svg new file mode 100644 index 0000000000..dc9e44cff7 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-beer-glass.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-bell-off.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-bell-off.svg new file mode 100644 index 0000000000..d7c9422857 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-bell-off.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-bell.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-bell.svg new file mode 100644 index 0000000000..6843f8dbc7 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-bell.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-bill-dollar.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-bill-dollar.svg new file mode 100644 index 0000000000..7ed749b1c8 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-bill-dollar.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-bill-euro.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-bill-euro.svg new file mode 100644 index 0000000000..c526f106a0 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-bill-euro.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-bill-pound.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-bill-pound.svg new file mode 100644 index 0000000000..339006dc08 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-bill-pound.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-bill-yen.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-bill-yen.svg new file mode 100644 index 0000000000..76b9a1d318 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-bill-yen.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-bill.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-bill.svg new file mode 100644 index 0000000000..ad75b69591 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-bill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-billboard.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-billboard.svg new file mode 100644 index 0000000000..468e0dc8bd --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-billboard.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-bills-dollar.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-bills-dollar.svg new file mode 100644 index 0000000000..dcd3a8f227 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-bills-dollar.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-bills-euro.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-bills-euro.svg new file mode 100644 index 0000000000..3f2fbc38ef --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-bills-euro.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-bills-pound.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-bills-pound.svg new file mode 100644 index 0000000000..32a30de1c6 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-bills-pound.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-bills-yen.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-bills-yen.svg new file mode 100644 index 0000000000..ea352b33d5 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-bills-yen.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-bills.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-bills.svg new file mode 100644 index 0000000000..a6d9b7a76e --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-bills.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-binarycode.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-binarycode.svg new file mode 100644 index 0000000000..5a7e9ee9d2 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-binarycode.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-binoculars.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-binoculars.svg new file mode 100644 index 0000000000..407a88c58d --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-binoculars.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-bird.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-bird.svg new file mode 100644 index 0000000000..182a02be79 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-bird.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-birthday-cake.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-birthday-cake.svg new file mode 100644 index 0000000000..205a06715a --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-birthday-cake.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-block.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-block.svg new file mode 100644 index 0000000000..ed19756dd9 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-block.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-blueprint.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-blueprint.svg new file mode 100644 index 0000000000..4382644745 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-blueprint.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-bluetooth.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-bluetooth.svg new file mode 100644 index 0000000000..2c73fb77a7 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-bluetooth.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-boat-shipping.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-boat-shipping.svg new file mode 100644 index 0000000000..15a8d6f9ff --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-boat-shipping.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-bomb.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-bomb.svg new file mode 100644 index 0000000000..b39d18938c --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-bomb.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-bones.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-bones.svg new file mode 100644 index 0000000000..f956d77bf3 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-bones.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-book-alt-2.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-book-alt-2.svg new file mode 100644 index 0000000000..2003b3d233 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-book-alt-2.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-book-alt.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-book-alt.svg new file mode 100644 index 0000000000..c959cdea3a --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-book-alt.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-book.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-book.svg new file mode 100644 index 0000000000..2516a55f83 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-book.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-bookmark.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-bookmark.svg new file mode 100644 index 0000000000..a3268596ac --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-bookmark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-books.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-books.svg new file mode 100644 index 0000000000..ddb9162770 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-books.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-box-alt.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-box-alt.svg new file mode 100644 index 0000000000..0189daab6d --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-box-alt.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-box-open.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-box-open.svg new file mode 100644 index 0000000000..b2ec5d8bd3 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-box-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-box.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-box.svg new file mode 100644 index 0000000000..84fa9ca794 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-box.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-brackets.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-brackets.svg new file mode 100644 index 0000000000..142eb9f746 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-brackets.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-brick.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-brick.svg new file mode 100644 index 0000000000..e06862f8aa --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-brick.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-briefcase.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-briefcase.svg new file mode 100644 index 0000000000..e6e0705ecf --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-briefcase.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-browser-window.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-browser-window.svg new file mode 100644 index 0000000000..8279fb9198 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-browser-window.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-brush-alt-2.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-brush-alt-2.svg new file mode 100644 index 0000000000..3599902e1f --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-brush-alt-2.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-brush-alt.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-brush-alt.svg new file mode 100644 index 0000000000..65849ec18e --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-brush-alt.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-brush.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-brush.svg new file mode 100644 index 0000000000..a5b889c60d --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-brush.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-bug.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-bug.svg new file mode 100644 index 0000000000..9b5b667387 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-bug.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-bulleted-list.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-bulleted-list.svg new file mode 100644 index 0000000000..72bdf63388 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-bulleted-list.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-burn.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-burn.svg new file mode 100644 index 0000000000..4f6ebe6865 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-burn.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-bus.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-bus.svg new file mode 100644 index 0000000000..95e728138e --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-bus.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-calculator.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-calculator.svg new file mode 100644 index 0000000000..3dcce7ec35 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-calculator.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-calendar-alt.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-calendar-alt.svg new file mode 100644 index 0000000000..fb485f5a83 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-calendar-alt.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-calendar.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-calendar.svg new file mode 100644 index 0000000000..b405db5525 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-calendar.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-camcorder.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-camcorder.svg new file mode 100644 index 0000000000..de0b071abb --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-camcorder.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-camera-roll.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-camera-roll.svg new file mode 100644 index 0000000000..172e0d92b0 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-camera-roll.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-candy.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-candy.svg new file mode 100644 index 0000000000..1da869963f --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-candy.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-caps-lock.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-caps-lock.svg new file mode 100644 index 0000000000..1a466182c9 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-caps-lock.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-car.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-car.svg new file mode 100644 index 0000000000..9c72758d41 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-car.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-cash-register.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-cash-register.svg new file mode 100644 index 0000000000..f4f1779b86 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-cash-register.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-categories.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-categories.svg new file mode 100644 index 0000000000..2bd633a8f1 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-categories.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-certificate.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-certificate.svg new file mode 100644 index 0000000000..e3e16b5054 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-certificate.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-chart-curve.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-chart-curve.svg new file mode 100644 index 0000000000..219d05a1f8 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-chart-curve.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-chart.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-chart.svg new file mode 100644 index 0000000000..72c6534c38 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-chart.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-chat-active.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-chat-active.svg new file mode 100644 index 0000000000..b899dfc3fe --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-chat-active.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-chat.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-chat.svg new file mode 100644 index 0000000000..21abb255ef --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-chat.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-check.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-check.svg new file mode 100644 index 0000000000..5bd7161118 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-check.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-checkbox-dotted-active.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-checkbox-dotted-active.svg new file mode 100644 index 0000000000..f717f8063b --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-checkbox-dotted-active.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-checkbox-dotted.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-checkbox-dotted.svg new file mode 100644 index 0000000000..cf650c0085 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-checkbox-dotted.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-checkbox-empty.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-checkbox-empty.svg new file mode 100644 index 0000000000..9a3edf8a28 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-checkbox-empty.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-checkbox.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-checkbox.svg new file mode 100644 index 0000000000..748b269cd2 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-checkbox.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-chess.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-chess.svg new file mode 100644 index 0000000000..8cb5685672 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-chess.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-chip-alt.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-chip-alt.svg new file mode 100644 index 0000000000..5d459f13b1 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-chip-alt.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-chip.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-chip.svg new file mode 100644 index 0000000000..0d622b2506 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-chip.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-cinema.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-cinema.svg new file mode 100644 index 0000000000..f3bc729bf1 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-cinema.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-circle-dotted-active.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-circle-dotted-active.svg new file mode 100644 index 0000000000..f9c60410cb --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-circle-dotted-active.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-circle-dotted.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-circle-dotted.svg new file mode 100644 index 0000000000..9cd9fd9252 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-circle-dotted.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-circuits.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-circuits.svg new file mode 100644 index 0000000000..4feb78fa69 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-circuits.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-circus.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-circus.svg new file mode 100644 index 0000000000..ff712b18ed --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-circus.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-client.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-client.svg new file mode 100644 index 0000000000..23e23117e9 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-client.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-clothes-hanger.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-clothes-hanger.svg new file mode 100644 index 0000000000..f17c449cb0 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-clothes-hanger.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-cloud-drive.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-cloud-drive.svg new file mode 100644 index 0000000000..df5fcf03d7 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-cloud-drive.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-cloud-upload.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-cloud-upload.svg new file mode 100644 index 0000000000..41e015fa89 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-cloud-upload.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-cloud.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-cloud.svg new file mode 100644 index 0000000000..10a95d9562 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-cloud.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-cloudy.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-cloudy.svg new file mode 100644 index 0000000000..75aad3d4d2 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-cloudy.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-clubs.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-clubs.svg new file mode 100644 index 0000000000..68617041d5 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-clubs.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-cocktail.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-cocktail.svg new file mode 100644 index 0000000000..cc13e78a6f --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-cocktail.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-code.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-code.svg new file mode 100644 index 0000000000..2615644cea --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-code.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-coffee.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-coffee.svg new file mode 100644 index 0000000000..d9e23e1259 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-coffee.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-coin-dollar.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-coin-dollar.svg new file mode 100644 index 0000000000..6f3e0659da --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-coin-dollar.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-coin-euro.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-coin-euro.svg new file mode 100644 index 0000000000..20527e3526 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-coin-euro.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-coin-pound.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-coin-pound.svg new file mode 100644 index 0000000000..9ba0de073b --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-coin-pound.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-coin-yen.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-coin-yen.svg new file mode 100644 index 0000000000..2d7f31caf1 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-coin-yen.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-coin.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-coin.svg new file mode 100644 index 0000000000..ec3b147525 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-coin.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-coins-alt.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-coins-alt.svg new file mode 100644 index 0000000000..a2349aaa12 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-coins-alt.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-coins-dollar-alt.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-coins-dollar-alt.svg new file mode 100644 index 0000000000..8f283ea006 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-coins-dollar-alt.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-coins-dollar.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-coins-dollar.svg new file mode 100644 index 0000000000..0b2b7a2ea6 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-coins-dollar.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-coins-euro-alt.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-coins-euro-alt.svg new file mode 100644 index 0000000000..c8bbff6628 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-coins-euro-alt.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-coins-euro.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-coins-euro.svg new file mode 100644 index 0000000000..e1b77f5941 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-coins-euro.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-coins-pound-alt.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-coins-pound-alt.svg new file mode 100644 index 0000000000..e91acce12c --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-coins-pound-alt.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-coins-pound.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-coins-pound.svg new file mode 100644 index 0000000000..6d1443a6c9 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-coins-pound.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-coins-yen-alt.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-coins-yen-alt.svg new file mode 100644 index 0000000000..d477b86158 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-coins-yen-alt.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-coins-yen.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-coins-yen.svg new file mode 100644 index 0000000000..63aa1da6b2 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-coins-yen.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-coins.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-coins.svg new file mode 100644 index 0000000000..6af11e0945 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-coins.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-color-bucket.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-color-bucket.svg new file mode 100644 index 0000000000..08fcb021d9 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-color-bucket.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-colorpicker.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-colorpicker.svg new file mode 100644 index 0000000000..69d0362805 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-colorpicker.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-columns.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-columns.svg new file mode 100644 index 0000000000..913a681162 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-columns.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-comb.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-comb.svg new file mode 100644 index 0000000000..d15c099fbe --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-comb.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-combination-lock-open.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-combination-lock-open.svg new file mode 100644 index 0000000000..3cdba5cf20 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-combination-lock-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-combination-lock.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-combination-lock.svg new file mode 100644 index 0000000000..10332f449f --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-combination-lock.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-command.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-command.svg new file mode 100644 index 0000000000..7a4b9094f9 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-command.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-company.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-company.svg new file mode 100644 index 0000000000..b22bdb63ad --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-company.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-compress.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-compress.svg new file mode 100644 index 0000000000..48664d5a36 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-compress.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-connection.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-connection.svg new file mode 100644 index 0000000000..0619278e2a --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-connection.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-console.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-console.svg new file mode 100644 index 0000000000..8599aeb13a --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-console.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-contrast.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-contrast.svg new file mode 100644 index 0000000000..8dbd2d9e74 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-contrast.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-conversation-alt.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-conversation-alt.svg new file mode 100644 index 0000000000..111b651f89 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-conversation-alt.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-conversation.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-conversation.svg new file mode 100644 index 0000000000..d16115fdb0 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-conversation.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-coverflow.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-coverflow.svg new file mode 100644 index 0000000000..72a230d15d --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-coverflow.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-credit-card-alt.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-credit-card-alt.svg new file mode 100644 index 0000000000..c113197cf5 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-credit-card-alt.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-credit-card.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-credit-card.svg new file mode 100644 index 0000000000..af896af5f9 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-credit-card.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-crop.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-crop.svg new file mode 100644 index 0000000000..570c261e8a --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-crop.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-crosshair.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-crosshair.svg new file mode 100644 index 0000000000..802be32987 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-crosshair.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-crown-alt.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-crown-alt.svg new file mode 100644 index 0000000000..20bbacb2eb --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-crown-alt.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-crown.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-crown.svg new file mode 100644 index 0000000000..8ad5f1464a --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-crown.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-cupcake.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-cupcake.svg new file mode 100644 index 0000000000..de3a7c7c81 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-cupcake.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-curve.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-curve.svg new file mode 100644 index 0000000000..ac749474d3 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-curve.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-cut.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-cut.svg new file mode 100644 index 0000000000..63d892780c --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-cut.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-dashboard.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-dashboard.svg new file mode 100644 index 0000000000..13cd3bfa96 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-dashboard.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-defrag.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-defrag.svg new file mode 100644 index 0000000000..e26de351e9 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-defrag.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-delete-key.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-delete-key.svg new file mode 100644 index 0000000000..c738e7c8cf --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-delete-key.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-delete.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-delete.svg new file mode 100644 index 0000000000..1d931cc09b --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-delete.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-departure.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-departure.svg new file mode 100644 index 0000000000..243f789b80 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-departure.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-desk.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-desk.svg new file mode 100644 index 0000000000..270e0f1dff --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-desk.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-desktop.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-desktop.svg new file mode 100644 index 0000000000..de5366b44b --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-desktop.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-diagnostics.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-diagnostics.svg new file mode 100644 index 0000000000..66e2b4c653 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-diagnostics.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-diagonal-arrow-alt.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-diagonal-arrow-alt.svg new file mode 100644 index 0000000000..fb4970d319 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-diagonal-arrow-alt.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-diagonal-arrow.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-diagonal-arrow.svg new file mode 100644 index 0000000000..c10853ffcc --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-diagonal-arrow.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-diamond.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-diamond.svg new file mode 100644 index 0000000000..e831ae1172 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-diamond.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-diamonds.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-diamonds.svg new file mode 100644 index 0000000000..bf1cd0b8a4 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-diamonds.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-dice.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-dice.svg new file mode 100644 index 0000000000..bbb86cf68d --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-dice.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-diploma-alt.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-diploma-alt.svg new file mode 100644 index 0000000000..b4a5fcfeb9 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-diploma-alt.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-diploma.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-diploma.svg new file mode 100644 index 0000000000..31b542d112 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-diploma.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-directions-alt.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-directions-alt.svg new file mode 100644 index 0000000000..4a07354199 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-directions-alt.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-directions.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-directions.svg new file mode 100644 index 0000000000..a56100bb2a --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-directions.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-disc.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-disc.svg new file mode 100644 index 0000000000..8f694bc43f --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-disc.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-disk-image.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-disk-image.svg new file mode 100644 index 0000000000..80a69f30d0 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-disk-image.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-display.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-display.svg new file mode 100644 index 0000000000..22cf1ef0eb --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-display.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-dna.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-dna.svg new file mode 100644 index 0000000000..a4cc100411 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-dna.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-dock-connector.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-dock-connector.svg new file mode 100644 index 0000000000..82480deaa1 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-dock-connector.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-document-dashed-line.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-document-dashed-line.svg new file mode 100644 index 0000000000..6117d4c7f4 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-document-dashed-line.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-document.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-document.svg new file mode 100644 index 0000000000..d2da15ad9e --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-document.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-documents.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-documents.svg new file mode 100644 index 0000000000..4e56ca7b49 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-documents.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-dollar-bag.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-dollar-bag.svg new file mode 100644 index 0000000000..bc9dcb83cf --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-dollar-bag.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-donate.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-donate.svg new file mode 100644 index 0000000000..c2669c3a5c --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-donate.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-door-open-alt.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-door-open-alt.svg new file mode 100644 index 0000000000..57552cc455 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-door-open-alt.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-door-open.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-door-open.svg new file mode 100644 index 0000000000..02339e4b8b --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-door-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-download-alt.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-download-alt.svg new file mode 100644 index 0000000000..3b788ff610 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-download-alt.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-download.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-download.svg new file mode 100644 index 0000000000..a328fe65f7 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-download.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-drop.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-drop.svg new file mode 100644 index 0000000000..7523004cad --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-drop.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-eco.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-eco.svg new file mode 100644 index 0000000000..dad4c564c0 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-eco.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-economy.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-economy.svg new file mode 100644 index 0000000000..4e710b572a --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-economy.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-edit.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-edit.svg new file mode 100644 index 0000000000..2faeea0891 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-edit.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-eject.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-eject.svg new file mode 100644 index 0000000000..5f74a4ccc1 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-eject.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-employee.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-employee.svg new file mode 100644 index 0000000000..b1c4877b2b --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-employee.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-energy-saving-bulb.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-energy-saving-bulb.svg new file mode 100644 index 0000000000..a45bbf5f4f --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-energy-saving-bulb.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-enter.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-enter.svg new file mode 100644 index 0000000000..4dc8c5af3d --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-enter.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-equalizer.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-equalizer.svg new file mode 100644 index 0000000000..64b43e8537 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-equalizer.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-escape.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-escape.svg new file mode 100644 index 0000000000..edb544f9d3 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-escape.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-ethernet.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-ethernet.svg new file mode 100644 index 0000000000..2b16efe994 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-ethernet.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-euro-bag.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-euro-bag.svg new file mode 100644 index 0000000000..cb19eafeb9 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-euro-bag.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-exit-fullscreen.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-exit-fullscreen.svg new file mode 100644 index 0000000000..820c79522d --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-exit-fullscreen.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-eye.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-eye.svg new file mode 100644 index 0000000000..8ea896a1f4 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-eye.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-facebook-like.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-facebook-like.svg new file mode 100644 index 0000000000..0f27d54a06 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-facebook-like.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-factory.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-factory.svg new file mode 100644 index 0000000000..855727aad5 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-factory.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-favorite.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-favorite.svg new file mode 100644 index 0000000000..5fee2201b0 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-favorite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-female-symbol.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-female-symbol.svg new file mode 100644 index 0000000000..9e003e51e5 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-female-symbol.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-file-cabinet.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-file-cabinet.svg new file mode 100644 index 0000000000..8d34dc684f --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-file-cabinet.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-files.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-files.svg new file mode 100644 index 0000000000..228b54980d --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-files.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-filter-arrows.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-filter-arrows.svg new file mode 100644 index 0000000000..2f17186e0c --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-filter-arrows.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-filter.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-filter.svg new file mode 100644 index 0000000000..41a89b3eb0 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-filter.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-fingerprint.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-fingerprint.svg new file mode 100644 index 0000000000..b80eebd81c --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-fingerprint.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-fire.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-fire.svg new file mode 100644 index 0000000000..b0811f59f0 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-fire.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-firewall.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-firewall.svg new file mode 100644 index 0000000000..b857e4349e --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-firewall.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-firewire.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-firewire.svg new file mode 100644 index 0000000000..64807e9110 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-firewire.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-flag-alt.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-flag-alt.svg new file mode 100644 index 0000000000..16d5b65bb6 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-flag-alt.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-flag.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-flag.svg new file mode 100644 index 0000000000..3665770b99 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-flag.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-flash.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-flash.svg new file mode 100644 index 0000000000..f37a9e3483 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-flash.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-flashlight.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-flashlight.svg new file mode 100644 index 0000000000..4610c03a5b --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-flashlight.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-flowerpot.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-flowerpot.svg new file mode 100644 index 0000000000..0524daab90 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-flowerpot.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-folder-open.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-folder-open.svg new file mode 100644 index 0000000000..44de932cfd --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-folder-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-folder-outline.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-folder-outline.svg new file mode 100644 index 0000000000..77059225c7 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-folder-outline.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-folder.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-folder.svg new file mode 100644 index 0000000000..ce20ed705e --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-folder.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-folders.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-folders.svg new file mode 100644 index 0000000000..08875280e9 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-folders.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-font.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-font.svg new file mode 100644 index 0000000000..91689c5b49 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-font.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-food.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-food.svg new file mode 100644 index 0000000000..d39b58346a --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-food.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-footprints.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-footprints.svg new file mode 100644 index 0000000000..118b8dc44f --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-footprints.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-forking.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-forking.svg new file mode 100644 index 0000000000..441dead254 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-forking.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-frame-alt.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-frame-alt.svg new file mode 100644 index 0000000000..24c8834c55 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-frame-alt.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-frame.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-frame.svg new file mode 100644 index 0000000000..6d48f3165d --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-frame.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-fullscreen-alt.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-fullscreen-alt.svg new file mode 100644 index 0000000000..c2145c8171 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-fullscreen-alt.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-fullscreen.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-fullscreen.svg new file mode 100644 index 0000000000..a63101cfe0 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-fullscreen.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-game.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-game.svg new file mode 100644 index 0000000000..78797b5601 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-game.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-geometry.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-geometry.svg new file mode 100644 index 0000000000..c7ed79e0a1 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-geometry.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-gift.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-gift.svg new file mode 100644 index 0000000000..4e2030a6f1 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-gift.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-glasses.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-glasses.svg new file mode 100644 index 0000000000..72b7c8b05b --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-glasses.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-globe-alt.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-globe-alt.svg new file mode 100644 index 0000000000..b02f41ad97 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-globe-alt.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-globe-asia.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-globe-asia.svg new file mode 100644 index 0000000000..6a7334d5e8 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-globe-asia.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-globe-europe-africa.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-globe-europe-africa.svg new file mode 100644 index 0000000000..4c9b6fac01 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-globe-europe-africa.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-globe-inverted-america.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-globe-inverted-america.svg new file mode 100644 index 0000000000..a95c1a1975 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-globe-inverted-america.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-globe-inverted-asia.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-globe-inverted-asia.svg new file mode 100644 index 0000000000..c62b1e835a --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-globe-inverted-asia.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-globe-inverted-europe-africa.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-globe-inverted-europe-africa.svg new file mode 100644 index 0000000000..af162e7949 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-globe-inverted-europe-africa.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-globe.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-globe.svg new file mode 100644 index 0000000000..28e4b649be --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-globe.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-gps.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-gps.svg new file mode 100644 index 0000000000..90572f37f6 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-gps.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-graduate.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-graduate.svg new file mode 100644 index 0000000000..81067c4cdb --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-graduate.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-grid.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-grid.svg new file mode 100644 index 0000000000..2e363c0c47 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-grid.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-hammer.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-hammer.svg new file mode 100644 index 0000000000..dfafc27d3c --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-hammer.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-hand-active-alt.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-hand-active-alt.svg new file mode 100644 index 0000000000..102ae217cf --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-hand-active-alt.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-hand-active.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-hand-active.svg new file mode 100644 index 0000000000..49612abea3 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-hand-active.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-hand-pointer-alt.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-hand-pointer-alt.svg new file mode 100644 index 0000000000..3e1185fe73 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-hand-pointer-alt.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-hand-pointer.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-hand-pointer.svg new file mode 100644 index 0000000000..a002ace0f3 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-hand-pointer.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-handprint.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-handprint.svg new file mode 100644 index 0000000000..c9c254204b --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-handprint.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-handshake.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-handshake.svg new file mode 100644 index 0000000000..b83e3bf315 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-handshake.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-handtool-alt.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-handtool-alt.svg new file mode 100644 index 0000000000..1464083b74 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-handtool-alt.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-handtool.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-handtool.svg new file mode 100644 index 0000000000..2202ced6d6 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-handtool.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-hard-drive-alt.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-hard-drive-alt.svg new file mode 100644 index 0000000000..89ce2fea0e --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-hard-drive-alt.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-hard-drive.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-hard-drive.svg new file mode 100644 index 0000000000..98a2a993d4 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-hard-drive.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-hat.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-hat.svg new file mode 100644 index 0000000000..c8a1106446 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-hat.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-hd.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-hd.svg new file mode 100644 index 0000000000..77bf64d4fa --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-hd.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-headphones.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-headphones.svg new file mode 100644 index 0000000000..811c656c7e --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-headphones.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-headset.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-headset.svg new file mode 100644 index 0000000000..98db2e30db --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-headset.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-hearts.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-hearts.svg new file mode 100644 index 0000000000..c2edaafe03 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-hearts.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-height.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-height.svg new file mode 100644 index 0000000000..bf6a75975b --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-height.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-help-alt.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-help-alt.svg new file mode 100644 index 0000000000..101c65318d --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-help-alt.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-help.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-help.svg new file mode 100644 index 0000000000..a1c5fdc7c1 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-help.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-home.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-home.svg new file mode 100644 index 0000000000..ebb93f4056 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-home.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-hourglass.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-hourglass.svg new file mode 100644 index 0000000000..c918b5407b --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-hourglass.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-imac.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-imac.svg new file mode 100644 index 0000000000..76a01b97b8 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-imac.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-inactive-line.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-inactive-line.svg new file mode 100644 index 0000000000..60a16437c2 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-inactive-line.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-inbox-full.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-inbox-full.svg new file mode 100644 index 0000000000..c0beabfa29 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-inbox-full.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-inbox.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-inbox.svg new file mode 100644 index 0000000000..0fd7c71b22 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-inbox.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-indent.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-indent.svg new file mode 100644 index 0000000000..329b0f413e --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-indent.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-infinity.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-infinity.svg new file mode 100644 index 0000000000..c42e3bd49e --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-infinity.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-info.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-info.svg new file mode 100644 index 0000000000..f07ac8e7d1 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-info.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-invoice.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-invoice.svg new file mode 100644 index 0000000000..dd8457a63a --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-invoice.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-ipad.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-ipad.svg new file mode 100644 index 0000000000..e79195e670 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-ipad.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-iphone.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-iphone.svg new file mode 100644 index 0000000000..b235644b03 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-iphone.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-item-arrangement.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-item-arrangement.svg new file mode 100644 index 0000000000..fd9939dba3 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-item-arrangement.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-junk.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-junk.svg new file mode 100644 index 0000000000..a8f48754d4 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-junk.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-key.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-key.svg new file mode 100644 index 0000000000..4a32d0004e --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-key.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-keyboard.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-keyboard.svg new file mode 100644 index 0000000000..c08d8d9e93 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-keyboard.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-keychain.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-keychain.svg new file mode 100644 index 0000000000..e206a9150e --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-keychain.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-keyhole.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-keyhole.svg new file mode 100644 index 0000000000..b97b156002 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-keyhole.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-lab.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-lab.svg new file mode 100644 index 0000000000..b750677812 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-lab.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-laptop.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-laptop.svg new file mode 100644 index 0000000000..dc120cca29 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-laptop.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-layers-alt.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-layers-alt.svg new file mode 100644 index 0000000000..e02a1cadbf --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-layers-alt.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-layers.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-layers.svg new file mode 100644 index 0000000000..c6965e3e95 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-layers.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-layout.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-layout.svg new file mode 100644 index 0000000000..355f314f0f --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-layout.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-left-double-arrow.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-left-double-arrow.svg new file mode 100644 index 0000000000..0958c291da --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-left-double-arrow.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-legal.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-legal.svg new file mode 100644 index 0000000000..e4ea59375e --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-legal.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-lense.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-lense.svg new file mode 100644 index 0000000000..229a7749bd --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-lense.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-library.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-library.svg new file mode 100644 index 0000000000..1551dec3c9 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-library.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-light-down.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-light-down.svg new file mode 100644 index 0000000000..c214226f59 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-light-down.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-light-up.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-light-up.svg new file mode 100644 index 0000000000..dbac39d507 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-light-up.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-lightbulb-active.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-lightbulb-active.svg new file mode 100644 index 0000000000..522460a54c --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-lightbulb-active.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-lightbulb.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-lightbulb.svg new file mode 100644 index 0000000000..2d67f238ee --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-lightbulb.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-lightning.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-lightning.svg new file mode 100644 index 0000000000..4f8b5a1146 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-lightning.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-link.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-link.svg new file mode 100644 index 0000000000..2bb9b95c01 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-link.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-linux-tux.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-linux-tux.svg new file mode 100644 index 0000000000..cd90e979cd --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-linux-tux.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-list.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-list.svg new file mode 100644 index 0000000000..78a748f1a1 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-list.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-load.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-load.svg new file mode 100644 index 0000000000..6f4ac6197c --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-load.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-loading.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-loading.svg new file mode 100644 index 0000000000..91372f4e34 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-loading.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-locate.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-locate.svg new file mode 100644 index 0000000000..b6d715d2f0 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-locate.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-location-near-me.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-location-near-me.svg new file mode 100644 index 0000000000..69028bf985 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-location-near-me.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-location-nearby.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-location-nearby.svg new file mode 100644 index 0000000000..884edb711b --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-location-nearby.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-lock.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-lock.svg new file mode 100644 index 0000000000..8f94c7cb96 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-lock.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-log-out.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-log-out.svg new file mode 100644 index 0000000000..07c4ae9856 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-log-out.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-logout.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-logout.svg new file mode 100644 index 0000000000..e3eb856a9a --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-logout.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-loupe.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-loupe.svg new file mode 100644 index 0000000000..ab5137dc24 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-loupe.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-magnet.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-magnet.svg new file mode 100644 index 0000000000..b7c9ed6ab1 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-magnet.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-mailbox.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-mailbox.svg new file mode 100644 index 0000000000..e55cdc452c --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-mailbox.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-male-and-female.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-male-and-female.svg new file mode 100644 index 0000000000..5ddf3b95e1 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-male-and-female.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-male-symbol.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-male-symbol.svg new file mode 100644 index 0000000000..eb91f509f5 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-male-symbol.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-map-alt.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-map-alt.svg new file mode 100644 index 0000000000..fcc39f5bb0 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-map-alt.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-map-location.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-map-location.svg new file mode 100644 index 0000000000..ad9efb05ed --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-map-location.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-map-marker.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-map-marker.svg new file mode 100644 index 0000000000..a4f71dc084 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-map-marker.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-map.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-map.svg new file mode 100644 index 0000000000..2c9ae42407 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-map.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-medal.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-medal.svg new file mode 100644 index 0000000000..c97ccf0f32 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-medal.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-medical-emergency.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-medical-emergency.svg new file mode 100644 index 0000000000..0265c28988 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-medical-emergency.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-medicine.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-medicine.svg new file mode 100644 index 0000000000..1e2cb6aeef --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-medicine.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-meeting.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-meeting.svg new file mode 100644 index 0000000000..09af0d97c2 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-meeting.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-megaphone.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-megaphone.svg new file mode 100644 index 0000000000..25387c9357 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-megaphone.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-merge.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-merge.svg new file mode 100644 index 0000000000..57ac52511a --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-merge.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-message-open.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-message-open.svg new file mode 100644 index 0000000000..98aaeeeb64 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-message-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-message-unopened.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-message-unopened.svg new file mode 100644 index 0000000000..e29ecbf60d --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-message-unopened.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-message.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-message.svg new file mode 100644 index 0000000000..75785c1896 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-message.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-microscope.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-microscope.svg new file mode 100644 index 0000000000..9985552be3 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-microscope.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-mindmap.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-mindmap.svg new file mode 100644 index 0000000000..043927014b --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-mindmap.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-mobile.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-mobile.svg new file mode 100644 index 0000000000..92532b8005 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-mobile.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-molecular-network.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-molecular-network.svg new file mode 100644 index 0000000000..20d87126aa --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-molecular-network.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-molecular.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-molecular.svg new file mode 100644 index 0000000000..904e0976c3 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-molecular.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-mountain.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-mountain.svg new file mode 100644 index 0000000000..a21324f819 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-mountain.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-mouse-cursor.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-mouse-cursor.svg new file mode 100644 index 0000000000..b64dd7f88b --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-mouse-cursor.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-mouse.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-mouse.svg new file mode 100644 index 0000000000..f063df5259 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-mouse.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-movie-alt.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-movie-alt.svg new file mode 100644 index 0000000000..1878dc9ec0 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-movie-alt.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-movie.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-movie.svg new file mode 100644 index 0000000000..d5b3481a83 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-movie.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-multiple-credit-cards.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-multiple-credit-cards.svg new file mode 100644 index 0000000000..1330826784 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-multiple-credit-cards.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-multiple-windows.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-multiple-windows.svg new file mode 100644 index 0000000000..c8b3ac5669 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-multiple-windows.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-music.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-music.svg new file mode 100644 index 0000000000..87f4ba36a6 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-music.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-name-badge.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-name-badge.svg new file mode 100644 index 0000000000..22f66c9234 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-name-badge.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-navigation-bottom.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-navigation-bottom.svg new file mode 100644 index 0000000000..eeb3e79bdc --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-navigation-bottom.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-navigation-down.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-navigation-down.svg new file mode 100644 index 0000000000..007e8dd8ff --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-navigation-down.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-navigation-first.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-navigation-first.svg new file mode 100644 index 0000000000..85d44722ee --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-navigation-first.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-navigation-horizontal.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-navigation-horizontal.svg new file mode 100644 index 0000000000..4988e723eb --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-navigation-horizontal.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-navigation-last.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-navigation-last.svg new file mode 100644 index 0000000000..53f70d36fd --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-navigation-last.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-navigation-left.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-navigation-left.svg new file mode 100644 index 0000000000..56cdecce10 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-navigation-left.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-navigation-right.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-navigation-right.svg new file mode 100644 index 0000000000..251b6934d3 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-navigation-right.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-navigation-road.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-navigation-road.svg new file mode 100644 index 0000000000..f05567099f --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-navigation-road.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-navigation-top.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-navigation-top.svg new file mode 100644 index 0000000000..c932714737 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-navigation-top.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-navigation-up.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-navigation-up.svg new file mode 100644 index 0000000000..612ec9193e --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-navigation-up.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-navigation-vertical.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-navigation-vertical.svg new file mode 100644 index 0000000000..fbe3906472 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-navigation-vertical.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-navigation.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-navigation.svg new file mode 100644 index 0000000000..67d9c2a0c2 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-navigation.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-navigational-arrow.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-navigational-arrow.svg new file mode 100644 index 0000000000..bf60bde293 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-navigational-arrow.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-network-alt.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-network-alt.svg new file mode 100644 index 0000000000..e6b2c25036 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-network-alt.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-newspaper-alt.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-newspaper-alt.svg new file mode 100644 index 0000000000..1bcf422c64 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-newspaper-alt.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-newspaper.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-newspaper.svg new file mode 100644 index 0000000000..d585513f28 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-newspaper.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-next-media.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-next-media.svg new file mode 100644 index 0000000000..57d4c8e0e3 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-next-media.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-next.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-next.svg new file mode 100644 index 0000000000..b00b37dd17 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-next.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-nodes.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-nodes.svg new file mode 100644 index 0000000000..e912e52d15 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-nodes.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-notepad-alt.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-notepad-alt.svg new file mode 100644 index 0000000000..bca4d0c462 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-notepad-alt.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-notepad.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-notepad.svg new file mode 100644 index 0000000000..5fc428082e --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-notepad.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-old-key.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-old-key.svg new file mode 100644 index 0000000000..a4003027a4 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-old-key.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-old-phone.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-old-phone.svg new file mode 100644 index 0000000000..8eb7804e74 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-old-phone.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-operator.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-operator.svg new file mode 100644 index 0000000000..ad78b8602f --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-operator.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-ordered-list.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-ordered-list.svg new file mode 100644 index 0000000000..de140283b8 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-ordered-list.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-os-x.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-os-x.svg new file mode 100644 index 0000000000..170f3aad47 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-os-x.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-out.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-out.svg new file mode 100644 index 0000000000..73d2cfe147 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-out.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-outbox.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-outbox.svg new file mode 100644 index 0000000000..3b59c93933 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-outbox.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-outdent.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-outdent.svg new file mode 100644 index 0000000000..85ff01f440 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-outdent.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-page-add.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-page-add.svg new file mode 100644 index 0000000000..538defed20 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-page-add.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-page-down.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-page-down.svg new file mode 100644 index 0000000000..2fae28dfd9 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-page-down.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-page-remove.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-page-remove.svg new file mode 100644 index 0000000000..c5009604d4 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-page-remove.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-page-restricted.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-page-restricted.svg new file mode 100644 index 0000000000..4a7c8696e2 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-page-restricted.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-page-up.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-page-up.svg new file mode 100644 index 0000000000..db6ad88927 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-page-up.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-paint-roller.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-paint-roller.svg new file mode 100644 index 0000000000..59665d6232 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-paint-roller.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-palette.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-palette.svg new file mode 100644 index 0000000000..26c9f7f6b2 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-palette.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-panel-show.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-panel-show.svg new file mode 100644 index 0000000000..91c3e9f0ca --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-panel-show.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-pannel-close.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-pannel-close.svg new file mode 100644 index 0000000000..8006a60541 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-pannel-close.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-pants.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-pants.svg new file mode 100644 index 0000000000..9604660ce0 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-pants.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-paper-bag.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-paper-bag.svg new file mode 100644 index 0000000000..e7435caf75 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-paper-bag.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-paper-plane-alt.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-paper-plane-alt.svg new file mode 100644 index 0000000000..2c4259e954 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-paper-plane-alt.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-paper-plane.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-paper-plane.svg new file mode 100644 index 0000000000..fe60af779b --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-paper-plane.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-parachute-drop.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-parachute-drop.svg new file mode 100644 index 0000000000..cdd4a501d7 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-parachute-drop.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-parental-control.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-parental-control.svg new file mode 100644 index 0000000000..3a19170b80 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-parental-control.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-partly-cloudy.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-partly-cloudy.svg new file mode 100644 index 0000000000..57af81127e --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-partly-cloudy.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-paste-in.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-paste-in.svg new file mode 100644 index 0000000000..eae1a1bd12 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-paste-in.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-path.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-path.svg new file mode 100644 index 0000000000..a8f5df6e4c --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-path.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-pause.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-pause.svg new file mode 100644 index 0000000000..7d9dce3838 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-pause.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-pc.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-pc.svg new file mode 100644 index 0000000000..a8c8196797 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-pc.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-people-alt-2.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-people-alt-2.svg new file mode 100644 index 0000000000..4aa84eae75 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-people-alt-2.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-people-alt.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-people-alt.svg new file mode 100644 index 0000000000..7f129069c7 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-people-alt.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-people-female.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-people-female.svg new file mode 100644 index 0000000000..0cf6088418 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-people-female.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-people.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-people.svg new file mode 100644 index 0000000000..ab861f8814 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-people.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-phone-ring.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-phone-ring.svg new file mode 100644 index 0000000000..1797603bbc --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-phone-ring.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-phone.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-phone.svg new file mode 100644 index 0000000000..085e41600e --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-phone.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-photo-album.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-photo-album.svg new file mode 100644 index 0000000000..532615bf2a --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-photo-album.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-picture.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-picture.svg new file mode 100644 index 0000000000..9250074063 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-picture.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-pictures-alt-2.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-pictures-alt-2.svg new file mode 100644 index 0000000000..4bc1120750 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-pictures-alt-2.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-pictures-alt.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-pictures-alt.svg new file mode 100644 index 0000000000..eee91913ed --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-pictures-alt.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-pictures.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-pictures.svg new file mode 100644 index 0000000000..8844e068b2 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-pictures.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-pie-chart.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-pie-chart.svg new file mode 100644 index 0000000000..1db73f63d3 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-pie-chart.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-piggy-bank.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-piggy-bank.svg new file mode 100644 index 0000000000..45c74819f8 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-piggy-bank.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-pin-location.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-pin-location.svg new file mode 100644 index 0000000000..5907d6e541 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-pin-location.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-piracy.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-piracy.svg new file mode 100644 index 0000000000..577d04e517 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-piracy.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-plane.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-plane.svg new file mode 100644 index 0000000000..c09d4522e2 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-plane.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-planet.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-planet.svg new file mode 100644 index 0000000000..455ce2da3a --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-planet.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-play.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-play.svg new file mode 100644 index 0000000000..3a041b7e2b --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-play.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-playing-cards.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-playing-cards.svg new file mode 100644 index 0000000000..132d264d5b --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-playing-cards.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-playlist.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-playlist.svg new file mode 100644 index 0000000000..0492601307 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-playlist.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-plugin.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-plugin.svg new file mode 100644 index 0000000000..c88e396dfa --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-plugin.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-podcast.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-podcast.svg new file mode 100644 index 0000000000..796c263f0a --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-podcast.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-poker-chip.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-poker-chip.svg new file mode 100644 index 0000000000..6f3698bd66 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-poker-chip.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-poll.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-poll.svg new file mode 100644 index 0000000000..089fa647d1 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-poll.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-post-it.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-post-it.svg new file mode 100644 index 0000000000..61a2787df8 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-post-it.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-pound-bag.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-pound-bag.svg new file mode 100644 index 0000000000..dfb460d22a --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-pound-bag.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-power-outlet.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-power-outlet.svg new file mode 100644 index 0000000000..41e3e51e20 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-power-outlet.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-power.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-power.svg new file mode 100644 index 0000000000..9c4fb4b087 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-power.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-presentation.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-presentation.svg new file mode 100644 index 0000000000..59e720c81e --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-presentation.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-previous-media.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-previous-media.svg new file mode 100644 index 0000000000..656c29d14c --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-previous-media.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-previous.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-previous.svg new file mode 100644 index 0000000000..f858829633 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-previous.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-price-dollar.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-price-dollar.svg new file mode 100644 index 0000000000..72d42718a9 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-price-dollar.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-price-euro.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-price-euro.svg new file mode 100644 index 0000000000..0c437bc769 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-price-euro.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-price-pound.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-price-pound.svg new file mode 100644 index 0000000000..0858462da8 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-price-pound.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-price-yen.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-price-yen.svg new file mode 100644 index 0000000000..96c3917842 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-price-yen.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-print.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-print.svg new file mode 100644 index 0000000000..3bce86bedd --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-print.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-printer-alt.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-printer-alt.svg new file mode 100644 index 0000000000..050ec1d81c --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-printer-alt.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-projector.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-projector.svg new file mode 100644 index 0000000000..afc95283c3 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-projector.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-pulse.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-pulse.svg new file mode 100644 index 0000000000..8a655631af --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-pulse.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-pushpin.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-pushpin.svg new file mode 100644 index 0000000000..586bf4fdc0 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-pushpin.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-qr-code.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-qr-code.svg new file mode 100644 index 0000000000..bdbf3d64fb --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-qr-code.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-quote.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-quote.svg new file mode 100644 index 0000000000..16746643b6 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-quote.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-radio-alt.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-radio-alt.svg new file mode 100644 index 0000000000..c7dcf65677 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-radio-alt.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-radio-receiver.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-radio-receiver.svg new file mode 100644 index 0000000000..a3bea41462 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-radio-receiver.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-radio.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-radio.svg new file mode 100644 index 0000000000..2cf8718a2e --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-radio.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-rain.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-rain.svg new file mode 100644 index 0000000000..0007d98d6c --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-rain.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-rate.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-rate.svg new file mode 100644 index 0000000000..aac2ba0481 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-rate.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-re-post.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-re-post.svg new file mode 100644 index 0000000000..80b7a9673d --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-re-post.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-readonly.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-readonly.svg new file mode 100644 index 0000000000..b0840b5171 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-readonly.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-receipt-alt.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-receipt-alt.svg new file mode 100644 index 0000000000..d7b6f5e860 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-receipt-alt.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-receipt-dollar.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-receipt-dollar.svg new file mode 100644 index 0000000000..6784c6e8df --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-receipt-dollar.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-receipt-euro.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-receipt-euro.svg new file mode 100644 index 0000000000..82e99b2eec --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-receipt-euro.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-receipt-pound.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-receipt-pound.svg new file mode 100644 index 0000000000..b28cf9e00d --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-receipt-pound.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-receipt-yen.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-receipt-yen.svg new file mode 100644 index 0000000000..529fd87d4e --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-receipt-yen.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-reception.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-reception.svg new file mode 100644 index 0000000000..01a61f4ae9 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-reception.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-record.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-record.svg new file mode 100644 index 0000000000..5a928928d3 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-record.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-redo.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-redo.svg new file mode 100644 index 0000000000..b1fcfc01d6 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-redo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-refresh.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-refresh.svg new file mode 100644 index 0000000000..b3c932583f --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-refresh.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-remote.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-remote.svg new file mode 100644 index 0000000000..ff3d1de672 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-remote.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-remove.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-remove.svg new file mode 100644 index 0000000000..97bff0169f --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-remove.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-repeat-one.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-repeat-one.svg new file mode 100644 index 0000000000..054185177f --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-repeat-one.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-repeat.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-repeat.svg new file mode 100644 index 0000000000..68e7b30f8a --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-repeat.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-reply-arrow.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-reply-arrow.svg new file mode 100644 index 0000000000..fd708c5173 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-reply-arrow.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-resize.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-resize.svg new file mode 100644 index 0000000000..e26e9fb196 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-resize.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-return-to-top.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-return-to-top.svg new file mode 100644 index 0000000000..15e5d82289 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-return-to-top.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-right-double-arrow.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-right-double-arrow.svg new file mode 100644 index 0000000000..a8f4a03cb7 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-right-double-arrow.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-road.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-road.svg new file mode 100644 index 0000000000..e9dedee5bd --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-road.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-roadsign.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-roadsign.svg new file mode 100644 index 0000000000..4115c305c4 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-roadsign.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-rocket.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-rocket.svg new file mode 100644 index 0000000000..5fab3cf1ba --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-rocket.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-rss.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-rss.svg new file mode 100644 index 0000000000..96a210c20c --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-rss.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-ruler-alt.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-ruler-alt.svg new file mode 100644 index 0000000000..695d110031 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-ruler-alt.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-ruler.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-ruler.svg new file mode 100644 index 0000000000..15eff181d8 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-ruler.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-safe.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-safe.svg new file mode 100644 index 0000000000..9bdce49ca5 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-safe.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-safedial.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-safedial.svg new file mode 100644 index 0000000000..da9a6a6275 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-safedial.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-sandbox-toys.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-sandbox-toys.svg new file mode 100644 index 0000000000..727e62cc18 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-sandbox-toys.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-satellite-dish.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-satellite-dish.svg new file mode 100644 index 0000000000..235d67c316 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-satellite-dish.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-save.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-save.svg new file mode 100644 index 0000000000..c4a56c7268 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-save.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-scan.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-scan.svg new file mode 100644 index 0000000000..de0a84c4b6 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-scan.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-school.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-school.svg new file mode 100644 index 0000000000..216a3ff764 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-school.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-screensharing.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-screensharing.svg new file mode 100644 index 0000000000..065d04c278 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-screensharing.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-script-alt.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-script-alt.svg new file mode 100644 index 0000000000..4efc77d194 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-script-alt.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-script.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-script.svg new file mode 100644 index 0000000000..224b12e0a4 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-script.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-scull.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-scull.svg new file mode 100644 index 0000000000..fc2d5cc4b2 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-scull.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-search.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-search.svg new file mode 100644 index 0000000000..1e0715b800 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-search.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-security-camera.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-security-camera.svg new file mode 100644 index 0000000000..09bb6613aa --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-security-camera.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-sensor.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-sensor.svg new file mode 100644 index 0000000000..a73de18bb1 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-sensor.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-server-alt.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-server-alt.svg new file mode 100644 index 0000000000..e1d6a449a5 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-server-alt.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-server.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-server.svg new file mode 100644 index 0000000000..04636e254a --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-server.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-settings-alt-2.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-settings-alt-2.svg new file mode 100644 index 0000000000..da84deb9e5 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-settings-alt-2.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-settings-alt.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-settings-alt.svg new file mode 100644 index 0000000000..ab9eb44951 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-settings-alt.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-settings.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-settings.svg new file mode 100644 index 0000000000..6311ad6122 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-settings.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-share-alt-2.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-share-alt-2.svg new file mode 100644 index 0000000000..6f220aaa6d --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-share-alt-2.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-share-alt.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-share-alt.svg new file mode 100644 index 0000000000..7cff7b3840 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-share-alt.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-share.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-share.svg new file mode 100644 index 0000000000..2e4584a775 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-share.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-sharing-iphone.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-sharing-iphone.svg new file mode 100644 index 0000000000..04d34e0fe3 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-sharing-iphone.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-shield.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-shield.svg new file mode 100644 index 0000000000..f34195c7d4 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-shield.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-shift.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-shift.svg new file mode 100644 index 0000000000..3d6f9aea9d --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-shift.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-shipping-box.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-shipping-box.svg new file mode 100644 index 0000000000..66e12cea40 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-shipping-box.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-shipping.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-shipping.svg new file mode 100644 index 0000000000..102cb0be70 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-shipping.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-shoe.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-shoe.svg new file mode 100644 index 0000000000..f784c832a6 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-shoe.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-shopping-basket-alt-2.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-shopping-basket-alt-2.svg new file mode 100644 index 0000000000..06beb2dc40 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-shopping-basket-alt-2.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-shopping-basket-alt.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-shopping-basket-alt.svg new file mode 100644 index 0000000000..5cdd20bf19 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-shopping-basket-alt.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-shopping-basket.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-shopping-basket.svg new file mode 100644 index 0000000000..71cdd1de20 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-shopping-basket.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-shorts.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-shorts.svg new file mode 100644 index 0000000000..e3f8ab2625 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-shorts.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-shuffle.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-shuffle.svg new file mode 100644 index 0000000000..546f0a2965 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-shuffle.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-sience.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-sience.svg new file mode 100644 index 0000000000..e692a2ec0c --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-sience.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-simcard.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-simcard.svg new file mode 100644 index 0000000000..a65fc9ffb5 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-simcard.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-single-note.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-single-note.svg new file mode 100644 index 0000000000..f98a14c285 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-single-note.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-sitemap.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-sitemap.svg new file mode 100644 index 0000000000..0cbd6659a4 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-sitemap.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-sleep.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-sleep.svg new file mode 100644 index 0000000000..c85ac0dc8a --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-sleep.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-slideshow.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-slideshow.svg new file mode 100644 index 0000000000..484e86cd28 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-slideshow.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-smiley-inverted.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-smiley-inverted.svg new file mode 100644 index 0000000000..8c2bc19928 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-smiley-inverted.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-smiley.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-smiley.svg new file mode 100644 index 0000000000..5504a545cd --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-smiley.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-snow.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-snow.svg new file mode 100644 index 0000000000..2ba7134a91 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-snow.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-sound-low.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-sound-low.svg new file mode 100644 index 0000000000..1a5abc0193 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-sound-low.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-sound-medium.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-sound-medium.svg new file mode 100644 index 0000000000..d6497c7c88 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-sound-medium.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-sound-off.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-sound-off.svg new file mode 100644 index 0000000000..173ea47692 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-sound-off.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-sound-waves.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-sound-waves.svg new file mode 100644 index 0000000000..cf21b5606b --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-sound-waves.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-sound.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-sound.svg new file mode 100644 index 0000000000..ff3ba17166 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-sound.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-spades.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-spades.svg new file mode 100644 index 0000000000..f97a5457f8 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-spades.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-speaker.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-speaker.svg new file mode 100644 index 0000000000..dc922a00d3 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-speaker.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-speed-gauge.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-speed-gauge.svg new file mode 100644 index 0000000000..ce52283ddb --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-speed-gauge.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-split-alt.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-split-alt.svg new file mode 100644 index 0000000000..a5ad049940 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-split-alt.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-split.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-split.svg new file mode 100644 index 0000000000..1bdfd8a66b --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-split.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-sprout.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-sprout.svg new file mode 100644 index 0000000000..1d139dd355 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-sprout.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-squiggly-line.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-squiggly-line.svg new file mode 100644 index 0000000000..2c88881018 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-squiggly-line.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-ssd.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-ssd.svg new file mode 100644 index 0000000000..483398df2d --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-ssd.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-stacked-disks.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-stacked-disks.svg new file mode 100644 index 0000000000..0c6fb6e8d1 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-stacked-disks.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-stamp.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-stamp.svg new file mode 100644 index 0000000000..2468ef2c74 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-stamp.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-stop-alt.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-stop-alt.svg new file mode 100644 index 0000000000..ed61a5b6cc --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-stop-alt.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-stop-hand.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-stop-hand.svg new file mode 100644 index 0000000000..3742256b80 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-stop-hand.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-stop.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-stop.svg new file mode 100644 index 0000000000..6b61d9904b --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-stop.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-store.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-store.svg new file mode 100644 index 0000000000..3a9618fef2 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-store.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-stream.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-stream.svg new file mode 100644 index 0000000000..97ea6bed56 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-stream.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-sunny.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-sunny.svg new file mode 100644 index 0000000000..07391fec24 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-sunny.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-sweatshirt.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-sweatshirt.svg new file mode 100644 index 0000000000..3b33e88738 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-sweatshirt.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-sync.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-sync.svg new file mode 100644 index 0000000000..b3e8e396bc --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-sync.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-t-shirt.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-t-shirt.svg new file mode 100644 index 0000000000..860762bf35 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-t-shirt.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-tab-key.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-tab-key.svg new file mode 100644 index 0000000000..dcd4d918c8 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-tab-key.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-tab.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-tab.svg new file mode 100644 index 0000000000..a03fd4e379 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-tab.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-tactics.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-tactics.svg new file mode 100644 index 0000000000..ba1a383471 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-tactics.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-tag.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-tag.svg new file mode 100644 index 0000000000..ffeadcc433 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-tag.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-tags.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-tags.svg new file mode 100644 index 0000000000..681f38712d --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-tags.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-takeaway-cup.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-takeaway-cup.svg new file mode 100644 index 0000000000..5360908485 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-takeaway-cup.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-target.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-target.svg new file mode 100644 index 0000000000..f61b84adc6 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-target.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-temperatrure-alt.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-temperatrure-alt.svg new file mode 100644 index 0000000000..235b8a1eb9 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-temperatrure-alt.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-temperature.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-temperature.svg new file mode 100644 index 0000000000..fa6c7859fa --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-temperature.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-terminal.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-terminal.svg new file mode 100644 index 0000000000..edb6b159e0 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-terminal.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-theater.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-theater.svg new file mode 100644 index 0000000000..b3a293ab48 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-theater.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-theif.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-theif.svg new file mode 100644 index 0000000000..f3b3ec69ad --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-theif.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-thought-bubble.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-thought-bubble.svg new file mode 100644 index 0000000000..59b9b71150 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-thought-bubble.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-thumb-down.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-thumb-down.svg new file mode 100644 index 0000000000..933450deab --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-thumb-down.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-thumb-up.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-thumb-up.svg new file mode 100644 index 0000000000..1068c845bd --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-thumb-up.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-thumbnail-list.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-thumbnail-list.svg new file mode 100644 index 0000000000..c6ddb31ec2 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-thumbnail-list.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-thumbnails-small.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-thumbnails-small.svg new file mode 100644 index 0000000000..ced7db6e71 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-thumbnails-small.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-thumbnails.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-thumbnails.svg new file mode 100644 index 0000000000..b8800a7000 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-thumbnails.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-ticket.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-ticket.svg new file mode 100644 index 0000000000..b69e61beec --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-ticket.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-time.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-time.svg new file mode 100644 index 0000000000..cd65d62e2e --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-time.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-timer.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-timer.svg new file mode 100644 index 0000000000..f93538095b --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-timer.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-tools.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-tools.svg new file mode 100644 index 0000000000..a8a1aec10f --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-tools.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-top.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-top.svg new file mode 100644 index 0000000000..2bea18be1e --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-top.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-traffic-alt.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-traffic-alt.svg new file mode 100644 index 0000000000..9cb29dfaa0 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-traffic-alt.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-trafic.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-trafic.svg new file mode 100644 index 0000000000..420832c521 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-trafic.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-train.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-train.svg new file mode 100644 index 0000000000..b1de2d26e1 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-train.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-trash-alt-2.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-trash-alt-2.svg new file mode 100644 index 0000000000..8bb390a394 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-trash-alt-2.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-trash-alt.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-trash-alt.svg new file mode 100644 index 0000000000..5fba12abc3 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-trash-alt.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-trash.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-trash.svg new file mode 100644 index 0000000000..abae234837 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-trash.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-tree.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-tree.svg new file mode 100644 index 0000000000..8da9048412 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-tree.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-trophy.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-trophy.svg new file mode 100644 index 0000000000..75229f89a8 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-trophy.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-truck.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-truck.svg new file mode 100644 index 0000000000..112c57941f --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-truck.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-tv-old.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-tv-old.svg new file mode 100644 index 0000000000..6bde7d04ac --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-tv-old.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-tv.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-tv.svg new file mode 100644 index 0000000000..d3fc2d2cd9 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-tv.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-umb-content.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-umb-content.svg new file mode 100644 index 0000000000..3ee9892c48 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-umb-content.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-umb-contour.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-umb-contour.svg new file mode 100644 index 0000000000..2590acf88d --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-umb-contour.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-umb-deploy.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-umb-deploy.svg new file mode 100644 index 0000000000..37c500ddd3 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-umb-deploy.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-umb-developer.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-umb-developer.svg new file mode 100644 index 0000000000..f99365c4b4 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-umb-developer.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-umb-media.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-umb-media.svg new file mode 100644 index 0000000000..9230e040bd --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-umb-media.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-umb-members.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-umb-members.svg new file mode 100644 index 0000000000..9193b6baa6 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-umb-members.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-umb-settings.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-umb-settings.svg new file mode 100644 index 0000000000..554ecdef32 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-umb-settings.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-umb-users.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-umb-users.svg new file mode 100644 index 0000000000..f8ef77731c --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-umb-users.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-umbrella.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-umbrella.svg new file mode 100644 index 0000000000..620eac158a --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-umbrella.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-undo.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-undo.svg new file mode 100644 index 0000000000..ca5152f6da --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-undo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-universal.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-universal.svg new file mode 100644 index 0000000000..49118f2935 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-universal.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-unlocked.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-unlocked.svg new file mode 100644 index 0000000000..2254dfc910 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-unlocked.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-untitled.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-untitled.svg new file mode 100644 index 0000000000..4e621d71c1 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-untitled.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-usb-connector.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-usb-connector.svg new file mode 100644 index 0000000000..0d97d4094d --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-usb-connector.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-usb.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-usb.svg new file mode 100644 index 0000000000..7478d50f9e --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-usb.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-user-female.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-user-female.svg new file mode 100644 index 0000000000..61b554428b --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-user-female.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-user-females-alt.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-user-females-alt.svg new file mode 100644 index 0000000000..e4813ece0b --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-user-females-alt.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-user-females.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-user-females.svg new file mode 100644 index 0000000000..b4f9776fa5 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-user-females.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-user-glasses.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-user-glasses.svg new file mode 100644 index 0000000000..3816df4ce6 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-user-glasses.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-user.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-user.svg new file mode 100644 index 0000000000..9f46a9853d --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-user.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-users-alt.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-users-alt.svg new file mode 100644 index 0000000000..6d87232e4d --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-users-alt.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-users.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-users.svg new file mode 100644 index 0000000000..65f0703bcc --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-users.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-utilities.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-utilities.svg new file mode 100644 index 0000000000..5181d06074 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-utilities.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-vcard.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-vcard.svg new file mode 100644 index 0000000000..485275849a --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-vcard.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-video.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-video.svg new file mode 100644 index 0000000000..48cb996cae --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-video.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-voice.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-voice.svg new file mode 100644 index 0000000000..786dc7572c --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-voice.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-wall-plug.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-wall-plug.svg new file mode 100644 index 0000000000..d7d6d12e26 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-wall-plug.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-wallet.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-wallet.svg new file mode 100644 index 0000000000..36506ede4a --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-wallet.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-wand.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-wand.svg new file mode 100644 index 0000000000..beeffe5d4e --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-wand.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-war.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-war.svg new file mode 100644 index 0000000000..ae01d407d2 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-war.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-weight.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-weight.svg new file mode 100644 index 0000000000..cd5f49fbb8 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-weight.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-width.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-width.svg new file mode 100644 index 0000000000..77864d37b5 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-width.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-wifi.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-wifi.svg new file mode 100644 index 0000000000..0118ed13b1 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-wifi.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-window-popin.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-window-popin.svg new file mode 100644 index 0000000000..aec2173f09 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-window-popin.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-window-sizes.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-window-sizes.svg new file mode 100644 index 0000000000..26af884833 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-window-sizes.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-windows.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-windows.svg new file mode 100644 index 0000000000..3e00719e9e --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-windows.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-wine-glass.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-wine-glass.svg new file mode 100644 index 0000000000..abb0115e23 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-wine-glass.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-wrench.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-wrench.svg new file mode 100644 index 0000000000..96040a486e --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-wrench.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-wrong.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-wrong.svg new file mode 100644 index 0000000000..09f0a7376d --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-wrong.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-yen-bag.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-yen-bag.svg new file mode 100644 index 0000000000..2680a40cca --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-yen-bag.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-zip.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-zip.svg new file mode 100644 index 0000000000..2691f1c586 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-zip.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-zom-out.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-zom-out.svg new file mode 100644 index 0000000000..8afc74de61 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-zom-out.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/assets/icons/icon-zoom-in.svg b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-zoom-in.svg new file mode 100644 index 0000000000..df7748938e --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/icons/icon-zoom-in.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbappheader.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbappheader.directive.js index 8efaf0c024..b52b0a5763 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbappheader.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbappheader.directive.js @@ -1,7 +1,7 @@ (function () { "use strict"; - function AppHeaderDirective(eventsService, appState, userService, focusService, backdropService) { + function AppHeaderDirective(eventsService, appState, userService, focusService, backdropService, overlayService) { function link(scope, el, attr, ctrl) { @@ -71,21 +71,17 @@ }; scope.avatarClick = function () { - if (!scope.userDialog) { - backdropService.open(); - scope.userDialog = { - view: "user", - show: true, - close: function (oldModel) { - scope.userDialog.show = false; - scope.userDialog = null; - backdropService.close(); - } - }; - } else { - scope.userDialog.show = false; - scope.userDialog = null; - } + + const dialog = { + view: "user", + position: "right", + name: "overlay-user", + close: function () { + overlayService.close(); + } + }; + + overlayService.open(dialog); }; } diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbcontextmenu.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbcontextmenu.directive.js index 32034818a3..f666e62587 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbcontextmenu.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbcontextmenu.directive.js @@ -1,3 +1,11 @@ +/** +* @ngdoc directive +* @name umbraco.directives.directive:umbContextMenu +* @restrict A + * + * @description + * Handles the click events on the context menu +**/ angular.module("umbraco.directives") .directive('umbContextMenu', function (navigationService, keyboardService, backdropService) { return { diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbdrawer/umbdrawer.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbdrawer/umbdrawer.directive.js index e2e94e466f..8fa1bf4a46 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbdrawer/umbdrawer.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbdrawer/umbdrawer.directive.js @@ -57,7 +57,7 @@ The drawer component is a global component and is already added to the umbraco m
  • {@link umbraco.directives.directive:umbDrawerView umbDrawerView}
  • {@link umbraco.directives.directive:umbDrawerHeader umbDrawerHeader}
  • -
  • {@link umbraco.directives.directive:umbDrawerView umbDrawerContent}
  • +
  • {@link umbraco.directives.directive:umbDrawerContent umbDrawerContent}
  • {@link umbraco.directives.directive:umbDrawerFooter umbDrawerFooter}
@@ -106,4 +106,4 @@ function Drawer($location, $routeParams, helpService, userService, localizationS }; } - angular.module('umbraco.directives').directive("umbDrawer", Drawer); \ No newline at end of file + angular.module('umbraco.directives').directive("umbDrawer", Drawer); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umblogin.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umblogin.directive.js index c2b298ad24..36eeb173d6 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umblogin.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umblogin.directive.js @@ -13,7 +13,10 @@ } }); - function UmbLoginController($scope, $location, currentUserResource, formHelper, mediaHelper, umbRequestHelper, Upload, localizationService, userService, externalLoginInfo, resetPasswordCodeInfo, $timeout, authResource, $q, $route) { + function UmbLoginController($scope, $location, currentUserResource, formHelper, + mediaHelper, umbRequestHelper, Upload, localizationService, + userService, externalLoginInfo, externalLoginInfoService, + resetPasswordCodeInfo, $timeout, authResource, $q, $route) { const vm = this; @@ -43,9 +46,18 @@ vm.allowPasswordReset = Umbraco.Sys.ServerVariables.umbracoSettings.canSendRequiredEmail && Umbraco.Sys.ServerVariables.umbracoSettings.allowPasswordReset; vm.errorMsg = ""; vm.externalLoginFormAction = Umbraco.Sys.ServerVariables.umbracoUrls.externalLoginsUrl; - vm.externalLoginProviders = externalLoginInfo.providers; + vm.externalLoginProviders = externalLoginInfoService.getLoginProviders(); + vm.externalLoginProviders.forEach(x => { + x.customView = externalLoginInfoService.getLoginProviderView(x); + // if there are errors set for this specific provider than assign them directly to the model + if (externalLoginInfo.errorProvider === x.authType) { + x.errors = externalLoginInfo.errors; + } + }); + vm.denyLocalLogin = externalLoginInfoService.hasDenyLocalLogin(); vm.externalLoginInfo = externalLoginInfo; vm.resetPasswordCodeInfo = resetPasswordCodeInfo; + vm.logoImage = Umbraco.Sys.ServerVariables.umbracoSettings.loginLogoImage; vm.backgroundImage = Umbraco.Sys.ServerVariables.umbracoSettings.loginBackgroundImage; vm.usernameIsEmail = Umbraco.Sys.ServerVariables.umbracoSettings.usernameIsEmail; @@ -62,7 +74,7 @@ vm.setPasswordSubmit = setPasswordSubmit; vm.labels = {}; localizationService.localizeMany([ - vm.usernameIsEmail ? "general_email" : "general_username", + vm.usernameIsEmail ? "general_email" : "general_username", vm.usernameIsEmail ? "placeholders_email" : "placeholders_usernameHint", vm.usernameIsEmail ? "placeholders_emptyEmail" : "placeholders_emptyUsername", "placeholders_emptyPassword"] @@ -72,9 +84,11 @@ vm.labels.usernameError = data[2]; vm.labels.passwordError = data[3]; }); - + vm.twoFactor = {}; + vm.loginSuccess = loginSuccess; + function onInit() { // Check if it is a new user @@ -98,11 +112,11 @@ //localize the text localizationService.localize("errorHandling_errorInPasswordFormat", [ - vm.invitedUserPasswordModel.passwordPolicies.minPasswordLength, - vm.invitedUserPasswordModel.passwordPolicies.minNonAlphaNumericChars - ]).then(function (data) { - vm.invitedUserPasswordModel.passwordPolicyText = data; - }); + vm.invitedUserPasswordModel.passwordPolicies.minPasswordLength, + vm.invitedUserPasswordModel.passwordPolicies.minNonAlphaNumericChars + ]).then(function (data) { + vm.invitedUserPasswordModel.passwordPolicyText = data; + }); }) ]).then(function () { vm.inviteStep = Number(inviteVal); @@ -144,14 +158,14 @@ function getStarted() { $location.search('invite', null); - if(vm.onLogin) { + if (vm.onLogin) { vm.onLogin(); } } - function inviteSavePassword () { + function inviteSavePassword() { - if (formHelper.submitForm({ scope: $scope })) { + if (formHelper.submitForm({ scope: $scope, formCtrl: vm.inviteUserPasswordForm })) { vm.invitedUserPasswordModel.buttonState = "busy"; @@ -159,7 +173,7 @@ .then(function (data) { //success - formHelper.resetForm({ scope: $scope }); + formHelper.resetForm({ scope: $scope, formCtrl: vm.inviteUserPasswordForm }); vm.invitedUserPasswordModel.buttonState = "success"; //set the user and set them as logged in vm.invitedUser = data; @@ -168,6 +182,7 @@ vm.inviteStep = 2; }, function (err) { + formHelper.resetForm({ scope: $scope, hasErrors: true, formCtrl: vm.inviteUserPasswordForm }); formHelper.handleError(err); vm.invitedUserPasswordModel.buttonState = "error"; }); @@ -196,37 +211,41 @@ SetTitle(); } + function loginSuccess() { + vm.loginStates.submitButton = "success"; + userService._retryRequestQueue(true); + if (vm.onLogin) { + vm.onLogin(); + } + } + function loginSubmit() { - - if (formHelper.submitForm({ scope: $scope })) { + + if (formHelper.submitForm({ scope: $scope, formCtrl: vm.loginForm })) { //if the login and password are not empty we need to automatically // validate them - this is because if there are validation errors on the server // then the user has to change both username & password to resubmit which isn't ideal, // so if they're not empty, we'll just make sure to set them to valid. - if (vm.login && vm.password && vm.login.length > 0 && vm.password.length > 0) { + if (vm.login && vm.password && vm.login.length > 0 && vm.password.length > 0) { vm.loginForm.username.$setValidity('auth', true); vm.loginForm.password.$setValidity('auth', true); } - + if (vm.loginForm.$invalid) { SetTitle(); return; } - + // make sure that we are returning to the login view. vm.view = "login"; vm.loginStates.submitButton = "busy"; userService.authenticate(vm.login, vm.password) - .then(function(data) { - vm.loginStates.submitButton = "success"; - userService._retryRequestQueue(true); - if (vm.onLogin) { - vm.onLogin(); - } - }, - function(reason) { + .then(function (data) { + loginSuccess(); + }, + function (reason) { //is Two Factor required? if (reason.status === 402) { @@ -248,13 +267,13 @@ //setup a watch for both of the model values changing, if they change // while the form is invalid, then revalidate them so that the form can // be submitted again. - vm.loginForm.username.$viewChangeListeners.push(function() { + vm.loginForm.username.$viewChangeListeners.push(function () { if (vm.loginForm.$invalid) { vm.loginForm.username.$setValidity('auth', true); vm.loginForm.password.$setValidity('auth', true); } }); - vm.loginForm.password.$viewChangeListeners.push(function() { + vm.loginForm.password.$viewChangeListeners.push(function () { if (vm.loginForm.$invalid) { vm.loginForm.username.$setValidity('auth', true); vm.loginForm.password.$setValidity('auth', true); @@ -459,7 +478,7 @@ case "2fa-login": title = "Two Factor Authentication"; break; - } + } $scope.$emit("$changeTitle", title); } diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbsearch.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbsearch.directive.js index e03e63b68f..42daf1dd75 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbsearch.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbsearch.directive.js @@ -160,7 +160,8 @@ searchService.searchAll(search).then(function (result) { //result is a dictionary of group Title and it's results var filtered = {}; - _.each(result, function (value, key) { + Object.keys(result).forEach(key => { + let value = result[key]; if (value.results.length > 0) { filtered[key] = value; } diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbtour.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbtour.directive.js index 514e26f38f..b34a9eb8fd 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbtour.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbtour.directive.js @@ -11,7 +11,7 @@ You can easily add you own tours to the Help-drawer or show and start tours from anywhere in the Umbraco backoffice. To see a real world example of a custom tour implementation, install The Starter Kit in Umbraco 7.8

Extending the help drawer with custom tours

-The easiet way to add new tours to Umbraco is through the Help-drawer. All it requires is a my-tour.json file. +The easiet way to add new tours to Umbraco is through the Help-drawer. All it requires is a my-tour.json file. Place the file in App_Plugins/{MyPackage}/backoffice/tours/{my-tour}.json and it will automatically be picked up by Umbraco and shown in the Help-drawer. @@ -277,7 +277,6 @@ In the following example you see how to run some custom logic before a step goes } startStep(); - // tour completed - final step } else { // tour completed - final step scope.loadingStep = true; diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/buttons/umbbutton.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/buttons/umbbutton.directive.js index dc012945dd..dd6fa5c5c8 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/buttons/umbbutton.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/buttons/umbbutton.directive.js @@ -55,21 +55,21 @@ Use this directive to render an umbraco button. The directive can be used to gen @param {callback} action The button action which should be performed when the button is clicked. -@param {string=} href Url/Path to navigato to. -@param {string=} type Set the button type ("button" or "submit"). +@param {string=} href Url/Path to navigato to. (requires "type" to be set to "link") +@param {string=} type Set the button type ("button", "link", "submit"). @param {string=} buttonStyle Set the style of the button. The directive uses the default bootstrap styles ("primary", "info", "success", "warning", "danger", "inverse", "link", "block"). Pass in array to add multple styles [success,block]. @param {string=} state Set a progress state on the button ("init", "busy", "success", "error"). @param {string=} shortcut Set a keyboard shortcut for the button ("ctrl+c"). @param {string=} label Set the button label. -@param {string=} labelKey Set a localization key to make a multi lingual button ("general_buttonText"). +@param {string=} labelKey Set a localization key to make a multi-lingual button ("general_buttonText"). @param {string=} icon Set a button icon. -@param {string=} size Set a button icon ("xs", "m", "l", "xl"). +@param {string=} size Set a button size ("xs", "m", "l", "xl"). @param {boolean=} disabled Set to true to disable the button. -@param {string=} addEllipsis Adds an ellipsis character (…) to the button label which means the button will open a dialog or prompt the user for more information. -@param {string=} showCaret Shows a caret on the right side of the button label -@param {string=} autoFocus add autoFocus to the button -@param {string=} hasPopup Used to expose to the accessibility API whether the button will trigger a popup or not -@param {string=]} isExpanded Used to add an aria-expanded attribute and expose whether the button has expanded a popup or not +@param {boolean=} addEllipsis Adds an ellipsis character (…) to the button label which means the button will open a dialog or prompt the user for more information. +@param {boolean=} showCaret Shows a caret on the right side of the button label +@param {boolean=} autoFocus add autoFocus to the button +@param {boolean=} hasPopup Used to expose to the accessibility API whether the button will trigger a popup or not +@param {boolean=} isExpanded Used to add an aria-expanded attribute and expose whether the button controls collapsible content **/ @@ -86,6 +86,7 @@ Use this directive to render an umbraco button. The directive can be used to gen bindings: { action: "&?", href: "@?", + hrefTarget: "@?", type: "@", buttonStyle: "@?", state: "Added in Umbraco version 8.7.0 Use this directive to render an umbraco ellipsis. + +

Markup example

+
+    
+ + + + +
+
+ +@param {callback} action Callback when the value of the checkbox changes through interaction. +@param {string} text Set the text for the checkbox label. +@param {string=} labelKey Set a dictionary/localization string for the checkbox label +@param {string=} cssClass Set a css class modifier +@param {string=} color Set a hex code e.g. #f5c1bc. #000000 by default +@param {boolean=} showText Set to true to show the text. false by default +@param {string=} element Highlights a DOM-element (HTML selector) e.g. "my-div-name" +@param {string=} state Set the initial state of the component. To have it hidden use hidden +@param {string=} mode Set the mode, which decides how to style the component. "small" and "tab" are currently supported +**/ + +(function () { + 'use strict'; + + function UmbButtonEllipsis($timeout, localizationService) { + + var vm = this; + + vm.$onInit = onInit; + vm.clickButton = clickButton; + + function onInit() { + setText(); + setColor(); + } + + function clickButton(event) { + if(vm.action) { + vm.action({$event: event}); + } + } + + function setText() { + if (vm.labelKey) { + localizationService.localize(vm.labelKey).then(function (data) { + // If a labelKey is passed let's update the returned text if it's does not contain an opening square bracket [ + if(data.indexOf('[') === -1){ + vm.text = data; + } + }); + } + } + + function setColor() { + vm.color = vm.color ? vm.color : '#000000'; + } + } + + var component = { + templateUrl: 'views/components/buttons/umb-button-ellipsis.html', + controller: UmbButtonEllipsis, + controllerAs: 'vm', + transclude: true, + bindings: { + text: "@", + labelKey: "@?", + action: "&", + cssClass: "@?", + color: "@?", + showText: " 1) { - // nodes with variants - scope.currentUrls = _.filter(scope.node.urls, (url) => (scope.currentVariant.language && scope.currentVariant.language.culture === url.culture)); - } else { - // invariant nodes - scope.currentUrls = scope.node.urls; - } + // when there is no selected language (allow vary by culture == false), show all urls of the node. + scope.currentUrls = _.filter(scope.node.urls, (url) => (scope.currentVariant.language == null || scope.currentVariant.language.culture === url.culture)); - // figure out if multiple cultures apply across the content urls + // figure out if multiple cultures apply across the content URLs scope.currentUrlsHaveMultipleCultures = _.keys(_.groupBy(scope.currentUrls, url => url.culture)).length > 1; } diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbvariantcontent.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbvariantcontent.directive.js index 2d3a8e2238..55e66c5706 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbvariantcontent.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbvariantcontent.directive.js @@ -23,8 +23,8 @@ controllerAs: 'vm', controller: umbVariantContentController }; - - function umbVariantContentController($scope) { + + function umbVariantContentController($scope, contentAppHelper) { var unsubscribe = []; @@ -41,20 +41,19 @@ vm.showBackButton = showBackButton; function onInit() { - + // Make copy of apps, so we can have a variant specific model for the App. (needed for validation etc.) vm.editor.variantApps = Utilities.copy(vm.content.apps); var activeApp = vm.content.apps.find((app) => app.active); onAppChanged(activeApp); - } - + function showBackButton() { return vm.page.listViewPath !== null && vm.showBack; } - + /** Called when the component has linked all elements, this is when the form controller is available */ function postLink() { //set the content to dirty if the header changes @@ -65,7 +64,7 @@ } })); } - + function onDestroy() { for (var i = 0; i < unsubscribe.length; i++) { unsubscribe[i](); @@ -88,12 +87,12 @@ */ function selectApp(item) { // call the callback if any is registered - if(vm.onSelectApp) { - vm.onSelectApp({"app": item}); + if (vm.onSelectApp) { + vm.onSelectApp({ "app": item }); } } - - $scope.$on("editors.apps.appChanged", function($event, $args) { + + $scope.$on("editors.apps.appChanged", function ($event, $args) { var activeApp = $args.app; // sync varaintApps active with new active. @@ -104,11 +103,14 @@ onAppChanged(activeApp); }); + $scope.$on("listView.itemsChanged", function ($event, $args) { + vm.disableActionsMenu = $args.items.length > 0; + }); + function onAppChanged(activeApp) { // disable the name field if the active content app is not "Content" or "Info" - vm.nameDisabled = (activeApp && activeApp.alias !== "umbContent" && activeApp.alias !== "umbInfo" && activeApp.alias !== "umbListView"); - + vm.nameDisabled = (activeApp && !contentAppHelper.isContentBasedApp(activeApp)); } /** @@ -117,8 +119,8 @@ */ function selectAppAnchor(item, anchor) { // call the callback if any is registered - if(vm.onSelectAppAnchor) { - vm.onSelectAppAnchor({"app": item, "anchor": anchor}); + if (vm.onSelectAppAnchor) { + vm.onSelectAppAnchor({ "app": item, "anchor": anchor }); } } diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbvariantcontenteditors.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbvariantcontenteditors.directive.js index a188a83d83..3e227bfcb3 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbvariantcontenteditors.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbvariantcontenteditors.directive.js @@ -20,7 +20,7 @@ controller: umbVariantContentEditorsController }; - function umbVariantContentEditorsController($scope, $location, contentEditingHelper) { + function umbVariantContentEditorsController($scope, $location, eventsService) { var prevContentDateUpdated = null; @@ -36,6 +36,9 @@ vm.selectVariant = selectVariant; vm.selectApp = selectApp; vm.selectAppAnchor = selectAppAnchor; + vm.requestSplitView = requestSplitView; + + vm.getScope = getScope;// used by property editors to get a scope that is the root of split view, content apps etc. //Used to track how many content views there are (for split view there will be 2, it could support more in theory) vm.editors = []; @@ -66,7 +69,7 @@ /** Allows us to deep watch whatever we want - executes on every digest cycle */ function doCheck() { - if (!angular.equals(vm.content.updateDate, prevContentDateUpdated)) { + if (!Utilities.equals(vm.content.updateDate, prevContentDateUpdated)) { setActiveVariant(); prevContentDateUpdated = Utilities.copy(vm.content.updateDate); } @@ -84,11 +87,12 @@ function setActiveVariant() { // set the active variant var activeVariant = null; - _.each(vm.content.variants, function (v) { + vm.content.variants.forEach(v => { if ((vm.culture === "invariant" || v.language && v.language.culture === vm.culture) && v.segment === vm.segment) { activeVariant = v; } }); + if (!activeVariant) { // Set the first variant to active if we can't find it. // If the content item is invariant, then only one item exists in the array. @@ -101,13 +105,16 @@ //now re-sync any other editor content (i.e. if split view is open) for (var s = 1; s < vm.editors.length; s++) { //get the variant from the scope model - var variant = _.find(vm.content.variants, function (v) { - return (!v.language || v.language.culture === vm.editors[s].content.language.culture) && v.segment === vm.editors[s].content.segment; - }); + var variant = vm.content.variants.find(v => + (!v.language || v.language.culture === vm.editors[s].content.language.culture) && v.segment === vm.editors[s].content.segment); + vm.editors[s].content = variant; } } - + + if (vm.content.variants.length > 1) { + eventsService.emit('editors.content.cultureChanged', activeVariant.language); + } } /** @@ -158,32 +165,29 @@ */ function openSplitView(selectedVariant) { // enforce content contentApp in splitview. - var contentApp = vm.content.apps.find((app) => app.alias === "umbContent"); + var contentApp = vm.content.apps.find(app => app.alias === "umbContent"); if(contentApp) { selectApp(contentApp); } insertVariantEditor(vm.editors.length, selectedVariant); - splitViewChanged(); - + splitViewChanged(); } - - $scope.$on("editors.content.splitViewRequest", function(event, args) {requestSplitView(args);}); - vm.requestSplitView = requestSplitView; + function requestSplitView(args) { var culture = args.culture; var segment = args.segment; - var variant = _.find(vm.content.variants, function (v) { - return (!v.language || v.language.culture === culture) && v.segment === segment; - }); + var variant = vm.content.variants.find(v => + (!v.language || v.language.culture === culture) && v.segment === segment); if (variant != null) { openSplitView(variant); } } + var unbindSplitViewRequest = eventsService.on("editors.content.splitViewRequest", (_, args) => requestSplitView(args)); /** Closes the split view */ function closeSplitView(editorIndex) { // TODO: hacking animation states - these should hopefully be easier to do when we upgrade angular @@ -192,9 +196,11 @@ editor.content.active = false; //update the current culture to reflect the last open variant (closing the split view corresponds to selecting the other variant) - - $location.search({"cculture": vm.editors[0].content.language ? vm.editors[0].content.language.culture : null, "csegment": vm.editors[0].content.segment}); + const culture = vm.editors[0].content.language ? vm.editors[0].content.language.culture : null; + + $location.search({"cculture": culture, "csegment": vm.editors[0].content.segment}); splitViewChanged(); + unbindSplitViewRequest(); } /** @@ -220,11 +226,9 @@ $location.search("cculture", variantCulture).search("csegment", variantSegment); } else { - //update the editors collection - insertVariantEditor(editorIndex, variant); - - } + insertVariantEditor(editorIndex, variant); + } } /** @@ -242,6 +246,9 @@ vm.onSelectAppAnchor({"app": app, "anchor": anchor}); } } + function getScope() { + return $scope; + } } diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbbreadcrumbs.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbbreadcrumbs.directive.js index f80b3ceb3e..1be2bb9739 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbbreadcrumbs.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbbreadcrumbs.directive.js @@ -13,7 +13,8 @@ Use this directive to generate a list of breadcrumbs. + entity-type="content" + on-open="clickBreadcrumb(ancestor)"> @@ -32,6 +33,9 @@ Use this directive to generate a list of breadcrumbs. vm.ancestors = ancestors; }); + $scope.clickBreadcrumb = function(ancestor) { + // manipulate breadcrumb display + } } angular.module("umbraco").controller("My.Controller", Controller); @@ -40,7 +44,7 @@ Use this directive to generate a list of breadcrumbs. @param {array} ancestors Array of ancestors @param {string} entityType The content entity type (member, media, content). -@param {callback} Callback when an ancestor is clicked. It will override the default link behaviour. +@param {callback=} onOpen Function callback when an ancestor is clicked. This will override the default link behaviour. **/ (function () { diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorcontentheader.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorcontentheader.directive.js index 9b8f92d7c3..846d5c85fe 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorcontentheader.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorcontentheader.directive.js @@ -2,10 +2,10 @@ 'use strict'; function EditorContentHeader(serverValidationManager, localizationService, editorState) { - function link(scope) { + function link(scope) { var unsubscribe = []; - + if (!scope.serverValidationNameField) { scope.serverValidationNameField = "Name"; } @@ -46,55 +46,55 @@ scope.vm.variantsWithError = []; scope.vm.defaultVariant = null; scope.vm.errorsOnOtherVariants = false;// indicating wether to show that other variants, than the current, have errors. - + function updateVaraintErrors() { - scope.content.variants.forEach( function (variant) { + scope.content.variants.forEach(function (variant) { variant.hasError = scope.variantHasError(variant); - + }); checkErrorsOnOtherVariants(); } function checkErrorsOnOtherVariants() { var check = false; - scope.content.variants.forEach( function (variant) { + scope.content.variants.forEach(function (variant) { if (variant.active !== true && variant.hasError) { check = true; } }); scope.vm.errorsOnOtherVariants = check; } - + function onVariantValidation(valid, errors, allErrors, culture, segment) { // only want to react to property errors: - if(errors.findIndex(error => {return error.propertyAlias !== null;}) === -1) { + if (errors.findIndex(error => { return error.propertyAlias !== null; }) === -1) { // we dont have any errors for properties, meaning we will back out. return; } // If error coming back is invariant, we will assign the error to the default variant by picking the defaultVariant language. - if(culture === "invariant") { + if (culture === "invariant" && scope.vm.defaultVariant) { culture = scope.vm.defaultVariant.language.culture; } var index = scope.vm.variantsWithError.findIndex((item) => item.culture === culture && item.segment === segment) - if(valid === true) { + if (valid === true) { if (index !== -1) { scope.vm.variantsWithError.splice(index, 1); } } else { if (index === -1) { - scope.vm.variantsWithError.push({"culture": culture, "segment": segment}); + scope.vm.variantsWithError.push({ "culture": culture, "segment": segment }); } } scope.$evalAsync(updateVaraintErrors); } - + function onInit() { - + // find default + check if we have variants. - scope.content.variants.forEach( function (variant) { + scope.content.variants.forEach(function (variant) { if (variant.language !== null && variant.language.isDefault) { scope.vm.defaultVariant = variant; } @@ -113,41 +113,41 @@ scope.vm.variantMenu = []; if (scope.vm.hasCulture) { - scope.content.variants.forEach( (v) => { + scope.content.variants.forEach((v) => { if (v.language !== null && v.segment === null) { var variantMenuEntry = { key: String.CreateGuid(), open: v.language && v.language.culture === scope.editor.culture, variant: v, - subVariants: scope.content.variants.filter( (subVariant) => subVariant.language.culture === v.language.culture && subVariant.segment !== null) + subVariants: scope.content.variants.filter((subVariant) => subVariant.language.culture === v.language.culture && subVariant.segment !== null) }; scope.vm.variantMenu.push(variantMenuEntry); } }); } else { - scope.content.variants.forEach( (v) => { + scope.content.variants.forEach((v) => { scope.vm.variantMenu.push({ key: String.CreateGuid(), variant: v }); }); } - - scope.editor.variantApps.forEach( (app) => { + + scope.editor.variantApps.forEach((app) => { if (app.alias === "umbContent") { app.anchors = scope.editor.content.tabs; } }); - scope.content.variants.forEach( function (variant) { - + scope.content.variants.forEach(function (variant) { + // if we are looking for the variant with default language then we also want to check for invariant variant. - if (variant.language && variant.language.culture === scope.vm.defaultVariant.language.culture && variant.segment === null) { + if (variant.language && scope.vm.defaultVariant && variant.language.culture === scope.vm.defaultVariant.language.culture && variant.segment === null) { unsubscribe.push(serverValidationManager.subscribe(null, "invariant", null, onVariantValidation, null)); } unsubscribe.push(serverValidationManager.subscribe(null, variant.language !== null ? variant.language.culture : null, null, onVariantValidation, variant.segment)); }); - + } scope.goBack = function () { @@ -164,15 +164,15 @@ } }; - scope.selectNavigationItem = function(item) { - if(scope.onSelectNavigationItem) { - scope.onSelectNavigationItem({"item": item}); + scope.selectNavigationItem = function (item) { + if (scope.onSelectNavigationItem) { + scope.onSelectNavigationItem({ "item": item }); } } - scope.selectAnchorItem = function(item, anchor) { - if(scope.onSelectAnchorItem) { - scope.onSelectAnchorItem({"item": item, "anchor": anchor}); + scope.selectAnchorItem = function (item, anchor) { + if (scope.onSelectAnchorItem) { + scope.onSelectAnchorItem({ "item": item, "anchor": anchor }); } } @@ -188,20 +188,20 @@ scope.onOpenInSplitView({ "variant": variant }); } }; - + /** * Check whether a variant has a error, used to display errors in variant switcher. * @param {any} culture */ - scope.variantHasError = function(variant) { - if(scope.vm.variantsWithError.find((item) => (!variant.language || item.culture === variant.language.culture) && item.segment === variant.segment) !== undefined) { + scope.variantHasError = function (variant) { + if (scope.vm.variantsWithError.find((item) => (!variant.language || item.culture === variant.language.culture) && item.segment === variant.segment) !== undefined) { return true; } return false; } onInit(); - + scope.$on('$destroy', function () { for (var u in unsubscribe) { unsubscribe[u](); @@ -220,6 +220,7 @@ nameDisabled: " @param {string} name The content name. +@param {boolean=} nameRequired Require name to be defined. (True by default) @param {array=} tabs Array of tabs. See example above. @param {array=} navigation Array of sub views. See example above. @param {boolean=} nameLocked Set to true to lock the name. @@ -198,8 +199,9 @@ Use this directive to construct a header inside the main editor window. @param {boolean=} aliasLocked Set to true to lock the alias. @param {boolean=} hideAlias Set to true to hide alias. @param {string=} description Add a description to the content. +@param {boolean=} descriptionLocked Set to true to lock the description. @param {boolean=} hideDescription Set to true to hide description. -@param {boolean=} setpagetitle If true the page title will be set to reflect the type of data the header is working with +@param {boolean=} setpagetitle If true the page title will be set to reflect the type of data the header is working with @param {string=} editorfor The localization to use to aid accessibility on the edit and create screen **/ @@ -207,7 +209,7 @@ Use this directive to construct a header inside the main editor window. 'use strict'; function EditorHeaderDirective(editorService, localizationService, editorState, $rootScope) { - + function link(scope, $injector) { scope.vm = {}; @@ -227,6 +229,9 @@ Use this directive to construct a header inside the main editor window. // to make it work for language edit/create setAccessibilityForEditorState(); scope.loading = false; + } else if (scope.name) { + setAccessibilityForName(); + scope.loading = false; } else { scope.loading = false; } @@ -265,6 +270,15 @@ Use this directive to construct a header inside the main editor window. editorService.iconPicker(iconPicker); }; + function setAccessibilityForName() { + var setTitle = false; + if (scope.setpagetitle !== undefined) { + setTitle = scope.setpagetitle; + } + if (setTitle) { + setAccessibilityHeaderDirective(false, scope.editorfor, scope.nameLocked, scope.name, "", true); + } + } function setAccessibilityForEditorState() { var isNew = editorState.current.id === 0 || editorState.current.id === "0" || @@ -329,11 +343,11 @@ Use this directive to construct a header inside the main editor window. } scope.accessibility.a11yMessageVisible = !isEmptyOrSpaces(scope.accessibility.a11yMessage); scope.accessibility.a11yNameVisible = !isEmptyOrSpaces(scope.accessibility.a11yName); - + }); } - + function isEmptyOrSpaces(str) { return str === null || str===undefined || str.trim ===''; @@ -343,12 +357,15 @@ Use this directive to construct a header inside the main editor window. scope.$emit("$changeTitle", title); } - $rootScope.$on('$setAccessibleHeader', function (event, isNew, editorFor, nameLocked, name, contentTypeName, setTitle) { + var unbindEventHandler = $rootScope.$on('$setAccessibleHeader', function (event, isNew, editorFor, nameLocked, name, contentTypeName, setTitle) { setAccessibilityHeaderDirective(isNew, editorFor, nameLocked, name, contentTypeName, setTitle); }); + scope.$on('$destroy', function () { + unbindEventHandler(); + }); } - + var directive = { transclude: true, @@ -358,6 +375,7 @@ Use this directive to construct a header inside the main editor window. scope: { name: "=", nameLocked: "=", + nameRequired: "=?", menu: "=", hideActionsMenu: " 1 && !vm.expanded; vm.onOpen({item:vm.item}); }; diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditors.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditors.directive.js index 672b3fa286..20fba6eb6e 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditors.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditors.directive.js @@ -11,13 +11,13 @@ var sectionId = '#leftcolumn'; var isLeftColumnAbove = false; scope.editors = []; - + function addEditor(editor) { editor.inFront = true; editor.moveRight = true; editor.level = 0; editor.styleIndex = 0; - + // push the new editor to the dom scope.editors.push(editor); @@ -32,20 +32,20 @@ $timeout(() => { editor.moveRight = false; }) - + editor.animating = true; setTimeout(revealEditorContent.bind(this, editor), 400); - + updateEditors(); } - + function removeEditor(editor) { editor.moveRight = true; - + editor.animating = true; setTimeout(removeEditorFromDOM.bind(this, editor), 400); - + updateEditors(-1); if(scope.editors.length === 1){ @@ -56,17 +56,17 @@ isLeftColumnAbove = false; } } - + function revealEditorContent(editor) { - + editor.animating = false; - + scope.$digest(); - + } - + function removeEditorFromDOM(editor) { - + // push the new editor to the dom var index = scope.editors.indexOf(editor); if (index !== -1) { @@ -74,42 +74,42 @@ } updateEditors(); - + scope.$digest(); - + } - + /** update layer positions. With ability to offset positions, needed for when an item is moving out, then we dont want it to influence positions */ function updateEditors(offset) { - + offset = offset || 0;// fallback value. - + var len = scope.editors.length; var calcLen = len + offset; var ceiling = Math.min(calcLen, allowedNumberOfVisibleEditors); - var origin = Math.max(calcLen-1, 0)-ceiling; + var origin = Math.max(calcLen - 1, 0) - ceiling; var i = 0; - while(i= ceiling; i++; } } - + evts.push(eventsService.on("appState.editors.open", function (name, args) { addEditor(args.editor); })); evts.push(eventsService.on("appState.editors.close", function (name, args) { // remove the closed editor - if(args && args.editor) { + if (args && args.editor) { removeEditor(args.editor); } // close all editors - if(args && !args.editor && args.editors.length === 0) { + if (args && !args.editor && args.editors.length === 0) { scope.editors = []; } })); @@ -134,6 +134,74 @@ } + // This directive allows for us to run a custom $compile for the view within the repeater which allows + // us to maintain a $scope hierarchy with the rendered view based on the $scope that initiated the + // infinite editing. The retain the $scope hiearchy a special $parentScope property is passed in to the model. + function EditorRepeaterDirective($http, $templateCache, $compile, angularHelper) { + function link(scope, el, attr, ctrl) { + + var editor = scope && scope.$parent ? scope.$parent.model : null; + if (!editor) { + return; + } + + var unsubscribe = []; + + //if a custom parent scope is defined then we need to manually compile the view + if (editor.$parentScope) { + var element = el.find(".scoped-view"); + $http.get(editor.view, { cache: $templateCache }) + .then(function (response) { + var templateScope = editor.$parentScope.$new(); + + unsubscribe.push(function () { + templateScope.$destroy(); + }); + + // NOTE: the 'model' name here directly affects the naming convention used in infinite editors, this why you access the model + // like $scope.model.If this is changed, everything breaks.This is because we are entirely reliant upon ng-include and inheriting $scopes. + // by default without a $parentScope used for infinite editing the 'model' propety will be set because the view creates the scopes in + // ng-repeat by ng-repeat="model in editors" + templateScope.model = editor; + + element.show(); + + // if a parentForm is supplied then we can link them but to do that we need to inject a top level form + if (editor.$parentForm) { + element.html("" + response.data + ""); + } + + $compile(element)(templateScope); + + // if a parentForm is supplied then we can link them + if (editor.$parentForm) { + editor.$parentForm.$addControl(templateScope.infiniteEditorForm); + } + }); + } + + scope.$on('$destroy', function () { + for (var i = 0; i < unsubscribe.length; i++) { + unsubscribe[i](); + } + }); + } + + var directive = { + restrict: 'E', + replace: true, + transclude: true, + scope: { + editors: "=" + }, + template: "
", + link: link + }; + + return directive; + } + angular.module('umbraco.directives').directive('umbEditors', EditorsDirective); + angular.module('umbraco.directives').directive('umbEditorRepeater', EditorRepeaterDirective); })(); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/events/onOutsideClick.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/events/onOutsideClick.directive.js index a657fcbc55..07c8dc88fe 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/events/onOutsideClick.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/events/onOutsideClick.directive.js @@ -44,7 +44,7 @@ } // please to not use angularHelper.safeApply here, it won't work - scope.$apply(attrs.onOutsideClick); + scope.$evalAsync(attrs.onOutsideClick); } diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/checklistmodel.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/checklistmodel.directive.js index d944989bab..491dff3a41 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/checklistmodel.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/checklistmodel.directive.js @@ -11,7 +11,7 @@ angular.module('umbraco.directives') function contains(arr, item) { if (Utilities.isArray(arr)) { for (var i = 0; i < arr.length; i++) { - if (angular.equals(arr[i], item)) { + if (Utilities.equals(arr[i], item)) { return true; } } @@ -19,23 +19,23 @@ angular.module('umbraco.directives') return false; } - // add + // add function add(arr, item) { arr = Utilities.isArray(arr) ? arr : []; for (var i = 0; i < arr.length; i++) { - if (angular.equals(arr[i], item)) { + if (Utilities.equals(arr[i], item)) { return arr; } - } + } arr.push(item); return arr; - } + } // remove function remove(arr, item) { if (Utilities.isArray(arr)) { for (var i = 0; i < arr.length; i++) { - if (angular.equals(arr[i], item)) { + if (Utilities.equals(arr[i], item)) { arr.splice(i, 1); break; } diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/focuswhen.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/focuswhen.directive.js index d8dbcc1012..dda5c51175 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/focuswhen.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/focuswhen.directive.js @@ -2,13 +2,20 @@ angular.module("umbraco.directives").directive('focusWhen', function ($timeout) return { restrict: 'A', link: function (scope, elm, attrs, ctrl) { + + var delayTimer; + attrs.$observe("focusWhen", function (newValue) { - if (newValue === "true") { - $timeout(function () { - elm.trigger("focus"); - }); + if (newValue === "true" && document.activeelement !== elm[0]) { + delayTimer = $timeout(function () { + elm[0].focus(); + }); } }); + + scope.$on('$destroy', function() { + $timeout.cancel(delayTimer); + }); } }; }); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbautofocus.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbautofocus.directive.js index eb3503f799..98c4ef691e 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbautofocus.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbautofocus.directive.js @@ -1,18 +1,23 @@ angular.module("umbraco.directives") .directive('umbAutoFocus', function($timeout) { - return function(scope, element, attr){ + return function (scope, element, attrs) { + var update = function() { //if it uses its default naming - if(element.val() === "" || attr.focusOnFilled){ + if (element.val() === "" || attrs.focusOnFilled) { element.trigger("focus"); } }; - if (attr.umbAutoFocus !== "false") { - $timeout(function() { - update(); - }); - } - }; + attrs.$observe("umbAutoFocus", function (newVal) { + var enabled = (newVal === "false" || newVal === 0 || newVal === false) ? false : true; + if (enabled) { + $timeout(function() { + update(); + }); + } + }); + + }; }); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbcheckbox.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbcheckbox.directive.js index 389aec2044..717cefbb0a 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbcheckbox.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbcheckbox.directive.js @@ -31,8 +31,10 @@ @param {boolean} disabled Set the checkbox to be disabled. @param {boolean} required Set the checkbox to be required. @param {callback} onChange Callback when the value of the checkbox change by interaction. -@param {string} cssClass Set a css class modifier -@param {boolean} disableDirtyCheck Disable checking if the model is dirty +@param {string} cssClass Set a css class modifier. +@deprecated @param {string} iconClass Set an icon next to checkbox. Use "icon" parameter instead. +@param {string} icon Set an icon next to checkbox. +@param {boolean} disableDirtyCheck Disable checking if the model is dirty. **/ @@ -49,6 +51,8 @@ function onInit() { vm.inputId = vm.inputId || "umb-check_" + String.CreateGuid(); + vm.icon = vm.icon || vm.iconClass || null; + // If a labelKey is passed let's update the returned text if it's does not contain an opening square bracket [ if (vm.labelKey) { localizationService.localize(vm.labelKey).then(function (data) { @@ -85,7 +89,8 @@ required: "<", onChange: "&?", cssClass: "@?", - iconClass: "@?", + iconClass: "@?", // deprecated + icon: "@?", disableDirtyCheck: "=?" } }; diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbfocuslock.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbfocuslock.directive.js new file mode 100644 index 0000000000..569f49b88a --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbfocuslock.directive.js @@ -0,0 +1,82 @@ +(function() { + 'use strict'; + + function FocusLock($timeout) { + + function getAutoFocusElement (elements) { + var elmentWithAutoFocus = null; + + elements.forEach((element) => { + if(element.getAttribute('umb-auto-focus') === 'true') { + elmentWithAutoFocus = element; + } + }); + + return elmentWithAutoFocus; + } + + function link(scope, element) { + + function onInit() { + // List of elements that can be focusable within the focus lock + var focusableElementsSelector = 'a[href]:not([disabled]), button:not([disabled]), textarea:not([disabled]), input:not([disabled]), select:not([disabled])'; + var bodyElement = document.querySelector('body'); + + $timeout(function() { + var target = element[0]; + + var focusableElements = target.querySelectorAll(focusableElementsSelector); + var defaultFocusedElement = getAutoFocusElement(focusableElements); + var firstFocusableElement = focusableElements[0]; + var lastFocusableElement = focusableElements[focusableElements.length -1]; + + // We need to add the tabbing-active class in order to highlight the focused button since the default style is + // outline: none; set in the stylesheet specifically + bodyElement.classList.add('tabbing-active'); + + // If there is no default focused element put focus on the first focusable element in the nodelist + if(defaultFocusedElement === null ){ + firstFocusableElement.focus(); + } + + target.addEventListener('keydown', function(event){ + var isTabPressed = (event.key === 'Tab' || event.keyCode === 9); + + if (!isTabPressed){ + return; + } + + // If shift + tab key + if(event.shiftKey){ + // Set focus on the last focusable element if shift+tab are pressed meaning we go backwards + if(document.activeElement === firstFocusableElement){ + lastFocusableElement.focus(); + event.preventDefault(); + } + } + // Else only the tab key is pressed + else{ + // Using only the tab key we set focus on the first focusable element mening we go forward + if (document.activeElement === lastFocusableElement) { + firstFocusableElement.focus(); + event.preventDefault(); + } + } + }); + }, 250); + } + + onInit(); + } + + var directive = { + restrict: 'A', + link: link + }; + + return directive; + } + + angular.module('umbraco.directives').directive('umbFocusLock', FocusLock); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbradiobutton.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbradiobutton.directive.js index d79140f947..8c7157c414 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbradiobutton.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbradiobutton.directive.js @@ -26,17 +26,21 @@ @param {string} value Set the value of the radiobutton. @param {string} name Set the name of the radiobutton. @param {string} text Set the text for the radiobutton label. -@param {string} labelKey Set a dictinary/localization string for the checkbox label +@param {string} labelKey Set a dictinary/localization string for the checkbox label. +@param {string} serverValidationField Set the val-server-field of the radiobutton. @param {boolean} disabled Set the radiobutton to be disabled. @param {boolean} required Set the radiobutton to be required. @param {callback} onChange Callback when the value of the radiobutton change by interaction. +@param {string} cssClass Set a css class modifier. +@param {string} iconClass Set an icon next to radiobutton. +@param {boolean} disableDirtyCheck Disable checking if the model is dirty. **/ (function () { 'use strict'; - function UmbRadiobuttonController($timeout) { + function UmbRadiobuttonController($timeout, localizationService) { var vm = this; @@ -46,6 +50,8 @@ function onInit() { vm.inputId = vm.inputId || "umb-radio_" + String.CreateGuid(); + vm.icon = vm.icon || vm.iconClass || null; + // If a labelKey is passed let's update the returned text if it's does not contain an opening square bracket [ if (vm.labelKey) { localizationService.localize(vm.labelKey).then(function (data) { @@ -77,9 +83,14 @@ name: "@", text: "@", labelKey: "@?", + serverValidationField: "@", disabled: "<", required: "<", - onChange: "&?" + onChange: "&?", + cssClass: "@?", + iconClass: "@?", // deprecated + icon: "@?", + disableDirtyCheck: "=?" } }; diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbsearchfilter.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbsearchfilter.directive.js new file mode 100644 index 0000000000..efbc384cb4 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbsearchfilter.directive.js @@ -0,0 +1,85 @@ +/** +@ngdoc directive +@name umbraco.directives.directive:umbSearchFilter +@restrict E +@scope + +@description +Added in Umbraco version 8.7.0 Use this directive to render an umbraco search filter. + +

Markup example

+
+    
+ + + + +
+
+ +@param {boolean} model Set to true or false to set the checkbox to checked or unchecked. +@param {string} inputId Set the id of the checkbox. +@param {string} text Set the text for the checkbox label. +@param {string} labelKey Set a dictinary/localization string for the checkbox label +@param {callback} onChange Callback when the value of the checkbox change by interaction. +@param {boolean} autoFocus Add autofocus to the input field +@param {boolean} preventSubmitOnEnter Set the enter prevent directive or not + +**/ + +(function () { + 'use strict'; + + function UmbSearchFilterController($timeout, localizationService) { + + var vm = this; + + vm.$onInit = onInit; + vm.change = change; + + function onInit() { + vm.inputId = vm.inputId || "umb-check_" + String.CreateGuid(); + + // If a labelKey is passed let's update the returned text if it's does not contain an opening square bracket [ + if (vm.labelKey) { + localizationService.localize(vm.labelKey).then(function (data) { + if(data.indexOf('[') === -1){ + vm.text = data; + } + }); + } + } + + function change() { + if (vm.onChange) { + $timeout(function () { + vm.onChange({ model: vm.model, value: vm.value }); + }, 0); + } + } + } + + var component = { + templateUrl: 'views/components/forms/umb-search-filter.html', + controller: UmbSearchFilterController, + controllerAs: 'vm', + transclude: true, + bindings: { + model: "=", + inputId: "@", + text: "@", + labelKey: "@?", + onChange: "&?", + autoFocus: "true to hide the label. +@param {string=} alias The alias of the field within the control group. +@param {string=} labelFor The alias of the field that the label is for, used for validation. +@param {boolean=} required Set to true to mark the field as required. + **/ + angular.module("umbraco.directives.html") .directive('umbControlGroup', function (localizationService) { return { diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/imaging/umbimagecrop.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/imaging/umbimagecrop.directive.js index 1625986751..f1f2cb38e8 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/imaging/umbimagecrop.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/imaging/umbimagecrop.directive.js @@ -5,38 +5,38 @@ * @function **/ angular.module("umbraco.directives") - .directive('umbImageCrop', - function ($timeout, cropperHelper) { - return { - restrict: 'E', - replace: true, - templateUrl: 'views/components/imaging/umb-image-crop.html', - scope: { - src: '=', - width: '@', - height: '@', - crop: "=", - center: "=", - maxSize: '@' - }, + .directive('umbImageCrop', + function ($timeout, cropperHelper) { + return { + restrict: 'E', + replace: true, + templateUrl: 'views/components/imaging/umb-image-crop.html', + scope: { + src: '=', + width: '@', + height: '@', + crop: "=", + center: "=", + maxSize: '@' + }, - link: function(scope, element, attrs) { + link: function (scope, element, attrs) { let sliderRef = null; - scope.width = 400; - scope.height = 320; + scope.width = 400; + scope.height = 320; - scope.dimensions = { - image: {}, - cropper:{}, - viewport:{}, - margin: 20, - scale: { - min: 0, - max: 3, - current: 1 - } + scope.dimensions = { + image: {}, + cropper: {}, + viewport: {}, + margin: 20, + scale: { + min: 0, + max: 3, + current: 1 + } }; scope.sliderOptions = { @@ -84,211 +84,232 @@ angular.module("umbraco.directives") } }; - //live rendering of viewport and image styles - scope.style = function () { - return { - 'height': (parseInt(scope.dimensions.viewport.height, 10)) + 'px', - 'width': (parseInt(scope.dimensions.viewport.width, 10)) + 'px' - }; - }; + //live rendering of viewport and image styles + scope.style = function () { + return { + 'height': (parseInt(scope.dimensions.viewport.height, 10)) + 'px', + 'width': (parseInt(scope.dimensions.viewport.width, 10)) + 'px' + }; + }; - //elements - var $viewport = element.find(".viewport"); - var $image = element.find("img"); - var $overlay = element.find(".overlay"); - var $container = element.find(".crop-container"); + //elements + var $viewport = element.find(".viewport"); + var $image = element.find("img"); + var $overlay = element.find(".overlay"); + var $container = element.find(".crop-container"); - //default constraints for drag n drop - var constraints = {left: {max: scope.dimensions.margin, min: scope.dimensions.margin}, top: {max: scope.dimensions.margin, min: scope.dimensions.margin} }; - scope.constraints = constraints; + //default constraints for drag n drop + var constraints = { left: { max: scope.dimensions.margin, min: scope.dimensions.margin }, top: { max: scope.dimensions.margin, min: scope.dimensions.margin } }; + scope.constraints = constraints; - //set constaints for cropping drag and drop - var setConstraints = function(){ - constraints.left.min = scope.dimensions.margin + scope.dimensions.cropper.width - scope.dimensions.image.width; - constraints.top.min = scope.dimensions.margin + scope.dimensions.cropper.height - scope.dimensions.image.height; - }; + //set constaints for cropping drag and drop + var setConstraints = function () { + constraints.left.min = scope.dimensions.margin + scope.dimensions.cropper.width - scope.dimensions.image.width; + constraints.top.min = scope.dimensions.margin + scope.dimensions.cropper.height - scope.dimensions.image.height; + }; - var setDimensions = function(originalImage){ - originalImage.width("auto"); - originalImage.height("auto"); + var setDimensions = function (originalImage) { + originalImage.width("auto"); + originalImage.height("auto"); - var image = {}; - image.originalWidth = originalImage.width(); - image.originalHeight = originalImage.height(); + var image = {}; + image.originalWidth = originalImage.width(); + image.originalHeight = originalImage.height(); - image.width = image.originalWidth; - image.height = image.originalHeight; - image.left = originalImage[0].offsetLeft; - image.top = originalImage[0].offsetTop; + image.width = image.originalWidth; + image.height = image.originalHeight; + image.left = originalImage[0].offsetLeft; + image.top = originalImage[0].offsetTop; - scope.dimensions.image = image; + scope.dimensions.image = image; - //unscaled editor size - //var viewPortW = $viewport.width(); - //var viewPortH = $viewport.height(); - var _viewPortW = parseInt(scope.width, 10); - var _viewPortH = parseInt(scope.height, 10); + //unscaled editor size + //var viewPortW = $viewport.width(); + //var viewPortH = $viewport.height(); + var _viewPortW = parseInt(scope.width, 10); + var _viewPortH = parseInt(scope.height, 10); - //if we set a constraint we will scale it down if needed - if(scope.maxSize){ - var ratioCalculation = cropperHelper.scaleToMaxSize( - _viewPortW, - _viewPortH, - scope.maxSize); + //if we set a constraint we will scale it down if needed + if (scope.maxSize) { + var ratioCalculation = cropperHelper.scaleToMaxSize( + _viewPortW, + _viewPortH, + scope.maxSize); - //so if we have a max size, override the thumb sizes - _viewPortW = ratioCalculation.width; - _viewPortH = ratioCalculation.height; - } + //so if we have a max size, override the thumb sizes + _viewPortW = ratioCalculation.width; + _viewPortH = ratioCalculation.height; + } - scope.dimensions.viewport.width = _viewPortW + 2 * scope.dimensions.margin; - scope.dimensions.viewport.height = _viewPortH + 2 * scope.dimensions.margin; - scope.dimensions.cropper.width = _viewPortW; // scope.dimensions.viewport.width - 2 * scope.dimensions.margin; - scope.dimensions.cropper.height = _viewPortH; // scope.dimensions.viewport.height - 2 * scope.dimensions.margin; - }; + scope.dimensions.viewport.width = _viewPortW + 2 * scope.dimensions.margin; + scope.dimensions.viewport.height = _viewPortH + 2 * scope.dimensions.margin; + scope.dimensions.cropper.width = _viewPortW; // scope.dimensions.viewport.width - 2 * scope.dimensions.margin; + scope.dimensions.cropper.height = _viewPortH; // scope.dimensions.viewport.height - 2 * scope.dimensions.margin; + }; - //resize to a given ratio - var resizeImageToScale = function(ratio){ - //do stuff - var size = cropperHelper.calculateSizeToRatio(scope.dimensions.image.originalWidth, scope.dimensions.image.originalHeight, ratio); - scope.dimensions.image.width = size.width; - scope.dimensions.image.height = size.height; + //resize to a given ratio + var resizeImageToScale = function (ratio) { + //do stuff + var size = cropperHelper.calculateSizeToRatio(scope.dimensions.image.originalWidth, scope.dimensions.image.originalHeight, ratio); + scope.dimensions.image.width = size.width; + scope.dimensions.image.height = size.height; - setConstraints(); - validatePosition(scope.dimensions.image.left, scope.dimensions.image.top); - }; + setConstraints(); + validatePosition(scope.dimensions.image.left, scope.dimensions.image.top); + }; - //resize the image to a predefined crop coordinate - var resizeImageToCrop = function(){ - scope.dimensions.image = cropperHelper.convertToStyle( - scope.crop, - {width: scope.dimensions.image.originalWidth, height: scope.dimensions.image.originalHeight}, - scope.dimensions.cropper, - scope.dimensions.margin); + //resize the image to a predefined crop coordinate + var resizeImageToCrop = function () { + scope.dimensions.image = cropperHelper.convertToStyle( + scope.crop, + { width: scope.dimensions.image.originalWidth, height: scope.dimensions.image.originalHeight }, + scope.dimensions.cropper, + scope.dimensions.margin); - var ratioCalculation = cropperHelper.calculateAspectRatioFit( - scope.dimensions.image.originalWidth, - scope.dimensions.image.originalHeight, - scope.dimensions.cropper.width, - scope.dimensions.cropper.height, - true); + var ratioCalculation = cropperHelper.calculateAspectRatioFit( + scope.dimensions.image.originalWidth, + scope.dimensions.image.originalHeight, + scope.dimensions.cropper.width, + scope.dimensions.cropper.height, + true); - scope.dimensions.scale.current = scope.dimensions.image.ratio; + scope.dimensions.scale.current = scope.dimensions.image.ratio; - // Update min and max based on original width/height - scope.dimensions.scale.min = ratioCalculation.ratio; + // Update min and max based on original width/height + scope.dimensions.scale.min = ratioCalculation.ratio; scope.dimensions.scale.max = 2; - }; + }; - var validatePosition = function(left, top){ - if(left > constraints.left.max) - { - left = constraints.left.max; - } + var validatePosition = function (left, top) { + if (left > constraints.left.max) { + left = constraints.left.max; + } - if(left <= constraints.left.min){ - left = constraints.left.min; - } + if (left <= constraints.left.min) { + left = constraints.left.min; + } - if(top > constraints.top.max) - { - top = constraints.top.max; - } - if(top <= constraints.top.min){ - top = constraints.top.min; - } + if (top > constraints.top.max) { + top = constraints.top.max; + } + if (top <= constraints.top.min) { + top = constraints.top.min; + } - if(scope.dimensions.image.left !== left){ - scope.dimensions.image.left = left; - } + if (scope.dimensions.image.left !== left) { + scope.dimensions.image.left = left; + } - if(scope.dimensions.image.top !== top){ - scope.dimensions.image.top = top; - } - }; + if (scope.dimensions.image.top !== top) { + scope.dimensions.image.top = top; + } + }; - //sets scope.crop to the recalculated % based crop - var calculateCropBox = function(){ - scope.crop = cropperHelper.pixelsToCoordinates(scope.dimensions.image, scope.dimensions.cropper.width, scope.dimensions.cropper.height, scope.dimensions.margin); - }; + //sets scope.crop to the recalculated % based crop + var calculateCropBox = function () { + scope.crop = cropperHelper.pixelsToCoordinates(scope.dimensions.image, scope.dimensions.cropper.width, scope.dimensions.cropper.height, scope.dimensions.margin); + }; - //Drag and drop positioning, using jquery ui draggable - var onStartDragPosition, top, left; - $overlay.draggable({ - drag: function(event, ui) { - scope.$apply(function(){ - validatePosition(ui.position.left, ui.position.top); - }); - }, - stop: function(event, ui){ - scope.$apply(function(){ - //make sure that every validates one more time... - validatePosition(ui.position.left, ui.position.top); + //Drag and drop positioning, using jquery ui draggable + var onStartDragPosition, top, left; + $overlay.draggable({ + drag: function (event, ui) { + scope.$apply(function () { + validatePosition(ui.position.left, ui.position.top); + }); + }, + stop: function (event, ui) { + scope.$apply(function () { + //make sure that every validates one more time... + validatePosition(ui.position.left, ui.position.top); - calculateCropBox(); - scope.dimensions.image.rnd = Math.random(); - }); - } - }); + calculateCropBox(); + scope.dimensions.image.rnd = Math.random(); + }); + } + }); - var init = function(image){ - scope.loaded = false; + var init = function (image) { + scope.loaded = false; - //set dimensions on image, viewport, cropper etc - setDimensions(image); + //set dimensions on image, viewport, cropper etc + setDimensions(image); - //create a default crop if we haven't got one already + //create a default crop if we haven't got one already var createDefaultCrop = !scope.crop; if (createDefaultCrop) { calculateCropBox(); } - resizeImageToCrop(); + resizeImageToCrop(); //if we're creating a new crop, make sure to zoom out fully if (createDefaultCrop) { scope.dimensions.scale.current = scope.dimensions.scale.min; - resizeImageToScale(scope.dimensions.scale.min); + resizeImageToScale(scope.dimensions.scale.min); + + if (scope.center) { + // Move image to focal point if set + // Repeating a few calls here, but logic is too difficult to follow elsewhere + var x1 = Math.min( + Math.max( + scope.center.left * scope.dimensions.image.width - scope.dimensions.cropper.width / 2, + 0 + ), + scope.dimensions.image.width - scope.dimensions.cropper.width + ); + var y1 = Math.min( + Math.max( + scope.center.top * scope.dimensions.image.height - scope.dimensions.cropper.height / 2, + 0 + ), + scope.dimensions.image.height - scope.dimensions.cropper.height + ); + scope.dimensions.image.left = x1; + scope.dimensions.image.top = y1; + calculateCropBox(); + resizeImageToCrop(); + } } - //sets constaints for the cropper - setConstraints(); - scope.loaded = true; - }; + //sets constaints for the cropper + setConstraints(); + scope.loaded = true; + }; - // Watchers - scope.$watchCollection('[width, height]', function(newValues, oldValues){ - // We have to reinit the whole thing if - // one of the external params changes - if(newValues !== oldValues){ - setDimensions($image); - setConstraints(); - } - }); + // Watchers + scope.$watchCollection('[width, height]', function (newValues, oldValues) { + // We have to reinit the whole thing if + // one of the external params changes + if (newValues !== oldValues) { + setDimensions($image); + setConstraints(); + } + }); - var throttledResizing = _.throttle(function(){ + var throttledResizing = _.throttle(function () { resizeImageToScale(scope.dimensions.scale.current); - calculateCropBox(); - }, 15); + calculateCropBox(); + }, 15); - // Happens when we change the scale + // Happens when we change the scale scope.$watch("dimensions.scale.current", function (newValue, oldValue) { - if (scope.loaded) { - throttledResizing(); - } - }); + if (scope.loaded) { + throttledResizing(); + } + }); - // Init - $image.on("load", function(){ - $timeout(function(){ - init($image); - }); - }); - } - }; - }); + // Init + $image.on("load", function () { + $timeout(function () { + init($image); + }); + }); + } + }; + }); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/imaging/umbimagegravity.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/imaging/umbimagegravity.directive.js index dfa58f34f8..fd9a236f87 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/imaging/umbimagegravity.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/imaging/umbimagegravity.directive.js @@ -160,7 +160,7 @@ function onChanges(changes) { if (changes.center && !changes.center.isFirstChange() && changes.center.currentValue - && !angular.equals(changes.center.currentValue, changes.center.previousValue)) { + && !Utilities.equals(changes.center.currentValue, changes.center.previousValue)) { //when center changes update the dimensions setDimensions(); } diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/media/umbmedianodeinfo.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/media/umbmedianodeinfo.directive.js index dfa1afc247..6f34cfc0a1 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/media/umbmedianodeinfo.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/media/umbmedianodeinfo.directive.js @@ -1,7 +1,7 @@ (function () { 'use strict'; - function MediaNodeInfoDirective($timeout, $location, eventsService, userService, dateHelper, editorService, mediaHelper, mediaResource, $q) { + function MediaNodeInfoDirective($timeout, $location, $q, eventsService, userService, dateHelper, editorService, mediaHelper, mediaResource) { function link(scope, element, attrs, ctrl) { @@ -37,7 +37,7 @@ }); }); - // get document type details + // get media type details scope.mediaType = scope.node.contentType; // set the media link initially diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/member/umbmembernodeinfo.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/member/umbmembernodeinfo.directive.js index 3b6a2c069a..8dd6d56139 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/member/umbmembernodeinfo.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/member/umbmembernodeinfo.directive.js @@ -11,6 +11,19 @@ scope.allowChangeMemberType = false; function onInit() { + + userService.getCurrentUser().then(function (user) { + // only allow change of member type if user has access to the settings sections + Utilities.forEach(user.sections, function (section) { + if (section.alias === "settings") { + scope.allowChangeMemberType = true; + } + }); + }); + + // get member type details + scope.memberType = scope.node.contentType; + // make sure dates are formatted to the user's locale formatDatesToLocal(); } diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/overlays/umboverlay.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/overlays/umboverlay.directive.js index ad396e7a9a..f7ba043ef1 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/overlays/umboverlay.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/overlays/umboverlay.directive.js @@ -221,6 +221,10 @@ Opens an overlay to show a custom YSOD.
$timeout(function () { + if (!scope.name) { + scope.name = 'overlay'; + } + if (scope.position === "target" && scope.model.event) { setTargetPosition(); @@ -280,7 +284,7 @@ Opens an overlay to show a custom YSOD.
templateScope.model = scope.model; element.html(response.data); element.show(); - $compile(element.contents())(templateScope); + $compile(element)(templateScope); }); } } @@ -403,11 +407,15 @@ Opens an overlay to show a custom YSOD.
function setTargetPosition() { - var container = $("#contentwrapper"); - var containerLeft = container[0].offsetLeft; - var containerRight = containerLeft + container[0].offsetWidth; - var containerTop = container[0].offsetTop; - var containerBottom = containerTop + container[0].offsetHeight; + var overlay = $(scope.model.event.target).closest('.umb-overlay'); + var container = overlay.length > 0 ? overlay : $("#contentwrapper"); + + let rect = container[0].getBoundingClientRect(); + + var containerLeft = rect.left; + var containerRight = containerLeft + rect.width; + var containerTop = rect.top; + var containerBottom = containerTop + rect.height; var mousePositionClickX = null; var mousePositionClickY = null; @@ -430,8 +438,9 @@ Opens an overlay to show a custom YSOD.
elementWidth = el[0].clientWidth; // move element to this position - position.left = mousePositionClickX - (elementWidth / 2); - position.top = mousePositionClickY - (elementHeight / 2); + // when using hotkey it fallback to center of container + position.left = mousePositionClickX ? mousePositionClickX - (elementWidth / 2) : (containerLeft + containerRight) / 2 - (elementWidth / 2); + position.top = mousePositionClickY ? mousePositionClickY - (elementHeight / 2) : (containerTop + containerBottom) / 2 - (elementHeight / 2); // check to see if element is outside screen // outside right @@ -459,11 +468,12 @@ Opens an overlay to show a custom YSOD.
} el.css(position); + el.css("visibility", "visible"); } scope.submitForm = function (model) { if (scope.model.submit) { - if (formHelper.submitForm({ scope: scope, skipValidation: scope.model.skipFormValidation })) { + if (formHelper.submitForm({ scope: scope, skipValidation: scope.model.skipFormValidation, keepServerValidation: true })) { if (scope.model.confirmSubmit && scope.model.confirmSubmit.enable && !scope.directive.enableConfirmButton) { //wrap in a when since we don't know if this is a promise or not @@ -530,6 +540,7 @@ Opens an overlay to show a custom YSOD.
view: "=", position: "@", size: "=?", + name: "=?", parentScope: "=?" }, link: link diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/property/umbproperty.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/property/umbproperty.directive.js index ad62bcd3db..2b2f36dd7d 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/property/umbproperty.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/property/umbproperty.directive.js @@ -3,46 +3,75 @@ * @name umbraco.directives.directive:umbProperty * @restrict E **/ -angular.module("umbraco.directives") - .directive('umbProperty', function (userService) { - return { - scope: { +(function () { + 'use strict'; + + angular + .module("umbraco.directives") + .component('umbProperty', { + templateUrl: 'views/components/property/umb-property.html', + controller: UmbPropertyController, + controllerAs: 'vm', + transclude: true, + require: { + parentUmbProperty: '?^^umbProperty', + parentForm: '?^^form' + }, + bindings: { property: "=", + elementKey: "@", + // optional, if set this will be used for the property alias validation path (hack required because NC changes the actual property.alias :/) + propertyAlias: "@", showInherit: "<", inheritsFrom: "<" - }, - transclude: true, - restrict: 'E', - replace: true, - templateUrl: 'views/components/property/umb-property.html', - link: function (scope) { - - scope.controlLabelTitle = null; - if(Umbraco.Sys.ServerVariables.isDebuggingEnabled) { - userService.getCurrentUser().then(function (u) { - if(u.allowedSections.indexOf("settings") !== -1 ? true : false) { - scope.controlLabelTitle = scope.property.alias; - } - }); - } - }, - //Define a controller for this directive to expose APIs to other directives - controller: function ($scope) { - - var self = this; - - //set the API properties/methods - - self.property = $scope.property; - self.setPropertyError = function (errorMsg) { - $scope.property.propertyErrorMessage = errorMsg; - }; - - $scope.propertyActions = []; - self.setPropertyActions = function(actions) { - $scope.propertyActions = actions; - }; - } + }); + + + + function UmbPropertyController($scope, userService, serverValidationManager, udiService, angularHelper) { + + const vm = this; + + vm.$onInit = onInit; + + vm.setPropertyError = function (errorMsg) { + vm.property.propertyErrorMessage = errorMsg; }; - }); + + vm.propertyActions = []; + vm.setPropertyActions = function (actions) { + vm.propertyActions = actions; + }; + + // returns the validation path for the property to be used as the validation key for server side validation logic + vm.getValidationPath = function () { + + var parentValidationPath = vm.parentUmbProperty ? vm.parentUmbProperty.getValidationPath() : null; + var propAlias = vm.propertyAlias ? vm.propertyAlias : vm.property.alias; + // the elementKey will be empty when this is not a nested property + var valPath = vm.elementKey ? vm.elementKey + "/" + propAlias : propAlias; + return serverValidationManager.createPropertyValidationKey(valPath, parentValidationPath); + } + + function onInit() { + vm.controlLabelTitle = null; + if (Umbraco.Sys.ServerVariables.isDebuggingEnabled) { + userService.getCurrentUser().then(function (u) { + if (u.allowedSections.indexOf("settings") !== -1 ? true : false) { + vm.controlLabelTitle = vm.property.alias; + } + }); + } + + if (!vm.parentUmbProperty) { + // not found, then fallback to searching the scope chain, this may be needed when DOM inheritance isn't maintained but scope + // inheritance is (i.e.infinite editing) + var found = angularHelper.traverseScopeChain($scope, s => s && s.vm && s.vm.constructor.name === "UmbPropertyController"); + vm.parentUmbProperty = found ? found.vm : null; + } + } + + } + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/views/components/property/property-actions/umbpropertyactions.component.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/property/umbpropertyactions.component.js similarity index 51% rename from src/Umbraco.Web.UI.Client/src/views/components/property/property-actions/umbpropertyactions.component.js rename to src/Umbraco.Web.UI.Client/src/common/directives/components/property/umbpropertyactions.component.js index b0dc15d6cd..41dbd9f547 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/property/property-actions/umbpropertyactions.component.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/property/umbpropertyactions.component.js @@ -5,50 +5,77 @@ * A component to render the property action toggle */ - function umbPropertyActionsController(keyboardService) { + function umbPropertyActionsController(keyboardService, localizationService) { var vm = this; vm.isOpen = false; + vm.labels = { + openText: "Open Property Actions", + closeText: "Close Property Actions" + }; + + vm.open = open; + vm.close = close; + vm.toggle = toggle; + vm.executeAction = executeAction; + + vm.$onDestroy = onDestroy; + vm.$onInit = onInit; function initDropDown() { keyboardService.bind("esc", vm.close); } + function destroyDropDown() { keyboardService.unbind("esc"); } - vm.toggle = function() { + function toggle() { if (vm.isOpen === true) { vm.close(); } else { vm.open(); } } - vm.open = function() { + + function open() { vm.isOpen = true; initDropDown(); } - vm.close = function() { + + function close() { vm.isOpen = false; destroyDropDown(); } - vm.executeAction = function(action) { + function executeAction(action) { action.method(); vm.close(); } - vm.$onDestroy = function () { + function onDestroy() { if (vm.isOpen === true) { destroyDropDown(); } } - + + function onInit() { + + var labelKeys = [ + "propertyActions_tooltipForPropertyActionsMenu", + "propertyActions_tooltipForPropertyActionsMenuClose" + ] + + localizationService.localizeMany(labelKeys).then(values => { + vm.labels.openText = values[0]; + vm.labels.closeText = values[1]; + }); + } } var umbPropertyActionsComponent = { - templateUrl: 'views/components/property/property-actions/umb-property-actions.html', + templateUrl: 'views/components/property/umb-property-actions.html', bindings: { actions: "<" }, diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtree.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtree.directive.js index 0ccaf7b05c..7868f79809 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtree.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtree.directive.js @@ -88,9 +88,8 @@ function umbTreeDirective($q, $rootScope, treeService, notificationsService, use /** Helper function to emit tree events */ function emitEvent(eventName, args) { if (registeredCallbacks[eventName] && Utilities.isArray(registeredCallbacks[eventName])) { - _.each(registeredCallbacks[eventName], function (c) { - c(args);//call it - }); + // call it + registeredCallbacks[eventName].forEach(c => c(args)); } } @@ -342,26 +341,17 @@ function umbTreeDirective($q, $rootScope, treeService, notificationsService, use var css = []; if (node.cssClasses) { - _.each(node.cssClasses, function (c) { - css.push(c); - }); + node.cssClasses.forEach(c => css.push(c)); } return css.join(" "); }; - $scope.selectEnabledNodeClass = function (node) { - return node ? - node.selected ? - 'icon umb-tree-icon sprTree icon-check green temporary' : - '' : - ''; - }; + $scope.selectEnabledNodeClass = node => + node && node.selected ? 'icon sprTree icon-check green temporary' : '-hidden'; /* helper to force reloading children of a tree node */ - $scope.loadChildren = function (node, forceReload) { - return loadChildren(node, forceReload); - }; + $scope.loadChildren = (node, forceReload) => loadChildren(node, forceReload); /** Method called when the options button next to the root node is called. @@ -419,8 +409,8 @@ function umbTreeDirective($q, $rootScope, treeService, notificationsService, use //load the tree loadTree().then(function () { //because angular doesn't return a promise for the resolve method, we need to resort to some hackery, else - //like normal JS promises we could do resolve(...).then() - if (args && args.onLoaded && angular.isFunction(args.onLoaded)) { + //like normal JS promises we could do resolve(...).then() + if (args && args.onLoaded && Utilities.isFunction(args.onLoaded)) { args.onLoaded(); } }); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreeitem.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreeitem.directive.js index 8767de446a..3ec1756e6c 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreeitem.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreeitem.directive.js @@ -70,9 +70,7 @@ angular.module("umbraco.directives") var css = []; if (node.cssClasses) { - _.each(node.cssClasses, function(c) { - css.push(c); - }); + node.cssClasses.forEach(c => css.push(c)); } if (node.selected) { css.push("umb-tree-node-checked"); @@ -192,7 +190,7 @@ angular.module("umbraco.directives") var evts = []; - //listen for section changes + // Listen for section changes evts.push(eventsService.on("appState.sectionState.changed", function(e, args) { if (args.key === "currentSection") { //when the section changes disable all delete animations @@ -200,6 +198,13 @@ angular.module("umbraco.directives") } })); + // Update tree icon if changed + evts.push(eventsService.on("editors.tree.icon.changed", function (e, args) { + if (args.icon !== scope.node.icon && args.id === scope.node.id) { + scope.node.icon = args.icon; + } + })); + /** Depending on if any menu is shown and if the menu is shown for the current node, toggle delete animations */ function toggleDeleteAnimations() { //if both are false then remove animations diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreesearchresults.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreesearchresults.directive.js index bc74cbb13b..b006f75e6b 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreesearchresults.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreesearchresults.directive.js @@ -9,13 +9,14 @@ function treeSearchResults() { return { scope: { results: "=", - selectResultCallback: "=" + selectResultCallback: "=", + emptySearchResultPosition: '@' }, restrict: "E", // restrict to an element replace: true, // replace the html element with the template templateUrl: 'views/components/tree/umb-tree-search-results.html', link: function (scope, element, attrs, ctrl) { - + scope.emptySearchResultPosition = scope.emptySearchResultPosition || "center"; } }; } diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbaceeditor.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbaceeditor.directive.js index 10fc44c2c6..5e1f2489e6 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbaceeditor.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbaceeditor.directive.js @@ -82,7 +82,7 @@ if (Utilities.isDefined(opts.firstLineNumber)) { if (Utilities.isNumber(opts.firstLineNumber)) { session.setOption('firstLineNumber', opts.firstLineNumber); - } else if (angular.isFunction(opts.firstLineNumber)) { + } else if (Utilities.isFunction(opts.firstLineNumber)) { session.setOption('firstLineNumber', opts.firstLineNumber()); } } @@ -116,7 +116,7 @@ // onLoad callbacks angular.forEach(opts.callbacks, function(cb) { - if (angular.isFunction(cb)) { + if (Utilities.isFunction(cb)) { cb(acee); } }); @@ -126,7 +126,7 @@ // Load in ace library assetsService.load(['lib/ace-builds/src-min-noconflict/ace.js', 'lib/ace-builds/src-min-noconflict/ext-language_tools.js'], scope).then(function () { - if (angular.isUndefined(window.ace)) { + if (Utilities.isUndefined(window.ace)) { throw new Error('ui-ace need ace to work... (o rly?)'); } else { // init editor @@ -208,7 +208,7 @@ if (Utilities.isDefined(callback)) { scope.$evalAsync(function() { - if (angular.isFunction(callback)) { + if (Utilities.isFunction(callback)) { callback(args); } else { throw new Error('ui-ace use a function as callback.'); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbavatar.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbavatar.directive.js index 7dd2f0d7a3..321cd8a59d 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbavatar.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbavatar.directive.js @@ -50,18 +50,20 @@ Use this directive to render an avatar. (function() { 'use strict'; - function AvatarDirective() { + function AvatarDirective(localizationService) { function link(scope, element, attrs, ctrl) { var eventBindings = []; scope.initials = ""; + scope.avatarAlt = ""; function onInit() { if (!scope.unknownChar) { scope.unknownChar = "?"; } scope.initials = getNameInitials(scope.name); + setAvatarAlt(scope.name); } function getNameInitials(name) { @@ -77,10 +79,23 @@ Use this directive to render an avatar. return null; } + function setAvatarAlt(name) { + if (name) { + localizationService + .localize('general_avatar') + .then(function(data) { + scope.avatarAlt = data + ' ' + name; + } + ); + } + scope.avatarAlt = null; + } + eventBindings.push(scope.$watch('name', function (newValue, oldValue) { if (newValue === oldValue) { return; } if (oldValue === undefined || newValue === undefined) { return; } scope.initials = getNameInitials(newValue); + setAvatarAlt(newValue); })); onInit(); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbchildselector.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbchildselector.directive.js index 96ce8735eb..9a841e3e4a 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbchildselector.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbchildselector.directive.js @@ -21,14 +21,6 @@ Use this directive to render a ui component for selecting child items to a paren on-remove="vm.removeChild"> - - - - @@ -37,7 +29,7 @@ Use this directive to render a ui component for selecting child items to a paren (function () { "use strict"; - function Controller() { + function Controller(overlayService) { var vm = this; @@ -64,23 +56,29 @@ Use this directive to render a ui component for selecting child items to a paren vm.removeChild = removeChild; function addChild($event) { - vm.overlay = { + + const dialog = { view: "itempicker", title: "Choose child", availableItems: vm.availableChildren, selectedItems: vm.selectedChildren, event: $event, - show: true, submit: function(model) { - - // add selected child - vm.selectedChildren.push(model.selectedItem); + + if (model.selectedItem) { + // add selected child + vm.selectedChildren.push(model.selectedItem); + } // close overlay - vm.overlay.show = false; - vm.overlay = null; + overlayService.close(); + }, + close: function() { + overlayService.close(); } }; + + overlayService.open(dialog); } function removeChild($index) { diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbcolorpicker.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbcolorpicker.directive.js new file mode 100644 index 0000000000..b8731c9c51 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbcolorpicker.directive.js @@ -0,0 +1,243 @@ +/** +@ngdoc directive +@name umbraco.directives.directive:umbColorPicker +@restrict E +@scope + +@description +Added in Umbraco v. 8.10: Use this directive to render a color picker. + +

Markup example

+
+    
+ + + + +
+
+ +

Controller example

+
+    (function () {
+        "use strict";
+    
+        function Controller() {
+    
+            var vm = this;
+
+            vm.options = {
+                type: "color",
+                color: defaultColor,
+                showAlpha: false,
+                showPalette: true,
+                showPaletteOnly: false,
+                preferredFormat: "hex",
+            };
+            
+            vm.show = show;
+            vm.hide = hide;
+            vm.change = change;
+
+            function show(color) {
+                color.toHexString().trimStart("#");
+            }
+
+            function hide(color) {
+                color.toHexString().trimStart("#");
+            }
+
+            function change(color) {
+                color.toHexString().trimStart("#");
+            }
+        }
+    
+        angular.module("umbraco").controller("My.ColorController", Controller);
+    
+    })();
+
+ +@param {string} ngModel (binding): Value for the color picker. +@param {object} options (binding): Config object for the color picker. +@param {function} onBeforeShow (expression): Callback function before color picker is shown. +@param {function} onChange (expression): Callback function when the color is changed. +@param {function} onShow (expression): Callback function when color picker is shown. +@param {function} onHide (expression): Callback function when color picker is hidden. +@param {function} onMove (expression): Callback function when the color is moved in color picker. + +**/ + +(function () { + 'use strict'; + + function ColorPickerController($scope, $element, $timeout, assetsService, localizationService) { + + const ctrl = this; + + let colorPickerInstance = null; + let labels = {}; + + ctrl.$onInit = function () { + + // load the separate css for the editor to avoid it blocking our js loading + assetsService.loadCss("lib/spectrum/spectrum.css", $scope); + + // load the js file for the color picker + assetsService.load([ + //"lib/spectrum/tinycolor.js", + "lib/spectrum/spectrum.js" + ], $scope).then(function () { + + // init color picker + grabElementAndRun(); + }); + } + + ctrl.$onChanges = function (changes) { + if (colorPickerInstance && changes.ngModel) { + colorPickerInstance.spectrum("set", changes.ngModel.currentValue); + } + } + + function grabElementAndRun() { + + var labelKeys = [ + "general_cancel", + "general_choose", + "general_clear" + ]; + + localizationService.localizeMany(labelKeys).then(values => { + labels.cancel = values[0]; + labels.choose = values[1]; + labels.clear = values[2]; + }); + + $timeout(function () { + const element = $element.find('.umb-color-picker > input')[0]; + setColorPicker(element, labels); + }, 0, true); + + } + + function setColorPicker(element, labels) { + + // Spectrum options: https://seballot.github.io/spectrum/#options + + const defaultOptions = { + type: "color", + color: null, + showAlpha: false, + showInitial: false, + showInput: true, + cancelText: labels.cancel, + clearText: labels.clear, + chooseText: labels.choose, + preferredFormat: "hex", + clickoutFiresChange: true + }; + + // If has ngModel set the color + if (ctrl.ngModel) { + defaultOptions.color = ctrl.ngModel; + } + + //const options = ctrl.options ? ctrl.options : defaultOptions; + const options = Utilities.extend(defaultOptions, ctrl.options); + + var elem = angular.element(element); + + // Create new color pickr instance + const colorPicker = elem.spectrum(options); + + colorPickerInstance = colorPicker; + + if (colorPickerInstance) { + // destroy the color picker instance when the dom element is removed + elem.on('$destroy', function () { + colorPickerInstance.spectrum('destroy'); + }); + } + + setUpCallbacks(); + + // Refresh the scope + $scope.$applyAsync(); + } + + // Spectrum events: https://seballot.github.io/spectrum/#events + + function setUpCallbacks() { + + if (colorPickerInstance) { + + // bind hook for beforeShow + if (ctrl.onBeforeShow) { + colorPickerInstance.on('beforeShow.spectrum', (e, tinycolor) => { + $timeout(function () { + ctrl.onBeforeShow({ color: tinycolor }); + }); + }); + } + + // bind hook for show + if (ctrl.onShow) { + colorPickerInstance.on('show.spectrum', (e, tinycolor) => { + $timeout(function () { + ctrl.onShow({ color: tinycolor }); + }); + }); + } + + // bind hook for hide + if (ctrl.onHide) { + colorPickerInstance.on('hide.spectrum', (e, tinycolor) => { + $timeout(function () { + ctrl.onHide({ color: tinycolor }); + }); + }); + } + + // bind hook for change + if (ctrl.onChange) { + colorPickerInstance.on('change.spectrum', (e, tinycolor) => { + $timeout(function () { + ctrl.onChange({ color: tinycolor }); + }); + }); + } + + // bind hook for move + if (ctrl.onMove) { + colorPickerInstance.on('move.spectrum', (e, tinycolor) => { + $timeout(function () { + ctrl.onMove({ color: tinycolor }); + }); + }); + } + + } + } + } + + angular + .module('umbraco.directives') + .component('umbColorPicker', { + template: '
', + controller: ColorPickerController, + bindings: { + ngModel: '<', + options: '<', + onBeforeShow: '&', + onShow: '&', + onHide: '&', + onChange: '&', + onMove: '&' + } + }); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbcolorswatches.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbcolorswatches.directive.js index 9d7927f59a..d6eda76940 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbcolorswatches.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbcolorswatches.directive.js @@ -30,15 +30,15 @@ Use this directive to generate color swatches to pick from. function link(scope, el, attr, ctrl) { // Set default to true if not defined - if (angular.isUndefined(scope.useColorClass)) { + if (Utilities.isUndefined(scope.useColorClass)) { scope.useColorClass = false; } // Set default to "btn" if not defined - if (angular.isUndefined(scope.colorClassNamePrefix)) { + if (Utilities.isUndefined(scope.colorClassNamePrefix)) { scope.colorClassNamePrefix = "btn"; } - + scope.setColor = function (color, $index, $event) { if (scope.onSelect) { // did the value change? diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbdatetimepicker.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbdatetimepicker.directive.js index 0498b81963..ed9c011d72 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbdatetimepicker.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbdatetimepicker.directive.js @@ -189,7 +189,7 @@ Use this directive to render a date time picker }; } - // bind hook for onOpen + // bind hook for onOpen if (ctrl.options && ctrl.onClose) { ctrl.options.onClose = function (selectedDates, dateStr, instance) { $timeout(function () { diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbdropdown.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbdropdown.directive.js index d6006114a6..cfba5be2ce 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbdropdown.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbdropdown.directive.js @@ -22,7 +22,7 @@ - {{ item.name }} + @@ -110,7 +110,7 @@ // Stop listening when scope is destroyed. scope.$on('$destroy', stopListening); - + } var directive = { diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgroupsbuilder.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgroupsbuilder.directive.js index 3f53a1e18c..06e1c61f1e 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgroupsbuilder.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgroupsbuilder.directive.js @@ -161,19 +161,17 @@ var resourceLookup = scope.contentType === "documentType" ? contentTypeResource.getAvailableCompositeContentTypes : mediaTypeResource.getAvailableCompositeContentTypes; return resourceLookup(scope.model.id, selectedContentTypeAliases, propAliasesExisting).then(function (filteredAvailableCompositeTypes) { - _.each(scope.compositionsDialogModel.availableCompositeContentTypes, function (current) { + scope.compositionsDialogModel.availableCompositeContentTypes.forEach(current => { //reset first current.allowed = true; //see if this list item is found in the response (allowed) list - var found = _.find(filteredAvailableCompositeTypes, function (f) { - return current.contentType.alias === f.contentType.alias; - }); + var found = filteredAvailableCompositeTypes.find(f => current.contentType.alias === f.contentType.alias); //allow if the item was found in the response (allowed) list - // and ensure its set to allowed if it is currently checked, // DO not allow if it's a locked content type. - current.allowed = scope.model.lockedCompositeContentTypes.indexOf(current.contentType.alias) === -1 && - (selectedContentTypeAliases.indexOf(current.contentType.alias) !== -1) || ((found !== null && found !== undefined) ? found.allowed : false); + current.allowed = scope.model.lockedCompositeContentTypes.includes(current.contentType.alias) && + (selectedContentTypeAliases.includes(current.contentType.alias)) || (found ? found.allowed : false); }); }); @@ -192,15 +190,15 @@ function setupAvailableContentTypesModel(result) { scope.compositionsDialogModel.availableCompositeContentTypes = result; //iterate each one and set it up - _.each(scope.compositionsDialogModel.availableCompositeContentTypes, function (c) { + scope.compositionsDialogModel.availableCompositeContentTypes.forEach(c => { //enable it if it's part of the selected model - if (scope.compositionsDialogModel.compositeContentTypes.indexOf(c.contentType.alias) !== -1) { + if (scope.compositionsDialogModel.compositeContentTypes.includes(c.contentType.alias)) { c.allowed = true; } //set the inherited flags c.inherited = false; - if (scope.model.lockedCompositeContentTypes.indexOf(c.contentType.alias) > -1) { + if (scope.model.lockedCompositeContentTypes.includes(c.contentType.alias)) { c.inherited = true; } // convert icons for composite content types @@ -281,6 +279,8 @@ }, selectCompositeContentType: function (selectedContentType) { + var deferred = $q.defer(); + //first check if this is a new selection - we need to store this value here before any further digests/async // because after that the scope.model.compositeContentTypes will be populated with the selected value. var newSelection = scope.model.compositeContentTypes.indexOf(selectedContentType.alias) === -1; @@ -308,7 +308,10 @@ //based on the selection, we need to filter the available composite types list filterAvailableCompositions(selectedContentType, newSelection).then(function () { + deferred.resolve({ selectedContentType, newSelection }); // TODO: Here we could probably re-enable selection if we previously showed a throbber or something + }, function () { + deferred.reject(); }); }); } @@ -318,10 +321,14 @@ //based on the selection, we need to filter the available composite types list filterAvailableCompositions(selectedContentType, newSelection).then(function () { + deferred.resolve({ selectedContentType, newSelection }); // TODO: Here we could probably re-enable selection if we previously showed a throbber or something + }, function () { + deferred.reject(); }); } + return deferred.promise; } }; @@ -346,7 +353,7 @@ }), //get where used document types whereUsedContentTypeResource(scope.model.id).then(function (whereUsed) { - //pass to the dialog model the content type eg documentType or mediaType + //pass to the dialog model the content type eg documentType or mediaType scope.compositionsDialogModel.section = scope.contentType; //pass the list of 'where used' document types scope.compositionsDialogModel.whereCompositionUsed = whereUsed; @@ -559,6 +566,7 @@ property.isSensitiveValue = propertyModel.isSensitiveValue; property.allowCultureVariant = propertyModel.allowCultureVariant; property.allowSegmentVariant = propertyModel.allowSegmentVariant; + property.labelOnTop = propertyModel.labelOnTop; // update existing data types if (model.updateSameDataTypes) { @@ -640,7 +648,8 @@ mandatoryMessage: null, pattern: null, patternMessage: null - } + }, + labelOnTop: false }; // check if there already is an init property diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbicon.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbicon.directive.js new file mode 100644 index 0000000000..87d976f6d9 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbicon.directive.js @@ -0,0 +1,94 @@ +/** +@ngdoc directive +@name umbraco.directives.directive:umbIcon +@restrict E +@scope +@description +Use this directive to show an render an umbraco backoffice svg icon. All svg icons used by this directive should use the following naming convention to keep things consistent: icon-[name of icon]. For example
icon-alert.svg
+ +

Markup example

+ +Simple icon +
+    
+
+ +Icon with additional attribute. It can be treated like any other dom element +
+    
+
+@example + **/ + +(function () { + "use strict"; + + function UmbIconDirective(iconHelper) { + + var directive = { + replace: true, + transclude: true, + templateUrl: "views/components/umb-icon.html", + scope: { + icon: "@", + svgString: "=?" + }, + + link: function (scope, element) { + if (scope.svgString === undefined && scope.svgString !== null && scope.icon !== undefined && scope.icon !== null) { + const observer = new IntersectionObserver(_lazyRequestIcon, {rootMargin: "100px"}); + const iconEl = element[0]; + + observer.observe(iconEl); + + // make sure to disconnect the observer when the scope is destroyed + scope.$on('$destroy', function () { + observer.disconnect(); + }); + } + + scope.$watch("icon", function (newValue, oldValue) { + if (newValue && oldValue) { + var newicon = newValue.split(" ")[0]; + var oldicon = oldValue.split(" ")[0]; + + if (newicon !== oldicon) { + _requestIcon(newicon); + } + } + }); + + function _lazyRequestIcon(entries, observer) { + entries.forEach(entry => { + if (entry.isIntersecting === true) { + observer.disconnect(); + + var icon = scope.icon.split(" ")[0]; // Ensure that only the first part of the icon is used as sometimes the color is added too, e.g. see umbeditorheader.directive scope.openIconPicker + _requestIcon(icon); + } + }); + } + + function _requestIcon(icon) { + // Reset svg string before requesting new icon. + scope.svgString = null; + + iconHelper.getIcon(icon) + .then(data => { + if (data !== null && data.svgString !== undefined) { + // Watch source SVG string + //icon.svgString.$$unwrapTrustedValue(); + scope.svgString = data.svgString; + } + }); + } + } + + }; + + return directive; + } + + angular.module("umbraco.directives").directive("umbIcon", UmbIconDirective); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbmediagrid.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbmediagrid.directive.js index aa28d49c4a..241f1e80e8 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbmediagrid.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbmediagrid.directive.js @@ -322,6 +322,41 @@ Use this directive to generate a thumbnail grid of media items. scope.$on('$destroy', function() { unbindItemsWatcher(); }); + //determine if sort is current + scope.sortColumn = "name"; + scope.sortReverse = false; + scope.sortDirection = "asc"; + //check sort status + scope.isSortDirection = function (col, direction) { + return col === scope.sortColumn && direction === scope.sortDirection; + }; + //change sort + scope.setSort = function (col) { + if (scope.sortColumn === col) { + scope.sortReverse = !scope.sortReverse; + } + else { + scope.sortColumn = col; + if (col === "updateDate") { + scope.sortReverse = true; + } + else { + scope.sortReverse = false; + } + } + scope.sortDirection = scope.sortReverse ? "desc" : "asc"; + + } + // sort function + scope.sortBy = function (item) { + if (scope.sortColumn === "updateDate") { + return [-item['isFolder'],item['updateDate']]; + } + else { + return [-item['isFolder'],item['name']]; + } + }; + } @@ -345,7 +380,8 @@ Use this directive to generate a thumbnail grid of media items. onlyImages: "@", onlyFolders: "@", includeSubFolders: "@", - currentFolderId: "@" + currentFolderId: "@", + showMediaList: "=" }, link: link }; diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbminilistview.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbminilistview.directive.js index 66e03a7302..783cd7f90a 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbminilistview.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbminilistview.directive.js @@ -63,7 +63,7 @@ } // update children miniListView.children = data.items; - _.each(miniListView.children, function(c) { + miniListView.children.forEach(c => { // child allowed by default c.allowed = true; @@ -92,10 +92,11 @@ // advanced item filtering is handled here if (scope.entityTypeFilter && scope.entityTypeFilter.filter && scope.entityTypeFilter.filterAdvanced) { - var filtered = angular.isFunction(scope.entityTypeFilter.filter) + var filtered = Utilities.isFunction(scope.entityTypeFilter.filter) ? _.filter(miniListView.children, scope.entityTypeFilter.filter) : _.where(miniListView.children, scope.entityTypeFilter.filter); - _.each(filtered, (node) => node.allowed = false); + + filtered.forEach(node => node.allowed = false); } // update pagination diff --git a/src/Umbraco.Web.UI.Client/src/views/components/umb-mini-search/umbminisearch.component.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbminisearch.component.js similarity index 79% rename from src/Umbraco.Web.UI.Client/src/views/components/umb-mini-search/umbminisearch.component.js rename to src/Umbraco.Web.UI.Client/src/common/directives/components/umbminisearch.component.js index d7aee744e4..6c65cb6e23 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/umb-mini-search/umbminisearch.component.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbminisearch.component.js @@ -4,7 +4,7 @@ angular .module('umbraco') .component('umbMiniSearch', { - templateUrl: 'views/components/umb-mini-search/umb-mini-search.html', + templateUrl: 'views/components/umb-mini-search.html', controller: UmbMiniSearchController, controllerAs: 'vm', bindings: { @@ -18,6 +18,9 @@ function UmbMiniSearchController($scope) { var vm = this; + + vm.onKeyDown = onKeyDown; + vm.onChange = onChange; var searchDelay = _.debounce(function () { $scope.$apply(function () { @@ -27,23 +30,23 @@ }); }, 500); - vm.onKeyDown = function (ev) { + function onKeyDown(evt) { //13: enter - switch (ev.keyCode) { + switch (evt.keyCode) { case 13: if (vm.onSearch) { vm.onSearch(); } break; } - }; + } - vm.onChange = function () { + function onChange() { if (vm.onStartTyping) { vm.onStartTyping(); } searchDelay(); - }; + } } diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbnestedcontent.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbnestedcontent.directive.js index 72dba3ca2f..f9b26c81a5 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbnestedcontent.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbnestedcontent.directive.js @@ -49,6 +49,7 @@ } }); + } }); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbpagination.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbpagination.directive.js index b49d47b979..f939eb5e46 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbpagination.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbpagination.directive.js @@ -16,6 +16,7 @@ Use this directive to generate a pagination. total-pages="vm.pagination.totalPages" on-next="vm.nextPage" on-prev="vm.prevPage" + on-change="vm.changePage" on-go-to-page="vm.goToPage"> @@ -34,10 +35,11 @@ Use this directive to generate a pagination. vm.pagination = { pageNumber: 1, totalPages: 10 - } + }; vm.nextPage = nextPage; vm.prevPage = prevPage; + vm.changePage = changePage; vm.goToPage = goToPage; function nextPage(pageNumber) { @@ -51,6 +53,12 @@ Use this directive to generate a pagination. console.log(pageNumber); alert("prevpage"); } + + function changePage(pageNumber) { + // do magic here + console.log(pageNumber); + alert("changepage"); + } function goToPage(pageNumber) { // do magic here @@ -81,6 +89,11 @@ Use this directive to generate a pagination.
  • pageNumber: The page number
+@param {callback=} onChange (binding): Callback method when changing page. +

The callback returns:

+
    +
  • pageNumber: The page number
  • +
**/ (function() { @@ -175,9 +188,7 @@ Use this directive to generate a pagination. scope.onGoToPage(scope.pageNumber); } if (scope.onChange) { - if (scope.onChange) { - scope.onChange({ "pageNumber": scope.pageNumber }); - } + scope.onChange({ "pageNumber": scope.pageNumber }); } }; diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbrangeslider.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbrangeslider.directive.js index e467522c84..ed74f94f26 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbrangeslider.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbrangeslider.directive.js @@ -46,7 +46,7 @@ For extra details about options and events take a look here: https://refreshless @param {object} ngModel (binding): Value for the slider. -@param {object} options (binding): Config object for the date picker. +@param {object} options (binding): Config object for the slider. @param {callback} onSetup (callback): onSetup gets triggered when the slider is initialized @param {callback} onUpdate (callback): onUpdate fires every time the slider values are changed. @param {callback} onSlide (callback): onSlide gets triggered when the handle is being dragged. diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbtable.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbtable.directive.js index 3f1929e97d..1554c136b6 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbtable.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbtable.directive.js @@ -98,9 +98,9 @@ })(); -@param {string} icon (binding): The node icon. -@param {string} name (binding): The node name. -@param {string} published (binding): The node published state. +@param {array} items (binding): The items for the table. +@param {array} itemProperties (binding): The properties for the items to use in table. +@param {boolean} allowSelectAll (binding): Specify whether to allow select all. @param {function} onSelect (expression): Callback function when the row is selected. @param {function} onClick (expression): Callback function when the "Name" column link is clicked. @param {function} onSelectAll (expression): Callback function when selecting all items. diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbtextarea.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbtextarea.directive.js new file mode 100644 index 0000000000..8b584b873c --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbtextarea.directive.js @@ -0,0 +1,59 @@ +(function () { + 'use strict'; + + function umbTextarea($document) { + + function autogrow(scope, element, attributes) { + if (!element.hasClass("autogrow")) { + // no autogrow for you today + return; + } + + // get possible minimum height style + var minHeight = parseInt(window.getComputedStyle(element[0]).getPropertyValue("min-height")) || 0; + + // prevent newlines in textbox + element.on("keydown", function (evt) { + if (evt.which === 13) { + //evt.preventDefault(); + } + }); + + element.on("input", function (evt) { + element.css({ + height: 'auto', + minHeight: 0 + }); + + var contentHeight = this.scrollHeight; + var borderHeight = 1; + var paddingHeight = 4; + + element.css({ + minHeight: null, // remove property + height: contentHeight + borderHeight + paddingHeight + "px" // because we're using border-box + }); + }); + + // watch model changes from the outside to adjust height + scope.$watch(attributes.ngModel, trigger); + + // set initial size + trigger(); + + function trigger() { + setTimeout(element.triggerHandler.bind(element, "input"), 1); + } + } + + var directive = { + restrict: 'E', + link: autogrow + }; + + return directive; + } + + angular.module('umbraco.directives').directive('textarea', umbTextarea); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbtooltip.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbtooltip.directive.js index ef7006be2c..ce1885a7cf 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbtooltip.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbtooltip.directive.js @@ -77,13 +77,17 @@ Use this directive to render a tooltip. scope.tooltipStyles.left = 0; scope.tooltipStyles.top = 0; - function setTooltipPosition(event) { + function setTooltipPosition(event) { - var container = $("#contentwrapper"); - var containerLeft = container[0].offsetLeft; - var containerRight = containerLeft + container[0].offsetWidth; - var containerTop = container[0].offsetTop; - var containerBottom = containerTop + container[0].offsetHeight; + var overlay = $(event.target).closest('.umb-overlay'); + var container = overlay.length > 0 ? overlay : $("#contentwrapper"); + + let rect = container[0].getBoundingClientRect(); + + var containerLeft = rect.left; + var containerRight = containerLeft + rect.width; + var containerTop = rect.top; + var containerBottom = containerTop + rect.height; var elementHeight = null; var elementWidth = null; @@ -102,39 +106,43 @@ Use this directive to render a tooltip. position.left = event.pageX - (elementWidth / 2); position.top = event.pageY; - // check to see if element is outside screen - // outside right - if (position.left + elementWidth > containerRight) { - position.right = 10; - position.left = "inherit"; + if (overlay.length > 0) { + position.left = event.pageX - rect.left - (elementWidth / 2); + position.top = event.pageY - rect.top; } + else { + // check to see if element is outside screen + // outside right + if (position.left + elementWidth > containerRight) { + position.right = 10; + position.left = "inherit"; + } - // outside bottom - if (position.top + elementHeight > containerBottom) { - position.bottom = 10; - position.top = "inherit"; - } + // outside bottom + if (position.top + elementHeight > containerBottom) { + position.bottom = 10; + position.top = "inherit"; + } - // outside left - if (position.left < containerLeft) { - position.left = containerLeft + 10; - position.right = "inherit"; - } + // outside left + if (position.left < containerLeft) { + position.left = containerLeft + 10; + position.right = "inherit"; + } - // outside top - if (position.top < containerTop) { - position.top = 10; - position.bottom = "inherit"; + // outside top + if (position.top < containerTop) { + position.top = 10; + position.bottom = "inherit"; + } } scope.tooltipStyles = position; el.css(position); - } setTooltipPosition(scope.event); - } var directive = { diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbfileupload.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbfileupload.directive.js index 6a8ffa7969..3581aed9e0 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbfileupload.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbfileupload.directive.js @@ -19,6 +19,17 @@ function umbFileUpload() { //clear the element value - this allows us to pick the same file again and again el.val(''); }); + + el.on('drag dragstart dragend dragover dragenter dragleave drop', function (e) { + e.preventDefault(); + e.stopPropagation(); + }) + .on('dragover dragenter', function () { + scope.$emit("isDragover", { value: true }); + }) + .on('dragleave dragend drop', function () { + scope.$emit("isDragover", { value: false }); + }); } }; } diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbpropertyfileupload.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbpropertyfileupload.directive.js index 653b4f427c..db1e38adc6 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbpropertyfileupload.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbpropertyfileupload.directive.js @@ -78,6 +78,8 @@ /** Called when the component initializes */ function onInit() { $scope.$on("filesSelected", onFilesSelected); + $scope.$on("isDragover", isDragover); + initialize(); } @@ -118,7 +120,9 @@ isImage: mediaHelper.detectIfImageByExtension(file), extension: getExtension(file) }; + f.fileSrc = getThumbnail(f); + return f; }); @@ -228,19 +232,22 @@ var index = i; //capture var isImage = mediaHelper.detectIfImageByExtension(files[i].name); + var extension = getExtension(files[i].name); - //save the file object to the files collection - vm.files.push({ + var f = { isImage: isImage, - extension: getExtension(files[i].name), + extension: extension, fileName: files[i].name, isClientSide: true - }); + }; + + // Save the file object to the files collection + vm.files.push(f); //special check for a comma in the name newVal += files[i].name.split(',').join('-') + ","; - if (isImage) { + if (isImage || extension === "svg") { var deferred = $q.defer(); @@ -293,6 +300,11 @@ } } + function isDragover(e, args) { + vm.dragover = args.value; + angularHelper.safeApply($scope); + } + }; var umbPropertyFileUploadComponent = { @@ -303,6 +315,7 @@ propertyAlias: "@", value: "<", hideSelection: "<", + dragover: "<", /** * Called when a file is selected on this instance */ diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/util/umbkeyboardlist.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/util/umbkeyboardlist.directive.js index 20572fdf16..0b743d0f10 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/util/umbkeyboardlist.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/util/umbkeyboardlist.directive.js @@ -10,12 +10,12 @@
     
@@ -33,11 +33,11 @@ angular.module('umbraco.directives') return { restrict: 'A', link: function (scope, element, attr) { - + var listItems = []; var currentIndex = 0; var focusSet = false; - + $timeout(function(){ // get list of all links in the list listItems = element.find("li :tabbable"); @@ -82,7 +82,7 @@ angular.module('umbraco.directives') function arrowDown() { if (currentIndex < listItems.length - 1) { - // only bump the current index if the focus is already + // only bump the current index if the focus is already // set else we just want to focus the first element if (focusSet) { currentIndex++; @@ -112,4 +112,4 @@ angular.module('umbraco.directives') } }; - }]); \ No newline at end of file + }]); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/validation/showvalidationonsubmit.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/validation/showvalidationonsubmit.directive.js index 78dd00e64b..ad646748b7 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/validation/showvalidationonsubmit.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/validation/showvalidationonsubmit.directive.js @@ -11,6 +11,7 @@ link: function (scope, element, attr, ctrl) { var formMgr = ctrl.length > 1 ? ctrl[1] : null; + const hiddenClass = 'ng-hide'; //We can either get the form submitted status by the parent directive valFormManager //or we can check upwards in the DOM for the css class... lets try both :) @@ -18,17 +19,17 @@ //reset the status. var submitted = element.closest(".show-validation").length > 0 || (formMgr && formMgr.showValidation); if (!submitted) { - element.hide(); + element[0].classList.add(hiddenClass); } var unsubscribe = []; unsubscribe.push(scope.$on("formSubmitting", function (ev, args) { - element.show(); + element[0].classList.remove(hiddenClass); })); unsubscribe.push(scope.$on("formSubmitted", function (ev, args) { - element.hide(); + element[0].classList.add(hiddenClass); })); //no isolate scope to listen to element destroy diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valformmanager.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valformmanager.directive.js index 6638ed4e6d..c7894da171 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valformmanager.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valformmanager.directive.js @@ -12,48 +12,88 @@ * Another thing this directive does is to ensure that any .control-group that contains form elements that are invalid will * be marked with the 'error' css class. This ensures that labels included in that control group are styled correctly. **/ -function valFormManager(serverValidationManager, $rootScope, $timeout, $location, overlayService, eventsService, $routeParams, navigationService, editorService, localizationService) { +function valFormManager(serverValidationManager, $rootScope, $timeout, $location, overlayService, eventsService, $routeParams, navigationService, editorService, localizationService, angularHelper) { var SHOW_VALIDATION_CLASS_NAME = "show-validation"; var SAVING_EVENT_NAME = "formSubmitting"; var SAVED_EVENT_NAME = "formSubmitted"; + function notify(scope) { + scope.$broadcast("valStatusChanged", { form: scope.formCtrl }); + } + + function ValFormManagerController($scope) { + //This exposes an API for direct use with this directive + + // We need this as a way to reference this directive in the scope chain. Since this directive isn't a component and + // because it's an attribute instead of an element, we can't use controllerAs or anything like that. Plus since this is + // an attribute an isolated scope doesn't work so it's a bit weird. By doing this we are able to lookup the parent valFormManager + // in the scope hierarchy even if the DOM hierarchy doesn't match (i.e. in infinite editing) + $scope.valFormManager = this; + + var unsubscribe = []; + var self = this; + + //This is basically the same as a directive subscribing to an event but maybe a little + // nicer since the other directive can use this directive's API instead of a magical event + this.onValidationStatusChanged = function (cb) { + unsubscribe.push($scope.$on("valStatusChanged", function (evt, args) { + cb.apply(self, [evt, args]); + })); + }; + + this.isShowingValidation = () => $scope.showValidation === true; + + this.notify = function () { + notify($scope); + } + + this.isValid = function () { + return !$scope.formCtrl.$invalid; + } + + //Ensure to remove the event handlers when this instance is destroyted + $scope.$on('$destroy', function () { + for (var u in unsubscribe) { + unsubscribe[u](); + } + }); + } + + /** + * Find's the valFormManager in the scope/DOM hierarchy + * @param {any} scope + * @param {any} ctrls + * @param {any} index + */ + function getAncestorValFormManager(scope, ctrls, index) { + + // first check the normal directive inheritance which relies on DOM inheritance + var found = ctrls[index]; + if (found) { + return found; + } + + // not found, then fallback to searching the scope chain, this may be needed when DOM inheritance isn't maintained but scope + // inheritance is (i.e.infinite editing) + var found = angularHelper.traverseScopeChain(scope, s => s && s.valFormManager && s.valFormManager.constructor.name === "ValFormManagerController"); + return found ? found.valFormManager : null; + } + return { require: ["form", "^^?valFormManager", "^^?valSubView"], restrict: "A", - controller: function($scope) { - //This exposes an API for direct use with this directive - - var unsubscribe = []; - var self = this; - - //This is basically the same as a directive subscribing to an event but maybe a little - // nicer since the other directive can use this directive's API instead of a magical event - this.onValidationStatusChanged = function (cb) { - unsubscribe.push($scope.$on("valStatusChanged", function(evt, args) { - cb.apply(self, [evt, args]); - })); - }; - - this.showValidation = $scope.showValidation === true; - - //Ensure to remove the event handlers when this instance is destroyted - $scope.$on('$destroy', function () { - for (var u in unsubscribe) { - unsubscribe[u](); - } - }); - }, + controller: ValFormManagerController, link: function (scope, element, attr, ctrls) { function notifySubView() { - if (subView){ + if (subView) { subView.valStatusChanged({ form: formCtrl, showValidation: scope.showValidation }); } } - var formCtrl = ctrls[0]; - var parentFormMgr = ctrls.length > 0 ? ctrls[1] : null; + var formCtrl = scope.formCtrl = ctrls[0]; + var parentFormMgr = scope.parentFormMgr = getAncestorValFormManager(scope, ctrls, 1); var subView = ctrls.length > 1 ? ctrls[2] : null; var labels = {}; @@ -72,45 +112,22 @@ function valFormManager(serverValidationManager, $rootScope, $timeout, $location }); //watch the list of validation errors to notify the application of any validation changes - scope.$watch(function () { - //the validators are in the $error collection: https://docs.angularjs.org/api/ng/type/form.FormController#$error - //since each key is the validator name (i.e. 'required') we can't just watch the number of keys, we need to watch - //the sum of the items inside of each key + scope.$watch(() => angularHelper.countAllFormErrors(formCtrl), + function (e) { + + notify(scope); + + notifySubView(); + + //find all invalid elements' .control-group's and apply the error class + var inError = element.find(".control-group .ng-invalid").closest(".control-group"); + inError.addClass("error"); + + //find all control group's that have no error and ensure the class is removed + var noInError = element.find(".control-group .ng-valid").closest(".control-group").not(inError); + noInError.removeClass("error"); - //get the lengths of each array for each key in the $error collection - var validatorLengths = _.map(formCtrl.$error, function (val, key) { - // if there are child ng-forms, include the $error collections in those as well - var innerErrorCount = _.reduce( - _.map(val, v => - _.reduce( - _.map(v.$error, e => e.length), - (m, n) => m + n - ) - ), - (memo, num) => memo + num - ); - return val.length + innerErrorCount; }); - //sum up all numbers in the resulting array - var sum = _.reduce(validatorLengths, function (memo, num) { - return memo + num; - }, 0); - //this is the value we watch to notify of any validation changes on the form - return sum; - }, function (e) { - scope.$broadcast("valStatusChanged", { form: formCtrl }); - - notifySubView(); - - //find all invalid elements' .control-group's and apply the error class - var inError = element.find(".control-group .ng-invalid").closest(".control-group"); - inError.addClass("error"); - - //find all control group's that have no error and ensure the class is removed - var noInError = element.find(".control-group .ng-valid").closest(".control-group").not(inError); - noInError.removeClass("error"); - - }); //This tracks if the user is currently saving a new item, we use this to determine // if we should display the warning dialog that they are leaving the page - if a new item @@ -119,7 +136,7 @@ function valFormManager(serverValidationManager, $rootScope, $timeout, $location var isSavingNewItem = false; //we should show validation if there are any msgs in the server validation collection - if (serverValidationManager.items.length > 0 || (parentFormMgr && parentFormMgr.showValidation)) { + if (serverValidationManager.items.length > 0 || (parentFormMgr && parentFormMgr.isShowingValidation())) { element.addClass(SHOW_VALIDATION_CLASS_NAME); scope.showValidation = true; notifySubView(); @@ -128,7 +145,7 @@ function valFormManager(serverValidationManager, $rootScope, $timeout, $location var unsubscribe = []; //listen for the forms saving event - unsubscribe.push(scope.$on(SAVING_EVENT_NAME, function(ev, args) { + unsubscribe.push(scope.$on(SAVING_EVENT_NAME, function (ev, args) { element.addClass(SHOW_VALIDATION_CLASS_NAME); scope.showValidation = true; notifySubView(); @@ -137,21 +154,18 @@ function valFormManager(serverValidationManager, $rootScope, $timeout, $location })); //listen for the forms saved event - unsubscribe.push(scope.$on(SAVED_EVENT_NAME, function(ev, args) { + unsubscribe.push(scope.$on(SAVED_EVENT_NAME, function (ev, args) { //remove validation class element.removeClass(SHOW_VALIDATION_CLASS_NAME); scope.showValidation = false; notifySubView(); - //clear form state as at this point we retrieve new data from the server - //and all validation will have cleared at this point - formCtrl.$setPristine(); })); var confirmed = false; //This handles the 'unsaved changes' dialog which is triggered when a route is attempting to be changed but // the form has pending changes - var locationEvent = $rootScope.$on('$locationChangeStart', function(event, nextLocation, currentLocation) { + var locationEvent = $rootScope.$on('$locationChangeStart', function (event, nextLocation, currentLocation) { var infiniteEditors = editorService.getEditors(); @@ -178,10 +192,10 @@ function valFormManager(serverValidationManager, $rootScope, $timeout, $location "disableEscKey": true, "submitButtonLabel": labels.stayButton, "closeButtonLabel": labels.discardChangesButton, - submit: function() { + submit: function () { overlayService.close(); }, - close: function() { + close: function () { // close all editors editorService.closeAll(); // allow redirection @@ -190,7 +204,7 @@ function valFormManager(serverValidationManager, $rootScope, $timeout, $location var parts = nextPath.split("?"); var query = {}; if (parts.length > 1) { - _.each(parts[1].split("&"), function(q) { + parts[1].split("&").forEach(q => { var keyVal = q.split("="); query[keyVal[0]] = keyVal[1]; }); @@ -215,13 +229,15 @@ function valFormManager(serverValidationManager, $rootScope, $timeout, $location unsubscribe.push(locationEvent); //Ensure to remove the event handler when this instance is destroyted - scope.$on('$destroy', function() { + scope.$on('$destroy', function () { for (var u in unsubscribe) { unsubscribe[u](); } }); - $timeout(function(){ + // TODO: I'm unsure why this exists, i believe this may be a hack for something like tinymce which might automatically + // change a form value on load but we need it to be $pristine? + $timeout(function () { formCtrl.$setPristine(); }, 1000); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valpropertymsg.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valpropertymsg.directive.js index 30d3530efb..26c0403f85 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valpropertymsg.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valpropertymsg.directive.js @@ -8,24 +8,26 @@ * We will listen for server side validation changes * and when an error is detected for this property we'll show the error message. * In order for this directive to work, the valFormManager directive must be placed on the containing form. +* We don't set the validity of this validator to false when client side validation fails, only when server side +* validation fails however we do respond to the client side validation changes to display error and adjust UI state. **/ -function valPropertyMsg(serverValidationManager, localizationService) { +function valPropertyMsg(serverValidationManager, localizationService, angularHelper) { return { - require: ['^^form', '^^valFormManager', '^^umbProperty', '?^^umbVariantContent'], + require: ['^^form', '^^valFormManager', '^^umbProperty', '?^^umbVariantContent', '?^^valPropertyMsg'], replace: true, restrict: "E", template: "
{{errorMsg}}
", scope: {}, link: function (scope, element, attrs, ctrl) { - + var unsubscribe = []; var watcher = null; - var hasError = false; + var hasError = false; // tracks if there is a child error or an explicit error //create properties on our custom scope so we can use it in our template - scope.errorMsg = ""; - + scope.errorMsg = ""; + //the property form controller api var formCtrl = ctrl[0]; //the valFormManager controller api @@ -33,21 +35,19 @@ function valPropertyMsg(serverValidationManager, localizationService) { //the property controller api var umbPropCtrl = ctrl[2]; //the variants controller api - var umbVariantCtrl = ctrl[3]; - + var umbVariantCtrl = ctrl[3]; + var currentProperty = umbPropCtrl.property; scope.currentProperty = currentProperty; var currentCulture = currentProperty.culture; - var currentSegment = currentProperty.segment; - + var currentSegment = currentProperty.segment; + // validation object won't exist when editor loads outside the content form (ie in settings section when modifying a content type) var isMandatory = currentProperty.validation ? currentProperty.validation.mandatory : undefined; var labels = {}; - localizationService.localize("errors_propertyHasErrors").then(function (data) { - labels.propertyHasErrors = data; - }); + var showValidation = false; if (umbVariantCtrl) { //if we are inside of an umbVariantContent directive @@ -61,17 +61,16 @@ function valPropertyMsg(serverValidationManager, localizationService) { return; } } - + // if we have reached this part, and there is no culture, then lets fallback to invariant. To get the validation feedback for invariant language. currentCulture = currentCulture || "invariant"; - // Gets the error message to display function getErrorMsg() { //this can be null if no property was assigned if (scope.currentProperty) { //first try to get the error msg from the server collection - var err = serverValidationManager.getPropertyError(scope.currentProperty.alias, null, "", null); + var err = serverValidationManager.getPropertyError(umbPropCtrl.getValidationPath(), null, "", null); //if there's an error message use it if (err && err.errorMsg) { return err.errorMsg; @@ -84,43 +83,108 @@ function valPropertyMsg(serverValidationManager, localizationService) { return labels.propertyHasErrors; } - // We need to subscribe to any changes to our model (based on user input) - // This is required because when we have a server error we actually invalidate - // the form which means it cannot be resubmitted. - // So once a field is changed that has a server error assigned to it - // we need to re-validate it for the server side validator so the user can resubmit - // the form. Of course normal client-side validators will continue to execute. + // check the current errors in the form (and recursive sub forms), if there is 1 or 2 errors + // we can check if those are valPropertyMsg or valServer and can clear our error in those cases. + function checkAndClearError() { + + var errCount = angularHelper.countAllFormErrors(formCtrl); + + if (errCount === 0) { + return true; + } + + if (errCount > 2) { + return false; + } + + var hasValServer = Utilities.isArray(formCtrl.$error.valServer); + if (errCount === 1 && hasValServer) { + return true; + } + + var hasOwnErr = hasExplicitError(); + if ((errCount === 1 && hasOwnErr) || (errCount === 2 && hasOwnErr && hasValServer)) { + + var propertyValidationPath = umbPropCtrl.getValidationPath(); + // check if we can clear it based on child server errors, if we are the only explicit one remaining we can clear ourselves + if (isLastServerError(propertyValidationPath)) { + serverValidationManager.removePropertyError(propertyValidationPath, currentCulture, "", currentSegment); + return true; + } + return false; + } + + return false; + } + + // returns true if there is an explicit valPropertyMsg validation error on the form + function hasExplicitError() { + return Utilities.isArray(formCtrl.$error.valPropertyMsg); + } + + // returns true if there is only a single server validation error for this property validation key in it's validation path + function isLastServerError(propertyValidationPath) { + var nestedErrs = serverValidationManager.getPropertyErrorsByValidationPath( + propertyValidationPath, + currentCulture, + currentSegment, + { matchType: "prefix" }); + if (nestedErrs.length === 0 || (nestedErrs.length === 1 && nestedErrs[0].propertyAlias === propertyValidationPath)) { + + return true; + } + return false; + } + + // a custom $validator function called on when each child ngModelController changes a value. + function resetServerValidityValidator(fieldController) { + const storedFieldController = fieldController; // pin a reference to this + return (modelValue, viewValue) => { + // if the ngModelController value has changed, then we can check and clear the error + if (storedFieldController.$dirty) { + if (checkAndClearError()) { + resetError(); + } + } + return true; // this validator is always 'valid' + }; + } + + // collect all ng-model controllers recursively within the umbProperty form + // until it reaches the next nested umbProperty form + function collectAllNgModelControllersRecursively(controls, ngModels) { + controls.forEach(ctrl => { + if (angularHelper.isForm(ctrl)) { + // if it's not another umbProperty form then recurse + if (ctrl.$name !== formCtrl.$name) { + collectAllNgModelControllersRecursively(ctrl.$getControls(), ngModels); + } + } + else if (ctrl.hasOwnProperty('$modelValue') && Utilities.isObject(ctrl.$validators)) { + ngModels.push(ctrl); + } + }); + } + + // We start the watch when there's server validation errors detected. + // We watch on the current form's properties and on first watch or if they are dynamically changed + // we find all ngModel controls recursively on this form (but stop recursing before we get to the next) + // umbProperty form). Then for each ngModelController we assign a new $validator. This $validator + // will execute whenever the value is changed which allows us to check and reset the server validator function startWatch() { - //if there's not already a watch if (!watcher) { - watcher = scope.$watch("currentProperty.value", - function (newValue, oldValue) { - if (angular.equals(newValue, oldValue)) { - return; - } - var errCount = 0; - - for (var e in formCtrl.$error) { - if (Utilities.isArray(formCtrl.$error[e])) { - errCount++; + watcher = scope.$watchCollection( + () => formCtrl, + function (updatedFormController) { + var ngModels = []; + collectAllNgModelControllersRecursively(updatedFormController.$getControls(), ngModels); + ngModels.forEach(x => { + if (!x.$validators.serverValidityResetter) { + x.$validators.serverValidityResetter = resetServerValidityValidator(x); } - } - - //we are explicitly checking for valServer errors here, since we shouldn't auto clear - // based on other errors. We'll also check if there's no other validation errors apart from valPropertyMsg, if valPropertyMsg - // is the only one, then we'll clear. - - if (errCount === 0 - || (errCount === 1 && Utilities.isArray(formCtrl.$error.valPropertyMsg)) - || (formCtrl.$invalid && Utilities.isArray(formCtrl.$error.valServer))) { - scope.errorMsg = ""; - formCtrl.$setValidity('valPropertyMsg', true); - } else if (showValidation && scope.errorMsg === "") { - formCtrl.$setValidity('valPropertyMsg', false); - scope.errorMsg = getErrorMsg(); - } - }, true); + }); + }); } } @@ -132,22 +196,31 @@ function valPropertyMsg(serverValidationManager, localizationService) { } } + function resetError() { + stopWatch(); + hasError = false; + formCtrl.$setValidity('valPropertyMsg', true); + scope.errorMsg = ""; + + } + + // This deals with client side validation changes and is executed anytime validators change on the containing + // valFormManager. This allows us to know when to display or clear the property error data for non-server side errors. function checkValidationStatus() { if (formCtrl.$invalid) { //first we need to check if the valPropertyMsg validity is invalid if (formCtrl.$error.valPropertyMsg && formCtrl.$error.valPropertyMsg.length > 0) { //since we already have an error we'll just return since this means we've already set the - // hasError and errorMsg properties which occurs below in the serverValidationManager.subscribe + //hasError and errorMsg properties which occurs below in the serverValidationManager.subscribe return; } //if there are any errors in the current property form that are not valPropertyMsg else if (_.without(_.keys(formCtrl.$error), "valPropertyMsg").length > 0) { - + // errors exist, but if the property is NOT mandatory and has no value, the errors should be cleared if (isMandatory !== undefined && isMandatory === false && !currentProperty.value) { - hasError = false; - showValidation = false; - scope.errorMsg = ""; + + resetError(); // if there's no value, the controls can be reset, which clears the error state on formCtrl for (let control of formCtrl.$getControls()) { @@ -164,99 +237,115 @@ function valPropertyMsg(serverValidationManager, localizationService) { } } else { - hasError = false; - scope.errorMsg = ""; + resetError(); } } else { - hasError = false; - scope.errorMsg = ""; + resetError(); } } - //if there's any remaining errors in the server validation service then we should show them. - var showValidation = serverValidationManager.items.length > 0; - if (!showValidation) { - //We can either get the form submitted status by the parent directive valFormManager (if we add a property to it) - //or we can just check upwards in the DOM for the css class (easier for now). - //The initial hidden state can't always be hidden because when we switch variants in the content editor we cannot - //reset the status. - showValidation = element.closest(".show-validation").length > 0; - } + function onInit() { + localizationService.localize("errors_propertyHasErrors").then(function (data) { - //listen for form validation changes. - //The alternative is to add a watch to formCtrl.$invalid but that would lead to many more watches then - // subscribing to this single watch. - valFormManager.onValidationStatusChanged(function (evt, args) { - checkValidationStatus(); - }); + labels.propertyHasErrors = data; - //listen for the forms saving event - unsubscribe.push(scope.$on("formSubmitting", function (ev, args) { - showValidation = true; - if (hasError && scope.errorMsg === "") { - scope.errorMsg = getErrorMsg(); - startWatch(); - } - else if (!hasError) { - scope.errorMsg = ""; - stopWatch(); - } - })); - - //listen for the forms saved event - unsubscribe.push(scope.$on("formSubmitted", function (ev, args) { - showValidation = false; - scope.errorMsg = ""; - formCtrl.$setValidity('valPropertyMsg', true); - stopWatch(); - })); - - //listen for server validation changes - // NOTE: we pass in "" in order to listen for all validation changes to the content property, not for - // validation changes to fields in the property this is because some server side validators may not - // return the field name for which the error belongs too, just the property for which it belongs. - // It's important to note that we need to subscribe to server validation changes here because we always must - // indicate that a content property is invalid at the property level since developers may not actually implement - // the correct field validation in their property editors. - - if (scope.currentProperty) { //this can be null if no property was assigned - - function serverValidationManagerCallback(isValid, propertyErrors, allErrors) { - hasError = !isValid; - if (hasError) { - //set the error message to the server message - scope.errorMsg = propertyErrors[0].errorMsg; - //flag that the current validator is invalid - formCtrl.$setValidity('valPropertyMsg', false); - startWatch(); + //if there's any remaining errors in the server validation service then we should show them. + showValidation = serverValidationManager.items.length > 0; + if (!showValidation) { + //We can either get the form submitted status by the parent directive valFormManager (if we add a property to it) + //or we can just check upwards in the DOM for the css class (easier for now). + //The initial hidden state can't always be hidden because when we switch variants in the content editor we cannot + //reset the status. + showValidation = element.closest(".show-validation").length > 0; } - else { - scope.errorMsg = ""; - //flag that the current validator is valid - formCtrl.$setValidity('valPropertyMsg', true); - stopWatch(); + + //listen for form validation changes. + //The alternative is to add a watch to formCtrl.$invalid but that would lead to many more watches then + // subscribing to this single watch. + valFormManager.onValidationStatusChanged(function (evt, args) { + checkValidationStatus(); + }); + + //listen for the forms saving event + unsubscribe.push(scope.$on("formSubmitting", function (ev, args) { + showValidation = true; + if (hasError && scope.errorMsg === "") { + scope.errorMsg = getErrorMsg(); + startWatch(); + } + else if (!hasError) { + resetError(); + } + })); + + //listen for the forms saved event + unsubscribe.push(scope.$on("formSubmitted", function (ev, args) { + showValidation = false; + resetError(); + })); + + if (scope.currentProperty) { //this can be null if no property was assigned + + // listen for server validation changes for property validation path prefix. + // We pass in "" in order to listen for all validation changes to the content property, not for + // validation changes to fields in the property this is because some server side validators may not + // return the field name for which the error belongs too, just the property for which it belongs. + // It's important to note that we need to subscribe to server validation changes here because we always must + // indicate that a content property is invalid at the property level since developers may not actually implement + // the correct field validation in their property editors. + + function serverValidationManagerCallback(isValid, propertyErrors, allErrors) { + var hadError = hasError; + hasError = !isValid; + if (hasError) { + //set the error message to the server message + scope.errorMsg = propertyErrors.length > 1 ? labels.propertyHasErrors : propertyErrors[0].errorMsg || labels.propertyHasErrors; + //flag that the current validator is invalid + formCtrl.$setValidity('valPropertyMsg', false); + startWatch(); + + // This check is required in order to be able to reset ourselves and is typically for complex editor + // scenarios where the umb-property itself doesn't contain any ng-model controls which means that the + // above serverValidityResetter technique will not work to clear valPropertyMsg errors. + // In order for this to work we rely on the current form controller's $pristine state. This means that anytime + // the form is submitted whether there are validation errors or not the state must be reset... this is automatically + // taken care of with the formHelper.resetForm method that should be used in all cases. $pristine is required because it's + // a value that is cascaded to all form controls based on the hierarchy of child ng-model controls. This allows us to easily + // know if a value has changed. The alternative is what we used to do which was to put a deep $watch on the entire complex value + // which is hugely inefficient. + if (propertyErrors.length === 1 && hadError && !formCtrl.$pristine) { + var propertyValidationPath = umbPropCtrl.getValidationPath(); + serverValidationManager.removePropertyError(propertyValidationPath, currentCulture, "", currentSegment); + resetError(); + } + } + else { + resetError(); + } + } + + unsubscribe.push(serverValidationManager.subscribe( + umbPropCtrl.getValidationPath(), + currentCulture, + "", + serverValidationManagerCallback, + currentSegment, + { matchType: "prefix" } // match property validation path prefix + )); } - } - - unsubscribe.push(serverValidationManager.subscribe(scope.currentProperty.alias, - currentCulture, - "", - serverValidationManagerCallback, - currentSegment - ) - ); + }); } //when the scope is disposed we need to unsubscribe scope.$on('$destroy', function () { stopWatch(); - for (var u in unsubscribe) { - unsubscribe[u](); - } + unsubscribe.forEach(u => u()); }); + + onInit(); } diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valpropertyvalidator.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valpropertyvalidator.directive.js index 37303d22ad..d66e4bd2af 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valpropertyvalidator.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valpropertyvalidator.directive.js @@ -28,9 +28,9 @@ function valPropertyValidator(serverValidationManager) { var modelCtrl = ctrls[0]; var propCtrl = ctrls.length > 1 ? ctrls[1] : null; - - // Check whether the scope has a valPropertyValidator method - if (!scope.valPropertyValidator || !angular.isFunction(scope.valPropertyValidator)) { + + // Check whether the scope has a valPropertyValidator method + if (!scope.valPropertyValidator || !Utilities.isFunction(scope.valPropertyValidator)) { throw new Error('val-property-validator directive must specify a function to call'); } diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valserver.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valserver.directive.js index 3fa9220f7b..4180457792 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valserver.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valserver.directive.js @@ -13,14 +13,14 @@ function valServer(serverValidationManager) { link: function (scope, element, attr, ctrls) { var modelCtrl = ctrls[0]; - var umbPropCtrl = ctrls.length > 1 ? ctrls[1] : null; + var umbPropCtrl = ctrls[1]; if (!umbPropCtrl) { //we cannot proceed, this validator will be disabled return; } // optional reference to the varaint-content-controller, needed to avoid validation when the field is invariant on non-default languages. - var umbVariantCtrl = ctrls.length > 2 ? ctrls[2] : null; + var umbVariantCtrl = ctrls[2]; var currentProperty = umbPropCtrl.property; var currentCulture = currentProperty.culture; @@ -55,8 +55,11 @@ function valServer(serverValidationManager) { } } - //Need to watch the value model for it to change, previously we had subscribed to - //modelCtrl.$viewChangeListeners but this is not good enough if you have an editor that + // Get the property validation path if there is one, this is how wiring up any nested/virtual property validation works + var propertyValidationPath = umbPropCtrl ? umbPropCtrl.getValidationPath() : currentProperty.alias; + + // Need to watch the value model for it to change, previously we had subscribed to + // modelCtrl.$viewChangeListeners but this is not good enough if you have an editor that // doesn't specifically have a 2 way ng binding. This is required because when we // have a server error we actually invalidate the form which means it cannot be // resubmitted. So once a field is changed that has a server error assigned to it @@ -69,14 +72,16 @@ function valServer(serverValidationManager) { return modelCtrl.$modelValue; }, function (newValue, oldValue) { - if (!newValue || angular.equals(newValue, oldValue)) { + if (!newValue || Utilities.equals(newValue, oldValue)) { return; } if (modelCtrl.$invalid) { modelCtrl.$setValidity('valServer', true); + //clear the server validation entry - serverValidationManager.removePropertyError(currentProperty.alias, currentCulture, fieldName, currentSegment); + serverValidationManager.removePropertyError(propertyValidationPath, currentCulture, fieldName, currentSegment); + stopWatch(); } }, true); @@ -105,7 +110,9 @@ function valServer(serverValidationManager) { stopWatch(); } } - unsubscribe.push(serverValidationManager.subscribe(currentProperty.alias, + + unsubscribe.push(serverValidationManager.subscribe( + propertyValidationPath, currentCulture, fieldName, serverValidationManagerCallback, @@ -114,9 +121,7 @@ function valServer(serverValidationManager) { scope.$on('$destroy', function () { stopWatch(); - for (var u in unsubscribe) { - unsubscribe[u](); - } + unsubscribe.forEach(u => u()); }); } }; diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valservermatch.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valservermatch.directive.js new file mode 100644 index 0000000000..5f8600c8c0 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valservermatch.directive.js @@ -0,0 +1,125 @@ +/** + * @ngdoc directive + * @name umbraco.directives.directive:valServerMatch + * @restrict A + * @description A custom validator applied to a form/ng-form within an umbProperty that validates server side validation data + * contained within the serverValidationManager. The data can be matched on "exact", "prefix", "suffix" or "contains" matches against + * a property validation key. The attribute value can be in multiple value types: + * - STRING = The property validation key to have an exact match on. If matched, then the form will have a valServerMatch validator applied. + * - OBJECT = A dictionary where the key is the match type: "contains", "prefix", "suffix" and the value is either: + * - ARRAY = A list of property validation keys to match on. If any are matched then the form will have a valServerMatch validator applied. + * - OBJECT = A dictionary where the key is the validator error name applied to the form and the value is the STRING of the property validation key to match on +**/ +function valServerMatch(serverValidationManager) { + + return { + require: ['form', '^^umbProperty', '?^^umbVariantContent'], + restrict: "A", + scope: { + valServerMatch: "=" + }, + link: function (scope, element, attr, ctrls) { + + var formCtrl = ctrls[0]; + var umbPropCtrl = ctrls[1]; + if (!umbPropCtrl) { + //we cannot proceed, this validator will be disabled + return; + } + + // optional reference to the varaint-content-controller, needed to avoid validation when the field is invariant on non-default languages. + var umbVariantCtrl = ctrls[2]; + + var currentProperty = umbPropCtrl.property; + var currentCulture = currentProperty.culture; + var currentSegment = currentProperty.segment; + + if (umbVariantCtrl) { + //if we are inside of an umbVariantContent directive + + var currentVariant = umbVariantCtrl.editor.content; + + // Lets check if we have variants and we are on the default language then ... + if (umbVariantCtrl.content.variants.length > 1 && (!currentVariant.language || !currentVariant.language.isDefault) && !currentCulture && !currentSegment && !currentProperty.unlockInvariantValue) { + //This property is locked cause its a invariant property shown on a non-default language. + //Therefor do not validate this field. + return; + } + } + + // if we have reached this part, and there is no culture, then lets fallback to invariant. To get the validation feedback for invariant language. + currentCulture = currentCulture || "invariant"; + + var unsubscribe = []; + + function bindCallback(validationKey, matchVal, matchType) { + + if (!matchVal) return; + + if (Utilities.isString(matchVal)) { + matchVal = [matchVal]; // normalize to an array since the value can also natively be an array + } + + // match for each string in the array + matchVal.forEach(m => { + unsubscribe.push(serverValidationManager.subscribe( + m, + currentCulture, + "", + // the callback + function (isValid, propertyErrors, allErrors) { + if (!isValid) { + formCtrl.$setValidity(validationKey, false); + } + else { + formCtrl.$setValidity(validationKey, true); + } + }, + currentSegment, + matchType ? { matchType: matchType } : null // specify the match type + )); + }); + + } + + if (Utilities.isObject(scope.valServerMatch)) { + var allowedKeys = ["contains", "prefix", "suffix"]; + Object.keys(scope.valServerMatch).forEach(matchType => { + if (allowedKeys.indexOf(matchType) === -1) { + throw "valServerMatch dictionary keys must be one of " + allowedKeys.join(); + } + + var matchVal = scope.valServerMatch[matchType]; + + if (Utilities.isObject(matchVal)) { + + // as an object, the key will be the validation error instead of the default "valServerMatch" + Object.keys(matchVal).forEach(valKey => { + + // matchVal[valKey] can be an ARRAY or a STRING + bindCallback(valKey, matchVal[valKey], matchType); + }); + } + else { + + // matchVal can be an ARRAY or a STRING + bindCallback("valServerMatch", matchVal, matchType); + } + }); + } + else if (Utilities.isString(scope.valServerMatch)) { + + // a STRING match which will be an exact match on the string supplied as the property validation key + bindCallback("valServerMatch", scope.valServerMatch, null); + } + else { + throw "valServerMatch value must be a string or a dictionary"; + } + + scope.$on('$destroy', function () { + unsubscribe.forEach(u => u()); + }); + } + }; +} +angular.module('umbraco.directives.validation').directive("valServerMatch", valServerMatch); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valsubview.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valsubview.directive.js index 1f5aaaa1c2..97d11879e9 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valsubview.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valsubview.directive.js @@ -1,36 +1,49 @@ /** -* @ngdoc directive -* @name umbraco.directives.directive:valSubView -* @restrict A -* @description Used to show validation warnings for a editor sub view to indicate that the section content has validation errors in its data. -* In order for this directive to work, the valFormManager directive must be placed on the containing form. + * @ngdoc directive + * @name umbraco.directives.directive:valSubView + * @restrict A + * @description Used to show validation warnings for a editor sub view (used in conjunction with: + * umb-editor-sub-view or umb-editor-sub-views) to indicate that the section content has validation errors in its data. + * In order for this directive to work, the valFormManager directive must be placed on the containing form. + * When applied to **/ (function () { 'use strict'; + // Since this is a directive applied as an attribute, the value of that attribtue is the 'model' object property + // of the current inherited scope that the hasError/errorClass properties will apply to. + // This directive cannot have it's own scope because it's an attribute applied to another scoped directive. + // Due to backwards compatibility we can't really change this, ideally this would have it's own scope/properties. + function valSubViewDirective() { - function controller($scope, $element) { + function controller($scope, $element, $attrs) { + + var model = $scope.model; // this is the default and required for backwards compat + if ($attrs && $attrs.valSubView) { + // get the property to use + model = $scope[$attrs.valSubView]; + } + //expose api return { valStatusChanged: function (args) { - // TODO: Verify this is correct, does $scope.model ever exist? - if ($scope.model) { + if (model) { if (!args.form.$valid) { var subViewContent = $element.find(".ng-invalid"); if (subViewContent.length > 0) { - $scope.model.hasError = true; - $scope.model.errorClass = args.showValidation ? 'show-validation' : null; + model.hasError = true; + model.errorClass = args.showValidation ? 'show-validation' : null; } else { - $scope.model.hasError = false; - $scope.model.errorClass = null; + model.hasError = false; + model.errorClass = null; } } else { - $scope.model.hasError = false; - $scope.model.errorClass = null; + model.hasError = false; + model.errorClass = null; } } } @@ -40,12 +53,18 @@ function link(scope, el, attr, ctrl) { //if there are no containing form or valFormManager controllers, then we do nothing - if (!ctrl || !Utilities.isArray(ctrl) || ctrl.length !== 2 || !ctrl[0] || !ctrl[1]) { + if (!ctrl[1]) { return; } + var model = scope.model; // this is the default and required for backwards compat + if (attr && attr.valSubView) { + // get the property to use + model = scope[attr.valSubView]; + } + var valFormManager = ctrl[1]; - scope.model.hasError = false; + model.hasError = false; //listen for form validation changes valFormManager.onValidationStatusChanged(function (evt, args) { @@ -54,14 +73,14 @@ var subViewContent = el.find(".ng-invalid"); if (subViewContent.length > 0) { - scope.model.hasError = true; + model.hasError = true; } else { - scope.model.hasError = false; + model.hasError = false; } } else { - scope.model.hasError = false; + model.hasError = false; } }); diff --git a/src/Umbraco.Web.UI.Client/src/common/filters/compareArrays.filter.js b/src/Umbraco.Web.UI.Client/src/common/filters/compareArrays.filter.js index 13f603260d..0cbf3077e6 100644 --- a/src/Umbraco.Web.UI.Client/src/common/filters/compareArrays.filter.js +++ b/src/Umbraco.Web.UI.Client/src/common/filters/compareArrays.filter.js @@ -2,13 +2,17 @@ angular.module("umbraco.filters") .filter('compareArrays', function() { return function inArray(array, compareArray, compareProperty) { + if (!compareArray || !compareArray.length) { + return [...array]; + } + var result = []; - angular.forEach(array, function(arrayItem){ + array.forEach(function(arrayItem){ var exists = false; - angular.forEach(compareArray, function(compareItem){ + compareArray.forEach(function(compareItem){ if( arrayItem[compareProperty] === compareItem[compareProperty]) { exists = true; } diff --git a/src/Umbraco.Web.UI.Client/src/common/filters/umbCmsJoinArray.filter.js b/src/Umbraco.Web.UI.Client/src/common/filters/umbCmsJoinArray.filter.js index a519f81054..870e497541 100644 --- a/src/Umbraco.Web.UI.Client/src/common/filters/umbCmsJoinArray.filter.js +++ b/src/Umbraco.Web.UI.Client/src/common/filters/umbCmsJoinArray.filter.js @@ -13,7 +13,7 @@ */ angular.module("umbraco.filters").filter('umbCmsJoinArray', function () { return function join(array, separator, prop) { - return (!angular.isUndefined(prop) ? array.map(function (item) { + return (!Utilities.isUndefined(prop) ? array.map(function (item) { return item[prop]; }) : array).join(separator || ''); }; diff --git a/src/Umbraco.Web.UI.Client/src/common/filters/umbwordlimit.filter.js b/src/Umbraco.Web.UI.Client/src/common/filters/umbwordlimit.filter.js index 93f0bddc96..bd597b21d0 100644 --- a/src/Umbraco.Web.UI.Client/src/common/filters/umbwordlimit.filter.js +++ b/src/Umbraco.Web.UI.Client/src/common/filters/umbwordlimit.filter.js @@ -17,7 +17,7 @@ return collection; } - if (angular.isUndefined(property)) { + if (Utilities.isUndefined(property)) { return collection; } diff --git a/src/Umbraco.Web.UI.Client/src/common/interceptors/_module.js b/src/Umbraco.Web.UI.Client/src/common/interceptors/_module.js index 69a4fe35c9..6b2d993fd5 100644 --- a/src/Umbraco.Web.UI.Client/src/common/interceptors/_module.js +++ b/src/Umbraco.Web.UI.Client/src/common/interceptors/_module.js @@ -6,6 +6,7 @@ angular.module('umbraco.interceptors', []) $httpProvider.interceptors.push('securityInterceptor'); $httpProvider.interceptors.push('debugRequestInterceptor'); + $httpProvider.interceptors.push('requiredHeadersInterceptor'); $httpProvider.interceptors.push('doNotPostDollarVariablesOnPostRequestInterceptor'); $httpProvider.interceptors.push('cultureRequestInterceptor'); diff --git a/src/Umbraco.Web.UI.Client/src/common/interceptors/requiredheaders.interceptor.js b/src/Umbraco.Web.UI.Client/src/common/interceptors/requiredheaders.interceptor.js new file mode 100644 index 0000000000..3d96b86acd --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/interceptors/requiredheaders.interceptor.js @@ -0,0 +1,28 @@ +(function () { + 'use strict'; + + /** + * Used to set required headers on all requests where necessary + * @param {any} $q + * @param {any} urlHelper + */ + function requiredHeadersInterceptor($q, urlHelper) { + return { + //dealing with requests: + 'request': function (config) { + + // This is a standard header that should be sent for all ajax requests and is required for + // how the server handles auth rejections, etc... see + // https://github.com/aspnet/AspNetKatana/blob/e2b18ec84ceab7ffa29d80d89429c9988ab40144/src/Microsoft.Owin.Security.Cookies/Provider/DefaultBehavior.cs + // https://brockallen.com/2013/10/27/using-cookie-authentication-middleware-with-web-api-and-401-response-codes/ + config.headers["X-Requested-With"] = "XMLHttpRequest"; + + return config; + } + }; + } + + angular.module('umbraco.interceptors').factory('requiredHeadersInterceptor', requiredHeadersInterceptor); + + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/common/mocks/resources/_utils.js b/src/Umbraco.Web.UI.Client/src/common/mocks/resources/_utils.js index f07a2a441c..8293443dd4 100644 --- a/src/Umbraco.Web.UI.Client/src/common/mocks/resources/_utils.js +++ b/src/Umbraco.Web.UI.Client/src/common/mocks/resources/_utils.js @@ -1,5 +1,5 @@ angular.module('umbraco.mocks'). - factory('mocksUtils', ['$cookies', function ($cookies) { + factory('mocksUtils', ['$cookies', 'udiService', function ($cookies, udiService) { 'use strict'; //by default we will perform authorization @@ -40,13 +40,17 @@ angular.module('umbraco.mocks'). }, /** Creats a mock content object */ - getMockContent: function(id) { + getMockContent: function (id, key, udi) { + key = key || String.CreateGuid(); + var udi = udi || udiService.build("content", key); var node = { name: "My content with id: " + id, updateDate: new Date().toIsoDateTimeString(), publishDate: new Date().toIsoDateTimeString(), createDate: new Date().toIsoDateTimeString(), id: id, + key: key, + udi: udi, parentId: 1234, icon: "icon-umb-content", owner: { name: "Administrator", id: 0 }, @@ -280,6 +284,182 @@ angular.module('umbraco.mocks'). return node; }, + + /** Creats a mock variant content object */ + getMockVariantContent: function(id, key, udi) { + key = key || String.CreateGuid(); + var udi = udi || udiService.build("content", key); + var node = { + name: "My content with id: " + id, + updateDate: new Date().toIsoDateTimeString(), + publishDate: new Date().toIsoDateTimeString(), + createDate: new Date().toIsoDateTimeString(), + id: id, + key: key, + udi: udi, + parentId: 1234, + icon: "icon-umb-content", + owner: { name: "Administrator", id: 0 }, + updater: { name: "Per Ploug Krogslund", id: 1 }, + path: "-1,1234,2455", + allowedActions: ["U", "H", "A"], + contentTypeAlias: "testAlias", + contentTypeKey: "7C5B74D1-E2F9-45A3-AE4B-FC7A829BF8AB", + apps: [], + variants: [ + { + name: "", + language: null, + segment: null, + state: "NotCreated", + updateDate: "0001-01-01 00:00:00", + createDate: "0001-01-01 00:00:00", + publishDate: null, + releaseDate: null, + expireDate: null, + notifications: [], + tabs: [ + { + label: "Content", + id: 2, + properties: [ + { alias: "testproperty", label: "Test property", view: "textbox", value: "asdfghjk" }, + { alias: "valTest", label: "Validation test", view: "validationtest", value: "asdfasdf" }, + { alias: "bodyText", label: "Body Text", description: "Here you enter the primary article contents", view: "rte", value: "

askjdkasj lasjd

", config: {} }, + { alias: "textarea", label: "textarea", view: "textarea", value: "ajsdka sdjkds", config: { rows: 4 } }, + { alias: "media", label: "Media picker", view: "mediapicker", value: "1234,23242,23232,23231", config: {multiPicker: 1} } + ] + }, + { + label: "Sample Editor", + id: 3, + properties: [ + { alias: "datepicker", label: "Datepicker", view: "datepicker", config: { pickTime: false, format: "yyyy-MM-dd" } }, + { alias: "tags", label: "Tags", view: "tags", value: "" } + ] + }, + { + label: "This", + id: 4, + properties: [ + { alias: "valTest4", label: "Validation test", view: "validationtest", value: "asdfasdf" }, + { alias: "bodyText4", label: "Body Text", description: "Here you enter the primary article contents", view: "rte", value: "

askjdkasj lasjd

", config: {} }, + { alias: "textarea4", label: "textarea", view: "textarea", value: "ajsdka sdjkds", config: { rows: 4 } }, + { alias: "content4", label: "Content picker", view: "contentpicker", value: "1234,23242,23232,23231" } + ] + }, + { + label: "Is", + id: 5, + properties: [ + { alias: "valTest5", label: "Validation test", view: "validationtest", value: "asdfasdf" }, + { alias: "bodyText5", label: "Body Text", description: "Here you enter the primary article contents", view: "rte", value: "

askjdkasj lasjd

", config: {} }, + { alias: "textarea5", label: "textarea", view: "textarea", value: "ajsdka sdjkds", config: { rows: 4 } }, + { alias: "content5", label: "Content picker", view: "contentpicker", value: "1234,23242,23232,23231" } + ] + }, + { + label: "Overflown", + id: 6, + properties: [ + { alias: "valTest6", label: "Validation test", view: "validationtest", value: "asdfasdf" }, + { alias: "bodyText6", label: "Body Text", description: "Here you enter the primary article contents", view: "rte", value: "

askjdkasj lasjd

", config: {} }, + { alias: "textarea6", label: "textarea", view: "textarea", value: "ajsdka sdjkds", config: { rows: 4 } }, + { alias: "content6", label: "Content picker", view: "contentpicker", value: "1234,23242,23232,23231" } + ] + }, + { + label: "Generic Properties", + id: 0, + properties: [ + { + label: 'Id', + value: 1234, + view: "readonlyvalue", + alias: "_umb_id" + }, + { + label: 'Created by', + description: 'Original author', + value: "Administrator", + view: "readonlyvalue", + alias: "_umb_createdby" + }, + { + label: 'Created', + description: 'Date/time this document was created', + value: new Date().toIsoDateTimeString(), + view: "readonlyvalue", + alias: "_umb_createdate" + }, + { + label: 'Updated', + description: 'Date/time this document was created', + value: new Date().toIsoDateTimeString(), + view: "readonlyvalue", + alias: "_umb_updatedate" + }, + { + label: 'Document Type', + value: "Home page", + view: "readonlyvalue", + alias: "_umb_doctype" + }, + { + label: 'Publish at', + description: 'Date/time to publish this document', + value: new Date().toIsoDateTimeString(), + view: "datepicker", + alias: "_umb_releasedate" + }, + { + label: 'Unpublish at', + description: 'Date/time to un-publish this document', + value: new Date().toIsoDateTimeString(), + view: "datepicker", + alias: "_umb_expiredate" + }, + { + label: 'Template', + value: "myTemplate", + view: "dropdown", + alias: "_umb_template", + config: { + items: { + "" : "-- Choose template --", + "myTemplate" : "My Templates", + "home" : "Home Page", + "news" : "News Page" + } + } + }, + { + label: 'Link to document', + value: ["/testing" + id, "http://localhost/testing" + id, "http://mydomain.com/testing" + id].join(), + view: "urllist", + alias: "_umb_urllist" + }, + { + alias: "test", label: "Stuff", view: "test", value: "", + config: { + fields: [ + { alias: "embedded", label: "Embbeded", view: "textstring", value: "" }, + { alias: "embedded2", label: "Embbeded 2", view: "contentpicker", value: "" }, + { alias: "embedded3", label: "Embbeded 3", view: "textarea", value: "" }, + { alias: "embedded4", label: "Embbeded 4", view: "datepicker", value: "" } + ] + } + } + ] + } + ] + } + ] + }; + + return node; + }, + getMockEntity : function(id){ return {name: "hello", id: id, icon: "icon-file"}; }, diff --git a/src/Umbraco.Web.UI.Client/src/common/mocks/resources/media.mocks.js b/src/Umbraco.Web.UI.Client/src/common/mocks/resources/media.mocks.js index 3b58c127a7..aeb6229360 100644 --- a/src/Umbraco.Web.UI.Client/src/common/mocks/resources/media.mocks.js +++ b/src/Umbraco.Web.UI.Client/src/common/mocks/resources/media.mocks.js @@ -8,12 +8,12 @@ angular.module('umbraco.mocks'). } function returnNodebyIds(status, data, headers) { - var ids = mocksUtils.getParameterByName(data, "ids") || "1234,1234,4234"; + var ids = mocksUtils.getParameterByName(data, "ids") || ['1234','1234','4234']; var items = []; - _.each(ids, function(id){ - items.push(_getNode( parseInt( id, 10 )) ); - }); + for (var i = 0; i < ids.length; i += 1) { + items.push(_getNode(parseInt(ids[i], 10))); + } return [200, items, null]; } @@ -26,8 +26,6 @@ angular.module('umbraco.mocks'). var id = mocksUtils.getParameterByName(data, "id") || 1234; id = parseInt(id, 10); - - return [200, _getNode(id), null]; } diff --git a/src/Umbraco.Web.UI.Client/src/common/mocks/resources/variantcontent.mocks.js b/src/Umbraco.Web.UI.Client/src/common/mocks/resources/variantcontent.mocks.js new file mode 100644 index 0000000000..3a434bdadc --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/mocks/resources/variantcontent.mocks.js @@ -0,0 +1,56 @@ +angular.module('umbraco.mocks'). + factory('variantContentMocks', ['$httpBackend', 'mocksUtils', function ($httpBackend, mocksUtils) { + 'use strict'; + + function returnEmptyVariantNode(status, data, headers) { + + if (!mocksUtils.checkAuth()) { + return [401, null, null]; + } + + var response = returnVariantNodebyId(200, "", null); + var node = response[1]; + var parentId = mocksUtils.getParameterByName(data, "parentId") || 1234; + + node.name = ""; + node.id = 0; + node.parentId = parentId; + + node.tabs.forEach(function(tab) { + tab.properties.forEach(function(property) { + property.value = ""; + }); + }); + + return response; + } + + function returnVariantNodebyId(status, data, headers) { + + if (!mocksUtils.checkAuth()) { + return [401, null, null]; + } + + var id = mocksUtils.getParameterByName(data, "id") || "1234"; + id = parseInt(id, 10); + + var node = mocksUtils.getMockVariantContent(id); + + return [200, node, null]; + } + + + return { + register: function () { + + $httpBackend + .whenGET(mocksUtils.urlRegex('/umbraco/UmbracoApi/Content/GetById?')) + .respond(returnVariantNodebyId); + + $httpBackend + .whenGET(mocksUtils.urlRegex('/umbraco/UmbracoApi/Content/GetEmpty')) + .respond(returnEmptyVariantNode); + + } + }; +}]); diff --git a/src/Umbraco.Web.UI.Client/src/common/mocks/umbraco.servervariables.js b/src/Umbraco.Web.UI.Client/src/common/mocks/umbraco.servervariables.js index da6f78a6a5..868bc4c6d5 100644 --- a/src/Umbraco.Web.UI.Client/src/common/mocks/umbraco.servervariables.js +++ b/src/Umbraco.Web.UI.Client/src/common/mocks/umbraco.servervariables.js @@ -19,7 +19,8 @@ Umbraco.Sys.ServerVariables = { "dashboardApiBaseUrl": "/umbraco/UmbracoApi/Dashboard/", "updateCheckApiBaseUrl": "/umbraco/Api/UpdateCheck/", "relationApiBaseUrl": "/umbraco/UmbracoApi/Relation/", - "rteApiBaseUrl": "/umbraco/UmbracoApi/RichTextPreValue/" + "rteApiBaseUrl": "/umbraco/UmbracoApi/RichTextPreValue/", + "iconApiBaseUrl": "/umbraco/UmbracoApi/Icon/" }, umbracoSettings: { "umbracoPath": "/umbraco", diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/auth.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/auth.resource.js index 936f69e738..e26ac26f7c 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/auth.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/auth.resource.js @@ -12,7 +12,24 @@ function authResource($q, $http, umbRequestHelper, angularHelper) { return { - + /** + * @ngdoc method + * @name umbraco.resources.authResource#get2FAProviders + * @methodOf umbraco.resources.authResource + * + * @description + * Logs the Umbraco backoffice user in if the credentials are good + * + * ##usage + *
+     * authResource.get2FAProviders()
+     *    .then(function(data) {
+     *        //Do stuff ...
+     *    });
+     * 
+ * @returns {Promise} resourcePromise object + * + */ get2FAProviders: function () { return umbRequestHelper.resourcePromise( @@ -23,6 +40,25 @@ function authResource($q, $http, umbRequestHelper, angularHelper) { 'Could not retrive two factor provider info'); }, + /** + * @ngdoc method + * @name umbraco.resources.authResource#get2FAProviders + * @methodOf umbraco.resources.authResource + * + * @description + * Generate the two-factor authentication code for the provider and send it to the user + * + * ##usage + *
+    * authResource.send2FACode(provider)
+    *    .then(function(data) {
+    *        //Do stuff ...
+    *    });
+    * 
+ * @param {string} provider Name of the provider + * @returns {Promise} resourcePromise object + * + */ send2FACode: function (provider) { return umbRequestHelper.resourcePromise( @@ -34,6 +70,26 @@ function authResource($q, $http, umbRequestHelper, angularHelper) { 'Could not send code'); }, + /** + * @ngdoc method + * @name umbraco.resources.authResource#get2FAProviders + * @methodOf umbraco.resources.authResource + * + * @description + * Verify the two-factor authentication code entered by the user against the provider + * + * ##usage + *
+    * authResource.verify2FACode(provider, code)
+    *    .then(function(data) {
+    *        //Do stuff ...
+    *    });
+    * 
+ * @param {string} provider Name of the provider + * @param {string} code The two-factor authentication code + * @returns {Promise} resourcePromise object + * + */ verify2FACode: function (provider, code) { return umbRequestHelper.resourcePromise( diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js index 7bc2a9d2c8..368eab2339 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js @@ -42,6 +42,25 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { return { + /** + * @ngdoc method + * @name umbraco.resources.contentResource#allowsCultureVariation + * @methodOf umbraco.resources.contentResource + * + * @description + * Check whether any content types have culture variant enabled + * + * ##usage + *
+        * contentResource.allowsCultureVariation()
+        *    .then(function() {
+        *       Do stuff...
+        *    });
+        * 
+ * + * @returns {Promise} resourcePromise object. + * + */ allowsCultureVariation: function () { return umbRequestHelper.resourcePromise( $http.get( @@ -51,6 +70,26 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { 'Failed to retrieve variant content types'); }, + /** + * @ngdoc method + * @name umbraco.resources.contentResource#savePermissions + * @methodOf umbraco.resources.contentResource + * + * @description + * Save user group permissions for the content + * + * ##usage + *
+        * contentResource.savePermissions(saveModel)
+        *    .then(function() {
+        *       Do stuff...
+        *    });
+        * 
+ * + * @param {object} The object which contains the user group permissions for the content + * @returns {Promise} resourcePromise object. + * + */ savePermissions: function (saveModel) { if (!saveModel) { throw "saveModel cannot be null"; @@ -68,7 +107,25 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { 'Failed to save permissions'); }, - + /** + * @ngdoc method + * @name umbraco.resources.contentResource#getRecycleBin + * @methodOf umbraco.resources.contentResource + * + * @description + * Get the recycle bin + * + * ##usage + *
+        * contentResource.getRecycleBin()
+        *    .then(function() {
+        *       Do stuff...
+        *    });
+        * 
+ * + * @returns {Promise} resourcePromise object. + * + */ getRecycleBin: function () { return umbRequestHelper.resourcePromise( $http.get( @@ -337,6 +394,26 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { 'Failed to delete item ' + id); }, + /** + * @ngdoc method + * @name umbraco.resources.contentResource#deleteBlueprint + * @methodOf umbraco.resources.contentResource + * + * @description + * Deletes a content blueprint item with a given id + * + * ##usage + *
+        * contentResource.deleteBlueprint(1234)
+        *    .then(function() {
+        *        alert('its gone!');
+        *    });
+        * 
+ * + * @param {Int} id id of content blueprint item to delete + * @returns {Promise} resourcePromise object. + * + */ deleteBlueprint: function (id) { return umbRequestHelper.resourcePromise( $http.post( @@ -382,29 +459,90 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { }); }, + /** + * @ngdoc method + * @name umbraco.resources.contentResource#getBlueprintById + * @methodOf umbraco.resources.contentResource + * + * @description + * Gets a content blueprint item with a given id + * + * ##usage + *
+        * contentResource.getBlueprintById(1234)
+        *    .then(function() {
+        *       Do stuff...
+        *    });
+        * 
+ * + * @param {Int} id id of content blueprint item to retrieve + * @returns {Promise} resourcePromise object. + * + */ getBlueprintById: function (id) { return umbRequestHelper.resourcePromise( $http.get( umbRequestHelper.getApiUrl( "contentApiBaseUrl", "GetBlueprintById", - [{ id: id }])), + { id: id })), 'Failed to retrieve data for content id ' + id) .then(function (result) { return $q.when(umbDataFormatter.formatContentGetData(result)); }); }, + /** + * @ngdoc method + * @name umbraco.resources.contentResource#getNotifySettingsById + * @methodOf umbraco.resources.contentResource + * + * @description + * Gets notification options for a content item with a given id for the current user + * + * ##usage + *
+        * contentResource.getNotifySettingsById(1234)
+        *    .then(function() {
+        *       Do stuff...
+        *    });
+        * 
+ * + * @param {Int} id id of content item + * @returns {Promise} resourcePromise object. + * + */ getNotifySettingsById: function (id) { return umbRequestHelper.resourcePromise( $http.get( umbRequestHelper.getApiUrl( "contentApiBaseUrl", "GetNotificationOptions", - [{ contentId: id }])), + { contentId: id })), 'Failed to retrieve data for content id ' + id); }, + /** + * @ngdoc method + * @name umbraco.resources.contentResource#getNotifySettingsById + * @methodOf umbraco.resources.contentResource + * + * @description + * Sets notification settings for a content item with a given id for the current user + * + * ##usage + *
+        * contentResource.setNotifySettingsById(1234,["D", "F", "H"])
+        *    .then(function() {
+        *       Do stuff...
+        *    });
+        * 
+ * + * @param {Int} id id of content item + * @param {Array} options the notification options to set for the content item + * @returns {Promise} resourcePromise object. + * + */ setNotifySettingsById: function (id, options) { if (!id) { throw "contentId cannot be null"; @@ -442,9 +580,7 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { getByIds: function (ids) { var idQuery = ""; - _.each(ids, function (item) { - idQuery += "ids=" + item + "&"; - }); + ids.forEach(id => idQuery += `ids=${id}&`); return umbRequestHelper.resourcePromise( $http.get( @@ -455,9 +591,7 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { 'Failed to retrieve data for content with multiple ids') .then(function (result) { //each item needs to be re-formatted - _.each(result, function (r) { - umbDataFormatter.formatContentGetData(r) - }); + result.forEach(r => umbDataFormatter.formatContentGetData(r)); return $q.when(result); }); }, @@ -502,12 +636,57 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { umbRequestHelper.getApiUrl( "contentApiBaseUrl", "GetEmpty", - [{ contentTypeAlias: alias }, { parentId: parentId }])), + { contentTypeAlias: alias, parentId: parentId })), 'Failed to retrieve data for empty content item type ' + alias) .then(function (result) { return $q.when(umbDataFormatter.formatContentGetData(result)); }); }, + /** + * @ngdoc method + * @name umbraco.resources.contentResource#getScaffoldByKey + * @methodOf umbraco.resources.contentResource + * + * @description + * Returns a scaffold of an empty content item, given the id of the content item to place it underneath and the content type alias. + * + * - Parent Id must be provided so umbraco knows where to store the content + * - Content Type Id must be provided so umbraco knows which properties to put on the content scaffold + * + * The scaffold is used to build editors for content that has not yet been populated with data. + * + * ##usage + *
+         * contentResource.getScaffoldByKey(1234, '...')
+         *    .then(function(scaffold) {
+         *        var myDoc = scaffold;
+          *        myDoc.name = "My new document";
+         *
+         *        contentResource.publish(myDoc, true)
+         *            .then(function(content){
+         *                alert("Retrieved, updated and published again");
+         *            });
+         *    });
+          * 
+ * + * @param {Int} parentId id of content item to return + * @param {String} contentTypeGuid contenttype guid to base the scaffold on + * @returns {Promise} resourcePromise object containing the content scaffold. + * + */ + getScaffoldByKey: function (parentId, contentTypeKey) { + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "GetEmptyByKey", + { contentTypeKey: contentTypeKey, parentId: parentId })), + 'Failed to retrieve data for empty content item id ' + contentTypeKey) + .then(function (result) { + return $q.when(umbDataFormatter.formatContentGetData(result)); + }); + }, getBlueprintScaffold: function (parentId, blueprintId) { @@ -516,7 +695,7 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { umbRequestHelper.getApiUrl( "contentApiBaseUrl", "GetEmpty", - [{ blueprintId: blueprintId }, { parentId: parentId }])), + { blueprintId: blueprintId, parentId: parentId })), 'Failed to retrieve blueprint for id ' + blueprintId) .then(function (result) { return $q.when(umbDataFormatter.formatContentGetData(result)); @@ -656,7 +835,7 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { * @methodOf umbraco.resources.contentResource * * @description - * Saves changes made to a content item to its current version, if the content item is new, the isNew paramater must be passed to force creation + * Saves changes made to a content item to its current version, if the content item is new, the isNew parameter must be passed to force creation * if the content item needs to have files attached, they must be provided as the files param and passed separately * * @@ -686,6 +865,34 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { return saveContentItem(content, "save" + (isNew ? "New" : ""), files, endpoint, showNotifications); }, + /** + * @ngdoc method + * @name umbraco.resources.contentResource#saveBlueprint + * @methodOf umbraco.resources.contentResource + * + * @description + * Saves changes made to a content blueprint item to its current version, if the content blueprint item is new, the isNew parameter must be passed to force creation + * if the content item needs to have files attached, they must be provided as the files param and passed separately + * + * ##usage + *
+        * contentResource.getById(1234)
+        *    .then(function(content) {
+        *          content.name = "I want a new name!";
+        *          contentResource.saveBlueprint(content, false)
+        *            .then(function(content){
+        *                alert("Retrieved, updated and saved again");
+        *            });
+        *    });
+        * 
+ * + * @param {Object} content The content blueprint item object with changes applied + * @param {Bool} isNew set to true to create a new item or to update an existing + * @param {Array} files collection of files for the document + * @param {Bool} showNotifications an option to disable/show notifications (default is true) + * @returns {Promise} resourcePromise object containing the saved content item. + * + */ saveBlueprint: function (content, isNew, files, showNotifications) { var endpoint = umbRequestHelper.getApiUrl( "contentApiBaseUrl", @@ -699,7 +906,7 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { * @methodOf umbraco.resources.contentResource * * @description - * Saves and publishes changes made to a content item to a new version, if the content item is new, the isNew paramater must be passed to force creation + * Saves and publishes changes made to a content item to a new version, if the content item is new, the isNew parameter must be passed to force creation * if the content item needs to have files attached, they must be provided as the files param and passed separately * * @@ -729,6 +936,35 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { return saveContentItem(content, "publish" + (isNew ? "New" : ""), files, endpoint, showNotifications); }, + /** + * @ngdoc method + * @name umbraco.resources.contentResource#publish + * @methodOf umbraco.resources.contentResource + * + * @description + * Saves and publishes changes made to a content item and its descendants to a new version, if the content item is new, the isNew parameter must be passed to force creation + * if the content items needs to have files attached, they must be provided as the files param and passed separately + * + * + * ##usage + *
+        * contentResource.getById(1234)
+        *    .then(function(content) {
+        *          content.name = "I want a new name, and be published!";
+        *          contentResource.publishWithDescendants(content, false)
+        *            .then(function(content){
+        *                alert("Retrieved, updated and published again");
+        *            });
+        *    });
+        * 
+ * + * @param {Object} content The content item object with changes applied + * @param {Bool} isNew set to true to create a new item or to update an existing + * @param {Array} files collection of files for the document + * @param {Bool} showNotifications an option to disable/show notifications (default is true) + * @returns {Promise} resourcePromise object containing the saved content item. + * + */ publishWithDescendants: function (content, isNew, force, files, showNotifications) { var endpoint = umbRequestHelper.getApiUrl( "contentApiBaseUrl", @@ -832,6 +1068,27 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { }, + /** + * @ngdoc method + * @name umbraco.resources.contentResource#createBlueprintFromContent + * @methodOf umbraco.resources.contentResource + * + * @description + * Creates a content blueprint with a given name from a given content id + * + * ##usage + *
+        * contentResource.createBlueprintFromContent(1234,"name")
+        *    .then(function(content) {
+        *        alert("created");
+        *    });
+            * 
+ * + * @param {Int} id The ID of the content to create the content blueprint from + * @param {string} id The name of the content blueprint + * @returns {Promise} resourcePromise object + * + */ createBlueprintFromContent: function (contentId, name) { return umbRequestHelper.resourcePromise( $http.post( diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/contenttype.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/contenttype.resource.js index 97bebef062..6acf702546 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/contenttype.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/contenttype.resource.js @@ -7,6 +7,25 @@ function contentTypeResource($q, $http, umbRequestHelper, umbDataFormatter, loca return { + /** + * @ngdoc method + * @name umbraco.resources.contentTypeResource#getCount + * @methodOf umbraco.resources.contentTypeResource + * + * @description + * Gets the count of content types + * + * ##usage + *
+        * contentTypeResource.getCount()
+        *    .then(function(data) {
+        *        console.log(data);
+        *    });
+        * 
+ * + * @returns {Promise} resourcePromise object. + * + */ getCount: function () { return umbRequestHelper.resourcePromise( $http.get( @@ -16,6 +35,29 @@ function contentTypeResource($q, $http, umbRequestHelper, umbDataFormatter, loca 'Failed to retrieve count'); }, + /** + * @ngdoc method + * @name umbraco.resources.contentTypeResource#getAvailableCompositeContentTypes + * @methodOf umbraco.resources.contentTypeResource + * + * @description + * Gets the compositions for a content type + * + * ##usage + *
+        * contentTypeResource.getAvailableCompositeContentTypes()
+        *    .then(function(data) {
+        *        console.log(data);
+        *    });
+        * 
+ * + * @param {Int} contentTypeId id of the content type to retrieve the list of the compositions + * @param {Array} filterContentTypes array of content types to filter out + * @param {Array} filterPropertyTypes array of property aliases to filter out. If specified any content types with the property aliases will be filtered out + * @param {Boolean} isElement whether the composite content types should be applicable for an element type + * @returns {Promise} resourcePromise object. + * + */ getAvailableCompositeContentTypes: function (contentTypeId, filterContentTypes, filterPropertyTypes, isElement) { if (!filterContentTypes) { filterContentTypes = []; @@ -86,6 +128,7 @@ function contentTypeResource($q, $http, umbRequestHelper, umbDataFormatter, loca * $scope.type = type; * }); * + * * @param {Int} contentTypeId id of the content item to retrive allowed child types for * @returns {Promise} resourcePromise object. * @@ -110,6 +153,14 @@ function contentTypeResource($q, $http, umbRequestHelper, umbDataFormatter, loca * @description * Returns a list of defined property type aliases * + * ##usage + *
+         * contentTypeResource.getAllPropertyTypeAliases()
+         *    .then(function(array) {
+         *       Do stuff...
+         *    });
+         * 
+ * * @returns {Promise} resourcePromise object. * */ @@ -123,6 +174,25 @@ function contentTypeResource($q, $http, umbRequestHelper, umbDataFormatter, loca 'Failed to retrieve property type aliases'); }, + /** + * @ngdoc method + * @name umbraco.resources.contentTypeResource#getAllStandardFields + * @methodOf umbraco.resources.contentTypeResource + * + * @description + * Returns a list of standard property type aliases + * + * ##usage + *
+        * contentTypeResource.getAllStandardFields()
+        *    .then(function(array) {
+        *       Do stuff...
+        *    });
+        * 
+ * + * @returns {Promise} resourcePromise object. + * + */ getAllStandardFields: function () { return umbRequestHelper.resourcePromise( @@ -133,6 +203,26 @@ function contentTypeResource($q, $http, umbRequestHelper, umbDataFormatter, loca 'Failed to retrieve standard fields'); }, + /** + * @ngdoc method + * @name umbraco.resources.contentTypeResource#getPropertyTypeScaffold + * @methodOf umbraco.resources.contentTypeResource + * + * @description + * Returns the property display for a given datatype id + * + * ##usage + *
+        * contentTypeResource.getPropertyTypeScaffold(1234)
+        *    .then(function(array) {
+        *       Do stuff...
+        *    });
+        * 
+ * + * @param {Int} id the id of the datatype + * @returns {Promise} resourcePromise object. + * + */ getPropertyTypeScaffold: function (id) { return umbRequestHelper.resourcePromise( $http.get( @@ -143,6 +233,26 @@ function contentTypeResource($q, $http, umbRequestHelper, umbDataFormatter, loca 'Failed to retrieve property type scaffold'); }, + /** + * @ngdoc method + * @name umbraco.resources.contentTypeResource#getById + * @methodOf umbraco.resources.contentTypeResource + * + * @description + * Get the content type with a given id + * + * ##usage + *
+        * contentTypeResource.getById("64058D0F-4911-4AB7-B3BA-000D89F00A26")
+        *    .then(function(array) {
+        *       Do stuff...
+        *    });
+        * 
+ * + * @param {String} id the guid id of the content type + * @returns {Promise} resourcePromise object. + * + */ getById: function (id) { return umbRequestHelper.resourcePromise( @@ -154,6 +264,26 @@ function contentTypeResource($q, $http, umbRequestHelper, umbDataFormatter, loca 'Failed to retrieve content type'); }, + /** + * @ngdoc method + * @name umbraco.resources.contentTypeResource#deleteById + * @methodOf umbraco.resources.contentTypeResource + * + * @description + * Delete the content type of a given id + * + * ##usage + *
+        * contentTypeResource.deleteById(1234)
+        *    .then(function(array) {
+        *       Do stuff...
+        *    });
+        * 
+ * + * @param {Int} id the id of the content type + * @returns {Promise} resourcePromise object. + * + */ deleteById: function (id) { return umbRequestHelper.resourcePromise( @@ -165,6 +295,26 @@ function contentTypeResource($q, $http, umbRequestHelper, umbDataFormatter, loca 'Failed to delete content type'); }, + /** + * @ngdoc method + * @name umbraco.resources.contentTypeResource#deleteContainerById + * @methodOf umbraco.resources.contentTypeResource + * + * @description + * Delete the content type container of a given id + * + * ##usage + *
+        * contentTypeResource.deleteContainerById(1234)
+        *    .then(function(array) {
+        *       Do stuff...
+        *    });
+        * 
+ * + * @param {Int} id the id of the content type container + * @returns {Promise} resourcePromise object. + * + */ deleteContainerById: function (id) { return umbRequestHelper.resourcePromise( @@ -177,16 +327,16 @@ function contentTypeResource($q, $http, umbRequestHelper, umbDataFormatter, loca }, /** - * @ngdoc method - * @name umbraco.resources.contentTypeResource#getAll - * @methodOf umbraco.resources.contentTypeResource - * - * @description - * Returns a list of all content types - * - * @returns {Promise} resourcePromise object. - * - */ + * @ngdoc method + * @name umbraco.resources.contentTypeResource#getAll + * @methodOf umbraco.resources.contentTypeResource + * + * @description + * Returns a list of all content types + * + * @returns {Promise} resourcePromise object. + * + */ getAll: function () { return umbRequestHelper.resourcePromise( @@ -197,6 +347,26 @@ function contentTypeResource($q, $http, umbRequestHelper, umbDataFormatter, loca 'Failed to retrieve all content types'); }, + /** + * @ngdoc method + * @name umbraco.resources.contentTypeResource#getScaffold + * @methodOf umbraco.resources.contentTypeResource + * + * @description + * Returns an empty content type for use as a scaffold when creating a new content type + * + * ##usage + *
+        * contentTypeResource.getScaffold(1234)
+        *    .then(function(array) {
+        *       Do stuff...
+        *    });
+        * 
+ * + * @param {Int} id the parent id + * @returns {Promise} resourcePromise object. + * + */ getScaffold: function (parentId) { return umbRequestHelper.resourcePromise( @@ -240,14 +410,14 @@ function contentTypeResource($q, $http, umbRequestHelper, umbDataFormatter, loca *
          * contentTypeResource.move({ parentId: 1244, id: 123 })
          *    .then(function() {
-         *        alert("node was moved");
+         *        alert("content type was moved");
          *    }, function(err){
-         *      alert("node didnt move:" + err.data.Message);
+         *      alert("content type didnt move:" + err.data.Message);
          *    });
          * 
* @param {Object} args arguments object - * @param {Int} args.idd the ID of the node to move - * @param {Int} args.parentId the ID of the parent node to move to + * @param {Int} args.id the ID of the content type to move + * @param {Int} args.parentId the ID of the parent content type to move to * @returns {Promise} resourcePromise object. * */ @@ -271,6 +441,29 @@ function contentTypeResource($q, $http, umbRequestHelper, umbDataFormatter, loca 'Failed to move content'); }, + /** + * @ngdoc method + * @name umbraco.resources.contentTypeResource#copy + * @methodOf umbraco.resources.contentTypeResource + * + * @description + * Copied a content type underneath a new parentId + * + * ##usage + *
+         * contentTypeResource.copy({ parentId: 1244, id: 123 })
+         *    .then(function() {
+         *        alert("content type was copied");
+         *    }, function(err){
+         *      alert("content type didnt copy:" + err.data.Message);
+         *    });
+         * 
+ * @param {Object} args arguments object + * @param {Int} args.id the ID of the content type to copy + * @param {Int} args.parentId the ID of the parent content type to copy to + * @returns {Promise} resourcePromise object. + * + */ copy: function (args) { if (!args) { throw "args cannot be null"; @@ -291,22 +484,57 @@ function contentTypeResource($q, $http, umbRequestHelper, umbDataFormatter, loca 'Failed to copy content'); }, + /** + * @ngdoc method + * @name umbraco.resources.contentTypeResource#createContainer + * @methodOf umbraco.resources.contentTypeResource + * + * @description + * Create a new content type container of a given name underneath a given parent item + * + * ##usage + *
+        * contentTypeResource.createContainer(1244,"testcontainer")
+        *    .then(function() {
+        *       Do stuff..
+        *    });
+        * 
+ * + * @param {Int} parentId the ID of the parent content type underneath which to create the container + * @param {String} name the name of the container + * @returns {Promise} resourcePromise object. + * + */ createContainer: function (parentId, name) { return umbRequestHelper.resourcePromise( $http.post(umbRequestHelper.getApiUrl("contentTypeApiBaseUrl", "PostCreateContainer", { parentId: parentId, name: encodeURIComponent(name) })), 'Failed to create a folder under parent id ' + parentId); - }, - createCollection: function (parentId, collectionName, collectionCreateTemplate, collectionItemName, collectionItemCreateTemplate, collectionIcon, collectionItemIcon) { - - return umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper.getApiUrl("contentTypeApiBaseUrl", "PostCreateCollection", { parentId: parentId, collectionName: collectionName, collectionCreateTemplate: collectionCreateTemplate, collectionItemName: collectionItemName, collectionItemCreateTemplate: collectionItemCreateTemplate, collectionIcon: collectionIcon, collectionItemIcon: collectionItemIcon})), - 'Failed to create collection under ' + parentId); - - }, + }, + /** + * @ngdoc method + * @name umbraco.resources.contentTypeResource#renameContainer + * @methodOf umbraco.resources.contentTypeResource + * + * @description + * Rename a container of a given id + * + * ##usage + *
+        * contentTypeResource.renameContainer( 1244,"testcontainer")
+        *    .then(function() {
+        *       Do stuff..
+        *    });
+        * 
+ * + * @param {Int} id the ID of the container to rename + * @param {String} name the new name of the container + * @returns {Promise} resourcePromise object. + * + */ renameContainer: function (id, name) { return umbRequestHelper.resourcePromise( @@ -318,6 +546,27 @@ function contentTypeResource($q, $http, umbRequestHelper, umbDataFormatter, loca }, + /** + * @ngdoc method + * @name umbraco.resources.contentTypeResource#export + * @methodOf umbraco.resources.contentTypeResource + * + * @description + * Export a content type of a given id. + * + * ##usage + *
+        * contentTypeResource.export(1234){
+        *    .then(function() {
+        *       Do stuff..
+        *    });
+        * 
+ * + * @param {Int} id the ID of the container to rename + * @param {String} name the new name of the container + * @returns {Promise} resourcePromise object. + * + */ export: function (id) { if (!id) { throw "id cannot be null"; @@ -336,6 +585,26 @@ function contentTypeResource($q, $http, umbRequestHelper, umbDataFormatter, loca }); }, + /** + * @ngdoc method + * @name umbraco.resources.contentTypeResource#import + * @methodOf umbraco.resources.contentTypeResource + * + * @description + * Import a content type from a file + * + * ##usage + *
+        * contentTypeResource.import("path to file"){
+        *    .then(function() {
+        *       Do stuff..
+        *    });
+        * 
+ * + * @param {String} file path of the file to import + * @returns {Promise} resourcePromise object. + * + */ import: function (file) { if (!file) { throw "file cannot be null"; @@ -347,12 +616,52 @@ function contentTypeResource($q, $http, umbRequestHelper, umbDataFormatter, loca ); }, + /** + * @ngdoc method + * @name umbraco.resources.contentTypeResource#createDefaultTemplate + * @methodOf umbraco.resources.contentTypeResource + * + * @description + * Create a default template for a content type with a given id + * + * ##usage + *
+        * contentTypeResource.createDefaultTemplate(1234){
+        *    .then(function() {
+        *       Do stuff..
+        *    });
+        * 
+ * + * @param {Int} id the id of the content type for which to create the default template + * @returns {Promise} resourcePromise object. + * + */ createDefaultTemplate: function (id) { return umbRequestHelper.resourcePromise( $http.post(umbRequestHelper.getApiUrl("contentTypeApiBaseUrl", "PostCreateDefaultTemplate", { id: id })), 'Failed to create default template for content type with id ' + id); }, + /** + * @ngdoc method + * @name umbraco.resources.contentTypeResource#hasContentNodes + * @methodOf umbraco.resources.contentTypeResource + * + * @description + * Returns whether a content type has content nodes + * + * ##usage + *
+        * contentTypeResource.hasContentNodes(1234){
+        *    .then(function() {
+        *       Do stuff..
+        *    });
+        * 
+ * + * @param {Int} id the id of the content type + * @returns {Promise} resourcePromise object. + * + */ hasContentNodes: function (id) { return umbRequestHelper.resourcePromise( $http.get( diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/elementtype.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/elementtype.resource.js new file mode 100644 index 0000000000..b097a65447 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/resources/elementtype.resource.js @@ -0,0 +1,43 @@ +/** + * @ngdoc service + * @name umbraco.resources.elementTypeResource + * @description Loads in data for element types + **/ +function elementTypeResource($q, $http, umbRequestHelper) { + + return { + + /** + * @ngdoc method + * @name umbraco.resources.elementTypeResource#getAll + * @methodOf umbraco.resources.elementTypeResource + * + * @description + * Gets a list of all element types + * + * ##usage + *
+        * elementTypeResource.getAll()
+        *    .then(function() {
+        *        alert('Found it!');
+        *    });
+        * 
+ * + * @returns {Promise} resourcePromise object. + * + **/ + getAll: function () { + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "elementTypeApiBaseUrl", + "GetAll")), + "Failed to retrieve element types"); + + } + + }; +} + +angular.module("umbraco.resources").factory("elementTypeResource", elementTypeResource); diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js index 61d646afc0..838e8f1b80 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js @@ -207,7 +207,7 @@ function entityResource($q, $http, umbRequestHelper) { getAnchors: function (rteContent) { if (!rteContent || rteContent.length === 0) { - return []; + return $q.when([]); } return umbRequestHelper.resourcePromise( diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/media.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/media.resource.js index e24f4786eb..06eb2ed4d2 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/media.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/media.resource.js @@ -226,9 +226,7 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { getByIds: function (ids) { var idQuery = ""; - _.each(ids, function (item) { - idQuery += "ids=" + item + "&"; - }); + ids.forEach(id => idQuery += `ids=${id}&`); return umbRequestHelper.resourcePromise( $http.get( diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/membergroup.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/membergroup.resource.js index c83a31e47c..f9b9da9944 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/membergroup.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/membergroup.resource.js @@ -32,9 +32,7 @@ function memberGroupResource($q, $http, umbRequestHelper) { getByIds: function (ids) { var idQuery = ""; - _.each(ids, function (item) { - idQuery += "ids=" + item + "&"; - }); + ids.forEach(id => idQuery += `ids=${id}&`); return umbRequestHelper.resourcePromise( $http.get( diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/membertype.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/membertype.resource.js index 6c15b89c0e..2314fa6d6c 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/membertype.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/membertype.resource.js @@ -16,16 +16,15 @@ function memberTypeResource($q, $http, umbRequestHelper, umbDataFormatter) { } var query = ""; - _.each(filterContentTypes, function (item) { - query += "filterContentTypes=" + item + "&"; - }); + filterContentTypes.forEach(fct => query += `filterContentTypes=${fct}&`); + // if filterContentTypes array is empty we need a empty variable in the querystring otherwise the service returns a error if (filterContentTypes.length === 0) { query += "filterContentTypes=&"; } - _.each(filterPropertyTypes, function (item) { - query += "filterPropertyTypes=" + item + "&"; - }); + + filterPropertyTypes.forEach(fpt => query += `filterPropertyTypes=${fpt}&`); + // if filterPropertyTypes array is empty we need a empty variable in the querystring otherwise the service returns a error if (filterPropertyTypes.length === 0) { query += "filterPropertyTypes=&"; diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/modelsbuildermanagement.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/modelsbuildermanagement.resource.js index ee3cd80c71..8352ca07e6 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/modelsbuildermanagement.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/modelsbuildermanagement.resource.js @@ -1,18 +1,82 @@ + +/** +* @ngdoc service +* @name umbraco.resources.modelsBuilderManagementResource +* @description Resources to get information on modelsbuilder status and build models +**/ function modelsBuilderManagementResource($q, $http, umbRequestHelper) { return { + + /** + * @ngdoc method + * @name umbraco.resources.modelsBuilderManagementResource#getModelsOutOfDateStatus + * @methodOf umbraco.resources.modelsBuilderManagementResource + * + * @description + * Gets the status of modelsbuilder + * + * ##usage + *
+        * modelsBuilderManagementResource.getModelsOutOfDateStatus()
+        *  .then(function() {
+        *        Do stuff...*
+        * });
+        * 
+ * + * @returns {Promise} resourcePromise object. + * + */ getModelsOutOfDateStatus: function () { return umbRequestHelper.resourcePromise( $http.get(umbRequestHelper.getApiUrl("modelsBuilderBaseUrl", "GetModelsOutOfDateStatus")), "Failed to get models out-of-date status"); }, + /** + * @ngdoc method + * @name umbraco.resources.modelsBuilderManagementResource#buildModels + * @methodOf umbraco.resources.modelsBuilderManagementResource + * + * @description + * Builds the models + * + * ##usage + *
+        * modelsBuilderManagementResource.buildModels()
+        *  .then(function() {
+        *        Do stuff...*
+        * });
+        * 
+ * + * @returns {Promise} resourcePromise object. + * + */ buildModels: function () { return umbRequestHelper.resourcePromise( $http.post(umbRequestHelper.getApiUrl("modelsBuilderBaseUrl", "BuildModels")), "Failed to build models"); }, + /** + * @ngdoc method + * @name umbraco.resources.modelsBuilderManagementResource#getDashboard + * @methodOf umbraco.resources.modelsBuilderManagementResource + * + * @description + * Gets the modelsbuilder dashboard + * + * ##usage + *
+        * modelsBuilderManagementResource.getDashboard()
+        *  .then(function() {
+        *        Do stuff...*
+        * });
+        * 
+ * + * @returns {Promise} resourcePromise object. + * + */ getDashboard: function () { return umbRequestHelper.resourcePromise( $http.get(umbRequestHelper.getApiUrl("modelsBuilderBaseUrl", "GetDashboard")), diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/users.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/users.resource.js index 2b9e0c0fd5..91f00a36e3 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/users.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/users.resource.js @@ -295,6 +295,38 @@ "Failed to retrieve data for user " + userId); } + + /** + * @ngdoc method + * @name umbraco.resources.usersResource#getUsers + * @methodOf umbraco.resources.usersResource + * + * @description + * Gets users from ids + * + * ##usage + *
+          * usersResource.getUsers([1,2,3])
+          *    .then(function(data) {
+          *        alert("It's here");
+          *    });
+          * 
+ * + * @param {Array} userIds user ids. + * @returns {Promise} resourcePromise object containing the users array. + * + */ + function getUsers(userIds) { + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "userApiBaseUrl", + "GetByIds", + { ids: userIds })), + "Failed to retrieve data for users " + userIds); + } + /** * @ngdoc method * @name umbraco.resources.usersResource#createUser @@ -481,6 +513,7 @@ setUserGroupsOnUsers: setUserGroupsOnUsers, getPagedResults: getPagedResults, getUser: getUser, + getUsers: getUsers, createUser: createUser, inviteUser: inviteUser, saveUser: saveUser, diff --git a/src/Umbraco.Web.UI.Client/src/common/services/angularhelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/angularhelper.service.js index f2ff711ac9..c4fd431a12 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/angularhelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/angularhelper.service.js @@ -7,17 +7,102 @@ * Some angular helper/extension methods */ function angularHelper($q) { + + var requiredFormProps = ["$error", "$name", "$dirty", "$pristine", "$valid", "$submitted", "$pending"]; + + function collectAllFormErrorsRecursively(formCtrl, allErrors) { + // skip if the control is already added to the array + if (allErrors.indexOf(formCtrl) !== -1) { + return; + } + + // loop over the error dictionary (see https://docs.angularjs.org/api/ng/type/form.FormController#$error) + var keys = Object.keys(formCtrl.$error); + if (keys.length === 0) { + return; + } + keys.forEach(validationKey => { + var ctrls = formCtrl.$error[validationKey]; + ctrls.forEach(ctrl => { + if (!ctrl) { + // this happens when $setValidity('err', true) is called on a form controller without specifying the 3rd parameter for the control/form + // which just means that this is an error on the formCtrl itself + allErrors.push(formCtrl); // add the error + } + else if (isForm(ctrl)) { + // sometimes the control in error is the same form so we cannot recurse else we'll cause an infinite loop + // and in this case it means the error is assigned directly to the form, not a control + if (ctrl === formCtrl) { + allErrors.push(ctrl); // add the error + return; + } + + // recurse with the sub form + collectAllFormErrorsRecursively(ctrl, allErrors); + } + else { + // it's a normal control + allErrors.push(ctrl); // add the error + } + }); + }); + } + + function isForm(obj) { + + // a method to check that the collection of object prop names contains the property name expected + function allPropertiesExist(objectPropNames) { + //ensure that every required property name exists on the current object + return _.every(requiredFormProps, function (item) { + return _.contains(objectPropNames, item); + }); + } + + //get the keys of the property names for the current object + var props = _.keys(obj); + //if the length isn't correct, try the next prop + if (props.length < requiredFormProps.length) { + return false; + } + + //ensure that every required property name exists on the current scope property + return allPropertiesExist(props); + } + return { + countAllFormErrors: function (formCtrl) { + var allErrors = []; + collectAllFormErrorsRecursively(formCtrl, allErrors); + return allErrors.length; + }, + + /** + * Will traverse up the $scope chain to all ancestors until the predicate matches for the current scope or until it's at the root. + * @param {any} scope + * @param {any} predicate + */ + traverseScopeChain: function (scope, predicate) { + var s = scope.$parent; + while (s) { + var result = predicate(s); + if (result === true) { + return s; + } + s = s.$parent; + } + return null; + }, + /** * Method used to re-run the $parsers for a given ngModel - * @param {} scope - * @param {} ngModel - * @returns {} + * @param {} scope + * @param {} ngModel + * @returns {} */ revalidateNgModel: function (scope, ngModel) { this.safeApply(scope, function() { - angular.forEach(ngModel.$parsers, function (parser) { + ngModel.$parsers.forEach(function (parser) { parser(ngModel.$viewValue); }); }); @@ -25,8 +110,8 @@ function angularHelper($q) { /** * Execute a list of promises sequentially. Unlike $q.all which executes all promises at once, this will execute them in sequence. - * @param {} promises - * @returns {} + * @param {} promises + * @returns {} */ executeSequentialPromises: function (promises) { @@ -69,12 +154,12 @@ function angularHelper($q) { */ safeApply: function (scope, fn) { if (scope.$$phase || (scope.$root && scope.$root.$$phase)) { - if (angular.isFunction(fn)) { + if (Utilities.isFunction(fn)) { fn(); } } else { - if (angular.isFunction(fn)) { + if (Utilities.isFunction(fn)) { scope.$apply(fn); } else { @@ -83,6 +168,9 @@ function angularHelper($q) { } }, + + isForm: isForm, + /** * @ngdoc function * @name getCurrentForm @@ -97,38 +185,17 @@ function angularHelper($q) { //NOTE: There isn't a way in angular to get a reference to the current form object since the form object // is just defined as a property of the scope when it is named but you'll always need to know the name which // isn't very convenient. If we want to watch for validation changes we need to get a form reference. - // The way that we detect the form object is a bit hackerific in that we detect all of the required properties + // The way that we detect the form object is a bit hackerific in that we detect all of the required properties // that exist on a form object. // //The other way to do it in a directive is to require "^form", but in a controller the only other way to do it // is to inject the $element object and use: $element.inheritedData('$formController'); var form = null; - var requiredFormProps = ["$error", "$name", "$dirty", "$pristine", "$valid", "$submitted", "$pending"]; - - // a method to check that the collection of object prop names contains the property name expected - function propertyExists(objectPropNames) { - //ensure that every required property name exists on the current scope property - return _.every(requiredFormProps, function (item) { - - return _.contains(objectPropNames, item); - }); - } for (var p in scope) { - if (_.isObject(scope[p]) && p !== "this" && p.substr(0, 1) !== "$") { - //get the keys of the property names for the current property - var props = _.keys(scope[p]); - //if the length isn't correct, try the next prop - if (props.length < requiredFormProps.length) { - continue; - } - - //ensure that every required property name exists on the current scope property - var containProperty = propertyExists(props); - - if (containProperty) { + if (this.isForm(scope[p])) { form = scope[p]; break; } @@ -179,7 +246,7 @@ function angularHelper($q) { $submitted: false, $pending: undefined, $addControl: Utilities.noop, - $removeControl: Utilities.noop, + $removeControl: Utilities.noop, $setValidity: Utilities.noop, $setDirty: Utilities.noop, $setPristine: Utilities.noop, diff --git a/src/Umbraco.Web.UI.Client/src/common/services/assets.service.js b/src/Umbraco.Web.UI.Client/src/common/services/assets.service.js index 30e59e9a88..b474b66174 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/assets.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/assets.service.js @@ -276,7 +276,7 @@ angular.module('umbraco.services') //blocking var promises = []; var assets = []; - _.each(nonEmpty, function (path) { + nonEmpty.forEach(path => { path = convertVirtualPath(path); var asset = service._getAssetPromise(path); //if not previously loaded, add to list of promises @@ -325,19 +325,17 @@ angular.module('umbraco.services') scope = $rootScope; } angularHelper.safeApply(scope, - function () { - asset.deferred.resolve(true); - }); + () => 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); }); + var cssPaths = cssAssets.map(css => appendRnd(css.path)); + LazyLoad.css(cssPaths, () => cssAssets.forEach(assetLoaded)); } if (jsAssets.length > 0) { - var jsPaths = _.map(jsAssets, function (asset) { return appendRnd(asset.path) }); - LazyLoad.js(jsPaths, function () { _.each(jsAssets, assetLoaded); }); + var jsPaths = jsAssets.map(js => appendRnd(js.path)); + LazyLoad.js(jsPaths, () => jsAssets.forEach(assetLoaded)); } return promise; diff --git a/src/Umbraco.Web.UI.Client/src/common/services/blockeditor.service.js b/src/Umbraco.Web.UI.Client/src/common/services/blockeditor.service.js new file mode 100644 index 0000000000..122d430165 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/services/blockeditor.service.js @@ -0,0 +1,173 @@ +/** + * @ngdoc service + * @name umbraco.services.blockEditorService + * + * @description + * Added in Umbraco 8.7. Service for dealing with Block Editors. + * + * Block Editor Service provides the basic features for a block editor. + * The main feature is the ability to create a Model Object which takes care of your data for your Block Editor. + * + * + * ##Samples + * + * ####Instantiate a Model Object for your property editor: + * + *
+ *     modelObject = blockEditorService.createModelObject(vm.model.value, vm.model.editor, vm.model.config.blocks, $scope);
+ *     modelObject.load().then(onLoaded);
+ * 
+ * + * + * See {@link umbraco.services.blockEditorModelObject BlockEditorModelObject} for more samples. + * + */ +(function () { + 'use strict'; + + + + /** + * When performing a runtime copy of Block Editors entries, we copy the ElementType Data Model and inner IDs are kept identical, to ensure new IDs are changed on paste we need to provide a resolver for the ClipboardService. + */ + angular.module('umbraco').run(['clipboardService', 'udiService', function (clipboardService, udiService) { + + function replaceUdi(obj, key, dataObject) { + var udi = obj[key]; + var newUdi = udiService.create("element"); + obj[key] = newUdi; + dataObject.forEach((data) => { + if (data.udi === udi) { + data.udi = newUdi; + } + }); + } + function replaceUdisOfObject(obj, propValue) { + for (var k in obj) { + if(k === "contentUdi") { + replaceUdi(obj, k, propValue.contentData); + } else if(k === "settingsUdi") { + replaceUdi(obj, k, propValue.settingsData); + } else { + // lets crawl through all properties of layout to make sure get captured all `contentUdi` and `settingsUdi` properties. + var propType = typeof obj[k]; + if(propType != null && (propType === "object" || propType === "array")) { + replaceUdisOfObject(obj[k], propValue) + } + } + } + } + function removeBlockReferences(obj) { + for (var k in obj) { + if(k === "contentUdi") { + delete obj[k]; + } else if(k === "settingsUdi") { + delete obj[k]; + } else { + // lets crawl through all properties of layout to make sure get captured all `contentUdi` and `settingsUdi` properties. + var propType = typeof obj[k]; + if(propType != null && (propType === "object" || propType === "array")) { + removeBlockReferences(obj[k]) + } + } + } + } + + + function elementTypeBlockResolver(obj, propPasteResolverMethod) { + // we could filter for specific Property Editor Aliases, but as the Block Editor structure can be used by many Property Editor we do not in this code know a good way to detect that this is a Block Editor and will therefor leave it to the value structure to determin this. + rawBlockResolver(obj.value, propPasteResolverMethod); + } + + clipboardService.registerPastePropertyResolver(elementTypeBlockResolver, clipboardService.TYPES.ELEMENT_TYPE); + + + function rawBlockResolver(value, propPasteResolverMethod) { + if (value != null && typeof value === "object") { + + // we got an object, and it has these three props then we are most likely dealing with a Block Editor. + if ((value.layout !== undefined && value.contentData !== undefined && value.settingsData !== undefined)) { + + replaceUdisOfObject(value.layout, value); + + // run resolvers for inner properties of this Blocks content data. + if(value.contentData.length > 0) { + value.contentData.forEach((item) => { + for (var k in item) { + propPasteResolverMethod(item[k], clipboardService.TYPES.RAW); + } + }); + } + // run resolvers for inner properties of this Blocks settings data. + if(value.settingsData.length > 0) { + value.settingsData.forEach((item) => { + for (var k in item) { + propPasteResolverMethod(item[k], clipboardService.TYPES.RAW); + } + }); + } + + } + } + } + + clipboardService.registerPastePropertyResolver(rawBlockResolver, clipboardService.TYPES.RAW); + + + function provideNewUdisForBlockResolver(block, propPasteResolverMethod) { + + if(block.layout) { + // We do not support layout child blocks currently, these should be stripped out as we only will be copying a single entry. + removeBlockReferences(block.layout); + } + + if(block.data) { + // Make new UDI for content-element + block.data.udi = block.layout.contentUdi = udiService.create("element"); + } + + if(block.settingsData) { + // Make new UDI for settings-element + block.settingsData.udi = block.layout.settingsUdi = udiService.create("element"); + } + + } + + clipboardService.registerPastePropertyResolver(provideNewUdisForBlockResolver, clipboardService.TYPES.BLOCK); + + }]); + + + + + function blockEditorService(blockEditorModelObject) { + + /** + * @ngdocs function + * @name createModelObject + * @methodOf umbraco.services.blockEditorService + * + * @description + * Create a new Block Editor Model Object. + * See {@link umbraco.services.blockEditorModelObject blockEditorModelObject} + * + * @see umbraco.services.blockEditorModelObject + * @param {object} propertyModelValue data object of the property editor, usually model.value. + * @param {string} propertyEditorAlias alias of the property. + * @param {object} blockConfigurations block configurations. + * @param {angular-scope} scopeOfExistance A local angularJS scope that exists as long as the data exists. + * @param {angular-scope} propertyEditorScope A local angularJS scope that represents the property editors scope. + * @return {blockEditorModelObject} A instance of the BlockEditorModelObject class. + */ + function createModelObject(propertyModelValue, propertyEditorAlias, blockConfigurations, scopeOfExistance, propertyEditorScope) { + return new blockEditorModelObject(propertyModelValue, propertyEditorAlias, blockConfigurations, scopeOfExistance, propertyEditorScope); + } + + return { + createModelObject: createModelObject + } + } + + angular.module('umbraco.services').service('blockEditorService', blockEditorService); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/common/services/blockeditormodelobject.service.js b/src/Umbraco.Web.UI.Client/src/common/services/blockeditormodelobject.service.js new file mode 100644 index 0000000000..2ee7d513ca --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/services/blockeditormodelobject.service.js @@ -0,0 +1,900 @@ +/** + * @ngdoc service + * @name umbraco.services.blockEditorModelObject + * + * @description + * Added in Umbraco 8.7. Model Object for dealing with data of Block Editors. + * + * Block Editor Model Object provides the basic features for editing data of a block editor.
+ * Use the Block Editor Service to instantiate the Model Object.
+ * See {@link umbraco.services.blockEditorService blockEditorService} + * + */ +(function () { + 'use strict'; + + function blockEditorModelObjectFactory($interpolate, $q, udiService, contentResource, localizationService, umbRequestHelper, clipboardService, notificationsService) { + + /** + * Simple mapping from property model content entry to editing model, + * needs to stay simple to avoid deep watching. + */ + function mapToElementModel(elementModel, dataModel) { + + if (!elementModel || !elementModel.variants || !elementModel.variants.length) { return; } + + var variant = elementModel.variants[0]; + + for (var t = 0; t < variant.tabs.length; t++) { + var tab = variant.tabs[t]; + + for (var p = 0; p < tab.properties.length; p++) { + var prop = tab.properties[p]; + if (dataModel[prop.alias]) { + prop.value = dataModel[prop.alias]; + } + } + } + + } + + /** + * Simple mapping from elementModel to property model content entry, + * needs to stay simple to avoid deep watching. + */ + function mapToPropertyModel(elementModel, dataModel) { + + if (!elementModel || !elementModel.variants || !elementModel.variants.length) { return; } + + var variant = elementModel.variants[0]; + + for (var t = 0; t < variant.tabs.length; t++) { + var tab = variant.tabs[t]; + + for (var p = 0; p < tab.properties.length; p++) { + var prop = tab.properties[p]; + if (prop.value) { + dataModel[prop.alias] = prop.value; + } + } + } + + } + + /** + * Map property values from an ElementModel to another ElementModel. + * Used to tricker watchers for synchronization. + * @param {Object} fromModel ElementModel to recive property values from. + * @param {Object} toModel ElementModel to recive property values from. + */ + function mapElementValues(fromModel, toModel) { + if (!fromModel || !fromModel.variants) { + toModel.variants = null; + return; + } + if (!fromModel.variants.length) { + toModel.variants = []; + return; + } + + var fromVariant = fromModel.variants[0]; + if (!fromVariant) { + toModel.variants = [null]; + return; + } + + var toVariant = toModel.variants[0]; + + for (var t = 0; t < fromVariant.tabs.length; t++) { + var fromTab = fromVariant.tabs[t]; + var toTab = toVariant.tabs[t]; + + for (var p = 0; p < fromTab.properties.length; p++) { + var fromProp = fromTab.properties[p]; + var toProp = toTab.properties[p]; + toProp.value = fromProp.value; + } + } + } + + + /** + * Generate label for Block, uses either the labelInterpolator or falls back to the contentTypeName. + * @param {Object} blockObject BlockObject to recive data values from. + */ + function getBlockLabel(blockObject) { + if (blockObject.labelInterpolator !== undefined) { + var labelVars = Object.assign({"$settings": blockObject.settingsData || {}, "$layout": blockObject.layout || {}, "$index": (blockObject.index || 0)+1 }, blockObject.data); + return blockObject.labelInterpolator(labelVars); + } + return blockObject.content.contentTypeName; + } + + + /** + * Used to add watchers on all properties in a content or settings model + */ + function addWatchers(blockObject, isolatedScope, forSettings) { + var model = forSettings ? blockObject.settings : blockObject.content; + if (!model || !model.variants || !model.variants.length) { return; } + + // Start watching each property value. + var variant = model.variants[0]; + var field = forSettings ? "settings" : "content"; + var watcherCreator = forSettings ? createSettingsModelPropWatcher : createContentModelPropWatcher; + for (var t = 0; t < variant.tabs.length; t++) { + var tab = variant.tabs[t]; + for (var p = 0; p < tab.properties.length; p++) { + var prop = tab.properties[p]; + + // Watch value of property since this is the only value we want to keep synced. + // Do notice that it is not performing a deep watch, meaning that we are only watching primitive and changes directly to the object of property-value. + // But we like to sync non-primitive values as well! Yes, and this does happen, just not through this code, but through the nature of JavaScript. + // Non-primitive values act as references to the same data and are therefor synced. + blockObject.__watchers.push(isolatedScope.$watch("blockObjects._" + blockObject.key + "." + field + ".variants[0].tabs[" + t + "].properties[" + p + "].value", watcherCreator(blockObject, prop))); + + // We also like to watch our data model to be able to capture changes coming from other places. + if (forSettings === true) { + blockObject.__watchers.push(isolatedScope.$watch("blockObjects._" + blockObject.key + "." + "settingsData" + "." + prop.alias, createLayoutSettingsModelWatcher(blockObject, prop))); + } else { + blockObject.__watchers.push(isolatedScope.$watch("blockObjects._" + blockObject.key + "." + "data" + "." + prop.alias, createDataModelWatcher(blockObject, prop))); + } + } + } + if (blockObject.__watchers.length === 0) { + // If no watcher where created, it means we have no properties to watch. This means that nothing will activate our generate the label, since its only triggered by watchers. + blockObject.updateLabel(); + } + } + + /** + * Used to create a prop watcher for the data in the property editor data model. + */ + function createDataModelWatcher(blockObject, prop) { + return function () { + if (prop.value !== blockObject.data[prop.alias]) { + + // sync data: + prop.value = blockObject.data[prop.alias]; + + blockObject.updateLabel(); + } + } + } + + /** + * Used to create a prop watcher for the settings in the property editor data model. + */ + function createLayoutSettingsModelWatcher(blockObject, prop) { + return function () { + if (prop.value !== blockObject.settingsData[prop.alias]) { + // sync data: + prop.value = blockObject.settingsData[prop.alias]; + } + } + } + + /** + * Used to create a scoped watcher for a content property on a blockObject. + */ + function createContentModelPropWatcher(blockObject, prop) { + return function () { + if (blockObject.data[prop.alias] !== prop.value) { + // sync data: + blockObject.data[prop.alias] = prop.value; + } + + blockObject.updateLabel(); + } + } + + /** + * Used to create a scoped watcher for a settings property on a blockObject. + */ + function createSettingsModelPropWatcher(blockObject, prop) { + return function () { + if (blockObject.settingsData[prop.alias] !== prop.value) { + // sync data: + blockObject.settingsData[prop.alias] = prop.value; + } + } + } + + function createDataEntry(elementTypeKey, dataItems) { + var data = { + contentTypeKey: elementTypeKey, + udi: udiService.create("element") + }; + dataItems.push(data); + return data.udi; + } + + function getDataByUdi(udi, dataItems) { + return dataItems.find(entry => entry.udi === udi) || null; + } + + /** + * Set the udi and key property for the content item + * @param {any} contentData + * @param {any} udi + */ + function ensureUdiAndKey(contentData, udi) { + contentData.udi = udi; + // Change the content.key to the GUID part of the udi, else it's just random which we don't want, it must be consistent + contentData.key = udiService.getKey(udi); + } + + /** + * Used to highlight unsupported properties for the user, changes unsupported properties into a unsupported-property. + */ + var notSupportedProperties = [ + "Umbraco.Tags", + "Umbraco.UploadField", + "Umbraco.ImageCropper", + "Umbraco.NestedContent" + ]; + + + /** + * Formats the content apps and ensures unsupported property's have the notsupported view + * @param {any} scaffold + */ + function formatScaffoldData(scaffold) { + + // deal with not supported props + scaffold.variants.forEach((variant) => { + variant.tabs.forEach((tab) => { + tab.properties.forEach((property) => { + if (notSupportedProperties.indexOf(property.editor) !== -1) { + property.view = "notsupported"; + } + }); + }); + }); + + // could be empty in tests + if (!scaffold.apps) { + console.warn("No content apps found in scaffold"); + return scaffold; + } + + // replace view of content app + + var contentApp = scaffold.apps.find(entry => entry.alias === "umbContent"); + if (contentApp) { + contentApp.view = "views/common/infiniteeditors/blockeditor/blockeditor.content.html"; + } + + // remove info app + var infoAppIndex = scaffold.apps.findIndex(entry => entry.alias === "umbInfo"); + if (infoAppIndex >= 0) { + scaffold.apps.splice(infoAppIndex, 1); + } + + return scaffold; + } + + /** + * Creates a settings content app, we only want to do this if settings is present on the specific block. + * @param {any} contentModel + */ + function appendSettingsContentApp(contentModel, settingsName) { + if (!contentModel.apps) { + return + } + + // add the settings app + var settingsTab = { + "name": settingsName, + "alias": "settings", + "icon": "icon-settings", + "view": "views/common/infiniteeditors/blockeditor/blockeditor.settings.html", + "hasError": false + }; + contentModel.apps.push(settingsTab); + } + + /** + * @ngdoc method + * @name constructor + * @methodOf umbraco.services.blockEditorModelObject + * @description Constructor of the model object used to handle Block Editor data. + * @param {object} propertyModelValue data object of the property editor, usually model.value. + * @param {string} propertyEditorAlias alias of the property. + * @param {object} blockConfigurations block configurations. + * @param {angular-scope} scopeOfExistance A local angularJS scope that exists as long as the data exists. + * @param {angular-scope} propertyEditorScope A local angularJS scope that represents the property editors scope. + * @returns {BlockEditorModelObject} A instance of BlockEditorModelObject. + */ + function BlockEditorModelObject(propertyModelValue, propertyEditorAlias, blockConfigurations, scopeOfExistance, propertyEditorScope) { + + if (!propertyModelValue) { + throw new Error("propertyModelValue cannot be undefined, to ensure we keep the binding to the angular model we need minimum an empty object."); + } + + this.__watchers = []; + + this.__labels = {}; + + // ensure basic part of data-structure is in place: + this.value = propertyModelValue; + this.value.layout = this.value.layout || {}; + this.value.contentData = this.value.contentData || []; + this.value.settingsData = this.value.settingsData || []; + + this.propertyEditorAlias = propertyEditorAlias; + this.blockConfigurations = blockConfigurations; + + this.blockConfigurations.forEach(blockConfiguration => { + if (blockConfiguration.view != null && blockConfiguration.view !== "") { + blockConfiguration.view = umbRequestHelper.convertVirtualToAbsolutePath(blockConfiguration.view); + } + if (blockConfiguration.stylesheet != null && blockConfiguration.stylesheet !== "") { + blockConfiguration.stylesheet = umbRequestHelper.convertVirtualToAbsolutePath(blockConfiguration.stylesheet); + } + if (blockConfiguration.thumbnail != null && blockConfiguration.thumbnail !== "") { + blockConfiguration.thumbnail = umbRequestHelper.convertVirtualToAbsolutePath(blockConfiguration.thumbnail); + } + }); + + this.scaffolds = []; + + this.isolatedScope = scopeOfExistance.$new(true); + this.isolatedScope.blockObjects = {}; + + this.__watchers.push(this.isolatedScope.$on("$destroy", this.destroy.bind(this))); + this.__watchers.push(propertyEditorScope.$on("formSubmittingFinalPhase", this.sync.bind(this))); + + }; + + BlockEditorModelObject.prototype = { + + update: function (propertyModelValue, propertyEditorScope) { + // clear watchers + this.__watchers.forEach(w => { w(); }); + delete this.__watchers; + + // clear block objects + for (const key in this.isolatedScope.blockObjects) { + this.destroyBlockObject(this.isolatedScope.blockObjects[key]); + } + this.isolatedScope.blockObjects = {}; + + // update our values + this.value = propertyModelValue; + this.value.layout = this.value.layout || {}; + this.value.contentData = this.value.contentData || []; + this.value.settingsData = this.value.settingsData || []; + + // re-create the watchers + this.__watchers = []; + this.__watchers.push(this.isolatedScope.$on("$destroy", this.destroy.bind(this))); + this.__watchers.push(propertyEditorScope.$on("formSubmittingFinalPhase", this.sync.bind(this))); + }, + + /** + * @ngdoc method + * @name getBlockConfiguration + * @methodOf umbraco.services.blockEditorModelObject + * @description Get block configuration object for a given contentElementTypeKey. + * @param {string} key contentElementTypeKey to recive the configuration model for. + * @returns {Object | null} Configuration model for the that specific block. Or ´null´ if the contentElementTypeKey isnt available in the current block configurations. + */ + getBlockConfiguration: function (key) { + return this.blockConfigurations.find(bc => bc.contentElementTypeKey === key) || null; + }, + + /** + * @ngdoc method + * @name load + * @methodOf umbraco.services.blockEditorModelObject + * @description Load the scaffolding models for the given configuration, these are needed to provide useful models for each block. + * @param {Object} blockObject BlockObject to receive data values from. + * @returns {Promise} A Promise object which resolves when all scaffold models are loaded. + */ + load: function () { + + var self = this; + + var tasks = []; + + tasks.push(localizationService.localize("blockEditor_tabBlockSettings").then( + function (settingsName) { + // self.__labels might not exists anymore, this happens if this instance has been destroyed before the load is complete. + if(self.__labels) { + self.__labels.settingsName = settingsName; + } + } + )); + + var scaffoldKeys = []; + + this.blockConfigurations.forEach(blockConfiguration => { + scaffoldKeys.push(blockConfiguration.contentElementTypeKey); + if (blockConfiguration.settingsElementTypeKey != null) { + scaffoldKeys.push(blockConfiguration.settingsElementTypeKey); + } + }); + + // removing duplicates. + scaffoldKeys = scaffoldKeys.filter((value, index, self) => self.indexOf(value) === index); + + scaffoldKeys.forEach(contentTypeKey => { + tasks.push(contentResource.getScaffoldByKey(-20, contentTypeKey).then(scaffold => { + // self.scaffolds might not exists anymore, this happens if this instance has been destroyed before the load is complete. + if (self.scaffolds) { + self.scaffolds.push(formatScaffoldData(scaffold)); + } + }).catch( + () => { + // Do nothing if we get an error. + } + )); + }); + + return $q.all(tasks); + }, + + /** + * @ngdoc method + * @name getAvailableAliasesForBlockContent + * @methodOf umbraco.services.blockEditorModelObject + * @description Retrive a list of aliases that are available for content of blocks in this property editor, does not contain aliases of block settings. + * @return {Array} array of strings representing alias. + */ + getAvailableAliasesForBlockContent: function () { + return this.blockConfigurations.map( + (blockConfiguration) => { + var scaffold = this.getScaffoldFromKey(blockConfiguration.contentElementTypeKey); + if (scaffold) { + return scaffold.contentTypeAlias; + } + } + ); + }, + + /** + * @ngdoc method + * @name getAvailableBlocksForBlockPicker + * @methodOf umbraco.services.blockEditorModelObject + * @description Retrive a list of available blocks, the list containing object with the confirugation model(blockConfigModel) and the element type model(elementTypeModel). + * The purpose of this data is to provide it for the Block Picker. + * @return {Array} array of objects representing available blocks, each object containing properties blockConfigModel and elementTypeModel. + */ + getAvailableBlocksForBlockPicker: function () { + + var blocks = []; + + this.blockConfigurations.forEach(blockConfiguration => { + var scaffold = this.getScaffoldFromKey(blockConfiguration.contentElementTypeKey); + if (scaffold) { + blocks.push({ + blockConfigModel: blockConfiguration, + elementTypeModel: scaffold.documentType + }); + } + }); + + return blocks; + }, + + /** + * @ngdoc method + * @name getScaffoldFromKey + * @methodOf umbraco.services.blockEditorModelObject + * @description Get scaffold model for a given contentTypeKey. + * @param {string} key contentTypeKey to recive the scaffold model for. + * @returns {Object | null} Scaffold model for the that content type. Or null if the scaffolding model dosnt exist in this context. + */ + getScaffoldFromKey: function (contentTypeKey) { + return this.scaffolds.find(o => o.contentTypeKey === contentTypeKey); + }, + + /** + * @ngdoc method + * @name getScaffoldFromAlias + * @methodOf umbraco.services.blockEditorModelObject + * @description Get scaffold model for a given contentTypeAlias, used by clipboardService. + * @param {string} alias contentTypeAlias to recive the scaffold model for. + * @returns {Object | null} Scaffold model for the that content type. Or null if the scaffolding model dosnt exist in this context. + */ + getScaffoldFromAlias: function (contentTypeAlias) { + return this.scaffolds.find(o => o.contentTypeAlias === contentTypeAlias); + }, + + /** + * @ngdoc method + * @name getBlockObject + * @methodOf umbraco.services.blockEditorModelObject + * @description Retrieve a Block Object for the given layout entry. + * The Block Object offers the necessary data to display and edit a block. + * The Block Object setups live syncronization of content and settings models back to the data of your Property Editor model. + * The returned object, named ´BlockObject´, contains several usefull models to make editing of this block happen. + * The ´BlockObject´ contains the following properties: + * - key {string}: runtime generated key, usefull for tracking of this object + * - content {Object}: Content model, the content data in a ElementType model. + * - settings {Object}: Settings model, the settings data in a ElementType model. + * - config {Object}: A local deep copy of the block configuration model. + * - label {string}: The label for this block. + * - updateLabel {Method}: Method to trigger an update of the label for this block. + * - data {Object}: A reference to the content data object from your property editor model. + * - settingsData {Object}: A reference to the settings data object from your property editor model. + * - layout {Object}: A refernce to the layout entry from your property editor model. + * @param {Object} layoutEntry the layout entry object to build the block model from. + * @return {Object | null} The BlockObject for the given layout entry. Or null if data or configuration wasnt found for this block. + */ + getBlockObject: function (layoutEntry) { + + var contentUdi = layoutEntry.contentUdi; + + var dataModel = getDataByUdi(contentUdi, this.value.contentData); + + if (dataModel === null) { + console.error("Couldn't find content data of " + contentUdi) + return null; + } + + var blockConfiguration = this.getBlockConfiguration(dataModel.contentTypeKey); + var contentScaffold = null; + + if (blockConfiguration === null) { + console.warn("The block of " + contentUdi + " is not being initialized because its contentTypeKey('" + dataModel.contentTypeKey + "') is not allowed for this PropertyEditor"); + } else { + contentScaffold = this.getScaffoldFromKey(blockConfiguration.contentElementTypeKey); + if (contentScaffold === null) { + console.error("The block of " + contentUdi + " is not begin initialized cause its Element Type was not loaded."); + } + } + + if (blockConfiguration === null || contentScaffold === null) { + + blockConfiguration = { + label: "Unsupported", + unsupported: true + }; + } + + var blockObject = {}; + // Set an angularJS cloneNode method, to avoid this object begin cloned. + blockObject.cloneNode = function () { + return null;// angularJS accept this as a cloned value as long as the + } + blockObject.key = String.CreateGuid().replace(/-/g, ""); + blockObject.config = Utilities.copy(blockConfiguration); + if (blockObject.config.label && blockObject.config.label !== "") { + blockObject.labelInterpolator = $interpolate(blockObject.config.label); + } + blockObject.__scope = this.isolatedScope; + blockObject.updateLabel = _.debounce( + function () { + // Check wether scope still exists, maybe object was destoyed in these seconds. + if (this.__scope) { + this.label = getBlockLabel(this); + this.__scope.$evalAsync(); + } + }.bind(blockObject) + , 10); + + // make basics from scaffold + if(contentScaffold !== null) {// We might not have contentScaffold + blockObject.content = Utilities.copy(contentScaffold); + ensureUdiAndKey(blockObject.content, contentUdi); + + mapToElementModel(blockObject.content, dataModel); + } else { + blockObject.content = null; + } + + blockObject.data = dataModel; + blockObject.layout = layoutEntry; + blockObject.__watchers = []; + + if (blockConfiguration.settingsElementTypeKey) { + var settingsScaffold = this.getScaffoldFromKey(blockConfiguration.settingsElementTypeKey); + if (settingsScaffold !== null) { + + if (!layoutEntry.settingsUdi) { + // if this block does not have settings data, then create it. This could happen because settings model has been added later than this content was created. + layoutEntry.settingsUdi = createDataEntry(blockConfiguration.settingsElementTypeKey, this.value.settingsData); + } + + var settingsUdi = layoutEntry.settingsUdi; + + var settingsData = getDataByUdi(settingsUdi, this.value.settingsData); + if (settingsData === null) { + console.error("Couldnt find settings data of " + settingsUdi) + return null; + } + + blockObject.settingsData = settingsData; + + // make basics from scaffold + blockObject.settings = Utilities.copy(settingsScaffold); + ensureUdiAndKey(blockObject.settings, settingsUdi); + + mapToElementModel(blockObject.settings, settingsData); + + // add settings content-app + appendSettingsContentApp(blockObject.content, this.__labels.settingsName); + } + } + + blockObject.retrieveValuesFrom = function (content, settings) { + if (this.content !== null) { + mapElementValues(content, this.content); + } + if (this.config.settingsElementTypeKey !== null) { + mapElementValues(settings, this.settings); + } + }; + + blockObject.sync = function () { + if (this.content !== null) { + mapToPropertyModel(this.content, this.data); + } + if (this.config.settingsElementTypeKey !== null) { + mapToPropertyModel(this.settings, this.settingsData); + } + }; + + // first time instant update of label. + blockObject.label = getBlockLabel(blockObject); + + // Add blockObject to our isolated scope to enable watching its values: + this.isolatedScope.blockObjects["_" + blockObject.key] = blockObject; + addWatchers(blockObject, this.isolatedScope); + addWatchers(blockObject, this.isolatedScope, true); + + blockObject.destroy = function () { + // remove property value watchers: + this.__watchers.forEach(w => { w(); }); + delete this.__watchers; + + // help carbage collector: + delete this.config; + + delete this.layout; + delete this.data; + delete this.settingsData; + delete this.content; + delete this.settings; + + // remove model from isolatedScope. + delete this.__scope.blockObjects["_" + this.key]; + // NOTE: It seems like we should call this.__scope.$destroy(); since that is the only way to remove a scope from it's parent, + // however that is not the case since __scope is actually this.isolatedScope which gets cleaned up when the outer scope is + // destroyed. If we do that here it breaks the scope chain and validation. + delete this.__scope; + + // removes this method, making it impossible to destroy again. + delete this.destroy; + + // lets remove the key to make things blow up if this is still referenced: + delete this.key; + } + + return blockObject; + }, + + /** + * @ngdoc method + * @name removeDataAndDestroyModel + * @methodOf umbraco.services.blockEditorModelObject + * @description Removes the data and destroys the Block Model. + * Notice this method does not remove the block from your layout, this will need to be handlede by the Property Editor since this services donst know about your layout structure. + * @param {Object} blockObject The BlockObject to be removed and destroyed. + */ + removeDataAndDestroyModel: function (blockObject) { + var udi = blockObject.layout.contentUdi; + var settingsUdi = blockObject.layout.settingsUdi || null; + this.destroyBlockObject(blockObject); + this.removeDataByUdi(udi); + if (settingsUdi) { + this.removeSettingsByUdi(settingsUdi); + } + }, + + /** + * @ngdoc method + * @name destroyBlockObject + * @methodOf umbraco.services.blockEditorModelObject + * @description Destroys the Block Model, but all data is kept. + * @param {Object} blockObject The BlockObject to be destroyed. + */ + destroyBlockObject: function (blockObject) { + blockObject.destroy(); + }, + + /** + * @ngdoc method + * @name getLayout + * @methodOf umbraco.services.blockEditorModelObject + * @description Retrieve the layout object from this specific property editor model. + * @param {object} defaultStructure if no data exist the layout of your poerty editor will be set to this object. + * @return {Object} Layout object, structure depends on the model of your property editor. + */ + getLayout: function (defaultStructure) { + if (!this.value.layout[this.propertyEditorAlias]) { + this.value.layout[this.propertyEditorAlias] = defaultStructure; + } + return this.value.layout[this.propertyEditorAlias]; + }, + + /** + * @ngdoc method + * @name create + * @methodOf umbraco.services.blockEditorModelObject + * @description Create a empty layout entry, notice the layout entry is not added to the property editors model layout object, since the layout sturcture depends on the property editor. + * @param {string} contentElementTypeKey the contentElementTypeKey of the block you wish to create, if contentElementTypeKey is not avaiable in the block configuration then ´null´ will be returned. + * @return {Object | null} Layout entry object, to be inserted at a decired location in the layout object. Or null if contentElementTypeKey is unavaiaible. + */ + create: function (contentElementTypeKey) { + + var blockConfiguration = this.getBlockConfiguration(contentElementTypeKey); + if (blockConfiguration === null) { + return null; + } + + var entry = { + contentUdi: createDataEntry(contentElementTypeKey, this.value.contentData) + } + + if (blockConfiguration.settingsElementTypeKey != null) { + entry.settingsUdi = createDataEntry(blockConfiguration.settingsElementTypeKey, this.value.settingsData) + } + + return entry; + }, + + /** + * @ngdoc method + * @name createFromElementType + * @methodOf umbraco.services.blockEditorModelObject + * @description Insert data from ElementType Model + * @return {Object | null} Layout entry object, to be inserted at a decired location in the layout object. Or ´null´ if the given ElementType isnt supported by the block configuration. + */ + createFromElementType: function (elementTypeDataModel) { + + elementTypeDataModel = clipboardService.parseContentForPaste(elementTypeDataModel, clipboardService.TYPES.ELEMENT_TYPE); + + var contentElementTypeKey = elementTypeDataModel.contentTypeKey; + + var layoutEntry = this.create(contentElementTypeKey); + if (layoutEntry === null) { + return null; + } + + var dataModel = getDataByUdi(layoutEntry.contentUdi, this.value.contentData); + if (dataModel === null) { + return null; + } + + mapToPropertyModel(elementTypeDataModel, dataModel); + + return layoutEntry; + + }, + /** + * @ngdoc method + * @name createFromBlockData + * @methodOf umbraco.services.blockEditorModelObject + * @description Insert data from raw models + * @return {Object | null} Layout entry object, to be inserted at a decired location in the layout object. Or ´null´ if the given ElementType isnt supported by the block configuration. + */ + createFromBlockData: function (blockData) { + + blockData = clipboardService.parseContentForPaste(blockData, clipboardService.TYPES.BLOCK); + + // As the blockData is a cloned object we can use its layout part for our layout entry. + var layoutEntry = blockData.layout; + if (layoutEntry === null) { + return null; + } + + var blockConfiguration; + + if (blockData.data) { + // Ensure that we support the alias: + blockConfiguration = this.getBlockConfiguration(blockData.data.contentTypeKey); + if(blockConfiguration === null) { + return null; + } + + this.value.contentData.push(blockData.data); + } else { + // We do not have data, this cannot be succesful paste. + return null; + } + + if (blockData.settingsData) { + // Ensure that we support the alias: + if(blockConfiguration.settingsElementTypeKey) { + // If we have settings for this Block Configuration, we need to check that they align, if we dont we do not want to fail. + if(blockConfiguration.settingsElementTypeKey === blockData.settingsData.contentTypeKey) { + this.value.settingsData.push(blockData.settingsData); + } else { + notificationsService.error("Clipboard", "Couldn't paste because settings-data is not compatible."); + return null; + } + } else { + // We do not have settings currently, so lets get rid of the settings part and move on with the paste. + delete layoutEntry.settingUdi; + } + } + + return layoutEntry; + + }, + + /** + * @ngdoc method + * @name sync + * @methodOf umbraco.services.blockEditorModelObject + * @description Force immidiate update of the blockobject models to the property model. + */ + sync: function () { + for (const key in this.isolatedScope.blockObjects) { + this.isolatedScope.blockObjects[key].sync(); + } + }, + + /** + * @ngdoc method + * @name removeDataByUdi + * @methodOf umbraco.services.blockEditorModelObject + * @description Removes the content data of a given UDI. + * Notice this method does not remove the block from your layout, this will need to be handled by the Property Editor since this services don't know about your layout structure. + * @param {string} udi The UDI of the content data to be removed. + */ + removeDataByUdi: function (udi) { + const index = this.value.contentData.findIndex(o => o.udi === udi); + if (index !== -1) { + this.value.contentData.splice(index, 1); + } + }, + + /** + * @ngdoc method + * @name removeSettingsByUdi + * @methodOf umbraco.services.blockEditorModelObject + * @description Removes the settings data of a given UDI. + * Notice this method does not remove the settingsUdi from your layout, this will need to be handled by the Property Editor since this services don't know about your layout structure. + * @param {string} udi The UDI of the settings data to be removed. + */ + removeSettingsByUdi: function (udi) { + const index = this.value.settingsData.findIndex(o => o.udi === udi); + if (index !== -1) { + this.value.settingsData.splice(index, 1); + } + }, + + /** + * @ngdoc method + * @name destroy + * @methodOf umbraco.services.blockEditorModelObject + * @description Notice you should not need to destroy the BlockEditorModelObject since it will automaticly be destroyed when the scope of existance gets destroyed. + */ + destroy: function () { + + this.__watchers.forEach(w => { w(); }); + for (const key in this.isolatedScope.blockObjects) { + this.destroyBlockObject(this.isolatedScope.blockObjects[key]); + } + + delete this.__watchers; + delete this.value; + delete this.propertyEditorAlias; + delete this.blockConfigurations; + delete this.scaffolds; + this.isolatedScope.$destroy(); + delete this.isolatedScope; + delete this.destroy; + } + } + + return BlockEditorModelObject; + } + + angular.module('umbraco.services').service('blockEditorModelObject', blockEditorModelObjectFactory); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/common/services/clipboard.service.js b/src/Umbraco.Web.UI.Client/src/common/services/clipboard.service.js index d7d6ec862c..83fd3d08c2 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/clipboard.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/clipboard.service.js @@ -10,14 +10,69 @@ * The service has a set way for defining a data-set by a entryType and alias, which later will be used to retrive the posible entries for a paste scenario. * */ -function clipboardService(notificationsService, eventsService, localStorageService, iconHelper) { - +function clipboardService($window, notificationsService, eventsService, localStorageService, iconHelper) { - var clearPropertyResolvers = []; - + const TYPES = {}; + TYPES.ELEMENT_TYPE = "elementType"; + TYPES.BLOCK = "block"; + TYPES.RAW = "raw"; + + var clearPropertyResolvers = {}; + var pastePropertyResolvers = {}; + var clipboardTypeResolvers = {}; + + clipboardTypeResolvers[TYPES.ELEMENT_TYPE] = function(element, propMethod) { + for (var t = 0; t < element.variants[0].tabs.length; t++) { + var tab = element.variants[0].tabs[t]; + for (var p = 0; p < tab.properties.length; p++) { + var prop = tab.properties[p]; + propMethod(prop, TYPES.ELEMENT_TYPE); + } + } + } + clipboardTypeResolvers[TYPES.BLOCK] = function (block, propMethod) { + + propMethod(block, TYPES.BLOCK); + + if(block.data) { + Object.keys(block.data).forEach( key => { + if(key === 'udi' || key === 'contentTypeKey') { + return; + } + propMethod(block.data[key], TYPES.RAW); + }); + } + + if(block.settingsData) { + Object.keys(block.settingsData).forEach( key => { + if(key === 'udi' || key === 'contentTypeKey') { + return; + } + propMethod(block.settingsData[key], TYPES.RAW); + }); + } + + /* + // Concept for supporting Block that contains other Blocks. + // Missing clarifications: + // How do we ensure that the inner blocks of a block is supported in the new scenario. Not that likely but still relevant, so considerations should be made. + if(block.references) { + // A Block clipboard entry can contain other Block Clipboard Entries, here we will make sure to resolve those identical to the main entry. + for (var r = 0; r < block.references.length; r++) { + clipboardTypeResolvers[TYPES.BLOCK](block.references[r], propMethod); + } + } + */ + } + clipboardTypeResolvers[TYPES.RAW] = function(data, propMethod) { + for (var p = 0; p < data.length; p++) { + propMethod(data[p], TYPES.RAW); + } + } + var STORAGE_KEY = "umbClipboardService"; - + var retriveStorage = function() { if (localStorageService.isSupported === false) { return null; @@ -27,58 +82,62 @@ function clipboardService(notificationsService, eventsService, localStorageServi if (dataString != null) { dataJSON = JSON.parse(dataString); } - + if(dataJSON == null) { dataJSON = new Object(); } - + if(dataJSON.entries === undefined) { dataJSON.entries = []; } - + return dataJSON; } - + var saveStorage = function(storage) { var storageString = JSON.stringify(storage); - + try { + // Check that we can parse the JSON: var storageJSON = JSON.parse(storageString); + + // Store the string: localStorageService.set(STORAGE_KEY, storageString); - + eventsService.emit("clipboardService.storageUpdate"); - + return true; } catch(e) { return false; } - + return false; } - function clearPropertyForStorage(prop) { + function resolvePropertyForStorage(prop, type) { - for (var i=0; i allowedAlias === entry.alias).length > 0) - || + || (entry.aliases && entry.aliases.filter(entryAlias => allowedAliases.filter(allowedAlias => allowedAlias === entryAlias).length > 0).length === entry.aliases.length) ); } - - + + + + + function resolvePropertyForPaste(prop, type) { + + type = type || "raw"; + var resolvers = pastePropertyResolvers[type]; + if (resolvers) { + for (var i=0; i { return entry.unique !== uniqueKey; } ); - - var entry = {unique:uniqueKey, type:type, alias:alias, data:prepareEntryForStorage(data, firstLevelClearupMethod), label:displayLabel, icon:displayIcon}; + + var entry = {unique:uniqueKey, type:type, alias:alias, data:prepareEntryForStorage(type, data, firstLevelClearupMethod), label:displayLabel, icon:displayIcon, date:Date.now()}; storage.entries.push(entry); - + if (saveStorage(storage) === true) { notificationsService.success("Clipboard", "Copied to clipboard."); } else { notificationsService.error("Clipboard", "Couldnt copy this data to clipboard."); } - + }; @@ -188,32 +336,35 @@ function clipboardService(notificationsService, eventsService, localStorageServi * Saves a single JS-object with a type and alias to the clipboard. */ service.copyArray = function(type, aliases, datas, displayLabel, displayIcon, uniqueKey, firstLevelClearupMethod) { - + + if (type === "elementTypeArray") { + type = "elementType"; + } + var storage = retriveStorage(); - + // Clean up each entry - var copiedDatas = datas.map(data => prepareEntryForStorage(data, firstLevelClearupMethod)); - + var copiedDatas = datas.map(data => prepareEntryForStorage(type, data, firstLevelClearupMethod)); + // remove previous copies of this entry: storage.entries = storage.entries.filter( (entry) => { return entry.unique !== uniqueKey; } ); - - var entry = {unique:uniqueKey, type:type, aliases:aliases, data:copiedDatas, label:displayLabel, icon:displayIcon}; + var entry = {unique:uniqueKey, type:type, aliases:aliases, data:copiedDatas, label:displayLabel, icon:displayIcon, date:Date.now()}; storage.entries.push(entry); - + if (saveStorage(storage) === true) { notificationsService.success("Clipboard", "Copied to clipboard."); } else { notificationsService.error("Clipboard", "Couldnt copy this data to clipboard."); } - + }; - - + + /** * @ngdoc method * @name umbraco.services.supportsCopy#supported @@ -225,7 +376,7 @@ function clipboardService(notificationsService, eventsService, localStorageServi service.isSupported = function() { return localStorageService.isSupported; }; - + /** * @ngdoc method * @name umbraco.services.supportsCopy#hasEntriesOfType @@ -238,14 +389,14 @@ function clipboardService(notificationsService, eventsService, localStorageServi * Determines whether the current clipboard has entries that match a given type and one of the aliases. */ service.hasEntriesOfType = function(type, aliases) { - + if(service.retriveEntriesOfType(type, aliases).length > 0) { return true; } - + return false; }; - + /** * @ngdoc method * @name umbraco.services.supportsCopy#retriveEntriesOfType @@ -253,24 +404,24 @@ function clipboardService(notificationsService, eventsService, localStorageServi * * @param {string} type A string defining the type of data to recive. * @param {string} aliases A array of strings providing the alias of the data you want to recive. - * + * * @description * Returns an array of entries matching the given type and one of the provided aliases. */ service.retriveEntriesOfType = function(type, allowedAliases) { - + var storage = retriveStorage(); - + // Find entries that are fulfilling the criteria for this nodeType and nodeTypesAliases. var filteretEntries = storage.entries.filter( (entry) => { return isEntryCompatible(entry, type, allowedAliases); } ); - + return filteretEntries; }; - + /** * @ngdoc method * @name umbraco.services.supportsCopy#retriveEntriesOfType @@ -278,14 +429,14 @@ function clipboardService(notificationsService, eventsService, localStorageServi * * @param {string} type A string defining the type of data to recive. * @param {string} aliases A array of strings providing the alias of the data you want to recive. - * + * * @description * Returns an array of data of entries matching the given type and one of the provided aliases. */ service.retriveDataOfType = function(type, aliases) { return service.retriveEntriesOfType(type, aliases).map((x) => x.data); }; - + /** * @ngdoc method * @name umbraco.services.supportsCopy#retriveEntriesOfType @@ -293,12 +444,12 @@ function clipboardService(notificationsService, eventsService, localStorageServi * * @param {string} type A string defining the type of data to remove. * @param {string} aliases A array of strings providing the alias of the data you want to remove. - * + * * @description * Removes entries matching the given type and one of the provided aliases. */ service.clearEntriesOfType = function(type, allowedAliases) { - + var storage = retriveStorage(); // Find entries that are NOT fulfilling the criteria for this nodeType and nodeTypesAliases. @@ -307,17 +458,24 @@ function clipboardService(notificationsService, eventsService, localStorageServi return !isEntryCompatible(entry, type, allowedAliases); } ); - + storage.entries = filteretEntries; saveStorage(storage); }; - - - + + + var emitClipboardStorageUpdate = _.debounce(function(e) { + eventsService.emit("clipboardService.storageUpdate"); + }, 1000); + + // Fires if LocalStorage was changed from another tab than this one. + $window.addEventListener("storage", emitClipboardStorageUpdate); + + + return service; } angular.module("umbraco.services").factory("clipboardService", clipboardService); - diff --git a/src/Umbraco.Web.UI.Client/src/common/services/contentapphelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/contentapphelper.service.js new file mode 100644 index 0000000000..0b3dc2c6e0 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/services/contentapphelper.service.js @@ -0,0 +1,35 @@ + +/** +* @ngdoc service +* @name umbraco.services.contentAppHelper +* @description A helper service for content app related functions. +**/ +function contentAppHelper() { + + var service = {}; + + /** + * Default known content based apps. + */ + service.CONTENT_BASED_APPS = [ "umbContent", "umbInfo", "umbListView" ]; + + /** + * @ngdoc method + * @name umbraco.services.contentAppHelper#isContentBasedApp + * @methodOf umbraco.services.contentAppHelper + * + * @param {object} app A content app to check + * + * @description + * Determines whether the supplied content app is a known content based app + * + */ + service.isContentBasedApp = function (app) { + return service.CONTENT_BASED_APPS.indexOf(app.alias) !== -1; + } + + return service; + +} + +angular.module('umbraco.services').factory('contentAppHelper', contentAppHelper); diff --git a/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js index bfcc0d536e..b301960eab 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js @@ -32,6 +32,26 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, editorSt return true; } + function showNotificationsForModelsState(ms) { + for (const [key, value] of Object.entries(ms)) { + + var errorMsg = value[0]; + // if the error message is json it's a complex editor validation response that we need to parse + if ((Utilities.isString(errorMsg) && errorMsg.startsWith("[")) || Utilities.isArray(errorMsg)) { + // flatten the json structure, create validation paths for each property and add each as a property error + var idsToErrors = serverValidationManager.parseComplexEditorError(errorMsg, ""); + idsToErrors.forEach(x => { + if (x.modelState) { + showNotificationsForModelsState(x.modelState); + } + }); + } + else if (value[0]) { + notificationsService.error("Validation", value[0]); + } + } + } + return { //TODO: We need to move some of this to formHelper for saving, too many editors use this method for saving when this entire @@ -64,7 +84,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, editorSt //when true, the url will change but it won't actually re-route //this is merely here for compatibility, if only the content/media/members used this service we'd prob be ok but tons of editors //use this service unfortunately and probably packages too. - args.softRedirect = false; + args.softRedirect = false; } @@ -97,12 +117,18 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, editorSt return $q.resolve(data); }, function (err) { + + formHelper.resetForm({ scope: args.scope, hasErrors: true }); + self.handleSaveError({ showNotifications: args.showNotifications, softRedirect: args.softRedirect, err: err, rebindCallback: function () { - rebindCallback.apply(self, [args.content, err.data]); + // if the error contains data, we want to map that back as we want to continue editing this save. Especially important when the content is new as the returned data will contain ID etc. + if(err.data) { + rebindCallback.apply(self, [args.content, err.data]); + } } }); @@ -131,7 +157,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, editorSt // first check if tab is already added var foundInfoTab = false; - angular.forEach(tabs, function (tab) { + tabs.forEach(function (tab) { if (tab.id === infoTab.id && tab.alias === infoTab.alias) { foundInfoTab = true; } @@ -278,7 +304,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, editorSt } // if publishing is allowed also allow schedule publish - // we add this manually becuase it doesn't have a permission so it wont + // we add this manually becuase it doesn't have a permission so it wont // get picked up by the loop through permissions if (_.contains(args.content.allowedActions, "U")) { buttons.subButtons.push(createButtonDefinition("SCHEDULE")); @@ -569,7 +595,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, editorSt //instead of having a property editor $watch their expression to check if it has // been updated, instead we'll check for the existence of a special method on their model // and just call it. - if (angular.isFunction(origProp.onValueChanged)) { + if (Utilities.isFunction(origProp.onValueChanged)) { //send the newVal + oldVal origProp.onValueChanged(origProp.value, origVal); } @@ -602,7 +628,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, editorSt if (!args.err) { throw "args.err cannot be null"; } - + //When the status is a 400 status with a custom header: X-Status-Reason: Validation failed, we have validation errors. //Otherwise the error is probably due to invalid data (i.e. someone mucking around with the ids or something). //Or, some strange server error @@ -615,17 +641,15 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, editorSt //add model state errors to notifications if (args.showNotifications) { - for (var e in args.err.data.ModelState) { - notificationsService.error("Validation", args.err.data.ModelState[e][0]); - } + showNotificationsForModelsState(args.err.data.ModelState); } if (!this.redirectToCreatedContent(args.err.data.id, args.softRedirect) || args.softRedirect) { // If we are not redirecting it's because this is not newly created content, else in some cases we are - // soft-redirecting which means the URL will change but the route wont (i.e. creating content). + // soft-redirecting which means the URL will change but the route wont (i.e. creating content). // In this case we need to detect what properties have changed and re-bind them with the server data. - if (args.rebindCallback && angular.isFunction(args.rebindCallback)) { + if (args.rebindCallback && Utilities.isFunction(args.rebindCallback)) { args.rebindCallback(); } @@ -669,10 +693,10 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, editorSt if (!this.redirectToCreatedContent(args.redirectId ? args.redirectId : args.savedContent.id, args.softRedirect) || args.softRedirect) { // If we are not redirecting it's because this is not newly created content, else in some cases we are - // soft-redirecting which means the URL will change but the route wont (i.e. creating content). + // soft-redirecting which means the URL will change but the route wont (i.e. creating content). // In this case we need to detect what properties have changed and re-bind them with the server data. - if (args.rebindCallback && angular.isFunction(args.rebindCallback)) { + if (args.rebindCallback && Utilities.isFunction(args.rebindCallback)) { args.rebindCallback(); } } @@ -705,7 +729,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, editorSt navigationService.setSoftRedirect(); } //change to new path - $location.path("/" + $routeParams.section + "/" + $routeParams.tree + "/" + $routeParams.method + "/" + id); + $location.path("/" + $routeParams.section + "/" + $routeParams.tree + "/" + $routeParams.method + "/" + id); //don't add a browser history for this $location.replace(); return true; diff --git a/src/Umbraco.Web.UI.Client/src/common/services/contenttypehelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/contenttypehelper.service.js index 1be66cc68f..9cec15d519 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/contenttypehelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/contenttypehelper.service.js @@ -11,7 +11,7 @@ function contentTypeHelper(contentTypeResource, dataTypeResource, $filter, $inje var newArray = []; - angular.forEach(array, function (arrayItem) { + array.forEach(function (arrayItem) { if (Utilities.isObject(arrayItem)) { newArray.push(arrayItem.id); @@ -116,13 +116,12 @@ function contentTypeHelper(contentTypeResource, dataTypeResource, $filter, $inje throw new Error("Cannot add this composition, these properties already exist on the content type: " + overlappingAliases.join()); } - angular.forEach(compositeContentType.groups, function (compositionGroup) { - + compositeContentType.groups.forEach(function (compositionGroup) { // order composition groups based on sort order compositionGroup.properties = $filter('orderBy')(compositionGroup.properties, 'sortOrder'); // get data type details - angular.forEach(compositionGroup.properties, function (property) { + compositionGroup.properties.forEach(function (property) { dataTypeResource.getById(property.dataTypeId) .then(function (dataType) { property.dataTypeIcon = dataType.icon; @@ -134,7 +133,7 @@ function contentTypeHelper(contentTypeResource, dataTypeResource, $filter, $inje compositionGroup.inherited = true; // set inherited state on properties - angular.forEach(compositionGroup.properties, function (compositionProperty) { + compositionGroup.properties.forEach(function (compositionProperty) { compositionProperty.inherited = true; }); @@ -142,7 +141,7 @@ function contentTypeHelper(contentTypeResource, dataTypeResource, $filter, $inje compositionGroup.tabState = "inActive"; // if groups are named the same - merge the groups - angular.forEach(contentType.groups, function (contentTypeGroup) { + contentType.groups.forEach(function (contentTypeGroup) { if (contentTypeGroup.name === compositionGroup.name) { @@ -224,7 +223,7 @@ function contentTypeHelper(contentTypeResource, dataTypeResource, $filter, $inje var groups = []; - angular.forEach(contentType.groups, function (contentTypeGroup) { + contentType.groups.forEach(function (contentTypeGroup) { if (contentTypeGroup.tabState !== "init") { @@ -238,7 +237,7 @@ function contentTypeHelper(contentTypeResource, dataTypeResource, $filter, $inje var properties = []; // remove all properties from composite content type - angular.forEach(contentTypeGroup.properties, function (property) { + contentTypeGroup.properties.forEach(function (property) { if (property.contentTypeId !== compositeContentType.id) { properties.push(property); } @@ -283,7 +282,7 @@ function contentTypeHelper(contentTypeResource, dataTypeResource, $filter, $inje var sortOrder = 0; - angular.forEach(properties, function (property) { + properties.forEach(function (property) { if (!property.inherited && property.propertyState !== "init") { property.sortOrder = sortOrder; } diff --git a/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js b/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js index 538bd41ce0..326123f797 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js @@ -179,8 +179,7 @@ When building a custom infinite editor view you can use the same components as a } else { focus(); } - }); - + }); /** * @ngdoc method @@ -248,11 +247,11 @@ When building a custom infinite editor view you can use the same components as a * @methodOf umbraco.services.editorService * * @description - * Method to open a new editor in infinite editing + * Method to open a new editor in infinite editing. * - * @param {Object} editor rendering options - * @param {String} editor.view Path to view - * @param {String} editor.size Sets the size of the editor ("small" || "medium"). If nothing is set it will use full width. + * @param {object} editor rendering options. + * @param {string} editor.view Path to view. + * @param {string} editor.size Sets the size of the editor ("small" || "medium"). If nothing is set it will use full width. */ function open(editor) { @@ -342,18 +341,18 @@ When building a custom infinite editor view you can use the same components as a * @methodOf umbraco.services.editorService * * @description - * Opens a content editor in infinite editing, the submit callback returns the updated content item - * @param {Object} editor rendering options - * @param {String} editor.id The id of the content item - * @param {Boolean} editor.create Create new content item - * @param {Function} editor.submit Callback function when the publish and close button is clicked. Returns the editor model object - * @param {Function} editor.close Callback function when the close button is clicked. - * @param {String} editor.parentId If editor.create is true, provide parentId for the creation of the content item - * @param {String} editor.documentTypeAlias If editor.create is true, provide document type alias for the creation of the content item - * @param {Boolean} editor.allowSaveAndClose If editor is being used in infinite editing allows the editor to close when the save action is performed - * @param {Boolean} editor.allowPublishAndClose If editor is being used in infinite editing allows the editor to close when the publish action is performed + * Opens a content editor in infinite editing, the submit callback returns the updated content item. + * @param {object} editor rendering options. + * @param {string} editor.id The id of the content item. + * @param {boolean} editor.create Create new content item. + * @param {function} editor.submit Callback function when the publish and close button is clicked. Returns the editor model object. + * @param {function} editor.close Callback function when the close button is clicked. + * @param {string} editor.parentId If editor.create is true, provide parentId for the creation of the content item. + * @param {string} editor.documentTypeAlias If editor.create is true, provide document type alias for the creation of the content item. + * @param {boolean} editor.allowSaveAndClose If editor is being used in infinite editing allows the editor to close when the save action is performed. + * @param {boolean} editor.allowPublishAndClose If editor is being used in infinite editing allows the editor to close when the publish action is performed. * - * @returns {Object} editor object + * @returns {object} editor object */ function contentEditor(editor) { editor.view = "views/content/edit.html"; @@ -366,15 +365,15 @@ When building a custom infinite editor view you can use the same components as a * @methodOf umbraco.services.editorService * * @description - * Opens a content picker in infinite editing, the submit callback returns an array of selected items + * Opens a content picker in infinite editing, the submit callback returns an array of selected items. * - * @param {Object} editor rendering options - * @param {Boolean} editor.multiPicker Pick one or multiple items - * @param {Int} editor.startNodeId Set the startnode of the picker (optional) - * @param {Function} editor.submit Callback function when the submit button is clicked. Returns the editor model object - * @param {Function} editor.close Callback function when the close button is clicked. + * @param {object} editor rendering options. + * @param {boolean} editor.multiPicker Pick one or multiple items. + * @param {number} editor.startNodeId Set the startnode of the picker (optional). + * @param {function} editor.submit Callback function when the submit button is clicked. Returns the editor model object. + * @param {function} editor.close Callback function when the close button is clicked. * - * @returns {Object} editor object + * @returns {object} editor object. */ function contentPicker(editor) { editor.view = "views/common/infiniteeditors/treepicker/treepicker.html"; @@ -392,12 +391,12 @@ When building a custom infinite editor view you can use the same components as a * @description * Opens a content type picker in infinite editing, the submit callback returns an array of selected items * - * @param {Object} editor rendering options - * @param {Boolean} editor.multiPicker Pick one or multiple items - * @param {Function} editor.submit Callback function when the submit button is clicked. Returns the editor model object - * @param {Function} editor.close Callback function when the close button is clicked. + * @param {object} editor rendering options. + * @param {boolean} editor.multiPicker Pick one or multiple items. + * @param {function} editor.submit Callback function when the submit button is clicked. Returns the editor model object. + * @param {function} editor.close Callback function when the close button is clicked. * - * @returns {Object} editor object + * @returns {object} editor object */ function contentTypePicker(editor) { editor.view = "views/common/infiniteeditors/treepicker/treepicker.html"; @@ -413,14 +412,14 @@ When building a custom infinite editor view you can use the same components as a * @methodOf umbraco.services.editorService * * @description - * Opens a media type picker in infinite editing, the submit callback returns an array of selected items + * Opens a media type picker in infinite editing, the submit callback returns an array of selected items. * - * @param {Object} editor rendering options - * @param {Boolean} editor.multiPicker Pick one or multiple items - * @param {Function} editor.submit Callback function when the submit button is clicked. Returns the editor model object - * @param {Function} editor.close Callback function when the close button is clicked. + * @param {object} editor rendering options. + * @param {boolean} editor.multiPicker Pick one or multiple items. + * @param {function} editor.submit Callback function when the submit button is clicked. Returns the editor model object. + * @param {function} editor.close Callback function when the close button is clicked. * - * @returns {Object} editor object + * @returns {object} editor object. */ function mediaTypePicker(editor) { editor.view = "views/common/infiniteeditors/treepicker/treepicker.html"; @@ -436,14 +435,14 @@ When building a custom infinite editor view you can use the same components as a * @methodOf umbraco.services.editorService * * @description - * Opens a member type picker in infinite editing, the submit callback returns an array of selected items + * Opens a member type picker in infinite editing, the submit callback returns an array of selected items. * - * @param {Object} editor rendering options - * @param {Boolean} editor.multiPicker Pick one or multiple items - * @param {Function} editor.submit Callback function when the submit button is clicked. Returns the editor model object - * @param {Function} editor.close Callback function when the close button is clicked. + * @param {object} editor rendering options. + * @param {boolean} editor.multiPicker Pick one or multiple items. + * @param {function} editor.submit Callback function when the submit button is clicked. Returns the editor model object. + * @param {function} editor.close Callback function when the close button is clicked. * - * @returns {Object} editor object + * @returns {object} editor object. */ function memberTypePicker(editor) { editor.view = "views/common/infiniteeditors/treepicker/treepicker.html"; @@ -458,12 +457,12 @@ When building a custom infinite editor view you can use the same components as a * @methodOf umbraco.services.editorService * * @description - * Opens a copy editor in infinite editing, the submit callback returns an array of selected items - * @param {String} editor.section The node entity type - * @param {String} editor.currentNode The current node id - * @param {Callback} editor.submit Saves, submits, and closes the editor - * @param {Callback} editor.close Closes the editor - * @returns {Object} editor object + * Opens a copy editor in infinite editing, the submit callback returns an array of selected items. + * @param {string} editor.section The node entity type. + * @param {string} editor.currentNode The current node id. + * @param {function} editor.submit Saves, submits, and closes the editor. + * @param {function} editor.close Closes the editor. + * @returns {object} editor object. */ function copy(editor) { @@ -479,11 +478,11 @@ When building a custom infinite editor view you can use the same components as a * * @description * Opens a move editor in infinite editing. - * @param {String} editor.section The node entity type - * @param {String} editor.currentNode The current node id - * @param {Callback} editor.submit Saves, submits, and closes the editor - * @param {Callback} editor.close Closes the editor - * @returns {Object} editor object + * @param {string} editor.section The node entity type. + * @param {string} editor.currentNode The current node id. + * @param {function} editor.submit Saves, submits, and closes the editor. + * @param {function} editor.close Closes the editor. + * @returns {object} editor object. */ function move(editor) { @@ -499,9 +498,9 @@ When building a custom infinite editor view you can use the same components as a * * @description * Opens an embed editor in infinite editing. - * @param {Callback} editor.submit Saves, submits, and closes the editor - * @param {Callback} editor.close Closes the editor - * @returns {Object} editor object + * @param {function} editor.submit Saves, submits, and closes the editor. + * @param {function} editor.close Closes the editor. + * @returns {object} editor object. */ function embed(editor) { @@ -517,10 +516,10 @@ When building a custom infinite editor view you can use the same components as a * * @description * Opens a rollback editor in infinite editing. - * @param {String} editor.node The node to rollback - * @param {Callback} editor.submit Saves, submits, and closes the editor - * @param {Callback} editor.close Closes the editor - * @returns {Object} editor object + * @param {string} editor.node The node to rollback. + * @param {function} editor.submit Saves, submits, and closes the editor. + * @param {function} editor.close Closes the editor. + * @returns {object} editor object. */ function rollback(editor) { @@ -536,12 +535,12 @@ When building a custom infinite editor view you can use the same components as a * * @description * Opens an embed editor in infinite editing. - * @param {Object} editor rendering options - * @param {String} editor.icon The icon class - * @param {String} editor.color The color class - * @param {Callback} editor.submit Saves, submits, and closes the editor - * @param {Callback} editor.close Closes the editor - * @returns {Object} editor object + * @param {object} editor rendering options. + * @param {string} editor.icon The icon class. + * @param {string} editor.color The color class. + * @param {function} editor.submit Saves, submits, and closes the editor. + * @param {function} editor.close Closes the editor. + * @returns {object} editor object. */ function linkPicker(editor) { editor.view = "views/common/infiniteeditors/linkpicker/linkpicker.html"; @@ -555,13 +554,13 @@ When building a custom infinite editor view you can use the same components as a * @methodOf umbraco.services.editorService * * @description - * Opens a media editor in infinite editing, the submit callback returns the updated media item - * @param {Object} editor rendering options - * @param {String} editor.id The id of the media item - * @param {Boolean} editor.create Create new media item - * @param {Callback} editor.submit Saves, submits, and closes the editor - * @param {Callback} editor.close Closes the editor - * @returns {Object} editor object + * Opens a media editor in infinite editing, the submit callback returns the updated media item. + * @param {object} editor rendering options. + * @param {string} editor.id The id of the media item. + * @param {boolean} editor.create Create new media item. + * @param {function} editor.submit Saves, submits, and closes the editor. + * @param {function} editor.close Closes the editor. + * @returns {object} editor object. */ function mediaEditor(editor) { editor.view = "views/media/edit.html"; @@ -574,17 +573,17 @@ When building a custom infinite editor view you can use the same components as a * @methodOf umbraco.services.editorService * * @description - * Opens a media picker in infinite editing, the submit callback returns an array of selected media items - * @param {Object} editor rendering options - * @param {Int} editor.startNodeId Set the startnode of the picker (optional) - * @param {Boolean} editor.multiPicker Pick one or multiple items - * @param {Boolean} editor.onlyImages Only display files that have an image file-extension - * @param {Boolean} editor.disableFolderSelect Disable folder selection - * @param {Boolean} editor.disableFocalPoint Disable focal point editor for selected media - * @param {Array} editor.updatedMediaNodes A list of ids for media items that have been updated through the media picker - * @param {Callback} editor.submit Submits the editor - * @param {Callback} editor.close Closes the editor - * @returns {Object} editor object + * Opens a media picker in infinite editing, the submit callback returns an array of selected media items. + * @param {object} editor rendering options. + * @param {number} editor.startNodeId Set the startnode of the picker (optional). + * @param {boolean} editor.multiPicker Pick one or multiple items. + * @param {boolean} editor.onlyImages Only display files that have an image file-extension. + * @param {boolean} editor.disableFolderSelect Disable folder selection. + * @param {boolean} editor.disableFocalPoint Disable focal point editor for selected media. + * @param {array} editor.updatedMediaNodes A list of ids for media items that have been updated through the media picker. + * @param {function} editor.submit Submits the editor. + * @param {function} editor.close Closes the editor. + * @returns {object} editor object. */ function mediaPicker(editor) { editor.view = "views/common/infiniteeditors/mediapicker/mediapicker.html"; @@ -593,19 +592,36 @@ When building a custom infinite editor view you can use the same components as a open(editor); } + /** + * @ngdoc method + * @name umbraco.services.editorService#mediaCropDetails + * @methodOf umbraco.services.editorService + * + * @description + * Opens the media crop details editor in infinite editing, the submit callback returns the updated media object. + * @param {object} editor rendering options. + * @param {function} editor.submit Submits the editor. + * @param {function} editor.close Closes the editor. + * @returns {object} editor object + */ + function mediaCropDetails(editor) { + editor.view = "views/common/infiniteeditors/mediapicker/overlays/mediacropdetails.html"; + open(editor); + } + /** * @ngdoc method * @name umbraco.services.editorService#iconPicker * @methodOf umbraco.services.editorService * * @description - * Opens an icon picker in infinite editing, the submit callback returns the selected icon - * @param {Object} editor rendering options - * @param {String} editor.icon The CSS class representing the icon - eg. "icon-autofill". - * @param {String} editor.color The CSS class representing the color - eg. "color-red". - * @param {Callback} editor.submit Submits the editor - * @param {Callback} editor.close Closes the editor - * @returns {Object} editor object + * Opens an icon picker in infinite editing, the submit callback returns the selected icon. + * @param {object} editor rendering options. + * @param {string} editor.icon The CSS class representing the icon - eg. "icon-autofill". + * @param {string} editor.color The CSS class representing the color - eg. "color-red". + * @param {callback} editor.submit Submits the editor. + * @param {callback} editor.close Closes the editor. + * @returns {object} editor object. */ function iconPicker(editor) { editor.view = "views/common/infiniteeditors/iconpicker/iconpicker.html"; @@ -620,15 +636,15 @@ When building a custom infinite editor view you can use the same components as a * * @description * Opens the document type editor in infinite editing, the submit callback returns the alias of the saved document type. - * @param {Object} editor rendering options - * @param {Number} editor.id Indicates the ID of the document type to be edited. Alternatively the ID may be set to `-1` in combination with `create` being set to `true` to open the document type editor for creating a new document type. - * @param {Boolean} editor.create Set to `true` to open the document type editor for creating a new document type. - * @param {Boolean} editor.noTemplate If `true` and in combination with `create` being set to `true`, the document type editor will not create a corresponding template by default. This is similar to selecting the "Document Type without a template" in the Create dialog. - * @param {Boolean} editor.isElement If `true` and in combination with `create` being set to `true`, the "Is an Element type" option will be selected by default in the document type editor. - * @param {Boolean} editor.allowVaryByCulture If `true` and in combination with `create`, the "Allow varying by culture" option will be selected by default in the document type editor. - * @param {Callback} editor.submit Submits the editor. - * @param {Callback} editor.close Closes the editor. - * @returns {Object} editor object + * @param {object} editor rendering options. + * @param {number} editor.id Indicates the ID of the document type to be edited. Alternatively the ID may be set to `-1` in combination with `create` being set to `true` to open the document type editor for creating a new document type. + * @param {boolean} editor.create Set to `true` to open the document type editor for creating a new document type. + * @param {boolean} editor.noTemplate If `true` and in combination with `create` being set to `true`, the document type editor will not create a corresponding template by default. This is similar to selecting the "Document Type without a template" in the Create dialog. + * @param {boolean} editor.isElement If `true` and in combination with `create` being set to `true`, the "Is an Element type" option will be selected by default in the document type editor. + * @param {boolean} editor.allowVaryByCulture If `true` and in combination with `create`, the "Allow varying by culture" option will be selected by default in the document type editor. + * @param {function} editor.submit Submits the editor. + * @param {function} editor.close Closes the editor. + * @returns {object} editor object. */ function documentTypeEditor(editor) { editor.view = "views/documenttypes/edit.html"; @@ -641,11 +657,11 @@ When building a custom infinite editor view you can use the same components as a * @methodOf umbraco.services.editorService * * @description - * Opens the media type editor in infinite editing, the submit callback returns the saved media type - * @param {Object} editor rendering options - * @param {Callback} editor.submit Submits the editor - * @param {Callback} editor.close Closes the editor - * @returns {Object} editor object + * Opens the media type editor in infinite editing, the submit callback returns the saved media type. + * @param {object} editor rendering options. + * @param {function} editor.submit Submits the editor. + * @param {function} editor.close Closes the editor. + * @returns {object} editor object */ function mediaTypeEditor(editor) { editor.view = "views/mediatypes/edit.html"; @@ -658,28 +674,11 @@ When building a custom infinite editor view you can use the same components as a * @methodOf umbraco.services.editorService * * @description - * Opens the member type editor in infinite editing, the submit callback returns the saved member type - * @param {Object} editor rendering options - * @param {Callback} editor.submit Submits the editor - * @param {Callback} editor.close Closes the editor - * @returns {Object} editor object - */ - function memberTypeEditor(editor) { - editor.view = "views/membertypes/edit.html"; - open(editor); - } - - /** - * @ngdoc method - * @name umbraco.services.editorService#memberTypeEditor - * @methodOf umbraco.services.editorService - * - * @description - * Opens the member type editor in infinite editing, the submit callback returns the saved member type - * @param {Object} editor rendering options - * @param {Callback} editor.submit Submits the editor - * @param {Callback} editor.close Closes the editor - * @returns {Object} editor object + * Opens the member type editor in infinite editing, the submit callback returns the saved member type. + * @param {object} editor rendering options. + * @param {function} editor.submit Submits the editor. + * @param {function} editor.close Closes the editor. + * @returns {object} editor object. */ function memberTypeEditor(editor) { editor.view = "views/membertypes/edit.html"; @@ -692,11 +691,11 @@ When building a custom infinite editor view you can use the same components as a * @methodOf umbraco.services.editorService * * @description - * Opens the query builder in infinite editing, the submit callback returns the generted query - * @param {Object} editor rendering options - * @param {Callback} editor.submit Submits the editor - * @param {Callback} editor.close Closes the editor - * @returns {Object} editor object + * Opens the query builder in infinite editing, the submit callback returns the generated query. + * @param {object} editor rendering options. + * @param {function} editor.submit Submits the editor. + * @param {function} editor.close Closes the editor. + * @returns {object} editor object. */ function queryBuilder(editor) { editor.view = "views/common/infiniteeditors/querybuilder/querybuilder.html"; @@ -709,14 +708,14 @@ When building a custom infinite editor view you can use the same components as a * @methodOf umbraco.services.editorService * * @description - * Opens the query builder in infinite editing, the submit callback returns the generted query - * @param {Object} editor rendering options - * @param {String} options.section tree section to display - * @param {String} options.treeAlias specific tree to display - * @param {Boolean} options.multiPicker should the tree pick one or multiple items before returning - * @param {Callback} editor.submit Submits the editor - * @param {Callback} editor.close Closes the editor - * @returns {Object} editor object + * Opens the query builder in infinite editing, the submit callback returns the generted query. + * @param {object} editor rendering options. + * @param {string} options.section tree section to display. + * @param {string} options.treeAlias specific tree to display. + * @param {boolean} options.multiPicker should the tree pick one or multiple items before returning. + * @param {function} editor.submit Submits the editor. + * @param {function} editor.close Closes the editor. + * @returns {object} editor object. */ function treePicker(editor) { editor.view = "views/common/infiniteeditors/treepicker/treepicker.html"; @@ -731,10 +730,10 @@ When building a custom infinite editor view you can use the same components as a * * @description * Opens the an editor to set node permissions. - * @param {Object} editor rendering options - * @param {Callback} editor.submit Submits the editor - * @param {Callback} editor.close Closes the editor - * @returns {Object} editor object + * @param {object} editor rendering options. + * @param {function} editor.submit Submits the editor. + * @param {function} editor.close Closes the editor. + * @returns {object} editor object. */ function nodePermissions(editor) { editor.view = "views/common/infiniteeditors/nodepermissions/nodepermissions.html"; @@ -748,11 +747,11 @@ When building a custom infinite editor view you can use the same components as a * @methodOf umbraco.services.editorService * * @description - * Open an editor to insert code snippets into the code editor - * @param {Object} editor rendering options - * @param {Callback} editor.submit Submits the editor - * @param {Callback} editor.close Closes the editor - * @returns {Object} editor object + * Open an editor to insert code snippets into the code editor. + * @param {object} editor rendering options. + * @param {function} editor.submit Submits the editor. + * @param {function} editor.close Closes the editor. + * @returns {object} editor object. */ function insertCodeSnippet(editor) { editor.view = "views/common/infiniteeditors/insertcodesnippet/insertcodesnippet.html"; @@ -766,11 +765,11 @@ When building a custom infinite editor view you can use the same components as a * @methodOf umbraco.services.editorService * * @description - * Opens the user group picker in infinite editing, the submit callback returns an array of the selected user groups - * @param {Object} editor rendering options - * @param {Callback} editor.submit Submits the editor - * @param {Callback} editor.close Closes the editor - * @returns {Object} editor object + * Opens the user group picker in infinite editing, the submit callback returns an array of the selected user groups. + * @param {object} editor rendering options. + * @param {function} editor.submit Submits the editor. + * @param {function} editor.close Closes the editor. + * @returns {object} editor object. */ function userGroupPicker(editor) { editor.view = "views/common/infiniteeditors/usergrouppicker/usergrouppicker.html"; @@ -778,18 +777,35 @@ When building a custom infinite editor view you can use the same components as a open(editor); } + /** + * @ngdoc method + * @name umbraco.services.editorService#userGroupEditor + * @methodOf umbraco.services.editorService + * + * @description + * Opens the user group picker in infinite editing, the submit callback returns the saved user group + * @param {object} editor rendering options. + * @param {function} editor.submit Submits the editor. + * @param {function} editor.close Closes the editor. + * @returns {object} editor object. + */ + function userGroupEditor(editor) { + editor.view = "views/users/group.html"; + open(editor); + } + /** * @ngdoc method * @name umbraco.services.editorService#templateEditor * @methodOf umbraco.services.editorService * * @description - * Opens the template editor in infinite editing, the submit callback returns the saved template - * @param {Object} editor rendering options - * @param {String} editor.id The template id - * @param {Callback} editor.submit Submits the editor - * @param {Callback} editor.close Closes the editor - * @returns {Object} editor object + * Opens the template editor in infinite editing, the submit callback returns the saved template. + * @param {object} editor rendering options. + * @param {string} editor.id The template id. + * @param {function} editor.submit Submits the editor. + * @param {function} editor.close Closes the editor. + * @returns {object} editor object. */ function templateEditor(editor) { editor.view = "views/templates/edit.html"; @@ -802,11 +818,11 @@ When building a custom infinite editor view you can use the same components as a * @methodOf umbraco.services.editorService * * @description - * Opens the section picker in infinite editing, the submit callback returns an array of the selected sections¨ - * @param {Object} editor rendering options - * @param {Callback} editor.submit Submits the editor - * @param {Callback} editor.close Closes the editor - * @returns {Object} editor object + * Opens the section picker in infinite editing, the submit callback returns an array of the selected sections. + * @param {object} editor rendering options. + * @param {function} editor.submit Submits the editor. + * @param {function} editor.close Closes the editor. + * @returns {object} editor object. */ function sectionPicker(editor) { editor.view = "views/common/infiniteeditors/sectionpicker/sectionpicker.html"; @@ -820,11 +836,11 @@ When building a custom infinite editor view you can use the same components as a * @methodOf umbraco.services.editorService * * @description - * Opens the insert field editor in infinite editing, the submit callback returns the code snippet - * @param {Object} editor rendering options - * @param {Callback} editor.submit Submits the editor - * @param {Callback} editor.close Closes the editor - * @returns {Object} editor object + * Opens the insert field editor in infinite editing, the submit callback returns the code snippet. + * @param {object} editor rendering options. + * @param {function} editor.submit Submits the editor. + * @param {function} editor.close Closes the editor. + * @returns {object} editor object. */ function insertField(editor) { editor.view = "views/common/infiniteeditors/insertfield/insertfield.html"; @@ -838,11 +854,11 @@ When building a custom infinite editor view you can use the same components as a * @methodOf umbraco.services.editorService * * @description - * Opens the template sections editor in infinite editing, the submit callback returns the type to insert - * @param {Object} editor rendering options - * @param {Callback} editor.submit Submits the editor - * @param {Callback} editor.close Closes the editor - * @returns {Object} editor object + * Opens the template sections editor in infinite editing, the submit callback returns the type to insert. + * @param {object} editor rendering options. + * @param {function} editor.submit Submits the editor. + * @param {function} editor.close Closes the editor. + * @returns {object} editor object. */ function templateSections(editor) { editor.view = "views/common/infiniteeditors/templatesections/templatesections.html"; @@ -856,11 +872,11 @@ When building a custom infinite editor view you can use the same components as a * @methodOf umbraco.services.editorService * * @description - * Opens the section picker in infinite editing, the submit callback returns an array of the selected users - * @param {Object} editor rendering options - * @param {Callback} editor.submit Submits the editor - * @param {Callback} editor.close Closes the editor - * @returns {Object} editor object + * Opens the section picker in infinite editing, the submit callback returns an array of the selected users. + * @param {object} editor rendering options. + * @param {function} editor.submit Submits the editor. + * @param {function} editor.close Closes the editor. + * @returns {object} editor object. */ function userPicker(editor) { editor.view = "views/common/infiniteeditors/userpicker/userpicker.html"; @@ -874,15 +890,15 @@ When building a custom infinite editor view you can use the same components as a * @methodOf umbraco.services.editorService * * @description - * Opens the section picker in infinite editing, the submit callback returns an array of the selected items + * Opens the section picker in infinite editing, the submit callback returns an array of the selected items. * - * @param {Object} editor rendering options - * @param {Array} editor.availableItems Array of available items. - * @param {Array} editor.selectedItems Array of selected items. When passed in the selected items will be filtered from the available items. - * @param {Boolean} editor.filter Set to false to hide the filter. - * @param {Callback} editor.submit Submits the editor. - * @param {Callback} editor.close Closes the editor. - * @returns {Object} editor object + * @param {object} editor rendering options. + * @param {array} editor.availableItems Array of available items. + * @param {array} editor.selectedItems Array of selected items. When passed in the selected items will be filtered from the available items. + * @param {boolean} editor.filter Set to false to hide the filter. + * @param {function} editor.submit Submits the editor. + * @param {function} editor.close Closes the editor. + * @returns {object} editor object. */ function itemPicker(editor) { editor.view = "views/common/infiniteeditors/itempicker/itempicker.html"; @@ -896,11 +912,11 @@ When building a custom infinite editor view you can use the same components as a * @methodOf umbraco.services.editorService * * @description - * Opens a macro picker in infinite editing, the submit callback returns an array of the selected items + * Opens a macro picker in infinite editing, the submit callback returns an array of the selected items. * - * @param {Callback} editor.submit Submits the editor. - * @param {Callback} editor.close Closes the editor. - * @returns {Object} editor object + * @param {function} editor.submit Submits the editor. + * @param {function} editor.close Closes the editor. + * @returns {object} editor object. */ function macroPicker(editor) { editor.view = "views/common/infiniteeditors/macropicker/macropicker.html"; @@ -916,11 +932,11 @@ When building a custom infinite editor view you can use the same components as a * @description * Opens a member group picker in infinite editing. * - * @param {Object} editor rendering options - * @param {Object} editor.multiPicker Pick one or multiple items. - * @param {Callback} editor.submit Submits the editor. - * @param {Callback} editor.close Closes the editor. - * @returns {Object} editor object + * @param {object} editor rendering options. + * @param {object} editor.multiPicker Pick one or multiple items. + * @param {function} editor.submit Submits the editor. + * @param {function} editor.close Closes the editor. + * @returns {object} editor object. */ function memberGroupPicker(editor) { editor.view = "views/common/infiniteeditors/membergrouppicker/membergrouppicker.html"; @@ -934,14 +950,14 @@ When building a custom infinite editor view you can use the same components as a * @methodOf umbraco.services.editorService * * @description - * Opens a member picker in infinite editing, the submit callback returns an array of selected items + * Opens a member picker in infinite editing, the submit callback returns an array of selected items. * - * @param {Object} editor rendering options - * @param {Boolean} editor.multiPicker Pick one or multiple items - * @param {Function} editor.submit Callback function when the submit button is clicked. Returns the editor model object - * @param {Function} editor.close Callback function when the close button is clicked. + * @param {object} editor rendering options. + * @param {boolean} editor.multiPicker Pick one or multiple items. + * @param {function} editor.submit Callback function when the submit button is clicked. Returns the editor model object. + * @param {function} editor.close Callback function when the close button is clicked. * - * @returns {Object} editor object + * @returns {object} editor object. */ function memberPicker(editor) { editor.view = "views/common/infiniteeditors/treepicker/treepicker.html"; @@ -957,15 +973,15 @@ When building a custom infinite editor view you can use the same components as a * @methodOf umbraco.services.editorService * * @description - * Opens a member editor in infinite editing, the submit callback returns the updated member - * @param {Object} editor rendering options - * @param {String} editor.id The id (GUID) of the member - * @param {Boolean} editor.create Create new member - * @param {Function} editor.submit Callback function when the submit button is clicked. Returns the editor model object - * @param {Function} editor.close Callback function when the close button is clicked. - * @param {String} editor.doctype If editor.create is true, provide member type for the creation of the member + * Opens a member editor in infinite editing, the submit callback returns the updated member. + * @param {object} editor rendering options. + * @param {string} editor.id The id (GUID) of the member. + * @param {boolean} editor.create Create new member. + * @param {function} editor.submit Callback function when the submit button is clicked. Returns the editor model object. + * @param {function} editor.close Callback function when the close button is clicked. + * @param {string} editor.doctype If editor.create is true, provide member type for the creation of the member. * - * @returns {Object} editor object + * @returns {object} editor object. */ function memberEditor(editor) { editor.view = "views/member/edit.html"; @@ -981,7 +997,7 @@ When building a custom infinite editor view you can use the same components as a * * @description * Internal method to keep track of keyboard shortcuts registered - * to each editor so they can be rebound when an editor closes + * to each editor so they can be rebound when an editor closes. * */ function unbindKeyboardShortcuts() { @@ -1001,7 +1017,7 @@ When building a custom infinite editor view you can use the same components as a * @methodOf umbraco.services.editorService * * @description - * Internal method to rebind keyboard shortcuts for the editor in focus + * Internal method to rebind keyboard shortcuts for the editor in focus. * */ function rebindKeyboardShortcuts() { @@ -1045,6 +1061,7 @@ When building a custom infinite editor view you can use the same components as a nodePermissions: nodePermissions, insertCodeSnippet: insertCodeSnippet, userGroupPicker: userGroupPicker, + userGroupEditor: userGroupEditor, templateEditor: templateEditor, sectionPicker: sectionPicker, insertField: insertField, @@ -1054,7 +1071,8 @@ When building a custom infinite editor view you can use the same components as a macroPicker: macroPicker, memberGroupPicker: memberGroupPicker, memberPicker: memberPicker, - memberEditor: memberEditor + memberEditor: memberEditor, + mediaCropDetails }; return service; diff --git a/src/Umbraco.Web.UI.Client/src/common/services/events.service.js b/src/Umbraco.Web.UI.Client/src/common/services/events.service.js index 965ac3d635..c7ef5bd28f 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/events.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/events.service.js @@ -34,7 +34,7 @@ function eventsService($q, $rootScope) { /** pass in the result of 'on' to this method, or just call the method returned from 'on' to unsubscribe */ unsubscribe: function(handle) { - if (angular.isFunction(handle)) { + if (Utilities.isFunction(handle)) { handle(); } } diff --git a/src/Umbraco.Web.UI.Client/src/common/services/externallogininfo.service.js b/src/Umbraco.Web.UI.Client/src/common/services/externallogininfo.service.js new file mode 100644 index 0000000000..31914f4e58 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/services/externallogininfo.service.js @@ -0,0 +1,67 @@ +/** + * @ngdoc service + * @name umbraco.services.externalLoginInfoService + * @description A service for working with external login providers + **/ +function externalLoginInfoService(externalLoginInfo, umbRequestHelper) { + + function getLoginProvider(provider) { + if (provider) { + var found = _.find(externalLoginInfo.providers, x => x.authType == provider); + return found; + } + return null; + } + + function getLoginProviderView(provider) { + if (provider && provider.properties && provider.properties.UmbracoBackOfficeExternalLoginOptions && provider.properties.UmbracoBackOfficeExternalLoginOptions.CustomBackOfficeView) { + return umbRequestHelper.convertVirtualToAbsolutePath(provider.properties.UmbracoBackOfficeExternalLoginOptions.CustomBackOfficeView); + } + return null; + } + + /** + * Returns true if any provider denies local login if `provider` is null, else whether the passed + * @param {any} provider + */ + function hasDenyLocalLogin(provider) { + if (!provider) { + return _.some(externalLoginInfo.providers, x => x.properties && x.properties.UmbracoBackOfficeExternalLoginOptions && (x.properties.UmbracoBackOfficeExternalLoginOptions.DenyLocalLogin === true)); + } + else { + return provider && provider.properties && provider.properties.UmbracoBackOfficeExternalLoginOptions && (provider.properties.UmbracoBackOfficeExternalLoginOptions.DenyLocalLogin === true); + } + } + + /** + * Returns all login providers + */ + function getLoginProviders() { + return externalLoginInfo.providers; + } + + /** Returns all logins providers that have options that the user can interact with */ + function getLoginProvidersWithOptions() { + // only include providers that allow manual linking or ones that provide a custom view + var providers = _.filter(externalLoginInfo.providers, x => { + // transform the data and also include the custom view as a nicer property + x.customView = getLoginProviderView(x); + if (x.customView) { + return true; + } + else { + return x.properties.ExternalSignInAutoLinkOptions.AllowManualLinking; + } + }); + return providers; + } + + return { + hasDenyLocalLogin: hasDenyLocalLogin, + getLoginProvider: getLoginProvider, + getLoginProviders: getLoginProviders, + getLoginProvidersWithOptions: getLoginProvidersWithOptions, + getLoginProviderView: getLoginProviderView + }; +} +angular.module('umbraco.services').factory('externalLoginInfoService', externalLoginInfoService); diff --git a/src/Umbraco.Web.UI.Client/src/common/services/focuslock.service.js b/src/Umbraco.Web.UI.Client/src/common/services/focuslock.service.js new file mode 100644 index 0000000000..54acaf6426 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/services/focuslock.service.js @@ -0,0 +1,30 @@ +(function () { + "use strict"; + + function focusLockService($document) { + var elementToInert = $document[0].querySelector('#mainwrapper'); + + function addInertAttribute() { + if (elementToInert) { + elementToInert.setAttribute('inert', true); + } + } + + function removeInertAttribute() { + if (elementToInert) { + elementToInert.removeAttribute('inert'); + } + } + + var service = { + addInertAttribute: addInertAttribute, + removeInertAttribute: removeInertAttribute + } + + return service; + + } + + angular.module("umbraco.services").factory("focusLockService", focusLockService); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/common/services/formhelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/formhelper.service.js index 90fbd76ec9..773aa85f6f 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/formhelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/formhelper.service.js @@ -17,16 +17,102 @@ function formHelper(angularHelper, serverValidationManager, notificationsService * @function * * @description - * Called by controllers when submitting a form - this ensures that all client validation is checked, + * Called by controllers when submitting a form - this ensures that all client validation is checked, * server validation is cleared, that the correct events execute and status messages are displayed. * This returns true if the form is valid, otherwise false if form submission cannot continue. - * + * * @param {object} args An object containing arguments for form submission */ submitForm: function (args) { var currentForm; + if (!args) { + throw "args cannot be null"; + } + if (!args.scope) { + throw "args.scope cannot be null"; + } + + if (!args.formCtrl) { + //try to get the closest form controller + currentForm = angularHelper.getRequiredCurrentForm(args.scope); + } + else { + currentForm = args.formCtrl; + } + + //the first thing any form must do is broadcast the formSubmitting event + args.scope.$broadcast("formSubmitting", { scope: args.scope, action: args.action }); + + this.focusOnFirstError(currentForm); + + // Some property editors need to perform an action after all property editors have reacted to the formSubmitting. + args.scope.$broadcast("formSubmittingFinalPhase", { scope: args.scope, action: args.action }); + + // Set the form state to submitted + currentForm.$setSubmitted(); + + //then check if the form is valid + if (!args.skipValidation) { + if (currentForm.$invalid) { + + return false; + } + } + + //reset the server validations if required (default is true), otherwise notify existing ones of changes + if (!args.keepServerValidation) { + serverValidationManager.reset(); + } + else { + serverValidationManager.notify(); + } + + return true; + }, + + /** + * @ngdoc function + * @name umbraco.services.formHelper#focusOnFirstError + * @methodOf umbraco.services.formHelper + * @function + * + * @description + * Called by submitForm when a form has been submitted, it will fire a focus on the first found invalid umb-property it finds in the form.. + * + * @param {object} form Pass in a form object. + */ + focusOnFirstError: function(form) { + var invalidNgForms = form.$$element.find(`.umb-property ng-form.ng-invalid, .umb-property-editor ng-form.ng-invalid-required`); + var firstInvalidNgForm = invalidNgForms.first(); + + if(firstInvalidNgForm.length !== 0) { + var focusableFields = [...firstInvalidNgForm.find("umb-range-slider .noUi-handle,input,textarea,select,button")]; + if(focusableFields.length !== 0) { + var firstErrorEl = focusableFields.find(el => el.type !== "hidden" && el.hasAttribute("readonly") === false); + if(firstErrorEl !== undefined) { + firstErrorEl.focus(); + } + } + } + }, + + /** + * @ngdoc function + * @name umbraco.services.formHelper#submitForm + * @methodOf umbraco.services.formHelper + * @function + * + * @description + * Called by controllers when a form has been successfully submitted, this ensures the correct events are raised. + * + * @param {object} args An object containing arguments for form submission + */ + resetForm: function (args) { + + var currentForm; + if (!args) { throw "args cannot be null"; } @@ -41,42 +127,11 @@ function formHelper(angularHelper, serverValidationManager, notificationsService currentForm = args.formCtrl; } - //the first thing any form must do is broadcast the formSubmitting event - args.scope.$broadcast("formSubmitting", { scope: args.scope, action: args.action }); + // Set the form state to pristine + currentForm.$setPristine(); + currentForm.$setUntouched(); - //then check if the form is valid - if (!args.skipValidation) { - if (currentForm.$invalid) { - return false; - } - } - - //reset the server validations - serverValidationManager.reset(); - - return true; - }, - - /** - * @ngdoc function - * @name umbraco.services.formHelper#submitForm - * @methodOf umbraco.services.formHelper - * @function - * - * @description - * Called by controllers when a form has been successfully submitted, this ensures the correct events are raised. - * - * @param {object} args An object containing arguments for form submission - */ - resetForm: function (args) { - if (!args) { - throw "args cannot be null"; - } - if (!args.scope) { - throw "args.scope cannot be null"; - } - - args.scope.$broadcast("formSubmitted", { scope: args.scope }); + args.scope.$broadcast(args.hasErrors ? "formSubmittedValidationFailed" : "formSubmitted", { scope: args.scope }); }, showNotifications: function (args) { @@ -101,7 +156,7 @@ function formHelper(angularHelper, serverValidationManager, notificationsService * @description * Needs to be called when a form submission fails, this will wire up all server validation errors in ModelState and * add the correct messages to the notifications. If a server error has occurred this will show a ysod. - * + * * @param {object} err The error object returned from the http promise */ handleError: function (err) { @@ -140,77 +195,11 @@ function formHelper(angularHelper, serverValidationManager, notificationsService * * @description * This wires up all of the server validation model state so that valServer and valServerField directives work - * + * * @param {object} err The error object returned from the http promise */ handleServerValidation: function (modelState) { - for (var e in modelState) { - - //This is where things get interesting.... - // We need to support validation for all editor types such as both the content and content type editors. - // The Content editor ModelState is quite specific with the way that Properties are validated especially considering - // that each property is a User Developer property editor. - // The way that Content Type Editor ModelState is created is simply based on the ASP.Net validation data-annotations - // system. - // So, to do this there's some special ModelState syntax we need to know about. - // For Content Properties, which are user defined, we know that they will exist with a prefixed - // ModelState of "_Properties.", so if we detect this, then we know it's for a content Property. - - //the alias in model state can be in dot notation which indicates - // * the first part is the content property alias - // * the second part is the field to which the valiation msg is associated with - //There will always be at least 4 parts for content properties since all model errors for properties are prefixed with "_Properties" - //If it is not prefixed with "_Properties" that means the error is for a field of the object directly. - - // Example: "_Properties.headerImage.en-US.mySegment.myField" - // * it's for a property since it has a _Properties prefix - // * it's for the headerImage property type - // * it's for the en-US culture - // * it's for the mySegment segment - // * it's for the myField html field (optional) - - var parts = e.split("."); - - //Check if this is for content properties - specific to content/media/member editors because those are special - // user defined properties with custom controls. - if (parts.length > 1 && parts[0] === "_Properties") { - - var propertyAlias = parts[1]; - - var culture = null; - if (parts.length > 2) { - culture = parts[2]; - //special check in case the string is formatted this way - if (culture === "null") { - culture = null; - } - } - - var segment = null; - if (parts.length > 3) { - segment = parts[3]; - //special check in case the string is formatted this way - if (segment === "null") { - segment = null; - } - } - - var htmlFieldReference = ""; - if (parts.length > 4) { - htmlFieldReference = parts[4] || ""; - } - - // add a generic error for the property - serverValidationManager.addPropertyError(propertyAlias, culture, htmlFieldReference, modelState[e][0], segment); - - } else { - - //Everthing else is just a 'Field'... the field name could contain any level of 'parts' though, for example: - // Groups[0].Properties[2].Alias - serverValidationManager.addFieldError(e, modelState[e][0]); - } - - } + serverValidationManager.addErrorsForModelState(modelState); } }; } diff --git a/src/Umbraco.Web.UI.Client/src/common/services/iconhelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/iconhelper.service.js index 0fa2d0df1a..f26763bd14 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/iconhelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/iconhelper.service.js @@ -3,7 +3,7 @@ * @name umbraco.services.iconHelper * @description A helper service for dealing with icons, mostly dealing with legacy tree icons **/ -function iconHelper($q, $timeout) { +function iconHelper($http, $q, $sce, $timeout, umbRequestHelper) { var converter = [ { oldIcon: ".sprNew", newIcon: "add" }, @@ -31,7 +31,7 @@ function iconHelper($q, $timeout) { { oldIcon: ".sprToPublish", newIcon: "mail-forward" }, { oldIcon: ".sprTranslate", newIcon: "comments" }, { oldIcon: ".sprUpdate", newIcon: "save" }, - + { oldIcon: ".sprTreeSettingDomain", newIcon: "icon-home" }, { oldIcon: ".sprTreeDoc", newIcon: "icon-document" }, { oldIcon: ".sprTreeDoc2", newIcon: "icon-diploma-alt" }, @@ -39,21 +39,21 @@ function iconHelper($q, $timeout) { { oldIcon: ".sprTreeDoc4", newIcon: "icon-newspaper-alt" }, { oldIcon: ".sprTreeDoc5", newIcon: "icon-notepad-alt" }, - { oldIcon: ".sprTreeDocPic", newIcon: "icon-picture" }, + { oldIcon: ".sprTreeDocPic", newIcon: "icon-picture" }, { oldIcon: ".sprTreeFolder", newIcon: "icon-folder" }, { oldIcon: ".sprTreeFolder_o", newIcon: "icon-folder" }, { oldIcon: ".sprTreeMediaFile", newIcon: "icon-music" }, { oldIcon: ".sprTreeMediaMovie", newIcon: "icon-movie" }, { oldIcon: ".sprTreeMediaPhoto", newIcon: "icon-picture" }, - + { oldIcon: ".sprTreeMember", newIcon: "icon-user" }, { oldIcon: ".sprTreeMemberGroup", newIcon: "icon-users" }, { oldIcon: ".sprTreeMemberType", newIcon: "icon-users" }, - + { oldIcon: ".sprTreeNewsletter", newIcon: "icon-file-text-alt" }, { oldIcon: ".sprTreePackage", newIcon: "icon-box" }, { oldIcon: ".sprTreeRepository", newIcon: "icon-server-alt" }, - + { oldIcon: ".sprTreeSettingDataType", newIcon: "icon-autofill" }, // TODO: Something needs to be done with the old tree icons that are commented out. @@ -61,7 +61,7 @@ function iconHelper($q, $timeout) { { oldIcon: ".sprTreeSettingAgent", newIcon: "" }, { oldIcon: ".sprTreeSettingCss", newIcon: "" }, { oldIcon: ".sprTreeSettingCssItem", newIcon: "" }, - + { oldIcon: ".sprTreeSettingDataTypeChild", newIcon: "" }, { oldIcon: ".sprTreeSettingDomain", newIcon: "" }, { oldIcon: ".sprTreeSettingLanguage", newIcon: "" }, @@ -85,14 +85,18 @@ function iconHelper($q, $timeout) { { oldIcon: ".sprTreeDeveloperPython", newIcon: "icon-linux" } ]; + var collectedIcons; + var imageConverter = [ {oldImage: "contour.png", newIcon: "icon-umb-contour"} ]; - var collectedIcons; - + var iconCache = []; + var liveRequests = []; + var allIconsRequested = false; + return { - + /** Used by the create dialogs for content/media types to format the data so that the thumbnails are styled properly */ formatContentTypeThumbnails: function (contentTypes) { for (var i = 0; i < contentTypes.length; i++) { @@ -154,54 +158,6 @@ function iconHelper($q, $timeout) { return false; }, - /** Return a list of icons, optionally filter them */ - /** It fetches them directly from the active stylesheets in the browser */ - getIcons: function(){ - var deferred = $q.defer(); - $timeout(function(){ - if(collectedIcons){ - deferred.resolve(collectedIcons); - }else{ - collectedIcons = []; - var c = ".icon-"; - - for (var i = document.styleSheets.length - 1; i >= 0; i--) { - var classes = null; - try { - classes = document.styleSheets[i].rules || document.styleSheets[i].cssRules; - } catch (e) { - console.warn("Can't read the css rules of: " + document.styleSheets[i].href, e); - continue; - } - - if (classes !== null) { - for(var x=0;x0){ - s = s.substring(0, hasSpace); - } - var hasPseudo = s.indexOf(":"); - if(hasPseudo>0){ - s = s.substring(0, hasPseudo); - } - - if(collectedIcons.indexOf(s) < 0){ - collectedIcons.push(s); - } - } - } - } - } - deferred.resolve(collectedIcons); - } - }, 100); - - return deferred.promise; - }, - /** Converts the icon from legacy to a new one if an old one is detected */ convertFromLegacyIcon: function (icon) { if (this.isLegacyIcon(icon)) { @@ -227,6 +183,137 @@ function iconHelper($q, $timeout) { return this.convertFromLegacyIcon(treeNode.icon); } return treeNode.icon; + }, + + /** Gets a single IconModel */ + getIcon: function(iconName) { + return $q((resolve, reject) => { + var icon = this._getIconFromCache(iconName); + + if(icon !== undefined) { + resolve(icon); + } else { + var iconRequestPath = Umbraco.Sys.ServerVariables.umbracoUrls.iconApiBaseUrl + 'GetIcon?iconName=' + iconName; + + // If the current icon is being requested, wait a bit so that we don't have to make another http request and can instead get the icon from the cache. + // This is a bit rough and ready and could probably be improved used an event based system + if(liveRequests.indexOf(iconRequestPath) >= 0) { + setTimeout(() => { + resolve(this.getIcon(iconName)); + }, 10); + } else { + liveRequests.push(iconRequestPath); + // TODO - fix bug where Umbraco.Sys.ServerVariables.umbracoUrls.iconApiBaseUrl is undefinied when help icon + umbRequestHelper.resourcePromise( + $http.get(iconRequestPath) + ,'Failed to retrieve icon: ' + iconName) + .then(icon => { + if(icon) { + var trustedIcon = this.defineIcon(icon.Name, icon.SvgString); + + liveRequests = _.filter(liveRequests, iconRequestPath); + + resolve(trustedIcon); + } + }) + .catch(err => { + console.warn(err); + }); + }; + + } + }); + }, + + /** Gets all the available icons in the backoffice icon folder and returns them as an array of IconModels */ + getAllIcons: function() { + return $q((resolve, reject) => { + if(allIconsRequested === false) { + allIconsRequested = true; + + umbRequestHelper.resourcePromise( + $http.get(Umbraco.Sys.ServerVariables.umbracoUrls.iconApiBaseUrl + 'GetAllIcons') + ,'Failed to retrieve icons') + .then(icons => { + icons.forEach(icon => { + this.defineIcon(icon.Name, icon.SvgString); + }); + + resolve(iconCache); + }) + .catch(err => { + console.warn(err); + });; + } else { + resolve(iconCache); + } + }); + }, + + /** LEGACY - Return a list of icons from icon fonts, optionally filter them */ + /** It fetches them directly from the active stylesheets in the browser */ + getIcons: function(){ + var deferred = $q.defer(); + $timeout(function(){ + if(collectedIcons){ + deferred.resolve(collectedIcons); + }else{ + collectedIcons = []; + var c = ".icon-"; + + for (var i = document.styleSheets.length - 1; i >= 0; i--) { + var classes = null; + try { + classes = document.styleSheets[i].rules || document.styleSheets[i].cssRules; + } catch (e) { + console.warn("Can't read the css rules of: " + document.styleSheets[i].href, e); + continue; + } + + if (classes !== null) { + for(var x=0;x0){ + s = s.substring(0, hasSpace); + } + var hasPseudo = s.indexOf(":"); + if(hasPseudo>0){ + s = s.substring(0, hasPseudo); + } + + if(collectedIcons.indexOf(s) < 0){ + collectedIcons.push(s); + } + } + } + } + } + deferred.resolve(collectedIcons); + } + }, 100); + + return deferred.promise; + }, + + /** Creates a icon object, and caches it in a runtime cache */ + defineIcon: function(name, svg) { + var icon = iconCache.find(x => x.name === name); + if(icon === undefined) { + icon = { + name: name, + svgString: $sce.trustAsHtml(svg) + }; + iconCache.push(icon); + } + return icon; + }, + + /** Returns the cached icon or undefined */ + _getIconFromCache: function(iconName) { + return _.find(iconCache, {name: iconName}); } }; } diff --git a/src/Umbraco.Web.UI.Client/src/common/services/listviewhelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/listviewhelper.service.js index 28156e70c3..14643dc9cd 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/listviewhelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/listviewhelper.service.js @@ -45,8 +45,7 @@ (function () { 'use strict'; - function listViewHelper($location, localStorageService, urlHelper) { - + function listViewHelper($location, $rootScope, localStorageService, urlHelper, editorService) { var firstSelectedIndex = 0; var localStorageKey = "umblistViewLayout"; @@ -286,6 +285,7 @@ selection.push(obj); item.selected = true; + $rootScope.$broadcast("listView.itemsChanged", { items: selection }); } } @@ -308,6 +308,7 @@ if ((item.id !== 2147483647 && item.id === selectedItem.id) || (item.key && item.key === selectedItem.key)) { selection.splice(i, 1); item.selected = false; + $rootScope.$broadcast("listView.itemsChanged", { items: selection }); } } } @@ -339,12 +340,13 @@ } } - if(Utilities.isArray(folders)) { + if (Utilities.isArray(folders)) { for (i = 0; folders.length > i; i++) { var folder = folders[i]; folder.selected = false; } } + $rootScope.$broadcast("listView.itemsChanged", { items: selection }); } /** @@ -395,10 +397,11 @@ if (clearSelection) { selection.length = 0; } + $rootScope.$broadcast("listView.itemsChanged", { items: selection }); } - - + + /** * @ngdoc method * @name umbraco.services.listViewHelper#selectAllItemsToggle @@ -410,29 +413,29 @@ * @param {Array} items Items to toggle selection on, should be $scope.items * @param {Array} selection Listview selection, available as $scope.selection */ - + function selectAllItemsToggle(items, selection) { - + if (!Utilities.isArray(items)) { return; } - + if (isSelectedAll(items, selection)) { // unselect all items - angular.forEach(items, function (item) { + items.forEach(function (item) { item.selected = false; }); - + // reset selection without loosing reference. selection.length = 0; - + } else { - + // reset selection without loosing reference. selection.length = 0; - + // select all items - angular.forEach(items, function (item) { + items.forEach(function (item) { var obj = { id: item.id }; @@ -443,6 +446,7 @@ selection.push(obj); }); } + $rootScope.$broadcast("listView.itemsChanged", { items: selection }); } @@ -558,7 +562,7 @@ }; } - + /** * @ngdoc method * @name umbraco.services.listViewHelper#editItem @@ -569,22 +573,57 @@ * * @param {Object} item The item to edit */ - function editItem(item) { + function editItem(item, scope) { if (!item.editPath) { return; } + + if (scope.options.useInfiniteEditor) + { + var editorModel = { + id: item.id, + submit: function(model) { + editorService.close(); + scope.getContent(scope.contentId); + }, + close: function() { + editorService.close(); + scope.getContent(scope.contentId); + } + }; + + if (item.editPath.indexOf("/content/") == 0) + { + editorService.contentEditor(editorModel); + return; + } + + if (item.editPath.indexOf("/media/") == 0) + { + editorService.mediaEditor(editorModel); + return; + } + + if (item.editPath.indexOf("/member/") == 0) + { + editorModel.id = item.key; + editorService.memberEditor(editorModel); + return; + } + } + var parts = item.editPath.split("?"); var path = parts[0]; var params = parts[1] - ? urlHelper.getQueryStringParams("?" + parts[1]) - : {}; - + ? urlHelper.getQueryStringParams("?" + parts[1]) + : {}; + $location.path(path); for (var p in params) { $location.search(p, params[p]); } } - + function isMatchingLayout(id, layout) { // legacy format uses "nodeId", be sure to look for both return layout.id === id || layout.nodeId === id; @@ -592,21 +631,21 @@ var service = { - getLayout: getLayout, - getFirstAllowedLayout: getFirstAllowedLayout, - setLayout: setLayout, - saveLayoutInLocalStorage: saveLayoutInLocalStorage, - selectHandler: selectHandler, - selectItem: selectItem, - deselectItem: deselectItem, - clearSelection: clearSelection, - selectAllItems: selectAllItems, - selectAllItemsToggle: selectAllItemsToggle, - isSelectedAll: isSelectedAll, - setSortingDirection: setSortingDirection, - setSorting: setSorting, - getButtonPermissions: getButtonPermissions, - editItem: editItem + getLayout: getLayout, + getFirstAllowedLayout: getFirstAllowedLayout, + setLayout: setLayout, + saveLayoutInLocalStorage: saveLayoutInLocalStorage, + selectHandler: selectHandler, + selectItem: selectItem, + deselectItem: deselectItem, + clearSelection: clearSelection, + selectAllItems: selectAllItems, + selectAllItemsToggle: selectAllItemsToggle, + isSelectedAll: isSelectedAll, + setSortingDirection: setSortingDirection, + setSorting: setSorting, + getButtonPermissions: getButtonPermissions, + editItem: editItem }; diff --git a/src/Umbraco.Web.UI.Client/src/common/services/localization.service.js b/src/Umbraco.Web.UI.Client/src/common/services/localization.service.js index 6081cbd9ad..99162eaf53 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/localization.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/localization.service.js @@ -67,6 +67,9 @@ angular.module('umbraco.services') // loads the language resource file from the server initLocalizedResources: function () { + + // TODO: This promise handling is super ugly, we should just be returnning the promise from $http and returning inner values. + var deferred = $q.defer(); if (resourceFileLoadStatus === "loaded") { @@ -179,7 +182,7 @@ angular.module('umbraco.services') */ localize: function (value, tokens, fallbackValue) { return service.initLocalizedResources().then(function (dic) { - return _lookup(value, tokens, dic, fallbackValue); + return _lookup(value, tokens, dic, fallbackValue); }); }, @@ -327,6 +330,7 @@ angular.module('umbraco.services') resourceFileLoadStatus = "none"; resourceLoadingPromise = []; }); + // return the local instance when called return service; diff --git a/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js b/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js index f903c44ad5..c628e3a5b1 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js @@ -30,6 +30,7 @@ function navigationService($routeParams, $location, $q, $injector, eventsService var element = $(args.element); element.addClass('above-backdrop'); }); + //A list of query strings defined that when changed will not cause a reload of the route var nonRoutingQueryStrings = ["mculture", "cculture", "csegment", "lq", "sr"]; @@ -127,6 +128,13 @@ function navigationService($routeParams, $location, $q, $injector, eventsService } } + function showBackdrop() { + var backDropOptions = { + 'element': $('#leftcolumn')[0] + }; + backdropService.open(backDropOptions); + } + var service = { /** @@ -169,7 +177,7 @@ function navigationService($routeParams, $location, $q, $injector, eventsService //if the routing parameter keys are the same, we'll compare their values to see if any have changed and if so then the routing will be allowed. if (diff1.length === 0 && diff2.length === 0) { var partsChanged = 0; - _.each(currRoutingKeys, function (k) { + currRoutingKeys.forEach(k => { if (currUrlParams[k] != nextUrlParams[k]) { partsChanged++; } @@ -206,7 +214,8 @@ function navigationService($routeParams, $location, $q, $injector, eventsService var toRetain = _.union(retainedQueryStrings, toRetain); var currentSearch = $location.search(); $location.search(''); - _.each(toRetain, function (k) { + + toRetain.forEach(k => { if (currentSearch[k]) { $location.search(k, currentSearch[k]); } @@ -240,7 +249,7 @@ function navigationService($routeParams, $location, $q, $injector, eventsService var toRetain = Utilities.copy(nextRouteParams); var updated = false; - _.each(retainedQueryStrings, function (r) { + retainedQueryStrings.forEach(r => { // if mculture is set to null in nextRouteParams, the value will be undefined and we will not retain any query string that has a value of "null" if (currRouteParams[r] && nextRouteParams[r] !== undefined && !nextRouteParams[r]) { toRetain[r] = currRouteParams[r]; @@ -426,13 +435,9 @@ function navigationService($routeParams, $location, $q, $injector, eventsService showMenu: function (args) { var self = this; - var backDropOptions = { - 'element': $('#leftcolumn')[0] - }; - return treeService.getMenu({ treeNode: args.node }) .then(function (data) { - backdropService.open(backDropOptions); + showBackdrop(); //check for a default //NOTE: event will be undefined when a call to hideDialog is made so it won't re-load the default again. // but perhaps there's a better way to deal with with an additional parameter in the args ? it works though. @@ -543,6 +548,7 @@ function navigationService($routeParams, $location, $q, $injector, eventsService } } else { + showBackdrop(); service.showDialog({ node: node, action: action, diff --git a/src/Umbraco.Web.UI.Client/src/common/services/notifications.service.js b/src/Umbraco.Web.UI.Client/src/common/services/notifications.service.js index 196e0e3baa..000ba05f1e 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/notifications.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/notifications.service.js @@ -52,6 +52,7 @@ angular.module('umbraco.services') * @param {String} item.message longer text for the notication, trimmed after 200 characters, which can then be exanded * @param {String} item.type Notification type, can be: "success","warning","error" or "info" * @param {String} item.url url to open when notification is clicked + * @param {String} item.target the target used together with `url`. Empty if not specified. * @param {String} item.view path to custom view to load into the notification box * @param {Array} item.actions Collection of button actions to append (label, func, cssClass) * @param {Boolean} item.sticky if set to true, the notification will not auto-close diff --git a/src/Umbraco.Web.UI.Client/src/common/services/overlay.service.js b/src/Umbraco.Web.UI.Client/src/common/services/overlay.service.js index 119f40e114..ea05dad4e7 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/overlay.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/overlay.service.js @@ -8,14 +8,14 @@ (function () { "use strict"; - function overlayService(eventsService, backdropService) { + function overlayService(eventsService, backdropService, focusLockService) { var currentOverlay = null; function open(newOverlay) { // prevent two open overlays at the same time - if(currentOverlay) { + if (currentOverlay) { close(); } @@ -23,32 +23,34 @@ var overlay = newOverlay; // set the default overlay position to center - if(!overlay.position) { + if (!overlay.position) { overlay.position = "center"; } // set the default overlay size to small - if(!overlay.size) { + if (!overlay.size) { overlay.size = "small"; } // use a default empty view if nothing is set - if(!overlay.view) { + if (!overlay.view) { overlay.view = "views/common/overlays/default/default.html"; } // option to disable backdrop clicks - if(overlay.disableBackdropClick) { + if (overlay.disableBackdropClick) { backdropOptions.disableEventsOnClick = true; } overlay.show = true; + focusLockService.addInertAttribute(); backdropService.open(backdropOptions); currentOverlay = overlay; eventsService.emit("appState.overlay", overlay); } function close() { + focusLockService.removeInertAttribute(); backdropService.close(); currentOverlay = null; eventsService.emit("appState.overlay", null); @@ -76,7 +78,12 @@ case "delete": if (!overlay.confirmMessageStyle) overlay.confirmMessageStyle = "danger"; if (!overlay.submitButtonStyle) overlay.submitButtonStyle = "danger"; - if (!overlay.submitButtonLabelKey) overlay.submitButtonLabelKey = "contentTypeEditor_yesDelete"; + if (!overlay.submitButtonLabelKey) overlay.submitButtonLabelKey = "actions_delete"; + break; + + case "remove": + if (!overlay.submitButtonStyle) overlay.submitButtonStyle = "primary"; + if (!overlay.submitButtonLabelKey) overlay.submitButtonLabelKey = "actions_remove"; break; default: @@ -85,7 +92,6 @@ } open(overlay); - } function confirmDelete(overlay) { @@ -93,12 +99,18 @@ confirm(overlay); } + function confirmRemove(overlay) { + overlay.confirmType = "remove"; + confirm(overlay); + } + var service = { open: open, close: close, ysod: ysod, confirm: confirm, - confirmDelete: confirmDelete + confirmDelete: confirmDelete, + confirmRemove: confirmRemove }; return service; diff --git a/src/Umbraco.Web.UI.Client/src/common/services/search.service.js b/src/Umbraco.Web.UI.Client/src/common/services/search.service.js index fef286ec7e..eda36a5fce 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/search.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/search.service.js @@ -12,7 +12,7 @@ * *
  *      searchService.searchMembers({term: 'bob'}).then(function(results){
- *          angular.forEach(results, function(result){
+ *          results.forEach(function(result){
  *                  //returns:
  *                  {name: "name", id: 1234, menuUrl: "url", editorPath: "url", metaData: {}, subtitle: "/path/etc" }
  *           })
@@ -21,8 +21,8 @@
  * 
*/ angular.module('umbraco.services') - .factory('searchService', function ($q, $log, entityResource, contentResource, umbRequestHelper, $injector, searchResultFormatter) { - + .factory('searchService', function (entityResource, $injector, searchResultFormatter) { + return { /** @@ -42,12 +42,11 @@ angular.module('umbraco.services') throw "args.term is required"; } - return entityResource.search(args.term, "Member", args.searchFrom).then(function (data) { - _.each(data, function (item) { - searchResultFormatter.configureMemberResult(item); + return entityResource.search(args.term, "Member", args.searchFrom) + .then(data => { + data.forEach(item => searchResultFormatter.configureMemberResult(item)); + return data; }); - return data; - }); }, /** @@ -67,12 +66,12 @@ angular.module('umbraco.services') throw "args.term is required"; } - return entityResource.search(args.term, "Document", args.searchFrom, args.canceler, args.dataTypeKey).then(function (data) { - _.each(data, function (item) { - searchResultFormatter.configureContentResult(item); + return entityResource.search(args.term, "Document", args.searchFrom, args.canceler, args.dataTypeKey) + .then(data => { + data.forEach(item => searchResultFormatter.configureContentResult(item)); + return data; }); - return data; - }); + }, /** @@ -92,12 +91,11 @@ angular.module('umbraco.services') throw "args.term is required"; } - return entityResource.search(args.term, "Media", args.searchFrom, args.canceler, args.dataTypeKey).then(function (data) { - _.each(data, function (item) { - searchResultFormatter.configureMediaResult(item); + return entityResource.search(args.term, "Media", args.searchFrom, args.canceler, args.dataTypeKey) + .then(data => { + data.forEach(item => searchResultFormatter.configureMediaResult(item)); + return data; }); - return data; - }); }, /** @@ -117,10 +115,8 @@ angular.module('umbraco.services') throw "args.term is required"; } - return entityResource.searchAll(args.term, args.canceler).then(function (data) { - - _.each(data, function (resultByType) { - + return entityResource.searchAll(args.term, args.canceler).then(data => { + Object.values(data).forEach(resultByType => { //we need to format the search result data to include things like the subtitle, urls, etc... // this is done with registered angular services as part of the SearchableTreeAttribute, if that // is not found, than we format with the default formatter @@ -140,7 +136,7 @@ angular.module('umbraco.services') } } //now apply the formatter for each result - _.each(resultByType.results, function (item) { + resultByType.results.forEach(item => { formatterMethod.apply(this, [item, resultByType.treeAlias, resultByType.appAlias]); }); @@ -148,12 +144,10 @@ angular.module('umbraco.services') return data; }); - }, // TODO: This doesn't do anything! setCurrent: function (sectionAlias) { - var currentSection = sectionAlias; } }; diff --git a/src/Umbraco.Web.UI.Client/src/common/services/servervalidationmgr.service.js b/src/Umbraco.Web.UI.Client/src/common/services/servervalidationmgr.service.js index 718e44d66e..8df5a9ce8c 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/servervalidationmgr.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/servervalidationmgr.service.js @@ -7,37 +7,80 @@ * Used to handle server side validation and wires up the UI with the messages. There are 2 types of validation messages, one * is for user defined properties (called Properties) and the other is for field properties which are attached to the native * model objects (not user defined). The methods below are named according to these rules: Properties vs Fields. + * + * For a more indepth explanation of how server side validation works with the angular app, see this GitHub PR: + * https://github.com/umbraco/Umbraco-CMS/pull/8339 + * */ function serverValidationManager($timeout) { + // The array of callback objects, each object is: + // - propertyAlias (this is the property's 'path' if it's a nested error) + // - culture + // - fieldName + // - segment + // - callback (function) + // - id (unique identifier, auto-generated, used internally for unsubscribing the callback) + // - options (used for complex properties, can contain options.matchType which can be either "suffix" or "prefix" or "contains") var callbacks = []; - - /** calls the callback specified with the errors specified, used internally */ - function executeCallback(self, errorsForCallback, callback, culture, segment) { - callback.apply(self, [ - false, // pass in a value indicating it is invalid + // The array of error message objects, each object 'key' is: + // - propertyAlias (this is the property's 'path' if it's a nested error) + // - culture + // - fieldName + // - segment + // The object also contains: + // - errorMsg + var errorMsgItems = []; + + var defaultMatchOptions = { + matchType: null + } + + /** calls the callback specified with the errors specified, used internally */ + function executeCallback(errorsForCallback, callback, culture, segment, isValid) { + + callback.apply(instance, [ + isValid, // pass in a value indicating it is invalid errorsForCallback, // pass in the errors for this item - self.items, // pass in all errors in total + errorMsgItems, // pass in all errors in total culture, // pass the culture that we are listing for. segment // pass the segment that we are listing for. ] ); } - function getFieldErrors(self, fieldName) { + /** + * @ngdoc function + * @name notify + * @methodOf umbraco.services.serverValidationManager + * @function + * + * @description + * Notifies all subscriptions again. Called when there are changes to subscriptions or errors. + */ + function notify() { + + $timeout(function () { + for (var i = 0; i < errorMsgItems.length; i++) { + var item = errorMsgItems[i]; + } + notifyCallbacks(); + }); + } + + function getFieldErrors(fieldName) { if (!Utilities.isString(fieldName)) { throw "fieldName must be a string"; } //find errors for this field name - return _.filter(self.items, function (item) { + return _.filter(errorMsgItems, function (item) { return (item.propertyAlias === null && item.culture === "invariant" && item.fieldName === fieldName); }); } - - function getPropertyErrors(self, propertyAlias, culture, segment, fieldName) { + function getPropertyErrors(propertyAlias, culture, segment, fieldName, options) { if (!Utilities.isString(propertyAlias)) { throw "propertyAlias must be a string"; } @@ -52,13 +95,50 @@ function serverValidationManager($timeout) { segment = null; } + if (!options) { + options = defaultMatchOptions; + } + //find all errors for this property - return _.filter(self.items, function (item) { - return (item.propertyAlias === propertyAlias && item.culture === culture && item.segment === segment && (item.fieldName === fieldName || (fieldName === undefined || fieldName === ""))); + return _.filter(errorMsgItems, function (errMsgItem) { + + if (!errMsgItem.propertyAlias) { + return false; + } + + var matchProp = matchErrMsgItemProperty(errMsgItem, propertyAlias, options); + + return matchProp + && errMsgItem.culture === culture + && errMsgItem.segment === segment + // ignore field matching if match options are used + && (options.matchType || (errMsgItem.fieldName === fieldName || (fieldName === undefined || fieldName === ""))); }); } - - function getVariantErrors(self, culture, segment) { + + /** + * Returns true if the error message item's data matches the property validation key with a match type provided by the options + * @param {any} errMsgItem The error message item + * @param {any} propertyValidationKey The property validation key) + * @param {any} options The match type options + */ + function matchErrMsgItemProperty(errMsgItem, propertyValidationKey, options) { + if (errMsgItem.propertyAlias === propertyValidationKey) { + return true; + } + if (options.matchType === "prefix" && errMsgItem.propertyAlias.startsWith(propertyValidationKey + '/')) { + return true; + } + if (options.matchType === "suffix" && errMsgItem.propertyAlias.endsWith('/' + propertyValidationKey)) { + return true; + } + if (options.matchType === "contains" && errMsgItem.propertyAlias.includes('/' + propertyValidationKey + '/')) { + return true; + } + return false; + } + + function getVariantErrors(culture, segment) { if (!culture) { culture = "invariant"; @@ -68,39 +148,453 @@ function serverValidationManager($timeout) { } //find all errors for this property - return _.filter(self.items, function (item) { + return _.filter(errorMsgItems, function (item) { return (item.culture === culture && item.segment === segment); }); } - function notifyCallbacks(self) { - for (var cb in callbacks) { - if (callbacks[cb].propertyAlias === null && callbacks[cb].fieldName !== null) { - //its a field error callback - var fieldErrors = getFieldErrors(self, callbacks[cb].fieldName); - if (fieldErrors.length > 0) { - executeCallback(self, fieldErrors, callbacks[cb].callback, callbacks[cb].culture, callbacks[cb].segment); - } + function notifyCallback(cb) { + if (cb.propertyAlias === null && cb.fieldName !== null) { + //its a field error callback + const fieldErrors = getFieldErrors(cb.fieldName); + const valid = fieldErrors.length === 0; + executeCallback(fieldErrors, cb.callback, cb.culture, cb.segment, valid); + } + else if (cb.propertyAlias != null) { + //its a property error + const propErrors = getPropertyErrors(cb.propertyAlias, cb.culture, cb.segment, cb.fieldName, cb.options); + const valid = propErrors.length === 0; + executeCallback(propErrors, cb.callback, cb.culture, cb.segment, valid); + } + else { + //its a variant error + const variantErrors = getVariantErrors(cb.culture, cb.segment); + const valid = variantErrors.length === 0; + executeCallback(variantErrors, cb.callback, cb.culture, cb.segment, valid); + } + } + + /** Call all registered callbacks indicating if the data they are subscribed to is valid or invalid */ + function notifyCallbacks() { + + // nothing to call + if (errorMsgItems.length === 0) { + return; + } + + callbacks.forEach(cb => notifyCallback(cb)); + } + + /** + * Flattens the complex errror result json into an array of the block's id/parent id and it's corresponding validation ModelState + * @param {any} errorMsg + * @param {any} parentPropertyAlias The parent property alias for the json error + */ + function parseComplexEditorError(errorMsg, parentPropertyAlias) { + + var json = Utilities.isArray(errorMsg) ? errorMsg : JSON.parse(errorMsg); + + var result = []; + + function extractModelState(validation, parentPath) { + if (validation.$id && validation.ModelState) { + var ms = { + validationPath: `${parentPath}/${validation.$id}`, + modelState: validation.ModelState + }; + result.push(ms); + return ms; } - else if (callbacks[cb].propertyAlias != null) { - //its a property error - var propErrors = getPropertyErrors(self, callbacks[cb].propertyAlias, callbacks[cb].culture, callbacks[cb].segment, callbacks[cb].fieldName); - if (propErrors.length > 0) { - executeCallback(self, propErrors, callbacks[cb].callback, callbacks[cb].culture, callbacks[cb].segment); + return null; + } + + function iterateErrorBlocks(blocks, parentPath) { + for (var i = 0; i < blocks.length; i++) { + var validation = blocks[i]; + var ms = extractModelState(validation, parentPath); + if (!ms) { + continue; } - } - else { - //its a variant error - var variantErrors = getVariantErrors(self, callbacks[cb].culture, callbacks[cb].segment); - if (variantErrors.length > 0) { - executeCallback(self, variantErrors, callbacks[cb].callback, callbacks[cb].culture, callbacks[cb].segment); + var nested = _.omit(validation, "$id", "$elementTypeAlias", "ModelState"); + for (const [key, value] of Object.entries(nested)) { + if (Array.isArray(value)) { + iterateErrorBlocks(value, `${ms.validationPath}/${key}`); // recurse + } } } } + + iterateErrorBlocks(json, parentPropertyAlias); + + return result; } - - return { - + + /** + * @ngdoc function + * @name getPropertyCallbacks + * @methodOf umbraco.services.serverValidationManager + * @function + * + * @description + * Gets all callbacks that has been registered using the subscribe method for the propertyAlias + fieldName combo. + * This will always return any callbacks registered for just the property (i.e. field name is empty) and for ones with an + * explicit field name set. + */ + function getPropertyCallbacks(propertyAlias, culture, fieldName, segment) { + + //normalize culture to "invariant" + if (!culture) { + culture = "invariant"; + } + //normalize segment to null + if (!segment) { + segment = null; + } + + var found = _.filter(callbacks, function (cb) { + + if (!cb.options) { + cb.options = defaultMatchOptions; + } + + var matchProp = matchCallbackItemProperty(cb, propertyAlias); + + //returns any callback that have been registered directly against the field and for only the property + return matchProp + && cb.culture === culture + && cb.segment === segment + // ignore field matching if match options are used + && (cb.options.matchType || (cb.fieldName === fieldName || (cb.fieldName === undefined || cb.fieldName === ""))); + }); + return found; + } + + /** + * Returns true if the callback item's data and match options matches the property validation key + * @param {any} cb + * @param {any} propertyValidationKey + */ + function matchCallbackItemProperty(cb, propertyValidationKey) { + if (cb.propertyAlias === propertyValidationKey) { + return true; + } + if (cb.options.matchType === "prefix" && propertyValidationKey.startsWith(cb.propertyAlias + '/')) { + return true; + } + if (cb.options.matchType === "suffix" && propertyValidationKey.endsWith('/' + cb.propertyAlias)) { + return true; + } + if (cb.options.matchType === "contains" && propertyValidationKey.includes('/' + cb.propertyAlias + '/')) { + return true; + } + return false; + } + + /** + * @ngdoc function + * @name getFieldCallbacks + * @methodOf umbraco.services.serverValidationManager + * @function + * + * @description + * Gets all callbacks that has been registered using the subscribe method for the field. + */ + function getFieldCallbacks(fieldName) { + var found = _.filter(callbacks, function (item) { + //returns any callback that have been registered directly against the field + return (item.propertyAlias === null && item.culture === "invariant" && item.segment === null && item.fieldName === fieldName); + }); + return found; + } + + /** + * @ngdoc function + * @name getVariantCallbacks + * @methodOf umbraco.services.serverValidationManager + * @function + * + * @description + * Gets all callbacks that has been registered using the subscribe method for the culture and segment. + */ + function getVariantCallbacks(culture, segment) { + var found = _.filter(callbacks, function (item) { + //returns any callback that have been registered directly against the given culture and given segment. + return (item.culture === culture && item.segment === segment && item.propertyAlias === null && item.fieldName === null); + }); + return found; + } + + /** + * @ngdoc function + * @name addFieldError + * @methodOf umbraco.services.serverValidationManager + * @function + * + * @description + * Adds an error message for a native content item field (not a user defined property, for Example, 'Name') + */ + function addFieldError(fieldName, errorMsg) { + if (!fieldName) { + return; + } + + //only add the item if it doesn't exist + if (!hasFieldError(fieldName)) { + errorMsgItems.push({ + propertyAlias: null, + culture: "invariant", + segment: null, + fieldName: fieldName, + errorMsg: errorMsg + }); + } + + //find all errors for this item + var errorsForCallback = getFieldErrors(fieldName); + //we should now call all of the call backs registered for this error + var cbs = getFieldCallbacks(fieldName); + //call each callback for this error + for (var cb in cbs) { + executeCallback(errorsForCallback, cbs[cb].callback, null, null, false); + } + } + + /** + * @ngdoc function + * @name addPropertyError + * @methodOf umbraco.services.serverValidationManager + * @function + * + * @description + * Adds an error message for the content property + */ + function addPropertyError(propertyAlias, culture, fieldName, errorMsg, segment) { + + if (!propertyAlias) { + return; + } + + //normalize culture to "invariant" + if (!culture) { + culture = "invariant"; + } + //normalize segment to null + if (!segment) { + segment = null; + } + //normalize errorMsg to empty + if (!errorMsg) { + errorMsg = ""; + } + + // remove all non printable chars and whitespace from the string + // (this can be a json string for complex errors and in some test cases contains odd whitespace) + if (Utilities.isString(errorMsg)) { + errorMsg = errorMsg.trimStartSpecial().trim(); + } + + // if the error message is json it's a complex editor validation response that we need to parse + if ((Utilities.isString(errorMsg) && errorMsg.startsWith("[")) || Utilities.isArray(errorMsg)) { + + // flatten the json structure, create validation paths for each property and add each as a property error + var idsToErrors = parseComplexEditorError(errorMsg, propertyAlias); + idsToErrors.forEach(x => addErrorsForModelState(x.modelState, x.validationPath)); + + // We need to clear the error message else it will show up as a giant json block against the property + errorMsg = ""; + } + + //only add the item if it doesn't exist + if (!hasPropertyError(propertyAlias, culture, fieldName, segment)) { + errorMsgItems.push({ + propertyAlias: propertyAlias, + culture: culture, + segment: segment, + fieldName: fieldName, + errorMsg: errorMsg + }); + } + } + + /** + * @ngdoc function + * @name hasPropertyError + * @methodOf umbraco.services.serverValidationManager + * @function + * + * @description + * Checks if the content property + culture + field name combo has an error + */ + function hasPropertyError(propertyAlias, culture, fieldName, segment) { + + //normalize culture to null + if (!culture) { + culture = "invariant"; + } + //normalize segment to null + if (!segment) { + segment = null; + } + + var err = _.find(errorMsgItems, function (item) { + //return true if the property alias matches and if an empty field name is specified or the field name matches + return (item.propertyAlias === propertyAlias && item.culture === culture && item.segment === segment && (item.fieldName === fieldName || (fieldName === undefined || fieldName === ""))); + }); + return err ? true : false; + } + + /** + * @ngdoc function + * @name hasFieldError + * @methodOf umbraco.services.serverValidationManager + * @function + * + * @description + * Checks if a content field has an error + */ + function hasFieldError(fieldName) { + var err = _.find(errorMsgItems, function (item) { + //return true if the property alias matches and if an empty field name is specified or the field name matches + return (item.propertyAlias === null && item.culture === "invariant" && item.segment === null && item.fieldName === fieldName); + }); + return err ? true : false; + } + + /** + * @ngdoc function + * @name addErrorsForModelState + * @methodOf umbraco.services.serverValidationManager + * @param {any} modelState the modelState object + * @param {any} parentValidationPath optional parameter specifying a nested element's UDI for which this property belongs (for complex editors) + * @description + * This wires up all of the server validation model state so that valServer and valServerField directives work + */ + function addErrorsForModelState(modelState, parentValidationPath) { + + if (!Utilities.isObject(modelState)) { + throw "modelState is not an object"; + } + + for (const [key, value] of Object.entries(modelState)) { + + //This is where things get interesting.... + // We need to support validation for all editor types such as both the content and content type editors. + // The Content editor ModelState is quite specific with the way that Properties are validated especially considering + // that each property is a User Developer property editor. + // The way that Content Type Editor ModelState is created is simply based on the ASP.Net validation data-annotations + // system. + // So, to do this there's some special ModelState syntax we need to know about. + // For Content Properties, which are user defined, we know that they will exist with a prefixed + // ModelState of "_Properties.", so if we detect this, then we know it's for a content Property. + + //the alias in model state can be in dot notation which indicates + // * the first part is the content property alias + // * the second part is the field to which the valiation msg is associated with + //There will always be at least 4 parts for content properties since all model errors for properties are prefixed with "_Properties" + //If it is not prefixed with "_Properties" that means the error is for a field of the object directly. + + // Example: "_Properties.headerImage.en-US.mySegment.myField" + // * it's for a property since it has a _Properties prefix + // * it's for the headerImage property type + // * it's for the en-US culture + // * it's for the mySegment segment + // * it's for the myField html field (optional) + + var parts = key.split("."); + + //Check if this is for content properties - specific to content/media/member editors because those are special + // user defined properties with custom controls. + if (parts.length > 1 && parts[0] === "_Properties") { + + // create the validation key, might just be the prop alias but if it's nested will be validation path + // like "myBlockEditor/34E3A26C-103D-4A05-AB9D-7E14032309C3/addresses/7170A4DD-2441-4B1B-A8D3-437D75C4CBC9/city" + var propertyValidationKey = createPropertyValidationKey(parts[1], parentValidationPath); + + var culture = null; + if (parts.length > 2) { + culture = parts[2]; + //special check in case the string is formatted this way + if (culture === "null") { + culture = null; + } + } + + var segment = null; + if (parts.length > 3) { + segment = parts[3]; + //special check in case the string is formatted this way + if (segment === "null") { + segment = null; + } + } + + var htmlFieldReference = ""; + if (parts.length > 4) { + htmlFieldReference = parts[4] || ""; + } + + // add a generic error for the property + addPropertyError(propertyValidationKey, culture, htmlFieldReference, value && Array.isArray(value) && value.length > 0 ? value[0] : null, segment); + } + else { + + //Everthing else is just a 'Field'... the field name could contain any level of 'parts' though, for example: + // Groups[0].Properties[2].Alias + addFieldError(key, value[0]); + } + } + + if (hasPropertyError) { + // ensure all callbacks are called after property errors are added + notifyCallbacks(); + } + } + + function createPropertyValidationKey(propertyAlias, parentValidationPath) { + return parentValidationPath ? (parentValidationPath + "/" + propertyAlias) : propertyAlias; + } + + /** + * @ngdoc function + * @name reset + * @methodOf umbraco.services.serverValidationManager + * @function + * + * @description + * Clears all errors and notifies all callbacks that all server errros are now valid - used when submitting a form + */ + function reset() { + clear(); + for (var cb in callbacks) { + callbacks[cb].callback.apply(instance, [ + true, //pass in a value indicating it is VALID + [], //pass in empty collection + [], + null, + null] + ); + } + } + + /** + * @ngdoc function + * @name clear + * @methodOf umbraco.services.serverValidationManager + * @function + * + * @description + * Clears all errors + */ + function clear() { + errorMsgItems = []; + } + + var instance = { + + addErrorsForModelState: addErrorsForModelState, + parseComplexEditorError: parseComplexEditorError, + createPropertyValidationKey: createPropertyValidationKey, + /** * @ngdoc function * @name notifyAndClearAllSubscriptions @@ -108,42 +602,27 @@ function serverValidationManager($timeout) { * @function * * @description - * This method needs to be called once all field and property errors are wired up. + * This method can be called once all field and property errors are wired up. * * In some scenarios where the error collection needs to be persisted over a route change * (i.e. when a content item (or any item) is created and the route redirects to the editor) * the controller should call this method once the data is bound to the scope * so that any persisted validation errors are re-bound to their controls. Once they are re-binded this then clears the validation * colleciton so that if another route change occurs, the previously persisted validation errors are not re-bound to the new item. + * + * In the case of content with complex editors, variants and different views, those editors don't call this method and instead + * manage the server validation manually by calling notify when necessary and clear/reset when necessary. */ notifyAndClearAllSubscriptions: function() { - var self = this; - $timeout(function () { - notifyCallbacks(self); + notifyCallbacks(); //now that they are all executed, we're gonna clear all of the errors we have - self.clear(); + clear(); }); }, - /** - * @ngdoc function - * @name notify - * @methodOf umbraco.services.serverValidationManager - * @function - * - * @description - * This method isn't used very often but can be used if all subscriptions need to be notified again. This can be - * handy if a view needs to be reloaded/rebuild like when switching variants in the content editor. - */ - notify: function() { - var self = this; - - $timeout(function () { - notifyCallbacks(self); - }); - }, + notify: notify, /** * @ngdoc function @@ -158,7 +637,7 @@ function serverValidationManager($timeout) { * field alias to listen for. * If propertyAlias is null, then this subscription is for a field property (not a user defined property). */ - subscribe: function (propertyAlias, culture, fieldName, callback, segment) { + subscribe: function (propertyAlias, culture, fieldName, callback, segment, options) { if (!callback) { return; } @@ -173,30 +652,34 @@ function serverValidationManager($timeout) { if (!segment) { segment = null; } - + + let cb = null; + if (propertyAlias === null) { - callbacks.push({ + cb = { propertyAlias: null, culture: culture, segment: segment, fieldName: fieldName, callback: callback, id: id - }); + }; } else if (propertyAlias !== undefined) { - //normalize culture to null - - callbacks.push({ + + cb = { propertyAlias: propertyAlias, - culture: culture, + culture: culture, segment: segment, fieldName: fieldName, callback: callback, - id: id - }); + id: id, + options: options + }; } + callbacks.push(cb); + function unsubscribeId() { //remove all callbacks for the content field callbacks = _.reject(callbacks, function (item) { @@ -204,6 +687,9 @@ function serverValidationManager($timeout) { }); } + // Notify the new callback + notifyCallback(cb); + //return a function to unsubscribe this subscription by uniqueId return unsubscribeId; }, @@ -244,52 +730,8 @@ function serverValidationManager($timeout) { } }, - - /** - * @ngdoc function - * @name getPropertyCallbacks - * @methodOf umbraco.services.serverValidationManager - * @function - * - * @description - * Gets all callbacks that has been registered using the subscribe method for the propertyAlias + fieldName combo. - * This will always return any callbacks registered for just the property (i.e. field name is empty) and for ones with an - * explicit field name set. - */ - getPropertyCallbacks: function (propertyAlias, culture, fieldName, segment) { - - //normalize culture to "invariant" - if (!culture) { - culture = "invariant"; - } - //normalize segment to null - if (!segment) { - segment = null; - } - - var found = _.filter(callbacks, function (item) { - //returns any callback that have been registered directly against the field and for only the property - return (item.propertyAlias === propertyAlias && item.culture === culture && item.segment === segment && (item.fieldName === fieldName || (item.fieldName === undefined || item.fieldName === ""))); - }); - return found; - }, - - /** - * @ngdoc function - * @name getFieldCallbacks - * @methodOf umbraco.services.serverValidationManager - * @function - * - * @description - * Gets all callbacks that has been registered using the subscribe method for the field. - */ - getFieldCallbacks: function (fieldName) { - var found = _.filter(callbacks, function (item) { - //returns any callback that have been registered directly against the field - return (item.propertyAlias === null && item.culture === "invariant" && item.segment === null && item.fieldName === fieldName); - }); - return found; - }, + getPropertyCallbacks: getPropertyCallbacks, + getFieldCallbacks: getFieldCallbacks, /** * @ngdoc function @@ -308,107 +750,12 @@ function serverValidationManager($timeout) { return found; }, - /** - * @ngdoc function - * @name getVariantCallbacks - * @methodOf umbraco.services.serverValidationManager - * @function - * - * @description - * Gets all callbacks that has been registered using the subscribe method for the culture and segment. - */ - getVariantCallbacks: function (culture, segment) { - var found = _.filter(callbacks, function (item) { - //returns any callback that have been registered directly against the given culture and given segment. - return (item.culture === culture && item.segment === segment && item.propertyAlias === null && item.fieldName === null); - }); - return found; - }, - - /** - * @ngdoc function - * @name addFieldError - * @methodOf umbraco.services.serverValidationManager - * @function - * - * @description - * Adds an error message for a native content item field (not a user defined property, for Example, 'Name') - */ - addFieldError: function(fieldName, errorMsg) { - if (!fieldName) { - return; - } - - //only add the item if it doesn't exist - if (!this.hasFieldError(fieldName)) { - this.items.push({ - propertyAlias: null, - culture: "invariant", - segment: null, - fieldName: fieldName, - errorMsg: errorMsg - }); - } - - //find all errors for this item - var errorsForCallback = getFieldErrors(this, fieldName); - //we should now call all of the call backs registered for this error - var cbs = this.getFieldCallbacks(fieldName); - //call each callback for this error - for (var cb in cbs) { - executeCallback(this, errorsForCallback, cbs[cb].callback, null, null); - } - }, + getVariantCallbacks: getVariantCallbacks, + addFieldError: addFieldError, - /** - * @ngdoc function - * @name addPropertyError - * @methodOf umbraco.services.serverValidationManager - * @function - * - * @description - * Adds an error message for the content property - */ addPropertyError: function (propertyAlias, culture, fieldName, errorMsg, segment) { - if (!propertyAlias) { - return; - } - - //normalize culture to "invariant" - if (!culture) { - culture = "invariant"; - } - //normalize segment to null - if (!segment) { - segment = null; - } - - //only add the item if it doesn't exist - if (!this.hasPropertyError(propertyAlias, culture, fieldName, segment)) { - this.items.push({ - propertyAlias: propertyAlias, - culture: culture, - segment: segment, - fieldName: fieldName, - errorMsg: errorMsg - }); - } - - //find all errors for this item - var errorsForCallback = getPropertyErrors(this, propertyAlias, culture, segment, fieldName); - //we should now call all of the call backs registered for this error - var cbs = this.getPropertyCallbacks(propertyAlias, culture, fieldName, segment); - //call each callback for this error - for (var cb in cbs) { - executeCallback(this, errorsForCallback, cbs[cb].callback, culture, segment); - } - - //execute variant specific callbacks here too when a propery error is added - var variantCbs = this.getVariantCallbacks(culture, segment); - //call each callback for this error - for (var cb in variantCbs) { - executeCallback(this, errorsForCallback, variantCbs[cb].callback, culture, segment); - } + addPropertyError(propertyAlias, culture, fieldName, errorMsg, segment); + notifyCallbacks(); // ensure all callbacks are called }, /** @@ -420,61 +767,19 @@ function serverValidationManager($timeout) { * @description * Removes an error message for the content property */ - removePropertyError: function (propertyAlias, culture, fieldName, segment) { + removePropertyError: function (propertyAlias, culture, fieldName, segment, options) { - if (!propertyAlias) { - return; - } + var errors = getPropertyErrors(propertyAlias, culture, segment, fieldName, options); + errorMsgItems = errorMsgItems.filter(v => errors.indexOf(v) === -1); - //normalize culture to null - if (!culture) { - culture = "invariant"; - } - //normalize segment to null - if (!segment) { - segment = null; - } - - //remove the item - this.items = _.reject(this.items, function (item) { - return (item.propertyAlias === propertyAlias && item.culture === culture && item.segment === segment && (item.fieldName === fieldName || (fieldName === undefined || fieldName === ""))); - }); - }, - - /** - * @ngdoc function - * @name reset - * @methodOf umbraco.services.serverValidationManager - * @function - * - * @description - * Clears all errors and notifies all callbacks that all server errros are now valid - used when submitting a form - */ - reset: function () { - this.clear(); - for (var cb in callbacks) { - callbacks[cb].callback.apply(this, [ - true, //pass in a value indicating it is VALID - [], //pass in empty collection - [], - null, - null] - ); + if (errors.length > 0) { + // removal was successful, re-notify all subscribers + notifyCallbacks(); } }, - /** - * @ngdoc function - * @name clear - * @methodOf umbraco.services.serverValidationManager - * @function - * - * @description - * Clears all errors - */ - clear: function() { - this.items = []; - }, + reset: reset, + clear: clear, /** * @ngdoc function @@ -486,23 +791,17 @@ function serverValidationManager($timeout) { * Gets the error message for the content property */ getPropertyError: function (propertyAlias, culture, fieldName, segment) { - - //normalize culture to "invariant" - if (!culture) { - culture = "invariant"; + var errors = getPropertyErrors(propertyAlias, culture, segment, fieldName); + if (errors.length > 0) { // should only ever contain one + return errors[0]; } - //normalize segment to null - if (!segment) { - segment = null; - } - - var err = _.find(this.items, function (item) { - //return true if the property alias matches and if an empty field name is specified or the field name matches - return (item.propertyAlias === propertyAlias && item.culture === culture && item.segment === segment && (item.fieldName === fieldName || (fieldName === undefined || fieldName === ""))); - }); - return err; + return undefined; }, - + + getPropertyErrorsByValidationPath: function (propertyValidationPath, culture, segment, options) { + return getPropertyErrors(propertyValidationPath, culture, segment, "", options); + }, + /** * @ngdoc function * @name getFieldError @@ -513,56 +812,15 @@ function serverValidationManager($timeout) { * Gets the error message for a content field */ getFieldError: function (fieldName) { - var err = _.find(this.items, function (item) { + var err = _.find(errorMsgItems, function (item) { //return true if the property alias matches and if an empty field name is specified or the field name matches return (item.propertyAlias === null && item.culture === "invariant" && item.segment === null && item.fieldName === fieldName); }); return err; }, - /** - * @ngdoc function - * @name hasPropertyError - * @methodOf umbraco.services.serverValidationManager - * @function - * - * @description - * Checks if the content property + culture + field name combo has an error - */ - hasPropertyError: function (propertyAlias, culture, fieldName, segment) { - - //normalize culture to null - if (!culture) { - culture = "invariant"; - } - //normalize segment to null - if (!segment) { - segment = null; - } - - var err = _.find(this.items, function (item) { - //return true if the property alias matches and if an empty field name is specified or the field name matches - return (item.propertyAlias === propertyAlias && item.culture === culture && item.segment === segment && (item.fieldName === fieldName || (fieldName === undefined || fieldName === ""))); - }); - return err ? true : false; - }, - - /** - * @ngdoc function - * @name hasFieldError - * @methodOf umbraco.services.serverValidationManager - * @function - * - * @description - * Checks if a content field has an error - */ - hasFieldError: function (fieldName) { - var err = _.find(this.items, function (item) { - //return true if the property alias matches and if an empty field name is specified or the field name matches - return (item.propertyAlias === null && item.culture === "invariant" && item.segment === null && item.fieldName === fieldName); - }); - return err ? true : false; - }, + hasPropertyError: hasPropertyError, + hasFieldError: hasFieldError, /** * @ngdoc function @@ -580,7 +838,7 @@ function serverValidationManager($timeout) { culture = "invariant"; } - var err = _.find(this.items, function (item) { + var err = _.find(errorMsgItems, function (item) { return (item.culture === culture && item.segment === null); }); return err ? true : false; @@ -606,14 +864,25 @@ function serverValidationManager($timeout) { segment = null; } - var err = _.find(this.items, function (item) { + var err = _.find(errorMsgItems, function (item) { return (item.culture === culture && item.segment === segment); }); return err ? true : false; - }, - /** The array of error messages */ - items: [] + } + }; + + // Used to return the 'items' array as a reference/getter + Object.defineProperty(instance, "items", { + get: function () { + return errorMsgItems; + }, + set: function (value) { + throw "Cannot set the items array"; + } + }); + + return instance; } angular.module('umbraco.services').factory('serverValidationManager', serverValidationManager); diff --git a/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js b/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js index 41f240d3af..1d7154288f 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js @@ -107,7 +107,7 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s //queue rules loading if (configuredStylesheets) { - angular.forEach(configuredStylesheets, function (val, key) { + configuredStylesheets.forEach(function (val, key) { if (val.indexOf(Umbraco.Sys.ServerVariables.umbracoSettings.cssPath + "/") === 0) { // current format (full path to stylesheet) @@ -119,7 +119,7 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s } promises.push(stylesheetResource.getRulesByName(val).then(function (rules) { - angular.forEach(rules, function (rule) { + rules.forEach(function (rule) { var r = {}; r.title = rule.name; if (rule.selector[0] == ".") { @@ -1367,6 +1367,9 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s // throw "args.model.value is required"; //} + // force TinyMCE to load plugins/themes from minified files (see http://archive.tinymce.com/wiki.php/api4:property.tinymce.suffix.static) + args.editor.suffix = ".min"; + var unwatch = null; //Starts a watch on the model value so that we can update TinyMCE if the model changes behind the scenes or from the server @@ -1504,6 +1507,8 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s }); args.editor.on('Dirty', function (e) { + syncContent(); // Set model.value to the RTE's content + //make the form dirty manually so that the track changes works, setting our model doesn't trigger // the angular bits because tinymce replaces the textarea. if (args.currentForm) { diff --git a/src/Umbraco.Web.UI.Client/src/common/services/tree.service.js b/src/Umbraco.Web.UI.Client/src/common/services/tree.service.js index 0d6216f7cc..9970995a28 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/tree.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/tree.service.js @@ -54,7 +54,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS //take the last child var childPath = this.getPath(node.children[node.children.length - 1]).join(","); //check if this already exists, if so exit - if (expandedPaths.indexOf(childPath) !== -1) { + if (expandedPaths.includes(childPath)) { return; } @@ -65,18 +65,18 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS var clonedPaths = expandedPaths.slice(0); //make a copy to iterate over so we can modify the original in the iteration - _.each(clonedPaths, function (p) { + clonedPaths.forEach(p => { if (childPath.startsWith(p + ",")) { //this means that the node's path supercedes this path stored so we can remove the current 'p' and replace it with node.path expandedPaths.splice(expandedPaths.indexOf(p), 1); //remove it - if (expandedPaths.indexOf(childPath) === -1) { + if (expandedPaths.includes(childPath) === false) { expandedPaths.push(childPath); //replace it } } else if (p.startsWith(childPath + ",")) { //this means we've already tracked a deeper node so we shouldn't track this one } - else if (expandedPaths.indexOf(childPath) === -1) { + else if (expandedPaths.includes(childPath) === false) { expandedPaths.push(childPath); //track it } }); @@ -135,7 +135,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS if (treeNode.iconIsClass === undefined || treeNode.iconIsClass) { var converted = iconHelper.convertFromLegacyTreeNodeIcon(treeNode); treeNode.cssClass = standardCssClass + " " + converted; - if (converted.startsWith('.')) { + if (converted && converted.startsWith('.')) { //its legacy so add some width/height treeNode.style = "height:16px;width:16px;"; } @@ -235,7 +235,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS } }); } - else if (args.filter && angular.isFunction(args.filter)) { + else if (args.filter && Utilities.isFunction(args.filter)) { //if a filter is supplied a cacheKey must be supplied as well if (!args.cacheKey) { throw "args.cacheKey is required if args.filter is supplied"; @@ -315,7 +315,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS args.node.hasChildren = true; //Since we've removed the children & reloaded them, we need to refresh the UI now because the tree node UI doesn't operate on normal angular $watch since that will be pretty slow - if (angular.isFunction(args.node.updateNodeData)) { + if (Utilities.isFunction(args.node.updateNodeData)) { args.node.updateNodeData(args.node); } } @@ -349,7 +349,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS * @param {object} treeNode the node to remove */ removeNode: function (treeNode) { - if (!angular.isFunction(treeNode.parent)) { + if (!Utilities.isFunction(treeNode.parent)) { return; } @@ -509,7 +509,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS if (current.metaData && current.metaData["treeAlias"]) { root = current; } - else if (angular.isFunction(current.parent)) { + else if (Utilities.isFunction(current.parent)) { //we can only continue if there is a parent() method which means this // tree node was loaded in as part of a real tree, not just as a single tree // node from the server. @@ -706,7 +706,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS //to fire, instead we're just going to replace all the properties of this node. //there should always be a method assigned but we'll check anyways - if (angular.isFunction(node.parent().children[index].updateNodeData)) { + if (Utilities.isFunction(node.parent().children[index].updateNodeData)) { node.parent().children[index].updateNodeData(found); } else { @@ -741,7 +741,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS if (!node) { throw "node cannot be null"; } - if (!angular.isFunction(node.parent)) { + if (!Utilities.isFunction(node.parent)) { throw "node.parent is not a function, the path cannot be resolved"; } diff --git a/src/Umbraco.Web.UI.Client/src/common/services/udi.service.js b/src/Umbraco.Web.UI.Client/src/common/services/udi.service.js new file mode 100644 index 0000000000..7b08c68e4e --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/services/udi.service.js @@ -0,0 +1,51 @@ +(function () { + "use strict"; + + /** + * @ngdoc service + * @name umbraco.services.udiService + * @description A service for UDIs + **/ + function udiService() { + return { + + /** + * @ngdoc method + * @name umbraco.services.udiService#create + * @methodOf umbraco.services.udiService + * @function + * + * @description + * Generates a Udi string with a new ID + * + * @param {string} entityType The entityType as a string. + * @returns {string} The generated UDI + */ + create: function(entityType) { + return this.build(entityType, String.CreateGuid()); + }, + + build: function (entityType, guid) { + return "umb://" + entityType + "/" + (guid.replace(/-/g, "")); + }, + + getKey: function (udi) { + if (!Utilities.isString(udi)) { + throw "udi is not a string"; + } + if (!udi.startsWith("umb://")) { + throw "udi does not start with umb://"; + } + var withoutScheme = udi.substr("umb://".length); + var withoutHost = withoutScheme.substr(withoutScheme.indexOf("/") + 1).trim(); + if (withoutHost.length !== 32) { + throw "udi is not 32 chars"; + } + return `${withoutHost.substr(0, 8)}-${withoutHost.substr(8, 4)}-${withoutHost.substr(12, 4)}-${withoutHost.substr(16, 4)}-${withoutHost.substr(20)}`; + } + } + } + + angular.module("umbraco.services").factory("udiService", udiService); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/common/services/umbdataformatter.service.js b/src/Umbraco.Web.UI.Client/src/common/services/umbdataformatter.service.js index 109fff0919..c646c4833f 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/umbdataformatter.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/umbdataformatter.service.js @@ -83,7 +83,7 @@ }); var saveProperties = _.map(realProperties, function (p) { - var saveProperty = _.pick(p, 'id', 'alias', 'description', 'validation', 'label', 'sortOrder', 'dataTypeId', 'groupId', 'memberCanEdit', 'showOnMemberProfile', 'isSensitiveData', 'allowCultureVariant', 'allowSegmentVariant'); + var saveProperty = _.pick(p, 'id', 'alias', 'description', 'validation', 'label', 'sortOrder', 'dataTypeId', 'groupId', 'memberCanEdit', 'showOnMemberProfile', 'isSensitiveData', 'allowCultureVariant', 'allowSegmentVariant', 'labelOnTop'); return saveProperty; }); @@ -404,7 +404,7 @@ if (displayModel.variants && displayModel.variants.length > 1) { // Collect all invariant properties from the variants that are either the // default language variant or the default segment variant. - var defaultVariants = _.filter(displayModel.variants, function (variant) { + var defaultVariants = _.filter(displayModel.variants, function (variant) { var isDefaultLanguage = variant.language && variant.language.isDefault; var isDefaultSegment = variant.segment == null; @@ -433,7 +433,7 @@ return variant !== defaultVariant; }); - // now assign this same invariant property instance to the same index of the other variants property array + // now assign this same invariant property instance to the same index of the other variants property array _.each(otherVariants, function (variant) { _.each(invariantProps, function (invProp) { var tab = variant.tabs[invProp.tabIndex]; diff --git a/src/Umbraco.Web.UI.Client/src/common/services/umbrequesthelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/umbrequesthelper.service.js index 4cbc5e567a..0a4009264d 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/umbrequesthelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/umbrequesthelper.service.js @@ -15,10 +15,10 @@ function umbRequestHelper($http, $q, notificationsService, eventsService, formHe * * @description * This will convert a virtual path (i.e. ~/App_Plugins/Blah/Test.html ) to an absolute path - * + * * @param {string} a virtual path, if this is already an absolute path it will just be returned, if this is a relative path an exception will be thrown */ - convertVirtualToAbsolutePath: function(virtualPath) { + convertVirtualToAbsolutePath: function (virtualPath) { if (virtualPath.startsWith("/")) { return virtualPath; } @@ -31,6 +31,7 @@ function umbRequestHelper($http, $q, notificationsService, eventsService, formHe return Umbraco.Sys.ServerVariables.application.applicationPath + virtualPath.trimStart("~/"); }, + /** * @ngdoc method * @name umbraco.services.umbRequestHelper#dictionaryToQueryString @@ -39,7 +40,7 @@ function umbRequestHelper($http, $q, notificationsService, eventsService, formHe * * @description * This will turn an array of key/value pairs or a standard dictionary into a query string - * + * * @param {Array} queryStrings An array of key/value pairs */ dictionaryToQueryString: function (queryStrings) { @@ -76,9 +77,9 @@ function umbRequestHelper($http, $q, notificationsService, eventsService, formHe * * @description * This will return the webapi Url for the requested key based on the servervariables collection - * + * * @param {string} apiName The webapi name that is found in the servervariables["umbracoUrls"] dictionary - * @param {string} actionName The webapi action name + * @param {string} actionName The webapi action name * @param {object} queryStrings Can be either a string or an array containing key/value pairs */ getApiUrl: function (apiName, actionName, queryStrings) { @@ -103,7 +104,7 @@ function umbRequestHelper($http, $q, notificationsService, eventsService, formHe * * @description * This returns a promise with an underlying http call, it is a helper method to reduce - * the amount of duplicate code needed to query http resources and automatically handle any + * the amount of duplicate code needed to query http resources and automatically handle any * Http errors. See /docs/source/using-promises-resources.md * * @param {object} opts A mixed object which can either be a string representing the error message to be @@ -117,7 +118,7 @@ function umbRequestHelper($http, $q, notificationsService, eventsService, formHe * The error callback must return an object containing: {errorMsg: errorMessage, data: originalData, status: status } */ resourcePromise: function (httpPromise, opts) { - + /** The default success callback used if one is not supplied in the opts */ function defaultSuccess(data, status, headers, config) { //when it's successful, just return the data @@ -135,7 +136,7 @@ function umbRequestHelper($http, $q, notificationsService, eventsService, formHe }; // if "opts" is a promise, we set "err.errorMsg" to be that promise - if (typeof(opts) == "object" && typeof(opts.then) == "function") { + if (typeof (opts) == "object" && typeof (opts.then) == "function") { err.errorMsg = opts; } @@ -151,7 +152,7 @@ function umbRequestHelper($http, $q, notificationsService, eventsService, formHe return httpPromise.then(function (response) { - //invoke the callback + //invoke the callback var result = callbacks.success.apply(this, [response.data, response.status, response.headers, response.config]); formHelper.showNotifications(response.data); @@ -165,11 +166,9 @@ function umbRequestHelper($http, $q, notificationsService, eventsService, formHe return; //sometimes oddly this happens, nothing we can do } - if (!response.status && response.message && response.stack) { - //this is a JS/angular error that we should deal with - return $q.reject({ - errorMsg: response.message - }); + if (!response.status) { + //this is a JS/angular error + return $q.reject(response); } //invoke the callback @@ -180,12 +179,12 @@ function umbRequestHelper($http, $q, notificationsService, eventsService, formHe //show a ysod dialog if (Umbraco.Sys.ServerVariables["isDebuggingEnabled"] === true) { - const error = { errorMsg: 'An error occured', data: response.data }; + const error = { errorMsg: 'An error occurred', data: response.data }; // TODO: All YSOD handling should be done with an interceptor overlayService.ysod(error); } else { - //show a simple error notification + //show a simple error notification notificationsService.error("Server error", "Contact administrator, see log for full details.
" + result.errorMsg + ""); } @@ -211,7 +210,7 @@ function umbRequestHelper($http, $q, notificationsService, eventsService, formHe * * @description * Used for saving content/media/members specifically - * + * * @param {Object} args arguments object * @returns {Promise} http promise object. */ @@ -235,7 +234,7 @@ function umbRequestHelper($http, $q, notificationsService, eventsService, formHe if (args.showNotifications === null || args.showNotifications === undefined) { args.showNotifications = true; } - + //save the active tab id so we can set it when the data is returned. var activeTab = _.find(args.content.tabs, function (item) { return item.active; @@ -265,9 +264,7 @@ function umbRequestHelper($http, $q, notificationsService, eventsService, formHe //reset the tabs and set the active one if (response.data.tabs && response.data.tabs.length > 0) { - _.each(response.data.tabs, function (item) { - item.active = false; - }); + response.data.tabs.forEach(item => item.active = false); response.data.tabs[activeTabIndex].active = true; } @@ -280,7 +277,11 @@ function umbRequestHelper($http, $q, notificationsService, eventsService, formHe //the data returned is the up-to-date data so the UI will refresh return $q.resolve(response.data); }, function (response) { - //failure callback + + if (!response.status) { + //this is a JS/angular error + return $q.reject(response); + } //when there's a 500 (unhandled) error show a YSOD overlay if debugging is enabled. if (response.status >= 500 && response.status < 600) { @@ -293,12 +294,12 @@ function umbRequestHelper($http, $q, notificationsService, eventsService, formHe } else if (Umbraco.Sys.ServerVariables["isDebuggingEnabled"] === true) { //show a ysod dialog - const error = { errorMsg: 'An error occured', data: response.data }; + const error = { errorMsg: 'An error occurred', data: response.data }; // TODO: All YSOD handling should be done with an interceptor overlayService.ysod(error); } else { - //show a simple error notification + //show a simple error notification notificationsService.error("Server error", "Contact administrator, see log for full details.
" + response.data.ExceptionMessage + ""); } @@ -324,12 +325,12 @@ function umbRequestHelper($http, $q, notificationsService, eventsService, formHe if (!jsonData) { throw "jsonData cannot be null"; } if (Utilities.isArray(jsonData)) { - _.each(jsonData, function (item) { + jsonData.forEach(item => { if (!item.key || !item.value) { throw "jsonData array item must have both a key and a value property"; } }); } else if (!jsonData.key || !jsonData.value) { throw "jsonData object must have both a key and a value property"; } - + return $http({ method: 'POST', url: url, @@ -338,11 +339,11 @@ function umbRequestHelper($http, $q, notificationsService, eventsService, formHe // and setting the Content-type manually will not set this boundary parameter. For whatever reason, setting the Content-type to 'undefined' // will force the request to automatically populate the headers properly including the boundary parameter. headers: { 'Content-Type': undefined }, - transformRequest: function(data) { + transformRequest: function (data) { var formData = new FormData(); //add the json data if (Utilities.isArray(data)) { - _.each(data, function(item) { + data.forEach(item => { formData.append(item.key, !Utilities.isString(item.value) ? Utilities.toJson(item.value) : item.value); }); } @@ -358,13 +359,13 @@ function umbRequestHelper($http, $q, notificationsService, eventsService, formHe return formData; }, data: jsonData - }).then(function(response) { + }).then(function (response) { return $q.resolve(response); - }, function(response) { + }, function (response) { return $q.reject(response); }); }, - + /** * @ngdoc method * @name umbraco.resources.contentResource#downloadFile @@ -372,11 +373,11 @@ function umbRequestHelper($http, $q, notificationsService, eventsService, formHe * * @description * Downloads a file to the client using AJAX/XHR - * + * * @param {string} httpPath the path (url) to the resource being downloaded * @returns {Promise} http promise object. */ - downloadFile : function (httpPath) { + downloadFile: function (httpPath) { /** * Based on an implementation here: web.student.tuwien.ac.at/~e0427417/jsdownload.html @@ -386,7 +387,7 @@ function umbRequestHelper($http, $q, notificationsService, eventsService, formHe // Use an arraybuffer return $http.get(httpPath, { responseType: 'arraybuffer' }) .then(function (response) { - + var octetStreamMime = 'application/octet-stream'; var success = false; @@ -470,7 +471,7 @@ function umbRequestHelper($http, $q, notificationsService, eventsService, formHe window.open(httpPath, '_blank', ''); } - return $q.resolve(); + return $q.resolve(response); }, function (response) { @@ -483,4 +484,5 @@ function umbRequestHelper($http, $q, notificationsService, eventsService, formHe } }; } + angular.module('umbraco.services').factory('umbRequestHelper', umbRequestHelper); diff --git a/src/Umbraco.Web.UI.Client/src/common/services/user.service.js b/src/Umbraco.Web.UI.Client/src/common/services/user.service.js index de6fbaf782..00871caab1 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/user.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/user.service.js @@ -1,5 +1,5 @@ angular.module('umbraco.services') - .factory('userService', function ($rootScope, eventsService, $q, $location, requestRetryQueue, authResource, emailMarketingResource, $timeout, angularHelper) { + .factory('userService', function ($rootScope, eventsService, $q, $location, $window, requestRetryQueue, authResource, emailMarketingResource, $timeout, angularHelper) { var currentUser = null; var lastUserId = null; @@ -166,7 +166,7 @@ angular.module('umbraco.services') }, /** Internal method to retry all request after sucessfull login */ - _retryRequestQueue: function(success) { + _retryRequestQueue: function (success) { retryRequestQueue(success) }, @@ -185,18 +185,22 @@ angular.module('umbraco.services') authenticate: function (login, password) { return authResource.performLogin(login, password) - .then(function(data) { + .then(function (data) { // Check if user has a start node set. - if(data.startContentIds.length === 0 && data.startMediaIds.length === 0){ + if (data.startContentIds.length === 0 && data.startMediaIds.length === 0) { var errorMsg = "User has no start-nodes"; var result = { errorMsg: errorMsg, user: data, authenticated: false, lastUserId: lastUserId, loginType: "credentials" }; eventsService.emit("app.notAuthenticated", result); + // TODO: How does this make sense? How can you throw from a promise? Does this get caught by the rejection? + // If so then return $q.reject should be used. throw result; } - + return data; - + + }, function (err) { + return $q.reject(err); }).then(this.setAuthenticationSuccessful); }, setAuthenticationSuccessful: function (data) { @@ -218,8 +222,14 @@ angular.module('umbraco.services') return authResource.performLogout() .then(function (data) { userAuthExpired(); - //done! - return null; + + if (data && data.signOutRedirectUrl) { + $window.location.replace(data.signOutRedirectUrl); + } + else { + //done! + return null; + } }); }, @@ -235,9 +245,9 @@ angular.module('umbraco.services') setCurrentUser(data); deferred.resolve(currentUser); - }, function () { + }, function (err) { //it failed, so they are not logged in - deferred.reject(); + deferred.reject(err); }); return deferred.promise; @@ -245,7 +255,7 @@ angular.module('umbraco.services') /** Returns the current user object in a promise */ getCurrentUser: function (args) { - + if (!currentUser) { return authResource.getCurrentUser() .then(function (data) { @@ -260,9 +270,9 @@ angular.module('umbraco.services') setCurrentUser(data); return $q.when(currentUser); - }, function () { + }, function (err) { //it failed, so they are not logged in - return $q.reject(currentUser); + return $q.reject(err); }); } diff --git a/src/Umbraco.Web.UI.Client/src/common/services/usershelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/usershelper.service.js index 1bda86c3b3..e6450798fb 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/usershelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/usershelper.service.js @@ -12,36 +12,23 @@ { "value": 4, "name": "Inactive", "key": "Inactive", "color": "warning" } ]; - localizationService.localizeMany(_.map(userStates, function (userState) { - return "user_state" + userState.key; - })).then(function (data) { - var reg = /^\[[\S\s]*]$/g; - _.each(data, function (value, index) { - if (!reg.test(value)) { - // Only translate if key exists - userStates[index].name = value; - } + localizationService.localizeMany(userStates.map(userState => "user_state" + userState.key)) + .then(data => { + var reg = /^\[[\S\s]*]$/g; + data.forEach((value, index) => { + if (!reg.test(value)) { + // Only translate if key exists + userStates[index].name = value; + } + }); }); - }); - function getUserStateFromValue(value) { - var foundUserState; - angular.forEach(userStates, function (userState) { - if(userState.value === value) { - foundUserState = userState; - } - }); - return foundUserState; + function getUserStateFromValue(value) { + return userStates.find(userState => userState.value === value); } function getUserStateByKey(key) { - var foundUserState; - angular.forEach(userStates, function (userState) { - if(userState.key === key) { - foundUserState = userState; - } - }); - return foundUserState; + return userStates.find(userState => userState.key === key); } function getUserStatesFilter(userStatesObject) { @@ -49,7 +36,7 @@ var userStatesFilter = []; for (var key in userStatesObject) { - if (userStatesObject.hasOwnProperty(key)) { + if (hasOwnProperty.call(userStatesObject, key)) { var userState = getUserStateByKey(key); if(userState) { userState.count = userStatesObject[key]; @@ -59,7 +46,6 @@ } return userStatesFilter; - } //////////// @@ -71,10 +57,7 @@ }; return service; - } angular.module('umbraco.services').factory('usersHelper', usersHelperService); - - })(); diff --git a/src/Umbraco.Web.UI.Client/src/common/services/util.service.js b/src/Umbraco.Web.UI.Client/src/common/services/util.service.js index 82353df744..1fb5884d4c 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/util.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/util.service.js @@ -173,9 +173,9 @@ function umbModelMapper() { /** This converts the source model to a basic entity model, it will throw an exception if there isn't enough data to create the model */ convertToEntityBasic: function (source) { var required = ["id", "name", "icon", "parentId", "path"]; - _.each(required, function (k) { - if (!_.has(source, k)) { - throw "The source object does not contain the property " + k; + required.forEach(k => { + if (!hasOwnProperty.call(source, k)) { + throw `The source object does not contain the property ${k}`; } }); var optional = ["metaData", "key", "alias"]; diff --git a/src/Umbraco.Web.UI.Client/src/install.loader.js b/src/Umbraco.Web.UI.Client/src/install.loader.js index 997c8cbe84..8c17b8cd64 100644 --- a/src/Umbraco.Web.UI.Client/src/install.loader.js +++ b/src/Umbraco.Web.UI.Client/src/install.loader.js @@ -1,6 +1,6 @@ LazyLoad.js([ 'lib/jquery/jquery.min.js', - + 'lib/angular/angular.js', 'lib/angular-cookies/angular-cookies.js', 'lib/angular-touch/angular-touch.js', @@ -8,10 +8,14 @@ LazyLoad.js([ 'lib/angular-messages/angular-messages.js', 'lib/angular-aria/angular-aria.min.js', 'lib/underscore/underscore-min.js', - 'lib/angular-ui-sortable/sortable.js', + 'lib/angular-ui-sortable/sortable.js', + + 'js/utilities.js', + 'js/installer.app.js', 'js/umbraco.directives.js', 'js/umbraco.installer.js' + ], function () { jQuery(document).ready(function () { angular.bootstrap(document, ['umbraco']); diff --git a/src/Umbraco.Web.UI.Client/src/installer/installer.service.js b/src/Umbraco.Web.UI.Client/src/installer/installer.service.js index 05c391c3e7..74858d652e 100644 --- a/src/Umbraco.Web.UI.Client/src/installer/installer.service.js +++ b/src/Umbraco.Web.UI.Client/src/installer/installer.service.js @@ -85,12 +85,12 @@ angular.module("umbraco.install").factory('installerService', function ($rootSco /** Have put this here because we are not referencing our other modules */ function safeApply (scope, fn) { if (scope.$$phase || scope.$root.$$phase) { - if (angular.isFunction(fn)) { + if (Utilities.isFunction(fn)) { fn(); } } else { - if (angular.isFunction(fn)) { + if (Utilities.isFunction(fn)) { scope.$apply(fn); } else { diff --git a/src/Umbraco.Web.UI.Client/src/installer/steps/database.controller.js b/src/Umbraco.Web.UI.Client/src/installer/steps/database.controller.js index 687fce95ae..cbd2bb9242 100644 --- a/src/Umbraco.Web.UI.Client/src/installer/steps/database.controller.js +++ b/src/Umbraco.Web.UI.Client/src/installer/steps/database.controller.js @@ -10,7 +10,7 @@ angular.module("umbraco.install").controller("Umbraco.Installer.DataBaseControll { name: 'Custom connection string', id: -1 } ]; - if (angular.isUndefined(installerService.status.current.model.dbType) || installerService.status.current.model.dbType === null) { + if (Utilities.isUndefined(installerService.status.current.model.dbType) || installerService.status.current.model.dbType === null) { installerService.status.current.model.dbType = 0; } diff --git a/src/Umbraco.Web.UI.Client/src/installer/steps/database.html b/src/Umbraco.Web.UI.Client/src/installer/steps/database.html index fc870858cf..ebffc4cf97 100644 --- a/src/Umbraco.Web.UI.Client/src/installer/steps/database.html +++ b/src/Umbraco.Web.UI.Client/src/installer/steps/database.html @@ -1,118 +1,121 @@
-

Configure your database

-

- Enter connection and authentication details for the database you want to install Umbraco on -

+

Configure your database

+

+ Enter connection and authentication details for the database you want to install Umbraco on +

-
-
- What type of database do you use? - -
- -
-
+ +
+ What type of database do you use? + +
+ +
+
-
+

Great! No need to configure anything, you can simply click the continue button below to continue to the next step

-
+
-
- What is the exact connection string we should use? -
- -
- - - Enter a valid database connection string. -
-
-
- -
-
- Where do we find your database? -
-
- -
- - Enter server domain or IP -
-
-
- -
-
- -
- - Enter the name of the database -
-
-
-
- -
- What credentials are used to access the database? -
-
- -
- - Enter the database user name -
-
-
- -
-
- -
- - Enter the database password -
-
-
- -
-
- -
-
-
-
- -
-
-
- - - - - - Validating your database connection... - - - - Could not connect to database - +
+ What is the exact connection string we should use? +
+ +
+ + Enter a valid database connection string.
-
-
+
+
+
+
+ Where do we find your database? +
+
+ +
+ + Enter server domain or IP +
+
+
- +
+
+ +
+ + Enter the name of the database +
+
+
+
+ +
+ What credentials are used to access the database? +
+
+ +
+ + Enter the database user name +
+
+
+ +
+
+ +
+ + Enter the database password +
+
+
+ +
+
+ +
+
+
+
+ +
+
+
+ + + + + + Validating your database connection... + + + + Could not connect to database + +
+
+
+
diff --git a/src/Umbraco.Web.UI.Client/src/installer/steps/starterkit.html b/src/Umbraco.Web.UI.Client/src/installer/steps/starterkit.html index 92f5cc8d9d..d7c21dc4a9 100644 --- a/src/Umbraco.Web.UI.Client/src/installer/steps/starterkit.html +++ b/src/Umbraco.Web.UI.Client/src/installer/steps/starterkit.html @@ -10,13 +10,13 @@ diff --git a/src/Umbraco.Web.UI.Client/src/less/alerts.less b/src/Umbraco.Web.UI.Client/src/less/alerts.less index 3907b59f58..3539e21064 100644 --- a/src/Umbraco.Web.UI.Client/src/less/alerts.less +++ b/src/Umbraco.Web.UI.Client/src/less/alerts.less @@ -7,6 +7,7 @@ // ------------------------- .alert { + position: relative; padding: 8px 35px 8px 14px; margin-bottom: @baseLineHeight; background-color: @warningBackground; @@ -98,3 +99,29 @@ .alert-block p + p { margin-top: 5px; } + + +// Property error alerts +// ------------------------- +.alert.property-error { + + display: inline-block; + font-size: 14px; + padding: 6px 16px 6px 12px; + margin-bottom: 6px; + + &::after { + content:''; + position: absolute; + bottom:-6px; + left: 6px; + width: 0; + height: 0; + border-left: 6px solid transparent; + border-right: 6px solid transparent; + border-top: 6px solid; + } + &.alert-error::after { + border-top-color: @errorBackground; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/less/application/grid.less b/src/Umbraco.Web.UI.Client/src/less/application/grid.less index 9e91da4792..68160923c8 100644 --- a/src/Umbraco.Web.UI.Client/src/less/application/grid.less +++ b/src/Umbraco.Web.UI.Client/src/less/application/grid.less @@ -171,13 +171,13 @@ body.umb-drawer-is-visible #mainwrapper{ } @media (min-width: 1101px) { - #contentwrapper, #speechbubble {left: 360px;} - .emptySection #contentwrapper {left:0px;} + #contentwrapper, #speechbubble { left: 360px; } + .emptySection #contentwrapper { left: 0 !important; } } //empty section modification -.emptySection #speechbubble {left: 0;} -.emptySection #navigation {display: none} +.emptySection #speechbubble { left: 0; } +.emptySection #navigation { display: none } .login-only #speechbubble { z-index: 10000; diff --git a/src/Umbraco.Web.UI.Client/src/less/belle.less b/src/Umbraco.Web.UI.Client/src/less/belle.less index f0d7c6f1e1..359c3dd427 100644 --- a/src/Umbraco.Web.UI.Client/src/less/belle.less +++ b/src/Umbraco.Web.UI.Client/src/less/belle.less @@ -77,7 +77,6 @@ @import "main.less"; @import "listview.less"; @import "gridview.less"; -@import "filter-toggle.less"; @import "forms/umb-validation-label.less"; @@ -144,9 +143,11 @@ @import "components/umb-property-editor.less"; @import "components/umb-property-actions.less"; @import "components/umb-code-snippet.less"; +@import "components/umb-color-picker.less"; @import "components/umb-color-swatches.less"; @import "components/check-circle.less"; @import "components/umb-file-icon.less"; +@import "components/umb-icon.less"; @import "components/umb-iconpicker.less"; @import "components/umb-insert-code-box.less"; @import "components/umb-packages.less"; @@ -172,13 +173,17 @@ @import "components/umb-textstring.less"; @import "components/umb-textarea.less"; @import "components/umb-dropdown.less"; +@import "components/umb-filter.less"; @import "components/umb-range-slider.less"; @import "components/umb-number.less"; +@import "components/umb-tags-editor.less"; +@import "components/umb-search-filter.less"; @import "components/buttons/umb-button.less"; @import "components/buttons/umb-button-group.less"; @import "components/buttons/umb-toggle.less"; @import "components/buttons/umb-toggle-group.less"; +@import "components/buttons/umb-button-ellipsis.less"; @import "components/notifications/umb-notifications.less"; @import "components/umb-file-dropzone.less"; @@ -199,6 +204,19 @@ @import "components/umbemailmarketing.less"; +// Property Editors +@import "../views/components/blockcard/umb-block-card-grid.less"; +@import "../views/components/blockcard/umb-block-card.less"; +@import "../views/components/umb-property-info-button/umb-property-info-button.less"; +@import "../views/propertyeditors/blocklist/umb-block-list-property-editor.less"; +@import "../views/propertyeditors/blocklist/prevalue/blocklist.blockconfiguration.less"; +@import "../views/propertyeditors/blocklist/prevalue/blocklist.blockconfiguration.overlay.less"; +@import "../views/propertyeditors/notsupported/notsupported.less"; +@import "../views/propertyeditors/blocklist/blocklistentryeditors/unsupportedblock/unsupportedblock.editor.less"; +@import "../views/propertyeditors/blocklist/blocklistentryeditors/labelblock/labelblock.editor.less"; +@import "../views/propertyeditors/blocklist/blocklistentryeditors/inlineblock/inlineblock.editor.less"; + + // Utilities @import "utilities/layout/_display.less"; @import "utilities/theme/_opacity.less"; @@ -221,6 +239,7 @@ // Used for prevalue editors @import "components/prevalues/multivalues.less"; +@import "../views/prevalueeditors/numberrange.less"; // Dashboards @import "dashboards/getstarted.less"; diff --git a/src/Umbraco.Web.UI.Client/src/less/buttons.less b/src/Umbraco.Web.UI.Client/src/less/buttons.less index 2b50b60ae8..c446a02424 100644 --- a/src/Umbraco.Web.UI.Client/src/less/buttons.less +++ b/src/Umbraco.Web.UI.Client/src/less/buttons.less @@ -336,20 +336,17 @@ input[type="submit"].btn { // This is lifted from umb-group-builder.less .btn-icon { - border: none; - + border: none; font-size: 18px; position: relative; cursor: pointer; color: @ui-icon; - margin: 0; - padding: 5px 10px; + padding: 3px 5px; width: auto; overflow: visible; background: transparent; line-height: normal; - outline: 0; -webkit-appearance: none; &:hover { diff --git a/src/Umbraco.Web.UI.Client/src/less/canvas-designer.less b/src/Umbraco.Web.UI.Client/src/less/canvas-designer.less index 7135692ae8..24d03d01a1 100644 --- a/src/Umbraco.Web.UI.Client/src/less/canvas-designer.less +++ b/src/Umbraco.Web.UI.Client/src/less/canvas-designer.less @@ -192,6 +192,7 @@ body { #demo-iframe-wrapper { transition: all 240ms cubic-bezier(0.165, 0.84, 0.44, 1); + flex-shrink:0; } .fullsize { diff --git a/src/Umbraco.Web.UI.Client/src/less/colors.less b/src/Umbraco.Web.UI.Client/src/less/colors.less index 4cb70cdf5e..ba4909c997 100644 --- a/src/Umbraco.Web.UI.Client/src/less/colors.less +++ b/src/Umbraco.Web.UI.Client/src/less/colors.less @@ -24,7 +24,7 @@ /* Colors based on https://zavoloklom.github.io/material-design-color-palette/colors.html */ .btn-color-black {background-color: @black;} -.color-black i { color: @black;} +.color-black, .color-black i { color: @black !important;} .btn-color-blue-grey {background-color: @blueGrey;} .color-blue-grey, .color-blue-grey i { color: @blueGrey !important;} @@ -78,4 +78,4 @@ .color-deep-purple, .color-deep-purple i { color: @deepPurpleIcon !important; } .btn-color-indigo{background-color: @indigoIcon;} -.color-indigo, .color-indigo i { color: @indigoIcon !important; } \ No newline at end of file +.color-indigo, .color-indigo i { color: @indigoIcon !important; } diff --git a/src/Umbraco.Web.UI.Client/src/less/components/application/umb-language-picker.less b/src/Umbraco.Web.UI.Client/src/less/components/application/umb-language-picker.less index 4e3741905f..34a339b4c9 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/application/umb-language-picker.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/application/umb-language-picker.less @@ -24,6 +24,7 @@ .umb-language-picker__expand { font-size: 14px; + pointer-events: none; } .umb-language-picker__toggle:hover { diff --git a/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-button-ellipsis.less b/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-button-ellipsis.less new file mode 100644 index 0000000000..7104e6478f --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-button-ellipsis.less @@ -0,0 +1,81 @@ +.umb-button-ellipsis{ + padding: 0 5px; + text-align: center; + margin: 0 auto; + cursor: pointer; + border-radius: @baseBorderRadius; + color: @ui-action-discreet-type; + position: relative; + opacity: 0.8; + transition: opacity 120ms, color 120ms; + + &--absolute { + position: absolute; + } + + &--small { + height: 15px; + } + + &.show-text { + display: flex; + flex-wrap: wrap; + justify-content: center; + } + + &:hover { + color: @ui-action-discreet-type-hover; + } + + .umb-button-ellipsis--tab, + .umb-tour-is-visible .umb-tree &, + &:hover, + &:focus{ + opacity: 1; + } + + &--hidden{ + opacity: 0; + + &:hover, + &:focus { + opacity: 1; + } + } + + &__content { + display: flex; + flex-wrap: wrap; + } + + &__icon { + color: inherit; + flex-basis: 100%; + font-size: 12px; + + .umb-button-ellipsis--tab & { + margin: 0 0 7px; + } + + .umb-button-ellipsis--small & { + font-size: 8px; + position: relative; + top: -2px; + } + } + + &__text { + color: inherit; + font-size: 12px; + line-height: 1em; + flex-basis: 100%; + + .umb-button-ellipsis--tab & { + position: absolute; + right: 0; + left: 0; + bottom: 13px; + margin: 0 auto; + } + } +} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-button-group.less b/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-button-group.less index 02b67460f6..24800c1142 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-button-group.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-button-group.less @@ -14,7 +14,7 @@ display: flex; } -.umb-button-group { +.umb-button-group.-with-button-group-toggle { .umb-button__button { border-radius: @baseBorderRadius 0 0 @baseBorderRadius; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-button.less b/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-button.less index 4127c2201c..b6800aba65 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-button.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-button.less @@ -31,6 +31,11 @@ margin-left: 5px; } +.umb-button__button[disabled] .umb-button__caret { + border-top-color: @gray-7; + border-bottom-color: @gray-7; +} + .umb-button__progress { position: absolute; left: 50%; @@ -58,6 +63,11 @@ border-left-color: @white; } +.umb-button__progress.-black { + border-color: rgba(255, 255, 255, 0.4); + border-left-color: @black; +} + .umb-button__success, .umb-button__error { position: absolute; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-toggle-group.less b/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-toggle-group.less index df60c8aed1..83dcc258a4 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-toggle-group.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-toggle-group.less @@ -20,6 +20,12 @@ justify-content: center; flex: 1 1 auto; cursor: pointer; + + label { + padding: unset; + margin: unset; + pointer-events: none; + } } .umb-toggle-group-item--disabled .umb-toggle-group-item__toggle, diff --git a/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-toggle.less b/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-toggle.less index ff4122b258..701af849cc 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-toggle.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-toggle.less @@ -41,9 +41,6 @@ } } - - - .umb-toggle__handler { position: absolute; top: 1px; @@ -59,10 +56,8 @@ transform: translateX(20px); background-color: @white; } - } - /* Icons */ .umb-toggle__icon { @@ -78,9 +73,7 @@ color:@white; transition: opacity 120ms; opacity: 0; - // .umb-toggle:hover & { - // color: @ui-btn-hover; - // } + .umb-toggle--checked & { opacity: 1; } @@ -93,6 +86,7 @@ right: 5px; color: @ui-btn; transition: opacity 120ms; + .umb-toggle--checked & { opacity: 0; } @@ -101,23 +95,41 @@ } } - - -.umb-toggle.umb-toggle--disabled.umb-toggle--checked, .umb-toggle.umb-toggle--disabled { .umb-toggle__toggle { cursor: not-allowed; - background-color: @gray-9; - border-color: @gray-9; } - .umb-toggle__icon--left { - color: @gray-6; + + &, &.umb-toggle--checked { + + .umb-toggle__toggle { + .umb-toggle__handler { + background-color: @gray-10; + } + } } - .umb-toggle__icon--right { - color: @gray-6; + + &:not(.umb-toggle--checked) { + .umb-toggle__toggle { + background-color: @gray-8; + border-color: @gray-8; + } + + .umb-toggle__icon--left, + .umb-toggle__icon--right { + color: @gray-6; + } } - .umb-toggle__handler { - background-color: @gray-10; + + &.umb-toggle--checked { + .umb-toggle__toggle { + background-color: lighten(@ui-btn, 50%); + border-color: lighten(@ui-btn, 50%); + } + .umb-toggle__icon--left, + .umb-toggle__icon--right { + color: @gray-9; + } } } diff --git a/src/Umbraco.Web.UI.Client/src/less/components/card.less b/src/Umbraco.Web.UI.Client/src/less/components/card.less index 68854e3c53..7174c0f290 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/card.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/card.less @@ -104,19 +104,18 @@ margin: 0 auto; list-style: none; width: 100%; - display: flex; flex-flow: row wrap; justify-content: flex-start; } .umb-card-grid li { - overflow: hidden; font-size: 12px; text-align: center; box-sizing: border-box; position: relative; width: 100px; + margin-bottom: 5px; } .umb-card-grid.-six-in-row li { @@ -142,32 +141,47 @@ .umb-card-grid .umb-card-grid-item { position: relative; display: block; - width: 100%; - //height: 100%; - padding-top: 100%; + width: 100%; + height: 100%; + padding: 10px 5px; border-radius: @baseBorderRadius * 2; transition: background-color 120ms; + font-size: 13px; + line-height: 1.3em; + display: flex; + flex-direction: column; + align-items: center; + justify-content: flex-start; > span { - position: absolute; - top: 10px; - bottom: 10px; - left: 10px; - right: 10px; + position: relative; display: flex; align-items: center; justify-content: center; flex-direction: column; background-color: transparent; + word-break: break-word; } } -.umb-card-grid .umb-card-grid-item:hover, -.umb-card-grid .umb-card-grid-item:focus { +.umb-card-grid .umb-card-grid-item:hover { background-color: @ui-option-hover; color: @ui-option-type-hover; } +.umb-card-grid .umb-card-grid-item:focus { + color: @ui-option-type-hover; +} + +.umb-card-grid .umb-card-grid-item.--creator { + > span { + border: 2px dashed @ui-action-discreet-border; + border-radius: @baseBorderRadius; + &:hover { + border-color: @ui-action-discreet-border-hover; + } + } +} .umb-card-grid .umb-card-grid-item-slot { position: relative; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/editor.less b/src/Umbraco.Web.UI.Client/src/less/components/editor.less index ac55c6ffb1..d4069cdd2b 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/editor.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/editor.less @@ -73,6 +73,11 @@ height: @editorHeaderHeight; } +.umb-editor-header .umb-button__button[disabled] { + // do not dim down the background color of disabled buttons in the header + background-color: unset; +} + .umb-editor-header__back { background: transparent; border: 0; @@ -135,22 +140,19 @@ input.umb-editor-header__name-input:disabled { margin-left: auto; } -a.umb-editor-header__close-split-view { - +.umb-editor-header__close-split-view { display: flex; justify-content: center; align-items: center; position: relative; height: 69px; width: 69px; - - font-size: 20px; - color: @gray-6; - text-decoration: none !important; -} + font-size: 20px; + color: @gray-6; -a.umb-editor-header__close-split-view:hover { - color: @black; + &:hover { + color: @black; + } } .umb-editor-header { @@ -162,9 +164,7 @@ a.umb-editor-header__close-split-view:hover { } } - // container - .umb-editor-container { position: absolute; top: @editorHeaderHeight; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/editor/umb-editor.less b/src/Umbraco.Web.UI.Client/src/less/components/editor/umb-editor.less index e81df77772..19d6a1306e 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/editor/umb-editor.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/editor/umb-editor.less @@ -12,6 +12,7 @@ .absolute(); background: @brownGrayLight; z-index: @zIndexEditor; + max-width: 100%; &--infiniteMode { transform: none; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/editor/umb-variant-switcher.less b/src/Umbraco.Web.UI.Client/src/less/components/editor/umb-variant-switcher.less index 8dbc070856..95625d9e73 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/editor/umb-variant-switcher.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/editor/umb-variant-switcher.less @@ -50,6 +50,19 @@ button.umb-variant-switcher__toggle { font-weight: bold; background-color: @errorBackground; color: @errorText; + + animation-duration: 1.4s; + animation-iteration-count: infinite; + animation-name: umb-variant-switcher__toggle--badge-bounce; + animation-timing-function: ease; + @keyframes umb-variant-switcher__toggle--badge-bounce { + 0% { transform: translateY(0); } + 20% { transform: translateY(-6px); } + 40% { transform: translateY(0); } + 55% { transform: translateY(-3px); } + 70% { transform: translateY(0); } + 100% { transform: translateY(0); } + } } } } @@ -77,6 +90,7 @@ button.umb-variant-switcher__toggle { align-items: center; border-bottom: 1px solid @gray-9; position: relative; + .umb-variant-switcher__name-wrapper:hover { .umb-variant-switcher__name { color: @blueMid; @@ -86,6 +100,11 @@ button.umb-variant-switcher__toggle { } } } + +.umb-variant-switcher__item.--not-allowed:not(.--current) .umb-variant-switcher__name-wrapper:hover { + cursor: default; +} + .umb-variant-switcher__item.--state-notCreated:not(.--active) { .umb-variant-switcher__name-wrapper::before { content: "+"; @@ -93,38 +112,44 @@ button.umb-variant-switcher__toggle { float: left; font-size: 15px; font-weight: 900; - padding: 8px 16px 8px 6px; + padding: 8px 12px 8px 0; color: @gray-5; } + .umb-variant-switcher__item-expand-button + .umb-variant-switcher__name-wrapper::before { padding: 8px 16px 8px 20px; } + .umb-variant-switcher__name { color: @gray-5; } + .umb-variant-switcher__state { color: @gray-6; } + .umb-variant-switcher__name-wrapper::after { content: ""; position: absolute; z-index: 1; - border: 1px dashed @gray-9; - top: 7px; - bottom: 7px; - left: 7px; - right: 7px; - border-radius: 3px; + border: 2px dashed @gray-9; + margin: 2px; + top: 0; + bottom: 0; + left: 0; + right: 0; pointer-events: none; } - + .umb-variant-switcher__name-wrapper:hover { &::before { color: @blueMid; } + .umb-variant-switcher__name { color: @blueMid; } + .umb-variant-switcher__state { color: @blueMid; } @@ -170,8 +195,11 @@ button.umb-variant-switcher__toggle { } .umb-variant-switcher__item.--current { - //color: @ui-light-active-type; + color: @ui-light-active-type; //background-color: @pinkExtraLight; + .umb-variant-switcher__name-wrapper { + border-left: 4px solid @ui-active; + } .umb-variant-switcher__name { //color: @ui-light-active-type; font-weight: 700; @@ -203,7 +231,7 @@ button.umb-variant-switcher__toggle { .umb-variant-switcher__item:focus-within .umb-variant-switcher__split-view, .umb-variant-switcher__item:hover .umb-variant-switcher__split-view, .umb-variant-switcher__split-view:focus { - display: block; + display: flex; cursor: pointer; } @@ -226,6 +254,19 @@ button.umb-variant-switcher__toggle { font-weight: bold; background-color: @errorBackground; color: @errorText; + + animation-duration: 1.4s; + animation-iteration-count: infinite; + animation-name: umb-variant-switcher__name--badge-bounce; + animation-timing-function: ease; + @keyframes umb-variant-switcher__name--badge-bounce { + 0% { transform: translateY(0); } + 20% { transform: translateY(-6px); } + 40% { transform: translateY(0); } + 55% { transform: translateY(-3px); } + 70% { transform: translateY(0); } + 100% { transform: translateY(0); } + } } } } @@ -237,7 +278,10 @@ button.umb-variant-switcher__toggle { cursor: pointer; background-color: transparent; border: none; + padding-top: 6px !important; + padding-bottom: 6px !important; } + .dropdown-menu>li { > .umb-variant-switcher__name-wrapper { padding-top: 10px; @@ -264,7 +308,7 @@ button.umb-variant-switcher__toggle { .umb-variant-switcher__split-view { font-size: 12px; display: none; - padding: 20px 20px; + padding: 16px 20px; position: absolute; right: 0; top: 0; @@ -277,7 +321,6 @@ button.umb-variant-switcher__toggle { } } - .umb-variant-switcher__sub-variants { position: relative; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/html/umb-expansion-panel.less b/src/Umbraco.Web.UI.Client/src/less/components/html/umb-expansion-panel.less index 2a8137e5f9..ea08794c23 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/html/umb-expansion-panel.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/html/umb-expansion-panel.less @@ -10,11 +10,12 @@ font-weight: bold; display: flex; align-items: center; - cursor: pointer; justify-content: space-between; color: @black; + width: 100%; - &:hover .umb-expansion-panel__expand { + &:hover .umb-expansion-panel__expand, + &:focus .umb-expansion-panel__expand { color: @gray-6; } } diff --git a/src/Umbraco.Web.UI.Client/src/less/components/html/umb-group-panel.less b/src/Umbraco.Web.UI.Client/src/less/components/html/umb-group-panel.less index 15f85e1c77..76c0c55fca 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/html/umb-group-panel.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/html/umb-group-panel.less @@ -8,11 +8,11 @@ .umb-group-panel__header { padding: 12px 20px; font-weight: bold; - font-size: 16px; + font-size: 14px; display: flex; align-items: center; justify-content: space-between; - color: @grayDark; + color: @grayDarker; border-bottom: 1px solid @gray-9; } diff --git a/src/Umbraco.Web.UI.Client/src/less/components/overlays.less b/src/Umbraco.Web.UI.Client/src/less/components/overlays.less index 381157c8bc..035bf02f91 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/overlays.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/overlays.less @@ -106,7 +106,6 @@ height: auto; top: 50%; left: 50%; - transform: translate(-50%, 0); transform: translate(-50%, -50%); border-radius: @baseBorderRadius; } @@ -128,10 +127,15 @@ width: 400px; max-height: 100vh; box-sizing: border-box; + visibility: hidden; border-radius: @baseBorderRadius; + &.umb-overlay--small { + width: 420px; + } + &.umb-overlay--medium { - width: 480px; + width: 520px; } } diff --git a/src/Umbraco.Web.UI.Client/src/less/components/prevalues/multivalues.less b/src/Umbraco.Web.UI.Client/src/less/components/prevalues/multivalues.less index 7036d60a63..6cdc5b1c99 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/prevalues/multivalues.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/prevalues/multivalues.less @@ -6,7 +6,7 @@ width: 500px; } - p{ + p { margin: 7px 0; } } @@ -23,21 +23,18 @@ align-items: center; } -.umb-prevalues-multivalues__add { +.umb-prevalues-multivalues__add { display: flex; -} -.umb-prevalues-multivalues__add input { - width: 320px; -} + input { + display: flex; + width: 320px; + } -.umb-prevalues-multivalues__add input { - display: flex; -} - -.umb-prevalues-multivalues__add button { - margin: 0 6px 0 0; - margin-left: auto; + button { + margin: 0 6px 0 0; + margin-left: auto; + } } .umb-prevalues-multivalues__listitem { @@ -45,20 +42,26 @@ padding: 6px; margin: 10px 0px !important; background: @gray-10; - cursor: move; -} -.umb-prevalues-multivalues__listitem i { - display: flex; - align-items: center; - margin-right: 5px -} + &.ui-sortable-handle, + .ui-sortable-handle, + .handle + { + cursor: move; + } -.umb-prevalues-multivalues__listitem a { - cursor: pointer; - margin-left: auto; -} + i { + display: flex; + align-items: center; + margin-right: 5px + } -.umb-prevalues-multivalues__listitem input { - width: 295px; + a { + cursor: pointer; + margin-left: auto; + } + + input { + width: 295px; + } } diff --git a/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-tree-item.less b/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-tree-item.less index 4a483ce3f0..97caf66497 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-tree-item.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-tree-item.less @@ -17,8 +17,9 @@ .umb-tree-item__arrow { position: relative; margin-left: -16px; - width: 16px; - height: 16px; + margin-right: 4px; + width: 12px; + height: 12px; visibility: hidden; text-decoration: none; font-size: 12px; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-tree-root.less b/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-tree-root.less index a3529d5504..ba33883b08 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-tree-root.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-tree-root.less @@ -1,5 +1,7 @@ -.umb-tree-root { +.umb-tree-root { + border: 2px solid transparent; + &-link { display: flex; align-items: center; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-tree.less b/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-tree.less index 839e61c5f9..a39a38fbde 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-tree.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-tree.less @@ -15,6 +15,16 @@ text-decoration: none; } + .umb-tree-item__arrow { + visibility: hidden; + text-decoration: none; + font-size: 12px; + transition: color 120ms; + + &:hover { + color: @ui-option-type-hover; + } + } i.noSpr { display: inline-block; margin-top: 1px; @@ -38,6 +48,8 @@ color: @gray-7; display: block; padding-left: 35px; + white-space: initial; + text-align: left; } } @@ -75,8 +87,8 @@ body.touch .umb-tree { &:hover { background: @ui-option-hover; - color: @ui-option-type-hover; + a { color: @ui-option-type-hover; } @@ -85,12 +97,14 @@ body.touch .umb-tree { position: relative; width: auto; height: auto; - margin: 0 10px 0 auto; - padding: 9px 5px; overflow: visible; clip: auto; } - + + .umb-button-ellipsis--hidden { + opacity: 1; + } + .umb-tree-icon { color: @ui-option-type-hover; } @@ -119,7 +133,6 @@ body.touch .umb-tree { .umb-tree .umb-search-group { position: inherit; display: inherit; - list-style: none; h6 { @@ -142,6 +155,18 @@ body.touch .umb-tree { &-link { display: block; + width: 100%; + text-align: left; + } + + &-name { + display: flex; + + &__text { + margin: 1px 0 0; + overflow:hidden; + text-overflow: ellipsis; + } } } @@ -156,7 +181,9 @@ body.touch .umb-tree { .umb-tree .umb-tree-node-checked > .umb-tree-item__inner > i[class^="icon-"], .umb-tree .umb-tree-node-checked > .umb-tree-item__inner > i[class*=" icon-"], .umb-tree .umb-tree-node-checked .umb-search-group-item-name > i[class^="icon-"], -.umb-tree .umb-tree-node-checked .umb-search-group-item-name > i[class*=" icon-"] { +.umb-tree .umb-tree-node-checked .umb-search-group-item-name > i[class*=" icon-"], +.umb-tree .umb-tree-node-checked > i[class^="icon-"], +.umb-tree .umb-tree-node-checked > i[class*="icon-"] { font-family: 'icomoon' !important; color: @green !important; @@ -171,11 +198,15 @@ body.touch .umb-tree { display: flex; flex: 0 0 auto; justify-content: flex-end; - padding: 9px 5px; text-align: center; margin: 0 10px 0 auto; cursor: pointer; border-radius: @baseBorderRadius; + transition: background-color 120ms; + + .umb-button-ellipsis { + padding: 3px 5px; + } i { height: 5px !important; @@ -184,18 +215,20 @@ body.touch .umb-tree { display: inline-block; margin: 0 2px 0 0; background: @ui-active-type; - + &:last-child { margin: 0; } } + &:hover { - background: rgba(255, 255, 255, .5); + background-color: rgba(255, 255, 255, .8); + i { background: @ui-active-type-hover; } } - + // NOTE - We're having to repeat ourselves here due to an .sr-only class appearing in umbraco/lib/font-awesome/css/font-awesome.min.css &.sr-only--hoverable:hover, &.sr-only--focusable:focus { @@ -203,9 +236,9 @@ body.touch .umb-tree { display: flex; flex: 0 0 auto; justify-content: flex-end; - padding: 9px 5px; + padding: 7px 5px; text-align: center; - margin: 0 10px 0 auto; + margin: 0 auto; cursor: pointer; border-radius: 3px; } @@ -229,6 +262,7 @@ body.touch .umb-tree { > .umb-tree-item__inner > i.icon, > .umb-tree-item__inner > a { cursor: not-allowed; + opacity: 0.4; } } @@ -306,9 +340,13 @@ body.touch .umb-tree { .umb-tree-icon { vertical-align: middle; margin: 0 13px 0 0; - //color: @gray-1; color: @ui-option-type; - font-size: 20px; + font-size: 20px; + + &.-hidden { + display: none; + visibility: hidden; + } &.blue { color: @blue; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-badge.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-badge.less index 9a18bef2de..18294f9e59 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-badge.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-badge.less @@ -8,35 +8,88 @@ border-radius: 100px; } +.umb-badge__count { + display: flex; + width: 1rem; + height: 1rem; + line-height: 1; + justify-content: center; + align-items: center; + border-radius: 50%; + font-size: 12px; +} + // Colors .umb-badge--primary { background-color: @blueDark; color: @white; + + .umb-badge__count { + background-color: darken(@blueDark, 5%); + } } .umb-badge--secondary { background-color: @blueExtraDark; color: @white; + + .umb-badge__count { + background-color: darken(@blueExtraDark, 8%); + } } .umb-badge--gray { background-color: @sand-2; color: @blueDark; + + .umb-badge__count { + background-color: darken(@sand-2, 8%); + } } .umb-badge--danger { background-color: @red; color: @white; + + .umb-badge__count { + background-color: darken(@red, 8%); + } +} + +.umb-badge--info { + background-color: @blue; + color: @white; + + .umb-badge__count { + background-color: darken(@blue, 8%); + } } .umb-badge--warning { background-color: @orange; color: @white; + + .umb-badge__count { + background-color: darken(@orange, 8%); + } } .umb-badge--success { background-color: @green; color: @white; + + .umb-badge__count { + background-color: darken(@green, 8%); + } +} + +.umb-badge--dark { + background-color: @grayDark; + color: @white; + + .umb-badge__count { + background-color: darken(@grayDark, 8%); + } } // Size @@ -46,7 +99,8 @@ } .umb-badge--xs { - font-size: 13px; + font-size: 12px; + font-weight: 600; padding: 1px 10px; } diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-checkbox-list.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-checkbox-list.less index 11194eeb43..c9f47a66df 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-checkbox-list.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-checkbox-list.less @@ -37,6 +37,7 @@ align-items: center; flex: 0 0 30px; margin-right: 5px; + position: relative; } .umb-checkbox-list__item-icon { @@ -44,6 +45,17 @@ font-size: 16px; } +.umb-checkbox-list__item-icon-wrapper { + position: relative; + + .umb-button__progress { + width: 10px; + height: 10px; + margin-left: -10px; + margin-top: -8px; + } +} + .umb-checkbox-list__item-text { font-size: 14px; margin-bottom: 0; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-checkmark.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-checkmark.less index f82e47bf88..6ab6169eec 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-checkmark.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-checkmark.less @@ -17,10 +17,21 @@ } } +.umb-checkmark__action { + &:hover, + &:focus { + .umb-checkmark { + border-color:@ui-action-discreet-border-hover; + color: @ui-selected-type-hover; + } + } +} + .umb-checkmark--checked { background: @ui-selected-border; border-color: @ui-selected-border; color: @white; + &:hover { background: @ui-selected-border-hover; border-color: @ui-selected-border-hover; @@ -28,6 +39,17 @@ } } +.umb-checkmark__action { + &:hover, + &:focus { + .umb-checkmark--checked { + background: @ui-selected-border-hover; + border-color: @ui-selected-border-hover; + color: @white; + } + } +} + .umb-checkmark--xs { width: 20px; height: 20px; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-code-snippet.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-code-snippet.less index b372841910..96b4fc08c8 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-code-snippet.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-code-snippet.less @@ -18,6 +18,7 @@ justify-content: flex-start; flex-grow: 1; padding: 2px 10px; + text-transform: uppercase; } button { diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-color-picker.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-color-picker.less new file mode 100644 index 0000000000..21439efeca --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-color-picker.less @@ -0,0 +1,21 @@ +.umb-color-picker { + + .sp-replacer { + display: inline-flex; + margin-right: 18px; + height: 32px; + + &.sp-light { + background-color: @white; + } + + .sp-preview { + margin: 5px; + height: auto; + } + + .sp-dd { + line-height: 2rem; + } + } +} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-confirm-action.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-confirm-action.less index f972633bf4..112194f012 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-confirm-action.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-confirm-action.less @@ -13,19 +13,6 @@ left: 0; animation: fadeInUp 0.2s; flex-direction: column; - - .umb_confirm-action__overlay-action { - margin-bottom: 5px; - } - - .umb_confirm-action__overlay-action.-confirm { - order: 1; - } - - .umb_confirm-action__overlay-action.-cancel { - order: 2; - } - } .umb_confirm-action__overlay.-right { @@ -35,18 +22,6 @@ left: auto; animation: fadeInLeft 0.2s; flex-direction: row; - - .umb_confirm-action__overlay-action { - margin-left: 5px; - } - - .umb_confirm-action__overlay-action.-confirm { - order: 2; - } - - .umb_confirm-action__overlay-action.-cancel { - order: 1; - } } .umb_confirm-action__overlay.-bottom { @@ -56,18 +31,6 @@ left: 0; animation: fadeInDown 0.2s; flex-direction: column; - - .umb_confirm-action__overlay-action { - margin-top: 5px; - } - - .umb_confirm-action__overlay-action.-confirm { - order: 2; - } - - .umb_confirm-action__overlay-action.-cancel { - order: 1; - } } .umb_confirm-action__overlay.-left { @@ -77,55 +40,59 @@ left: -50px; animation: fadeInRight 0.2s; flex-direction: row; +} + +.umb_confirm-action__overlay { .umb_confirm-action__overlay-action { margin-right: 5px; + + &.-confirm { + order: 1; + } + + &.-cancel { + order: 2; + } } - .umb_confirm-action__overlay-action.-confirm { - order: 1; - } + // BUTTONS + .umb_confirm-action__overlay-action { + width: 20px; + height: 20px; + display: flex; + align-items: center; + justify-content: center; + color: @white; + border-radius: 40px; + box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.26); + font-size: 18px; + cursor: pointer; + user-select: none; - .umb_confirm-action__overlay-action.-cancel { - order: 2; + &:hover { + text-decoration: none; + color: @white; + } + + // confirm button + &.-confirm { + background: @white; + color: @green !important; + + &:hover { + color: @green !important; + } + } + + // cancel button + &.-cancel { + background: @white; + color: @red !important; + + &:hover { + color: @red !important; + } + } } } - -// BUTTONS -.umb_confirm-action__overlay-action { - width: 20px; - height: 20px; - display: flex; - align-items: center; - justify-content: center; - color: @white; - border-radius: 40px; - box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.26); - font-size: 18px; - cursor: pointer; -} - -.umb_confirm-action__overlay-action:hover { - text-decoration: none; - color: @white; -} - -// confirm button -.umb_confirm-action__overlay-action.-confirm { - background: @white; - color: @green !important; -} - -.umb_confirm-action__overlay-action.-confirm:hover { - color: @green !important; -} - -// cancel button -.umb_confirm-action__overlay-action.-cancel { - background: @white; - color: @red !important; -} - -.umb_confirm-action__overlay-action.-cancel:hover { - color: @red !important; -} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-date-time-picker.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-date-time-picker.less index 5bcfdd1c71..b8084bc435 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-date-time-picker.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-date-time-picker.less @@ -3,23 +3,28 @@ box-shadow: 0 5px 10px 0 rgba(0,0,0,0.16); } -span.flatpickr-day { +.flatpickr-day { border-radius: @baseBorderRadius; border: none; &.today:not(.active) { border: 1px solid; } -} -span.flatpickr-day:hover { - background-color: @gray-10; -} + &:hover { + background-color: @gray-10; + } -span.flatpickr-day.selected { - background-color: @ui-selected; -} + &.selected, &.startRange, &.endRange { + background-color: @ui-selected-type !important; + border-color: @ui-selected-type !important; -span.flatpickr-day.selected:hover { - background-color: @ui-selected-hover; + &:hover { + background-color: @ui-selected-type-hover !important; + } + + &.startRange + .endRange:not(:nth-child(7n+1)) { + box-shadow: -10px 0 0 @ui-selected-type !important; + } + } } diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-editor-navigation-item.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-editor-navigation-item.less index f3c41dbc33..5e9772fb26 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-editor-navigation-item.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-editor-navigation-item.less @@ -20,12 +20,18 @@ justify-content: center; height: calc(~'@{editorHeaderHeight}'- ~'1px'); // need to offset the 1px border-bottom on .umb-editor-header - avoids overflowing top of the container color: @ui-active-type; + user-select: none; &:hover { color: @ui-active-type-hover !important; text-decoration: none; } + &:disabled { + pointer-events: none; + color: @gray-6; + } + &::before { content: ""; position: absolute; @@ -40,13 +46,46 @@ } &.is-active { - color: @ui-light-active-type; + color: @ui-light-active-type !important; &::before { opacity: 1; height: 4px; } } + + // Validation + .show-validation &.-has-error { + color: @red; + + &:hover { + color: @red !important; + } + + &::before { + background-color: @red; + } + + &:not(.is-active) { + .badge { + animation-duration: 1.4s; + animation-iteration-count: infinite; + animation-name: umb-sub-views-nav-item--badge-bounce; + animation-timing-function: ease; + @keyframes umb-sub-views-nav-item--badge-bounce { + 0% { transform: translateY(0); } + 20% { transform: translateY(-6px); } + 40% { transform: translateY(0); } + 55% { transform: translateY(-3px); } + 70% { transform: translateY(0); } + 100% { transform: translateY(0); } + } + } + .badge.--error-badge { + display: block; + } + } + } } &__action:active, @@ -54,8 +93,8 @@ .box-shadow(~"inset 0 2px 4px rgba(0,0,0,.15), 0 1px 2px rgba(0,0,0,.05)"); } - &:focus-within &__anchor_dropdown, - &:hover &__anchor_dropdown { + & > &__anchor_dropdown.is-expanded, + &:hover > &__anchor_dropdown { visibility: visible; opacity: 1; transition: opacity 20ms 0; @@ -95,6 +134,10 @@ height: 12px; min-width: 12px; } + &.--error-badge { + display: none; + font-weight: 900; + } } &-text { @@ -176,13 +219,3 @@ } } } - -// Validation -.show-validation .umb-sub-views-nav-item__action.-has-error, -.show-validation .umb-sub-views-nav-item > a.-has-error { - color: @red; - - &::before { - background-color: @red; - } -} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-filter.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-filter.less new file mode 100644 index 0000000000..7432555472 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-filter.less @@ -0,0 +1,13 @@ +.umb-filter { + position: relative; +} + +.umb-filter .umb-filter__toggle { + display: flex; +} + +.umb-filter .umb-filter__label { + margin-left: 5px; + margin-right: 3px; + max-width: 150px; +} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-form-check.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-form-check.less index 8a24d948ac..a96c59de84 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-form-check.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-form-check.less @@ -1,25 +1,18 @@ @checkboxWidth: 18px; @checkboxHeight: 18px; -label.umb-form-check--checkbox{ - margin:3px 0; -} .umb-form-check { display: flex; + align-items: center; position: relative; - padding-left: 0px; - margin: 0; - min-height: 22px; + padding-left: 0; + margin: 5px 0; + min-height: 20px; cursor: pointer !important; - .umb-form-check__symbol { - margin-top: 4px; - margin-right: 10px; - } .umb-form-check__info { - margin-left:20px; + margin-left: 30px; position: relative; - top: 3px; } @@ -84,7 +77,6 @@ label.umb-form-check--checkbox{ } .tabbing-active &.umb-form-check--radiobutton &__input:focus ~ .umb-form-check__state .umb-form-check__check { - //outline: 2px solid @inputBorderTabFocus; border: 2px solid @inputBorderTabFocus; margin: -1px; } @@ -102,13 +94,14 @@ label.umb-form-check--checkbox{ &__state { display: flex; - height: 18px; + height: 20px; + width: 20px; position: absolute; - margin-top: 2px; top: 0; - left: -1px; + } + &__check { display: flex; position: relative; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-grid.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-grid.less index 26d61412ae..3b084c9905 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-grid.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-grid.less @@ -392,7 +392,7 @@ // EDITOR PLACEHOLDER // ------------------------- .umb-grid .umb-editor-placeholder { - min-height: 65px; + min-height: 110px; padding: 20px; padding-bottom: 30px; position: relative; @@ -400,7 +400,7 @@ border: 4px dashed @gray-8; text-align: center; text-align: -moz-center; - cursor: pointer; + width: 100%; } .umb-grid .umb-editor-placeholder i { @@ -413,6 +413,7 @@ .umb-grid .umb-editor-preview { position: relative; + width: 100%; .umb-editor-preview-overlay { cursor: pointer; @@ -474,9 +475,19 @@ } } +// Control states +.umb-grid-media--controls { + display:none; + position: absolute; + top:0.5rem; + right:0.5rem; +} - - +.umb-grid .umb-row .umb-control.-active { + .umb-grid-media--controls { + display:flex; + } +} // Title bar and tools .umb-grid .umb-row-title-bar { @@ -596,9 +607,10 @@ } .umb-grid .iconBox i { - font-size: 16px !important; - color: @gray-3 ; + color: @gray-3; display: block; + font-size: 16px; + line-height: inherit; } .umb-grid .help-text { @@ -609,8 +621,6 @@ clear: both; } - - // TINYMCE EDITOR // ------------------------- @@ -751,9 +761,19 @@ margin-right: 5px; position: relative; cursor: pointer; - display:inline-block; + display: inline-block; } +.umb-grid .cell-tools, +.umb-grid .umb-control-tool { + .btn-icon { + padding: 0; + } +} + +.umb-grid .umb-control-tool .btn-icon { + color: @white; +} // Template @@ -820,7 +840,6 @@ position: absolute; width: 100%; left: 0; - bottom: -25px; padding-top: 15px; } @@ -1007,8 +1026,6 @@ display: block; } - - // Configuration specific styles // ------------------------- .umb-grid-configuration .umb-templates { diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-group-builder.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-group-builder.less index 6eded29b8b..87e46f5d85 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-group-builder.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-group-builder.less @@ -347,23 +347,20 @@ input.umb-group-builder__group-title-input:disabled:hover { .umb-group-builder__property-actions { flex: 0 0 44px; display: flex; - align-items: center; + align-items: flex-start; justify-content: flex-end; } .umb-group-builder__property-action { - position: relative; margin: 5px 0; - > button { + button.btn-icon { border: none; - font-size: 18px; position: relative; cursor: pointer; color: @ui-icon; - margin: 0; padding: 5px 10px; width: auto; @@ -399,18 +396,21 @@ input.umb-group-builder__group-title-input:disabled:hover { padding: 0 4px; display: flex; border-radius: 3px; -} + align-items: center; -.umb-group-builder__property-tag:first-child { - margin-left: 0; -} + &:first-child { + margin-left: 0; + } -.umb-group-builder__property-tag.-white { - background-color: @white; + &.-white { + background-color: @white; + } } .umb-group-builder__property-tag-icon { margin-right: 3px; + display: flex; + align-items: center; } /* ---------- SORTABLE ---------- */ @@ -513,17 +513,14 @@ input.umb-group-builder__group-sort-value { display: flex; align-items: center; align-content: stretch; - min-height: 80px; - border: 1px solid @gray-9; color: @ui-action-discreet-type; border-radius: @baseBorderRadius; - } .editor-info { - flex: 1 0 auto; + flex: 1 1 auto; text-align: left; display: flex; align-items: center; @@ -537,6 +534,9 @@ input.umb-group-builder__group-sort-value { } .editor-icon-wrapper { + display: flex; + justify-content: center; + align-items: center; width: 60px; height: 60px; text-align: center; @@ -548,12 +548,12 @@ input.umb-group-builder__group-sort-value { font-size: 32px; } } - + .editor-details { flex: 1 1 auto; margin-top: 10px; margin-bottom: 10px; - + .editor-name { display: block; font-weight: bold; @@ -570,9 +570,9 @@ input.umb-group-builder__group-sort-value { width: 48px; height: 48px; font-size: 18px; - min-height: 80px; color: @ui-action-discreet-type; + &:hover { color: @ui-action-discreet-type-hover; background-color: @ui-action-discreet-hover; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-icon.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-icon.less new file mode 100644 index 0000000000..318ce0a563 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-icon.less @@ -0,0 +1,33 @@ +.umb-icon { + display: inline-block; + width: 1em; + height: 1em; + flex-shrink: 0; + + svg { + width: 100%; + height: 100%; + fill: currentColor; + } + + &.large{ + width: 32px; + height: 32px; + } + &.medium{ + width: 24px; + height: 24px; + } + &.small{ + width: 14px; + height: 14px; + } + + &:before, &:after { + content: none !important; + } + + > i { + font-family: inherit; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-iconpicker.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-iconpicker.less index 98b2b1d72d..5062aae660 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-iconpicker.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-iconpicker.less @@ -26,6 +26,11 @@ padding: 15px 0; text-decoration: none; border-radius: 3px; + background: none; + background: none; + border: none; + cursor: pointer; + color: currentColor; } .umb-iconpicker-item button:hover, @@ -41,6 +46,13 @@ box-sizing: border-box; } +.umb-iconpicker-svg svg { + display: block; + width: 30px; + height: 30px; + fill: currentColor; +} + .umb-iconpicker-item button:active { background: @gray-10; } @@ -49,26 +61,25 @@ font-size: 30px; } - // Color swatch .button { - border: none; - color: @white; - padding: 5px; - text-align: center; - text-decoration: none; - display: inline-block; - margin: 5px; - border-radius: 3px; - } + border: none; + color: @white; + padding: 5px; + text-align: center; + text-decoration: none; + display: inline-block; + margin: 5px; + border-radius: 3px; +} - // Circle behind the checkmark - i.small.active{ - font-size: 14px; - display: inline-block; - width: 20px; - height: 20px; - border-radius: 50%; - background-color: rgba(0,0,0,.15); - float: right; - } +// Circle behind the checkmark +i.small.active{ + font-size: 14px; + display: inline-block; + width: 20px; + height: 20px; + border-radius: 50%; + background-color: rgba(0,0,0,.15); + float: right; +} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-insert-code-box.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-insert-code-box.less index f3b53f4def..006dae09dc 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-insert-code-box.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-insert-code-box.less @@ -8,20 +8,20 @@ padding: 15px 20px; margin-bottom: 10px; border-radius: 3px; - cursor: pointer; + text-align: left; } .umb-insert-code-box:hover, .umb-insert-code-box.-selected { background-color: @ui-option-hover; color: @ui-action-type-hover; - //border-color: @ui-action-border-hover; } .umb-insert-code-box__title { font-size: 15px; margin-bottom: 5px; font-weight: bold; + line-height: 1; } .umb-insert-code-box__description { diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-list.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-list.less index 44955e8f8e..57ba73305a 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-list.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-list.less @@ -40,7 +40,7 @@ a.umb-list-item:focus { } .umb-list-item__description--checkbox{ - margin: 0 0 0 26px; + margin: 0 0 0 30px; } .umb-list-checkbox { diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-logviewer.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-logviewer.less index 8beff55b7c..32dcccb8da 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-logviewer.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-logviewer.less @@ -78,19 +78,22 @@ padding-right: 160px; } - .icon-rate { + .save-search, + .filter-search { position: absolute; top: 0; - line-height: 32px; + display: flex; + align-items: center;; + height: 32px; + } + + .save-search { right: 140px; color: @yellow-d1; cursor: pointer; } - .icon-wrong { - position: absolute; - top: 0; - line-height: 32px; + .filter-search { right: 120px; color: @gray-7; cursor: pointer; @@ -119,6 +122,11 @@ .table { table-layout: fixed; + table { + display: table; + width: 100%; + } + thead th:first-child, thead th:nth-child(3) { width: 20%; } @@ -127,9 +135,16 @@ width: 15%; } + tr td:nth-child(3) { word-break: break-word; } + + button { + white-space: normal; + word-break: break-word; + text-align-last: left; + } } .exception { diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-media-grid.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-media-grid.less index 5d6b7ad962..5f79d65de1 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-media-grid.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-media-grid.less @@ -19,11 +19,19 @@ align-items: center; align-self: stretch; + border-radius: @baseBorderRadius; + margin: 10px; position: relative; user-select: none; box-shadow: 0 1px 1px 0 rgba(0,0,0,.2); transition: box-shadow 150ms ease-in-out; + + > div { + overflow: hidden; + border-radius: @baseBorderRadius; + } + } .umb-media-grid__item.-unselectable { @@ -35,7 +43,8 @@ left: 0; right: 0; bottom: 0; - background-color: rgba(230, 230, 230, .5); + border-radius: @baseBorderRadius; + background-color: rgba(230, 230, 230, .8); pointer-events: none; } } @@ -138,6 +147,7 @@ .umb-media-grid__item-overlay { display: flex; + align-items: center; width: 100%; opacity: 0; position: absolute; @@ -217,3 +227,23 @@ text-decoration: none; box-shadow: 0 1px 2px rgba(0,0,0,.2); } + +/*for the listview*/ + +.umb-media-grid__list-item.selected, .umb-media-grid__list-item.selected:hover, .umb-media-grid__list-item.selected:focus { + border: 2px solid #f5c1bc !important; +} + +.umb-media-grid__list-view .umb-table-cell.umb-table__name { + flex: 1 1 25%; + max-width: none; + white-space: normal; +} + +.umb-media-grid__list-view .umb-table-cell.umb-table__name .item-name { + white-space:normal; +} +.umb-media-grid__list-view .umb-table-cell.umb-table__name ins { + text-decoration: none; + margin-top: 3px; +} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-mini-list-view.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-mini-list-view.less index 8cd08f5045..ecb1ad1904 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-mini-list-view.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-mini-list-view.less @@ -44,6 +44,10 @@ color: @black; } +.umb-minilistview { + overflow: hidden; +} + /* Animations */ .umb-mini-list-view.ng-animate { transition: 120ms cubic-bezier(0.250, 0.250, 0.750, 0.750) all; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-multiple-textbox.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-multiple-textbox.less index 30a32b8123..39eb7e92e4 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-multiple-textbox.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-multiple-textbox.less @@ -1,9 +1,11 @@ -.umb-multiple-textbox{ - &__confirm{ +.umb-multiple-textbox { + .umb-property-editor--limit-width(); + + &__confirm { position: relative; display: inline-block; - &-action{ + &-action { margin: -2px 0 0 0; padding: 2px; background: transparent; @@ -12,10 +14,6 @@ } } -.umb-multiple-textbox .icon-wrapper { - width: 50px; -} - .umb-multiple-textbox .textbox-wrapper { align-items: center; margin-bottom: 15px; @@ -30,15 +28,10 @@ } .umb-multiple-textbox .textbox-wrapper i.handle { - margin-left: 5px; + margin-left: 10px; cursor: move; } -.umb-multiple-textbox .textbox-wrapper a.remove { - margin-left: 5px; - text-decoration: none; -} - .umb-multiple-textbox .add-link { &:extend(.umb-node-preview-add); } diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-nested-content.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-nested-content.less index 716693c778..bd787e2329 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-nested-content.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-nested-content.less @@ -4,6 +4,7 @@ .umb-nested-content-property-container { position: relative; + &:not(:last-child){ margin-bottom: 12px; } @@ -19,7 +20,7 @@ yeah so this is a pain, but we must be super specific in targeting the mandatory property labels, otherwise all properties within a reqired, nested, nested content property will all appear mandatory */ - > ng-form > .control-group > .umb-el-wrap > .control-header label:after { + .umb-property > ng-form > .control-group > .umb-el-wrap > .control-header label:after { content: '*'; color: @red; } @@ -38,6 +39,15 @@ position: relative; text-align: left; background: @white; + border: 1px solid @gray-9; + border-radius: @baseBorderRadius; + transition: border-color 120ms; + margin-bottom: 4px; + margin-top: 4px; + + &.--error { + border-color: @formErrorBorder !important; + } } .umb-nested-content__item.ui-sortable-placeholder { @@ -45,19 +55,19 @@ visibility: visible !important; } -.umb-nested-content__item--single > .umb-nested-content__content { +.umb-nested-content__item--single { border: 0; -} -.umb-nested-content__item--single > .umb-nested-content__content > .umb-pane { - margin: 0; + > .umb-nested-content__content { + > .umb-pane { + margin: 0; + } + } } .umb-nested-content__header-bar { - border-bottom: 1px solid @gray-9; cursor: pointer; background-color: @white; - -moz-user-select: none; -khtml-user-select: none; -webkit-user-select: none; @@ -69,24 +79,21 @@ padding-right: 60px; } } - } .umb-nested-content__heading { + display: flex; + padding: 15px; line-height: 20px; - position: relative; - margin-top:1px; - padding: 15px 5px; - color:@ui-option-type; - border-radius: 3px 3px 0 0; + color: @ui-option-type; &:hover { - color:@ui-option-type-hover; + color: @ui-option-type-hover; } - i { - position: absolute; - margin-top: -1px; + .umb-nested-content__item-icon { + margin-top: -3px; + font-size: 22px; } .umb-nested-content__item-name { @@ -98,10 +105,9 @@ padding-left: 5px; &.--has-icon { - padding-left: 30px; + padding-left: 10px; } } - } .umb-nested-content__icons { @@ -109,7 +115,7 @@ transition: opacity 120ms ease-in-out; position: absolute; right: 0; - top: 3px; + top: 5px; padding: 5px; background-color: @white; } @@ -117,13 +123,16 @@ .umb-nested-content__item--active > .umb-nested-content__header-bar { .umb-nested-content__heading { background-color: @ui-active; + &:hover { - color:@ui-option-type; + color: @ui-option-type; } + .umb-nested-content__item-name { padding-right: 60px; } } + .umb-nested-content__icons { background-color: @ui-active; &:before { @@ -132,8 +141,6 @@ } } - - .umb-nested-content__header-bar:hover .umb-nested-content__icons, .umb-nested-content__header-bar:focus .umb-nested-content__icons, .umb-nested-content__header-bar:focus-within .umb-nested-content__icons, @@ -141,8 +148,6 @@ opacity: 1; } - - .umb-nested-content__icon { background: transparent; border: 0 none; @@ -160,7 +165,7 @@ .umb-nested-content__icon .icon { display: block; - font-size: 16px !important; + font-size: 18px !important; } .umb-nested-content__icon--disabled { @@ -172,9 +177,6 @@ } } - - - .umb-nested-content__footer-bar { margin-top: 20px; } @@ -201,15 +203,14 @@ .umb-nested-content__add-content.--disabled:hover { color: @gray-7; border-color: @gray-7; - cursor: default; + cursor: not-allowed; } - .umb-nested-content__content { border-top: 1px solid transparent; - border-bottom: 1px solid @gray-9; - border-left: 1px solid @gray-9; - border-right: 1px solid @gray-9; + border-bottom: 1px solid transparent; + border-left: 1px solid transparent; + border-right: 1px solid transparent; border-radius: 0 0 3px 3px; } @@ -232,17 +233,17 @@ } .umb-nested-content__doctypepicker table td.icon-navigation, -.umb-nested-content__doctypepicker i.umb-nested-content__help-icon { +.umb-nested-content__doctypepicker .umb-nested-content__help-icon { vertical-align: middle; color: @gray-7; } .umb-nested-content__doctypepicker table td.icon-navigation:hover, -.umb-nested-content__doctypepicker i.umb-nested-content__help-icon:hover { +.umb-nested-content__doctypepicker .umb-nested-content__help-icon:hover { color: @gray-2; } -.umb-nested-content__doctypepicker i.umb-nested-content__help-icon { +.umb-nested-content__doctypepicker .umb-nested-content__help-action { margin-left: 10px; } @@ -291,4 +292,4 @@ .umb-textarea, .umb-textstring { width:100%; } -} +} \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-node-preview.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-node-preview.less index 939fd79826..ac7277109a 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-node-preview.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-node-preview.less @@ -28,8 +28,6 @@ .umb-node-preview__icon { display: flex; - width: 25px; - min-height: 25px; height: 100%; justify-content: center; align-items: center; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-packages.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-packages.less index b808e6574a..0045bed140 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-packages.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-packages.less @@ -59,15 +59,12 @@ .umb-package-link { display: block; - flex-wrap: wrap; - flex-direction: column; - justify-content: center; position: relative; box-sizing: border-box; height: 100%; width: 100%; border-radius: 3px; - border: 0 none; + border: 1px solid transparent; text-decoration: none !important; transition: border-color 100ms ease; background-color: @white; @@ -78,8 +75,6 @@ } } - - // Icon .umb-package-icon { display: flex; @@ -94,22 +89,20 @@ border-top-right-radius: 3px; border-top-left-radius: 3px; min-height: 60px; -} -.umb-package-icon img { - max-width: 70px; - width: 70px; - height: auto; + img { + max-width: 70px; + width: 70px; + height: auto; + } } - // Info .umb-package-info { padding: 15px; text-align: center; } - // Name .umb-package-name { font-size: 14px; @@ -285,7 +278,7 @@ flex: 1 1 auto; margin-right: 20px; width: calc(~'100%' - ~'@{sidebarwidth}' - ~'20px'); // Make sure that the main content area doesn't gets affected by inline styling - min-width: 500px; + min-width: 480px; } .umb-package-details__sidebar { diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-pagination.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-pagination.less index 8d3d563cab..f44aa645c4 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-pagination.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-pagination.less @@ -1,3 +1,5 @@ -.umb-pagination ul { - box-shadow: none; -} \ No newline at end of file +.umb-pagination { + ul { + box-shadow: none; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-property-actions.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-property-actions.less index 17c4bf1a55..6b7d22d0b7 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-property-actions.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-property-actions.less @@ -1,9 +1,4 @@ -.umb-property-actions { - display: inline; -} - -.umb-property-actions__toggle, -.umb-property-actions__menu-open-toggle { +.umb-property-actions__toggle { position: relative; display: flex; flex: 0 0 auto; @@ -11,7 +6,6 @@ text-align: center; cursor: pointer; border-radius: 3px; - background-color: @ui-action-hover; i { @@ -32,27 +26,20 @@ } } } -.umb-property-actions__menu-open-toggle { - position: absolute; - z-index:1; - outline: none;// this is not acceccible by keyboard, since we use the .umb-property-actions__toggle for that. - top: -15px; - border-radius: 3px 3px 0 0; - - border-top-left-radius: 3px; - border-top-right-radius: 3px; - - border: 1px solid @dropdownBorder; - - border-bottom: 1px solid @gray-9; - - .box-shadow(0 5px 20px rgba(0,0,0,.3)); - - background-color: @white; +.umb-property-actions { + display: inline; + &.-open { + .umb-property-actions__toggle { + background-color: @white; + border-radius: 3px 3px 0 0; + border: 1px solid @dropdownBorder; + border-bottom: 1px solid @gray-9; + .box-shadow(0 5px 20px rgba(0,0,0,.3)); + } + } } - .umb-property .umb-property-actions { float: left; } @@ -66,32 +53,22 @@ .umb-property .umb-property-actions__toggle:focus { opacity: 1; } + // Revert-style-hack that ensures that we only show property-actions on properties that are directly begin hovered. .umb-property:hover .umb-property:not(:hover) .umb-property-actions__toggle { opacity: 0; } .umb-property-actions__menu { - position: absolute; z-index: 1000; - display: block; - float: left; min-width: 160px; list-style: none; .umb-contextmenu { - border-top-left-radius: 0; - margin-top:1px; - - } - - .umb-contextmenu-item > button { - - z-index:2;// need to stay on top of menu-toggle-open shadow. - + margin-top: 0; } } diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-property-file-upload.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-property-file-upload.less index 39b2f4002e..83774e2dae 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-property-file-upload.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-property-file-upload.less @@ -14,6 +14,12 @@ color: @gray-8; } + &:focus-within { + .tabbing-active & { + box-shadow: 0 0 2px @ui-outline, inset 0 0 2px 1px @ui-outline; + } + } + i.icon { font-size: 55px; line-height: 70px @@ -26,4 +32,10 @@ width: 100%; } } + + .drag-over { + .umb-upload-button-big { + border-color: @gray-1; + } + } } diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-search-filter.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-search-filter.less new file mode 100644 index 0000000000..b96d3e8569 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-search-filter.less @@ -0,0 +1,31 @@ +html .umb-search-filter { + position: relative; + height: 30px; + width: 190px; + + &.w-100 { + width: 100%; + } + + &.mb-15 { + margin-bottom: 15px; + } + + &__input { + padding-left: 30px; + padding-right: 6px; + width: inherit; + margin: 0; + } + + // "icon-search" class it kept for backward compatibility + .umb-icon, + .icon-search { + color: #d8d7d9; + position: absolute; + top: 0; + bottom: 0; + left: 10px; + margin: auto 0; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-stylesheet.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-stylesheet.less index 38ceba2a59..36d9913d66 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-stylesheet.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-stylesheet.less @@ -41,6 +41,7 @@ textarea.umb-stylesheet-rule-styles { width: 300px; height: 100px; resize: none; + font-family: @monoFontFamily; } .umb-stylesheet-rule-preview { diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-table.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-table.less index 202c488bb4..d0427cad0a 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-table.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-table.less @@ -164,7 +164,7 @@ input.umb-table__input { } } -.umb-table-body__icon { +.umb-table-body .umb-table-body__icon { margin: 0 auto; font-size: 20px; line-height: 20px; @@ -173,7 +173,7 @@ input.umb-table__input { text-decoration: none; } -.umb-table-body__checkicon { +.umb-table-body .umb-table-body__icon.umb-table-body__checkicon { display: none; font-size: 18px; line-height: 20px; @@ -218,15 +218,15 @@ input.umb-table__input { } // Show checkmark when checked, hide file icon -.umb-table-row.-selected { - - .umb-table-body__fileicon { - display: none; - } - .umb-table-body__checkicon { - display: inline-block; - } - +.umb-table-body .umb-table-row.-selected { + .umb-table-body__icon { + &.umb-table-body__fileicon { + display: none; + } + &.umb-table-body__checkicon { + display: inline-block; + } + } } // Table Row Styles diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-tags-editor.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-tags-editor.less new file mode 100644 index 0000000000..213807e685 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-tags-editor.less @@ -0,0 +1,52 @@ +.umb-tags-editor { + border: @inputBorder solid 1px; + padding: 5px; + min-height: 54px; + font-size: 13px; + text-shadow: none; + box-sizing: border-box; + + .tag { + cursor: default; + margin: 10px; + padding: 10px 15px; + background: @blueExtraDark; + position: relative; + user-select: all; + + .umb_confirm-action { + + > .btn-icon { + color: @white; + padding: 0; + position: relative; + cursor: pointer; + padding-left: 2px; + font-size: 15px; + right: -5px; + bottom: -1px; + user-select: none; + } + + .umb_confirm-action__overlay.-left { + top: 8px; + left: auto; + right: 15px; + } + } + } + + .twitter-typeahead { + margin: 10px; + margin-top: 16px; + vertical-align: top; + max-width: calc(100% - 20px); + + input { + border: none; + background: @white; + padding-left: 0; + max-width: 100%; + } + } +} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/users/umb-user-picker-list.less b/src/Umbraco.Web.UI.Client/src/less/components/users/umb-user-picker-list.less index 2e0d79e803..4a343f780a 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/users/umb-user-picker-list.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/users/umb-user-picker-list.less @@ -8,6 +8,8 @@ margin-bottom: 5px; padding: 10px; align-items: center; + width: 100%; + text-align: left; } .umb-user-picker-list-item:active, @@ -39,4 +41,4 @@ .umb-user-picker-list-item__name { font-size: 15px; font-weight: bold; -} \ No newline at end of file +} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/users/umb-user-preview.less b/src/Umbraco.Web.UI.Client/src/less/components/users/umb-user-preview.less index f62f3afd37..d0dca54403 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/users/umb-user-preview.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/users/umb-user-preview.less @@ -4,6 +4,11 @@ display: flex; box-sizing: border-box; border-bottom: 1px solid @gray-9; + flex-wrap: wrap; +} + +.umb-editor-wrapper .umb-user-preview { + .umb-property-editor--limit-width(); } .umb-user-preview:last-of-type { @@ -29,22 +34,26 @@ flex: 0 0 auto; display: flex; align-items: center; + margin-left: auto; } .umb-user-preview__action { + background: transparent; + padding: 0; + border: 0 none; margin-left: 5px; margin-right: 5px; font-size: 13px; font-weight: bold; - color: @gray-5; + color: @ui-action-type; } .umb-user-preview__action:hover { - color: @turquoise; + color: @ui-action-type-hover; text-decoration: none; opacity: 1; } .umb-user-preview__action--red:hover { color: @red; -} \ No newline at end of file +} diff --git a/src/Umbraco.Web.UI.Client/src/less/filter-toggle.less b/src/Umbraco.Web.UI.Client/src/less/filter-toggle.less deleted file mode 100644 index 82f9f3f553..0000000000 --- a/src/Umbraco.Web.UI.Client/src/less/filter-toggle.less +++ /dev/null @@ -1,20 +0,0 @@ -.filter-toggle{ - margin: 0; - padding: 0 8px 0 0; - position: relative; -} - -.filter-toggle__level{ - display: inline-block; - font-weight: 700; - margin: 0 5px; - max-width: 150px; -} - -.filter-toggle__icon{ - position: absolute; - top: 0; - bottom: 0; - right: 0; - margin: auto 0; -} diff --git a/src/Umbraco.Web.UI.Client/src/less/fonts.less b/src/Umbraco.Web.UI.Client/src/less/fonts.less index 73bc36037f..e9a84656f9 100644 --- a/src/Umbraco.Web.UI.Client/src/less/fonts.less +++ b/src/Umbraco.Web.UI.Client/src/less/fonts.less @@ -15,8 +15,7 @@ font-family: 'Lato'; src: local('LatoLatin Black'), local('LatoLatin-Black'), - url('@{latoPath}/LatoLatin-Black.woff2') format('woff2'), /* Super Modern Browsers */ - url('@{latoPath}/LatoLatin-Black.woff') format('woff'); /* Modern Browsers */ + url('@{latoPath}/LatoLatin-Black.woff2') format('woff2'); font-style: normal; font-display: swap; font-weight: 900; @@ -28,8 +27,7 @@ font-family: 'Lato'; src: local('LatoLatin BlackItalic'), local('LatoLatin-BlackItalic'), - url('@{latoPath}/LatoLatin-BlackItalic.woff2') format('woff2'), /* Super Modern Browsers */ - url('@{latoPath}/LatoLatin-BlackItalic.woff') format('woff'); /* Modern Browsers */ + url('@{latoPath}/LatoLatin-BlackItalic.woff2') format('woff2'); font-style: italic; font-weight: 900; font-display: swap; @@ -41,8 +39,7 @@ font-family: 'Lato'; src: local('LatoLatin Bold'), local('LatoLatin-Bold'), - url('@{latoPath}/LatoLatin-Bold.woff2') format('woff2'), /* Super Modern Browsers */ - url('@{latoPath}/LatoLatin-Bold.woff') format('woff'); /* Modern Browsers */ + url('@{latoPath}/LatoLatin-Bold.woff2') format('woff2'); font-style: normal; font-weight: 700; font-display: swap; @@ -54,8 +51,7 @@ font-family: 'Lato'; src: local('LatoLatin BoldItalic'), local('LatoLatin-BoldItalic'), - url('@{latoPath}/LatoLatin-BoldItalic.woff2') format('woff2'), /* Super Modern Browsers */ - url('@{latoPath}/LatoLatin-BoldItalic.woff') format('woff'); /* Modern Browsers */ + url('@{latoPath}/LatoLatin-BoldItalic.woff2') format('woff2'); font-style: italic; font-weight: 700; font-display: swap; @@ -67,8 +63,7 @@ font-family: 'Lato'; src: local('LatoLatin Italic'), local('LatoLatin-Italic'), - url('@{latoPath}/LatoLatin-Italic.woff2') format('woff2'), /* Super Modern Browsers */ - url('@{latoPath}/LatoLatin-Italic.woff') format('woff'); /* Modern Browsers */ + url('@{latoPath}/LatoLatin-Italic.woff2') format('woff2'); font-style: italic; font-weight: 400; font-display: swap; @@ -80,8 +75,7 @@ font-family: 'Lato'; src: local('LatoLatin Regular'), local('LatoLatin-Regular'), - url('@{latoPath}/LatoLatin-Regular.woff2') format('woff2'), /* Super Modern Browsers */ - url('@{latoPath}/LatoLatin-Regular.woff') format('woff'); /* Modern Browsers */ + url('@{latoPath}/LatoLatin-Regular.woff2') format('woff2'); font-style: normal; font-weight: 400; font-display: swap; @@ -93,8 +87,7 @@ font-family: 'Lato'; src: local('LatoLatin Light'), local('LatoLatin-Light'), - url('@{latoPath}/LatoLatin-Light.woff2') format('woff2'), /* Super Modern Browsers */ - url('@{latoPath}/LatoLatin-Light.woff') format('woff'); /* Modern Browsers */ + url('@{latoPath}/LatoLatin-Light.woff2') format('woff2'); font-style: normal; font-weight: 300; font-display: swap; @@ -106,8 +99,7 @@ font-family: 'Lato'; src: local('LatoLatin LightItalic'), local('LatoLatin-LightItalic'), - url('@{latoPath}/LatoLatin-LightItalic.woff2') format('woff2'), /* Super Modern Browsers */ - url('@{latoPath}/LatoLatin-LightItalic.woff') format('woff'); /* Modern Browsers */ + url('@{latoPath}/LatoLatin-LightItalic.woff2') format('woff2'); font-style: italic; font-weight: 300; font-display: swap; diff --git a/src/Umbraco.Web.UI.Client/src/less/forms.less b/src/Umbraco.Web.UI.Client/src/less/forms.less index ffc0626981..90b2dbe37e 100644 --- a/src/Umbraco.Web.UI.Client/src/less/forms.less +++ b/src/Umbraco.Web.UI.Client/src/less/forms.less @@ -29,7 +29,7 @@ label.control-label, .control-label { } -.controls-row label{padding: 0 10px 0 10px; vertical-align: middle;} +.controls-row label:not(.umb-form-check){padding: 0 10px 0 10px; vertical-align: middle;} @@ -68,8 +68,9 @@ label.control-label, .control-label { .form-search .icon, .form-search .icon-search { position: absolute; z-index: 1; - top: 6px; + top: 50%; left: 6px; + transform: translateY(-50%); color: @gray-8; } @@ -113,6 +114,13 @@ label.control-label, .control-label { } } +.form-search .umb-search-field { + width: 100%; +} + +.macro-select .form-search { + margin: 0 0 10px; +} // GENERAL STYLES // -------------- @@ -810,6 +818,7 @@ legend + .control-group { // Labels on own row .form-horizontal .control-label { float:none; + text-align: inherit; width: 100%; } .form-horizontal .controls { diff --git a/src/Umbraco.Web.UI.Client/src/less/gridview.less b/src/Umbraco.Web.UI.Client/src/less/gridview.less index 11ba7b2795..80f13fbf1f 100644 --- a/src/Umbraco.Web.UI.Client/src/less/gridview.less +++ b/src/Umbraco.Web.UI.Client/src/less/gridview.less @@ -21,7 +21,7 @@ overflow: hidden; padding: 5px; border-radius:5px; - box-shadow: 3px 3px 12px 0px rgba(50, 50, 50, 0.45); + box-shadow: 3px 3px 12px 0 rgba(50, 50, 50, 0.45); } .usky-grid .ui-sortable-helper *{ @@ -150,7 +150,7 @@ .usky-grid .cell-tools-add { position: absolute; text-align: center; - bottom: 0px; + bottom: 0; left: 0; right: 0; margin: 0 45px 1px 0; @@ -160,14 +160,14 @@ } } -.usky-grid .usky-control:hover .cell-tools-add{ +.usky-grid .usky-control:hover .cell-tools-add{ opacity: 1; } -.usky-grid .cell-tools-remove { +.usky-grid .cell-tools-remove { display:inline-block; position: absolute; - top: 0px; + top: 0; right: 5px; text-align: right; z-index: 500; @@ -221,7 +221,7 @@ top: -22px; left: -1px; text-decoration: none; - padding: 0px 7px; + padding: 0 7px; display:none; font-size:0.8em; background-color: @white; @@ -234,7 +234,7 @@ .usky-grid .usky-row-inner > ins.item-label{ top: -20px; - left: 0px; + left: 0; } .usky-grid .usky-control-inner.selectedControl , .usky-grid .usky-row-inner.selectedRow{ @@ -408,7 +408,7 @@ .usky-grid ul li { display: inline-block; width: 120px; - margin: 8px 8px 0px 8px; + margin: 8px 8px 0 8px; } @@ -435,7 +435,7 @@ padding: -1px; position: absolute; margin: -1px -1px 0 -1px; - box-shadow: 2px 2px 10px 0px rgba(50, 50, 50, 0.14); + box-shadow: 2px 2px 10px 0 rgba(50, 50, 50, 0.14); z-index: 9999999; } @@ -558,7 +558,8 @@ margin: 20px; } - .usky-grid .uSky-templates-template a.tb:hover { + .usky-grid .uSky-templates-template button.tb:hover, + .usky-grid .uSky-templates-template button.tb:focus { border:5px solid @blueMid; } @@ -587,7 +588,9 @@ border-right: 1px dashed @gray-8 !important; } - .usky-grid a.uSky-templates-column:hover, .usky-grid a.uSky-templates-column.selected{ + .usky-grid button.uSky-templates-column:hover, + .usky-grid button.uSky-templates-column:focus, + .usky-grid button.uSky-templates-column.selected{ background-color: @blueMid; } @@ -628,13 +631,13 @@ transition: border 200ms linear; &.prevalues-rows { - margin: 0px 20px 20px 0px; + margin: 0 20px 20px 0; width: 80px; float:left; } &.prevalues-templates { - margin: 0px 20px 20px 0px; + margin: 0 20px 20px 0; float:left; } @@ -803,7 +806,7 @@ .usky-grid-configuration .uSky-templates .uSky-templates-template .tb{ max-height: 50px; border-width: 2px !important; - padding: 0px; + padding: 0; border-spacing:2px; overflow: hidden; } @@ -844,13 +847,13 @@ } .usky-grid-configuration .uSky-templates-rows .uSky-templates-row{ - margin: 0px 50px 20px 0px; + margin: 0 50px 20px 0; width: 60px; } .usky-grid-configuration .uSky-templates-rows .uSky-templates-row .tb{ border-width: 2px !important; - padding: 0px; + padding: 0; border-spacing:2px; } @@ -858,4 +861,6 @@ height: 10px !important; } -.usky-grid-configuration a.uSky-templates-column{height: 70px !important;} +.usky-grid-configuration button.uSky-templates-column { + height: 70px !important; +} diff --git a/src/Umbraco.Web.UI.Client/src/less/helveticons.less b/src/Umbraco.Web.UI.Client/src/less/helveticons.less index 82663270e9..cded6b8269 100644 --- a/src/Umbraco.Web.UI.Client/src/less/helveticons.less +++ b/src/Umbraco.Web.UI.Client/src/less/helveticons.less @@ -8,35 +8,29 @@ font-style: normal; } + [class^="icon-"], -[class*=" icon-"] { - font-family: icomoon; - font-weight: normal; - font-style: normal; - text-decoration: inherit; - -webkit-font-smoothing: antialiased; - *margin-right: .3em; +[class*=" icon-"]{ + font-family: 'icomoon'; + speak: none; + font-style: normal; + font-weight: normal; + font-variant: normal; + text-transform: none; + line-height: 1; + + /* Better Font Rendering =========== */ + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; } [class^="icon-"]:before, [class*=" icon-"]:before { - text-decoration: inherit; - display: inline-block; - speak: none; + text-decoration: inherit; + display: inline-block; + speak: none; } -/* -[class^="icon-"]:before, [class*=" icon-"]:before { - font-family: 'icomoon'; - speak: none; - font-weight: normal; - font-style: normal; - display: inline-block; - text-decoration: inherit; - font-size: 14px; - -webkit-font-smoothing: antialiased; -}*/ - i.large{ font-size: 32px; } @@ -164,22 +158,22 @@ i.small{ .icon-umb-deploy:before { content: "\e027"; } -.icon-umb-contour:before, .traycontour:before, { +.icon-umb-contour:before, .traycontour:before { content: "\e028"; } -.icon-umb-settings:before, .traysettings:before, { +.icon-umb-settings:before, .traysettings:before { content: "\e029"; } .icon-umb-users:before, .trayuser:before, .trayusers:before{ content: "\e02a"; } -.icon-umb-media:before, .traymedia:before, { +.icon-umb-media:before, .traymedia:before { content: "\e02b"; } .icon-umb-content:before, .traycontent:before{ content: "\e02c"; } -.icon-umb-developer:before, .traydeveloper:before, { +.icon-umb-developer:before, .traydeveloper:before { content: "\e02d"; } .icon-umb-members:before, .traymember:before { @@ -711,7 +705,7 @@ i.small{ .icon-paper-bag:before { content: "\e0da"; } -.icon-name-badge:before { +.icon-badge:before { content: "\e0db"; } .icon-medicine:before { diff --git a/src/Umbraco.Web.UI.Client/src/less/installer.less b/src/Umbraco.Web.UI.Client/src/less/installer.less index 4e24161e59..9ce519186a 100644 --- a/src/Umbraco.Web.UI.Client/src/less/installer.less +++ b/src/Umbraco.Web.UI.Client/src/less/installer.less @@ -57,7 +57,8 @@ body { #installer { margin: auto; background: @white; - width: 750px; + width: 80%; + max-width: 750px; height: 600px; text-align: left; padding: 30px; diff --git a/src/Umbraco.Web.UI.Client/src/less/listview.less b/src/Umbraco.Web.UI.Client/src/less/listview.less index fe8af6dbc4..582da12804 100644 --- a/src/Umbraco.Web.UI.Client/src/less/listview.less +++ b/src/Umbraco.Web.UI.Client/src/less/listview.less @@ -32,7 +32,10 @@ position: absolute; padding: 5px 8px; pointer-events: none; - top: 2px; + + &i { + top: 2px; + } } input[type="text"] { @@ -256,20 +259,21 @@ margin-right: 15px; } +.list-view-layout__icon-wrapper { + margin-right: 10px; +} + .list-view-layout__icon { font-size: 18px; - margin-right: 10px; vertical-align: middle; border: 1px solid @gray-8; background: @white; - padding: 6px 8px; - display: block; -} - -.list-view-layout__icon:hover, -.list-view-layout__icon:focus, -.list-view-layout__icon:active { - text-decoration: none; + padding: 0; + display: flex; + align-items: center; + justify-content: center; + width: 30px; + height: 30px; } .list-view-layout__remove { @@ -278,20 +282,9 @@ } .list-view-add-layout { - width:100%; - background:0 0; - margin-top: 10px; - color: @ui-action-discreet-type; - border: 1px dashed @ui-action-discreet-border; - display: flex; - align-items: center; - justify-content: center; - padding: 5px 0; - box-sizing: border-box; + &:extend(.umb-node-preview-add); } .list-view-add-layout:hover { - text-decoration: none; - color: @ui-action-discreet-type-hover; - border-color: @ui-action-discreet-border-hover; + &:extend(.umb-node-preview-add:hover); } diff --git a/src/Umbraco.Web.UI.Client/src/less/main.less b/src/Umbraco.Web.UI.Client/src/less/main.less index 57d867ccd5..31bb8484c4 100644 --- a/src/Umbraco.Web.UI.Client/src/less/main.less +++ b/src/Umbraco.Web.UI.Client/src/less/main.less @@ -151,7 +151,7 @@ h6.-black { } } -.umb-property:last-of-type .umb-control-group { +umb-property:last-of-type .umb-control-group { &::after { margin-top: 0px; height: 0; @@ -208,6 +208,13 @@ h6.-black { .control-description { display: block; clear: both; + overflow-wrap: break-word; + } + + &::after { + content: ''; + display: block; + clear: both; } } @@ -221,6 +228,7 @@ h6.-black { padding-top: 5px; padding-bottom: 0; text-align: left; + margin-bottom: 5px; .control-label { width: auto; @@ -230,7 +238,7 @@ h6.-black { .control-description { max-width:480px;// avoiding description becoming too wide when its placed on top of property. - margin-bottom: 10px; + margin-bottom: 5px; } } @media (max-width: 767px) { @@ -238,9 +246,27 @@ h6.-black { .form-horizontal .umb-control-group .control-header { float: none; width: 100%; + &::after { + content: ""; + display: table; + clear: both; + } } - +} +.form-horizontal .umb-control-group.--label-on-top > .umb-el-wrap { + & > .control-header { + float: none; + width: 100%; + &::after { + content: ""; + display: table; + clear: both; + } + } + & > .controls { + margin-left: 0; + } } /* LABELS*/ @@ -272,6 +298,7 @@ label:not([for]) { /* CONTROL VALIDATION */ .umb-control-required { color: @controlRequiredColor; + font-weight: 900; } .controls-row { @@ -283,7 +310,7 @@ label:not([for]) { margin-left: 0; } -.controls-row label { +.controls-row label:not(.umb-form-check) { display: inline-block; } @@ -579,6 +606,9 @@ table thead button:focus{ display: inline; } +.relative { + position:relative; +} // Input label styles // @Simon: not sure where to put this part yet @@ -639,3 +669,8 @@ input[type=checkbox]:checked + .input-label--small { background-color: @green-l3; text-decoration: none; } + +.language-icon { + color: #BBBABF; + margin-right: 5px; +} diff --git a/src/Umbraco.Web.UI.Client/src/less/mixins.less b/src/Umbraco.Web.UI.Client/src/less/mixins.less index a87080a326..9739a90dae 100644 --- a/src/Umbraco.Web.UI.Client/src/less/mixins.less +++ b/src/Umbraco.Web.UI.Client/src/less/mixins.less @@ -138,7 +138,9 @@ // additional targetting of the ng-invalid class. .formFieldState(@textColor: @gray-4, @borderColor: @gray-7, @backgroundColor: @gray-10) { // Set the text color - .control-label, + > .control-label, + > .umb-el-wrap > .control-label, + > .umb-el-wrap > .control-header > .control-label, .help-block, .help-inline { color: @textColor; diff --git a/src/Umbraco.Web.UI.Client/src/less/modals.less b/src/Umbraco.Web.UI.Client/src/less/modals.less index 4ce907d06f..a23756f412 100644 --- a/src/Umbraco.Web.UI.Client/src/less/modals.less +++ b/src/Umbraco.Web.UI.Client/src/less/modals.less @@ -24,18 +24,18 @@ } .umb-modalcolumn-body { - padding: 0px; + padding: 0; background: @white; top: @editorHeaderHeight; position: absolute; - left: 0px; - right: 0px; - bottom: 0px; + left: 0; + right: 0; + bottom: 0; overflow: auto; } .no-padding .umb-modalcolumn-body { - padding: 0px + padding: 0; } .umb-modalcolumn .umb-modalcolumn-header .btn { @@ -46,12 +46,12 @@ .umb-modalcolumn iframe.auto-expand, .umb-modal iframe.auto-expand { border: none; - padding: 0px; - margin: 0px; - top: 0px; - bottom: 0px; - left: 0px; - right: 0px; + padding: 0; + margin: 0; + top: 0; + bottom: 0; + left: 0; + right: 0; position: absolute; } @@ -79,12 +79,12 @@ /* umb.dialog is used for the dialogs on the conent tree*/ .umb-dialog { outline: none; - top: 0px; - left: 0px; - right: 0px; - bottom: 0px; + top: 0; + left: 0; + right: 0; + bottom: 0; position: absolute; - padding: 0px; + padding: 0; background: @white; } @@ -100,9 +100,9 @@ .umb-dialog-body{ position: absolute; overflow:auto; - top: 0px; - left: 0px; - right: 0px; + top: 0; + left: 0; + right: 0; bottom: 49px; } .umb-dialog-body .umb-pane{margin-top: 15px;} @@ -112,9 +112,9 @@ overflow:auto; text-align: right; height: 32px; - left: 0px; - right: 0px; - bottom: 0px; + left: 0; + right: 0; + bottom: 0; padding: 8px; margin: 0; @@ -129,13 +129,13 @@ height: auto !Important; padding: 20px; } -.umbracoDialog .umb-pane{margin-left: 0px; margin-right: 0px; margin-top: 0px;} +.umbracoDialog .umb-pane{margin-left: 0; margin-right: 0; margin-top: 0;} .umbracoDialog .umb-dialog-body .umb-pane{margin-left: 20px; margin-right: 20px; margin-top: 20px;} .umbracoDialog form{height: 100%;} /*ensures dialogs doesnt have side-by-side labels*/ .umbracoDialog .controls-row, -.umb-modal .controls-row{margin-left: 0px !important;} +.umb-modal .controls-row{margin-left: 0 !important;} /* modal and umb-modal are used for right.hand dialogs */ .modal { @@ -185,7 +185,7 @@ height: auto; } .umb-modal .umb-panel-body{ - padding: 0px 20px 0px 20px; + padding: 0 20px 0 20px; } .umb-modal.fade.in.wide { diff --git a/src/Umbraco.Web.UI.Client/src/less/navs.less b/src/Umbraco.Web.UI.Client/src/less/navs.less index c347404619..6dab771c94 100644 --- a/src/Umbraco.Web.UI.Client/src/less/navs.less +++ b/src/Umbraco.Web.UI.Client/src/less/navs.less @@ -12,7 +12,10 @@ padding-right: 7px; } -.icon.handle{color: @gray-8;} +.umb-icon.handle, +.icon.handle { + color: @gray-8; +} // BASE CLASS @@ -168,8 +171,7 @@ // Active state .nav-pills > .active > a, -.nav-pills > .active > a:hover, -.nav-pills > .active > a:focus { +.nav-pills > .active > a:hover { color: @white; background-color: @linkColor; } @@ -184,7 +186,14 @@ float: none; } .nav-stacked > li > a { + position: relative; margin-right: 0; // no need for the gap between nav items + color: @ui-action-discreet-type; + border-radius: 3px; +} +.nav-stacked > li > a:hover { + color: @ui-action-discreet-type-hover; + background-color: @ui-action-discreet-hover; } // Tabs diff --git a/src/Umbraco.Web.UI.Client/src/less/pages/login.less b/src/Umbraco.Web.UI.Client/src/less/pages/login.less index 818b1d84d1..cf49af526b 100644 --- a/src/Umbraco.Web.UI.Client/src/less/pages/login.less +++ b/src/Umbraco.Web.UI.Client/src/less/pages/login.less @@ -125,9 +125,8 @@ user-select: none; margin-left: auto; - a { + button { opacity: .5; - cursor: pointer; display: inline-block; z-index: 1; -webkit-tap-highlight-color: transparent; @@ -135,7 +134,7 @@ .password-text { background-repeat: no-repeat; background-size: 18px; - background-position: 0px 1px; + background-position: 0 1px; padding-left: 24px; &.show { diff --git a/src/Umbraco.Web.UI.Client/src/less/pages/welcome-dashboard.less b/src/Umbraco.Web.UI.Client/src/less/pages/welcome-dashboard.less index 59c58c914e..426ffcb5e2 100644 --- a/src/Umbraco.Web.UI.Client/src/less/pages/welcome-dashboard.less +++ b/src/Umbraco.Web.UI.Client/src/less/pages/welcome-dashboard.less @@ -39,21 +39,20 @@ text-decoration: none; display: block; margin: 10px; -} -.welcome-dashboard__info-box:hover { - border: 2px solid @turquoise; - cursor: pointer; - transition: border-color 150ms ease-in-out; - text-decoration: none; -} + &:hover { + border: 2px solid @turquoise; + cursor: pointer; + transition: border-color 150ms ease-in-out; + text-decoration: none; + } -.welcome-dashboard__info-box:active, -.welcome-dashboard__info-box:focus { - text-decoration: none; + &:active, + &:focus { + text-decoration: none; + } } - .welcome-dashboard__info-box-title { color: @turquoise-d1; font-size: 16px; @@ -74,7 +73,7 @@ } .welcome-dashboard__card { - background-color: @gray-10; + background-color: @grayLighter; border-radius: 3px; margin: 10px; display: flex; @@ -113,6 +112,6 @@ } .welcome-dashboard__card-teaser { - font-size: 14px; + font-size: 13px; margin-bottom: 15px; -} \ No newline at end of file +} diff --git a/src/Umbraco.Web.UI.Client/src/less/panel.less b/src/Umbraco.Web.UI.Client/src/less/panel.less index 40c70f5331..a036267c85 100644 --- a/src/Umbraco.Web.UI.Client/src/less/panel.less +++ b/src/Umbraco.Web.UI.Client/src/less/panel.less @@ -48,6 +48,10 @@ .form-search { flex: 1; + .icon-search { + top: 16px; + } + &__toggle { margin: 10px 0; display: flex; @@ -259,9 +263,14 @@ .umb-dialog a.muted:hover, .umb-dialog a.muted:focus, +.umb-dialog button.muted:hover, +.umb-dialog button.muted:focus, .umb-panel a.muted:hover, -.umb-panel a.muted:focus { +.umb-panel a.muted:focus, +.umb-panel button.muted:hover, +.umb-panel button.muted:focus { color: darken(@gray-5, 10%); + text-decoration: underline; } .umb-dialog .text-warning, @@ -392,6 +401,7 @@ font-size: 30px; color: @gray-7; transition: opacity 120ms; + line-height: 1; } .umb-panel-header-icon-text { @@ -408,6 +418,8 @@ input.umb-panel-header-name-input.name-is-empty { .umb-panel-header-name { font-size: 16px; font-weight: bold; + margin: 0; + line-height: 1.2; } @@ -428,7 +440,7 @@ input.umb-panel-header-description { .umb-panel-header-locked-description { font-size: 12px; - margin-top: 2px; + margin: 2px 0 0 0; height: 22px; line-height: 22px; } diff --git a/src/Umbraco.Web.UI.Client/src/less/properties.less b/src/Umbraco.Web.UI.Client/src/less/properties.less index 9e951feb1a..7960aae2c9 100644 --- a/src/Umbraco.Web.UI.Client/src/less/properties.less +++ b/src/Umbraco.Web.UI.Client/src/less/properties.less @@ -16,8 +16,8 @@ border-left: 1px solid @gray-10; } -.date-wrapper__date .flatpickr-input > a { - +.date-wrapper__date .flatpickr-input > a, +.date-wrapper__date .flatpickr-input > button { display: flex; align-items: center; justify-content: center; @@ -25,16 +25,15 @@ padding: 4px 15px; box-sizing: border-box; min-width: 200px; - color: @ui-action-discreet-type; border: 1px dashed @ui-action-discreet-border; border-radius: 3px; - + &:hover, &:focus { text-decoration: none; color: @ui-action-discreet-type-hover; border-color: @ui-action-discreet-border-hover; - + localize { text-decoration: none; } @@ -120,7 +119,9 @@ } .history-item__date { - font-size: 13px; + font-size: 12px; + margin-top: -4px; + display: block; color: @gray-5; } @@ -132,7 +133,10 @@ } .history-item__badge { - margin-right: 5px; + margin-right: 10px; +} +.history-item__description { + color: @gray-5; } /* RESPONSIVE */ diff --git a/src/Umbraco.Web.UI.Client/src/less/property-editors.less b/src/Umbraco.Web.UI.Client/src/less/property-editors.less index a3bdbb0996..0d8f270f1b 100644 --- a/src/Umbraco.Web.UI.Client/src/less/property-editors.less +++ b/src/Umbraco.Web.UI.Client/src/less/property-editors.less @@ -104,6 +104,20 @@ // -------------------------------------------------- /* pre-value editor */ +.umb-prevalues-multivalues.umb-colors { + max-width: 600px; + width: 100%; + min-width: 66.6%; + + @media (min-width: 1101px) and (max-width: 1300px), (max-width: 930px) { + max-width: none; + } + + .umb-overlay__form & { + width: 100%; + } +} + .control-group.color-picker-preval { .thumbnail { width: 34px; @@ -131,7 +145,7 @@ pre { display: inline-flex; - font-family: monospace; + font-family: @monoFontFamily; margin-left: 15px; margin-right: 15px; white-space: nowrap; @@ -160,15 +174,10 @@ width: auto; } - .sp-replacer { - display: inline-flex; - margin-right: 18px; - } - label { border: 1px solid @white; padding: 6px 10px; - font-family: monospace; + font-family: @monoFontFamily; border: 1px solid @gray-8; background: @gray-11; margin: 0 15px 0 3px; @@ -320,6 +329,11 @@ box-shadow:none !important; } +.umb-sortable-thumbnails-container { + display: flex; + flex-wrap: wrap; + background-color: @white; +} .umb-sortable-thumbnails { list-style-type: none; @@ -707,8 +721,9 @@ margin-top: 0; } } + // -// folder-browser +// Folder browser // -------------------------------------------------- .umb-folderbrowser .add-link { display: inline-block; @@ -806,55 +821,18 @@ } +// +// Slider +// -------------------------------------------------- +.umb-slider { + .umb-property-editor--limit-width(); +} + // // Tags // -------------------------------------------------- .umb-tags { - border: @inputBorder solid 1px; - padding: 5px; - min-height: 54px; - font-size: 13px; - text-shadow: none; - box-sizing: border-box; .umb-property-editor--limit-width(); - - .tag { - cursor: default; - margin: 10px; - padding: 10px 15px; - background: @blueExtraDark; - position: relative; - user-select: all; - - .icon-trash { - position: relative; - cursor: pointer; - padding-left: 2px; - font-size: 15px; - right: -5px; - bottom: -1px; - } - - .umb_confirm-action__overlay.-left{ - top: 6px; - left: auto; - right: 15px; - } - } - - input { - border: none; - background: @white; - } - - .twitter-typeahead { - margin: 10px; - margin-top: 16px; - vertical-align: top; - input { - padding-left: 0; - } - } } // diff --git a/src/Umbraco.Web.UI.Client/src/less/rte.less b/src/Umbraco.Web.UI.Client/src/less/rte.less index 0e43f428ae..0eab4b346b 100644 --- a/src/Umbraco.Web.UI.Client/src/less/rte.less +++ b/src/Umbraco.Web.UI.Client/src/less/rte.less @@ -35,6 +35,11 @@ box-sizing: border-box; } +.umb-rte .mce-top-part { + position: sticky; + top: 0; +} + /* make sure the menu wraps */ .umb-rte .mce-top-part.mce-container div { white-space: normal; diff --git a/src/Umbraco.Web.UI.Client/src/less/utilities/_spacing.less b/src/Umbraco.Web.UI.Client/src/less/utilities/_spacing.less index 787e50f204..d2968696dc 100644 --- a/src/Umbraco.Web.UI.Client/src/less/utilities/_spacing.less +++ b/src/Umbraco.Web.UI.Client/src/less/utilities/_spacing.less @@ -35,10 +35,13 @@ 7 = 7th step in spacing scale */ -.m-center { - margin-left: auto; +.m-center, +.mx-auto { + margin-left: auto; margin-right: auto; } +.ml-auto { margin-left: auto; } +.mr-auto { margin-right: auto; } .mt0 { margin-top: @spacing-none; } .mt1 { margin-top: @spacing-extra-small; } diff --git a/src/Umbraco.Web.UI.Client/src/less/variables.less b/src/Umbraco.Web.UI.Client/src/less/variables.less index d94e4e1fb1..cab0745a42 100644 --- a/src/Umbraco.Web.UI.Client/src/less/variables.less +++ b/src/Umbraco.Web.UI.Client/src/less/variables.less @@ -156,11 +156,11 @@ @ui-active-type-hover: @blueMid; @ui-selected: @sand-5; -@ui-selected-hover: ligthen(@sand-5, 10); +@ui-selected-hover: ligthen(@sand-5, 10%); @ui-selected-type: @blueExtraDark; @ui-selected-type-hover: @blueMid; @ui-selected-border: @blueDark; -@ui-selected-border-hover: darken(@blueDark, 10); +@ui-selected-border-hover: darken(@blueDark, 10%); @ui-light-border: @pinkLight; @ui-light-type: @gray-4; @@ -199,11 +199,13 @@ @ui-btn-positive-type: @white; @ui-btn-negative: @red; +@ui-btn-negative-type: @white; @ui-btn-negative-hover: @red; @ui-icon: @blueNight; @ui-icon-hover: @blueMid; +@ui-drop-area-color: @blueMidLight; // Scaffolding @@ -254,6 +256,7 @@ // Disabled this to keep consistency throughout the backoffice UI. Untill a better solution is thought up, this will do. @baseBorderRadius: 3px; // 2px; +@doubleBorderRadius: 6px; @borderRadiusLarge: 3px; // 6px; @borderRadiusSmall: 3px; // 3px; @@ -271,8 +274,8 @@ @btnBackgroundHighlight: @gray-10; @btnBorder: @gray-9; -@btnPrimaryBackground: @ui-btn-positive; -@btnPrimaryBackgroundHighlight: @ui-btn-positive-hover; +@btnPrimaryBackground: @ui-btn; +@btnPrimaryBackgroundHighlight: @ui-btn-hover; @btnInfoType: @ui-btn-type;// updated 2019 @btnInfoTypeHover: @ui-btn-type;// updated 2019 @@ -478,7 +481,7 @@ @formErrorText: @errorBackground; @formErrorBackground: lighten(@errorBackground, 55%); -@formErrorBorder: darken(spin(@errorBackground, -10), 3%); +@formErrorBorder: @red; @formSuccessText: @successBackground; @formSuccessBackground: lighten(@successBackground, 48%); @@ -515,7 +518,7 @@ // ------------------------- @gridColumns: 12; @gridColumnWidth: 60px; -@gridGutterWidth: 0px; +@gridGutterWidth: 0; @gridRowWidth: (@gridColumns * @gridColumnWidth) + (@gridGutterWidth * (@gridColumns - 1)); // 1200px min diff --git a/src/Umbraco.Web.UI.Client/src/main.controller.js b/src/Umbraco.Web.UI.Client/src/main.controller.js index 297d93f4bc..d21331f106 100644 --- a/src/Umbraco.Web.UI.Client/src/main.controller.js +++ b/src/Umbraco.Web.UI.Client/src/main.controller.js @@ -31,13 +31,17 @@ function MainController($scope, $location, appState, treeService, notificationsS // For more information about this approach, see https://hackernoon.com/removing-that-ugly-focus-ring-and-keeping-it-too-6c8727fefcd2 function handleFirstTab(evt) { if (evt.keyCode === 9) { - $scope.tabbingActive = true; - $scope.$digest(); - window.removeEventListener('keydown', handleFirstTab); - window.addEventListener('mousedown', disableTabbingActive); + enableTabbingActive(); } } + function enableTabbingActive() { + $scope.tabbingActive = true; + $scope.$digest(); + window.addEventListener('mousedown', disableTabbingActive); + window.removeEventListener("keydown", handleFirstTab); + } + function disableTabbingActive(evt) { $scope.tabbingActive = false; $scope.$digest(); @@ -47,6 +51,12 @@ function MainController($scope, $location, appState, treeService, notificationsS window.addEventListener("keydown", handleFirstTab); + $scope.$on("showFocusOutline", function() { + $scope.tabbingActive = true; + window.addEventListener('mousedown', disableTabbingActive); + window.removeEventListener("keydown", handleFirstTab); + }); + $scope.removeNotification = function (index) { notificationsService.remove(index); diff --git a/src/Umbraco.Web.UI.Client/src/preview/preview.controller.js b/src/Umbraco.Web.UI.Client/src/preview/preview.controller.js index 5ff8dd3633..703082eb53 100644 --- a/src/Umbraco.Web.UI.Client/src/preview/preview.controller.js +++ b/src/Umbraco.Web.UI.Client/src/preview/preview.controller.js @@ -5,13 +5,13 @@ var app = angular.module("umbraco.preview", ['umbraco.resources', 'umbraco.services']) - .controller("previewController", function ($scope, $window, $location) { + .controller("previewController", function ($scope, $window, $location, $http) { $scope.currentCulture = { iso: '', title: '...', icon: 'icon-loading' } var cultures = []; $scope.tabbingActive = false; - // There are a number of ways to detect when a focus state should be shown when using the tab key and this seems to be the simplest solution. + // There are a number of ways to detect when a focus state should be shown when using the tab key and this seems to be the simplest solution. // For more information about this approach, see https://hackernoon.com/removing-that-ugly-focus-ring-and-keeping-it-too-6c8727fefcd2 function handleFirstTab(evt) { if (evt.keyCode === 9) { @@ -29,7 +29,28 @@ var app = angular.module("umbraco.preview", ['umbraco.resources', 'umbraco.servi window.addEventListener("keydown", handleFirstTab); } + var iframeWrapper = angular.element("#demo-iframe-wrapper"); + var canvasDesignerPanel = angular.element("#canvasdesignerPanel"); + window.addEventListener("keydown", handleFirstTab); + window.addEventListener("resize", scaleIframeWrapper); + iframeWrapper.on("transitionend", scaleIframeWrapper); + + function scaleIframeWrapper() { + if ($scope.previewDevice.name == "fullsize") { // dont scale fullsize preview + iframeWrapper.css({"transform": ""}); + } + else { + var wrapWidth = canvasDesignerPanel.width(); // width of the wrapper + var wrapHeight = canvasDesignerPanel.height(); + var childWidth = iframeWrapper.width() + 30; // width of child iframe plus some space + var childHeight = iframeWrapper.height() + 30; // child height plus some space + var wScale = wrapWidth / childWidth; + var hScale = wrapHeight / childHeight; + var scale = Math.min(wScale,hScale,1); // get the lowest ratio, but not higher than 1 + iframeWrapper.css({"transform": "scale("+scale+")" }); // set scale + } + } //gets a real query string value @@ -85,11 +106,18 @@ var app = angular.module("umbraco.preview", ['umbraco.resources', 'umbraco.servi .fail(function () { console.log("Could not connect to SignalR preview hub."); }); } + function fixExternalLinks(iframe) { + // Make sure external links don't open inside the iframe + Array.from(iframe.contentDocument.getElementsByTagName("a")) + .filter(a => a.hostname !== location.hostname && !a.target) + .forEach(a => a.target = "_top"); + } + var isInit = getParameterByName("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. + //this flag. This is a required trick to get around chrome popup mgr. return; } @@ -100,7 +128,7 @@ var app = angular.module("umbraco.preview", ['umbraco.resources', 'umbraco.servi $scope.valueAreLoaded = false; $scope.devices = [ - { name: "fullsize", css: "fullsize", icon: "icon-application-window-alt", title: "Browser" }, + { name: "fullsize", css: "fullsize", icon: "icon-application-window-alt", title: "Fit browser" }, { name: "desktop", css: "desktop shadow", icon: "icon-display", title: "Desktop" }, { name: "laptop - 1366px", css: "laptop shadow", icon: "icon-laptop", title: "Laptop" }, { name: "iPad portrait - 768px", css: "iPad-portrait shadow", icon: "icon-ipad", title: "Tablet portrait" }, @@ -136,17 +164,33 @@ var app = angular.module("umbraco.preview", ['umbraco.resources', 'umbraco.servi $scope.windowClickHandler = function () { closeOthers(); } + function windowBlurHandler() { closeOthers(); $scope.$digest(); } + window.addEventListener("blur", windowBlurHandler); - var win = $($window); + function windowVisibilityHandler(e) { - win.on("blur", windowBlurHandler); + var amountOfPreviewSessions = localStorage.getItem('UmbPreviewSessionAmount'); + + // When tab is visible again: + if(document.hidden === false) { + checkPreviewState(); + } + } + document.addEventListener("visibilitychange", windowVisibilityHandler); + + function beforeUnloadHandler(e) { + endPreviewSession(); + } + window.addEventListener("beforeunload", beforeUnloadHandler, false); $scope.$on("$destroy", function () { - win.off("blur", handleBlwindowBlurHandlerur); + window.removeEventListener("blur", windowBlurHandler); + document.removeEventListener("visibilitychange", windowVisibilityHandler); + window.removeEventListener("beforeunload", beforeUnloadHandler); }); @@ -162,6 +206,208 @@ var app = angular.module("umbraco.preview", ['umbraco.resources', 'umbraco.servi $scope.pageUrl = "frame?" + query; } } + function getCookie(cname) { + var name = cname + "="; + var ca = document.cookie.split(";"); + for(var i = 0; i < ca.length; i++) { + var c = ca[i]; + while (c.charAt(0) == " ") { + c = c.substring(1); + } + if (c.indexOf(name) == 0) { + return c.substring(name.length, c.length); + } + } + return null; + } + function setCookie(cname, cvalue, exminutes) { + var d = new Date(); + d.setTime(d.getTime() + (exminutes * 60 * 1000)); + document.cookie = cname + "=" + cvalue + ";expires="+d.toUTCString() + ";path=/"; + } + var hasPreviewDialog = false; + function checkPreviewState() { + if (getCookie("UMB_PREVIEW") === null) { + + if(hasPreviewDialog === true) return; + hasPreviewDialog = true; + + // Ask to re-enter preview mode? + + const localizeVarsFallback = { + "returnToPreviewHeadline": "Preview website?", + "returnToPreviewDescription":"You have ended preview mode, do you want to enable it again to view the latest saved version of your website?", + "returnToPreviewAcceptButton":"Preview latest version", + "returnToPreviewDeclineButton":"View published version" + }; + const umbLocalizedVars = Object.assign(localizeVarsFallback, $window.umbLocalizedVars); + + + // This modal is also used in websitepreview.js + var modelStyles = ` + + /* Webfont: LatoLatin-Bold */ + @font-face { + font-family: 'Lato'; + src: url('https://fonts.googleapis.com/css2?family=Lato:wght@700&display=swap'); + font-style: normal; + font-weight: 700; + font-display: swap; + text-rendering: optimizeLegibility; + } + + .umbraco-preview-dialog { + position: fixed; + display: flex; + align-items: center; + justify-content: center; + z-index: 99999999; + top:0; + bottom:0; + left:0; + right:0; + overflow: auto; + background-color: rgba(0,0,0,0.6); + } + + .umbraco-preview-dialog__modal { + background-color: #fff; + border-radius: 6px; + box-shadow: 0 3px 7px rgba(0,0,0,0.3); + margin: auto; + padding: 30px 40px; + width: 100%; + max-width: 540px; + font-family: Lato,Helvetica Neue,Helvetica,Arial,sans-serif; + font-size: 15px; + } + + .umbraco-preview-dialog__headline { + font-weight: 700; + font-size: 22px; + color: #1b264f; + margin-top:10px; + margin-bottom:20px; + } + .umbraco-preview-dialog__question { + margin-bottom:30px; + } + .umbraco-preview-dialog__modal > button { + display: inline-block; + cursor: pointer; + padding: 8px 18px; + text-align: center; + vertical-align: middle; + border-radius: 3px; + border:none; + font-family: inherit; + font-weight: 700; + font-size: 15px; + float:right; + margin-left:10px; + + color: #1b264f; + background-color: #f6f1ef; + } + .umbraco-preview-dialog__modal > button:hover { + color: #2152a3; + background-color: #f6f1ef; + } + .umbraco-preview-dialog__modal > button.umbraco-preview-dialog__continue { + color: #fff; + background-color: #2bc37c; + } + .umbraco-preview-dialog__modal > button.umbraco-preview-dialog__continue:hover { + background-color: #39d38b; + } + `; + + var bodyEl = document.getElementsByTagName("BODY")[0]; + + var fragment = document.createElement("div"); + var shadowRoot = fragment.attachShadow({ mode: 'open' }); + + var style = document.createElement("style"); + style.innerHTML = modelStyles; + shadowRoot.appendChild(style); + + var con = document.createElement("div"); + con.className = "umbraco-preview-dialog"; + shadowRoot.appendChild(con); + + var modal = document.createElement("div"); + modal.className = "umbraco-preview-dialog__modal"; + modal.innerHTML = `
${umbLocalizedVars.returnToPreviewHeadline}
+
${umbLocalizedVars.returnToPreviewDescription}
`; + con.appendChild(modal); + + var declineButton = document.createElement("button"); + declineButton.type = "button"; + declineButton.innerHTML = umbLocalizedVars.returnToPreviewDeclineButton; + declineButton.addEventListener("click", () => { + bodyEl.removeChild(fragment); + $scope.exitPreview(); + hasPreviewDialog = false; + }); + modal.appendChild(declineButton); + + var continueButton = document.createElement("button"); + continueButton.type = "button"; + continueButton.className = "umbraco-preview-dialog__continue"; + continueButton.innerHTML = umbLocalizedVars.returnToPreviewAcceptButton; + continueButton.addEventListener("click", () => { + bodyEl.removeChild(fragment); + reenterPreviewMode(); + hasPreviewDialog = false; + }); + modal.appendChild(continueButton); + + bodyEl.appendChild(fragment); + continueButton.focus(); + + } + } + function reenterPreviewMode() { + //Re-enter Preview Mode + $http({ + method: 'POST', + url: '../preview/enterPreview', + params: { + id: $scope.pageId + } + }) + startPreviewSession(); + } + function getPageURL() { + var culture = $location.search().culture || getParameterByName("culture"); + var relativeUrl = "/" + $scope.pageId; + if (culture) { + relativeUrl += '?culture=' + culture; + } + return relativeUrl; + } + + function startPreviewSession() { + // lets registrer this preview session. + var amountOfPreviewSessions = Math.max(localStorage.getItem('UmbPreviewSessionAmount') || 0, 0); + amountOfPreviewSessions++; + localStorage.setItem('UmbPreviewSessionAmount', amountOfPreviewSessions); + } + function resetPreviewSessions() { + localStorage.setItem('UmbPreviewSessionAmount', 0); + } + function endPreviewSession() { + var amountOfPreviewSessions = localStorage.getItem('UmbPreviewSessionAmount') || 0; + amountOfPreviewSessions--; + localStorage.setItem('UmbPreviewSessionAmount', amountOfPreviewSessions); + + if(amountOfPreviewSessions <= 0) { + // We are good to end preview mode. + navigator.sendBeacon("../preview/end"); + } + } + startPreviewSession(); + /*****************************************************************************/ /* Preview devices */ /*****************************************************************************/ @@ -171,23 +417,29 @@ var app = angular.module("umbraco.preview", ['umbraco.resources', 'umbraco.servi $scope.previewDevice = device; }; + /*****************************************************************************/ + /* Open website in preview mode */ + /*****************************************************************************/ + + $scope.openInBrowser = function () { + setCookie("UMB-WEBSITE-PREVIEW-ACCEPT", "true", 5); + window.open(getPageURL(), "_blank"); + }; + /*****************************************************************************/ /* Exit Preview */ /*****************************************************************************/ $scope.exitPreview = function () { - - var culture = $location.search().culture || getParameterByName("culture"); - var relativeUrl = "/" + $scope.pageId; - if (culture) { - relativeUrl += '?culture=' + culture; - } - window.top.location.href = "../preview/end?redir=" + encodeURIComponent(relativeUrl); + resetPreviewSessions(); + window.top.location.href = "../preview/end?redir=" + encodeURIComponent(getPageURL()); }; $scope.onFrameLoaded = function (iframe) { + $scope.frameLoaded = true; configureSignalR(iframe); + fixExternalLinks(iframe); $scope.currentCultureIso = $location.search().culture || null; }; @@ -239,8 +491,8 @@ var app = angular.module("umbraco.preview", ['umbraco.resources', 'umbraco.servi var vm = this; vm.$postLink = function () { - var resultFrame = $element.find("#resultFrame"); - resultFrame.on("load", iframeReady); + var resultFrame = $element.find("#resultFrame").get(0); + resultFrame.addEventListener("load", iframeReady); }; function iframeReady() { diff --git a/src/Umbraco.Web.UI.Client/src/utilities.js b/src/Umbraco.Web.UI.Client/src/utilities.js index 9121ba0e25..64884b589b 100644 --- a/src/Umbraco.Web.UI.Client/src/utilities.js +++ b/src/Umbraco.Web.UI.Client/src/utilities.js @@ -18,7 +18,7 @@ /** * Facade to angular.copy */ - const copy = val => angular.copy(val); + const copy = (src, dst) => angular.copy(src, dst); /** * Equivalent to angular.isArray diff --git a/src/Umbraco.Web.UI.Client/src/views/common/drawers/help/help.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/drawers/help/help.controller.js index 268bfb3a8c..c212a08951 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/drawers/help/help.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/drawers/help/help.controller.js @@ -157,8 +157,8 @@ } function openTourGroup(tourAlias) { - angular.forEach(vm.tours, function (group) { - angular.forEach(group, function (tour) { + vm.tours.forEach(function (group) { + group.tours.forEach(function (tour) { if (tour.alias === tourAlias) { group.open = true; } @@ -168,9 +168,9 @@ function getTourGroupCompletedPercentage() { // Finding out, how many tours are completed for the progress circle - angular.forEach(vm.tours, function(group){ + vm.tours.forEach(function(group){ var completedTours = 0; - angular.forEach(group.tours, function(tour){ + group.tours.forEach(function(tour){ if(tour.completed) { completedTours++; } diff --git a/src/Umbraco.Web.UI.Client/src/views/common/drawers/help/help.html b/src/Umbraco.Web.UI.Client/src/views/common/drawers/help/help.html index aa6126e73e..4f6b283fd8 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/drawers/help/help.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/drawers/help/help.html @@ -2,8 +2,8 @@ + title="{{vm.title}}" + description="{{vm.subtitle}}"> @@ -14,20 +14,19 @@
-
-
+
{{ tour.name }}
- +
-
+
@@ -98,58 +97,58 @@ - + + + + + - -
-
- Videos -
- -
+ +
+
+ Videos +
+ +
- - @@ -159,6 +158,7 @@ diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/blockeditor/blockeditor.content.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/blockeditor/blockeditor.content.html new file mode 100644 index 0000000000..15c3b9594f --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/blockeditor/blockeditor.content.html @@ -0,0 +1 @@ + diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/blockeditor/blockeditor.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/blockeditor/blockeditor.controller.js new file mode 100644 index 0000000000..88cda027a8 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/blockeditor/blockeditor.controller.js @@ -0,0 +1,99 @@ +angular.module("umbraco") + .controller("Umbraco.Editors.BlockEditorController", + function ($scope, localizationService, formHelper, overlayService) { + var vm = this; + + vm.model = $scope.model; + vm.tabs = []; + + localizationService.localizeMany([ + vm.model.createFlow ? "general_cancel" : (vm.model.liveEditing ? "prompt_discardChanges" : "general_close"), + vm.model.createFlow ? "general_create" : (vm.model.liveEditing ? "buttons_confirmActionConfirm" : "buttons_submitChanges") + ]).then(function (data) { + vm.closeLabel = data[0]; + vm.submitLabel = data[1]; + }); + + if (vm.model.content && vm.model.content.variants) { + + var apps = vm.model.content.apps; + + // configure the content app based on settings + var contentApp = apps.find(entry => entry.alias === "umbContent"); + if (contentApp) { + if (vm.model.hideContent) { + apps.splice(apps.indexOf(contentApp), 1); + } + contentApp.active = (vm.model.openSettings !== true); + } + + if (vm.model.settings && vm.model.settings.variants) { + var settingsApp = apps.find(entry => entry.alias === "settings"); + if (settingsApp) { + settingsApp.active = (vm.model.openSettings === true); + } + } + + vm.tabs = apps; + } + + vm.submitAndClose = function () { + if (vm.model && vm.model.submit) { + + // always keep server validations since this will be a nested editor and server validations are global + if (formHelper.submitForm({ + scope: $scope, + formCtrl: vm.blockForm, + keepServerValidation: true + })) { + vm.model.submit(vm.model); + vm.saveButtonState = "success"; + } else { + vm.saveButtonState = "error"; + } + } + } + + vm.close = function () { + if (vm.model && vm.model.close) { + // TODO: At this stage there could very well have been server errors that have been cleared + // but if we 'close' we are basically cancelling the value changes which means we'd want to cancel + // all of the server errors just cleared. It would be possible to do that but also quite annoying. + // The rudimentary way would be to: + // * Track all cleared server errors here by subscribing to the prefix validation of controls contained here + // * If this is closed, re-add all of those server validation errors + // A more robust way to do this would be to: + // * Add functionality to the serverValidationManager whereby we can remove validation errors and it will + // maintain a copy of the original errors + // * It would have a 'commit' method to commit the removed errors - which we would call in the formHelper.submitForm when it's successful + // * It would have a 'rollback' method to reset the removed errors - which we would call here + + if (vm.model.createFlow === true || vm.blockForm.$dirty === true) { + var labels = vm.model.createFlow === true ? ["blockEditor_confirmCancelBlockCreationHeadline", "blockEditor_confirmCancelBlockCreationMessage"] : ["prompt_discardChanges", "blockEditor_blockHasChanges"]; + localizationService.localizeMany(labels).then(function (localizations) { + const confirm = { + title: localizations[0], + view: "default", + content: localizations[1], + submitButtonLabelKey: "general_discard", + submitButtonStyle: "danger", + closeButtonLabelKey: "prompt_stay", + submit: function () { + overlayService.close(); + vm.model.close(vm.model); + }, + close: function () { + overlayService.close(); + } + }; + overlayService.open(confirm); + }); + } else { + vm.model.close(vm.model); + } + + } + } + + } + ); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/blockeditor/blockeditor.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/blockeditor/blockeditor.html new file mode 100644 index 0000000000..2367771804 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/blockeditor/blockeditor.html @@ -0,0 +1,59 @@ +
+ + + + + + + + +
+ + + +
+ + +
+ +
+ + + + + + + + + + + + + + + + +
+
+
diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/blockeditor/blockeditor.settings.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/blockeditor/blockeditor.settings.html new file mode 100644 index 0000000000..df69e2e648 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/blockeditor/blockeditor.settings.html @@ -0,0 +1 @@ + diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/blockpicker/blockpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/blockpicker/blockpicker.controller.js new file mode 100644 index 0000000000..2894e0bef4 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/blockpicker/blockpicker.controller.js @@ -0,0 +1,60 @@ +angular.module("umbraco") +.controller("Umbraco.Editors.BlockPickerController", + function ($scope, localizationService) { + var vm = this; + + + vm.navigation = []; + + + localizationService.localizeMany(["blockEditor_tabCreateEmpty", "blockEditor_tabClipboard"]).then( + function (data) { + + vm.navigation = [{ + "alias": "empty", + "name": data[0], + "icon": "icon-add", + "active": true, + "view": "" + }, + { + "alias": "clipboard", + "name": data[1], + "icon": "icon-paste-in", + "view": "", + "disabled": vm.model.clipboardItems.length === 0 + }]; + + vm.activeTab = vm.navigation[0]; + } + ); + + + vm.onNavigationChanged = function(tab) { + vm.activeTab.active = false; + vm.activeTab = tab; + vm.activeTab.active = true; + } + + vm.clickClearClipboard = function() { + vm.onNavigationChanged(vm.navigation[0]); + vm.navigation[1].disabled = true;// disabled ws determined when creating the navigation, so we need to update it here. + vm.model.clipboardItems = [];// This dialog is not connected via the clipboardService events, so we need to update manually. + vm.model.clickClearClipboard(); + } + + vm.model = $scope.model; + + vm.selectItem = function(item, $event) { + vm.model.selectedItem = item; + vm.model.submit($scope.model, $event); + } + + vm.close = function() { + if ($scope.model && $scope.model.close) { + $scope.model.close($scope.model); + } + } + + } +); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/blockpicker/blockpicker.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/blockpicker/blockpicker.html new file mode 100644 index 0000000000..4b08d4e5fc --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/blockpicker/blockpicker.html @@ -0,0 +1,83 @@ +
+ + + + + + +
+ +
+ + + +
+ + + +
+
+ +
+
+ + +
+ +
+ + + +
+
+ +
+ + + + + + + + + + + +
+
diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/compositions/compositions.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/compositions/compositions.controller.js index aa0cd54dff..526d2d4dfa 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/compositions/compositions.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/compositions/compositions.controller.js @@ -1,15 +1,17 @@ (function () { "use strict"; - function CompositionsController($scope, $location, $filter, overlayService, localizationService) { + function CompositionsController($scope, $location, $filter, $timeout, overlayService, localizationService) { var vm = this; var oldModel = null; vm.showConfirmSubmit = false; + vm.loadingAlias = null; vm.isSelected = isSelected; vm.openContentType = openContentType; + vm.selectCompositeContentType = selectCompositeContentType; vm.submit = submit; vm.close = close; @@ -23,10 +25,13 @@ $scope.model.title = "Compositions"; } - // group the content types by their container paths + // Group the content types by their container paths vm.availableGroups = $filter("orderBy")( _.map( _.groupBy($scope.model.availableCompositeContentTypes, function (compositeContentType) { + + compositeContentType.selected = isSelected(compositeContentType.contentType.alias); + return compositeContentType.contentType.metaData.containerPath; }), function (group) { return { @@ -39,12 +44,12 @@ }); } - - + function isSelected(alias) { if ($scope.model.contentType.compositeContentTypes.indexOf(alias) !== -1) { return true; } + return false; } function openContentType(contentType, section) { @@ -52,6 +57,25 @@ $location.path(url); } + function selectCompositeContentType(compositeContentType) { + vm.loadingAlias = compositeContentType.contentType.alias + + var contentType = compositeContentType.contentType; + + $scope.model.selectCompositeContentType(contentType).then(function (response) { + vm.loadingAlias = null; + }); + + // Check if the template is already selected. + var index = $scope.model.contentType.compositeContentTypes.indexOf(contentType.alias); + + if (index === -1) { + $scope.model.contentType.compositeContentTypes.push(contentType.alias); + } else { + $scope.model.contentType.compositeContentTypes.splice(index, 1); + } + } + function submit() { if ($scope.model && $scope.model.submit) { diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/compositions/compositions.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/compositions/compositions.html index 4096192081..98db2b0336 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/compositions/compositions.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/compositions/compositions.html @@ -17,32 +17,28 @@
- + + + +
- +
- - + + - + @@ -50,7 +46,7 @@

    -
  • @@ -68,21 +64,21 @@
  • - -
    - + ng-class="{'-disabled': (compositeContentType.allowed === false && !compositeContentType.selected) || compositeContentType.inherited, '-selected': compositeContentType.selected}"> +
    + +
    -
    diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/datatypeconfigurationpicker/datatypeconfigurationpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/datatypeconfigurationpicker/datatypeconfigurationpicker.controller.js index 9ee80488af..dc68771ed0 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/datatypeconfigurationpicker/datatypeconfigurationpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/datatypeconfigurationpicker/datatypeconfigurationpicker.controller.js @@ -42,15 +42,14 @@ var filteredConfigs = []; - _.each(configs, function(configGroup) { - for(var i = 0; i { + for(var i = 0; i < configGroup.length; i++) { + if (configGroup[i].alias === $scope.model.editor.alias) { + filteredConfigs.push(configGroup[i]); } } - ); - + }); + vm.configs = filteredConfigs; vm.loading = false; }); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/datatypepicker/datatypepicker.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/datatypepicker/datatypepicker.html index d6e2d8133d..3d8e1d4d0b 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/datatypepicker/datatypepicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/datatypepicker/datatypepicker.html @@ -15,20 +15,16 @@ -
    - -
    + + diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/embed/embed.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/embed/embed.controller.js index 515f54e3d7..c3d1312109 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/embed/embed.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/embed/embed.controller.js @@ -22,7 +22,7 @@ }; if ($scope.model.modify) { - angular.extend($scope.model.embed, $scope.model.modify); + Utilities.extend($scope.model.embed, $scope.model.modify); showPreview(); } @@ -34,7 +34,7 @@ vm.close = close; function onInit() { - if(!$scope.model.title) { + if (!$scope.model.title) { localizationService.localize("general_embed").then(function(value){ $scope.model.title = value; }); @@ -122,7 +122,6 @@ if ($scope.model.embed.url !== "") { showPreview(); } - } function toggleConstrain() { @@ -130,19 +129,18 @@ } function submit() { - if($scope.model && $scope.model.submit) { + if ($scope.model && $scope.model.submit) { $scope.model.submit($scope.model); } } function close() { - if($scope.model && $scope.model.close) { + if ($scope.model && $scope.model.close) { $scope.model.close(); } } onInit(); - } angular.module("umbraco").controller("Umbraco.Editors.EmbedController", EmbedController); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/embed/embed.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/embed/embed.html index 5862ca7059..19cf9b2278 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/embed/embed.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/embed/embed.html @@ -16,7 +16,7 @@ - + { + vm.icons = icons; + + // Get's legacy icons, removes duplicates then maps them to IconModel + iconHelper.getIcons() + .then(icons => { + if (icons && icons.length > 0) { + let legacyIcons = icons + .filter(icon => !vm.icons.find(x => x.name == icon)) + .map(icon => { return { name: icon, svgString: null }; }); + + vm.icons = legacyIcons.concat(vm.icons); + } + + vm.loading = false; + }); + }); // set a default color if nothing is passed in vm.color = $scope.model.color ? findColor($scope.model.color) : vm.colors.find(x => x.default); + // if an icon is passed in - preselect it vm.icon = $scope.model.icon ? $scope.model.icon : undefined; } @@ -87,7 +102,7 @@ function IconPickerController($scope, iconHelper, localizationService) { } function submit() { - if ($scope.model && $scope.model.submit) { + if ($scope.model && $scope.model.submit) { $scope.model.submit($scope.model); } } diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/iconpicker/iconpicker.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/iconpicker/iconpicker.html index c2b0d0e458..7368d2f39b 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/iconpicker/iconpicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/iconpicker/iconpicker.html @@ -18,17 +18,14 @@
    - + +
    @@ -45,10 +42,10 @@
      -
    • -
    diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/insertcodesnippet/insertcodesnippet.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/insertcodesnippet/insertcodesnippet.html index ea247c77e5..2ce7af8d9d 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/insertcodesnippet/insertcodesnippet.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/insertcodesnippet/insertcodesnippet.html @@ -9,32 +9,32 @@ hide-icon="true" hide-description="true"> - +
    -
    -
    ...
    -
    -
    -
    -
    ...
    -
    - -
    -
    -
    -
    ...
    -
    - -
    -
    -
    -
    ...
    -
    -
    + + + +
    @@ -54,5 +54,5 @@ - +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/insertfield/insertfield.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/insertfield/insertfield.html index bbb2e8c798..00b18565f7 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/insertfield/insertfield.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/insertfield/insertfield.html @@ -71,7 +71,7 @@
    -
    {{ vm.generateOutputSample() }}
    + {{ vm.generateOutputSample() }}
    diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/itempicker/itempicker.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/itempicker/itempicker.html index 18d0c40b6a..c563394ab3 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/itempicker/itempicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/itempicker/itempicker.html @@ -13,7 +13,7 @@ -
    diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/overlays/mediacropdetails.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/overlays/mediacropdetails.controller.js new file mode 100644 index 0000000000..c6927cbaa9 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/overlays/mediacropdetails.controller.js @@ -0,0 +1,65 @@ +angular.module("umbraco") + .controller("Umbraco.Editors.MediaCropDetailsController", + function ($scope) { + + var vm = this; + + vm.submit = submit; + vm.close = close; + vm.hasCrops = cropSet() === true; + vm.focalPointChanged = focalPointChanged; + vm.disableFocalPoint = false; + + if(typeof $scope.model.disableFocalPoint === "boolean") { + vm.disableFocalPoint = $scope.model.disableFocalPoint + } + else { + vm.disableFocalPoint = ($scope.model.disableFocalPoint !== undefined && $scope.model.disableFocalPoint !== "0") ? true : false; + } + + if (!$scope.model.target.coordinates && !$scope.model.target.focalPoint) { + $scope.model.target.focalPoint = { left: .5, top: .5 }; + } + + if (!$scope.model.target.image) { + $scope.model.target.image = $scope.model.target.url; + } + + if (!$scope.model.target + || $scope.model.target.id + || ($scope.model.target.url && $scope.model.target.url.toLowerCase().startsWith("blob:"))) { + vm.shouldShowUrl = false; + } else { + vm.shouldShowUrl = true; + } + + /** + * Called when the umbImageGravity component updates the focal point value + * @param {any} left + * @param {any} top + */ + function focalPointChanged(left, top) { + // update the model focalpoint value + $scope.model.target.focalPoint = { + left: left, + top: top + }; + } + + function submit() { + if ($scope.model && $scope.model.submit) { + $scope.model.submit($scope.model); + } + } + + function close() { + if ($scope.model && $scope.model.close) { + $scope.model.close($scope.model); + } + } + + function cropSet() { + var model = $scope.model; + return (model.cropSize || {}).width !== undefined && (model.cropSize || {}).height !== undefined; + } + }); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/overlays/mediacropdetails.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/overlays/mediacropdetails.html new file mode 100644 index 0000000000..de936da163 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/overlays/mediacropdetails.html @@ -0,0 +1,97 @@ +
    + + + + + + + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    +
    + Preview +
    + + {{model.target.name}} +
    + +
    +
    + Focal point +
    + +
    + + +
    +
    + +
    +
    + Crop section +
    + +
    + + +
    +
    + +
    + +
    + + + + + + + + + + + + + +
    +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/propertysettings/propertysettings.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/propertysettings/propertysettings.controller.js index a6d1383640..3c09745f15 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/propertysettings/propertysettings.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/propertysettings/propertysettings.controller.js @@ -35,6 +35,7 @@ vm.toggleShowOnMemberProfile = toggleShowOnMemberProfile; vm.toggleMemberCanEdit = toggleMemberCanEdit; vm.toggleIsSensitiveData = toggleIsSensitiveData; + vm.toggleLabelOnTop = toggleLabelOnTop; function onInit() { @@ -42,23 +43,24 @@ vm.showSensitiveData = user.userGroups.indexOf("sensitiveData") != -1; }); - //make the default the same as the content type + //make the default the same as the content type if (!$scope.model.property.dataTypeId) { $scope.model.property.allowCultureVariant = $scope.model.contentTypeAllowCultureVariant; } - + loadValidationTypes(); - + } function loadValidationTypes() { var labels = [ - "validation_validateAsEmail", - "validation_validateAsNumber", - "validation_validateAsUrl", + "validation_validateAsEmail", + "validation_validateAsNumber", + "validation_validateAsUrl", "validation_enterCustomValidation", - "validation_fieldIsMandatory" + "validation_fieldIsMandatory", + "contentTypeEditor_displaySettingsLabelOnTop" ]; localizationService.localizeMany(labels) @@ -69,6 +71,7 @@ vm.labels.validateAsUrl = data[2]; vm.labels.customValidation = data[3]; vm.labels.fieldIsMandatory = data[4]; + vm.labels.displaySettingsLabelOnTop = data[5]; vm.validationTypes = [ { @@ -121,7 +124,7 @@ $scope.model.updateSameDataTypes = model.updateSameDataTypes; vm.focusOnMandatoryField = true; - + // update property property.config = model.property.config; property.editor = model.property.editor; @@ -179,7 +182,7 @@ if(event && event.keyCode === 13) { submit(); } - } + } function submit() { if($scope.model.submit) { @@ -202,7 +205,7 @@ var match = false; // find and show if a match from the list has been chosen - angular.forEach(vm.validationTypes, function (validationType, index) { + vm.validationTypes.forEach(function (validationType, index) { if ($scope.model.property.validation.pattern === validationType.pattern) { vm.selectedValidationType = vm.validationTypes[index]; vm.showValidationPattern = true; @@ -212,7 +215,7 @@ // if there is no match - choose the custom validation option. if (!match) { - angular.forEach(vm.validationTypes, function (validationType) { + vm.validationTypes.forEach(function (validationType) { if (validationType.key === "custom") { vm.selectedValidationType = validationType; vm.showValidationPattern = true; @@ -245,28 +248,31 @@ return !settingValue; } - function toggleAllowCultureVariants() { + function toggleAllowCultureVariants() { $scope.model.property.allowCultureVariant = toggleValue($scope.model.property.allowCultureVariant); } - function toggleAllowSegmentVariants() { + function toggleAllowSegmentVariants() { $scope.model.property.allowSegmentVariant = toggleValue($scope.model.property.allowSegmentVariant); } function toggleValidation() { - $scope.model.property.validation.mandatory = toggleValue($scope.model.property.validation.mandatory); + $scope.model.property.validation.mandatory = toggleValue($scope.model.property.validation.mandatory); } function toggleShowOnMemberProfile() { - $scope.model.property.showOnMemberProfile = toggleValue($scope.model.property.showOnMemberProfile); + $scope.model.property.showOnMemberProfile = toggleValue($scope.model.property.showOnMemberProfile); } function toggleMemberCanEdit() { - $scope.model.property.memberCanEdit = toggleValue($scope.model.property.memberCanEdit); + $scope.model.property.memberCanEdit = toggleValue($scope.model.property.memberCanEdit); } function toggleIsSensitiveData() { - $scope.model.property.isSensitiveData = toggleValue($scope.model.property.isSensitiveData); + $scope.model.property.isSensitiveData = toggleValue($scope.model.property.isSensitiveData); + } + function toggleLabelOnTop() { + $scope.model.property.labelOnTop = toggleValue($scope.model.property.labelOnTop); } onInit(); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/propertysettings/propertysettings.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/propertysettings/propertysettings.html index af9295f1ed..482345c3b3 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/propertysettings/propertysettings.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/propertysettings/propertysettings.html @@ -135,6 +135,21 @@ ng-if="vm.showValidationPattern" ng-keypress="vm.submitOnEnter($event)" /> + +
    + +
    + + + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/querybuilder/querybuilder.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/querybuilder/querybuilder.html index 37a0d41207..b7a39261e9 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/querybuilder/querybuilder.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/querybuilder/querybuilder.html @@ -33,9 +33,9 @@ - + @@ -69,11 +69,11 @@ show-caret="true"> - + - + @@ -90,9 +90,9 @@ - + @@ -112,13 +112,13 @@ - + - +
    @@ -137,9 +137,9 @@ - + @@ -159,7 +159,7 @@ diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.controller.js index fb8b649b55..0d49c7dd9c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.controller.js @@ -2,7 +2,7 @@ "use strict"; function RollbackController($scope, contentResource, localizationService, assetsService, dateHelper, userService) { - + var vm = this; vm.rollback = rollback; @@ -56,7 +56,7 @@ }); }); - + } function changeLanguage(language) { @@ -103,7 +103,7 @@ var timestampFormatted = dateHelper.getLocalDate(version.versionDate, currentUser.locale, 'LLL'); version.displayValue = timestampFormatted + ' - ' + version.versionAuthorName; return version; - }); + }); }); }); } @@ -146,7 +146,7 @@ var diffProperty = { "alias": property.alias, "label": property.label, - "diff": JsDiff.diffWords(property.value, oldProperty.value), + "diff": (property.isObject) ? JsDiff.diffJson(property.value, oldProperty.value) : JsDiff.diffWords(property.value, oldProperty.value), "isObject": (property.isObject || oldProperty.isObject) ? true : false }; @@ -163,7 +163,7 @@ const nodeId = $scope.model.node.id; const versionId = vm.previousVersion.versionId; - const culture = $scope.model.node.variants.length > 1 ? vm.currentVersion.language.culture : null; + const culture = $scope.model.node.variants.length > 1 ? vm.currentVersion.language.culture : null; return contentResource.rollback(nodeId, versionId, culture) .then(data => { diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/sectionpicker/sectionpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/sectionpicker/sectionpicker.controller.js index 55d7a75000..f2a89ba7ea 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/sectionpicker/sectionpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/sectionpicker/sectionpicker.controller.js @@ -47,8 +47,8 @@ } function preSelect(selection) { - angular.forEach(selection, function(selected){ - angular.forEach(vm.sections, function(section){ + selection.forEach(function(selected){ + vm.sections.forEach(function(section){ if(selected.alias === section.alias) { section.selected = true; } @@ -65,7 +65,7 @@ } else { - angular.forEach($scope.model.selection, function(selectedSection, index){ + $scope.model.selection.forEach(function(selectedSection, index){ if(selectedSection.alias === section.alias) { section.selected = false; $scope.model.selection.splice(index, 1); @@ -77,7 +77,7 @@ } function setSectionIcon(sections) { - angular.forEach(sections, function(section) { + sections.forEach(function(section) { section.icon = "icon-section"; }); } diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/sectionpicker/sectionpicker.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/sectionpicker/sectionpicker.html index 33906dcd75..feb3b9ab53 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/sectionpicker/sectionpicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/sectionpicker/sectionpicker.html @@ -19,12 +19,12 @@ diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/templatesections/templatesections.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/templatesections/templatesections.html index 045a1403e2..b4e8d7fbe8 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/templatesections/templatesections.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/templatesections/templatesections.html @@ -45,7 +45,9 @@
    - + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/treepicker/treepicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/treepicker/treepicker.controller.js index 387dd71da8..0c5fe9af1b 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/treepicker/treepicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/treepicker/treepicker.controller.js @@ -173,7 +173,7 @@ angular.module("umbraco").controller("Umbraco.Editors.TreePickerController", $scope.model.filterAdvanced = false; //used advanced filtering - if (angular.isFunction($scope.model.filter)) { + if (Utilities.isFunction($scope.model.filter)) { $scope.model.filterAdvanced = true; } else if (Utilities.isObject($scope.model.filter)) { @@ -188,8 +188,17 @@ angular.module("umbraco").controller("Umbraco.Editors.TreePickerController", //used advanced filtering if ($scope.model.filter.startsWith("{")) { $scope.model.filterAdvanced = true; - //convert to object - $scope.model.filter = Utilities.fromJson($scope.model.filter); + + if ($scope.model.filterByMetadata && !Utilities.isFunction($scope.model.filter)) + { + var filter = Utilities.fromJson($scope.model.filter); + $scope.model.filter = function (node){ return _.isMatch(node.metaData, filter);}; + } + else + { + //convert to object + $scope.model.filter = Utilities.fromJson($scope.model.filter); + } } } } @@ -274,19 +283,14 @@ angular.module("umbraco").controller("Umbraco.Editors.TreePickerController", if (Utilities.isArray(args.children)) { //iterate children - _.each(args.children, - function (child) { - - //now we need to look in the already selected search results and - // toggle the check boxes for those ones that are listed - var exists = _.find(vm.searchInfo.selectedSearchResults, - function (selected) { - return child.id == selected.id; - }); - if (exists) { - child.selected = true; - } - }); + args.children.forEach(child => { + //now we need to look in the already selected search results and + // toggle the check boxes for those ones that are listed + var exists = vm.searchInfo.selectedSearchResults.find(selected => child.id === selected.id); + if (exists) { + child.selected = true; + } + }); //check filter performFiltering(args.children); @@ -452,12 +456,11 @@ angular.module("umbraco").controller("Umbraco.Editors.TreePickerController", if ($scope.model.filterAdvanced) { //filter either based on a method or an object - var filtered = angular.isFunction($scope.model.filter) + var filtered = Utilities.isFunction($scope.model.filter) ? _.filter(nodes, $scope.model.filter) : _.where(nodes, $scope.model.filter); - angular.forEach(filtered, - function (value, key) { + filtered.forEach(function (value) { value.filtered = true; if ($scope.model.filterCssClass) { if (!value.cssClasses) { @@ -470,8 +473,7 @@ angular.module("umbraco").controller("Umbraco.Editors.TreePickerController", } else { var a = $scope.model.filter.toLowerCase().replace(/\s/g, '').split(','); - angular.forEach(nodes, - function (value, key) { + nodes.forEach(function (value) { var found = a.indexOf(value.metaData.contentType.toLowerCase()) >= 0; @@ -541,78 +543,66 @@ angular.module("umbraco").controller("Umbraco.Editors.TreePickerController", //we need to ensure that any currently displayed nodes that get selected // from the search get updated to have a check box! function checkChildren(children) { - _.each(children, - function (child) { - //check if the id is in the selection, if so ensure it's flagged as selected - var exists = _.find(vm.searchInfo.selectedSearchResults, - function (selected) { - return child.id == selected.id; + children.forEach(child => { + //check if the id is in the selection, if so ensure it's flagged as selected + var exists = vm.searchInfo.selectedSearchResults.find(selected => child.id === selected.id); + //if the curr node exists in selected search results, ensure it's checked + if (exists) { + child.selected = true; + } + //if the curr node does not exist in the selected search result, and the curr node is a child of a list view search result + else if (child.metaData.isSearchResult) { + //if this tree node is under a list view it means that the node was added + // to the tree dynamically under the list view that was searched, so we actually want to remove + // it all together from the tree + var listView = child.parent(); + listView.children = _.reject(listView.children, + function (c) { + return c.id == child.id; }); - //if the curr node exists in selected search results, ensure it's checked - if (exists) { - child.selected = true; - } - //if the curr node does not exist in the selected search result, and the curr node is a child of a list view search result - else if (child.metaData.isSearchResult) { - //if this tree node is under a list view it means that the node was added - // to the tree dynamically under the list view that was searched, so we actually want to remove - // it all together from the tree - var listView = child.parent(); - listView.children = _.reject(listView.children, - function (c) { - return c.id == child.id; - }); - } + } - //check if the current node is a list view and if so, check if there's any new results - // that need to be added as child nodes to it based on search results selected - if (child.metaData.isContainer) { + //check if the current node is a list view and if so, check if there's any new results + // that need to be added as child nodes to it based on search results selected + if (child.metaData.isContainer) { - child.cssClasses = _.reject(child.cssClasses, - function (c) { - return c === 'tree-node-slide-up-hide-active'; - }); + child.cssClasses = _.reject(child.cssClasses, + function (c) { + return c === 'tree-node-slide-up-hide-active'; + }); - var listViewResults = _.filter(vm.searchInfo.selectedSearchResults, - function (i) { - return i.parentId == child.id; - }); - _.each(listViewResults, - function (item) { - var childExists = _.find(child.children, - function (c) { - return c.id == item.id; - }); - if (!childExists) { - var parent = child; - child.children.unshift({ - id: item.id, - name: item.name, - cssClass: "icon umb-tree-icon sprTree " + item.icon, - level: child.level + 1, - metaData: { - isSearchResult: true - }, - hasChildren: false, - parent: function () { - return parent; - } - }); - } - }); - } + var listViewResults = vm.searchInfo.selectedSearchResults.filter(i => i.parentId === child.id); - //recurse - if (child.children && child.children.length > 0) { - checkChildren(child.children); - } - }); + listViewResults.forEach(item => { + var childExists = child.children.find(c => c.id === item.id); + + if (!childExists) { + var parent = child; + child.children.unshift({ + id: item.id, + name: item.name, + cssClass: "icon umb-tree-icon sprTree " + item.icon, + level: child.level + 1, + metaData: { + isSearchResult: true + }, + hasChildren: false, + parent: () => parent + }); + } + }); + } + + //recurse + if (child.children && child.children.length > 0) { + checkChildren(child.children); + } + }); } checkChildren(tree.root.children); } - vm.searchInfo.showSearch = false; vm.searchInfo.searchFromId = vm.startNodeId; vm.searchInfo.searchFromName = null; @@ -625,24 +615,16 @@ angular.module("umbraco").controller("Umbraco.Editors.TreePickerController", performFiltering(results); //now actually remove all filtered items so they are not even displayed - results = _.filter(results, - function (item) { - return !item.filtered; - }); - + results = results.filter(item => !item.filtered); vm.searchInfo.results = results; //sync with the curr selected results - _.each(vm.searchInfo.results, - function (result) { - var exists = _.find($scope.model.selection, - function (item) { - return result.id == item.id; - }); - if (exists) { - result.selected = true; - } - }); + vm.searchInfo.results.forEach(result => { + var exists = $scope.model.selection.find(item => result.id === item.id); + if (exists) { + result.selected = true; + } + }); vm.searchInfo.showSearch = true; } @@ -664,12 +646,8 @@ angular.module("umbraco").controller("Umbraco.Editors.TreePickerController", } function listViewItemsLoaded(items) { - var selectedIds = _.pluck($scope.model.selection, "id"); - _.each(items, function (item) { - if (_.contains(selectedIds, item.id)) { - item.selected = true; - } - }); + var selectedIds = $scope.model.selection.map(x => x.id); + items.forEach(item => item.selected = selectedIds.includes(item.id)); } function submit(model) { diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/treepicker/treepicker.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/treepicker/treepicker.html index 0c10d94136..522301d76f 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/treepicker/treepicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/treepicker/treepicker.html @@ -19,18 +19,17 @@
    -
    -
    {{vm.selectedLanguage.name}}
    -   -
    +
    - {{language.name}} +
    -
    +
    + + + - + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/userpicker/userpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/userpicker/userpicker.controller.js index 2bd73a5558..a7021b2867 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/userpicker/userpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/userpicker/userpicker.controller.js @@ -1,7 +1,7 @@ (function () { "use strict"; - function UserPickerController($scope, usersResource, localizationService) { + function UserPickerController($scope, usersResource, localizationService, eventsService) { var vm = this; @@ -15,15 +15,18 @@ vm.submit = submit; vm.close = close; - ////////// + vm.multiPicker = $scope.model.multiPicker === false ? false : true; function onInit() { vm.loading = true; // set default title - if(!$scope.model.title) { - localizationService.localize("defaultdialogs_selectUsers").then(function(value){ + if (!$scope.model.title) { + + var labelKey = vm.multiPicker ? "defaultdialogs_selectUsers" : "defaultdialogs_selectUser"; + + localizationService.localize(labelKey).then(function(value){ $scope.model.title = value; }); } @@ -35,12 +38,11 @@ // get users getUsers(); - } function preSelect(selection, users) { - angular.forEach(selection, function(selected){ - angular.forEach(users, function(user){ + Utilities.forEach(selection, function(selected){ + Utilities.forEach(users, function(user){ if(selected.id === user.id) { user.selected = true; } @@ -50,22 +52,39 @@ function selectUser(user) { - if(!user.selected) { - + if (!user.selected) { user.selected = true; $scope.model.selection.push(user); - } else { - angular.forEach($scope.model.selection, function(selectedUser, index){ - if(selectedUser.id === user.id) { - user.selected = false; - $scope.model.selection.splice(index, 1); + if (user.selected) { + Utilities.forEach($scope.model.selection, function (selectedUser, index) { + if (selectedUser.id === user.id) { + user.selected = false; + $scope.model.selection.splice(index, 1); + } + }); + } else { + if (!vm.multiPicker) { + deselectAllUsers($scope.model.selection); } - }); - + eventsService.emit("dialogs.userPicker.select", user); + user.selected = true; + $scope.model.selection.push(user); + } } + if (!vm.multiPicker) { + submit($scope.model); + } + } + + function deselectAllUsers(users) { + for (var i = 0; i < users.length; i++) { + var user = users[i]; + user.selected = false; + } + users.length = 0; } var search = _.debounce(function () { @@ -95,7 +114,6 @@ preSelect($scope.model.selection, vm.users); vm.loading = false; - }); } @@ -105,14 +123,14 @@ } function submit(model) { - if($scope.model.submit) { + if ($scope.model.submit) { $scope.model.submit(model); } } function close() { - if($scope.model.close) { - $scope.model.close(); + if ($scope.model.close) { + $scope.model.close(); } } diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/userpicker/userpicker.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/userpicker/userpicker.html index e39d693b47..4e1c6b8e6e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/userpicker/userpicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/userpicker/userpicker.html @@ -9,32 +9,30 @@ hide-icon="true" hide-description="true"> - + - + - + + + - +
    - +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/confirm/confirm.html b/src/Umbraco.Web.UI.Client/src/views/common/overlays/confirm/confirm.html index 79dd6bd4f6..f35c24e96a 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/confirm/confirm.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/confirm/confirm.html @@ -1,13 +1,13 @@ 
    +

    +
    - {{model.confirmMessage}} + 'umb-alert--info': model.confirmMessageStyle === 'info'}" + ng-bind-html="model.confirmMessage">
    -

    {{model.content}}

    -
    diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/itempicker/itempicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/overlays/itempicker/itempicker.controller.js index 17184ae9a3..c4f60b766a 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/itempicker/itempicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/itempicker/itempicker.controller.js @@ -1,5 +1,9 @@ function ItemPickerOverlay($scope, localizationService) { + $scope.filter = { + searchTerm: '' + }; + function onInit() { $scope.model.hideSubmitButton = true; @@ -24,11 +28,11 @@ function ItemPickerOverlay($scope, localizationService) { event: null }; - $scope.showTooltip = function(item, $event) { + $scope.showTooltip = function (item, $event) { if (!item.tooltip) { - $scope.mouseLeave(); return; } + $scope.tooltip = { show: true, event: $event, diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/itempicker/itempicker.html b/src/Umbraco.Web.UI.Client/src/views/common/overlays/itempicker/itempicker.html index ff2ce6379f..7074497a98 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/itempicker/itempicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/itempicker/itempicker.html @@ -1,9 +1,8 @@
    - - @@ -86,7 +92,8 @@
-
+ +
Change password diff --git a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-app-header.html b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-app-header.html index c6c4f98e25..7271294a09 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-app-header.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-app-header.html @@ -14,7 +14,7 @@ Open backoffice search... - +
  • @@ -22,7 +22,7 @@ Open/Close backoffice help... - +
  • @@ -45,12 +45,4 @@
  • - - -
    diff --git a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-contextmenu.html b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-contextmenu.html index 820ef4a886..0aa2e258a3 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-contextmenu.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-contextmenu.html @@ -1,16 +1,24 @@ -
    -
    -

    {{menuDialogTitle}}

    -
    +