diff --git a/.angular-cli.json b/.angular-cli.json index 0e3fb2b06..035ee9939 100644 --- a/.angular-cli.json +++ b/.angular-cli.json @@ -37,7 +37,7 @@ "test": "test.ts", "tsconfig": "tsconfig.app.json", "testTsconfig": "tsconfig.spec.json", - "prefix": "app", + "prefix": "aca", "styles": [ "./assets/fonts/material-icons/material-icons.css", "./assets/fonts/muli/muli.css", @@ -46,7 +46,8 @@ "scripts": [ "../node_modules/pdfjs-dist/build/pdf.js", "../node_modules/pdfjs-dist/lib/shared/compatibility.js", - "../node_modules/pdfjs-dist/web/pdf_viewer.js" + "../node_modules/pdfjs-dist/web/pdf_viewer.js", + "../node_modules/moment/min/moment.min.js" ], "environmentSource": "environments/environment.ts", "environments": { diff --git a/.circleci/config.yml b/.circleci/config.yml index bef30d031..309640f2d 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,6 +1,7 @@ version: 2 + jobs: - build: + test: working_directory: ~/alfresco-content-app docker: - image: circleci/node:8-browsers @@ -14,3 +15,48 @@ jobs: paths: - "node_modules" - run: xvfb-run -a npm run test:ci + lint: + working_directory: ~/alfresco-content-app + docker: + - image: circleci/node:8-browsers + steps: + - checkout + - restore_cache: + key: alfresco-content-app-{{ .Branch }}-{{ checksum "package.json" }} + - run: npm install + - run: npm run lint + spellcheck: + working_directory: ~/alfresco-content-app + docker: + - image: circleci/node:8-browsers + steps: + - checkout + - restore_cache: + key: alfresco-content-app-{{ .Branch }}-{{ checksum "package.json" }} + - run: npm install + - run: npm run spellcheck + build: + working_directory: ~/alfresco-content-app + docker: + - image: circleci/node:8-browsers + steps: + - checkout + - restore_cache: + key: alfresco-content-app-{{ .Branch }}-{{ checksum "package.json" }} + - run: npm install + - run: npm run build + +workflows: + version: 2 + build_and_test: + jobs: + - test + - lint: + requires: + - test + - spellcheck: + requires: + - test + - build: + requires: + - test diff --git a/.env b/.env deleted file mode 100644 index c0235eaab..000000000 --- a/.env +++ /dev/null @@ -1,5 +0,0 @@ -ALFRESCO_TAG=6.0.4-ea -SHARE_TAG=6.0.a -SOLR6_TAG=1.1.0 -POSTGRES_TAG=10.1 -ACA_TAG=development diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 7f29dd9c0..5ccd54108 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,10 +1,11 @@ ## PR Checklist Please check if your PR fulfills the following requirements: +``` - [ ] The commit message follows our guidelines: https://github.com/Alfresco/alfresco-content-app/blob/master/CONTRIBUTING.md#commit - [ ] Tests for the changes have been added (for bug fixes / features) - [ ] Docs have been added / updated (for bug fixes / features) - +``` ## PR Type What kind of change does this PR introduce? diff --git a/.gitignore b/.gitignore index c5d885dad..e2df279f2 100644 --- a/.gitignore +++ b/.gitignore @@ -34,6 +34,7 @@ npm-debug.log testem.log /typings +/www # e2e /e2e/*.js diff --git a/.travis.yml b/.travis.yml index 11e39bebb..f97f4facd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,13 +1,24 @@ dist: trusty sudo: required +services: + - docker + +addons: + chrome: stable + language: node_js node_js: - "8" +before_script: + # Disable services enabled by default + - sudo /etc/init.d/postgresql stop + install: - npm install -g npm@latest - npm ci script: - - npm run test:ci + # - docker-compose stop + - npm run build && npm run e2e:docker diff --git a/.vscode/settings.json b/.vscode/settings.json index acadd47de..831fd7c98 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,6 @@ { - "cSpell.words": [ - "sidenav" - ] -} \ No newline at end of file + "javascript.preferences.quoteStyle": "single", + "typescript.preferences.quoteStyle": "single", + "javascript.preferences.importModuleSpecifier": "relative", + "typescript.preferences.importModuleSpecifier": "relative" +} diff --git a/Dockerfile b/Dockerfile index 423c40099..3ce9f8c7b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ FROM nginx:alpine -LABEL version="1.2" +LABEL version="1.3" LABEL maintainer="Denys Vuika " COPY nginx.conf /etc/nginx/nginx.conf diff --git a/README.md b/README.md index fa4065e22..35743a0f9 100644 --- a/README.md +++ b/README.md @@ -1,37 +1,54 @@ +

Alfresco - make business flow

+ # Alfresco Example Content Application -

- Alfresco -

- ## Introduction The Alfresco Content Application is an example application built using -[Alfresco Application Development Framework (ADF)](https://github.com/Alfresco/alfresco-ng2-components) components and was generated with [Angular CLI](https://github.com/angular/angular-cli) version 1.6.6. +[Alfresco Application Development Framework (ADF)](https://github.com/Alfresco/alfresco-ng2-components) components and was generated with [Angular CLI](https://github.com/angular/angular-cli). ### Who is this example application for -This example application demonstrates to Angular software engineers -how to construct a content application using the Alfresco ADF. +This project demonstrates how to construct an application for Alfresco Content Services using the Alfresco ADF and it represents a meaningful composition of ADF components that provide end users with a simple easy to use interface for working with files in the content repository. -This example application represents a meaningful composition of ADF components that provide end users -with a simple and easy to use interface for working with files stored in the Alfresco Content Services repository. +### Where to get help +There are a number of resources available to help get you started with the Content App and the ADF: +* [Content App Documentation](https://alfresco.github.io/alfresco-content-app/) +* [Alfresco ADF Documentation](https://alfresco.github.io/adf-component-catalog/) +* [Alfresco Community](https://community.alfresco.com/) +* [ADF Gitter Channel](https://gitter.im/Alfresco/alfresco-ng2-components) -[Public documentation](https://alfresco.github.io/alfresco-content-app/) +To get help on Angular CLI use ng help or read the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md). ### Raising issues and feature requests +Isuses can be raised in GitHub or in the Alfresco JIRA project. +Please include a clear description, steps to reproduce and screenshots where appropriate.All issues will be reviewed; bugs will be categorized if reproducible and enhancement/feature suggestions will be considered against existing priorities if the use case serves a general-purpose need. -Log any issues in the ['ACA' JIRA project](https://issues.alfresco.com/jira/projects/ACA), -please include a clear description, steps to reproduce and screenshots where appropriate. +#### High level features planned for Q3 2018 (July - September) +* Library Management - create, find, join and manage file libraries. +* Commenting - View and add comments to files and folders +* Sharing Files - activate and deactivate shared file both manually and automatically. +* Permissions - update file and folder permissions. +* Application Extensibility - Extension framework to provide simple ways to extend the application. -All issues will be reviewed; bugs will be categorized if reproducible and enhancement/feature suggestions -will be considered against existing priorities if the use case serves a general-purpose need. +### Want to help? +Want to file a bug, contribute some code, or improve documentation? Excellent! Read up on our guidelines for [contributing](https://github.com/Alfresco/alfresco-content-app/blob/master/CONTRIBUTING.md) and then check out one of our issues in the [Jira](https://issues.alfresco.com/jira/projects/ACA) or [GitHub](https://github.com/Alfresco/alfresco-content-app/issues) -## Want to help? +## Available Features +| Feature | Description | +|------------------|----------------------------------------------------------------| +| Document List | Folder/File browsing of Personal Files, and File Libraries | +| Shared Files | Lists all files that have shared. | +| Recent Files | List files created and/or modified by the logged users within the last 30 days| +| Favorites | Lists all favorited files for the user. | +| Trash | Lists all deleted items stored in the trash can, users can restore or permanently remove. Admin user will see items deleted by all users.| +| Upload | Files and folders can be uploaded through the New button or by dragging and dropping into the browser.| +| Search | Quick search with live results, and full faceted search results page.| +| Actions | A number of actions can be performed on files and/or folders, either individually or multiples at a time| +| Viewer | Viewing files in natively in the browser, unsupported formats are transformed by the repository | +| Metadata | The information drawer can be configured in the app.config.json to display metadata information, by default file the Properties Aspect is shown and images will also include EXIF information.| +| Versioning | The version manager provides access and management of previous file versions, and the ability to upload new versions.| -Want to file a bug, contribute some code, or improve documentation? Excellent! -Read up on our guidelines for [contributing][contributing] -and then check out one of our issues in the [Jira][jira] or [GitHub][github] ## Development server @@ -40,21 +57,38 @@ The app will automatically reload if you change any of the source files. ## Build -Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. -Use the `--prod` flag for a production build. +Run `npm run build` to build the project in the production mode. The build artifacts will be stored in the `dist/` directory. ## Running unit tests -Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). +Run `npm test` to execute the unit tests via [Karma](https://karma-runner.github.io). ## Running end-to-end tests -Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/). +Run the local instance of the application packaged into the docker image together with the ACS images: + +```sh +npm run build +npm run start:docker +``` + +The ACA runs on port 4000 inside the docker container. +Run `npm run e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/). + +```sh +npm run e2e +``` + +When testing is over you can stop all corresponding containers: + +```sh +npm run stop:docker +``` ## Further help To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md). -[contributing]: ttps://github.com/Alfresco/alfresco-content-app/blob/master/CONTRIBUTING.md +[contributing]: https://github.com/Alfresco/alfresco-content-app/blob/master/CONTRIBUTING.md [github]: https://github.com/Alfresco/alfresco-content-app/issues [jira]: https://issues.alfresco.com/jira/projects/ACA diff --git a/alfresco.png b/alfresco.png index 7f6326b0b..fc2bcdecc 100644 Binary files a/alfresco.png and b/alfresco.png differ diff --git a/cspell.json b/cspell.json new file mode 100644 index 000000000..6401bf8bd --- /dev/null +++ b/cspell.json @@ -0,0 +1,44 @@ +{ + "version": "0.1", + "language": "en", + "words": [ + "succes", + + "ngrx", + "ngstack", + "sidenav", + "injectable", + "truthy", + "cryptodoc", + "mysites", + "afts", + "classlist", + "folderlink", + "filelink", + "datatable", + "repo", + "snackbar", + "promisify", + "xdescribe", + "unfavorite", + "Snackbar", + "devtools", + + "unshare", + "validators", + "guid", + "polyfill", + "polyfills", + "jsonp", + "hammerjs", + "pdfjs", + "xpath", + "tooltip", + "tooltips", + "unindent", + "exif" + ], + "dictionaries": [ + "html" + ] +} diff --git a/docker-compose.yml b/docker-compose.yml index 012911596..4d2ac0f0f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,13 +2,10 @@ version: "3" services: alfresco: - image: alfresco/alfresco-content-repository-community:${ALFRESCO_TAG} + image: alfresco/alfresco-content-repository-community:6.0.7-ga depends_on: - postgres environment: - CATALINA_OPTS : " - -agentlib:jdwp=transport=dt_socket,address=8000,server=y,suspend=n - " JAVA_OPTS : " -Ddb.driver=org.postgresql.Driver -Ddb.username=alfresco @@ -19,15 +16,17 @@ services: -Dsolr.secureComms=none -Dsolr.base.url=/solr -Dindex.subsystem.name=solr6 + -Dshare.host=localhost + -Ddeployment.method=DOCKER_COMPOSE + -Dcsrf.filter.enabled=false " networks: - internal ports: - 8080:8080 #Browser port - - 8000:8000 #Debug port share: - image: alfresco/alfresco-share:${SHARE_TAG} + image: alfresco/alfresco-share:6.0.b depends_on: - alfresco environment: @@ -39,18 +38,19 @@ services: - 8081:8080 postgres: - image: postgres:${POSTGRES_TAG} + image: postgres:10.1 environment: - POSTGRES_PASSWORD=alfresco - POSTGRES_USER=alfresco - POSTGRES_DB=alfresco + command: postgres -c max_connections=300 -c log_min_messages=LOG networks: - internal ports: - 5432:5432 solr6: - image: alfresco/alfresco-search-services:${SOLR6_TAG} + image: alfresco/alfresco-search-services:1.1.1 depends_on: - alfresco environment: @@ -68,14 +68,14 @@ services: - 8983:8983 #Browser port content-app: - image: alfresco/alfresco-content-app:${ACA_TAG} + image: alfresco/alfresco-content-app:latest build: . depends_on: - alfresco networks: - internal ports: - - 3001:80 + - 4001:80 # volumes: # - ./app.config.json:/usr/share/nginx/html/app.config.json # - ./nginx.conf:/etc/nginx/conf.d/default.conf @@ -83,13 +83,13 @@ services: proxy: image: nginx depends_on: - - content-app + - content-app volumes: - - ./docker-compose/nginx.conf:/etc/nginx/conf.d/default.conf + - ./docker-compose/nginx.conf:/etc/nginx/conf.d/default.conf networks: - - internal + - internal ports: - - 3000:80 + - 4000:80 networks: internal: diff --git a/docker-compose/.env b/docker-compose/.env deleted file mode 100644 index c0235eaab..000000000 --- a/docker-compose/.env +++ /dev/null @@ -1,5 +0,0 @@ -ALFRESCO_TAG=6.0.4-ea -SHARE_TAG=6.0.a -SOLR6_TAG=1.1.0 -POSTGRES_TAG=10.1 -ACA_TAG=development diff --git a/docker-compose/docker-compose.yml b/docker-compose/docker-compose.yml index b6153d718..9e553cb29 100644 --- a/docker-compose/docker-compose.yml +++ b/docker-compose/docker-compose.yml @@ -2,13 +2,10 @@ version: "3" services: alfresco: - image: alfresco/alfresco-content-repository-community:${ALFRESCO_TAG} + image: alfresco/alfresco-content-repository-community:6.0.7-ga depends_on: - postgres environment: - CATALINA_OPTS : " - -agentlib:jdwp=transport=dt_socket,address=8000,server=y,suspend=n - " JAVA_OPTS : " -Ddb.driver=org.postgresql.Driver -Ddb.username=alfresco @@ -19,15 +16,17 @@ services: -Dsolr.secureComms=none -Dsolr.base.url=/solr -Dindex.subsystem.name=solr6 + -Dshare.host=localhost + -Ddeployment.method=DOCKER_COMPOSE + -Dcsrf.filter.enabled=false " networks: - internal ports: - 8080:8080 #Browser port - - 8000:8000 #Debug port share: - image: alfresco/alfresco-share:${SHARE_TAG} + image: alfresco/alfresco-share:6.0.b depends_on: - alfresco environment: @@ -39,18 +38,19 @@ services: - 8081:8080 postgres: - image: postgres:${POSTGRES_TAG} + image: postgres:10.1 environment: - POSTGRES_PASSWORD=alfresco - POSTGRES_USER=alfresco - POSTGRES_DB=alfresco + command: postgres -c max_connections=300 -c log_min_messages=LOG networks: - internal ports: - 5432:5432 solr6: - image: alfresco/alfresco-search-services:${SOLR6_TAG} + image: alfresco/alfresco-search-services:1.1.1 depends_on: - alfresco environment: @@ -68,7 +68,7 @@ services: - 8983:8983 #Browser port content-app: - image: alfresco/alfresco-content-app:${ACA_TAG} + image: alfresco/alfresco-content-app:master depends_on: - alfresco networks: diff --git a/docs/README.md b/docs/README.md index 472b78e36..48833ac89 100644 --- a/docs/README.md +++ b/docs/README.md @@ -17,35 +17,395 @@ with a simple and easy to use interface for working with files stored in the Alf This application uses the latest releases from Alfresco: -- [Alfresco ADF version 2.3](https://community.alfresco.com/community/application-development-framework/pages/get-started) -- [Alfresco Content Services version 5.2.3](https://www.alfresco.com/platform/content-services-ecm) -- [Alfresco Community Edition 201802 EA](https://www.alfresco.com/products/community/download) +- [Alfresco ADF (2.4.0)](https://community.alfresco.com/community/application-development-framework/pages/get-started) +- [Alfresco Content Services (5.2.3)](https://www.alfresco.com/platform/content-services-ecm) + or [Alfresco Community Edition (201802 EA)](https://www.alfresco.com/products/community/download)

-You also need node.js (8.9.1 or later) installed to build it locally from source code. +You also need node.js (LTS) installed to build it locally from source code.

The latest version of the Alfresco Content platform is required due to the application using the latest [REST APIs](https://docs.alfresco.com/5.2/pra/1/topics/pra-welcome.html) developments. -## Contribution Policy +## Features -### How to contribute +The concept of this example is a simple user interface which makes accessing files in the Alfresco Content Services repository easy. -Fork our repository and submit a pull request when your code is ready for review. -To be considered the Travis build must be green and all our automation tests must run without regressions. +Often Content Management systems provide more capabilities out of the box than most users need; +providing too many capabilities to these users prevents them from working efficiently, +so they may end up using unsanctioned file management solutions which presents a proliferation of content storage +and collaboration solutions as well as compliance issues for organizations. -### Contribute to the existing code base +This application demonstrates how the complexity of Content Management can be simplified +using the Alfresco Application Development Framework to easily and quickly create custom solutions for specific user cases. -What are we reviewing for? +### User Interface - layout -- **License**: Every file should contain the Alfresco LICENSE header, LGPL Licence. -- **Tests**: Add unit cases to cover the new behavior, and make sure all the existing tests are still green. -- **JS Documentation**: Every class needs to have its own inline jsdoc, this documentation should explain the general purpose of the class and of each method. -- **Documentation**: Update the documentation explaining how to use the new functionality, may not be necessary in the cases where change impacts only the CSS style. -- **Clean Coding**: Some good rules are enforced by the tslint, but we want also our code to be easy to read. Please avoid comments inside the code or leaving pieces of code commented out. -- **Localization**: Your contribution needs to support localization, with all new strings externalized, all translations are inside the i18n. The minimum requirement is English. +There are three main areas of the application controlled by the [Layout component](https://github.com/Alfresco/alfresco-content-app/tree/master/src/app/components/layout): + +- [(1) Application Header](#header) +- [(2) Side Navigation](#side-navigation) +- [(3) Document List](#document-list-layout) + +![Features](images/features-01.png) + +### Header + +The application [header](https://github.com/Alfresco/alfresco-content-app/tree/master/src/app/components/header) has three main elements. + +1. [Logo and Color](#logo-and-color) +2. [Search](#search) +3. [Current User](#current-user) + +![Header](images/header.png) + +#### Logo and Color + +Logo & app primary color - logo and color are configurable by updating the +[app.config.json](https://github.com/Alfresco/alfresco-content-app/blob/master/src/app.config.json) file in the root folder of the project. +Please refer to the [Application Configuration](/getting-started#application-logo) documentation for more information on how to change the logo and color. + +#### Search + +The application [Search](https://github.com/Alfresco/alfresco-content-app/tree/master/src/app/components/search) - +uses the [ADF Search Component](https://github.com/Alfresco/alfresco-ng2-components/tree/master/lib/content-services/search) +the app provides a 'live' search feature, where users can open files and folders directly from the Search API results. + +![Search Input](images/search.png) + +If you type `Enter` in the text input area, you are going to see [Search Results](#search-results) page +with advanced filtering and faceted search. + +#### Current User + +[Current User](https://github.com/Alfresco/alfresco-content-app/tree/development/src/app/components/current-user) - +displays the user's name, and a menu where users can logout. +Optionally through updating the [app.config.json](https://github.com/Alfresco/alfresco-content-app/blob/master/src/app.config.json) +a language switching menu can be displayed. + +![Current User](images/current-user.png) + +### Side Navigation + +The application [side navigation](https://github.com/Alfresco/alfresco-content-app/tree/master/src/app/components/sidenav) has two features: +a button menu and navigation links. + +![Side Navigation](images/side-nav.png) + +#### New button + +The New button displays a menu which provides three actions: + +- Create a new folder - provides a dialog which allows the creation of a new folder, the folder name is mandatory and the description is optional. +- Upload a file - invokes the operating system file browser and allows a user to select file(s) to upload into their current location in the content repository. +- Upload a folder - invokes the operating system folder browser and allows a user to select a folder to upload to their current location in the content repository. + +When an upload starts the [upload component](https://github.com/Alfresco/alfresco-ng2-components/tree/master/lib/content-services/upload) +is displayed which shows the user the progress of the uploads they have started. +The upload dialog persists on the screen and can be minimized; users are able to continue using the application whilst uploads are in progress +and uploads can be canceled which will stop uploads in progress or permanently delete already completed uploads. + +![Uploader](images/uploader.png) + +#### Navigation + +The navigation links are configurable via the [app.config.json](https://github.com/Alfresco/alfresco-content-app/blob/master/src/app.config.json). +Default configuration creates two sections. +See [Navigation](/getting-started#navigation) for more information about configuring the side navigation. + +### Document List Layout + +The main area of the application is composed of several individual ADF components: + +- (1) [Breadcrumb](https://alfresco.github.io/adf-component-catalog/components/BreadcrumbComponent.html) +- (2) [Toolbar](https://alfresco.github.io/adf-component-catalog/components/ToolbarComponent.html) +- (3) [Document List](https://alfresco.github.io/adf-component-catalog/components/DocumentListComponent.html) +- (4) [Pagination](https://alfresco.github.io/adf-component-catalog/components/PaginationComponent.html) + +![](images/doclist.png) + +The application has seven different Document List views which share commonalities between each view and subtle differences depending on the content being loaded which are explained below. + +#### Personal Files + +Personal Files retrieves all content from the logged in user's home area (`/User Homes//`) in the repository; +if the user is ‘admin’ who does not have a home folder then the repository root folder is shown. + +Personal Files is the [Files](https://github.com/Alfresco/alfresco-content-app/tree/master/src/app/components/files) component, +using the [Nodes API](https://api-explorer.alfresco.com/api-explorer/#/nodes). + +#### File Libraries + +File Libraries retrieves all the sites that the user is a member of including what type of site it is: public, moderated or private. +File Libraries is the [Libraries](https://github.com/Alfresco/alfresco-content-app/tree/master/src/app/components/libraries) component, +using the [Sites API](https://api-explorer.alfresco.com/api-explorer/#/sites). + +When a user opens one of their sites then the content for the site's document library is shown. +To display the files and folders from a site (`/Sites//Document Library/`) the [Files](https://github.com/Alfresco/alfresco-content-app/tree/master/src/app/components/files) component, +using the [Nodes API](https://api-explorer.alfresco.com/api-explorer/#/nodes) is used. + +#### Shared Files + +The Shared Files view aggregates all files that have been shared using the QuickShare feature in the content repository. +The [Shared Files](https://github.com/Alfresco/alfresco-content-app/tree/master/src/app/components/shared-files) component uses the [shared-links API](https://api-explorer.alfresco.com/api-explorer/#/shared-links) +and includes extra columns to display where the file is +[located](https://github.com/Alfresco/alfresco-content-app/tree/master/src/app/components/location-link) +in the content repository and who created the shared link. + +A feature for creating and removing Shared Links will be added in the future. + +#### Recent Files + +The Recent Files view shows all the files that have been created or modified within the last 30 days by the current user. +The [Recent Files](https://github.com/Alfresco/alfresco-content-app/tree/master/src/app/components/recent-files) +component uses the Search API to query SOLR for changes made by the user and includes an extra column to display where the file is +[located](https://github.com/Alfresco/alfresco-content-app/tree/master/src/app/components/location-link) +in the content repository. + +#### Favorites + +The Favorites view shows all files and folders from the content repository that have been marked as a favorite by the current user. +The [Favorites](https://github.com/Alfresco/alfresco-content-app/tree/master/src/app/components/favorites) component uses the +[favorites](https://api-explorer.alfresco.com/api-explorer/#/favorites) API to retrieve all the favorite nodes for the user +and includes an extra column to display where the file is +[located](https://github.com/Alfresco/alfresco-content-app/tree/master/src/app/components/location-link) +in the content repository. + +#### Trash + +The Trash view shows all the items that a user has deleted, admin will see items deleted by all users. +The actions available in this view are Restore and Permanently Delete. +The [Trashcan](https://github.com/Alfresco/alfresco-content-app/tree/master/src/app/components/trashcan) component uses the +[trashcan](https://api-explorer.alfresco.com/api-explorer/#/trashcan) API to retrieve the deleted items +and perform the actions requested by the user and includes an extra column to display where the item was +[located](https://github.com/Alfresco/alfresco-content-app/tree/master/src/app/components/location-link) +in the content repository before it was deleted. + +#### Search Results + +The Search Results view shows the found items for a search query. It has a custom layout template and users can easily browse the results and perform actions on items. +For more information on the [SearchComponent](https://github.com/Alfresco/alfresco-content-app/tree/master/src/app/components/search), please also check this [Search Results](#search-results1) section. + +#### Actions and the Actions Toolbar + +All the views incorporate the [toolbar](https://alfresco.github.io/adf-component-catalog/components/ToolbarComponent.html) +component from the Alfresco Application Development Framework; +apart from the Trash view they all display the following actions when the current user has the necessary permissions, +actions are automatically hidden when the user does not have permission. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ActionFileFolder
View + Opens the selected file using the Preview component, + where the file cannot be displayed natively in a browser a PDF rendition is obtained from the repository. + Not applicable
DownloadDownloads single files to the user's computer, when multiple files are selected they are compressed into a ZIP and then downloaded.Folders are automatically compressed into a ZIP and then downloaded to the user's computer.
EditNot applicableThe folder name and description can be edited in a dialog.
Favorite + Toggle the favorite mark on or off for files and folders, when multiple items are selected + and one or more are not favorites then the mark will be toggled on. +
Copy + Files and folders can be copied to another location in the content repository using the + content-node-selector component; + once the copy action has completed the user is notified and can undo the action (which permanently deletes the created copies). +
Move + Files and folders can be moved to another location in the content repository using the + content-node-selector component; + once the move action has completed the user is notified and can undo the action (which moves the items back to the original location). +
Delete + Files and folders can be deleted from their location in the content repository; + once the delete action has completed the user is notified and can undo the action (which restores the items from the trash). +
Manage Versions + Versions of files can be viewed, uploaded, restored, downloaded and deleted by using the version manager dialog; + once each action has completed the list of versions is updated according to the change. + Not applicable
+ +Besides the actions available in the toolbar users can single click an item to select it, +or double click on a file to view it, and a folder to open it. + +### File Viewer + +The File Viewer has been created using the [ViewerComponent](https://alfresco.github.io/adf-component-catalog/components/ViewerComponent.html) from the ADF. The Viewer has four main areas: + +![File Viewer](images/File-Viewer.png) + +1. [Header & Toolbar](#header-and-toolbar) +2. [Content](#content) +3. [Thumbnails side pane](#thumbnails-side-pane) +4. [Viewer Controls](#viewer-controls) + +#### Header and Toolbar + +The Header & Toolbar section of the viewer contains a number of features that relate to the file currently being displayed: + +- Close 'X' will return the user to the folder that contains the file. +- The name and file type icon is shown in the middle. +- Next and previous buttons will be displayed either side of the file name so that users can navigate to other files in the folder without navigating away from the viewer. +- Finally, on the right hand side an actions toolbar provides users with the ability to download, favorite, move, copy, delete, manage versions and view info panel. + +#### Content + +The File Viewer consists of four separate views that handle displaying the content based on four types of content, covering various [file/mime](https://alfresco.github.io/adf-component-catalog/components/ViewerComponent.html#supported-file-formats) types: + +- Document View: PDFs are displayed in the application File Viewer, for other document types (DOCX etc) then a PDF rendition is automatically retrieved. +- Image View: JPEG, PNG, GIF, BMP and SVG images are natively displayed in the application File Viewer. +- Media View: MP4, MP3, WAV, OGG and WEBM files are played natively application File Viewer. The File Viewer will download, by default, 50MB of the content at a time to ensure a smooth playback of the content. +- Text View: TXT, XML, JS, HTML, JSON and TS files are natively displayed as text in the application File Viewer. + +#### Thumbnails side pane + +The Document View includes a thumbnails pane which can be activated by a button in the Viewer Actions toolbar. Clicking on a thumbnail will take a user directly to the selected page and as users scroll through a document the current page is highlighted in the pane. + +#### Viewer Controls + +At the bottom of the content the Viewer Controls allow users to interact with the content in various ways; the actions available are dependant on the type of content being displayed. + +- Document View: + - Activate/Deactivate thumbnails pane + - Previous/Next page + - Jump to page number + - Zoom in/out + - Fit to page +- Image View: + - Zoom in/out + - Rotate left/right (does not alter content in the repository) + - Reset image +- Media View: + - Play/pause + - Timeline position + - Audio mute/unmute + - Audio volume + - Full screen + +### Info Drawer + +The Info Drawer displays node information in the right sidebar panel. It is created by using the [InfoDrawerComponent](https://alfresco.github.io/adf-component-catalog/components/InfoDrawerComponent.html). This info is available for both folder and file nodes. + +Currently, there are 2 tabs available: Properties and Versions. + +#### Properties tab + +The Properties tab displays the node's metadata info by using the [ContentMetadataCardComponent](https://alfresco.github.io/adf-component-catalog/components/ContentMetadataCardComponent.html). + +![](images/content-metadata.png) + +For more information, please check also the ADF's [ContentMetadataComponent](https://alfresco.github.io/adf-component-catalog/components/ContentMetadataComponent.html). + +#### Versions tab + +The Versions tab displays info about the node's versions and allows users to [manage versions](#version-manager), according to their permissions. Only the file nodes have version data available. + +![Version Manager Tab](images/version-manager-tab.png) + +It uses the [VersionManagerComponent](https://alfresco.github.io/adf-component-catalog/components/VersionManagerComponent.html) from ADF framework. +Managing versions of a file can be possible also by accessing the 'Manage Versions' option from the 'More actions' menu. + +### Version Manager + +The versions of a file can be viewed & managed by using the [VersionManagerComponent](https://alfresco.github.io/adf-component-catalog/components/VersionManagerComponent.html). + +There are 2 ways users can access the Version Manager: + +1) From the 'Manage Versions' option of the 'More actions' menu (check [Actions and the Actions Toolbar](#actions-and-the-actions-toolbar)): + +![Version Manager Menu](images/version-manager-action.png) +![Version Manager Dialog](images/version-manager-dialog.png) + +2) From the [Info Drawer](#info-drawer) (the Details right panel): + +![Version Manager Inline](images/version-manager-tab.png) + +#### Upload new version + +A new version for the selected file can be added by using this button. Users can upload a new file version using a file that is does not have the same name, or mimetype as the current version, whilst allowing the user to choose the type of version (minor or major) and inputting supporting comments. +Please also check the [UploadVersionButtonComponent](https://alfresco.github.io/adf-component-catalog/components/UploadVersionButtonComponent.html). + +#### Actions Menu + +Each item in the version list has a couple of actions available: Restore, Download and Delete. These are displayed if user has permission to do that specific action. The 'Download' and 'Delete' can be also disabled from the app.config. + +In the app.config.json file, these are the current settings for the ACA version manager: + +```json +{ + "adf-version-manager": { + "allowComments": true, + "allowDownload": true + } +} +``` + +Set the allowComments to false if the version comments should not be displayed on the version list. + +Clicking to delete a version of a file triggers a confirmation dialog. Please see the [ConfirmDialogComponent](https://alfresco.github.io/adf-component-catalog/components/ConfirmDialogComponent.html) for more info. + +### Search Results + +Once you type the text in the Search Input component and press `Enter` you are going to see the Search Results page + +![Search Results](images/aca-search-results.png) + +This page consists of the following ADF components: + +- [Search Filter](https://github.com/Alfresco/alfresco-ng2-components/blob/master/docs/content-services/search-filter.component.md) +- [Search Chip List](https://github.com/Alfresco/alfresco-ng2-components/blob/master/docs/content-services/search-chip-list.component.md) +- [Search Sorting Picker](https://github.com/Alfresco/alfresco-ng2-components/blob/master/docs/content-services/search-sorting-picker.component.md) +- [Document List](https://github.com/Alfresco/alfresco-ng2-components/blob/master/docs/content-services/document-list.component.md) with custom layout template +- [Info Drawer](#info-drawer) with Metadata and [Version Management](#version-manager) +- [Toolbar with basic actions](#actions-and-the-actions-toolbar) like `Preview`, `Download`, `Favorite`, `Copy`, etc. + +And also the Info Drawer, Toolbar and Node Selector dialogs for copy and move operations. + +## How to contribute + +Want to file a bug, contribute some code, or improve documentation? Excellent! +Read up on our guidelines for [contributing][contributing] +and then check out one of our issues in the [Jira][jira] or [GitHub][github] ### How long will it take for my contribution to be reviewed The time necessary for a code review will vary, smaller changes may be reviewed within days, while larger changes may take longer. + +[contributing]: https://github.com/Alfresco/alfresco-content-app/blob/master/CONTRIBUTING.md +[github]: https://github.com/Alfresco/alfresco-content-app/issues +[jira]: https://issues.alfresco.com/jira/projects/ACA diff --git a/docs/build.md b/docs/build.md deleted file mode 100644 index 0d486c479..000000000 --- a/docs/build.md +++ /dev/null @@ -1,56 +0,0 @@ -# Building from source code - -The Content App is based on [Angular CLI](https://cli.angular.io), and you can use all the commands, generators and blueprints supported by the CLI. - -## Prerequisites - -- [Node.js](https://nodejs.org/en/) 8.9.1 or later LTS version -- [Angular CLI](https://cli.angular.io/) - -## Cloning and running - -Use the following commands to clone the project, install dependencies and run it. - -```sh -git clone https://github.com/Alfresco/alfresco-content-app.git -cd alfresco-content-app -npm install -npm start -``` - -The application runs at port 4200 by default, and should automatically open in the default browser once project compilation finishes. - -## Proxy settings - -The Content App provides a proxy configuration for local development server -that allows you to address specific scenarios with CORS and native authentication dialog. - -You can find settings in the "proxy.conf.js" file in the project root directory. - -

-The proxy settings get automatically applied every time you run the application with "npm start" script. -You must restart the application every time you change the settings values. -

- -## Running documentation locally - -For development purposes, you can run and test documentation locally. -This is useful when working in different branches instead of a `master` one. - -Run the following command to install the lightweight development server [wsrv](https://denysvuika.gitlab.io/wsrv/#/): - -```sh -npm install -g wsrv -``` - -Now you can use the next command to serve the documentation folder in the browser: - -```sh -wsrv docs/ -s -l -o -``` - -The browser page is going to automatically reload upon changes. - -## Running unit tests - -Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). diff --git a/docs/configuration.md b/docs/configuration.md deleted file mode 100644 index 258ddb488..000000000 --- a/docs/configuration.md +++ /dev/null @@ -1,141 +0,0 @@ -# Application Configuration - -The Content Application provides support for a global settings file `app.config.json` that you can use to customize the behavior of ACA and ADF components. - -## Server settings - -Once the Content Application starts, it needs to know where the Alfresco Content Services (either Community or Enterprise) server is. -The "ecmHost" property allows you to set the address of the server using the dynamic or static format. - -### Dynamic address - -The example below demonstrates the most common dynamic format for development environment: - -```json -{ - "ecmHost": "http://{hostname}{:port}", - ... -} -``` - -The configuration above assumes you are running ACS and Content App on the same server and port -and allows deploying to different servers having the same unified configuration file. - -For example, a proxy server at `localhost:4200` hosting the Content App as the root application, -and `localhost:4200/alfresco` for the ACS repository. - -At runtime, the application is going to automatically substitute the "{hostname}" value with the original hostname. -Optionally it can also use the value of the original port if present, for example, "4200" at local machines, or skip the value for port 80. - -### Static address - -Alternatively, you can provide a static address for the ACS server if necessary: - -```json -{ - "ecmHost": "http://localhost:4200", - ... -} -``` - -## Application settings - -There are many settings you can change to alter the default behavior of the application. - -### Application Name - -The following block allows you to change the name of the application. - -```json -{ - ..., - "application": { - "name": "Alfresco Example Content Application" - } -} -``` - -The value of the `application.name` key gets appended to every browser tab title at runtime -with the format `[page title] - [application name]`, -for example: "Personal Files - Alfresco Example Content Application". - -### Application Logo - -The default logo displayed in the top left corner of the Alfresco Content Application can be easily changed: - -1. Place your custom logo image file in the [app-name]/src/assets/images folder. The displayed image will resize automatically, an image with extreme width/height might not retain its dimensions. - -2. In the app.config.json file, set the value of the application.logo to contain the name of the custom logo image: "logo": "/assets/images/[image-name].[extension]" - - -```json -{ - ..., - "application": { - "logo": "/assets/images/alfresco-logo-white.svg" - } -} -``` - -### Header Background color - -You can change the header background color by specifying color code for the "headerColor" key: - -```json -{ - ..., - "headerColor": "#2196F3" -} -``` - - -### Restricted content - -You can restrict users from uploading certain types of files and folders by setting or extending the list of rules at the "files.excluded" path. - -By default, the application ships with the following rules already predefined: - -```json -{ - ..., - "files": { - "excluded": [ - ".DS_Store", - "desktop.ini", - "thumbs.db", - ".git" - ] - }, - ... -} -``` - -

-You can get more details on the supported rules in the following article: Upload Service. -

- -### Pagination settings - -You can change the default settings of the pagination that gets applied to all the document lists in the application. - -```json -{ - ..., - "pagination": { - "supportedPageSizes": [ - 25, - 50, - 100 - ] - }, - ... -} -``` - -## Your custom settings - -You can store any information in the application configuration file, and access it at runtime by using the `AppConfigService` service provided by ADF. - -

-Please refer to the AppConfigService documentation to get more details on Application Configuration features and API available. -

diff --git a/docs/cors.md b/docs/cors.md deleted file mode 100644 index 6ea79e47a..000000000 --- a/docs/cors.md +++ /dev/null @@ -1,21 +0,0 @@ -# Cross Origin Resource Sharing (CORS) - -## Chrome Workaround - -For the Chrome browser, you can use the following plugin that allows you to toggle CORS: -[Allow-Control-Allow-Origin](https://chrome.google.com/webstore/detail/allow-control-allow-origi/nlfbmbojpeacfghkpbjhddihlkkiljbi) - -## Firefox Workaround - -Firefox users can try the following plugin: [CORS Everywhere](https://addons.mozilla.org/en-Gb/firefox/addon/cors-everywhere/) - -## Safari Workaround - -If you are developing or testing with Safari then you can use the "Develop" menu to toggle the CORS mode. -Please note that the page must be reloaded every time you change CORS settings. - -![](images/safari-develop-menu.png) - -## See also - -- [Using CORS](https://www.html5rocks.com/en/tutorials/cors/) diff --git a/docs/doc-list.md b/docs/doc-list.md deleted file mode 100644 index e3e243347..000000000 --- a/docs/doc-list.md +++ /dev/null @@ -1,143 +0,0 @@ -### Document List Layout - -The main area of the application is composed of several individual ADF components: - -- (1) [Breadcrumb](https://alfresco.github.io/adf-component-catalog/components/BreadcrumbComponent.html) -- (2) [Toolbar](https://alfresco.github.io/adf-component-catalog/components/ToolbarComponent.html) -- (3) [Document List](https://alfresco.github.io/adf-component-catalog/components/DocumentListComponent.html) -- (4) [Pagination](https://alfresco.github.io/adf-component-catalog/components/PaginationComponent.html) - -![](images/doclist.png) - -The application has six different Document List views which share commonalities between each view and subtle differences depending on the content being loaded which are explained below. - -#### Personal Files - -Personal Files retrieves all content from the logged in user's home area (`/User Homes//`) in the repository; -if the user is ‘admin’ who does not have a home folder then the repository root folder is shown. - -Personal Files is the [Files](https://github.com/Alfresco/alfresco-content-app/tree/master/src/app/components/files) component, -using the [Nodes API](https://api-explorer.alfresco.com/api-explorer/#/nodes). - -#### File Libraries - -File Libraries retrieves all the sites that the user is a member of including what type of site it is: public, moderated or private. -File Libraries is the [Libraries](https://github.com/Alfresco/alfresco-content-app/tree/master/src/app/components/libraries) component, -using the [Sites API](https://api-explorer.alfresco.com/api-explorer/#/sites). - -When a user opens one of their sites then the content for the site's document library is shown. -To display the files and folders from a site (`/Sites//Document Library/`) the [Files](https://github.com/Alfresco/alfresco-content-app/tree/master/src/app/components/files) component, -using the [Nodes API](https://api-explorer.alfresco.com/api-explorer/#/nodes) is used. - -#### Shared Files - -The Shared Files view aggregates all files that have been shared using the QuickShare feature in the content repository. -The [Shared Files](https://github.com/Alfresco/alfresco-content-app/tree/master/src/app/components/shared-files) component uses the [shared-links API](https://api-explorer.alfresco.com/api-explorer/#/shared-links) -and includes extra columns to display where the file is -[located](https://github.com/Alfresco/alfresco-content-app/tree/master/src/app/components/location-link) -in the content repository and who created the shared link. - -A feature for creating and removing Shared Links will be added in the future. - -#### Recent Files - -The Recent Files view shows all the files that have been created or modified within the last 30 days by the current user. -The [Recent Files](https://github.com/Alfresco/alfresco-content-app/tree/master/src/app/components/current-user) -component uses the Search API to query SOLR for changes made by the user and includes an extra column to display where the file is -[located](https://github.com/Alfresco/alfresco-content-app/tree/master/src/app/components/location-link) -in the content repository. - -#### Favorites - -The Favorites view shows all files and folders from the content repository that have been marked as a favorite by the current user. -The [Favorites](https://github.com/Alfresco/alfresco-content-app/tree/master/src/app/components/favorites) component uses the -[favorites](https://api-explorer.alfresco.com/api-explorer/#/favorites) API to retrieve all the favorite nodes for the user -and includes an extra column to display where the file is -[located](https://github.com/Alfresco/alfresco-content-app/tree/master/src/app/components/location-link) -in the content repository. - -#### Trash - -The Trash view shows all the items that a user has deleted, admin will see items deleted by all users. -The actions available in this view are Restore and Permanently Delete. -The [Trashcan](https://github.com/Alfresco/alfresco-content-app/tree/master/src/app/components/trashcan) component uses the -[trashcan](https://api-explorer.alfresco.com/api-explorer/#/trashcan) API to retrieve the deleted items -and perform the actions requested by the user and includes an extra column to display where the item was -[located](https://github.com/Alfresco/alfresco-content-app/tree/master/src/app/components/location-link) -in the content repository before it was deleted. - -#### Actions and the Actions Toolbar - -All the views incorporate the [toolbar](https://alfresco.github.io/adf-component-catalog/components/ToolbarComponent.html) -component from the Alfresco Application Development Framework; -apart from the Trash view they all display the following actions when the current user has the necessary permissions, -actions are automatically hidden when the user does not have permission. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ActionFileFolder
View - Opens the selected file using the Preview component, - where the file cannot be displayed natively in a browser a PDF rendition is obtained from the repository. - Not applicable
DownloadDownloads single files to the user's computer, when multiple files are selected they are compressed into a ZIP and then downloaded.Folders are automatically compressed into a ZIP and then downloaded to the user's computer.
EditNot applicableThe folder name and description can be edited in a dialog.
Favorite - Toggle the favorite mark on or off for files and folders, when multiple items are selected - and one or more are not favorites then the mark will be toggled on. -
Copy - Files and folders can be copied to another location in the content repository using the - content-node-selector component; - once the copy action has completed the user is notified and can undo the action (which permanently deletes the created copies). -
Move - Files and folders can be moved to another location in the content repository using the - content-node-selector component; - once the move action has completed the user is notified and can undo the action (which moves the items back to the original location). -
Delete - Files and folders can be deleted from their location in the content repository; - once the delete action has completed the user is notified and can undo the action (which restores the items from the trash). -
Manage Versions - Versions of files can be viewed, uploaded, restored, downloaded and deleted by using the version manager dialog; - once each action has completed the list of versions is updated according to the change. - Not applicable
- -Besides the actions available in the toolbar users can single click an item to select it, -or double click on a file to view it, and a folder to open it. diff --git a/docs/docker.md b/docs/docker.md deleted file mode 100644 index 86d5ce13d..000000000 --- a/docs/docker.md +++ /dev/null @@ -1,158 +0,0 @@ -# Using with Docker - -

-This article assumes you are familiar with Docker and know how to create images and containers. -

- -You can create a Docker image to run Alfresco Content App in the container. - -## Using public Docker images - -You can find all latest images for ACA in the [alfresco-content-app]( https://hub.docker.com/r/alfresco/alfresco-content-app/) DockerHub repository. - -### Tags - -- `latest`: latest stable release (`master` branch), available with 1.1 release or later -- `development`: most recent code (`development` branch) - -In addition, there are images for feature branches, pull requests and Travis CI builds. - -### Example - -You can run latest `development` build locally with the following command: - -```sh -docker run -p 3000:80 alfresco/alfresco-content-app:development -``` - -The default image expects an ACS 5.2.2 or later running at port `8080`. -You may also need CORS settings to be applied for your ACS installation or image. - -## Building from source code - -You need to run the following commands to build the project from the source code: - -```sh -npm install -npm run build -``` - -That produces a build in the "dist" folder that you can use with a Docker image. - -

-Also, you may need to update the `dist/app.config.json` file with the settings relevant to your scenario. -

- -## Creating an image - -The Content Application provides a "Dockerfile" file in the repository root. -You can build the image with the following command: - -```sh -docker image build -t content-app . -``` - -## Running image in a container - -To run the image locally, you can use the following command: - -```sh -docker container run -p 8888:80 --rm content-app -``` - -Navigate to "http://localhost:8888" to access the running application. - -## Docker Compose file - -You can also use the "docker-compose" file for local development and testing. -To build and run a container run the following command in the root project folder: - -```sh -docker-compose up -``` - -To perform a cleanup operation, use the next command: - -```sh -docker-compose down --rmi all -``` - -Navigate to "http://localhost:4200" to access the running application. - -

-Please keep in mind that you should manually build the project every time you want to publish the image or run it locally with the container. -

- -## Using with local ACS setup - -If you run ACS at port 8080 as a Docker container (typical development configuration), you can use the following command to build the project before creating an image: - -```sh -npm run build:dev -``` - -The command above updates the "dist/app.config.json" file to point the Content App to "http://localhost:8080" upon startup. -Alternatively, you can change the configuration file manually before generating an image. - -So, the development workflow, in this case, is going to be: - -```sh -npm run build:dev -docker-compose up -``` - -Navigate to "http://localhost:4200" to access the running application. - -To perform a cleanup operation, use the next command: - -```sh -docker-compose down --rmi all -``` - -## Publishing to Docker Hub - -First of all, if you do not have a Docker Hub account, you can register here: https://hub.docker.com/, the registration is absolutely free. - -Next, it is recommended that you get a clean build of the application: - -```sh -npm install -npm run build:dev -``` - -The commands above are going to produce a fresh build that is stored in the `dist` folder. -At this point, you can make modifications to the final code in the `dist` folder if needed. -For example you may want to change the `app.config.json` file content. - -Now you can build your first version of the image: - -```sh -docker image build -t myaccount/content-app:1.0 . -``` - -Where `myaccount` is usually your Docker Hub account name. - -

-Please note the ending "." symbol at the end of the command. It instructs the Docker to take current folder where the `Dockerfile` is located. -

- -To publish the newly created image use the next command: - -```sh -docker push myaccount/content-app:1.0 -``` - -## Running from Docker Hub - -To quickly test the published image, or run it on another machine, use the following command: - -```sh -docker container run -p 80:80 --rm myaccount/content-app:1.0 -``` - -The `--rm` switch means the Docker will cleanup the container and image data once you stop the process. - -

-You may also want to remove your local image before trying out the Docker Hub:
-`docker image rm myaccount/content-app:1.0` -

diff --git a/docs/faq.md b/docs/faq.md deleted file mode 100644 index 68d192eb7..000000000 --- a/docs/faq.md +++ /dev/null @@ -1,33 +0,0 @@ -# Frequently asked questions - -## How do I log an issue (bug, enhancement, feature)? - -Log any issues in the ['ACA' JIRA project](https://issues.alfresco.com/jira/projects/ACA), -please include a clear description, steps to reproduce and screenshots where appropriate. -All issues will be reviewed; bugs will be categorized if reproducible and enhancement/feature suggestions -will be considered against existing priorities if the use case serves a general-purpose need. - -## Does Alfresco provide customer support for the example content application? - -Alfresco does not provide Customer Support, it is an example application for developers; [Developer Support Services](https://www.alfresco.com/alfresco-developer-support-services) are available from Alfresco. - -## Does this/Will this application replace Alfresco Share? - -This example application is designed to demonstrate how to construct a content application using the Alfresco Application Development Framework, -it is not intended to be a replacement for Alfresco Share. - -## Where can I get help building an application? - -See [Where to get help](/?id=where-to-get-help) section. - -## How do I contribute to the project? - -See [Contribution Policy](/?id=contribution-policy) section. - -## What would you like me to contribute? - -Please refer to the ['ACA' JIRA project](https://issues.alfresco.com/jira/projects/ACA) for tickets in the project backlog. - -## How often will this project be updated? - -This project will continue to evolve as the Alfresco ADF evolves, with Alfresco and community developers contributing to its progress. diff --git a/docs/features.md b/docs/features.md deleted file mode 100644 index 39de383ac..000000000 --- a/docs/features.md +++ /dev/null @@ -1,19 +0,0 @@ -# Features -## Introduction -The concept of this example is a simple user interface which makes accessing files in the Alfresco Content Services repository easy. - -Often Content Management systems provide more capabilities out of the box than most users need; -providing too many capabilities to these users prevents them from working efficiently, -so they may end up using unsanctioned file management solutions which presents a proliferation of content storage -and collaboration solutions as well as compliance issues for organizations. - -This application demonstrates how the complexity of Content Management can be simplified -using the Alfresco Application Development Framework to easily and quickly create custom solutions for specific user cases. - -## User Interface - layout -There are three main areas of the application controlled by the [Layout component](https://github.com/Alfresco/alfresco-content-app/tree/master/src/app/components/layout): -- [(1) Application Header](/header) -- [(2) Side Navigation](/side-nav) -- [(3) Document List](/doc-list) - -![](images/features-01.png) diff --git a/docs/file-viewer.md b/docs/file-viewer.md deleted file mode 100644 index 9ce7d5107..000000000 --- a/docs/file-viewer.md +++ /dev/null @@ -1,43 +0,0 @@ -### File Viewer - -The File Viewer has been created using the [ViewerComponent](https://alfresco.github.io/adf-component-catalog/components/ViewerComponent.html) from the ADF. The Viewer has four main areas: - -![](images/File-Viewer.png) - -#### Header & Toolbar (1) -The Header & Toolbar section of the viewer contains a number of features that relate to the file currently being displayed: -- Close 'X' will return the user to the folder that contains the file. -- The name and file type icon is shown in the middle. -- Next and previous buttons will be displayed either side of the file name so that users can navigate to other files in the folder without navigating away from the viewer. -- Finally, on the right hand side an actions toolbar provides users with the ability to download, favorite, move, copy, delete, manage versions and view info panel. - -#### Content (2) -The File Viewer consists of four separate views that handle displaying the content based on four types of content, covering various [file/mime](https://alfresco.github.io/adf-component-catalog/components/ViewerComponent.html#supported-file-formats) types: - -- Document View: PDFs are displayed in the application File Viewer, for other document types (DOCX etc) then a PDF rendition is automatically retrieved. -- Image View: JPEG, PNG, GIF, BMP and SVG images are natively displayed in the application File Viewer. -- Media View: MP4, MP3, WAV, OGG and WEBM files are played natively application File Viewer. The File Viewer will download, by default, 50MB of the content at a time to ensure a smooth playback of the content. -- Text View: TXT, XML, JS, HTML, JSON and TS files are natively displayed as text in the application File Viewer. - -#### Thumbnails side pane (3) -The Document View includes a thumbnails pane which can be activated by a button in the Viewer Actions toolbar. Clicking on a thumbnail will take a user directly to the selected page and as users scroll through a document the current page is highlighted in the pane. - -#### Viewer Controls (4) -At the bottom of the content the Viewer Controls allow users to interact with the content in various ways; the actions available are dependant on the type of content being displayed. - -- Document View: - - Activate/Deactivate thumbnails pane - - Previous/Next page - - Jump to page number - - Zoom in/out - - Fit to page -- Image View: - - Zoom in/out - - Rotate left/right (does not alter content in the repository) - - Reset image -- Media View: - - Play/pause - - Timeline position - - Audio mute/unmute - - Audio volume - - Full screen diff --git a/docs/getting-started.md b/docs/getting-started.md new file mode 100644 index 000000000..07e42cd36 --- /dev/null +++ b/docs/getting-started.md @@ -0,0 +1,556 @@ +# Getting Started + +## Prerequisites + +This application uses the latest releases from Alfresco: + +- [Alfresco ADF (2.4.0)](https://community.alfresco.com/community/application-development-framework/pages/get-started) +- [Alfresco Content Services (5.2.3)](https://www.alfresco.com/platform/content-services-ecm) + or [Alfresco Community Edition (201802 EA)](https://www.alfresco.com/products/community/download) + +

+You also need node.js (LTS) installed to build it locally from source code. +

+ +The latest version of the Alfresco Content platform is required +due to the application using the latest [REST APIs](https://docs.alfresco.com/5.2/pra/1/topics/pra-welcome.html) developments. + +## Building from source + +The Content App is based on [Angular CLI](https://cli.angular.io), and you can use all the commands, generators and blueprints supported by the CLI. + +### Prerequisites for building + +- [Node.js](https://nodejs.org/en/) LTS +- [Angular CLI](https://cli.angular.io/) 1.7.3 + +### Cloning and running + +Use the following commands to clone the project, install dependencies and run it. + +```sh +git clone https://github.com/Alfresco/alfresco-content-app.git +cd alfresco-content-app +npm install +npm start +``` + +The application runs at port `4200` by default, and should automatically open in the default browser once project compilation finishes. + +### Proxy settings + +The Content App provides a proxy configuration for local development server +that allows you to address specific scenarios with CORS and native authentication dialog. + +You can find settings in the "proxy.conf.js" file in the project root directory. + +

+The proxy settings get automatically applied every time you run the application with "npm start" script. +You must restart the application every time you change the settings values. +

+ +### Running unit tests + +Run `npm test` to execute the unit tests via [Karma](https://karma-runner.github.io). + +## Internationalization (i18n) + +The Content Application provides support for the following languages: + +- German (`de`) +- English (`en`) +- Spanish (`es`) +- French (`fr`) +- Italian (`it`) +- Japanese (`ja`) +- Norwegian (`nb`) +- Dutch (`nl`) +- Brazilian Portuguese (`pt-BR`) +- Russian (`ru`) +- Simplified Chinese (`zh-CN`) + +The fallback locale is the English one, however current browser language is taken as the default one automatically when the application starts. + +### User-defined language + +You can allow users to set custom language that gets saved to user preferences. +The main application menu already has the [ADF Language Menu](https://github.com/Alfresco/alfresco-ng2-components/blob/master/docs/core/language-menu.component.md) component integrated and pre-filled with the supported items. + +To change the default language set edit the `app.config.json` file and add or remove items: + +```json +{ + ..., + "languages": [ + { + "key": "de", + "label": "German" + }, + { + "key": "en", + "label": "English" + }, + { + "key": "es", + "label": "Spanish" + }, + ... + ] +} +``` + +The file is located at the following path: `/src/app.config.json`. + +### Custom languages + +To add a custom language, add a new "JSON" file to the "/src/assets/i18n" folder +with the name of the target locale, for instance, a "de.json" for the "German". + +Translate the resource strings based on the default "en.json" file. +You can copy the content over to your newly created file and replace English values with translated text. + +```json +{ + "APP": { + "SIGN_IN": "Anmelden", + "SIGN_OUT": "Abmelden", + "NEW_MENU": { + "LABEL": "Neu", + "MENU_ITEMS": { + "CREATE_FOLDER": "Ordner erstellen", + "UPLOAD_FILE": "Datei hochladen", + "UPLOAD_FOLDER": "Ordner hochladen" + }, + ... + } + }, + ... +} +``` + +The Content Application automatically bundles your file upon project build. +You can test your locale by changing the browser language settings and reloading the page. + +Optionally, you can extend the [ADF Language Menu](https://github.com/Alfresco/alfresco-ng2-components/blob/master/docs/core/language-menu.component.md) component with the newly added language by updating the `app.config.json` file. + +### Customizing ADF translations + +In addition to creating a custom language file for the Content Application, +you can also provide translations for the ADF resources. + +Your `/src/assets/i18n/.json` file can reflect the structure of one of the ADF language files: + +- ADF Core ([en.json](https://github.com/Alfresco/alfresco-ng2-components/blob/master/lib/core/i18n/en.json)) +- ADF Content Services ([en.json](https://github.com/Alfresco/alfresco-ng2-components/blob/master/lib/content-services/i18n/en.json)) +- ADF Process Services ([en.json](https://github.com/Alfresco/alfresco-ng2-components/blob/master/lib/process-services/i18n/en.json)) +- ADF Insights ([en.json](https://github.com/Alfresco/alfresco-ng2-components/blob/master/lib/insights/i18n/en.json)) + +At runtime, the application-level strings have the highest priority. +That means you can replace the value of any ADF resource string if needed. + +For example, let's change the title of the "Create Folder" dialog shipped with the ADF. +Modify the `/src/assets/i18n/en.json` file and append the "CORE" section like in the example below: + +```json +{ + "APP": { + ... + }, + "CORE": { + "FOLDER_DIALOG": { + "CREATE_FOLDER_TITLE": "Custom title" + } + } +} +``` + +Now, if you run the application and click the "New → Create Folder" menu, +the title of the dialog should look like the following: + +![](images/aca-i18n-01.png) + +### Language picker + +You can enable internal language picker in the `app.config.json` file: + +```json +{ + ..., + + "languagePicker": true, + + ... +} +``` + +![](images/aca-i18n-02.png) + +## CORS + +The ACA already comes with the proxy configuration for Angular CLI to address CORS-related issues for development. +Also, the docker images contain Nginx settings needed for CORS when developing and debugging application locally. + +### Chrome Workaround + +For the Chrome browser, you can use the following plugin that allows you to toggle CORS: +[Allow-Control-Allow-Origin](https://chrome.google.com/webstore/detail/allow-control-allow-origi/nlfbmbojpeacfghkpbjhddihlkkiljbi) + +### Firefox Workaround + +Firefox users can try the following plugin: [CORS Everywhere](https://addons.mozilla.org/en-Gb/firefox/addon/cors-everywhere/) + +### Safari Workaround + +If you are developing or testing with Safari then you can use the "Develop" menu to toggle the CORS mode. +Please note that the page must be reloaded every time you change CORS settings. + +![](images/safari-develop-menu.png) + +### See also + +- [Using CORS](https://www.html5rocks.com/en/tutorials/cors/) + +## Configuration + +The Content Application provides support for a global settings file `app.config.json` that you can use to customize the behavior of ACA and ADF components. + +### Server settings + +Once the Content Application starts, it needs to know where the Alfresco Content Services (either Community or Enterprise) server is. +The "ecmHost" property allows you to set the address of the server using the dynamic or static format. + +#### Dynamic address + +The example below demonstrates the most common dynamic format for development environment: + +```json +{ + "ecmHost": "http://{hostname}{:port}", + ... +} +``` + +The configuration above assumes you are running ACS and Content App on the same server and port +and allows deploying to different servers having the same unified configuration file. + +For example, a proxy server at `localhost:4200` hosting the Content App as the root application, +and `localhost:4200/alfresco` for the ACS repository. + +At runtime, the application is going to automatically substitute the "{hostname}" value with the original hostname. +Optionally it can also use the value of the original port if present, for example, "4200" at local machines, or skip the value for port 80. + +#### Static address + +Alternatively, you can provide a static address for the ACS server if necessary: + +```json +{ + "ecmHost": "http://localhost:4200", + ... +} +``` + +### Application settings + +There are many settings you can change to alter the default behavior of the application. + +#### Application Name + +The following block allows you to change the name of the application. + +```json +{ + ..., + "application": { + "name": "Alfresco Example Content Application" + } +} +``` + +The value of the `application.name` key gets appended to every browser tab title at runtime +with the format `[page title] - [application name]`, +for example: "Personal Files - Alfresco Example Content Application". + +#### Application Logo + +The default logo displayed in the top left corner of the Alfresco Content Application can be easily changed: + +1. Place your custom logo image file in the [app-name]/src/assets/images folder. The displayed image will resize automatically, an image with extreme width/height might not retain its dimensions. + +2. In the app.config.json file, set the value of the application.logo to contain the name of the custom logo image: "logo": "/assets/images/[image-name].[extension]" + + +```json +{ + ..., + "application": { + "logo": "/assets/images/alfresco-logo-white.svg" + } +} +``` + +#### Header Background color + +You can change the header background color by specifying color code for the "headerColor" key: + +```json +{ + ..., + "headerColor": "#2196F3" +} +``` + +#### Restricted content + +You can restrict users from uploading certain types of files and folders by setting or extending the list of rules at the "files.excluded" path. + +By default, the application ships with the following rules already predefined: + +```json +{ + ..., + "files": { + "excluded": [ + ".DS_Store", + "desktop.ini", + "thumbs.db", + ".git" + ] + }, + ... +} +``` + +

+You can get more details on the supported rules in the following article: Upload Service. +

+ +#### Pagination settings + +You can change the default settings of the pagination that gets applied to all the document lists in the application. + +```json +{ + ..., + "pagination": { + "supportedPageSizes": [ + 25, + 50, + 100 + ] + }, + ... +} +``` + +### Your custom settings + +You can store any information in the application configuration file, and access it at runtime by using the `AppConfigService` service provided by ADF. + +

+Please refer to the AppConfigService documentation to get more details on Application Configuration features and API available. +

+ +## Navigation + +The Alfresco Content Application provides the following navigation links: + +- Personal Files +- File Libraries +- Shared +- Recent Files +- Favorites +- Trash + +The side navigation provides support to customize the appearance of the links by editing the `app.config.json`. + +### Customization + +Navigation configuration supports array and object like schema. Defining an object helps navigation to render visual delimiters between different groups of links. + +```json +{ + "navigation": { + "main": [ + ... + ], + "secondary": [ + ... + ] + } +} +``` + +![](images/navigation-01.png) + +```json +{ + "navigation": [ + { ... }, + { ... }, + ... + ] +} +``` + +![](images/navigation-02.png) + +#### Customize icons and text + +`icon` - supported value can be anything from [Material Design](https://material.io/icons) icons library. If not defined, the link will render just the label value. + +`title` - instructs the link to render a native browser tooltip with the given value. It can be a string or a i18n defined reference. If not defined, the link will not show a tooltip. + +`label` - represents the visual name of the link. It can be a string or a i18n defined reference. + +

+ Changing ` "route": { "url": "/..." } ` value will affect the navigation since these are mapped to application routing system. +

+ +#### Custom text (i18n) + +To change the `title` and `label` of navigation links edit the values under `BROWSE` entry found at `/src/assets/i18n/en.json` + +```json +"APP" : { + ... + "BROWSE": { + "PERSONAL": { + "TITLE": "Personal Files", + "SIDENAV_LINK": { + "LABEL": "Personal Files", + "TOOLTIP": "View your Personal Files" + } + }, + ... + } +} +``` + +For more information about internationalization see [Internationalization (i18n)](#internationalization-i18n) section. + +### User-defined navigation + +To add custom navigation link for the application, first we need to create a component. + +`src/app/components/custom-page/custom-page.component.ts` + +```js +import { Component } from '@angular/core'; + +@Component({ +template: ` +

{{ title }}

+ ` +}) +export class CustomPage { + title = 'My Custom Page' +} +``` + +Register the component in ```app.module.ts``` + +```javascript + + ... + import { CustomPage } from './components/custom-page/custom-page.component'; + + @NgModule({ + ... + declarations: [ + ..., + CustomPage + ], + ... +}) + +``` + +In the `app.config.json` define a link entry which will point to the custom page + +```json +{ + ..., + "navigation": [ + "main": [ ... ], + "secondary": [ ... ], + "custom": [ + { + "icon": "work", + "label": "Link", + "title": "My custome link", + "route": { + "url": "/custom-route" + } + } + ] + ] +} + +``` + +Map the `/custom-route` in `app.routes.ts` as a child of `LayoutComponent` definition. + +```js + + import { CustomPage } from './components/custom-page/custom-page.component.ts'; + + ... + { + path: '', + component: LayoutComponent, + children: [ + ..., + { + path: 'custom-route', + component: CustomPage + } + ] + } + ..., + +``` + +![](images/navigation-03.png) + +For more information about the content of a custom page see [Document List Layout](/#document-list-layout) section. + +## Docker + +The ACA comes with the ACS 6.0 Community Edition preconfigured. +The application runs in to modes: + +- Development (runs latest source code, requires building application) +- Preview (runs with latest published containers, master branch) + +### Development Mode + +Run the local instance of the application packaged into the docker image together with the ACS images: + +```sh +npm run build +npm run start:docker +``` + +The ACA runs on port `4000` when served from within container. + +Use the following command to stop all the containers: + +```sh +npm run stop:docker +``` + +### Preview Mode + +

+With this mode, you do not need building application from source code or installing dependencies. +

+ +To run the latest published container go to the `docker-compose` folder and start docker compose from there: + +```sh +cd docker-compose +docker-compose up +``` + +The application is available at the `http://localhost:3000` address. diff --git a/docs/header.md b/docs/header.md deleted file mode 100644 index cf77f6c99..000000000 --- a/docs/header.md +++ /dev/null @@ -1,25 +0,0 @@ -## Header - -The application [header](https://github.com/Alfresco/alfresco-content-app/tree/master/src/app/components/header) has three main elements. - -![](images/header.png) - -### (1) Logo and Color -Logo & app primary color - logo and color are configurable by updating the -[app.config.json](https://github.com/Alfresco/alfresco-content-app/blob/master/src/app.config.json) file in the root folder of the project. -Please refer to the [Application Configuration](https://github.com/Alfresco/alfresco-content-app/blob/master/docs/configuration.md#application-logo) documentation for more information on how to change the logo and color. - -### (2) Search -The application [Search](https://github.com/Alfresco/alfresco-content-app/tree/master/src/app/components/search) - -uses the [ADF Search Component](https://github.com/Alfresco/alfresco-ng2-components/tree/master/lib/content-services/search) -the app provides a 'live' search feature, where users can open files and folders directly from the Search API results. - -![](images/search.png) - -### (3) Current User -[Current User](https://github.com/Alfresco/alfresco-content-app/tree/development/src/app/components/current-user) - -displays the user's name, and a menu where users can logout. -Optionally through updating the [app.config.json](https://github.com/Alfresco/alfresco-content-app/blob/master/src/app.config.json) -a language switching menu can be displayed. - -![](images/current-user.png) diff --git a/docs/help.md b/docs/help.md index f547b977e..54652ee0f 100644 --- a/docs/help.md +++ b/docs/help.md @@ -32,11 +32,30 @@ The most cost-effective way to take advantage of this valuable training is throu Visit the Alfresco University section on the Alfresco website for more information: https://www.alfresco.com/alfresco-university -# Building and running locally +## Frequently asked questions -Please refer to the [developer docs](/build) to get more details on building and running application on your local machine. +### How do I log an issue (bug, enhancement, feature)? -# Using with Docker +Log any issues in the [Jira][jira], +please include a clear description, steps to reproduce and screenshots where appropriate. +All issues will be reviewed; bugs will be categorized if reproducible and enhancement/feature suggestions +will be considered against existing priorities if the use case serves a general-purpose need. -The Content App provides a "Dockerfile" and "docker-compose" files to aid in running application in a container. -Please refer to the "[Using with Docker](/docker)" article for more details. +### Does this/Will this application replace Alfresco Share? + +This example application is designed to demonstrate how to construct a content application using the Alfresco Application Development Framework, +it is not intended to be a replacement for Alfresco Share. + +### How do I contribute to the project? + +Want to file a bug, contribute some code, or improve documentation? Excellent! +Read up on our guidelines for [contributing][contributing] +and then check out one of our issues in the [Jira][jira] or [GitHub][github] + +### How often will this project be updated? + +This project will continue to evolve as the Alfresco ADF evolves, with Alfresco and community developers contributing to its progress. + +[contributing]: https://github.com/Alfresco/alfresco-content-app/blob/master/CONTRIBUTING.md +[github]: https://github.com/Alfresco/alfresco-content-app/issues +[jira]: https://issues.alfresco.com/jira/projects/ACA diff --git a/docs/i18n.md b/docs/i18n.md deleted file mode 100644 index 52d700fef..000000000 --- a/docs/i18n.md +++ /dev/null @@ -1,131 +0,0 @@ -# Internationalization (i18n) - -The Content Application provides support for the following languages: - -- German (`de`) -- English (`en`) -- Spanish (`es`) -- French (`fr`) -- Italian (`it`) -- Japanese (`ja`) -- Norwegian (`nb`) -- Dutch (`nl`) -- Brazilian Portuguese (`pt-BR`) -- Russian (`ru`) -- Simplified Chinese (`zh-CN`) - -The fallback locale is the English one, however current browser language is taken as the default one automatically when the application starts. - -## User-defined language - -You can allow users to set custom language that gets saved to user preferences. -The main application menu already has the [ADF Language Menu](https://github.com/Alfresco/alfresco-ng2-components/blob/development/docs/language-menu.component.md) component integrated and pre-filled with the supported items. - -To change the default language set edit the `app.config.json` file and add or remove items: - -```json -{ - ..., - "languages": [ - { - "key": "de", - "label": "German" - }, - { - "key": "en", - "label": "English" - }, - { - "key": "es", - "label": "Spanish" - }, - ... - ] -} -``` - -The file is located at the following path: `/src/app.config.json`. - -## Custom languages - -To add a custom language, add a new "JSON" file to the "/src/assets/i18n" folder -with the name of the target locale, for instance, a "de.json" for the "German". - -Translate the resource strings based on the default "en.json" file. -You can copy the content over to your newly created file and replace English values with translated text. - -```json -{ - "APP": { - "SIGN_IN": "Anmelden", - "SIGN_OUT": "Abmelden", - "NEW_MENU": { - "LABEL": "Neu", - "MENU_ITEMS": { - "CREATE_FOLDER": "Ordner erstellen", - "UPLOAD_FILE": "Datei hochladen", - "UPLOAD_FOLDER": "Ordner hochladen" - }, - ... - } - }, - ... -} -``` - -The Content Application automatically bundles your file upon project build. -You can test your locale by changing the browser language settings and reloading the page. - -Optionally, you can extend the [ADF Language Menu](https://github.com/Alfresco/alfresco-ng2-components/blob/development/docs/language-menu.component.md) component with the newly added language by updating the `app.config.json` file. - -## Customizing ADF translations - -In addition to creating a custom language file for the Content Application, -you can also provide translations for the ADF resources. - -Your `/src/assets/i18n/.json` file can reflect the structure of one of the ADF language files: - -- ADF Core ([en.json](https://github.com/Alfresco/alfresco-ng2-components/blob/master/lib/core/i18n/en.json)) -- ADF Content Services ([en.json](https://github.com/Alfresco/alfresco-ng2-components/blob/master/lib/content-services/i18n/en.json)) -- ADF Process Services ([en.json](https://github.com/Alfresco/alfresco-ng2-components/blob/master/lib/process-services/i18n/en.json)) -- ADF Insights ([en.json](https://github.com/Alfresco/alfresco-ng2-components/blob/master/lib/insights/i18n/en.json)) - -At runtime, the application-level strings have the highest priority. -That means you can replace the value of any ADF resource string if needed. - -For example, let's change the title of the "Create Folder" dialog shipped with the ADF. -Modify the `/src/assets/i18n/en.json` file and append the "CORE" section like in the example below: - -```json -{ - "APP": { - ... - }, - "CORE": { - "FOLDER_DIALOG": { - "CREATE_FOLDER_TITLE": "Custom title" - } - } -} -``` - -Now, if you run the application and click the "New → Create Folder" menu, -the title of the dialog should look like the following: - -![](images/aca-i18n-01.png) - -## Language picker - -You can enable internal language picker in the `app.config.json` file: - -```json -{ - ..., - - "languagePicker": true, - - ... -} -``` - -![](images/aca-i18n-02.png) diff --git a/docs/images/aca-search-results.png b/docs/images/aca-search-results.png new file mode 100644 index 000000000..0a0e02603 Binary files /dev/null and b/docs/images/aca-search-results.png differ diff --git a/docs/index.html b/docs/index.html index d9acb344e..1ea7e81e3 100644 --- a/docs/index.html +++ b/docs/index.html @@ -19,76 +19,8 @@ path: '/' }, { - title: 'Features', - type: 'dropdown', - items: [ - { - title: 'Introduction', - path: 'features' - }, - { - title: 'Application Header', - path: 'header' - }, - { - title: 'Side Navigation', - path: 'side-nav' - }, - { - title: 'Document List', - path: 'doc-list' - }, - { - title: 'File Viewer', - path: 'file-viewer' - }, - { - title: 'Info Drawer', - path: 'info-drawer' - }, - { - title: 'Version Manager', - path: 'version-manager' - } - ] - }, - { - title: 'Building', - path: 'build' - }, - { - title: 'Docker', - path: 'docker' - }, - { - title: 'FAQ', - path: 'faq' - }, - { - title: 'Guides', - type: 'dropdown', - items: [ - { - title: 'Building', - path: 'build' - }, - { - title: 'Internationalization (i18n)', - path: 'i18n' - }, - { - title: 'CORS', - path: 'cors' - }, - { - title: 'Configuration', - path: 'configuration' - }, - { - title: 'Navigation', - path: 'navigation' - } - ] + title: 'Getting Started', + path: 'getting-started' }, { title: 'Get Help', diff --git a/docs/info-drawer.md b/docs/info-drawer.md deleted file mode 100644 index 6d22172cc..000000000 --- a/docs/info-drawer.md +++ /dev/null @@ -1,23 +0,0 @@ -### Info Drawer - -The Info Drawer displays node information in the right sidebar panel. It is created by using the [InfoDrawerComponent](https://alfresco.github.io/adf-component-catalog/components/InfoDrawerComponent.html). This info is available for both folder and file nodes. - -Currently, there are 2 tabs available: Properties and Versions. - -#### Properties tab - -The Properties tab displays the node's metadata info by using the [ContentMetadataCardComponent](https://alfresco.github.io/adf-component-catalog/components/ContentMetadataCardComponent.html). - -![](images/content-metadata.png) - -For more information, please check also the ADF's [ContentMetadataComponent](https://alfresco.github.io/adf-component-catalog/components/ContentMetadataComponent.html). - -#### Versions tab - -The Versions tab displays info about the node's versions and allows users to [manage versions](/version-manager), according to their permissions. Only the file nodes have version data available. - -![](images/version-manager-tab.png) - -It uses the [VersionManagerComponent](https://alfresco.github.io/adf-component-catalog/components/VersionManagerComponent.html) from ADF framework. - -Managing versions of a file can be possible also by accessing the 'Manage Versions' option from the 'More actions' menu. For more info on manage versions, please check the [version manager](/version-manager) page. diff --git a/docs/navigation.md b/docs/navigation.md deleted file mode 100644 index 4a283a272..000000000 --- a/docs/navigation.md +++ /dev/null @@ -1,163 +0,0 @@ -# Navigation - -The Alfresco Content Application provides the following navigation links: - -- Personal Files -- File Libraries -- Shared -- Recent Files -- Favorites -- Trash - -The side navigation provides support to customize the appearance of the links by editing the `app.config.json`. - -## Customization - -Navigation configuration supports array and object like schema. Defining an object helps navigation to render visual delimiters between different groups of links. - -```json -{ - "navigation": { - "main": [ - ... - ], - "secondary": [ - ... - ] - } -} -``` - -![](images/navigation-01.png) - -```json -{ - "navigation": [ - { ... }, - { ... }, - ... - ] -} -``` - -![](images/navigation-02.png) - -### Customize icons and text - -`icon` - supported value can be anything from [Material Design](https://material.io/icons) icons library. If not defined, the link will render just the label value. - -`title` - instructs the link to render a native browser tooltip with the given value. It can be a string or a i18n defined reference. If not defined, the link will not show a tooltip. - -`label` - represents the visual name of the link. It can be a string or a i18n defined reference. - -

- Changing ` "route": { "url": "/..." } ` value will affect the navigation since these are mapped to application routing system. -

- -### Custom text (i18n) - -To change the `title` and `label` of navigation links edit the values under `BROWSE` entry found at `/src/assets/i18n/en.json` - -```json -"APP" : { - ... - "BROWSE": { - "PERSONAL": { - "TITLE": "Personal Files", - "SIDENAV_LINK": { - "LABEL": "Personal Files", - "TOOLTIP": "View your Personal Files" - } - }, - ... - } -} -``` - -For more information about internationalization see [Internationalization (i18n)](/i18n) section. - -## User-defined navigation - -To add custom navigation link for the application, first we need to create a component. - -`src/app/components/custom-page/custom-page.component.ts` - -```js -import { Component } from '@angular/core'; - -@Component({ -template: ` -

{{ title }}

- ` -}) -export class CustomPage { - title = 'My Custom Page' -} -``` - -Register the component in ```app.module.ts``` - -```javascript - - ... - import { CustomPage } from './components/custom-page/custom-page.component'; - - @NgModule({ - ... - declarations: [ - ..., - CustomPage - ], - ... -}) - -``` - -In the `app.config.json` define a link entry which will point to the custom page - -```json -{ - ..., - "navigation": [ - "main": [ ... ], - "secondary": [ ... ], - "custom": [ - { - "icon": "work", - "label": "Link", - "title": "My custome link", - "route": { - "url": "/custom-route" - } - } - ] - ] -} - -``` - -Map the `/custom-route` in `app.routes.ts` as a child of `LayoutComponent` definition. - -```js - - import { CustomPage } from './components/custom-page/custom-page.component.ts'; - - ... - { - path: '', - component: LayoutComponent, - children: [ - ..., - { - path: 'custom-route', - component: CustomPage - } - ] - } - ..., - -``` - -![](images/navigation-03.png) - -For more information about the content of a custom page see [Document List Layout](/doc-list) section. diff --git a/docs/side-nav.md b/docs/side-nav.md deleted file mode 100644 index 98ac4258d..000000000 --- a/docs/side-nav.md +++ /dev/null @@ -1,27 +0,0 @@ -### Side Nav - -The application [side navigation](https://github.com/Alfresco/alfresco-content-app/tree/master/src/app/components/sidenav) has two features: -a button menu and navigation links. - -![](images/side-nav.png) - -#### New button - -The New button displays a menu which provides three actions: - -- Create a new folder - provides a dialog which allows the creation of a new folder, the folder name is mandatory and the description is optional. -- Upload a file - invokes the operating system file browser and allows a user to select file(s) to upload into their current location in the content repository. -- Upload a folder - invokes the operating system folder browser and allows a user to select a folder to upload to their current location in the content repository. - -When an upload starts the [upload component](https://github.com/Alfresco/alfresco-ng2-components/tree/master/lib/content-services/upload) -is displayed which shows the user the progress of the uploads they have started. -The upload dialog persists on the screen and can be minimized; users are able to continue using the application whilst uploads are in progress -and uploads can be canceled which will stop uploads in progress or permanently delete already completed uploads. - -![](images/uploader.png) - -#### Navigation - -The navigation links are configurable via the [app.config.json](https://github.com/Alfresco/alfresco-content-app/blob/master/src/app.config.json). -Default configuration creates two sections. -See [Navigation](/navigation) for more information about configuring the side navigation. diff --git a/docs/version-manager.md b/docs/version-manager.md deleted file mode 100644 index c291c84a8..000000000 --- a/docs/version-manager.md +++ /dev/null @@ -1,33 +0,0 @@ -## Version Manager - -The versions of a file can be viewed & managed by using the [VersionManagerComponent](https://alfresco.github.io/adf-component-catalog/components/VersionManagerComponent.html). - -There are 2 ways users can access the Version Manager: - -1. From the 'Manage Versions' option of the 'More actions' menu (check [Actions and the Actions Toolbar](/doc-list?id=actions-and-the-actions-toolbar)): -![](images/version-manager-action.png) -![](images/version-manager-dialog.png) - -2. From the [Info Drawer](/info-drawer) (the Details right panel): -![](images/version-manager-tab.png) - -### Upload new version - -A new version for the selected file can be added by using this button. There is a restriction currently to only upload files of the same extension as the old version. The new version file will be automatically renamed to have the same name as the old version has. Please also check the [UploadVersionButtonComponent](https://alfresco.github.io/adf-component-catalog/components/UploadVersionButtonComponent.html). - -### Actions Menu - -Each item in the version list has a couple of actions available: Restore, Download and Delete. These are displayed if user has permission to do that specific action. The 'Download' and 'Delete' can be also disabled from the app.config. - -In the app.config.json file, these are the current settings for the ACA version manager: -``` - "adf-version-manager": { - "allowComments": true, - "allowDownload": true, - "allowDelete": true - }, - ... -``` -Set the allowComments to false if the version comments should not be displayed on the version list. - -Clicking to delete a version of a file triggers a confirmation dialog. Please see the [ConfirmDialogComponent](https://alfresco.github.io/adf-component-catalog/components/ConfirmDialogComponent.html) for more info. diff --git a/e2e/components/component.ts b/e2e/components/component.ts new file mode 100755 index 000000000..134287c0c --- /dev/null +++ b/e2e/components/component.ts @@ -0,0 +1,43 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { ElementFinder, element, by, ExpectedConditions as EC, browser } from 'protractor'; +import { BROWSER_WAIT_TIMEOUT } from '../configs'; + +export abstract class Component { + component: ElementFinder; + + constructor(selector: string, ancestor?: ElementFinder) { + const locator = by.css(selector); + + this.component = ancestor + ? ancestor.element(locator) + : element(locator); + } + + wait() { + return browser.wait(EC.presenceOf(this.component), BROWSER_WAIT_TIMEOUT); + } +} diff --git a/e2e/components/components.ts b/e2e/components/components.ts new file mode 100755 index 000000000..604be941d --- /dev/null +++ b/e2e/components/components.ts @@ -0,0 +1,32 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +export * from './login/login'; +export * from './header/header'; +export * from './header/user-info'; +export * from './data-table/data-table'; +export * from './pagination/pagination'; +export * from './sidenav/sidenav'; +export * from './toolbar/toolbar'; diff --git a/e2e/components/data-table/data-table.ts b/e2e/components/data-table/data-table.ts new file mode 100755 index 000000000..c8ec0d9c7 --- /dev/null +++ b/e2e/components/data-table/data-table.ts @@ -0,0 +1,243 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { ElementFinder, ElementArrayFinder, promise, by, browser, ExpectedConditions as EC, protractor } from 'protractor'; +import { BROWSER_WAIT_TIMEOUT } from '../../configs'; +import { Component } from '../component'; +import { Utils } from '../../utilities/utils'; + +export class DataTable extends Component { + private static selectors = { + root: 'adf-datatable', + + head: '.adf-datatable-header', + columnHeader: '.adf-datatable-row .adf-datatable-table-cell-header', + sortedColumnHeader: ` + .adf-data-table__header--sorted-asc, + .adf-data-table__header--sorted-desc + `, + + body: '.adf-datatable-body', + row: '.adf-datatable-row[role]', + selectedRow: '.adf-datatable-row.is-selected', + cell: '.adf-data-table-cell', + locationLink: 'aca-location-link', + + selectedIcon: '.mat-icon', + + emptyListContainer: 'div.adf-no-content-container', + emptyFolderDragAndDrop: '.adf-empty-list_template .adf-empty-folder', + + emptyListTitle: '.adf-empty-content__title', + emptyListSubtitle: '.adf-empty-content__subtitle', + emptyListText: '.adf-empty-content__text' + }; + + head: ElementFinder = this.component.element(by.css(DataTable.selectors.head)); + body: ElementFinder = this.component.element(by.css(DataTable.selectors.body)); + cell = by.css(DataTable.selectors.cell); + locationLink = by.css(DataTable.selectors.locationLink); + emptyList: ElementFinder = this.component.element(by.css(DataTable.selectors.emptyListContainer)); + emptyFolderDragAndDrop: ElementFinder = this.component.element(by.css(DataTable.selectors.emptyFolderDragAndDrop)); + emptyListTitle: ElementFinder = this.component.element(by.css(DataTable.selectors.emptyListTitle)); + emptyListSubtitle: ElementFinder = this.component.element(by.css(DataTable.selectors.emptyListSubtitle)); + emptyListText: ElementArrayFinder = this.component.all(by.css(DataTable.selectors.emptyListText)); + + constructor(ancestor?: ElementFinder) { + super(DataTable.selectors.root, ancestor); + } + + // Wait methods (waits for elements) + waitForHeader() { + return browser.wait(EC.presenceOf(this.head), BROWSER_WAIT_TIMEOUT); + } + + waitForEmptyState() { + return browser.wait(EC.presenceOf(this.emptyList), BROWSER_WAIT_TIMEOUT); + } + + // Header/Column methods + getColumnHeaders(): ElementArrayFinder { + const locator = by.css(DataTable.selectors.columnHeader); + return this.head.all(locator); + } + + getNthColumnHeader(nth: number): ElementFinder { + return this.getColumnHeaders().get(nth - 1); + } + + getColumnHeaderByLabel(label: string): ElementFinder { + const locator = by.cssContainingText(DataTable.selectors.columnHeader, label); + return this.head.element(locator); + } + + getSortedColumnHeader(): ElementFinder { + const locator = by.css(DataTable.selectors.sortedColumnHeader); + return this.head.element(locator); + } + + getSortingOrder() { + return this.getSortedColumnHeader().getAttribute('class') + .then(str => { + if (str.includes('asc')) { + return 'asc'; + } else { + if (str.includes('desc')) { + return 'desc'; + } + } + }); + } + + sortByColumn(columnName: string): promise.Promise { + const column = this.getColumnHeaderByLabel(columnName); + const click = browser.actions().mouseMove(column).click(); + + return click.perform(); + } + + // Rows methods + getRows(): ElementArrayFinder { + return this.body.all(by.css(DataTable.selectors.row)); + } + + getSelectedRows(): ElementArrayFinder { + return this.body.all(by.css(DataTable.selectors.selectedRow)); + } + + countSelectedRows(): promise.Promise { + return this.getSelectedRows().count(); + } + + getNthRow(nth: number): ElementFinder { + return this.getRows().get(nth - 1); + } + + getRowName(name: string): ElementFinder { + return this.body.element(by.cssContainingText(`.adf-data-table-cell span`, name)); + } + + getItemNameTooltip(name: string): promise.Promise { + return this.getRowName(name).getAttribute('title'); + } + + countRows(): promise.Promise { + return this.getRows().count(); + } + + hasCheckMarkIcon(itemName: string) { + return this.getRowName(itemName).element(by.xpath(`./ancestor::div[contains(@class, 'adf-datatable-row')]`)) + .element(by.css(DataTable.selectors.selectedIcon)).isPresent(); + } + + // Navigation/selection methods + doubleClickOnItemName(name: string): promise.Promise { + const dblClick = browser.actions() + .mouseMove(this.getRowName(name)) + .click() + .click(); + + return dblClick.perform(); + } + + clickOnItemName(name: string): promise.Promise { + const item = this.getRowName(name); + return Utils.waitUntilElementClickable(item) + .then(() => this.getRowName(name).click()); + } + + selectMultipleItems(names: string[]): promise.Promise { + return this.clearSelection() + .then(() => browser.actions().sendKeys(protractor.Key.COMMAND).perform()) + .then(() => { + names.forEach(name => { + this.clickOnItemName(name); + }); + }) + .then(() => browser.actions().sendKeys(protractor.Key.NULL).perform()); + } + + clearSelection(): promise.Promise { + return this.getSelectedRows().count() + .then(count => { + if (count !== 0) { browser.refresh().then(() => this.waitForHeader()); } + }); + } + + getItemLocation(name: string) { + return this.getRowName(name).element(by.xpath(`./ancestor::div[contains(@class, 'adf-datatable-row')]`)) + .element(this.locationLink); + } + + getItemLocationTooltip(name: string): promise.Promise { + return this.getItemLocation(name).$('a').getAttribute('title'); + } + + clickItemLocation(name: string) { + return this.getItemLocation(name).click(); + } + + // empty state methods + isEmptyList(): promise.Promise { + return this.emptyList.isPresent(); + } + + isEmptyWithDragAndDrop(): promise.Promise { + return this.emptyFolderDragAndDrop.isDisplayed(); + } + + getEmptyDragAndDropText(): promise.Promise { + return this.isEmptyWithDragAndDrop() + .then(() => { + return this.emptyFolderDragAndDrop.getText(); + }); + } + + getEmptyStateTitle(): promise.Promise { + return this.isEmptyList() + .then(() => { + return this.emptyListTitle.getText(); + }); + } + + getEmptyStateSubtitle(): promise.Promise { + return this.isEmptyList() + .then(() => { + return this.emptyListSubtitle.getText(); + }); + } + + getEmptyStateText(): promise.Promise { + return this.isEmptyList() + .then(() => { + return this.emptyListText.getText(); + }); + } + + getCellsContainingName(name: string) { + return this.getRows().all(by.cssContainingText(DataTable.selectors.cell, name)) + .map(cell => cell.getText()); + } +} diff --git a/e2e/components/dialog/create-edit-folder-dialog.ts b/e2e/components/dialog/create-edit-folder-dialog.ts new file mode 100755 index 000000000..696746e63 --- /dev/null +++ b/e2e/components/dialog/create-edit-folder-dialog.ts @@ -0,0 +1,108 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { ElementFinder, by, browser, protractor, ExpectedConditions as EC, promise } from 'protractor'; +import { BROWSER_WAIT_TIMEOUT } from '../../configs'; +import { Component } from '../component'; +import { Utils } from '../../utilities/utils'; + +export class CreateOrEditFolderDialog extends Component { + private static selectors = { + root: 'adf-folder-dialog', + + title: '.mat-dialog-title', + nameInput: 'input[placeholder="Name" i]', + descriptionTextArea: 'textarea[placeholder="Description" i]', + button: '.mat-dialog-actions button', + validationMessage: '.mat-hint span' + }; + + title: ElementFinder = this.component.element(by.css(CreateOrEditFolderDialog.selectors.title)); + nameInput: ElementFinder = this.component.element(by.css(CreateOrEditFolderDialog.selectors.nameInput)); + descriptionTextArea: ElementFinder = this.component.element(by.css(CreateOrEditFolderDialog.selectors.descriptionTextArea)); + createButton: ElementFinder = this.component.element(by.cssContainingText(CreateOrEditFolderDialog.selectors.button, 'Create')); + cancelButton: ElementFinder = this.component.element(by.cssContainingText(CreateOrEditFolderDialog.selectors.button, 'Cancel')); + updateButton: ElementFinder = this.component.element(by.cssContainingText(CreateOrEditFolderDialog.selectors.button, 'Update')); + validationMessage: ElementFinder = this.component.element(by.css(CreateOrEditFolderDialog.selectors.validationMessage)); + + constructor(ancestor?: ElementFinder) { + super(CreateOrEditFolderDialog.selectors.root, ancestor); + } + + waitForDialogToOpen() { + return browser.wait(EC.presenceOf(this.title), BROWSER_WAIT_TIMEOUT) + .then(() => browser.wait(EC.presenceOf(browser.element(by.css('.cdk-overlay-backdrop'))), BROWSER_WAIT_TIMEOUT)); + + } + + waitForDialogToClose() { + return browser.wait(EC.stalenessOf(this.title), BROWSER_WAIT_TIMEOUT); + } + + getTitle(): promise.Promise { + return this.title.getText(); + } + + isValidationMessageDisplayed(): promise.Promise { + return this.validationMessage.isDisplayed(); + } + + getValidationMessage(): promise.Promise { + return this.isValidationMessageDisplayed() + .then(() => this.validationMessage.getText()); + } + + enterName(name: string) { + return this.nameInput.clear() + // .then(() => this.nameInput.sendKeys(name)); + .then(() => Utils.typeInField(this.nameInput, name)); + } + + enterDescription(description: string) { + return this.descriptionTextArea.clear() + // .then(() => this.descriptionTextArea.sendKeys(description)); + .then(() => Utils.typeInField(this.descriptionTextArea, description)); + } + + deleteNameWithBackspace(): promise.Promise { + return this.nameInput.clear() + .then(() => { + return this.nameInput.sendKeys(' ', protractor.Key.CONTROL, 'a', protractor.Key.NULL, protractor.Key.BACK_SPACE); + }); + } + + clickCreate() { + return this.createButton.click(); + } + + clickCancel() { + return this.cancelButton.click() + .then(() => this.waitForDialogToClose()); + } + + clickUpdate() { + return this.updateButton.click(); + } +} diff --git a/e2e/components/header/header.ts b/e2e/components/header/header.ts new file mode 100755 index 000000000..c5e5f944a --- /dev/null +++ b/e2e/components/header/header.ts @@ -0,0 +1,42 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { ElementFinder, by } from 'protractor'; +import { Component } from '../component'; +import { UserInfo } from './user-info'; + +export class Header extends Component { + private locators = { + logoLink: by.css('.app-menu__title'), + userInfo: by.css('aca-current-user') + }; + + logoLink: ElementFinder = this.component.element(this.locators.logoLink); + userInfo: UserInfo = new UserInfo(this.component); + + constructor(ancestor?: ElementFinder) { + super('aca-header', ancestor); + } +} diff --git a/e2e/components/header/user-info.ts b/e2e/components/header/user-info.ts new file mode 100755 index 000000000..36a14b186 --- /dev/null +++ b/e2e/components/header/user-info.ts @@ -0,0 +1,64 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { ElementFinder, by, promise } from 'protractor'; +import { Menu } from '../menu/menu'; +import { Component } from '../component'; + +export class UserInfo extends Component { + private locators = { + avatar: by.css('.current-user__avatar'), + fullName: by.css('.current-user__full-name'), + menuItems: by.css('[mat-menu-item]') + }; + + fullName: ElementFinder = this.component.element(this.locators.fullName); + avatar: ElementFinder = this.component.element(this.locators.avatar); + + menu: Menu = new Menu(); + + constructor(ancestor?: ElementFinder) { + super('aca-current-user', ancestor); + } + + openMenu(): promise.Promise { + const { menu, avatar } = this; + + return avatar.click() + .then(() => menu.wait()) + .then(() => menu); + } + + get name(): promise.Promise { + return this.fullName.getText(); + } + + signOut(): promise.Promise { + return this.openMenu() + .then(menu => { + menu.clickMenuItem('Sign out'); + }); + } +} diff --git a/e2e/components/login/login.ts b/e2e/components/login/login.ts new file mode 100755 index 000000000..5b6a306a8 --- /dev/null +++ b/e2e/components/login/login.ts @@ -0,0 +1,105 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { by, ElementFinder, promise } from 'protractor'; +import { Component } from '../component'; + +export class LoginComponent extends Component { + static selector = 'adf-login'; + + private locators = { + usernameInput: by.css('input#username'), + passwordInput: by.css('input#password'), + passwordVisibility: by.css('.adf-login-password-icon'), + submitButton: by.css('button#login-button'), + errorMessage: by.css('.login-error-message'), + copyright: by.css('.copyright') + }; + + usernameInput: ElementFinder = this.component.element(this.locators.usernameInput); + passwordInput: ElementFinder = this.component.element(this.locators.passwordInput); + submitButton: ElementFinder = this.component.element(this.locators.submitButton); + errorMessage: ElementFinder = this.component.element(this.locators.errorMessage); + copyright: ElementFinder = this.component.element(this.locators.copyright); + passwordVisibility: ElementFinder = this.component.element(this.locators.passwordVisibility); + + constructor(ancestor?: ElementFinder) { + super(LoginComponent.selector, ancestor); + } + + enterUsername(username: string): LoginComponent { + const { usernameInput } = this; + + usernameInput.clear(); + usernameInput.sendKeys(username); + + return this; + } + + enterPassword(password: string): LoginComponent { + const { passwordInput } = this; + + passwordInput.clear(); + passwordInput.sendKeys(password); + + return this; + } + + enterCredentials(username: string, password: string) { + this.enterUsername(username).enterPassword(password); + + return this; + } + + submit(): promise.Promise { + return this.submitButton.click(); + } + + getPasswordVisibility() { + return this.passwordVisibility.getText() + .then(text => { + if (text.endsWith('visibility_off')) { + return false; + } else { + if (text.endsWith('visibility')) { + return true; + } + } + }); + } + + isPasswordShown() { + return this.passwordInput.getAttribute('type') + .then(type => { + if (type === 'text') { + return true; + } else { + if (type === 'password') { + return false; + } + } + }); + } +} diff --git a/e2e/components/menu/menu.ts b/e2e/components/menu/menu.ts new file mode 100755 index 000000000..ba6796a8c --- /dev/null +++ b/e2e/components/menu/menu.ts @@ -0,0 +1,106 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { ElementFinder, ElementArrayFinder, by, browser, ExpectedConditions as EC, promise } from 'protractor'; +import { BROWSER_WAIT_TIMEOUT } from '../../configs'; +import { Component } from '../component'; + +export class Menu extends Component { + private static selectors = { + root: '.mat-menu-panel', + item: '.mat-menu-item', + icon: '.mat-icon', + uploadFiles: 'input[id="upload-multiple-files"]' + }; + + items: ElementArrayFinder = this.component.all(by.css(Menu.selectors.item)); + backdrop: ElementFinder = browser.element(by.css('.cdk-overlay-backdrop')); + uploadFiles: ElementFinder = this.component.element(by.css(Menu.selectors.uploadFiles)); + + constructor(ancestor?: ElementFinder) { + super(Menu.selectors.root, ancestor); + } + + waitForMenuToOpen() { + return browser.wait(EC.presenceOf(browser.element(by.css('.mat-menu-panel'))), BROWSER_WAIT_TIMEOUT) + .then(() => browser.wait(EC.presenceOf(browser.element(by.css('.cdk-overlay-backdrop'))), BROWSER_WAIT_TIMEOUT)) + .then(() => browser.wait(EC.visibilityOf(this.items.get(0)), BROWSER_WAIT_TIMEOUT)); + } + + waitForMenuToClose() { + return browser.wait(EC.not(EC.presenceOf(browser.element(by.css('.mat-menu-panel')))), BROWSER_WAIT_TIMEOUT); + } + + closeMenu() { + if (this.backdrop.isPresent()) { + return this.backdrop.click(); + } else { + return browser.actions().mouseMove(browser.$('body'), { x: 0, y: 0 }).click().perform(); + } + } + + getNthItem(nth: number): ElementFinder { + return this.items.get(nth - 1); + } + + getItemByLabel(menuItem: string): ElementFinder { + return this.component.element(by.cssContainingText(Menu.selectors.item, menuItem)); + } + + getItemTooltip(menuItem: string): promise.Promise { + return this.getItemByLabel(menuItem).getAttribute('title'); + } + + getItemIconText(menuItem: string) { + return this.getItemByLabel(menuItem).element(by.css(Menu.selectors.icon)).getText(); + + } + + getItemsCount(): promise.Promise { + return this.items.count(); + } + + clickNthItem(nth: number): promise.Promise { + const elem = this.getNthItem(nth); + return browser.wait(EC.elementToBeClickable(elem), BROWSER_WAIT_TIMEOUT) + .then(() => browser.actions().mouseMove(elem).click().perform()) + .then(() => this.waitForMenuToClose()); + } + + clickMenuItem(menuItem: string): promise.Promise { + const elem = this.getItemByLabel(menuItem); + return browser.wait(EC.elementToBeClickable(elem), BROWSER_WAIT_TIMEOUT) + .then(() => elem.click()) + .then(() => this.waitForMenuToClose()); + } + + isMenuItemPresent(title: string): promise.Promise { + return this.component.element(by.cssContainingText(Menu.selectors.item, title)).isPresent(); + } + + uploadFile() { + return this.uploadFiles; + } +} diff --git a/e2e/components/pagination/pagination.ts b/e2e/components/pagination/pagination.ts new file mode 100755 index 000000000..47a07dd85 --- /dev/null +++ b/e2e/components/pagination/pagination.ts @@ -0,0 +1,98 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { ElementFinder, promise, by } from 'protractor'; +import { Menu } from '../menu/menu'; +import { Component } from '../component'; + +export class Pagination extends Component { + private static selectors = { + root: 'adf-pagination', + range: '.adf-pagination__range', + maxItems: '.adf-pagination__max-items', + currentPage: '.adf-pagination__current-page', + totalPages: '.adf-pagination__total-pages', + + previousButton: '.adf-pagination__previous-button', + nextButton: '.adf-pagination__next-button', + maxItemsButton: '.adf-pagination__max-items + button[mat-icon-button]', + pagesButton: '.adf-pagination__current-page + button[mat-icon-button]' + }; + + range: ElementFinder = this.component.element(by.css(Pagination.selectors.range)); + maxItems: ElementFinder = this.component.element(by.css(Pagination.selectors.maxItems)); + currentPage: ElementFinder = this.component.element(by.css(Pagination.selectors.currentPage)); + totalPages: ElementFinder = this.component.element(by.css(Pagination.selectors.totalPages)); + previousButton: ElementFinder = this.component.element(by.css(Pagination.selectors.previousButton)); + nextButton: ElementFinder = this.component.element(by.css(Pagination.selectors.nextButton)); + maxItemsButton: ElementFinder = this.component.element(by.css(Pagination.selectors.maxItemsButton)); + pagesButton: ElementFinder = this.component.element(by.css(Pagination.selectors.pagesButton)); + + menu: Menu = new Menu(); + + constructor(ancestor?: ElementFinder) { + super(Pagination.selectors.root, ancestor); + } + + openMaxItemsMenu(): promise.Promise { + const { menu, maxItemsButton } = this; + + return maxItemsButton.click() + .then(() => menu.waitForMenuToOpen()) + .then(() => menu); + } + + openCurrentPageMenu(): promise.Promise { + const { menu, pagesButton } = this; + + return pagesButton.click() + .then(() => menu.waitForMenuToOpen()) + .then(() => menu); + } + + getText(elem: ElementFinder) { + return elem.getText(); + } + + resetToDefaultPageSize(): promise.Promise { + return this.openMaxItemsMenu() + .then(menu => menu.clickMenuItem('25')) + .then(() => this.menu.waitForMenuToClose()); + } + + resetToDefaultPageNumber(): promise.Promise { + return this.openCurrentPageMenu() + .then(menu => menu.clickMenuItem('1')) + .then(() => this.menu.waitForMenuToClose()); + } + + clickNext(): promise.Promise { + return this.nextButton.click(); + } + + clickPrevious(): promise.Promise { + return this.previousButton.click(); + } +} diff --git a/e2e/components/sidenav/sidenav.ts b/e2e/components/sidenav/sidenav.ts new file mode 100755 index 000000000..3c14615b6 --- /dev/null +++ b/e2e/components/sidenav/sidenav.ts @@ -0,0 +1,82 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { ElementFinder, ElementArrayFinder, by, promise } from 'protractor'; +import { Menu } from '../menu/menu'; +import { Component } from '../component'; + +export class Sidenav extends Component { + private static selectors = { + root: 'app-sidenav', + link: '.sidenav-menu__item', + label: '.menu__item--label', + activeLink: '.menu__item--active', + newButton: '.adf-sidebar-action-menu-button' + }; + + links: ElementArrayFinder = this.component.all(by.css(Sidenav.selectors.link)); + activeLink: ElementFinder = this.component.element(by.css(Sidenav.selectors.activeLink)); + newButton: ElementArrayFinder = this.component.all(by.css(Sidenav.selectors.newButton)); + + menu: Menu = new Menu(); + + constructor(ancestor?: ElementFinder) { + super(Sidenav.selectors.root, ancestor); + } + + openNewMenu(): promise.Promise { + const { menu, newButton } = this; + + return newButton.click() + .then(() => menu.waitForMenuToOpen()) + .then(() => menu); + } + + openCreateDialog(): any { + return this.openNewMenu() + .then(() => this.menu.clickMenuItem('Create folder')); + } + + isActiveByLabel(label: string): promise.Promise { + return this.getLinkByLabel(label).getAttribute('class') + .then(className => className.includes(Sidenav.selectors.activeLink.replace('.', ''))); + } + + getLink(label: string): ElementFinder { + return this.component.element(by.cssContainingText(Sidenav.selectors.link, label)); + } + + getLinkByLabel(label: string): ElementFinder { + return this.component.element(by.cssContainingText(Sidenav.selectors.label, label)); + } + + getLinkTooltip(label: string): promise.Promise { + return this.getLink(label).getAttribute('title'); + } + + navigateToLinkByLabel(label: string): promise.Promise { + return this.getLinkByLabel(label).click(); + } +} diff --git a/e2e/components/toolbar/toolbar-actions.ts b/e2e/components/toolbar/toolbar-actions.ts new file mode 100755 index 000000000..6847a306d --- /dev/null +++ b/e2e/components/toolbar/toolbar-actions.ts @@ -0,0 +1,64 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { ElementFinder, ElementArrayFinder, by, promise } from 'protractor'; +import { Menu } from '../menu/menu'; +import { Component } from '../component'; + +export class ToolbarActions extends Component { + private static selectors = { + root: 'adf-toolbar', + button: '.mat-icon-button' + }; + + menu: Menu = new Menu(); + buttons: ElementArrayFinder = this.component.all(by.css(ToolbarActions.selectors.button)); + + constructor(ancestor?: ElementFinder) { + super(ToolbarActions.selectors.root, ancestor); + } + + isEmpty(): promise.Promise { + return this.buttons.count().then(count => (count === 0)); + } + + isButtonPresent(title: string): promise.Promise { + return this.component.element(by.css(`${ToolbarActions.selectors.button}[title="${title}"]`)).isPresent(); + } + + getButtonByLabel(label: string): ElementFinder { + return this.component.element(by.cssContainingText(ToolbarActions.selectors.button, label)); + } + + getButtonByTitleAttribute(title: string): ElementFinder { + return this.component.element(by.css(`${ToolbarActions.selectors.button}[title="${title}"]`)); + } + + openMoreMenu() { + return this.getButtonByTitleAttribute('More actions').click() + .then(() => this.menu.waitForMenuToOpen()) + .then(() => this.menu); + } +} diff --git a/e2e/components/toolbar/toolbar-breadcrumb.ts b/e2e/components/toolbar/toolbar-breadcrumb.ts new file mode 100755 index 000000000..7b939d120 --- /dev/null +++ b/e2e/components/toolbar/toolbar-breadcrumb.ts @@ -0,0 +1,82 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { ElementFinder, ElementArrayFinder, by, promise } from 'protractor'; +import { Component } from '../component'; + +export class ToolbarBreadcrumb extends Component { + private static selectors = { + root: 'adf-breadcrumb', + item: '.adf-breadcrumb-item', + currentItem: '.adf-breadcrumb-item-current' + }; + + items: ElementArrayFinder = this.component.all(by.css(ToolbarBreadcrumb.selectors.item)); + currentItem: ElementFinder = this.component.element(by.css(ToolbarBreadcrumb.selectors.currentItem)); + + constructor(ancestor?: ElementFinder) { + super(ToolbarBreadcrumb.selectors.root, ancestor); + } + + getNthItem(nth: number): ElementFinder { + return this.items.get(nth - 1); + } + + getNthItemName(nth: number) { + return this.getNthItem(nth).getText(); + } + + getItemsCount(): promise.Promise { + return this.items.count(); + } + + getAllItems() { + return this.items.map(elem => elem.getText().then(str => str.split('\nchevron_right')[0])); + } + + getFirstItemName(): promise.Promise { + return this.items.get(0).getText(); + } + + getCurrentItem() { + return this.currentItem; + } + + getCurrentItemName(): promise.Promise { + return this.currentItem.getText(); + } + + clickItem(name: string) { + return this.component.element(by.css(`${ToolbarBreadcrumb.selectors.item}[title=${name}]`)).click(); + } + + clickNthItem(nth: number) { + return this.getNthItem(nth).click(); + } + + getNthItemTooltip(nth: number) { + return this.getNthItem(nth).getAttribute('title'); + } +} diff --git a/e2e/components/toolbar/toolbar.ts b/e2e/components/toolbar/toolbar.ts new file mode 100755 index 000000000..7bfbcfba0 --- /dev/null +++ b/e2e/components/toolbar/toolbar.ts @@ -0,0 +1,42 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { ElementFinder } from 'protractor'; +import { Component } from '../component'; +import { ToolbarActions } from './toolbar-actions'; +import { ToolbarBreadcrumb } from './toolbar-breadcrumb'; + +export class Toolbar extends Component { + private static selectors = { + root: '.inner-layout__header' + }; + + actions: ToolbarActions = new ToolbarActions(this.component); + breadcrumb: ToolbarBreadcrumb = new ToolbarBreadcrumb(this.component); + + constructor(ancestor?: ElementFinder) { + super(Toolbar.selectors.root, ancestor); + } +} diff --git a/e2e/configs.ts b/e2e/configs.ts new file mode 100755 index 000000000..3ac50a40d --- /dev/null +++ b/e2e/configs.ts @@ -0,0 +1,78 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +export const BROWSER_RESOLUTION_WIDTH = 1200; +export const BROWSER_RESOLUTION_HEIGHT = 800; + +export const BROWSER_WAIT_TIMEOUT = 30000; + +// Application configs +export const APP_HOST = 'http://localhost:3000'; + +// Repository configs +export const REPO_API_HOST = 'http://localhost:8080'; +export const REPO_API_TENANT = '-default-'; + +// Admin details +export const ADMIN_USERNAME = 'admin'; +export const ADMIN_PASSWORD = 'admin'; +export const ADMIN_FULL_NAME = 'Administrator'; + +// Application Routes +export const APP_ROUTES = { + FAVORITES: '/favorites', + FILE_LIBRARIES: '/libraries', + LOGIN: '/login', + LOGOUT: '/logout', + PERSONAL_FILES: '/personal-files', + RECENT_FILES: '/recent-files', + SHARED_FILES: '/shared', + TRASHCAN: '/trashcan' +}; + +// Sidebar labels +export const SIDEBAR_LABELS = { + PERSONAL_FILES: 'Personal Files', + FILE_LIBRARIES: 'File Libraries', + SHARED_FILES: 'Shared', + RECENT_FILES: 'Recent Files', + FAVORITES: 'Favorites', + TRASH: 'Trash' +}; + +// Site visibility +export const SITE_VISIBILITY = { + PUBLIC: 'PUBLIC', + MODERATED: 'MODERATED', + PRIVATE: 'PRIVATE' +}; + +// Site roles +export const SITE_ROLES = { + SITE_CONSUMER: 'SiteConsumer', + SITE_COLLABORATOR: 'SiteCollaborator', + SITE_CONTRIBUTOR: 'SiteContributor', + SITE_MANAGER: 'SiteManager' +}; diff --git a/e2e/pages/browsing-page.ts b/e2e/pages/browsing-page.ts new file mode 100755 index 000000000..d5eab3de1 --- /dev/null +++ b/e2e/pages/browsing-page.ts @@ -0,0 +1,40 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { promise } from 'protractor'; +import { Header, DataTable, Pagination, Toolbar, Sidenav } from '../components/components'; +import { Page } from './page'; + +export class BrowsingPage extends Page { + header = new Header(this.app); + sidenav = new Sidenav(this.app); + toolbar = new Toolbar(this.app); + dataTable = new DataTable(this.app); + pagination = new Pagination(this.app); + + signOut(): promise.Promise { + return this.header.userInfo.signOut(); + } +} diff --git a/e2e/pages/login-page.ts b/e2e/pages/login-page.ts new file mode 100755 index 000000000..87fcf6981 --- /dev/null +++ b/e2e/pages/login-page.ts @@ -0,0 +1,75 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +import { browser, ExpectedConditions as EC, promise } from 'protractor'; +import { LoginComponent } from '../components/components'; +import { Page } from './page'; +import { Utils } from '../utilities/utils'; + +import { + ADMIN_USERNAME, + ADMIN_PASSWORD, + BROWSER_WAIT_TIMEOUT, + APP_ROUTES +} from '../configs'; + +export class LoginPage extends Page { + login: LoginComponent = new LoginComponent(this.app); + + /** @override */ + constructor() { + super(APP_ROUTES.LOGIN); + } + + /** @override */ + load(): promise.Promise { + return super.load().then(() => { + const { submitButton } = this.login; + const hasSubmitButton = EC.presenceOf(submitButton); + + return browser.wait(hasSubmitButton, BROWSER_WAIT_TIMEOUT) + .then(() => Utils.clearLocalStorage()) + .then(() => browser.manage().deleteAllCookies()); + }); + } + + loginWith(username: string, password?: string): promise.Promise { + const pass = password || username; + return this.load() + .then(() => this.login.enterCredentials(username, pass).submit()) + .then(() => super.waitForApp()); + } + + loginWithAdmin(): promise.Promise { + return this.load() + .then(() => this.loginWith(ADMIN_USERNAME, ADMIN_PASSWORD)); + } + + tryLoginWith(username: string, password?: string): promise.Promise { + const pass = password || username; + return this.load() + .then(() => this.login.enterCredentials(username, pass).submit()) + .then(() => browser.wait(EC.presenceOf(this.login.errorMessage), BROWSER_WAIT_TIMEOUT)); + } +} diff --git a/e2e/pages/logout-page.ts b/e2e/pages/logout-page.ts new file mode 100755 index 000000000..b78cb0443 --- /dev/null +++ b/e2e/pages/logout-page.ts @@ -0,0 +1,43 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { promise } from 'protractor'; +import { Page } from './page'; +import { APP_ROUTES } from '../configs'; +import { Utils } from '../utilities/utils'; + +export class LogoutPage extends Page { + /** @override */ + constructor() { + super(APP_ROUTES.LOGIN); + } + + /** @override */ + load(): promise.Promise { + return Utils.clearLocalStorage() + .then(() => Utils.clearSessionStorage()) + .then(() => super.load()); + } +} diff --git a/e2e/pages/page.ts b/e2e/pages/page.ts new file mode 100755 index 000000000..570b40c00 --- /dev/null +++ b/e2e/pages/page.ts @@ -0,0 +1,110 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { browser, element, by, ElementFinder, promise, ExpectedConditions as EC } from 'protractor'; +import { BROWSER_WAIT_TIMEOUT } from './../configs'; + +export abstract class Page { + private static USE_HASH_STRATEGY = true; + + private locators = { + app: by.css('app-root'), + layout: by.css('app-layout'), + overlay: by.css('.cdk-overlay-container'), + dialogContainer: by.css('.mat-dialog-container'), + snackBarContainer: '.cdk-overlay-pane snack-bar-container.mat-snack-bar-container', + snackBar: 'simple-snack-bar', + snackBarAction: 'button.mat-simple-snackbar-action' + }; + + public app: ElementFinder = element(this.locators.app); + public layout: ElementFinder = element(this.locators.layout); + public overlay: ElementFinder = element(this.locators.overlay); + snackBar: ElementFinder = browser.$(this.locators.snackBar); + dialogContainer: ElementFinder = element(this.locators.dialogContainer); + snackBarContainer: ElementFinder = browser.$(this.locators.snackBarContainer); + snackBarAction: ElementFinder = browser.$(this.locators.snackBarAction); + + constructor(public url: string = '') {} + + get title(): promise.Promise { + return browser.getTitle(); + } + + load(relativeUrl: string = ''): promise.Promise { + const hash = Page.USE_HASH_STRATEGY ? '/#' : ''; + const path = `${hash}${this.url}${relativeUrl}`; + + return browser.get(path); + } + + waitForApp() { + return browser.wait(EC.presenceOf(this.layout), BROWSER_WAIT_TIMEOUT); + } + + waitForSnackBarToAppear() { + return browser.wait(EC.visibilityOf(this.snackBarContainer), BROWSER_WAIT_TIMEOUT); + } + + waitForSnackBarToClose() { + return browser.wait(EC.not(EC.visibilityOf(this.snackBarContainer)), BROWSER_WAIT_TIMEOUT); + } + + waitForDialog() { + return browser.wait(EC.visibilityOf(this.dialogContainer), BROWSER_WAIT_TIMEOUT); + } + + waitForDialogToClose() { + return browser.wait(EC.not(EC.visibilityOf(this.dialogContainer)), BROWSER_WAIT_TIMEOUT); + } + + refresh(): promise.Promise { + return browser.refresh(); + } + + getDialogActionByLabel(label) { + return element(by.cssContainingText('.mat-button-wrapper', label)); + } + + isSnackBarDisplayed(): promise.Promise { + return this.snackBar.isDisplayed(); + } + + getSnackBarMessage(): promise.Promise { + return this.waitForSnackBarToAppear() + .then(() => this.snackBar.getAttribute('innerText')); + } + + getSnackBarAction() { + return this.waitForSnackBarToAppear() + .then(() => this.snackBarAction); + } + + clickSnackBarAction() { + return browser.executeScript(function (elem) { + elem.click(); + }, this.snackBarAction); + } +} diff --git a/e2e/pages/pages.ts b/e2e/pages/pages.ts new file mode 100755 index 000000000..196228230 --- /dev/null +++ b/e2e/pages/pages.ts @@ -0,0 +1,28 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +export * from './browsing-page'; +export * from './login-page'; +export * from './logout-page'; diff --git a/e2e/suites/actions/create-folder.test.ts b/e2e/suites/actions/create-folder.test.ts new file mode 100755 index 000000000..d3b4024d8 --- /dev/null +++ b/e2e/suites/actions/create-folder.test.ts @@ -0,0 +1,278 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { browser } from 'protractor'; + +import { SIDEBAR_LABELS, SITE_VISIBILITY, SITE_ROLES } from '../../configs'; +import { LoginPage, LogoutPage, BrowsingPage } from '../../pages/pages'; +import { CreateOrEditFolderDialog } from '../../components/dialog/create-edit-folder-dialog'; +import { Utils } from '../../utilities/utils'; +import { RepoClient } from '../../utilities/repo-client/repo-client'; + +describe('Create folder', () => { + const username = `user-${Utils.random()}`; + + const parent = `parent-${Utils.random()}`; + const folderName1 = `folder-${Utils.random()}`; + const folderName2 = `folder-${Utils.random()}`; + const folderDescription = 'description of my folder'; + const duplicateFolderName = `folder-${Utils.random()}`; + const nameWithSpaces = ` folder-${Utils.random()} `; + + const siteName = `site-private-${Utils.random()}`; + + const apis = { + admin: new RepoClient(), + user: new RepoClient(username, username) + }; + + const loginPage = new LoginPage(); + const logoutPage = new LogoutPage(); + const personalFilesPage = new BrowsingPage(); + const createDialog = new CreateOrEditFolderDialog(); + const { dataTable } = personalFilesPage; + + beforeAll(done => { + apis.admin.people.createUser(username) + .then(() => apis.admin.sites.createSite(siteName, SITE_VISIBILITY.PRIVATE)) + .then(() => apis.admin.nodes.createFolders([ folderName1 ], `Sites/${siteName}/documentLibrary`)) + .then(() => apis.admin.sites.addSiteMember(siteName, username, SITE_ROLES.SITE_CONSUMER)) + .then(() => apis.user.nodes.createFolders([ duplicateFolderName ], parent)) + .then(() => loginPage.loginWith(username)) + .then(done); + }); + + beforeEach(done => { + personalFilesPage.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.PERSONAL_FILES) + .then(() => dataTable.waitForHeader()) + .then(done); + }); + + afterEach(done => { + browser.actions().mouseMove(browser.$('body'), { x: 0, y: 0 }).click().perform().then(done); + }); + + afterAll(done => { + Promise + .all([ + apis.admin.sites.deleteSite(siteName), + apis.user.nodes.deleteNodes([ parent ]), + logoutPage.load() + ]) + .then(done); + }); + + it('option is enabled when having enough permissions', () => { + personalFilesPage.dataTable.doubleClickOnItemName(parent) + .then(() => personalFilesPage.sidenav.openNewMenu()) + .then(menu => { + const isEnabled = menu.getItemByLabel('Create folder').isEnabled(); + expect(isEnabled).toBe(true, 'Create folder is not enabled'); + }); + }); + + it('creates new folder with name', () => { + personalFilesPage.dataTable.doubleClickOnItemName(parent) + .then(() => personalFilesPage.sidenav.openCreateDialog()) + .then(() => createDialog.waitForDialogToOpen()) + .then(() => createDialog.enterName(folderName1)) + .then(() => createDialog.clickCreate()) + .then(() => createDialog.waitForDialogToClose()) + .then(() => dataTable.waitForHeader()) + .then(() => { + const isPresent = dataTable.getRowName(folderName1).isPresent(); + expect(isPresent).toBe(true, 'Folder not displayed in list view'); + }); + }); + + it('creates new folder with name and description', () => { + personalFilesPage.dataTable.doubleClickOnItemName(parent) + .then(() => personalFilesPage.sidenav.openCreateDialog()) + .then(() => createDialog.waitForDialogToOpen()) + .then(() => createDialog.enterName(folderName2)) + .then(() => createDialog.enterDescription(folderDescription)) + .then(() => createDialog.clickCreate()) + .then(() => createDialog.waitForDialogToClose()) + .then(() => dataTable.waitForHeader()) + .then(() => expect(dataTable.getRowName(folderName2).isPresent()).toBe(true, 'Folder not displayed')) + .then(() => apis.user.nodes.getNodeDescription(folderName2, parent)) + .then(desc => expect(desc).toEqual(folderDescription)); + }); + + it('enabled option tooltip', () => { + personalFilesPage.dataTable.doubleClickOnItemName(parent) + .then(() => personalFilesPage.sidenav.openNewMenu()) + .then(menu => browser.actions().mouseMove(menu.getItemByLabel('Create folder')).perform() + .then(() => menu)) + .then(menu => { + expect(menu.getItemTooltip('Create folder')).toContain('Create new folder'); + }); + }); + + it('option is disabled when not enough permissions', () => { + const fileLibrariesPage = new BrowsingPage(); + + fileLibrariesPage.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.FILE_LIBRARIES) + .then(() => fileLibrariesPage.dataTable.doubleClickOnItemName(siteName)) + .then(() => fileLibrariesPage.dataTable.doubleClickOnItemName(folderName1)) + .then(() => fileLibrariesPage.sidenav.openNewMenu()) + .then(menu => { + const isEnabled = menu.getItemByLabel('Create folder').isEnabled(); + expect(isEnabled).toBe(false, 'Create folder is not disabled'); + }); + }); + + it('disabled option tooltip', () => { + const fileLibrariesPage = new BrowsingPage(); + + fileLibrariesPage.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.FILE_LIBRARIES) + .then(() => fileLibrariesPage.dataTable.doubleClickOnItemName(siteName)) + .then(() => fileLibrariesPage.dataTable.doubleClickOnItemName(folderName1)) + .then(() => fileLibrariesPage.sidenav.openNewMenu()) + .then(menu => browser.actions().mouseMove(menu.getItemByLabel('Create folder')).perform() + .then(() => menu)) + .then(menu => { + const tooltip = menu.getItemTooltip('Create folder'); + expect(tooltip).toContain(`You can't create a folder here`); + }); + }); + + it('dialog UI elements', () => { + personalFilesPage.dataTable.doubleClickOnItemName(parent) + .then(() => personalFilesPage.sidenav.openCreateDialog()) + .then(() => createDialog.waitForDialogToOpen()) + .then(() => { + const dialogTitle = createDialog.getTitle(); + const isFolderNameDisplayed = createDialog.nameInput.isDisplayed(); + const isDescriptionDisplayed = createDialog.descriptionTextArea.isDisplayed(); + const isCreateEnabled = createDialog.createButton.isEnabled(); + const isCancelEnabled = createDialog.cancelButton.isEnabled(); + + expect(dialogTitle).toMatch('Create new folder'); + expect(isFolderNameDisplayed).toBe(true, 'Name input is not displayed'); + expect(isDescriptionDisplayed).toBe(true, 'Description field is not displayed'); + expect(isCreateEnabled).toBe(false, 'Create button is not disabled'); + expect(isCancelEnabled).toBe(true, 'Cancel button is not enabled'); + }); + }); + + it('with empty folder name', () => { + personalFilesPage.dataTable.doubleClickOnItemName(parent) + .then(() => personalFilesPage.sidenav.openCreateDialog()) + .then(() => createDialog.waitForDialogToOpen()) + .then(() => createDialog.deleteNameWithBackspace()) + .then(() => { + const isCreateEnabled = createDialog.createButton.isEnabled(); + const validationMessage = createDialog.getValidationMessage(); + + expect(isCreateEnabled).toBe(false, 'Create button is enabled'); + expect(validationMessage).toMatch('Folder name is required'); + }); + }); + + it('with folder name ending with a dot "."', () => { + personalFilesPage.dataTable.doubleClickOnItemName(parent) + .then(() => personalFilesPage.sidenav.openCreateDialog()) + .then(() => createDialog.waitForDialogToOpen()) + .then(() => createDialog.enterName('folder-name.')) + .then(() => { + const isCreateEnabled = createDialog.createButton.isEnabled(); + const validationMessage = createDialog.getValidationMessage(); + + expect(isCreateEnabled).toBe(false, 'Create button is not disabled'); + expect(validationMessage).toMatch(`Folder name can't end with a period .`); + }); + }); + + it('with folder name containing special characters', () => { + const namesWithSpecialChars = [ 'a*a', 'a"a', 'aa', `a\\a`, 'a/a', 'a?a', 'a:a', 'a|a' ]; + + personalFilesPage.dataTable.doubleClickOnItemName(parent) + .then(() => personalFilesPage.sidenav.openCreateDialog()) + .then(() => createDialog.waitForDialogToOpen()) + .then(() => namesWithSpecialChars.forEach(name => { + createDialog.enterName(name); + + const isCreateEnabled = createDialog.createButton.isEnabled(); + const validationMessage = createDialog.getValidationMessage(); + + expect(isCreateEnabled).toBe(false, 'Create button is not disabled'); + expect(validationMessage).toContain(`Folder name can't contain these characters`); + })); + }); + + it('with folder name containing only spaces', () => { + personalFilesPage.dataTable.doubleClickOnItemName(parent) + .then(() => personalFilesPage.sidenav.openCreateDialog()) + .then(() => createDialog.waitForDialogToOpen()) + .then(() => createDialog.enterName(' ')) + .then(() => { + const isCreateEnabled = createDialog.createButton.isEnabled(); + const validationMessage = createDialog.getValidationMessage(); + + expect(isCreateEnabled).toBe(false, 'Create button is not disabled'); + expect(validationMessage).toMatch(`Folder name can't contain only spaces`); + }); + }); + + it('cancel folder creation', () => { + personalFilesPage.dataTable.doubleClickOnItemName(parent) + .then(() => personalFilesPage.sidenav.openCreateDialog()) + .then(() => createDialog.waitForDialogToOpen()) + .then(() => createDialog.enterName('test')) + .then(() => createDialog.enterDescription('test description')) + .then(() => createDialog.clickCancel()) + .then(() => { + expect(createDialog.component.isPresent()).not.toBe(true, 'dialog is not closed'); + }); + }); + + it('duplicate folder name', () => { + personalFilesPage.dataTable.doubleClickOnItemName(parent) + .then(() => personalFilesPage.sidenav.openCreateDialog()) + .then(() => createDialog.waitForDialogToOpen()) + .then(() => createDialog.enterName(duplicateFolderName)) + .then(() => createDialog.clickCreate()) + .then(() => personalFilesPage.getSnackBarMessage()) + .then(message => { + expect(message).toEqual(`There's already a folder with this name. Try a different name.`); + expect(createDialog.component.isPresent()).toBe(true, 'dialog is not present'); + }); + }); + + it('trim ending spaces from folder name', () => { + personalFilesPage.dataTable.doubleClickOnItemName(parent) + .then(() => personalFilesPage.sidenav.openCreateDialog()) + .then(() => createDialog.waitForDialogToOpen()) + .then(() => createDialog.enterName(nameWithSpaces)) + .then(() => createDialog.clickCreate()) + .then(() => createDialog.waitForDialogToClose()) + .then(() => dataTable.waitForHeader()) + .then(() => { + const isPresent = dataTable.getRowName(nameWithSpaces.trim()).isPresent(); + expect(isPresent).toBe(true, 'Folder not displayed in list view'); + }); + }); +}); diff --git a/e2e/suites/actions/delete.test.ts b/e2e/suites/actions/delete.test.ts new file mode 100755 index 000000000..f3a7b4c2f --- /dev/null +++ b/e2e/suites/actions/delete.test.ts @@ -0,0 +1,504 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { browser } from 'protractor'; +import { LoginPage, LogoutPage, BrowsingPage } from '../../pages/pages'; +import { SIDEBAR_LABELS } from '../../configs'; +import { RepoClient } from '../../utilities/repo-client/repo-client'; +import { Utils } from '../../utilities/utils'; + +describe('Delete content', () => { + const username = `user-${Utils.random()}`; + + const apis = { + admin: new RepoClient(), + user: new RepoClient(username, username) + }; + + const loginPage = new LoginPage(); + const logoutPage = new LogoutPage(); + const page = new BrowsingPage(); + const { dataTable, toolbar } = page; + + beforeAll(done => { + apis.admin.people.createUser(username).then(done); + }); + + afterAll(done => { + apis.admin.trashcan.emptyTrash().then(done); + }); + + xit(''); + + describe('on Personal Files', () => { + const file1 = `file1-${Utils.random()}.txt`; let file1Id; + const file2 = `file2-${Utils.random()}.txt`; let file2Id; + const file3 = `file3-${Utils.random()}.txt`; + const file4 = `file4-${Utils.random()}.txt`; let file4Id; + const folder1 = `folder1-${Utils.random()}`; let folder1Id; + const folder2 = `folder2-${Utils.random()}`; let folder2Id; + const fileLocked1 = `fileLocked-${Utils.random()}.txt`; let fileLocked1Id; + + beforeAll(done => { + apis.user.nodes.createFile(file1).then(resp => file1Id = resp.data.entry.id) + .then(() => apis.user.nodes.createFile(file2).then(resp => file2Id = resp.data.entry.id)) + .then(() => apis.user.nodes.createFolder(folder1).then(resp => folder1Id = resp.data.entry.id)) + .then(() => apis.user.nodes.createFolder(folder2).then(resp => folder2Id = resp.data.entry.id)) + .then(() => apis.user.nodes.createFile(file3, folder1Id)) + .then(() => apis.user.nodes.createFile(file4, folder2Id).then(resp => file4Id = resp.data.entry.id)) + .then(() => apis.user.nodes.lockFile(file4Id)) + + .then(() => apis.user.nodes.createFile(fileLocked1).then(resp => fileLocked1Id = resp.data.entry.id)) + .then(() => apis.user.nodes.lockFile(fileLocked1Id)) + + .then(() => loginPage.loginWith(username)) + .then(done); + }); + + beforeEach(done => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.PERSONAL_FILES) + .then(() => dataTable.waitForHeader()) + .then(done); + }); + + afterEach(done => { + page.refresh().then(done); + }); + + afterAll(done => { + Promise.all([ + logoutPage.load(), + apis.user.nodes.unlockFile(file4Id) + .then(() => apis.user.nodes.unlockFile(fileLocked1Id)) + .then(() => apis.user.nodes.deleteNodesById([file1Id, file2Id, folder1Id, folder2Id, fileLocked1Id])) + ]) + .then(done); + }); + + it('delete a file and check notification', () => { + let items: number; + page.dataTable.countRows().then(number => { items = number; }); + + dataTable.clickOnItemName(file1) + .then(() => toolbar.actions.openMoreMenu()) + .then(() => toolbar.actions.menu.clickMenuItem('Delete')) + .then(() => page.getSnackBarMessage()) + .then(message => { + expect(message).toContain(`${file1} deleted`); + expect(dataTable.getRowName(file1).isPresent()).toBe(false, 'Item was not removed from list'); + items--; + expect(page.pagination.range.getText()).toContain(`1-${items} of ${items}`); + }) + .then(() => page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.TRASH)) + .then(() => expect(dataTable.getRowName(file1).isPresent()).toBe(true, 'Item is not in trash')) + + .then(() => apis.user.trashcan.restore(file1Id)); + }); + + it('delete multiple files and check notification', () => { + let items: number; + page.dataTable.countRows().then(number => { items = number; }); + + dataTable.selectMultipleItems([file1, file2]) + .then(() => toolbar.actions.openMoreMenu()) + .then(() => toolbar.actions.menu.clickMenuItem('Delete')) + .then(() => page.getSnackBarMessage()) + .then(message => { + expect(message).toContain(`Deleted 2 items`); + expect(dataTable.getRowName(file1).isPresent()).toBe(false, `${file1} was not removed from list`); + expect(dataTable.getRowName(file2).isPresent()).toBe(false, `${file2} was not removed from list`); + items = items - 2; + expect(page.pagination.range.getText()).toContain(`1-${items} of ${items}`); + }) + .then(() => page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.TRASH)) + .then(() => { + expect(dataTable.getRowName(file1).isPresent()).toBe(true, `${file1} is not in trash`); + expect(dataTable.getRowName(file2).isPresent()).toBe(true, `${file2} is not in trash`); + }) + + .then(() => apis.user.trashcan.restore(file1Id)) + .then(() => apis.user.trashcan.restore(file2Id)); + }); + + it('delete a folder with content', () => { + let items: number; + page.dataTable.countRows().then(number => { items = number; }); + + dataTable.clickOnItemName(folder1) + .then(() => toolbar.actions.openMoreMenu()) + .then(() => toolbar.actions.menu.clickMenuItem('Delete')) + .then(() => { + expect(dataTable.getRowName(folder1).isPresent()).toBe(false, 'Item was not removed from list'); + items--; + expect(page.pagination.range.getText()).toContain(`1-${items} of ${items}`); + }) + .then(() => page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.TRASH)) + .then(() => { + expect(dataTable.getRowName(folder1).isPresent()).toBe(true, 'Item is not in trash'); + expect(dataTable.getRowName(file3).isPresent()).toBe(false, 'Item is in trash'); + }) + + .then(() => apis.user.trashcan.restore(folder1Id)); + }); + + it('delete a folder containing locked files', () => { + dataTable.clickOnItemName(folder2) + .then(() => toolbar.actions.openMoreMenu()) + .then(() => toolbar.actions.menu.clickMenuItem('Delete')) + .then(() => page.getSnackBarMessage()) + .then(message => { + expect(message).toContain(`${folder2} couldn't be deleted`); + expect(dataTable.getRowName(folder2).isPresent()).toBe(true, 'Item was removed from list'); + }) + .then(() => page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.TRASH)) + .then(() => { + expect(dataTable.getRowName(folder2).isPresent()).toBe(false, 'Item is in trash'); + expect(dataTable.getRowName(file4).isPresent()).toBe(false, 'Item is in trash'); + }); + }); + + it('notification on multiple items deletion - some items fail to delete', () => { + dataTable.selectMultipleItems([file1, folder2]) + .then(() => toolbar.actions.openMoreMenu()) + .then(() => toolbar.actions.menu.clickMenuItem('Delete')) + .then(() => page.getSnackBarMessage()) + .then(message => expect(message).toContain(`Deleted 1 item, 1 couldn't be deleted`)) + + .then(() => apis.user.trashcan.restore(file1Id)); + }); + + // TODO: needs to operate on two folders containing locked items + xit('Notification on multiple items deletion - all items fail to delete', () => { + dataTable.selectMultipleItems([fileLocked1, folder2]) + .then(() => toolbar.actions.openMoreMenu()) + .then(() => toolbar.actions.menu.clickMenuItem('Delete')) + .then(() => page.getSnackBarMessage()) + .then(message => expect(message).toEqual(`2 items couldn't be deleted`)); + }); + }); + + describe('on Shared Files', () => { + const sharedFile1 = `sharedFile1-${Utils.random()}.txt`; let sharedFile1Id; + const sharedFile2 = `sharedFile2-${Utils.random()}.txt`; let sharedFile2Id; + const sharedFile3 = `sharedFile3-${Utils.random()}.txt`; let sharedFile3Id; + + beforeAll(done => { + apis.user.nodes.createFile(sharedFile1).then(resp => sharedFile1Id = resp.data.entry.id) + .then(() => apis.user.nodes.createFile(sharedFile2).then(resp => sharedFile2Id = resp.data.entry.id)) + .then(() => apis.user.nodes.createFile(sharedFile3).then(resp => sharedFile3Id = resp.data.entry.id)) + .then(() => apis.user.shared.shareFilesByIds([sharedFile1Id, sharedFile2Id, sharedFile3Id])) + .then(() => apis.user.shared.waitForApi({ expect: 3 })) + + .then(() => loginPage.loginWith(username)) + .then(done); + }); + + beforeEach(done => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.SHARED_FILES) + .then(() => dataTable.waitForHeader()) + .then(done); + }); + + afterEach(done => { + page.refresh().then(done); + }); + + afterAll(done => { + Promise.all([ + logoutPage.load(), + apis.user.nodes.deleteNodesById([sharedFile1Id, sharedFile2Id, sharedFile3Id]) + ]) + .then(done); + }); + + it('delete a file and check notification', () => { + dataTable.clickOnItemName(sharedFile1) + .then(() => toolbar.actions.openMoreMenu()) + .then(() => toolbar.actions.menu.clickMenuItem('Delete')) + .then(() => page.getSnackBarMessage()) + .then(message => { + expect(message).toContain(`${sharedFile1} deleted`); + expect(dataTable.getRowName(sharedFile1).isPresent()).toBe(false, 'Item was not removed from list'); + }) + .then(() => page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.TRASH)) + .then(() => expect(dataTable.getRowName(sharedFile1).isPresent()).toBe(true, 'Item is not in trash')) + + .then(() => apis.user.trashcan.restore(sharedFile1Id)); + }); + + it('delete multiple files and check notification', () => { + dataTable.selectMultipleItems([sharedFile2, sharedFile3]) + .then(() => toolbar.actions.openMoreMenu()) + .then(() => toolbar.actions.menu.clickMenuItem('Delete')) + .then(() => page.getSnackBarMessage()) + .then(message => { + expect(message).toContain(`Deleted 2 items`); + expect(dataTable.getRowName(sharedFile2).isPresent()).toBe(false, `${sharedFile2} was not removed from list`); + expect(dataTable.getRowName(sharedFile3).isPresent()).toBe(false, `${sharedFile3} was not removed from list`); + }) + .then(() => page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.TRASH)) + .then(() => { + expect(dataTable.getRowName(sharedFile2).isPresent()).toBe(true, `${sharedFile2} is not in trash`); + expect(dataTable.getRowName(sharedFile3).isPresent()).toBe(true, `${sharedFile3} is not in trash`); + }) + + .then(() => apis.user.trashcan.restore(sharedFile2Id)) + .then(() => apis.user.trashcan.restore(sharedFile3Id)); + }); + }); + + describe('on Favorites', () => { + const favoriteFile1 = `favFile1-${Utils.random()}.txt`; let favoriteFile1Id; + const favoriteFile2 = `favFile2-${Utils.random()}.txt`; let favoriteFile2Id; + const favoriteFile3 = `favFile3-${Utils.random()}.txt`; + const favoriteFile4 = `favFile4-${Utils.random()}.txt`; let favoriteFile4Id; + const favoriteFolder1 = `favFolder1-${Utils.random()}`; let favoriteFolder1Id; + const favoriteFolder2 = `favFolder2-${Utils.random()}`; let favoriteFolder2Id; + const favoriteFileLocked1 = `favFileLocked-${Utils.random()}.txt`; let favoriteFileLocked1Id; + + beforeAll(done => { + apis.user.nodes.createFile(favoriteFile1).then(resp => favoriteFile1Id = resp.data.entry.id) + .then(() => apis.user.nodes.createFile(favoriteFile2).then(resp => favoriteFile2Id = resp.data.entry.id)) + .then(() => apis.user.nodes.createFolder(favoriteFolder1).then(resp => favoriteFolder1Id = resp.data.entry.id)) + .then(() => apis.user.nodes.createFolder(favoriteFolder2).then(resp => favoriteFolder2Id = resp.data.entry.id)) + .then(() => apis.user.nodes.createFile(favoriteFile3, favoriteFolder1Id)) + .then(() => apis.user.nodes.createFile(favoriteFile4, favoriteFolder2Id).then(resp => favoriteFile4Id = resp.data.entry.id)) + .then(() => apis.user.nodes.lockFile(favoriteFile4Id)) + + .then(() => apis.user.nodes.createFile(favoriteFileLocked1).then(resp => favoriteFileLocked1Id = resp.data.entry.id)) + .then(() => apis.user.nodes.lockFile(favoriteFileLocked1Id)) + + .then(() => apis.user.favorites.addFavoritesByIds('file', [favoriteFile1Id, favoriteFile2Id, favoriteFileLocked1Id])) + .then(() => apis.user.favorites.addFavoritesByIds('folder', [favoriteFolder1Id, favoriteFolder2Id])) + .then(() => apis.user.favorites.waitForApi({ expect: 5 })) + + .then(() => loginPage.loginWith(username)) + .then(done); + }); + + beforeEach(done => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.FAVORITES) + .then(() => dataTable.waitForHeader()) + .then(done); + }); + + afterEach(done => { + page.refresh().then(done); + }); + + afterAll(done => { + Promise.all([ + logoutPage.load(), + apis.user.nodes.unlockFile(favoriteFile4Id) + .then(() => apis.user.nodes.unlockFile(favoriteFileLocked1Id)) + .then(() => apis.user.nodes.deleteNodesById([ + favoriteFile1Id, favoriteFile2Id, favoriteFolder1Id, favoriteFolder2Id, favoriteFileLocked1Id + ])) + ]) + .then(done); + }); + + it('delete a file and check notification', () => { + let items: number; + page.dataTable.countRows().then(number => { items = number; }); + + dataTable.clickOnItemName(favoriteFile1) + .then(() => toolbar.actions.openMoreMenu()) + .then(() => toolbar.actions.menu.clickMenuItem('Delete')) + .then(() => page.getSnackBarMessage()) + .then(message => { + expect(message).toContain(`${favoriteFile1} deleted`); + expect(dataTable.getRowName(favoriteFile1).isPresent()).toBe(false, 'Item was not removed from list'); + items--; + expect(page.pagination.range.getText()).toContain(`1-${items} of ${items}`); + }) + .then(() => page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.TRASH)) + .then(() => expect(dataTable.getRowName(favoriteFile1).isPresent()).toBe(true, 'Item is not in trash')) + + .then(() => apis.user.trashcan.restore(favoriteFile1Id)); + }); + + it('delete multiple files and check notification', () => { + let items: number; + page.dataTable.countRows().then(number => { items = number; }); + + dataTable.selectMultipleItems([favoriteFile1, favoriteFile2]) + .then(() => toolbar.actions.openMoreMenu()) + .then(() => toolbar.actions.menu.clickMenuItem('Delete')) + .then(() => page.getSnackBarMessage()) + .then(message => { + expect(message).toContain(`Deleted 2 items`); + expect(dataTable.getRowName(favoriteFile1).isPresent()).toBe(false, `${favoriteFile1} was not removed from list`); + expect(dataTable.getRowName(favoriteFile2).isPresent()).toBe(false, `${favoriteFile2} was not removed from list`); + items = items - 2; + expect(page.pagination.range.getText()).toContain(`1-${items} of ${items}`); + }) + .then(() => page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.TRASH)) + .then(() => { + expect(dataTable.getRowName(favoriteFile1).isPresent()).toBe(true, `${favoriteFile1} is not in trash`); + expect(dataTable.getRowName(favoriteFile2).isPresent()).toBe(true, `${favoriteFile2} is not in trash`); + }) + + .then(() => apis.user.trashcan.restore(favoriteFile1Id)) + .then(() => apis.user.trashcan.restore(favoriteFile2Id)); + }); + + it('delete a folder with content', () => { + let items: number; + page.dataTable.countRows().then(number => { items = number; }); + dataTable.clickOnItemName(favoriteFolder1) + .then(() => toolbar.actions.openMoreMenu()) + .then(() => toolbar.actions.menu.clickMenuItem('Delete')) + .then(() => { + expect(dataTable.getRowName(favoriteFolder1).isPresent()).toBe(false, 'Item was not removed from list'); + items--; + expect(page.pagination.range.getText()).toContain(`1-${items} of ${items}`); + }) + .then(() => page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.TRASH)) + .then(() => { + expect(dataTable.getRowName(favoriteFolder1).isPresent()).toBe(true, 'Item is not in trash'); + expect(dataTable.getRowName(favoriteFile3).isPresent()).toBe(false, 'Item is in trash'); + }) + + .then(() => apis.user.trashcan.restore(favoriteFolder1Id)); + }); + + it('delete a folder containing locked files', () => { + dataTable.clickOnItemName(favoriteFolder2) + .then(() => toolbar.actions.openMoreMenu()) + .then(() => toolbar.actions.menu.clickMenuItem('Delete')) + .then(() => page.getSnackBarMessage()) + .then(message => { + expect(message).toContain(`${favoriteFolder2} couldn't be deleted`); + expect(dataTable.getRowName(favoriteFolder2).isPresent()).toBe(true, 'Item was removed from list'); + }) + .then(() => page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.TRASH)) + .then(() => { + expect(dataTable.getRowName(favoriteFolder2).isPresent()).toBe(false, 'Item is in trash'); + expect(dataTable.getRowName(favoriteFile4).isPresent()).toBe(false, 'Item is in trash'); + }); + }); + + it('notification on multiple items deletion - some items fail to delete', () => { + dataTable.selectMultipleItems([favoriteFile1, favoriteFolder2]) + .then(() => toolbar.actions.openMoreMenu()) + .then(() => toolbar.actions.menu.clickMenuItem('Delete')) + .then(() => page.getSnackBarMessage()) + .then(message => { + expect(message).toContain(`Deleted 1 item, 1 couldn't be deleted`); + }) + + .then(() => apis.user.trashcan.restore(favoriteFile1Id)); + }); + + it('Notification on multiple items deletion - all items fail to delete', () => { + dataTable.selectMultipleItems([favoriteFileLocked1, favoriteFolder2]) + .then(() => toolbar.actions.openMoreMenu()) + .then(() => toolbar.actions.menu.clickMenuItem('Delete')) + .then(() => page.getSnackBarMessage()) + .then(message => { + expect(message).toEqual(`2 items couldn't be deleted`); + }); + }); + }); + + describe('on Recent Files', () => { + const recentFile1 = `recentFile1-${Utils.random()}.txt`; let recentFile1Id; + const recentFile2 = `recentFile2-${Utils.random()}.txt`; let recentFile2Id; + const recentFile3 = `recentFile3-${Utils.random()}.txt`; let recentFile3Id; + + beforeAll(done => { + apis.user.nodes.createFile(recentFile1).then(resp => recentFile1Id = resp.data.entry.id) + .then(() => apis.user.nodes.createFile(recentFile2).then(resp => recentFile2Id = resp.data.entry.id)) + .then(() => apis.user.nodes.createFile(recentFile3).then(resp => recentFile3Id = resp.data.entry.id)) + .then(() => apis.user.search.waitForApi(username, { expect: 3 })) + + .then(() => loginPage.loginWith(username)) + + .then((): any => page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.RECENT_FILES) + .then(() => dataTable.isEmptyList()) + .then(empty => { + if (empty) { + browser.sleep(6000).then(() => page.refresh()); + } + }) + ) + .then(done); + }); + + beforeEach(done => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.RECENT_FILES) + .then(() => dataTable.waitForHeader()) + .then(done); + }); + + afterEach(done => { + page.refresh().then(done); + }); + + afterAll(done => { + Promise.all([ + logoutPage.load(), + apis.user.nodes.deleteNodesById([recentFile1Id, recentFile2Id, recentFile3Id]) + ]) + .then(done); + }); + + xit('delete a file and check notification', () => { + dataTable.clickOnItemName(recentFile1) + .then(() => toolbar.actions.openMoreMenu()) + .then(() => toolbar.actions.menu.clickMenuItem('Delete')) + .then(() => page.getSnackBarMessage()) + .then(message => { + expect(message).toContain(`${recentFile1} deleted`); + expect(dataTable.getRowName(recentFile1).isPresent()).toBe(false, 'Item was not removed from list'); + }) + .then(() => page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.TRASH)) + .then(() => expect(dataTable.getRowName(recentFile1).isPresent()).toBe(true, 'Item is not in trash')) + + .then(() => apis.user.trashcan.restore(recentFile1Id)); + }); + + xit('delete multiple files and check notification', () => { + dataTable.selectMultipleItems([recentFile2, recentFile3]) + .then(() => toolbar.actions.openMoreMenu()) + .then(() => toolbar.actions.menu.clickMenuItem('Delete')) + .then(() => page.getSnackBarMessage()) + .then(message => { + expect(message).toContain(`Deleted 2 items`); + expect(dataTable.getRowName(recentFile2).isPresent()).toBe(false, `${recentFile2} was not removed from list`); + expect(dataTable.getRowName(recentFile3).isPresent()).toBe(false, `${recentFile3} was not removed from list`); + }) + .then(() => page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.TRASH)) + .then(() => { + expect(dataTable.getRowName(recentFile2).isPresent()).toBe(true, `${recentFile2} is not in trash`); + expect(dataTable.getRowName(recentFile3).isPresent()).toBe(true, `${recentFile3} is not in trash`); + }) + + .then(() => apis.user.trashcan.restore(recentFile2Id)) + .then(() => apis.user.trashcan.restore(recentFile3Id)); + }); + }); +}); diff --git a/e2e/suites/actions/edit-folder.test.ts b/e2e/suites/actions/edit-folder.test.ts new file mode 100755 index 000000000..137dcfe3f --- /dev/null +++ b/e2e/suites/actions/edit-folder.test.ts @@ -0,0 +1,187 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { protractor, browser } from 'protractor'; +import { LoginPage, LogoutPage, BrowsingPage } from '../../pages/pages'; +import { SIDEBAR_LABELS, SITE_VISIBILITY, SITE_ROLES } from '../../configs'; +import { RepoClient } from '../../utilities/repo-client/repo-client'; +import { CreateOrEditFolderDialog } from '../../components/dialog/create-edit-folder-dialog'; +import { Utils } from '../../utilities/utils'; + +describe('Edit folder', () => { + const username = `user-${Utils.random()}`; + + const parent = `parent-${Utils.random()}`; + const folderName = `folder-${Utils.random()}`; + const folderDescription = 'my folder description'; + + const folderNameToEdit = `folder-${Utils.random()}`; + const duplicateFolderName = `folder-${Utils.random()}`; + + const folderNameEdited = `folder-${Utils.random()}`; + const folderDescriptionEdited = 'description edited'; + + const siteName = `site-private-${Utils.random()}`; + + const apis = { + admin: new RepoClient(), + user: new RepoClient(username, username) + }; + + const loginPage = new LoginPage(); + const logoutPage = new LogoutPage(); + const personalFilesPage = new BrowsingPage(); + const editDialog = new CreateOrEditFolderDialog(); + const { dataTable } = personalFilesPage; + const editButton = personalFilesPage.toolbar.actions.getButtonByTitleAttribute('Edit'); + + beforeAll(done => { + apis.admin.people.createUser(username) + .then(() => apis.admin.sites.createSite(siteName, SITE_VISIBILITY.PRIVATE)) + .then(() => apis.admin.nodes.createFolders([ folderName ], `Sites/${siteName}/documentLibrary`)) + .then(() => apis.admin.sites.addSiteMember(siteName, username, SITE_ROLES.SITE_CONSUMER)) + + .then(() => apis.user.nodes.createFolder( parent )) + .then(resp => apis.user.nodes.createFolder( folderName, resp.data.entry.id, '', folderDescription )) + .then(() => apis.user.nodes.createFolders([ folderNameToEdit, duplicateFolderName ], parent)) + + .then(() => loginPage.loginWith(username)) + .then(done); + }); + + beforeEach(done => { + personalFilesPage.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.PERSONAL_FILES) + .then(() => dataTable.waitForHeader()) + .then(() => dataTable.doubleClickOnItemName(parent)) + .then(() => dataTable.waitForHeader()) + .then(done); + }); + + afterEach(done => { + browser.actions().sendKeys(protractor.Key.ESCAPE).perform().then(done); + }); + + afterAll(done => { + Promise + .all([ + apis.admin.sites.deleteSite(siteName), + apis.user.nodes.deleteNodes([ parent ]), + logoutPage.load() + ]) + .then(done); + }); + + it('dialog UI defaults', () => { + dataTable.clickOnItemName(folderName) + .then(() => editButton.click()) + .then(() => { + expect(editDialog.getTitle()).toEqual('Edit folder'); + expect(editDialog.nameInput.getAttribute('value')).toBe(folderName); + expect(editDialog.descriptionTextArea.getAttribute('value')).toBe(folderDescription); + expect(editDialog.updateButton.isEnabled()).toBe(true, 'upload button is not enabled'); + expect(editDialog.cancelButton.isEnabled()).toBe(true, 'cancel button is not enabled'); + }); + }); + + it('properties are modified when pressing OK', () => { + dataTable.clickOnItemName(folderNameToEdit) + .then(() => editButton.click()) + .then(() => editDialog.waitForDialogToOpen()) + .then(() => editDialog.enterDescription(folderDescriptionEdited)) + .then(() => editDialog.enterName(folderNameEdited)) + .then(() => editDialog.clickUpdate()) + .then(() => editDialog.waitForDialogToClose()) + .then(() => dataTable.waitForHeader()) + .then(() => expect(dataTable.getRowName(folderNameEdited).isPresent()).toBe(true, 'Folder not displayed')) + .then(() => apis.user.nodes.getNodeDescription(folderNameEdited, parent)) + .then(desc => expect(desc).toEqual(folderDescriptionEdited)); + }); + + it('with empty folder name', () => { + dataTable.clickOnItemName(folderName) + .then(() => editButton.click()) + .then(() => editDialog.deleteNameWithBackspace()) + .then(() => { + expect(editDialog.updateButton.isEnabled()).toBe(false, 'upload button is not enabled'); + expect(editDialog.getValidationMessage()).toMatch('Folder name is required'); + }); + }); + + it('with name with special characters', () => { + const namesWithSpecialChars = [ 'a*a', 'a"a', 'aa', `a\\a`, 'a/a', 'a?a', 'a:a', 'a|a' ]; + + dataTable.clickOnItemName(folderName) + .then(() => editButton.click()) + .then(() => namesWithSpecialChars.forEach(name => { + editDialog.enterName(name); + + expect(editDialog.updateButton.isEnabled()).toBe(false, 'upload button is not disabled'); + expect(editDialog.getValidationMessage()).toContain(`Folder name can't contain these characters`); + })); + }); + + it('with name ending with a dot', () => { + dataTable.clickOnItemName(folderName) + .then(() => editButton.click()) + .then(() => editDialog.nameInput.sendKeys('.')) + .then(() => { + expect(editDialog.updateButton.isEnabled()).toBe(false, 'upload button is not enabled'); + expect(editDialog.getValidationMessage()).toMatch(`Folder name can't end with a period .`); + }); + }); + + it('Cancel button', () => { + dataTable.clickOnItemName(folderName) + .then(() => editButton.click()) + .then(() => editDialog.clickCancel()) + .then(() => { + expect(editDialog.component.isPresent()).not.toBe(true, 'dialog is not closed'); + }); + }); + + it('with duplicate folder name', () => { + dataTable.clickOnItemName(folderName) + .then(() => editButton.click()) + .then(() => editDialog.enterName(duplicateFolderName)) + .then(() => editDialog.clickUpdate()) + .then(() => personalFilesPage.getSnackBarMessage()) + .then(message => { + expect(message).toEqual(`There's already a folder with this name. Try a different name.`); + expect(editDialog.component.isPresent()).toBe(true, 'dialog is not present'); + }); + }); + + it('trim ending spaces', () => { + dataTable.clickOnItemName(folderName) + .then(() => editButton.click()) + .then(() => editDialog.nameInput.sendKeys(' ')) + .then(() => editDialog.clickUpdate()) + .then(() => editDialog.waitForDialogToClose()) + .then(() => { + expect(personalFilesPage.snackBar.isPresent()).not.toBe(true, 'notification appears'); + expect(dataTable.getRowName(folderName).isPresent()).toBe(true, 'Folder not displayed in list view'); + }); + }); +}); diff --git a/e2e/suites/actions/mark-favorite.test.ts b/e2e/suites/actions/mark-favorite.test.ts new file mode 100644 index 000000000..cc861d022 --- /dev/null +++ b/e2e/suites/actions/mark-favorite.test.ts @@ -0,0 +1,419 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { LoginPage, LogoutPage, BrowsingPage } from '../../pages/pages'; +import { SIDEBAR_LABELS } from '../../configs'; +import { RepoClient } from '../../utilities/repo-client/repo-client'; +import { Utils } from '../../utilities/utils'; +import { browser } from 'protractor'; + +describe('Mark items as favorites', () => { + const username = `user-${Utils.random()}`; + + const file1NotFav = `file-${Utils.random()}.txt`; + const file2NotFav = `file-${Utils.random()}.txt`; + const file3Fav = `file-${Utils.random()}.txt`; + const file4Fav = `file-${Utils.random()}.txt`; + const folder1 = `folder-${Utils.random()}`; + + let file1Id, file2Id, file3Id, file4Id, folder1Id; + + const apis = { + admin: new RepoClient(), + user: new RepoClient(username, username) + }; + + const loginPage = new LoginPage(); + const logoutPage = new LogoutPage(); + const page = new BrowsingPage(); + const { dataTable, toolbar } = page; + + beforeAll(done => { + apis.admin.people.createUser(username) + .then(() => apis.user.nodes.createFile( file1NotFav ).then(resp => file1Id = resp.data.entry.id)) + .then(() => apis.user.nodes.createFile( file2NotFav ).then(resp => file2Id = resp.data.entry.id)) + .then(() => apis.user.nodes.createFile( file3Fav ).then(resp => file3Id = resp.data.entry.id)) + .then(() => apis.user.nodes.createFile( file4Fav ).then(resp => file4Id = resp.data.entry.id)) + .then(() => apis.user.nodes.createFolder( folder1 ).then(resp => folder1Id = resp.data.entry.id)) + + .then(() => apis.user.favorites.addFavoriteById('file', file3Id)) + .then(() => apis.user.favorites.addFavoriteById('file', file4Id)) + + .then(() => loginPage.loginWith(username)) + .then(done); + }); + + afterAll(done => { + Promise.all([ + apis.user.nodes.deleteNodesById([ file1Id, file2Id, file3Id, file4Id, folder1Id ]), + logoutPage.load() + ]) + .then(done); + }); + + xit(''); + + describe('on Personal Files', () => { + beforeAll(done => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.PERSONAL_FILES) + .then(() => dataTable.waitForHeader()) + .then(done); + }); + + afterEach(done => { + // browser.actions().sendKeys(protractor.Key.ESCAPE).perform().then(done); + browser.actions().mouseMove(browser.$('body'), { x: 0, y: 0 }).click().perform().then(done); + }); + + it('Favorite action has empty star icon for an item not marked as favorite', () => { + dataTable.clickOnItemName(file1NotFav) + .then(() => toolbar.actions.openMoreMenu()) + .then(() => expect(toolbar.actions.menu.getItemIconText('Favorite')).toEqual('star_border')); + }); + + it('Favorite action has empty star icon for multiple selection of items when some are not favorite', () => { + dataTable.selectMultipleItems([ file1NotFav, file3Fav ]) + .then(() => toolbar.actions.openMoreMenu()) + .then(() => expect(toolbar.actions.menu.getItemIconText('Favorite')).toEqual('star_border')); + }); + + it('Favorite action has full star icon for items marked as favorite', () => { + dataTable.clickOnItemName(file3Fav) + .then(() => toolbar.actions.openMoreMenu()) + .then(() => expect(toolbar.actions.menu.getItemIconText('Favorite')).toEqual('star')); + }); + + it('favorite a file', () => { + dataTable.clickOnItemName(file1NotFav) + .then(() => toolbar.actions.openMoreMenu()) + .then(() => toolbar.actions.menu.clickMenuItem('Favorite')) + .then(() => apis.user.favorites.waitForApi({ expect: 3 })) + .then(() => apis.user.favorites.isFavorite(file1Id)) + .then(isFavorite => expect(isFavorite).toBe(true, `${file1NotFav} not marked as favorite`)) + + .then(() => apis.user.favorites.removeFavoriteById(file1Id)); + }); + + it('favorite a folder', () => { + dataTable.clickOnItemName(folder1) + .then(() => toolbar.actions.openMoreMenu()) + .then(() => toolbar.actions.menu.clickMenuItem('Favorite')) + .then(() => apis.user.favorites.waitForApi({ expect: 3 })) + .then(() => apis.user.favorites.isFavorite(folder1Id)) + .then(isFavorite => expect(isFavorite).toBe(true, `${folder1} not marked as favorite`)) + + .then(() => apis.user.favorites.removeFavoriteById(folder1Id)); + }); + + it('unfavorite an item', () => { + dataTable.clickOnItemName(file3Fav) + .then(() => toolbar.actions.openMoreMenu()) + .then(() => toolbar.actions.menu.clickMenuItem('Favorite')) + .then(() => apis.user.favorites.waitForApi({ expect: 1 })) + .then(() => apis.user.favorites.isFavorite(file3Id)) + .then(isFavorite => expect(isFavorite).toBe(false, `${file3Fav} is marked as favorite`)) + + .then(() => apis.user.favorites.addFavoriteById('file', file3Id)); + }); + + it('favorite multiple items - all unfavorite', () => { + dataTable.selectMultipleItems([ file1NotFav, file2NotFav ]) + .then(() => toolbar.actions.openMoreMenu()) + .then(() => toolbar.actions.menu.clickMenuItem('Favorite')) + .then(() => apis.user.favorites.waitForApi({ expect: 4 })) + .then(() => Promise.all([ + apis.user.favorites.isFavorite(file1Id), + apis.user.favorites.isFavorite(file2Id) + ])) + .then(resp => { + expect(resp[0]).toBe(true, 'item not marked as favorite'); + expect(resp[1]).toBe(true, 'item not marked as favorite'); + }) + + .then(() => apis.user.favorites.removeFavoriteById(file1Id)) + .then(() => apis.user.favorites.removeFavoriteById(file2Id)); + }); + + it('favorite multiple items - some favorite and some unfavorite', () => { + dataTable.selectMultipleItems([ file1NotFav, file3Fav ]) + .then(() => toolbar.actions.openMoreMenu()) + .then(() => toolbar.actions.menu.clickMenuItem('Favorite')) + .then(() => apis.user.favorites.waitForApi({ expect: 3 })) + .then(() => Promise.all([ + apis.user.favorites.isFavorite(file1Id), + apis.user.favorites.isFavorite(file3Id) + ])) + .then(resp => { + expect(resp[0]).toBe(true, 'item not marked as favorite'); + expect(resp[1]).toBe(true, 'item not marked as favorite'); + }) + + .then(() => apis.user.favorites.removeFavoriteById(file1Id)); + }); + + it('unfavorite multiple items', () => { + dataTable.selectMultipleItems([ file3Fav, file4Fav ]) + .then(() => toolbar.actions.openMoreMenu()) + .then(() => toolbar.actions.menu.clickMenuItem('Favorite')) + .then(() => browser.sleep(2000)) + .then(() => Promise.all([ + apis.user.favorites.isFavorite(file3Id), + apis.user.favorites.isFavorite(file4Id) + ])) + .then(resp => { + expect(resp[0]).toBe(false, 'item marked as favorite'); + expect(resp[1]).toBe(false, 'item marked as favorite'); + }) + + .then(() => apis.user.favorites.addFavoriteById('file', file3Id)) + .then(() => apis.user.favorites.addFavoriteById('file', file4Id)); + }); + }); + + describe('on Recent Files', () => { + beforeAll(done => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.RECENT_FILES) + .then(() => dataTable.waitForHeader()) + .then(done); + }); + + afterEach(done => { + // browser.actions().sendKeys(protractor.Key.ESCAPE).perform().then(done); + browser.actions().mouseMove(browser.$('body'), { x: 0, y: 0 }).click().perform().then(done); + }); + + it('favorite a file', () => { + dataTable.clickOnItemName(file1NotFav) + .then(() => toolbar.actions.openMoreMenu()) + .then(() => toolbar.actions.menu.clickMenuItem('Favorite')) + .then(() => apis.user.favorites.waitForApi({ expect: 3 })) + .then(() => apis.user.favorites.isFavorite(file1Id)) + .then(isFavorite => expect(isFavorite).toBe(true, `${file1NotFav} not marked as favorite`)) + + .then(() => apis.user.favorites.removeFavoriteById(file1Id)); + }); + + it('unfavorite an item', () => { + dataTable.clickOnItemName(file3Fav) + .then(() => toolbar.actions.openMoreMenu()) + .then(() => toolbar.actions.menu.clickMenuItem('Favorite')) + .then(() => apis.user.favorites.waitForApi({ expect: 1 })) + .then(() => apis.user.favorites.isFavorite(file3Id)) + .then(isFavorite => expect(isFavorite).toBe(false, `${file3Fav} is marked as favorite`)) + + .then(() => apis.user.favorites.addFavoriteById('file', file3Id)); + }); + + it('favorite multiple items - all unfavorite', () => { + dataTable.selectMultipleItems([ file1NotFav, file2NotFav ]) + .then(() => toolbar.actions.openMoreMenu()) + .then(() => toolbar.actions.menu.clickMenuItem('Favorite')) + .then(() => apis.user.favorites.waitForApi({ expect: 4 })) + .then(() => Promise.all([ + apis.user.favorites.isFavorite(file1Id), + apis.user.favorites.isFavorite(file2Id) + ])) + .then(resp => { + expect(resp[0]).toBe(true, 'item not marked as favorite'); + expect(resp[1]).toBe(true, 'item not marked as favorite'); + }) + + .then(() => apis.user.favorites.removeFavoriteById(file1Id)) + .then(() => apis.user.favorites.removeFavoriteById(file2Id)); + }); + + it('favorite multiple items - some favorite and some unfavorite', () => { + dataTable.selectMultipleItems([ file1NotFav, file3Fav ]) + .then(() => toolbar.actions.openMoreMenu()) + .then(() => toolbar.actions.menu.clickMenuItem('Favorite')) + .then(() => apis.user.favorites.waitForApi({ expect: 3 })) + .then(() => Promise.all([ + apis.user.favorites.isFavorite(file1Id), + apis.user.favorites.isFavorite(file3Id) + ])) + .then(resp => { + expect(resp[0]).toBe(true, 'item not marked as favorite'); + expect(resp[1]).toBe(true, 'item not marked as favorite'); + }) + + .then(() => apis.user.favorites.removeFavoriteById(file1Id)); + }); + + it('unfavorite multiple items', () => { + dataTable.selectMultipleItems([ file3Fav, file4Fav ]) + .then(() => toolbar.actions.openMoreMenu()) + .then(() => toolbar.actions.menu.clickMenuItem('Favorite')) + .then(() => browser.sleep(2000)) + .then(() => Promise.all([ + apis.user.favorites.isFavorite(file3Id), + apis.user.favorites.isFavorite(file4Id) + ])) + .then(resp => { + expect(resp[0]).toBe(false, 'item marked as favorite'); + expect(resp[1]).toBe(false, 'item marked as favorite'); + }) + + .then(() => apis.user.favorites.addFavoriteById('file', file3Id)) + .then(() => apis.user.favorites.addFavoriteById('file', file4Id)); + }); + }); + + describe('on Shared Files', () => { + beforeAll(done => { + apis.user.shared.shareFilesByIds([ file1Id, file2Id, file3Id, file4Id ]) + .then(() => apis.user.shared.waitForApi({ expect: 4 })) + .then(() => page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.SHARED_FILES)) + .then(() => dataTable.waitForHeader()) + .then(done); + }); + + afterEach(done => { + // browser.actions().sendKeys(protractor.Key.ESCAPE).perform().then(done); + browser.actions().mouseMove(browser.$('body'), { x: 0, y: 0 }).click().perform().then(done); + }); + + it('favorite a file', () => { + dataTable.clickOnItemName(file1NotFav) + .then(() => toolbar.actions.openMoreMenu()) + .then(() => toolbar.actions.menu.clickMenuItem('Favorite')) + .then(() => apis.user.favorites.waitForApi({ expect: 3 })) + .then(() => apis.user.favorites.isFavorite(file1Id)) + .then(isFavorite => expect(isFavorite).toBe(true, `${file1NotFav} not marked as favorite`)) + + .then(() => apis.user.favorites.removeFavoriteById(file1Id)); + }); + + it('unfavorite an item', () => { + dataTable.clickOnItemName(file3Fav) + .then(() => toolbar.actions.openMoreMenu()) + .then(() => toolbar.actions.menu.clickMenuItem('Favorite')) + .then(() => apis.user.favorites.waitForApi({ expect: 1 })) + .then(() => apis.user.favorites.isFavorite(file3Id)) + .then(isFavorite => expect(isFavorite).toBe(false, `${file3Fav} is marked as favorite`)) + + .then(() => apis.user.favorites.addFavoriteById('file', file3Id)); + }); + + it('favorite multiple items - all unfavorite', () => { + dataTable.selectMultipleItems([ file1NotFav, file2NotFav ]) + .then(() => toolbar.actions.openMoreMenu()) + .then(() => toolbar.actions.menu.clickMenuItem('Favorite')) + .then(() => apis.user.favorites.waitForApi({ expect: 4 })) + .then(() => Promise.all([ + apis.user.favorites.isFavorite(file1Id), + apis.user.favorites.isFavorite(file2Id) + ])) + .then(resp => { + expect(resp[0]).toBe(true, 'item not marked as favorite'); + expect(resp[1]).toBe(true, 'item not marked as favorite'); + }) + + .then(() => apis.user.favorites.removeFavoriteById(file1Id)) + .then(() => apis.user.favorites.removeFavoriteById(file2Id)); + }); + + it('favorite multiple items - some favorite and some unfavorite', () => { + dataTable.selectMultipleItems([ file1NotFav, file3Fav ]) + .then(() => toolbar.actions.openMoreMenu()) + .then(() => toolbar.actions.menu.clickMenuItem('Favorite')) + .then(() => apis.user.favorites.waitForApi({ expect: 3 })) + .then(() => Promise.all([ + apis.user.favorites.isFavorite(file1Id), + apis.user.favorites.isFavorite(file3Id) + ])) + .then(resp => { + expect(resp[0]).toBe(true, 'item not marked as favorite'); + expect(resp[1]).toBe(true, 'item not marked as favorite'); + }) + + .then(() => apis.user.favorites.removeFavoriteById(file1Id)); + }); + + it('unfavorite multiple items', () => { + dataTable.selectMultipleItems([ file3Fav, file4Fav ]) + .then(() => toolbar.actions.openMoreMenu()) + .then(() => toolbar.actions.menu.clickMenuItem('Favorite')) + .then(() => browser.sleep(2000)) + .then(() => Promise.all([ + apis.user.favorites.isFavorite(file3Id), + apis.user.favorites.isFavorite(file4Id) + ])) + .then(resp => { + expect(resp[0]).toBe(false, 'item marked as favorite'); + expect(resp[1]).toBe(false, 'item marked as favorite'); + }) + + .then(() => apis.user.favorites.addFavoriteById('file', file3Id)) + .then(() => apis.user.favorites.addFavoriteById('file', file4Id)); + }); + }); + + describe('on Favorites', () => { + beforeAll(done => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.FAVORITES) + .then(() => dataTable.waitForHeader()) + .then(done); + }); + + afterEach(done => { + page.refresh().then(done); + }); + + it('unfavorite an item', () => { + dataTable.clickOnItemName(file3Fav) + .then(() => toolbar.actions.openMoreMenu()) + .then(() => toolbar.actions.menu.clickMenuItem('Favorite')) + .then(() => apis.user.favorites.waitForApi({ expect: 1 })) + .then(() => apis.user.favorites.isFavorite(file3Id)) + .then(isFavorite => { + expect(isFavorite).toBe(false, 'item is marked as favorite'); + expect(dataTable.getRowName(file3Fav).isPresent()).toBe(false, 'item still displayed'); + }) + + .then(() => apis.user.favorites.addFavoriteById('file', file3Id)); + }); + + it('unfavorite multiple items', () => { + dataTable.selectMultipleItems([ file3Fav, file4Fav ]) + .then(() => toolbar.actions.openMoreMenu()) + .then(() => toolbar.actions.menu.clickMenuItem('Favorite')) + .then(() => browser.sleep(2000)) + .then(() => apis.user.favorites.isFavorite(file3Id)) + .then(resp => { + expect(resp).toBe(false, 'file3 marked as favorite'); + expect(dataTable.getRowName(file3Fav).isPresent()).toBe(false, 'file3 still displayed'); + }) + .then(() => apis.user.favorites.isFavorite(file4Id)) + .then(resp => { + expect(resp).toBe(false, 'file4 marked as favorite'); + expect(dataTable.getRowName(file4Fav).isPresent()).toBe(false, 'file4 still displayed'); + }) + + .then(() => apis.user.favorites.addFavoriteById('file', file3Id)) + .then(() => apis.user.favorites.addFavoriteById('file', file4Id)); + }); + }); + +}); diff --git a/e2e/suites/actions/permanently-delete.test.ts b/e2e/suites/actions/permanently-delete.test.ts new file mode 100755 index 000000000..1025feefd --- /dev/null +++ b/e2e/suites/actions/permanently-delete.test.ts @@ -0,0 +1,122 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { LoginPage, LogoutPage, BrowsingPage } from '../../pages/pages'; +import { SIDEBAR_LABELS } from '../../configs'; +import { RepoClient } from '../../utilities/repo-client/repo-client'; +import { Utils } from '../../utilities/utils'; + +describe('Permanently delete from Trash', () => { + const username = `user-${Utils.random()}`; + + const file1 = `file-${Utils.random()}.txt`; + const file2 = `file-${Utils.random()}.txt`; + let filesIds; + + const folder1 = `folder-${Utils.random()}`; + const folder2 = `folder-${Utils.random()}`; + let foldersIds; + + const apis = { + admin: new RepoClient(), + user: new RepoClient(username, username) + }; + + const loginPage = new LoginPage(); + const logoutPage = new LogoutPage(); + const trashPage = new BrowsingPage(); + const { dataTable, toolbar } = trashPage; + + beforeAll(done => { + apis.admin.people.createUser(username) + .then(() => apis.user.nodes.createFiles([ file1, file2 ])) + .then(resp => filesIds = resp.data.list.entries.map(entries => entries.entry.id)) + .then(() => apis.user.nodes.createFolders([ folder1, folder2 ])) + .then(resp => foldersIds = resp.data.list.entries.map(entries => entries.entry.id)) + + .then(() => apis.user.nodes.deleteNodesById(filesIds, false)) + .then(() => apis.user.nodes.deleteNodesById(foldersIds, false)) + + .then(() => loginPage.loginWith(username)) + .then(done); + }); + + beforeEach(done => { + trashPage.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.TRASH) + .then(() => dataTable.waitForHeader()) + .then(done); + }); + + afterAll(done => { + Promise.all([ + apis.admin.trashcan.emptyTrash(), + logoutPage.load() + ]) + .then(done); + }); + + it('delete file [C217094] [C217091] [C217092]', () => { + dataTable.clickOnItemName(file1) + .then(() => toolbar.actions.getButtonByTitleAttribute('Permanently delete').click()) + .then(() => trashPage.waitForDialog()) + .then(() => trashPage.getDialogActionByLabel('Delete')) + .then((elm) => elm.click()) + .then(() => trashPage.waitForDialogToClose()) + .then(() => trashPage.getSnackBarMessage()) + .then(text => { + expect(text).toEqual(`${file1} deleted`); + expect(dataTable.getRowName(file1).isPresent()).toBe(false, 'Item was not deleted'); + }); + }); + + it('delete folder [C217091] [C217092]', () => { + dataTable.clickOnItemName(folder1) + .then(() => toolbar.actions.getButtonByTitleAttribute('Permanently delete').click()) + .then(() => trashPage.waitForDialog()) + .then(() => trashPage.getDialogActionByLabel('Delete')) + .then((elm) => elm.click()) + .then(() => trashPage.waitForDialogToClose()) + .then(() => trashPage.getSnackBarMessage()) + .then(text => { + expect(text).toEqual(`${folder1} deleted`); + expect(dataTable.getRowName(folder1).isPresent()).toBe(false, 'Item was not deleted'); + }); + }); + + it('delete multiple items [C217093]', () => { + dataTable.selectMultipleItems([ file2, folder2 ]) + .then(() => toolbar.actions.getButtonByTitleAttribute('Permanently delete').click()) + .then(() => trashPage.waitForDialog()) + .then(() => trashPage.getDialogActionByLabel('Delete')) + .then((elm) => elm.click()) + .then(() => trashPage.waitForDialogToClose()) + .then(() => trashPage.getSnackBarMessage()) + .then(text => { + expect(text).toEqual(`2 items deleted`); + expect(dataTable.getRowName(file2).isPresent()).toBe(false, 'Item was not deleted'); + expect(dataTable.getRowName(folder2).isPresent()).toBe(false, 'Item was not deleted'); + }); + }); +}); diff --git a/e2e/suites/actions/restore.test.ts b/e2e/suites/actions/restore.test.ts new file mode 100755 index 000000000..c6f379407 --- /dev/null +++ b/e2e/suites/actions/restore.test.ts @@ -0,0 +1,268 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { browser } from 'protractor'; +import { LoginPage, LogoutPage, BrowsingPage } from '../../pages/pages'; +import { APP_ROUTES, SIDEBAR_LABELS } from '../../configs'; +import { RepoClient } from '../../utilities/repo-client/repo-client'; +import { Utils } from '../../utilities/utils'; + +describe('Restore from Trash', () => { + const username = `user-${Utils.random()}`; + + const apis = { + admin: new RepoClient(), + user: new RepoClient(username, username) + }; + + const loginPage = new LoginPage(); + const logoutPage = new LogoutPage(); + const page = new BrowsingPage(); + const { dataTable, toolbar } = page; + + beforeAll(done => { + apis.admin.people.createUser(username) + .then(() => loginPage.loginWith(username)) + .then(done); + }); + + afterAll(done => { + Promise.all([ + apis.admin.trashcan.emptyTrash(), + logoutPage.load() + ]) + .then(done); + }); + + xit(''); + + describe('successful restore', () => { + const file = `file-${Utils.random()}.txt`; let fileId; + const folder = `folder-${Utils.random()}`; let folderId; + + beforeAll(done => { + apis.user.nodes.createFile(file).then(resp => fileId = resp.data.entry.id) + .then(() => apis.user.nodes.createFolder(folder).then(resp => folderId = resp.data.entry.id)) + .then(() => apis.user.nodes.deleteNodesById([ fileId, folderId ], false)) + .then(done); + }); + + beforeEach(done => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.TRASH) + .then(() => dataTable.waitForHeader()) + .then(done); + }); + + afterAll(done => { + apis.user.trashcan.emptyTrash().then(done); + }); + + it('restore file', () => { + dataTable.clickOnItemName(file) + .then(() => toolbar.actions.getButtonByTitleAttribute('Restore').click()) + .then(() => page.getSnackBarMessage()) + .then(text => { + expect(text).toContain(`${file} restored`); + expect(text).toContain(`View`); + expect(dataTable.getRowName(file).isPresent()).toBe(false, 'Item was not removed from list'); + }) + .then(() => page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.PERSONAL_FILES)) + .then(() => page.dataTable.waitForHeader()) + .then(() => { + expect(page.dataTable.getRowName(file).isPresent()).toBe(true, 'Item not displayed in list'); + }) + + .then(() => apis.user.nodes.deleteNodeById(fileId, false)); + }); + + it('restore folder', () => { + dataTable.clickOnItemName(folder) + .then(() => toolbar.actions.getButtonByTitleAttribute('Restore').click()) + .then(() => page.getSnackBarMessage()) + .then(text => { + expect(text).toContain(`${folder} restored`); + expect(text).toContain(`View`); + expect(dataTable.getRowName(folder).isPresent()).toBe(false, 'Item was not removed from list'); + }) + .then(() => page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.PERSONAL_FILES)) + .then(() => page.dataTable.waitForHeader()) + .then(() => { + expect(page.dataTable.getRowName(folder).isPresent()).toBe(true, 'Item not displayed in list'); + }) + + .then(() => apis.user.nodes.deleteNodeById(folderId, false)); + }); + + it('restore multiple items', () => { + dataTable.selectMultipleItems([ file, folder ]) + .then(() => toolbar.actions.getButtonByTitleAttribute('Restore').click()) + .then(() => page.getSnackBarMessage()) + .then(text => { + expect(text).toContain(`Restore successful`); + expect(text).not.toContain(`View`); + expect(dataTable.getRowName(file).isPresent()).toBe(false, 'Item was not removed from list'); + expect(dataTable.getRowName(folder).isPresent()).toBe(false, 'Item was not removed from list'); + }) + .then(() => page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.PERSONAL_FILES)) + .then(() => page.dataTable.waitForHeader()) + .then(() => { + expect(page.dataTable.getRowName(file).isPresent()).toBe(true, 'Item not displayed in list'); + expect(page.dataTable.getRowName(folder).isPresent()).toBe(true, 'Item not displayed in list'); + }) + + .then(() => apis.user.nodes.deleteNodesById([ fileId, folderId ], false)); + }); + + it('View from notification', () => { + dataTable.clickOnItemName(file) + .then(() => toolbar.actions.getButtonByTitleAttribute('Restore').click()) + .then(() => page.clickSnackBarAction()) + .then(() => page.dataTable.waitForHeader()) + .then(() => { + expect(page.sidenav.isActiveByLabel('Personal Files')).toBe(true, 'Personal Files sidebar link not active'); + expect(browser.getCurrentUrl()).toContain(APP_ROUTES.PERSONAL_FILES); + }) + + .then(() => apis.user.nodes.deleteNodeById(fileId, false)); + }); + }); + + describe('failure to restore', () => { + const file1 = `file-${Utils.random()}.txt`; let file1Id1, file1Id2; + const file2 = `file-${Utils.random()}.txt`; let file2Id; + + const folder1 = `folder-${Utils.random()}`; let folder1Id; + const folder2 = `folder-${Utils.random()}`; let folder2Id; + + beforeAll(done => { + apis.user.nodes.createFolder(folder1).then(resp => folder1Id = resp.data.entry.id) + .then(() => apis.user.nodes.createFile(file1, folder1Id).then(resp => file1Id1 = resp.data.entry.id)) + .then(() => apis.user.nodes.deleteNodeById(file1Id1, false)) + .then(() => apis.user.nodes.createFile(file1, folder1Id).then(resp => file1Id2 = resp.data.entry.id)) + + .then(() => apis.user.nodes.createFolder(folder2).then(resp => folder2Id = resp.data.entry.id)) + .then(() => apis.user.nodes.createFile(file2, folder2Id).then(resp => file2Id = resp.data.entry.id)) + .then(() => apis.user.nodes.deleteNodeById(file2Id, false)) + .then(() => apis.user.nodes.deleteNodeById(folder2Id, false)) + + .then(done); + }); + + beforeEach(done => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.TRASH) + .then(() => dataTable.waitForHeader()) + .then(done); + }); + + afterAll(done => { + Promise.all([ + apis.user.nodes.deleteNodeById(file1Id2), + apis.user.trashcan.emptyTrash() + ]) + .then(done); + }); + + it('Restore a file when another file with same name exists on the restore location', () => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.TRASH) + .then(() => dataTable.clickOnItemName(file1)) + .then(() => toolbar.actions.getButtonByTitleAttribute('Restore').click()) + .then(() => page.getSnackBarMessage()) + .then(text => expect(text).toEqual(`Can't restore, ${file1} already exists`)); + }); + + it('Restore a file when original location no longer exists', () => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.TRASH) + .then(() => dataTable.clickOnItemName(file2)) + .then(() => toolbar.actions.getButtonByTitleAttribute('Restore').click()) + .then(() => page.getSnackBarMessage()) + .then(text => expect(text).toEqual(`Can't restore ${file2}, the original location no longer exists`)); + }); + + }); + + describe('Notification on partial success', () => { + const folder1 = `folder1-${Utils.random()}.txt`; let folder1Id; + const folder2 = `folder2-${Utils.random()}.txt`; let folder2Id; + const file1 = `file-${Utils.random()}.txt`; let file1Id; + const file2 = `file-${Utils.random()}.txt`; let file2Id; + + const folder3 = `folder3-${Utils.random()}.txt`; let folder3Id; + const folder4 = `folder4-${Utils.random()}.txt`; let folder4Id; + const file3 = `file3-${Utils.random()}.txt`; let file3Id; + const file4 = `file4-${Utils.random()}.txt`; let file4Id; + const file5 = `file5-${Utils.random()}.txt`; let file5Id; + + beforeAll(done => { + apis.user.nodes.createFolder(folder1).then(resp => folder1Id = resp.data.entry.id) + .then(() => apis.user.nodes.createFile(file1, folder1Id).then(resp => file1Id = resp.data.entry.id)) + .then(() => apis.user.nodes.createFolder(folder2).then(resp => folder2Id = resp.data.entry.id)) + .then(() => apis.user.nodes.createFile(file2, folder2Id).then(resp => file2Id = resp.data.entry.id)) + .then(() => apis.user.nodes.deleteNodeById(file1Id, false)) + .then(() => apis.user.nodes.deleteNodeById(folder1Id, false)) + .then(() => apis.user.nodes.deleteNodeById(file2Id, false)) + + .then(() => apis.user.nodes.createFolder(folder3).then(resp => folder3Id = resp.data.entry.id)) + .then(() => apis.user.nodes.createFile(file3, folder3Id).then(resp => file3Id = resp.data.entry.id)) + .then(() => apis.user.nodes.createFile(file4, folder3Id).then(resp => file4Id = resp.data.entry.id)) + .then(() => apis.user.nodes.createFolder(folder4).then(resp => folder4Id = resp.data.entry.id)) + .then(() => apis.user.nodes.createFile(file5, folder4Id).then(resp => file5Id = resp.data.entry.id)) + .then(() => apis.user.nodes.deleteNodeById(file3Id, false)) + .then(() => apis.user.nodes.deleteNodeById(file4Id, false)) + .then(() => apis.user.nodes.deleteNodeById(folder3Id, false)) + .then(() => apis.user.nodes.deleteNodeById(file5Id, false)) + + .then(() => loginPage.loginWith(username)) + .then(done); + }); + + beforeEach(done => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.TRASH) + .then(() => dataTable.waitForHeader()) + .then(done); + }); + + afterAll(done => { + Promise.all([ + apis.user.trashcan.emptyTrash(), + logoutPage.load() + ]) + .then(done); + }); + + it('one failure', () => { + dataTable.selectMultipleItems([ file1, file2 ]) + .then(() => toolbar.actions.getButtonByTitleAttribute('Restore').click()) + .then(() => page.getSnackBarMessage()) + .then(text => expect(text).toEqual(`Can't restore ${file1}, the original location no longer exists`)); + }); + + it('multiple failures', () => { + dataTable.selectMultipleItems([ file3, file4, file5 ]) + .then(() => toolbar.actions.getButtonByTitleAttribute('Restore').click()) + .then(() => page.getSnackBarMessage()) + .then(text => expect(text).toEqual('2 items not restored because of issues with the restore location')); + }); + }); +}); diff --git a/e2e/suites/actions/toolbar-multiple-selection.test.ts b/e2e/suites/actions/toolbar-multiple-selection.test.ts new file mode 100755 index 000000000..56c39453b --- /dev/null +++ b/e2e/suites/actions/toolbar-multiple-selection.test.ts @@ -0,0 +1,570 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { browser, protractor } from 'protractor'; +import { LoginPage, LogoutPage, BrowsingPage } from '../../pages/pages'; +import { SITE_VISIBILITY, SITE_ROLES, SIDEBAR_LABELS } from '../../configs'; +import { RepoClient } from '../../utilities/repo-client/repo-client'; +import { Utils } from '../../utilities/utils'; + +describe('Toolbar actions - multiple selection : ', () => { + const user1 = `user-${Utils.random()}`; + const user2 = `user-${Utils.random()}`; + + const file1 = `file-${Utils.random()}.txt`; + let file1Id; + const file2 = `file-${Utils.random()}.txt`; + let file2Id; + + const folder1 = `folder-${Utils.random()}`; + let folder1Id; + const folder2 = `folder-${Utils.random()}`; + let folder2Id; + + const fileForDelete1 = `file-${Utils.random()}.txt`; let fileForDelete1Id; + const fileForDelete2 = `file-${Utils.random()}.txt`; let fileForDelete2Id; + const folderForDelete1 = `folder-${Utils.random()}`; let folderForDelete1Id; + const folderForDelete2 = `folder-${Utils.random()}`; let folderForDelete2Id; + + const siteName = `site-private-${Utils.random()}`; + const file1Admin = `file-${Utils.random()}.txt`; + const file2Admin = `file-${Utils.random()}.txt`; + const folder1Admin = `folder-${Utils.random()}`; + const folder2Admin = `folder-${Utils.random()}`; + + const apis = { + admin: new RepoClient(), + user: new RepoClient(user1, user1) + }; + + const loginPage = new LoginPage(); + const logoutPage = new LogoutPage(); + const page = new BrowsingPage(); + const { dataTable } = page; + const { toolbar } = page; + + beforeAll(done => { + apis.admin.people.createUser(user1) + .then(() => apis.user.nodes.createFiles([ file1 ]).then(resp => file1Id = resp.data.entry.id)) + .then(() => apis.user.nodes.createFiles([ file2 ]).then(resp => file2Id = resp.data.entry.id)) + .then(() => apis.user.nodes.createFolders([ folder1 ]).then(resp => folder1Id = resp.data.entry.id)) + .then(() => apis.user.nodes.createFolders([ folder2 ]).then(resp => folder2Id = resp.data.entry.id)) + .then(() => apis.user.nodes.createFiles([ fileForDelete1 ]).then(resp => fileForDelete1Id = resp.data.entry.id)) + .then(() => apis.user.nodes.createFiles([ fileForDelete2 ]).then(resp => fileForDelete2Id = resp.data.entry.id)) + .then(() => apis.user.nodes.createFolders([ folderForDelete1 ]).then(resp => folderForDelete1Id = resp.data.entry.id)) + .then(() => apis.user.nodes.createFolders([ folderForDelete2 ]).then(resp => folderForDelete2Id = resp.data.entry.id)) + + .then(() => apis.user.shared.shareFilesByIds([ file1Id, file2Id ])) + + .then(() => apis.user.favorites.addFavoritesByIds('file', [ file1Id, file2Id ])) + .then(() => apis.user.favorites.addFavoritesByIds('folder', [ folder1Id, folder2Id ])) + + .then(() => apis.user.nodes.deleteNodesById([ + fileForDelete1Id, fileForDelete2Id, folderForDelete1Id, folderForDelete2Id + ], false)) + + .then(done); + }); + + afterAll(done => { + Promise.all([ + apis.user.nodes.deleteNodesById([ file1Id, file2Id, folder1Id, folder2Id ]), + apis.user.trashcan.emptyTrash(), + logoutPage.load() + ]) + .then(done); + }); + + xit(''); + + describe('Personal Files', () => { + beforeAll(done => { + loginPage.loginWith(user1).then(done); + }); + + beforeEach(done => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.PERSONAL_FILES) + .then(() => dataTable.waitForHeader()) + .then(done); + }); + + afterAll(done => { + logoutPage.load().then(done); + }); + + it('unselect selected items - single click', () => { + dataTable.selectMultipleItems([ file1, file2, folder1, folder2 ]) + .then(() => expect(dataTable.countSelectedRows()).toEqual(4, 'incorrect selected rows number')) + .then(() => dataTable.clickOnItemName(file1)) + .then(() => expect(dataTable.countSelectedRows()).toEqual(1, 'incorrect selected rows number')) + .then(() => dataTable.clearSelection()); + }); + + it('unselect selected items - CMD+click', () => { + dataTable.selectMultipleItems([ file1, file2, folder1, folder2 ]) + .then(() => expect(dataTable.countSelectedRows()).toEqual(4, 'incorrect selected rows number')) + .then(() => browser.actions().sendKeys(protractor.Key.COMMAND).perform()) + .then(() => dataTable.clickOnItemName(file1)) + .then(() => dataTable.clickOnItemName(file2)) + .then(() => browser.actions().sendKeys(protractor.Key.NULL).perform()) + .then(() => expect(dataTable.countSelectedRows()).toEqual(2, 'incorrect selected rows number')) + .then(() => dataTable.clearSelection()); + }); + + it('correct actions appear when multiple files are selected', () => { + dataTable.selectMultipleItems([file1, file2]) + .then(() => toolbar.actions.openMoreMenu()) + .then(menu => { + expect(menu.isMenuItemPresent('Copy')).toBe(true, `Copy is not displayed for selected files`); + expect(menu.isMenuItemPresent('Delete')).toBe(true, `Delete is not displayed for selected files`); + expect(menu.isMenuItemPresent('Move')).toBe(true, `Move is not displayed for selected files`); + expect(menu.isMenuItemPresent('Favorite')).toBe(true, `Favorite is not displayed for selected files`); + }) + // .then(() => browser.$('body').click()) + .then(() => browser.actions().mouseMove(browser.$('body'), { x: 0, y: 0 }).click().perform()) + .then(() => dataTable.clearSelection()); + }); + + it('correct actions appear when multiple folders are selected', () => { + dataTable.selectMultipleItems([folder1, folder2]) + .then(() => toolbar.actions.openMoreMenu()) + .then(menu => { + expect(menu.isMenuItemPresent('Copy')).toBe(true, `Copy is not displayed for selected files`); + expect(menu.isMenuItemPresent('Delete')).toBe(true, `Delete is not displayed for selected files`); + expect(menu.isMenuItemPresent('Move')).toBe(true, `Move is not displayed for selected files`); + expect(menu.isMenuItemPresent('Favorite')).toBe(true, `Favorite is not displayed for selected files`); + }) + // .then(() => browser.$('body').click()) + .then(() => browser.actions().mouseMove(browser.$('body'), { x: 0, y: 0 }).click().perform()) + .then(() => dataTable.clearSelection()); + }); + + it('should not display View action when multiple entries selected', async () => { + await dataTable.selectMultipleItems([folder1, file1, folder2]); + expect(toolbar.actions.isButtonPresent('View')).toBe(false, 'Action is displayed'); + }); + + + it('should display View action when one file is selected', async () => { + await dataTable.selectMultipleItems([file1]); + expect(toolbar.actions.isButtonPresent('View')).toBe(true, 'Action is not displayed'); + }); + + it('should not display View action when only folders selected', async () => { + await dataTable.selectMultipleItems([folder1, folder2]); + expect(toolbar.actions.isButtonPresent('View')).toBe(false, 'Action is displayed'); + }); + + it('should display Download action for selected items', async () => { + await dataTable.selectMultipleItems([folder1, file1, folder2]); + expect(toolbar.actions.isButtonPresent('Download')).toBe(true, 'Action is not displayed'); + }); + + it('should not display Download action for empty selection', async () => { + await dataTable.selectMultipleItems([folder1, file1, folder2]); + await dataTable.clearSelection(); + expect(toolbar.actions.isButtonPresent('Download')).toBe(false, 'Action is displayed'); + }); + + it('should display Edit action when single folder selected', async () => { + await dataTable.selectMultipleItems([folder1]); + expect(toolbar.actions.isButtonPresent('Edit')).toBe(true, 'Action is not displayed'); + }); + + it('should not display Edit action when multiple folders selected', async () => { + await dataTable.selectMultipleItems([folder1, file1, folder2]); + expect(toolbar.actions.isButtonPresent('Edit')).toBe(false, 'Action is displayed'); + }); + + it('should not display Edit action if no folders selected', async () => { + await dataTable.selectMultipleItems([file1, file2]); + expect(toolbar.actions.isButtonPresent('Edit')).toBe(false, 'Action is displayed'); + }); + + it('correct actions appear when both files and folders are selected', () => { + dataTable.selectMultipleItems([file1, file2, folder1, folder2]) + .then(() => toolbar.actions.openMoreMenu()) + .then(menu => { + expect(menu.isMenuItemPresent('Copy')).toBe(true, `Copy is not displayed for selected files`); + expect(menu.isMenuItemPresent('Delete')).toBe(true, `Delete is not displayed for selected files`); + expect(menu.isMenuItemPresent('Move')).toBe(true, `Move is not displayed for selected files`); + expect(menu.isMenuItemPresent('Favorite')).toBe(true, `Favorite is not displayed for selected files`); + }) + // .then(() => browser.$('body').click()) + .then(() => browser.actions().mouseMove(browser.$('body'), { x: 0, y: 0 }).click().perform()) + .then(() => dataTable.clearSelection()); + }); + }); + + describe('File Libraries', () => { + beforeAll(done => { + apis.admin.sites.createSite(siteName, SITE_VISIBILITY.PUBLIC) + .then(() => apis.admin.people.createUser(user2)) + .then(() => apis.admin.sites.addSiteMember(siteName, user1, SITE_ROLES.SITE_MANAGER)) + .then(() => apis.admin.sites.addSiteMember(siteName, user2, SITE_ROLES.SITE_CONSUMER)) + .then(() => apis.admin.nodes.createFiles([ file1Admin, file2Admin ], `Sites/${siteName}/documentLibrary`)) + .then(() => apis.admin.nodes.createFolders([ folder1Admin, folder2Admin ], `Sites/${siteName}/documentLibrary`)) + .then(done); + }); + + beforeEach(done => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.FILE_LIBRARIES) + .then(() => dataTable.waitForHeader()) + .then(() => dataTable.doubleClickOnItemName(siteName)) + .then(() => dataTable.waitForHeader()) + .then(done); + }); + + afterAll(done => { + apis.admin.sites.deleteSite(siteName).then(done); + }); + + xit(''); + + describe('user is Manager', () => { + beforeAll(done => { + loginPage.loginWith(user1).then(done); + }); + + afterAll(done => { + logoutPage.load().then(done); + }); + + it('correct actions appear when multiple files are selected', () => { + dataTable.selectMultipleItems([file1Admin, file2Admin]) + .then(() => { + expect(toolbar.actions.isButtonPresent('View')).toBe(false, 'View is displayed for selected files'); + expect(toolbar.actions.isButtonPresent('Download')).toBe(true, 'Download is not displayed for selected files'); + expect(toolbar.actions.isButtonPresent('Edit')).toBe(false, 'Edit is displayed for selected files'); + }) + .then(() => toolbar.actions.openMoreMenu()) + .then(menu => { + expect(menu.isMenuItemPresent('Copy')).toBe(true, `Copy is not displayed for selected files`); + expect(menu.isMenuItemPresent('Delete')).toBe(true, `Delete is not displayed for selected files`); + expect(menu.isMenuItemPresent('Move')).toBe(true, `Move is not displayed for selected files`); + expect(menu.isMenuItemPresent('Favorite')).toBe(true, `Favorite is not displayed for selected files`); + }) + // .then(() => browser.$('body').click()) + .then(() => browser.actions().mouseMove(browser.$('body'), { x: 0, y: 0 }).click().perform()) + .then(() => dataTable.clearSelection()); + }); + + it('correct actions appear when multiple folders are selected', () => { + dataTable.selectMultipleItems([folder1Admin, folder2Admin]) + .then(() => { + expect(toolbar.actions.isButtonPresent('View')).toBe(false, 'View is displayed'); + expect(toolbar.actions.isButtonPresent('Download')).toBe(true, 'Download is not displayed'); + expect(toolbar.actions.isButtonPresent('Edit')).toBe(false, 'Edit is displayed'); + }) + .then(() => toolbar.actions.openMoreMenu()) + .then(menu => { + expect(menu.isMenuItemPresent('Copy')).toBe(true, `Copy is not displayed for selected files`); + expect(menu.isMenuItemPresent('Delete')).toBe(true, `Delete is not displayed for selected files`); + expect(menu.isMenuItemPresent('Move')).toBe(true, `Move is not displayed for selected files`); + expect(menu.isMenuItemPresent('Favorite')).toBe(true, `Favorite is not displayed for selected files`); + }) + // .then(() => browser.$('body').click()) + .then(() => browser.actions().mouseMove(browser.$('body'), { x: 0, y: 0 }).click().perform()) + .then(() => dataTable.clearSelection()); + }); + + it('correct actions appear when both files and folders are selected', () => { + dataTable.selectMultipleItems([file1Admin, file2Admin, folder1Admin, folder2Admin]) + .then(() => { + expect(toolbar.actions.isButtonPresent('View')).toBe(false, 'View is displayed'); + expect(toolbar.actions.isButtonPresent('Download')).toBe(true, 'Download is not displayed'); + expect(toolbar.actions.isButtonPresent('Edit')).toBe(false, 'Edit is displayed'); + }) + .then(() => toolbar.actions.openMoreMenu()) + .then(menu => { + expect(menu.isMenuItemPresent('Copy')).toBe(true, `Copy is not displayed for selected files`); + expect(menu.isMenuItemPresent('Delete')).toBe(true, `Delete is not displayed for selected files`); + expect(menu.isMenuItemPresent('Move')).toBe(true, `Move is not displayed for selected files`); + expect(menu.isMenuItemPresent('Favorite')).toBe(true, `Favorite is not displayed for selected files`); + }) + // .then(() => browser.$('body').click()) + .then(() => browser.actions().mouseMove(browser.$('body'), { x: 0, y: 0 }).click().perform()) + .then(() => dataTable.clearSelection()); + }); + }); + + describe('user is Consumer', () => { + beforeAll(done => { + loginPage.loginWith(user2).then(done); + }); + + afterAll(done => { + logoutPage.load().then(done); + }); + + it('correct actions appear when multiple files are selected', () => { + dataTable.selectMultipleItems([file1Admin, file2Admin]) + .then(() => { + expect(toolbar.actions.isButtonPresent('View')).toBe(false, 'View is displayed for selected files'); + expect(toolbar.actions.isButtonPresent('Download')).toBe(true, 'Download is not displayed for selected files'); + expect(toolbar.actions.isButtonPresent('Edit')).toBe(false, 'Edit is displayed for selected files'); + }) + .then(() => toolbar.actions.openMoreMenu()) + .then(menu => { + expect(menu.isMenuItemPresent('Copy')).toBe(true, `Copy is not displayed for selected files`); + expect(menu.isMenuItemPresent('Delete')).toBe(false, `Delete is displayed for selected files`); + expect(menu.isMenuItemPresent('Move')).toBe(false, `Move is displayed for selected files`); + expect(menu.isMenuItemPresent('Favorite')).toBe(true, `Favorite is not displayed for selected files`); + }) + // .then(() => browser.$('body').click()) + .then(() => browser.actions().mouseMove(browser.$('body'), { x: 0, y: 0 }).click().perform()) + .then(() => dataTable.clearSelection()); + }); + + it('correct actions appear when multiple folders are selected', () => { + dataTable.selectMultipleItems([folder1Admin, folder2Admin]) + .then(() => { + expect(toolbar.actions.isButtonPresent('View')).toBe(false, 'View is displayed'); + expect(toolbar.actions.isButtonPresent('Download')).toBe(true, 'Download is not displayed'); + expect(toolbar.actions.isButtonPresent('Edit')).toBe(false, 'Edit is displayed'); + }) + .then(() => toolbar.actions.openMoreMenu()) + .then(menu => { + expect(menu.isMenuItemPresent('Copy')).toBe(true, `Copy is not displayed`); + expect(menu.isMenuItemPresent('Delete')).toBe(false, `Delete is displayed`); + expect(menu.isMenuItemPresent('Move')).toBe(false, `Move is displayed`); + expect(menu.isMenuItemPresent('Favorite')).toBe(true, `Favorite is not displayed`); + }) + // .then(() => browser.$('body').click()) + .then(() => browser.actions().mouseMove(browser.$('body'), { x: 0, y: 0 }).click().perform()) + .then(() => dataTable.clearSelection()); + }); + + it('correct actions appear when both files and folders are selected', () => { + dataTable.selectMultipleItems([file1Admin, file2Admin, folder1Admin, folder2Admin]) + .then(() => { + expect(toolbar.actions.isButtonPresent('View')).toBe(false, 'View is displayed'); + expect(toolbar.actions.isButtonPresent('Download')).toBe(true, 'Download is not displayed for selected files'); + expect(toolbar.actions.isButtonPresent('Edit')).toBe(false, 'Edit is displayed'); + }) + .then(() => toolbar.actions.openMoreMenu()) + .then(menu => { + expect(menu.isMenuItemPresent('Copy')).toBe(true, `Copy is not displayed for selected files`); + expect(menu.isMenuItemPresent('Delete')).toBe(false, `Delete is not displayed for selected files`); + expect(menu.isMenuItemPresent('Move')).toBe(false, `Move is not displayed for selected files`); + expect(menu.isMenuItemPresent('Favorite')).toBe(true, `Favorite is not displayed for selected files`); + }) + // .then(() => browser.$('body').click()) + .then(() => browser.actions().mouseMove(browser.$('body'), { x: 0, y: 0 }).click().perform()) + .then(() => dataTable.clearSelection()); + }); + }); + }); + + describe('Shared Files', () => { + beforeAll(done => { + loginPage.loginWith(user1).then(done); + }); + + beforeEach(done => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.SHARED_FILES) + .then(() => dataTable.waitForHeader()) + .then(done); + }); + + afterAll(done => { + logoutPage.load().then(done); + }); + + it('correct actions appear when multiple files are selected', () => { + dataTable.selectMultipleItems([file1, file2]) + .then(() => { + expect(toolbar.actions.isButtonPresent('View')).toBe(false, 'View is displayed'); + expect(toolbar.actions.isButtonPresent('Download')).toBe(true, 'Download is not displayed for selected files'); + expect(toolbar.actions.isButtonPresent('Edit')).toBe(false, 'Edit is displayed for selected files'); + }) + .then(() => toolbar.actions.openMoreMenu()) + .then(menu => { + expect(menu.isMenuItemPresent('Copy')).toBe(true, `Copy is not displayed for selected files`); + expect(menu.isMenuItemPresent('Delete')).toBe(true, `Delete is not displayed for selected files`); + expect(menu.isMenuItemPresent('Move')).toBe(true, `Move is not displayed for selected files`); + expect(menu.isMenuItemPresent('Favorite')).toBe(true, `Favorite is not displayed for selected files`); + }) + // .then(() => browser.$('body').click()) + .then(() => browser.actions().mouseMove(browser.$('body'), { x: 0, y: 0 }).click().perform()) + .then(() => dataTable.clearSelection()); + }); + }); + + describe('Recent Files', () => { + beforeAll(done => { + loginPage.loginWith(user1).then(done); + }); + + beforeEach(done => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.RECENT_FILES) + .then(() => dataTable.waitForHeader()) + .then(done); + }); + + afterAll(done => { + logoutPage.load().then(done); + }); + + it('correct actions appear when multiple files are selected', () => { + dataTable.selectMultipleItems([file1, file2]) + .then(() => { + expect(toolbar.actions.isButtonPresent('View')).toBe(false, 'View is displayed'); + expect(toolbar.actions.isButtonPresent('Download')).toBe(true, 'Download is not displayed'); + expect(toolbar.actions.isButtonPresent('Edit')).toBe(false, 'Edit is displayed'); + }) + .then(() => toolbar.actions.openMoreMenu()) + .then(menu => { + expect(menu.isMenuItemPresent('Copy')).toBe(true, `Copy is not displayed for selected files`); + expect(menu.isMenuItemPresent('Delete')).toBe(true, `Delete is not displayed for selected files`); + expect(menu.isMenuItemPresent('Move')).toBe(true, `Move is not displayed for selected files`); + expect(menu.isMenuItemPresent('Favorite')).toBe(true, `Favorite is not displayed for selected files`); + }) + // .then(() => browser.$('body').click()) + .then(() => browser.actions().mouseMove(browser.$('body'), { x: 0, y: 0 }).click().perform()) + .then(() => dataTable.clearSelection()); + }); + }); + + describe('Favorites', () => { + beforeAll(done => { + loginPage.loginWith(user1).then(done); + }); + + beforeEach(done => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.FAVORITES) + .then(() => dataTable.waitForHeader()) + .then(done); + }); + + afterAll(done => { + logoutPage.load().then(done); + }); + + it('correct actions appear when multiple files are selected', () => { + dataTable.selectMultipleItems([file1, file2]) + .then(() => { + expect(toolbar.actions.isButtonPresent('View')).toBe(false, 'View is displayed'); + expect(toolbar.actions.isButtonPresent('Download')).toBe(true, 'Download is not displayed'); + expect(toolbar.actions.isButtonPresent('Edit')).toBe(false, 'Edit is displayed'); + }) + .then(() => toolbar.actions.openMoreMenu()) + .then(menu => { + expect(menu.isMenuItemPresent('Copy')).toBe(true, `Copy is not displayed for selected files`); + expect(menu.isMenuItemPresent('Delete')).toBe(true, `Delete is not displayed for selected files`); + expect(menu.isMenuItemPresent('Move')).toBe(true, `Move is not displayed for selected files`); + expect(menu.isMenuItemPresent('Favorite')).toBe(true, `Favorite is not displayed for selected files`); + }) + // .then(() => browser.$('body').click()) + .then(() => browser.actions().mouseMove(browser.$('body'), { x: 0, y: 0 }).click().perform()) + .then(() => dataTable.clearSelection()); + }); + + it('correct actions appear when multiple folders are selected', () => { + dataTable.selectMultipleItems([folder1, folder2]) + .then(() => { + expect(toolbar.actions.isButtonPresent('View')).toBe(false, 'View is displayed'); + expect(toolbar.actions.isButtonPresent('Download')).toBe(true, 'Download is not displayed'); + expect(toolbar.actions.isButtonPresent('Edit')).toBe(false, 'Edit is displayed'); + }) + .then(() => toolbar.actions.openMoreMenu()) + .then(menu => { + expect(menu.isMenuItemPresent('Copy')).toBe(true, `Copy is not displayed for selected files`); + expect(menu.isMenuItemPresent('Delete')).toBe(true, `Delete is not displayed for selected files`); + expect(menu.isMenuItemPresent('Move')).toBe(true, `Move is not displayed for selected files`); + expect(menu.isMenuItemPresent('Favorite')).toBe(true, `Favorite is not displayed for selected files`); + }) + // .then(() => browser.$('body').click()) + .then(() => browser.actions().mouseMove(browser.$('body'), { x: 0, y: 0 }).click().perform()) + .then(() => dataTable.clearSelection()); + }); + + it('correct actions appear when both files and folders are selected', () => { + dataTable.selectMultipleItems([file1, file2, folder1, folder2]) + .then(() => { + expect(toolbar.actions.isButtonPresent('View')).toBe(false, 'View is displayed'); + expect(toolbar.actions.isButtonPresent('Download')).toBe(true, 'Download is not displayed for selected files'); + expect(toolbar.actions.isButtonPresent('Edit')).toBe(false, 'Edit is displayed'); + }) + .then(() => toolbar.actions.openMoreMenu()) + .then(menu => { + expect(menu.isMenuItemPresent('Copy')).toBe(true, `Copy is not displayed for selected files`); + expect(menu.isMenuItemPresent('Delete')).toBe(true, `Delete is not displayed for selected files`); + expect(menu.isMenuItemPresent('Move')).toBe(true, `Move is not displayed for selected files`); + expect(menu.isMenuItemPresent('Favorite')).toBe(true, `Favorite is not displayed for selected files`); + }) + // .then(() => browser.$('body').click()) + .then(() => browser.actions().mouseMove(browser.$('body'), { x: 0, y: 0 }).click().perform()) + .then(() => dataTable.clearSelection()); + }); + }); + + // [C217090] + describe('Trash', () => { + beforeAll(done => { + loginPage.loginWith(user1).then(done); + }); + + beforeEach(done => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.TRASH) + .then(() => dataTable.waitForHeader()) + .then(done); + }); + + afterAll(done => { + logoutPage.load().then(done); + }); + + it('correct actions appear when multiple files are selected', () => { + dataTable.selectMultipleItems([fileForDelete1, fileForDelete2]) + .then(() => { + expect(toolbar.actions.isButtonPresent('Permanently delete')) + .toBe(true, 'Permanently delete is displayed for selected files'); + expect(toolbar.actions.isButtonPresent('Restore')).toBe(true, 'Restore is not displayed for selected files'); + }) + .then(() => dataTable.clearSelection()); + }); + + it('correct actions appear when multiple folders are selected', () => { + dataTable.selectMultipleItems([folderForDelete1, folderForDelete2]) + .then(() => { + expect(toolbar.actions.isButtonPresent('Permanently delete')) + .toBe(true, 'Permanently delete is displayed for selected files'); + expect(toolbar.actions.isButtonPresent('Restore')).toBe(true, 'Restore is not displayed for selected files'); + }) + .then(() => dataTable.clearSelection()); + }); + + it('correct actions appear when both files and folders are selected', () => { + dataTable.selectMultipleItems([fileForDelete1, fileForDelete2, folderForDelete1, folderForDelete2]) + .then(() => { + expect(toolbar.actions.isButtonPresent('Permanently delete')) + .toBe(true, 'Permanently delete is displayed for selected files'); + expect(toolbar.actions.isButtonPresent('Restore')).toBe(true, 'Restore is not displayed for selected files'); + }) + .then(() => dataTable.clearSelection()); + }); + }); +}); diff --git a/e2e/suites/actions/toolbar-single-selection.test.ts b/e2e/suites/actions/toolbar-single-selection.test.ts new file mode 100755 index 000000000..d97d7f65b --- /dev/null +++ b/e2e/suites/actions/toolbar-single-selection.test.ts @@ -0,0 +1,763 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { browser } from 'protractor'; +import { LoginPage, LogoutPage, BrowsingPage } from '../../pages/pages'; +import { SITE_VISIBILITY, SITE_ROLES, SIDEBAR_LABELS } from '../../configs'; +import { RepoClient } from '../../utilities/repo-client/repo-client'; +import { Utils } from '../../utilities/utils'; + +describe('Toolbar actions - single selection : ', () => { + const username = `user-${Utils.random()}`; + const username2 = `user-${Utils.random()}`; + + const fileUser = `file-${Utils.random()}.txt`; let fileUserId; + + const folderUser = `folder-${Utils.random()}`; let folderUserId; + + const fileForDelete = `file-${Utils.random()}.txt`; let fileForDeleteId; + + const folderForDelete = `folder-${Utils.random()}`; let folderForDeleteId; + + const siteName = `site-private-${Utils.random()}`; + const fileAdmin = `file-${Utils.random()}.txt`; + const folderAdmin = `folder-${Utils.random()}`; + + const apis = { + admin: new RepoClient(), + user: new RepoClient(username, username) + }; + + const loginPage = new LoginPage(); + const logoutPage = new LogoutPage(); + const page = new BrowsingPage(); + const { dataTable, toolbar } = page; + + beforeAll(done => { + apis.admin.people.createUser(username) + .then(() => apis.user.nodes.createFiles([ fileUser ])) + .then(resp => fileUserId = resp.data.entry.id) + .then(() => apis.user.nodes.createFiles([ fileForDelete ])) + .then(resp => fileForDeleteId = resp.data.entry.id) + .then(() => apis.user.nodes.createFolders([ folderForDelete ])) + .then(resp => folderForDeleteId = resp.data.entry.id) + .then(() => apis.user.nodes.createFolders([ folderUser ])) + .then(resp => folderUserId = resp.data.entry.id) + .then(() => apis.user.shared.shareFileById(fileUserId)) + .then(() => apis.user.favorites.addFavoriteById('file', fileUserId)) + .then(() => apis.user.favorites.addFavoriteById('folder', folderUserId)) + .then(done); + }); + + afterAll(done => { + Promise.all([ + apis.user.nodes.deleteNodeById(fileUserId), + apis.user.nodes.deleteNodeById(folderUserId), + logoutPage.load() + ]) + .then(done); + }); + + xit(''); + + describe('General tests', () => { + const userSite = `site-${Utils.random()}`; + + beforeAll(done => { + apis.user.sites.createSite(userSite, SITE_VISIBILITY.PUBLIC) + .then(() => loginPage.loginWith(username)) + .then(done); + }); + + afterAll(done => { + Promise.all([ + apis.user.sites.deleteSite(userSite), + logoutPage.load() + ]) + .then(done); + }); + + xit('actions not displayed for top level of File Libraries', () => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.FILE_LIBRARIES) + .then(() => dataTable.waitForHeader()) + .then(() => dataTable.clickOnItemName(userSite)) + .then(() => expect(toolbar.actions.isEmpty()).toBe(true, 'toolbar not empty')); + }); + + it('selected row is marked with a check circle icon', () => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.PERSONAL_FILES) + .then(() => dataTable.waitForHeader()) + .then(() => dataTable.clickOnItemName(fileUser)) + .then(() => expect(dataTable.hasCheckMarkIcon(fileUser)).toBe(true, 'check mark missing')); + }); + + describe('granular permissions', () => { + const site = `site-${Utils.random()}`; + const file1 = `file-${Utils.random()}`; let file1Id; + const file2 = `file-${Utils.random()}`; let file2Id; + + beforeAll(done => { + apis.admin.sites.createSite(site, SITE_VISIBILITY.PRIVATE) + .then(() => apis.admin.nodes.createFiles([ file1 ], `Sites/${site}/documentLibrary`) + .then(resp => file1Id = resp.data.entry.id)) + .then(() => apis.admin.nodes.createFiles([ file2 ], `Sites/${site}/documentLibrary`) + .then(resp => file2Id = resp.data.entry.id)) + .then(() => apis.admin.sites.addSiteMember(site, username, SITE_ROLES.SITE_CONSUMER)) + .then(() => apis.admin.nodes.setGranularPermission(file1Id, false, username, SITE_ROLES.SITE_CONSUMER)) + .then(() => apis.admin.nodes.setGranularPermission(file2Id, false, username, SITE_ROLES.SITE_MANAGER)) + + .then(() => apis.user.shared.shareFileById(file1Id)) + .then(() => apis.admin.shared.shareFileById(file2Id)) + + .then(() => apis.user.shared.waitForApi({ expect: 1 })) + .then(() => apis.admin.shared.waitForApi({ expect: 1 })) + + .then(() => apis.user.favorites.addFavoritesByIds('file', [file1Id, file2Id])) + + .then(() => loginPage.loginWith(username)) + .then(done); + }); + + afterAll(done => { + Promise.all([ + apis.admin.sites.deleteSite(site), + logoutPage.load() + ]) + .then(done); + }); + + describe('actions update accordingly for files with different granular permissions', () => { + it('on File Libraries', () => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.FILE_LIBRARIES) + .then(() => dataTable.waitForHeader()) + .then(() => dataTable.doubleClickOnItemName(site)) + .then(() => dataTable.waitForHeader()) + .then(() => dataTable.clickOnItemName(file1)) + .then(() => { + expect(toolbar.actions.isButtonPresent('View')).toBe(true, `View is not displayed for ${file1}`); + expect(toolbar.actions.isButtonPresent('Download')).toBe(true, `Download is not displayed for ${file1}`); + expect(toolbar.actions.isButtonPresent('Edit')).toBe(false, `Edit is displayed for ${file1}`); + }) + .then(() => toolbar.actions.openMoreMenu()) + .then(menu => { + expect(menu.isMenuItemPresent('Copy')).toBe(true, `Copy is not displayed for ${file1}`); + expect(menu.isMenuItemPresent('Delete')).toBe(false, `Delete is displayed for ${file1}`); + expect(menu.isMenuItemPresent('Move')).toBe(false, `Move is displayed for ${file1}`); + expect(menu.isMenuItemPresent('Favorite')).toBe(true, `Favorite is not displayed for ${file1}`); + }) + .then(() => browser.actions().mouseMove(browser.$('body'), { x: 0, y: 0 }).click().perform()) + .then(() => dataTable.clickOnItemName(file2)) + .then(() => { + expect(toolbar.actions.isButtonPresent('View')).toBe(true, `View is not displayed for ${file2}`); + expect(toolbar.actions.isButtonPresent('Download')).toBe(true, `Download is not displayed for ${file2}`); + expect(toolbar.actions.isButtonPresent('Edit')).toBe(false, `Edit is displayed for ${file2}`); + }) + .then(() => toolbar.actions.openMoreMenu()) + .then(menu => { + expect(menu.isMenuItemPresent('Copy')).toBe(true, `Copy is not displayed for ${file2}`); + expect(menu.isMenuItemPresent('Delete')).toBe(true, `Delete is not displayed for ${file2}`); + expect(menu.isMenuItemPresent('Move')).toBe(true, `Move is not displayed for ${file2}`); + expect(menu.isMenuItemPresent('Favorite')).toBe(true, `Favorite is not displayed for ${file2}`); + }) + .then(() => browser.actions().mouseMove(browser.$('body'), { x: 0, y: 0 }).click().perform()); + }); + + xit('on Shared Files', () => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.SHARED_FILES) + .then(() => dataTable.waitForHeader()) + .then(() => dataTable.clickOnItemName(file1)) + .then(() => { + expect(toolbar.actions.isButtonPresent('View')).toBe(true, `View is not displayed for ${file1}`); + expect(toolbar.actions.isButtonPresent('Download')).toBe(true, `Download is not displayed for ${file1}`); + expect(toolbar.actions.isButtonPresent('Edit')).toBe(false, `Edit is displayed for ${file1}`); + }) + .then(() => toolbar.actions.openMoreMenu()) + .then(menu => { + expect(menu.isMenuItemPresent('Copy')).toBe(true, `Copy is not displayed for ${file1}`); + expect(menu.isMenuItemPresent('Delete')).toBe(false, `Delete is displayed for ${file1}`); + expect(menu.isMenuItemPresent('Move')).toBe(false, `Move is displayed for ${file1}`); + expect(menu.isMenuItemPresent('Favorite')).toBe(true, `Favorite is not displayed for ${file1}`); + }) + .then(() => browser.actions().mouseMove(browser.$('body'), { x: 0, y: 0 }).click().perform()) + .then(() => dataTable.clickOnItemName(file2)) + .then(() => { + expect(toolbar.actions.isButtonPresent('View')).toBe(true, `View is not displayed for ${file2}`); + expect(toolbar.actions.isButtonPresent('Download')).toBe(true, `Download is not displayed for ${file2}`); + expect(toolbar.actions.isButtonPresent('Edit')).toBe(false, `Edit is displayed for ${file2}`); + }) + .then(() => toolbar.actions.openMoreMenu()) + .then(menu => { + expect(menu.isMenuItemPresent('Copy')).toBe(true, `Copy is not displayed for ${file2}`); + expect(menu.isMenuItemPresent('Delete')).toBe(true, `Delete is not displayed for ${file2}`); + expect(menu.isMenuItemPresent('Move')).toBe(true, `Move is not displayed for ${file2}`); + expect(menu.isMenuItemPresent('Favorite')).toBe(true, `Favorite is not displayed for ${file2}`); + }) + .then(() => browser.actions().mouseMove(browser.$('body'), { x: 0, y: 0 }).click().perform()); + }); + + // disabled until ACA-1184 is done + xit('on Favorites', () => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.FAVORITES) + .then(() => dataTable.waitForHeader()) + .then(() => dataTable.clickOnItemName(file1)) + .then(() => { + expect(toolbar.actions.isButtonPresent('View')).toBe(true, `View is not displayed for ${file1}`); + expect(toolbar.actions.isButtonPresent('Download')).toBe(true, `Download is not displayed for ${file1}`); + expect(toolbar.actions.isButtonPresent('Edit')).toBe(false, `Edit is displayed for ${file1}`); + }) + .then(() => toolbar.actions.openMoreMenu()) + .then(menu => { + expect(menu.isMenuItemPresent('Copy')).toBe(true, `Copy is not displayed for ${file1}`); + expect(menu.isMenuItemPresent('Delete')).toBe(false, `Delete is displayed for ${file1}`); + expect(menu.isMenuItemPresent('Move')).toBe(false, `Move is displayed for ${file1}`); + expect(menu.isMenuItemPresent('Favorite')).toBe(true, `Favorite is not displayed for ${file1}`); + }) + .then(() => browser.actions().mouseMove(browser.$('body'), { x: 0, y: 0 }).click().perform()) + .then(() => dataTable.clickOnItemName(file2)) + .then(() => { + expect(toolbar.actions.isButtonPresent('View')).toBe(true, `View is not displayed for ${file2}`); + expect(toolbar.actions.isButtonPresent('Download')).toBe(true, `Download is not displayed for ${file2}`); + expect(toolbar.actions.isButtonPresent('Edit')).toBe(false, `Edit is displayed for ${file2}`); + }) + .then(() => toolbar.actions.openMoreMenu()) + .then(menu => { + expect(menu.isMenuItemPresent('Copy')).toBe(true, `Copy is not displayed for ${file2}`); + expect(menu.isMenuItemPresent('Delete')).toBe(true, `Delete is not displayed for ${file2}`); + expect(menu.isMenuItemPresent('Move')).toBe(true, `Move is not displayed for ${file2}`); + expect(menu.isMenuItemPresent('Favorite')).toBe(true, `Favorite is not displayed for ${file2}`); + }) + .then(() => browser.actions().mouseMove(browser.$('body'), { x: 0, y: 0 }).click().perform()); + }); + }); + + describe('correct actions are displayed when selecting multiple files with different granular permissions', () => { + it('on File Libraries', () => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.FILE_LIBRARIES) + .then(() => dataTable.waitForHeader()) + .then(() => dataTable.doubleClickOnItemName(site)) + .then(() => dataTable.waitForHeader()) + .then(() => dataTable.selectMultipleItems([ file1, file2 ])) + .then(() => { + expect(toolbar.actions.isButtonPresent('View')).toBe(false, `View is displayed for selected files`); + expect(toolbar.actions.isButtonPresent('Download')).toBe(true, `Download is not displayed for selected files`); + expect(toolbar.actions.isButtonPresent('Edit')).toBe(false, `Edit is displayed for selected files`); + }) + .then(() => toolbar.actions.openMoreMenu()) + .then(menu => { + expect(menu.isMenuItemPresent('Copy')).toBe(true, `Copy is not displayed for selected files`); + expect(menu.isMenuItemPresent('Delete')).toBe(false, `Delete is displayed for selected files`); + expect(menu.isMenuItemPresent('Move')).toBe(false, `Move is displayed for selected files`); + expect(menu.isMenuItemPresent('Favorite')).toBe(true, `Favorite is not displayed for selected files`); + }) + .then(() => browser.actions().mouseMove(browser.$('body'), { x: 0, y: 0 }).click().perform()); + }); + + xit('on Shared Files', () => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.SHARED_FILES) + .then(() => dataTable.waitForHeader()) + .then(() => dataTable.selectMultipleItems([ file1, file2 ])) + .then(() => { + expect(toolbar.actions.isButtonPresent('View')).toBe(false, `View is displayed for selected files`); + expect(toolbar.actions.isButtonPresent('Download')).toBe(true, `Download is not displayed for selected files`); + expect(toolbar.actions.isButtonPresent('Edit')).toBe(false, `Edit is displayed for selected files`); + }) + .then(() => toolbar.actions.openMoreMenu()) + .then(menu => { + expect(menu.isMenuItemPresent('Copy')).toBe(true, `Copy is not displayed for selected files`); + expect(menu.isMenuItemPresent('Delete')).toBe(false, `Delete is displayed for selected files`); + expect(menu.isMenuItemPresent('Move')).toBe(false, `Move is displayed for selected files`); + expect(menu.isMenuItemPresent('Favorite')).toBe(true, `Favorite is not displayed for selected files`); + }) + .then(() => browser.actions().mouseMove(browser.$('body'), { x: 0, y: 0 }).click().perform()); + }); + + // disabled until ACA-1184 is done + xit('on Favorites', () => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.FAVORITES) + .then(() => dataTable.waitForHeader()) + .then(() => dataTable.selectMultipleItems([ file1, file2 ])) + .then(() => { + expect(toolbar.actions.isButtonPresent('View')).toBe(false, `View is displayed for selected files`); + expect(toolbar.actions.isButtonPresent('Download')).toBe(true, `Download is not displayed for selected files`); + expect(toolbar.actions.isButtonPresent('Edit')).toBe(false, `Edit is displayed for selected files`); + }) + .then(() => toolbar.actions.openMoreMenu()) + .then(menu => { + expect(menu.isMenuItemPresent('Copy')).toBe(true, `Copy is not displayed for selected files`); + expect(menu.isMenuItemPresent('Delete')).toBe(false, `Delete is displayed for selected files`); + expect(menu.isMenuItemPresent('Move')).toBe(false, `Move is displayed for selected files`); + expect(menu.isMenuItemPresent('Favorite')).toBe(true, `Favorite is not displayed for selected files`); + }) + .then(() => browser.actions().mouseMove(browser.$('body'), { x: 0, y: 0 }).click().perform()); + }); + }); + + xit(''); + }); + }); + + describe('Personal Files', () => { + beforeAll(done => { + loginPage.loginWith(username).then(done); + }); + + beforeEach(done => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.PERSONAL_FILES) + .then(() => dataTable.waitForHeader()) + .then(done); + }); + + afterAll(done => { + logoutPage.load().then(done); + }); + + it('actions are not displayed when no item is selected', () => { + expect(toolbar.actions.isEmpty()).toBe(true, `actions displayed though nothing selected`); + }); + + it('actions are displayed when a file is selected', () => { + dataTable.clickOnItemName(fileUser) + .then(() => { + expect(toolbar.actions.isEmpty()).toBe(false, `actions not displayed for ${fileUser}`); + }); + }); + + it('actions are displayed when a folder is selected', () => { + dataTable.clickOnItemName(folderUser) + .then(() => { + expect(toolbar.actions.isEmpty()).toBe(false, `actions not displayed for ${folderUser}`); + }); + }); + + it('correct actions appear when a file is selected', () => { + dataTable.clickOnItemName(fileUser) + .then(() => { + expect(toolbar.actions.isButtonPresent('View')).toBe(true, `View is not displayed for ${fileUser}`); + expect(toolbar.actions.isButtonPresent('Download')).toBe(true, `Download is not displayed for ${fileUser}`); + expect(toolbar.actions.isButtonPresent('Edit')).toBe(false, `Edit is displayed for ${fileUser}`); + }) + .then(() => toolbar.actions.openMoreMenu()) + .then(menu => { + expect(menu.isMenuItemPresent('Copy')).toBe(true, `Copy is not displayed for ${fileUser}`); + expect(menu.isMenuItemPresent('Delete')).toBe(true, `Delete is not displayed for ${fileUser}`); + expect(menu.isMenuItemPresent('Move')).toBe(true, `Move is not displayed for ${fileUser}`); + expect(menu.isMenuItemPresent('Favorite')).toBe(true, `Favorite is not displayed for ${fileUser}`); + }) + .then(() => browser.actions().mouseMove(browser.$('body'), { x: 0, y: 0 }).click().perform()); + }); + + it('correct actions appear when a folder is selected', () => { + dataTable.clickOnItemName(folderUser) + .then(() => { + expect(toolbar.actions.isButtonPresent('View')).toBe(false, `View is displayed for ${folderUser}`); + expect(toolbar.actions.isButtonPresent('Download')).toBe(true, `Download is not enabled for ${folderUser}`); + expect(toolbar.actions.isButtonPresent('Edit')).toBe(true, `Edit is not displayed for ${folderUser}`); + }) + .then(() => toolbar.actions.openMoreMenu()) + .then(menu => { + expect(menu.isMenuItemPresent('Copy')).toBe(true, `Copy is not displayed for ${folderUser}`); + expect(menu.isMenuItemPresent('Delete')).toBe(true, `Delete is not displayed for ${folderUser}`); + expect(menu.isMenuItemPresent('Move')).toBe(true, `Move is not displayed for ${folderUser}`); + expect(menu.isMenuItemPresent('Favorite')).toBe(true, `Favorite is not displayed for ${folderUser}`); + }) + .then(() => browser.actions().mouseMove(browser.$('body'), { x: 0, y: 0 }).click().perform()); + }); + }); + + describe('File Libraries', () => { + beforeAll(done => { + apis.admin.sites.createSite(siteName, SITE_VISIBILITY.PUBLIC) + .then(() => apis.admin.people.createUser(username2)) + .then(() => apis.admin.sites.addSiteMember(siteName, username, SITE_ROLES.SITE_MANAGER)) + .then(() => apis.admin.sites.addSiteMember(siteName, username2, SITE_ROLES.SITE_CONSUMER)) + .then(() => apis.admin.nodes.createFiles([ fileAdmin ], `Sites/${siteName}/documentLibrary`)) + .then(() => apis.admin.nodes.createFolders([ folderAdmin ], `Sites/${siteName}/documentLibrary`)) + .then(done); + }); + + beforeEach(done => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.FILE_LIBRARIES) + .then(() => dataTable.waitForHeader()) + .then(() => dataTable.doubleClickOnItemName(siteName)) + .then(() => dataTable.waitForHeader()) + .then(done); + }); + + afterAll(done => { + apis.admin.sites.deleteSite(siteName).then(done); + }); + + xit(''); + + describe('user is Manager', () => { + beforeAll(done => { + loginPage.loginWith(username).then(done); + }); + + afterAll(done => { + logoutPage.load().then(done); + }); + + it('actions are not displayed when no item is selected', () => { + expect(toolbar.actions.isEmpty()).toBe(true, `actions displayed though nothing selected`); + }); + + it('actions are displayed when a file is selected', () => { + dataTable.clickOnItemName(fileAdmin) + .then(() => { + expect(toolbar.actions.isEmpty()).toBe(false, `actions not displayed for ${fileAdmin}`); + }); + }); + + it('actions are displayed when a folder is selected', () => { + dataTable.clickOnItemName(folderAdmin) + .then(() => { + expect(toolbar.actions.isEmpty()).toBe(false, `actions not displayed for ${folderAdmin}`); + }); + }); + + it('correct actions appear when a file is selected', () => { + dataTable.clickOnItemName(fileAdmin) + .then(() => { + expect(toolbar.actions.isButtonPresent('View')).toBe(true, `View is not displayed for ${fileAdmin}`); + expect(toolbar.actions.isButtonPresent('Download')).toBe(true, `Download is not displayed for ${fileAdmin}`); + expect(toolbar.actions.isButtonPresent('Edit')).toBe(false, `Edit is displayed for ${fileAdmin}`); + }) + .then(() => toolbar.actions.openMoreMenu()) + .then(menu => { + expect(menu.isMenuItemPresent('Copy')).toBe(true, `Copy is not displayed for ${fileAdmin}`); + expect(menu.isMenuItemPresent('Delete')).toBe(true, `Delete is not displayed for ${fileAdmin}`); + expect(menu.isMenuItemPresent('Move')).toBe(true, `Move is not displayed for ${fileAdmin}`); + expect(menu.isMenuItemPresent('Favorite')).toBe(true, `Favorite is not displayed for ${fileAdmin}`); + }) + .then(() => browser.actions().mouseMove(browser.$('body'), { x: 0, y: 0 }).click().perform()); + }); + + it('correct actions appear when a folder is selected', () => { + dataTable.clickOnItemName(folderAdmin) + .then(() => { + expect(toolbar.actions.isButtonPresent('View')).toBe(false, `View is displayed for ${folderAdmin}`); + expect(toolbar.actions.isButtonPresent('Download')).toBe(true, `Download is not enabled for ${folderAdmin}`); + expect(toolbar.actions.isButtonPresent('Edit')).toBe(true, `Edit is not displayed for ${folderAdmin}`); + }) + .then(() => toolbar.actions.openMoreMenu()) + .then(menu => { + expect(menu.isMenuItemPresent('Copy')).toBe(true, `Copy is not displayed for ${folderAdmin}`); + expect(menu.isMenuItemPresent('Delete')).toBe(true, `Delete is not displayed for ${folderAdmin}`); + expect(menu.isMenuItemPresent('Move')).toBe(true, `Move is not displayed for ${folderAdmin}`); + expect(menu.isMenuItemPresent('Favorite')).toBe(true, `Favorite is not displayed for ${folderAdmin}`); + }) + .then(() => browser.actions().mouseMove(browser.$('body'), { x: 0, y: 0 }).click().perform()); + }); + }); + + describe('user is Consumer', () => { + beforeAll(done => { + loginPage.loginWith(username2).then(done); + }); + + afterAll(done => { + logoutPage.load().then(done); + }); + + it('actions are not displayed when no item is selected', () => { + expect(toolbar.actions.isEmpty()).toBe(true, `actions displayed though nothing selected`); + }); + + it('actions are displayed when a file is selected', () => { + dataTable.clickOnItemName(fileAdmin) + .then(() => { + expect(toolbar.actions.isEmpty()).toBe(false, `actions not displayed for ${fileAdmin}`); + }); + }); + + it('actions are displayed when a folder is selected', () => { + dataTable.clickOnItemName(folderAdmin) + .then(() => { + expect(toolbar.actions.isEmpty()).toBe(false, `actions not displayed for ${folderAdmin}`); + }); + }); + + it('correct actions appear when a file is selected', () => { + dataTable.clickOnItemName(fileAdmin) + .then(() => { + expect(toolbar.actions.isButtonPresent('View')).toBe(true, `View is not displayed for ${fileAdmin}`); + expect(toolbar.actions.isButtonPresent('Download')).toBe(true, `Download is not displayed for ${fileAdmin}`); + expect(toolbar.actions.isButtonPresent('Edit')).toBe(false, `Edit is displayed for ${fileAdmin}`); + }) + .then(() => toolbar.actions.openMoreMenu()) + .then(menu => { + expect(menu.isMenuItemPresent('Copy')).toBe(true, `Copy is not displayed for ${fileAdmin}`); + expect(menu.isMenuItemPresent('Delete')).toBe(false, `Delete is displayed for ${fileAdmin}`); + expect(menu.isMenuItemPresent('Move')).toBe(false, `Move is displayed for ${fileAdmin}`); + expect(menu.isMenuItemPresent('Favorite')).toBe(true, `Favorite is not displayed for ${fileAdmin}`); + }) + .then(() => browser.actions().mouseMove(browser.$('body'), { x: 0, y: 0 }).click().perform()); + }); + + it('correct actions appear when a folder is selected', () => { + dataTable.clickOnItemName(folderAdmin) + .then(() => { + expect(toolbar.actions.isButtonPresent('View')).toBe(false, `View is displayed for ${folderAdmin}`); + expect(toolbar.actions.isButtonPresent('Download')).toBe(true, `Download is not enabled for ${folderAdmin}`); + expect(toolbar.actions.isButtonPresent('Edit')).toBe(false, `Edit is displayed for ${folderAdmin}`); + }) + .then(() => toolbar.actions.openMoreMenu()) + .then(menu => { + expect(menu.isMenuItemPresent('Copy')).toBe(true, `Copy is not displayed for ${folderAdmin}`); + expect(menu.isMenuItemPresent('Delete')).toBe(false, `Delete is displayed for ${folderAdmin}`); + expect(menu.isMenuItemPresent('Move')).toBe(false, `Move is displayed for ${folderAdmin}`); + expect(menu.isMenuItemPresent('Favorite')).toBe(true, `Favorite is not displayed for ${folderAdmin}`); + }) + .then(() => browser.actions().mouseMove(browser.$('body'), { x: 0, y: 0 }).click().perform()); + }); + }); + }); + + describe('Shared Files', () => { + beforeAll(done => { + loginPage.loginWith(username).then(done); + }); + + beforeEach(done => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.SHARED_FILES) + .then(() => dataTable.waitForHeader()) + .then(done); + }); + + afterEach(() => { + dataTable.clearSelection(); + }); + + afterAll(done => { + logoutPage.load().then(done); + }); + + it('actions are not displayed when no item is selected', () => { + expect(toolbar.actions.isEmpty()).toBe(true, `actions displayed though nothing selected`); + }); + + it('actions are displayed when a file is selected', () => { + dataTable.clickOnItemName(fileUser) + .then(() => { + expect(toolbar.actions.isEmpty()).toBe(false, `actions not displayed for ${fileUser}`); + }); + }); + + it('correct actions appear when a file is selected', () => { + dataTable.clickOnItemName(fileUser) + .then(() => { + expect(toolbar.actions.isButtonPresent('View')).toBe(true, `View is not displayed for ${fileUser}`); + expect(toolbar.actions.isButtonPresent('Download')).toBe(true, `Download is not displayed for ${fileUser}`); + expect(toolbar.actions.isButtonPresent('Edit')).toBe(false, `Edit is displayed for ${fileUser}`); + }) + .then(() => toolbar.actions.openMoreMenu()) + .then(menu => { + expect(menu.isMenuItemPresent('Copy')).toBe(true, `Copy is not displayed for ${fileUser}`); + expect(menu.isMenuItemPresent('Delete')).toBe(true, `Delete is not displayed for ${fileUser}`); + expect(menu.isMenuItemPresent('Move')).toBe(true, `Move is not displayed for ${fileUser}`); + expect(menu.isMenuItemPresent('Favorite')).toBe(true, `Favorite is not displayed for ${fileUser}`); + }) + .then(() => browser.actions().mouseMove(browser.$('body'), { x: 0, y: 0 }).click().perform()); + }); + }); + + describe('Recent Files', () => { + beforeAll(done => { + loginPage.loginWith(username).then(done); + }); + + beforeEach(done => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.RECENT_FILES) + .then(() => dataTable.waitForHeader()) + .then(done); + }); + + afterEach(() => { + dataTable.clearSelection(); + }); + + afterAll(done => { + logoutPage.load().then(done); + }); + + it('actions are not displayed when no item is selected', () => { + expect(toolbar.actions.isEmpty()).toBe(true, `actions displayed though nothing selected`); + }); + + it('actions are displayed when a file is selected', () => { + dataTable.clickOnItemName(fileUser) + .then(() => { + expect(toolbar.actions.isEmpty()).toBe(false, `actions not displayed for ${fileUser}`); + }); + }); + + it('correct actions appear when a file is selected', () => { + dataTable.clickOnItemName(fileUser) + .then(() => { + expect(toolbar.actions.isButtonPresent('View')).toBe(true, `View is not displayed for ${fileUser}`); + expect(toolbar.actions.isButtonPresent('Download')).toBe(true, `Download is not displayed for ${fileUser}`); + expect(toolbar.actions.isButtonPresent('Edit')).toBe(false, `Edit is displayed for ${fileUser}`); + }) + .then(() => toolbar.actions.openMoreMenu()) + .then(menu => { + expect(menu.isMenuItemPresent('Copy')).toBe(true, `Copy is not displayed for ${fileUser}`); + expect(menu.isMenuItemPresent('Delete')).toBe(true, `Delete is not displayed for ${fileUser}`); + expect(menu.isMenuItemPresent('Move')).toBe(true, `Move is not displayed for ${fileUser}`); + expect(menu.isMenuItemPresent('Favorite')).toBe(true, `Favorite is not displayed for ${fileUser}`); + }) + .then(() => browser.actions().mouseMove(browser.$('body'), { x: 0, y: 0 }).click().perform()); + }); + }); + + describe('Favorites', () => { + beforeAll(done => { + loginPage.loginWith(username).then(done); + }); + + beforeEach(done => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.FAVORITES) + .then(() => dataTable.waitForHeader()) + .then(done); + }); + + afterAll(done => { + logoutPage.load().then(done); + }); + + it('actions are not displayed when no item is selected', () => { + expect(toolbar.actions.isEmpty()).toBe(true, `actions displayed though nothing selected`); + }); + + it('actions are displayed when a file is selected', () => { + dataTable.clickOnItemName(fileUser) + .then(() => { + expect(toolbar.actions.isEmpty()).toBe(false, `actions not displayed for ${fileUser}`); + }); + }); + + it('actions are displayed when a folder is selected', () => { + dataTable.clickOnItemName(folderUser) + .then(() => { + expect(toolbar.actions.isEmpty()).toBe(false, `actions not displayed for ${folderUser}`); + }); + }); + + it('correct actions appear when a file is selected', () => { + dataTable.clickOnItemName(fileUser) + .then(() => { + expect(toolbar.actions.isButtonPresent('View')).toBe(true, `View is not displayed for ${fileUser}`); + expect(toolbar.actions.isButtonPresent('Download')).toBe(true, `Download is not displayed for ${fileUser}`); + expect(toolbar.actions.isButtonPresent('Edit')).toBe(false, `Edit is displayed for ${fileUser}`); + }) + .then(() => toolbar.actions.openMoreMenu()) + .then(menu => { + expect(menu.isMenuItemPresent('Copy')).toBe(true, `Copy is not displayed for ${fileUser}`); + expect(menu.isMenuItemPresent('Delete')).toBe(true, `Delete is not displayed for ${fileUser}`); + expect(menu.isMenuItemPresent('Move')).toBe(true, `Move is not displayed for ${fileUser}`); + expect(menu.isMenuItemPresent('Favorite')).toBe(true, `Favorite is not displayed for ${fileUser}`); + }) + .then(() => browser.actions().mouseMove(browser.$('body'), { x: 0, y: 0 }).click().perform()); + }); + + it('correct actions appear when a folder is selected', () => { + dataTable.clickOnItemName(folderUser) + .then(() => { + expect(toolbar.actions.isButtonPresent('View')).toBe(false, `View is displayed for ${folderUser}`); + expect(toolbar.actions.isButtonPresent('Download')).toBe(true, `Download is not enabled for ${folderUser}`); + expect(toolbar.actions.isButtonPresent('Edit')).toBe(true, `Edit is not displayed for ${folderUser}`); + }) + .then(() => toolbar.actions.openMoreMenu()) + .then(menu => { + expect(menu.isMenuItemPresent('Copy')).toBe(true, `Copy is not displayed for ${folderUser}`); + expect(menu.isMenuItemPresent('Delete')).toBe(true, `Delete is not displayed for ${folderUser}`); + expect(menu.isMenuItemPresent('Move')).toBe(true, `Move is not displayed for ${folderUser}`); + expect(menu.isMenuItemPresent('Favorite')).toBe(true, `Favorite is not displayed for ${folderUser}`); + }) + .then(() => browser.actions().mouseMove(browser.$('body'), { x: 0, y: 0 }).click().perform()); + }); + }); + + // [C217090] + describe('Trash', () => { + beforeAll(done => { + apis.user.nodes.deleteNodeById(fileForDeleteId, false) + .then(() => apis.user.nodes.deleteNodeById(folderForDeleteId, false)) + .then(() => loginPage.loginWith(username)) + .then(done); + }); + + beforeEach(done => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.TRASH) + .then(() => dataTable.waitForHeader()) + .then(done); + }); + + afterAll(done => { + Promise.all([ + apis.user.trashcan.permanentlyDelete(fileForDeleteId), + apis.user.trashcan.permanentlyDelete(folderForDeleteId), + logoutPage.load() + ]) + .then(done); + }); + + it('actions are not displayed when no item is selected', () => { + expect(toolbar.actions.isEmpty()).toBe(true, `actions displayed though nothing selected`); + }); + + it('actions are displayed when a file is selected', () => { + dataTable.clickOnItemName(fileForDelete) + .then(() => { + expect(toolbar.actions.isEmpty()).toBe(false, `actions not displayed for ${fileForDelete}`); + }); + }); + + it('actions are displayed when a folder is selected', () => { + dataTable.clickOnItemName(folderForDelete) + .then(() => { + expect(toolbar.actions.isEmpty()).toBe(false, `actions not displayed for ${folderForDelete}`); + }); + }); + + it('correct actions appear when a file is selected', () => { + dataTable.clickOnItemName(fileForDelete) + .then(() => { + expect(toolbar.actions.isButtonPresent('Permanently delete')) + .toBe(true, `Permanently delete is not displayed for ${fileForDelete}`); + expect(toolbar.actions.isButtonPresent('Restore')).toBe(true, `Restore is not displayed for ${fileForDelete}`); + }); + }); + + it('correct actions appear when a folder is selected', () => { + dataTable.clickOnItemName(folderForDelete) + .then(() => { + expect(toolbar.actions.isButtonPresent('Permanently delete')) + .toBe(true, `Permanently delete is displayed for ${folderForDelete}`); + expect(toolbar.actions.isButtonPresent('Restore')).toBe(true, `Restore is not enabled for ${folderForDelete}`); + }); + }); + }); +}); diff --git a/e2e/suites/actions/undo-delete.test.ts b/e2e/suites/actions/undo-delete.test.ts new file mode 100755 index 000000000..a780644bc --- /dev/null +++ b/e2e/suites/actions/undo-delete.test.ts @@ -0,0 +1,423 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { browser } from 'protractor'; +import { LoginPage, LogoutPage, BrowsingPage } from '../../pages/pages'; +import { SIDEBAR_LABELS } from '../../configs'; +import { RepoClient } from '../../utilities/repo-client/repo-client'; +import { Utils } from '../../utilities/utils'; + +describe('Undo delete content', () => { + const username = `user-${Utils.random()}`; + + const apis = { + admin: new RepoClient(), + user: new RepoClient(username, username) + }; + + const loginPage = new LoginPage(); + const logoutPage = new LogoutPage(); + const page = new BrowsingPage(); + const { dataTable, toolbar } = page; + + beforeAll(done => { + apis.admin.people.createUser(username).then(done); + }); + + afterAll(done => { + apis.admin.trashcan.emptyTrash().then(done); + }); + + xit(''); + + describe('on Personal Files', () => { + const file1 = `file1-${Utils.random()}.txt`; let file1Id; + const file2 = `file2-${Utils.random()}.txt`; let file2Id; + const file3 = `file3-${Utils.random()}.txt`; let file3Id; + const file4 = `file4-${Utils.random()}.txt`; + const folder1 = `folder1-${Utils.random()}`; let folder1Id; + const folder2 = `folder2-${Utils.random()}`; let folder2Id; + const fileLocked2 = `fileLocked2-${Utils.random()}.txt`; let fileLocked2Id; + + beforeAll(done => { + apis.user.nodes.createFile(file1).then(resp => file1Id = resp.data.entry.id) + .then(() => apis.user.nodes.createFile(file2).then(resp => file2Id = resp.data.entry.id)) + .then(() => apis.user.nodes.createFile(file3).then(resp => file3Id = resp.data.entry.id)) + .then(() => apis.user.nodes.createFolder(folder1).then(resp => folder1Id = resp.data.entry.id)) + .then(() => apis.user.nodes.createFile(file4, folder1Id)) + .then(() => apis.user.nodes.createFolder(folder2).then(resp => folder2Id = resp.data.entry.id)) + .then(() => apis.user.nodes.createFile(fileLocked2, folder2Id).then(resp => fileLocked2Id = resp.data.entry.id)) + .then(() => apis.user.nodes.lockFile(fileLocked2Id)) + + .then(() => loginPage.loginWith(username)) + .then(done); + }); + + beforeEach(done => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.PERSONAL_FILES) + .then(() => dataTable.waitForHeader()) + .then(done); + }); + + afterEach(done => { + page.refresh().then(done); + }); + + afterAll(done => { + Promise.all([ + logoutPage.load(), + apis.user.nodes.unlockFile(fileLocked2Id) + .then(() => apis.user.nodes.deleteNodesById([file1Id, file2Id, file3Id, folder1Id, folder2Id])) + ]) + .then(done); + }); + + it('Successful delete notification shows Undo action', () => { + dataTable.clickOnItemName(file1) + .then(() => toolbar.actions.openMoreMenu()) + .then(() => toolbar.actions.menu.clickMenuItem('Delete')) + .then(() => page.getSnackBarMessage()) + .then(message => { + expect(message).toContain(`Undo`); + }) + + .then(() => apis.user.trashcan.restore(file1Id)); + }); + + it('Unsuccessful delete notification does not show Undo action', () => { + dataTable.clickOnItemName(folder2) + .then(() => toolbar.actions.openMoreMenu()) + .then(() => toolbar.actions.menu.clickMenuItem('Delete')) + .then(() => page.getSnackBarMessage()) + .then(message => { + expect(message).not.toContain(`Undo`); + }); + }); + + it('Undo delete of file', () => { + let items: number; + page.dataTable.countRows().then(number => { items = number; }); + + dataTable.clickOnItemName(file1) + .then(() => toolbar.actions.openMoreMenu()) + .then(() => toolbar.actions.menu.clickMenuItem('Delete')) + .then(() => page.clickSnackBarAction()) + .then(() => { + expect(dataTable.getRowName(file1).isPresent()).toBe(true, 'Item was not restored'); + expect(page.pagination.range.getText()).toContain(`1-${items} of ${items}`); + }); + }); + + it('Undo delete of folder with content', () => { + let items: number; + page.dataTable.countRows().then(number => { items = number; }); + + dataTable.clickOnItemName(folder1) + .then(() => toolbar.actions.openMoreMenu()) + .then(() => toolbar.actions.menu.clickMenuItem('Delete')) + .then(() => page.clickSnackBarAction()) + .then(() => { + expect(dataTable.getRowName(folder1).isPresent()).toBe(true, 'Item was not restored'); + expect(page.pagination.range.getText()).toContain(`1-${items} of ${items}`); + }) + .then(() => dataTable.doubleClickOnItemName(folder1)) + .then(() => { + expect(dataTable.getRowName(file4).isPresent()).toBe(true, 'file from folder not restored'); + }); + }); + + it('undo delete of multiple files', () => { + let items: number; + page.dataTable.countRows().then(number => { items = number; }); + + dataTable.selectMultipleItems([file2, file3]) + .then(() => toolbar.actions.openMoreMenu()) + .then(() => toolbar.actions.menu.clickMenuItem('Delete')) + .then(() => page.clickSnackBarAction()) + .then(() => { + expect(dataTable.getRowName(file2).isPresent()).toBe(true, `${file2} was not removed from list`); + expect(dataTable.getRowName(file3).isPresent()).toBe(true, `${file3} was not removed from list`); + expect(page.pagination.range.getText()).toContain(`1-${items} of ${items}`); + }); + }); + }); + + describe('on Shared Files', () => { + const sharedFile1 = `sharedFile1-${Utils.random()}`; let sharedFile1Id; + const sharedFile2 = `sharedFile2-${Utils.random()}`; let sharedFile2Id; + const sharedFile3 = `sharedFile3-${Utils.random()}`; let sharedFile3Id; + const sharedFile4 = `sharedFile4-${Utils.random()}`; let sharedFile4Id; + + beforeAll(done => { + apis.user.nodes.createFile(sharedFile1).then(resp => sharedFile1Id = resp.data.entry.id) + .then(() => apis.user.nodes.createFile(sharedFile2).then(resp => sharedFile2Id = resp.data.entry.id)) + .then(() => apis.user.nodes.createFile(sharedFile3).then(resp => sharedFile3Id = resp.data.entry.id)) + .then(() => apis.user.nodes.createFile(sharedFile4).then(resp => sharedFile4Id = resp.data.entry.id)) + .then(() => apis.user.shared.shareFilesByIds([sharedFile1Id, sharedFile2Id, sharedFile3Id, sharedFile4Id])) + .then(() => apis.user.shared.waitForApi({ expect: 4 })) + + .then(() => loginPage.loginWith(username)) + .then(done); + }); + + beforeEach(done => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.SHARED_FILES) + .then(() => dataTable.waitForHeader()) + .then(done); + }); + + afterEach(done => { + page.refresh().then(done); + }); + + afterAll(done => { + Promise.all([ + logoutPage.load(), + apis.user.nodes.deleteNodesById([sharedFile2Id, sharedFile3Id, sharedFile4Id]) + ]) + .then(done); + }); + + it('Successful delete notification shows Undo action', () => { + dataTable.clickOnItemName(sharedFile1) + .then(() => toolbar.actions.openMoreMenu()) + .then(() => toolbar.actions.menu.clickMenuItem('Delete')) + .then(() => page.getSnackBarMessage()) + .then(message => expect(message).toContain(`Undo`)); + }); + + it('Undo delete of file', () => { + dataTable.clickOnItemName(sharedFile2) + .then(() => toolbar.actions.openMoreMenu()) + .then(() => toolbar.actions.menu.clickMenuItem('Delete')) + .then(() => page.clickSnackBarAction()) + .then(() => page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.TRASH)) + .then(() => expect(dataTable.getRowName(sharedFile2).isPresent()).toBe(false, 'Item was not restored')); + }); + + it('undo delete of multiple files', () => { + dataTable.selectMultipleItems([sharedFile3, sharedFile4]) + .then(() => toolbar.actions.openMoreMenu()) + .then(() => toolbar.actions.menu.clickMenuItem('Delete')) + .then(() => page.clickSnackBarAction()) + .then(() => page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.TRASH)) + .then(() => { + expect(dataTable.getRowName(sharedFile3).isPresent()).toBe(false, `${sharedFile3} was not restored`); + expect(dataTable.getRowName(sharedFile4).isPresent()).toBe(false, `${sharedFile4} was not restored`); + }); + }); + }); + + describe('on Favorites', () => { + const favoriteFile1 = `favFile1-${Utils.random()}.txt`; let favoriteFile1Id; + const favoriteFile2 = `favFile2-${Utils.random()}.txt`; let favoriteFile2Id; + const favoriteFile4 = `favFile4-${Utils.random()}.txt`; + const favoriteFileLocked2 = `favFileLocked2-${Utils.random()}.txt`; let favoriteFileLocked2Id; + const favoriteFolder1 = `favFolder1-${Utils.random()}`; let favoriteFolder1Id; + const favoriteFolder2 = `favFolder2-${Utils.random()}`; let favoriteFolder2Id; + + beforeAll(done => { + apis.user.nodes.createFile(favoriteFile1).then(resp => favoriteFile1Id = resp.data.entry.id) + .then(() => apis.user.nodes.createFile(favoriteFile2).then(resp => favoriteFile2Id = resp.data.entry.id)) + .then(() => apis.user.nodes.createFolder(favoriteFolder1).then(resp => favoriteFolder1Id = resp.data.entry.id)) + .then(() => apis.user.nodes.createFile(favoriteFile4, favoriteFolder1Id)) + .then(() => apis.user.nodes.createFolder(favoriteFolder2).then(resp => favoriteFolder2Id = resp.data.entry.id)) + .then(() => apis.user.nodes.createFile(favoriteFileLocked2, favoriteFolder2Id) + .then(resp => favoriteFileLocked2Id = resp.data.entry.id)) + .then(() => apis.user.nodes.lockFile(favoriteFileLocked2Id)) + + .then(() => apis.user.favorites.addFavoritesByIds('file', [favoriteFile1Id, favoriteFile2Id])) + .then(() => apis.user.favorites.addFavoritesByIds('folder', [favoriteFolder1Id, favoriteFolder2Id])) + .then(() => apis.user.favorites.waitForApi({ expect: 4 })) + + .then(() => loginPage.loginWith(username)) + .then(done); + }); + + beforeEach(done => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.FAVORITES) + .then(() => dataTable.waitForHeader()) + .then(done); + }); + + afterEach(done => { + page.refresh().then(done); + }); + + afterAll(done => { + Promise.all([ + logoutPage.load(), + apis.user.nodes.unlockFile(favoriteFileLocked2Id) + .then(() => apis.user.nodes.deleteNodesById([favoriteFile1Id, favoriteFile2Id, favoriteFolder1Id, favoriteFolder2Id])) + ]) + .then(done); + }); + + it('Successful delete notification shows Undo action', () => { + dataTable.clickOnItemName(favoriteFile1) + .then(() => toolbar.actions.openMoreMenu()) + .then(() => toolbar.actions.menu.clickMenuItem('Delete')) + .then(() => page.getSnackBarMessage()) + .then(message => expect(message).toContain(`Undo`)) + + .then(() => apis.user.trashcan.restore(favoriteFile1Id)); + }); + + it('Unsuccessful delete notification does not show Undo action', () => { + dataTable.clickOnItemName(favoriteFolder2) + .then(() => toolbar.actions.openMoreMenu()) + .then(() => toolbar.actions.menu.clickMenuItem('Delete')) + .then(() => page.getSnackBarMessage()) + .then(message => expect(message).not.toContain(`Undo`)); + }); + + it('Undo delete of file', () => { + let items: number; + page.dataTable.countRows().then(number => { items = number; }); + + dataTable.clickOnItemName(favoriteFile1) + .then(() => toolbar.actions.openMoreMenu()) + .then(() => toolbar.actions.menu.clickMenuItem('Delete')) + .then(() => page.clickSnackBarAction()) + .then(() => { + expect(dataTable.getRowName(favoriteFile1).isPresent()).toBe(true, 'Item was not restored'); + expect(page.pagination.range.getText()).toContain(`1-${items} of ${items}`); + }); + }); + + it('Undo delete of folder with content', () => { + let items: number; + page.dataTable.countRows().then(number => { items = number; }); + + dataTable.clickOnItemName(favoriteFolder1) + .then(() => toolbar.actions.openMoreMenu()) + .then(() => toolbar.actions.menu.clickMenuItem('Delete')) + .then(() => page.clickSnackBarAction()) + .then(() => { + expect(dataTable.getRowName(favoriteFolder1).isPresent()).toBe(true, 'Item was not restored'); + expect(page.pagination.range.getText()).toContain(`1-${items} of ${items}`); + }) + .then(() => dataTable.doubleClickOnItemName(favoriteFolder1)) + .then(() => expect(dataTable.getRowName(favoriteFile4).isPresent()).toBe(true, 'file from folder not restored')); + }); + + it('undo delete of multiple files', () => { + let items: number; + page.dataTable.countRows().then(number => { items = number; }); + + dataTable.selectMultipleItems([favoriteFile1, favoriteFile2]) + .then(() => toolbar.actions.openMoreMenu()) + .then(() => toolbar.actions.menu.clickMenuItem('Delete')) + .then(() => page.clickSnackBarAction()) + .then(() => { + expect(dataTable.getRowName(favoriteFile1).isPresent()).toBe(true, `${favoriteFile1} was not removed from list`); + expect(dataTable.getRowName(favoriteFile2).isPresent()).toBe(true, `${favoriteFile2} was not removed from list`); + expect(page.pagination.range.getText()).toContain(`1-${items} of ${items}`); + }); + }); + }); + + describe('on Recent Files', () => { + const recentFile1 = `recentFile1-${Utils.random()}.txt`; let recentFile1Id; + const recentFile2 = `recentFile2-${Utils.random()}.txt`; let recentFile2Id; + const recentFile3 = `recentFile3-${Utils.random()}.txt`; let recentFile3Id; + const recentFile4 = `recentFile4-${Utils.random()}.txt`; let recentFile4Id; + + beforeAll(done => { + apis.user.nodes.createFile(recentFile1).then(resp => recentFile1Id = resp.data.entry.id) + .then(() => apis.user.nodes.createFile(recentFile2).then(resp => recentFile2Id = resp.data.entry.id)) + .then(() => apis.user.nodes.createFile(recentFile3).then(resp => recentFile3Id = resp.data.entry.id)) + .then(() => apis.user.nodes.createFile(recentFile4).then(resp => recentFile4Id = resp.data.entry.id)) + .then(() => apis.user.search.waitForApi(username, { expect: 4 })) + + .then(() => loginPage.loginWith(username)) + + .then((): any => page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.RECENT_FILES) + .then(() => dataTable.isEmptyList()) + .then(empty => { + if (empty) { + browser.sleep(6000).then(() => page.refresh()); + } + }) + ) + .then(done); + }); + + beforeEach(done => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.RECENT_FILES) + .then(() => dataTable.waitForHeader()) + .then(done); + }); + + afterEach(done => { + page.refresh().then(done); + }); + + afterAll(done => { + Promise.all([ + logoutPage.load(), + apis.user.nodes.deleteNodesById([recentFile2Id, recentFile3Id, recentFile4Id]) + ]) + .then(done); + }); + + xit('Successful delete notification shows Undo action', () => { + dataTable.clickOnItemName(recentFile1) + .then(() => toolbar.actions.openMoreMenu()) + .then(() => toolbar.actions.menu.clickMenuItem('Delete')) + .then(() => page.getSnackBarMessage()) + .then(message => expect(message).toContain(`Undo`)); + }); + + // due to the fact that the search api is slow to update, + // we cannot test that the restored file is displayed in the Recent Files list + // without adding a very big browser.sleep followed by a page.refresh + // so for the moment we're testing that the restored file is not displayed in the Trash + xit('Undo delete of file', () => { + dataTable.clickOnItemName(recentFile2) + .then(() => toolbar.actions.openMoreMenu()) + .then(() => toolbar.actions.menu.clickMenuItem('Delete')) + .then(() => page.clickSnackBarAction()) + .then(() => page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.TRASH)) + .then(() => expect(dataTable.getRowName(recentFile2).isPresent()).toBe(false, 'Item is in Trash')); + }); + + // due to the fact that the search api is slow to update, + // we cannot test that the restored file is displayed in the Recent Files list + // without adding a very big browser.sleep followed by a page.refresh + // so for the moment we're testing that the restored file is not displayed in the Trash + xit('undo delete of multiple files', () => { + dataTable.selectMultipleItems([recentFile3, recentFile4]) + .then(() => toolbar.actions.openMoreMenu()) + .then(() => toolbar.actions.menu.clickMenuItem('Delete')) + .then(() => page.clickSnackBarAction()) + .then(() => page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.TRASH)) + .then(() => { + expect(dataTable.getRowName(recentFile3).isPresent()).toBe(false, `${recentFile3} is in Trash`); + expect(dataTable.getRowName(recentFile4).isPresent()).toBe(false, `${recentFile4} is in Trash`); + }); + }); + }); +}); diff --git a/e2e/suites/actions/upload-file.test.ts b/e2e/suites/actions/upload-file.test.ts new file mode 100755 index 000000000..7789d4366 --- /dev/null +++ b/e2e/suites/actions/upload-file.test.ts @@ -0,0 +1,74 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +// import { browser, protractor, promise } from 'protractor'; +import { LoginPage, LogoutPage, BrowsingPage } from '../../pages/pages'; +import { SIDEBAR_LABELS } from '../../configs'; +import { RepoClient } from '../../utilities/repo-client/repo-client'; +import { Utils } from '../../utilities/utils'; + +describe('Upload files', () => { + const username = `user-${Utils.random()}`; + + const folder1 = `folder1-${Utils.random()}`; let folder1Id; + + const apis = { + admin: new RepoClient(), + user: new RepoClient(username, username) + }; + + const loginPage = new LoginPage(); + const logoutPage = new LogoutPage(); + const page = new BrowsingPage(); + const { dataTable } = page; + + beforeAll(done => { + apis.admin.people.createUser(username) + .then(() => apis.user.nodes.createFolder(folder1).then(resp => folder1Id = resp.data.entry.id)) + + .then(() => loginPage.loginWith(username)) + .then(done); + }); + + beforeEach(done => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.PERSONAL_FILES) + .then(() => dataTable.waitForHeader()) + .then(done); + }); + + afterAll(done => { + Promise.all([ + // apis.user.nodes.deleteNodeById(folder1Id), + logoutPage.load() + ]) + .then(done); + }); + + it('Upload a file', () => { + dataTable.doubleClickOnItemName(folder1) + .then(() => page.sidenav.openNewMenu()) + .then(() => page.sidenav.menu.uploadFile().sendKeys(`${__dirname}/create-folder.test.ts`)); + }); +}); diff --git a/e2e/suites/application/page-titles.test.ts b/e2e/suites/application/page-titles.test.ts new file mode 100755 index 000000000..07197ca50 --- /dev/null +++ b/e2e/suites/application/page-titles.test.ts @@ -0,0 +1,128 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { browser } from 'protractor'; + +import { SIDEBAR_LABELS } from '../../configs'; +import { LoginPage, LogoutPage, BrowsingPage } from '../../pages/pages'; + +describe('Page titles', () => { + const loginPage = new LoginPage(); + const logoutPage = new LogoutPage(); + const page = new BrowsingPage(); + + xit(''); + + describe('on Login / Logout pages', () => { + it('on Login page', () => { + loginPage.load() + .then(() => { + expect(browser.getTitle()).toContain('Sign in'); + }); + }); + + it('after logout', () => { + loginPage.loginWithAdmin() + .then(() => page.signOut()) + .then(() => { + expect(browser.getTitle()).toContain('Sign in'); + }); + }); + + it('when pressing Back after Logout', () => { + loginPage.loginWithAdmin() + .then(() => page.signOut()) + .then(() => browser.navigate().back()) + .then(() => { + expect(browser.getTitle()).toContain('Sign in'); + }); + }); + }); + + describe('on list views', () => { + beforeAll(done => { + loginPage.loginWithAdmin().then(done); + }); + + afterAll(done => { + logoutPage.load() + .then(done); + }); + + it('Personal Files page', () => { + const label = SIDEBAR_LABELS.PERSONAL_FILES; + + page.sidenav.navigateToLinkByLabel(label) + .then(() => { + expect(browser.getTitle()).toContain(label); + }); + }); + + it('File Libraries page', () => { + const label = SIDEBAR_LABELS.FILE_LIBRARIES; + + page.sidenav.navigateToLinkByLabel(label) + .then(() => { + expect(browser.getTitle()).toContain(label); + }); + }); + + it('Shared Files page', () => { + const label = SIDEBAR_LABELS.SHARED_FILES; + + page.sidenav.navigateToLinkByLabel(label) + .then(() => { + expect(browser.getTitle()).toContain(label); + }); + }); + + it('Recent Files page', () => { + const label = SIDEBAR_LABELS.RECENT_FILES; + + page.sidenav.navigateToLinkByLabel(label) + .then(() => { + expect(browser.getTitle()).toContain(label); + }); + }); + + it('Favorites page', () => { + const label = SIDEBAR_LABELS.FAVORITES; + + page.sidenav.navigateToLinkByLabel(label) + .then(() => { + expect(browser.getTitle()).toContain(label); + }); + }); + + it('Trash page', () => { + const label = SIDEBAR_LABELS.TRASH; + + page.sidenav.navigateToLinkByLabel(label) + .then(() => { + expect(browser.getTitle()).toContain(label); + }); + }); + }); +}); diff --git a/e2e/suites/authentication/login.test.ts b/e2e/suites/authentication/login.test.ts new file mode 100755 index 000000000..e17028248 --- /dev/null +++ b/e2e/suites/authentication/login.test.ts @@ -0,0 +1,239 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { browser } from 'protractor'; + +import { APP_ROUTES } from '../../configs'; +import { LoginPage, LogoutPage, BrowsingPage } from '../../pages/pages'; +import { Utils } from '../../utilities/utils'; +import { RepoClient } from '../../utilities/repo-client/repo-client'; + +describe('Login', () => { + const peopleApi = new RepoClient().people; + const loginPage = new LoginPage(); + const logoutPage = new LogoutPage(); + + /* cspell:disable-next-line */ + const testUser = `user-${Utils.random()}@alfness`; + + const russianUser = { + /* cspell:disable-next-line */ + username: `пользвате${Utils.random()}`, + password: '密碼中國' + }; + + const johnDoe = { + username: `user-${Utils.random()}`, + get password() { return this.username; }, + firstName: 'John', + lastName: 'Doe' + }; + + const disabledUser = `user-${Utils.random()}`; + const testUser2 = { + username: `user-${Utils.random()}`, + password: 'user2 password' + }; + const newPassword = 'new password'; + + beforeAll(done => { + Promise + .all([ + peopleApi.createUser(testUser), + peopleApi.createUser(russianUser.username, russianUser.password), + peopleApi.createUser(johnDoe.username, johnDoe.password, { + firstName: johnDoe.firstName, + lastName: johnDoe.lastName + }), + peopleApi.createUser(disabledUser).then(() => peopleApi.disableUser(disabledUser)), + peopleApi.createUser(testUser2.username, testUser2.password) + ]) + .then(done); + }); + + afterEach(done => { + logoutPage.load() + .then(() => Utils.clearLocalStorage()) + .then(done); + }); + + xit(''); + + describe('general tests', () => { + beforeEach(done => { + loginPage.load().then(done); + }); + + it('login page default values', () => { + expect(loginPage.login.usernameInput.isEnabled()).toBe(true, 'username input is not enabled'); + expect(loginPage.login.passwordInput.isEnabled()).toBe(true, 'password input is not enabled'); + expect(loginPage.login.submitButton.isEnabled()).toBe(false, 'SIGN IN button is enabled'); + expect(loginPage.login.getPasswordVisibility()).toBe(false, 'Password is not hidden by default'); + }); + + it('change password visibility', () => { + loginPage.login.enterPassword('some password'); + expect(loginPage.login.isPasswordShown()).toBe(false, 'password is visible'); + loginPage.login.passwordVisibility.click() + .then(() => { + expect(loginPage.login.getPasswordVisibility()).toBe(true, 'Password visibility not changed'); + expect(loginPage.login.isPasswordShown()).toBe(true, 'password is not visible'); + }); + }); + }); + + describe('with valid credentials', () => { + it('navigate to "Personal Files"', () => { + const { username } = johnDoe; + + loginPage.loginWith(username) + .then(() => { + expect(browser.getCurrentUrl()).toContain(APP_ROUTES.PERSONAL_FILES); + }); + }); + + it(`displays user's name in header`, () => { + const { userInfo } = new BrowsingPage(APP_ROUTES.PERSONAL_FILES).header; + const { username, firstName, lastName } = johnDoe; + + loginPage.loginWith(username) + .then(() => { + expect(userInfo.name).toEqual(`${firstName} ${lastName}`); + }); + }); + + it(`logs in with user having username containing "@"`, () => { + loginPage + .loginWith(testUser) + .then(() => { + expect(browser.getCurrentUrl()).toContain(APP_ROUTES.PERSONAL_FILES); + }); + }); + + it('logs in with user with non-latin characters', () => { + const { username, password } = russianUser; + + loginPage + .loginWith(username, password) + .then(() => { + expect(browser.getCurrentUrl()).toContain(APP_ROUTES.PERSONAL_FILES); + }); + }); + + it('redirects to Home Page when navigating to the Login page while already logged in', () => { + const { username } = johnDoe; + + loginPage + .loginWith(username) + .then(() => browser.get(APP_ROUTES.LOGIN) + .then(() => { + expect(browser.getCurrentUrl()).toContain(APP_ROUTES.PERSONAL_FILES); + }) + ); + }); + + it('redirects to Personal Files when pressing browser Back while already logged in ', () => { + const { username } = johnDoe; + + loginPage + .loginWith(username) + .then(() => browser.navigate().back()) + .then(() => { + expect(browser.getCurrentUrl()).toContain(APP_ROUTES.PERSONAL_FILES); + }); + }); + + it('user is able to login after changing his password', () => { + loginPage.loginWith(testUser2.username, testUser2.password) + .then(() => logoutPage.load()) + .then(() => peopleApi.changePassword(testUser2.username, newPassword)) + .then(() => loginPage.loginWith(testUser2.username, newPassword)) + .then(() => { + expect(browser.getCurrentUrl()).toContain(APP_ROUTES.PERSONAL_FILES); + }); + }); + }); + + describe('with invalid credentials', () => { + const { login: loginComponent } = loginPage; + const { submitButton, errorMessage } = loginComponent; + + beforeEach(done => { + loginPage.load().then(done); + }); + + it('disabled submit button when no credentials are entered', () => { + expect(submitButton.isEnabled()).toBe(false); + }); + + it('disabled submit button when password is empty', () => { + loginComponent.enterUsername('any-username'); + expect(submitButton.isEnabled()).toBe(false); + }); + + it('disabled submit button when username is empty', () => { + loginPage.login.enterPassword('any-password'); + expect(submitButton.isEnabled()).toBe(false); + }); + + it('shows error when entering nonexistent user', () => { + loginPage + .tryLoginWith('nonexistent-user', 'any-password') + .then(() => { + expect(browser.getCurrentUrl()).toContain(APP_ROUTES.LOGIN); + expect(errorMessage.isDisplayed()).toBe(true); + expect(errorMessage.getText()).toBe(`You've entered an unknown username or password`); + }); + }); + + it('shows error when entering invalid password', () => { + const { username } = johnDoe; + + loginPage + .tryLoginWith(username, 'incorrect-password') + .then(() => { + expect(browser.getCurrentUrl()).toContain(APP_ROUTES.LOGIN); + expect(errorMessage.isDisplayed()).toBe(true); + expect(errorMessage.getText()).toBe(`You've entered an unknown username or password`); + }); + }); + + it('unauthenticated user is redirected to Login page', () => { + browser.get(APP_ROUTES.PERSONAL_FILES) + .then(() => { + expect(browser.getCurrentUrl()).toContain(APP_ROUTES.LOGIN); + }); + }); + + it('disabled user is not logged in', () => { + loginPage.tryLoginWith(disabledUser) + .then(() => { + expect(browser.getCurrentUrl()).toContain(APP_ROUTES.LOGIN); + expect(errorMessage.isDisplayed()).toBe(true); + expect(errorMessage.getText()).toBe(`You've entered an unknown username or password`); + }); + }); + }); +}); diff --git a/e2e/suites/authentication/logout.test.ts b/e2e/suites/authentication/logout.test.ts new file mode 100755 index 000000000..dfb79669a --- /dev/null +++ b/e2e/suites/authentication/logout.test.ts @@ -0,0 +1,82 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { browser } from 'protractor'; +import { LoginPage, LogoutPage, BrowsingPage } from '../../pages/pages'; +import { Utils } from '../../utilities/utils'; +import { RepoClient } from '../../utilities/repo-client/repo-client'; +import { APP_ROUTES } from '../../configs'; + +describe('Logout', () => { + const page = new BrowsingPage(); + const loginPage = new LoginPage(); + const logoutPage = new LogoutPage(); + + const peopleApi = new RepoClient().people; + + const johnDoe = `user-${Utils.random()}`; + + beforeAll((done) => { + peopleApi + .createUser(johnDoe) + .then(done); + }); + + beforeEach((done) => { + loginPage.loginWith(johnDoe).then(done); + }); + + afterEach((done) => { + logoutPage.load().then(done); + }); + + it('Sign out option is available [C213143]', () => { + page.header.userInfo.openMenu() + .then(() => expect(page.header.userInfo.menu.isMenuItemPresent('Sign out')).toBe(true, 'Sign out option not displayed')); + }); + + it('redirects to Login page on sign out [C213144]', () => { + page.signOut() + .then(() => { + expect(browser.getCurrentUrl()).toContain(APP_ROUTES.LOGIN); + }); + }); + + it('redirects to Login page when pressing browser Back after logout [C213145]', () => { + page.signOut() + .then(() => browser.navigate().back()) + .then(() => { + expect(browser.getCurrentUrl()).toContain(APP_ROUTES.LOGIN); + }); + }); + + it('redirects to Login page when trying to access a part of the app after logout [C213146]', () => { + page.signOut() + .then(() => page.load('/favorites')) + .then(() => { + expect(browser.getCurrentUrl()).toContain(APP_ROUTES.LOGIN); + }); + }); +}); diff --git a/e2e/suites/list-views/empty-list.test.ts b/e2e/suites/list-views/empty-list.test.ts new file mode 100755 index 000000000..dd9ea40cd --- /dev/null +++ b/e2e/suites/list-views/empty-list.test.ts @@ -0,0 +1,108 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { SIDEBAR_LABELS } from '../../configs'; +import { LoginPage, LogoutPage, BrowsingPage } from '../../pages/pages'; +import { Utils } from '../../utilities/utils'; +import { RepoClient } from '../../utilities/repo-client/repo-client'; + +describe('Empty list views', () => { + const username = `user-${Utils.random()}`; + const password = username; + + const apis = { + admin: new RepoClient(), + user: new RepoClient(username, password) + }; + + const loginPage = new LoginPage(); + const logoutPage = new LogoutPage(); + const page = new BrowsingPage(); + const { dataTable } = page; + + beforeAll(done => { + apis.admin.people.createUser(username) + .then(() => loginPage.loginWith(username)) + .then(done); + }); + + afterAll(done => { + logoutPage.load().then(done); + }); + + it('empty Personal Files', () => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.PERSONAL_FILES) + .then(() => { + expect(dataTable.isEmptyList()).toBe(true, 'list is not empty'); + expect(dataTable.getEmptyDragAndDropText()).toContain('Drag and drop'); + }); + }); + + it('empty File Libraries [C217099]', () => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.FILE_LIBRARIES) + .then(() => { + expect(dataTable.isEmptyList()).toBe(true, 'list is not empty'); + expect(dataTable.getEmptyStateTitle()).toContain(`You aren't a member of any File Libraries yet`); + expect(dataTable.getEmptyStateSubtitle()).toContain('Join sites to upload, view, and share files.'); + }); + }); + + it('empty Shared Files', () => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.SHARED_FILES) + .then(() => { + expect(dataTable.isEmptyList()).toBe(true, 'list is not empty'); + expect(dataTable.getEmptyStateTitle()).toContain('No shared files or folders'); + expect(dataTable.getEmptyStateSubtitle()).toContain('Items you share using the Share option are shown here.'); + }); + }); + + it('empty Recent Files [C213169]', () => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.RECENT_FILES) + .then(() => { + expect(dataTable.isEmptyList()).toBe(true, 'list is not empty'); + expect(dataTable.getEmptyStateTitle()).toContain('No recent files'); + expect(dataTable.getEmptyStateSubtitle()).toContain('Items you upload or edit in the last 30 days are shown here.'); + }); + }); + + it('empty Favorites', () => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.FAVORITES) + .then(() => { + expect(dataTable.isEmptyList()).toBe(true, 'list is not empty'); + expect(dataTable.getEmptyStateTitle()).toContain('No favorite files or folders'); + expect(dataTable.getEmptyStateSubtitle()).toContain('Favorite items that you want to easily find later.'); + }); + }); + + it('empty Trash', () => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.TRASH) + .then(() => { + expect(dataTable.isEmptyList()).toBe(true, 'list is not empty'); + expect(dataTable.getEmptyStateTitle()).toContain('Trash is empty'); + expect(dataTable.getEmptyStateText()).toContain('Items you delete are moved to the Trash.'); + expect(dataTable.getEmptyStateText()).toContain('Empty Trash to permanently delete items.'); + }); + }); +}); diff --git a/e2e/suites/list-views/favorites.test.ts b/e2e/suites/list-views/favorites.test.ts new file mode 100755 index 000000000..3cd8ae546 --- /dev/null +++ b/e2e/suites/list-views/favorites.test.ts @@ -0,0 +1,156 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { SITE_VISIBILITY, SITE_ROLES, SIDEBAR_LABELS } from '../../configs'; +import { LoginPage, LogoutPage, BrowsingPage } from '../../pages/pages'; +import { Utils } from '../../utilities/utils'; +import { RepoClient } from '../../utilities/repo-client/repo-client'; + +describe('Favorites', () => { + const username = `user-${Utils.random()}`; + const password = username; + + const siteName = `site-${Utils.random()}`; + const folderName = `folder-${Utils.random()}`; + const fileName1 = `file1-${Utils.random()}.txt`; + const fileName2 = `file2-${Utils.random()}.txt`; + const fileName3 = `file3-${Utils.random()}.txt`; let file3Id; + const fileName4 = `file4-${Utils.random()}.txt`; let file4Id; + + const apis = { + admin: new RepoClient(), + user: new RepoClient(username, password) + }; + + const loginPage = new LoginPage(); + const logoutPage = new LogoutPage(); + const favoritesPage = new BrowsingPage(); + const { dataTable } = favoritesPage; + const { breadcrumb } = favoritesPage.toolbar; + + beforeAll(done => { + apis.admin.people.createUser(username) + + .then(() => apis.admin.sites.createSite(siteName, SITE_VISIBILITY.PUBLIC)) + .then(() => apis.admin.sites.addSiteMember(siteName, username, SITE_ROLES.SITE_MANAGER)) + .then(() => apis.admin.nodes.createFiles([ fileName1 ], `Sites/${siteName}/documentLibrary`) + .then(resp => apis.user.favorites.addFavoriteById('file', resp.data.entry.id))) + .then(() => apis.user.nodes.createFolders([ folderName ]) + .then(resp => apis.user.favorites.addFavoriteById('folder', resp.data.entry.id))) + .then(() => apis.user.nodes.createFiles([ fileName2 ], folderName) + .then(resp => apis.user.favorites.addFavoriteById('file', resp.data.entry.id))) + .then(() => apis.user.nodes.createFiles([ fileName3 ], folderName) + .then(resp => file3Id = resp.data.entry.id) + .then(() => apis.user.favorites.addFavoriteById('file', file3Id)) + .then(() => apis.user.nodes.deleteNodeById(file3Id, false))) + .then(() => apis.user.nodes.createFiles([ fileName4 ], folderName) + .then(resp => file4Id = resp.data.entry.id) + .then(() => apis.user.favorites.addFavoriteById('file', file4Id)) + .then(() => apis.user.nodes.deleteNodeById(file4Id, false)) + .then(() => apis.user.trashcan.restore(file4Id))) + + .then(() => loginPage.loginWith(username)) + .then(done); + }); + + beforeEach(done => { + favoritesPage.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.FAVORITES) + .then(() => dataTable.waitForHeader()) + .then(done); + }); + + afterAll(done => { + Promise.all([ + apis.admin.sites.deleteSite(siteName), + apis.user.nodes.deleteNodes([ folderName ]), + apis.admin.trashcan.emptyTrash(), + logoutPage.load() + ]) + .then(done); + }); + + it('has the correct columns', () => { + const labels = [ 'Name', 'Location', 'Size', 'Modified', 'Modified by' ]; + const elements = labels.map(label => dataTable.getColumnHeaderByLabel(label)); + + expect(dataTable.getColumnHeaders().count()).toBe(5 + 1, 'Incorrect number of columns'); + + elements.forEach((element, index) => { + expect(element.isPresent()).toBe(true, `"${labels[index]}" is missing`); + }); + }); + + it('displays the favorite files and folders [C213226]', () => { + expect(dataTable.countRows()).toEqual(4, 'Incorrect number of items displayed'); + expect(dataTable.getRowName(fileName1).isPresent()).toBe(true, `${fileName1} not displayed`); + expect(dataTable.getRowName(fileName2).isPresent()).toBe(true, `${fileName2} not displayed`); + expect(dataTable.getRowName(folderName).isPresent()).toBe(true, `${folderName} not displayed`); + }); + + it(`file not displayed if it's in the Trashcan [C213228]`, () => { + expect(dataTable.getRowName(fileName3).isPresent()).not.toBe(true, `${fileName3} is displayed`); + }); + + it(`file is displayed after it is restored from Trashcan [C213229]`, () => { + expect(dataTable.getRowName(fileName4).isPresent()).toBe(true, `${fileName4} not displayed`); + }); + + it('Location column displays the parent folder of the files [C213231]', () => { + expect(dataTable.getItemLocation(fileName1).getText()).toEqual(siteName); + expect(dataTable.getItemLocation(fileName2).getText()).toEqual(folderName); + expect(dataTable.getItemLocation(folderName).getText()).toEqual('Personal Files'); + }); + + it('Location column displays a tooltip with the entire path of the file [C213671]', () => { + expect(dataTable.getItemLocationTooltip(fileName1)).toEqual(`File Libraries/${siteName}`); + expect(dataTable.getItemLocationTooltip(fileName2)).toEqual(`Personal Files/${folderName}`); + expect(dataTable.getItemLocationTooltip(folderName)).toEqual('Personal Files'); + }); + + it('Location column redirect - item in user Home [C213650] [C260968]', () => { + dataTable.clickItemLocation(folderName) + .then(() => expect(breadcrumb.getAllItems()).toEqual([ 'Personal Files' ])); + }); + + it('Location column redirect - file in folder [C213650] [C260968]', () => { + dataTable.clickItemLocation(fileName2) + .then(() => expect(breadcrumb.getAllItems()).toEqual([ 'Personal Files', folderName ])); + }); + + it('Location column redirect - file in site [C213650] [C260969]', () => { + dataTable.clickItemLocation(fileName1) + .then(() => expect(breadcrumb.getAllItems()).toEqual([ 'File Libraries', siteName ])); + }); + + it('Navigate into folder from Favorites [C213230]', () => { + dataTable.doubleClickOnItemName(folderName) + .then(() => dataTable.waitForHeader()) + .then(() => breadcrumb.getCurrentItemName()) + .then(name => { + expect(name).toBe(folderName); + }); + }); + +}); diff --git a/e2e/suites/list-views/file-libraries.test.ts b/e2e/suites/list-views/file-libraries.test.ts new file mode 100755 index 000000000..3f6567ddd --- /dev/null +++ b/e2e/suites/list-views/file-libraries.test.ts @@ -0,0 +1,161 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { by } from 'protractor'; + +import { SITE_VISIBILITY, SITE_ROLES, SIDEBAR_LABELS } from '../../configs'; +import { LoginPage, LogoutPage, BrowsingPage } from '../../pages/pages'; +import { Utils } from '../../utilities/utils'; +import { RepoClient } from '../../utilities/repo-client/repo-client'; + +describe('File Libraries', () => { + const username = `user-${Utils.random()}`; + const password = username; + + const sitePrivate = `private-${Utils.random()}`; + const siteModerated = `moderated-${Utils.random()}`; + const sitePublic = `public-${Utils.random()}`; + const siteName = `siteName-${Utils.random()}`; + const siteId1 = Utils.random(); + const siteId2 = Utils.random(); + const adminSite = `admin-${Utils.random()}`; + + const siteDescription = 'my site description'; + + const apis = { + admin: new RepoClient(), + user: new RepoClient(username, password) + }; + + const loginPage = new LoginPage(); + const logoutPage = new LogoutPage(); + const fileLibrariesPage = new BrowsingPage(); + const { dataTable } = fileLibrariesPage; + + beforeAll(done => { + Promise + .all([ + apis.admin.people.createUser(username), + apis.admin.sites.createSite(sitePublic, SITE_VISIBILITY.PUBLIC), + apis.admin.sites.createSite(siteModerated, SITE_VISIBILITY.MODERATED, { description: siteDescription }), + apis.admin.sites.createSite(sitePrivate, SITE_VISIBILITY.PRIVATE, { description: '' }), + apis.admin.sites.createSite(adminSite, SITE_VISIBILITY.PUBLIC), + apis.admin.sites.createSite(siteName, SITE_VISIBILITY.PUBLIC, { id: siteId1 }), + apis.admin.sites.createSite(siteName, SITE_VISIBILITY.PUBLIC, { id: siteId2 }) + ]) + .then(() => apis.admin.sites.addSiteMember(sitePublic, username, SITE_ROLES.SITE_CONSUMER)) + .then(() => apis.admin.sites.addSiteMember(siteModerated, username, SITE_ROLES.SITE_MANAGER)) + .then(() => apis.admin.sites.addSiteMember(sitePrivate, username, SITE_ROLES.SITE_CONTRIBUTOR)) + .then(() => apis.admin.sites.addSiteMember(siteId1, username, SITE_ROLES.SITE_CONTRIBUTOR)) + .then(() => apis.admin.sites.addSiteMember(siteId2, username, SITE_ROLES.SITE_CONTRIBUTOR)) + + .then(() => loginPage.loginWith(username)) + .then(done); + }); + + beforeEach(done => { + fileLibrariesPage.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.FILE_LIBRARIES) + .then(() => dataTable.waitForHeader()) + .then(done); + }); + + afterAll(done => { + Promise.all([ + apis.admin.sites.deleteSites([ + sitePublic, + siteModerated, + sitePrivate, + adminSite, + siteId1, + siteId2 + ]), + logoutPage.load() + ]) + .then(done); + }); + + it('has the correct columns', () => { + const labels = [ 'Title', 'Status' ]; + const elements = labels.map(label => dataTable.getColumnHeaderByLabel(label)); + + expect(dataTable.getColumnHeaders().count()).toBe(2 + 1, 'Incorrect number of columns'); + + elements.forEach((element, index) => { + expect(element.isPresent()).toBe(true, `"${labels[index]}" is missing`); + }); + }); + + it('User can see only the sites he is a member of [C217095]', () => { + const sitesCount = dataTable.countRows(); + + const expectedSites = { + [sitePrivate]: SITE_VISIBILITY.PRIVATE, + [siteModerated]: SITE_VISIBILITY.MODERATED, + [sitePublic]: SITE_VISIBILITY.PUBLIC + }; + + expect(sitesCount).toEqual(5, 'Incorrect number of sites displayed'); + expect(dataTable.getRowName(adminSite).isPresent()).toBe(false, 'Incorrect site appears in list'); + + dataTable.getRows() + .map((row) => { + return row.all(dataTable.cell).map(cell => cell.getText()); + }) + .then((rowCells) => { + return rowCells.reduce((acc, cell) => { + acc[cell[1]] = cell[2].toUpperCase(); + return acc; + }, {}); + }) + .then((sitesList) => { + Object.keys(expectedSites).forEach((site) => { + expect(sitesList[site]).toEqual(expectedSites[site]); + }); + }); + }); + + it('Site ID is displayed when two sites have the same name [C217098]', () => { + const expectedSites = [ + `${siteName} (${siteId1})`, + `${siteName} (${siteId2})` + ]; + dataTable.getCellsContainingName(siteName) + .then(resp => { + const expectedJSON = JSON.stringify(expectedSites.sort()); + const actualJSON = JSON.stringify(resp.sort()); + expect(actualJSON).toEqual(expectedJSON); + }); + }); + + it('Tooltip for sites without description [C217096]', () => { + const tooltip = dataTable.getItemNameTooltip(sitePrivate); + expect(tooltip).toBe(`${sitePrivate}`); + }); + + it('Tooltip for sites with description [C217097]', () => { + const tooltip = dataTable.getItemNameTooltip(siteModerated); + expect(tooltip).toBe(`${siteDescription}`); + }); +}); diff --git a/e2e/suites/list-views/permissions.test.ts b/e2e/suites/list-views/permissions.test.ts new file mode 100755 index 000000000..9b637babb --- /dev/null +++ b/e2e/suites/list-views/permissions.test.ts @@ -0,0 +1,181 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { SITE_VISIBILITY, SITE_ROLES, SIDEBAR_LABELS } from '../../configs'; +import { LoginPage, LogoutPage, BrowsingPage } from '../../pages/pages'; +import { Utils } from '../../utilities/utils'; +import { RepoClient } from '../../utilities/repo-client/repo-client'; + +describe('Special permissions', () => { + const username = `user-${Utils.random()}`; + const password = username; + + const apis = { + admin: new RepoClient(), + user: new RepoClient(username, password) + }; + + const loginPage = new LoginPage(); + const logoutPage = new LogoutPage(); + const recentFilesPage = new BrowsingPage(); + const favoritesPage = new BrowsingPage(); + const sharedPage = new BrowsingPage(); + const { dataTable } = recentFilesPage; + + xit(''); + + beforeAll(done => { + apis.admin.people.createUser(username).then(done); + }); + + describe('file not displayed if user no longer has permissions on it', () => { + const sitePrivate = `private-${Utils.random()}`; + const fileName = `file-${Utils.random()}.txt`; + let fileId; + + beforeAll(done => { + apis.admin.sites.createSite(sitePrivate, SITE_VISIBILITY.PRIVATE) + .then(() => apis.admin.sites.addSiteMember(sitePrivate, username, SITE_ROLES.SITE_COLLABORATOR)) + .then(() => apis.admin.nodes.createFiles([ fileName ], `Sites/${sitePrivate}/documentLibrary`) + .then(resp => fileId = resp.data.entry.id)) + .then(() => apis.user.favorites.addFavoriteById('file', fileId)) + .then(() => apis.admin.shared.shareFileById(fileId)) + .then(() => apis.user.nodes.editNodeContent(fileId, 'edited by user')) + + .then(() => apis.user.search.waitForApi(username, { expect: 1 })) + .then(() => apis.user.shared.waitForApi({ expect: 1 })) + + .then(() => loginPage.loginWith(username)) + .then(done); + }); + + afterEach(done => { + apis.admin.sites.addSiteMember(sitePrivate, username, SITE_ROLES.SITE_COLLABORATOR).then(done); + }); + + afterAll(done => { + Promise.all([ + apis.admin.sites.deleteSite(sitePrivate), + logoutPage.load() + ]) + .then(done); + }); + + it('on Recent Files [C213173]', () => { + recentFilesPage.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.RECENT_FILES) + .then(() => dataTable.waitForHeader()) + .then(() => { + expect(dataTable.countRows()).toBe(1, 'Incorrect number of items'); + }) + .then(() => apis.admin.sites.deleteSiteMember(sitePrivate, username)) + .then(() => recentFilesPage.refresh()) + .then(() => { + expect(dataTable.countRows()).toBe(0, 'Incorrect number of items'); + }); + }); + + it('on Favorites [C213227]', () => { + favoritesPage.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.FAVORITES) + .then(() => dataTable.waitForHeader()) + .then(() => { + expect(dataTable.countRows()).toBe(1, 'Incorrect number of items'); + }) + .then(() => apis.admin.sites.deleteSiteMember(sitePrivate, username)) + .then(() => favoritesPage.refresh()) + .then(() => { + expect(dataTable.countRows()).toBe(0, 'Incorrect number of items'); + }); + }); + + it('on Shared Files [C213116]', () => { + sharedPage.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.SHARED_FILES) + .then(() => dataTable.waitForHeader()) + .then(() => { + expect(dataTable.countRows()).toBe(1, 'Incorrect number of items'); + }) + .then(() => apis.admin.sites.deleteSiteMember(sitePrivate, username)) + .then(() => sharedPage.refresh()) + .then(() => { + expect(dataTable.countRows()).toBe(0, 'Incorrect number of items'); + }); + }); + }); + + describe(`Location column is empty if user doesn't have permissions on the file's parent folder`, () => { + const sitePrivate = `private-${Utils.random()}`; + const fileName = `file-${Utils.random()}.txt`; + let fileId; + + beforeAll(done => { + apis.admin.sites.createSite(sitePrivate, SITE_VISIBILITY.PRIVATE) + .then(() => apis.admin.sites.addSiteMember(sitePrivate, username, SITE_ROLES.SITE_COLLABORATOR)) + .then(() => apis.admin.sites.getDocLibId(sitePrivate)) + .then(resp => apis.user.nodes.createFile(fileName, resp)) + .then(resp => fileId = resp.data.entry.id) + .then(() => apis.user.favorites.addFavoriteById('file', fileId)) + .then(() => apis.user.shared.shareFileById(fileId)) + .then(() => apis.user.shared.waitForApi({ expect: 1 })) + .then(() => apis.user.search.waitForApi(username, { expect: 1 })) + .then(() => apis.admin.sites.deleteSiteMember(sitePrivate, username)) + .then(() => loginPage.loginWith(username)) + .then(done); + }); + + afterAll(done => { + Promise.all([ + apis.admin.sites.deleteSite(sitePrivate), + logoutPage.load() + ]) + .then(done); + }); + + it(`on Recent Files [C213178]`, () => { + recentFilesPage.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.RECENT_FILES) + .then(() => dataTable.waitForHeader()) + .then(() => { + expect(dataTable.countRows()).toBe(1, 'Incorrect number of items'); + expect(dataTable.getItemLocation(fileName).getText()).toEqual(''); + }); + }); + + it(`on Favorites [C213672]`, () => { + favoritesPage.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.FAVORITES) + .then(() => dataTable.waitForHeader()) + .then(() => { + expect(dataTable.countRows()).toBe(1, 'Incorrect number of items'); + expect(dataTable.getItemLocation(fileName).getText()).toEqual(''); + }); + }); + + it(`on Shared Files [C213668]`, () => { + sharedPage.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.SHARED_FILES) + .then(() => dataTable.waitForHeader()) + .then(() => { + expect(dataTable.countRows()).toBe(1, 'Incorrect number of items'); + expect(dataTable.getItemLocation(fileName).getText()).toEqual(''); + }); + }); + }); +}); diff --git a/e2e/suites/list-views/personal-files.test.ts b/e2e/suites/list-views/personal-files.test.ts new file mode 100755 index 000000000..88bdd95de --- /dev/null +++ b/e2e/suites/list-views/personal-files.test.ts @@ -0,0 +1,176 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { browser } from 'protractor'; + +import { SIDEBAR_LABELS, APP_ROUTES } from '../../configs'; +import { LoginPage, LogoutPage, BrowsingPage } from '../../pages/pages'; +import { Utils } from '../../utilities/utils'; +import { RepoClient } from '../../utilities/repo-client/repo-client'; + +describe('Personal Files', () => { + const username = `user-${Utils.random()}`; + + const apis = { + admin: new RepoClient(), + user: new RepoClient(username, username) + }; + + const loginPage = new LoginPage(); + const logoutPage = new LogoutPage(); + const personalFilesPage = new BrowsingPage(); + const { dataTable } = personalFilesPage; + + const adminFolder = `admin-folder-${Utils.random()}`; + + const userFolder = `user-folder-${Utils.random()}`; + const userFile = `file-${Utils.random()}.txt`; + + beforeAll(done => { + Promise + .all([ + apis.admin.people.createUser(username), + apis.admin.nodes.createFolders([ adminFolder ]) + ]) + .then(() => apis.user.nodes.createFolders([ userFolder ])) + .then(() => apis.user.nodes.createFiles([ userFile ], userFolder)) + .then(done); + }); + + afterAll(done => { + Promise + .all([ + apis.admin.nodes.deleteNodes([ adminFolder ]), + apis.user.nodes.deleteNodes([ userFolder ]) + ]) + .then(done); + }); + + xit(''); + + describe(`Admin user's personal files`, () => { + beforeAll(done => { + loginPage.loginWithAdmin().then(done); + }); + + beforeEach(done => { + personalFilesPage.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.PERSONAL_FILES) + .then(() => dataTable.waitForHeader()) + .then(done); + }); + + afterAll(done => { + logoutPage.load().then(done); + }); + + it('has "Data Dictionary" folder [C213241]', () => { + expect(dataTable.getRowName('Data Dictionary').isPresent()).toBe(true); + }); + + it('has created content', () => { + expect(dataTable.getRowName(adminFolder).isPresent()).toBe(true); + }); + }); + + describe(`Regular user's personal files`, () => { + beforeAll(done => { + loginPage.loginWith(username).then(done); + }); + + beforeEach(done => { + personalFilesPage.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.PERSONAL_FILES) + .then(() => dataTable.waitForHeader()) + .then(done); + }); + + afterAll(done => { + logoutPage.load().then(done); + }); + + it('has the correct columns [C217142]', () => { + const labels = [ 'Name', 'Size', 'Modified', 'Modified by' ]; + const elements = labels.map(label => dataTable.getColumnHeaderByLabel(label)); + + expect(dataTable.getColumnHeaders().count()).toBe(4 + 1, 'Incorrect number of columns'); + + elements.forEach((element, index) => { + expect(element.isPresent()).toBe(true, `"${labels[index]}" is missing`); + }); + }); + + it('has default sorted column [C217143]', () => { + expect(dataTable.getSortedColumnHeader().getText()).toBe('Modified'); + }); + + it('has user created content [C213242]', () => { + expect(dataTable.getRowName(userFolder).isPresent()) + .toBe(true); + }); + + it('navigates to folder [C213244]', () => { + const getNodeIdPromise = apis.user.nodes + .getNodeByPath(`/${userFolder}`) + .then(response => response.data.entry.id); + + const navigatePromise = dataTable + .doubleClickOnItemName(userFolder) + .then(() => dataTable.waitForHeader()); + + Promise + .all([ + getNodeIdPromise, + navigatePromise + ]) + .then(([ nodeId ]) => { + expect(browser.getCurrentUrl()) + .toContain(nodeId, 'Node ID is not in the URL'); + + expect(dataTable.getRowName(userFile).isPresent()) + .toBe(true, 'user file is missing'); + }); + }); + + it('redirects to Personal Files on clicking the link from sidebar [C213245]', () => { + personalFilesPage.dataTable.doubleClickOnItemName(userFolder) + .then(() => personalFilesPage.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.PERSONAL_FILES)) + .then(() => browser.getCurrentUrl()) + .then(url => expect(url.endsWith(APP_ROUTES.PERSONAL_FILES)).toBe(true, 'incorrect url')); + }); + + it('page loads correctly after browser refresh [C213246]', () => { + personalFilesPage.refresh() + .then(() => expect(browser.getCurrentUrl()).toContain(APP_ROUTES.PERSONAL_FILES)); + }); + + it('page load by URL [C213247]', () => { + let url; + browser.getCurrentUrl() + .then(resp => url = resp) + .then(() => personalFilesPage.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.TRASH)) + .then(() => browser.get(url)) + .then(() => expect(browser.getCurrentUrl()).toContain(APP_ROUTES.PERSONAL_FILES)); + }); + }); +}); diff --git a/e2e/suites/list-views/recent-files.test.ts b/e2e/suites/list-views/recent-files.test.ts new file mode 100755 index 000000000..0902c7c70 --- /dev/null +++ b/e2e/suites/list-views/recent-files.test.ts @@ -0,0 +1,141 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { SITE_VISIBILITY, SIDEBAR_LABELS } from '../../configs'; +import { LoginPage, LogoutPage, BrowsingPage } from '../../pages/pages'; +import { Utils } from '../../utilities/utils'; +import { RepoClient } from '../../utilities/repo-client/repo-client'; + +describe('Recent Files', () => { + const username = `user-${Utils.random()}`; + + const folderName = `folder-${Utils.random()}`; let folderId; + const fileName1 = `file-${Utils.random()}.txt`; + const fileName2 = `file-${Utils.random()}.txt`; let file2Id; + const fileName3 = `file-${Utils.random()}.txt`; + + const siteName = `site-${Utils.random()}`; + const folderSite = `folder2-${Utils.random()}`; let folderSiteId; + const fileSite = `file-${Utils.random()}.txt`; + + const apis = { + admin: new RepoClient(), + user: new RepoClient(username, username) + }; + + const loginPage = new LoginPage(); + const logoutPage = new LogoutPage(); + const recentFilesPage = new BrowsingPage(); + const { dataTable } = recentFilesPage; + const { breadcrumb } = recentFilesPage.toolbar; + + beforeAll(done => { + apis.admin.people.createUser(username) + .then(() => apis.user.nodes.createFolders([ folderName ])).then(resp => folderId = resp.data.entry.id) + .then(() => apis.user.nodes.createFiles([ fileName1 ], folderName)) + .then(() => apis.user.nodes.createFiles([ fileName2 ])).then(resp => file2Id = resp.data.entry.id) + .then(() => apis.user.nodes.createFiles([ fileName3 ]).then(resp => apis.user.nodes.deleteNodeById(resp.data.entry.id, false))) + + .then(() => apis.user.sites.createSite(siteName, SITE_VISIBILITY.PUBLIC)) + .then(() => apis.user.sites.getDocLibId(siteName)) + .then(resp => apis.user.nodes.createFolder(folderSite, resp)).then(resp => folderSiteId = resp.data.entry.id) + .then(() => apis.user.nodes.createFile(fileSite, folderSiteId)) + + .then(() => apis.user.search.waitForApi(username, { expect: 3 })) + + .then(() => loginPage.loginWith(username)) + .then(done); + }); + + beforeEach(done => { + recentFilesPage.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.RECENT_FILES) + .then(() => dataTable.waitForHeader()) + .then(done); + }); + + afterAll(done => { + Promise.all([ + apis.user.nodes.deleteNodesById([ folderId, file2Id ]), + apis.user.sites.deleteSite(siteName), + apis.admin.trashcan.emptyTrash(), + logoutPage.load() + ]) + .then(done); + }); + + it('has the correct columns [C213168]', () => { + const labels = [ 'Name', 'Location', 'Size', 'Modified' ]; + const elements = labels.map(label => dataTable.getColumnHeaderByLabel(label)); + + expect(dataTable.getColumnHeaders().count()).toBe(4 + 1, 'Incorrect number of columns'); + + elements.forEach((element, index) => { + expect(element.isPresent()).toBe(true, `"${labels[index]}" is missing`); + }); + }); + + it('default sorting column [C213171]', () => { + expect(dataTable.getSortedColumnHeader().getText()).toBe('Modified'); + expect(dataTable.getSortingOrder()).toBe('desc'); + }); + + it('displays the files added by the current user in the last 30 days [C213170]', () => { + expect(dataTable.countRows()).toEqual(3, 'Incorrect number of files displayed'); + expect(dataTable.getRowName(fileName1).isPresent()).toBe(true, `${fileName1} not displayed`); + expect(dataTable.getRowName(fileName2).isPresent()).toBe(true, `${fileName2} not displayed`); + expect(dataTable.getRowName(fileSite).isPresent()).toBe(true, `${fileSite} not displayed`); + }); + + it(`file not displayed if it's in the Trashcan [C213174]`, () => { + expect(dataTable.getRowName(fileName3).isPresent()).not.toBe(true, `${fileName3} is displayed`); + }); + + it('Location column displays the parent folder of the file [C213175]', () => { + expect(dataTable.getItemLocation(fileName1).getText()).toEqual(folderName); + expect(dataTable.getItemLocation(fileName2).getText()).toEqual('Personal Files'); + expect(dataTable.getItemLocation(fileSite).getText()).toEqual(folderSite); + }); + + it('Location column displays a tooltip with the entire path of the file [C213177]', () => { + expect(dataTable.getItemLocationTooltip(fileName1)).toEqual(`Personal Files/${folderName}`); + expect(dataTable.getItemLocationTooltip(fileName2)).toEqual('Personal Files'); + expect(dataTable.getItemLocationTooltip(fileSite)).toEqual(`File Libraries/${siteName}/${folderSite}`); + }); + + it('Location column redirect - file in user Home [C213176] [C260968]', () => { + dataTable.clickItemLocation(fileName2) + .then(() => expect(breadcrumb.getAllItems()).toEqual([ 'Personal Files' ])); + }); + + it('Location column redirect - file in folder [C213176] [C260968]', () => { + dataTable.clickItemLocation(fileName1) + .then(() => expect(breadcrumb.getAllItems()).toEqual([ 'Personal Files', folderName ])); + }); + + it('Location column redirect - file in site [C213176] [C260969]', () => { + dataTable.clickItemLocation(fileSite) + .then(() => expect(breadcrumb.getAllItems()).toEqual([ 'File Libraries', siteName, folderSite ])); + }); +}); diff --git a/e2e/suites/list-views/shared-files.test.ts b/e2e/suites/list-views/shared-files.test.ts new file mode 100755 index 000000000..b81fc56a9 --- /dev/null +++ b/e2e/suites/list-views/shared-files.test.ts @@ -0,0 +1,142 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { SITE_VISIBILITY, SITE_ROLES, SIDEBAR_LABELS } from '../../configs'; +import { LoginPage, LogoutPage, BrowsingPage } from '../../pages/pages'; +import { Utils } from '../../utilities/utils'; +import { RepoClient } from '../../utilities/repo-client/repo-client'; + +describe('Shared Files', () => { + const username = `user-${Utils.random()}`; + const password = username; + + const siteName = `site-${Utils.random()}`; + const fileAdmin = `file-${Utils.random()}.txt`; + + const folderUser = `folder-${Utils.random()}`; + const file1User = `file1-${Utils.random()}.txt`; let file1Id; + const file2User = `file2-${Utils.random()}.txt`; let file2Id; + const file3User = `file3-${Utils.random()}.txt`; let file3Id; + + const apis = { + admin: new RepoClient(), + user: new RepoClient(username, password) + }; + + const loginPage = new LoginPage(); + const logoutPage = new LogoutPage(); + const sharedFilesPage = new BrowsingPage(); + const { dataTable } = sharedFilesPage; + const { breadcrumb } = sharedFilesPage.toolbar; + + beforeAll(done => { + apis.admin.people.createUser(username) + .then(() => apis.admin.sites.createSite(siteName, SITE_VISIBILITY.PUBLIC)) + .then(() => apis.admin.sites.addSiteMember(siteName, username, SITE_ROLES.SITE_CONSUMER)) + .then(() => apis.admin.nodes.createFiles([ fileAdmin ], `Sites/${siteName}/documentLibrary`)) + .then(resp => apis.admin.shared.shareFileById(resp.data.entry.id)) + + .then(() => apis.user.nodes.createFolders([ folderUser ])) + .then(() => apis.user.nodes.createFiles([ file1User ], folderUser)).then(resp => file1Id = resp.data.entry.id) + .then(() => apis.user.nodes.createFile(file2User)).then(resp => file2Id = resp.data.entry.id) + .then(() => apis.user.nodes.createFile(file3User)).then(resp => file3Id = resp.data.entry.id) + .then(() => apis.user.shared.shareFilesByIds([file1Id, file2Id, file3Id])) + + .then(() => apis.user.shared.waitForApi({ expect: 4 })) + .then(() => apis.user.nodes.deleteNodeById(file2Id)) + .then(() => apis.user.shared.unshareFile(file3User)) + + .then(() => loginPage.loginWith(username)) + .then(done); + }); + + beforeEach(done => { + sharedFilesPage.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.SHARED_FILES) + .then(() => dataTable.waitForHeader()) + .then(done); + }); + + afterEach(done => { + sharedFilesPage.refresh().then(done); + }); + + afterAll(done => { + Promise.all([ + apis.admin.sites.deleteSite(siteName), + apis.user.nodes.deleteNodes([ folderUser ]), + logoutPage.load() + ]) + .then(done); + }); + + it('has the correct columns [C213113]', () => { + const labels = [ 'Name', 'Location', 'Size', 'Modified', 'Modified by', 'Shared by' ]; + const elements = labels.map(label => dataTable.getColumnHeaderByLabel(label)); + + expect(dataTable.getColumnHeaders().count()).toBe(6 + 1, 'Incorrect number of columns'); + + elements.forEach((element, index) => { + expect(element.isPresent()).toBe(true, `"${labels[index]}" is missing`); + }); + }); + + it('default sorting column [C213115]', () => { + expect(dataTable.getSortedColumnHeader().getText()).toBe('Modified'); + expect(dataTable.getSortingOrder()).toBe('desc'); + }); + + it('displays the files shared by everyone [C213114]', () => { + expect(dataTable.getRowName(fileAdmin).isPresent()).toBe(true, `${fileAdmin} not displayed`); + expect(dataTable.getRowName(file1User).isPresent()).toBe(true, `${file1User} not displayed`); + }); + + it(`file not displayed if it's in the Trashcan [C213117]`, () => { + expect(dataTable.getRowName(file2User).isPresent()).toBe(false, `${file2User} is displayed`); + }); + + xit('unshared file is not displayed [C213118]', () => { + expect(dataTable.getRowName(file3User).isPresent()).toBe(false, `${file3User} is displayed`); + }); + + it('Location column displays the parent folder of the file [C213665]', () => { + expect(dataTable.getItemLocation(fileAdmin).getText()).toEqual(siteName); + expect(dataTable.getItemLocation(file1User).getText()).toEqual(folderUser); + }); + + it('Location column redirect - file in user Home [C213666] [C260968]', () => { + dataTable.clickItemLocation(file1User) + .then(() => expect(breadcrumb.getAllItems()).toEqual([ 'Personal Files', folderUser ])); + }); + + it('Location column redirect - file in site [C213666] [C260969]', () => { + dataTable.clickItemLocation(fileAdmin) + .then(() => expect(breadcrumb.getAllItems()).toEqual([ 'File Libraries', siteName ])); + }); + + it('Location column displays a tooltip with the entire path of the file [C213667]', () => { + expect(dataTable.getItemLocationTooltip(fileAdmin)).toEqual(`File Libraries/${siteName}`); + expect(dataTable.getItemLocationTooltip(file1User)).toEqual(`Personal Files/${folderUser}`); + }); +}); diff --git a/e2e/suites/list-views/tooltips.test.ts b/e2e/suites/list-views/tooltips.test.ts new file mode 100755 index 000000000..43e12985b --- /dev/null +++ b/e2e/suites/list-views/tooltips.test.ts @@ -0,0 +1,330 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { SIDEBAR_LABELS } from '../../configs'; +import { LoginPage, LogoutPage, BrowsingPage } from '../../pages/pages'; +import { Utils } from '../../utilities/utils'; +import { RepoClient } from '../../utilities/repo-client/repo-client'; + +describe('File / folder tooltips', () => { + const username = `user-${Utils.random()}`; + + const apis = { + admin: new RepoClient(), + user: new RepoClient(username, username) + }; + + const parent = `parent-${Utils.random()}`; + + const file = `file1-${Utils.random()}`; + const fileWithDesc = `file2-${Utils.random()}`; + const fileWithTitle = `file3-${Utils.random()}`; + const fileWithTitleAndDesc = `file4-${Utils.random()}`; + const fileNameEqTitleEqDesc = `file5-${Utils.random()}`; + const fileNameEqTitleDiffDesc = `file6-${Utils.random()}`; + const fileNameEqDescDiffTitle = `file7-${Utils.random()}`; + const fileTitleEqDesc = `file8-${Utils.random()}`; + let parentId, file1Id, file2Id, file3Id, file4Id, file5Id, file6Id, file7Id, file8Id; + + const fileTitle = 'file title'; + const fileDescription = 'file description'; + + const loginPage = new LoginPage(); + const logoutPage = new LogoutPage(); + const page = new BrowsingPage(); + const { dataTable } = page; + + beforeAll(done => { + apis.admin.people.createUser(username) + .then(() => apis.user.nodes.createFolder( parent )) + .then(resp => parentId = resp.data.entry.id) + + .then(() => Promise.all([ + apis.user.nodes.createFile(file, parentId).then(resp => file1Id = resp.data.entry.id), + apis.user.nodes.createFile(fileWithDesc, parentId, '', fileDescription).then(resp => file2Id = resp.data.entry.id), + apis.user.nodes.createFile(fileWithTitle, parentId, fileTitle).then(resp => file3Id = resp.data.entry.id), + apis.user.nodes.createFile(fileWithTitleAndDesc, parentId, fileTitle, fileDescription) + .then(resp => file4Id = resp.data.entry.id), + apis.user.nodes.createFile(fileNameEqTitleEqDesc, parentId, fileNameEqTitleEqDesc, fileNameEqTitleEqDesc) + .then(resp => file5Id = resp.data.entry.id), + apis.user.nodes.createFile(fileNameEqTitleDiffDesc, parentId, fileNameEqTitleDiffDesc, fileDescription) + .then(resp => file6Id = resp.data.entry.id), + apis.user.nodes.createFile(fileNameEqDescDiffTitle, parentId, fileTitle, fileNameEqDescDiffTitle) + .then(resp => file7Id = resp.data.entry.id), + apis.user.nodes.createFile(fileTitleEqDesc, parentId, fileTitle, fileTitle).then(resp => file8Id = resp.data.entry.id) + ])) + + .then(() => apis.user.shared.shareFilesByIds([ file1Id, file2Id, file3Id, file4Id, file5Id, file6Id, file7Id, file8Id ])) + + .then(() => apis.user.favorites.addFavoritesByIds('file', [ + file1Id, file2Id, file3Id, file4Id, file5Id, file6Id, file7Id, file8Id + ])) + + .then(() => loginPage.loginWith(username)) + .then(done); + }); + + afterAll(done => { + Promise.all([ + apis.user.nodes.deleteNodes([ parent ]), + apis.admin.trashcan.emptyTrash(), + logoutPage.load() + ]) + .then(done); + }); + + xit(''); + + describe('on Personal Files', () => { + beforeAll(done => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.PERSONAL_FILES) + .then(() => dataTable.doubleClickOnItemName(parent)) + .then(done); + }); + + it('File with name, no title, no description', () => { + expect(dataTable.getItemNameTooltip(file)).toEqual(`${file}`); + }); + + it('File with name and description, no title', () => { + expect(dataTable.getItemNameTooltip(fileWithDesc)).toEqual(`${fileWithDesc}\n${fileDescription}`); + }); + + it('File with name and title, no description', () => { + expect(dataTable.getItemNameTooltip(fileWithTitle)).toEqual(`${fileWithTitle}\n${fileTitle}`); + }); + + it('File with name and title and description, all different', () => { + expect(dataTable.getItemNameTooltip(fileWithTitleAndDesc)).toEqual(`${fileTitle}\n${fileDescription}`); + }); + + it('File with name and title and description, all equal', () => { + expect(dataTable.getItemNameTooltip(fileNameEqTitleEqDesc)).toEqual(`${fileNameEqTitleEqDesc}`); + }); + + it('File with name = title, different description', () => { + expect(dataTable.getItemNameTooltip(fileNameEqTitleDiffDesc)).toEqual(`${fileNameEqTitleDiffDesc}\n${fileDescription}`); + }); + + it('File with name = description, different title', () => { + expect(dataTable.getItemNameTooltip(fileNameEqDescDiffTitle)).toEqual(`${fileTitle}\n${fileNameEqDescDiffTitle}`); + }); + + it('File with title = description, different name', () => { + expect(dataTable.getItemNameTooltip(fileTitleEqDesc)).toEqual(`${fileTitle}`); + }); + }); + + describe('on Recent Files', () => { + beforeAll(done => { + apis.user.search.waitForApi(username, { expect: 8 }) + .then(() => page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.RECENT_FILES)) + .then(done); + }); + + it('File with name, no title, no description', () => { + expect(dataTable.getItemNameTooltip(file)).toEqual(`${file}`); + }); + + it('File with name and description, no title', () => { + expect(dataTable.getItemNameTooltip(fileWithDesc)).toEqual(`${fileWithDesc}\n${fileDescription}`); + }); + + it('File with name and title, no description', () => { + expect(dataTable.getItemNameTooltip(fileWithTitle)).toEqual(`${fileWithTitle}\n${fileTitle}`); + }); + + it('File with name and title and description, all different', () => { + expect(dataTable.getItemNameTooltip(fileWithTitleAndDesc)).toEqual(`${fileTitle}\n${fileDescription}`); + }); + + it('File with name and title and description, all equal', () => { + expect(dataTable.getItemNameTooltip(fileNameEqTitleEqDesc)).toEqual(`${fileNameEqTitleEqDesc}`); + }); + + it('File with name = title, different description', () => { + expect(dataTable.getItemNameTooltip(fileNameEqTitleDiffDesc)).toEqual(`${fileNameEqTitleDiffDesc}\n${fileDescription}`); + }); + + it('File with name = description, different title', () => { + expect(dataTable.getItemNameTooltip(fileNameEqDescDiffTitle)).toEqual(`${fileTitle}\n${fileNameEqDescDiffTitle}`); + }); + + it('File with title = description, different name', () => { + expect(dataTable.getItemNameTooltip(fileTitleEqDesc)).toEqual(`${fileTitle}`); + }); + }); + + // disabled until ACA-518 is done + xdescribe('on Shared Files', () => { + beforeAll(done => { + apis.user.shared.waitForApi({ expect: 8 }) + .then(() => page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.SHARED_FILES)) + .then(done); + }); + + it('File with name, no title, no description', () => { + expect(dataTable.getItemNameTooltip(file)).toEqual(`${file}`); + }); + + it('File with name and description, no title', () => { + expect(dataTable.getItemNameTooltip(fileWithDesc)).toEqual(`${fileWithDesc}\n${fileDescription}`); + }); + + it('File with name and title, no description', () => { + expect(dataTable.getItemNameTooltip(fileWithTitle)).toEqual(`${fileWithTitle}\n${fileTitle}`); + }); + + it('File with name and title and description, all different', () => { + expect(dataTable.getItemNameTooltip(fileWithTitleAndDesc)).toEqual(`${fileTitle}\n${fileDescription}`); + }); + + it('File with name and title and description, all equal', () => { + expect(dataTable.getItemNameTooltip(fileNameEqTitleEqDesc)).toEqual(`${fileNameEqTitleEqDesc}`); + }); + + it('File with name = title, different description', () => { + expect(dataTable.getItemNameTooltip(fileNameEqTitleDiffDesc)).toEqual(`${fileNameEqTitleDiffDesc}\n${fileDescription}`); + }); + + it('File with name = description, different title', () => { + expect(dataTable.getItemNameTooltip(fileNameEqDescDiffTitle)).toEqual(`${fileTitle}\n${fileNameEqDescDiffTitle}`); + }); + + it('File with title = description, different name', () => { + expect(dataTable.getItemNameTooltip(fileTitleEqDesc)).toEqual(`${fileTitle}`); + }); + }); + + describe('on Favorites', () => { + beforeAll(done => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.FAVORITES).then(done); + }); + + it('File with name, no title, no description', () => { + expect(dataTable.getItemNameTooltip(file)).toEqual(`${file}`); + }); + + it('File with name and description, no title', () => { + expect(dataTable.getItemNameTooltip(fileWithDesc)).toEqual(`${fileWithDesc}\n${fileDescription}`); + }); + + it('File with name and title, no description', () => { + expect(dataTable.getItemNameTooltip(fileWithTitle)).toEqual(`${fileWithTitle}\n${fileTitle}`); + }); + + it('File with name and title and description, all different', () => { + expect(dataTable.getItemNameTooltip(fileWithTitleAndDesc)).toEqual(`${fileTitle}\n${fileDescription}`); + }); + + it('File with name and title and description, all equal', () => { + expect(dataTable.getItemNameTooltip(fileNameEqTitleEqDesc)).toEqual(`${fileNameEqTitleEqDesc}`); + }); + + it('File with name = title, different description', () => { + expect(dataTable.getItemNameTooltip(fileNameEqTitleDiffDesc)).toEqual(`${fileNameEqTitleDiffDesc}\n${fileDescription}`); + }); + + it('File with name = description, different title', () => { + expect(dataTable.getItemNameTooltip(fileNameEqDescDiffTitle)).toEqual(`${fileTitle}\n${fileNameEqDescDiffTitle}`); + }); + + it('File with title = description, different name', () => { + expect(dataTable.getItemNameTooltip(fileTitleEqDesc)).toEqual(`${fileTitle}`); + }); + }); + + describe('on Trash', () => { + const parentForTrash = `parent-${Utils.random()}`; + let parentForTrashId, file1TrashId, file2TrashId, file3TrashId, file4TrashId; + let file5TrashId, file6TrashId, file7TrashId, file8TrashId; + + beforeAll(done => { + apis.user.nodes.createFolder( parentForTrash ) + .then(resp => parentForTrashId = resp.data.entry.id) + .then(() => Promise.all([ + apis.user.nodes.createFile(file, parentForTrashId) + .then(resp => file1TrashId = resp.data.entry.id), + apis.user.nodes.createFile(fileWithDesc, parentForTrashId, '', fileDescription) + .then(resp => file2TrashId = resp.data.entry.id), + apis.user.nodes.createFile(fileWithTitle, parentForTrashId, fileTitle) + .then(resp => file3TrashId = resp.data.entry.id), + apis.user.nodes.createFile(fileWithTitleAndDesc, parentForTrashId, fileTitle, fileDescription) + .then(resp => file4TrashId = resp.data.entry.id), + apis.user.nodes.createFile(fileNameEqTitleEqDesc, parentForTrashId, fileNameEqTitleEqDesc, fileNameEqTitleEqDesc) + .then(resp => file5TrashId = resp.data.entry.id), + apis.user.nodes.createFile(fileNameEqTitleDiffDesc, parentForTrashId, fileNameEqTitleDiffDesc, fileDescription) + .then(resp => file6TrashId = resp.data.entry.id), + apis.user.nodes.createFile(fileNameEqDescDiffTitle, parentForTrashId, fileTitle, fileNameEqDescDiffTitle) + .then(resp => file7TrashId = resp.data.entry.id), + apis.user.nodes.createFile(fileTitleEqDesc, parentForTrashId, fileTitle, fileTitle) + .then(resp => file8TrashId = resp.data.entry.id) + ])) + + .then(() => apis.user.nodes.deleteNodesById([ + file1TrashId, file2TrashId, file3TrashId, file4TrashId, file5TrashId, file6TrashId, file7TrashId, file8TrashId + ], false)) + + .then(() => page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.TRASH)) + .then(done); + }); + + afterAll(done => { + apis.user.nodes.deleteNodes([ parentForTrash ]).then(done); + }); + + it('File with name, no title, no description', () => { + expect(dataTable.getItemNameTooltip(file)).toEqual(`${file}`); + }); + + it('File with name and description, no title', () => { + expect(dataTable.getItemNameTooltip(fileWithDesc)).toEqual(`${fileWithDesc}\n${fileDescription}`); + }); + + it('File with name and title, no description', () => { + expect(dataTable.getItemNameTooltip(fileWithTitle)).toEqual(`${fileWithTitle}\n${fileTitle}`); + }); + + it('File with name and title and description, all different', () => { + expect(dataTable.getItemNameTooltip(fileWithTitleAndDesc)).toEqual(`${fileTitle}\n${fileDescription}`); + }); + + it('File with name and title and description, all equal', () => { + expect(dataTable.getItemNameTooltip(fileNameEqTitleEqDesc)).toEqual(`${fileNameEqTitleEqDesc}`); + }); + + it('File with name = title, different description', () => { + expect(dataTable.getItemNameTooltip(fileNameEqTitleDiffDesc)).toEqual(`${fileNameEqTitleDiffDesc}\n${fileDescription}`); + }); + + it('File with name = description, different title', () => { + expect(dataTable.getItemNameTooltip(fileNameEqDescDiffTitle)).toEqual(`${fileTitle}\n${fileNameEqDescDiffTitle}`); + }); + + it('File with title = description, different name', () => { + expect(dataTable.getItemNameTooltip(fileTitleEqDesc)).toEqual(`${fileTitle}`); + }); + }); +}); diff --git a/e2e/suites/list-views/trash.test.ts b/e2e/suites/list-views/trash.test.ts new file mode 100755 index 000000000..df378e765 --- /dev/null +++ b/e2e/suites/list-views/trash.test.ts @@ -0,0 +1,169 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + + +import { SITE_VISIBILITY, SITE_ROLES, SIDEBAR_LABELS } from '../../configs'; +import { LoginPage, LogoutPage, BrowsingPage } from '../../pages/pages'; +import { Utils } from '../../utilities/utils'; +import { RepoClient } from '../../utilities/repo-client/repo-client'; + +describe('Trash', () => { + const username = `user-${Utils.random()}`; + + const siteName = `site-${Utils.random()}`; + const fileSite = `file-${Utils.random()}.txt`; let fileSiteId; + + const folderAdmin = `folder-${Utils.random()}`; let folderAdminId; + const fileAdmin = `file-${Utils.random()}.txt`; let fileAdminId; + + const folderUser = `folder-${Utils.random()}`; let folderUserId; + const fileUser = `file-${Utils.random()}.txt`; let fileUserId; + + const apis = { + admin: new RepoClient(), + user: new RepoClient(username, username) + }; + + const loginPage = new LoginPage(); + const logoutPage = new LogoutPage(); + const trashPage = new BrowsingPage(); + const { dataTable } = trashPage; + const { breadcrumb } = trashPage.toolbar; + + beforeAll(done => { + apis.admin.people.createUser(username) + .then(() => apis.admin.nodes.createFiles([ fileAdmin ]).then(resp => fileAdminId = resp.data.entry.id)) + .then(() => apis.admin.nodes.createFolders([ folderAdmin ]).then(resp => folderAdminId = resp.data.entry.id)) + .then(() => apis.admin.sites.createSite(siteName, SITE_VISIBILITY.PUBLIC)) + .then(() => apis.admin.sites.addSiteMember(siteName, username, SITE_ROLES.SITE_MANAGER)) + .then(() => apis.admin.nodes.createFiles([ fileSite ], `Sites/${siteName}/documentLibrary`) + .then(resp => fileSiteId = resp.data.entry.id)) + .then(() => apis.user.nodes.createFiles([ fileUser ]).then(resp => fileUserId = resp.data.entry.id)) + .then(() => apis.user.nodes.createFolders([ folderUser ]).then(resp => folderUserId = resp.data.entry.id)) + + .then(() => apis.admin.nodes.deleteNodesById([ fileAdminId, folderAdminId ], false)) + .then(() => apis.user.nodes.deleteNodesById([ fileSiteId, fileUserId, folderUserId ], false)) + + .then(done); + }); + + afterAll(done => { + Promise.all([ + apis.admin.sites.deleteSite(siteName), + apis.admin.trashcan.emptyTrash() + ]) + .then(done); + }); + + xit(''); + + describe('as admin', () => { + beforeAll(done => { + loginPage.loginWithAdmin().then(done); + }); + + beforeEach(done => { + trashPage.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.TRASH) + .then(() => dataTable.waitForHeader()) + .then(done); + }); + + afterAll(done => { + logoutPage.load().then(done); + }); + + it('has the correct columns', () => { + const labels = [ 'Name', 'Location', 'Size', 'Deleted', 'Deleted by' ]; + const elements = labels.map(label => dataTable.getColumnHeaderByLabel(label)); + + expect(dataTable.getColumnHeaders().count()).toBe(5 + 1, 'Incorrect number of columns'); + + elements.forEach((element, index) => { + expect(element.isPresent()).toBe(true, `"${labels[index]}" is missing`); + }); + }); + + it('displays the files and folders deleted by everyone [C213217]', () => { + expect(dataTable.countRows()).toEqual(5, 'Incorrect number of deleted items displayed'); + + expect(dataTable.getRowName(fileAdmin).isPresent()).toBe(true, `${fileAdmin} not displayed`); + expect(dataTable.getRowName(folderAdmin).isPresent()).toBe(true, `${folderAdmin} not displayed`); + expect(dataTable.getRowName(fileUser).isPresent()).toBe(true, `${fileUser} not displayed`); + expect(dataTable.getRowName(folderUser).isPresent()).toBe(true, `${folderUser} not displayed`); + expect(dataTable.getRowName(fileSite).isPresent()).toBe(true, `${fileSite} not displayed`); + }); + }); + + describe('as user', () => { + beforeAll(done => { + loginPage.loginWith(username).then(done); + }); + + beforeEach(done => { + trashPage.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.TRASH) + .then(() => dataTable.waitForHeader()) + .then(done); + }); + + afterAll(done => { + logoutPage.load().then(done); + }); + + it('has the correct columns', () => { + const labels = [ 'Name', 'Location', 'Size', 'Deleted']; + const elements = labels.map(label => dataTable.getColumnHeaderByLabel(label)); + + expect(dataTable.getColumnHeaders().count()).toBe(4 + 1, 'Incorrect number of columns'); + + elements.forEach((element, index) => { + expect(element.isPresent()).toBe(true, `"${labels[index]}" is missing`); + }); + }); + + it('displays the files and folders deleted by the user [C213218]', () => { + expect(dataTable.countRows()).toEqual(3, 'Incorrect number of deleted items displayed'); + + expect(dataTable.getRowName(fileSite).isPresent()).toBe(true, `${fileSite} not displayed`); + expect(dataTable.getRowName(fileUser).isPresent()).toBe(true, `${fileUser} not displayed`); + expect(dataTable.getRowName(folderUser).isPresent()).toBe(true, `${folderUser} not displayed`); + expect(dataTable.getRowName(fileAdmin).isPresent()).toBe(false, `${fileAdmin} is displayed`); + }); + + it('default sorting column [C213219]', () => { + expect(dataTable.getSortedColumnHeader().getText()).toBe('Deleted'); + expect(dataTable.getSortingOrder()).toBe('desc'); + }); + + it('Location column redirect - file in user Home [C217144] [C260968]', () => { + dataTable.clickItemLocation(fileUser) + .then(() => expect(breadcrumb.getAllItems()).toEqual([ 'Personal Files' ])); + }); + + it('Location column redirect - file in site [C217144] [C260969]', () => { + dataTable.clickItemLocation(fileSite) + .then(() => expect(breadcrumb.getAllItems()).toEqual([ 'File Libraries', siteName ])); + }); + }); +}); diff --git a/e2e/suites/navigation/breadcrumb.test.ts b/e2e/suites/navigation/breadcrumb.test.ts new file mode 100755 index 000000000..974c62df7 --- /dev/null +++ b/e2e/suites/navigation/breadcrumb.test.ts @@ -0,0 +1,244 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { browser } from 'protractor'; + +import { SIDEBAR_LABELS, SITE_VISIBILITY } from '../../configs'; +import { LoginPage, LogoutPage, BrowsingPage } from '../../pages/pages'; +import { Utils } from '../../utilities/utils'; +import { RepoClient } from '../../utilities/repo-client/repo-client'; + +describe('Breadcrumb', () => { + const username = `user-${Utils.random()}`; + + const parent = `parent-${Utils.random()}`; let parentId; + const subFolder1 = `subFolder1-${Utils.random()}`; let subFolder1Id; + const subFolder2 = `subFolder2-${Utils.random()}`; let subFolder2Id; + const fileName1 = `file1-${Utils.random()}.txt`; + + const siteName = `site-${Utils.random()}`; + + const parent2 = `parent2-${Utils.random()}`; let parent2Id; + const folder1 = `folder1-${Utils.random()}`; let folder1Id; + const folder1Renamed = `renamed-${Utils.random()}`; + + const loginPage = new LoginPage(); + const logoutPage = new LogoutPage(); + const page = new BrowsingPage(); + const { breadcrumb } = page.toolbar; + + const apis = { + admin: new RepoClient(), + user: new RepoClient(username, username) + }; + + beforeAll(done => { + apis.admin.people.createUser(username) + .then(() => apis.user.nodes.createFolder(parent)).then(resp => parentId = resp.data.entry.id) + .then(() => apis.user.nodes.createFolder(subFolder1, parentId)).then(resp => subFolder1Id = resp.data.entry.id) + .then(() => apis.user.nodes.createFolder(subFolder2, subFolder1Id)).then(resp => subFolder2Id = resp.data.entry.id) + .then(() => apis.user.nodes.createFile(fileName1, subFolder2Id)) + + .then(() => apis.user.nodes.createFolder(parent2)).then(resp => parent2Id = resp.data.entry.id) + .then(() => apis.user.nodes.createFolder(folder1, parent2Id)).then(resp => folder1Id = resp.data.entry.id) + + .then(() => apis.user.sites.createSite(siteName, SITE_VISIBILITY.PUBLIC)) + .then(() => apis.user.sites.getDocLibId(siteName)) + .then(resp => apis.user.nodes.createFolder(parent, resp)).then(resp => parentId = resp.data.entry.id) + .then(() => apis.user.nodes.createFolder(subFolder1, parentId)).then(resp => subFolder1Id = resp.data.entry.id) + .then(() => apis.user.nodes.createFolder(subFolder2, subFolder1Id)).then(resp => subFolder2Id = resp.data.entry.id) + .then(() => apis.user.nodes.createFile(fileName1, subFolder2Id)) + + .then(() => loginPage.loginWith(username)) + .then(done); + }); + + afterAll(done => { + Promise.all([ + apis.user.nodes.deleteNodeById(parentId), + apis.user.nodes.deleteNodeById(parent2Id), + apis.user.sites.deleteSite(siteName), + logoutPage.load() + ]) + .then(done); + }); + + it('Personal Files breadcrumb main node [C260964]', () => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.PERSONAL_FILES) + .then(() => { + expect(breadcrumb.getItemsCount()).toEqual(1, 'Breadcrumb has incorrect number of items'); + expect(breadcrumb.getCurrentItemName()).toBe('Personal Files'); + }); + }); + + it('File Libraries breadcrumb main node [C260966]', () => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.FILE_LIBRARIES) + .then(() => { + expect(breadcrumb.getItemsCount()).toEqual(1, 'Breadcrumb has incorrect number of items'); + expect(breadcrumb.getCurrentItemName()).toBe('File Libraries'); + }); + }); + + it('Recent Files breadcrumb main node [C260971]', () => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.RECENT_FILES) + .then(() => { + expect(breadcrumb.getItemsCount()).toEqual(1, 'Breadcrumb has incorrect number of items'); + expect(breadcrumb.getCurrentItemName()).toBe('Recent Files'); + }); + }); + + it('Shared Files breadcrumb main node [C260972]', () => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.SHARED_FILES) + .then(() => { + expect(breadcrumb.getItemsCount()).toEqual(1, 'Breadcrumb has incorrect number of items'); + expect(breadcrumb.getCurrentItemName()).toBe('Shared Files'); + }); + }); + + it('Favorites breadcrumb main node [C260973]', () => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.FAVORITES) + .then(() => { + expect(breadcrumb.getItemsCount()).toEqual(1, 'Breadcrumb has incorrect number of items'); + expect(breadcrumb.getCurrentItemName()).toBe('Favorites'); + }); + }); + + it('Trash breadcrumb main node [C260974]', () => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.TRASH) + .then(() => { + expect(breadcrumb.getItemsCount()).toEqual(1, 'Breadcrumb has incorrect number of items'); + expect(breadcrumb.getCurrentItemName()).toBe('Trash'); + }); + }); + + it('Personal Files breadcrumb for a folder hierarchy [C260965]', () => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.PERSONAL_FILES) + .then(() => page.dataTable.waitForHeader()) + .then(() => page.dataTable.doubleClickOnItemName(parent)) + .then(() => page.dataTable.doubleClickOnItemName(subFolder1)) + .then(() => page.dataTable.doubleClickOnItemName(subFolder2)) + .then(() => { + const expectedBreadcrumb = [ 'Personal Files', parent, subFolder1, subFolder2 ]; + expect(breadcrumb.getAllItems()).toEqual(expectedBreadcrumb); + }); + }); + + it('File Libraries breadcrumb for a folder hierarchy [C260967]', () => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.FILE_LIBRARIES) + .then(() => page.dataTable.waitForHeader()) + .then(() => page.dataTable.doubleClickOnItemName(siteName)) + .then(() => page.dataTable.doubleClickOnItemName(parent)) + .then(() => page.dataTable.doubleClickOnItemName(subFolder1)) + .then(() => page.dataTable.doubleClickOnItemName(subFolder2)) + .then(() => { + const expectedItems = [ 'File Libraries', siteName, parent, subFolder1, subFolder2 ]; + expect(breadcrumb.getAllItems()).toEqual(expectedItems); + }); + }); + + it('User can navigate to any location by clicking on a step from the breadcrumb [C213235]', () => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.PERSONAL_FILES) + .then(() => page.dataTable.waitForHeader()) + .then(() => page.dataTable.doubleClickOnItemName(parent)) + .then(() => page.dataTable.doubleClickOnItemName(subFolder1)) + .then(() => page.dataTable.doubleClickOnItemName(subFolder2)) + .then(() => breadcrumb.clickItem(subFolder1)) + .then(() => { + const expectedBreadcrumb = [ 'Personal Files', parent, subFolder1 ]; + expect(breadcrumb.getAllItems()).toEqual(expectedBreadcrumb); + }); + }); + + it('Tooltip appears on hover on a step in breadcrumb [C213237]', () => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.PERSONAL_FILES) + .then(() => page.dataTable.waitForHeader()) + .then(() => page.dataTable.doubleClickOnItemName(parent)) + .then(() => page.dataTable.doubleClickOnItemName(subFolder1)) + .then(() => page.dataTable.doubleClickOnItemName(subFolder2)) + .then(() => { + expect(breadcrumb.getNthItemTooltip(3)).toEqual(subFolder1); + }); + }); + + it('Breadcrumb updates correctly when folder is renamed [C213238]', () => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.PERSONAL_FILES) + .then(() => page.dataTable.waitForHeader()) + .then(() => page.dataTable.doubleClickOnItemName(parent2)) + .then(() => page.dataTable.doubleClickOnItemName(folder1)) + .then(() => page.dataTable.wait()) + .then(() => apis.user.nodes.renameNode(folder1Id, folder1Renamed).then(done => done)) + .then(() => page.refresh()) + .then(() => page.dataTable.wait()) + .then(() => { + expect(breadcrumb.getCurrentItemName()).toEqual(folder1Renamed); + }); + }); + + it('Browser back navigates to previous location regardless of breadcrumb steps [C213240]', () => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.PERSONAL_FILES) + .then(() => page.dataTable.waitForHeader()) + .then(() => page.dataTable.doubleClickOnItemName(parent)) + .then(() => page.dataTable.doubleClickOnItemName(subFolder1)) + .then(() => page.dataTable.doubleClickOnItemName(subFolder2)) + .then(() => page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.TRASH)) + .then(() => page.dataTable.waitForEmptyState()) + .then(() => browser.navigate().back()) + .then(() => { + const expectedBreadcrumb = [ 'Personal Files', parent, subFolder1, subFolder2 ]; + expect(breadcrumb.getAllItems()).toEqual(expectedBreadcrumb); + }); + }); + + // disabled cause of ACA-1039 + xdescribe('as admin', () => { + const user2 = 'a_user'; + const userFolder = `userFolder-${Utils.random()}`; let userFolderId; + const user2Api = new RepoClient(user2, user2); + + beforeAll(done => { + logoutPage.load() + .then(() => apis.admin.people.createUser(user2)) + .then(() => user2Api.nodes.createFolder(userFolder).then(resp => userFolderId = resp.data.entry.id)) + .then(() => loginPage.loginWithAdmin()) + .then(done); + }); + + afterAll(done => { + Promise.all([ + user2Api.nodes.deleteNodeById(userFolderId), + logoutPage.load() + ]) + .then(done); + }); + + it(`Breadcrumb on navigation to a user's home [C260970]`, () => { + page.dataTable.doubleClickOnItemName('User Homes') + .then(() => page.dataTable.doubleClickOnItemName(user2)) + .then(() => expect(breadcrumb.getAllItems()).toEqual([ 'Personal Files', 'User Homes', user2 ])) + .then(() => page.dataTable.doubleClickOnItemName(userFolder)) + .then(() => expect(breadcrumb.getAllItems()).toEqual([ 'Personal Files', 'User Homes', user2, userFolder ])); + }); + }); +}); diff --git a/e2e/suites/navigation/sidebar.test.ts b/e2e/suites/navigation/sidebar.test.ts new file mode 100755 index 000000000..531fc36d2 --- /dev/null +++ b/e2e/suites/navigation/sidebar.test.ts @@ -0,0 +1,139 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { browser } from 'protractor'; + +import { APP_ROUTES, SIDEBAR_LABELS } from '../../configs'; +import { LoginPage, LogoutPage, BrowsingPage } from '../../pages/pages'; + +describe('Sidebar', () => { + const loginPage = new LoginPage(); + const logoutPage = new LogoutPage(); + const page = new BrowsingPage(); + const { sidenav } = page; + + beforeAll(done => { + loginPage.loginWithAdmin().then(done); + }); + + afterAll(done => { + logoutPage.load().then(done); + }); + + it('has "Personal Files" as default', () => { + expect(browser.getCurrentUrl()).toContain(APP_ROUTES.PERSONAL_FILES); + expect(sidenav.isActiveByLabel('Personal Files')).toBe(true, 'Active link'); + }); + + it('navigates to "File Libraries"', () => { + sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.FILE_LIBRARIES) + .then(() => { + expect(browser.getCurrentUrl()).toContain(APP_ROUTES.FILE_LIBRARIES); + expect(sidenav.isActiveByLabel(SIDEBAR_LABELS.FILE_LIBRARIES)).toBe(true); + }); + }); + + it('navigates to "Personal Files"', () => { + sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.PERSONAL_FILES) + .then(() => { + expect(browser.getCurrentUrl()).toContain(APP_ROUTES.PERSONAL_FILES); + expect(sidenav.isActiveByLabel(SIDEBAR_LABELS.PERSONAL_FILES)).toBe(true); + }); + }); + + it('navigates to "Shared Files"', () => { + sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.SHARED_FILES) + .then(() => { + expect(browser.getCurrentUrl()).toContain(APP_ROUTES.SHARED_FILES); + expect(sidenav.isActiveByLabel(SIDEBAR_LABELS.SHARED_FILES)).toBe(true); + }); + }); + + it('navigates to "Recent Files"', () => { + sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.RECENT_FILES) + .then(() => { + expect(browser.getCurrentUrl()).toContain(APP_ROUTES.RECENT_FILES); + expect(sidenav.isActiveByLabel(SIDEBAR_LABELS.RECENT_FILES)).toBe(true); + }); + }); + + it('navigates to "Favorites"', () => { + sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.FAVORITES) + .then(() => { + expect(browser.getCurrentUrl()).toContain(APP_ROUTES.FAVORITES); + expect(sidenav.isActiveByLabel(SIDEBAR_LABELS.FAVORITES)).toBe(true); + }); + }); + + it('navigates to "Trash"', () => { + sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.TRASH) + .then(() => { + expect(browser.getCurrentUrl()).toContain(APP_ROUTES.TRASHCAN); + expect(sidenav.isActiveByLabel(SIDEBAR_LABELS.TRASH)).toBe(true); + }); + }); + + it('Personal Files tooltip', () => { + sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.PERSONAL_FILES) + .then(() => { + expect(sidenav.getLinkTooltip(SIDEBAR_LABELS.PERSONAL_FILES)).toContain('View your Personal Files'); + }); + }); + + it('File Libraries tooltip', () => { + sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.FILE_LIBRARIES) + .then(() => { + expect(sidenav.getLinkTooltip(SIDEBAR_LABELS.FILE_LIBRARIES)).toContain('Access File Libraries'); + }); + }); + + it('Shared Files tooltip', () => { + sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.SHARED_FILES) + .then(() => { + expect(sidenav.getLinkTooltip(SIDEBAR_LABELS.SHARED_FILES)).toContain('View files that have been shared'); + }); + }); + + it('Recent Files tooltip', () => { + sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.RECENT_FILES) + .then(() => { + expect(sidenav.getLinkTooltip(SIDEBAR_LABELS.RECENT_FILES)).toContain('View files you recently edited'); + }); + }); + + it('Favorites tooltip', () => { + sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.FAVORITES) + .then(() => { + expect(sidenav.getLinkTooltip(SIDEBAR_LABELS.FAVORITES)).toContain('View your favorite files and folders'); + }); + }); + + it('Trash tooltip', () => { + sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.TRASH) + .then(() => { + expect(sidenav.getLinkTooltip(SIDEBAR_LABELS.TRASH)).toContain('View deleted files in the trash'); + }); + }); +}); diff --git a/e2e/suites/pagination/pag-favorites.test.ts b/e2e/suites/pagination/pag-favorites.test.ts new file mode 100755 index 000000000..2244be6e8 --- /dev/null +++ b/e2e/suites/pagination/pag-favorites.test.ts @@ -0,0 +1,228 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { browser } from 'protractor'; +import { SIDEBAR_LABELS } from '../../configs'; +import { LoginPage, LogoutPage, BrowsingPage } from '../../pages/pages'; +import { Utils } from '../../utilities/utils'; +import { RepoClient } from '../../utilities/repo-client/repo-client'; + +describe('Pagination on Favorites', () => { + const username = `user-${Utils.random()}`; + + const apis = { + admin: new RepoClient(), + user: new RepoClient(username, username) + }; + const { nodes: nodesApi, favorites: favoritesApi } = apis.user; + + const loginPage = new LoginPage(); + const logoutPage = new LogoutPage(); + const page = new BrowsingPage(); + const { dataTable, pagination } = page; + + const parent = `parent-${Utils.random()}`; + const files = Array(101) + .fill('file') + .map((name, index): string => `${name}-${index + 1}.txt`); + let filesIds; + + const file = `file-${Utils.random()}.txt`; let fileId; + + beforeAll(done => { + apis.admin.people.createUser(username).then(done); + }); + + xit(''); + + describe('on empty page', () => { + beforeAll(done => { + loginPage.loginWith(username).then(done); + }); + + afterAll(done => { + logoutPage.load().then(done); + }); + + it('pagination controls not displayed [C213164]', () => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.FAVORITES) + .then(() => { + expect(pagination.range.isPresent()).toBe(false); + expect(pagination.maxItems.isPresent()).toBe(false); + expect(pagination.currentPage.isPresent()).toBe(false); + expect(pagination.totalPages.isPresent()).toBe(false); + expect(pagination.previousButton.isPresent()).toBe(false); + expect(pagination.nextButton.isPresent()).toBe(false); + }); + }); + }); + + describe('on single page', () => { + beforeAll(done => { + nodesApi.createFile(file).then(resp => fileId = resp.data.entry.id) + .then(() => favoritesApi.addFavoriteById('file', fileId)) + .then(() => loginPage.loginWith(username)) + .then(done); + }); + + afterAll(done => { + Promise.all([ + nodesApi.deleteNodeById(fileId), + logoutPage.load() + ]) + .then(done); + }); + + it('page selector not displayed when having a single page [C213165]', () => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.FAVORITES) + .then(() => dataTable.waitForHeader()) + .then(() => expect(pagination.pagesButton.isPresent()).toBe(false, 'page selector displayed')); + }); + }); + + describe('on multiple pages', () => { + beforeAll(done => { + nodesApi.createFiles(files, parent) + .then(resp => filesIds = resp.data.list.entries.map(entries => entries.entry.id)) + .then(() => favoritesApi.addFavoritesByIds('file', filesIds)) + .then(() => favoritesApi.waitForApi({ expect: 101 })) + .then(() => loginPage.loginWith(username)) + .then(done); + }); + + beforeEach(done => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.FAVORITES) + .then(() => dataTable.waitForHeader()) + .then(done); + }); + + afterEach(done => { + browser.actions().mouseMove(browser.$('body'), { x: 0, y: 0 }).click().perform().then(done); + }); + + afterAll(done => { + Promise.all([ + nodesApi.deleteNodes([ parent ]), + logoutPage.load() + ]) + .then(done); + }); + + it('default values [C213157]', () => { + expect(pagination.range.getText()).toContain('1-25 of 101'); + expect(pagination.maxItems.getText()).toContain('25'); + expect(pagination.currentPage.getText()).toContain('Page 1'); + expect(pagination.totalPages.getText()).toContain('of 5'); + expect(pagination.previousButton.isEnabled()).toBe(false, 'Previous button is enabled'); + expect(pagination.nextButton.isEnabled()).toBe(true, 'Next button is not enabled'); + }); + + it('page sizes [C213157]', () => { + pagination.openMaxItemsMenu() + .then(() => { + const [ first, second, third ] = [1, 2, 3] + .map(nth => pagination.menu.getNthItem(nth).getText()); + expect(first).toBe('25'); + expect(second).toBe('50'); + expect(third).toBe('100'); + }) + .then(() => pagination.menu.closeMenu()); + }); + + it('change the page size [C213158]', () => { + pagination.openMaxItemsMenu() + .then(() => pagination.menu.clickMenuItem('50')) + .then(() => { + expect(pagination.getText(pagination.maxItems)).toContain('50'); + expect(pagination.getText(pagination.totalPages)).toContain('of 3'); + }) + .then(() => pagination.openMaxItemsMenu()) + .then(() => pagination.menu.clickMenuItem('100')) + .then(() => { + expect(pagination.getText(pagination.maxItems)).toContain('100'); + expect(pagination.getText(pagination.totalPages)).toContain('of 2'); + }) + .then(() => pagination.resetToDefaultPageSize()); + }); + + it('current page menu items', () => { + pagination.openCurrentPageMenu() + .then(() => expect(pagination.menu.getItemsCount()).toBe(5)) + .then(() => pagination.menu.closeMenu()); + }); + + it('change the current page from menu [C260518]', () => { + pagination.openCurrentPageMenu() + .then(() => pagination.menu.clickNthItem(3)) + .then(() => { + expect(pagination.getText(pagination.range)).toContain('51-75 of 101'); + expect(pagination.getText(pagination.currentPage)).toContain('Page 3'); + expect(pagination.previousButton.isEnabled()).toBe(true, 'Previous button is not enabled'); + expect(pagination.nextButton.isEnabled()).toBe(true, 'Next button is not enabled'); + expect(dataTable.getRowName('file-40.txt').isPresent()).toBe(true, 'File not found on page'); + }) + .then(() => pagination.resetToDefaultPageNumber()); + }); + + it('navigate to next page [C213160]', () => { + pagination.nextButton.click() + .then(() => dataTable.waitForHeader()) + .then(() => { + expect(pagination.range.getText()).toContain('26-50 of 101'); + expect(dataTable.getRowName('file-70.txt').isPresent()).toBe(true, 'File not found on page'); + }) + .then(() => pagination.resetToDefaultPageNumber()); + }); + + it('navigate to previous page [C213160]', () => { + pagination.openCurrentPageMenu() + .then(() => pagination.menu.clickNthItem(2)) + .then(() => dataTable.waitForHeader()) + .then(() => pagination.previousButton.click()) + .then(() => dataTable.waitForHeader()) + .then(() => { + expect(pagination.range.getText()).toContain('1-25 of 101'); + expect(dataTable.getRowName('file-88.txt').isPresent()) + .toBe(true, 'File not found on page'); + }) + .then(() => pagination.resetToDefaultPageNumber()); + }); + + it('Previous button is disabled on first page [C260519]', () => { + expect(pagination.currentPage.getText()).toContain('Page 1'); + expect(pagination.previousButton.isEnabled()).toBe(false, 'Previous button is enabled on first page'); + }); + + it('Next button is disabled on last page [C260519]', () => { + pagination.openCurrentPageMenu() + .then(() => pagination.menu.clickNthItem(5)) + .then(() => { + expect(dataTable.countRows()).toBe(1, 'Incorrect number of items on the last page'); + expect(pagination.currentPage.getText()).toContain('Page 5'); + expect(pagination.nextButton.isEnabled()).toBe(false, 'Next button is enabled on last page'); + }); + }); + }); +}); diff --git a/e2e/suites/pagination/pag-personal-files.test.ts b/e2e/suites/pagination/pag-personal-files.test.ts new file mode 100755 index 000000000..beb580638 --- /dev/null +++ b/e2e/suites/pagination/pag-personal-files.test.ts @@ -0,0 +1,228 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { browser } from 'protractor'; +import { SIDEBAR_LABELS } from '../../configs'; +import { LoginPage, LogoutPage, BrowsingPage } from '../../pages/pages'; +import { Utils } from '../../utilities/utils'; +import { RepoClient } from '../../utilities/repo-client/repo-client'; + +describe('Pagination on Personal Files', () => { + const username = `user-${Utils.random()}`; + + const apis = { + admin: new RepoClient(), + user: new RepoClient(username, username) + }; + const { nodes: nodesApi } = apis.user; + + const loginPage = new LoginPage(); + const logoutPage = new LogoutPage(); + const page = new BrowsingPage(); + const { dataTable, pagination } = page; + + const parent = `parent-${Utils.random()}`; + const files = Array(101) + .fill('file') + .map((name, index): string => `${name}-${index + 1}.txt`); + + const file = `file-${Utils.random()}.txt`; let fileId; + + beforeAll(done => { + apis.admin.people.createUser(username).then(done); + }); + + xit(''); + + describe('on empty page', () => { + beforeAll(done => { + loginPage.loginWith(username).then(done); + }); + + afterAll(done => { + logoutPage.load().then(done); + }); + + it('pagination controls not displayed [C213164]', () => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.PERSONAL_FILES) + .then(() => { + expect(pagination.range.isPresent()).toBe(false); + expect(pagination.maxItems.isPresent()).toBe(false); + expect(pagination.currentPage.isPresent()).toBe(false); + expect(pagination.totalPages.isPresent()).toBe(false); + expect(pagination.previousButton.isPresent()).toBe(false); + expect(pagination.nextButton.isPresent()).toBe(false); + }); + }); + }); + + describe('on single page', () => { + beforeAll(done => { + nodesApi.createFile(file).then(resp => fileId = resp.data.entry.id) + .then(() => loginPage.loginWith(username)) + .then(done); + }); + + afterAll(done => { + Promise.all([ + nodesApi.deleteNodeById(fileId), + logoutPage.load() + ]) + .then(done); + }); + + it('page selector not displayed when having a single page [C213165]', () => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.PERSONAL_FILES) + .then(() => dataTable.waitForHeader()) + .then(() => expect(pagination.pagesButton.isPresent()).toBe(false, 'page selector displayed')); + }); + }); + + describe('on multiple pages', () => { + beforeAll(done => { + nodesApi.createFiles(files, parent) + .then(() => loginPage.loginWith(username)) + .then(done); + }); + + beforeEach(done => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.PERSONAL_FILES) + .then(() => dataTable.waitForHeader()) + .then(() => dataTable.doubleClickOnItemName(parent)) + .then(done); + }); + + afterEach(done => { + browser.actions().mouseMove(browser.$('body'), { x: 0, y: 0 }).click().perform().then(done); + }); + + afterAll(done => { + Promise.all([ + nodesApi.deleteNodes([ parent ]), + logoutPage.load() + ]) + .then(done); + }); + + it('default values [C213157]', () => { + expect(pagination.range.getText()).toContain('1-25 of 101'); + expect(pagination.maxItems.getText()).toContain('25'); + expect(pagination.currentPage.getText()).toContain('Page 1'); + expect(pagination.totalPages.getText()).toContain('of 5'); + expect(pagination.previousButton.isEnabled()).toBe(false, 'Previous button is enabled'); + expect(pagination.nextButton.isEnabled()).toBe(true, 'Next button is not enabled'); + }); + + it('page sizes [C213157]', () => { + pagination.openMaxItemsMenu() + .then(() => { + const [ first, second, third ] = [1, 2, 3] + .map(nth => pagination.menu.getNthItem(nth).getText()); + expect(first).toBe('25'); + expect(second).toBe('50'); + expect(third).toBe('100'); + }) + .then(() => pagination.menu.closeMenu()); + }); + + it('change the page size [C213158]', () => { + pagination.openMaxItemsMenu() + .then(() => pagination.menu.clickMenuItem('50')) + .then(() => dataTable.waitForHeader()) + .then(() => { + expect(pagination.maxItems.getText()).toContain('50'); + expect(pagination.totalPages.getText()).toContain('of 3'); + }) + .then(() => pagination.openMaxItemsMenu()) + .then(() => pagination.menu.clickMenuItem('100')) + .then(() => { + expect(pagination.getText(pagination.maxItems)).toContain('100'); + expect(pagination.getText(pagination.totalPages)).toContain('of 2'); + }) + .then(() => pagination.resetToDefaultPageSize()); + }); + + it('current page menu items', () => { + pagination.openCurrentPageMenu() + .then(() => expect(pagination.menu.getItemsCount()).toBe(5)) + .then(() => pagination.menu.closeMenu()); + }); + + it('change the current page from menu [C260518]', () => { + pagination.openCurrentPageMenu() + .then(() => pagination.menu.clickNthItem(3)) + .then(() => dataTable.waitForHeader()) + .then(() => { + expect(pagination.range.getText()).toContain('51-75 of 101'); + expect(pagination.currentPage.getText()).toContain('Page 3'); + expect(pagination.previousButton.isEnabled()).toBe(true, 'Previous button is not enabled'); + expect(pagination.nextButton.isEnabled()).toBe(true, 'Next button is not enabled'); + expect(dataTable.getRowName('file-60.txt').isPresent()).toBe(true, 'File not found on page'); + }) + + .then(() => pagination.resetToDefaultPageNumber()); + }); + + it('navigate to next page [C213160]', () => { + pagination.clickNext() + .then(() => dataTable.waitForHeader()) + .then(() => { + expect(pagination.range.getText()).toContain('26-50 of 101'); + expect(dataTable.getRowName('file-30.txt').isPresent()).toBe(true, 'File not found on page'); + }) + + .then(() => pagination.resetToDefaultPageNumber()); + }); + + it('navigate to previous page [C213160]', () => { + pagination.openCurrentPageMenu() + .then(() => pagination.menu.clickNthItem(2)) + .then(() => dataTable.waitForHeader()) + .then(() => pagination.clickPrevious()) + .then(() => dataTable.waitForHeader()) + .then(() => { + expect(pagination.range.getText()).toContain('1-25 of 101'); + expect(dataTable.getRowName('file-12.txt').isPresent()).toBe(true, 'File not found on page'); + }) + + .then(() => pagination.resetToDefaultPageNumber()); + }); + + it('Previous button is disabled on first page [C260519]', () => { + expect(pagination.currentPage.getText()).toContain('Page 1'); + expect(pagination.previousButton.isEnabled()).toBe(false, 'Previous button is enabled on first page'); + }); + + it('Next button is disabled on last page [C260519]', () => { + pagination.openCurrentPageMenu() + .then(() => pagination.menu.clickNthItem(5)) + .then(() => { + expect(dataTable.countRows()).toBe(1, 'Incorrect number of items on the last page'); + expect(pagination.currentPage.getText()).toContain('Page 5'); + expect(pagination.nextButton.isEnabled()).toBe(false, 'Next button is enabled on last page'); + }); + }); + }); +}); diff --git a/e2e/suites/pagination/pag-recent-files.test.ts b/e2e/suites/pagination/pag-recent-files.test.ts new file mode 100755 index 000000000..bf3baa066 --- /dev/null +++ b/e2e/suites/pagination/pag-recent-files.test.ts @@ -0,0 +1,230 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { browser } from 'protractor'; +import { SIDEBAR_LABELS } from '../../configs'; +import { LoginPage, LogoutPage, BrowsingPage } from '../../pages/pages'; +import { Utils } from '../../utilities/utils'; +import { RepoClient } from '../../utilities/repo-client/repo-client'; + +describe('Pagination on Recent Files', () => { + const username = `user-${Utils.random()}`; + + const apis = { + admin: new RepoClient(), + user: new RepoClient(username, username) + }; + const { nodes: nodesApi, search: searchApi } = apis.user; + + const loginPage = new LoginPage(); + const logoutPage = new LogoutPage(); + const page = new BrowsingPage(); + const { dataTable, pagination } = page; + + const parent = `parent-${Utils.random()}`; + const files = Array(101) + .fill('file') + .map((name, index): string => `${name}-${index + 1}.txt`); + + const file = `file-${Utils.random()}.txt`; let fileId; + + beforeAll(done => { + apis.admin.people.createUser(username).then(done); + }); + + xit(''); + + describe('on empty page', () => { + beforeAll(done => { + loginPage.loginWith(username).then(done); + }); + + afterAll(done => { + logoutPage.load().then(done); + }); + + it('pagination controls not displayed [C213164]', () => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.RECENT_FILES) + .then(() => { + expect(pagination.range.isPresent()).toBe(false); + expect(pagination.maxItems.isPresent()).toBe(false); + expect(pagination.currentPage.isPresent()).toBe(false); + expect(pagination.totalPages.isPresent()).toBe(false); + expect(pagination.previousButton.isPresent()).toBe(false); + expect(pagination.nextButton.isPresent()).toBe(false); + }); + }); + }); + + describe('on single page', () => { + beforeAll(done => { + nodesApi.createFile(file).then(resp => fileId = resp.data.entry.id) + .then(() => searchApi.waitForApi(username, { expect: 1 })) + .then(() => loginPage.loginWith(username)) + .then(done); + }); + + afterAll(done => { + Promise.all([ + nodesApi.deleteNodeById(fileId), + logoutPage.load() + ]) + .then(done); + }); + + it('page selector not displayed when having a single page [C213165]', () => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.RECENT_FILES) + .then(() => dataTable.waitForHeader()) + .then(() => expect(pagination.pagesButton.isPresent()).toBe(false, 'page selector displayed')); + }); + }); + + describe('on multiple pages', () => { + beforeAll(done => { + nodesApi.createFiles(files, parent) + .then(() => searchApi.waitForApi(username, { expect: 101 })) + + .then(() => loginPage.loginWith(username)) + .then(done); + }); + + beforeEach(done => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.RECENT_FILES) + .then(() => dataTable.waitForHeader()) + .then(done); + }); + + afterEach(done => { + browser.actions().mouseMove(browser.$('body'), { x: 0, y: 0 }).click().perform().then(done); + }); + + afterAll(done => { + Promise.all([ + nodesApi.deleteNodes([ parent ]), + logoutPage.load() + ]) + .then(done); + }); + + it('default values [C213157]', () => { + expect(pagination.range.getText()).toContain('1-25 of 101'); + expect(pagination.maxItems.getText()).toContain('25'); + expect(pagination.currentPage.getText()).toContain('Page 1'); + expect(pagination.totalPages.getText()).toContain('of 5'); + expect(pagination.previousButton.isEnabled()).toBe(false, 'Previous button is enabled'); + expect(pagination.nextButton.isEnabled()).toBe(true, 'Next button is not enabled'); + }); + + it('page sizes [C213157]', () => { + pagination.openMaxItemsMenu() + .then(() => { + const [ first, second, third ] = [1, 2, 3] + .map(nth => pagination.menu.getNthItem(nth).getText()); + expect(first).toBe('25'); + expect(second).toBe('50'); + expect(third).toBe('100'); + }) + .then(() => pagination.menu.closeMenu()); + }); + + it('change the page size [C213158]', () => { + pagination.openMaxItemsMenu() + .then(() => pagination.menu.clickMenuItem('50')) + .then(() => dataTable.waitForHeader()) + .then(() => { + expect(pagination.maxItems.getText()).toContain('50'); + expect(pagination.totalPages.getText()).toContain('of 3'); + }) + .then(() => pagination.openMaxItemsMenu()) + .then(() => pagination.menu.clickMenuItem('100')) + .then(() => { + expect(pagination.getText(pagination.maxItems)).toContain('100'); + expect(pagination.getText(pagination.totalPages)).toContain('of 2'); + }) + .then(() => pagination.resetToDefaultPageSize()); + }); + + it('current page menu items', () => { + pagination.openCurrentPageMenu() + .then(() => expect(pagination.menu.getItemsCount()).toBe(5)) + .then(() => pagination.menu.closeMenu()); + }); + + it('change the current page from menu [C260518]', () => { + pagination.openCurrentPageMenu() + .then(() => pagination.menu.clickNthItem(3)) + .then(() => dataTable.waitForHeader()) + .then(() => { + expect(pagination.range.getText()).toContain('51-75 of 101'); + expect(pagination.currentPage.getText()).toContain('Page 3'); + expect(pagination.previousButton.isEnabled()).toBe(true, 'Previous button is not enabled'); + expect(pagination.nextButton.isEnabled()).toBe(true, 'Next button is not enabled'); + expect(dataTable.getRowName('file-40.txt').isPresent()).toBe(true, 'File not found on page'); + }) + + .then(() => pagination.resetToDefaultPageNumber()); + }); + + it('navigate to next page [C213160]', () => { + pagination.nextButton.click() + .then(() => dataTable.waitForHeader()) + .then(() => { + expect(pagination.range.getText()).toContain('26-50 of 101'); + expect(dataTable.getRowName('file-70.txt').isPresent()).toBe(true, 'File not found on page'); + }) + + .then(() => pagination.resetToDefaultPageNumber()); + }); + + it('navigate to previous page [C213160]', () => { + pagination.openCurrentPageMenu() + .then(() => pagination.menu.clickNthItem(2)) + .then(() => dataTable.waitForHeader()) + .then(() => pagination.previousButton.click()) + .then(() => dataTable.waitForHeader()) + .then(() => { + expect(pagination.range.getText()).toContain('1-25 of 101'); + expect(dataTable.getRowName('file-88.txt').isPresent()).toBe(true, 'File not found on page'); + }) + + .then(() => pagination.resetToDefaultPageNumber()); + }); + + it('Previous button is disabled on first page [C260519]', () => { + expect(pagination.currentPage.getText()).toContain('Page 1'); + expect(pagination.previousButton.isEnabled()).toBe(false, 'Previous button is enabled on first page'); + }); + + it('Next button is disabled on last page [C260519]', () => { + pagination.openCurrentPageMenu() + .then(() => pagination.menu.clickNthItem(5)) + .then(() => { + expect(dataTable.countRows()).toBe(1, 'Incorrect number of items on the last page'); + expect(pagination.currentPage.getText()).toContain('Page 5'); + expect(pagination.nextButton.isEnabled()).toBe(false, 'Next button is enabled on last page'); + }); + }); + }); +}); diff --git a/e2e/suites/pagination/pag-shared-files.test.ts b/e2e/suites/pagination/pag-shared-files.test.ts new file mode 100755 index 000000000..ae0b47511 --- /dev/null +++ b/e2e/suites/pagination/pag-shared-files.test.ts @@ -0,0 +1,236 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { browser } from 'protractor'; +import { SIDEBAR_LABELS } from '../../configs'; +import { LoginPage, LogoutPage, BrowsingPage } from '../../pages/pages'; +import { Utils } from '../../utilities/utils'; +import { RepoClient } from '../../utilities/repo-client/repo-client'; + +describe('Pagination on Shared Files', () => { + const username = `user-${Utils.random()}`; + + const apis = { + admin: new RepoClient(), + user: new RepoClient(username, username) + }; + const { nodes: nodesApi, shared: sharedApi } = apis.user; + + const loginPage = new LoginPage(); + const logoutPage = new LogoutPage(); + const page = new BrowsingPage(); + const { dataTable, pagination } = page; + + const parent = `parent-${Utils.random()}`; + const files = Array(101) + .fill('file') + .map((name, index): string => `${name}-${index + 1}.txt`); + let filesIds; + + const file = `file-${Utils.random()}.txt`; let fileId; + + beforeAll(done => { + apis.admin.people.createUser(username).then(done); + }); + + xit(''); + + describe('on empty page', () => { + beforeAll(done => { + loginPage.loginWith(username).then(done); + }); + + afterAll(done => { + logoutPage.load().then(done); + }); + + it('pagination controls not displayed [C213164]', () => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.SHARED_FILES) + .then(() => { + expect(pagination.range.isPresent()).toBe(false); + expect(pagination.maxItems.isPresent()).toBe(false); + expect(pagination.currentPage.isPresent()).toBe(false); + expect(pagination.totalPages.isPresent()).toBe(false); + expect(pagination.previousButton.isPresent()).toBe(false); + expect(pagination.nextButton.isPresent()).toBe(false); + }); + }); + }); + + describe('on single page', () => { + beforeAll(done => { + nodesApi.createFile(file).then(resp => fileId = resp.data.entry.id) + .then(() => sharedApi.shareFileById(fileId)) + .then(() => sharedApi.waitForApi({ expect: 1 })) + .then(() => loginPage.loginWith(username)) + .then(done); + }); + + afterAll(done => { + Promise.all([ + nodesApi.deleteNodeById(fileId), + logoutPage.load() + ]) + .then(done); + }); + + it('page selector not displayed when having a single page [C213165]', () => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.SHARED_FILES) + .then(() => dataTable.waitForHeader()) + .then(() => expect(pagination.pagesButton.isPresent()).toBe(false, 'page selector displayed')); + }); + }); + + describe('on multiple pages', () => { + beforeAll(done => { + nodesApi.createFiles(files, parent) + .then(resp => filesIds = resp.data.list.entries.map(entries => entries.entry.id)) + + .then(() => sharedApi.shareFilesByIds(filesIds)) + .then(() => sharedApi.waitForApi({ expect: 101 })) + + .then(() => loginPage.loginWith(username)) + .then(done); + }); + + beforeEach(done => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.SHARED_FILES) + .then(() => dataTable.waitForHeader()) + .then(done); + }); + + afterEach(done => { + browser.actions().mouseMove(browser.$('body'), { x: 0, y: 0 }).click().perform().then(done); + }); + + afterAll(done => { + Promise.all([ + nodesApi.deleteNodes([ parent ]), + logoutPage.load() + ]) + .then(done); + }); + + it('default values [C213157]', () => { + expect(pagination.range.getText()).toContain('1-25 of 101'); + expect(pagination.maxItems.getText()).toContain('25'); + expect(pagination.currentPage.getText()).toContain('Page 1'); + expect(pagination.totalPages.getText()).toContain('of 5'); + expect(pagination.previousButton.isEnabled()).toBe(false, 'Previous button is enabled'); + expect(pagination.nextButton.isEnabled()).toBe(true, 'Next button is not enabled'); + }); + + it('page sizes [C213157]', () => { + pagination.openMaxItemsMenu() + .then(() => { + const [ first, second, third ] = [1, 2, 3] + .map(nth => pagination.menu.getNthItem(nth).getText()); + expect(first).toBe('25'); + expect(second).toBe('50'); + expect(third).toBe('100'); + }) + .then(() => pagination.menu.closeMenu()); + }); + + it('change the page size [C213158]', () => { + pagination.openMaxItemsMenu() + .then(() => pagination.menu.clickMenuItem('50')) + .then(() => dataTable.waitForHeader()) + .then(() => { + expect(pagination.maxItems.getText()).toContain('50'); + expect(pagination.totalPages.getText()).toContain('of 3'); + }) + .then(() => pagination.openMaxItemsMenu()) + .then(() => pagination.menu.clickMenuItem('100')) + .then(() => { + expect(pagination.getText(pagination.maxItems)).toContain('100'); + expect(pagination.getText(pagination.totalPages)).toContain('of 2'); + }) + .then(() => pagination.resetToDefaultPageSize()); + }); + + it('current page menu items', () => { + pagination.openCurrentPageMenu() + .then(() => expect(pagination.menu.getItemsCount()).toBe(5)) + .then(() => pagination.menu.closeMenu()); + }); + + it('change the current page from menu [C260518]', () => { + pagination.openCurrentPageMenu() + .then(() => pagination.menu.clickNthItem(3)) + .then(() => dataTable.waitForHeader()) + .then(() => { + expect(pagination.range.getText()).toContain('51-75 of 101'); + expect(pagination.currentPage.getText()).toContain('Page 3'); + expect(pagination.previousButton.isEnabled()).toBe(true, 'Previous button is not enabled'); + expect(pagination.nextButton.isEnabled()).toBe(true, 'Next button is not enabled'); + expect(dataTable.getRowName('file-40.txt').isPresent()) + .toBe(true, 'File not found on page'); + }) + + .then(() => pagination.resetToDefaultPageNumber()); + }); + + it('navigate to next page [C213160]', () => { + pagination.nextButton.click() + .then(() => dataTable.waitForHeader()) + .then(() => { + expect(pagination.range.getText()).toContain('26-50 of 101'); + expect(dataTable.getRowName('file-70.txt').isPresent()).toBe(true, 'File not found on page'); + }) + + .then(() => pagination.resetToDefaultPageNumber()); + }); + + it('navigate to previous page [C213160]', () => { + pagination.openCurrentPageMenu() + .then(() => pagination.menu.clickNthItem(2)) + .then(() => dataTable.waitForHeader()) + .then(() => pagination.previousButton.click()) + .then(() => dataTable.waitForHeader()) + .then(() => { + expect(pagination.range.getText()).toContain('1-25 of 101'); + expect(dataTable.getRowName('file-88.txt').isPresent()).toBe(true, 'File not found on page'); + }) + + .then(() => pagination.resetToDefaultPageNumber()); + }); + + it('Previous button is disabled on first page [C260519]', () => { + expect(pagination.currentPage.getText()).toContain('Page 1'); + expect(pagination.previousButton.isEnabled()).toBe(false, 'Previous button is enabled on first page'); + }); + + it('Next button is disabled on last page [C260519]', () => { + pagination.openCurrentPageMenu() + .then(() => pagination.menu.clickNthItem(5)) + .then(() => { + expect(dataTable.countRows()).toBe(1, 'Incorrect number of items on the last page'); + expect(pagination.currentPage.getText()).toContain('Page 5'); + expect(pagination.nextButton.isEnabled()).toBe(false, 'Next button is enabled on last page'); + }); + }); + }); +}); diff --git a/e2e/suites/pagination/pag-trash.test.ts b/e2e/suites/pagination/pag-trash.test.ts new file mode 100755 index 000000000..35452d2c3 --- /dev/null +++ b/e2e/suites/pagination/pag-trash.test.ts @@ -0,0 +1,233 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { browser } from 'protractor'; +import { SIDEBAR_LABELS } from '../../configs'; +import { LoginPage, LogoutPage, BrowsingPage } from '../../pages/pages'; +import { Utils } from '../../utilities/utils'; +import { RepoClient } from '../../utilities/repo-client/repo-client'; + +describe('Pagination on Trash', () => { + const username = `user-${Utils.random()}`; + + const apis = { + admin: new RepoClient(), + user: new RepoClient(username, username) + }; + const { nodes: nodesApi, trashcan: trashApi } = apis.user; + + const loginPage = new LoginPage(); + const logoutPage = new LogoutPage(); + const page = new BrowsingPage(); + const { dataTable, pagination } = page; + + const filesForDelete = Array(101) + .fill('file') + .map((name, index): string => `${name}-${index + 1}.txt`); + let filesDeletedIds; + + const file = `file-${Utils.random()}.txt`; let fileId; + + beforeAll(done => { + apis.admin.people.createUser(username).then(done); + }); + + xit(''); + + describe('on empty page', () => { + beforeAll(done => { + loginPage.loginWith(username).then(done); + }); + + afterAll(done => { + logoutPage.load().then(done); + }); + + it('pagination controls not displayed [C213164]', () => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.TRASH) + .then(() => { + expect(pagination.range.isPresent()).toBe(false); + expect(pagination.maxItems.isPresent()).toBe(false); + expect(pagination.currentPage.isPresent()).toBe(false); + expect(pagination.totalPages.isPresent()).toBe(false); + expect(pagination.previousButton.isPresent()).toBe(false); + expect(pagination.nextButton.isPresent()).toBe(false); + }); + }); + }); + + describe('on single page', () => { + beforeAll(done => { + nodesApi.createFile(file).then(resp => fileId = resp.data.entry.id) + .then(() => nodesApi.deleteNodeById(fileId, false)) + .then(() => trashApi.waitForApi({ expect: 1 })) + .then(() => loginPage.loginWith(username)) + .then(done); + }); + + afterAll(done => { + Promise.all([ + trashApi.emptyTrash(), + logoutPage.load() + ]) + .then(done); + }); + + it('page selector not displayed when having a single page [C213165]', () => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.TRASH) + .then(() => dataTable.waitForHeader()) + .then(() => expect(pagination.pagesButton.isPresent()).toBe(false, 'page selector displayed')); + }); + }); + + describe('on multiple pages', () => { + beforeAll(done => { + nodesApi.createFiles(filesForDelete) + .then(resp => filesDeletedIds = resp.data.list.entries.map(entries => entries.entry.id)) + .then(() => nodesApi.deleteNodesById(filesDeletedIds, false)) + .then(() => trashApi.waitForApi({expect: 101})) + + .then(() => loginPage.loginWith(username)) + .then(done); + }); + + beforeEach(done => { + page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.TRASH) + .then(() => dataTable.waitForHeader()) + .then(done); + }); + + afterEach(done => { + browser.actions().mouseMove(browser.$('body'), { x: 0, y: 0 }).click().perform().then(done); + }); + + afterAll(done => { + Promise.all([ + trashApi.emptyTrash(), + logoutPage.load() + ]) + .then(done); + }); + + it('default values [C213157]', () => { + expect(pagination.range.getText()).toContain('1-25 of 101'); + expect(pagination.maxItems.getText()).toContain('25'); + expect(pagination.currentPage.getText()).toContain('Page 1'); + expect(pagination.totalPages.getText()).toContain('of 5'); + expect(pagination.previousButton.isEnabled()).toBe(false, 'Previous button is enabled'); + expect(pagination.nextButton.isEnabled()).toBe(true, 'Next button is not enabled'); + }); + + it('page sizes [C213157]', () => { + pagination.openMaxItemsMenu() + .then(() => { + const [ first, second, third ] = [1, 2, 3] + .map(nth => pagination.menu.getNthItem(nth).getText()); + expect(first).toBe('25'); + expect(second).toBe('50'); + expect(third).toBe('100'); + }) + .then(() => pagination.menu.closeMenu()); + }); + + it('change the page size [C213158]', () => { + pagination.openMaxItemsMenu() + .then(() => pagination.menu.clickMenuItem('50')) + .then(() => dataTable.waitForHeader()) + .then(() => { + expect(pagination.maxItems.getText()).toContain('50'); + expect(pagination.totalPages.getText()).toContain('of 3'); + }) + .then(() => pagination.openMaxItemsMenu()) + .then(() => pagination.menu.clickMenuItem('100')) + .then(() => { + expect(pagination.getText(pagination.maxItems)).toContain('100'); + expect(pagination.getText(pagination.totalPages)).toContain('of 2'); + }) + .then(() => pagination.resetToDefaultPageSize()); + }); + + it('current page menu items', () => { + pagination.openCurrentPageMenu() + .then(() => expect(pagination.menu.getItemsCount()).toBe(5)) + .then(() => pagination.menu.closeMenu()); + }); + + it('change the current page from menu [C260518]', () => { + pagination.openCurrentPageMenu() + .then(() => pagination.menu.clickNthItem(3)) + .then(() => dataTable.waitForHeader()) + .then(() => { + expect(pagination.range.getText()).toContain('51-75 of 101'); + expect(pagination.currentPage.getText()).toContain('Page 3'); + expect(pagination.previousButton.isEnabled()).toBe(true, 'Previous button is not enabled'); + expect(pagination.nextButton.isEnabled()).toBe(true, 'Next button is not enabled'); + expect(dataTable.getRowName('file-40.txt').isPresent()).toBe(true, 'File not found on page'); + }) + + .then(() => pagination.resetToDefaultPageNumber()); + }); + + it('navigate to next page [C213160]', () => { + pagination.nextButton.click() + .then(() => dataTable.waitForHeader()) + .then(() => { + expect(pagination.range.getText()).toContain('26-50 of 101'); + expect(dataTable.getRowName('file-70.txt').isPresent()).toBe(true, 'File not found on page'); + }) + + .then(() => pagination.resetToDefaultPageNumber()); + }); + + it('navigate to previous page [C213160]', () => { + pagination.openCurrentPageMenu() + .then(() => pagination.menu.clickNthItem(2)) + .then(() => dataTable.waitForHeader()) + .then(() => pagination.previousButton.click()) + .then(() => dataTable.waitForHeader()) + .then(() => { + expect(pagination.range.getText()).toContain('1-25 of 101'); + expect(dataTable.getRowName('file-88.txt').isPresent()).toBe(true, 'File not found on page'); + }) + + .then(() => pagination.resetToDefaultPageNumber()); + }); + + it('Previous button is disabled on first page [C260519]', () => { + expect(pagination.currentPage.getText()).toContain('Page 1'); + expect(pagination.previousButton.isEnabled()).toBe(false, 'Previous button is enabled on first page'); + }); + + it('Next button is disabled on last page [C260519]', () => { + pagination.openCurrentPageMenu() + .then(() => pagination.menu.clickNthItem(5)) + .then(() => { + expect(dataTable.countRows()).toBe(1, 'Incorrect number of items on the last page'); + expect(pagination.currentPage.getText()).toContain('Page 5'); + expect(pagination.nextButton.isEnabled()).toBe(false, 'Next button is enabled on last page'); + }); + }); + }); +}); diff --git a/e2e/tsconfig.e2e.json b/e2e/tsconfig.e2e.json old mode 100644 new mode 100755 diff --git a/e2e/utilities/repo-client/apis/favorites/favorites-api.ts b/e2e/utilities/repo-client/apis/favorites/favorites-api.ts new file mode 100755 index 000000000..e2de4f8c4 --- /dev/null +++ b/e2e/utilities/repo-client/apis/favorites/favorites-api.ts @@ -0,0 +1,118 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { promise } from 'protractor'; +import { RepoApi } from '../repo-api'; +import { NodesApi } from '../nodes/nodes-api'; +import { RepoClient } from './../../repo-client'; +import { Utils } from '../../../../utilities/utils'; + +export class FavoritesApi extends RepoApi { + + addFavorite(api: RepoClient, nodeType: string, name: string): Promise { + return api.nodes.getNodeByPath(name) + .then((response) => { + const { id } = response.data.entry; + return ([{ + target: { + [nodeType]: { + guid: id + } + } + }]); + }) + .then((data) => { + return this.post(`/people/-me-/favorites`, { data }); + }) + .catch(this.handleError); + } + + addFavoriteById(nodeType: 'file' | 'folder', id: string): Promise { + const data = [{ + target: { + [nodeType]: { + guid: id + } + } + }]; + return this + .post(`/people/-me-/favorites`, { data }) + .catch(this.handleError); + } + + addFavoritesByIds(nodeType: 'file' | 'folder', ids: string[]): Promise { + return ids.reduce((previous, current) => ( + previous.then(() => this.addFavoriteById(nodeType, current)) + ), Promise.resolve()); + } + + getFavorites(): Promise { + return this + .get('/people/-me-/favorites') + .catch(this.handleError); + } + + getFavoriteById(nodeId: string): Promise { + return this + .get(`/people/-me-/favorites/${nodeId}`) + .catch(this.handleError); + } + + isFavorite(nodeId: string) { + return this.getFavorites() + .then(resp => JSON.stringify(resp.data.list.entries).includes(nodeId)); + } + + removeFavorite(api: RepoClient, nodeType: string, name: string): Promise { + return api.nodes.getNodeByPath(name) + .then((response) => { + const { id } = response.data.entry; + return this.delete(`/people/-me-/favorites/${id}`); + }) + .catch(this.handleError); + } + + removeFavoriteById(nodeId: string) { + return this + .delete(`/people/-me-/favorites/${nodeId}`) + .catch(this.handleError); + } + + waitForApi(data) { + const favoriteFiles = () => { + return this.getFavorites() + .then(response => response.data.list.pagination.totalItems) + .then(totalItems => { + if ( totalItems < data.expect) { + return Promise.reject(totalItems); + } else { + return Promise.resolve(totalItems); + } + }); + }; + + return Utils.retryCall(favoriteFiles); + } +} diff --git a/e2e/utilities/repo-client/apis/nodes/node-body-create.ts b/e2e/utilities/repo-client/apis/nodes/node-body-create.ts new file mode 100755 index 000000000..d82201ab2 --- /dev/null +++ b/e2e/utilities/repo-client/apis/nodes/node-body-create.ts @@ -0,0 +1,38 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +export const NODE_TYPE_FILE = 'cm:content'; +export const NODE_TYPE_FOLDER = 'cm:folder'; +export const NODE_TITLE = 'cm:title'; +export const NODE_DESCRIPTION = 'cm:description'; + +export class NodeBodyCreate { + constructor( + public name: string, + public nodeType: string, + public relativePath: string = '/', + public properties?: any[] + ) {} +} diff --git a/e2e/utilities/repo-client/apis/nodes/node-content-tree.ts b/e2e/utilities/repo-client/apis/nodes/node-content-tree.ts new file mode 100755 index 000000000..65915d480 --- /dev/null +++ b/e2e/utilities/repo-client/apis/nodes/node-content-tree.ts @@ -0,0 +1,85 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { NodeBodyCreate, NODE_TYPE_FILE, NODE_TYPE_FOLDER, NODE_TITLE, NODE_DESCRIPTION } from './node-body-create'; + +export interface NodeContentTree { + name?: string; + files?: string[]; + folders?: (string|NodeContentTree)[]; + title?: string; + description?: string; +} + +export function flattenNodeContentTree(content: NodeContentTree, relativePath: string = '/'): NodeBodyCreate[] { + const { name, files, folders, title, description } = content; + let data: NodeBodyCreate[] = []; + let properties: any; + + properties = { + [NODE_TITLE]: title, + [NODE_DESCRIPTION]: description + }; + + if (name) { + data = data.concat([{ + nodeType: NODE_TYPE_FOLDER, + name, + relativePath, + properties + }]); + + relativePath = (relativePath === '/') + ? `/${name}` + : `${relativePath}/${name}`; + } + + if (folders) { + const foldersData: NodeBodyCreate[] = folders + .map((folder: (string|NodeContentTree)): NodeBodyCreate[] => { + const folderData: NodeContentTree = (typeof folder === 'string') + ? { name: folder } + : folder; + + return flattenNodeContentTree(folderData, relativePath); + }) + .reduce((nodesData: NodeBodyCreate[], folderData: NodeBodyCreate[]) => nodesData.concat(folderData), []); + + data = data.concat(foldersData); + } + + if (files) { + const filesData: NodeBodyCreate[] = files + .map((filename: string): NodeBodyCreate => ({ + nodeType: NODE_TYPE_FILE, + name: filename, + relativePath + })); + + data = data.concat(filesData); + } + + return data; +} diff --git a/e2e/utilities/repo-client/apis/nodes/nodes-api.ts b/e2e/utilities/repo-client/apis/nodes/nodes-api.ts new file mode 100755 index 000000000..bde0a2a11 --- /dev/null +++ b/e2e/utilities/repo-client/apis/nodes/nodes-api.ts @@ -0,0 +1,182 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { RepoApi } from '../repo-api'; +import { NodeBodyCreate, NODE_TYPE_FILE, NODE_TYPE_FOLDER } from './node-body-create'; +import { NodeContentTree, flattenNodeContentTree } from './node-content-tree'; + +export class NodesApi extends RepoApi { + // nodes + getNodeByPath(relativePath: string = '/'): Promise { + return this + .get(`/nodes/-my-`, { parameters: { relativePath } }) + .catch(this.handleError); + } + + getNodeById(id: string): Promise { + return this + .get(`/nodes/${id}`) + .catch(this.handleError); + } + + getNodeDescription(name: string, relativePath: string = '/') { + relativePath = (relativePath === '/') + ? `${name}` + : `${relativePath}/${name}`; + + return this.getNodeByPath(`${relativePath}`) + .then(response => response.data.entry.properties['cm:description']); + } + + deleteNodeById(id: string, permanent: boolean = true): Promise { + return this + .delete(`/nodes/${id}?permanent=${permanent}`) + .catch(this.handleError); + } + + deleteNodeByPath(path: string, permanent: boolean = true): Promise { + return this + .getNodeByPath(path) + .then((response: any): string => response.data.entry.id) + .then((id: string): any => this.deleteNodeById(id, permanent)) + .catch(this.handleError); + } + + deleteNodes(names: string[], relativePath: string = '', permanent: boolean = true): Promise { + return names.reduce((previous, current) => ( + previous.then(() => this.deleteNodeByPath(`${relativePath}/${current}`, permanent)) + ), Promise.resolve()); + } + + deleteNodesById(ids: string[], permanent: boolean = true): Promise { + return ids.reduce((previous, current) => ( + previous.then(() => this.deleteNodeById(current, permanent)) + ), Promise.resolve()); + } + + // children + getNodeChildren(nodeId: string): Promise { + return this + .get(`/nodes/${nodeId}/children`) + .catch(this.handleError); + } + + createNode(nodeType: string, name: string, parentId: string = '-my-', title: string = '', description: string = ''): Promise { + const data = { + name: name, + nodeType: nodeType, + properties: { + 'cm:title': title, 'cm:description': description + } + }; + + return this + .post(`/nodes/${parentId}/children`, { data }) + .catch(this.handleError); + } + + createFile(name: string, parentId: string = '-my-', title: string = '', description: string = ''): Promise { + return this.createNode('cm:content', name, parentId, title, description); + } + + createFolder(name: string, parentId: string = '-my-', title: string = '', description: string = ''): Promise { + return this.createNode('cm:folder', name, parentId, title, description); + } + + createChildren(data: NodeBodyCreate[]): Promise { + return this + .post(`/nodes/-my-/children`, { data }) + .catch(this.handleError); + } + + createContent(content: NodeContentTree, relativePath: string = '/'): Promise { + return this.createChildren(flattenNodeContentTree(content, relativePath)); + } + + createFolders(names: string[], relativePath: string = '/'): Promise { + return this.createContent({ folders: names }, relativePath); + } + + createFiles(names: string[], relativePath: string = '/'): Promise { + return this.createContent({ files: names }, relativePath); + } + + // node content + getNodeContent(nodeId: string): Promise { + return this + .get(`/nodes/${nodeId}/content`) + .catch(this.handleError); + } + + editNodeContent(nodeId: string, content: string): Promise { + return this + .put(`/nodes/${nodeId}/content`, { data: content }) + .catch(this.handleError); + } + + renameNode(nodeId: string, newName: string): Promise { + return this + .put(`/nodes/${nodeId}`, { data: { name: newName } }) + .catch(this.handleError); + } + + // node permissions + setGranularPermission(nodeId: string, inheritPermissions: boolean = false, username: string, role: string): Promise { + const data = { + permissions: { + isInheritanceEnabled: inheritPermissions, + locallySet: [ + { + authorityId: username, + name: role + } + ] + } + }; + + return this + .put(`/nodes/${nodeId}`, { data }) + .catch(this.handleError); + } + + getNodePermissions(nodeId: string): Promise { + return this + .get(`/nodes/${nodeId}?include=permissions`) + .catch(this.handleError); + } + + // lock node + lockFile(nodeId: string, lockType: string = 'FULL') { + return this + .post(`/nodes/${nodeId}/lock?include=isLocked`, { data: { 'type': lockType } }) + .catch(this.handleError); + } + + unlockFile(nodeId: string) { + return this + .post(`/nodes/${nodeId}/unlock`) + .catch(this.handleError); + } +} diff --git a/e2e/utilities/repo-client/apis/people/people-api-models.ts b/e2e/utilities/repo-client/apis/people/people-api-models.ts new file mode 100755 index 000000000..93be7980c --- /dev/null +++ b/e2e/utilities/repo-client/apis/people/people-api-models.ts @@ -0,0 +1,43 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +export class Person { + id?: string; + password?: string; + firstName?: string; + lastName?: string; + email?: string; + properties?: any; + + constructor(username: string, password: string, details: Person) { + this.id = username; + this.password = password || username; + this.firstName = username; + this.lastName = username; + this.email = `${username}@alfresco.com`; + + Object.assign(this, details); + } +} diff --git a/e2e/utilities/repo-client/apis/people/people-api.ts b/e2e/utilities/repo-client/apis/people/people-api.ts new file mode 100755 index 000000000..2ede55982 --- /dev/null +++ b/e2e/utilities/repo-client/apis/people/people-api.ts @@ -0,0 +1,70 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { RepoApi } from '../repo-api'; +import { Person } from './people-api-models'; + +export class PeopleApi extends RepoApi { + getUser(username: string) { + return this + .get(`/people/${username}`) + .catch(this.handleError); + } + + updateUser(username: string, details?: Person): Promise { + if (details.id) { + delete details.id; + } + + return this + .put(`/people/${username}`, { data: details }) + .catch(this.handleError); + } + + createUser(username: string, password?: string, details?: Person): Promise { + const person: Person = new Person(username, password, details); + const onSuccess = (response) => response; + const onError = (response) => { + return (response.statusCode === 409) + ? Promise.resolve(this.updateUser(username, person)) + : Promise.reject(response); + }; + + return this + .post(`/people`, { data: person }) + .then(onSuccess, onError) + .catch(this.handleError); + } + + disableUser(username: string): Promise { + return this.put(`/people/${username}`, { data: { enabled: false } }) + .catch(this.handleError); + } + + changePassword(username: string, newPassword: string) { + return this.put(`/people/${username}`, { data: { password: newPassword } }) + .catch(this.handleError); + } +} diff --git a/e2e/utilities/repo-client/apis/repo-api.ts b/e2e/utilities/repo-client/apis/repo-api.ts new file mode 100755 index 000000000..ee61ce6df --- /dev/null +++ b/e2e/utilities/repo-client/apis/repo-api.ts @@ -0,0 +1,71 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { RestClient, RestClientArgs, RestClientResponse } from '../../rest-client/rest-client'; +import { RepoClientAuth, RepoClientConfig } from '../repo-client-models'; + +export abstract class RepoApi { + private client: RestClient; + private defaults: RepoClientConfig = new RepoClientConfig(); + + constructor( + auth: RepoClientAuth = new RepoClientAuth(), + private config?: RepoClientConfig + ) { + const { username, password } = auth; + + this.client = new RestClient(username, password); + } + + private createEndpointUri(endpoint: string, apiDefinition: string = 'alfresco'): string { + const { defaults, config } = this; + const { host, tenant } = Object.assign(defaults, config); + + return `${host}/alfresco/api/${tenant}/public/${apiDefinition}/versions/1${endpoint}`; + } + + protected handleError(response: RestClientResponse) { + const { request: { method, path, data }, data: error } = response; + + console.log(`ERROR on ${method}\n${path}\n${data}`); + console.log(error); + } + + protected get(endpoint: string, args: RestClientArgs = {}, apiDefinition: string = 'alfresco') { + return this.client.get(this.createEndpointUri(endpoint, apiDefinition), args); + } + + protected post(endpoint: string, args: RestClientArgs = {}, apiDefinition: string = 'alfresco') { + return this.client.post(this.createEndpointUri(endpoint, apiDefinition), args); + } + + protected put(endpoint: string, args: RestClientArgs = {}, apiDefinition: string = 'alfresco') { + return this.client.put(this.createEndpointUri(endpoint, apiDefinition), args); + } + + protected delete(endpoint: string, args: RestClientArgs = {}, apiDefinition: string = 'alfresco') { + return this.client.delete(this.createEndpointUri(endpoint, apiDefinition), args); + } +} diff --git a/e2e/utilities/repo-client/apis/search/search-api.ts b/e2e/utilities/repo-client/apis/search/search-api.ts new file mode 100755 index 000000000..bb09fabd4 --- /dev/null +++ b/e2e/utilities/repo-client/apis/search/search-api.ts @@ -0,0 +1,71 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { RepoApi } from '../repo-api'; +import { Utils } from '../../../../utilities/utils'; + +export class SearchApi extends RepoApi { + apiDefinition = 'search'; + + search(data: any[]): Promise { + return this + .post(`/search`, { data }, this.apiDefinition) + .catch(this.handleError); + } + + queryRecentFiles(username: string): Promise { + const data = { + query: { + query: '*', + language: 'afts' + }, + filterQueries: [ + { query: `cm:modified:[NOW/DAY-30DAYS TO NOW/DAY+1DAY]` }, + { query: `cm:modifier:${username} OR cm:creator:${username}` }, + { query: `TYPE:"content" AND -TYPE:"app:filelink" AND -TYPE:"fm:post"` } + ] + }; + + return this + .post(`/search`, { data }, this.apiDefinition) + .catch(this.handleError); + } + + waitForApi(username, data) { + const recentFiles = () => { + return this.queryRecentFiles(username) + .then(response => response.data.list.pagination.totalItems) + .then(totalItems => { + if ( totalItems < data.expect) { + return Promise.reject(totalItems); + } else { + return Promise.resolve(totalItems); + } + }); + }; + + return Utils.retryCall(recentFiles); + } +} diff --git a/e2e/utilities/repo-client/apis/shared-links/shared-links-api.ts b/e2e/utilities/repo-client/apis/shared-links/shared-links-api.ts new file mode 100755 index 000000000..6140dcd74 --- /dev/null +++ b/e2e/utilities/repo-client/apis/shared-links/shared-links-api.ts @@ -0,0 +1,79 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { RepoApi } from '../repo-api'; +import { NodesApi } from '../nodes/nodes-api'; +import { RepoClient } from './../../repo-client'; +import { Utils } from '../../../../utilities/utils'; + +export class SharedLinksApi extends RepoApi { + + shareFileById(id: string): Promise { + const data = [{ nodeId: id }]; + + return this.post(`/shared-links`, { data }) + .catch(this.handleError); + } + + shareFilesByIds(ids: string[]): Promise { + return ids.reduce((previous, current) => ( + previous.then(() => this.shareFileById(current)) + ), Promise.resolve()); + } + + getSharedIdOfNode(name: string) { + return this.getSharedLinks() + .then(resp => resp.data.list.entries.find(entries => entries.entry.name === name)) + .then(resp => resp.entry.id) + .catch(this.handleError); + } + + unshareFile(name: string) { + return this.getSharedIdOfNode(name) + .then(id => this.delete(`/shared-links/${id}`)) + .catch(this.handleError); + } + + getSharedLinks(): Promise { + return this.get(`/shared-links`) + .catch(this.handleError); + } + + waitForApi(data) { + const sharedFiles = () => { + return this.getSharedLinks() + .then(response => response.data.list.pagination.totalItems) + .then(totalItems => { + if ( totalItems < data.expect) { + return Promise.reject(totalItems); + } else { + return Promise.resolve(totalItems); + } + }); + }; + + return Utils.retryCall(sharedFiles); + } +} diff --git a/e2e/utilities/repo-client/apis/sites/sites-api-models.ts b/e2e/utilities/repo-client/apis/sites/sites-api-models.ts new file mode 100755 index 000000000..598aad900 --- /dev/null +++ b/e2e/utilities/repo-client/apis/sites/sites-api-models.ts @@ -0,0 +1,42 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { SITE_VISIBILITY } from '../../../../configs'; + +export class Site { + title?: string; + visibility?: string = SITE_VISIBILITY.PUBLIC; + id?: string; + description?: string; + + constructor(title: string, visibility: string, details: Site) { + this.title = title; + this.visibility = visibility; + this.id = title; + this.description = `${title} description`; + + Object.assign(this, details); + } +} diff --git a/e2e/utilities/repo-client/apis/sites/sites-api.ts b/e2e/utilities/repo-client/apis/sites/sites-api.ts new file mode 100755 index 000000000..a824893ba --- /dev/null +++ b/e2e/utilities/repo-client/apis/sites/sites-api.ts @@ -0,0 +1,124 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { RepoApi } from '../repo-api'; +import { Site } from './sites-api-models'; + +export class SitesApi extends RepoApi { + getSite(id: string): Promise { + return this + .get(`/sites/${id}`) + .catch(this.handleError); + } + + getSiteContainers(siteId: string): Promise { + return this + .get(`/sites/${siteId}/containers`) + .then(resp => resp.data.list.entries) + .catch(this.handleError); + } + + getDocLibId(siteId: string) { + return this.getSiteContainers(siteId) + .then(resp => resp[0].entry.id) + .catch(this.handleError); + } + + updateSite(id: string, details?: Site): Promise { + if (details.id) { + delete details.id; + } + + return this + .put(`/sites/${id}`, { data: details }) + .catch(this.handleError); + } + + createOrUpdateSite(title: string, visibility: string, details?: Site): Promise { + const site: Site = new Site(title, visibility, details); + const onSuccess = (response) => response; + const onError = (response) => { + return (response.statusCode === 409) + ? Promise.resolve(this.updateSite(site.id, site)) + : Promise.reject(response); + }; + + return this + .post(`/sites`, { data: site }) + .then(onSuccess, onError) + .catch(this.handleError); + } + + createSite(title: string, visibility: string, details?: Site): Promise { + const site: Site = new Site(title, visibility, details); + return this + .post(`/sites`, { data: site }) + .catch(this.handleError); + } + + createSites(titles: string[], visibility: string): Promise { + return titles.reduce((previous, current) => ( + previous.then(() => this.createSite(current, visibility)) + ), Promise.resolve()); + } + + deleteSite(id: string, permanent: boolean = true): Promise { + return this + .delete(`/sites/${id}?permanent=${permanent}`) + .catch(this.handleError); + } + + deleteSites(ids: string[], permanent: boolean = true): Promise { + return ids.reduce((previous, current) => ( + previous.then(() => this.deleteSite(current)) + ), Promise.resolve()); + } + + updateSiteMember(siteId: string, userId: string, role: string): Promise { + return this + .put(`/sites/${siteId}/members/${userId}`, { data: { role } }) + .catch(this.handleError); + } + + addSiteMember(siteId: string, userId: string, role: string): Promise { + const onSuccess = (response) => response; + const onError = (response) => { + return (response.statusCode === 409) + ? Promise.resolve(this.updateSiteMember(siteId, userId, role)) + : Promise.reject(response); + }; + + return this + .post(`/sites/${siteId}/members`, { data: { role, id: userId } }) + .then(onSuccess, onError) + .catch(this.handleError); + } + + deleteSiteMember(siteId: string, userId: string): Promise { + return this + .delete(`/sites/${siteId}/members/${userId}`) + .catch(this.handleError); + } +} diff --git a/e2e/utilities/repo-client/apis/trashcan/trashcan-api.ts b/e2e/utilities/repo-client/apis/trashcan/trashcan-api.ts new file mode 100755 index 000000000..0f94d5ee6 --- /dev/null +++ b/e2e/utilities/repo-client/apis/trashcan/trashcan-api.ts @@ -0,0 +1,76 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { RepoApi } from '../repo-api'; +import { Utils } from '../../../../utilities/utils'; + +export class TrashcanApi extends RepoApi { + permanentlyDelete(id: string): Promise { + return this + .delete(`/deleted-nodes/${id}`) + .catch(this.handleError); + } + + restore(id: string) { + return this + .post(`/deleted-nodes/${id}/restore`) + .catch(this.handleError); + } + + getDeletedNodes(): Promise { + return this + .get(`/deleted-nodes?maxItems=1000`) + .catch(this.handleError); + } + + emptyTrash(): Promise { + return this.getDeletedNodes() + .then(resp => { + return resp.data.list.entries.map(entries => entries.entry.id); + }) + .then(ids => { + return ids.reduce((previous, current) => ( + previous.then(() => this.permanentlyDelete(current)) + ), Promise.resolve()); + }) + .catch(this.handleError); + } + + waitForApi(data) { + const deletedFiles = () => { + return this.getDeletedNodes() + .then(response => response.data.list.pagination.totalItems) + .then(totalItems => { + if ( totalItems < data.expect) { + return Promise.reject(totalItems); + } else { + return Promise.resolve(totalItems); + } + }); + }; + + return Utils.retryCall(deletedFiles); + } +} diff --git a/e2e/utilities/repo-client/repo-client-models.ts b/e2e/utilities/repo-client/repo-client-models.ts new file mode 100755 index 000000000..a352eb882 --- /dev/null +++ b/e2e/utilities/repo-client/repo-client-models.ts @@ -0,0 +1,46 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { + ADMIN_USERNAME, + ADMIN_PASSWORD, + REPO_API_HOST, + REPO_API_TENANT +} from '../../configs'; + +export class RepoClientAuth { + static DEFAULT_USERNAME: string = ADMIN_USERNAME; + static DEFAULT_PASSWORD: string = ADMIN_PASSWORD; + + constructor( + public username: string = RepoClientAuth.DEFAULT_USERNAME, + public password: string = RepoClientAuth.DEFAULT_PASSWORD + ) {} +} + +export class RepoClientConfig { + host?: string = REPO_API_HOST; + tenant?: string = REPO_API_TENANT; +} diff --git a/e2e/utilities/repo-client/repo-client.ts b/e2e/utilities/repo-client/repo-client.ts new file mode 100755 index 000000000..e3ffc0107 --- /dev/null +++ b/e2e/utilities/repo-client/repo-client.ts @@ -0,0 +1,59 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { RepoClientAuth, RepoClientConfig } from './repo-client-models'; + +import { PeopleApi } from './apis/people/people-api'; +import { NodesApi } from './apis/nodes/nodes-api'; +import { SitesApi } from './apis/sites/sites-api'; +import { FavoritesApi } from './apis/favorites/favorites-api'; +import { SharedLinksApi } from './apis/shared-links/shared-links-api'; +import { TrashcanApi } from './apis/trashcan/trashcan-api'; +import { SearchApi } from './apis/search/search-api'; + +export class RepoClient { + public people: PeopleApi = new PeopleApi(this.auth, this.config); + public nodes: NodesApi = new NodesApi(this.auth, this.config); + public sites: SitesApi = new SitesApi(this.auth, this.config); + public favorites: FavoritesApi = new FavoritesApi(this.auth, this.config); + public shared: SharedLinksApi = new SharedLinksApi(this.auth, this.config); + public trashcan: TrashcanApi = new TrashcanApi(this.auth, this.config); + public search: SearchApi = new SearchApi(this.auth, this.config); + + constructor( + private username: string = RepoClientAuth.DEFAULT_USERNAME, + private password: string = RepoClientAuth.DEFAULT_PASSWORD, + private config?: RepoClientConfig + ) {} + + private get auth(): RepoClientAuth { + const { username, password } = this; + return { username, password }; + } +} + +export * from './apis/nodes/node-body-create'; +export * from './apis/nodes/node-content-tree'; +export * from './apis/nodes/nodes-api'; diff --git a/e2e/utilities/reporters/console/console-logger.ts b/e2e/utilities/reporters/console/console-logger.ts new file mode 100755 index 000000000..76d6683e5 --- /dev/null +++ b/e2e/utilities/reporters/console/console-logger.ts @@ -0,0 +1,79 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +/* tslint:disable */ +const chalk = require('chalk'); +/* tslint:enable */ + +export const log = { + i: 0, + + get indentation(): string { + return new Array(this.i).fill(' ').join(''); + }, + + indent() { + this.i++; + return this; + }, + + unindent() { + this.i--; + return this; + }, + + log(message: string = '', options: any = { ignoreIndentation: false }) { + const indentation = (!options.ignoreIndentation) + ? this.indentation + : ''; + + console.log(`${indentation}${message}`); + + return this; + }, + + blank() { + return this.log(); + }, + + info(message: string = '', options: any = { bold: false, title: false }) { + const { bold } = options; + const style = (bold ? chalk.bold : chalk).gray; + + return this.log(style(message), options); + }, + + success(message: string = '', options: any = { bold: false }) { + const style = options.bold ? chalk.bold.green : chalk.green; + + return this.log(style(message), options); + }, + + error(message: string = '', options: any = { bold: false }) { + const style = options.bold ? chalk.bold.red : chalk.red; + + return this.log(style(message), options); + } +}; diff --git a/e2e/utilities/reporters/console/console.ts b/e2e/utilities/reporters/console/console.ts new file mode 100755 index 000000000..ad08110be --- /dev/null +++ b/e2e/utilities/reporters/console/console.ts @@ -0,0 +1,90 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { log } from './console-logger'; + +const errors = []; + +export const consoleReporter = { + jasmineStarted(suiteInfo) { + log.blank().info( + `Running ${suiteInfo.totalSpecsDefined} tests`, + { bold: true, title: true } + ).blank(); + }, + + suiteStarted(suite) { + log.info(suite.description).indent(); + }, + + specDone: (spec) => { + const { + status, + description, + failedExpectations + } = spec; + + if (status === 'passed') { + log.success(`∙ ${description}`); + } + + if (status === 'failed') { + log.error(`✕ ${description}`, { bold: true }); + + errors.push(spec); + + failedExpectations.forEach((failed) => { + log.error(` ${failed.message}`); + }); + } + }, + + suiteDone: (result) => { + log.unindent(); + }, + + jasmineDone: (result) => { + if (!!errors.length) { + log .blank() + .blank() + .info(`${errors.length} failing tests`, { bold: true, title: true }); + + errors.forEach(error => { + log .blank() + .error(`✕ ${error.fullName}`, { bold: true }); + + error.failedExpectations.forEach(failed => { + log .info(`${failed.message}`) + .blank() + .error(`${failed.stack}`); + }); + }); + } else { + log.success(`All tests passed!`, { bold: true }); + } + + log.blank().blank(); + } +}; diff --git a/e2e/utilities/rest-client/rest-client-models.ts b/e2e/utilities/rest-client/rest-client-models.ts new file mode 100755 index 000000000..6b5098d40 --- /dev/null +++ b/e2e/utilities/rest-client/rest-client-models.ts @@ -0,0 +1,63 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +interface RequestConfig { + timeout?: number; + noDelay?: boolean; + keepAlive?: boolean; + keepAliveDelay?: number; +} + +interface ResponseConfig { + timeout?: number; +} + +interface ResponseRequest { + method: string; + path: string; + data: string; +} + +export interface NodeRestClient { + get(uri: string, callback: Function): Function; + post(uri: string, callback: Function): Function; + put(uri: string, callback: Function): Function; + delete(uri: string, callback: Function): Function; +} + +export interface RestClientArgs { + data?: any; + parameters?: any; + headers?: any; + requestConfig?: RequestConfig; + responseConfig?: ResponseConfig; +} + +export interface RestClientResponse { + request: ResponseRequest; + data: any; + statusMessage: string; + statusCode: number; +} diff --git a/e2e/utilities/rest-client/rest-client.ts b/e2e/utilities/rest-client/rest-client.ts new file mode 100755 index 000000000..49b724a9b --- /dev/null +++ b/e2e/utilities/rest-client/rest-client.ts @@ -0,0 +1,89 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { Client } from 'node-rest-client'; +import { NodeRestClient, RestClientArgs, RestClientResponse } from './rest-client-models'; + +export * from './rest-client-models'; + +export class RestClient { + private static DEFAULT_HEADERS = { + 'Content-Type': 'application/json', + 'Accept': 'application/json' + }; + + private client: NodeRestClient; + + constructor(user: string, password: string) { + this.client = (new Client({ user, password })); + } + + get(uri: string, args: RestClientArgs = {}): Promise { + return this.promisify('get', uri, args); + } + + post(uri: string, args: RestClientArgs = {}): Promise { + return this.promisify('post', uri, args); + } + + put(uri: string, args: RestClientArgs = {}): Promise { + return this.promisify('put', uri, args); + } + + delete(uri: string, args: RestClientArgs = {}): Promise { + return this.promisify('delete', uri, args); + } + + private createArgs(args: RestClientArgs = {}): RestClientArgs { + const data = JSON.stringify(args.data); + + return Object.assign({}, RestClient.DEFAULT_HEADERS, args, { data }); + } + + private promisify(fnName: string, uri: string, args: RestClientArgs): Promise { + const fn: Function = this.client[fnName]; + const fnArgs = [ encodeURI(uri), this.createArgs(args) ]; + + return new Promise((resolve, reject) => { + const fnCallback = (data, rawResponse) => { + const { + statusCode, statusMessage, + req: { method, path } + } = rawResponse; + + const response: RestClientResponse = { + data, statusCode, statusMessage, + request: { method, path, data: args.data } + }; + + (response.statusCode >= 400) + ? reject(response) + : resolve(response); + }; + + fn(...fnArgs, fnCallback); + }); + } +} diff --git a/e2e/utilities/utils.ts b/e2e/utilities/utils.ts new file mode 100755 index 000000000..92da973bf --- /dev/null +++ b/e2e/utilities/utils.ts @@ -0,0 +1,67 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { browser, promise, ElementFinder, ExpectedConditions as EC } from 'protractor'; +import { BROWSER_WAIT_TIMEOUT } from '../configs'; + +export class Utils { + // generate a random value + static random(): string { + return Math.random().toString(36).substring(3, 10).toLowerCase(); + } + + // local storage + static clearLocalStorage(): promise.Promise { + return browser.executeScript('window.localStorage.clear();'); + } + + // session storage + static clearSessionStorage(): promise.Promise { + return browser.executeScript('window.sessionStorage.clear();'); + } + + static retryCall(fn: () => Promise , retry: number = 30, delay: number = 1000): Promise { + const pause = (duration) => new Promise(res => setTimeout(res, duration)); + + const run = (retries) => + fn().catch(err => retries > 1 + ? pause(delay).then(() => run(retries - 1)) + : Promise.reject(err)); + + return run(retry); + } + + static waitUntilElementClickable(element: ElementFinder) { + return browser.wait(EC.elementToBeClickable(element), BROWSER_WAIT_TIMEOUT); + } + + static typeInField(elem: ElementFinder, value: string) { + for ( let i = 0; i < value.length; i++ ) { + const c = value.charAt(i); + elem.sendKeys(c); + browser.sleep(100); + } + } +} diff --git a/package-lock.json b/package-lock.json index f998d1fae..b1061ba32 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,15 +1,15 @@ { "name": "alfresco-content-app", - "version": "1.1.0", + "version": "1.3.0", "lockfileVersion": 1, "requires": true, "dependencies": { "@alfresco/adf-content-services": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@alfresco/adf-content-services/-/adf-content-services-2.3.0.tgz", - "integrity": "sha512-nLGjVv9xT1masbyxLg4ZY77qKFgs3kaMpm4zblpSNxN8gQnY1uG/rB5l+bevmlKosXqQgezjwgvEpvjU7Nb2oQ==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@alfresco/adf-content-services/-/adf-content-services-2.4.0.tgz", + "integrity": "sha512-WHQFo1bokmc9hYi3C3zlQImvuajOJuycfjSmkyQ27sEQhsjzH7+lPXV7Loa8p5kGFIP6KBL61BxReCfJqIJDCA==", "requires": { - "@alfresco/adf-core": "2.3.0", + "@alfresco/adf-core": "2.4.0", "@angular/animations": "5.1.1", "@angular/cdk": "5.0.1", "@angular/common": "5.1.1", @@ -24,7 +24,7 @@ "@angular/platform-browser-dynamic": "5.1.1", "@angular/router": "5.1.1", "@ngx-translate/core": "9.1.1", - "alfresco-js-api": "2.3.0", + "alfresco-js-api": "2.4.0", "chart.js": "2.5.0", "core-js": "2.4.1", "hammerjs": "2.0.8", @@ -36,16 +36,17 @@ "reflect-metadata": "0.1.10", "rxjs": "5.5.2", "systemjs": "0.19.27", - "tslib": "1.9.0", + "tslib": "^1.7.1", "zone.js": "0.8.14" }, "dependencies": { "alfresco-js-api": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/alfresco-js-api/-/alfresco-js-api-2.3.0.tgz", - "integrity": "sha512-IhsSNoPl8cbw/V24kw420sGoVp6rBakC2kN4gKe3bPdERvSWRehw5bojMQhnSPDmS2PqC5C23HaVV+whOwkpDg==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/alfresco-js-api/-/alfresco-js-api-2.4.0.tgz", + "integrity": "sha512-XVf/B3fE0Rl632SU3w0/m6EX/HEwOebIHjP0tluJoD174VONiTsoBzUuicliZvsfuHAeLC5YqthCV4PaKnehCg==", "requires": { "event-emitter": "0.3.4", + "jsrsasign": "^8.0.12", "superagent": "3.8.2" } }, @@ -59,7 +60,7 @@ "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-1.5.404.tgz", "integrity": "sha1-hYXGUWquIU1ZCXXo+ys8PzrxTO8=", "requires": { - "node-ensure": "0.0.0" + "node-ensure": "^0.0.0" } }, "zone.js": { @@ -70,9 +71,9 @@ } }, "@alfresco/adf-core": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@alfresco/adf-core/-/adf-core-2.3.0.tgz", - "integrity": "sha512-Gl8L2EKuCydUaNn7u6grT7yjxg4k04cvtT+i9fz+rloBp3YTF/a2XoqGhcHgQfi0hO6Cc8O+oVmDfDjGkW9QoQ==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@alfresco/adf-core/-/adf-core-2.4.0.tgz", + "integrity": "sha512-sK0C3Q9yDjqpXV3l5DQXqyRRSiZ+RaUKU81BHEY7Bq1szbBhJq9bJ3rF6PfABzGwB9DKhy+nU+fftCneLJQcnA==", "requires": { "@angular/animations": "5.1.1", "@angular/cdk": "5.0.1", @@ -88,20 +89,50 @@ "@angular/platform-browser-dynamic": "5.1.1", "@angular/router": "5.1.1", "@ngx-translate/core": "9.1.1", - "alfresco-js-api": "2.3.0", + "alfresco-js-api": "2.4.0", "chart.js": "2.5.0", - "core-js": "2.5.3", + "core-js": "2.4.1", "hammerjs": "2.0.8", "minimatch": "3.0.4", "moment": "2.20.1", "ng2-charts": "1.6.0", - "pdfjs-dist": "2.0.303", + "pdfjs-dist": "1.5.404", "raphael": "2.2.7", "reflect-metadata": "0.1.10", "rxjs": "5.5.2", "systemjs": "0.19.27", - "tslib": "1.9.0", - "zone.js": "0.8.20" + "tslib": "^1.7.1", + "zone.js": "0.8.14" + }, + "dependencies": { + "alfresco-js-api": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/alfresco-js-api/-/alfresco-js-api-2.4.0.tgz", + "integrity": "sha512-XVf/B3fE0Rl632SU3w0/m6EX/HEwOebIHjP0tluJoD174VONiTsoBzUuicliZvsfuHAeLC5YqthCV4PaKnehCg==", + "requires": { + "event-emitter": "0.3.4", + "jsrsasign": "^8.0.12", + "superagent": "3.8.2" + } + }, + "core-js": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.4.1.tgz", + "integrity": "sha1-TekR5mew6ukSTjQlS1OupvxhjT4=" + }, + "pdfjs-dist": { + "version": "1.5.404", + "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-1.5.404.tgz", + "integrity": "sha1-hYXGUWquIU1ZCXXo+ys8PzrxTO8=", + "requires": { + "node-ensure": "^0.0.0" + } + }, + "zone.js": { + "version": "0.8.14", + "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.8.14.tgz", + "integrity": "sha1-DE2ySxeCMidMy0P3jJnbfzZCts8=" + } } }, "@angular-devkit/build-optimizer": { @@ -110,10 +141,10 @@ "integrity": "sha512-U0BCZtThq5rUfY08shHXpxe8ZhSsiYB/cJjUvAWRTs/ORrs8pbngS6xwseQws8d/vHoVrtqGD9GU9h8AmFRERQ==", "dev": true, "requires": { - "loader-utils": "1.1.0", - "source-map": "0.5.7", - "typescript": "2.6.2", - "webpack-sources": "1.1.0" + "loader-utils": "^1.1.0", + "source-map": "^0.5.6", + "typescript": "~2.6.2", + "webpack-sources": "^1.0.1" }, "dependencies": { "typescript": { @@ -130,10 +161,10 @@ "integrity": "sha512-zABk/iP7YX5SVbmK4e+IX7j2d0D37MQJQiKgWdV3JzfvVJhNJzddiirtT980pIafoq+KyvTgVwXtc+vnux0oeQ==", "dev": true, "requires": { - "ajv": "5.5.2", - "chokidar": "1.7.0", - "rxjs": "5.5.9", - "source-map": "0.5.7" + "ajv": "~5.5.1", + "chokidar": "^1.7.0", + "rxjs": "^5.5.6", + "source-map": "^0.5.6" }, "dependencies": { "ajv": { @@ -142,16 +173,16 @@ "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", "dev": true, "requires": { - "co": "4.6.0", - "fast-deep-equal": "1.1.0", - "fast-json-stable-stringify": "2.0.0", - "json-schema-traverse": "0.3.1" + "co": "^4.6.0", + "fast-deep-equal": "^1.0.0", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.3.0" } }, "rxjs": { - "version": "5.5.9", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.5.9.tgz", - "integrity": "sha512-DHG9AHmCmgaFWgjBcXp6NxFDmh3MvIA62GqTWmLnTzr/3oZ6h5hLD8NA+9j+GF0jEwklNIpI4KuuyLG8UWMEvQ==", + "version": "5.5.10", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.5.10.tgz", + "integrity": "sha512-SRjimIDUHJkon+2hFo7xnvNC4ZEHGzCRwh9P7nzX3zPkCGFEg/tuElrNR7L/rZMagnK2JeH2jQwPRpmyXyLB6A==", "dev": true, "requires": { "symbol-observable": "1.0.1" @@ -171,14 +202,14 @@ "integrity": "sha512-B6zZoqvHaTJy+vVdA6EtlxnCdGMa5elCa4j9lQLC3JI8DLvMXUWkCIPVbPzJ/GSRR9nsKWpvYMYaJyfBDUqfhw==", "dev": true, "requires": { - "@ngtools/json-schema": "1.2.0", - "rxjs": "5.5.9" + "@ngtools/json-schema": "^1.1.0", + "rxjs": "^5.5.6" }, "dependencies": { "rxjs": { - "version": "5.5.9", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.5.9.tgz", - "integrity": "sha512-DHG9AHmCmgaFWgjBcXp6NxFDmh3MvIA62GqTWmLnTzr/3oZ6h5hLD8NA+9j+GF0jEwklNIpI4KuuyLG8UWMEvQ==", + "version": "5.5.10", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.5.10.tgz", + "integrity": "sha512-SRjimIDUHJkon+2hFo7xnvNC4ZEHGzCRwh9P7nzX3zPkCGFEg/tuElrNR7L/rZMagnK2JeH2jQwPRpmyXyLB6A==", "dev": true, "requires": { "symbol-observable": "1.0.1" @@ -197,7 +228,7 @@ "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-5.1.1.tgz", "integrity": "sha512-PHLBWDnAzr5b5l52pk5ZYmv/6m0YUe2ICwu5dmbS0d8Kf5dXadMphAWCDbljMF+djGyZeFq2/dQ/t7ygYl3YuA==", "requires": { - "tslib": "1.9.0" + "tslib": "^1.7.1" } }, "@angular/cdk": { @@ -205,7 +236,7 @@ "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-5.0.1.tgz", "integrity": "sha512-uK4Vyaf06J8KqePzq35BxMHRGolt35EnbZf9wjCs7eYaghbQ7Pk2xUGoynu5Lj1wAOn5N1/C1nT2/aAH/EE2rw==", "requires": { - "tslib": "1.9.0" + "tslib": "^1.7.1" } }, "@angular/cli": { @@ -221,64 +252,64 @@ "@ngtools/webpack": "1.10.2", "@schematics/angular": "0.3.2", "@schematics/package-update": "0.3.2", - "ajv": "6.4.0", - "autoprefixer": "7.2.6", - "cache-loader": "1.2.2", - "chalk": "2.2.2", - "circular-dependency-plugin": "4.4.0", - "clean-css": "4.1.11", - "common-tags": "1.7.2", - "copy-webpack-plugin": "4.4.3", - "core-object": "3.1.5", - "denodeify": "1.2.1", - "ember-cli-string-utils": "1.1.0", - "extract-text-webpack-plugin": "3.0.2", - "file-loader": "1.1.11", - "fs-extra": "4.0.3", - "glob": "7.1.2", - "html-webpack-plugin": "2.30.1", - "istanbul-instrumenter-loader": "3.0.1", - "karma-source-map-support": "1.2.0", - "less": "2.7.3", - "less-loader": "4.1.0", - "license-webpack-plugin": "1.3.1", + "ajv": "^6.1.1", + "autoprefixer": "^7.2.3", + "cache-loader": "^1.2.0", + "chalk": "~2.2.0", + "circular-dependency-plugin": "^4.2.1", + "clean-css": "^4.1.11", + "common-tags": "^1.3.1", + "copy-webpack-plugin": "~4.4.1", + "core-object": "^3.1.0", + "denodeify": "^1.2.1", + "ember-cli-string-utils": "^1.0.0", + "extract-text-webpack-plugin": "^3.0.2", + "file-loader": "^1.1.5", + "fs-extra": "^4.0.0", + "glob": "^7.0.3", + "html-webpack-plugin": "^2.29.0", + "istanbul-instrumenter-loader": "^3.0.0", + "karma-source-map-support": "^1.2.0", + "less": "^2.7.2", + "less-loader": "^4.0.5", + "license-webpack-plugin": "^1.0.0", "loader-utils": "1.1.0", - "lodash": "4.17.5", - "memory-fs": "0.4.1", - "minimatch": "3.0.4", - "node-modules-path": "1.0.1", - "node-sass": "4.8.3", - "nopt": "4.0.1", - "opn": "5.1.0", - "portfinder": "1.0.13", - "postcss": "6.0.21", - "postcss-import": "11.1.0", - "postcss-loader": "2.1.3", - "postcss-url": "7.3.2", - "raw-loader": "0.5.1", - "resolve": "1.7.1", - "rxjs": "5.5.9", - "sass-loader": "6.0.7", - "semver": "5.5.0", - "silent-error": "1.1.0", - "source-map-support": "0.4.18", - "style-loader": "0.19.1", - "stylus": "0.54.5", - "stylus-loader": "3.0.2", - "uglifyjs-webpack-plugin": "1.2.4", - "url-loader": "0.6.2", - "webpack": "3.11.0", - "webpack-dev-middleware": "1.12.2", - "webpack-dev-server": "2.11.2", - "webpack-merge": "4.1.2", - "webpack-sources": "1.1.0", - "webpack-subresource-integrity": "1.0.4" + "lodash": "^4.11.1", + "memory-fs": "^0.4.1", + "minimatch": "^3.0.4", + "node-modules-path": "^1.0.0", + "node-sass": "^4.7.2", + "nopt": "^4.0.1", + "opn": "~5.1.0", + "portfinder": "~1.0.12", + "postcss": "^6.0.16", + "postcss-import": "^11.0.0", + "postcss-loader": "^2.0.10", + "postcss-url": "^7.1.2", + "raw-loader": "^0.5.1", + "resolve": "^1.1.7", + "rxjs": "^5.5.6", + "sass-loader": "^6.0.6", + "semver": "^5.1.0", + "silent-error": "^1.0.0", + "source-map-support": "^0.4.1", + "style-loader": "^0.19.1", + "stylus": "^0.54.5", + "stylus-loader": "^3.0.1", + "uglifyjs-webpack-plugin": "^1.1.8", + "url-loader": "^0.6.2", + "webpack": "~3.11.0", + "webpack-dev-middleware": "~1.12.0", + "webpack-dev-server": "~2.11.0", + "webpack-merge": "^4.1.0", + "webpack-sources": "^1.0.0", + "webpack-subresource-integrity": "^1.0.1" }, "dependencies": { "rxjs": { - "version": "5.5.9", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.5.9.tgz", - "integrity": "sha512-DHG9AHmCmgaFWgjBcXp6NxFDmh3MvIA62GqTWmLnTzr/3oZ6h5hLD8NA+9j+GF0jEwklNIpI4KuuyLG8UWMEvQ==", + "version": "5.5.10", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.5.10.tgz", + "integrity": "sha512-SRjimIDUHJkon+2hFo7xnvNC4ZEHGzCRwh9P7nzX3zPkCGFEg/tuElrNR7L/rZMagnK2JeH2jQwPRpmyXyLB6A==", "dev": true, "requires": { "symbol-observable": "1.0.1" @@ -297,7 +328,7 @@ "resolved": "https://registry.npmjs.org/@angular/common/-/common-5.1.1.tgz", "integrity": "sha512-SFRzdDthoiKaMLuV+TAwjKXFWwTRFGuidlWC3BhUf8/HzNSePAdvfdQcqbEaE5buMn403OV105S9Tyx5tILQeA==", "requires": { - "tslib": "1.9.0" + "tslib": "^1.7.1" } }, "@angular/compiler": { @@ -305,7 +336,7 @@ "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-5.1.1.tgz", "integrity": "sha512-k4J2kRiBjtjkDcDut2JVUpqQGLJWd8j3Don+swzZHuEklbLmsVRGM6u/fmH0K9TMwKHtC5Ycap8kj4bWXUYfwg==", "requires": { - "tslib": "1.9.0" + "tslib": "^1.7.1" } }, "@angular/compiler-cli": { @@ -314,10 +345,10 @@ "integrity": "sha512-X3n1V0fAsZzJDRLM2OPiOri8rrQ2ILFS0VDqPdHMa1HbpF0ZKe1Yyux2rhGSbS83a1Eanx6RqfDkrUalKEprbw==", "dev": true, "requires": { - "chokidar": "1.7.0", - "minimist": "1.2.0", - "reflect-metadata": "0.1.10", - "tsickle": "0.25.6" + "chokidar": "^1.4.2", + "minimist": "^1.2.0", + "reflect-metadata": "^0.1.2", + "tsickle": "^0.25.5" }, "dependencies": { "minimist": { @@ -333,7 +364,7 @@ "resolved": "https://registry.npmjs.org/@angular/core/-/core-5.1.1.tgz", "integrity": "sha512-8HJ0lNM5Z+pf+JfOl5mAWgNfrdtnMhVcEGCEniJAQweKOfYCziuyB0ALkX/Q6jGmd2IshR36SarwCYEc5ttt/w==", "requires": { - "tslib": "1.9.0" + "tslib": "^1.7.1" } }, "@angular/flex-layout": { @@ -341,7 +372,7 @@ "resolved": "https://registry.npmjs.org/@angular/flex-layout/-/flex-layout-2.0.0-beta.12.tgz", "integrity": "sha512-QTOKZxehYTh8fj64V/pNVWNbfNtebSbssyMIXiGJuHTzfyF7GYdRmtjoR2pNpllycz3rE5NYX77EB140Y6BCnw==", "requires": { - "tslib": "1.9.0" + "tslib": "^1.7.1" } }, "@angular/forms": { @@ -349,7 +380,7 @@ "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-5.1.1.tgz", "integrity": "sha512-4iN/8N0DgnV82XIb/8PqlFIGrog8BHJlzQ9sdAlpT29biPFezFpqpsXkjLBouBc7oBFTgoyXMgWDj8IGRmwLGQ==", "requires": { - "tslib": "1.9.0" + "tslib": "^1.7.1" } }, "@angular/http": { @@ -357,7 +388,7 @@ "resolved": "https://registry.npmjs.org/@angular/http/-/http-5.1.1.tgz", "integrity": "sha512-oeiLX00TaFlGS5Y4EAGnxxVitN8T9X8olhSC+XDDAAL3JHTAyh4dj7me8vNZk1VaqPFa9AXu4D34vu1Zsm0c1g==", "requires": { - "tslib": "1.9.0" + "tslib": "^1.7.1" } }, "@angular/language-service": { @@ -371,7 +402,7 @@ "resolved": "https://registry.npmjs.org/@angular/material/-/material-5.0.1.tgz", "integrity": "sha512-k95i58ZIVneLE61a5JliM10NSasy9P5C2JJUESo3s/rxt9dq/9XOWpUvNCy49OHYBRFJBlsyrLM6E2V7/tmq4w==", "requires": { - "tslib": "1.9.0" + "tslib": "^1.7.1" } }, "@angular/material-moment-adapter": { @@ -379,7 +410,7 @@ "resolved": "https://registry.npmjs.org/@angular/material-moment-adapter/-/material-moment-adapter-5.0.1.tgz", "integrity": "sha512-SyFsoxnwXHAR4zLkFh7Z4NmxZANyBoLGAomVZRi2r9w1prc+kbaCaJ7LYjI6zM7Su4ltSQx0libbat1ppgow2w==", "requires": { - "tslib": "1.9.0" + "tslib": "^1.7.1" } }, "@angular/platform-browser": { @@ -387,7 +418,7 @@ "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-5.1.1.tgz", "integrity": "sha512-QpkNXoO2pqURQJxXPhZo6RFeirKbr56O0SwoMpYfXGGN1qEIicoWZHobCUTp7/jvjx5Xjc7886Fvu/qJrE7wVA==", "requires": { - "tslib": "1.9.0" + "tslib": "^1.7.1" } }, "@angular/platform-browser-dynamic": { @@ -395,7 +426,7 @@ "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-5.1.1.tgz", "integrity": "sha512-xnin1eK5nF7EO4tYZvRlhT28DyhL3p4NKWsZQwfqyBwSF0T2mJ1vjhjCZVT0MmaOyt5D+0eUkHIhBDqeZyBMMQ==", "requires": { - "tslib": "1.9.0" + "tslib": "^1.7.1" } }, "@angular/router": { @@ -403,7 +434,7 @@ "resolved": "https://registry.npmjs.org/@angular/router/-/router-5.1.1.tgz", "integrity": "sha512-96mBZS1b1Dt7HFOGKh5zI/1U6F3zT4cdjIaBmcCKkbyKhs3WRAPXxxCkuCwr6lWmBeQt4iEvSdXiHQbD0iCG7Q==", "requires": { - "tslib": "1.9.0" + "tslib": "^1.7.1" } }, "@mat-datetimepicker/core": { @@ -416,6 +447,26 @@ "resolved": "https://registry.npmjs.org/@mat-datetimepicker/moment/-/moment-1.0.1.tgz", "integrity": "sha1-YYUwbd/QeTBlq9XbBjKpQZgjdPQ=" }, + "@ngrx/effects": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@ngrx/effects/-/effects-5.2.0.tgz", + "integrity": "sha1-qnYractv1GRNckoc7NJlyqQrrwk=" + }, + "@ngrx/router-store": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@ngrx/router-store/-/router-store-5.2.0.tgz", + "integrity": "sha1-v0sXTOGaNuuoIR/B3erx41rnQ2g=" + }, + "@ngrx/store": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@ngrx/store/-/store-5.2.0.tgz", + "integrity": "sha1-Yn7XTJzZVGKTBIXZEqVXEXsjkD4=" + }, + "@ngrx/store-devtools": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@ngrx/store-devtools/-/store-devtools-5.2.0.tgz", + "integrity": "sha1-L/+RapqjSTdYJncrNZ27ZLnl1iI=" + }, "@ngtools/json-schema": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@ngtools/json-schema/-/json-schema-1.2.0.tgz", @@ -428,14 +479,14 @@ "integrity": "sha512-3u2zg2rarG3qNLSukBClGADWuq/iNn5SQtlSeAbfKzwBeyLGbF0gN1z1tVx1Bcr8YwFzR6NdRePQmJGcoqq1fg==", "dev": true, "requires": { - "chalk": "2.2.2", - "enhanced-resolve": "3.4.1", - "loader-utils": "1.1.0", - "magic-string": "0.22.5", - "semver": "5.5.0", - "source-map": "0.5.7", - "tree-kill": "1.2.0", - "webpack-sources": "1.1.0" + "chalk": "~2.2.0", + "enhanced-resolve": "^3.1.0", + "loader-utils": "^1.0.2", + "magic-string": "^0.22.3", + "semver": "^5.3.0", + "source-map": "^0.5.6", + "tree-kill": "^1.0.0", + "webpack-sources": "^1.1.0" } }, "@ngx-translate/core": { @@ -449,7 +500,7 @@ "integrity": "sha512-Elrk0BA951s0ScFZU0AWrpUeJBYVR52DZ1QTIO5R0AhwEd1PW4olI8szPLGQlVW5Sd6H0FA/fyFLIvn2r9v6Rw==", "dev": true, "requires": { - "typescript": "2.6.2" + "typescript": "~2.6.2" }, "dependencies": { "typescript": { @@ -466,15 +517,15 @@ "integrity": "sha512-7aVP4994Hu8vRdTTohXkfGWEwLhrdNP3EZnWyBootm5zshWqlQojUGweZe5zwewsKcixeVOiy2YtW+aI4aGSLA==", "dev": true, "requires": { - "rxjs": "5.5.9", - "semver": "5.5.0", - "semver-intersect": "1.3.1" + "rxjs": "^5.5.6", + "semver": "^5.3.0", + "semver-intersect": "^1.1.2" }, "dependencies": { "rxjs": { - "version": "5.5.9", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.5.9.tgz", - "integrity": "sha512-DHG9AHmCmgaFWgjBcXp6NxFDmh3MvIA62GqTWmLnTzr/3oZ6h5hLD8NA+9j+GF0jEwklNIpI4KuuyLG8UWMEvQ==", + "version": "5.5.10", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.5.10.tgz", + "integrity": "sha512-SRjimIDUHJkon+2hFo7xnvNC4ZEHGzCRwh9P7nzX3zPkCGFEg/tuElrNR7L/rZMagnK2JeH2jQwPRpmyXyLB6A==", "dev": true, "requires": { "symbol-observable": "1.0.1" @@ -489,9 +540,9 @@ } }, "@types/jasmine": { - "version": "2.8.6", - "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-2.8.6.tgz", - "integrity": "sha512-clg9raJTY0EOo5pVZKX3ZlMjlYzVU73L71q5OV1jhE2Uezb7oF94jh4CvwrW6wInquQAdhOxJz5VDF2TLUGmmA==", + "version": "2.8.7", + "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-2.8.7.tgz", + "integrity": "sha512-RdbrPcW1aD78UmdLiDa9ZCKrbR5Go8PXh6GCpb4oIOkWVEusubSJJDrP4c5RYOu8m/CBz+ygZpicj6Pgms5a4Q==", "dev": true }, "@types/jasminewd2": { @@ -500,7 +551,7 @@ "integrity": "sha512-hYDVmQZT5VA2kigd4H4bv7vl/OhlympwREUemqBdOqtrYTo5Ytm12a5W5/nGgGYdanGVxj0x/VhZ7J3hOg/YKg==", "dev": true, "requires": { - "@types/jasmine": "2.8.6" + "@types/jasmine": "*" } }, "@types/node": { @@ -516,9 +567,9 @@ "dev": true }, "@types/selenium-webdriver": { - "version": "2.53.43", - "resolved": "https://registry.npmjs.org/@types/selenium-webdriver/-/selenium-webdriver-2.53.43.tgz", - "integrity": "sha512-UBYHWph6P3tutkbXpW6XYg9ZPbTKjw/YC2hGG1/GEvWwTbvezBUv3h+mmUFw79T3RFPnmedpiXdOBbXX+4l0jg==", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@types/selenium-webdriver/-/selenium-webdriver-3.0.8.tgz", + "integrity": "sha512-yrqQvb1EZhH+ONMzUmsEnBjzitortVv0lynRe5US2+FofdoMWUE4wf7v4peCd62fqXq8COCVTbpS1/jIg5EbuQ==", "dev": true }, "@types/strip-bom": { @@ -533,16 +584,6 @@ "integrity": "sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ==", "dev": true }, - "JSONStream": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.2.tgz", - "integrity": "sha1-wQI3G27Dp887hHygDCC7D85Mbeo=", - "dev": true, - "requires": { - "jsonparse": "1.3.1", - "through": "2.3.8" - } - }, "abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", @@ -555,7 +596,7 @@ "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=", "dev": true, "requires": { - "mime-types": "2.1.18", + "mime-types": "~2.1.18", "negotiator": "0.6.1" } }, @@ -571,7 +612,7 @@ "integrity": "sha1-x1K9IQvvZ5UBtsbLf8hPj0cVjMQ=", "dev": true, "requires": { - "acorn": "4.0.13" + "acorn": "^4.0.3" }, "dependencies": { "acorn": { @@ -582,16 +623,6 @@ } } }, - "acorn-node": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/acorn-node/-/acorn-node-1.3.0.tgz", - "integrity": "sha512-efP54n3d1aLfjL2UMdaXa6DsswwzJeI5rqhbFvXMrKiJ6eJFpf+7R0zN7t8IC+XKn2YOAFAv6xbBNgHUkoHWLw==", - "dev": true, - "requires": { - "acorn": "5.5.3", - "xtend": "4.0.1" - } - }, "addressparser": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/addressparser/-/addressparser-1.0.1.tgz", @@ -600,9 +631,9 @@ "optional": true }, "adm-zip": { - "version": "0.4.7", - "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.4.7.tgz", - "integrity": "sha1-hgbCy/HEJs6MjsABdER/1Jtur8E=", + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.4.4.tgz", + "integrity": "sha1-ph7VrmkFw66lizplfSUDMJEFJzY=", "dev": true }, "after": { @@ -612,21 +643,12 @@ "dev": true }, "agent-base": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-2.1.1.tgz", - "integrity": "sha1-1t4Q1a9hMtW9aSQn1G/FOFOQlMc=", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.2.0.tgz", + "integrity": "sha512-c+R/U5X+2zz2+UCrCFv6odQzJdoqI+YecuhnAJLa1zYaMc13zPfwMwZrr91Pd1DYNo/yPRbiM4WVf9whgwFsIg==", "dev": true, "requires": { - "extend": "3.0.1", - "semver": "5.0.3" - }, - "dependencies": { - "semver": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.0.3.tgz", - "integrity": "sha1-d0Zt5YnNXTyV8TiqeLxWmjy10no=", - "dev": true - } + "es6-promisify": "^5.0.0" } }, "ajv": { @@ -634,23 +656,24 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.4.0.tgz", "integrity": "sha1-06/3jpJ3VJdx2vAWTP9ISCt1T8Y=", "requires": { - "fast-deep-equal": "1.1.0", - "fast-json-stable-stringify": "2.0.0", - "json-schema-traverse": "0.3.1", - "uri-js": "3.0.2" + "fast-deep-equal": "^1.0.0", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.3.0", + "uri-js": "^3.0.2" } }, "ajv-keywords": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.1.0.tgz", - "integrity": "sha1-rCsnk5xUPpXSwG5/f1wnvkqlQ74=" + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.2.0.tgz", + "integrity": "sha1-6GuBnGAs+IIa1jdBNpjx3sAhhHo=" }, "alfresco-js-api": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/alfresco-js-api/-/alfresco-js-api-2.3.0.tgz", - "integrity": "sha512-IhsSNoPl8cbw/V24kw420sGoVp6rBakC2kN4gKe3bPdERvSWRehw5bojMQhnSPDmS2PqC5C23HaVV+whOwkpDg==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/alfresco-js-api/-/alfresco-js-api-2.4.0.tgz", + "integrity": "sha512-XVf/B3fE0Rl632SU3w0/m6EX/HEwOebIHjP0tluJoD174VONiTsoBzUuicliZvsfuHAeLC5YqthCV4PaKnehCg==", "requires": { "event-emitter": "0.3.4", + "jsrsasign": "^8.0.12", "superagent": "3.8.2" } }, @@ -660,9 +683,9 @@ "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", "dev": true, "requires": { - "kind-of": "3.2.2", - "longest": "1.0.1", - "repeat-string": "1.6.1" + "kind-of": "^3.0.2", + "longest": "^1.0.1", + "repeat-string": "^1.5.2" } }, "amdefine": { @@ -678,11 +701,11 @@ "dev": true, "optional": true, "requires": { - "bitsyntax": "0.0.4", - "bluebird": "3.5.1", + "bitsyntax": "~0.0.4", + "bluebird": "^3.4.6", "buffer-more-ints": "0.0.2", - "readable-stream": "1.1.14", - "safe-buffer": "5.1.1" + "readable-stream": "1.x >=1.1.9", + "safe-buffer": "^5.0.1" }, "dependencies": { "isarray": { @@ -699,10 +722,10 @@ "dev": true, "optional": true, "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", "isarray": "0.0.1", - "string_decoder": "0.10.31" + "string_decoder": "~0.10.x" } }, "string_decoder": { @@ -730,18 +753,16 @@ "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, "requires": { - "color-convert": "1.9.1" + "color-convert": "^1.9.0" }, "dependencies": { "color-convert": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.1.tgz", "integrity": "sha512-mjGanIiwQJskCC18rPR6OmrZ6fm2Lc7PeGFYwCmy5J34wC6F1PzdGL6xeMfmgicfYcNLGuVFA3WzXtIDCQSZxQ==", - "dev": true, "requires": { - "color-name": "1.1.3" + "color-name": "^1.1.1" } } } @@ -752,8 +773,8 @@ "integrity": "sha512-0XNayC8lTHQ2OI8aljNCN3sSx6hsr/1+rlcDAotXJR7C1oZZHCNsfpbKwMjRA3Uqb5tF1Rae2oloTr4xpq+WjA==", "dev": true, "requires": { - "micromatch": "2.3.11", - "normalize-path": "2.1.1" + "micromatch": "^2.1.5", + "normalize-path": "^2.0.0" } }, "app-root-path": { @@ -768,7 +789,7 @@ "integrity": "sha1-126/jKlNJ24keja61EpLdKthGZE=", "dev": true, "requires": { - "default-require-extensions": "1.0.0" + "default-require-extensions": "^1.0.0" } }, "aproba": { @@ -783,8 +804,8 @@ "integrity": "sha1-u13KOCu5TwXhUZQ3PRb9O6HKEQ0=", "dev": true, "requires": { - "delegates": "1.0.0", - "readable-stream": "2.3.6" + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" } }, "argparse": { @@ -793,7 +814,7 @@ "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "dev": true, "requires": { - "sprintf-js": "1.0.3" + "sprintf-js": "~1.0.2" } }, "arr-diff": { @@ -802,7 +823,7 @@ "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", "dev": true, "requires": { - "arr-flatten": "1.1.0" + "arr-flatten": "^1.0.1" } }, "arr-flatten": { @@ -817,12 +838,6 @@ "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", "dev": true }, - "array-filter": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/array-filter/-/array-filter-0.0.1.tgz", - "integrity": "sha1-fajPLiZijtcygDWB/SH2fKzS7uw=", - "dev": true - }, "array-find-index": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", @@ -841,22 +856,10 @@ "integrity": "sha1-GEtI9i2S10UrsxsyMWXH+L0CJm0=", "dev": true, "requires": { - "define-properties": "1.1.2", - "es-abstract": "1.11.0" + "define-properties": "^1.1.2", + "es-abstract": "^1.7.0" } }, - "array-map": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/array-map/-/array-map-0.0.0.tgz", - "integrity": "sha1-iKK6tz0c97zVwbEYoAP2b2ZfpmI=", - "dev": true - }, - "array-reduce": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/array-reduce/-/array-reduce-0.0.0.tgz", - "integrity": "sha1-FziZ0//Rx9k4PkR5Ul2+J4yrXys=", - "dev": true - }, "array-slice": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-0.2.3.tgz", @@ -869,7 +872,7 @@ "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", "dev": true, "requires": { - "array-uniq": "1.0.3" + "array-uniq": "^1.0.1" } }, "array-uniq": { @@ -915,9 +918,9 @@ "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==", "dev": true, "requires": { - "bn.js": "4.11.8", - "inherits": "2.0.3", - "minimalistic-assert": "1.0.1" + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" } }, "assert": { @@ -948,30 +951,13 @@ "dev": true, "optional": true }, - "astw": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/astw/-/astw-2.2.0.tgz", - "integrity": "sha1-e9QXhNMkk5h66yOba04cV6hzuRc=", - "dev": true, - "requires": { - "acorn": "4.0.13" - }, - "dependencies": { - "acorn": { - "version": "4.0.13", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-4.0.13.tgz", - "integrity": "sha1-EFSVrlNh1pe9GVyCUZLhrX8lN4c=", - "dev": true - } - } - }, "async": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/async/-/async-2.6.0.tgz", "integrity": "sha512-xAfGg1/NTLBBKlHFmnd7PlmUW9KhVQIUuSrYem9xzFUZy13ScvtyGGejaae9iAVRiRq9+Cx7DPFaAAhCpyxyPw==", "dev": true, "requires": { - "lodash": "4.17.5" + "lodash": "^4.14.0" } }, "async-each": { @@ -999,9 +985,9 @@ "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" }, "atob": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.0.tgz", - "integrity": "sha512-SuiKH8vbsOyCALjA/+EINmt/Kdl+TQPrtFgW7XZZcwtryFu9e5kQoX3bjCW6mIvGH1fbeAZZuvwGR5IlBRznGw==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.1.tgz", + "integrity": "sha1-ri1acpR38onWDdf5amMUoi3Wwio=", "dev": true }, "autoprefixer": { @@ -1010,12 +996,12 @@ "integrity": "sha512-Iq8TRIB+/9eQ8rbGhcP7ct5cYb/3qjNYAR2SnzLCEcwF6rvVOax8+9+fccgXk4bEhQGjOZd5TLhsksmAdsbGqQ==", "dev": true, "requires": { - "browserslist": "2.11.3", - "caniuse-lite": "1.0.30000828", - "normalize-range": "0.1.2", - "num2fraction": "1.2.2", - "postcss": "6.0.21", - "postcss-value-parser": "3.3.0" + "browserslist": "^2.11.3", + "caniuse-lite": "^1.0.30000805", + "normalize-range": "^0.1.2", + "num2fraction": "^1.2.2", + "postcss": "^6.0.17", + "postcss-value-parser": "^3.2.3" } }, "aws-sign2": { @@ -1038,6 +1024,28 @@ "optional": true, "requires": { "follow-redirects": "1.0.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "optional": true, + "requires": { + "ms": "2.0.0" + } + }, + "follow-redirects": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.0.0.tgz", + "integrity": "sha1-jjQpjL0uF28lTv/sdaHHjMhJ/Tc=", + "dev": true, + "optional": true, + "requires": { + "debug": "^2.2.0" + } + } } }, "babel-code-frame": { @@ -1046,9 +1054,9 @@ "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", "dev": true, "requires": { - "chalk": "1.1.3", - "esutils": "2.0.2", - "js-tokens": "3.0.2" + "chalk": "^1.1.3", + "esutils": "^2.0.2", + "js-tokens": "^3.0.2" }, "dependencies": { "ansi-styles": { @@ -1063,11 +1071,11 @@ "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "dev": true, "requires": { - "ansi-styles": "2.2.1", - "escape-string-regexp": "1.0.5", - "has-ansi": "2.0.0", - "strip-ansi": "3.0.1", - "supports-color": "2.0.0" + "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": { @@ -1084,14 +1092,14 @@ "integrity": "sha512-HyfwY6ApZj7BYTcJURpM5tznulaBvyio7/0d4zFOeMPUmfxkCjHocCuoLa2SAGzBI8AREcH3eP3758F672DppA==", "dev": true, "requires": { - "babel-messages": "6.23.0", - "babel-runtime": "6.26.0", - "babel-types": "6.26.0", - "detect-indent": "4.0.0", - "jsesc": "1.3.0", - "lodash": "4.17.5", - "source-map": "0.5.7", - "trim-right": "1.0.1" + "babel-messages": "^6.23.0", + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "detect-indent": "^4.0.0", + "jsesc": "^1.3.0", + "lodash": "^4.17.4", + "source-map": "^0.5.7", + "trim-right": "^1.0.1" } }, "babel-messages": { @@ -1100,7 +1108,7 @@ "integrity": "sha1-8830cDhYA1sqKVHG7F7fbGLyYw4=", "dev": true, "requires": { - "babel-runtime": "6.26.0" + "babel-runtime": "^6.22.0" } }, "babel-runtime": { @@ -1109,8 +1117,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "2.5.3", - "regenerator-runtime": "0.11.1" + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" } }, "babel-template": { @@ -1119,11 +1127,11 @@ "integrity": "sha1-3gPi0WOWsGn0bdn/+FIfsaDjXgI=", "dev": true, "requires": { - "babel-runtime": "6.26.0", - "babel-traverse": "6.26.0", - "babel-types": "6.26.0", - "babylon": "6.18.0", - "lodash": "4.17.5" + "babel-runtime": "^6.26.0", + "babel-traverse": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "lodash": "^4.17.4" } }, "babel-traverse": { @@ -1132,15 +1140,15 @@ "integrity": "sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4=", "dev": true, "requires": { - "babel-code-frame": "6.26.0", - "babel-messages": "6.23.0", - "babel-runtime": "6.26.0", - "babel-types": "6.26.0", - "babylon": "6.18.0", - "debug": "2.6.9", - "globals": "9.18.0", - "invariant": "2.2.4", - "lodash": "4.17.5" + "babel-code-frame": "^6.26.0", + "babel-messages": "^6.23.0", + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "debug": "^2.6.8", + "globals": "^9.18.0", + "invariant": "^2.2.2", + "lodash": "^4.17.4" }, "dependencies": { "debug": { @@ -1160,10 +1168,10 @@ "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", "dev": true, "requires": { - "babel-runtime": "6.26.0", - "esutils": "2.0.2", - "lodash": "4.17.5", - "to-fast-properties": "1.0.3" + "babel-runtime": "^6.26.0", + "esutils": "^2.0.2", + "lodash": "^4.17.4", + "to-fast-properties": "^1.0.3" } }, "babylon": { @@ -1189,13 +1197,13 @@ "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", "dev": true, "requires": { - "cache-base": "1.0.1", - "class-utils": "0.3.6", - "component-emitter": "1.2.1", - "define-property": "1.0.0", - "isobject": "3.0.1", - "mixin-deep": "1.3.1", - "pascalcase": "0.1.1" + "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": { @@ -1204,7 +1212,7 @@ "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", "dev": true, "requires": { - "is-descriptor": "1.0.2" + "is-descriptor": "^1.0.0" } }, "is-accessor-descriptor": { @@ -1213,7 +1221,7 @@ "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", "dev": true, "requires": { - "kind-of": "6.0.2" + "kind-of": "^6.0.0" } }, "is-data-descriptor": { @@ -1222,7 +1230,7 @@ "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", "dev": true, "requires": { - "kind-of": "6.0.2" + "kind-of": "^6.0.0" } }, "is-descriptor": { @@ -1231,9 +1239,9 @@ "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", "dev": true, "requires": { - "is-accessor-descriptor": "1.0.0", - "is-data-descriptor": "1.0.0", - "kind-of": "6.0.2" + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" } }, "isobject": { @@ -1257,9 +1265,9 @@ "dev": true }, "base64-js": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.2.3.tgz", - "integrity": "sha512-MsAhsUW1GxCdgYSO6tAfZrNapmUKk7mWx/k5mFY/A1gBtkaCaNapTg+FExCw1r9yeaZhqx/xPg43xgTFH6KL5w==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.0.tgz", + "integrity": "sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw==", "dev": true }, "base64id": { @@ -1281,7 +1289,7 @@ "dev": true, "optional": true, "requires": { - "tweetnacl": "0.14.5" + "tweetnacl": "^0.14.3" } }, "better-assert": { @@ -1321,7 +1329,7 @@ "dev": true, "optional": true, "requires": { - "readable-stream": "2.0.6" + "readable-stream": "~2.0.5" }, "dependencies": { "process-nextick-args": { @@ -1338,12 +1346,12 @@ "dev": true, "optional": true, "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "1.0.0", - "process-nextick-args": "1.0.7", - "string_decoder": "0.10.31", - "util-deprecate": "1.0.2" + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "~1.0.0", + "process-nextick-args": "~1.0.6", + "string_decoder": "~0.10.x", + "util-deprecate": "~1.0.1" } }, "string_decoder": { @@ -1368,16 +1376,16 @@ "dev": true, "optional": true, "requires": { - "inherits": "2.0.3" + "inherits": "~2.0.0" } }, "blocking-proxy": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/blocking-proxy/-/blocking-proxy-0.0.5.tgz", - "integrity": "sha1-RikF4Nz76pcPQao3Ij3anAexkSs=", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/blocking-proxy/-/blocking-proxy-1.0.1.tgz", + "integrity": "sha512-KE8NFMZr3mN2E0HcvCgRtX7DjhiIQrwle+nSVJVC/yqFb9+xznHl2ZcoBp2L9qzkI4t4cBFJ1efXF8Dwi132RA==", "dev": true, "requires": { - "minimist": "1.2.0" + "minimist": "^1.2.0" }, "dependencies": { "minimist": { @@ -1407,15 +1415,15 @@ "dev": true, "requires": { "bytes": "3.0.0", - "content-type": "1.0.4", + "content-type": "~1.0.4", "debug": "2.6.9", - "depd": "1.1.2", - "http-errors": "1.6.3", + "depd": "~1.1.1", + "http-errors": "~1.6.2", "iconv-lite": "0.4.19", - "on-finished": "2.3.0", + "on-finished": "~2.3.0", "qs": "6.5.1", "raw-body": "2.3.2", - "type-is": "1.6.16" + "type-is": "~1.6.15" }, "dependencies": { "debug": { @@ -1435,12 +1443,12 @@ "integrity": "sha1-jokKGD2O6aI5OzhExpGkK897yfU=", "dev": true, "requires": { - "array-flatten": "2.1.1", - "deep-equal": "1.0.1", - "dns-equal": "1.0.0", - "dns-txt": "2.0.2", - "multicast-dns": "6.2.3", - "multicast-dns-service-types": "1.1.0" + "array-flatten": "^2.1.0", + "deep-equal": "^1.0.1", + "dns-equal": "^1.0.0", + "dns-txt": "^2.0.2", + "multicast-dns": "^6.0.1", + "multicast-dns-service-types": "^1.1.0" } }, "boolbase": { @@ -1455,7 +1463,7 @@ "integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8=", "dev": true, "requires": { - "hoek": "2.16.3" + "hoek": "2.x.x" } }, "brace-expansion": { @@ -1463,7 +1471,7 @@ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "requires": { - "balanced-match": "1.0.0", + "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, @@ -1473,9 +1481,9 @@ "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", "dev": true, "requires": { - "expand-range": "1.8.2", - "preserve": "0.2.0", - "repeat-element": "1.1.2" + "expand-range": "^1.8.1", + "preserve": "^0.2.0", + "repeat-element": "^1.1.2" } }, "brorand": { @@ -1484,185 +1492,18 @@ "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=", "dev": true }, - "browser-pack": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/browser-pack/-/browser-pack-6.1.0.tgz", - "integrity": "sha512-erYug8XoqzU3IfcU8fUgyHqyOXqIE4tUTTQ+7mqUjQlvnXkOO6OlT9c/ZoJVHYoAaqGxr09CN53G7XIsO4KtWA==", - "dev": true, - "requires": { - "JSONStream": "1.3.2", - "combine-source-map": "0.8.0", - "defined": "1.0.0", - "safe-buffer": "5.1.1", - "through2": "2.0.3", - "umd": "3.0.3" - } - }, - "browser-resolve": { - "version": "1.11.2", - "resolved": "https://registry.npmjs.org/browser-resolve/-/browser-resolve-1.11.2.tgz", - "integrity": "sha1-j/CbCixCFxihBRwmCzLkj0QpOM4=", - "dev": true, - "requires": { - "resolve": "1.1.7" - }, - "dependencies": { - "resolve": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", - "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=", - "dev": true - } - } - }, - "browserify": { - "version": "14.5.0", - "resolved": "https://registry.npmjs.org/browserify/-/browserify-14.5.0.tgz", - "integrity": "sha512-gKfOsNQv/toWz+60nSPfYzuwSEdzvV2WdxrVPUbPD/qui44rAkB3t3muNtmmGYHqrG56FGwX9SUEQmzNLAeS7g==", - "dev": true, - "requires": { - "JSONStream": "1.3.2", - "assert": "1.4.1", - "browser-pack": "6.1.0", - "browser-resolve": "1.11.2", - "browserify-zlib": "0.2.0", - "buffer": "5.1.0", - "cached-path-relative": "1.0.1", - "concat-stream": "1.5.2", - "console-browserify": "1.1.0", - "constants-browserify": "1.0.0", - "crypto-browserify": "3.12.0", - "defined": "1.0.0", - "deps-sort": "2.0.0", - "domain-browser": "1.1.7", - "duplexer2": "0.1.4", - "events": "1.1.1", - "glob": "7.1.2", - "has": "1.0.1", - "htmlescape": "1.1.1", - "https-browserify": "1.0.0", - "inherits": "2.0.3", - "insert-module-globals": "7.0.6", - "labeled-stream-splicer": "2.0.1", - "module-deps": "4.1.1", - "os-browserify": "0.3.0", - "parents": "1.0.1", - "path-browserify": "0.0.0", - "process": "0.11.10", - "punycode": "1.4.1", - "querystring-es3": "0.2.1", - "read-only-stream": "2.0.0", - "readable-stream": "2.3.6", - "resolve": "1.7.1", - "shasum": "1.0.2", - "shell-quote": "1.6.1", - "stream-browserify": "2.0.1", - "stream-http": "2.8.1", - "string_decoder": "1.0.3", - "subarg": "1.0.0", - "syntax-error": "1.4.0", - "through2": "2.0.3", - "timers-browserify": "1.4.2", - "tty-browserify": "0.0.0", - "url": "0.11.0", - "util": "0.10.3", - "vm-browserify": "0.0.4", - "xtend": "4.0.1" - }, - "dependencies": { - "buffer": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.1.0.tgz", - "integrity": "sha512-YkIRgwsZwJWTnyQrsBTWefizHh+8GYj3kbL1BTiAQ/9pwpino0G7B2gp5tx/FUBqUlvtxV85KNR3mwfAtv15Yw==", - "dev": true, - "requires": { - "base64-js": "1.2.3", - "ieee754": "1.1.11" - } - }, - "concat-stream": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.5.2.tgz", - "integrity": "sha1-cIl4Yk2FavQaWnQd790mHadSwmY=", - "dev": true, - "requires": { - "inherits": "2.0.3", - "readable-stream": "2.0.6", - "typedarray": "0.0.6" - }, - "dependencies": { - "readable-stream": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz", - "integrity": "sha1-j5A0HmilPMySh4jaz80Rs265t44=", - "dev": true, - "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "1.0.0", - "process-nextick-args": "1.0.7", - "string_decoder": "0.10.31", - "util-deprecate": "1.0.2" - } - }, - "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 - } - } - }, - "domain-browser": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.1.7.tgz", - "integrity": "sha1-hnqksJP6oF8d4IwG9NeyH9+GmLw=", - "dev": true - }, - "process-nextick-args": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", - "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=", - "dev": true - }, - "punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", - "dev": true - }, - "string_decoder": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", - "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", - "dev": true, - "requires": { - "safe-buffer": "5.1.1" - } - }, - "timers-browserify": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-1.4.2.tgz", - "integrity": "sha1-ycWLV1voQHN1y14kYtrO50NZ9B0=", - "dev": true, - "requires": { - "process": "0.11.10" - } - } - } - }, "browserify-aes": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", "dev": true, "requires": { - "buffer-xor": "1.0.3", - "cipher-base": "1.0.4", - "create-hash": "1.2.0", - "evp_bytestokey": "1.0.3", - "inherits": "2.0.3", - "safe-buffer": "5.1.1" + "buffer-xor": "^1.0.3", + "cipher-base": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.3", + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" } }, "browserify-cipher": { @@ -1671,9 +1512,9 @@ "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", "dev": true, "requires": { - "browserify-aes": "1.2.0", - "browserify-des": "1.0.1", - "evp_bytestokey": "1.0.3" + "browserify-aes": "^1.0.4", + "browserify-des": "^1.0.0", + "evp_bytestokey": "^1.0.0" } }, "browserify-des": { @@ -1682,9 +1523,9 @@ "integrity": "sha512-zy0Cobe3hhgpiOM32Tj7KQ3Vl91m0njwsjzZQK1L+JDf11dzP9qIvjreVinsvXrgfjhStXwUWAEpB9D7Gwmayw==", "dev": true, "requires": { - "cipher-base": "1.0.4", - "des.js": "1.0.0", - "inherits": "2.0.3" + "cipher-base": "^1.0.1", + "des.js": "^1.0.0", + "inherits": "^2.0.1" } }, "browserify-rsa": { @@ -1693,8 +1534,8 @@ "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=", "dev": true, "requires": { - "bn.js": "4.11.8", - "randombytes": "2.0.6" + "bn.js": "^4.1.0", + "randombytes": "^2.0.1" } }, "browserify-sign": { @@ -1703,13 +1544,13 @@ "integrity": "sha1-qk62jl17ZYuqa/alfmMMvXqT0pg=", "dev": true, "requires": { - "bn.js": "4.11.8", - "browserify-rsa": "4.0.1", - "create-hash": "1.2.0", - "create-hmac": "1.1.7", - "elliptic": "6.4.0", - "inherits": "2.0.3", - "parse-asn1": "5.1.1" + "bn.js": "^4.1.1", + "browserify-rsa": "^4.0.0", + "create-hash": "^1.1.0", + "create-hmac": "^1.1.2", + "elliptic": "^6.0.0", + "inherits": "^2.0.1", + "parse-asn1": "^5.0.0" } }, "browserify-zlib": { @@ -1718,7 +1559,7 @@ "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", "dev": true, "requires": { - "pako": "1.0.6" + "pako": "~1.0.5" } }, "browserslist": { @@ -1727,8 +1568,8 @@ "integrity": "sha512-yWu5cXT7Av6mVwzWc8lMsJMHWn4xyjSuGYi4IozbVTLUOEYPSagUB8kiMDUHA1fS3zjr8nkxkn9jdvug4BBRmA==", "dev": true, "requires": { - "caniuse-lite": "1.0.30000828", - "electron-to-chromium": "1.3.42" + "caniuse-lite": "^1.0.30000792", + "electron-to-chromium": "^1.3.30" } }, "buffer": { @@ -1737,9 +1578,9 @@ "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", "dev": true, "requires": { - "base64-js": "1.2.3", - "ieee754": "1.1.11", - "isarray": "1.0.0" + "base64-js": "^1.0.2", + "ieee754": "^1.1.4", + "isarray": "^1.0.0" } }, "buffer-from": { @@ -1815,19 +1656,19 @@ "integrity": "sha512-Dph0MzuH+rTQzGPNT9fAnrPmMmjKfST6trxJeK7NQuHRaVw24VzPRWTmg9MpcwOVQZO0E1FBICUlFeNaKPIfHA==", "dev": true, "requires": { - "bluebird": "3.5.1", - "chownr": "1.0.1", - "glob": "7.1.2", - "graceful-fs": "4.1.11", - "lru-cache": "4.1.2", - "mississippi": "2.0.0", - "mkdirp": "0.5.1", - "move-concurrently": "1.0.1", - "promise-inflight": "1.0.1", - "rimraf": "2.6.2", - "ssri": "5.3.0", - "unique-filename": "1.1.0", - "y18n": "4.0.0" + "bluebird": "^3.5.1", + "chownr": "^1.0.1", + "glob": "^7.1.2", + "graceful-fs": "^4.1.11", + "lru-cache": "^4.1.1", + "mississippi": "^2.0.0", + "mkdirp": "^0.5.1", + "move-concurrently": "^1.0.1", + "promise-inflight": "^1.0.1", + "rimraf": "^2.6.2", + "ssri": "^5.2.4", + "unique-filename": "^1.1.0", + "y18n": "^4.0.0" } }, "cache-base": { @@ -1836,15 +1677,15 @@ "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", "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" + "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" }, "dependencies": { "isobject": { @@ -1861,18 +1702,12 @@ "integrity": "sha512-rsGh4SIYyB9glU+d0OcHwiXHXBoUgDhHZaQ1KAbiXqfz1CDPxtTboh1gPbJ0q2qdO8a9lfcjgC5CJ2Ms32y5bw==", "dev": true, "requires": { - "loader-utils": "1.1.0", - "mkdirp": "0.5.1", - "neo-async": "2.5.1", - "schema-utils": "0.4.5" + "loader-utils": "^1.1.0", + "mkdirp": "^0.5.1", + "neo-async": "^2.5.0", + "schema-utils": "^0.4.2" } }, - "cached-path-relative": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cached-path-relative/-/cached-path-relative-1.0.1.tgz", - "integrity": "sha1-0JxLUoAKpMB44t2BqGmqyQ0uVOc=", - "dev": true - }, "callsite": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz", @@ -1885,8 +1720,8 @@ "integrity": "sha1-yjw2iKTpzzpM2nd9xNy8cTJJz3M=", "dev": true, "requires": { - "no-case": "2.3.2", - "upper-case": "1.1.3" + "no-case": "^2.2.0", + "upper-case": "^1.1.1" } }, "camelcase": { @@ -1901,14 +1736,14 @@ "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", "dev": true, "requires": { - "camelcase": "2.1.1", - "map-obj": "1.0.1" + "camelcase": "^2.0.0", + "map-obj": "^1.0.0" } }, "caniuse-lite": { - "version": "1.0.30000828", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000828.tgz", - "integrity": "sha512-v+ySC6Ih8N8CyGZYd4svPipuFIqskKsTOi18chFM0qtu1G8mGuSYajb+h49XDWgmzX8MRDOp1Agw6KQaPUdIhg==", + "version": "1.0.30000833", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000833.tgz", + "integrity": "sha512-tKNuKu4WLImh4NxoTgntxFpDrRiA0Q6Q1NycNhuMST0Kx+Pt8YnRDW6V8xsyH6AtO2CpAoibatEk5eaEhP3O1g==", "dev": true }, "caseless": { @@ -1923,8 +1758,8 @@ "integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=", "dev": true, "requires": { - "align-text": "0.1.4", - "lazy-cache": "1.0.4" + "align-text": "^0.1.3", + "lazy-cache": "^1.0.3" } }, "chalk": { @@ -1933,9 +1768,9 @@ "integrity": "sha512-LvixLAQ4MYhbf7hgL4o5PeK32gJKvVzDRiSNIApDofQvyhl8adgG2lJVXn4+ekQoK7HL9RF8lqxwerpe0x2pCw==", "dev": true, "requires": { - "ansi-styles": "3.2.1", - "escape-string-regexp": "1.0.5", - "supports-color": "4.5.0" + "ansi-styles": "^3.1.0", + "escape-string-regexp": "^1.0.5", + "supports-color": "^4.0.0" } }, "chart.js": { @@ -1943,8 +1778,8 @@ "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-2.5.0.tgz", "integrity": "sha1-/m51Gok3afVucr7lrZEgfhxZKVc=", "requires": { - "chartjs-color": "2.2.0", - "moment": "2.20.1" + "chartjs-color": "^2.0.0", + "moment": "^2.10.6" } }, "chartjs-color": { @@ -1952,8 +1787,8 @@ "resolved": "https://registry.npmjs.org/chartjs-color/-/chartjs-color-2.2.0.tgz", "integrity": "sha1-hKL7dVeH7YXDndbdjHsdiEKbrq4=", "requires": { - "chartjs-color-string": "0.5.0", - "color-convert": "0.5.3" + "chartjs-color-string": "^0.5.0", + "color-convert": "^0.5.3" } }, "chartjs-color-string": { @@ -1961,7 +1796,7 @@ "resolved": "https://registry.npmjs.org/chartjs-color-string/-/chartjs-color-string-0.5.0.tgz", "integrity": "sha512-amWNvCOXlOUYxZVDSa0YOab5K/lmEhbFNKI55PWc4mlv28BDzA7zaoQTGxSBgJMHIW+hGX8YUrvw/FH4LyhwSQ==", "requires": { - "color-name": "1.1.3" + "color-name": "^1.0.0" } }, "chokidar": { @@ -1970,15 +1805,15 @@ "integrity": "sha1-eY5ol3gVHIB2tLNg5e3SjNortGg=", "dev": true, "requires": { - "anymatch": "1.3.2", - "async-each": "1.0.1", - "fsevents": "1.1.3", - "glob-parent": "2.0.0", - "inherits": "2.0.3", - "is-binary-path": "1.0.1", - "is-glob": "2.0.1", - "path-is-absolute": "1.0.1", - "readdirp": "2.1.0" + "anymatch": "^1.3.0", + "async-each": "^1.0.0", + "fsevents": "^1.0.0", + "glob-parent": "^2.0.0", + "inherits": "^2.0.1", + "is-binary-path": "^1.0.0", + "is-glob": "^2.0.0", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.0.0" } }, "chownr": { @@ -1993,8 +1828,8 @@ "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", "dev": true, "requires": { - "inherits": "2.0.3", - "safe-buffer": "5.1.1" + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" } }, "circular-dependency-plugin": { @@ -2004,9 +1839,9 @@ "dev": true }, "circular-json": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.5.3.tgz", - "integrity": "sha512-YlxLOimeIoQGHnMe3kbf8qIV2Bj7uXLbljMPRguNT49GmSAzooNfS9EJ91rSJKbLBOOzM5agvtx0WyechZN/Hw==", + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.5.4.tgz", + "integrity": "sha512-vnJA8KS0BfOihugYEUkLRcnmq21FbuivbxgzDLXNs3zIk4KllV4Mx4UuTzBXht9F00C7QfD1YqMXg1zP6EXpig==", "dev": true }, "class-utils": { @@ -2015,10 +1850,10 @@ "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", "dev": true, "requires": { - "arr-union": "3.1.0", - "define-property": "0.2.5", - "isobject": "3.0.1", - "static-extend": "0.1.2" + "arr-union": "^3.1.0", + "define-property": "^0.2.5", + "isobject": "^3.0.0", + "static-extend": "^0.1.1" }, "dependencies": { "define-property": { @@ -2027,7 +1862,7 @@ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "dev": true, "requires": { - "is-descriptor": "0.1.6" + "is-descriptor": "^0.1.0" } }, "isobject": { @@ -2044,7 +1879,7 @@ "integrity": "sha1-Ls3xRaujj1R0DybO/Q/z4D4SXWo=", "dev": true, "requires": { - "source-map": "0.5.7" + "source-map": "0.5.x" } }, "cliui": { @@ -2053,15 +1888,15 @@ "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", "dev": true, "requires": { - "string-width": "1.0.2", - "strip-ansi": "3.0.1", - "wrap-ansi": "2.1.0" + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wrap-ansi": "^2.0.0" } }, "clone": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", - "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.1.tgz", + "integrity": "sha1-0hfR6WERjjrJpLi7oyhVU79kfNs=", "dev": true }, "clone-deep": { @@ -2070,10 +1905,10 @@ "integrity": "sha512-SZegPTKjCgpQH63E+eN6mVEEPdQBOUzjyJm5Pora4lrwWRFS8I0QAxV/KD6vV/i0WuijHZWQC1fMsPEdxfdVCQ==", "dev": true, "requires": { - "for-own": "1.0.0", - "is-plain-object": "2.0.4", - "kind-of": "6.0.2", - "shallow-clone": "1.0.0" + "for-own": "^1.0.0", + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.0", + "shallow-clone": "^1.0.0" }, "dependencies": { "for-own": { @@ -2082,7 +1917,7 @@ "integrity": "sha1-xjMy9BXO3EsE2/5wz4NklMU8tEs=", "dev": true, "requires": { - "for-in": "1.0.2" + "for-in": "^1.0.1" } }, "kind-of": { @@ -2105,14 +1940,14 @@ "integrity": "sha512-MGMkPS5d9AqQEXTZ4grn/syl/7VvOehgWTeU2B41E22q767QolclfdfadKAndL287cIPEOEdwh9JBqCwQJLtFw==", "dev": true, "requires": { - "bluebird": "3.5.1", - "commander": "2.15.1", - "joi": "12.0.0", - "lcov-parse": "1.0.0", - "lodash": "4.17.5", - "log-driver": "1.2.7", - "request": "2.85.0", - "request-promise": "4.2.2" + "bluebird": "^3.5.x", + "commander": "^2.x", + "joi": "^12.x", + "lcov-parse": "^1.x", + "lodash": "^4.17.4", + "log-driver": "^1.x", + "request": "^2.83.0", + "request-promise": "^4.x" }, "dependencies": { "ajv": { @@ -2121,10 +1956,10 @@ "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", "dev": true, "requires": { - "co": "4.6.0", - "fast-deep-equal": "1.1.0", - "fast-json-stable-stringify": "2.0.0", - "json-schema-traverse": "0.3.1" + "co": "^4.6.0", + "fast-deep-equal": "^1.0.0", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.3.0" } }, "assert-plus": { @@ -2145,7 +1980,7 @@ "integrity": "sha1-T4owBctKfjiJ90kDD9JbluAdLjE=", "dev": true, "requires": { - "hoek": "4.2.1" + "hoek": "4.x.x" } }, "cryptiles": { @@ -2154,7 +1989,7 @@ "integrity": "sha1-qJ+7Ig9c4l7FboxKqKT9e1sNKf4=", "dev": true, "requires": { - "boom": "5.2.0" + "boom": "5.x.x" }, "dependencies": { "boom": { @@ -2163,7 +1998,7 @@ "integrity": "sha512-Z5BTk6ZRe4tXXQlkqftmsAUANpXmuwlsF5Oov8ThoMbQRzdGTA1ngYRW160GexgOgjsFOKJz0LYhoNi+2AMBUw==", "dev": true, "requires": { - "hoek": "4.2.1" + "hoek": "4.x.x" } } } @@ -2180,8 +2015,8 @@ "integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=", "dev": true, "requires": { - "ajv": "5.5.2", - "har-schema": "2.0.0" + "ajv": "^5.1.0", + "har-schema": "^2.0.0" } }, "hawk": { @@ -2190,10 +2025,10 @@ "integrity": "sha512-miowhl2+U7Qle4vdLqDdPt9m09K6yZhkLDTWGoUiUzrQCn+mHHSmfJgAyGaLRZbPmTqfFFjRV1QWCW0VWUJBbQ==", "dev": true, "requires": { - "boom": "4.3.1", - "cryptiles": "3.1.2", - "hoek": "4.2.1", - "sntp": "2.1.0" + "boom": "4.x.x", + "cryptiles": "3.x.x", + "hoek": "4.x.x", + "sntp": "2.x.x" } }, "hoek": { @@ -2208,9 +2043,9 @@ "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", "dev": true, "requires": { - "assert-plus": "1.0.0", - "jsprim": "1.4.1", - "sshpk": "1.14.1" + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" } }, "performance-now": { @@ -2225,28 +2060,28 @@ "integrity": "sha512-8H7Ehijd4js+s6wuVPLjwORxD4zeuyjYugprdOXlPSqaApmL/QOy+EB/beICHVCHkGMKNh5rvihb5ov+IDw4mg==", "dev": true, "requires": { - "aws-sign2": "0.7.0", - "aws4": "1.7.0", - "caseless": "0.12.0", - "combined-stream": "1.0.6", - "extend": "3.0.1", - "forever-agent": "0.6.1", - "form-data": "2.3.2", - "har-validator": "5.0.3", - "hawk": "6.0.2", - "http-signature": "1.2.0", - "is-typedarray": "1.0.0", - "isstream": "0.1.2", - "json-stringify-safe": "5.0.1", - "mime-types": "2.1.18", - "oauth-sign": "0.8.2", - "performance-now": "2.1.0", - "qs": "6.5.1", - "safe-buffer": "5.1.1", - "stringstream": "0.0.5", - "tough-cookie": "2.3.4", - "tunnel-agent": "0.6.0", - "uuid": "3.2.1" + "aws-sign2": "~0.7.0", + "aws4": "^1.6.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.5", + "extend": "~3.0.1", + "forever-agent": "~0.6.1", + "form-data": "~2.3.1", + "har-validator": "~5.0.3", + "hawk": "~6.0.2", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.17", + "oauth-sign": "~0.8.2", + "performance-now": "^2.1.0", + "qs": "~6.5.1", + "safe-buffer": "^5.1.1", + "stringstream": "~0.0.5", + "tough-cookie": "~2.3.3", + "tunnel-agent": "^0.6.0", + "uuid": "^3.1.0" } }, "sntp": { @@ -2255,7 +2090,7 @@ "integrity": "sha512-FL1b58BDrqS3A11lJ0zEdnJ3UOKqVxawAkF3k7F0CVN7VQ34aZrV+G8BZ1WC9ZL7NyrwsW0oviwsWDgRuVYtJg==", "dev": true, "requires": { - "hoek": "4.2.1" + "hoek": "4.x.x" } } } @@ -2267,17 +2102,17 @@ "dev": true }, "codelyzer": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/codelyzer/-/codelyzer-4.2.1.tgz", - "integrity": "sha512-CKwfgpfkqi9dyzy4s6ELaxJ54QgJ6A8iTSsM4bzHbLuTpbKncvNc3DUlCvpnkHBhK47gEf4qFsWoYqLrJPhy6g==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/codelyzer/-/codelyzer-4.3.0.tgz", + "integrity": "sha512-RLMrtLwrBS0dfo2/KTP+2NHofCpzcuh0bEp/A/naqvQonbUL4AW/qWQdbpn8dMNudtpmzEx9eS8KEpGdVPg1BA==", "dev": true, "requires": { - "app-root-path": "2.0.1", - "css-selector-tokenizer": "0.7.0", - "cssauron": "1.4.0", - "semver-dsl": "1.0.1", - "source-map": "0.5.7", - "sprintf-js": "1.0.3" + "app-root-path": "^2.0.1", + "css-selector-tokenizer": "^0.7.0", + "cssauron": "^1.4.0", + "semver-dsl": "^1.0.1", + "source-map": "^0.5.7", + "sprintf-js": "^1.0.3" } }, "collection-visit": { @@ -2286,8 +2121,8 @@ "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", "dev": true, "requires": { - "map-visit": "1.0.0", - "object-visit": "1.0.1" + "map-visit": "^1.0.0", + "object-visit": "^1.0.0" } }, "color-convert": { @@ -2312,27 +2147,7 @@ "integrity": "sha1-RYwH4J4NkA/Ci3Cj/sLazR0st/Y=", "dev": true, "requires": { - "lodash": "4.17.5" - } - }, - "combine-source-map": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/combine-source-map/-/combine-source-map-0.8.0.tgz", - "integrity": "sha1-pY0N8ELBhvz4IqjoAV9UUNLXmos=", - "dev": true, - "requires": { - "convert-source-map": "1.1.3", - "inline-source-map": "0.6.2", - "lodash.memoize": "3.0.4", - "source-map": "0.5.7" - }, - "dependencies": { - "convert-source-map": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.1.3.tgz", - "integrity": "sha1-SCnId+n+SbMWHzvzZziI4gRpmGA=", - "dev": true - } + "lodash": "^4.5.0" } }, "combined-stream": { @@ -2340,14 +2155,21 @@ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz", "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=", "requires": { - "delayed-stream": "1.0.0" + "delayed-stream": "~1.0.0" } }, "commander": { "version": "2.15.1", "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", - "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", - "dev": true + "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==" + }, + "comment-json": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/comment-json/-/comment-json-1.1.3.tgz", + "integrity": "sha1-aYbDMw/uDEyeAMI5jNYa+l2PI54=", + "requires": { + "json-parser": "^1.0.0" + } }, "common-tags": { "version": "1.7.2", @@ -2355,7 +2177,7 @@ "integrity": "sha512-joj9ZlUOjCrwdbmiLqafeUSgkUM74NqhLsZtSqDmhKudaIY197zTrb8JMl31fMnCUuxwFT23eC/oWvrZzDLRJQ==", "dev": true, "requires": { - "babel-runtime": "6.26.0" + "babel-runtime": "^6.26.0" } }, "commondir": { @@ -2393,7 +2215,7 @@ "integrity": "sha1-DRAgq5JLL9tNYnmHXH1tq6a6p6k=", "dev": true, "requires": { - "mime-db": "1.33.0" + "mime-db": ">= 1.33.0 < 2" } }, "compression": { @@ -2402,13 +2224,13 @@ "integrity": "sha1-qv+81qr4VLROuygDU9WtFlH1mmk=", "dev": true, "requires": { - "accepts": "1.3.5", + "accepts": "~1.3.4", "bytes": "3.0.0", - "compressible": "2.0.13", + "compressible": "~2.0.13", "debug": "2.6.9", - "on-headers": "1.0.1", + "on-headers": "~1.0.1", "safe-buffer": "5.1.1", - "vary": "1.1.2" + "vary": "~1.1.2" }, "dependencies": { "debug": { @@ -2419,6 +2241,12 @@ "requires": { "ms": "2.0.0" } + }, + "safe-buffer": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", + "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==", + "dev": true } } }, @@ -2433,10 +2261,36 @@ "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", "dev": true, "requires": { - "buffer-from": "1.0.0", - "inherits": "2.0.3", - "readable-stream": "2.3.6", - "typedarray": "0.0.6" + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "configstore": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/configstore/-/configstore-3.1.2.tgz", + "integrity": "sha512-vtv5HtGjcYUgFrXc6Kx747B83MRRVS5R1VTEQoXvuP+kMI+if6uywV0nDGoiydJRy4yk7h9od5Og0kxx4zUXmw==", + "requires": { + "dot-prop": "^4.1.0", + "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" + } + }, + "configstore-fork": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/configstore-fork/-/configstore-fork-3.1.6.tgz", + "integrity": "sha512-sQ31B6Ayj9Tqs2nPBrq+2cg8j9sb1Hd6iyqDx2rH+W1EoBDevYAkLk5ncNqktkj7hCeyrUzcNhQ5kUozKXd75A==", + "requires": { + "dot-prop": "^4.1.0", + "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" } }, "connect": { @@ -2447,7 +2301,7 @@ "requires": { "debug": "2.6.9", "finalhandler": "1.1.0", - "parseurl": "1.3.2", + "parseurl": "~1.3.2", "utils-merge": "1.0.1" }, "dependencies": { @@ -2467,12 +2321,12 @@ "dev": true, "requires": { "debug": "2.6.9", - "encodeurl": "1.0.2", - "escape-html": "1.0.3", - "on-finished": "2.3.0", - "parseurl": "1.3.2", - "statuses": "1.3.1", - "unpipe": "1.0.0" + "encodeurl": "~1.0.1", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.2", + "statuses": "~1.3.1", + "unpipe": "~1.0.0" } }, "statuses": { @@ -2495,7 +2349,7 @@ "integrity": "sha1-8CQcRXMKn8YyOyBtvzjtx0HQuxA=", "dev": true, "requires": { - "date-now": "0.1.4" + "date-now": "^0.1.4" } }, "console-control-strings": { @@ -2541,9 +2395,9 @@ "dev": true }, "cookiejar": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.1.tgz", - "integrity": "sha1-Qa1XsbVVlR7BcUEqgZQrHoIA00o=" + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.2.tgz", + "integrity": "sha512-Mw+adcfzPxcPeI+0WlvRrr/3lGVO0bD75SxX6811cxSh1Wbxx7xZBGK1eVtDf6si8rg2lhnUjsVLMFMfbRIuwA==" }, "copy-concurrently": { "version": "1.0.5", @@ -2551,12 +2405,12 @@ "integrity": "sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A==", "dev": true, "requires": { - "aproba": "1.2.0", - "fs-write-stream-atomic": "1.0.10", - "iferr": "0.1.5", - "mkdirp": "0.5.1", - "rimraf": "2.6.2", - "run-queue": "1.0.3" + "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" } }, "copy-descriptor": { @@ -2571,14 +2425,14 @@ "integrity": "sha512-v4THQ24Tks2NkyOvZuFDgZVfDD9YaA9rwYLZTrWg2GHIA8lrH5DboEyeoorh5Skki+PUbgSmnsCwhMWqYrQZrA==", "dev": true, "requires": { - "cacache": "10.0.4", - "find-cache-dir": "1.0.0", - "globby": "7.1.1", - "is-glob": "4.0.0", - "loader-utils": "1.1.0", - "minimatch": "3.0.4", - "p-limit": "1.2.0", - "serialize-javascript": "1.4.0" + "cacache": "^10.0.1", + "find-cache-dir": "^1.0.0", + "globby": "^7.1.1", + "is-glob": "^4.0.0", + "loader-utils": "^1.1.0", + "minimatch": "^3.0.4", + "p-limit": "^1.0.0", + "serialize-javascript": "^1.4.0" }, "dependencies": { "is-extglob": { @@ -2593,7 +2447,7 @@ "integrity": "sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A=", "dev": true, "requires": { - "is-extglob": "2.1.1" + "is-extglob": "^2.1.1" } } } @@ -2609,7 +2463,7 @@ "integrity": "sha512-sA2/4+/PZ/KV6CKgjrVrrUVBKCkdDO02CUlQ0YKTQoYUwPYNOtOAcWlbYhd5v/1JqYaA6oZ4sDlOU4ppVw6Wbg==", "dev": true, "requires": { - "chalk": "2.2.2" + "chalk": "^2.0.0" } }, "core-util-is": { @@ -2623,13 +2477,13 @@ "integrity": "sha512-GiNXLwAFPYHy25XmTPpafYvn3CLAkJ8FLsscq78MQd1Kh0OU6Yzhn4eV2MVF4G9WEQZoWEGltatdR+ntGPMl5A==", "dev": true, "requires": { - "is-directory": "0.3.1", - "js-yaml": "3.11.0", - "minimist": "1.2.0", - "object-assign": "4.1.1", - "os-homedir": "1.0.2", - "parse-json": "2.2.0", - "require-from-string": "1.2.1" + "is-directory": "^0.3.1", + "js-yaml": "^3.4.3", + "minimist": "^1.2.0", + "object-assign": "^4.1.0", + "os-homedir": "^1.0.1", + "parse-json": "^2.2.0", + "require-from-string": "^1.1.0" }, "dependencies": { "minimist": { @@ -2646,8 +2500,8 @@ "integrity": "sha512-iZvCCg8XqHQZ1ioNBTzXS/cQSkqkqcPs8xSX4upNB+DAk9Ht3uzQf2J32uAHNCne8LDmKr29AgZrEs4oIrwLuQ==", "dev": true, "requires": { - "bn.js": "4.11.8", - "elliptic": "6.4.0" + "bn.js": "^4.1.0", + "elliptic": "^6.0.0" } }, "create-hash": { @@ -2656,11 +2510,11 @@ "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", "dev": true, "requires": { - "cipher-base": "1.0.4", - "inherits": "2.0.3", - "md5.js": "1.3.4", - "ripemd160": "2.0.1", - "sha.js": "2.4.11" + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "md5.js": "^1.3.4", + "ripemd160": "^2.0.1", + "sha.js": "^2.4.0" } }, "create-hmac": { @@ -2669,12 +2523,12 @@ "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", "dev": true, "requires": { - "cipher-base": "1.0.4", - "create-hash": "1.2.0", - "inherits": "2.0.3", - "ripemd160": "2.0.1", - "safe-buffer": "5.1.1", - "sha.js": "2.4.11" + "cipher-base": "^1.0.3", + "create-hash": "^1.1.0", + "inherits": "^2.0.1", + "ripemd160": "^2.0.0", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" } }, "cross-spawn": { @@ -2684,8 +2538,8 @@ "dev": true, "optional": true, "requires": { - "lru-cache": "4.1.2", - "which": "1.3.0" + "lru-cache": "^4.0.1", + "which": "^1.2.9" } }, "cryptiles": { @@ -2694,7 +2548,7 @@ "integrity": "sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g=", "dev": true, "requires": { - "boom": "2.10.1" + "boom": "2.x.x" } }, "crypto-browserify": { @@ -2703,17 +2557,216 @@ "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", "dev": true, "requires": { - "browserify-cipher": "1.0.1", - "browserify-sign": "4.0.4", - "create-ecdh": "4.0.1", - "create-hash": "1.2.0", - "create-hmac": "1.1.7", - "diffie-hellman": "5.0.3", - "inherits": "2.0.3", - "pbkdf2": "3.0.14", - "public-encrypt": "4.0.2", - "randombytes": "2.0.6", - "randomfill": "1.0.4" + "browserify-cipher": "^1.0.0", + "browserify-sign": "^4.0.0", + "create-ecdh": "^4.0.0", + "create-hash": "^1.1.0", + "create-hmac": "^1.1.0", + "diffie-hellman": "^5.0.0", + "inherits": "^2.0.1", + "pbkdf2": "^3.0.3", + "public-encrypt": "^4.0.0", + "randombytes": "^2.0.0", + "randomfill": "^1.0.3" + } + }, + "crypto-random-string": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-1.0.0.tgz", + "integrity": "sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4=" + }, + "cspell": { + "version": "2.1.12", + "resolved": "https://registry.npmjs.org/cspell/-/cspell-2.1.12.tgz", + "integrity": "sha512-6ydTFFhPDirjOAl7ChrtlaVXiyqt1LmTRMpvYU1Jw9FarHxupnNq34itPL5BSDDjqeue5wI0A+VCjBnfAKuWMQ==", + "requires": { + "chalk": "^2.3.2", + "commander": "^2.15.1", + "comment-json": "^1.1.3", + "configstore-fork": "^3.1.6", + "cspell-dict-cpp": "^1.1.6", + "cspell-dict-django": "^1.0.2", + "cspell-dict-en-gb": "^1.1.0", + "cspell-dict-en_us": "^1.2.3", + "cspell-dict-golang": "^1.1.3", + "cspell-dict-latex": "^1.0.1", + "cspell-dict-php": "^1.0.2", + "cspell-dict-python": "^1.0.3", + "cspell-dict-rust": "^1.0.0", + "cspell-lib": "^2.0.2", + "cspell-trie": "^2.0.3", + "fs-extra": "^5.0.0", + "gensequence": "^2.1.1", + "glob": "^7.1.2", + "minimatch": "^3.0.4", + "rxjs": "^5.5.10", + "rxjs-from-iterable": "^1.0.5", + "vscode-uri": "^1.0.3", + "xregexp": "^4.1.1" + }, + "dependencies": { + "chalk": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "fs-extra": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-5.0.0.tgz", + "integrity": "sha512-66Pm4RYbjzdyeuqudYqhFiNBbCIuI9kgRqLPSHIlXHidW8NIQtVdkM1yeZ4lXwuhbTETv3EUGMNHAAw6hiundQ==", + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" + }, + "rxjs": { + "version": "5.5.10", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.5.10.tgz", + "integrity": "sha512-SRjimIDUHJkon+2hFo7xnvNC4ZEHGzCRwh9P7nzX3zPkCGFEg/tuElrNR7L/rZMagnK2JeH2jQwPRpmyXyLB6A==", + "requires": { + "symbol-observable": "1.0.1" + } + }, + "supports-color": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", + "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "requires": { + "has-flag": "^3.0.0" + } + }, + "symbol-observable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.0.1.tgz", + "integrity": "sha1-g0D8RwLDEi310iKI+IKD9RPT/dQ=" + }, + "xregexp": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-4.1.1.tgz", + "integrity": "sha512-QJ1gfSUV7kEOLfpKFCjBJRnfPErUzkNKFMso4kDSmGpp3x6ZgkyKf74inxI7PnnQCFYq5TqYJCd7DrgDN8Q05A==" + } + } + }, + "cspell-dict-cpp": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/cspell-dict-cpp/-/cspell-dict-cpp-1.1.6.tgz", + "integrity": "sha512-eVNenrvoViBsrfZYzChoJ1YJ5b0VxwgYCFhxxKjL3Bjk3Te98FM8Bk/ExSnv5KlwT56EhT5y+CBi0ejEv5XW/g==", + "requires": { + "configstore": "^3.1.0" + } + }, + "cspell-dict-django": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cspell-dict-django/-/cspell-dict-django-1.0.2.tgz", + "integrity": "sha512-t52Ga2S7GgsCYaLsN+iqWLgHUHfqGfkMUv0gSjp2QOVOxGNQ4kjj1oJ6GkcZB5k6UnI2sgQ7uku/bjmNlnctDw==", + "requires": { + "configstore": "^3.1.0" + } + }, + "cspell-dict-en-gb": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/cspell-dict-en-gb/-/cspell-dict-en-gb-1.1.2.tgz", + "integrity": "sha512-ry4yNIzjVMZDwSrK7U/0M9vXdbzo7ZFpUHUAYa4r6B4CwUFHBC/d8C0fcav2Hf1jjfQ866AApg00HiRJLP3Aug==", + "requires": { + "configstore": "^3.1.1" + } + }, + "cspell-dict-en_us": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/cspell-dict-en_us/-/cspell-dict-en_us-1.2.5.tgz", + "integrity": "sha512-OaoOaVzIGxJeeyAjZE6Re2+S5OuLHsMFzQcZ3qaD87uO0Q7U9TF6EFPJEmwNuR0Ye9o1rXYYMiFKan6EzH40qg==", + "requires": { + "configstore": "^3.1.0" + } + }, + "cspell-dict-golang": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/cspell-dict-golang/-/cspell-dict-golang-1.1.3.tgz", + "integrity": "sha512-maImSmm4ctZ5cQ3tFyZr5nglBQ/GwY5OYT1eq6O6USjJRQxzWtzmI1EV8WKng1UEA1i07NqgUDQLejTx3W1hIg==", + "requires": { + "configstore": "^3.1.0" + } + }, + "cspell-dict-latex": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cspell-dict-latex/-/cspell-dict-latex-1.0.1.tgz", + "integrity": "sha512-RyK6TgEpt6AfGbXStPi5G7Fy2CFJGmIAYs1ie6Qsyss7h3sAsHk19F2yZvwJU0dlHXyw4LujFbzo9ZR3HR7BIA==", + "requires": { + "configstore": "^3.1.0" + } + }, + "cspell-dict-php": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cspell-dict-php/-/cspell-dict-php-1.0.2.tgz", + "integrity": "sha512-F2NyT9rZ6CmcLhbeGH8OKwLlD9Q5zQConpNDVdu/E/ucEQ+HXIf65Ou5KJzlQcyUGC8P9PUmmUBEF60oW5HcCA==", + "requires": { + "configstore": "^3.1.0" + } + }, + "cspell-dict-python": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/cspell-dict-python/-/cspell-dict-python-1.0.3.tgz", + "integrity": "sha512-zQYJu/kWvsZusuMelRk7e9B5xINglh49uLswLv598T+ZptzRzKO83im/yCG0RHBWvgYS+ze0eCpr0Ja5uqHu3Q==", + "requires": { + "configstore": "^3.1.0" + } + }, + "cspell-dict-rust": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/cspell-dict-rust/-/cspell-dict-rust-1.0.0.tgz", + "integrity": "sha512-9Rew1ad6yPXQW9fxFQ/J41+fbKg6t9p4oSm7JS0aHuCtWc3w0knFYK7Ty/mVMw6oDFlemBNaBCPZON0M3+oIKg==", + "requires": { + "configstore": "^3.1.0" + } + }, + "cspell-lib": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/cspell-lib/-/cspell-lib-2.0.2.tgz", + "integrity": "sha512-AcNlLBuOFR0xZGO9SwP0g42oFvu8TMooehLJ7FqDzAvCWqtO1u0ddvJ7bm8l8UZgABVchUSy2oqcLVAr4XdCvA==", + "requires": { + "iconv-lite": "^0.4.19", + "rxjs": "^5.5.6", + "rxjs-stream": "^1.1.0" + }, + "dependencies": { + "rxjs": { + "version": "5.5.10", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.5.10.tgz", + "integrity": "sha512-SRjimIDUHJkon+2hFo7xnvNC4ZEHGzCRwh9P7nzX3zPkCGFEg/tuElrNR7L/rZMagnK2JeH2jQwPRpmyXyLB6A==", + "requires": { + "symbol-observable": "1.0.1" + } + }, + "symbol-observable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.0.1.tgz", + "integrity": "sha1-g0D8RwLDEi310iKI+IKD9RPT/dQ=" + } + } + }, + "cspell-trie": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/cspell-trie/-/cspell-trie-2.0.3.tgz", + "integrity": "sha512-5jJiOPbKWzHNoJAwOQ/fkBXJ2xs+P5zfQmj2OEqq1k4Ck5/88KLEkXJmNOPDG8nDP1va5kaLz3vUtAxABo1bwg==", + "requires": { + "commander": "^2.11.0", + "cspell-lib": "^2.0.1", + "fs-extra": "^4.0.2", + "gensequence": "^2.1.1", + "rxjs": "^5.5.2", + "rxjs-from-iterable": "^1.0.5", + "rxjs-stream": "^1.0.4" } }, "css-parse": { @@ -2728,10 +2781,10 @@ "integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=", "dev": true, "requires": { - "boolbase": "1.0.0", - "css-what": "2.1.0", + "boolbase": "~1.0.0", + "css-what": "2.1", "domutils": "1.5.1", - "nth-check": "1.0.1" + "nth-check": "~1.0.1" } }, "css-selector-tokenizer": { @@ -2740,9 +2793,9 @@ "integrity": "sha1-5piEdK6MlTR3v15+/s/OzNnPTIY=", "dev": true, "requires": { - "cssesc": "0.1.0", - "fastparse": "1.1.1", - "regexpu-core": "1.0.0" + "cssesc": "^0.1.0", + "fastparse": "^1.1.1", + "regexpu-core": "^1.0.0" } }, "css-what": { @@ -2757,7 +2810,7 @@ "integrity": "sha1-pmAt/34EqDBtwNuaVR6S6LVmKtg=", "dev": true, "requires": { - "through": "2.3.8" + "through": "X.X.X" } }, "cssesc": { @@ -2778,7 +2831,7 @@ "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=", "dev": true, "requires": { - "array-find-index": "1.0.2" + "array-find-index": "^1.0.1" } }, "custom-event": { @@ -2798,7 +2851,7 @@ "resolved": "https://registry.npmjs.org/d/-/d-0.1.1.tgz", "integrity": "sha1-2hhMU10Y2O57oqoim5FACfrhEwk=", "requires": { - "es5-ext": "0.10.42" + "es5-ext": "~0.10.2" } }, "dashdash": { @@ -2807,7 +2860,7 @@ "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", "dev": true, "requires": { - "assert-plus": "1.0.0" + "assert-plus": "^1.0.0" }, "dependencies": { "assert-plus": { @@ -2876,7 +2929,7 @@ "integrity": "sha1-836hXT4T/9m0N9M+GnW1+5eHTLg=", "dev": true, "requires": { - "strip-bom": "2.0.0" + "strip-bom": "^2.0.0" } }, "define-properties": { @@ -2885,8 +2938,8 @@ "integrity": "sha1-g6c/L+pWmJj7c3GTyPhzyvbUXJQ=", "dev": true, "requires": { - "foreach": "2.0.5", - "object-keys": "1.0.11" + "foreach": "^2.0.5", + "object-keys": "^1.0.8" } }, "define-property": { @@ -2895,8 +2948,8 @@ "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", "dev": true, "requires": { - "is-descriptor": "1.0.2", - "isobject": "3.0.1" + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" }, "dependencies": { "is-accessor-descriptor": { @@ -2905,7 +2958,7 @@ "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", "dev": true, "requires": { - "kind-of": "6.0.2" + "kind-of": "^6.0.0" } }, "is-data-descriptor": { @@ -2914,7 +2967,7 @@ "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", "dev": true, "requires": { - "kind-of": "6.0.2" + "kind-of": "^6.0.0" } }, "is-descriptor": { @@ -2923,9 +2976,9 @@ "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", "dev": true, "requires": { - "is-accessor-descriptor": "1.0.0", - "is-data-descriptor": "1.0.0", - "kind-of": "6.0.2" + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" } }, "isobject": { @@ -2942,12 +2995,6 @@ } } }, - "defined": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz", - "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=", - "dev": true - }, "degenerator": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-1.0.4.tgz", @@ -2955,9 +3002,9 @@ "dev": true, "optional": true, "requires": { - "ast-types": "0.11.3", - "escodegen": "1.9.1", - "esprima": "3.1.3" + "ast-types": "0.x.x", + "escodegen": "1.x.x", + "esprima": "3.x.x" }, "dependencies": { "esprima": { @@ -2975,12 +3022,12 @@ "integrity": "sha1-U+z2mf/LyzljdpGrE7rxYIGXZuU=", "dev": true, "requires": { - "globby": "6.1.0", - "is-path-cwd": "1.0.0", - "is-path-in-cwd": "1.0.1", - "p-map": "1.2.0", - "pify": "3.0.0", - "rimraf": "2.6.2" + "globby": "^6.1.0", + "is-path-cwd": "^1.0.0", + "is-path-in-cwd": "^1.0.0", + "p-map": "^1.1.1", + "pify": "^3.0.0", + "rimraf": "^2.2.8" }, "dependencies": { "globby": { @@ -2989,11 +3036,11 @@ "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", "dev": true, "requires": { - "array-union": "1.0.2", - "glob": "7.1.2", - "object-assign": "4.1.1", - "pify": "2.3.0", - "pinkie-promise": "2.0.1" + "array-union": "^1.0.1", + "glob": "^7.0.3", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" }, "dependencies": { "pify": { @@ -3029,26 +3076,14 @@ "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", "dev": true }, - "deps-sort": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/deps-sort/-/deps-sort-2.0.0.tgz", - "integrity": "sha1-CRckkC6EZYJg65EHSMzNGvbiH7U=", - "dev": true, - "requires": { - "JSONStream": "1.3.2", - "shasum": "1.0.2", - "subarg": "1.0.0", - "through2": "2.0.3" - } - }, "des.js": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.0.tgz", "integrity": "sha1-wHTS4qpqipoH29YfmhXCzYPsjsw=", "dev": true, "requires": { - "inherits": "2.0.3", - "minimalistic-assert": "1.0.1" + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" } }, "destroy": { @@ -3063,7 +3098,7 @@ "integrity": "sha1-920GQ1LN9Docts5hnE7jqUdd4gg=", "dev": true, "requires": { - "repeating": "2.0.1" + "repeating": "^2.0.0" } }, "detect-node": { @@ -3072,16 +3107,6 @@ "integrity": "sha1-ogM8CcyOFY03dI+951B4Mr1s4Sc=", "dev": true }, - "detective": { - "version": "4.7.1", - "resolved": "https://registry.npmjs.org/detective/-/detective-4.7.1.tgz", - "integrity": "sha512-H6PmeeUcZloWtdt4DAkFyzFL94arpHr3NOwwmVILFiy+9Qd4JTxxXrzfyGk/lmct2qVGBwTSwSXagqu2BxmWig==", - "dev": true, - "requires": { - "acorn": "5.5.3", - "defined": "1.0.0" - } - }, "di": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz", @@ -3100,9 +3125,9 @@ "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", "dev": true, "requires": { - "bn.js": "4.11.8", - "miller-rabin": "4.0.1", - "randombytes": "2.0.6" + "bn.js": "^4.1.0", + "miller-rabin": "^4.0.0", + "randombytes": "^2.0.0" } }, "dir-glob": { @@ -3111,8 +3136,8 @@ "integrity": "sha512-37qirFDz8cA5fimp9feo43fSuRo2gHwaIn6dXL8Ber1dGwUosDrGZeCCXq57WnIqE4aQ+u3eQZzsk1yOzhdwag==", "dev": true, "requires": { - "arrify": "1.0.1", - "path-type": "3.0.0" + "arrify": "^1.0.1", + "path-type": "^3.0.0" } }, "dns-equal": { @@ -3127,8 +3152,8 @@ "integrity": "sha512-0UxfQkMhYAUaZI+xrNZOz/as5KgDU0M/fQ9b6SpkyLbk3GEswDi6PADJVaYJradtRVsRIlF1zLyOodbcTCDzUg==", "dev": true, "requires": { - "ip": "1.1.5", - "safe-buffer": "5.1.1" + "ip": "^1.1.0", + "safe-buffer": "^5.0.1" } }, "dns-txt": { @@ -3137,7 +3162,7 @@ "integrity": "sha1-uR2Ab10nGI5Ks+fRB9iBocxGQrY=", "dev": true, "requires": { - "buffer-indexof": "1.1.1" + "buffer-indexof": "^1.0.0" } }, "dom-converter": { @@ -3146,7 +3171,7 @@ "integrity": "sha1-pF71cnuJDJv/5tfIduexnLDhfzs=", "dev": true, "requires": { - "utila": "0.3.3" + "utila": "~0.3" }, "dependencies": { "utila": { @@ -3163,10 +3188,10 @@ "integrity": "sha1-ViromZ9Evl6jB29UGdzVnrQ6yVs=", "dev": true, "requires": { - "custom-event": "1.0.1", - "ent": "2.2.0", - "extend": "3.0.1", - "void-elements": "2.0.1" + "custom-event": "~1.0.0", + "ent": "~2.2.0", + "extend": "^3.0.0", + "void-elements": "^2.0.0" } }, "dom-serializer": { @@ -3175,8 +3200,8 @@ "integrity": "sha1-BzxpdUbOB4DOI75KKOKT5AvDDII=", "dev": true, "requires": { - "domelementtype": "1.1.3", - "entities": "1.1.1" + "domelementtype": "~1.1.1", + "entities": "~1.1.1" }, "dependencies": { "domelementtype": { @@ -3205,7 +3230,7 @@ "integrity": "sha1-0mRvXlf2w7qxHPbLBdPArPdBJZQ=", "dev": true, "requires": { - "domelementtype": "1.3.0" + "domelementtype": "1" } }, "domutils": { @@ -3214,8 +3239,16 @@ "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", "dev": true, "requires": { - "dom-serializer": "0.1.0", - "domelementtype": "1.3.0" + "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": "sha512-tUMXrxlExSW6U2EXiiKGSBVdYgtV8qlHL+C10TsW4PURY/ic+eaysnSkwB4kA/mBlCyy/IKDJ+Lc3wbWeaXtuQ==", + "requires": { + "is-obj": "^1.0.0" } }, "double-ended-queue": { @@ -3225,25 +3258,16 @@ "dev": true, "optional": true }, - "duplexer2": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", - "integrity": "sha1-ixLauHjA1p4+eJEFFmKjL8a93ME=", - "dev": true, - "requires": { - "readable-stream": "2.3.6" - } - }, "duplexify": { "version": "3.5.4", "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.5.4.tgz", "integrity": "sha512-JzYSLYMhoVVBe8+mbHQ4KgpvHpm0DZpJuL8PY93Vyv1fW7jYJ90LoXa1di/CVbJM+TgMs91rbDapE/RNIfnJsA==", "dev": true, "requires": { - "end-of-stream": "1.4.1", - "inherits": "2.0.3", - "readable-stream": "2.3.6", - "stream-shift": "1.0.0" + "end-of-stream": "^1.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.0.0", + "stream-shift": "^1.0.0" } }, "ecc-jsbn": { @@ -3253,7 +3277,7 @@ "dev": true, "optional": true, "requires": { - "jsbn": "0.1.1" + "jsbn": "~0.1.0" } }, "ee-first": { @@ -3263,15 +3287,15 @@ "dev": true }, "ejs": { - "version": "2.5.8", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-2.5.8.tgz", - "integrity": "sha512-QIDZL54fyV8MDcAsO91BMH1ft2qGGaHIJsJIA/+t+7uvXol1dm413fPcUgUb4k8F/9457rx4/KFE4XfDifrQxQ==", + "version": "2.5.9", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-2.5.9.tgz", + "integrity": "sha512-GJCAeDBKfREgkBtgrYSf9hQy9kTb3helv0zGdzqhM7iAkW8FA/ZF97VQDbwFiwIT8MQLLOe5VlPZOEvZAqtUAQ==", "dev": true }, "electron-to-chromium": { - "version": "1.3.42", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.42.tgz", - "integrity": "sha1-lcM78B0MxAVVauyJn+Yf1NduoPk=", + "version": "1.3.45", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.45.tgz", + "integrity": "sha1-RYrBscXHYM6IEaFtK/vZfsMLr7g=", "dev": true }, "elliptic": { @@ -3280,13 +3304,13 @@ "integrity": "sha1-ysmvh2LIWDYYcAPI3+GT5eLq5d8=", "dev": true, "requires": { - "bn.js": "4.11.8", - "brorand": "1.1.0", - "hash.js": "1.1.3", - "hmac-drbg": "1.0.1", - "inherits": "2.0.3", - "minimalistic-assert": "1.0.1", - "minimalistic-crypto-utils": "1.0.1" + "bn.js": "^4.4.0", + "brorand": "^1.0.1", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.0" } }, "ember-cli-string-utils": { @@ -3312,7 +3336,7 @@ "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", "dev": true, "requires": { - "once": "1.4.0" + "once": "^1.4.0" } }, "engine.io": { @@ -3321,13 +3345,13 @@ "integrity": "sha512-D06ivJkYxyRrcEe0bTpNnBQNgP9d3xog+qZlLbui8EsMr/DouQpf5o9FzJnWYHEYE0YsFHllUv2R1dkgYZXHcA==", "dev": true, "requires": { - "accepts": "1.3.5", + "accepts": "~1.3.4", "base64id": "1.0.0", "cookie": "0.3.1", - "debug": "3.1.0", - "engine.io-parser": "2.1.2", - "uws": "9.14.0", - "ws": "3.3.3" + "debug": "~3.1.0", + "engine.io-parser": "~2.1.0", + "uws": "~9.14.0", + "ws": "~3.3.1" } }, "engine.io-client": { @@ -3338,14 +3362,14 @@ "requires": { "component-emitter": "1.2.1", "component-inherit": "0.0.3", - "debug": "3.1.0", - "engine.io-parser": "2.1.2", + "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.3", - "xmlhttprequest-ssl": "1.5.5", + "ws": "~3.3.1", + "xmlhttprequest-ssl": "~1.5.4", "yeast": "0.1.2" } }, @@ -3356,10 +3380,10 @@ "dev": true, "requires": { "after": "0.8.2", - "arraybuffer.slice": "0.0.7", + "arraybuffer.slice": "~0.0.7", "base64-arraybuffer": "0.1.5", "blob": "0.0.4", - "has-binary2": "1.0.2" + "has-binary2": "~1.0.2" } }, "enhanced-resolve": { @@ -3368,10 +3392,10 @@ "integrity": "sha1-BCHjOf1xQZs9oT0Smzl5BAIwR24=", "dev": true, "requires": { - "graceful-fs": "4.1.11", - "memory-fs": "0.4.1", - "object-assign": "4.1.1", - "tapable": "0.2.8" + "graceful-fs": "^4.1.2", + "memory-fs": "^0.4.0", + "object-assign": "^4.0.1", + "tapable": "^0.2.7" } }, "ent": { @@ -3392,7 +3416,7 @@ "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==", "dev": true, "requires": { - "prr": "1.0.1" + "prr": "~1.0.1" } }, "error-ex": { @@ -3401,7 +3425,7 @@ "integrity": "sha1-+FWobOYa3E6GIcPNoh56dhLDqNw=", "dev": true, "requires": { - "is-arrayish": "0.2.1" + "is-arrayish": "^0.2.1" } }, "es-abstract": { @@ -3410,11 +3434,11 @@ "integrity": "sha512-ZnQrE/lXTTQ39ulXZ+J1DTFazV9qBy61x2bY071B+qGco8Z8q1QddsLdt/EF8Ai9hcWH72dWS0kFqXLxOxqslA==", "dev": 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": "^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": { @@ -3423,9 +3447,9 @@ "integrity": "sha1-RTVSSKiJeQNLZ5Lhm7gfK3l13Q0=", "dev": true, "requires": { - "is-callable": "1.1.3", - "is-date-object": "1.0.1", - "is-symbol": "1.0.1" + "is-callable": "^1.1.1", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.1" } }, "es5-ext": { @@ -3433,9 +3457,9 @@ "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.42.tgz", "integrity": "sha512-AJxO1rmPe1bDEfSR6TJ/FgMFYuTBhR5R57KW58iCkYACMyFbrkqVyzXSurYoScDGvgyMpk7uRF/lPUPPTmsRSA==", "requires": { - "es6-iterator": "2.0.3", - "es6-symbol": "3.1.1", - "next-tick": "1.0.0" + "es6-iterator": "~2.0.3", + "es6-symbol": "~3.1.1", + "next-tick": "1" } }, "es6-iterator": { @@ -3443,9 +3467,9 @@ "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", "requires": { - "d": "1.0.0", - "es5-ext": "0.10.42", - "es6-symbol": "3.1.1" + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" }, "dependencies": { "d": { @@ -3453,7 +3477,7 @@ "resolved": "https://registry.npmjs.org/d/-/d-1.0.0.tgz", "integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=", "requires": { - "es5-ext": "0.10.42" + "es5-ext": "^0.10.9" } } } @@ -3464,12 +3488,12 @@ "integrity": "sha1-kTbgUD3MBqMBaQ8LsU/042TpSfA=", "dev": true, "requires": { - "d": "1.0.0", - "es5-ext": "0.10.42", - "es6-iterator": "2.0.3", - "es6-set": "0.1.5", - "es6-symbol": "3.1.1", - "event-emitter": "0.3.5" + "d": "1", + "es5-ext": "~0.10.14", + "es6-iterator": "~2.0.1", + "es6-set": "~0.1.5", + "es6-symbol": "~3.1.1", + "event-emitter": "~0.3.5" }, "dependencies": { "d": { @@ -3478,7 +3502,7 @@ "integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=", "dev": true, "requires": { - "es5-ext": "0.10.42" + "es5-ext": "^0.10.9" } }, "event-emitter": { @@ -3487,23 +3511,46 @@ "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=", "dev": true, "requires": { - "d": "1.0.0", - "es5-ext": "0.10.42" + "d": "1", + "es5-ext": "~0.10.14" } } } }, + "es6-promise": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.0.2.tgz", + "integrity": "sha1-AQ1YWEI6XxGJeWZfRkhqlcbuK7Y=", + "dev": true + }, + "es6-promisify": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", + "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", + "dev": true, + "requires": { + "es6-promise": "^4.0.3" + }, + "dependencies": { + "es6-promise": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.4.tgz", + "integrity": "sha512-/NdNZVJg+uZgtm9eS3O6lrOLYmQag2DjdEXuPaHlZ6RuVqgqaVZfgYCepEIKsLqwdQArOPtC3XzRLqGGfT8KQQ==", + "dev": true + } + } + }, "es6-set": { "version": "0.1.5", "resolved": "https://registry.npmjs.org/es6-set/-/es6-set-0.1.5.tgz", "integrity": "sha1-0rPsXU2ADO2BjbU40ol02wpzzLE=", "dev": true, "requires": { - "d": "1.0.0", - "es5-ext": "0.10.42", - "es6-iterator": "2.0.3", + "d": "1", + "es5-ext": "~0.10.14", + "es6-iterator": "~2.0.1", "es6-symbol": "3.1.1", - "event-emitter": "0.3.5" + "event-emitter": "~0.3.5" }, "dependencies": { "d": { @@ -3512,7 +3559,7 @@ "integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=", "dev": true, "requires": { - "es5-ext": "0.10.42" + "es5-ext": "^0.10.9" } }, "event-emitter": { @@ -3521,8 +3568,8 @@ "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=", "dev": true, "requires": { - "d": "1.0.0", - "es5-ext": "0.10.42" + "d": "1", + "es5-ext": "~0.10.14" } } } @@ -3532,8 +3579,8 @@ "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.1.tgz", "integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=", "requires": { - "d": "1.0.0", - "es5-ext": "0.10.42" + "d": "1", + "es5-ext": "~0.10.14" }, "dependencies": { "d": { @@ -3541,7 +3588,7 @@ "resolved": "https://registry.npmjs.org/d/-/d-1.0.0.tgz", "integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=", "requires": { - "es5-ext": "0.10.42" + "es5-ext": "^0.10.9" } } } @@ -3552,10 +3599,10 @@ "integrity": "sha1-XjqzIlH/0VOKH45f+hNXdy+S2W8=", "dev": true, "requires": { - "d": "1.0.0", - "es5-ext": "0.10.42", - "es6-iterator": "2.0.3", - "es6-symbol": "3.1.1" + "d": "1", + "es5-ext": "^0.10.14", + "es6-iterator": "^2.0.1", + "es6-symbol": "^3.1.1" }, "dependencies": { "d": { @@ -3564,7 +3611,7 @@ "integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=", "dev": true, "requires": { - "es5-ext": "0.10.42" + "es5-ext": "^0.10.9" } } } @@ -3578,8 +3625,7 @@ "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 + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" }, "escodegen": { "version": "1.9.1", @@ -3588,11 +3634,11 @@ "dev": true, "optional": true, "requires": { - "esprima": "3.1.3", - "estraverse": "4.2.0", - "esutils": "2.0.2", - "optionator": "0.8.2", - "source-map": "0.6.1" + "esprima": "^3.1.3", + "estraverse": "^4.2.0", + "esutils": "^2.0.2", + "optionator": "^0.8.1", + "source-map": "~0.6.1" }, "dependencies": { "esprima": { @@ -3617,10 +3663,10 @@ "integrity": "sha1-4Bl16BJ4GhY6ba392AOY3GTIicM=", "dev": true, "requires": { - "es6-map": "0.1.5", - "es6-weak-map": "2.0.2", - "esrecurse": "4.2.1", - "estraverse": "4.2.0" + "es6-map": "^0.1.3", + "es6-weak-map": "^2.0.1", + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" } }, "esprima": { @@ -3635,7 +3681,7 @@ "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", "dev": true, "requires": { - "estraverse": "4.2.0" + "estraverse": "^4.1.0" } }, "estraverse": { @@ -3666,14 +3712,14 @@ "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.4.tgz", "integrity": "sha1-jWPd+0z+H647MsomXExyAiIIC7U=", "requires": { - "d": "0.1.1", - "es5-ext": "0.10.42" + "d": "~0.1.1", + "es5-ext": "~0.10.7" } }, "eventemitter3": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-1.2.0.tgz", - "integrity": "sha1-HIaZHYFq0eUEdQ5zh0Ik7PO+xQg=", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.0.tgz", + "integrity": "sha512-ivIvhpq/Y0uSjcHDcOIccjmYjGLcP09MFGE7ysAwkAvkXfpZlC985pH2/ui64DKazbTW/4kN3yqozUxlXzI6cA==", "dev": true }, "events": { @@ -3688,7 +3734,7 @@ "integrity": "sha1-Cs7ehJ7X3RzMMsgRuxG5RNTykjI=", "dev": true, "requires": { - "original": "1.0.0" + "original": ">=0.0.5" } }, "evp_bytestokey": { @@ -3697,8 +3743,8 @@ "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", "dev": true, "requires": { - "md5.js": "1.3.4", - "safe-buffer": "5.1.1" + "md5.js": "^1.3.4", + "safe-buffer": "^5.1.1" } }, "execa": { @@ -3707,13 +3753,13 @@ "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", "dev": true, "requires": { - "cross-spawn": "5.1.0", - "get-stream": "3.0.0", - "is-stream": "1.1.0", - "npm-run-path": "2.0.2", - "p-finally": "1.0.0", - "signal-exit": "3.0.2", - "strip-eof": "1.0.0" + "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": { @@ -3722,9 +3768,9 @@ "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", "dev": true, "requires": { - "lru-cache": "4.1.2", - "shebang-command": "1.2.0", - "which": "1.3.0" + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" } } } @@ -3741,9 +3787,9 @@ "integrity": "sha1-SIsdHSRRyz06axks/AMPRMWFX+o=", "dev": true, "requires": { - "array-slice": "0.2.3", - "array-unique": "0.2.1", - "braces": "0.1.5" + "array-slice": "^0.2.3", + "array-unique": "^0.2.1", + "braces": "^0.1.2" }, "dependencies": { "braces": { @@ -3752,7 +3798,7 @@ "integrity": "sha1-wIVxEIUpHYt1/ddOqw+FlygHEeY=", "dev": true, "requires": { - "expand-range": "0.1.1" + "expand-range": "^0.1.0" } }, "expand-range": { @@ -3761,8 +3807,8 @@ "integrity": "sha1-TLjtoJk8pW+k9B/ELzy7TMrf8EQ=", "dev": true, "requires": { - "is-number": "0.1.1", - "repeat-string": "0.2.2" + "is-number": "^0.1.1", + "repeat-string": "^0.2.2" } }, "is-number": { @@ -3785,7 +3831,7 @@ "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", "dev": true, "requires": { - "is-posix-bracket": "0.1.1" + "is-posix-bracket": "^0.1.0" } }, "expand-range": { @@ -3794,7 +3840,7 @@ "integrity": "sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=", "dev": true, "requires": { - "fill-range": "2.2.3" + "fill-range": "^2.1.0" } }, "express": { @@ -3803,36 +3849,36 @@ "integrity": "sha1-avilAjUNsyRuzEvs9rWjTSL37VM=", "dev": true, "requires": { - "accepts": "1.3.5", + "accepts": "~1.3.5", "array-flatten": "1.1.1", "body-parser": "1.18.2", "content-disposition": "0.5.2", - "content-type": "1.0.4", + "content-type": "~1.0.4", "cookie": "0.3.1", "cookie-signature": "1.0.6", "debug": "2.6.9", - "depd": "1.1.2", - "encodeurl": "1.0.2", - "escape-html": "1.0.3", - "etag": "1.8.1", + "depd": "~1.1.2", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", "finalhandler": "1.1.1", "fresh": "0.5.2", "merge-descriptors": "1.0.1", - "methods": "1.1.2", - "on-finished": "2.3.0", - "parseurl": "1.3.2", + "methods": "~1.1.2", + "on-finished": "~2.3.0", + "parseurl": "~1.3.2", "path-to-regexp": "0.1.7", - "proxy-addr": "2.0.3", + "proxy-addr": "~2.0.3", "qs": "6.5.1", - "range-parser": "1.2.0", + "range-parser": "~1.2.0", "safe-buffer": "5.1.1", "send": "0.16.2", "serve-static": "1.13.2", "setprototypeof": "1.1.0", - "statuses": "1.4.0", - "type-is": "1.6.16", + "statuses": "~1.4.0", + "type-is": "~1.6.16", "utils-merge": "1.0.1", - "vary": "1.1.2" + "vary": "~1.1.2" }, "dependencies": { "array-flatten": { @@ -3849,6 +3895,12 @@ "requires": { "ms": "2.0.0" } + }, + "safe-buffer": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", + "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==", + "dev": true } } }, @@ -3863,8 +3915,8 @@ "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", "dev": true, "requires": { - "assign-symbols": "1.0.0", - "is-extendable": "1.0.1" + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" }, "dependencies": { "is-extendable": { @@ -3873,7 +3925,7 @@ "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", "dev": true, "requires": { - "is-plain-object": "2.0.4" + "is-plain-object": "^2.0.4" } } } @@ -3884,7 +3936,7 @@ "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", "dev": true, "requires": { - "is-extglob": "1.0.0" + "is-extglob": "^1.0.0" } }, "extract-text-webpack-plugin": { @@ -3893,10 +3945,10 @@ "integrity": "sha512-bt/LZ4m5Rqt/Crl2HiKuAl/oqg0psx1tsTLkvWbJen1CtD+fftkZhMaQ9HOtY2gWsl2Wq+sABmMVi9z3DhKWQQ==", "dev": true, "requires": { - "async": "2.6.0", - "loader-utils": "1.1.0", - "schema-utils": "0.3.0", - "webpack-sources": "1.1.0" + "async": "^2.4.1", + "loader-utils": "^1.1.0", + "schema-utils": "^0.3.0", + "webpack-sources": "^1.0.1" }, "dependencies": { "ajv": { @@ -3905,10 +3957,10 @@ "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", "dev": true, "requires": { - "co": "4.6.0", - "fast-deep-equal": "1.1.0", - "fast-json-stable-stringify": "2.0.0", - "json-schema-traverse": "0.3.1" + "co": "^4.6.0", + "fast-deep-equal": "^1.0.0", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.3.0" } }, "schema-utils": { @@ -3917,7 +3969,7 @@ "integrity": "sha1-9YdyIs4+kx7a4DnxfrNxbnE3+M8=", "dev": true, "requires": { - "ajv": "5.5.2" + "ajv": "^5.0.0" } } } @@ -3957,7 +4009,7 @@ "integrity": "sha1-TkkvjQTftviQA1B/btvy1QHnxvQ=", "dev": true, "requires": { - "websocket-driver": "0.7.0" + "websocket-driver": ">=0.5.1" } }, "file-loader": { @@ -3966,8 +4018,8 @@ "integrity": "sha512-TGR4HU7HUsGg6GCOPJnFk06RhWgEWFLAGWiT6rcD+GRC2keU3s9RGJ+b3Z6/U73jwwNb2gKLJ7YCrp+jvU4ALg==", "dev": true, "requires": { - "loader-utils": "1.1.0", - "schema-utils": "0.4.5" + "loader-utils": "^1.0.2", + "schema-utils": "^0.4.5" } }, "file-uri-to-path": { @@ -3989,8 +4041,8 @@ "integrity": "sha1-jnVIqW08wjJ+5eZ0FocjozO7oqA=", "dev": true, "requires": { - "glob": "7.1.2", - "minimatch": "3.0.4" + "glob": "^7.0.3", + "minimatch": "^3.0.3" } }, "fill-range": { @@ -3999,11 +4051,11 @@ "integrity": "sha1-ULd9/X5Gm8dJJHCWNpn+eoSFpyM=", "dev": true, "requires": { - "is-number": "2.1.0", - "isobject": "2.1.0", - "randomatic": "1.1.7", - "repeat-element": "1.1.2", - "repeat-string": "1.6.1" + "is-number": "^2.1.0", + "isobject": "^2.0.0", + "randomatic": "^1.1.3", + "repeat-element": "^1.1.2", + "repeat-string": "^1.5.2" } }, "finalhandler": { @@ -4013,12 +4065,12 @@ "dev": true, "requires": { "debug": "2.6.9", - "encodeurl": "1.0.2", - "escape-html": "1.0.3", - "on-finished": "2.3.0", - "parseurl": "1.3.2", - "statuses": "1.4.0", - "unpipe": "1.0.0" + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.2", + "statuses": "~1.4.0", + "unpipe": "~1.0.0" }, "dependencies": { "debug": { @@ -4038,9 +4090,9 @@ "integrity": "sha1-kojj6ePMN0hxfTnq3hfPcfww7m8=", "dev": true, "requires": { - "commondir": "1.0.1", - "make-dir": "1.2.0", - "pkg-dir": "2.0.0" + "commondir": "^1.0.1", + "make-dir": "^1.0.0", + "pkg-dir": "^2.0.0" } }, "find-up": { @@ -4049,7 +4101,7 @@ "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", "dev": true, "requires": { - "locate-path": "2.0.0" + "locate-path": "^2.0.0" } }, "flush-write-stream": { @@ -4058,30 +4110,17 @@ "integrity": "sha512-calZMC10u0FMUqoiunI2AiGIIUtUIvifNwkHhNupZH4cbNnW1Itkoh/Nf5HFYmDrwWPjrUxpkZT0KhuCq0jmGw==", "dev": true, "requires": { - "inherits": "2.0.3", - "readable-stream": "2.3.6" + "inherits": "^2.0.1", + "readable-stream": "^2.0.4" } }, "follow-redirects": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.0.0.tgz", - "integrity": "sha1-jjQpjL0uF28lTv/sdaHHjMhJ/Tc=", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.4.1.tgz", + "integrity": "sha512-uxYePVPogtya1ktGnAAXOacnbIuRMB4dkvqeNz2qTtTQsuzSfbDolV+wMMKxAmCx0bLgAKLbBOkjItMbbkR1vg==", "dev": true, - "optional": true, "requires": { - "debug": "2.6.9" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "optional": true, - "requires": { - "ms": "2.0.0" - } - } + "debug": "^3.1.0" } }, "for-in": { @@ -4096,7 +4135,7 @@ "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=", "dev": true, "requires": { - "for-in": "1.0.2" + "for-in": "^1.0.1" } }, "foreach": { @@ -4116,9 +4155,9 @@ "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.2.tgz", "integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=", "requires": { - "asynckit": "0.4.0", + "asynckit": "^0.4.0", "combined-stream": "1.0.6", - "mime-types": "2.1.18" + "mime-types": "^2.1.12" } }, "formidable": { @@ -4138,7 +4177,7 @@ "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", "dev": true, "requires": { - "map-cache": "0.2.2" + "map-cache": "^0.2.2" } }, "fresh": { @@ -4153,8 +4192,8 @@ "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=", "dev": true, "requires": { - "inherits": "2.0.3", - "readable-stream": "2.3.6" + "inherits": "^2.0.1", + "readable-stream": "^2.0.0" } }, "fs-access": { @@ -4163,18 +4202,17 @@ "integrity": "sha1-1qh/JiJxzv6+wwxVNAf7mV2od3o=", "dev": true, "requires": { - "null-check": "1.0.0" + "null-check": "^1.0.0" } }, "fs-extra": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.3.tgz", "integrity": "sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg==", - "dev": true, "requires": { - "graceful-fs": "4.1.11", - "jsonfile": "4.0.0", - "universalify": "0.1.1" + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" } }, "fs-write-stream-atomic": { @@ -4183,52 +4221,41 @@ "integrity": "sha1-tH31NJPvkR33VzHnCp3tAYnbQMk=", "dev": true, "requires": { - "graceful-fs": "4.1.11", - "iferr": "0.1.5", - "imurmurhash": "0.1.4", - "readable-stream": "2.3.6" + "graceful-fs": "^4.1.2", + "iferr": "^0.1.5", + "imurmurhash": "^0.1.4", + "readable-stream": "1 || 2" } }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, "fsevents": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.1.3.tgz", - "integrity": "sha512-WIr7iDkdmdbxu/Gh6eKEZJL6KPE74/5MEsf2whTOFNxbIoIixogroLdKYqB6FDav4Wavh/lZdzzd3b2KxIXC5Q==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.3.tgz", + "integrity": "sha512-X+57O5YkDTiEQGiw8i7wYc2nQgweIekqkepI8Q3y4wVlurgBt2SuwxTeYUYMZIGpLZH3r/TsMjczCMXE5ZOt7Q==", "dev": true, "optional": true, "requires": { - "nan": "2.10.0", - "node-pre-gyp": "0.6.39" + "nan": "^2.9.2", + "node-pre-gyp": "^0.9.0" }, "dependencies": { "abbrev": { - "version": "1.1.0", + "version": "1.1.1", "bundled": true, "dev": true, "optional": true }, - "ajv": { - "version": "4.11.8", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "co": "4.6.0", - "json-stable-stringify": "1.0.1" - } - }, "ansi-regex": { "version": "2.1.1", "bundled": true, "dev": true }, "aproba": { - "version": "1.1.1", + "version": "1.2.0", "bundled": true, "dev": true, "optional": true @@ -4239,92 +4266,26 @@ "dev": true, "optional": true, "requires": { - "delegates": "1.0.0", - "readable-stream": "2.2.9" + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" } }, - "asn1": { - "version": "0.2.3", - "bundled": true, - "dev": true, - "optional": true - }, - "assert-plus": { - "version": "0.2.0", - "bundled": true, - "dev": true, - "optional": true - }, - "asynckit": { - "version": "0.4.0", - "bundled": true, - "dev": true, - "optional": true - }, - "aws-sign2": { - "version": "0.6.0", - "bundled": true, - "dev": true, - "optional": true - }, - "aws4": { - "version": "1.6.0", - "bundled": true, - "dev": true, - "optional": true - }, "balanced-match": { - "version": "0.4.2", - "bundled": true, - "dev": true - }, - "bcrypt-pbkdf": { - "version": "1.0.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "tweetnacl": "0.14.5" - } - }, - "block-stream": { - "version": "0.0.9", - "bundled": true, - "dev": true, - "requires": { - "inherits": "2.0.3" - } - }, - "boom": { - "version": "2.10.1", - "bundled": true, - "dev": true, - "requires": { - "hoek": "2.16.3" - } - }, - "brace-expansion": { - "version": "1.1.7", - "bundled": true, - "dev": true, - "requires": { - "balanced-match": "0.4.2", - "concat-map": "0.0.1" - } - }, - "buffer-shims": { "version": "1.0.0", "bundled": true, "dev": true }, - "caseless": { - "version": "0.12.0", + "brace-expansion": { + "version": "1.1.11", "bundled": true, "dev": true, - "optional": true + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } }, - "co": { - "version": "4.6.0", + "chownr": { + "version": "1.0.1", "bundled": true, "dev": true, "optional": true @@ -4334,14 +4295,6 @@ "bundled": true, "dev": true }, - "combined-stream": { - "version": "1.0.5", - "bundled": true, - "dev": true, - "requires": { - "delayed-stream": "1.0.0" - } - }, "concat-map": { "version": "0.0.1", "bundled": true, @@ -4355,35 +4308,11 @@ "core-util-is": { "version": "1.0.2", "bundled": true, - "dev": true - }, - "cryptiles": { - "version": "2.0.5", - "bundled": true, "dev": true, - "requires": { - "boom": "2.10.1" - } - }, - "dashdash": { - "version": "1.14.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "assert-plus": "1.0.0" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - } - } + "optional": true }, "debug": { - "version": "2.6.8", + "version": "2.6.9", "bundled": true, "dev": true, "optional": true, @@ -4397,11 +4326,6 @@ "dev": true, "optional": true }, - "delayed-stream": { - "version": "1.0.0", - "bundled": true, - "dev": true - }, "delegates": { "version": "1.0.0", "bundled": true, @@ -4409,74 +4333,25 @@ "optional": true }, "detect-libc": { - "version": "1.0.2", + "version": "1.0.3", "bundled": true, "dev": true, "optional": true }, - "ecc-jsbn": { - "version": "0.1.1", + "fs-minipass": { + "version": "1.2.5", "bundled": true, "dev": true, "optional": true, "requires": { - "jsbn": "0.1.1" - } - }, - "extend": { - "version": "3.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "extsprintf": { - "version": "1.0.2", - "bundled": true, - "dev": true - }, - "forever-agent": { - "version": "0.6.1", - "bundled": true, - "dev": true, - "optional": true - }, - "form-data": { - "version": "2.1.4", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "asynckit": "0.4.0", - "combined-stream": "1.0.5", - "mime-types": "2.1.15" + "minipass": "^2.2.1" } }, "fs.realpath": { "version": "1.0.0", "bundled": true, - "dev": true - }, - "fstream": { - "version": "1.0.11", - "bundled": true, "dev": true, - "requires": { - "graceful-fs": "4.1.11", - "inherits": "2.0.3", - "mkdirp": "0.5.1", - "rimraf": "2.6.1" - } - }, - "fstream-ignore": { - "version": "1.0.5", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "fstream": "1.0.11", - "inherits": "2.0.3", - "minimatch": "3.0.4" - } + "optional": true }, "gauge": { "version": "2.7.4", @@ -4484,65 +4359,28 @@ "dev": true, "optional": true, "requires": { - "aproba": "1.1.1", - "console-control-strings": "1.1.0", - "has-unicode": "2.0.1", - "object-assign": "4.1.1", - "signal-exit": "3.0.2", - "string-width": "1.0.2", - "strip-ansi": "3.0.1", - "wide-align": "1.1.2" - } - }, - "getpass": { - "version": "0.1.7", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "assert-plus": "1.0.0" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - } + "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.2", "bundled": true, "dev": true, - "requires": { - "fs.realpath": "1.0.0", - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" - } - }, - "graceful-fs": { - "version": "4.1.11", - "bundled": true, - "dev": true - }, - "har-schema": { - "version": "1.0.5", - "bundled": true, - "dev": true, - "optional": true - }, - "har-validator": { - "version": "4.2.1", - "bundled": true, - "dev": true, "optional": true, "requires": { - "ajv": "4.11.8", - "har-schema": "1.0.5" + "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": { @@ -4551,40 +4389,32 @@ "dev": true, "optional": true }, - "hawk": { - "version": "3.1.3", - "bundled": true, - "dev": true, - "requires": { - "boom": "2.10.1", - "cryptiles": "2.0.5", - "hoek": "2.16.3", - "sntp": "1.0.9" - } - }, - "hoek": { - "version": "2.16.3", - "bundled": true, - "dev": true - }, - "http-signature": { - "version": "1.1.1", + "iconv-lite": { + "version": "0.4.21", "bundled": true, "dev": true, "optional": true, "requires": { - "assert-plus": "0.2.0", - "jsprim": "1.4.0", - "sshpk": "1.13.0" + "safer-buffer": "^2.1.0" + } + }, + "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.4.0", - "wrappy": "1.0.2" + "once": "^1.3.0", + "wrappy": "1" } }, "inherits": { @@ -4593,7 +4423,7 @@ "dev": true }, "ini": { - "version": "1.3.4", + "version": "1.3.5", "bundled": true, "dev": true, "optional": true @@ -4603,107 +4433,21 @@ "bundled": true, "dev": true, "requires": { - "number-is-nan": "1.0.1" + "number-is-nan": "^1.0.0" } }, - "is-typedarray": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - }, "isarray": { "version": "1.0.0", "bundled": true, - "dev": true - }, - "isstream": { - "version": "0.1.2", - "bundled": true, "dev": true, "optional": true }, - "jodid25519": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "jsbn": "0.1.1" - } - }, - "jsbn": { - "version": "0.1.1", - "bundled": true, - "dev": true, - "optional": true - }, - "json-schema": { - "version": "0.2.3", - "bundled": true, - "dev": true, - "optional": true - }, - "json-stable-stringify": { - "version": "1.0.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "jsonify": "0.0.0" - } - }, - "json-stringify-safe": { - "version": "5.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "jsonify": { - "version": "0.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "jsprim": { - "version": "1.4.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "assert-plus": "1.0.0", - "extsprintf": "1.0.2", - "json-schema": "0.2.3", - "verror": "1.3.6" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - } - } - }, - "mime-db": { - "version": "1.27.0", - "bundled": true, - "dev": true - }, - "mime-types": { - "version": "2.1.15", - "bundled": true, - "dev": true, - "requires": { - "mime-db": "1.27.0" - } - }, "minimatch": { "version": "3.0.4", "bundled": true, "dev": true, "requires": { - "brace-expansion": "1.1.7" + "brace-expansion": "^1.1.7" } }, "minimist": { @@ -4711,6 +4455,24 @@ "bundled": true, "dev": true }, + "minipass": { + "version": "2.2.4", + "bundled": true, + "dev": true, + "requires": { + "safe-buffer": "^5.1.1", + "yallist": "^3.0.0" + } + }, + "minizlib": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minipass": "^2.2.1" + } + }, "mkdirp": { "version": "0.5.1", "bundled": true, @@ -4725,23 +4487,33 @@ "dev": true, "optional": true }, - "node-pre-gyp": { - "version": "0.6.39", + "needle": { + "version": "2.2.0", "bundled": true, "dev": true, "optional": true, "requires": { - "detect-libc": "1.0.2", - "hawk": "3.1.3", - "mkdirp": "0.5.1", - "nopt": "4.0.1", - "npmlog": "4.1.0", - "rc": "1.2.1", - "request": "2.81.0", - "rimraf": "2.6.1", - "semver": "5.3.0", - "tar": "2.2.1", - "tar-pack": "3.4.0" + "debug": "^2.1.2", + "iconv-lite": "^0.4.4", + "sax": "^1.2.4" + } + }, + "node-pre-gyp": { + "version": "0.9.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "detect-libc": "^1.0.2", + "mkdirp": "^0.5.1", + "needle": "^2.2.0", + "nopt": "^4.0.1", + "npm-packlist": "^1.1.6", + "npmlog": "^4.0.2", + "rc": "^1.1.7", + "rimraf": "^2.6.1", + "semver": "^5.3.0", + "tar": "^4" } }, "nopt": { @@ -4750,20 +4522,36 @@ "dev": true, "optional": true, "requires": { - "abbrev": "1.1.0", - "osenv": "0.1.4" + "abbrev": "1", + "osenv": "^0.1.4" } }, - "npmlog": { - "version": "4.1.0", + "npm-bundled": { + "version": "1.0.3", + "bundled": true, + "dev": true, + "optional": true + }, + "npm-packlist": { + "version": "1.1.10", "bundled": true, "dev": true, "optional": true, "requires": { - "are-we-there-yet": "1.1.4", - "console-control-strings": "1.1.0", - "gauge": "2.7.4", - "set-blocking": "2.0.0" + "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": { @@ -4771,12 +4559,6 @@ "bundled": true, "dev": true }, - "oauth-sign": { - "version": "0.8.2", - "bundled": true, - "dev": true, - "optional": true - }, "object-assign": { "version": "4.1.1", "bundled": true, @@ -4788,7 +4570,7 @@ "bundled": true, "dev": true, "requires": { - "wrappy": "1.0.2" + "wrappy": "1" } }, "os-homedir": { @@ -4804,53 +4586,37 @@ "optional": true }, "osenv": { - "version": "0.1.4", + "version": "0.1.5", "bundled": true, "dev": true, "optional": true, "requires": { - "os-homedir": "1.0.2", - "os-tmpdir": "1.0.2" + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" } }, "path-is-absolute": { "version": "1.0.1", "bundled": true, - "dev": true - }, - "performance-now": { - "version": "0.2.0", - "bundled": true, "dev": true, "optional": true }, "process-nextick-args": { - "version": "1.0.7", - "bundled": true, - "dev": true - }, - "punycode": { - "version": "1.4.1", - "bundled": true, - "dev": true, - "optional": true - }, - "qs": { - "version": "6.4.0", + "version": "2.0.0", "bundled": true, "dev": true, "optional": true }, "rc": { - "version": "1.2.1", + "version": "1.2.6", "bundled": true, "dev": true, "optional": true, "requires": { - "deep-extend": "0.4.2", - "ini": "1.3.4", - "minimist": "1.2.0", - "strip-json-comments": "2.0.1" + "deep-extend": "~0.4.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" }, "dependencies": { "minimist": { @@ -4862,64 +4628,48 @@ } }, "readable-stream": { - "version": "2.2.9", - "bundled": true, - "dev": true, - "requires": { - "buffer-shims": "1.0.0", - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "1.0.0", - "process-nextick-args": "1.0.7", - "string_decoder": "1.0.1", - "util-deprecate": "1.0.2" - } - }, - "request": { - "version": "2.81.0", + "version": "2.3.6", "bundled": true, "dev": true, "optional": true, "requires": { - "aws-sign2": "0.6.0", - "aws4": "1.6.0", - "caseless": "0.12.0", - "combined-stream": "1.0.5", - "extend": "3.0.1", - "forever-agent": "0.6.1", - "form-data": "2.1.4", - "har-validator": "4.2.1", - "hawk": "3.1.3", - "http-signature": "1.1.1", - "is-typedarray": "1.0.0", - "isstream": "0.1.2", - "json-stringify-safe": "5.0.1", - "mime-types": "2.1.15", - "oauth-sign": "0.8.2", - "performance-now": "0.2.0", - "qs": "6.4.0", - "safe-buffer": "5.0.1", - "stringstream": "0.0.5", - "tough-cookie": "2.3.2", - "tunnel-agent": "0.6.0", - "uuid": "3.0.1" + "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.1", + "version": "2.6.2", "bundled": true, "dev": true, + "optional": true, "requires": { - "glob": "7.1.2" + "glob": "^7.0.5" } }, "safe-buffer": { - "version": "5.0.1", + "version": "5.1.1", "bundled": true, "dev": 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.3.0", + "version": "5.5.0", "bundled": true, "dev": true, "optional": true @@ -4936,69 +4686,31 @@ "dev": true, "optional": true }, - "sntp": { - "version": "1.0.9", - "bundled": true, - "dev": true, - "requires": { - "hoek": "2.16.3" - } - }, - "sshpk": { - "version": "1.13.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "asn1": "0.2.3", - "assert-plus": "1.0.0", - "bcrypt-pbkdf": "1.0.1", - "dashdash": "1.14.1", - "ecc-jsbn": "0.1.1", - "getpass": "0.1.7", - "jodid25519": "1.0.2", - "jsbn": "0.1.1", - "tweetnacl": "0.14.5" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - } - } - }, "string-width": { "version": "1.0.2", "bundled": true, "dev": true, "requires": { - "code-point-at": "1.1.0", - "is-fullwidth-code-point": "1.0.0", - "strip-ansi": "3.0.1" + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" } }, "string_decoder": { - "version": "1.0.1", + "version": "1.1.1", "bundled": true, "dev": true, + "optional": true, "requires": { - "safe-buffer": "5.0.1" + "safe-buffer": "~5.1.0" } }, - "stringstream": { - "version": "0.0.5", - "bundled": true, - "dev": true, - "optional": true - }, "strip-ansi": { "version": "3.0.1", "bundled": true, "dev": true, "requires": { - "ansi-regex": "2.1.1" + "ansi-regex": "^2.0.0" } }, "strip-json-comments": { @@ -5008,94 +4720,44 @@ "optional": true }, "tar": { - "version": "2.2.1", - "bundled": true, - "dev": true, - "requires": { - "block-stream": "0.0.9", - "fstream": "1.0.11", - "inherits": "2.0.3" - } - }, - "tar-pack": { - "version": "3.4.0", + "version": "4.4.1", "bundled": true, "dev": true, "optional": true, "requires": { - "debug": "2.6.8", - "fstream": "1.0.11", - "fstream-ignore": "1.0.5", - "once": "1.4.0", - "readable-stream": "2.2.9", - "rimraf": "2.6.1", - "tar": "2.2.1", - "uid-number": "0.0.6" + "chownr": "^1.0.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.2.4", + "minizlib": "^1.1.0", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.1", + "yallist": "^3.0.2" } }, - "tough-cookie": { - "version": "2.3.2", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "punycode": "1.4.1" - } - }, - "tunnel-agent": { - "version": "0.6.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "safe-buffer": "5.0.1" - } - }, - "tweetnacl": { - "version": "0.14.5", - "bundled": true, - "dev": true, - "optional": true - }, - "uid-number": { - "version": "0.0.6", - "bundled": true, - "dev": true, - "optional": true - }, "util-deprecate": { "version": "1.0.2", "bundled": true, - "dev": true - }, - "uuid": { - "version": "3.0.1", - "bundled": true, "dev": true, "optional": true }, - "verror": { - "version": "1.3.6", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "extsprintf": "1.0.2" - } - }, "wide-align": { "version": "1.1.2", "bundled": true, "dev": true, "optional": true, "requires": { - "string-width": "1.0.2" + "string-width": "^1.0.2" } }, "wrappy": { "version": "1.0.2", "bundled": true, "dev": true + }, + "yallist": { + "version": "3.0.2", + "bundled": true, + "dev": true } } }, @@ -5105,10 +4767,10 @@ "integrity": "sha1-XB+x8RdHcRTwYyoOtLcbPLD9MXE=", "dev": true, "requires": { - "graceful-fs": "4.1.11", - "inherits": "2.0.3", - "mkdirp": "0.5.1", - "rimraf": "2.6.2" + "graceful-fs": "^4.1.2", + "inherits": "~2.0.0", + "mkdirp": ">=0.5 0", + "rimraf": "2" } }, "ftp": { @@ -5118,7 +4780,7 @@ "dev": true, "optional": true, "requires": { - "readable-stream": "1.1.14", + "readable-stream": "1.1.x", "xregexp": "2.0.0" }, "dependencies": { @@ -5136,10 +4798,10 @@ "dev": true, "optional": true, "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", "isarray": "0.0.1", - "string_decoder": "0.10.31" + "string_decoder": "~0.10.x" } }, "string_decoder": { @@ -5163,14 +4825,14 @@ "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", "dev": true, "requires": { - "aproba": "1.2.0", - "console-control-strings": "1.1.0", - "has-unicode": "2.0.1", - "object-assign": "4.1.1", - "signal-exit": "3.0.2", - "string-width": "1.0.2", - "strip-ansi": "3.0.1", - "wide-align": "1.1.2" + "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" } }, "gaze": { @@ -5180,7 +4842,7 @@ "dev": true, "optional": true, "requires": { - "globule": "1.2.0" + "globule": "^1.0.0" } }, "generate-function": { @@ -5197,9 +4859,14 @@ "dev": true, "optional": true, "requires": { - "is-property": "1.0.2" + "is-property": "^1.0.0" } }, + "gensequence": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/gensequence/-/gensequence-2.1.1.tgz", + "integrity": "sha512-AyZrG5Qq8Tn0qnaDCnH2n9TsWnJLKBXEa2FcUlHWfEgl1rRS3MbcvB4OsarxyEekx/PwYlyKXvjQwNvYsByXAw==" + }, "get-caller-file": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.2.tgz", @@ -5225,12 +4892,12 @@ "dev": true, "optional": true, "requires": { - "data-uri-to-buffer": "1.2.0", - "debug": "2.6.9", - "extend": "3.0.1", - "file-uri-to-path": "1.0.0", - "ftp": "0.3.10", - "readable-stream": "2.3.6" + "data-uri-to-buffer": "1", + "debug": "2", + "extend": "3", + "file-uri-to-path": "1", + "ftp": "~0.3.10", + "readable-stream": "2" }, "dependencies": { "debug": { @@ -5257,7 +4924,7 @@ "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", "dev": true, "requires": { - "assert-plus": "1.0.0" + "assert-plus": "^1.0.0" }, "dependencies": { "assert-plus": { @@ -5272,14 +4939,13 @@ "version": "7.1.2", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", - "dev": true, "requires": { - "fs.realpath": "1.0.0", - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" + "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": { @@ -5288,8 +4954,8 @@ "integrity": "sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q=", "dev": true, "requires": { - "glob-parent": "2.0.0", - "is-glob": "2.0.1" + "glob-parent": "^2.0.0", + "is-glob": "^2.0.0" } }, "glob-parent": { @@ -5298,7 +4964,7 @@ "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=", "dev": true, "requires": { - "is-glob": "2.0.1" + "is-glob": "^2.0.0" } }, "globals": { @@ -5313,12 +4979,12 @@ "integrity": "sha1-+yzP+UAfhgCUXfral0QMypcrhoA=", "dev": true, "requires": { - "array-union": "1.0.2", - "dir-glob": "2.0.0", - "glob": "7.1.2", - "ignore": "3.3.7", - "pify": "3.0.0", - "slash": "1.0.0" + "array-union": "^1.0.1", + "dir-glob": "^2.0.0", + "glob": "^7.1.2", + "ignore": "^3.3.5", + "pify": "^3.0.0", + "slash": "^1.0.0" } }, "globule": { @@ -5328,16 +4994,15 @@ "dev": true, "optional": true, "requires": { - "glob": "7.1.2", - "lodash": "4.17.5", - "minimatch": "3.0.4" + "glob": "~7.1.1", + "lodash": "~4.17.4", + "minimatch": "~3.0.2" } }, "graceful-fs": { "version": "4.1.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", - "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", - "dev": true + "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=" }, "hammerjs": { "version": "2.0.8", @@ -5356,10 +5021,10 @@ "integrity": "sha1-Ywo13+ApS8KB7a5v/F0yn8eYLcw=", "dev": true, "requires": { - "async": "1.5.2", - "optimist": "0.6.1", - "source-map": "0.4.4", - "uglify-js": "2.8.29" + "async": "^1.4.0", + "optimist": "^0.6.1", + "source-map": "^0.4.4", + "uglify-js": "^2.6" }, "dependencies": { "async": { @@ -5382,8 +5047,8 @@ "dev": true, "optional": true, "requires": { - "center-align": "0.1.3", - "right-align": "0.1.3", + "center-align": "^0.1.1", + "right-align": "^0.1.1", "wordwrap": "0.0.2" } }, @@ -5393,7 +5058,7 @@ "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", "dev": true, "requires": { - "amdefine": "1.0.1" + "amdefine": ">=0.0.4" } }, "uglify-js": { @@ -5403,9 +5068,9 @@ "dev": true, "optional": true, "requires": { - "source-map": "0.5.7", - "uglify-to-browserify": "1.0.2", - "yargs": "3.10.0" + "source-map": "~0.5.1", + "uglify-to-browserify": "~1.0.0", + "yargs": "~3.10.0" }, "dependencies": { "source-map": { @@ -5424,9 +5089,9 @@ "dev": true, "optional": true, "requires": { - "camelcase": "1.2.1", - "cliui": "2.1.0", - "decamelize": "1.2.0", + "camelcase": "^1.0.2", + "cliui": "^2.1.0", + "decamelize": "^1.0.0", "window-size": "0.1.0" } } @@ -5444,8 +5109,8 @@ "integrity": "sha1-M0gdDxu/9gDdID11gSpqX7oALio=", "dev": true, "requires": { - "ajv": "4.11.8", - "har-schema": "1.0.5" + "ajv": "^4.9.1", + "har-schema": "^1.0.5" }, "dependencies": { "ajv": { @@ -5454,8 +5119,8 @@ "integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=", "dev": true, "requires": { - "co": "4.6.0", - "json-stable-stringify": "1.0.1" + "co": "^4.6.0", + "json-stable-stringify": "^1.0.1" } } } @@ -5466,7 +5131,7 @@ "integrity": "sha1-hGFzP1OLCDfJNh45qauelwTcLyg=", "dev": true, "requires": { - "function-bind": "1.1.1" + "function-bind": "^1.0.2" } }, "has-ansi": { @@ -5475,7 +5140,7 @@ "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", "dev": true, "requires": { - "ansi-regex": "2.1.1" + "ansi-regex": "^2.0.0" } }, "has-binary2": { @@ -5519,9 +5184,9 @@ "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", "dev": true, "requires": { - "get-value": "2.0.6", - "has-values": "1.0.0", - "isobject": "3.0.1" + "get-value": "^2.0.6", + "has-values": "^1.0.0", + "isobject": "^3.0.0" }, "dependencies": { "isobject": { @@ -5538,8 +5203,8 @@ "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", "dev": true, "requires": { - "is-number": "3.0.0", - "kind-of": "4.0.0" + "is-number": "^3.0.0", + "kind-of": "^4.0.0" }, "dependencies": { "is-number": { @@ -5548,7 +5213,7 @@ "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", "dev": true, "requires": { - "kind-of": "3.2.2" + "kind-of": "^3.0.2" }, "dependencies": { "kind-of": { @@ -5557,7 +5222,7 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "1.1.6" + "is-buffer": "^1.1.5" } } } @@ -5568,7 +5233,7 @@ "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", "dev": true, "requires": { - "is-buffer": "1.1.6" + "is-buffer": "^1.1.5" } } } @@ -5579,8 +5244,8 @@ "integrity": "sha1-X8hoaEfs1zSZQDMZprCj8/auSRg=", "dev": true, "requires": { - "inherits": "2.0.3", - "safe-buffer": "5.1.1" + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" } }, "hash.js": { @@ -5589,8 +5254,8 @@ "integrity": "sha512-/UETyP0W22QILqS+6HowevwhEFJ3MBJnwTf75Qob9Wz9t0DPuisL8kW8YZMK62dHAKE1c1p+gY1TtOLY+USEHA==", "dev": true, "requires": { - "inherits": "2.0.3", - "minimalistic-assert": "1.0.1" + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.0" } }, "hawk": { @@ -5599,10 +5264,10 @@ "integrity": "sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ=", "dev": true, "requires": { - "boom": "2.10.1", - "cryptiles": "2.0.5", - "hoek": "2.16.3", - "sntp": "1.0.9" + "boom": "2.x.x", + "cryptiles": "2.x.x", + "hoek": "2.x.x", + "sntp": "1.x.x" } }, "he": { @@ -5618,8 +5283,8 @@ "dev": true, "optional": true, "requires": { - "lodash": "4.17.5", - "request": "2.81.0" + "lodash": "^4.0.0", + "request": "^2.0.0" } }, "hmac-drbg": { @@ -5628,9 +5293,9 @@ "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", "dev": true, "requires": { - "hash.js": "1.1.3", - "minimalistic-assert": "1.0.1", - "minimalistic-crypto-utils": "1.0.1" + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" } }, "hoek": { @@ -5645,7 +5310,7 @@ "integrity": "sha1-TCu8inWJmP7r9e1oWA921GdotLw=", "dev": true, "requires": { - "parse-passwd": "1.0.0" + "parse-passwd": "^1.0.0" } }, "hosted-git-info": { @@ -5660,10 +5325,10 @@ "integrity": "sha1-h3dMCUnlE/QuhFdbPEVoH63ioLI=", "dev": true, "requires": { - "inherits": "2.0.3", - "obuf": "1.1.2", - "readable-stream": "2.3.6", - "wbuf": "1.7.3" + "inherits": "^2.0.1", + "obuf": "^1.0.0", + "readable-stream": "^2.0.1", + "wbuf": "^1.1.0" } }, "html-entities": { @@ -5673,18 +5338,18 @@ "dev": true }, "html-minifier": { - "version": "3.5.14", - "resolved": "https://registry.npmjs.org/html-minifier/-/html-minifier-3.5.14.tgz", - "integrity": "sha512-sZjw6zhQgyUnIlIPU+W80XpRjWjdxHtNcxjfyOskOsCTDKytcfLY04wsQY/83Yqb4ndoiD2FtauiL7Yg6uUQFQ==", + "version": "3.5.15", + "resolved": "https://registry.npmjs.org/html-minifier/-/html-minifier-3.5.15.tgz", + "integrity": "sha512-OZa4rfb6tZOZ3Z8Xf0jKxXkiDcFWldQePGYFDcgKqES2sXeWaEv9y6QQvWUtX3ySI3feApQi5uCsHLINQ6NoAw==", "dev": true, "requires": { - "camel-case": "3.0.0", - "clean-css": "4.1.11", - "commander": "2.15.1", - "he": "1.1.1", - "param-case": "2.1.1", - "relateurl": "0.2.7", - "uglify-js": "3.3.21" + "camel-case": "3.0.x", + "clean-css": "4.1.x", + "commander": "2.15.x", + "he": "1.1.x", + "param-case": "2.1.x", + "relateurl": "0.2.x", + "uglify-js": "3.3.x" } }, "html-webpack-plugin": { @@ -5693,12 +5358,12 @@ "integrity": "sha1-f5xCG36pHsRg9WUn1430hO51N9U=", "dev": true, "requires": { - "bluebird": "3.5.1", - "html-minifier": "3.5.14", - "loader-utils": "0.2.17", - "lodash": "4.17.5", - "pretty-error": "2.1.1", - "toposort": "1.0.6" + "bluebird": "^3.4.7", + "html-minifier": "^3.2.3", + "loader-utils": "^0.2.16", + "lodash": "^4.17.3", + "pretty-error": "^2.0.2", + "toposort": "^1.0.0" }, "dependencies": { "loader-utils": { @@ -5707,30 +5372,24 @@ "integrity": "sha1-+G5jdNQyBabmxg6RlvF8Apm/s0g=", "dev": true, "requires": { - "big.js": "3.2.0", - "emojis-list": "2.1.0", - "json5": "0.5.1", - "object-assign": "4.1.1" + "big.js": "^3.1.3", + "emojis-list": "^2.0.0", + "json5": "^0.5.0", + "object-assign": "^4.0.1" } } } }, - "htmlescape": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/htmlescape/-/htmlescape-1.1.1.tgz", - "integrity": "sha1-OgPtwiFLyjtmQko+eVk0lQnLA1E=", - "dev": true - }, "htmlparser2": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.3.0.tgz", "integrity": "sha1-zHDQWln2VC5D8OaFyYLhTJJKnv4=", "dev": true, "requires": { - "domelementtype": "1.3.0", - "domhandler": "2.1.0", - "domutils": "1.1.6", - "readable-stream": "1.0.34" + "domelementtype": "1", + "domhandler": "2.1", + "domutils": "1.1", + "readable-stream": "1.0" }, "dependencies": { "domutils": { @@ -5739,7 +5398,7 @@ "integrity": "sha1-vdw94Jm5ou+sxRxiPyj0FuzFdIU=", "dev": true, "requires": { - "domelementtype": "1.3.0" + "domelementtype": "1" } }, "isarray": { @@ -5754,10 +5413,10 @@ "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", "dev": true, "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", "isarray": "0.0.1", - "string_decoder": "0.10.31" + "string_decoder": "~0.10.x" } }, "string_decoder": { @@ -5780,48 +5439,37 @@ "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", "dev": true, "requires": { - "depd": "1.1.2", + "depd": "~1.1.2", "inherits": "2.0.3", "setprototypeof": "1.1.0", - "statuses": "1.4.0" + "statuses": ">= 1.4.0 < 2" } }, "http-parser-js": { - "version": "0.4.11", - "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.4.11.tgz", - "integrity": "sha512-QCR5O2AjjMW8Mo4HyI1ctFcv+O99j/0g367V3YoVnrNw5hkDvAWZD0lWGcc+F4yN3V55USPCVix4efb75HxFfA==", + "version": "0.4.12", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.4.12.tgz", + "integrity": "sha1-uc+/Sizybw/DSxDKFImid3HjR08=", "dev": true }, "http-proxy": { - "version": "1.16.2", - "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.16.2.tgz", - "integrity": "sha1-Bt/ykpUr9k2+hHH6nfcwZtTzd0I=", + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.17.0.tgz", + "integrity": "sha512-Taqn+3nNvYRfJ3bGvKfBSRwy1v6eePlm3oc/aWVxZp57DQr5Eq3xhKJi7Z4hZpS8PC3H4qI+Yly5EmFacGuA/g==", "dev": true, "requires": { - "eventemitter3": "1.2.0", - "requires-port": "1.0.0" + "eventemitter3": "^3.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" } }, "http-proxy-agent": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-1.0.0.tgz", - "integrity": "sha1-zBzjjkU7+YSg93AtLdWcc9CBKEo=", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz", + "integrity": "sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg==", "dev": true, "requires": { - "agent-base": "2.1.1", - "debug": "2.6.9", - "extend": "3.0.1" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - } + "agent-base": "4", + "debug": "3.1.0" } }, "http-proxy-middleware": { @@ -5830,10 +5478,10 @@ "integrity": "sha1-ZC6ISIUdZvCdTxJJEoRtuutBuDM=", "dev": true, "requires": { - "http-proxy": "1.16.2", - "is-glob": "3.1.0", - "lodash": "4.17.5", - "micromatch": "2.3.11" + "http-proxy": "^1.16.2", + "is-glob": "^3.1.0", + "lodash": "^4.17.2", + "micromatch": "^2.3.11" }, "dependencies": { "is-extglob": { @@ -5848,7 +5496,7 @@ "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", "dev": true, "requires": { - "is-extglob": "2.1.1" + "is-extglob": "^2.1.0" } } } @@ -5859,9 +5507,9 @@ "integrity": "sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8=", "dev": true, "requires": { - "assert-plus": "0.2.0", - "jsprim": "1.4.1", - "sshpk": "1.14.1" + "assert-plus": "^0.2.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" } }, "httpntlm": { @@ -5870,8 +5518,8 @@ "integrity": "sha1-rQFScUOi6Hc8+uapb1hla7UqNLI=", "dev": true, "requires": { - "httpreq": "0.4.24", - "underscore": "1.7.0" + "httpreq": ">=0.4.22", + "underscore": "~1.7.0" } }, "httpreq": { @@ -5887,32 +5535,19 @@ "dev": true }, "https-proxy-agent": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-1.0.0.tgz", - "integrity": "sha1-NffabEjOTdv6JkiRrFk+5f+GceY=", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.1.tgz", + "integrity": "sha512-HPCTS1LW51bcyMYbxUIOO4HEOlQ1/1qRaFWcyxvwaqUS9TY88aoEuHUY33kuAh1YhVVaDQhLZsnPd+XNARWZlQ==", "dev": true, "requires": { - "agent-base": "2.1.1", - "debug": "2.6.9", - "extend": "3.0.1" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - } + "agent-base": "^4.1.0", + "debug": "^3.1.0" } }, "iconv-lite": { "version": "0.4.19", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", - "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==", - "dev": true + "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==" }, "ieee754": { "version": "1.1.11", @@ -5927,9 +5562,9 @@ "dev": true }, "ignore": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.7.tgz", - "integrity": "sha512-YGG3ejvBNHRqu0559EOxxNFihD0AjpvHlC/pdGKd3X3ofe+CoJkYazwNJYTNebqpPKN+VVQbh4ZFn1DivMNuHA==", + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.8.tgz", + "integrity": "sha512-pUh+xUQQhQzevjRHHFqqcTy0/dP/kS9I8HSrUydhihjuD09W6ldVWFtIrwhXdUJHis3i2rZNqEHpZH/cbinFbg==", "dev": true }, "image-size": { @@ -5939,21 +5574,26 @@ "dev": true, "optional": true }, + "immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=", + "dev": true + }, "import-local": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/import-local/-/import-local-1.0.0.tgz", "integrity": "sha512-vAaZHieK9qjGo58agRBg+bhHX3hoTZU/Oa3GESWLz7t1U62fk63aHuDJJEteXoDeTCcPmUT+z38gkHPZkkmpmQ==", "dev": true, "requires": { - "pkg-dir": "2.0.0", - "resolve-cwd": "2.0.0" + "pkg-dir": "^2.0.0", + "resolve-cwd": "^2.0.0" } }, "imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "dev": true + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=" }, "in-publish": { "version": "2.0.0", @@ -5968,7 +5608,7 @@ "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", "dev": true, "requires": { - "repeating": "2.0.1" + "repeating": "^2.0.0" } }, "indexof": { @@ -5978,9 +5618,9 @@ "dev": true }, "inflection": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/inflection/-/inflection-1.10.0.tgz", - "integrity": "sha1-W//LEZetPoEFD44X4hZoCH7p6y8=", + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/inflection/-/inflection-1.12.0.tgz", + "integrity": "sha1-ogCTVlbW9fa8TcdQLhrstwMihBY=", "dev": true, "optional": true }, @@ -5988,10 +5628,9 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, "requires": { - "once": "1.4.0", - "wrappy": "1.0.2" + "once": "^1.3.0", + "wrappy": "1" } }, "inherits": { @@ -6005,39 +5644,13 @@ "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", "dev": true }, - "inline-source-map": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/inline-source-map/-/inline-source-map-0.6.2.tgz", - "integrity": "sha1-+Tk0ccGKedFyT4Y/o4tYY3Ct4qU=", - "dev": true, - "requires": { - "source-map": "0.5.7" - } - }, - "insert-module-globals": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/insert-module-globals/-/insert-module-globals-7.0.6.tgz", - "integrity": "sha512-R3sidKJr3SsggqQQ5cEwQb3pWG8RNx0UnpyeiOSR6jorRIeAOzH2gkTWnNdMnyRiVbjrG047K7UCtlMkQ1Mo9w==", - "dev": true, - "requires": { - "JSONStream": "1.3.2", - "combine-source-map": "0.8.0", - "concat-stream": "1.6.2", - "is-buffer": "1.1.6", - "lexical-scope": "1.2.0", - "path-is-absolute": "1.0.1", - "process": "0.11.10", - "through2": "2.0.3", - "xtend": "4.0.1" - } - }, "internal-ip": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/internal-ip/-/internal-ip-1.2.0.tgz", "integrity": "sha1-rp+/k7mEh4eF1QqN4bNWlWBYz1w=", "dev": true, "requires": { - "meow": "3.7.0" + "meow": "^3.3.0" } }, "interpret": { @@ -6052,7 +5665,7 @@ "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", "dev": true, "requires": { - "loose-envify": "1.3.1" + "loose-envify": "^1.0.0" } }, "invert-kv": { @@ -6079,7 +5692,7 @@ "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", "dev": true, "requires": { - "kind-of": "3.2.2" + "kind-of": "^3.0.2" } }, "is-arrayish": { @@ -6094,7 +5707,7 @@ "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", "dev": true, "requires": { - "binary-extensions": "1.11.0" + "binary-extensions": "^1.0.0" } }, "is-buffer": { @@ -6109,7 +5722,7 @@ "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", "dev": true, "requires": { - "builtin-modules": "1.1.1" + "builtin-modules": "^1.0.0" } }, "is-callable": { @@ -6124,7 +5737,7 @@ "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", "dev": true, "requires": { - "kind-of": "3.2.2" + "kind-of": "^3.0.2" } }, "is-date-object": { @@ -6139,9 +5752,9 @@ "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", "dev": true, "requires": { - "is-accessor-descriptor": "0.1.6", - "is-data-descriptor": "0.1.4", - "kind-of": "5.1.0" + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" }, "dependencies": { "kind-of": { @@ -6170,7 +5783,7 @@ "integrity": "sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ=", "dev": true, "requires": { - "is-primitive": "2.0.0" + "is-primitive": "^2.0.0" } }, "is-extendable": { @@ -6191,7 +5804,7 @@ "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=", "dev": true, "requires": { - "number-is-nan": "1.0.1" + "number-is-nan": "^1.0.0" } }, "is-fullwidth-code-point": { @@ -6200,7 +5813,7 @@ "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", "dev": true, "requires": { - "number-is-nan": "1.0.1" + "number-is-nan": "^1.0.0" } }, "is-glob": { @@ -6209,7 +5822,7 @@ "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", "dev": true, "requires": { - "is-extglob": "1.0.0" + "is-extglob": "^1.0.0" } }, "is-my-ip-valid": { @@ -6226,11 +5839,11 @@ "dev": true, "optional": true, "requires": { - "generate-function": "2.0.0", - "generate-object-property": "1.2.0", - "is-my-ip-valid": "1.0.0", - "jsonpointer": "4.0.1", - "xtend": "4.0.1" + "generate-function": "^2.0.0", + "generate-object-property": "^1.1.0", + "is-my-ip-valid": "^1.0.0", + "jsonpointer": "^4.0.0", + "xtend": "^4.0.0" } }, "is-number": { @@ -6239,16 +5852,21 @@ "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=", "dev": true, "requires": { - "kind-of": "3.2.2" + "kind-of": "^3.0.2" } }, + "is-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", + "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=" + }, "is-odd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-odd/-/is-odd-2.0.0.tgz", "integrity": "sha512-OTiixgpZAT1M4NHgS5IguFp/Vz2VI3U7Goh4/HA1adtwyLtSBrxYlcSYkhpAE07s4fKEcjrFxyvtQBND4vFQyQ==", "dev": true, "requires": { - "is-number": "4.0.0" + "is-number": "^4.0.0" }, "dependencies": { "is-number": { @@ -6271,7 +5889,7 @@ "integrity": "sha512-FjV1RTW48E7CWM7eE/J2NJvAEEVektecDBVBE5Hh3nM1Jd0kvhHtX68Pr3xsDf857xt3Y4AkwVULK1Vku62aaQ==", "dev": true, "requires": { - "is-path-inside": "1.0.1" + "is-path-inside": "^1.0.0" } }, "is-path-inside": { @@ -6280,7 +5898,7 @@ "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", "dev": true, "requires": { - "path-is-inside": "1.0.2" + "path-is-inside": "^1.0.1" } }, "is-plain-object": { @@ -6289,7 +5907,7 @@ "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", "dev": true, "requires": { - "isobject": "3.0.1" + "isobject": "^3.0.1" }, "dependencies": { "isobject": { @@ -6325,7 +5943,7 @@ "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", "dev": true, "requires": { - "has": "1.0.1" + "has": "^1.0.1" } }, "is-stream": { @@ -6381,7 +5999,7 @@ "integrity": "sha512-zfRhJn9rFSGhzU5tGZqepRSAj3+g6oTOHxMGGriWNJZzyLPUK8H7VHpqKntegnW8KLyGA9zwuNaCoopl40LTpg==", "dev": true, "requires": { - "punycode": "2.1.0" + "punycode": "2.x.x" } }, "isexe": { @@ -6411,18 +6029,18 @@ "integrity": "sha512-duj6AlLcsWNwUpfyfHt0nWIeRiZpuShnP40YTxOGQgtaN8fd6JYSxsvxUphTDy8V5MfDXo4s/xVCIIvVCO808g==", "dev": true, "requires": { - "async": "2.6.0", - "compare-versions": "3.1.0", - "fileset": "2.0.3", - "istanbul-lib-coverage": "1.2.0", - "istanbul-lib-hook": "1.2.0", - "istanbul-lib-instrument": "1.10.1", - "istanbul-lib-report": "1.1.4", - "istanbul-lib-source-maps": "1.2.4", - "istanbul-reports": "1.3.0", - "js-yaml": "3.11.0", - "mkdirp": "0.5.1", - "once": "1.4.0" + "async": "^2.1.4", + "compare-versions": "^3.1.0", + "fileset": "^2.0.2", + "istanbul-lib-coverage": "^1.2.0", + "istanbul-lib-hook": "^1.2.0", + "istanbul-lib-instrument": "^1.10.1", + "istanbul-lib-report": "^1.1.4", + "istanbul-lib-source-maps": "^1.2.4", + "istanbul-reports": "^1.3.0", + "js-yaml": "^3.7.0", + "mkdirp": "^0.5.1", + "once": "^1.4.0" } }, "istanbul-instrumenter-loader": { @@ -6431,10 +6049,10 @@ "integrity": "sha512-a5SPObZgS0jB/ixaKSMdn6n/gXSrK2S6q/UfRJBT3e6gQmVjwZROTODQsYW5ZNwOu78hG62Y3fWlebaVOL0C+w==", "dev": true, "requires": { - "convert-source-map": "1.5.1", - "istanbul-lib-instrument": "1.10.1", - "loader-utils": "1.1.0", - "schema-utils": "0.3.0" + "convert-source-map": "^1.5.0", + "istanbul-lib-instrument": "^1.7.3", + "loader-utils": "^1.1.0", + "schema-utils": "^0.3.0" }, "dependencies": { "ajv": { @@ -6443,10 +6061,10 @@ "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", "dev": true, "requires": { - "co": "4.6.0", - "fast-deep-equal": "1.1.0", - "fast-json-stable-stringify": "2.0.0", - "json-schema-traverse": "0.3.1" + "co": "^4.6.0", + "fast-deep-equal": "^1.0.0", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.3.0" } }, "schema-utils": { @@ -6455,7 +6073,7 @@ "integrity": "sha1-9YdyIs4+kx7a4DnxfrNxbnE3+M8=", "dev": true, "requires": { - "ajv": "5.5.2" + "ajv": "^5.0.0" } } } @@ -6472,7 +6090,7 @@ "integrity": "sha512-p3En6/oGkFQV55Up8ZPC2oLxvgSxD8CzA0yBrhRZSh3pfv3OFj9aSGVC0yoerAi/O4u7jUVnOGVX1eVFM+0tmQ==", "dev": true, "requires": { - "append-transform": "0.4.0" + "append-transform": "^0.4.0" } }, "istanbul-lib-instrument": { @@ -6481,13 +6099,13 @@ "integrity": "sha512-1dYuzkOCbuR5GRJqySuZdsmsNKPL3PTuyPevQfoCXJePT9C8y1ga75neU+Tuy9+yS3G/dgx8wgOmp2KLpgdoeQ==", "dev": true, "requires": { - "babel-generator": "6.26.1", - "babel-template": "6.26.0", - "babel-traverse": "6.26.0", - "babel-types": "6.26.0", - "babylon": "6.18.0", - "istanbul-lib-coverage": "1.2.0", - "semver": "5.5.0" + "babel-generator": "^6.18.0", + "babel-template": "^6.16.0", + "babel-traverse": "^6.18.0", + "babel-types": "^6.18.0", + "babylon": "^6.18.0", + "istanbul-lib-coverage": "^1.2.0", + "semver": "^5.3.0" } }, "istanbul-lib-report": { @@ -6496,10 +6114,10 @@ "integrity": "sha512-Azqvq5tT0U09nrncK3q82e/Zjkxa4tkFZv7E6VcqP0QCPn6oNljDPfrZEC/umNXds2t7b8sRJfs6Kmpzt8m2kA==", "dev": true, "requires": { - "istanbul-lib-coverage": "1.2.0", - "mkdirp": "0.5.1", - "path-parse": "1.0.5", - "supports-color": "3.2.3" + "istanbul-lib-coverage": "^1.2.0", + "mkdirp": "^0.5.1", + "path-parse": "^1.0.5", + "supports-color": "^3.1.2" }, "dependencies": { "has-flag": { @@ -6514,7 +6132,7 @@ "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", "dev": true, "requires": { - "has-flag": "1.0.0" + "has-flag": "^1.0.0" } } } @@ -6525,11 +6143,11 @@ "integrity": "sha512-UzuK0g1wyQijiaYQxj/CdNycFhAd2TLtO2obKQMTZrZ1jzEMRY3rvpASEKkaxbRR6brvdovfA03znPa/pXcejg==", "dev": true, "requires": { - "debug": "3.1.0", - "istanbul-lib-coverage": "1.2.0", - "mkdirp": "0.5.1", - "rimraf": "2.6.2", - "source-map": "0.5.7" + "debug": "^3.1.0", + "istanbul-lib-coverage": "^1.2.0", + "mkdirp": "^0.5.1", + "rimraf": "^2.6.1", + "source-map": "^0.5.3" } }, "istanbul-reports": { @@ -6538,26 +6156,24 @@ "integrity": "sha512-y2Z2IMqE1gefWUaVjrBm0mSKvUkaBy9Vqz8iwr/r40Y9hBbIteH5wqHG/9DLTfJ9xUnUT2j7A3+VVJ6EaYBllA==", "dev": true, "requires": { - "handlebars": "4.0.11" + "handlebars": "^4.0.3" } }, + "items": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/items/-/items-2.1.1.tgz", + "integrity": "sha1-i9FtnIOxlSneWuoyGsqtp4NkoZg=", + "dev": true + }, "jasmine": { - "version": "2.99.0", - "resolved": "https://registry.npmjs.org/jasmine/-/jasmine-2.99.0.tgz", - "integrity": "sha1-jKctEC5jm4Z8ZImFbg4YqceqQrc=", + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/jasmine/-/jasmine-2.8.0.tgz", + "integrity": "sha1-awicChFXax8W3xG4AUbZHU6Lij4=", "dev": true, "requires": { - "exit": "0.1.2", - "glob": "7.1.2", - "jasmine-core": "2.99.1" - }, - "dependencies": { - "jasmine-core": { - "version": "2.99.1", - "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-2.99.1.tgz", - "integrity": "sha1-5kAN8ea1bhMLYcS80JPap/boyhU=", - "dev": true - } + "exit": "^0.1.2", + "glob": "^7.0.6", + "jasmine-core": "~2.8.0" } }, "jasmine-core": { @@ -6567,13 +6183,13 @@ "dev": true }, "jasmine-reporters": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/jasmine-reporters/-/jasmine-reporters-2.3.0.tgz", - "integrity": "sha1-64y3NZZYVyqH7vSqCIo2MDbzeSo=", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/jasmine-reporters/-/jasmine-reporters-2.3.1.tgz", + "integrity": "sha1-9C1XjplmlhY0MdkRwxZ5cZ+0Ozs=", "dev": true, "requires": { - "mkdirp": "0.5.1", - "xmldom": "0.1.27" + "mkdirp": "^0.5.1", + "xmldom": "^0.1.22" } }, "jasmine-spec-reporter": { @@ -6591,9 +6207,9 @@ "integrity": "sha1-lARqq7x0rQpLdGvNTcMFB1h7Z+M=", "dev": true, "requires": { - "fs-extra": "0.26.7", - "mkdirp": "0.5.1", - "q": "1.5.1" + "fs-extra": "^0.26.5", + "mkdirp": "^0.5.1", + "q": "^1.4.1" }, "dependencies": { "fs-extra": { @@ -6602,11 +6218,11 @@ "integrity": "sha1-muH92UiXeY7at20JGM9C0MMYT6k=", "dev": true, "requires": { - "graceful-fs": "4.1.11", - "jsonfile": "2.4.0", - "klaw": "1.3.1", - "path-is-absolute": "1.0.1", - "rimraf": "2.6.2" + "graceful-fs": "^4.1.2", + "jsonfile": "^2.1.0", + "klaw": "^1.0.0", + "path-is-absolute": "^1.0.0", + "rimraf": "^2.2.8" } }, "jsonfile": { @@ -6615,7 +6231,7 @@ "integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=", "dev": true, "requires": { - "graceful-fs": "4.1.11" + "graceful-fs": "^4.1.6" } } } @@ -6632,9 +6248,9 @@ "integrity": "sha512-z0FNlV4NGgjQN1fdtHYXf5kmgludM65fG/JlXzU6+rwkt9U5UWuXVYnXa2FpK0u6+qBuCmrm5byPNuiiddAHvQ==", "dev": true, "requires": { - "hoek": "4.2.1", - "isemail": "3.1.2", - "topo": "2.0.2" + "hoek": "4.x.x", + "isemail": "3.x.x", + "topo": "2.x.x" }, "dependencies": { "hoek": { @@ -6664,8 +6280,8 @@ "integrity": "sha512-saJstZWv7oNeOyBh3+Dx1qWzhW0+e6/8eDzo7p5rDFqxntSztloLtuKu+Ejhtq82jsilwOIZYsCz+lIjthg1Hw==", "dev": true, "requires": { - "argparse": "1.0.10", - "esprima": "4.0.0" + "argparse": "^1.0.7", + "esprima": "^4.0.0" } }, "jsbn": { @@ -6687,6 +6303,21 @@ "integrity": "sha512-QLPs8Dj7lnf3e3QYS1zkCo+4ZwqOiF9d/nZnYozTISxXWCfNs9yuky5rJw4/W34s7POaNlbZmQGaB5NiXCbP4w==", "dev": true }, + "json-parser": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/json-parser/-/json-parser-1.1.5.tgz", + "integrity": "sha1-5i7FJh0aal/CDoEqMgdAxtkAVnc=", + "requires": { + "esprima": "^2.7.0" + }, + "dependencies": { + "esprima": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", + "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=" + } + } + }, "json-schema": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", @@ -6704,7 +6335,7 @@ "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", "dev": true, "requires": { - "jsonify": "0.0.0" + "jsonify": "~0.0.0" } }, "json-stringify-safe": { @@ -6728,9 +6359,8 @@ "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.11" + "graceful-fs": "^4.1.6" } }, "jsonify": { @@ -6739,12 +6369,6 @@ "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=", "dev": true }, - "jsonparse": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", - "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=", - "dev": true - }, "jsonpointer": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.0.1.tgz", @@ -6772,40 +6396,91 @@ } } }, - "karma": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/karma/-/karma-2.0.0.tgz", - "integrity": "sha512-K9Kjp8CldLyL9ANSUctDyxC7zH3hpqXj/K09qVf06K3T/kXaHtFZ5tQciK7OzQu68FLvI89Na510kqQ2LCbpIw==", + "jsrsasign": { + "version": "8.0.12", + "resolved": "https://registry.npmjs.org/jsrsasign/-/jsrsasign-8.0.12.tgz", + "integrity": "sha1-Iqu5ZW00owuVMENnIINeicLlwxY=" + }, + "jszip": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.1.5.tgz", + "integrity": "sha512-5W8NUaFRFRqTOL7ZDDrx5qWHJyBXy6velVudIzQUSoqAAYqzSh2Z7/m0Rf1QbmQJccegD0r+YZxBjzqoBiEeJQ==", "dev": true, "requires": { - "bluebird": "3.5.1", - "body-parser": "1.18.2", - "browserify": "14.5.0", - "chokidar": "1.7.0", - "colors": "1.1.2", - "combine-lists": "1.0.1", - "connect": "3.6.6", - "core-js": "2.5.3", - "di": "0.0.1", - "dom-serialize": "2.2.1", - "expand-braces": "0.1.2", - "glob": "7.1.2", - "graceful-fs": "4.1.11", - "http-proxy": "1.16.2", - "isbinaryfile": "3.0.2", - "lodash": "4.17.5", - "log4js": "2.5.3", - "mime": "1.6.0", - "minimatch": "3.0.4", - "optimist": "0.6.1", - "qjobs": "1.2.0", - "range-parser": "1.2.0", - "rimraf": "2.6.2", - "safe-buffer": "5.1.1", + "core-js": "~2.3.0", + "es6-promise": "~3.0.2", + "lie": "~3.1.0", + "pako": "~1.0.2", + "readable-stream": "~2.0.6" + }, + "dependencies": { + "core-js": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.3.0.tgz", + "integrity": "sha1-+rg/uwstjchfpjbEudNMdUIMbWU=", + "dev": true + }, + "process-nextick-args": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", + "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=", + "dev": true + }, + "readable-stream": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz", + "integrity": "sha1-j5A0HmilPMySh4jaz80Rs265t44=", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "~1.0.0", + "process-nextick-args": "~1.0.6", + "string_decoder": "~0.10.x", + "util-deprecate": "~1.0.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 + } + } + }, + "karma": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/karma/-/karma-2.0.2.tgz", + "integrity": "sha1-TS25QChQpmVR+nhLAWT7CCTtjEs=", + "dev": true, + "requires": { + "bluebird": "^3.3.0", + "body-parser": "^1.16.1", + "chokidar": "^1.4.1", + "colors": "^1.1.0", + "combine-lists": "^1.0.0", + "connect": "^3.6.0", + "core-js": "^2.2.0", + "di": "^0.0.1", + "dom-serialize": "^2.2.0", + "expand-braces": "^0.1.1", + "glob": "^7.1.1", + "graceful-fs": "^4.1.2", + "http-proxy": "^1.13.0", + "isbinaryfile": "^3.0.0", + "lodash": "^4.17.4", + "log4js": "^2.3.9", + "mime": "^1.3.4", + "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.0.4", - "source-map": "0.6.1", + "source-map": "^0.6.1", "tmp": "0.0.33", - "useragent": "2.3.0" + "useragent": "2.2.1" }, "dependencies": { "source-map": { @@ -6822,8 +6497,8 @@ "integrity": "sha512-uf/ZVpAabDBPvdPdveyk1EPgbnloPvFFGgmRhYLTDH7gEB4nZdSBk8yTU47w1g/drLSx5uMOkjKk7IWKfWg/+w==", "dev": true, "requires": { - "fs-access": "1.0.1", - "which": "1.3.0" + "fs-access": "^1.0.0", + "which": "^1.2.1" } }, "karma-cli": { @@ -6832,7 +6507,7 @@ "integrity": "sha1-rmw8WKMTodALRRZMRVubhs4X+WA=", "dev": true, "requires": { - "resolve": "1.7.1" + "resolve": "^1.1.6" } }, "karma-coverage-istanbul-reporter": { @@ -6841,14 +6516,14 @@ "integrity": "sha512-sQHexslLF+QHzaKfK8+onTYMyvSwv+p5cDayVxhpEELGa3z0QuB+l0IMsicIkkBNMOJKQaqueiRoW7iuo7lsog==", "dev": true, "requires": { - "istanbul-api": "1.3.1", - "minimatch": "3.0.4" + "istanbul-api": "^1.1.14", + "minimatch": "^3.0.4" } }, "karma-jasmine": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/karma-jasmine/-/karma-jasmine-1.1.1.tgz", - "integrity": "sha1-b+hA51oRYAydkehLM8RY4cRqNSk=", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/karma-jasmine/-/karma-jasmine-1.1.2.tgz", + "integrity": "sha1-OU8rJf+0pkS5rabyLUQ+L9CIhsM=", "dev": true }, "karma-jasmine-html-reporter": { @@ -6857,7 +6532,7 @@ "integrity": "sha1-SKjl7xiAdhfuK14zwRlMNbQ5Ukw=", "dev": true, "requires": { - "karma-jasmine": "1.1.1" + "karma-jasmine": "^1.0.2" } }, "karma-source-map-support": { @@ -6866,7 +6541,7 @@ "integrity": "sha1-G/gee7SwiWJ6s1LsQXnhF8QGpUA=", "dev": true, "requires": { - "source-map-support": "0.4.18" + "source-map-support": "^0.4.1" } }, "killable": { @@ -6881,7 +6556,7 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "1.1.6" + "is-buffer": "^1.1.5" } }, "klaw": { @@ -6890,26 +6565,7 @@ "integrity": "sha1-QIhDO0azsbolnXh4XY6W9zugJDk=", "dev": true, "requires": { - "graceful-fs": "4.1.11" - } - }, - "labeled-stream-splicer": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/labeled-stream-splicer/-/labeled-stream-splicer-2.0.1.tgz", - "integrity": "sha512-MC94mHZRvJ3LfykJlTUipBqenZz1pacOZEMhhQ8dMGcDHs0SBE5GbsavUXV7YtP3icBW17W0Zy1I0lfASmo9Pg==", - "dev": true, - "requires": { - "inherits": "2.0.3", - "isarray": "2.0.4", - "stream-splicer": "2.0.0" - }, - "dependencies": { - "isarray": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.4.tgz", - "integrity": "sha512-GMxXOiUirWg1xTKRipM0Ek07rX+ubx4nNVElTJdNLYmNO/2YrDkgJGw9CljXn+r4EWiDQg/8lsRdHyg2PJuUaA==", - "dev": true - } + "graceful-fs": "^4.1.9" } }, "lazy-cache": { @@ -6924,7 +6580,7 @@ "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", "dev": true, "requires": { - "invert-kv": "1.0.0" + "invert-kv": "^1.0.0" } }, "lcov-parse": { @@ -6939,14 +6595,14 @@ "integrity": "sha512-KPdIJKWcEAb02TuJtaLrhue0krtRLoRoo7x6BNJIBelO00t/CCdJQUnHW5V34OnHMWzIktSalJxRO+FvytQlCQ==", "dev": true, "requires": { - "errno": "0.1.7", - "graceful-fs": "4.1.11", - "image-size": "0.5.5", - "mime": "1.6.0", - "mkdirp": "0.5.1", - "promise": "7.3.1", + "errno": "^0.1.1", + "graceful-fs": "^4.1.2", + "image-size": "~0.5.0", + "mime": "^1.2.11", + "mkdirp": "^0.5.0", + "promise": "^7.1.1", "request": "2.81.0", - "source-map": "0.5.7" + "source-map": "^0.5.3" } }, "less-loader": { @@ -6955,9 +6611,9 @@ "integrity": "sha512-KNTsgCE9tMOM70+ddxp9yyt9iHqgmSs0yTZc5XH5Wo+g80RWRIYNqE58QJKm/yMud5wZEvz50ugRDuzVIkyahg==", "dev": true, "requires": { - "clone": "2.1.2", - "loader-utils": "1.1.0", - "pify": "3.0.0" + "clone": "^2.1.1", + "loader-utils": "^1.1.0", + "pify": "^3.0.0" } }, "levn": { @@ -6967,17 +6623,8 @@ "dev": true, "optional": true, "requires": { - "prelude-ls": "1.1.2", - "type-check": "0.3.2" - } - }, - "lexical-scope": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/lexical-scope/-/lexical-scope-1.2.0.tgz", - "integrity": "sha1-/Ope3HBKSzqHls3KQZw6CvryLfQ=", - "dev": true, - "requires": { - "astw": "2.2.0" + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" } }, "libbase64": { @@ -7017,7 +6664,16 @@ "integrity": "sha512-NqAFodJdpBUuf1iD+Ij8hQvF0rCFKlO2KaieoQzAPhFgzLCtJnC7Z7x5gQbGNjoe++wOKAtAmwVEIBLqq2Yp1A==", "dev": true, "requires": { - "ejs": "2.5.8" + "ejs": "^2.5.7" + } + }, + "lie": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz", + "integrity": "sha1-mkNrLMd0bKWd56QfpGmz77dr2H4=", + "dev": true, + "requires": { + "immediate": "~3.0.5" } }, "load-json-file": { @@ -7026,11 +6682,11 @@ "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", "dev": true, "requires": { - "graceful-fs": "4.1.11", - "parse-json": "2.2.0", - "pify": "2.3.0", - "pinkie-promise": "2.0.1", - "strip-bom": "2.0.0" + "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": { "pify": { @@ -7052,9 +6708,9 @@ "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.1.0.tgz", "integrity": "sha1-yYrvSIvM7aL/teLeZG1qdUQp9c0=", "requires": { - "big.js": "3.2.0", - "emojis-list": "2.1.0", - "json5": "0.5.1" + "big.js": "^3.1.3", + "emojis-list": "^2.0.0", + "json5": "^0.5.0" } }, "locate-path": { @@ -7063,14 +6719,14 @@ "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", "dev": true, "requires": { - "p-locate": "2.0.0", - "path-exists": "3.0.0" + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" } }, "lodash": { - "version": "4.17.5", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.5.tgz", - "integrity": "sha512-svL3uiZf1RwhH+cWrfZn3A4+U58wbP0tGVTLQPbjplZxZ8ROD9VLuNgsRniTlLe7OlSqR79RUehXgpBW/s0IQw==", + "version": "4.17.10", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", + "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==", "dev": true }, "lodash.assign": { @@ -7086,12 +6742,6 @@ "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", "dev": true }, - "lodash.memoize": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-3.0.4.tgz", - "integrity": "sha1-LcvSwofLwKVcxCMovQxzYVDVPj8=", - "dev": true - }, "lodash.mergewith": { "version": "4.6.1", "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.1.tgz", @@ -7112,23 +6762,23 @@ "dev": true }, "log4js": { - "version": "2.5.3", - "resolved": "https://registry.npmjs.org/log4js/-/log4js-2.5.3.tgz", - "integrity": "sha512-YL/qpTxYtK0iWWbuKCrevDZz5lh+OjyHHD+mICqpjnYGKdNRBvPeh/1uYjkKUemT1CSO4wwLOwphWMpKAnD9kw==", + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/log4js/-/log4js-2.6.0.tgz", + "integrity": "sha512-9rG2W9o0D4GJDzQjno1rRpe+hzK0IEG/uGdjzNROStW/DWhV3sNX2r8OdPKppThlK7gr+08C5FSReWqmaRb/Ww==", "dev": true, "requires": { - "amqplib": "0.5.2", - "axios": "0.15.3", - "circular-json": "0.5.3", - "date-format": "1.2.0", - "debug": "3.1.0", - "hipchat-notifier": "1.1.0", - "loggly": "1.1.1", - "mailgun-js": "0.7.15", - "nodemailer": "2.7.2", - "redis": "2.8.0", - "semver": "5.5.0", - "slack-node": "0.2.0", + "amqplib": "^0.5.2", + "axios": "^0.15.3", + "circular-json": "^0.5.4", + "date-format": "^1.2.0", + "debug": "^3.1.0", + "hipchat-notifier": "^1.1.0", + "loggly": "^1.1.0", + "mailgun-js": "^0.18.0", + "nodemailer": "^2.5.0", + "redis": "^2.7.1", + "semver": "^5.5.0", + "slack-node": "~0.2.0", "streamroller": "0.7.0" } }, @@ -7139,9 +6789,9 @@ "dev": true, "optional": true, "requires": { - "json-stringify-safe": "5.0.1", - "request": "2.75.0", - "timespan": "2.3.0" + "json-stringify-safe": "5.0.x", + "request": "2.75.x", + "timespan": "2.3.x" }, "dependencies": { "ansi-styles": { @@ -7165,11 +6815,11 @@ "dev": true, "optional": true, "requires": { - "ansi-styles": "2.2.1", - "escape-string-regexp": "1.0.5", - "has-ansi": "2.0.0", - "strip-ansi": "3.0.1", - "supports-color": "2.0.0" + "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" } }, "form-data": { @@ -7179,9 +6829,9 @@ "dev": true, "optional": true, "requires": { - "asynckit": "0.4.0", - "combined-stream": "1.0.6", - "mime-types": "2.1.18" + "asynckit": "^0.4.0", + "combined-stream": "^1.0.5", + "mime-types": "^2.1.11" } }, "har-validator": { @@ -7191,10 +6841,10 @@ "dev": true, "optional": true, "requires": { - "chalk": "1.1.3", - "commander": "2.15.1", - "is-my-json-valid": "2.17.2", - "pinkie-promise": "2.0.1" + "chalk": "^1.1.1", + "commander": "^2.9.0", + "is-my-json-valid": "^2.12.4", + "pinkie-promise": "^2.0.0" } }, "node-uuid": { @@ -7218,27 +6868,27 @@ "dev": true, "optional": true, "requires": { - "aws-sign2": "0.6.0", - "aws4": "1.7.0", - "bl": "1.1.2", - "caseless": "0.11.0", - "combined-stream": "1.0.6", - "extend": "3.0.1", - "forever-agent": "0.6.1", - "form-data": "2.0.0", - "har-validator": "2.0.6", - "hawk": "3.1.3", - "http-signature": "1.1.1", - "is-typedarray": "1.0.0", - "isstream": "0.1.2", - "json-stringify-safe": "5.0.1", - "mime-types": "2.1.18", - "node-uuid": "1.4.8", - "oauth-sign": "0.8.2", - "qs": "6.2.3", - "stringstream": "0.0.5", - "tough-cookie": "2.3.4", - "tunnel-agent": "0.4.3" + "aws-sign2": "~0.6.0", + "aws4": "^1.2.1", + "bl": "~1.1.2", + "caseless": "~0.11.0", + "combined-stream": "~1.0.5", + "extend": "~3.0.0", + "forever-agent": "~0.6.1", + "form-data": "~2.0.0", + "har-validator": "~2.0.6", + "hawk": "~3.1.3", + "http-signature": "~1.1.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.7", + "node-uuid": "~1.4.7", + "oauth-sign": "~0.8.1", + "qs": "~6.2.0", + "stringstream": "~0.0.4", + "tough-cookie": "~2.3.0", + "tunnel-agent": "~0.4.1" } }, "supports-color": { @@ -7275,7 +6925,7 @@ "integrity": "sha1-0aitM/qc4OcT1l/dCsi3SNR4yEg=", "dev": true, "requires": { - "js-tokens": "3.0.2" + "js-tokens": "^3.0.0" } }, "loud-rejection": { @@ -7284,8 +6934,8 @@ "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=", "dev": true, "requires": { - "currently-unhandled": "0.4.1", - "signal-exit": "3.0.2" + "currently-unhandled": "^0.4.1", + "signal-exit": "^3.0.0" } }, "lower-case": { @@ -7300,8 +6950,8 @@ "integrity": "sha512-wgeVXhrDwAWnIF/yZARsFnMBtdFXOg1b8RIrhilp+0iDYN4mdQcNZElDZ0e4B64BhaxeQ5zN7PMyvu7we1kPeQ==", "dev": true, "requires": { - "pseudomap": "1.0.2", - "yallist": "2.1.2" + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" } }, "magic-string": { @@ -7310,7 +6960,7 @@ "integrity": "sha512-oreip9rJZkzvA8Qzk9HFs8fZGF/u7H/gtrE8EN6RjKJ9kh2HlC+yQ2QezifqTZfGyiuAV0dRv5a+y/8gBb1m9w==", "dev": true, "requires": { - "vlq": "0.2.3" + "vlq": "^0.2.2" } }, "mailcomposer": { @@ -7325,78 +6975,29 @@ } }, "mailgun-js": { - "version": "0.7.15", - "resolved": "https://registry.npmjs.org/mailgun-js/-/mailgun-js-0.7.15.tgz", - "integrity": "sha1-7jZqINrGTDwVwD1sGz4O15UlKrs=", + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/mailgun-js/-/mailgun-js-0.18.0.tgz", + "integrity": "sha512-o0P6jjZlx5CQj12tvVgDTbgjTqVN0+5h6/6P1+3c6xmozVKBwniQ6Qt3MkCSF0+ueVTbobAfWyGpWRZMJu8t1g==", "dev": true, "optional": true, "requires": { - "async": "2.1.5", - "debug": "2.2.0", - "form-data": "2.1.4", - "inflection": "1.10.0", - "is-stream": "1.1.0", - "path-proxy": "1.0.0", - "proxy-agent": "2.0.0", - "q": "1.4.1", - "tsscmp": "1.0.5" - }, - "dependencies": { - "async": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/async/-/async-2.1.5.tgz", - "integrity": "sha1-5YfGhYCZSsZ/xW/4bTrFa9voELw=", - "dev": true, - "optional": true, - "requires": { - "lodash": "4.17.5" - } - }, - "debug": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", - "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", - "dev": true, - "optional": true, - "requires": { - "ms": "0.7.1" - } - }, - "form-data": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.1.4.tgz", - "integrity": "sha1-M8GDrPGTJ27KqYFDpp6Uv+4XUNE=", - "dev": true, - "optional": true, - "requires": { - "asynckit": "0.4.0", - "combined-stream": "1.0.6", - "mime-types": "2.1.18" - } - }, - "ms": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", - "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=", - "dev": true, - "optional": true - }, - "q": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/q/-/q-1.4.1.tgz", - "integrity": "sha1-VXBbzZPF82c1MMLCy8DCs63cKG4=", - "dev": true, - "optional": true - } + "async": "~2.6.0", + "debug": "~3.1.0", + "form-data": "~2.3.0", + "inflection": "~1.12.0", + "is-stream": "^1.1.0", + "path-proxy": "~1.0.0", + "promisify-call": "^2.0.2", + "proxy-agent": "~3.0.0", + "tsscmp": "~1.0.0" } }, "make-dir": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.2.0.tgz", "integrity": "sha512-aNUAa4UMg/UougV25bbrU4ZaaKNjJ/3/xnvg/twpmKROPdKZPZ9wGgI0opdZzO8q/zUFawoUuixuOv33eZ61Iw==", - "dev": true, "requires": { - "pify": "3.0.0" + "pify": "^3.0.0" } }, "make-error": { @@ -7423,7 +7024,7 @@ "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", "dev": true, "requires": { - "object-visit": "1.0.1" + "object-visit": "^1.0.0" } }, "md5.js": { @@ -7432,8 +7033,8 @@ "integrity": "sha1-6b296UogpawYsENA/Fdk1bCdkB0=", "dev": true, "requires": { - "hash-base": "3.0.4", - "inherits": "2.0.3" + "hash-base": "^3.0.0", + "inherits": "^2.0.1" } }, "media-typer": { @@ -7448,7 +7049,7 @@ "integrity": "sha1-Xt1StIXKHZAP5kiVUFOZoN+kX3Y=", "dev": true, "requires": { - "mimic-fn": "1.2.0" + "mimic-fn": "^1.0.0" } }, "memory-fs": { @@ -7457,8 +7058,8 @@ "integrity": "sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=", "dev": true, "requires": { - "errno": "0.1.7", - "readable-stream": "2.3.6" + "errno": "^0.1.3", + "readable-stream": "^2.0.1" } }, "meow": { @@ -7467,16 +7068,16 @@ "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", "dev": true, "requires": { - "camelcase-keys": "2.1.0", - "decamelize": "1.2.0", - "loud-rejection": "1.6.0", - "map-obj": "1.0.1", - "minimist": "1.2.0", - "normalize-package-data": "2.4.0", - "object-assign": "4.1.1", - "read-pkg-up": "1.0.1", - "redent": "1.0.0", - "trim-newlines": "1.0.0" + "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": { "minimist": { @@ -7504,19 +7105,19 @@ "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", "dev": true, "requires": { - "arr-diff": "2.0.0", - "array-unique": "0.2.1", - "braces": "1.8.5", - "expand-brackets": "0.1.5", - "extglob": "0.3.2", - "filename-regex": "2.0.1", - "is-extglob": "1.0.0", - "is-glob": "2.0.1", - "kind-of": "3.2.2", - "normalize-path": "2.1.1", - "object.omit": "2.0.1", - "parse-glob": "3.0.4", - "regex-cache": "0.4.4" + "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" } }, "miller-rabin": { @@ -7525,8 +7126,8 @@ "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", "dev": true, "requires": { - "bn.js": "4.11.8", - "brorand": "1.1.0" + "bn.js": "^4.0.0", + "brorand": "^1.0.1" } }, "mime": { @@ -7544,7 +7145,7 @@ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", "requires": { - "mime-db": "1.33.0" + "mime-db": "~1.33.0" } }, "mimic-fn": { @@ -7570,7 +7171,7 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "requires": { - "brace-expansion": "1.1.11" + "brace-expansion": "^1.1.7" } }, "minimist": { @@ -7585,16 +7186,16 @@ "integrity": "sha512-zHo8v+otD1J10j/tC+VNoGK9keCuByhKovAvdn74dmxJl9+mWHnx6EMsDN4lgRoMI/eYo2nchAxniIbUPb5onw==", "dev": true, "requires": { - "concat-stream": "1.6.2", - "duplexify": "3.5.4", - "end-of-stream": "1.4.1", - "flush-write-stream": "1.0.3", - "from2": "2.3.0", - "parallel-transform": "1.1.0", - "pump": "2.0.1", - "pumpify": "1.4.0", - "stream-each": "1.2.2", - "through2": "2.0.3" + "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": "^2.0.1", + "pumpify": "^1.3.3", + "stream-each": "^1.1.0", + "through2": "^2.0.0" } }, "mixin-deep": { @@ -7603,8 +7204,8 @@ "integrity": "sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ==", "dev": true, "requires": { - "for-in": "1.0.2", - "is-extendable": "1.0.1" + "for-in": "^1.0.2", + "is-extendable": "^1.0.1" }, "dependencies": { "is-extendable": { @@ -7613,7 +7214,7 @@ "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", "dev": true, "requires": { - "is-plain-object": "2.0.4" + "is-plain-object": "^2.0.4" } } } @@ -7624,8 +7225,8 @@ "integrity": "sha1-T7lJRB2rGCVA8f4DW6YOGUel5X4=", "dev": true, "requires": { - "for-in": "0.1.8", - "is-extendable": "0.1.1" + "for-in": "^0.1.3", + "is-extendable": "^0.1.1" }, "dependencies": { "for-in": { @@ -7645,70 +7246,6 @@ "minimist": "0.0.8" } }, - "module-deps": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/module-deps/-/module-deps-4.1.1.tgz", - "integrity": "sha1-IyFYM/HaE/1gbMuAh7RIUty4If0=", - "dev": true, - "requires": { - "JSONStream": "1.3.2", - "browser-resolve": "1.11.2", - "cached-path-relative": "1.0.1", - "concat-stream": "1.5.2", - "defined": "1.0.0", - "detective": "4.7.1", - "duplexer2": "0.1.4", - "inherits": "2.0.3", - "parents": "1.0.1", - "readable-stream": "2.3.6", - "resolve": "1.7.1", - "stream-combiner2": "1.1.1", - "subarg": "1.0.0", - "through2": "2.0.3", - "xtend": "4.0.1" - }, - "dependencies": { - "concat-stream": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.5.2.tgz", - "integrity": "sha1-cIl4Yk2FavQaWnQd790mHadSwmY=", - "dev": true, - "requires": { - "inherits": "2.0.3", - "readable-stream": "2.0.6", - "typedarray": "0.0.6" - }, - "dependencies": { - "readable-stream": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz", - "integrity": "sha1-j5A0HmilPMySh4jaz80Rs265t44=", - "dev": true, - "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "1.0.0", - "process-nextick-args": "1.0.7", - "string_decoder": "0.10.31", - "util-deprecate": "1.0.2" - } - } - } - }, - "process-nextick-args": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", - "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=", - "dev": true - }, - "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 - } - } - }, "moment": { "version": "2.20.1", "resolved": "https://registry.npmjs.org/moment/-/moment-2.20.1.tgz", @@ -7719,7 +7256,7 @@ "resolved": "https://registry.npmjs.org/moment-es6/-/moment-es6-1.0.0.tgz", "integrity": "sha1-VS/PQF1iVlsKH+hObB5peseTMt8=", "requires": { - "moment": "2.20.1" + "moment": "*" } }, "move-concurrently": { @@ -7728,12 +7265,12 @@ "integrity": "sha1-viwAX9oy4LKa8fBdfEszIUxwH5I=", "dev": true, "requires": { - "aproba": "1.2.0", - "copy-concurrently": "1.0.5", - "fs-write-stream-atomic": "1.0.10", - "mkdirp": "0.5.1", - "rimraf": "2.6.2", - "run-queue": "1.0.3" + "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" } }, "ms": { @@ -7747,8 +7284,8 @@ "integrity": "sha512-ji6J5enbMyGRHIAkAOu3WdV8nggqviKCEKtXcOqfphZZtQrmHKycfynJ2V7eVPUA4NhJ6V7Wf4TmGbTwKE9B6g==", "dev": true, "requires": { - "dns-packet": "1.3.1", - "thunky": "1.0.2" + "dns-packet": "^1.3.1", + "thunky": "^1.0.2" } }, "multicast-dns-service-types": { @@ -7770,18 +7307,18 @@ "integrity": "sha512-n8R9bS8yQ6eSXaV6jHUpKzD8gLsin02w1HSFiegwrs9E098Ylhw5jdyKPaYqvHknHaSCKTPp7C8dGCQ0q9koXA==", "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-odd": "2.0.0", - "is-windows": "1.0.2", - "kind-of": "6.0.2", - "object.pick": "1.3.0", - "regex-not": "1.0.2", - "snapdragon": "0.8.2", - "to-regex": "3.0.2" + "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-odd": "^2.0.0", + "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" }, "dependencies": { "arr-diff": { @@ -7833,7 +7370,7 @@ "resolved": "https://registry.npmjs.org/ng2-charts/-/ng2-charts-1.6.0.tgz", "integrity": "sha512-9w0WH69x5/nuqC1og2WaY39NbaBqTGIP1+5gZaH7/KPN6UEPonNg/pYnsIVklLj1DWPWXKa8+XXIJZ1jy5nLxg==", "requires": { - "chart.js": "2.7.2" + "chart.js": "^2.6.0" }, "dependencies": { "chart.js": { @@ -7841,8 +7378,8 @@ "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-2.7.2.tgz", "integrity": "sha512-90wl3V9xRZ8tnMvMlpcW+0Yg13BelsGS9P9t0ClaDxv/hdypHDr/YAGf+728m11P5ljwyB0ZHfPKCapZFqSqYA==", "requires": { - "chartjs-color": "2.2.0", - "moment": "2.20.1" + "chartjs-color": "^2.1.0", + "moment": "^2.10.2" } } } @@ -7853,7 +7390,7 @@ "integrity": "sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==", "dev": true, "requires": { - "lower-case": "1.1.4" + "lower-case": "^1.1.1" } }, "node-ensure": { @@ -7874,19 +7411,19 @@ "dev": true, "optional": true, "requires": { - "fstream": "1.0.11", - "glob": "7.1.2", - "graceful-fs": "4.1.11", - "minimatch": "3.0.4", - "mkdirp": "0.5.1", - "nopt": "3.0.6", - "npmlog": "4.1.2", - "osenv": "0.1.5", - "request": "2.81.0", - "rimraf": "2.6.2", - "semver": "5.3.0", - "tar": "2.2.1", - "which": "1.3.0" + "fstream": "^1.0.0", + "glob": "^7.0.3", + "graceful-fs": "^4.1.2", + "minimatch": "^3.0.2", + "mkdirp": "^0.5.0", + "nopt": "2 || 3", + "npmlog": "0 || 1 || 2 || 3 || 4", + "osenv": "0", + "request": "2", + "rimraf": "2", + "semver": "~5.3.0", + "tar": "^2.0.0", + "which": "1" }, "dependencies": { "nopt": { @@ -7896,7 +7433,7 @@ "dev": true, "optional": true, "requires": { - "abbrev": "1.1.1" + "abbrev": "1" } }, "semver": { @@ -7914,28 +7451,28 @@ "integrity": "sha512-5AzFzdoIMb89hBGMZglEegffzgRg+ZFoUmisQ8HI4j1KDdpx13J0taNp2y9xPbur6W61gepGDDotGBVQ7mfUCg==", "dev": true, "requires": { - "assert": "1.4.1", - "browserify-zlib": "0.2.0", - "buffer": "4.9.1", - "console-browserify": "1.1.0", - "constants-browserify": "1.0.0", - "crypto-browserify": "3.12.0", - "domain-browser": "1.2.0", - "events": "1.1.1", - "https-browserify": "1.0.0", - "os-browserify": "0.3.0", + "assert": "^1.1.1", + "browserify-zlib": "^0.2.0", + "buffer": "^4.3.0", + "console-browserify": "^1.1.0", + "constants-browserify": "^1.0.0", + "crypto-browserify": "^3.11.0", + "domain-browser": "^1.1.1", + "events": "^1.0.0", + "https-browserify": "^1.0.0", + "os-browserify": "^0.3.0", "path-browserify": "0.0.0", - "process": "0.11.10", - "punycode": "1.4.1", - "querystring-es3": "0.2.1", - "readable-stream": "2.3.6", - "stream-browserify": "2.0.1", - "stream-http": "2.8.1", - "string_decoder": "1.1.1", - "timers-browserify": "2.0.6", + "process": "^0.11.10", + "punycode": "^1.2.4", + "querystring-es3": "^0.2.0", + "readable-stream": "^2.3.3", + "stream-browserify": "^2.0.1", + "stream-http": "^2.7.2", + "string_decoder": "^1.0.0", + "timers-browserify": "^2.0.4", "tty-browserify": "0.0.0", - "url": "0.11.0", - "util": "0.10.3", + "url": "^0.11.0", + "util": "^0.10.3", "vm-browserify": "0.0.4" }, "dependencies": { @@ -7953,32 +7490,60 @@ "integrity": "sha1-QAlrCM560OoUaAhjr0ScfHWl0cg=", "dev": true }, + "node-rest-client": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/node-rest-client/-/node-rest-client-3.1.0.tgz", + "integrity": "sha1-4L623aeyDMC2enhHzxLF/EGcN8M=", + "dev": true, + "requires": { + "debug": "~2.2.0", + "follow-redirects": ">=1.2.0", + "xml2js": ">=0.2.4" + }, + "dependencies": { + "debug": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", + "dev": true, + "requires": { + "ms": "0.7.1" + } + }, + "ms": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", + "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=", + "dev": true + } + } + }, "node-sass": { - "version": "4.8.3", - "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.8.3.tgz", - "integrity": "sha512-tfFWhUsCk/Y19zarDcPo5xpj+IW3qCfOjVdHtYeG6S1CKbQOh1zqylnQK6cV3z9k80yxAnFX9Y+a9+XysDhhfg==", + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.9.0.tgz", + "integrity": "sha512-QFHfrZl6lqRU3csypwviz2XLgGNOoWQbo2GOvtsfQqOfL4cy1BtWnhx/XUeAO9LT3ahBzSRXcEO6DdvAH9DzSg==", "dev": true, "optional": true, "requires": { - "async-foreach": "0.1.3", - "chalk": "1.1.3", - "cross-spawn": "3.0.1", - "gaze": "1.1.2", - "get-stdin": "4.0.1", - "glob": "7.1.2", - "in-publish": "2.0.0", - "lodash.assign": "4.2.0", - "lodash.clonedeep": "4.5.0", - "lodash.mergewith": "4.6.1", - "meow": "3.7.0", - "mkdirp": "0.5.1", - "nan": "2.10.0", - "node-gyp": "3.6.2", - "npmlog": "4.1.2", - "request": "2.79.0", - "sass-graph": "2.2.4", - "stdout-stream": "1.4.0", - "true-case-path": "1.0.2" + "async-foreach": "^0.1.3", + "chalk": "^1.1.1", + "cross-spawn": "^3.0.0", + "gaze": "^1.0.0", + "get-stdin": "^4.0.1", + "glob": "^7.0.3", + "in-publish": "^2.0.0", + "lodash.assign": "^4.2.0", + "lodash.clonedeep": "^4.3.2", + "lodash.mergewith": "^4.6.0", + "meow": "^3.7.0", + "mkdirp": "^0.5.1", + "nan": "^2.10.0", + "node-gyp": "^3.3.1", + "npmlog": "^4.0.0", + "request": "~2.79.0", + "sass-graph": "^2.2.4", + "stdout-stream": "^1.4.0", + "true-case-path": "^1.0.2" }, "dependencies": { "ansi-styles": { @@ -8000,11 +7565,11 @@ "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "dev": true, "requires": { - "ansi-styles": "2.2.1", - "escape-string-regexp": "1.0.5", - "has-ansi": "2.0.0", - "strip-ansi": "3.0.1", - "supports-color": "2.0.0" + "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" } }, "form-data": { @@ -8014,9 +7579,9 @@ "dev": true, "optional": true, "requires": { - "asynckit": "0.4.0", - "combined-stream": "1.0.6", - "mime-types": "2.1.18" + "asynckit": "^0.4.0", + "combined-stream": "^1.0.5", + "mime-types": "^2.1.12" } }, "har-validator": { @@ -8026,10 +7591,10 @@ "dev": true, "optional": true, "requires": { - "chalk": "1.1.3", - "commander": "2.15.1", - "is-my-json-valid": "2.17.2", - "pinkie-promise": "2.0.1" + "chalk": "^1.1.1", + "commander": "^2.9.0", + "is-my-json-valid": "^2.12.4", + "pinkie-promise": "^2.0.0" } }, "qs": { @@ -8046,26 +7611,26 @@ "dev": true, "optional": true, "requires": { - "aws-sign2": "0.6.0", - "aws4": "1.7.0", - "caseless": "0.11.0", - "combined-stream": "1.0.6", - "extend": "3.0.1", - "forever-agent": "0.6.1", - "form-data": "2.1.4", - "har-validator": "2.0.6", - "hawk": "3.1.3", - "http-signature": "1.1.1", - "is-typedarray": "1.0.0", - "isstream": "0.1.2", - "json-stringify-safe": "5.0.1", - "mime-types": "2.1.18", - "oauth-sign": "0.8.2", - "qs": "6.3.2", - "stringstream": "0.0.5", - "tough-cookie": "2.3.4", - "tunnel-agent": "0.4.3", - "uuid": "3.2.1" + "aws-sign2": "~0.6.0", + "aws4": "^1.2.1", + "caseless": "~0.11.0", + "combined-stream": "~1.0.5", + "extend": "~3.0.0", + "forever-agent": "~0.6.1", + "form-data": "~2.1.1", + "har-validator": "~2.0.6", + "hawk": "~3.1.3", + "http-signature": "~1.1.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.7", + "oauth-sign": "~0.8.1", + "qs": "~6.3.0", + "stringstream": "~0.0.4", + "tough-cookie": "~2.3.0", + "tunnel-agent": "~0.4.1", + "uuid": "^3.0.0" } }, "supports-color": { @@ -8106,8 +7671,8 @@ "dev": true, "optional": true, "requires": { - "ip": "1.1.5", - "smart-buffer": "1.1.15" + "ip": "^1.1.2", + "smart-buffer": "^1.0.4" } } } @@ -8174,8 +7739,8 @@ "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", "dev": true, "requires": { - "abbrev": "1.1.1", - "osenv": "0.1.5" + "abbrev": "1", + "osenv": "^0.1.4" } }, "normalize-package-data": { @@ -8184,10 +7749,10 @@ "integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==", "dev": true, "requires": { - "hosted-git-info": "2.6.0", - "is-builtin-module": "1.0.0", - "semver": "5.5.0", - "validate-npm-package-license": "3.0.3" + "hosted-git-info": "^2.1.4", + "is-builtin-module": "^1.0.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" } }, "normalize-path": { @@ -8196,7 +7761,7 @@ "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", "dev": true, "requires": { - "remove-trailing-separator": "1.1.0" + "remove-trailing-separator": "^1.0.1" } }, "normalize-range": { @@ -8211,7 +7776,7 @@ "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", "dev": true, "requires": { - "path-key": "2.0.1" + "path-key": "^2.0.0" } }, "npmlog": { @@ -8220,10 +7785,10 @@ "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", "dev": true, "requires": { - "are-we-there-yet": "1.1.4", - "console-control-strings": "1.1.0", - "gauge": "2.7.4", - "set-blocking": "2.0.0" + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" } }, "nth-check": { @@ -8232,7 +7797,7 @@ "integrity": "sha1-mSms32KPwsQQmN6rgqxYDPFJquQ=", "dev": true, "requires": { - "boolbase": "1.0.0" + "boolbase": "~1.0.0" } }, "null-check": { @@ -8277,9 +7842,9 @@ "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", "dev": true, "requires": { - "copy-descriptor": "0.1.1", - "define-property": "0.2.5", - "kind-of": "3.2.2" + "copy-descriptor": "^0.1.0", + "define-property": "^0.2.5", + "kind-of": "^3.0.3" }, "dependencies": { "define-property": { @@ -8288,7 +7853,7 @@ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "dev": true, "requires": { - "is-descriptor": "0.1.6" + "is-descriptor": "^0.1.0" } } } @@ -8305,7 +7870,7 @@ "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", "dev": true, "requires": { - "isobject": "3.0.1" + "isobject": "^3.0.0" }, "dependencies": { "isobject": { @@ -8322,8 +7887,8 @@ "integrity": "sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo=", "dev": true, "requires": { - "for-own": "0.1.5", - "is-extendable": "0.1.1" + "for-own": "^0.1.4", + "is-extendable": "^0.1.1" } }, "object.pick": { @@ -8332,7 +7897,7 @@ "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", "dev": true, "requires": { - "isobject": "3.0.1" + "isobject": "^3.0.1" }, "dependencies": { "isobject": { @@ -8368,9 +7933,8 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, "requires": { - "wrappy": "1.0.2" + "wrappy": "1" } }, "opn": { @@ -8379,7 +7943,7 @@ "integrity": "sha512-iPNl7SyM8L30Rm1sjGdLLheyHVw5YXVfi3SKWJzBI7efxRwHojfRFjwE/OLM6qp9xJYMgab8WicTU1cPoY+Hpg==", "dev": true, "requires": { - "is-wsl": "1.1.0" + "is-wsl": "^1.1.0" } }, "optimist": { @@ -8388,8 +7952,8 @@ "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", "dev": true, "requires": { - "minimist": "0.0.8", - "wordwrap": "0.0.2" + "minimist": "~0.0.1", + "wordwrap": "~0.0.2" } }, "optionator": { @@ -8399,12 +7963,12 @@ "dev": true, "optional": true, "requires": { - "deep-is": "0.1.3", - "fast-levenshtein": "2.0.6", - "levn": "0.3.0", - "prelude-ls": "1.1.2", - "type-check": "0.3.2", - "wordwrap": "1.0.0" + "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" }, "dependencies": { "wordwrap": { @@ -8428,7 +7992,7 @@ "integrity": "sha1-kUf5P6FpbQS+YeAb1QuurKZWvTs=", "dev": true, "requires": { - "url-parse": "1.0.5" + "url-parse": "1.0.x" }, "dependencies": { "url-parse": { @@ -8437,8 +8001,8 @@ "integrity": "sha1-CFSGBCKv3P7+tsllxmLUgAFpkns=", "dev": true, "requires": { - "querystringify": "0.0.4", - "requires-port": "1.0.0" + "querystringify": "0.0.x", + "requires-port": "1.0.x" } } } @@ -8461,7 +8025,7 @@ "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", "dev": true, "requires": { - "lcid": "1.0.0" + "lcid": "^1.0.0" } }, "os-tmpdir": { @@ -8476,8 +8040,8 @@ "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", "dev": true, "requires": { - "os-homedir": "1.0.2", - "os-tmpdir": "1.0.2" + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" } }, "p-finally": { @@ -8492,7 +8056,7 @@ "integrity": "sha512-Y/OtIaXtUPr4/YpMv1pCL5L5ed0rumAaAeBSj12F+bSlMdys7i8oQF/GUJmfpTS/QoaRrS/k6pma29haJpsMng==", "dev": true, "requires": { - "p-try": "1.0.0" + "p-try": "^1.0.0" } }, "p-locate": { @@ -8501,7 +8065,7 @@ "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", "dev": true, "requires": { - "p-limit": "1.2.0" + "p-limit": "^1.1.0" } }, "p-map": { @@ -8517,63 +8081,34 @@ "dev": true }, "pac-proxy-agent": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-1.1.0.tgz", - "integrity": "sha512-QBELCWyLYPgE2Gj+4wUEiMscHrQ8nRPBzYItQNOHWavwBt25ohZHQC4qnd5IszdVVrFbLsQ+dPkm6eqdjJAmwQ==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-2.0.2.tgz", + "integrity": "sha512-cDNAN1Ehjbf5EHkNY5qnRhGPUCp6SnpyVof5fRzN800QV1Y2OkzbH9rmjZkbBRa8igof903yOnjIl6z0SlAhxA==", "dev": true, "optional": true, "requires": { - "agent-base": "2.1.1", - "debug": "2.6.9", - "extend": "3.0.1", - "get-uri": "2.0.1", - "http-proxy-agent": "1.0.0", - "https-proxy-agent": "1.0.0", - "pac-resolver": "2.0.0", - "raw-body": "2.3.2", - "socks-proxy-agent": "2.1.1" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "optional": true, - "requires": { - "ms": "2.0.0" - } - } + "agent-base": "^4.2.0", + "debug": "^3.1.0", + "get-uri": "^2.0.0", + "http-proxy-agent": "^2.1.0", + "https-proxy-agent": "^2.2.1", + "pac-resolver": "^3.0.0", + "raw-body": "^2.2.0", + "socks-proxy-agent": "^3.0.0" } }, "pac-resolver": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-2.0.0.tgz", - "integrity": "sha1-mbiNLxk/ve78HJpSnB8yYKtSd80=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-3.0.0.tgz", + "integrity": "sha512-tcc38bsjuE3XZ5+4vP96OfhOugrX+JcnpUbhfuc4LuXBLQhoTthOstZeoQJBDnQUDYzYmdImKsbz0xSl1/9qeA==", "dev": true, "optional": true, "requires": { - "co": "3.0.6", - "degenerator": "1.0.4", - "ip": "1.0.1", - "netmask": "1.0.6", - "thunkify": "2.1.2" - }, - "dependencies": { - "co": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/co/-/co-3.0.6.tgz", - "integrity": "sha1-FEXyJsXrlWE45oyawwFn6n0ua9o=", - "dev": true, - "optional": true - }, - "ip": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ip/-/ip-1.0.1.tgz", - "integrity": "sha1-x+NWzeoiWucbNtcPLnGpK6TkJZA=", - "dev": true, - "optional": true - } + "co": "^4.6.0", + "degenerator": "^1.0.4", + "ip": "^1.1.5", + "netmask": "^1.0.6", + "thunkify": "^2.1.2" } }, "pako": { @@ -8588,9 +8123,9 @@ "integrity": "sha1-1BDwZbBdojCB/NEPKIVMKb2jOwY=", "dev": true, "requires": { - "cyclist": "0.2.2", - "inherits": "2.0.3", - "readable-stream": "2.3.6" + "cyclist": "~0.2.2", + "inherits": "^2.0.3", + "readable-stream": "^2.1.5" } }, "param-case": { @@ -8599,16 +8134,7 @@ "integrity": "sha1-35T9jPZTHs915r75oIWPvHK+Ikc=", "dev": true, "requires": { - "no-case": "2.3.2" - } - }, - "parents": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parents/-/parents-1.0.1.tgz", - "integrity": "sha1-/t1NK/GTp3dF/nHjcdc8MwfZx1E=", - "dev": true, - "requires": { - "path-platform": "0.11.15" + "no-case": "^2.2.0" } }, "parse-asn1": { @@ -8617,11 +8143,11 @@ "integrity": "sha512-KPx7flKXg775zZpnp9SxJlz00gTd4BmJ2yJufSc44gMCRrRQ7NSzAcSJQfifuOLgW6bEi+ftrALtsgALeB2Adw==", "dev": true, "requires": { - "asn1.js": "4.10.1", - "browserify-aes": "1.2.0", - "create-hash": "1.2.0", - "evp_bytestokey": "1.0.3", - "pbkdf2": "3.0.14" + "asn1.js": "^4.0.0", + "browserify-aes": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.0", + "pbkdf2": "^3.0.3" } }, "parse-glob": { @@ -8630,10 +8156,10 @@ "integrity": "sha1-ssN2z7EfNVE7rdFz7wu246OIORw=", "dev": true, "requires": { - "glob-base": "0.3.0", - "is-dotfile": "1.0.3", - "is-extglob": "1.0.0", - "is-glob": "2.0.1" + "glob-base": "^0.3.0", + "is-dotfile": "^1.0.0", + "is-extglob": "^1.0.0", + "is-glob": "^2.0.0" } }, "parse-json": { @@ -8642,7 +8168,7 @@ "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", "dev": true, "requires": { - "error-ex": "1.3.1" + "error-ex": "^1.2.0" } }, "parse-passwd": { @@ -8657,7 +8183,7 @@ "integrity": "sha1-1SCKNzjkZ2bikbouoXNoSSGouJ0=", "dev": true, "requires": { - "better-assert": "1.0.2" + "better-assert": "~1.0.0" } }, "parseuri": { @@ -8666,7 +8192,7 @@ "integrity": "sha1-gCBKUNTbt3m/3G6+J3jZDkvOMgo=", "dev": true, "requires": { - "better-assert": "1.0.2" + "better-assert": "~1.0.0" } }, "parseurl": { @@ -8702,8 +8228,7 @@ "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 + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" }, "path-is-inside": { "version": "1.0.2", @@ -8723,12 +8248,6 @@ "integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=", "dev": true }, - "path-platform": { - "version": "0.11.15", - "resolved": "https://registry.npmjs.org/path-platform/-/path-platform-0.11.15.tgz", - "integrity": "sha1-6GQhf3TDaFDwhSt43Hv31KVyG/I=", - "dev": true - }, "path-proxy": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/path-proxy/-/path-proxy-1.0.0.tgz", @@ -8736,7 +8255,7 @@ "dev": true, "optional": true, "requires": { - "inflection": "1.3.8" + "inflection": "~1.3.0" }, "dependencies": { "inflection": { @@ -8760,20 +8279,20 @@ "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", "dev": true, "requires": { - "pify": "3.0.0" + "pify": "^3.0.0" } }, "pbkdf2": { - "version": "3.0.14", - "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.14.tgz", - "integrity": "sha512-gjsZW9O34fm0R7PaLHRJmLLVfSoesxztjPjE9o6R+qtVJij90ltg1joIovN9GKrRW3t1PzhDDG3UMEMFfZ+1wA==", + "version": "3.0.16", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.16.tgz", + "integrity": "sha512-y4CXP3thSxqf7c0qmOF+9UeOTrifiVTIM+u7NWlq+PRsHbr7r7dpCmvzrZxa96JJUNi0Y5w9VqG5ZNeCVMoDcA==", "dev": true, "requires": { - "create-hash": "1.2.0", - "create-hmac": "1.1.7", - "ripemd160": "2.0.1", - "safe-buffer": "5.1.1", - "sha.js": "2.4.11" + "create-hash": "^1.1.2", + "create-hmac": "^1.1.4", + "ripemd160": "^2.0.1", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" } }, "pdfjs-dist": { @@ -8781,8 +8300,8 @@ "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-2.0.303.tgz", "integrity": "sha1-jABTDyQihmA7/L/dLukXwYK51EE=", "requires": { - "node-ensure": "0.0.0", - "worker-loader": "1.1.1" + "node-ensure": "^0.0.0", + "worker-loader": "^1.1.0" } }, "performance-now": { @@ -8794,8 +8313,7 @@ "pify": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=" }, "pinkie": { "version": "2.0.4", @@ -8809,7 +8327,7 @@ "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", "dev": true, "requires": { - "pinkie": "2.0.4" + "pinkie": "^2.0.0" } }, "pkg-dir": { @@ -8818,7 +8336,7 @@ "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", "dev": true, "requires": { - "find-up": "2.1.0" + "find-up": "^2.1.0" } }, "portfinder": { @@ -8827,9 +8345,9 @@ "integrity": "sha1-uzLs2HwnEErm7kS1o8y/Drsa7ek=", "dev": true, "requires": { - "async": "1.5.2", - "debug": "2.6.9", - "mkdirp": "0.5.1" + "async": "^1.5.2", + "debug": "^2.2.0", + "mkdirp": "0.5.x" }, "dependencies": { "async": { @@ -8856,25 +8374,25 @@ "dev": true }, "postcss": { - "version": "6.0.21", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.21.tgz", - "integrity": "sha512-y/bKfbQz2Nn/QBC08bwvYUxEFOVGfPIUOTsJ2CK5inzlXW9SdYR1x4pEsG9blRAF/PX+wRNdOah+gx/hv4q7dw==", + "version": "6.0.22", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.22.tgz", + "integrity": "sha512-Toc9lLoUASwGqxBSJGTVcOQiDqjK+Z2XlWBg+IgYwQMY9vA2f7iMpXVc1GpPcfTSyM5lkxNo0oDwDRO+wm7XHA==", "dev": true, "requires": { - "chalk": "2.3.2", - "source-map": "0.6.1", - "supports-color": "5.3.0" + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.4.0" }, "dependencies": { "chalk": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.2.tgz", - "integrity": "sha512-ZM4j2/ld/YZDc3Ma8PgN7gyAk+kHMMMyzLNryCPGhWrsfAuDVeuid5bpRFTDgMH9JBK2lA4dyyAkkZYF/WcqDQ==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", "dev": true, "requires": { - "ansi-styles": "3.2.1", - "escape-string-regexp": "1.0.5", - "supports-color": "5.3.0" + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" } }, "has-flag": { @@ -8890,12 +8408,12 @@ "dev": true }, "supports-color": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.3.0.tgz", - "integrity": "sha512-0aP01LLIskjKs3lq52EC0aGBAJhLq7B2Rd8HC/DR/PtNNpcLilNmHC12O+hu0usQpo7wtHNRqtrhBwtDb0+dNg==", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", + "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", "dev": true, "requires": { - "has-flag": "3.0.0" + "has-flag": "^3.0.0" } } } @@ -8906,10 +8424,10 @@ "integrity": "sha512-5l327iI75POonjxkXgdRCUS+AlzAdBx4pOvMEhTKTCjb1p8IEeVR9yx3cPbmN7LIWJLbfnIXxAhoB4jpD0c/Cw==", "dev": true, "requires": { - "postcss": "6.0.21", - "postcss-value-parser": "3.3.0", - "read-cache": "1.0.0", - "resolve": "1.7.1" + "postcss": "^6.0.1", + "postcss-value-parser": "^3.2.3", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" } }, "postcss-load-config": { @@ -8918,10 +8436,10 @@ "integrity": "sha1-U56a/J3chiASHr+djDZz4M5Q0oo=", "dev": true, "requires": { - "cosmiconfig": "2.2.2", - "object-assign": "4.1.1", - "postcss-load-options": "1.2.0", - "postcss-load-plugins": "2.3.0" + "cosmiconfig": "^2.1.0", + "object-assign": "^4.1.0", + "postcss-load-options": "^1.2.0", + "postcss-load-plugins": "^2.3.0" } }, "postcss-load-options": { @@ -8930,8 +8448,8 @@ "integrity": "sha1-sJixVZ3awt8EvAuzdfmaXP4rbYw=", "dev": true, "requires": { - "cosmiconfig": "2.2.2", - "object-assign": "4.1.1" + "cosmiconfig": "^2.1.0", + "object-assign": "^4.1.0" } }, "postcss-load-plugins": { @@ -8940,20 +8458,20 @@ "integrity": "sha1-dFdoEWWZrKLwCfrUJrABdQSdjZI=", "dev": true, "requires": { - "cosmiconfig": "2.2.2", - "object-assign": "4.1.1" + "cosmiconfig": "^2.1.1", + "object-assign": "^4.1.0" } }, "postcss-loader": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-2.1.3.tgz", - "integrity": "sha512-RuBcNE8rjCkIB0IsbmkGFRmQJTeQJfCI88E0VTarPNTvaNSv9OFv1DvTwgtAN/qlzyiELsmmmtX/tEzKp/cdug==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-2.1.4.tgz", + "integrity": "sha512-L2p654oK945B/gDFUGgOhh7uzj19RWoY1SVMeJVoKno1H2MdbQ0RppR/28JGju4pMb22iRC7BJ9aDzbxXSLf4A==", "dev": true, "requires": { - "loader-utils": "1.1.0", - "postcss": "6.0.21", - "postcss-load-config": "1.2.0", - "schema-utils": "0.4.5" + "loader-utils": "^1.1.0", + "postcss": "^6.0.0", + "postcss-load-config": "^1.2.0", + "schema-utils": "^0.4.0" } }, "postcss-url": { @@ -8962,11 +8480,11 @@ "integrity": "sha512-QMV5mA+pCYZQcUEPQkmor9vcPQ2MT+Ipuu8qdi1gVxbNiIiErEGft+eny1ak19qALoBkccS5AHaCaCDzh7b9MA==", "dev": true, "requires": { - "mime": "1.6.0", - "minimatch": "3.0.4", - "mkdirp": "0.5.1", - "postcss": "6.0.21", - "xxhashjs": "0.2.2" + "mime": "^1.4.1", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.0", + "postcss": "^6.0.1", + "xxhashjs": "^0.2.1" } }, "postcss-value-parser": { @@ -8993,8 +8511,8 @@ "integrity": "sha1-X0+HyPkeWuPzuoerTPXgOxoX8aM=", "dev": true, "requires": { - "renderkid": "2.0.1", - "utila": "0.4.0" + "renderkid": "^2.0.1", + "utila": "~0.4" } }, "process": { @@ -9015,7 +8533,7 @@ "dev": true, "optional": true, "requires": { - "asap": "2.0.6" + "asap": "~2.0.3" } }, "promise-inflight": { @@ -9024,33 +8542,55 @@ "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=", "dev": true }, + "promisify-call": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/promisify-call/-/promisify-call-2.0.4.tgz", + "integrity": "sha1-1IwtRWUszM1SgB3ey9UzptS9X7o=", + "dev": true, + "optional": true, + "requires": { + "with-callback": "^1.0.2" + } + }, "protractor": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/protractor/-/protractor-5.1.2.tgz", - "integrity": "sha1-myIXQXCaTGLVzVPGqt1UpxE36V8=", + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/protractor/-/protractor-5.3.2.tgz", + "integrity": "sha512-pw4uwwiy5lHZjIguxNpkEwJJa7hVz+bJsvaTI+IbXlfn2qXwzbF8eghW/RmrZwE2sGx82I8etb8lVjQ+JrjejA==", "dev": true, "requires": { - "@types/node": "6.0.105", - "@types/q": "0.0.32", - "@types/selenium-webdriver": "2.53.43", - "blocking-proxy": "0.0.5", - "chalk": "1.1.3", - "glob": "7.1.2", - "jasmine": "2.99.0", - "jasminewd2": "2.2.0", - "optimist": "0.6.1", + "@types/node": "^6.0.46", + "@types/q": "^0.0.32", + "@types/selenium-webdriver": "~2.53.39", + "blocking-proxy": "^1.0.0", + "chalk": "^1.1.3", + "glob": "^7.0.3", + "jasmine": "2.8.0", + "jasminewd2": "^2.1.0", + "optimist": "~0.6.0", "q": "1.4.1", - "saucelabs": "1.3.0", - "selenium-webdriver": "3.0.1", - "source-map-support": "0.4.18", - "webdriver-js-extender": "1.0.0", - "webdriver-manager": "12.0.6" + "saucelabs": "^1.5.0", + "selenium-webdriver": "3.6.0", + "source-map-support": "~0.4.0", + "webdriver-js-extender": "^1.0.0", + "webdriver-manager": "^12.0.6" }, "dependencies": { "@types/node": { - "version": "6.0.105", - "resolved": "https://registry.npmjs.org/@types/node/-/node-6.0.105.tgz", - "integrity": "sha512-fMIbw7iw91TSInS3b2DtDse5VaQEZqs0oTjvRNIFHnoHbnji+dLwpzL1L6dYGL39RzDNPHM/Off+VNcMk4ahwQ==", + "version": "6.0.110", + "resolved": "https://registry.npmjs.org/@types/node/-/node-6.0.110.tgz", + "integrity": "sha512-LiaH3mF+OAqR+9Wo1OTJDbZDtCewAVjTbMhF1ZgUJ3fc8xqOJq6VqbpBh9dJVCVzByGmYIg2fREbuXNX0TKiJA==", + "dev": true + }, + "@types/selenium-webdriver": { + "version": "2.53.43", + "resolved": "https://registry.npmjs.org/@types/selenium-webdriver/-/selenium-webdriver-2.53.43.tgz", + "integrity": "sha512-UBYHWph6P3tutkbXpW6XYg9ZPbTKjw/YC2hGG1/GEvWwTbvezBUv3h+mmUFw79T3RFPnmedpiXdOBbXX+4l0jg==", + "dev": true + }, + "adm-zip": { + "version": "0.4.11", + "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.4.11.tgz", + "integrity": "sha512-L8vcjDTCOIJk7wFvmlEUN7AsSb8T+2JrdP7KINBjzr24TJ5Mwj590sLu3BC7zNZowvJWa/JtPmD8eJCzdtDWjA==", "dev": true }, "ansi-styles": { @@ -9065,11 +8605,11 @@ "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "dev": true, "requires": { - "ansi-styles": "2.2.1", - "escape-string-regexp": "1.0.5", - "has-ansi": "2.0.0", - "strip-ansi": "3.0.1", - "supports-color": "2.0.0" + "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" } }, "del": { @@ -9078,13 +8618,13 @@ "integrity": "sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=", "dev": true, "requires": { - "globby": "5.0.0", - "is-path-cwd": "1.0.0", - "is-path-in-cwd": "1.0.1", - "object-assign": "4.1.1", - "pify": "2.3.0", - "pinkie-promise": "2.0.1", - "rimraf": "2.6.2" + "globby": "^5.0.0", + "is-path-cwd": "^1.0.0", + "is-path-in-cwd": "^1.0.0", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0", + "rimraf": "^2.2.8" } }, "globby": { @@ -9093,12 +8633,12 @@ "integrity": "sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0=", "dev": true, "requires": { - "array-union": "1.0.2", - "arrify": "1.0.1", - "glob": "7.1.2", - "object-assign": "4.1.1", - "pify": "2.3.0", - "pinkie-promise": "2.0.1" + "array-union": "^1.0.1", + "arrify": "^1.0.0", + "glob": "^7.0.3", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" } }, "minimist": { @@ -9119,29 +8659,50 @@ "integrity": "sha1-VXBbzZPF82c1MMLCy8DCs63cKG4=", "dev": true }, + "selenium-webdriver": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-3.6.0.tgz", + "integrity": "sha512-WH7Aldse+2P5bbFBO4Gle/nuQOdVwpHMTL6raL3uuBj/vPG07k6uzt3aiahu352ONBr5xXh0hDlM3LhtXPOC4Q==", + "dev": true, + "requires": { + "jszip": "^3.1.3", + "rimraf": "^2.5.4", + "tmp": "0.0.30", + "xml2js": "^0.4.17" + } + }, "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 }, + "tmp": { + "version": "0.0.30", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.30.tgz", + "integrity": "sha1-ckGdSovn1s51FI/YsyTlk6cRwu0=", + "dev": true, + "requires": { + "os-tmpdir": "~1.0.1" + } + }, "webdriver-manager": { "version": "12.0.6", "resolved": "https://registry.npmjs.org/webdriver-manager/-/webdriver-manager-12.0.6.tgz", "integrity": "sha1-PfGkgZdwELTL+MnYXHpXeCjA5ws=", "dev": true, "requires": { - "adm-zip": "0.4.7", - "chalk": "1.1.3", - "del": "2.2.2", - "glob": "7.1.2", - "ini": "1.3.5", - "minimist": "1.2.0", - "q": "1.4.1", - "request": "2.81.0", - "rimraf": "2.6.2", - "semver": "5.5.0", - "xml2js": "0.4.19" + "adm-zip": "^0.4.7", + "chalk": "^1.1.1", + "del": "^2.2.0", + "glob": "^7.0.3", + "ini": "^1.3.4", + "minimist": "^1.2.0", + "q": "^1.4.1", + "request": "^2.78.0", + "rimraf": "^2.5.2", + "semver": "^5.3.0", + "xml2js": "^0.4.17" } } } @@ -9152,46 +8713,34 @@ "integrity": "sha512-jQTChiCJteusULxjBp8+jftSQE5Obdl3k4cnmLA6WXtK6XFuWRnvVL7aCiBqaLPM8c4ph0S4tKna8XvmIwEnXQ==", "dev": true, "requires": { - "forwarded": "0.1.2", + "forwarded": "~0.1.2", "ipaddr.js": "1.6.0" } }, "proxy-agent": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-2.0.0.tgz", - "integrity": "sha1-V+tTR6qAXXTsaByyVknbo5yTNJk=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-3.0.0.tgz", + "integrity": "sha512-g6n6vnk8fRf705ShN+FEXFG/SDJaW++lSs0d9KaJh4uBWW/wi7en4Cpo5VYQW3SZzAE121lhB/KLQrbURoubZw==", "dev": true, "optional": true, "requires": { - "agent-base": "2.1.1", - "debug": "2.6.9", - "extend": "3.0.1", - "http-proxy-agent": "1.0.0", - "https-proxy-agent": "1.0.0", - "lru-cache": "2.6.5", - "pac-proxy-agent": "1.1.0", - "socks-proxy-agent": "2.1.1" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "optional": true, - "requires": { - "ms": "2.0.0" - } - }, - "lru-cache": { - "version": "2.6.5", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.6.5.tgz", - "integrity": "sha1-5W1jVBSO3o13B7WNFDIg/QjfD9U=", - "dev": true, - "optional": true - } + "agent-base": "^4.2.0", + "debug": "^3.1.0", + "http-proxy-agent": "^2.1.0", + "https-proxy-agent": "^2.2.1", + "lru-cache": "^4.1.2", + "pac-proxy-agent": "^2.0.1", + "proxy-from-env": "^1.0.0", + "socks-proxy-agent": "^3.0.0" } }, + "proxy-from-env": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.0.0.tgz", + "integrity": "sha1-M8UDmPcOp+uW0h97gXYwpVeRx+4=", + "dev": true, + "optional": true + }, "prr": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", @@ -9210,11 +8759,11 @@ "integrity": "sha512-4kJ5Esocg8X3h8YgJsKAuoesBgB7mqH3eowiDzMUPKiRDDE7E/BqqZD1hnTByIaAFiwAw246YEltSq7tdrOH0Q==", "dev": true, "requires": { - "bn.js": "4.11.8", - "browserify-rsa": "4.0.1", - "create-hash": "1.2.0", - "parse-asn1": "5.1.1", - "randombytes": "2.0.6" + "bn.js": "^4.1.0", + "browserify-rsa": "^4.0.0", + "create-hash": "^1.1.0", + "parse-asn1": "^5.0.0", + "randombytes": "^2.0.1" } }, "pump": { @@ -9223,8 +8772,8 @@ "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", "dev": true, "requires": { - "end-of-stream": "1.4.1", - "once": "1.4.0" + "end-of-stream": "^1.1.0", + "once": "^1.3.1" } }, "pumpify": { @@ -9233,9 +8782,9 @@ "integrity": "sha512-2kmNR9ry+Pf45opRVirpNuIFotsxUGLaYqxIwuR77AYrYRMuFCz9eryHBS52L360O+NcR383CL4QYlMKPq4zYA==", "dev": true, "requires": { - "duplexify": "3.5.4", - "inherits": "2.0.3", - "pump": "2.0.1" + "duplexify": "^3.5.3", + "inherits": "^2.0.3", + "pump": "^2.0.0" } }, "punycode": { @@ -9284,8 +8833,8 @@ "integrity": "sha512-D5JUjPyJbaJDkuAazpVnSfVkLlpeO3wDlPROTMLGKG1zMFNFRgrciKo1ltz/AzNTkqE0HzDx655QOL51N06how==", "dev": true, "requires": { - "is-number": "3.0.0", - "kind-of": "4.0.0" + "is-number": "^3.0.0", + "kind-of": "^4.0.0" }, "dependencies": { "is-number": { @@ -9294,7 +8843,7 @@ "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", "dev": true, "requires": { - "kind-of": "3.2.2" + "kind-of": "^3.0.2" }, "dependencies": { "kind-of": { @@ -9303,7 +8852,7 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "1.1.6" + "is-buffer": "^1.1.5" } } } @@ -9314,7 +8863,7 @@ "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", "dev": true, "requires": { - "is-buffer": "1.1.6" + "is-buffer": "^1.1.5" } } } @@ -9325,7 +8874,7 @@ "integrity": "sha512-CIQ5OFxf4Jou6uOKe9t1AOgqpeU5fd70A8NPdHSGeYXqXsPe6peOwI0cUl88RWZ6sP1vPMV3avd/R6cZ5/sP1A==", "dev": true, "requires": { - "safe-buffer": "5.1.1" + "safe-buffer": "^5.1.0" } }, "randomfill": { @@ -9334,8 +8883,8 @@ "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", "dev": true, "requires": { - "randombytes": "2.0.6", - "safe-buffer": "5.1.1" + "randombytes": "^2.0.5", + "safe-buffer": "^5.1.0" } }, "range-parser": { @@ -9379,7 +8928,7 @@ "depd": "1.1.1", "inherits": "2.0.3", "setprototypeof": "1.0.3", - "statuses": "1.4.0" + "statuses": ">= 1.3.1 < 2" } }, "setprototypeof": { @@ -9402,7 +8951,7 @@ "integrity": "sha1-5mTvMRYRZsl1HNvo28+GtftY93Q=", "dev": true, "requires": { - "pify": "2.3.0" + "pify": "^2.3.0" }, "dependencies": { "pify": { @@ -9413,24 +8962,15 @@ } } }, - "read-only-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/read-only-stream/-/read-only-stream-2.0.0.tgz", - "integrity": "sha1-JyT9aoET1zdkrCiNQ4YnDB2/F/A=", - "dev": true, - "requires": { - "readable-stream": "2.3.6" - } - }, "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.1.0", - "normalize-package-data": "2.4.0", - "path-type": "1.1.0" + "load-json-file": "^1.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^1.0.0" }, "dependencies": { "path-type": { @@ -9439,9 +8979,9 @@ "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", "dev": true, "requires": { - "graceful-fs": "4.1.11", - "pify": "2.3.0", - "pinkie-promise": "2.0.1" + "graceful-fs": "^4.1.2", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" } }, "pify": { @@ -9458,8 +8998,8 @@ "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", "dev": true, "requires": { - "find-up": "1.1.2", - "read-pkg": "1.1.0" + "find-up": "^1.0.0", + "read-pkg": "^1.0.0" }, "dependencies": { "find-up": { @@ -9468,8 +9008,8 @@ "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", "dev": true, "requires": { - "path-exists": "2.1.0", - "pinkie-promise": "2.0.1" + "path-exists": "^2.0.0", + "pinkie-promise": "^2.0.0" } }, "path-exists": { @@ -9478,7 +9018,7 @@ "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", "dev": true, "requires": { - "pinkie-promise": "2.0.1" + "pinkie-promise": "^2.0.0" } } } @@ -9488,13 +9028,13 @@ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "requires": { - "core-util-is": "1.0.2", - "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.2" + "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" } }, "readdirp": { @@ -9503,10 +9043,10 @@ "integrity": "sha1-TtCtBg3zBzMAxIRANz9y0cxkLXg=", "dev": true, "requires": { - "graceful-fs": "4.1.11", - "minimatch": "3.0.4", - "readable-stream": "2.3.6", - "set-immediate-shim": "1.0.1" + "graceful-fs": "^4.1.2", + "minimatch": "^3.0.2", + "readable-stream": "^2.0.2", + "set-immediate-shim": "^1.0.1" } }, "redent": { @@ -9515,8 +9055,8 @@ "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=", "dev": true, "requires": { - "indent-string": "2.1.0", - "strip-indent": "1.0.1" + "indent-string": "^2.1.0", + "strip-indent": "^1.0.1" } }, "redis": { @@ -9526,9 +9066,9 @@ "dev": true, "optional": true, "requires": { - "double-ended-queue": "2.1.0-0", - "redis-commands": "1.3.5", - "redis-parser": "2.6.0" + "double-ended-queue": "^2.1.0-0", + "redis-commands": "^1.2.0", + "redis-parser": "^2.6.0" } }, "redis-commands": { @@ -9568,7 +9108,7 @@ "integrity": "sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ==", "dev": true, "requires": { - "is-equal-shallow": "0.1.3" + "is-equal-shallow": "^0.1.3" } }, "regex-not": { @@ -9577,8 +9117,8 @@ "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", "dev": true, "requires": { - "extend-shallow": "3.0.2", - "safe-regex": "1.1.0" + "extend-shallow": "^3.0.2", + "safe-regex": "^1.1.0" } }, "regexpu-core": { @@ -9587,9 +9127,9 @@ "integrity": "sha1-hqdj9Y7k18L2sQLkdkBQ3n7ZDGs=", "dev": true, "requires": { - "regenerate": "1.3.3", - "regjsgen": "0.2.0", - "regjsparser": "0.1.5" + "regenerate": "^1.2.1", + "regjsgen": "^0.2.0", + "regjsparser": "^0.1.4" } }, "regjsgen": { @@ -9604,7 +9144,7 @@ "integrity": "sha1-fuj4Tcb6eS0/0K4ijSS9lJ6tIFw=", "dev": true, "requires": { - "jsesc": "0.5.0" + "jsesc": "~0.5.0" }, "dependencies": { "jsesc": { @@ -9633,11 +9173,11 @@ "integrity": "sha1-iYyr/Ivt5Le5ETWj/9Mj5YwNsxk=", "dev": true, "requires": { - "css-select": "1.2.0", - "dom-converter": "0.1.4", - "htmlparser2": "3.3.0", - "strip-ansi": "3.0.1", - "utila": "0.3.3" + "css-select": "^1.1.0", + "dom-converter": "~0.1", + "htmlparser2": "~3.3.0", + "strip-ansi": "^3.0.0", + "utila": "~0.3" }, "dependencies": { "utila": { @@ -9666,7 +9206,7 @@ "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", "dev": true, "requires": { - "is-finite": "1.0.2" + "is-finite": "^1.0.0" } }, "request": { @@ -9675,28 +9215,28 @@ "integrity": "sha1-xpKJRqDgbF+Nb4qTM0af/aRimKA=", "dev": true, "requires": { - "aws-sign2": "0.6.0", - "aws4": "1.7.0", - "caseless": "0.12.0", - "combined-stream": "1.0.6", - "extend": "3.0.1", - "forever-agent": "0.6.1", - "form-data": "2.1.4", - "har-validator": "4.2.1", - "hawk": "3.1.3", - "http-signature": "1.1.1", - "is-typedarray": "1.0.0", - "isstream": "0.1.2", - "json-stringify-safe": "5.0.1", - "mime-types": "2.1.18", - "oauth-sign": "0.8.2", - "performance-now": "0.2.0", - "qs": "6.4.0", - "safe-buffer": "5.1.1", - "stringstream": "0.0.5", - "tough-cookie": "2.3.4", - "tunnel-agent": "0.6.0", - "uuid": "3.2.1" + "aws-sign2": "~0.6.0", + "aws4": "^1.2.1", + "caseless": "~0.12.0", + "combined-stream": "~1.0.5", + "extend": "~3.0.0", + "forever-agent": "~0.6.1", + "form-data": "~2.1.1", + "har-validator": "~4.2.1", + "hawk": "~3.1.3", + "http-signature": "~1.1.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.7", + "oauth-sign": "~0.8.1", + "performance-now": "^0.2.0", + "qs": "~6.4.0", + "safe-buffer": "^5.0.1", + "stringstream": "~0.0.4", + "tough-cookie": "~2.3.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.0.0" }, "dependencies": { "form-data": { @@ -9705,9 +9245,9 @@ "integrity": "sha1-M8GDrPGTJ27KqYFDpp6Uv+4XUNE=", "dev": true, "requires": { - "asynckit": "0.4.0", - "combined-stream": "1.0.6", - "mime-types": "2.1.18" + "asynckit": "^0.4.0", + "combined-stream": "^1.0.5", + "mime-types": "^2.1.12" } }, "qs": { @@ -9724,10 +9264,10 @@ "integrity": "sha1-0epG1lSm7k+O5qT+oQGMIpEZBLQ=", "dev": true, "requires": { - "bluebird": "3.5.1", + "bluebird": "^3.5.0", "request-promise-core": "1.1.1", - "stealthy-require": "1.1.1", - "tough-cookie": "2.3.4" + "stealthy-require": "^1.1.0", + "tough-cookie": ">=2.3.3" } }, "request-promise-core": { @@ -9736,7 +9276,7 @@ "integrity": "sha1-Pu4AssWqgyOc+wTFcA2jb4HNCLY=", "dev": true, "requires": { - "lodash": "4.17.5" + "lodash": "^4.13.1" } }, "requestretry": { @@ -9746,10 +9286,10 @@ "dev": true, "optional": true, "requires": { - "extend": "3.0.1", - "lodash": "4.17.5", - "request": "2.81.0", - "when": "3.7.8" + "extend": "^3.0.0", + "lodash": "^4.15.0", + "request": "^2.74.0", + "when": "^3.7.7" } }, "require-directory": { @@ -9782,7 +9322,7 @@ "integrity": "sha512-c7rwLofp8g1U+h1KNyHL/jicrKg1Ek4q+Lr33AL65uZTinUZHe30D5HlyN5V9NW0JX1D5dXQ4jqW5l7Sy/kGfw==", "dev": true, "requires": { - "path-parse": "1.0.5" + "path-parse": "^1.0.5" } }, "resolve-cwd": { @@ -9791,7 +9331,7 @@ "integrity": "sha1-AKn3OHVW4nA46uIyyqNypqWbZlo=", "dev": true, "requires": { - "resolve-from": "3.0.0" + "resolve-from": "^3.0.0" } }, "resolve-from": { @@ -9818,7 +9358,7 @@ "integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=", "dev": true, "requires": { - "align-text": "0.1.4" + "align-text": "^0.1.1" } }, "rimraf": { @@ -9827,28 +9367,17 @@ "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", "dev": true, "requires": { - "glob": "7.1.2" + "glob": "^7.0.5" } }, "ripemd160": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.1.tgz", - "integrity": "sha1-D0WEKVxTo2KK9+bXmsohzlfRxuc=", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", + "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", "dev": true, "requires": { - "hash-base": "2.0.2", - "inherits": "2.0.3" - }, - "dependencies": { - "hash-base": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-2.0.2.tgz", - "integrity": "sha1-ZuodhW206KVHDK32/OI65SRO8uE=", - "dev": true, - "requires": { - "inherits": "2.0.3" - } - } + "hash-base": "^3.0.0", + "inherits": "^2.0.1" } }, "run-queue": { @@ -9857,21 +9386,37 @@ "integrity": "sha1-6Eg5bwV9Ij8kOGkkYY4laUFh7Ec=", "dev": true, "requires": { - "aproba": "1.2.0" + "aproba": "^1.1.1" } }, + "rx": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/rx/-/rx-4.1.0.tgz", + "integrity": "sha1-pfE/957zt0D+MKqAP7CfmIBdR4I=", + "dev": true + }, "rxjs": { "version": "5.5.2", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.5.2.tgz", "integrity": "sha512-oRYoIKWBU3Ic37fLA5VJu31VqQO4bWubRntcHSJ+cwaDQBwdnZ9x4zmhJfm/nFQ2E82/I4loSioHnACamrKGgA==", "requires": { - "symbol-observable": "1.2.0" + "symbol-observable": "^1.0.1" } }, + "rxjs-from-iterable": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/rxjs-from-iterable/-/rxjs-from-iterable-1.0.5.tgz", + "integrity": "sha1-zqsVcAVLO7Bf0m15iBSCx9sbc9c=" + }, + "rxjs-stream": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/rxjs-stream/-/rxjs-stream-1.3.0.tgz", + "integrity": "sha512-Vj86/1qAYvCApBOIi7gghVteCrMCXTnQxICPnW/D+4vtFIpgSE/xDegUDYJy9bwKyxR7VteVDhZyCvQt4RV/xQ==" + }, "safe-buffer": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", - "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "safe-regex": { "version": "1.1.0", @@ -9879,7 +9424,7 @@ "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", "dev": true, "requires": { - "ret": "0.1.15" + "ret": "~0.1.10" } }, "sass-graph": { @@ -9889,10 +9434,10 @@ "dev": true, "optional": true, "requires": { - "glob": "7.1.2", - "lodash": "4.17.5", - "scss-tokenizer": "0.2.3", - "yargs": "7.1.0" + "glob": "^7.0.0", + "lodash": "^4.0.0", + "scss-tokenizer": "^0.2.3", + "yargs": "^7.0.0" } }, "sass-loader": { @@ -9901,20 +9446,41 @@ "integrity": "sha512-JoiyD00Yo1o61OJsoP2s2kb19L1/Y2p3QFcCdWdF6oomBGKVYuZyqHWemRBfQ2uGYsk+CH3eCguXNfpjzlcpaA==", "dev": true, "requires": { - "clone-deep": "2.0.2", - "loader-utils": "1.1.0", - "lodash.tail": "4.1.1", - "neo-async": "2.5.1", - "pify": "3.0.0" + "clone-deep": "^2.0.1", + "loader-utils": "^1.0.1", + "lodash.tail": "^4.1.1", + "neo-async": "^2.5.0", + "pify": "^3.0.0" } }, "saucelabs": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/saucelabs/-/saucelabs-1.3.0.tgz", - "integrity": "sha1-0kDoAJ33+ocwbsRXimm6O1xCT+4=", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/saucelabs/-/saucelabs-1.5.0.tgz", + "integrity": "sha512-jlX3FGdWvYf4Q3LFfFWS1QvPg3IGCGWxIc8QBFdPTbpTJnt/v17FHXYVAn7C8sHf1yUXo2c7yIM0isDryfYtHQ==", "dev": true, "requires": { - "https-proxy-agent": "1.0.0" + "https-proxy-agent": "^2.2.1" + }, + "dependencies": { + "agent-base": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.2.0.tgz", + "integrity": "sha512-c+R/U5X+2zz2+UCrCFv6odQzJdoqI+YecuhnAJLa1zYaMc13zPfwMwZrr91Pd1DYNo/yPRbiM4WVf9whgwFsIg==", + "dev": true, + "requires": { + "es6-promisify": "^5.0.0" + } + }, + "https-proxy-agent": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.1.tgz", + "integrity": "sha512-HPCTS1LW51bcyMYbxUIOO4HEOlQ1/1qRaFWcyxvwaqUS9TY88aoEuHUY33kuAh1YhVVaDQhLZsnPd+XNARWZlQ==", + "dev": true, + "requires": { + "agent-base": "^4.1.0", + "debug": "^3.1.0" + } + } } }, "sax": { @@ -9928,8 +9494,8 @@ "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-0.4.5.tgz", "integrity": "sha512-yYrjb9TX2k/J1Y5UNy3KYdZq10xhYcF8nMpAW6o3hy6Q8WSIEf9lJHG/ePnOBfziPM3fvQwfOwa13U/Fh8qTfA==", "requires": { - "ajv": "6.4.0", - "ajv-keywords": "3.1.0" + "ajv": "^6.1.0", + "ajv-keywords": "^3.1.0" } }, "scss-tokenizer": { @@ -9939,8 +9505,8 @@ "dev": true, "optional": true, "requires": { - "js-base64": "2.4.3", - "source-map": "0.4.4" + "js-base64": "^2.1.8", + "source-map": "^0.4.2" }, "dependencies": { "source-map": { @@ -9950,7 +9516,7 @@ "dev": true, "optional": true, "requires": { - "amdefine": "1.0.1" + "amdefine": ">=0.0.4" } } } @@ -9962,15 +9528,15 @@ "dev": true }, "selenium-webdriver": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-3.0.1.tgz", - "integrity": "sha1-ot6l2kqX9mcuiefKcnbO+jZRR6c=", + "version": "4.0.0-alpha.1", + "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-4.0.0-alpha.1.tgz", + "integrity": "sha512-z88rdjHAv3jmTZ7KSGUkTvo4rGzcDGMq0oXWHNIDK96Gs31JKVdu9+FMtT4KBrVoibg8dUicJDok6GnqqttO5Q==", "dev": true, "requires": { - "adm-zip": "0.4.7", - "rimraf": "2.6.2", + "jszip": "^3.1.3", + "rimraf": "^2.5.4", "tmp": "0.0.30", - "xml2js": "0.4.19" + "xml2js": "^0.4.17" }, "dependencies": { "tmp": { @@ -9979,7 +9545,7 @@ "integrity": "sha1-ckGdSovn1s51FI/YsyTlk6cRwu0=", "dev": true, "requires": { - "os-tmpdir": "1.0.2" + "os-tmpdir": "~1.0.1" } } } @@ -10005,7 +9571,7 @@ "integrity": "sha1-02eN5VVeimH2Ke7QJTZq5fJzQKA=", "dev": true, "requires": { - "semver": "5.5.0" + "semver": "^5.3.0" } }, "semver-intersect": { @@ -10014,7 +9580,7 @@ "integrity": "sha1-j6hKnhAovSOeRTDRo+GB5pjYhLo=", "dev": true, "requires": { - "semver": "5.5.0" + "semver": "^5.0.0" } }, "send": { @@ -10024,18 +9590,18 @@ "dev": true, "requires": { "debug": "2.6.9", - "depd": "1.1.2", - "destroy": "1.0.4", - "encodeurl": "1.0.2", - "escape-html": "1.0.3", - "etag": "1.8.1", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", "fresh": "0.5.2", - "http-errors": "1.6.3", + "http-errors": "~1.6.2", "mime": "1.4.1", "ms": "2.0.0", - "on-finished": "2.3.0", - "range-parser": "1.2.0", - "statuses": "1.4.0" + "on-finished": "~2.3.0", + "range-parser": "~1.2.0", + "statuses": "~1.4.0" }, "dependencies": { "debug": { @@ -10056,9 +9622,9 @@ } }, "serialize-javascript": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-1.4.0.tgz", - "integrity": "sha1-fJWFFNtqwkQ6irwGLcn3iGp/YAU=", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-1.5.0.tgz", + "integrity": "sha512-Ga8c8NjAAp46Br4+0oZ2WxJCwIzwP60Gq1YPgU+39PiTVxyed/iKE/zyZI6+UlVYH5Q4PaQdHhcegIFPZTUfoQ==", "dev": true }, "serve-index": { @@ -10067,13 +9633,13 @@ "integrity": "sha1-03aNabHn2C5c4FD/9bRTvqEqkjk=", "dev": true, "requires": { - "accepts": "1.3.5", + "accepts": "~1.3.4", "batch": "0.6.1", "debug": "2.6.9", - "escape-html": "1.0.3", - "http-errors": "1.6.3", - "mime-types": "2.1.18", - "parseurl": "1.3.2" + "escape-html": "~1.0.3", + "http-errors": "~1.6.2", + "mime-types": "~2.1.17", + "parseurl": "~1.3.2" }, "dependencies": { "debug": { @@ -10093,9 +9659,9 @@ "integrity": "sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw==", "dev": true, "requires": { - "encodeurl": "1.0.2", - "escape-html": "1.0.3", - "parseurl": "1.3.2", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.2", "send": "0.16.2" } }, @@ -10117,10 +9683,10 @@ "integrity": "sha512-hw0yxk9GT/Hr5yJEYnHNKYXkIA8mVJgd9ditYZCe16ZczcaELYYcfvaXesNACk2O8O0nTiPQcQhGUQj8JLzeeg==", "dev": true, "requires": { - "extend-shallow": "2.0.1", - "is-extendable": "0.1.1", - "is-plain-object": "2.0.4", - "split-string": "3.1.0" + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" }, "dependencies": { "extend-shallow": { @@ -10129,7 +9695,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "0.1.1" + "is-extendable": "^0.1.0" } } } @@ -10152,8 +9718,8 @@ "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", "dev": true, "requires": { - "inherits": "2.0.3", - "safe-buffer": "5.1.1" + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" } }, "shallow-clone": { @@ -10162,9 +9728,9 @@ "integrity": "sha512-oeXreoKR/SyNJtRJMAKPDSvd28OqEwG4eR/xc856cRGBII7gX9lvAqDxusPm0846z/w/hWYjI1NpKwJ00NHzRA==", "dev": true, "requires": { - "is-extendable": "0.1.1", - "kind-of": "5.1.0", - "mixin-object": "2.0.1" + "is-extendable": "^0.1.1", + "kind-of": "^5.0.0", + "mixin-object": "^2.0.1" }, "dependencies": { "kind-of": { @@ -10175,34 +9741,13 @@ } } }, - "shasum": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/shasum/-/shasum-1.0.2.tgz", - "integrity": "sha1-5wEjENj0F/TetXEhUOVni4euVl8=", - "dev": true, - "requires": { - "json-stable-stringify": "0.0.1", - "sha.js": "2.4.11" - }, - "dependencies": { - "json-stable-stringify": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-0.0.1.tgz", - "integrity": "sha1-YRwj6BTbN1Un34URk9tZ3Sryf0U=", - "dev": true, - "requires": { - "jsonify": "0.0.0" - } - } - } - }, "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": "^1.0.0" } }, "shebang-regex": { @@ -10211,23 +9756,10 @@ "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", "dev": true }, - "shell-quote": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.6.1.tgz", - "integrity": "sha1-9HgZSczkAmlxJ0MOo7PFR29IF2c=", - "dev": true, - "requires": { - "array-filter": "0.0.1", - "array-map": "0.0.0", - "array-reduce": "0.0.0", - "jsonify": "0.0.0" - } - }, "signal-exit": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", - "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", - "dev": true + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" }, "silent-error": { "version": "1.1.0", @@ -10235,7 +9767,7 @@ "integrity": "sha1-IglwbxyFCp8dENDYQJGLRvJuG8k=", "dev": true, "requires": { - "debug": "2.6.9" + "debug": "^2.2.0" }, "dependencies": { "debug": { @@ -10256,7 +9788,7 @@ "dev": true, "optional": true, "requires": { - "requestretry": "1.13.0" + "requestretry": "^1.2.2" } }, "slash": { @@ -10287,14 +9819,14 @@ "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", "dev": true, "requires": { - "base": "0.11.2", - "debug": "2.6.9", - "define-property": "0.2.5", - "extend-shallow": "2.0.1", - "map-cache": "0.2.2", - "source-map": "0.5.7", - "source-map-resolve": "0.5.1", - "use": "3.1.0" + "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": { @@ -10312,7 +9844,7 @@ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "dev": true, "requires": { - "is-descriptor": "0.1.6" + "is-descriptor": "^0.1.0" } }, "extend-shallow": { @@ -10321,7 +9853,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "0.1.1" + "is-extendable": "^0.1.0" } } } @@ -10332,9 +9864,9 @@ "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", "dev": true, "requires": { - "define-property": "1.0.0", - "isobject": "3.0.1", - "snapdragon-util": "3.0.1" + "define-property": "^1.0.0", + "isobject": "^3.0.0", + "snapdragon-util": "^3.0.1" }, "dependencies": { "define-property": { @@ -10343,7 +9875,7 @@ "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", "dev": true, "requires": { - "is-descriptor": "1.0.2" + "is-descriptor": "^1.0.0" } }, "is-accessor-descriptor": { @@ -10352,7 +9884,7 @@ "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", "dev": true, "requires": { - "kind-of": "6.0.2" + "kind-of": "^6.0.0" } }, "is-data-descriptor": { @@ -10361,7 +9893,7 @@ "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", "dev": true, "requires": { - "kind-of": "6.0.2" + "kind-of": "^6.0.0" } }, "is-descriptor": { @@ -10370,9 +9902,9 @@ "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", "dev": true, "requires": { - "is-accessor-descriptor": "1.0.0", - "is-data-descriptor": "1.0.0", - "kind-of": "6.0.2" + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" } }, "isobject": { @@ -10395,7 +9927,7 @@ "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", "dev": true, "requires": { - "kind-of": "3.2.2" + "kind-of": "^3.2.0" } }, "sntp": { @@ -10404,7 +9936,7 @@ "integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg=", "dev": true, "requires": { - "hoek": "2.16.3" + "hoek": "2.x.x" } }, "socket.io": { @@ -10413,11 +9945,11 @@ "integrity": "sha1-waRZDO/4fs8TxyZS8Eb3FrKeYBQ=", "dev": true, "requires": { - "debug": "2.6.9", - "engine.io": "3.1.5", - "socket.io-adapter": "1.1.1", + "debug": "~2.6.6", + "engine.io": "~3.1.0", + "socket.io-adapter": "~1.1.0", "socket.io-client": "2.0.4", - "socket.io-parser": "3.1.3" + "socket.io-parser": "~3.1.1" }, "dependencies": { "debug": { @@ -10447,14 +9979,14 @@ "base64-arraybuffer": "0.1.5", "component-bind": "1.0.0", "component-emitter": "1.2.1", - "debug": "2.6.9", - "engine.io-client": "3.1.6", + "debug": "~2.6.4", + "engine.io-client": "~3.1.0", "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.1.3", + "socket.io-parser": "~3.1.1", "to-array": "0.1.4" }, "dependencies": { @@ -10476,8 +10008,8 @@ "dev": true, "requires": { "component-emitter": "1.2.1", - "debug": "3.1.0", - "has-binary2": "1.0.2", + "debug": "~3.1.0", + "has-binary2": "~1.0.2", "isarray": "2.0.1" }, "dependencies": { @@ -10495,8 +10027,8 @@ "integrity": "sha512-V48klKZl8T6MzatbLlzzRNhMepEys9Y4oGFpypBFFn1gLI/QQ9HtLLyWJNbPlwGLelOVOEijUbTTJeLLI59jLw==", "dev": true, "requires": { - "faye-websocket": "0.10.0", - "uuid": "3.2.1" + "faye-websocket": "^0.10.0", + "uuid": "^3.0.1" } }, "sockjs-client": { @@ -10505,12 +10037,12 @@ "integrity": "sha1-W6vjhrd15M8U51IJEUUmVAFsixI=", "dev": true, "requires": { - "debug": "2.6.9", + "debug": "^2.6.6", "eventsource": "0.1.6", - "faye-websocket": "0.11.1", - "inherits": "2.0.3", - "json3": "3.3.2", - "url-parse": "1.3.0" + "faye-websocket": "~0.11.0", + "inherits": "^2.0.1", + "json3": "^3.3.2", + "url-parse": "^1.1.8" }, "dependencies": { "debug": { @@ -10528,7 +10060,7 @@ "integrity": "sha1-8O/hjE9W5PQK/H4Gxxn9XuYYjzg=", "dev": true, "requires": { - "websocket-driver": "0.7.0" + "websocket-driver": ">=0.5.1" } } } @@ -10539,19 +10071,18 @@ "integrity": "sha1-W4t/x8jzQcU+0FbpKbe/Tei6e1o=", "dev": true, "requires": { - "ip": "1.1.5", - "smart-buffer": "1.1.15" + "ip": "^1.1.4", + "smart-buffer": "^1.0.13" } }, "socks-proxy-agent": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-2.1.1.tgz", - "integrity": "sha512-sFtmYqdUK5dAMh85H0LEVFUCO7OhJJe1/z2x/Z6mxp3s7/QPf1RkZmpZy+BpuU0bEjcV9npqKjq9Y3kwFUjnxw==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-3.0.1.tgz", + "integrity": "sha512-ZwEDymm204mTzvdqyUqOdovVr2YRd2NYskrYrF2LXyZ9qDiMAoFESGK8CRphiO7rtbo2Y757k2Nia3x2hGtalA==", "dev": true, "requires": { - "agent-base": "2.1.1", - "extend": "3.0.1", - "socks": "1.1.10" + "agent-base": "^4.1.0", + "socks": "^1.1.10" } }, "source-list-map": { @@ -10572,11 +10103,11 @@ "integrity": "sha512-0KW2wvzfxm8NCTb30z0LMNyPqWCdDGE2viwzUaucqJdkTRXtZiSY3I+2A6nVAjmdOy0I4gU8DwnVVGsk9jvP2A==", "dev": true, "requires": { - "atob": "2.1.0", - "decode-uri-component": "0.2.0", - "resolve-url": "0.2.1", - "source-map-url": "0.4.0", - "urix": "0.1.0" + "atob": "^2.0.0", + "decode-uri-component": "^0.2.0", + "resolve-url": "^0.2.1", + "source-map-url": "^0.4.0", + "urix": "^0.1.0" } }, "source-map-support": { @@ -10585,7 +10116,7 @@ "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", "dev": true, "requires": { - "source-map": "0.5.7" + "source-map": "^0.5.6" } }, "source-map-url": { @@ -10600,8 +10131,8 @@ "integrity": "sha512-N19o9z5cEyc8yQQPukRCZ9EUmb4HUpnrmaL/fxS2pBo2jbfcFRVuFZ/oFC+vZz0MNNk0h80iMn5/S6qGZOL5+g==", "dev": true, "requires": { - "spdx-expression-parse": "3.0.0", - "spdx-license-ids": "3.0.0" + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" } }, "spdx-exceptions": { @@ -10616,8 +10147,8 @@ "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", "dev": true, "requires": { - "spdx-exceptions": "2.1.0", - "spdx-license-ids": "3.0.0" + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" } }, "spdx-license-ids": { @@ -10632,12 +10163,12 @@ "integrity": "sha1-Qv9B7OXMD5mjpsKKq7c/XDsDrLw=", "dev": true, "requires": { - "debug": "2.6.9", - "handle-thing": "1.2.5", - "http-deceiver": "1.2.7", - "safe-buffer": "5.1.1", - "select-hose": "2.0.0", - "spdy-transport": "2.1.0" + "debug": "^2.6.8", + "handle-thing": "^1.2.5", + "http-deceiver": "^1.2.7", + "safe-buffer": "^5.0.1", + "select-hose": "^2.0.0", + "spdy-transport": "^2.0.18" }, "dependencies": { "debug": { @@ -10657,13 +10188,13 @@ "integrity": "sha512-bpUeGpZcmZ692rrTiqf9/2EUakI6/kXX1Rpe0ib/DyOzbiexVfXkw6GnvI9hVGvIwVaUhkaBojjCZwLNRGQg1g==", "dev": true, "requires": { - "debug": "2.6.9", - "detect-node": "2.0.3", - "hpack.js": "2.1.6", - "obuf": "1.1.2", - "readable-stream": "2.3.6", - "safe-buffer": "5.1.1", - "wbuf": "1.7.3" + "debug": "^2.6.8", + "detect-node": "^2.0.3", + "hpack.js": "^2.1.6", + "obuf": "^1.1.1", + "readable-stream": "^2.2.9", + "safe-buffer": "^5.0.1", + "wbuf": "^1.7.2" }, "dependencies": { "debug": { @@ -10683,7 +10214,7 @@ "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", "dev": true, "requires": { - "extend-shallow": "3.0.2" + "extend-shallow": "^3.0.0" } }, "sprintf-js": { @@ -10698,14 +10229,14 @@ "integrity": "sha1-Ew9Zde3a2WPx1W+SuaxsUfqfg+s=", "dev": true, "requires": { - "asn1": "0.2.3", - "assert-plus": "1.0.0", - "bcrypt-pbkdf": "1.0.1", - "dashdash": "1.14.1", - "ecc-jsbn": "0.1.1", - "getpass": "0.1.7", - "jsbn": "0.1.1", - "tweetnacl": "0.14.5" + "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", + "tweetnacl": "~0.14.0" }, "dependencies": { "assert-plus": { @@ -10722,7 +10253,7 @@ "integrity": "sha512-XRSIPqLij52MtgoQavH/x/dU1qVKtWUAAZeOHsR9c2Ddi4XerFy3mc1alf+dLJKl9EUIm/Ht+EowFkTUOA6GAQ==", "dev": true, "requires": { - "safe-buffer": "5.1.1" + "safe-buffer": "^5.1.1" } }, "static-extend": { @@ -10731,8 +10262,8 @@ "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", "dev": true, "requires": { - "define-property": "0.2.5", - "object-copy": "0.1.0" + "define-property": "^0.2.5", + "object-copy": "^0.1.0" }, "dependencies": { "define-property": { @@ -10741,7 +10272,7 @@ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "dev": true, "requires": { - "is-descriptor": "0.1.6" + "is-descriptor": "^0.1.0" } } } @@ -10759,7 +10290,7 @@ "dev": true, "optional": true, "requires": { - "readable-stream": "2.3.6" + "readable-stream": "^2.0.1" } }, "stealthy-require": { @@ -10774,18 +10305,8 @@ "integrity": "sha1-ZiZu5fm9uZQKTkUUyvtDu3Hlyds=", "dev": true, "requires": { - "inherits": "2.0.3", - "readable-stream": "2.3.6" - } - }, - "stream-combiner2": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/stream-combiner2/-/stream-combiner2-1.1.1.tgz", - "integrity": "sha1-+02KFCDqNidk4hrUeAOXvry0HL4=", - "dev": true, - "requires": { - "duplexer2": "0.1.4", - "readable-stream": "2.3.6" + "inherits": "~2.0.1", + "readable-stream": "^2.0.2" } }, "stream-each": { @@ -10794,8 +10315,8 @@ "integrity": "sha512-mc1dbFhGBxvTM3bIWmAAINbqiuAk9TATcfIQC8P+/+HJefgaiTlMn2dHvkX8qlI12KeYKSQ1Ua9RrIqrn1VPoA==", "dev": true, "requires": { - "end-of-stream": "1.4.1", - "stream-shift": "1.0.0" + "end-of-stream": "^1.1.0", + "stream-shift": "^1.0.0" } }, "stream-http": { @@ -10804,11 +10325,11 @@ "integrity": "sha512-cQ0jo17BLca2r0GfRdZKYAGLU6JRoIWxqSOakUMuKOT6MOK7AAlE856L33QuDmAy/eeOrhLee3dZKX0Uadu93A==", "dev": true, "requires": { - "builtin-status-codes": "3.0.0", - "inherits": "2.0.3", - "readable-stream": "2.3.6", - "to-arraybuffer": "1.0.1", - "xtend": "4.0.1" + "builtin-status-codes": "^3.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.3.3", + "to-arraybuffer": "^1.0.0", + "xtend": "^4.0.0" } }, "stream-shift": { @@ -10817,26 +10338,16 @@ "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=", "dev": true }, - "stream-splicer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/stream-splicer/-/stream-splicer-2.0.0.tgz", - "integrity": "sha1-G2O+Q4oTPktnHMGTUZdgAXWRDYM=", - "dev": true, - "requires": { - "inherits": "2.0.3", - "readable-stream": "2.3.6" - } - }, "streamroller": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-0.7.0.tgz", "integrity": "sha512-WREzfy0r0zUqp3lGO096wRuUp7ho1X6uo/7DJfTlEi0Iv/4gT7YHqXDjKC2ioVGBZtE8QzsQD9nx1nIuoZ57jQ==", "dev": true, "requires": { - "date-format": "1.2.0", - "debug": "3.1.0", - "mkdirp": "0.5.1", - "readable-stream": "2.3.6" + "date-format": "^1.2.0", + "debug": "^3.1.0", + "mkdirp": "^0.5.1", + "readable-stream": "^2.3.0" } }, "string-width": { @@ -10845,9 +10356,9 @@ "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "dev": true, "requires": { - "code-point-at": "1.1.0", - "is-fullwidth-code-point": "1.0.0", - "strip-ansi": "3.0.1" + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" } }, "string_decoder": { @@ -10855,7 +10366,7 @@ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "requires": { - "safe-buffer": "5.1.1" + "safe-buffer": "~5.1.0" } }, "stringstream": { @@ -10870,7 +10381,7 @@ "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, "requires": { - "ansi-regex": "2.1.1" + "ansi-regex": "^2.0.0" } }, "strip-bom": { @@ -10879,7 +10390,7 @@ "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", "dev": true, "requires": { - "is-utf8": "0.2.1" + "is-utf8": "^0.2.0" } }, "strip-eof": { @@ -10894,7 +10405,7 @@ "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=", "dev": true, "requires": { - "get-stdin": "4.0.1" + "get-stdin": "^4.0.1" } }, "strip-json-comments": { @@ -10909,8 +10420,8 @@ "integrity": "sha512-IRE+ijgojrygQi3rsqT0U4dd+UcPCqcVvauZpCnQrGAlEe+FUIyrK93bUDScamesjP08JlQNsFJU+KmPedP5Og==", "dev": true, "requires": { - "loader-utils": "1.1.0", - "schema-utils": "0.3.0" + "loader-utils": "^1.0.2", + "schema-utils": "^0.3.0" }, "dependencies": { "ajv": { @@ -10919,10 +10430,10 @@ "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", "dev": true, "requires": { - "co": "4.6.0", - "fast-deep-equal": "1.1.0", - "fast-json-stable-stringify": "2.0.0", - "json-schema-traverse": "0.3.1" + "co": "^4.6.0", + "fast-deep-equal": "^1.0.0", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.3.0" } }, "schema-utils": { @@ -10931,7 +10442,7 @@ "integrity": "sha1-9YdyIs4+kx7a4DnxfrNxbnE3+M8=", "dev": true, "requires": { - "ajv": "5.5.2" + "ajv": "^5.0.0" } } } @@ -10942,12 +10453,12 @@ "integrity": "sha1-QrlWCTHKcJDOhRWnmLqeaqPW3Hk=", "dev": true, "requires": { - "css-parse": "1.7.0", - "debug": "3.1.0", - "glob": "7.0.6", - "mkdirp": "0.5.1", - "sax": "0.5.8", - "source-map": "0.1.43" + "css-parse": "1.7.x", + "debug": "*", + "glob": "7.0.x", + "mkdirp": "0.5.x", + "sax": "0.5.x", + "source-map": "0.1.x" }, "dependencies": { "glob": { @@ -10956,12 +10467,12 @@ "integrity": "sha1-IRuvr0nlJbjNkyYNFKsTYVKz9Xo=", "dev": true, "requires": { - "fs.realpath": "1.0.0", - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.2", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" } }, "source-map": { @@ -10970,7 +10481,7 @@ "integrity": "sha1-wkvBRspRfBRx9drL4lcbK3+eM0Y=", "dev": true, "requires": { - "amdefine": "1.0.1" + "amdefine": ">=0.0.4" } } } @@ -10981,9 +10492,9 @@ "integrity": "sha512-+VomPdZ6a0razP+zinir61yZgpw2NfljeSsdUF5kJuEzlo3khXhY19Fn6l8QQz1GRJGtMCo8nG5C04ePyV7SUA==", "dev": true, "requires": { - "loader-utils": "1.1.0", - "lodash.clonedeep": "4.5.0", - "when": "3.6.4" + "loader-utils": "^1.0.2", + "lodash.clonedeep": "^4.5.0", + "when": "~3.6.x" }, "dependencies": { "when": { @@ -10994,38 +10505,21 @@ } } }, - "subarg": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/subarg/-/subarg-1.0.0.tgz", - "integrity": "sha1-9izxdYHplrSPyWVpn1TAauJouNI=", - "dev": true, - "requires": { - "minimist": "1.2.0" - }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true - } - } - }, "superagent": { "version": "3.8.2", "resolved": "https://registry.npmjs.org/superagent/-/superagent-3.8.2.tgz", "integrity": "sha512-gVH4QfYHcY3P0f/BZzavLreHW3T1v7hG9B+hpMQotGQqurOvhv87GcMCd6LWySmBuf+BDR44TQd0aISjVHLeNQ==", "requires": { - "component-emitter": "1.2.1", - "cookiejar": "2.1.1", - "debug": "3.1.0", - "extend": "3.0.1", - "form-data": "2.3.2", - "formidable": "1.2.1", - "methods": "1.1.2", - "mime": "1.6.0", - "qs": "6.5.1", - "readable-stream": "2.3.6" + "component-emitter": "^1.2.0", + "cookiejar": "^2.1.0", + "debug": "^3.1.0", + "extend": "^3.0.0", + "form-data": "^2.3.1", + "formidable": "^1.1.1", + "methods": "^1.1.1", + "mime": "^1.4.1", + "qs": "^6.5.1", + "readable-stream": "^2.0.5" } }, "supports-color": { @@ -11034,7 +10528,7 @@ "integrity": "sha1-vnoN5ITexcXN34s9WRJQRJEvY1s=", "dev": true, "requires": { - "has-flag": "2.0.0" + "has-flag": "^2.0.0" } }, "symbol-observable": { @@ -11042,21 +10536,12 @@ "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz", "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==" }, - "syntax-error": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/syntax-error/-/syntax-error-1.4.0.tgz", - "integrity": "sha512-YPPlu67mdnHGTup2A8ff7BC2Pjq0e0Yp/IyTFN03zWO0RcK07uLcbi7C2KpGR2FvWbaB0+bfE27a+sBKebSo7w==", - "dev": true, - "requires": { - "acorn-node": "1.3.0" - } - }, "systemjs": { "version": "0.19.27", "resolved": "https://registry.npmjs.org/systemjs/-/systemjs-0.19.27.tgz", "integrity": "sha1-8XQNVlzmQ3GsDecHKk0eVHG6e6I=", "requires": { - "when": "3.7.8" + "when": "^3.7.5" } }, "tapable": { @@ -11072,9 +10557,9 @@ "dev": true, "optional": true, "requires": { - "block-stream": "0.0.9", - "fstream": "1.0.11", - "inherits": "2.0.3" + "block-stream": "*", + "fstream": "^1.0.2", + "inherits": "2" } }, "through": { @@ -11089,8 +10574,8 @@ "integrity": "sha1-AARWmzfHx0ujnEPzzteNGtlBQL4=", "dev": true, "requires": { - "readable-stream": "2.3.6", - "xtend": "4.0.1" + "readable-stream": "^2.1.5", + "xtend": "~4.0.1" } }, "thunkify": { @@ -11113,12 +10598,12 @@ "dev": true }, "timers-browserify": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.6.tgz", - "integrity": "sha512-HQ3nbYRAowdVd0ckGFvmJPPCOH/CHleFN/Y0YQCX1DVaB7t+KFvisuyN09fuP8Jtp1CpfSh8O8bMkHbdbPe6Pw==", + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.10.tgz", + "integrity": "sha512-YvC1SV1XdOUaL6gx5CoGroT3Gu49pK9+TZ38ErPldOWW4j49GI1HKs9DV+KGq/w6y+LZ72W1c8cKz2vzY+qpzg==", "dev": true, "requires": { - "setimmediate": "1.0.5" + "setimmediate": "^1.0.4" } }, "timespan": { @@ -11134,7 +10619,7 @@ "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", "dev": true, "requires": { - "os-tmpdir": "1.0.2" + "os-tmpdir": "~1.0.2" } }, "to-array": { @@ -11161,7 +10646,7 @@ "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", "dev": true, "requires": { - "kind-of": "3.2.2" + "kind-of": "^3.0.2" } }, "to-regex": { @@ -11170,10 +10655,10 @@ "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", "dev": true, "requires": { - "define-property": "2.0.2", - "extend-shallow": "3.0.2", - "regex-not": "1.0.2", - "safe-regex": "1.1.0" + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "regex-not": "^1.0.2", + "safe-regex": "^1.1.0" } }, "to-regex-range": { @@ -11182,8 +10667,8 @@ "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", "dev": true, "requires": { - "is-number": "3.0.0", - "repeat-string": "1.6.1" + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" }, "dependencies": { "is-number": { @@ -11192,7 +10677,7 @@ "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", "dev": true, "requires": { - "kind-of": "3.2.2" + "kind-of": "^3.0.2" } } } @@ -11203,7 +10688,7 @@ "integrity": "sha1-zVYVdSU5BXwNwEkaYhw7xvvh0YI=", "dev": true, "requires": { - "hoek": "4.2.1" + "hoek": "4.x.x" }, "dependencies": { "hoek": { @@ -11215,9 +10700,9 @@ } }, "toposort": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/toposort/-/toposort-1.0.6.tgz", - "integrity": "sha1-wxdI5V0hDv/AD9zcfW5o19e7nOw=", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/toposort/-/toposort-1.0.7.tgz", + "integrity": "sha1-LmhELZ9k7HILjMieZEOsbKqVACk=", "dev": true }, "tough-cookie": { @@ -11226,7 +10711,7 @@ "integrity": "sha512-TZ6TTfI5NtZnuyy/Kecv+CnoROnyXn2DN97LontgQpCwsX2XyLYCC0ENhYkehSOwAp8rTQKc/NUIF7BkQ5rKLA==", "dev": true, "requires": { - "punycode": "1.4.1" + "punycode": "^1.4.1" }, "dependencies": { "punycode": { @@ -11262,7 +10747,7 @@ "dev": true, "optional": true, "requires": { - "glob": "6.0.4" + "glob": "^6.0.4" }, "dependencies": { "glob": { @@ -11272,11 +10757,11 @@ "dev": true, "optional": true, "requires": { - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "2 || 3", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" } } } @@ -11287,27 +10772,27 @@ "integrity": "sha512-xcZH12oVg9PShKhy3UHyDmuDLV3y7iKwX25aMVPt1SIXSuAfWkFiGPEkg+th8R4YKW/QCxDoW7lJdb15lx6QWg==", "dev": true, "requires": { - "arrify": "1.0.1", - "chalk": "2.3.2", - "diff": "3.5.0", - "make-error": "1.3.4", - "minimist": "1.2.0", - "mkdirp": "0.5.1", - "source-map-support": "0.5.4", - "tsconfig": "7.0.0", - "v8flags": "3.0.2", - "yn": "2.0.0" + "arrify": "^1.0.0", + "chalk": "^2.3.0", + "diff": "^3.1.0", + "make-error": "^1.1.1", + "minimist": "^1.2.0", + "mkdirp": "^0.5.1", + "source-map-support": "^0.5.0", + "tsconfig": "^7.0.0", + "v8flags": "^3.0.0", + "yn": "^2.0.0" }, "dependencies": { "chalk": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.2.tgz", - "integrity": "sha512-ZM4j2/ld/YZDc3Ma8PgN7gyAk+kHMMMyzLNryCPGhWrsfAuDVeuid5bpRFTDgMH9JBK2lA4dyyAkkZYF/WcqDQ==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", "dev": true, "requires": { - "ansi-styles": "3.2.1", - "escape-string-regexp": "1.0.5", - "supports-color": "5.3.0" + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" } }, "has-flag": { @@ -11329,21 +10814,22 @@ "dev": true }, "source-map-support": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.4.tgz", - "integrity": "sha512-PETSPG6BjY1AHs2t64vS2aqAgu6dMIMXJULWFBGbh2Gr8nVLbCFDo6i/RMMvviIQ2h1Z8+5gQhVKSn2je9nmdg==", + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.5.tgz", + "integrity": "sha512-mR7/Nd5l1z6g99010shcXJiNEaf3fEtmLhRB/sBcQVJGodcHCULPp2y4Sfa43Kv2zq7T+Izmfp/WHCR6dYkQCA==", "dev": true, "requires": { - "source-map": "0.6.1" + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" } }, "supports-color": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.3.0.tgz", - "integrity": "sha512-0aP01LLIskjKs3lq52EC0aGBAJhLq7B2Rd8HC/DR/PtNNpcLilNmHC12O+hu0usQpo7wtHNRqtrhBwtDb0+dNg==", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", + "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", "dev": true, "requires": { - "has-flag": "3.0.0" + "has-flag": "^3.0.0" } } } @@ -11354,10 +10840,10 @@ "integrity": "sha512-vZXmzPrL+EmC4T/4rVlT2jNVMWCi/O4DIiSj3UHg1OE5kCKbk4mfrXc6dZksLgRM/TZlKnousKH9bbTazUWRRw==", "dev": true, "requires": { - "@types/strip-bom": "3.0.0", + "@types/strip-bom": "^3.0.0", "@types/strip-json-comments": "0.0.30", - "strip-bom": "3.0.0", - "strip-json-comments": "2.0.1" + "strip-bom": "^3.0.0", + "strip-json-comments": "^2.0.0" }, "dependencies": { "strip-bom": { @@ -11374,10 +10860,10 @@ "integrity": "sha1-tZXbFrI2chgk7u2ouyYjZbR+8zQ=", "dev": true, "requires": { - "minimist": "1.2.0", - "mkdirp": "0.5.1", - "source-map": "0.5.7", - "source-map-support": "0.4.18" + "minimist": "^1.2.0", + "mkdirp": "^0.5.1", + "source-map": "^0.5.6", + "source-map-support": "^0.4.2" }, "dependencies": { "minimist": { @@ -11399,29 +10885,29 @@ "integrity": "sha1-ElX4ej/1frCw4fDmEKi0dIBGya4=", "dev": true, "requires": { - "babel-code-frame": "6.26.0", - "builtin-modules": "1.1.1", - "chalk": "2.3.2", - "commander": "2.15.1", - "diff": "3.5.0", - "glob": "7.1.2", - "js-yaml": "3.11.0", - "minimatch": "3.0.4", - "resolve": "1.7.1", - "semver": "5.5.0", - "tslib": "1.9.0", - "tsutils": "2.26.1" + "babel-code-frame": "^6.22.0", + "builtin-modules": "^1.1.1", + "chalk": "^2.3.0", + "commander": "^2.12.1", + "diff": "^3.2.0", + "glob": "^7.1.1", + "js-yaml": "^3.7.0", + "minimatch": "^3.0.4", + "resolve": "^1.3.2", + "semver": "^5.3.0", + "tslib": "^1.8.0", + "tsutils": "^2.12.1" }, "dependencies": { "chalk": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.2.tgz", - "integrity": "sha512-ZM4j2/ld/YZDc3Ma8PgN7gyAk+kHMMMyzLNryCPGhWrsfAuDVeuid5bpRFTDgMH9JBK2lA4dyyAkkZYF/WcqDQ==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", "dev": true, "requires": { - "ansi-styles": "3.2.1", - "escape-string-regexp": "1.0.5", - "supports-color": "5.3.0" + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" } }, "has-flag": { @@ -11431,12 +10917,12 @@ "dev": true }, "supports-color": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.3.0.tgz", - "integrity": "sha512-0aP01LLIskjKs3lq52EC0aGBAJhLq7B2Rd8HC/DR/PtNNpcLilNmHC12O+hu0usQpo7wtHNRqtrhBwtDb0+dNg==", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", + "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", "dev": true, "requires": { - "has-flag": "3.0.0" + "has-flag": "^3.0.0" } } } @@ -11449,12 +10935,12 @@ "optional": true }, "tsutils": { - "version": "2.26.1", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.26.1.tgz", - "integrity": "sha512-bnm9bcjOqOr1UljleL94wVCDlpa6KjfGaTkefeLch4GRafgDkROxPizbB/FxTEdI++5JqhxczRy/Qub0syNqZA==", + "version": "2.26.2", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.26.2.tgz", + "integrity": "sha512-uzwnhmrSbyinPCiwfzGsOY3IulBTwoky7r83HmZdz9QNCjhSCzavkh47KLWuU0zF2F2WbpmmzoJUIEiYyd+jEQ==", "dev": true, "requires": { - "tslib": "1.9.0" + "tslib": "^1.8.1" } }, "tty-browserify": { @@ -11469,7 +10955,7 @@ "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", "dev": true, "requires": { - "safe-buffer": "5.1.1" + "safe-buffer": "^5.0.1" } }, "tweetnacl": { @@ -11485,7 +10971,7 @@ "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", "dev": true, "requires": { - "prelude-ls": "1.1.2" + "prelude-ls": "~1.1.2" } }, "type-is": { @@ -11495,7 +10981,7 @@ "dev": true, "requires": { "media-typer": "0.3.0", - "mime-types": "2.1.18" + "mime-types": "~2.1.18" } }, "typedarray": { @@ -11505,19 +10991,19 @@ "dev": true }, "typescript": { - "version": "2.5.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.5.3.tgz", - "integrity": "sha512-ptLSQs2S4QuS6/OD1eAKG+S5G8QQtrU5RT32JULdZQtM1L3WTi34Wsu48Yndzi8xsObRAB9RPt/KhA9wlpEF6w==", + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.7.2.tgz", + "integrity": "sha512-p5TCYZDAO0m4G344hD+wx/LATebLWZNkkh2asWUFqSsD2OrDNhbAHuSjobrmsUmdzjJjEeZVU9g1h3O6vpstnw==", "dev": true }, "uglify-js": { - "version": "3.3.21", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.3.21.tgz", - "integrity": "sha512-uy82472lH8tshK3jS3c5IFb5MmNKd/5qyBd0ih8sM42L3jWvxnE339U9gZU1zufnLVs98Stib9twq8dLm2XYCA==", + "version": "3.3.23", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.3.23.tgz", + "integrity": "sha512-Ks+KqLGDsYn4z+pU7JsKCzC0T3mPYl+rU+VcPZiQOazjE4Uqi4UCRY3qPMDbJi7ze37n1lDXj3biz1ik93vqvw==", "dev": true, "requires": { - "commander": "2.15.1", - "source-map": "0.6.1" + "commander": "~2.15.0", + "source-map": "~0.6.1" }, "dependencies": { "source-map": { @@ -11536,19 +11022,19 @@ "optional": true }, "uglifyjs-webpack-plugin": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/uglifyjs-webpack-plugin/-/uglifyjs-webpack-plugin-1.2.4.tgz", - "integrity": "sha512-z0IbjpW8b3O/OVn+TTZN4pI29RN1zktFBXLIzzfZ+++cUtZ1ERSlLWgpE/5OERuEUs1ijVQnpYAkSlpoVmQmSQ==", + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/uglifyjs-webpack-plugin/-/uglifyjs-webpack-plugin-1.2.5.tgz", + "integrity": "sha512-hIQJ1yxAPhEA2yW/i7Fr+SXZVMp+VEI3d42RTHBgQd2yhp/1UdBcR3QEWPV5ahBxlqQDMEMTuTEvDHSFINfwSw==", "dev": true, "requires": { - "cacache": "10.0.4", - "find-cache-dir": "1.0.0", - "schema-utils": "0.4.5", - "serialize-javascript": "1.4.0", - "source-map": "0.6.1", - "uglify-es": "3.3.9", - "webpack-sources": "1.1.0", - "worker-farm": "1.6.0" + "cacache": "^10.0.4", + "find-cache-dir": "^1.0.0", + "schema-utils": "^0.4.5", + "serialize-javascript": "^1.4.0", + "source-map": "^0.6.1", + "uglify-es": "^3.3.4", + "webpack-sources": "^1.1.0", + "worker-farm": "^1.5.2" }, "dependencies": { "commander": { @@ -11569,8 +11055,8 @@ "integrity": "sha512-r+MU0rfv4L/0eeW3xZrd16t4NZfK8Ld4SWVglYBb7ez5uXFWHuVRs6xCTrf1yirs9a4j4Y27nn7SRfO6v67XsQ==", "dev": true, "requires": { - "commander": "2.13.0", - "source-map": "0.6.1" + "commander": "~2.13.0", + "source-map": "~0.6.1" } } } @@ -11581,12 +11067,6 @@ "integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==", "dev": true }, - "umd": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/umd/-/umd-3.0.3.tgz", - "integrity": "sha512-4IcGSufhFshvLNcMCV80UnQVlZ5pMOC8mvNPForqwA4+lzYQuetTESLDQkeLmihq8bRcnpbQa48Wb8Lh16/xow==", - "dev": true - }, "underscore": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.7.0.tgz", @@ -11599,10 +11079,10 @@ "integrity": "sha1-XHHDTLW61dzr4+oM0IIHulqhrqQ=", "dev": true, "requires": { - "arr-union": "3.1.0", - "get-value": "2.0.6", - "is-extendable": "0.1.1", - "set-value": "0.4.3" + "arr-union": "^3.1.0", + "get-value": "^2.0.6", + "is-extendable": "^0.1.1", + "set-value": "^0.4.3" }, "dependencies": { "extend-shallow": { @@ -11611,7 +11091,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "0.1.1" + "is-extendable": "^0.1.0" } }, "set-value": { @@ -11620,10 +11100,10 @@ "integrity": "sha1-fbCPnT0i3H945Trzw79GZuzfzPE=", "dev": true, "requires": { - "extend-shallow": "2.0.1", - "is-extendable": "0.1.1", - "is-plain-object": "2.0.4", - "to-object-path": "0.3.0" + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.1", + "to-object-path": "^0.3.0" } } } @@ -11634,7 +11114,7 @@ "integrity": "sha1-0F8v5AMlYIcfMOk8vnNe6iAVFPM=", "dev": true, "requires": { - "unique-slug": "2.0.0" + "unique-slug": "^2.0.0" } }, "unique-slug": { @@ -11643,14 +11123,21 @@ "integrity": "sha1-22Z258fMBimHj/GWCXx4hVrp9Ks=", "dev": true, "requires": { - "imurmurhash": "0.1.4" + "imurmurhash": "^0.1.4" + } + }, + "unique-string": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-1.0.0.tgz", + "integrity": "sha1-nhBXzKhRq7kzmPizOuGHuZyuwRo=", + "requires": { + "crypto-random-string": "^1.0.0" } }, "universalify": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.1.tgz", - "integrity": "sha1-+nG63UQ3r0wUiEHjs7Fl+enlkLc=", - "dev": true + "integrity": "sha1-+nG63UQ3r0wUiEHjs7Fl+enlkLc=" }, "unpipe": { "version": "1.0.0", @@ -11664,8 +11151,8 @@ "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", "dev": true, "requires": { - "has-value": "0.3.1", - "isobject": "3.0.1" + "has-value": "^0.3.1", + "isobject": "^3.0.0" }, "dependencies": { "has-value": { @@ -11674,9 +11161,9 @@ "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", "dev": true, "requires": { - "get-value": "2.0.6", - "has-values": "0.1.4", - "isobject": "2.1.0" + "get-value": "^2.0.3", + "has-values": "^0.1.4", + "isobject": "^2.0.0" }, "dependencies": { "isobject": { @@ -11705,9 +11192,9 @@ } }, "upath": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/upath/-/upath-1.0.4.tgz", - "integrity": "sha512-d4SJySNBXDaQp+DPrziv3xGS6w3d2Xt69FijJr86zMPBy23JEloMCEOUBBzuN7xCtjLCnmB9tI/z7SBCahHBOw==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/upath/-/upath-1.0.5.tgz", + "integrity": "sha512-qbKn90aDQ0YEwvXoLqj0oiuUYroLX2lVHZ+b+xwjozFasAOC4GneDq5+OaIG5Zj+jFmbz/uO+f7a9qxjktJQww==", "dev": true }, "upper-case": { @@ -11721,7 +11208,7 @@ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-3.0.2.tgz", "integrity": "sha1-+QuFhQf4HepNz7s8TD2/orVX+qo=", "requires": { - "punycode": "2.1.0" + "punycode": "^2.1.0" } }, "urix": { @@ -11754,9 +11241,9 @@ "integrity": "sha512-h3qf9TNn53BpuXTTcpC+UehiRrl0Cv45Yr/xWayApjw6G8Bg2dGke7rIwDQ39piciWCWrC+WiqLjOh3SUp9n0Q==", "dev": true, "requires": { - "loader-utils": "1.1.0", - "mime": "1.6.0", - "schema-utils": "0.3.0" + "loader-utils": "^1.0.2", + "mime": "^1.4.1", + "schema-utils": "^0.3.0" }, "dependencies": { "ajv": { @@ -11765,10 +11252,10 @@ "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", "dev": true, "requires": { - "co": "4.6.0", - "fast-deep-equal": "1.1.0", - "fast-json-stable-stringify": "2.0.0", - "json-schema-traverse": "0.3.1" + "co": "^4.6.0", + "fast-deep-equal": "^1.0.0", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.3.0" } }, "schema-utils": { @@ -11777,25 +11264,25 @@ "integrity": "sha1-9YdyIs4+kx7a4DnxfrNxbnE3+M8=", "dev": true, "requires": { - "ajv": "5.5.2" + "ajv": "^5.0.0" } } } }, "url-parse": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.3.0.tgz", - "integrity": "sha512-zPvPA3T7P6M+0iNsgX+iAcAz4GshKrowtQBHHc/28tVsBc8jK7VRCNX+2GEcoE6zDB6XqXhcyiUWPVZY6C70Cg==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.4.0.tgz", + "integrity": "sha512-ERuGxDiQ6Xw/agN4tuoCRbmwRuZP0cJ1lJxJubXr5Q/5cDa78+Dc4wfvtxzhzhkm5VvmW6Mf8EVj9SPGN4l8Lg==", "dev": true, "requires": { - "querystringify": "1.0.0", - "requires-port": "1.0.0" + "querystringify": "^2.0.0", + "requires-port": "^1.0.0" }, "dependencies": { "querystringify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-1.0.0.tgz", - "integrity": "sha1-YoYkIRLFtxL6ZU5SZlK/ahP/Bcs=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.0.0.tgz", + "integrity": "sha512-eTPo5t/4bgaMNZxyjWx6N2a6AuE0mq51KWvpc7nU/MAqixcI6v6KrGUKES0HaomdnolQBBXU/++X6/QQ9KL4tw==", "dev": true } } @@ -11806,7 +11293,7 @@ "integrity": "sha512-6UJEQM/L+mzC3ZJNM56Q4DFGLX/evKGRg15UJHGB9X5j5Z3AFbgZvjUh2yq/UJUY4U5dh7Fal++XbNg1uzpRAw==", "dev": true, "requires": { - "kind-of": "6.0.2" + "kind-of": "^6.0.2" }, "dependencies": { "kind-of": { @@ -11818,13 +11305,21 @@ } }, "useragent": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/useragent/-/useragent-2.3.0.tgz", - "integrity": "sha512-4AoH4pxuSvHCjqLO04sU6U/uE65BYza8l/KKBS0b0hnUPWi+cQ2BpeTEwejCSx9SPV5/U03nniDTrWx5NrmKdw==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/useragent/-/useragent-2.2.1.tgz", + "integrity": "sha1-z1k+9PLRdYdei7ZY6pLhik/QbY4=", "dev": true, "requires": { - "lru-cache": "4.1.2", - "tmp": "0.0.33" + "lru-cache": "2.2.x", + "tmp": "0.0.x" + }, + "dependencies": { + "lru-cache": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.2.4.tgz", + "integrity": "sha1-bGWGGb7PFAMdDQtZSxYELOTcBj0=", + "dev": true + } } }, "util": { @@ -11880,7 +11375,7 @@ "integrity": "sha512-6sgSKoFw1UpUPd3cFdF7QGnrH6tDeBgW1F3v9gy8gLY0mlbiBXq8soy8aQpY6xeeCjH5K+JvC62Acp7gtl7wWA==", "dev": true, "requires": { - "homedir-polyfill": "1.0.1" + "homedir-polyfill": "^1.0.1" } }, "validate-npm-package-license": { @@ -11889,8 +11384,8 @@ "integrity": "sha512-63ZOUnL4SIXj4L0NixR3L1lcjO38crAbgrTpl28t8jjrfuiOBL5Iygm+60qPs/KsZGzPNg6Smnc/oY16QTjF0g==", "dev": true, "requires": { - "spdx-correct": "3.0.0", - "spdx-expression-parse": "3.0.0" + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" } }, "vary": { @@ -11905,9 +11400,9 @@ "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", "dev": true, "requires": { - "assert-plus": "1.0.0", + "assert-plus": "^1.0.0", "core-util-is": "1.0.2", - "extsprintf": "1.3.0" + "extsprintf": "^1.2.0" }, "dependencies": { "assert-plus": { @@ -11939,15 +11434,66 @@ "integrity": "sha1-wGavtYK7HLQSjWDqkjkulNXp2+w=", "dev": true }, - "watchpack": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.5.0.tgz", - "integrity": "sha512-RSlipNQB1u48cq0wH/BNfCu1tD/cJ8ydFIkNYhp9o+3d+8unClkIovpW5qpFPgmL9OE48wfAnlZydXByWP82AA==", + "vscode-uri": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-1.0.3.tgz", + "integrity": "sha1-Yxvb9xbcyrDmUpGo3CXCMjIIWlI=" + }, + "wait-on": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/wait-on/-/wait-on-2.1.0.tgz", + "integrity": "sha512-hDwJ674+7dfiiK/cxtYCwPxlnjXDjto/pCz1PF02sXUhqCqCWsgvxZln0699PReWqXXgkxqkF6DDo5Rj9sjNvw==", "dev": true, "requires": { - "chokidar": "2.0.3", - "graceful-fs": "4.1.11", - "neo-async": "2.5.1" + "core-js": "^2.4.1", + "joi": "^9.2.0", + "minimist": "^1.2.0", + "request": "^2.78.0", + "rx": "^4.1.0" + }, + "dependencies": { + "hoek": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-4.2.1.tgz", + "integrity": "sha512-QLg82fGkfnJ/4iy1xZ81/9SIJiq1NGFUMGs6ParyjBZr6jW2Ufj/snDqTHixNlHdPNwN2RLVD0Pi3igeK9+JfA==", + "dev": true + }, + "isemail": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/isemail/-/isemail-2.2.1.tgz", + "integrity": "sha1-A1PT2aYpUQgMJiwqoKQrjqjp4qY=", + "dev": true + }, + "joi": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/joi/-/joi-9.2.0.tgz", + "integrity": "sha1-M4WseQGSEwy+Iw6ALsAskhW7/to=", + "dev": true, + "requires": { + "hoek": "4.x.x", + "isemail": "2.x.x", + "items": "2.x.x", + "moment": "2.x.x", + "topo": "2.x.x" + } + }, + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + } + } + }, + "watchpack": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.6.0.tgz", + "integrity": "sha512-i6dHe3EyLjMmDlU1/bGQpEw25XSjkJULPuAVKCbNRefQVq48yXKUpwg538F7AZTf9kyr57zj++pQFltUa5H7yA==", + "dev": true, + "requires": { + "chokidar": "^2.0.2", + "graceful-fs": "^4.1.2", + "neo-async": "^2.5.0" }, "dependencies": { "anymatch": { @@ -11956,8 +11502,8 @@ "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", "dev": true, "requires": { - "micromatch": "3.1.10", - "normalize-path": "2.1.1" + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" } }, "arr-diff": { @@ -11978,16 +11524,16 @@ "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", "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.2", - "snapdragon-node": "2.1.1", - "split-string": "3.1.0", - "to-regex": "3.0.2" + "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": { @@ -11996,7 +11542,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "0.1.1" + "is-extendable": "^0.1.0" } } } @@ -12007,18 +11553,18 @@ "integrity": "sha512-zW8iXYZtXMx4kux/nuZVXjkLP+CyIK5Al5FHnj1OgTKGZfp4Oy6/ymtMSKFv3GD8DviEmUPmJg9eFdJ/JzudMg==", "dev": true, "requires": { - "anymatch": "2.0.0", - "async-each": "1.0.1", - "braces": "2.3.2", - "fsevents": "1.1.3", - "glob-parent": "3.1.0", - "inherits": "2.0.3", - "is-binary-path": "1.0.1", - "is-glob": "4.0.0", - "normalize-path": "2.1.1", - "path-is-absolute": "1.0.1", - "readdirp": "2.1.0", - "upath": "1.0.4" + "anymatch": "^2.0.0", + "async-each": "^1.0.0", + "braces": "^2.3.0", + "fsevents": "^1.1.2", + "glob-parent": "^3.1.0", + "inherits": "^2.0.1", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "normalize-path": "^2.1.1", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.0.0", + "upath": "^1.0.0" } }, "debug": { @@ -12036,13 +11582,13 @@ "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", "dev": true, "requires": { - "debug": "2.6.9", - "define-property": "0.2.5", - "extend-shallow": "2.0.1", - "posix-character-classes": "0.1.1", - "regex-not": "1.0.2", - "snapdragon": "0.8.2", - "to-regex": "3.0.2" + "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": { "define-property": { @@ -12051,7 +11597,7 @@ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "dev": true, "requires": { - "is-descriptor": "0.1.6" + "is-descriptor": "^0.1.0" } }, "extend-shallow": { @@ -12060,7 +11606,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "0.1.1" + "is-extendable": "^0.1.0" } }, "is-accessor-descriptor": { @@ -12069,7 +11615,7 @@ "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", "dev": true, "requires": { - "kind-of": "3.2.2" + "kind-of": "^3.0.2" }, "dependencies": { "kind-of": { @@ -12078,7 +11624,7 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "1.1.6" + "is-buffer": "^1.1.5" } } } @@ -12089,7 +11635,7 @@ "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", "dev": true, "requires": { - "kind-of": "3.2.2" + "kind-of": "^3.0.2" }, "dependencies": { "kind-of": { @@ -12098,7 +11644,7 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "1.1.6" + "is-buffer": "^1.1.5" } } } @@ -12109,9 +11655,9 @@ "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", "dev": true, "requires": { - "is-accessor-descriptor": "0.1.6", - "is-data-descriptor": "0.1.4", - "kind-of": "5.1.0" + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" } }, "kind-of": { @@ -12128,14 +11674,14 @@ "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", "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.2", - "snapdragon": "0.8.2", - "to-regex": "3.0.2" + "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": { @@ -12144,7 +11690,7 @@ "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", "dev": true, "requires": { - "is-descriptor": "1.0.2" + "is-descriptor": "^1.0.0" } }, "extend-shallow": { @@ -12153,7 +11699,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "0.1.1" + "is-extendable": "^0.1.0" } } } @@ -12164,10 +11710,10 @@ "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.1" + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" }, "dependencies": { "extend-shallow": { @@ -12176,7 +11722,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "0.1.1" + "is-extendable": "^0.1.0" } } } @@ -12187,8 +11733,8 @@ "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", "dev": true, "requires": { - "is-glob": "3.1.0", - "path-dirname": "1.0.2" + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" }, "dependencies": { "is-glob": { @@ -12197,7 +11743,7 @@ "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", "dev": true, "requires": { - "is-extglob": "2.1.1" + "is-extglob": "^2.1.0" } } } @@ -12208,7 +11754,7 @@ "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", "dev": true, "requires": { - "kind-of": "6.0.2" + "kind-of": "^6.0.0" } }, "is-data-descriptor": { @@ -12217,7 +11763,7 @@ "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", "dev": true, "requires": { - "kind-of": "6.0.2" + "kind-of": "^6.0.0" } }, "is-descriptor": { @@ -12226,9 +11772,9 @@ "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", "dev": true, "requires": { - "is-accessor-descriptor": "1.0.0", - "is-data-descriptor": "1.0.0", - "kind-of": "6.0.2" + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" } }, "is-extglob": { @@ -12243,7 +11789,7 @@ "integrity": "sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A=", "dev": true, "requires": { - "is-extglob": "2.1.1" + "is-extglob": "^2.1.1" } }, "is-number": { @@ -12252,7 +11798,7 @@ "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", "dev": true, "requires": { - "kind-of": "3.2.2" + "kind-of": "^3.0.2" }, "dependencies": { "kind-of": { @@ -12261,7 +11807,7 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "1.1.6" + "is-buffer": "^1.1.5" } } } @@ -12284,19 +11830,19 @@ "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", "dev": true, "requires": { - "arr-diff": "4.0.0", - "array-unique": "0.3.2", - "braces": "2.3.2", - "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.2", - "snapdragon": "0.8.2", - "to-regex": "3.0.2" + "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" } } } @@ -12307,7 +11853,7 @@ "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", "dev": true, "requires": { - "minimalistic-assert": "1.0.1" + "minimalistic-assert": "^1.0.0" } }, "web-animations-js": { @@ -12321,14 +11867,14 @@ "integrity": "sha1-gcUzqeM9W/tZe05j4s2yW1R3dRU=", "dev": true, "requires": { - "@types/selenium-webdriver": "2.53.43", - "selenium-webdriver": "2.53.3" + "@types/selenium-webdriver": "^2.53.35", + "selenium-webdriver": "^2.53.2" }, "dependencies": { - "adm-zip": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.4.4.tgz", - "integrity": "sha1-ph7VrmkFw66lizplfSUDMJEFJzY=", + "@types/selenium-webdriver": { + "version": "2.53.43", + "resolved": "https://registry.npmjs.org/@types/selenium-webdriver/-/selenium-webdriver-2.53.43.tgz", + "integrity": "sha512-UBYHWph6P3tutkbXpW6XYg9ZPbTKjw/YC2hGG1/GEvWwTbvezBUv3h+mmUFw79T3RFPnmedpiXdOBbXX+4l0jg==", "dev": true }, "sax": { @@ -12344,9 +11890,9 @@ "dev": true, "requires": { "adm-zip": "0.4.4", - "rimraf": "2.6.2", + "rimraf": "^2.2.8", "tmp": "0.0.24", - "ws": "1.1.5", + "ws": "^1.0.1", "xml2js": "0.4.4" } }, @@ -12368,8 +11914,8 @@ "integrity": "sha512-o3KqipXNUdS7wpQzBHSe180lBGO60SoK0yVo3CYJgb2MkobuWuBX6dhkYP5ORCLd55y+SaflMOV5fqAB53ux4w==", "dev": true, "requires": { - "options": "0.0.6", - "ultron": "1.0.2" + "options": ">=0.0.5", + "ultron": "1.0.x" } }, "xml2js": { @@ -12378,8 +11924,8 @@ "integrity": "sha1-MREBAAMAiuGSQOuhdJe1fHKcVV0=", "dev": true, "requires": { - "sax": "0.6.1", - "xmlbuilder": "9.0.7" + "sax": "0.6.x", + "xmlbuilder": ">=1.0.0" } } } @@ -12390,28 +11936,28 @@ "integrity": "sha512-3kOFejWqj5ISpJk4Qj/V7w98h9Vl52wak3CLiw/cDOfbVTq7FeoZ0SdoHHY9PYlHr50ZS42OfvzE2vB4nncKQg==", "dev": true, "requires": { - "acorn": "5.5.3", - "acorn-dynamic-import": "2.0.2", - "ajv": "6.4.0", - "ajv-keywords": "3.1.0", - "async": "2.6.0", - "enhanced-resolve": "3.4.1", - "escope": "3.6.0", - "interpret": "1.1.0", - "json-loader": "0.5.7", - "json5": "0.5.1", - "loader-runner": "2.3.0", - "loader-utils": "1.1.0", - "memory-fs": "0.4.1", - "mkdirp": "0.5.1", - "node-libs-browser": "2.1.0", - "source-map": "0.5.7", - "supports-color": "4.5.0", - "tapable": "0.2.8", - "uglifyjs-webpack-plugin": "0.4.6", - "watchpack": "1.5.0", - "webpack-sources": "1.1.0", - "yargs": "8.0.2" + "acorn": "^5.0.0", + "acorn-dynamic-import": "^2.0.0", + "ajv": "^6.1.0", + "ajv-keywords": "^3.1.0", + "async": "^2.1.2", + "enhanced-resolve": "^3.4.0", + "escope": "^3.6.0", + "interpret": "^1.0.0", + "json-loader": "^0.5.4", + "json5": "^0.5.1", + "loader-runner": "^2.3.0", + "loader-utils": "^1.1.0", + "memory-fs": "~0.4.1", + "mkdirp": "~0.5.0", + "node-libs-browser": "^2.0.0", + "source-map": "^0.5.3", + "supports-color": "^4.2.1", + "tapable": "^0.2.7", + "uglifyjs-webpack-plugin": "^0.4.6", + "watchpack": "^1.4.0", + "webpack-sources": "^1.0.1", + "yargs": "^8.0.2" }, "dependencies": { "ansi-regex": { @@ -12432,8 +11978,8 @@ "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=", "dev": true, "requires": { - "center-align": "0.1.3", - "right-align": "0.1.3", + "center-align": "^0.1.1", + "right-align": "^0.1.1", "wordwrap": "0.0.2" } }, @@ -12443,10 +11989,10 @@ "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", "dev": true, "requires": { - "graceful-fs": "4.1.11", - "parse-json": "2.2.0", - "pify": "2.3.0", - "strip-bom": "3.0.0" + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "strip-bom": "^3.0.0" } }, "os-locale": { @@ -12455,9 +12001,9 @@ "integrity": "sha512-3sslG3zJbEYcaC4YVAvDorjGxc7tv6KVATnLPZONiljsUncvihe9BQoVCEs0RZ1kmf4Hk9OBqlZfJZWI4GanKA==", "dev": true, "requires": { - "execa": "0.7.0", - "lcid": "1.0.0", - "mem": "1.1.0" + "execa": "^0.7.0", + "lcid": "^1.0.0", + "mem": "^1.1.0" } }, "path-type": { @@ -12466,7 +12012,7 @@ "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", "dev": true, "requires": { - "pify": "2.3.0" + "pify": "^2.0.0" } }, "pify": { @@ -12481,9 +12027,9 @@ "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", "dev": true, "requires": { - "load-json-file": "2.0.0", - "normalize-package-data": "2.4.0", - "path-type": "2.0.0" + "load-json-file": "^2.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^2.0.0" } }, "read-pkg-up": { @@ -12492,8 +12038,8 @@ "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", "dev": true, "requires": { - "find-up": "2.1.0", - "read-pkg": "2.0.0" + "find-up": "^2.0.0", + "read-pkg": "^2.0.0" } }, "string-width": { @@ -12502,8 +12048,8 @@ "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", "dev": true, "requires": { - "is-fullwidth-code-point": "2.0.0", - "strip-ansi": "4.0.0" + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" }, "dependencies": { "is-fullwidth-code-point": { @@ -12518,7 +12064,7 @@ "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", "dev": true, "requires": { - "ansi-regex": "3.0.0" + "ansi-regex": "^3.0.0" } } } @@ -12535,9 +12081,9 @@ "integrity": "sha1-KcVzMUgFe7Th913zW3qcty5qWd0=", "dev": true, "requires": { - "source-map": "0.5.7", - "uglify-to-browserify": "1.0.2", - "yargs": "3.10.0" + "source-map": "~0.5.1", + "uglify-to-browserify": "~1.0.0", + "yargs": "~3.10.0" }, "dependencies": { "yargs": { @@ -12546,9 +12092,9 @@ "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=", "dev": true, "requires": { - "camelcase": "1.2.1", - "cliui": "2.1.0", - "decamelize": "1.2.0", + "camelcase": "^1.0.2", + "cliui": "^2.1.0", + "decamelize": "^1.0.0", "window-size": "0.1.0" } } @@ -12560,9 +12106,9 @@ "integrity": "sha1-uVH0q7a9YX5m9j64kUmOORdj4wk=", "dev": true, "requires": { - "source-map": "0.5.7", - "uglify-js": "2.8.29", - "webpack-sources": "1.1.0" + "source-map": "^0.5.6", + "uglify-js": "^2.8.29", + "webpack-sources": "^1.0.1" } }, "which-module": { @@ -12583,19 +12129,19 @@ "integrity": "sha1-YpmpBVsc78lp/355wdkY3Osiw2A=", "dev": true, "requires": { - "camelcase": "4.1.0", - "cliui": "3.2.0", - "decamelize": "1.2.0", - "get-caller-file": "1.0.2", - "os-locale": "2.1.0", - "read-pkg-up": "2.0.0", - "require-directory": "2.1.1", - "require-main-filename": "1.0.1", - "set-blocking": "2.0.0", - "string-width": "2.1.1", - "which-module": "2.0.0", - "y18n": "3.2.1", - "yargs-parser": "7.0.0" + "camelcase": "^4.1.0", + "cliui": "^3.2.0", + "decamelize": "^1.1.1", + "get-caller-file": "^1.0.1", + "os-locale": "^2.0.0", + "read-pkg-up": "^2.0.0", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^2.0.0", + "which-module": "^2.0.0", + "y18n": "^3.2.1", + "yargs-parser": "^7.0.0" }, "dependencies": { "camelcase": { @@ -12610,9 +12156,9 @@ "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", "dev": true, "requires": { - "string-width": "1.0.2", - "strip-ansi": "3.0.1", - "wrap-ansi": "2.1.0" + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wrap-ansi": "^2.0.0" }, "dependencies": { "string-width": { @@ -12621,9 +12167,9 @@ "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "dev": true, "requires": { - "code-point-at": "1.1.0", - "is-fullwidth-code-point": "1.0.0", - "strip-ansi": "3.0.1" + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" } } } @@ -12636,7 +12182,7 @@ "integrity": "sha1-jQrELxbqVd69MyyvTEA4s+P139k=", "dev": true, "requires": { - "camelcase": "4.1.0" + "camelcase": "^4.1.0" }, "dependencies": { "camelcase": { @@ -12655,8 +12201,8 @@ "integrity": "sha1-/FcViMhVjad76e+23r3Fo7FyvcI=", "dev": true, "requires": { - "source-list-map": "0.1.8", - "source-map": "0.4.4" + "source-list-map": "~0.1.7", + "source-map": "~0.4.1" }, "dependencies": { "source-list-map": { @@ -12671,7 +12217,7 @@ "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", "dev": true, "requires": { - "amdefine": "1.0.1" + "amdefine": ">=0.0.4" } } } @@ -12682,11 +12228,11 @@ "integrity": "sha512-FCrqPy1yy/sN6U/SaEZcHKRXGlqU0DUaEBL45jkUYoB8foVb6wCnbIJ1HKIx+qUFTW+3JpVcCJCxZ8VATL4e+A==", "dev": true, "requires": { - "memory-fs": "0.4.1", - "mime": "1.6.0", - "path-is-absolute": "1.0.1", - "range-parser": "1.2.0", - "time-stamp": "2.0.0" + "memory-fs": "~0.4.1", + "mime": "^1.5.0", + "path-is-absolute": "^1.0.0", + "range-parser": "^1.0.3", + "time-stamp": "^2.0.0" } }, "webpack-dev-server": { @@ -12696,30 +12242,30 @@ "dev": true, "requires": { "ansi-html": "0.0.7", - "array-includes": "3.0.3", - "bonjour": "3.5.0", - "chokidar": "2.0.3", - "compression": "1.7.2", - "connect-history-api-fallback": "1.5.0", - "debug": "3.1.0", - "del": "3.0.0", - "express": "4.16.3", - "html-entities": "1.2.1", - "http-proxy-middleware": "0.17.4", - "import-local": "1.0.0", + "array-includes": "^3.0.3", + "bonjour": "^3.5.0", + "chokidar": "^2.0.0", + "compression": "^1.5.2", + "connect-history-api-fallback": "^1.3.0", + "debug": "^3.1.0", + "del": "^3.0.0", + "express": "^4.16.2", + "html-entities": "^1.2.0", + "http-proxy-middleware": "~0.17.4", + "import-local": "^1.0.0", "internal-ip": "1.2.0", - "ip": "1.1.5", - "killable": "1.0.0", - "loglevel": "1.6.1", - "opn": "5.1.0", - "portfinder": "1.0.13", - "selfsigned": "1.10.2", - "serve-index": "1.9.1", + "ip": "^1.1.5", + "killable": "^1.0.0", + "loglevel": "^1.4.1", + "opn": "^5.1.0", + "portfinder": "^1.0.9", + "selfsigned": "^1.9.1", + "serve-index": "^1.7.2", "sockjs": "0.3.19", "sockjs-client": "1.1.4", - "spdy": "3.4.7", - "strip-ansi": "3.0.1", - "supports-color": "5.3.0", + "spdy": "^3.4.1", + "strip-ansi": "^3.0.0", + "supports-color": "^5.1.0", "webpack-dev-middleware": "1.12.2", "yargs": "6.6.0" }, @@ -12730,8 +12276,8 @@ "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", "dev": true, "requires": { - "micromatch": "3.1.10", - "normalize-path": "2.1.1" + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" } }, "arr-diff": { @@ -12752,16 +12298,16 @@ "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", "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.2", - "snapdragon-node": "2.1.1", - "split-string": "3.1.0", - "to-regex": "3.0.2" + "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": { @@ -12770,7 +12316,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "0.1.1" + "is-extendable": "^0.1.0" } } } @@ -12787,18 +12333,18 @@ "integrity": "sha512-zW8iXYZtXMx4kux/nuZVXjkLP+CyIK5Al5FHnj1OgTKGZfp4Oy6/ymtMSKFv3GD8DviEmUPmJg9eFdJ/JzudMg==", "dev": true, "requires": { - "anymatch": "2.0.0", - "async-each": "1.0.1", - "braces": "2.3.2", - "fsevents": "1.1.3", - "glob-parent": "3.1.0", - "inherits": "2.0.3", - "is-binary-path": "1.0.1", - "is-glob": "4.0.0", - "normalize-path": "2.1.1", - "path-is-absolute": "1.0.1", - "readdirp": "2.1.0", - "upath": "1.0.4" + "anymatch": "^2.0.0", + "async-each": "^1.0.0", + "braces": "^2.3.0", + "fsevents": "^1.1.2", + "glob-parent": "^3.1.0", + "inherits": "^2.0.1", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "normalize-path": "^2.1.1", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.0.0", + "upath": "^1.0.0" } }, "expand-brackets": { @@ -12807,13 +12353,13 @@ "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", "dev": true, "requires": { - "debug": "2.6.9", - "define-property": "0.2.5", - "extend-shallow": "2.0.1", - "posix-character-classes": "0.1.1", - "regex-not": "1.0.2", - "snapdragon": "0.8.2", - "to-regex": "3.0.2" + "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": { @@ -12831,7 +12377,7 @@ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "dev": true, "requires": { - "is-descriptor": "0.1.6" + "is-descriptor": "^0.1.0" } }, "extend-shallow": { @@ -12840,7 +12386,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "0.1.1" + "is-extendable": "^0.1.0" } }, "is-accessor-descriptor": { @@ -12849,7 +12395,7 @@ "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", "dev": true, "requires": { - "kind-of": "3.2.2" + "kind-of": "^3.0.2" }, "dependencies": { "kind-of": { @@ -12858,7 +12404,7 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "1.1.6" + "is-buffer": "^1.1.5" } } } @@ -12869,7 +12415,7 @@ "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", "dev": true, "requires": { - "kind-of": "3.2.2" + "kind-of": "^3.0.2" }, "dependencies": { "kind-of": { @@ -12878,7 +12424,7 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "1.1.6" + "is-buffer": "^1.1.5" } } } @@ -12889,9 +12435,9 @@ "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", "dev": true, "requires": { - "is-accessor-descriptor": "0.1.6", - "is-data-descriptor": "0.1.4", - "kind-of": "5.1.0" + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" } }, "kind-of": { @@ -12908,14 +12454,14 @@ "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", "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.2", - "snapdragon": "0.8.2", - "to-regex": "3.0.2" + "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": { @@ -12924,7 +12470,7 @@ "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", "dev": true, "requires": { - "is-descriptor": "1.0.2" + "is-descriptor": "^1.0.0" } }, "extend-shallow": { @@ -12933,7 +12479,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "0.1.1" + "is-extendable": "^0.1.0" } } } @@ -12944,10 +12490,10 @@ "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.1" + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" }, "dependencies": { "extend-shallow": { @@ -12956,7 +12502,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "0.1.1" + "is-extendable": "^0.1.0" } } } @@ -12967,8 +12513,8 @@ "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", "dev": true, "requires": { - "is-glob": "3.1.0", - "path-dirname": "1.0.2" + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" }, "dependencies": { "is-glob": { @@ -12977,7 +12523,7 @@ "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", "dev": true, "requires": { - "is-extglob": "2.1.1" + "is-extglob": "^2.1.0" } } } @@ -12994,7 +12540,7 @@ "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", "dev": true, "requires": { - "kind-of": "6.0.2" + "kind-of": "^6.0.0" } }, "is-data-descriptor": { @@ -13003,7 +12549,7 @@ "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", "dev": true, "requires": { - "kind-of": "6.0.2" + "kind-of": "^6.0.0" } }, "is-descriptor": { @@ -13012,9 +12558,9 @@ "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", "dev": true, "requires": { - "is-accessor-descriptor": "1.0.0", - "is-data-descriptor": "1.0.0", - "kind-of": "6.0.2" + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" } }, "is-extglob": { @@ -13029,7 +12575,7 @@ "integrity": "sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A=", "dev": true, "requires": { - "is-extglob": "2.1.1" + "is-extglob": "^2.1.1" } }, "is-number": { @@ -13038,7 +12584,7 @@ "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", "dev": true, "requires": { - "kind-of": "3.2.2" + "kind-of": "^3.0.2" }, "dependencies": { "kind-of": { @@ -13047,7 +12593,7 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "1.1.6" + "is-buffer": "^1.1.5" } } } @@ -13070,28 +12616,28 @@ "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", "dev": true, "requires": { - "arr-diff": "4.0.0", - "array-unique": "0.3.2", - "braces": "2.3.2", - "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.2", - "snapdragon": "0.8.2", - "to-regex": "3.0.2" + "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" } }, "supports-color": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.3.0.tgz", - "integrity": "sha512-0aP01LLIskjKs3lq52EC0aGBAJhLq7B2Rd8HC/DR/PtNNpcLilNmHC12O+hu0usQpo7wtHNRqtrhBwtDb0+dNg==", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", + "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", "dev": true, "requires": { - "has-flag": "3.0.0" + "has-flag": "^3.0.0" } }, "y18n": { @@ -13106,19 +12652,19 @@ "integrity": "sha1-eC7CHvQDNF+DCoCMo9UTr1YGUgg=", "dev": true, "requires": { - "camelcase": "3.0.0", - "cliui": "3.2.0", - "decamelize": "1.2.0", - "get-caller-file": "1.0.2", - "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": "4.2.1" + "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": "^4.2.0" } }, "yargs-parser": { @@ -13127,7 +12673,7 @@ "integrity": "sha1-KczqwNxPA8bIe0qfIX3RjJ90hxw=", "dev": true, "requires": { - "camelcase": "3.0.0" + "camelcase": "^3.0.0" } } } @@ -13138,7 +12684,7 @@ "integrity": "sha512-/0QYwW/H1N/CdXYA2PNPVbsxO3u2Fpz34vs72xm03SRfg6bMNGfMJIQEpQjKRvkG2JvT6oRJFpDtSrwbX8Jzvw==", "dev": true, "requires": { - "lodash": "4.17.5" + "lodash": "^4.17.5" } }, "webpack-sources": { @@ -13147,8 +12693,8 @@ "integrity": "sha512-aqYp18kPphgoO5c/+NaUvEeACtZjMESmDChuD3NBciVpah3XpMEU9VAAtIaB1BsfJWWTSdv8Vv1m3T0aRk2dUw==", "dev": true, "requires": { - "source-list-map": "2.0.0", - "source-map": "0.6.1" + "source-list-map": "^2.0.0", + "source-map": "~0.6.1" }, "dependencies": { "source-map": { @@ -13165,7 +12711,7 @@ "integrity": "sha1-j6yKfo61n8ahZ2ioXJ2U7n+dDts=", "dev": true, "requires": { - "webpack-core": "0.6.9" + "webpack-core": "^0.6.8" } }, "websocket-driver": { @@ -13174,8 +12720,8 @@ "integrity": "sha1-DK+dLXVdk67gSdS90NP+LMoqJOs=", "dev": true, "requires": { - "http-parser-js": "0.4.11", - "websocket-extensions": "0.1.3" + "http-parser-js": ">=0.4.0", + "websocket-extensions": ">=0.1.1" } }, "websocket-extensions": { @@ -13195,7 +12741,7 @@ "integrity": "sha512-xcJpopdamTuY5duC/KnTTNBraPK54YwpenP4lzxU8H91GudWpFv38u0CKjclE1Wi2EH2EDz5LRcHcKbCIzqGyg==", "dev": true, "requires": { - "isexe": "2.0.0" + "isexe": "^2.0.0" } }, "which-module": { @@ -13210,7 +12756,7 @@ "integrity": "sha512-ijDLlyQ7s6x1JgCLur53osjm/UXUYD9+0PbYKrBsYisYXzCxN+HC3mYDNy/dWdmf3AwqwU3CXwDCvsNgGK1S0w==", "dev": true, "requires": { - "string-width": "1.0.2" + "string-width": "^1.0.2" } }, "window-size": { @@ -13219,6 +12765,13 @@ "integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=", "dev": true }, + "with-callback": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/with-callback/-/with-callback-1.0.2.tgz", + "integrity": "sha1-oJYpuakgAo1yFAT7Q1vc/1yRvCE=", + "dev": true, + "optional": true + }, "wordwrap": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", @@ -13231,7 +12784,7 @@ "integrity": "sha512-6w+3tHbM87WnSWnENBUvA2pxJPLhQUg5LKwUQHq3r+XPhIM+Gh2R5ycbwPCyuGbNg+lPgdcnQUhuC02kJCvffQ==", "dev": true, "requires": { - "errno": "0.1.7" + "errno": "~0.1.7" } }, "worker-loader": { @@ -13239,8 +12792,8 @@ "resolved": "https://registry.npmjs.org/worker-loader/-/worker-loader-1.1.1.tgz", "integrity": "sha512-qJZLVS/jMCBITDzPo/RuweYSIG8VJP5P67mP/71alGyTZRe1LYJFdwLjLalY3T5ifx0bMDRD3OB6P2p1escvlg==", "requires": { - "loader-utils": "1.1.0", - "schema-utils": "0.4.5" + "loader-utils": "^1.0.0", + "schema-utils": "^0.4.0" } }, "wrap-ansi": { @@ -13249,15 +12802,24 @@ "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", "dev": true, "requires": { - "string-width": "1.0.2", - "strip-ansi": "3.0.1" + "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 + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "write-file-atomic": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.3.0.tgz", + "integrity": "sha512-xuPeK4OdjWqtfi59ylvVL0Yn35SF3zgcAcv7rBPFHVaEapaDr4GdGgm3j7ckTwH9wHL7fGmgfAnb0+THrHb8tA==", + "requires": { + "graceful-fs": "^4.1.11", + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.2" + } }, "ws": { "version": "3.3.3", @@ -13265,19 +12827,24 @@ "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==", "dev": true, "requires": { - "async-limiter": "1.0.0", - "safe-buffer": "5.1.1", - "ultron": "1.1.1" + "async-limiter": "~1.0.0", + "safe-buffer": "~5.1.0", + "ultron": "~1.1.0" } }, + "xdg-basedir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-3.0.0.tgz", + "integrity": "sha1-SWsswQnsqNus/i3HK2A8F8WHCtQ=" + }, "xml2js": { "version": "0.4.19", "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz", "integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==", "dev": true, "requires": { - "sax": "1.2.4", - "xmlbuilder": "9.0.7" + "sax": ">=0.6.0", + "xmlbuilder": "~9.0.1" }, "dependencies": { "sax": { @@ -13325,7 +12892,7 @@ "integrity": "sha512-AkTuIuVTET12tpsVIQo+ZU6f/qDmKuRUcjaqR+OIvm+aCBsZ95i7UVY5WJ9TMsSaZ0DA2WxoZ4acu0sPH+OKAw==", "dev": true, "requires": { - "cuint": "0.2.2" + "cuint": "^0.2.2" } }, "y18n": { @@ -13347,19 +12914,19 @@ "dev": true, "optional": true, "requires": { - "camelcase": "3.0.0", - "cliui": "3.2.0", - "decamelize": "1.2.0", - "get-caller-file": "1.0.2", - "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" + "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" }, "dependencies": { "camelcase": { @@ -13385,7 +12952,7 @@ "dev": true, "optional": true, "requires": { - "camelcase": "3.0.0" + "camelcase": "^3.0.0" }, "dependencies": { "camelcase": { diff --git a/package.json b/package.json index 331c14da8..f38992d7b 100644 --- a/package.json +++ b/package.json @@ -1,26 +1,32 @@ { "name": "alfresco-content-app", - "version": "1.2.0", + "version": "1.3.0", "license": "LGPL-3.0", "scripts": { "ng": "ng", "start": "npm run server-versions && ng serve --open", "start:prod": "npm run server-versions && ng serve --prod --open", - "start:docker": "docker-compose up --build", "build": "npm run server-versions && ng build --prod", "build:prod": "npm run server-versions && ng build --prod", "build:dev": "npm run server-versions && ng build", "build:tomcat": "npm run server-versions && ng build --base-href ./", + "build:electron": "npm run server-versions && ng build --output-path www --base-href ./", "test": "ng test --code-coverage", "test:ci": "ng test --code-coverage --single-run --no-progress && cat ./coverage/lcov.info | ./node_modules/.bin/codacy-coverage && rm -rf ./coverage", "lint": "ng lint", - "e2e": "ng e2e", - "server-versions": "rimraf ./src/versions.json && npm list --depth=0 --json=true --prod=true > ./src/versions.json || exit 0" + "server-versions": "rimraf ./src/versions.json && npm list --depth=0 --json=true --prod=true > ./src/versions.json || exit 0", + "_e2e": "ng e2e", + "wd:update": "webdriver-manager update --gecko=false --versions.chrome=2.38", + "e2e": "npm run wd:update && protractor protractor.conf.js", + "start:docker": "docker-compose up -d --build && wait-on http://localhost:8080 && wait-on http://localhost:4000", + "stop:docker": "docker-compose stop", + "e2e:docker": "npm run start:docker && npm run e2e && npm run stop:docker", + "spellcheck": "cspell 'src/**/*.ts' 'e2e/**/*.ts'" }, "private": true, "dependencies": { - "@alfresco/adf-content-services": "2.3.0", - "@alfresco/adf-core": "2.3.0", + "@alfresco/adf-content-services": "2.4.0", + "@alfresco/adf-core": "2.4.0", "@angular/animations": "5.1.1", "@angular/cdk": "5.0.1", "@angular/common": "5.1.1", @@ -36,9 +42,14 @@ "@angular/router": "5.1.1", "@mat-datetimepicker/core": "1.0.1", "@mat-datetimepicker/moment": "1.0.1", + "@ngrx/effects": "^5.2.0", + "@ngrx/router-store": "^5.2.0", + "@ngrx/store": "^5.2.0", + "@ngrx/store-devtools": "^5.2.0", "@ngx-translate/core": "9.1.1", - "alfresco-js-api": "2.3.0", + "alfresco-js-api": "2.4.0", "core-js": "2.5.3", + "cspell": "^2.1.12", "hammerjs": "2.0.8", "moment-es6": "1.0.0", "pdfjs-dist": "2.0.303", @@ -53,22 +64,27 @@ "@types/jasmine": "^2.5.53", "@types/jasminewd2": "^2.0.2", "@types/node": "9.3.0", + "@types/selenium-webdriver": "^3.0.8", "codacy-coverage": "^2.0.3", "codelyzer": "^4.0.1", "jasmine-core": "~2.8.0", "jasmine-reporters": "^2.2.1", "jasmine-spec-reporter": "~4.2.1", "jasmine2-protractor-utils": "^1.3.0", - "karma": "~2.0.0", + "jasminewd2": "^2.2.0", + "karma": "2.0.2", "karma-chrome-launcher": "~2.2.0", "karma-cli": "~1.0.1", "karma-coverage-istanbul-reporter": "^1.2.1", "karma-jasmine": "~1.1.0", "karma-jasmine-html-reporter": "^0.2.2", - "protractor": "~5.1.2", + "node-rest-client": "^3.1.0", + "protractor": "5.3.2", "rimraf": "2.6.2", + "selenium-webdriver": "4.0.0-alpha.1", "ts-node": "~4.1.0", "tslint": "~5.9.1", - "typescript": "~2.5.3" + "typescript": "~2.7.2", + "wait-on": "2.1.0" } } diff --git a/protractor.conf.js b/protractor.conf.js old mode 100644 new mode 100755 index 9a1a1b39c..6b6fbb0d4 --- a/protractor.conf.js +++ b/protractor.conf.js @@ -11,9 +11,15 @@ const width = 1366; const height = 768; exports.config = { - allScriptsTimeout: 30000, + allScriptsTimeout: 60000, specs: [ + './e2e/suites/authentication/*.test.ts', + './e2e/suites/list-views/*.test.ts', + './e2e/suites/application/page-titles.test.ts', + './e2e/suites/navigation/*.test.ts', + './e2e/suites/pagination/*.test.ts', + './e2e/suites/actions/*.test.ts' ], capabilities: { @@ -28,12 +34,12 @@ exports.config = { directConnect: true, - baseUrl: 'http://localhost:4200', + baseUrl: 'http://localhost:4000', framework: 'jasmine2', jasmineNodeOpts: { showColors: true, - defaultTimeoutInterval: 50000, + defaultTimeoutInterval: 90000, print: function() {} }, diff --git a/src/app.config.json b/src/app.config.json index 6db201ac8..0673d0896 100644 --- a/src/app.config.json +++ b/src/app.config.json @@ -1,31 +1,32 @@ { "ecmHost": "http://{hostname}{:port}", + "providers": "ECM", + "authType" : "BASIC", "application": { "name": "Alfresco Example Content Application", - "logo": "assets/images/alfresco-logo-white.svg" + "logo": "assets/images/alfresco-logo-white.svg", + "copyright": + "© 2017 - 2018 Alfresco Software, Inc. All rights reserved." + }, + "experimental": { + "libraries": false }, "headerColor": "#2196F3", "languagePicker": false, "pagination": { "size": 25, - "supportedPageSizes": [ - 25, - 50, - 100 - ] + "supportedPageSizes": [25, 50, 100] }, "files": { - "excluded": [ - ".DS_Store", - "desktop.ini", - "thumbs.db", - ".git" - ] + "excluded": [".DS_Store", "desktop.ini", "Thumbs.db", ".git"] }, "adf-version-manager": { "allowComments": true, - "allowDownload": true, - "allowDelete": true + "allowDownload": true + }, + "sideNav": { + "preserveState": true, + "expandedSidenav": true }, "navigation": { "main": [ @@ -162,115 +163,132 @@ } }, "search": { - "limits": { - "permissionEvaluationTime": null, - "permissionEvaluationCount": null + "include": ["path", "allowableOperations", "properties"], + "sorting": { + "options": [ + { + "key": "score", + "label": "SEARCH.SORT.RELEVANCE", + "type": "FIELD", + "field": "score", + "ascending": true + }, + { + "key": "name", + "label": "SEARCH.SORT.FILENAME", + "type": "FIELD", + "field": "cm:name", + "ascending": true + }, + { + "key": "title", + "label": "SEARCH.SORT.TITLE", + "type": "FIELD", + "field": "cm:title", + "ascending": true + }, + { + "key": "modified", + "label": "SEARCH.SORT.MODIFIED_DATE", + "type": "FIELD", + "field": "cm:modified", + "ascending": true + }, + { + "key": "modified", + "label": "SEARCH.SORT.MODIFIER", + "type": "FIELD", + "field": "cm:modifier", + "ascending": true + }, + { + "key": "modified", + "label": "SEARCH.SORT.CREATE_DATE", + "type": "FIELD", + "field": "cm:created", + "ascending": true + }, + { + "key": "content.sizeInBytes", + "label": "SEARCH.SORT.SIZE", + "type": "FIELD", + "field": "content.size", + "ascending": true + }, + { + "key": "content.mimetype", + "label": "SEARCH.SORT.TYPE", + "type": "FIELD", + "field": "content.mimetype", + "ascending": true + } + ], + "defaults": [ + { + "key": "score", + "type": "FIELD", + "field": "score", + "ascending": true + } + ] }, "filterQueries": [ { "query": "TYPE:'cm:folder' OR TYPE:'cm:content'" }, - { "query": "NOT cm:creator:System" } + { "query": "NOT cm:creator:System" }, + { "query": "NOT TYPE:'dl:dataList' AND NOT TYPE:'dl:todoList' AND NOT TYPE:'dl:issue' AND NOT TYPE:'fm:topic' AND NOT TYPE:'lnk:link' AND NOT TYPE:'fm:post'" } ], - "facetFields": { - "facets": [ - { "field": "content.mimetype", "mincount": 1, "label": "Type" }, - { "field": "content.size", "mincount": 1, "label": "Size" }, - { "field": "creator", "mincount": 1, "label": "Creator" }, - { "field": "modifier", "mincount": 1, "label": "Modifier" } - ] - }, - "facetQueries": [ - { "query": "created:2018", "label": "Created This Year" }, - { "query": "content.mimetype", "label": "Type" }, - { "query": "content.size:[0 TO 10240]", "label": "Size: xtra small"}, - { "query": "content.size:[10240 TO 102400]", "label": "Size: small"}, - { "query": "content.size:[102400 TO 1048576]", "label": "Size: medium" }, - { "query": "content.size:[1048576 TO 16777216]", "label": "Size: large" }, - { "query": "content.size:[16777216 TO 134217728]", "label": "Size: xtra large" }, - { "query": "content.size:[134217728 TO MAX]", "label": "Size: XX large" } + "facetFields": [ + { "field": "content.mimetype", "mincount": 1, "label": "SEARCH.FACET_FIELDS.FILE_TYPE" }, + { "field": "creator", "mincount": 1, "label": "SEARCH.FACET_FIELDS.CREATOR" }, + { "field": "modifier", "mincount": 1, "label": "SEARCH.FACET_FIELDS.MODIFIER" }, + { "field": "SITE", "mincount": 1, "label": "SEARCH.FACET_FIELDS.FILE_LIBRARY" } ], - "query": { - "categories": [ - { - "id": "broken", - "name": "Broken Facet", - "enabled": false, - "expanded": false, - "component": { - "selector": "adf-search-text", - "settings": { - "field": "fieldname" - } - } - }, - { - "id": "queryName", - "name": "Name", - "enabled": true, - "expanded": true, - "component": { - "selector": "adf-search-text", - "settings": { - "pattern": "cm:name:'(.*?)'", - "field": "cm:name", - "placeholder": "Enter the name" - } - } - }, - { - "id": "queryFields", - "name": "Fields", - "enabled": true, - "expanded": false, - "component": { - "selector": "adf-search-fields", - "settings": { - "field": null, - "options": [ - { "name": "Name", "value": "name", "fields": ["name"], "default": true }, - { "name": "File Size", "value": "content.sizeInBytes", "fields": ["content"], "default": true }, - { "name": "Modified On", "value": "modifiedAt", "fields": ["modifiedAt"], "default": true }, - { "name": "Modified By", "value": "modifiedByUser.displayName", "fields": ["modifiedByUser"], "default": true } - ] - } - } - }, - { - "id": "queryType", - "name": "Type", - "enabled": true, - "expanded": false, - "component": { - "selector": "adf-search-radio", - "settings": { - "field": null, - "options": [ - { "name": "None", "value": "", "default": true }, - { "name": "All", "value": "TYPE:'cm:folder' OR TYPE:'cm:content'" }, - { "name": "Folder", "value": "TYPE:'cm:folder'" }, - { "name": "Document", "value": "TYPE:'cm:content'" } - ] - } - } - }, - { - "id": "queryLocations", - "name": "Locations", - "enabled": true, - "expanded": false, - "component": { - "selector": "adf-search-scope-locations", - "settings": { - "field": null, - "options": [ - { "name": "Default", "value": "nodes", "default": true }, - { "name": "Nodes", "value": "nodes" }, - { "name": "Deleted Nodes", "value": "deleted-nodes" }, - { "name": "Versions", "value": "versions" } - ] - } + "categories": [ + { + "id": "modifiedDate", + "name": "SEARCH.CATEGORIES.MODIFIED_DATE", + "enabled": true, + "component": { + "selector": "check-list", + "settings": { + "options": [ + { "name": "Today", "value": "cm:modified:[TODAY to TODAY]" }, + { "name": "This week", "value": "cm:modified:[NOW/DAY-7DAYS TO NOW/DAY+1DAY]" }, + { "name": "This month", "value": "cm:modified:[NOW/DAY-1MONTH TO NOW/DAY+1DAY]"}, + { "name": "In last 6 months", "value": "cm:modified:[NOW/DAY-6MONTHS TO NOW/DAY+1DAY]"}, + { "name": "This year", "value": "cm:modified:[NOW/DAY-1YEAR TO NOW/DAY+1DAY]"} + ] } } - ] - } + }, + { + "id": "size", + "name": "SEARCH.CATEGORIES.SIZE", + "enabled": true, + "component": { + "selector": "check-list", + "settings": { + "options": [ + { "name": "Small", "value": "content.size:[0 TO 1048576>" }, + { "name": "Medium", "value": "content.size:[1048576 TO 52428800]" }, + { "name": "Large", "value": "content.size:<52428800 TO 524288000]" }, + { "name": "Huge", "value": "content.size:<524288000 TO MAX]" } + ] + } + } + }, + { + "id": "createdDateRange", + "name": "SEARCH.CATEGORIES.CREATED_DATE", + "enabled": true, + "component": { + "selector": "date-range", + "settings": { + "field": "cm:created", + "dateFormat": "DD-MMM-YY" + } + } + } + ] } } diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 439960187..74d5e6aa4 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -25,7 +25,12 @@ import { Component, OnInit } from '@angular/core'; import { Router, ActivatedRoute, NavigationEnd } from '@angular/router'; -import { TranslationService, PageTitleService, UserPreferencesService, AppConfigService } from '@alfresco/adf-core'; +import { + PageTitleService, AppConfigService, + AuthenticationService, AlfrescoApiService } from '@alfresco/adf-core'; +import { Store } from '@ngrx/store'; +import { AppStore } from './store/states/app.state'; +import { SetHeaderColorAction, SetAppNameAction, SetLogoPathAction, SetLanguagePickerAction } from './store/actions'; @Component({ selector: 'app-root', @@ -37,15 +42,24 @@ export class AppComponent implements OnInit { private route: ActivatedRoute, private router: Router, private pageTitle: PageTitleService, - private translateService: TranslationService, - preferences: UserPreferencesService, - config: AppConfigService) { - // TODO: remove once ADF 2.3.0 is out (needs bug fixes) - preferences.defaults.supportedPageSizes = config.get('pagination.supportedPageSizes'); - preferences.defaults.paginationSize = config.get('pagination.size'); + private store: Store, + private config: AppConfigService, + private alfrescoApiService: AlfrescoApiService, + private authenticationService: AuthenticationService) { } ngOnInit() { + this.alfrescoApiService.getInstance().on('error', (error) => { + if (error.status === 401) { + if (!this.authenticationService.isLoggedIn()) { + this.router.navigate(['/login']); + } + } + }); + + + this.loadAppSettings(); + const { router, pageTitle, route } = this; router @@ -61,14 +75,24 @@ export class AppComponent implements OnInit { const snapshot: any = currentRoute.snapshot || {}; const data: any = snapshot.data || {}; - if (data.i18nTitle) { - this.translateService.translate - .stream(data.i18nTitle) - .subscribe((title) => pageTitle.setTitle(title)); - - } else { - pageTitle.setTitle(data.title || ''); - } + pageTitle.setTitle(data.title || ''); }); } + + private loadAppSettings() { + const headerColor = this.config.get('headerColor'); + if (headerColor) { + this.store.dispatch(new SetHeaderColorAction(headerColor)); + } + const appName = this.config.get('application.name'); + if (appName) { + this.store.dispatch(new SetAppNameAction(appName)); + } + const logoPath = this.config.get('application.logo'); + if (logoPath) { + this.store.dispatch(new SetLogoPathAction(logoPath)); + } + const languagePicker = this.config.get('languagePicker'); + this.store.dispatch(new SetLanguagePickerAction(languagePicker)); + } } diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 8c17e443a..f5f07237d 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -28,7 +28,7 @@ import { NgModule } from '@angular/core'; import { RouterModule } from '@angular/router'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; -import { TRANSLATION_PROVIDER, CoreModule } from '@alfresco/adf-core'; +import { TRANSLATION_PROVIDER, CoreModule, AppConfigService, PageTitleService, DebugAppConfigService } from '@alfresco/adf-core'; import { ContentModule } from '@alfresco/adf-content-services'; import { AppComponent } from './app.component'; @@ -44,30 +44,42 @@ import { RecentFilesComponent } from './components/recent-files/recent-files.com import { SharedFilesComponent } from './components/shared-files/shared-files.component'; import { TrashcanComponent } from './components/trashcan/trashcan.component'; import { LayoutComponent } from './components/layout/layout.component'; +import { SidenavViewsManagerDirective } from './components/layout/sidenav-views-manager.directive'; import { HeaderComponent } from './components/header/header.component'; import { CurrentUserComponent } from './components/current-user/current-user.component'; import { SearchInputComponent } from './components/search-input/search-input.component'; +import { SearchInputControlComponent } from './components/search-input-control/search-input-control.component'; import { SidenavComponent } from './components/sidenav/sidenav.component'; import { AboutComponent } from './components/about/about.component'; import { LocationLinkComponent } from './components/location-link/location-link.component'; -import { EmptyFolderComponent } from './components/empty-folder/empty-folder.component'; +import { CustomDlRowComponent } from './components/custom-dl-row/custom-dl-row.component'; import { NodeCopyDirective } from './common/directives/node-copy.directive'; import { NodeDeleteDirective } from './common/directives/node-delete.directive'; import { NodeMoveDirective } from './common/directives/node-move.directive'; import { NodeRestoreDirective } from './common/directives/node-restore.directive'; import { NodePermanentDeleteDirective } from './common/directives/node-permanent-delete.directive'; import { NodeUnshareDirective } from './common/directives/node-unshare.directive'; -import { NodeInfoDirective } from './common/directives/node-info.directive'; import { NodeVersionsDirective } from './common/directives/node-versions.directive'; -import { AppConfigPipe } from './common/pipes/app-config.pipe'; -import { VersionManagerDialogAdapterComponent } from './components/versions-dialog/version-manager-dialog-adapter.component'; +import { NodeVersionsDialogComponent } from './dialogs/node-versions/node-versions.dialog'; import { BrowsingFilesService } from './common/services/browsing-files.service'; import { ContentManagementService } from './common/services/content-management.service'; import { NodeActionsService } from './common/services/node-actions.service'; import { NodePermissionService } from './common/services/node-permission.service'; -import { MatMenuModule, MatIconModule, MatButtonModule, MatDialogModule, MatInputModule } from '@angular/material'; import { SearchComponent } from './components/search/search.component'; -import { NodeDownloadDirective } from './common/directives/node-download.directive'; +import { SettingsComponent } from './components/settings/settings.component'; +import { PageTitleService as AcaPageTitleService } from './common/services/page-title.service'; +import { ProfileResolver } from './common/services/profile.resolver'; + +import { InfoDrawerComponent } from './components/info-drawer/info-drawer.component'; +import { EditFolderDirective } from './directives/edit-folder.directive'; +import { CreateFolderDirective } from './directives/create-folder.directive'; +import { DownloadNodesDirective } from './directives/download-nodes.directive'; +import { AppStoreModule } from './store/app-store.module'; +import { PaginationDirective } from './directives/pagination.directive'; +import { DocumentListDirective } from './directives/document-list.directive'; +import { MaterialModule } from './material.module'; +import { ExperimentalDirective } from './directives/experimental.directive'; +import { ContentApiService } from './services/content-api.service'; @NgModule({ imports: [ @@ -79,22 +91,21 @@ import { NodeDownloadDirective } from './common/directives/node-download.directi useHash: true, enableTracing: false // enable for debug only }), - MatMenuModule, - MatIconModule, - MatButtonModule, - MatDialogModule, - MatInputModule, + MaterialModule, CoreModule, - ContentModule + ContentModule, + AppStoreModule ], declarations: [ AppComponent, GenericErrorComponent, LoginComponent, LayoutComponent, + SidenavViewsManagerDirective, HeaderComponent, CurrentUserComponent, SearchInputComponent, + SearchInputControlComponent, SidenavComponent, FilesComponent, FavoritesComponent, @@ -105,22 +116,28 @@ import { NodeDownloadDirective } from './common/directives/node-download.directi PreviewComponent, AboutComponent, LocationLinkComponent, - EmptyFolderComponent, + CustomDlRowComponent, NodeCopyDirective, NodeDeleteDirective, NodeMoveDirective, NodeRestoreDirective, NodePermanentDeleteDirective, NodeUnshareDirective, - NodeInfoDirective, NodeVersionsDirective, - AppConfigPipe, - VersionManagerDialogAdapterComponent, + NodeVersionsDialogComponent, SearchComponent, - // Workarounds for ADF 2.3.0 - NodeDownloadDirective + SettingsComponent, + InfoDrawerComponent, + EditFolderDirective, + CreateFolderDirective, + DownloadNodesDirective, + PaginationDirective, + DocumentListDirective, + ExperimentalDirective ], providers: [ + { provide: PageTitleService, useClass: AcaPageTitleService }, + { provide: AppConfigService, useClass: DebugAppConfigService }, { provide: TRANSLATION_PROVIDER, multi: true, @@ -132,10 +149,12 @@ import { NodeDownloadDirective } from './common/directives/node-download.directi BrowsingFilesService, ContentManagementService, NodeActionsService, - NodePermissionService + NodePermissionService, + ProfileResolver, + ContentApiService ], entryComponents: [ - VersionManagerDialogAdapterComponent + NodeVersionsDialogComponent ], bootstrap: [AppComponent] }) diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts index 3a098acb5..0a4b610a0 100644 --- a/src/app/app.routes.ts +++ b/src/app/app.routes.ts @@ -40,18 +40,29 @@ import { LoginComponent } from './components/login/login.component'; import { PreviewComponent } from './components/preview/preview.component'; import { GenericErrorComponent } from './components/generic-error/generic-error.component'; import { SearchComponent } from './components/search/search.component'; +import { SettingsComponent } from './components/settings/settings.component'; + +import { ProfileResolver } from './common/services/profile.resolver'; export const APP_ROUTES: Routes = [ { path: 'login', component: LoginComponent, data: { - i18nTitle: 'APP.SIGN_IN' + title: 'APP.SIGN_IN' + } + }, + { + path: 'settings', + component: SettingsComponent, + data: { + title: 'Settings' } }, { path: '', component: LayoutComponent, + resolve: { profile: ProfileResolver }, children: [ { path: '', @@ -61,21 +72,21 @@ export const APP_ROUTES: Routes = [ { path: 'favorites', data: { - preferencePrefix: 'favorites' + sortingPreferenceKey: 'favorites' }, children: [ { path: '', component: FavoritesComponent, data: { - i18nTitle: 'APP.BROWSE.FAVORITES.TITLE' + title: 'APP.BROWSE.FAVORITES.TITLE' } }, { path: 'preview/:nodeId', component: PreviewComponent, data: { - i18nTitle: 'APP.PREVIEW.TITLE', + title: 'APP.PREVIEW.TITLE', navigateMultiple: true, navigateSource: 'favorites' } @@ -85,27 +96,27 @@ export const APP_ROUTES: Routes = [ { path: 'libraries', data: { - preferencePrefix: 'libraries' + sortingPreferenceKey: 'libraries' }, children: [{ path: '', component: LibrariesComponent, data: { - i18nTitle: 'APP.BROWSE.LIBRARIES.TITLE' + title: 'APP.BROWSE.LIBRARIES.TITLE' } }, { path: ':folderId', component: FilesComponent, data: { - i18nTitle: 'APP.BROWSE.LIBRARIES.TITLE', - preferencePrefix: 'libraries-files' + title: 'APP.BROWSE.LIBRARIES.TITLE', + sortingPreferenceKey: 'libraries-files' } }, { path: ':folderId/preview/:nodeId', component: PreviewComponent, data: { - i18nTitle: 'APP.PREVIEW.TITLE', + title: 'APP.PREVIEW.TITLE', navigateMultiple: true, navigateSource: 'libraries' } @@ -115,14 +126,14 @@ export const APP_ROUTES: Routes = [ { path: 'personal-files', data: { - preferencePrefix: 'personal-files' + sortingPreferenceKey: 'personal-files' }, children: [ { path: '', component: FilesComponent, data: { - i18nTitle: 'APP.BROWSE.PERSONAL.TITLE', + title: 'APP.BROWSE.PERSONAL.TITLE', defaultNodeId: '-my-' } }, @@ -130,14 +141,14 @@ export const APP_ROUTES: Routes = [ path: ':folderId', component: FilesComponent, data: { - i18nTitle: 'APP.BROWSE.PERSONAL.TITLE' + title: 'APP.BROWSE.PERSONAL.TITLE' } }, { path: 'preview/:nodeId', component: PreviewComponent, data: { - i18nTitle: 'APP.PREVIEW.TITLE', + title: 'APP.PREVIEW.TITLE', navigateMultiple: true, navigateSource: 'personal-files' } @@ -146,7 +157,7 @@ export const APP_ROUTES: Routes = [ path: ':folderId/preview/:nodeId', component: PreviewComponent, data: { - i18nTitle: 'APP.PREVIEW.TITLE', + title: 'APP.PREVIEW.TITLE', navigateMultiple: true, navigateSource: 'personal-files' } @@ -156,21 +167,21 @@ export const APP_ROUTES: Routes = [ { path: 'recent-files', data: { - preferencePrefix: 'recent-files' + sortingPreferenceKey: 'recent-files' }, children: [ { path: '', component: RecentFilesComponent, data: { - i18nTitle: 'APP.BROWSE.RECENT.TITLE' + title: 'APP.BROWSE.RECENT.TITLE' } }, { path: 'preview/:nodeId', component: PreviewComponent, data: { - i18nTitle: 'APP.PREVIEW.TITLE', + title: 'APP.PREVIEW.TITLE', navigateMultiple: true, navigateSource: 'recent-files' } @@ -180,21 +191,21 @@ export const APP_ROUTES: Routes = [ { path: 'shared', data: { - preferencePrefix: 'shared-files' + sortingPreferenceKey: 'shared-files' }, children: [ { path: '', component: SharedFilesComponent, data: { - i18nTitle: 'APP.BROWSE.SHARED.TITLE' + title: 'APP.BROWSE.SHARED.TITLE' } }, { path: 'preview/:nodeId', component: PreviewComponent, data: { - i18nTitle: 'APP.PREVIEW.TITLE', + title: 'APP.PREVIEW.TITLE', navigateMultiple: true, navigateSource: 'shared' } @@ -205,20 +216,37 @@ export const APP_ROUTES: Routes = [ path: 'trashcan', component: TrashcanComponent, data: { - i18nTitle: 'APP.BROWSE.TRASHCAN.TITLE', - preferencePrefix: 'trashcan' + title: 'APP.BROWSE.TRASHCAN.TITLE', + sortingPreferenceKey: 'trashcan' } }, { path: 'about', component: AboutComponent, data: { - i18nTitle: 'APP.BROWSE.ABOUT.TITLE' + title: 'APP.BROWSE.ABOUT.TITLE' } }, { path: 'search', - component: SearchComponent + children: [ + { + path: '', + component: SearchComponent, + data: { + title: 'APP.BROWSE.SEARCH.TITLE' + } + }, + { + path: 'preview/:nodeId', + component: PreviewComponent, + data: { + title: 'APP.PREVIEW.TITLE', + navigateMultiple: true, + navigateSource: 'search' + } + } + ] }, { path: '**', diff --git a/src/app/common/directives/node-copy.directive.spec.ts b/src/app/common/directives/node-copy.directive.spec.ts index e32cd3c9a..c95b98546 100644 --- a/src/app/common/directives/node-copy.directive.spec.ts +++ b/src/app/common/directives/node-copy.directive.spec.ts @@ -26,26 +26,15 @@ import { Component, DebugElement } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; - import { Observable } from 'rxjs/Rx'; - -import { - TranslationService, NodesApiService, NotificationService, AlfrescoApiService, TranslationMock, - AppConfigService, StorageService, CookieService, ContentService, AuthenticationService, - UserPreferencesService, LogService, ThumbnailService -} from '@alfresco/adf-core'; -import { TranslateModule } from '@ngx-translate/core'; -import { HttpClientModule } from '@angular/common/http'; - +import { MatSnackBar } from '@angular/material'; import { NodeActionsService } from '../services/node-actions.service'; import { NodeCopyDirective } from './node-copy.directive'; -import { ContentManagementService } from '../services/content-management.service'; -import { MatSnackBarModule, MatDialogModule, MatIconModule } from '@angular/material'; -import { DocumentListService } from '@alfresco/adf-content-services'; -import { NoopAnimationsModule } from '@angular/platform-browser/animations'; +import { AppTestingModule } from '../../testing/app-testing.module'; +import { ContentApiService } from '../../services/content-api.service'; @Component({ - template: '
' + template: '
' }) class TestComponent { selection; @@ -55,62 +44,31 @@ describe('NodeCopyDirective', () => { let fixture: ComponentFixture; let component: TestComponent; let element: DebugElement; - let notificationService: NotificationService; - let nodesApiService: NodesApiService; + let snackBar: MatSnackBar; let service: NodeActionsService; - let translationService: TranslationService; + let contentApi: ContentApiService; beforeEach(() => { TestBed.configureTestingModule({ - imports: [ - NoopAnimationsModule, - HttpClientModule, - TranslateModule.forRoot(), - MatSnackBarModule, - MatDialogModule, - MatIconModule - ], + imports: [ AppTestingModule ], declarations: [ TestComponent, NodeCopyDirective - ], - providers: [ - AlfrescoApiService, - AuthenticationService, - AppConfigService, - StorageService, - ContentService, - UserPreferencesService, - LogService, - CookieService, - NotificationService, - NodesApiService, - NodeActionsService, - { provide: TranslationService, useClass: TranslationMock }, - ContentManagementService, - DocumentListService, - ThumbnailService ] }); + contentApi = TestBed.get(ContentApiService); + fixture = TestBed.createComponent(TestComponent); component = fixture.componentInstance; element = fixture.debugElement.query(By.directive(NodeCopyDirective)); - notificationService = TestBed.get(NotificationService); - nodesApiService = TestBed.get(NodesApiService); + snackBar = TestBed.get(MatSnackBar); service = TestBed.get(NodeActionsService); - translationService = TestBed.get(TranslationService); - }); - - beforeEach(() => { - spyOn(translationService, 'get').and.callFake((key) => { - return Observable.of(key); - }); }); describe('Copy node action', () => { beforeEach(() => { - spyOn(notificationService, 'openSnackMessageAction').and.callThrough(); + spyOn(snackBar, 'open').and.callThrough(); }); it('notifies successful copy of a node', () => { @@ -124,9 +82,7 @@ describe('NodeCopyDirective', () => { service.contentCopied.next(createdItems); expect(service.copyNodes).toHaveBeenCalled(); - expect(notificationService.openSnackMessageAction).toHaveBeenCalledWith( - 'APP.MESSAGES.INFO.NODE_COPY.SINGULAR', 'APP.ACTIONS.UNDO', 10000 - ); + expect(snackBar.open['calls'].argsFor(0)[0]).toBe('APP.MESSAGES.INFO.NODE_COPY.SINGULAR'); }); it('notifies successful copy of multiple nodes', () => { @@ -144,9 +100,7 @@ describe('NodeCopyDirective', () => { service.contentCopied.next(createdItems); expect(service.copyNodes).toHaveBeenCalled(); - expect(notificationService.openSnackMessageAction).toHaveBeenCalledWith( - 'APP.MESSAGES.INFO.NODE_COPY.PLURAL', 'APP.ACTIONS.UNDO', 10000 - ); + expect(snackBar.open['calls'].argsFor(0)[0]).toBe('APP.MESSAGES.INFO.NODE_COPY.PLURAL'); }); it('notifies partially copy of one node out of a multiple selection of nodes', () => { @@ -163,9 +117,7 @@ describe('NodeCopyDirective', () => { service.contentCopied.next(createdItems); expect(service.copyNodes).toHaveBeenCalled(); - expect(notificationService.openSnackMessageAction).toHaveBeenCalledWith( - 'APP.MESSAGES.INFO.NODE_COPY.PARTIAL_SINGULAR', 'APP.ACTIONS.UNDO', 10000 - ); + expect(snackBar.open['calls'].argsFor(0)[0]).toBe('APP.MESSAGES.INFO.NODE_COPY.PARTIAL_SINGULAR'); }); it('notifies partially copy of more nodes out of a multiple selection of nodes', () => { @@ -184,9 +136,7 @@ describe('NodeCopyDirective', () => { service.contentCopied.next(createdItems); expect(service.copyNodes).toHaveBeenCalled(); - expect(notificationService.openSnackMessageAction).toHaveBeenCalledWith( - 'APP.MESSAGES.INFO.NODE_COPY.PARTIAL_PLURAL', 'APP.ACTIONS.UNDO', 10000 - ); + expect(snackBar.open['calls'].argsFor(0)[0]).toBe('APP.MESSAGES.INFO.NODE_COPY.PARTIAL_PLURAL'); }); it('notifies of failed copy of multiple nodes', () => { @@ -203,9 +153,7 @@ describe('NodeCopyDirective', () => { service.contentCopied.next(createdItems); expect(service.copyNodes).toHaveBeenCalled(); - expect(notificationService.openSnackMessageAction).toHaveBeenCalledWith( - 'APP.MESSAGES.INFO.NODE_COPY.FAIL_PLURAL', '', 3000 - ); + expect(snackBar.open['calls'].argsFor(0)[0]).toBe('APP.MESSAGES.INFO.NODE_COPY.FAIL_PLURAL'); }); it('notifies of failed copy of one node', () => { @@ -220,9 +168,7 @@ describe('NodeCopyDirective', () => { service.contentCopied.next(createdItems); expect(service.copyNodes).toHaveBeenCalled(); - expect(notificationService.openSnackMessageAction).toHaveBeenCalledWith( - 'APP.MESSAGES.INFO.NODE_COPY.FAIL_SINGULAR', '', 3000 - ); + expect(snackBar.open['calls'].argsFor(0)[0]).toBe('APP.MESSAGES.INFO.NODE_COPY.FAIL_SINGULAR'); }); it('notifies error if success message was not emitted', () => { @@ -235,7 +181,7 @@ describe('NodeCopyDirective', () => { service.contentCopied.next(); expect(service.copyNodes).toHaveBeenCalled(); - expect(notificationService.openSnackMessageAction).toHaveBeenCalledWith('APP.MESSAGES.ERRORS.GENERIC', '', 3000); + expect(snackBar.open['calls'].argsFor(0)[0]).toBe('APP.MESSAGES.ERRORS.GENERIC'); }); it('notifies permission error on copy of node', () => { @@ -247,9 +193,7 @@ describe('NodeCopyDirective', () => { element.triggerEventHandler('click', null); expect(service.copyNodes).toHaveBeenCalled(); - expect(notificationService.openSnackMessageAction).toHaveBeenCalledWith( - 'APP.MESSAGES.ERRORS.PERMISSION', '', 3000 - ); + expect(snackBar.open['calls'].argsFor(0)[0]).toBe('APP.MESSAGES.ERRORS.PERMISSION'); }); it('notifies generic error message on all errors, but 403', () => { @@ -261,9 +205,7 @@ describe('NodeCopyDirective', () => { element.triggerEventHandler('click', null); expect(service.copyNodes).toHaveBeenCalled(); - expect(notificationService.openSnackMessageAction).toHaveBeenCalledWith( - 'APP.MESSAGES.ERRORS.GENERIC', '', 3000 - ); + expect(snackBar.open['calls'].argsFor(0)[0]).toBe('APP.MESSAGES.ERRORS.GENERIC'); }); }); @@ -271,13 +213,13 @@ describe('NodeCopyDirective', () => { beforeEach(() => { spyOn(service, 'copyNodes').and.returnValue(Observable.of('OPERATION.SUCCES.CONTENT.COPY')); - spyOn(notificationService, 'openSnackMessageAction').and.returnValue({ + spyOn(snackBar, 'open').and.returnValue({ onAction: () => Observable.of({}) }); }); it('should delete the newly created node on Undo action', () => { - spyOn(nodesApiService, 'deleteNode').and.returnValue(Observable.of(null)); + spyOn(contentApi, 'deleteNode').and.returnValue(Observable.of(null)); component.selection = [{ entry: { id: 'node-to-copy-id', name: 'name' } }]; const createdItems = [{ entry: { id: 'copy-id', name: 'name' } }]; @@ -287,15 +229,13 @@ describe('NodeCopyDirective', () => { service.contentCopied.next(createdItems); expect(service.copyNodes).toHaveBeenCalled(); - expect(notificationService.openSnackMessageAction).toHaveBeenCalledWith( - 'APP.MESSAGES.INFO.NODE_COPY.SINGULAR', 'APP.ACTIONS.UNDO', 10000 - ); + expect(snackBar.open['calls'].argsFor(0)[0]).toBe('APP.MESSAGES.INFO.NODE_COPY.SINGULAR'); - expect(nodesApiService.deleteNode).toHaveBeenCalledWith(createdItems[0].entry.id, { permanent: true }); + expect(contentApi.deleteNode).toHaveBeenCalledWith(createdItems[0].entry.id, { permanent: true }); }); it('should delete also the node created inside an already existing folder from destination', () => { - const spyOnDeleteNode = spyOn(nodesApiService, 'deleteNode').and.returnValue(Observable.of(null)); + const spyOnDeleteNode = spyOn(contentApi, 'deleteNode').and.returnValue(Observable.of(null)); component.selection = [ { entry: { id: 'node-to-copy-1', name: 'name1' } }, @@ -311,9 +251,7 @@ describe('NodeCopyDirective', () => { service.contentCopied.next(createdItems); expect(service.copyNodes).toHaveBeenCalled(); - expect(notificationService.openSnackMessageAction).toHaveBeenCalledWith( - 'APP.MESSAGES.INFO.NODE_COPY.PLURAL', 'APP.ACTIONS.UNDO', 10000 - ); + expect(snackBar.open['calls'].argsFor(0)[0]).toBe('APP.MESSAGES.INFO.NODE_COPY.PLURAL'); expect(spyOnDeleteNode).toHaveBeenCalled(); expect(spyOnDeleteNode.calls.allArgs()) @@ -321,7 +259,7 @@ describe('NodeCopyDirective', () => { }); it('notifies when error occurs on Undo action', () => { - spyOn(nodesApiService, 'deleteNode').and.returnValue(Observable.throw(null)); + spyOn(contentApi, 'deleteNode').and.returnValue(Observable.throw(null)); component.selection = [{ entry: { id: 'node-to-copy-id', name: 'name' } }]; const createdItems = [{ entry: { id: 'copy-id', name: 'name' } }]; @@ -331,14 +269,12 @@ describe('NodeCopyDirective', () => { service.contentCopied.next(createdItems); expect(service.copyNodes).toHaveBeenCalled(); - expect(nodesApiService.deleteNode).toHaveBeenCalled(); - expect(notificationService.openSnackMessageAction['calls'].allArgs()) - .toEqual([['APP.MESSAGES.INFO.NODE_COPY.SINGULAR', 'APP.ACTIONS.UNDO', 10000], - ['APP.MESSAGES.ERRORS.GENERIC', '', 3000]]); + expect(contentApi.deleteNode).toHaveBeenCalled(); + expect(snackBar.open['calls'].argsFor(0)[0]).toEqual('APP.MESSAGES.INFO.NODE_COPY.SINGULAR'); }); it('notifies when some error of type Error occurs on Undo action', () => { - spyOn(nodesApiService, 'deleteNode').and.returnValue(Observable.throw(new Error('oops!'))); + spyOn(contentApi, 'deleteNode').and.returnValue(Observable.throw(new Error('oops!'))); component.selection = [{ entry: { id: 'node-to-copy-id', name: 'name' } }]; const createdItems = [{ entry: { id: 'copy-id', name: 'name' } }]; @@ -348,14 +284,12 @@ describe('NodeCopyDirective', () => { service.contentCopied.next(createdItems); expect(service.copyNodes).toHaveBeenCalled(); - expect(nodesApiService.deleteNode).toHaveBeenCalled(); - expect(notificationService.openSnackMessageAction['calls'].allArgs()) - .toEqual([['APP.MESSAGES.INFO.NODE_COPY.SINGULAR', 'APP.ACTIONS.UNDO', 10000], - ['APP.MESSAGES.ERRORS.GENERIC', '', 3000]]); + expect(contentApi.deleteNode).toHaveBeenCalled(); + expect(snackBar.open['calls'].argsFor(0)[0]).toEqual('APP.MESSAGES.INFO.NODE_COPY.SINGULAR'); }); it('notifies permission error when it occurs on Undo action', () => { - spyOn(nodesApiService, 'deleteNode').and.returnValue(Observable.throw(new Error(JSON.stringify({error: {statusCode: 403}})))); + spyOn(contentApi, 'deleteNode').and.returnValue(Observable.throw(new Error(JSON.stringify({error: {statusCode: 403}})))); component.selection = [{ entry: { id: 'node-to-copy-id', name: 'name' } }]; const createdItems = [{ entry: { id: 'copy-id', name: 'name' } }]; @@ -365,10 +299,8 @@ describe('NodeCopyDirective', () => { service.contentCopied.next(createdItems); expect(service.copyNodes).toHaveBeenCalled(); - expect(nodesApiService.deleteNode).toHaveBeenCalled(); - expect(notificationService.openSnackMessageAction['calls'].allArgs()) - .toEqual([['APP.MESSAGES.INFO.NODE_COPY.SINGULAR', 'APP.ACTIONS.UNDO', 10000], - ['APP.MESSAGES.ERRORS.PERMISSION', '', 3000]]); + expect(contentApi.deleteNode).toHaveBeenCalled(); + expect(snackBar.open['calls'].argsFor(0)[0]).toEqual('APP.MESSAGES.INFO.NODE_COPY.SINGULAR'); }); }); diff --git a/src/app/common/directives/node-copy.directive.ts b/src/app/common/directives/node-copy.directive.ts index 91c61268e..c483da02d 100644 --- a/src/app/common/directives/node-copy.directive.ts +++ b/src/app/common/directives/node-copy.directive.ts @@ -25,18 +25,21 @@ import { Directive, HostListener, Input } from '@angular/core'; import { Observable } from 'rxjs/Rx'; +import { MatSnackBar } from '@angular/material'; -import { TranslationService, NodesApiService, NotificationService } from '@alfresco/adf-core'; +import { TranslationService } from '@alfresco/adf-core'; import { MinimalNodeEntity } from 'alfresco-js-api'; import { NodeActionsService } from '../services/node-actions.service'; import { ContentManagementService } from '../services/content-management.service'; +import { ContentApiService } from '../../services/content-api.service'; @Directive({ - selector: '[app-copy-node]' + selector: '[acaCopyNode]' }) export class NodeCopyDirective { - @Input('app-copy-node') + // tslint:disable-next-line:no-input-rename + @Input('acaCopyNode') selection: MinimalNodeEntity[]; @HostListener('click') @@ -46,9 +49,9 @@ export class NodeCopyDirective { constructor( private content: ContentManagementService, - private notification: NotificationService, + private contentApi: ContentApiService, + private snackBar: MatSnackBar, private nodeActionsService: NodeActionsService, - private nodesApi: NodesApiService, private translation: TranslationService ) {} @@ -105,24 +108,27 @@ export class NodeCopyDirective { } const undo = (numberOfCopiedItems > 0) ? this.translation.instant('APP.ACTIONS.UNDO') : ''; - const withUndo = (numberOfCopiedItems > 0) ? '_WITH_UNDO' : ''; - this.translation.get(i18nMessageString, { success: numberOfCopiedItems, failed: failedItems }).subscribe(message => { - this.notification.openSnackMessageAction(message, undo, NodeActionsService[`SNACK_MESSAGE_DURATION${withUndo}`]) - .onAction() - .subscribe(() => this.deleteCopy(newItems)); - }); + const message = this.translation.instant(i18nMessageString, { success: numberOfCopiedItems, failed: failedItems }); + + this.snackBar + .open(message, undo, { + panelClass: 'info-snackbar', + duration: 3000 + }) + .onAction() + .subscribe(() => this.deleteCopy(newItems)); } private deleteCopy(nodes: MinimalNodeEntity[]) { const batch = this.nodeActionsService.flatten(nodes) .filter(item => item.entry) - .map(item => this.nodesApi.deleteNode(item.entry.id, { permanent: true })); + .map(item => this.contentApi.deleteNode(item.entry.id, { permanent: true })); Observable.forkJoin(...batch) .subscribe( () => { - this.content.nodeDeleted.next(null); + this.content.nodesDeleted.next(null); }, (error) => { let i18nMessageString = 'APP.MESSAGES.ERRORS.GENERIC'; @@ -137,8 +143,11 @@ export class NodeCopyDirective { i18nMessageString = 'APP.MESSAGES.ERRORS.PERMISSION'; } - this.translation.get(i18nMessageString).subscribe(message => { - this.notification.openSnackMessageAction(message, '', NodeActionsService.SNACK_MESSAGE_DURATION); + const message = this.translation.instant(i18nMessageString); + + this.snackBar.open(message, '', { + panelClass: 'error-snackbar', + duration: 3000 }); } ); diff --git a/src/app/common/directives/node-delete.directive.spec.ts b/src/app/common/directives/node-delete.directive.spec.ts index 05cce99df..a9087766b 100644 --- a/src/app/common/directives/node-delete.directive.spec.ts +++ b/src/app/common/directives/node-delete.directive.spec.ts @@ -23,20 +23,24 @@ * along with Alfresco. If not, see . */ -import { TestBed, ComponentFixture } from '@angular/core/testing'; +import { TestBed, ComponentFixture, fakeAsync, tick } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; -import { TranslationService, NodesApiService, NotificationService, CoreModule } from '@alfresco/adf-core'; import { Component, DebugElement } from '@angular/core'; -import { Observable } from 'rxjs/Rx'; import { NodeDeleteDirective } from './node-delete.directive'; -import { ContentManagementService } from '../services/content-management.service'; -import { MatSnackBarModule } from '@angular/material'; -import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; -import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { EffectsModule, Actions, ofType } from '@ngrx/effects'; +import { NodeEffects } from '../../store/effects/node.effects'; +import { + SnackbarInfoAction, SNACKBAR_INFO, SNACKBAR_ERROR, + SnackbarErrorAction, SnackbarWarningAction, SNACKBAR_WARNING +} from '../../store/actions'; +import { map } from 'rxjs/operators'; +import { AppTestingModule } from '../../testing/app-testing.module'; +import { ContentApiService } from '../../services/content-api.service'; +import { Observable } from 'rxjs/Rx'; @Component({ - template: '
' + template: '
' }) class TestComponent { selection; @@ -46,78 +50,75 @@ describe('NodeDeleteDirective', () => { let component: TestComponent; let fixture: ComponentFixture; let element: DebugElement; - let notificationService: NotificationService; - let translationService: TranslationService; - let contentService: ContentManagementService; - let nodeApiService: NodesApiService; - let spySnackBar; + let actions$: Actions; + let contentApi: ContentApiService; beforeEach(() => { TestBed.configureTestingModule({ imports: [ - BrowserAnimationsModule, - FormsModule, - ReactiveFormsModule, - CoreModule, - MatSnackBarModule + AppTestingModule, + EffectsModule.forRoot([NodeEffects]) ], declarations: [ NodeDeleteDirective, TestComponent - ], - providers: [ - ContentManagementService ] }); + contentApi = TestBed.get(ContentApiService); + actions$ = TestBed.get(Actions); + fixture = TestBed.createComponent(TestComponent); component = fixture.componentInstance; element = fixture.debugElement.query(By.directive(NodeDeleteDirective)); - notificationService = TestBed.get(NotificationService); - translationService = TestBed.get(TranslationService); - nodeApiService = TestBed.get(NodesApiService); - contentService = TestBed.get(ContentManagementService); - }); - - beforeEach(() => { - spyOn(translationService, 'get').and.callFake((key) => { - return Observable.of(key); - }); }); describe('Delete action', () => { - beforeEach(() => { - spyOn(notificationService, 'openSnackMessageAction').and.callThrough(); - }); + it('should raise info message on successful single file deletion', fakeAsync(done => { + spyOn(contentApi, 'deleteNode').and.returnValue(Observable.of(null)); - it('notifies file deletion', () => { - spyOn(nodeApiService, 'deleteNode').and.returnValue(Observable.of(null)); + actions$.pipe( + ofType(SNACKBAR_INFO), + map(action => { + done(); + }) + ); component.selection = [{ entry: { id: '1', name: 'name1' } }]; fixture.detectChanges(); element.triggerEventHandler('click', null); - expect(notificationService.openSnackMessageAction).toHaveBeenCalledWith( - 'APP.MESSAGES.INFO.NODE_DELETION.SINGULAR', 'APP.ACTIONS.UNDO', 10000 - ); - }); + tick(); + })); - it('notifies failed file deletion', () => { - spyOn(nodeApiService, 'deleteNode').and.returnValue(Observable.throw(null)); + it('should raise error message on failed single file deletion', fakeAsync(done => { + spyOn(contentApi, 'deleteNode').and.returnValue(Observable.throw(null)); + + actions$.pipe( + ofType(SNACKBAR_ERROR), + map(action => { + done(); + }) + ); component.selection = [{ entry: { id: '1', name: 'name1' } }]; fixture.detectChanges(); element.triggerEventHandler('click', null); - expect(notificationService.openSnackMessageAction).toHaveBeenCalledWith( - 'APP.MESSAGES.ERRORS.NODE_DELETION', '', 10000 - ); - }); + tick(); + })); - it('notifies files deletion', () => { - spyOn(nodeApiService, 'deleteNode').and.returnValue(Observable.of(null)); + it('should raise info message on successful multiple files deletion', fakeAsync(done => { + spyOn(contentApi, 'deleteNode').and.returnValue(Observable.of(null)); + + actions$.pipe( + ofType(SNACKBAR_INFO), + map(action => { + done(); + }) + ); component.selection = [ { entry: { id: '1', name: 'name1' } }, @@ -127,13 +128,18 @@ describe('NodeDeleteDirective', () => { fixture.detectChanges(); element.triggerEventHandler('click', null); - expect(notificationService.openSnackMessageAction).toHaveBeenCalledWith( - 'APP.MESSAGES.INFO.NODE_DELETION.PLURAL', 'APP.ACTIONS.UNDO', 10000 - ); - }); + tick(); + })); - it('notifies failed files deletion', () => { - spyOn(nodeApiService, 'deleteNode').and.returnValue(Observable.throw(null)); + it('should raise error message failed multiple files deletion', fakeAsync(done => { + spyOn(contentApi, 'deleteNode').and.returnValue(Observable.throw(null)); + + actions$.pipe( + ofType(SNACKBAR_ERROR), + map(action => { + done(); + }) + ); component.selection = [ { entry: { id: '1', name: 'name1' } }, @@ -143,13 +149,11 @@ describe('NodeDeleteDirective', () => { fixture.detectChanges(); element.triggerEventHandler('click', null); - expect(notificationService.openSnackMessageAction).toHaveBeenCalledWith( - 'APP.MESSAGES.ERRORS.NODE_DELETION_PLURAL', '', 10000 - ); - }); + tick(); + })); - it('notifies partial deletion when only one file is successful', () => { - spyOn(nodeApiService, 'deleteNode').and.callFake((id) => { + it('should raise warning message when only one file is successful', fakeAsync(done => { + spyOn(contentApi, 'deleteNode').and.callFake((id) => { if (id === '1') { return Observable.throw(null); } else { @@ -157,6 +161,13 @@ describe('NodeDeleteDirective', () => { } }); + actions$.pipe( + ofType(SNACKBAR_WARNING), + map(action => { + done(); + }) + ); + component.selection = [ { entry: { id: '1', name: 'name1' } }, { entry: { id: '2', name: 'name2' } } @@ -165,13 +176,11 @@ describe('NodeDeleteDirective', () => { fixture.detectChanges(); element.triggerEventHandler('click', null); - expect(notificationService.openSnackMessageAction).toHaveBeenCalledWith( - 'APP.MESSAGES.INFO.NODE_DELETION.PARTIAL_SINGULAR', 'APP.ACTIONS.UNDO', 10000 - ); - }); + tick(); + })); - it('notifies partial deletion when some files are successful', () => { - spyOn(nodeApiService, 'deleteNode').and.callFake((id) => { + it('should raise warning message when some files are successfully deleted', fakeAsync(done => { + spyOn(contentApi, 'deleteNode').and.callFake((id) => { if (id === '1') { return Observable.throw(null); } @@ -185,6 +194,13 @@ describe('NodeDeleteDirective', () => { } }); + actions$.pipe( + ofType(SNACKBAR_WARNING), + map(action => { + done(); + }) + ); + component.selection = [ { entry: { id: '1', name: 'name1' } }, { entry: { id: '2', name: 'name2' } }, @@ -194,23 +210,18 @@ describe('NodeDeleteDirective', () => { fixture.detectChanges(); element.triggerEventHandler('click', null); - expect(notificationService.openSnackMessageAction).toHaveBeenCalledWith( - 'APP.MESSAGES.INFO.NODE_DELETION.PARTIAL_PLURAL', 'APP.ACTIONS.UNDO', 10000 - ); - }); + tick(); + })); }); + /* describe('Restore action', () => { beforeEach(() => { - spyOn(nodeApiService, 'deleteNode').and.returnValue(Observable.of(null)); - - spySnackBar = spyOn(notificationService, 'openSnackMessageAction').and.returnValue({ - onAction: () => Observable.of({}) - }); + spyOn(alfrescoApiService.nodesApi, 'deleteNode').and.returnValue(Promise.resolve(null)); }); it('notifies failed file on on restore', () => { - spyOn(nodeApiService, 'restoreNode').and.returnValue(Observable.throw(null)); + spyOn(alfrescoApiService.nodesApi, 'restoreNode').and.returnValue(Promise.reject(null)); component.selection = [ { entry: { id: '1', name: 'name1' } } @@ -224,7 +235,7 @@ describe('NodeDeleteDirective', () => { }); it('notifies failed files on on restore', () => { - spyOn(nodeApiService, 'restoreNode').and.returnValue(Observable.throw(null)); + spyOn(alfrescoApiService.nodesApi, 'restoreNode').and.returnValue(Promise.reject(null)); component.selection = [ { entry: { id: '1', name: 'name1' } }, @@ -240,11 +251,11 @@ describe('NodeDeleteDirective', () => { it('signals files restored', () => { spyOn(contentService.nodeRestored, 'next'); - spyOn(nodeApiService, 'restoreNode').and.callFake((id) => { + spyOn(alfrescoApiService.nodesApi, 'restoreNode').and.callFake((id) => { if (id === '1') { - return Observable.of(null); + return Promise.resolve(null); } else { - return Observable.throw(null); + return Promise.reject(null); } }); @@ -259,4 +270,5 @@ describe('NodeDeleteDirective', () => { expect(contentService.nodeRestored.next).toHaveBeenCalled(); }); }); + */ }); diff --git a/src/app/common/directives/node-delete.directive.ts b/src/app/common/directives/node-delete.directive.ts index dc76f3ab8..c66cc4562 100644 --- a/src/app/common/directives/node-delete.directive.ts +++ b/src/app/common/directives/node-delete.directive.ts @@ -24,222 +24,36 @@ */ import { Directive, HostListener, Input } from '@angular/core'; - -import { TranslationService, NodesApiService, NotificationService } from '@alfresco/adf-core'; import { MinimalNodeEntity } from 'alfresco-js-api'; -import { Observable } from 'rxjs/Rx'; - -import { ContentManagementService } from '../services/content-management.service'; +import { Store } from '@ngrx/store'; +import { AppStore } from '../../store/states/app.state'; +import { DeleteNodesAction } from '../../store/actions'; +import { NodeInfo } from '../../store/models'; @Directive({ - selector: '[app-delete-node]' + selector: '[acaDeleteNode]' }) export class NodeDeleteDirective { - static RESTORE_MESSAGE_DURATION: number = 3000; - static DELETE_MESSAGE_DURATION: number = 10000; - @Input('app-delete-node') + // tslint:disable-next-line:no-input-rename + @Input('acaDeleteNode') selection: MinimalNodeEntity[]; + constructor(private store: Store) {} + @HostListener('click') onClick() { - this.deleteSelected(); - } + if (this.selection && this.selection.length > 0) { + const toDelete: NodeInfo[] = this.selection.map(node => { + const { name } = node.entry; + const id = node.entry.nodeId || node.entry.id; - constructor( - private nodesApi: NodesApiService, - private notification: NotificationService, - private content: ContentManagementService, - private translation: TranslationService - ) {} - - private deleteSelected(): void { - const batch = []; - - this.selection.forEach((node) => { - batch.push(this.performAction('delete', node.entry)); - }); - - Observable.forkJoin(...batch) - .subscribe( - (data) => { - const processedData = this.processStatus(data); - - this.getDeleteMesssage(processedData) - .subscribe((message) => { - const withUndo = processedData.someSucceeded ? this.translation.translate.instant('APP.ACTIONS.UNDO') : ''; - - this.notification.openSnackMessageAction(message, withUndo, NodeDeleteDirective.DELETE_MESSAGE_DURATION) - .onAction() - .subscribe(() => this.restore(processedData.success)); - - if (processedData.someSucceeded) { - this.content.nodeDeleted.next(null); - } - }); - } - ); - } - - private restore(items): void { - const batch = []; - - items.forEach((item) => { - batch.push(this.performAction('restore', item)); - }); - - Observable.forkJoin(...batch) - .subscribe( - (data) => { - const processedData = this.processStatus(data); - - if (processedData.failed.length) { - this.getRestoreMessage(processedData) - .subscribe((message) => { - this.notification.openSnackMessageAction( - message, '' , NodeDeleteDirective.RESTORE_MESSAGE_DURATION - ); - }); - } - - if (processedData.someSucceeded) { - this.content.nodeRestored.next(null); - } - } - ); - } - - private performAction(action: string, item: any): Observable { - const { name } = item; - // Check if there's nodeId for Shared Files - const id = item.nodeId || item.id; - - let performedAction: any = null; - - if (action === 'delete') { - performedAction = this.nodesApi.deleteNode(id); - } else { - performedAction = this.nodesApi.restoreNode(id); - } - - return performedAction - .map(() => { return { id, - name, - status: 1 + name }; - }) - .catch((error: any) => { - return Observable.of({ - id, - name, - status: 0 - }); }); - } - - private processStatus(data): any { - const deleteStatus = { - success: [], - failed: [], - get someFailed() { - return !!(this.failed.length); - }, - get someSucceeded() { - return !!(this.success.length); - }, - get oneFailed() { - return this.failed.length === 1; - }, - get oneSucceeded() { - return this.success.length === 1; - }, - get allSucceeded() { - return this.someSucceeded && !this.someFailed; - }, - get allFailed() { - return this.someFailed && !this.someSucceeded; - } - }; - - return data.reduce( - (acc, next) => { - if (next.status === 1) { - acc.success.push(next); - } else { - acc.failed.push(next); - } - - return acc; - }, - deleteStatus - ); - } - - private getRestoreMessage(status): Observable { - if (status.someFailed && !status.oneFailed) { - return this.translation.get( - 'APP.MESSAGES.ERRORS.NODE_RESTORE_PLURAL', - { number: status.failed.length } - ); - } - - if (status.oneFailed) { - return this.translation.get( - 'APP.MESSAGES.ERRORS.NODE_RESTORE', - { name: status.failed[0].name } - ); - } - } - - private getDeleteMesssage(status): Observable { - if (status.allFailed && !status.oneFailed) { - return this.translation.get( - 'APP.MESSAGES.ERRORS.NODE_DELETION_PLURAL', - { number: status.failed.length } - ); - } - - if (status.allSucceeded && !status.oneSucceeded) { - return this.translation.get( - 'APP.MESSAGES.INFO.NODE_DELETION.PLURAL', - { number: status.success.length } - ); - } - - if (status.someFailed && status.someSucceeded && !status.oneSucceeded) { - return this.translation.get( - 'APP.MESSAGES.INFO.NODE_DELETION.PARTIAL_PLURAL', - { - success: status.success.length, - failed: status.failed.length - } - ); - } - - if (status.someFailed && status.oneSucceeded) { - return this.translation.get( - 'APP.MESSAGES.INFO.NODE_DELETION.PARTIAL_SINGULAR', - { - success: status.success.length, - failed: status.failed.length - } - ); - } - - if (status.oneFailed && !status.someSucceeded) { - return this.translation.get( - 'APP.MESSAGES.ERRORS.NODE_DELETION', - { name: status.failed[0].name } - ); - } - - if (status.oneSucceeded && !status.someFailed) { - return this.translation.get( - 'APP.MESSAGES.INFO.NODE_DELETION.SINGULAR', - { name: status.success[0].name } - ); + this.store.dispatch(new DeleteNodesAction(toDelete)); } } } diff --git a/src/app/common/directives/node-download.directive.ts b/src/app/common/directives/node-download.directive.ts deleted file mode 100644 index d2a6f4275..000000000 --- a/src/app/common/directives/node-download.directive.ts +++ /dev/null @@ -1,126 +0,0 @@ -/*! - * @license - * Copyright 2016 Alfresco Software, Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { Directive, Input, HostListener } from '@angular/core'; -import { MatDialog } from '@angular/material'; -import { MinimalNodeEntity } from 'alfresco-js-api'; -import { AlfrescoApiService } from '@alfresco/adf-core'; -import { DownloadZipDialogComponent } from '@alfresco/adf-content-services'; - -/** @deprecated workaround for the ADF 2.3.0 regression. */ -@Directive({ - selector: '[appNodeDownload]' -}) -export class NodeDownloadDirective { - - /** Nodes to download. */ - // tslint:disable-next-line:no-input-rename - @Input('appNodeDownload') - nodes: MinimalNodeEntity[]; - - @HostListener('click') - onClick() { - this.downloadNodes(this.nodes); - } - - constructor( - private apiService: AlfrescoApiService, - private dialog: MatDialog) { - } - - /** - * Downloads multiple selected nodes. - * Packs result into a .ZIP archive if there is more than one node selected. - * @param selection Multiple selected nodes to download - */ - downloadNodes(selection: Array) { - if (!selection || selection.length === 0) { - return; - } - - if (selection.length === 1) { - this.downloadNode(selection[0]); - } else { - this.downloadZip(selection); - } - } - - /** - * Downloads a single node. - * Packs result into a .ZIP archive is the node is a Folder. - * @param node Node to download - */ - downloadNode(node: MinimalNodeEntity) { - if (node && node.entry) { - const entry = node.entry; - - if (entry.isFile) { - this.downloadFile(node); - } - - if (entry.isFolder) { - this.downloadZip([node]); - } - - // Check if there's nodeId for Shared Files - if (!entry.isFile && !entry.isFolder && ( entry).nodeId) { - this.downloadFile(node); - } - } - } - - private downloadFile(node: MinimalNodeEntity) { - if (node && node.entry) { - const contentApi = this.apiService.getInstance().content; - const id = ( node.entry).nodeId || node.entry.id; - - const url = contentApi.getContentUrl(id, true); - const fileName = node.entry.name; - - this.download(url, fileName); - } - } - - private downloadZip(selection: Array) { - if (selection && selection.length > 0) { - // nodeId for Shared node - const nodeIds = selection.map((node: any) => (node.entry.nodeId || node.entry.id)); - - this.dialog.open(DownloadZipDialogComponent, { - width: '600px', - disableClose: true, - data: { - nodeIds - } - }); - } - } - - private download(url: string, fileName: string) { - if (url && fileName) { - const link = document.createElement('a'); - - link.style.display = 'none'; - link.download = fileName; - link.href = url; - - document.body.appendChild(link); - link.click(); - document.body.removeChild(link); - } - } -} diff --git a/src/app/common/directives/node-info.directive.spec.ts b/src/app/common/directives/node-info.directive.spec.ts deleted file mode 100644 index c063e2e80..000000000 --- a/src/app/common/directives/node-info.directive.spec.ts +++ /dev/null @@ -1,121 +0,0 @@ -/*! - * @license - * Alfresco Example Content Application - * - * Copyright (C) 2005 - 2018 Alfresco Software Limited - * - * This file is part of the Alfresco Example Content Application. - * If the software was purchased under a paid Alfresco license, the terms of - * the paid license agreement will prevail. Otherwise, the software is - * provided under the following open source license terms: - * - * The Alfresco Example Content Application is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * The Alfresco Example Content Application is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Alfresco. If not, see . - */ - -import { Component } from '@angular/core'; -import { ComponentFixture, TestBed, async, fakeAsync, tick } from '@angular/core/testing'; -import { AlfrescoApiService, CoreModule } from '@alfresco/adf-core'; -import { NodeInfoDirective } from './node-info.directive'; - -@Component({ - template: '
' -}) -class TestComponent { - selection; -} - -describe('NodeInfoDirective', () => { - let fixture: ComponentFixture; - let component: TestComponent; - let apiService: AlfrescoApiService; - let nodeService; - - beforeEach(async(() => { - TestBed.configureTestingModule({ - imports: [ - CoreModule - ], - declarations: [ - TestComponent, - NodeInfoDirective - ] - }); - - fixture = TestBed.createComponent(TestComponent); - component = fixture.componentInstance; - apiService = TestBed.get(AlfrescoApiService); - })); - - beforeEach(() => { - nodeService = apiService.getInstance().nodes; - - spyOn(nodeService, 'getNodeInfo').and.returnValue(Promise.resolve({ - entry: { name: 'borg' } - })); - }); - - it('should not get node info onInit when selection is empty', () => { - component.selection = []; - - fixture.detectChanges(); - - expect(nodeService.getNodeInfo).not.toHaveBeenCalled(); - }); - - it('should get node info onInit when selection is not empty', () => { - component.selection = [{ entry: { id: 'id' } }]; - - fixture.detectChanges(); - - expect(nodeService.getNodeInfo).toHaveBeenCalled(); - }); - - it('should not get node info on event when selection is empty', () => { - component.selection = []; - - fixture.detectChanges(); - - document.dispatchEvent(new CustomEvent('click')); - - expect(nodeService.getNodeInfo).not.toHaveBeenCalled(); - }); - - it('should get node info on event from selection', () => { - component.selection = [{ entry: { id: 'id' } }]; - - fixture.detectChanges(); - - document.dispatchEvent(new CustomEvent('click')); - - expect(nodeService.getNodeInfo).toHaveBeenCalledWith('id', { include: [ 'allowableOperations' ] }); - }); - - - it('should get node info of last entry when selecting multiple nodes', fakeAsync(() => { - component.selection = [ - { entry: { id: 'id1', isFile: true } }, - { entry: { id: 'id2', isFile: true } }, - { entry: { id: 'id3', isFile: true } } - ]; - - fixture.detectChanges(); - - document.dispatchEvent(new CustomEvent('click')); - - fixture.detectChanges(); - tick(); - - expect(nodeService.getNodeInfo).toHaveBeenCalledWith('id3', { include: [ 'allowableOperations' ] }); - })); -}); diff --git a/src/app/common/directives/node-info.directive.ts b/src/app/common/directives/node-info.directive.ts deleted file mode 100644 index 754b1a7df..000000000 --- a/src/app/common/directives/node-info.directive.ts +++ /dev/null @@ -1,82 +0,0 @@ -/*! - * @license - * Alfresco Example Content Application - * - * Copyright (C) 2005 - 2018 Alfresco Software Limited - * - * This file is part of the Alfresco Example Content Application. - * If the software was purchased under a paid Alfresco license, the terms of - * the paid license agreement will prevail. Otherwise, the software is - * provided under the following open source license terms: - * - * The Alfresco Example Content Application is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * The Alfresco Example Content Application is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Alfresco. If not, see . - */ - -import { Directive, HostListener, Input, Output, EventEmitter, OnInit } from '@angular/core'; -import { AlfrescoApiService } from '@alfresco/adf-core'; -import { MinimalNodeEntity, MinimalNodeEntryEntity } from 'alfresco-js-api'; - -@Directive({ - selector: '[app-node-info]', - exportAs: 'nodeInfo' -}) - -export class NodeInfoDirective implements OnInit { - @Input('app-node-info') selection: MinimalNodeEntity[]; - @Output() changed: EventEmitter = new EventEmitter(); - @Output() error: EventEmitter = new EventEmitter(); - - node: MinimalNodeEntryEntity; - loading: boolean = null; - - @HostListener('document:node-click', ['$event']) - onClick(event) { - this.getNodeInfo(); - } - - constructor(private apiService: AlfrescoApiService) {} - - ngOnInit() { - this.getNodeInfo(); - } - - getNodeInfo() { - if (!this.selection.length) { - this.node = null; - this.loading = false; - this.changed.emit(null); - return; - } - - const node = this.selection[this.selection.length - 1]; - - if (node) { - this.loading = true; - - this.apiService.getInstance().nodes - .getNodeInfo((node.entry).nodeId || node.entry.id, { - include: ['allowableOperations'] - }) - .then((data: MinimalNodeEntryEntity) => { - this.node = data; - this.changed.emit(data); - this.loading = false; - }) - .catch(() => { - this.error.emit(); - this.loading = false; - }); - } - } -} diff --git a/src/app/common/directives/node-move.directive.spec.ts b/src/app/common/directives/node-move.directive.spec.ts index e69699849..e23209b5a 100644 --- a/src/app/common/directives/node-move.directive.spec.ts +++ b/src/app/common/directives/node-move.directive.spec.ts @@ -24,19 +24,22 @@ */ import { Component, DebugElement } from '@angular/core'; -import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { ComponentFixture, TestBed, fakeAsync } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; import { Observable } from 'rxjs/Rx'; -import { TranslationService, NodesApiService, NotificationService, CoreModule } from '@alfresco/adf-core'; -import { DocumentListService } from '@alfresco/adf-content-services'; -import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; - +import { MatSnackBar } from '@angular/material'; +import { TranslationService } from '@alfresco/adf-core'; import { NodeActionsService } from '../services/node-actions.service'; import { NodeMoveDirective } from './node-move.directive'; -import { ContentManagementService } from '../services/content-management.service'; +import { EffectsModule, Actions, ofType } from '@ngrx/effects'; +import { NodeEffects } from '../../store/effects/node.effects'; +import { SnackbarErrorAction, SNACKBAR_ERROR } from '../../store/actions'; +import { map } from 'rxjs/operators'; +import { AppTestingModule } from '../../testing/app-testing.module'; +import { ContentApiService } from '../../services/content-api.service'; @Component({ - template: '
' + template: '
' }) class TestComponent { selection; @@ -46,54 +49,52 @@ describe('NodeMoveDirective', () => { let fixture: ComponentFixture; let component: TestComponent; let element: DebugElement; - let notificationService: NotificationService; - let nodesApiService: NodesApiService; let service: NodeActionsService; + let actions$: Actions; let translationService: TranslationService; + let contentApi: ContentApiService; + let snackBar: MatSnackBar; beforeEach(() => { TestBed.configureTestingModule({ imports: [ - BrowserAnimationsModule, - CoreModule + AppTestingModule, + EffectsModule.forRoot([NodeEffects]) ], declarations: [ NodeMoveDirective, TestComponent - ], - providers: [ - DocumentListService, - ContentManagementService, - NodeActionsService ] }); + contentApi = TestBed.get(ContentApiService); + translationService = TestBed.get(TranslationService); + + actions$ = TestBed.get(Actions); fixture = TestBed.createComponent(TestComponent); component = fixture.componentInstance; element = fixture.debugElement.query(By.directive(NodeMoveDirective)); - notificationService = TestBed.get(NotificationService); - nodesApiService = TestBed.get(NodesApiService); service = TestBed.get(NodeActionsService); - translationService = TestBed.get(TranslationService); + snackBar = TestBed.get(MatSnackBar); }); beforeEach(() => { - spyOn(translationService, 'get').and.callFake((keysArray) => { + spyOn(translationService, 'instant').and.callFake((keysArray) => { if (Array.isArray(keysArray)) { const processedKeys = {}; keysArray.forEach((key) => { processedKeys[key] = key; }); - return Observable.of(processedKeys); + return processedKeys; } else { - return Observable.of(keysArray); + return keysArray; } }); }); describe('Move node action', () => { beforeEach(() => { - spyOn(notificationService, 'openSnackMessageAction').and.callThrough(); + spyOn(snackBar, 'open').and.callThrough(); }); it('notifies successful move of a node', () => { @@ -114,9 +115,7 @@ describe('NodeMoveDirective', () => { service.contentMoved.next(moveResponse); expect(service.moveNodes).toHaveBeenCalled(); - expect(notificationService.openSnackMessageAction).toHaveBeenCalledWith( - 'APP.MESSAGES.INFO.NODE_MOVE.SINGULAR', 'APP.ACTIONS.UNDO', 10000 - ); + expect(snackBar.open['calls'].argsFor(0)[0]).toBe('APP.MESSAGES.INFO.NODE_MOVE.SINGULAR'); }); it('notifies successful move of multiple nodes', () => { @@ -139,9 +138,7 @@ describe('NodeMoveDirective', () => { service.contentMoved.next(moveResponse); expect(service.moveNodes).toHaveBeenCalled(); - expect(notificationService.openSnackMessageAction).toHaveBeenCalledWith( - 'APP.MESSAGES.INFO.NODE_MOVE.PLURAL', 'APP.ACTIONS.UNDO', 10000 - ); + expect(snackBar.open['calls'].argsFor(0)[0]).toBe('APP.MESSAGES.INFO.NODE_MOVE.PLURAL'); }); it('notifies partial move of a node', () => { @@ -162,9 +159,7 @@ describe('NodeMoveDirective', () => { service.contentMoved.next(moveResponse); expect(service.moveNodes).toHaveBeenCalled(); - expect(notificationService.openSnackMessageAction).toHaveBeenCalledWith( - 'APP.MESSAGES.INFO.NODE_MOVE.PARTIAL.SINGULAR', 'APP.ACTIONS.UNDO', 10000 - ); + expect(snackBar.open['calls'].argsFor(0)[0]).toBe('APP.MESSAGES.INFO.NODE_MOVE.PARTIAL.SINGULAR'); }); it('notifies partial move of multiple nodes', () => { @@ -187,9 +182,7 @@ describe('NodeMoveDirective', () => { service.contentMoved.next(moveResponse); expect(service.moveNodes).toHaveBeenCalled(); - expect(notificationService.openSnackMessageAction).toHaveBeenCalledWith( - 'APP.MESSAGES.INFO.NODE_MOVE.PARTIAL.PLURAL', 'APP.ACTIONS.UNDO', 10000 - ); + expect(snackBar.open['calls'].argsFor(0)[0]).toBe('APP.MESSAGES.INFO.NODE_MOVE.PARTIAL.PLURAL'); }); it('notifies successful move and the number of nodes that could not be moved', () => { @@ -211,9 +204,8 @@ describe('NodeMoveDirective', () => { service.contentMoved.next(moveResponse); expect(service.moveNodes).toHaveBeenCalled(); - expect(notificationService.openSnackMessageAction).toHaveBeenCalledWith( - 'APP.MESSAGES.INFO.NODE_MOVE.SINGULAR APP.MESSAGES.INFO.NODE_MOVE.PARTIAL.FAIL', 'APP.ACTIONS.UNDO', 10000 - ); + expect(snackBar.open['calls'].argsFor(0)[0]) + .toBe('APP.MESSAGES.INFO.NODE_MOVE.SINGULAR APP.MESSAGES.INFO.NODE_MOVE.PARTIAL.FAIL'); }); it('notifies successful move and the number of partially moved ones', () => { @@ -235,9 +227,8 @@ describe('NodeMoveDirective', () => { service.contentMoved.next(moveResponse); expect(service.moveNodes).toHaveBeenCalled(); - expect(notificationService.openSnackMessageAction).toHaveBeenCalledWith( - 'APP.MESSAGES.INFO.NODE_MOVE.SINGULAR APP.MESSAGES.INFO.NODE_MOVE.PARTIAL.SINGULAR', 'APP.ACTIONS.UNDO', 10000 - ); + expect(snackBar.open['calls'].argsFor(0)[0]) + .toBe('APP.MESSAGES.INFO.NODE_MOVE.SINGULAR APP.MESSAGES.INFO.NODE_MOVE.PARTIAL.SINGULAR'); }); it('notifies error if success message was not emitted', () => { @@ -257,7 +248,7 @@ describe('NodeMoveDirective', () => { service.contentMoved.next(moveResponse); expect(service.moveNodes).toHaveBeenCalled(); - expect(notificationService.openSnackMessageAction).toHaveBeenCalledWith('APP.MESSAGES.ERRORS.GENERIC', '', 3000); + expect(snackBar.open['calls'].argsFor(0)[0]).toBe('APP.MESSAGES.ERRORS.GENERIC'); }); it('notifies permission error on move of node', () => { @@ -269,9 +260,7 @@ describe('NodeMoveDirective', () => { element.triggerEventHandler('click', null); expect(service.moveNodes).toHaveBeenCalled(); - expect(notificationService.openSnackMessageAction).toHaveBeenCalledWith( - 'APP.MESSAGES.ERRORS.PERMISSION', '', 3000 - ); + expect(snackBar.open['calls'].argsFor(0)[0]).toBe('APP.MESSAGES.ERRORS.PERMISSION'); }); it('notifies generic error message on all errors, but 403', () => { @@ -283,9 +272,7 @@ describe('NodeMoveDirective', () => { element.triggerEventHandler('click', null); expect(service.moveNodes).toHaveBeenCalled(); - expect(notificationService.openSnackMessageAction).toHaveBeenCalledWith( - 'APP.MESSAGES.ERRORS.GENERIC', '', 3000 - ); + expect(snackBar.open['calls'].argsFor(0)[0]).toBe('APP.MESSAGES.ERRORS.GENERIC'); }); it('notifies conflict error message on 409', () => { @@ -297,9 +284,7 @@ describe('NodeMoveDirective', () => { element.triggerEventHandler('click', null); expect(service.moveNodes).toHaveBeenCalled(); - expect(notificationService.openSnackMessageAction).toHaveBeenCalledWith( - 'APP.MESSAGES.ERRORS.NODE_MOVE', '', 3000 - ); + expect(snackBar.open['calls'].argsFor(0)[0]).toBe('APP.MESSAGES.ERRORS.NODE_MOVE'); }); it('notifies error if move response has only failed items', () => { @@ -320,9 +305,7 @@ describe('NodeMoveDirective', () => { service.contentMoved.next(moveResponse); expect(service.moveNodes).toHaveBeenCalled(); - expect(notificationService.openSnackMessageAction).toHaveBeenCalledWith( - 'APP.MESSAGES.ERRORS.GENERIC', '', 3000 - ); + expect(snackBar.open['calls'].argsFor(0)[0]).toBe('APP.MESSAGES.ERRORS.GENERIC'); }); }); @@ -330,11 +313,11 @@ describe('NodeMoveDirective', () => { beforeEach(() => { spyOn(service, 'moveNodes').and.returnValue(Observable.of('OPERATION.SUCCES.CONTENT.MOVE')); - spyOn(notificationService, 'openSnackMessageAction').and.returnValue({ + spyOn(snackBar, 'open').and.returnValue({ onAction: () => Observable.of({}) }); - spyOn(notificationService, 'openSnackMessage').and.callThrough(); + // spyOn(snackBar, 'open').and.callThrough(); }); it('should move node back to initial parent, after succeeded move', () => { @@ -355,8 +338,7 @@ describe('NodeMoveDirective', () => { expect(service.moveNodeAction) .toHaveBeenCalledWith(movedItems.succeeded[0].itemMoved.entry, movedItems.succeeded[0].initialParentId); - expect(notificationService.openSnackMessageAction) - .toHaveBeenCalledWith('APP.MESSAGES.INFO.NODE_MOVE.SINGULAR', 'APP.ACTIONS.UNDO', 10000); + expect(snackBar.open['calls'].argsFor(0)[0]).toBe('APP.MESSAGES.INFO.NODE_MOVE.SINGULAR'); }); it('should move node back to initial parent, after succeeded move of a single file', () => { @@ -377,13 +359,12 @@ describe('NodeMoveDirective', () => { service.contentMoved.next(movedItems); expect(service.moveNodeAction).toHaveBeenCalledWith(node.entry, initialParent); - expect(notificationService.openSnackMessageAction) - .toHaveBeenCalledWith('APP.MESSAGES.INFO.NODE_MOVE.SINGULAR', 'APP.ACTIONS.UNDO', 10000); + expect(snackBar.open['calls'].argsFor(0)[0]).toBe('APP.MESSAGES.INFO.NODE_MOVE.SINGULAR'); }); it('should restore deleted folder back to initial parent, after succeeded moving all its files', () => { // when folder was deleted after all its children were moved to a folder with the same name from destination - spyOn(nodesApiService, 'restoreNode').and.returnValue(Observable.of(null)); + spyOn(contentApi, 'restoreNode').and.returnValue(Observable.of(null)); const initialParent = 'parent-id-0'; const node = { entry: { id: 'folder-to-move-id', name: 'conflicting-name', parentId: initialParent, isFolder: true } }; @@ -402,13 +383,17 @@ describe('NodeMoveDirective', () => { element.triggerEventHandler('click', null); service.contentMoved.next(movedItems); - expect(nodesApiService.restoreNode).toHaveBeenCalled(); - expect(notificationService.openSnackMessageAction) - .toHaveBeenCalledWith('APP.MESSAGES.INFO.NODE_MOVE.SINGULAR', 'APP.ACTIONS.UNDO', 10000); + expect(contentApi.restoreNode).toHaveBeenCalled(); + expect(snackBar.open['calls'].argsFor(0)[0]).toBe('APP.MESSAGES.INFO.NODE_MOVE.SINGULAR'); }); - it('should notify when error occurs on Undo Move action', () => { - spyOn(nodesApiService, 'restoreNode').and.returnValue(Observable.throw(null)); + it('should notify when error occurs on Undo Move action', fakeAsync(done => { + spyOn(contentApi, 'restoreNode').and.returnValue(Observable.throw(null)); + + actions$.pipe( + ofType(SNACKBAR_ERROR), + map(action => done()) + ); const initialParent = 'parent-id-0'; const node = { entry: { id: 'node-to-move-id', name: 'conflicting-name', parentId: initialParent } }; @@ -428,15 +413,16 @@ describe('NodeMoveDirective', () => { element.triggerEventHandler('click', null); service.contentMoved.next(movedItems); - expect(nodesApiService.restoreNode).toHaveBeenCalled(); - expect(notificationService.openSnackMessageAction) - .toHaveBeenCalledWith('APP.MESSAGES.INFO.NODE_MOVE.SINGULAR', 'APP.ACTIONS.UNDO', 10000); - expect(notificationService.openSnackMessage) - .toHaveBeenCalledWith('APP.MESSAGES.ERRORS.GENERIC', 3000); - }); + expect(contentApi.restoreNode).toHaveBeenCalled(); + })); - it('should notify when some error of type Error occurs on Undo Move action', () => { - spyOn(nodesApiService, 'restoreNode').and.returnValue(Observable.throw(new Error('oops!'))); + it('should notify when some error of type Error occurs on Undo Move action', fakeAsync(done => { + spyOn(contentApi, 'restoreNode').and.returnValue(Observable.throw(new Error('oops!'))); + + actions$.pipe( + ofType(SNACKBAR_ERROR), + map(action => done()) + ); const initialParent = 'parent-id-0'; const node = { entry: { id: 'node-to-move-id', name: 'name', parentId: initialParent } }; @@ -455,15 +441,16 @@ describe('NodeMoveDirective', () => { element.triggerEventHandler('click', null); service.contentMoved.next(movedItems); - expect(nodesApiService.restoreNode).toHaveBeenCalled(); - expect(notificationService.openSnackMessageAction) - .toHaveBeenCalledWith('APP.MESSAGES.INFO.NODE_MOVE.SINGULAR', 'APP.ACTIONS.UNDO', 10000); - expect(notificationService.openSnackMessage) - .toHaveBeenCalledWith('APP.MESSAGES.ERRORS.GENERIC', 3000); - }); + expect(contentApi.restoreNode).toHaveBeenCalled(); + })); - it('should notify permission error when it occurs on Undo Move action', () => { - spyOn(nodesApiService, 'restoreNode').and.returnValue(Observable.throw(new Error(JSON.stringify({error: {statusCode: 403}})))); + it('should notify permission error when it occurs on Undo Move action', fakeAsync(done => { + spyOn(contentApi, 'restoreNode').and.returnValue(Observable.throw(new Error(JSON.stringify({error: {statusCode: 403}})))); + + actions$.pipe( + ofType(SNACKBAR_ERROR), + map(action => done()) + ); const initialParent = 'parent-id-0'; const node = { entry: { id: 'node-to-move-id', name: 'name', parentId: initialParent } }; @@ -483,12 +470,8 @@ describe('NodeMoveDirective', () => { service.contentMoved.next(movedItems); expect(service.moveNodes).toHaveBeenCalled(); - expect(nodesApiService.restoreNode).toHaveBeenCalled(); - expect(notificationService.openSnackMessageAction) - .toHaveBeenCalledWith('APP.MESSAGES.INFO.NODE_MOVE.SINGULAR', 'APP.ACTIONS.UNDO', 10000); - expect(notificationService.openSnackMessage) - .toHaveBeenCalledWith('APP.MESSAGES.ERRORS.PERMISSION', 3000); - }); + expect(contentApi.restoreNode).toHaveBeenCalled(); + })); }); }); diff --git a/src/app/common/directives/node-move.directive.ts b/src/app/common/directives/node-move.directive.ts index 812e687ec..a1b645a47 100644 --- a/src/app/common/directives/node-move.directive.ts +++ b/src/app/common/directives/node-move.directive.ts @@ -25,19 +25,25 @@ import { Directive, HostListener, Input } from '@angular/core'; -import { TranslationService, NodesApiService, NotificationService } from '@alfresco/adf-core'; +import { TranslationService } from '@alfresco/adf-core'; import { MinimalNodeEntity } from 'alfresco-js-api'; +import { MatSnackBar } from '@angular/material'; import { ContentManagementService } from '../services/content-management.service'; import { NodeActionsService } from '../services/node-actions.service'; import { Observable } from 'rxjs/Rx'; +import { Store } from '@ngrx/store'; +import { AppStore } from '../../store/states/app.state'; +import { SnackbarErrorAction } from '../../store/actions'; +import { ContentApiService } from '../../services/content-api.service'; @Directive({ - selector: '[app-move-node]' + selector: '[acaMoveNode]' }) export class NodeMoveDirective { - @Input('app-move-node') + // tslint:disable-next-line:no-input-rename + @Input('acaMoveNode') selection: MinimalNodeEntity[]; @HostListener('click') @@ -46,11 +52,12 @@ export class NodeMoveDirective { } constructor( + private store: Store, + private contentApi: ContentApiService, private content: ContentManagementService, - private notification: NotificationService, private nodeActionsService: NodeActionsService, - private nodesApi: NodesApiService, - private translation: TranslationService + private translation: TranslationService, + private snackBar: MatSnackBar ) {} moveSelected() { @@ -64,7 +71,7 @@ export class NodeMoveDirective { const [ operationResult, moveResponse ] = result; this.toastMessage(operationResult, moveResponse); - this.content.nodeMoved.next(null); + this.content.nodesMoved.next(null); }, (error) => { this.toastMessage(error); @@ -86,7 +93,7 @@ export class NodeMoveDirective { // in case of success if (info.toLowerCase().indexOf('succes') !== -1) { - let i18nMessageString = 'APP.MESSAGES.INFO.NODE_MOVE.'; + const i18nMessageString = 'APP.MESSAGES.INFO.NODE_MOVE.'; let i18MessageSuffix = ''; if (succeeded) { @@ -118,8 +125,7 @@ export class NodeMoveDirective { errorMessage = this.getErrorMessage(info); } - const undo = (succeeded + partiallySucceeded > 0) ? this.translation.translate.instant('APP.ACTIONS.UNDO') : ''; - const withUndo = errorMessage ? '' : '_WITH_UNDO'; + const undo = (succeeded + partiallySucceeded > 0) ? this.translation.instant('APP.ACTIONS.UNDO') : ''; failedMessage = errorMessage ? errorMessage : failedMessage; const beforePartialSuccessMessage = (successMessage && partialSuccessMessage) ? ' ' : ''; @@ -127,20 +133,23 @@ export class NodeMoveDirective { const initialParentId = this.nodeActionsService.getEntryParentId(this.selection[0].entry); - this.translation.get( + const messages = this.translation.instant( [successMessage, partialSuccessMessage, failedMessage], - { success: succeeded, failed: failures, partially: partiallySucceeded}).subscribe(messages => { + { success: succeeded, failed: failures, partially: partiallySucceeded} + ); - this.notification.openSnackMessageAction( + // TODO: review in terms of i18n + this.snackBar + .open( messages[successMessage] + beforePartialSuccessMessage + messages[partialSuccessMessage] - + beforeFailedMessage + messages[failedMessage], - undo, - NodeActionsService[`SNACK_MESSAGE_DURATION${withUndo}`] - ) - .onAction() - .subscribe(() => this.revertMoving(moveResponse, initialParentId)); - }); + + beforeFailedMessage + messages[failedMessage] + , undo, { + panelClass: 'info-snackbar', + duration: 3000 + }) + .onAction() + .subscribe(() => this.revertMoving(moveResponse, initialParentId)); } getErrorMessage(errorObject): string { @@ -167,7 +176,9 @@ export class NodeMoveDirective { const restoreDeletedNodesBatch = this.nodeActionsService.moveDeletedEntries .map((folderEntry) => { - return this.nodesApi.restoreNode(folderEntry.nodeId || folderEntry.id); + return this.contentApi + .restoreNode(folderEntry.nodeId || folderEntry.id) + .map(node => node.entry); }); Observable.zip(...restoreDeletedNodesBatch, Observable.of(null)) @@ -190,26 +201,21 @@ export class NodeMoveDirective { }) .subscribe( () => { - this.content.nodeMoved.next(null); + this.content.nodesMoved.next(null); }, - (error) => { - - let i18nMessageString = 'APP.MESSAGES.ERRORS.GENERIC'; + error => { + let message = 'APP.MESSAGES.ERRORS.GENERIC'; let errorJson = null; try { errorJson = JSON.parse(error.message); - } catch (e) { // - } + } catch {} if (errorJson && errorJson.error && errorJson.error.statusCode === 403) { - i18nMessageString = 'APP.MESSAGES.ERRORS.PERMISSION'; + message = 'APP.MESSAGES.ERRORS.PERMISSION'; } - this.translation.get(i18nMessageString).subscribe(message => { - this.notification.openSnackMessage( - message, NodeActionsService.SNACK_MESSAGE_DURATION); - }); + this.store.dispatch(new SnackbarErrorAction(message)); } ); } diff --git a/src/app/common/directives/node-permanent-delete.directive.spec.ts b/src/app/common/directives/node-permanent-delete.directive.spec.ts index f08ffd5d8..efde1230f 100644 --- a/src/app/common/directives/node-permanent-delete.directive.spec.ts +++ b/src/app/common/directives/node-permanent-delete.directive.spec.ts @@ -24,16 +24,24 @@ */ import { Component, DebugElement } from '@angular/core'; -import { TestBed, ComponentFixture, async, fakeAsync, tick } from '@angular/core/testing'; +import { TestBed, ComponentFixture, fakeAsync, tick } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; import { Observable } from 'rxjs/Rx'; -import { AlfrescoApiService, TranslationService, NotificationService, CoreModule } from '@alfresco/adf-core'; import { NodePermanentDeleteDirective } from './node-permanent-delete.directive'; -import { MatDialogModule, MatDialog } from '@angular/material'; +import { MatDialog } from '@angular/material'; +import { Actions, ofType, EffectsModule } from '@ngrx/effects'; +import { + SNACKBAR_INFO, SnackbarWarningAction, SnackbarInfoAction, + SnackbarErrorAction, SNACKBAR_ERROR, SNACKBAR_WARNING +} from '../../store/actions'; +import { map } from 'rxjs/operators'; +import { NodeEffects } from '../../store/effects/node.effects'; +import { AppTestingModule } from '../../testing/app-testing.module'; +import { ContentApiService } from '../../services/content-api.service'; @Component({ - template: `
` + template: `
` }) class TestComponent { selection = []; @@ -43,64 +51,50 @@ describe('NodePermanentDeleteDirective', () => { let fixture: ComponentFixture; let element: DebugElement; let component: TestComponent; - let alfrescoService: AlfrescoApiService; - let translation: TranslationService; - let notificationService: NotificationService; - let nodesService; - let directiveInstance; let dialog: MatDialog; + let actions$: Actions; + let contentApi: ContentApiService; - beforeEach(async(() => { + beforeEach(() => { TestBed.configureTestingModule({ imports: [ - CoreModule, - MatDialogModule + AppTestingModule, + EffectsModule.forRoot([NodeEffects]) ], declarations: [ NodePermanentDeleteDirective, TestComponent ] - }) - .compileComponents() - .then(() => { - fixture = TestBed.createComponent(TestComponent); - component = fixture.componentInstance; - element = fixture.debugElement.query(By.directive(NodePermanentDeleteDirective)); - directiveInstance = element.injector.get(NodePermanentDeleteDirective); - - dialog = TestBed.get(MatDialog); - alfrescoService = TestBed.get(AlfrescoApiService); - translation = TestBed.get(TranslationService); - notificationService = TestBed.get(NotificationService); }); - })); - beforeEach(() => { - nodesService = alfrescoService.getInstance().nodes; + contentApi = TestBed.get(ContentApiService); + actions$ = TestBed.get(Actions); - spyOn(translation, 'get').and.returnValue(Observable.of('message')); - spyOn(notificationService, 'openSnackMessage').and.returnValue({}); + fixture = TestBed.createComponent(TestComponent); + component = fixture.componentInstance; + element = fixture.debugElement.query(By.directive(NodePermanentDeleteDirective)); + dialog = TestBed.get(MatDialog); spyOn(dialog, 'open').and.returnValue({ afterClosed() { - return Observable.of(true) + return Observable.of(true); } }); }); it('does not purge nodes if no selection', () => { - spyOn(nodesService, 'purgeDeletedNode'); + spyOn(contentApi, 'purgeDeletedNode'); component.selection = []; fixture.detectChanges(); element.triggerEventHandler('click', null); - expect(nodesService.purgeDeletedNode).not.toHaveBeenCalled(); + expect(contentApi.purgeDeletedNode).not.toHaveBeenCalled(); }); it('call purge nodes if selection is not empty', fakeAsync(() => { - spyOn(nodesService, 'purgeDeletedNode').and.returnValue(Promise.resolve()); + spyOn(contentApi, 'purgeDeletedNode').and.returnValue(Observable.of({})); component.selection = [ { entry: { id: '1' } } ]; @@ -108,22 +102,29 @@ describe('NodePermanentDeleteDirective', () => { element.triggerEventHandler('click', null); tick(); - expect(nodesService.purgeDeletedNode).toHaveBeenCalled(); + expect(contentApi.purgeDeletedNode).toHaveBeenCalled(); })); describe('notification', () => { - it('notifies on multiple fail and one success', fakeAsync(() => { - spyOn(nodesService, 'purgeDeletedNode').and.callFake((id) => { + it('raises warning on multiple fail and one success', fakeAsync(done => { + actions$.pipe( + ofType(SNACKBAR_WARNING), + map((action: SnackbarWarningAction) => { + done(); + }) + ); + + spyOn(contentApi, 'purgeDeletedNode').and.callFake((id) => { if (id === '1') { - return Promise.resolve(); + return Observable.of({}); } if (id === '2') { - return Promise.reject({}); + return Observable.throw({}); } if (id === '3') { - return Promise.reject({}); + return Observable.throw({}); } }); @@ -136,30 +137,31 @@ describe('NodePermanentDeleteDirective', () => { fixture.detectChanges(); element.triggerEventHandler('click', null); tick(); - - expect(notificationService.openSnackMessage).toHaveBeenCalled(); - expect(translation.get).toHaveBeenCalledWith( - 'APP.MESSAGES.INFO.TRASH.NODES_PURGE.PARTIAL_SINGULAR', - { name: 'name1', failed: 2 } - ); })); - it('notifies on multiple success and multiple fail', fakeAsync(() => { - spyOn(nodesService, 'purgeDeletedNode').and.callFake((id) => { + it('raises warning on multiple success and multiple fail', fakeAsync(done => { + actions$.pipe( + ofType(SNACKBAR_WARNING), + map((action: SnackbarWarningAction) => { + done(); + }) + ); + + spyOn(contentApi, 'purgeDeletedNode').and.callFake((id) => { if (id === '1') { - return Promise.resolve(); + return Observable.of({}); } if (id === '2') { - return Promise.reject({}); + return Observable.throw({}); } if (id === '3') { - return Promise.reject({}); + return Observable.throw({}); } if (id === '4') { - return Promise.resolve(); + return Observable.of({}); } }); @@ -173,16 +175,17 @@ describe('NodePermanentDeleteDirective', () => { fixture.detectChanges(); element.triggerEventHandler('click', null); tick(); - - expect(notificationService.openSnackMessage).toHaveBeenCalled(); - expect(translation.get).toHaveBeenCalledWith( - 'APP.MESSAGES.INFO.TRASH.NODES_PURGE.PARTIAL_PLURAL', - { number: 2, failed: 2 } - ); })); - it('notifies on one selected node success', fakeAsync(() => { - spyOn(nodesService, 'purgeDeletedNode').and.returnValue(Promise.resolve()); + it('raises info on one selected node success', fakeAsync(done => { + actions$.pipe( + ofType(SNACKBAR_INFO), + map((action: SnackbarInfoAction) => { + done(); + }) + ); + + spyOn(contentApi, 'purgeDeletedNode').and.returnValue(Observable.of({})); component.selection = [ { entry: { id: '1', name: 'name1' } } @@ -191,16 +194,17 @@ describe('NodePermanentDeleteDirective', () => { fixture.detectChanges(); element.triggerEventHandler('click', null); tick(); - - expect(notificationService.openSnackMessage).toHaveBeenCalled(); - expect(translation.get).toHaveBeenCalledWith( - 'APP.MESSAGES.INFO.TRASH.NODES_PURGE.SINGULAR', - { name: 'name1' } - ); })); - it('notifies on one selected node fail', fakeAsync(() => { - spyOn(nodesService, 'purgeDeletedNode').and.returnValue(Promise.reject({})); + it('raises error on one selected node fail', fakeAsync(done => { + actions$.pipe( + ofType(SNACKBAR_ERROR), + map((action: SnackbarErrorAction) => { + done(); + }) + ); + + spyOn(contentApi, 'purgeDeletedNode').and.returnValue(Observable.throw({})); component.selection = [ { entry: { id: '1', name: 'name1' } } @@ -209,22 +213,22 @@ describe('NodePermanentDeleteDirective', () => { fixture.detectChanges(); element.triggerEventHandler('click', null); tick(); - - expect(notificationService.openSnackMessage).toHaveBeenCalled(); - expect(translation.get).toHaveBeenCalledWith( - 'APP.MESSAGES.ERRORS.TRASH.NODES_PURGE.SINGULAR', - { name: 'name1' } - ); })); - it('notifies on selected nodes success', fakeAsync(() => { - spyOn(nodesService, 'purgeDeletedNode').and.callFake((id) => { + it('raises info on all nodes success', fakeAsync(done => { + actions$.pipe( + ofType(SNACKBAR_INFO), + map((action: SnackbarInfoAction) => { + done(); + }) + ); + spyOn(contentApi, 'purgeDeletedNode').and.callFake((id) => { if (id === '1') { - return Promise.resolve(); + return Observable.of({}); } if (id === '2') { - return Promise.resolve(); + return Observable.of({}); } }); @@ -236,22 +240,22 @@ describe('NodePermanentDeleteDirective', () => { fixture.detectChanges(); element.triggerEventHandler('click', null); tick(); - - expect(notificationService.openSnackMessage).toHaveBeenCalled(); - expect(translation.get).toHaveBeenCalledWith( - 'APP.MESSAGES.INFO.TRASH.NODES_PURGE.PLURAL', - { number: 2 } - ); })); - it('notifies on selected nodes fail', fakeAsync(() => { - spyOn(nodesService, 'purgeDeletedNode').and.callFake((id) => { + it('raises error on all nodes fail', fakeAsync(done => { + actions$.pipe( + ofType(SNACKBAR_ERROR), + map((action: SnackbarErrorAction) => { + done(); + }) + ); + spyOn(contentApi, 'purgeDeletedNode').and.callFake((id) => { if (id === '1') { - return Promise.reject({}); + return Observable.throw({}); } if (id === '2') { - return Promise.reject({}); + return Observable.throw({}); } }); @@ -263,96 +267,6 @@ describe('NodePermanentDeleteDirective', () => { fixture.detectChanges(); element.triggerEventHandler('click', null); tick(); - - expect(notificationService.openSnackMessage).toHaveBeenCalled(); - expect(translation.get).toHaveBeenCalledWith( - 'APP.MESSAGES.ERRORS.TRASH.NODES_PURGE.PLURAL', - { number: 2 } - ); - })); - }); - - describe('refresh()', () => { - it('resets selection on success', fakeAsync(() => { - spyOn(nodesService, 'purgeDeletedNode').and.returnValue(Promise.resolve()); - - component.selection = [ - { entry: { id: '1', name: 'name1' } } - ]; - - fixture.detectChanges(); - element.triggerEventHandler('click', null); - tick(); - - expect(directiveInstance.selection).toEqual([]); - })); - - it('resets selection on error', fakeAsync(() => { - spyOn(nodesService, 'purgeDeletedNode').and.returnValue(Promise.reject({})); - - component.selection = [ - { entry: { id: '1', name: 'name1' } } - ]; - - fixture.detectChanges(); - element.triggerEventHandler('click', null); - tick(); - - expect(directiveInstance.selection).toEqual([]); - })); - - it('resets status', fakeAsync(() => { - const status = directiveInstance.processStatus([ - { status: 0 }, - { status: 1 } - ]); - - expect(status.fail.length).toBe(1); - expect(status.success.length).toBe(1); - - status.reset(); - - expect(status.fail.length).toBe(0); - expect(status.success.length).toBe(0); - })); - - it('dispatch event on partial success', fakeAsync(() => { - spyOn(element.nativeElement, 'dispatchEvent'); - spyOn(nodesService, 'purgeDeletedNode').and.callFake((id) => { - if (id === '1') { - return Promise.reject({}); - } - - if (id === '2') { - return Promise.resolve(); - } - }); - - component.selection = [ - { entry: { id: '1', name: 'name1' } }, - { entry: { id: '2', name: 'name2' } } - ]; - - fixture.detectChanges(); - element.triggerEventHandler('click', null); - tick(); - - expect(element.nativeElement.dispatchEvent).toHaveBeenCalled(); - })); - - it('does not dispatch event on error', fakeAsync(() => { - spyOn(nodesService, 'purgeDeletedNode').and.returnValue(Promise.reject({})); - spyOn(element.nativeElement, 'dispatchEvent'); - - component.selection = [ - { entry: { id: '1', name: 'name1' } } - ]; - - fixture.detectChanges(); - element.triggerEventHandler('click', null); - tick(); - - expect(element.nativeElement.dispatchEvent).not.toHaveBeenCalled(); })); }); }); diff --git a/src/app/common/directives/node-permanent-delete.directive.ts b/src/app/common/directives/node-permanent-delete.directive.ts index a9aa7f90a..90f1e9f40 100644 --- a/src/app/common/directives/node-permanent-delete.directive.ts +++ b/src/app/common/directives/node-permanent-delete.directive.ts @@ -23,24 +23,30 @@ * along with Alfresco. If not, see . */ -import { Directive, ElementRef, HostListener, Input } from '@angular/core'; -import { Observable } from 'rxjs/Rx'; - -import { TranslationService, AlfrescoApiService, NotificationService } from '@alfresco/adf-core'; +import { Directive, HostListener, Input } from '@angular/core'; import { MinimalNodeEntity } from 'alfresco-js-api'; import { MatDialog } from '@angular/material'; import { ConfirmDialogComponent } from '@alfresco/adf-content-services'; +import { Store } from '@ngrx/store'; + +import { AppStore } from '../../store/states/app.state'; +import { PurgeDeletedNodesAction } from '../../store/actions'; +import { NodeInfo } from '../../store/models'; @Directive({ - // tslint:disable-next-line:directive-selector - selector: '[app-permanent-delete-node]' + selector: '[acaPermanentDelete]' }) export class NodePermanentDeleteDirective { // tslint:disable-next-line:no-input-rename - @Input('app-permanent-delete-node') + @Input('acaPermanentDelete') selection: MinimalNodeEntity[]; + constructor( + private store: Store, + private dialog: MatDialog + ) {} + @HostListener('click') onClick() { const dialogRef = this.dialog.open(ConfirmDialogComponent, { @@ -55,167 +61,17 @@ export class NodePermanentDeleteDirective { dialogRef.afterClosed().subscribe(result => { if (result === true) { - this.purge(); + const nodesToDelete: NodeInfo[] = this.selection.map(node => { + const { name } = node.entry; + const id = node.entry.nodeId || node.entry.id; + + return { + id, + name + }; + }); + this.store.dispatch(new PurgeDeletedNodesAction(nodesToDelete)); } }); } - - constructor( - private alfrescoApiService: AlfrescoApiService, - private translation: TranslationService, - private notification: NotificationService, - private el: ElementRef, - private dialog: MatDialog - ) {} - - private purge() { - if (!this.selection.length) { - return; - } - - const batch = this.getPurgedNodesBatch(this.selection); - - Observable.forkJoin(batch) - .subscribe( - (purgedNodes) => { - const status = this.processStatus(purgedNodes); - - this.purgeNotification(status); - - if (status.success.length) { - this.emitDone(); - } - - this.selection = []; - status.reset(); - } - ); - } - - private getPurgedNodesBatch(selection): Observable { - return selection.map((node: MinimalNodeEntity) => this.purgeDeletedNode(node)); - } - - private purgeDeletedNode(node): Observable { - const { id, name } = node.entry; - const promise = this.alfrescoApiService.getInstance().nodes.purgeDeletedNode(id); - - return Observable.from(promise) - .map(() => ({ - status: 1, - id, - name - })) - .catch((error) => { - return Observable.of({ - status: 0, - id, - name - }); - }); - } - - private purgeNotification(status): void { - this.getPurgeMessage(status) - .subscribe((message) => { - this.notification.openSnackMessage(message, 3000); - }); - } - - private getPurgeMessage(status): Observable { - if (status.oneSucceeded && status.someFailed && !status.oneFailed) { - return this.translation.get( - 'APP.MESSAGES.INFO.TRASH.NODES_PURGE.PARTIAL_SINGULAR', - { - name: status.success[0].name, - failed: status.fail.length - } - ); - } - - if (status.someSucceeded && !status.oneSucceeded && status.someFailed) { - return this.translation.get( - 'APP.MESSAGES.INFO.TRASH.NODES_PURGE.PARTIAL_PLURAL', - { - number: status.success.length, - failed: status.fail.length - } - ); - } - - if (status.oneSucceeded) { - return this.translation.get( - 'APP.MESSAGES.INFO.TRASH.NODES_PURGE.SINGULAR', - { name: status.success[0].name } - ); - } - - if (status.oneFailed) { - return this.translation.get( - 'APP.MESSAGES.ERRORS.TRASH.NODES_PURGE.SINGULAR', - { name: status.fail[0].name } - ); - } - - if (status.allSucceeded) { - return this.translation.get( - 'APP.MESSAGES.INFO.TRASH.NODES_PURGE.PLURAL', - { number: status.success.length } - ); - } - - if (status.allFailed) { - return this.translation.get( - 'APP.MESSAGES.ERRORS.TRASH.NODES_PURGE.PLURAL', - { number: status.fail.length } - ); - } - } - - private processStatus(data = []): any { - const status = { - fail: [], - success: [], - get someFailed() { - return !!(this.fail.length); - }, - get someSucceeded() { - return !!(this.success.length); - }, - get oneFailed() { - return this.fail.length === 1; - }, - get oneSucceeded() { - return this.success.length === 1; - }, - get allSucceeded() { - return this.someSucceeded && !this.someFailed; - }, - get allFailed() { - return this.someFailed && !this.someSucceeded; - }, - reset() { - this.fail = []; - this.success = []; - } - }; - - return data.reduce( - (acc, node) => { - if (node.status) { - acc.success.push(node); - } else { - acc.fail.push(node); - } - - return acc; - }, - status - ); - } - - private emitDone() { - const e = new CustomEvent('selection-node-deleted', { bubbles: true }); - this.el.nativeElement.dispatchEvent(e); - } } diff --git a/src/app/common/directives/node-restore.directive.spec.ts b/src/app/common/directives/node-restore.directive.spec.ts index 68b6cab5f..620c22ec9 100644 --- a/src/app/common/directives/node-restore.directive.spec.ts +++ b/src/app/common/directives/node-restore.directive.spec.ts @@ -24,19 +24,21 @@ */ import { Component, DebugElement } from '@angular/core'; -import { Router } from '@angular/router'; -import { RouterTestingModule } from '@angular/router/testing'; -import { TestBed, ComponentFixture, async, fakeAsync, tick } from '@angular/core/testing'; +import { TestBed, ComponentFixture, fakeAsync, tick } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; +import { NodeRestoreDirective } from './node-restore.directive'; +import { ContentManagementService } from '../services/content-management.service'; +import { Actions, ofType } from '@ngrx/effects'; +import { SnackbarErrorAction, + SNACKBAR_ERROR, SnackbarInfoAction, SNACKBAR_INFO, + NavigateRouteAction, NAVIGATE_ROUTE } from '../../store/actions'; +import { map } from 'rxjs/operators'; +import { AppTestingModule } from '../../testing/app-testing.module'; +import { ContentApiService } from '../../services/content-api.service'; import { Observable } from 'rxjs/Rx'; -import { AlfrescoApiService, TranslationService, NotificationService, CoreModule } from '@alfresco/adf-core'; - -import { NodeRestoreDirective } from './node-restore.directive'; -import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; - @Component({ - template: `
` + template: `
` }) class TestComponent { selection = []; @@ -46,73 +48,57 @@ describe('NodeRestoreDirective', () => { let fixture: ComponentFixture; let element: DebugElement; let component: TestComponent; - let alfrescoService: AlfrescoApiService; - let translation: TranslationService; - let notificationService: NotificationService; - let router: Router; - let nodesService; - let coreApi; - let directiveInstance; + let directiveInstance: NodeRestoreDirective; + let contentManagementService: ContentManagementService; + let actions$: Actions; + let contentApi: ContentApiService; - beforeEach(async(() => { + beforeEach(() => { TestBed.configureTestingModule({ - imports: [ - BrowserAnimationsModule, - RouterTestingModule, - CoreModule - ], + imports: [ AppTestingModule ], declarations: [ NodeRestoreDirective, TestComponent ] - }) - .compileComponents() - .then(() => { - fixture = TestBed.createComponent(TestComponent); - component = fixture.componentInstance; - element = fixture.debugElement.query(By.directive(NodeRestoreDirective)); - directiveInstance = element.injector.get(NodeRestoreDirective); - - alfrescoService = TestBed.get(AlfrescoApiService); - translation = TestBed.get(TranslationService); - notificationService = TestBed.get(NotificationService); - router = TestBed.get(Router); }); - })); - beforeEach(() => { - nodesService = alfrescoService.getInstance().nodes; - coreApi = alfrescoService.getInstance().core; + actions$ = TestBed.get(Actions); - spyOn(translation, 'get').and.returnValue(Observable.of('message')); + fixture = TestBed.createComponent(TestComponent); + component = fixture.componentInstance; + element = fixture.debugElement.query(By.directive(NodeRestoreDirective)); + directiveInstance = element.injector.get(NodeRestoreDirective); + + contentManagementService = TestBed.get(ContentManagementService); + contentApi = TestBed.get(ContentApiService); }); it('does not restore nodes if no selection', () => { - spyOn(nodesService, 'restoreNode'); + spyOn(contentApi, 'restoreNode'); component.selection = []; fixture.detectChanges(); element.triggerEventHandler('click', null); - expect(nodesService.restoreNode).not.toHaveBeenCalled(); + expect(contentApi.restoreNode).not.toHaveBeenCalled(); }); it('does not restore nodes if selection has nodes without path', () => { - spyOn(nodesService, 'restoreNode'); + spyOn(contentApi, 'restoreNode'); component.selection = [ { entry: { id: '1' } } ]; fixture.detectChanges(); element.triggerEventHandler('click', null); - expect(nodesService.restoreNode).not.toHaveBeenCalled(); + expect(contentApi.restoreNode).not.toHaveBeenCalled(); }); it('call restore nodes if selection has nodes with path', fakeAsync(() => { spyOn(directiveInstance, 'restoreNotification').and.callFake(() => null); - spyOn(nodesService, 'restoreNode').and.returnValue(Promise.resolve()); - spyOn(coreApi.nodesApi, 'getDeletedNodes').and.returnValue(Promise.resolve({ + spyOn(contentApi, 'restoreNode').and.returnValue(Observable.of({})); + spyOn(contentApi, 'getDeletedNodes').and.returnValue(Observable.of({ list: { entries: [] } })); @@ -122,80 +108,53 @@ describe('NodeRestoreDirective', () => { element.triggerEventHandler('click', null); tick(); - expect(nodesService.restoreNode).toHaveBeenCalled(); + expect(contentApi.restoreNode).toHaveBeenCalled(); })); describe('refresh()', () => { - it('reset selection', fakeAsync(() => { + it('dispatch event on finish', fakeAsync(done => { spyOn(directiveInstance, 'restoreNotification').and.callFake(() => null); - spyOn(nodesService, 'restoreNode').and.returnValue(Promise.resolve()); - spyOn(coreApi.nodesApi, 'getDeletedNodes').and.returnValue(Promise.resolve({ + spyOn(contentApi, 'restoreNode').and.returnValue(Observable.of({})); + spyOn(contentApi, 'getDeletedNodes').and.returnValue(Observable.of({ list: { entries: [] } })); component.selection = [{ entry: { id: '1', path: ['somewhere-over-the-rainbow'] } }]; - fixture.detectChanges(); - - expect(directiveInstance.selection.length).toBe(1); - - element.triggerEventHandler('click', null); - tick(); - - expect(directiveInstance.selection.length).toBe(0); - })); - - it('reset status', fakeAsync(() => { - directiveInstance.restoreProcessStatus.fail = [{}]; - directiveInstance.restoreProcessStatus.success = [{}]; - - directiveInstance.restoreProcessStatus.reset(); - - expect(directiveInstance.restoreProcessStatus.fail).toEqual([]); - expect(directiveInstance.restoreProcessStatus.success).toEqual([]); - })); - - it('dispatch event on finish', fakeAsync(() => { - spyOn(directiveInstance, 'restoreNotification').and.callFake(() => null); - spyOn(nodesService, 'restoreNode').and.returnValue(Promise.resolve()); - spyOn(coreApi.nodesApi, 'getDeletedNodes').and.returnValue(Promise.resolve({ - list: { entries: [] } - })); - spyOn(element.nativeElement, 'dispatchEvent'); - - component.selection = [{ entry: { id: '1', path: ['somewhere-over-the-rainbow'] } }]; - fixture.detectChanges(); element.triggerEventHandler('click', null); tick(); - expect(element.nativeElement.dispatchEvent).toHaveBeenCalled(); + contentManagementService.nodesRestored.subscribe(() => done()); })); }); describe('notification', () => { beforeEach(() => { - spyOn(coreApi.nodesApi, 'getDeletedNodes').and.returnValue(Promise.resolve({ + spyOn(contentApi, 'getDeletedNodes').and.returnValue(Observable.of({ list: { entries: [] } })); }); - it('notifies on partial multiple fail ', fakeAsync(() => { + it('should raise error message on partial multiple fail ', fakeAsync(done => { const error = { message: '{ "error": {} }' }; - spyOn(notificationService, 'openSnackMessageAction').and.returnValue({ onAction: () => Observable.throw(null) }); + actions$.pipe( + ofType(SNACKBAR_ERROR), + map(action => done()) + ); - spyOn(nodesService, 'restoreNode').and.callFake((id) => { + spyOn(contentApi, 'restoreNode').and.callFake((id) => { if (id === '1') { - return Promise.resolve(); + return Observable.of({}); } if (id === '2') { - return Promise.reject(error); + return Observable.throw(error); } if (id === '3') { - return Promise.reject(error); + return Observable.throw(error); } }); @@ -208,18 +167,16 @@ describe('NodeRestoreDirective', () => { fixture.detectChanges(); element.triggerEventHandler('click', null); tick(); - - expect(translation.get).toHaveBeenCalledWith( - 'APP.MESSAGES.ERRORS.TRASH.NODES_RESTORE.PARTIAL_PLURAL', - { number: 2 } - ); })); - it('notifies fail when restored node exist, error 409', fakeAsync(() => { + it('should raise error message when restored node exist, error 409', fakeAsync(done => { const error = { message: '{ "error": { "statusCode": 409 } }' }; + spyOn(contentApi, 'restoreNode').and.returnValue(Observable.throw(error)); - spyOn(notificationService, 'openSnackMessageAction').and.returnValue({ onAction: () => Observable.throw(null) }); - spyOn(nodesService, 'restoreNode').and.returnValue(Promise.reject(error)); + actions$.pipe( + ofType(SNACKBAR_ERROR), + map(action => done()) + ); component.selection = [ { entry: { id: '1', name: 'name1', path: ['somewhere-over-the-rainbow'] } } @@ -228,18 +185,17 @@ describe('NodeRestoreDirective', () => { fixture.detectChanges(); element.triggerEventHandler('click', null); tick(); - - expect(translation.get).toHaveBeenCalledWith( - 'APP.MESSAGES.ERRORS.TRASH.NODES_RESTORE.NODE_EXISTS', - { name: 'name1' } - ); })); - it('notifies fail when restored node returns different statusCode', fakeAsync(() => { + it('should raise error message when restored node returns different statusCode', fakeAsync(done => { const error = { message: '{ "error": { "statusCode": 404 } }' }; - spyOn(notificationService, 'openSnackMessageAction').and.returnValue({ onAction: () => Observable.throw(null) }); - spyOn(nodesService, 'restoreNode').and.returnValue(Promise.reject(error)); + spyOn(contentApi, 'restoreNode').and.returnValue(Observable.throw(error)); + + actions$.pipe( + ofType(SNACKBAR_ERROR), + map(action => done()) + ); component.selection = [ { entry: { id: '1', name: 'name1', path: ['somewhere-over-the-rainbow'] } } @@ -248,18 +204,17 @@ describe('NodeRestoreDirective', () => { fixture.detectChanges(); element.triggerEventHandler('click', null); tick(); - - expect(translation.get).toHaveBeenCalledWith( - 'APP.MESSAGES.ERRORS.TRASH.NODES_RESTORE.GENERIC', - { name: 'name1' } - ); })); - it('notifies fail when restored node location is missing', fakeAsync(() => { + it('should raise error message when restored node location is missing', fakeAsync(done => { const error = { message: '{ "error": { } }' }; - spyOn(notificationService, 'openSnackMessageAction').and.returnValue({ onAction: () => Observable.throw(null) }); - spyOn(nodesService, 'restoreNode').and.returnValue(Promise.reject(error)); + spyOn(contentApi, 'restoreNode').and.returnValue(Observable.throw(error)); + + actions$.pipe( + ofType(SNACKBAR_ERROR), + map(action => done()) + ); component.selection = [ { entry: { id: '1', name: 'name1', path: ['somewhere-over-the-rainbow'] } } @@ -268,25 +223,24 @@ describe('NodeRestoreDirective', () => { fixture.detectChanges(); element.triggerEventHandler('click', null); tick(); - - expect(translation.get).toHaveBeenCalledWith( - 'APP.MESSAGES.ERRORS.TRASH.NODES_RESTORE.LOCATION_MISSING', - { name: 'name1' } - ); })); - it('notifies success when restore multiple nodes', fakeAsync(() => { - spyOn(notificationService, 'openSnackMessageAction').and.returnValue({ onAction: () => Observable.throw(null) }); - spyOn(nodesService, 'restoreNode').and.callFake((id) => { + it('should raise info message when restore multiple nodes', fakeAsync(done => { + spyOn(contentApi, 'restoreNode').and.callFake((id) => { if (id === '1') { - return Promise.resolve(); + return Observable.of({}); } if (id === '2') { - return Promise.resolve(); + return Observable.of({}); } }); + actions$.pipe( + ofType(SNACKBAR_INFO), + map(action => done()) + ); + component.selection = [ { entry: { id: '1', name: 'name1', path: ['somewhere-over-the-rainbow'] } }, { entry: { id: '2', name: 'name2', path: ['somewhere-over-the-rainbow'] } } @@ -295,15 +249,15 @@ describe('NodeRestoreDirective', () => { fixture.detectChanges(); element.triggerEventHandler('click', null); tick(); - - expect(translation.get).toHaveBeenCalledWith( - 'APP.MESSAGES.INFO.TRASH.NODES_RESTORE.PLURAL' - ); })); - it('notifies success when restore selected node', fakeAsync(() => { - spyOn(notificationService, 'openSnackMessageAction').and.returnValue({ onAction: () => Observable.throw(null) }); - spyOn(nodesService, 'restoreNode').and.returnValue(Promise.resolve()); + xit('should raise info message when restore selected node', fakeAsync(done => { + spyOn(contentApi, 'restoreNode').and.returnValue(Observable.of({})); + + actions$.pipe( + ofType(SNACKBAR_INFO), + map(action => done()) + ); component.selection = [ { entry: { id: '1', name: 'name1', path: ['somewhere-over-the-rainbow'] } } @@ -312,17 +266,15 @@ describe('NodeRestoreDirective', () => { fixture.detectChanges(); element.triggerEventHandler('click', null); tick(); - - expect(translation.get).toHaveBeenCalledWith( - 'APP.MESSAGES.INFO.TRASH.NODES_RESTORE.SINGULAR', - { name: 'name1' } - ); })); - it('navigate to restore selected node location onAction', fakeAsync(() => { - spyOn(router, 'navigate'); - spyOn(nodesService, 'restoreNode').and.returnValue(Promise.resolve()); - spyOn(notificationService, 'openSnackMessageAction').and.returnValue({ onAction: () => Observable.of({}) }); + it('navigate to restore selected node location onAction', fakeAsync(done => { + spyOn(contentApi, 'restoreNode').and.returnValue(Observable.of({})); + + actions$.pipe( + ofType(NAVIGATE_ROUTE), + map(action => done()) + ); component.selection = [ { @@ -339,8 +291,6 @@ describe('NodeRestoreDirective', () => { fixture.detectChanges(); element.triggerEventHandler('click', null); tick(); - - expect(router.navigate).toHaveBeenCalled(); })); }); }); diff --git a/src/app/common/directives/node-restore.directive.ts b/src/app/common/directives/node-restore.directive.ts index 8d16d8a67..ddacb0b28 100644 --- a/src/app/common/directives/node-restore.directive.ts +++ b/src/app/common/directives/node-restore.directive.ts @@ -23,21 +23,33 @@ * along with Alfresco. If not, see . */ -import { Directive, ElementRef, HostListener, Input } from '@angular/core'; -import { Router } from '@angular/router'; +import { Directive, HostListener, Input } from '@angular/core'; import { Observable } from 'rxjs/Rx'; - -import { TranslationService, AlfrescoApiService, NotificationService } from '@alfresco/adf-core'; -import { MinimalNodeEntity, PathInfoEntity, DeletedNodesPaging } from 'alfresco-js-api'; +import { + MinimalNodeEntity, + MinimalNodeEntryEntity, + PathInfoEntity, + DeletedNodesPaging +} from 'alfresco-js-api'; +import { DeleteStatus, DeletedNodeInfo } from '../../store/models'; +import { ContentManagementService } from '../services/content-management.service'; +import { Store } from '@ngrx/store'; +import { AppStore } from '../../store/states/app.state'; +import { + NavigateRouteAction, + SnackbarAction, + SnackbarErrorAction, + SnackbarInfoAction, + SnackbarUserAction +} from '../../store/actions'; +import { ContentApiService } from '../../services/content-api.service'; @Directive({ - selector: '[app-restore-node]' + selector: '[acaRestoreNode]' }) export class NodeRestoreDirective { - private restoreProcessStatus; - - @Input('app-restore-node') - selection: MinimalNodeEntity[]; + // tslint:disable-next-line:no-input-rename + @Input('acaRestoreNode') selection: MinimalNodeEntity[]; @HostListener('click') onClick() { @@ -45,81 +57,59 @@ export class NodeRestoreDirective { } constructor( - private alfrescoApiService: AlfrescoApiService, - private translation: TranslationService, - private router: Router, - private notification: NotificationService, - private el: ElementRef - ) { - this.restoreProcessStatus = this.processStatus(); - } + private store: Store, + private contentApi: ContentApiService, + private contentManagementService: ContentManagementService + ) {} - private restore(selection: any) { + private restore(selection: MinimalNodeEntity[] = []) { if (!selection.length) { return; } - const nodesWithPath = this.getNodesWithPath(selection); + const nodesWithPath = selection.filter(node => node.entry.path); if (selection.length && !nodesWithPath.length) { - this.restoreProcessStatus.fail.push(...selection); - this.restoreNotification(); + const failedStatus = this.processStatus([]); + failedStatus.fail.push(...selection); + this.restoreNotification(failedStatus); this.refresh(); return; } - this.restoreNodesBatch(nodesWithPath) - .do((restoredNodes) => { - const status = this.processStatus(restoredNodes); + let status: DeleteStatus; - this.restoreProcessStatus.fail.push(...status.fail); - this.restoreProcessStatus.success.push(...status.success); + Observable.forkJoin(nodesWithPath.map(node => this.restoreNode(node))) + .do(restoredNodes => { + status = this.processStatus(restoredNodes); }) - .flatMap(() => this.getDeletedNodes()) - .subscribe( - (deletedNodesList: DeletedNodesPaging) => { - const { entries: nodelist } = deletedNodesList.list; - const { fail: restoreErrorNodes } = this.restoreProcessStatus; - const selectedNodes = this.diff(restoreErrorNodes, selection, false); - const remainingNodes = this.diff(selectedNodes, nodelist); + .flatMap(() => this.contentApi.getDeletedNodes()) + .subscribe((nodes: DeletedNodesPaging) => { + const selectedNodes = this.diff(status.fail, selection, false); + const remainingNodes = this.diff( + selectedNodes, + nodes.list.entries + ); - if (!remainingNodes.length) { - this.restoreNotification(); - this.refresh(); - } else { - this.restore(remainingNodes); - } + if (!remainingNodes.length) { + this.restoreNotification(status); + this.refresh(); + } else { + this.restore(remainingNodes); } - ); + }); } - private restoreNodesBatch(batch: MinimalNodeEntity[]): Observable { - return Observable.forkJoin(batch.map((node) => this.restoreNode(node))); - } - - private getNodesWithPath(selection): MinimalNodeEntity[] { - return selection.filter((node) => node.entry.path); - } - - private getDeletedNodes(): Observable { - const promise = this.alfrescoApiService.getInstance() - .core.nodesApi.getDeletedNodes({ include: [ 'path' ] }); - - return Observable.from(promise); - } - - private restoreNode(node): Observable { + private restoreNode(node: MinimalNodeEntity): Observable { const { entry } = node; - const promise = this.alfrescoApiService.getInstance().nodes.restoreNode(entry.id); - - return Observable.from(promise) + return this.contentApi.restoreNode(entry.id) .map(() => ({ status: 1, entry })) - .catch((error) => { - const { statusCode } = (JSON.parse(error.message)).error; + .catch(error => { + const { statusCode } = JSON.parse(error.message).error; return Observable.of({ status: 0, @@ -129,13 +119,7 @@ export class NodeRestoreDirective { }); } - private navigateLocation(path: PathInfoEntity) { - const parent = path.elements[path.elements.length - 1]; - - this.router.navigate([ '/personal-files', parent.id ]); - } - - private diff(selection , list, fromList = true): any { + private diff(selection, list, fromList = true): any { const ids = selection.map(item => item.entry.id); return list.filter(item => { @@ -147,15 +131,15 @@ export class NodeRestoreDirective { }); } - private processStatus(data = []): any { + private processStatus(data: DeletedNodeInfo[] = []): DeleteStatus { const status = { fail: [], success: [], get someFailed() { - return !!(this.fail.length); + return !!this.fail.length; }, get someSucceeded() { - return !!(this.success.length); + return !!this.success.length; }, get oneFailed() { return this.fail.length === 1; @@ -175,93 +159,89 @@ export class NodeRestoreDirective { } }; - return data.reduce( - (acc, node) => { - if (node.status) { - acc.success.push(node); - } else { - acc.fail.push(node); - } + return data.reduce((acc, node) => { + if (node.status) { + acc.success.push(node); + } else { + acc.fail.push(node); + } - return acc; - }, - status - ); + return acc; + }, status); } - private getRestoreMessage(): Observable { - const { restoreProcessStatus: status } = this; - + private getRestoreMessage(status: DeleteStatus): SnackbarAction { if (status.someFailed && !status.oneFailed) { - return this.translation.get( + return new SnackbarErrorAction( 'APP.MESSAGES.ERRORS.TRASH.NODES_RESTORE.PARTIAL_PLURAL', - { - number: status.fail.length - } + { number: status.fail.length } ); } if (status.oneFailed && status.fail[0].statusCode) { if (status.fail[0].statusCode === 409) { - return this.translation.get( + return new SnackbarErrorAction( 'APP.MESSAGES.ERRORS.TRASH.NODES_RESTORE.NODE_EXISTS', - { - name: status.fail[0].entry.name - } + { name: status.fail[0].entry.name } ); } else { - return this.translation.get( + return new SnackbarErrorAction( 'APP.MESSAGES.ERRORS.TRASH.NODES_RESTORE.GENERIC', - { - name: status.fail[0].entry.name - } + { name: status.fail[0].entry.name } ); } } if (status.oneFailed && !status.fail[0].statusCode) { - return this.translation.get( + return new SnackbarErrorAction( 'APP.MESSAGES.ERRORS.TRASH.NODES_RESTORE.LOCATION_MISSING', - { - name: status.fail[0].entry.name - } + { name: status.fail[0].entry.name } ); } if (status.allSucceeded && !status.oneSucceeded) { - return this.translation.get('APP.MESSAGES.INFO.TRASH.NODES_RESTORE.PLURAL'); + return new SnackbarInfoAction( + 'APP.MESSAGES.INFO.TRASH.NODES_RESTORE.PLURAL' + ); } if (status.allSucceeded && status.oneSucceeded) { - return this.translation.get( + return new SnackbarInfoAction( 'APP.MESSAGES.INFO.TRASH.NODES_RESTORE.SINGULAR', - { - name: status.success[0].entry.name - } + { name: status.success[0].entry.name } ); } + + return null; + } + + restoreNotification(status: DeleteStatus): void { + const message = this.getRestoreMessage(status); + + if (message) { + if (status.oneSucceeded && !status.someFailed) { + const isSite = this.isSite(status.success[0].entry); + const path: PathInfoEntity = status.success[0].entry.path; + const parent = path.elements[path.elements.length - 1]; + const route = isSite ? ['/libraries'] : ['/personal-files', parent.id]; + + const navigate = new NavigateRouteAction(route); + + message.userAction = new SnackbarUserAction( + 'APP.ACTIONS.VIEW', + navigate + ); + } + + this.store.dispatch(message); + } } - private restoreNotification(): void { - const status = Object.assign({}, this.restoreProcessStatus); - const action = (status.oneSucceeded && !status.someFailed) ? this.translation.translate.instant('APP.ACTIONS.VIEW') : ''; - - this.getRestoreMessage() - .subscribe((message) => { - this.notification.openSnackMessageAction(message, action, 3000) - .onAction() - .subscribe(() => this.navigateLocation(status.success[0].entry.path)); - }); + private isSite(entry: MinimalNodeEntryEntity): boolean { + return entry.nodeType === 'st:site'; } private refresh(): void { - this.restoreProcessStatus.reset(); - this.selection = []; - this.emitDone(); - } - - private emitDone() { - const e = new CustomEvent('selection-node-restored', { bubbles: true }); - this.el.nativeElement.dispatchEvent(e); + this.contentManagementService.nodesRestored.next(); } } diff --git a/src/app/common/directives/node-unshare.directive.ts b/src/app/common/directives/node-unshare.directive.ts index abd6f2b71..19fdedfa7 100644 --- a/src/app/common/directives/node-unshare.directive.ts +++ b/src/app/common/directives/node-unshare.directive.ts @@ -23,22 +23,23 @@ * along with Alfresco. If not, see . */ -import { Directive, HostListener, Input, ElementRef } from '@angular/core'; -import { AlfrescoApiService } from '@alfresco/adf-core'; +import { Directive, HostListener, Input } from '@angular/core'; import { MinimalNodeEntity } from 'alfresco-js-api'; +import { ContentManagementService } from '../services/content-management.service'; +import { ContentApiService } from '../../services/content-api.service'; @Directive({ - selector: '[appUnshareNode]' + selector: '[acaUnshareNode]' }) export class NodeUnshareDirective { // tslint:disable-next-line:no-input-rename - @Input('appUnshareNode') + @Input('acaUnshareNode') selection: MinimalNodeEntity[]; constructor( - private apiService: AlfrescoApiService, - private el: ElementRef) { + private contentApi: ContentApiService, + private contentManagement: ContentManagementService) { } @HostListener('click') @@ -49,14 +50,8 @@ export class NodeUnshareDirective { } private async unshareLinks(links: MinimalNodeEntity[]) { - const promises = links.map(link => this.apiService.sharedLinksApi.deleteSharedLink(link.entry.id)); + const promises = links.map(link => this.contentApi.deleteSharedLink(link.entry.id).toPromise()); await Promise.all(promises); - this.emitDone(); + this.contentManagement.linksUnshared.next(); } - - private emitDone() { - const e = new CustomEvent('links-unshared', { bubbles: true }); - this.el.nativeElement.dispatchEvent(e); - } - } diff --git a/src/app/common/directives/node-versions.directive.ts b/src/app/common/directives/node-versions.directive.ts index f822e1b82..55c862ef2 100644 --- a/src/app/common/directives/node-versions.directive.ts +++ b/src/app/common/directives/node-versions.directive.ts @@ -23,24 +23,21 @@ * along with Alfresco. If not, see . */ -import { Directive, EventEmitter, HostListener, Input, Output } from '@angular/core'; - -import { TranslationService, NotificationService, AlfrescoApiService } from '@alfresco/adf-core'; -import { MinimalNodeEntity } from 'alfresco-js-api'; - -import { VersionManagerDialogAdapterComponent } from '../../components/versions-dialog/version-manager-dialog-adapter.component'; +import { Directive, HostListener, Input } from '@angular/core'; +import { MinimalNodeEntity, MinimalNodeEntryEntity } from 'alfresco-js-api'; +import { NodeVersionsDialogComponent } from '../../dialogs/node-versions/node-versions.dialog'; import { MatDialog } from '@angular/material'; +import { Store } from '@ngrx/store'; +import { AppStore } from '../../store/states/app.state'; +import { SnackbarErrorAction } from '../../store/actions'; +import { ContentApiService } from '../../services/content-api.service'; @Directive({ - selector: '[app-node-versions]' + selector: '[acaNodeVersions]' }) export class NodeVersionsDirective { - - @Input('app-node-versions') - selection: MinimalNodeEntity[]; - - @Output() - nodeVersionError: EventEmitter = new EventEmitter(); + // tslint:disable-next-line:no-input-rename + @Input('acaNodeVersions') node: MinimalNodeEntity; @HostListener('click') onClick() { @@ -48,32 +45,40 @@ export class NodeVersionsDirective { } constructor( - private apiService: AlfrescoApiService, - private dialog: MatDialog, - private notification: NotificationService, - private translation: TranslationService + private store: Store, + private contentApi: ContentApiService, + private dialog: MatDialog ) {} - onManageVersions() { - const contentEntry = this.selection[0].entry; - const nodeId = (contentEntry).nodeId; - - this.apiService.getInstance().nodes.getNodeInfo(nodeId || contentEntry.id, { - include: ['allowableOperations'] - }).then(entry => this.openVersionManagerDialog(entry)); + async onManageVersions() { + if (this.node && this.node.entry) { + let entry = this.node.entry; + if (entry.nodeId || (entry).guid) { + entry = await this.contentApi.getNodeInfo( + entry.nodeId || (entry).id + ).toPromise(); + this.openVersionManagerDialog(entry); + } else { + this.openVersionManagerDialog(entry); + } + } else if (this.node) { + this.openVersionManagerDialog(this.node); + } } - openVersionManagerDialog(contentEntry) { - if (contentEntry.isFile) { - this.dialog.open( - VersionManagerDialogAdapterComponent, - { data: { contentEntry }, panelClass: 'adf-version-manager-dialog', width: '630px' }); + openVersionManagerDialog(node: MinimalNodeEntryEntity) { + // workaround Shared + if (node.isFile || node.nodeId) { + this.dialog.open(NodeVersionsDialogComponent, { + data: { node }, + panelClass: 'adf-version-manager-dialog-panel', + width: '630px' + }); } else { - const translatedErrorMessage: any = this.translation.get('APP.MESSAGES.ERRORS.PERMISSION'); - this.notification.openSnackMessage(translatedErrorMessage.value, 4000); - - this.nodeVersionError.emit(); + this.store.dispatch( + new SnackbarErrorAction('APP.MESSAGES.ERRORS.PERMISSION') + ); } } } diff --git a/src/app/common/services/content-management.service.ts b/src/app/common/services/content-management.service.ts index 48e71eb93..0d9e7284a 100644 --- a/src/app/common/services/content-management.service.ts +++ b/src/app/common/services/content-management.service.ts @@ -25,83 +25,15 @@ import { Subject } from 'rxjs/Rx'; import { Injectable } from '@angular/core'; -import { AlfrescoApiService, NotificationService, TranslationService } from '@alfresco/adf-core'; -import { Node } from 'alfresco-js-api'; - @Injectable() export class ContentManagementService { - - nodeDeleted = new Subject(); - nodeMoved = new Subject(); - nodeRestored = new Subject(); - - constructor(private api: AlfrescoApiService, - private notification: NotificationService, - private translation: TranslationService) { - } - - nodeHasPermission(node: Node, permission: string): boolean { - if (node && permission) { - const allowableOperations = node.allowableOperations || []; - - if (allowableOperations.indexOf(permission) > -1) { - return true; - } - } - - return false; - } - - canDeleteNode(node: Node): boolean { - return this.nodeHasPermission(node, 'delete'); - } - - canMoveNode(node: Node): boolean { - return this.nodeHasPermission(node, 'delete'); - } - - canCopyNode(node: Node): boolean { - return true; - } - - async deleteNode(node: Node) { - if (this.canDeleteNode(node)) { - try { - await this.api.nodesApi.deleteNode(node.id); - - this.notification - .openSnackMessageAction( - this.translation.instant('APP.MESSAGES.INFO.NODE_DELETION.SINGULAR', { name: node.name }), - this.translation.translate.instant('APP.ACTIONS.UNDO'), - 10000 - ) - .onAction() - .subscribe(() => { - this.restoreNode(node); - }); - - this.nodeDeleted.next(node.id); - } catch { - this.notification.openSnackMessage( - this.translation.instant('APP.MESSAGES.ERRORS.NODE_DELETION', { name: node.name }), - 10000 - ); - } - } - } - - async restoreNode(node: Node) { - if (node) { - try { - await this.api.nodesApi.restoreNode(node.id); - this.nodeRestored.next(node.id); - } catch { - this.notification.openSnackMessage( - this.translation.instant('APP.MESSAGES.ERRORS.NODE_RESTORE', { name: node.name }), - 3000 - ); - } - } - } + nodesMoved = new Subject(); + nodesDeleted = new Subject(); + nodesPurged = new Subject(); + nodesRestored = new Subject(); + folderEdited = new Subject(); + folderCreated = new Subject(); + siteDeleted = new Subject(); + linksUnshared = new Subject(); } diff --git a/src/app/common/services/node-actions.service.spec.ts b/src/app/common/services/node-actions.service.spec.ts index 2942e578e..9902a7ce3 100644 --- a/src/app/common/services/node-actions.service.spec.ts +++ b/src/app/common/services/node-actions.service.spec.ts @@ -24,21 +24,14 @@ */ import { TestBed, async } from '@angular/core/testing'; -import { MatDialog, MatDialogModule, MatIconModule } from '@angular/material'; -import { OverlayModule } from '@angular/cdk/overlay'; +import { MatDialog } from '@angular/material'; import { Observable } from 'rxjs/Rx'; -import { - TranslationMock, AlfrescoApiService, NodesApiService, - TranslationService, ContentService, AuthenticationService, - UserPreferencesService, AppConfigService, StorageService, - CookieService, LogService, ThumbnailService -} from '@alfresco/adf-core'; +import { AlfrescoApiService, TranslationService } from '@alfresco/adf-core'; import { DocumentListService } from '@alfresco/adf-content-services'; - import { NodeActionsService } from './node-actions.service'; import { MinimalNodeEntryEntity } from 'alfresco-js-api'; -import { TranslateModule } from '@ngx-translate/core'; -import { HttpClientModule } from '@angular/common/http'; +import { AppTestingModule } from '../../testing/app-testing.module'; +import { ContentApiService } from '../../services/content-api.service'; class TestNode { entry?: MinimalNodeEntryEntity; @@ -67,10 +60,10 @@ describe('NodeActionsService', () => { const emptyChildrenList = {list: {entries: []}}; let service: NodeActionsService; let apiService: AlfrescoApiService; - let nodesApiService: NodesApiService; let nodesApi; const spyOnSuccess = jasmine.createSpy('spyOnSuccess'); const spyOnError = jasmine.createSpy('spyOnError'); + let contentApi: ContentApiService; const helper = { fakeCopyNode: (isForbidden: boolean = false, nameExistingOnDestination?: string) => { @@ -110,33 +103,16 @@ describe('NodeActionsService', () => { beforeEach(() => { TestBed.configureTestingModule({ imports: [ - MatDialogModule, - MatIconModule, - HttpClientModule, - TranslateModule.forRoot(), - OverlayModule - ], - providers: [ - AlfrescoApiService, - NodesApiService, - { provide: TranslationService, useClass: TranslationMock }, - AuthenticationService, - UserPreferencesService, - AppConfigService, - CookieService, - LogService, - ThumbnailService, - StorageService, - ContentService, - DocumentListService, - NodeActionsService + AppTestingModule ] }); + contentApi = TestBed.get(ContentApiService); + service = TestBed.get(NodeActionsService); apiService = TestBed.get(AlfrescoApiService); apiService.reset(); - nodesApiService = TestBed.get(NodesApiService); + nodesApi = apiService.getInstance().nodes; }); @@ -884,7 +860,7 @@ describe('NodeActionsService', () => { beforeEach(() => { parentFolderToMove = new TestNode('parent-folder', !isFile, 'conflicting-name'); - spyOnDelete = spyOn(nodesApiService, 'deleteNode').and.returnValue(Observable.of(null)); + spyOnDelete = spyOn(contentApi, 'deleteNode').and.returnValue(Observable.of(null)); }); afterEach(() => { @@ -1069,21 +1045,21 @@ describe('NodeActionsService', () => { baseName: 'withExtension.txt', expected: 'withExtension-1.txt' }, { - name: 'with-lineStringSufix.txt', - baseName: 'with-lineStringSufix.txt', - expected: 'with-lineStringSufix-1.txt' + name: 'with-lineStringSuffix.txt', + baseName: 'with-lineStringSuffix.txt', + expected: 'with-lineStringSuffix-1.txt' }, { name: 'noExtension-1', baseName: 'noExtension-1', expected: 'noExtension-1-1' }, { - name: 'with-lineNumberSufix-1.txt', - baseName: 'with-lineNumberSufix-1.txt', - expected: 'with-lineNumberSufix-1-1.txt' + name: 'with-lineNumberSuffix-1.txt', + baseName: 'with-lineNumberSuffix-1.txt', + expected: 'with-lineNumberSuffix-1-1.txt' }, { - name: 'with-lineNumberSufix.txt', + name: 'with-lineNumberSuffix.txt', baseName: undefined, - expected: 'with-lineNumberSufix-1.txt' + expected: 'with-lineNumberSuffix-1.txt' }, { name: 'noExtension-1', baseName: 'noExtension', diff --git a/src/app/common/services/node-actions.service.ts b/src/app/common/services/node-actions.service.ts index 68e52b0df..1d49bf082 100644 --- a/src/app/common/services/node-actions.service.ts +++ b/src/app/common/services/node-actions.service.ts @@ -27,9 +27,10 @@ import { Injectable } from '@angular/core'; import { MatDialog } from '@angular/material'; import { Observable, Subject } from 'rxjs/Rx'; -import { AlfrescoApiService, ContentService, NodesApiService, DataColumn, TranslationService } from '@alfresco/adf-core'; +import { AlfrescoApiService, ContentService, DataColumn, TranslationService } from '@alfresco/adf-core'; import { DocumentListService, ContentNodeSelectorComponent, ContentNodeSelectorComponentData } from '@alfresco/adf-content-services'; import { MinimalNodeEntity, MinimalNodeEntryEntity, SitePaging } from 'alfresco-js-api'; +import { ContentApiService } from '../../services/content-api.service'; @Injectable() export class NodeActionsService { @@ -42,10 +43,10 @@ export class NodeActionsService { isSitesDestinationAvailable = false; constructor(private contentService: ContentService, + private contentApi: ContentApiService, private dialog: MatDialog, private documentListService: DocumentListService, private apiService: AlfrescoApiService, - private nodesApi: NodesApiService, private translation: TranslationService) {} /** @@ -422,7 +423,7 @@ export class NodeActionsService { // Check if there's nodeId for Shared Files const nodeEntryId = nodeEntry.nodeId || nodeEntry.id; // delete it from location - return this.nodesApi.deleteNode(nodeEntryId) + return this.contentApi.deleteNode(nodeEntryId) .flatMap(() => { this.moveDeletedEntries.push(nodeEntry); return Observable.of(newContent); diff --git a/src/app/common/services/page-title.service.ts b/src/app/common/services/page-title.service.ts new file mode 100644 index 000000000..c1a723c3a --- /dev/null +++ b/src/app/common/services/page-title.service.ts @@ -0,0 +1,67 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { Injectable } from '@angular/core'; +import { Title } from '@angular/platform-browser'; +import { AppConfigService, TranslationService } from '@alfresco/adf-core'; + +@Injectable() +export class PageTitleService { + + private originalTitle = ''; + private translatedTitle = ''; + + constructor( + private titleService: Title, + private appConfig: AppConfigService, + private translationService: TranslationService) { + translationService.translate.onLangChange.subscribe(() => this.onLanguageChanged()); + // TODO: contribute this fix to ADF 2.5.0 + translationService.translate.onTranslationChange.subscribe(() => this.onLanguageChanged()); + } + + /** + * Sets the page title. + * @param value The new title + */ + setTitle(value: string = '') { + this.originalTitle = value; + this.translatedTitle = this.translationService.instant(value); + + this.updateTitle(); + } + + private onLanguageChanged() { + this.translatedTitle = this.translationService.instant(this.originalTitle); + this.updateTitle(); + } + + private updateTitle() { + const name = this.appConfig.get('application.name') || 'Alfresco ADF Application'; + + const title = this.translatedTitle ? `${this.translatedTitle} - ${name}` : `${name}`; + this.titleService.setTitle(title); + } +} diff --git a/src/app/common/services/profile.resolver.ts b/src/app/common/services/profile.resolver.ts new file mode 100644 index 000000000..429bd3c44 --- /dev/null +++ b/src/app/common/services/profile.resolver.ts @@ -0,0 +1,61 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { Store } from '@ngrx/store'; +import { Injectable } from '@angular/core'; +import { Resolve, Router } from '@angular/router'; +import { Person } from 'alfresco-js-api'; +import { Observable } from 'rxjs/Observable'; +import { AppStore } from '../../store/states/app.state'; +import { SetUserAction } from '../../store/actions/user.actions'; +import { ContentApiService } from '../../services/content-api.service'; + +@Injectable() +export class ProfileResolver implements Resolve { + constructor( + private store: Store, + private contentApi: ContentApiService, + private router: Router + ) {} + + resolve(): Observable { + return new Observable(observer => { + this.contentApi.getPerson('-me-').subscribe( + person => { + this.store.dispatch(new SetUserAction(person.entry)); + observer.next(person.entry); + observer.complete(); + }, + err => { + if (err && err.status === 401) { + observer.next(null); + observer.complete(); + this.router.navigate(['login']); + } + } + ); + }); + } +} diff --git a/src/app/components/about/about.component.html b/src/app/components/about/about.component.html index ba1e413f5..057ba3e45 100644 --- a/src/app/components/about/about.component.html +++ b/src/app/components/about/about.component.html @@ -1,12 +1,12 @@
-
+
Alfresco Content Services
-

version: {{ ecmVersion.edition }} {{ ecmVersion.version.display }}

+

version: {{ repository.edition }} {{ repository.version.display }}

-
+
License
diff --git a/src/app/components/about/about.component.scss b/src/app/components/about/about.component.scss deleted file mode 100644 index 6e5d684cd..000000000 --- a/src/app/components/about/about.component.scss +++ /dev/null @@ -1,37 +0,0 @@ -@import 'variables'; - -article { - color: $alfresco-secondary-text-color; -} - -article:first-of-type { - padding-bottom: 0; -} - -article:last-of-type { - margin-bottom: 50px; -} - -header { - line-height: 24px; - font-size: 14px; - font-weight: 800; - letter-spacing: -0.2px; -} - -a { - text-decoration: none; - color: $alfresco-primary-text-color; -} - -.padding { - padding: 25px; -} - -.padding-top-bottom { - padding: 25px 0 25px 0; -} - -.padding-left-right { - padding: 0 25px 0 25px; -} diff --git a/src/app/components/about/about.component.theme.scss b/src/app/components/about/about.component.theme.scss new file mode 100644 index 000000000..71be08396 --- /dev/null +++ b/src/app/components/about/about.component.theme.scss @@ -0,0 +1,39 @@ +@mixin aca-about-component-theme($theme) { + $foreground: map-get($theme, foreground); + + article { + color: mat-color($foreground, text, 0.54); + } + + article:first-of-type { + padding-bottom: 0; + } + + article:last-of-type { + margin-bottom: 50px; + } + + header { + line-height: 24px; + font-size: 14px; + font-weight: 800; + letter-spacing: -0.2px; + } + + a { + text-decoration: none; + color: mat-color($foreground, text, 0.87); + } + + .padding { + padding: 25px; + } + + .padding-top-bottom { + padding: 25px 0 25px 0; + } + + .padding-left-right { + padding: 0 25px 0 25px; + } +} diff --git a/src/app/components/about/about.component.ts b/src/app/components/about/about.component.ts index 8bcad5897..3b5cb988f 100644 --- a/src/app/components/about/about.component.ts +++ b/src/app/components/about/about.component.ts @@ -25,16 +25,16 @@ import { Component, OnInit } from '@angular/core'; import { Http } from '@angular/http'; -import { DiscoveryApiService } from '@alfresco/adf-core'; -import { EcmProductVersionModel, ObjectDataTableAdapter } from '@alfresco/adf-core'; +import { ObjectDataTableAdapter } from '@alfresco/adf-core'; +import { ContentApiService } from '../../services/content-api.service'; +import { RepositoryInfo } from 'alfresco-js-api'; @Component({ selector: 'app-about', - templateUrl: './about.component.html', - styleUrls: [ './about.component.scss' ] + templateUrl: './about.component.html' }) export class AboutComponent implements OnInit { - ecmVersion: EcmProductVersionModel = null; + repository: RepositoryInfo; data: ObjectDataTableAdapter; status: ObjectDataTableAdapter; license: ObjectDataTableAdapter; @@ -43,15 +43,17 @@ export class AboutComponent implements OnInit { releaseVersion = ''; constructor( - private discovery: DiscoveryApiService, + private contentApi: ContentApiService, private http: Http ) {} ngOnInit() { - this.discovery.getEcmProductInfo().subscribe((ecmVers) => { - this.ecmVersion = ecmVers; + this.contentApi.getRepositoryInformation() + .map(node => node.entry.repository) + .subscribe(repository => { + this.repository = repository; - this.modules = new ObjectDataTableAdapter(this.ecmVersion.modules, [ + this.modules = new ObjectDataTableAdapter(repository.modules, [ {type: 'text', key: 'id', title: 'ID', sortable: true}, {type: 'text', key: 'title', title: 'Title', sortable: true}, {type: 'text', key: 'version', title: 'Description', sortable: true}, @@ -61,22 +63,25 @@ export class AboutComponent implements OnInit { {type: 'text', key: 'versionMax', title: 'Version Max', sortable: true} ]); - this.status = new ObjectDataTableAdapter([this.ecmVersion.status], [ + this.status = new ObjectDataTableAdapter([repository.status], [ {type: 'text', key: 'isReadOnly', title: 'Read Only', sortable: true}, {type: 'text', key: 'isAuditEnabled', title: 'Audit Enable', sortable: true}, {type: 'text', key: 'isQuickShareEnabled', title: 'Quick Shared Enable', sortable: true}, {type: 'text', key: 'isThumbnailGenerationEnabled', title: 'Thumbnail Generation', sortable: true} ]); - this.license = new ObjectDataTableAdapter([this.ecmVersion.license], [ - {type: 'date', key: 'issuedAt', title: 'Issued At', sortable: true}, - {type: 'date', key: 'expiresAt', title: 'Expires At', sortable: true}, - {type: 'text', key: 'remainingDays', title: 'Remaining Days', sortable: true}, - {type: 'text', key: 'holder', title: 'Holder', sortable: true}, - {type: 'text', key: 'mode', title: 'Type', sortable: true}, - {type: 'text', key: 'isClusterEnabled', title: 'Cluster Enabled', sortable: true}, - {type: 'text', key: 'isCryptodocEnabled', title: 'Cryptodoc Enable', sortable: true} - ]); + + if (repository.license) { + this.license = new ObjectDataTableAdapter([repository.license], [ + {type: 'date', key: 'issuedAt', title: 'Issued At', sortable: true}, + {type: 'date', key: 'expiresAt', title: 'Expires At', sortable: true}, + {type: 'text', key: 'remainingDays', title: 'Remaining Days', sortable: true}, + {type: 'text', key: 'holder', title: 'Holder', sortable: true}, + {type: 'text', key: 'mode', title: 'Type', sortable: true}, + {type: 'text', key: 'isClusterEnabled', title: 'Cluster Enabled', sortable: true}, + {type: 'text', key: 'isCryptodocEnabled', title: 'Cryptodoc Enable', sortable: true} + ]); + } }); this.http.get('/versions.json') diff --git a/src/app/components/current-user/current-user.component.html b/src/app/components/current-user/current-user.component.html index 638f9230b..46858be8c 100644 --- a/src/app/components/current-user/current-user.component.html +++ b/src/app/components/current-user/current-user.component.html @@ -1,25 +1,23 @@ -
-
-
{{ userName }}
-
- {{ userInitials }} -
+
+
{{ (profile$ | async)?.userName }}
+
+ {{ (profile$ | async)?.initials }}
- - - - - - - - - -
+ + + + + + + + + + diff --git a/src/app/components/current-user/current-user.component.scss b/src/app/components/current-user/current-user.component.scss deleted file mode 100644 index b301ce7bc..000000000 --- a/src/app/components/current-user/current-user.component.scss +++ /dev/null @@ -1,49 +0,0 @@ -@import 'variables'; - -$am-avatar-size: 40px; - -$am-avatar-light-bg: rgba(white, .15); -$am-avatar-dark-bg: rgba(black, .15); - -:host { - font-weight: lighter; - position: relative; - - color: $alfresco-white; - line-height: 20px; - - .am-avatar { - margin-left: 5px; - cursor: pointer; - - display: inline-block; - width: $am-avatar-size; - height: $am-avatar-size; - line-height: $am-avatar-size; - font-size: 1.2em; - text-align: center; - color: inherit; - border-radius: 100%; - - &--light { - background: $am-avatar-light-bg; - } - - &--dark { - background: $am-avatar-dark-bg; - } - } - - .current-user__full-name { - width: 100px; - text-align: right; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - display:inline-block; - height: 20px; - font-size: 14px; - line-height: 1.43; - letter-spacing: -0.2px; - } -} diff --git a/src/app/components/current-user/current-user.component.spec.ts b/src/app/components/current-user/current-user.component.spec.ts deleted file mode 100644 index 5eef09d37..000000000 --- a/src/app/components/current-user/current-user.component.spec.ts +++ /dev/null @@ -1,91 +0,0 @@ -/*! - * @license - * Alfresco Example Content Application - * - * Copyright (C) 2005 - 2018 Alfresco Software Limited - * - * This file is part of the Alfresco Example Content Application. - * If the software was purchased under a paid Alfresco license, the terms of - * the paid license agreement will prevail. Otherwise, the software is - * provided under the following open source license terms: - * - * The Alfresco Example Content Application is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * The Alfresco Example Content Application is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Alfresco. If not, see . - */ - -import { NO_ERRORS_SCHEMA } from '@angular/core'; -import { TestBed, async } from '@angular/core/testing'; -import { RouterTestingModule } from '@angular/router/testing'; -import { Observable } from 'rxjs/Rx'; -import { HttpClientModule } from '@angular/common/http'; -import { TranslateModule } from '@ngx-translate/core'; -import { NoopAnimationsModule } from '@angular/platform-browser/animations'; -import { MatMenuModule } from '@angular/material'; -import { - TranslationService, TranslationMock, AlfrescoApiService, - AppConfigService, StorageService, PeopleContentService - } from '@alfresco/adf-core'; - -import { CurrentUserComponent } from './current-user.component'; - -describe('CurrentUserComponent', () => { - let fixture; - let component; - let peopleApi: PeopleContentService; - let user; - - beforeEach(() => { - user = { entry: { firstName: 'joe', lastName: 'doe' } }; - }); - - beforeEach(async(() => { - TestBed.configureTestingModule({ - imports: [ - HttpClientModule, - TranslateModule.forRoot(), - NoopAnimationsModule, - MatMenuModule, - RouterTestingModule - ], - declarations: [ - CurrentUserComponent - ], - providers: [ - { provide: TranslationService, useClass: TranslationMock }, - AlfrescoApiService, - AppConfigService, - StorageService, - PeopleContentService - ], - schemas: [ NO_ERRORS_SCHEMA ] - }) - .compileComponents() - .then(() => { - fixture = TestBed.createComponent(CurrentUserComponent); - component = fixture.componentInstance; - peopleApi = TestBed.get(PeopleContentService); - - spyOn(peopleApi, 'getCurrentPerson').and.returnValue(Observable.of(user)); - - fixture.detectChanges(); - }); - })); - - it('updates user data', () => { - expect(component.user).toBe(user.entry); - }); - - it('get user initials', () => { - expect(component.userInitials).toBe('jd'); - }); -}); diff --git a/src/app/components/current-user/current-user.component.theme.scss b/src/app/components/current-user/current-user.component.theme.scss new file mode 100644 index 000000000..6a22fa665 --- /dev/null +++ b/src/app/components/current-user/current-user.component.theme.scss @@ -0,0 +1,38 @@ +@mixin aca-current-user-theme($theme) { + $background: map-get($theme, background); + $am-avatar-size: 40px; + + .aca-current-user { + font-weight: lighter; + position: relative; + color: mat-color($background, card); + line-height: 20px; + + .am-avatar { + margin-left: 5px; + cursor: pointer; + display: inline-block; + width: $am-avatar-size; + height: $am-avatar-size; + line-height: $am-avatar-size; + font-size: 1.2em; + text-align: center; + color: inherit; + border-radius: 100%; + background-color: mat-color($background, card, .15); + } + + .current-user__full-name { + width: 100px; + text-align: right; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + display:inline-block; + height: 20px; + font-size: 14px; + line-height: 1.43; + letter-spacing: -0.2px; + } + } +} diff --git a/src/app/components/current-user/current-user.component.ts b/src/app/components/current-user/current-user.component.ts index 6ef40663a..2f10576f6 100644 --- a/src/app/components/current-user/current-user.component.ts +++ b/src/app/components/current-user/current-user.component.ts @@ -23,56 +23,24 @@ * along with Alfresco. If not, see . */ -import { Component, OnInit, OnDestroy } from '@angular/core'; -import { PeopleContentService, AppConfigService } from '@alfresco/adf-core'; -import { Subscription } from 'rxjs/Rx'; +import { Component, ViewEncapsulation } from '@angular/core'; +import { Store } from '@ngrx/store'; +import { Observable } from 'rxjs/Rx'; +import { selectUser, appLanguagePicker } from '../../store/selectors/app.selectors'; +import { AppStore, ProfileState } from '../../store/states'; @Component({ - selector: 'app-current-user', + selector: 'aca-current-user', templateUrl: './current-user.component.html', - styleUrls: [ './current-user.component.scss' ] + encapsulation: ViewEncapsulation.None, + host: { class: 'aca-current-user' } }) -export class CurrentUserComponent implements OnInit, OnDestroy { - private subscriptions: Subscription[] = []; +export class CurrentUserComponent { + profile$: Observable; + languagePicker$: Observable; - user: any = null; - - constructor( - private peopleApi: PeopleContentService, - private appConfig: AppConfigService - ) {} - - ngOnInit() { - this.subscriptions = this.subscriptions.concat([ - this.peopleApi.getCurrentPerson().subscribe((person: any) => this.user = person.entry) - ]); - } - - ngOnDestroy() { - this.subscriptions.forEach(s => s.unsubscribe()); - } - - get userFirstName(): string { - const { user } = this; - return user ? (user.firstName || '') : ''; - } - - get userLastName(): string { - const { user } = this; - return user ? (user.lastName || '') : ''; - } - - get userName(): string { - const { userFirstName: first, userLastName: last } = this; - return `${first} ${last}`; - } - - get userInitials(): string { - const { userFirstName: first, userLastName: last } = this; - return [ first[0], last[0] ].join(''); - } - - get showLanguagePicker() { - return this.appConfig.get('languagePicker') || false; + constructor(store: Store) { + this.profile$ = store.select(selectUser); + this.languagePicker$ = store.select(appLanguagePicker); } } diff --git a/src/app/components/custom-dl-row/custom-dl-row.component.html b/src/app/components/custom-dl-row/custom-dl-row.component.html new file mode 100644 index 000000000..be695e086 --- /dev/null +++ b/src/app/components/custom-dl-row/custom-dl-row.component.html @@ -0,0 +1,21 @@ +
+
+ {{ name }} + + {{ name }} + + ( {{ title }} ) +
+ +
{{ description }}
+ +
+ {{ 'APP.BROWSE.SEARCH.CUSTOM_ROW.MODIFIED' | translate }}: {{ modifiedAt | date:'medium' }} + + by {{ user }} + + | {{ 'APP.BROWSE.SEARCH.CUSTOM_ROW.SIZE' | translate }}: {{ size | adfFileSize }} +
+ +
{{ 'APP.BROWSE.SEARCH.CUSTOM_ROW.LOCATION' | translate }}:
+
diff --git a/src/app/components/custom-dl-row/custom-dl-row.component.scss b/src/app/components/custom-dl-row/custom-dl-row.component.scss new file mode 100644 index 000000000..39b45278b --- /dev/null +++ b/src/app/components/custom-dl-row/custom-dl-row.component.scss @@ -0,0 +1,24 @@ +@import 'mixins'; + +.app-custom-search-row { + @include flex-column; +} + +.line { + margin: 5px 0; +} + +.bold { + font-weight: 400; + color: rgba(0, 0, 0, 0.87); +} + +.link { + text-decoration: none; + color: rgba(0, 0, 0, 0.87); +} + +.link:hover { + color: #2196F3; + text-decoration: underline; +} diff --git a/src/app/components/custom-dl-row/custom-dl-row.component.ts b/src/app/components/custom-dl-row/custom-dl-row.component.ts new file mode 100644 index 000000000..51a8b4514 --- /dev/null +++ b/src/app/components/custom-dl-row/custom-dl-row.component.ts @@ -0,0 +1,108 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { Component, Input, OnInit } from '@angular/core'; +import { MinimalNodeEntryEntity } from 'alfresco-js-api'; +import { ViewNodeAction } from '../../store/actions/viewer.actions'; +import { Store } from '@ngrx/store'; +import { AppStore } from '../../store/states/app.state'; + +@Component({ + selector: 'app-custom-dl-row', + templateUrl: './custom-dl-row.component.html', + styleUrls: ['./custom-dl-row.component.scss'] +}) +export class CustomDlRowComponent implements OnInit { + private node: MinimalNodeEntryEntity; + + @Input() + context: any; + + constructor(private store: Store) {} + + ngOnInit() { + this.node = this.context.row.node.entry; + } + + + get name() { + return this.getValue('name'); + } + + get title() { + return this.getValue('properties["cm:title"]'); + } + + get description() { + return this.getValue('properties["cm:description"]'); + } + + get modifiedAt() { + return this.getValue('modifiedAt'); + } + + get size() { + return this.getValue('content.sizeInBytes'); + } + + get user() { + return this.getValue('modifiedByUser.displayName'); + } + + get hasDescription() { + return this.description; + } + + get hasTitle() { + return this.title; + } + + get hasSize() { + return this.size; + } + + get isFile() { + return this.getValue('isFile'); + } + + showPreview() { + const { id, name} = this.node; + + this.store.dispatch(new ViewNodeAction({ + id, + name + })); + } + + private getValue(path) { + return path + .replace('["', '.') + .replace('"]', '') + .replace('[', '.') + .replace(']', '') + .split('.') + .reduce((acc, part) => acc ? acc[part] : null, this.node); + } +} diff --git a/src/app/components/empty-folder/empty-folder.component.html b/src/app/components/empty-folder/empty-folder.component.html deleted file mode 100644 index 43ecf6bf8..000000000 --- a/src/app/components/empty-folder/empty-folder.component.html +++ /dev/null @@ -1,6 +0,0 @@ -
- {{ icon }} -

{{ title | translate }}

-

{{ subtitle | translate }}

- -
diff --git a/src/app/components/empty-folder/empty-folder.component.scss b/src/app/components/empty-folder/empty-folder.component.scss deleted file mode 100644 index 2da90ab1c..000000000 --- a/src/app/components/empty-folder/empty-folder.component.scss +++ /dev/null @@ -1,29 +0,0 @@ -@import 'variables'; - -.app-empty-folder { - color: $alfresco-secondary-text-color; - display: flex; - flex-direction: column; - align-items: center; - - &__icon { - font-size: 52px; - height: 52px; - width: 52px; - } - - p { - line-height: 0; - } - - &__title { - font-size: 18px; - font-weight: 600; - } - - &__subtitle, - &__text { - font-size: 14px; - font-weight: 300; - } -} diff --git a/src/app/components/empty-folder/empty-folder.component.spec.ts b/src/app/components/empty-folder/empty-folder.component.spec.ts deleted file mode 100644 index 32e4d5571..000000000 --- a/src/app/components/empty-folder/empty-folder.component.spec.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { MatIconModule } from '@angular/material'; -import { TranslateModule } from '@ngx-translate/core'; - -import { EmptyFolderComponent } from './empty-folder.component'; - -describe('EmptyFolderComponent', () => { - let component: EmptyFolderComponent; - let fixture: ComponentFixture; - - beforeEach(async(() => { - TestBed.configureTestingModule({ - imports: [ - MatIconModule, - TranslateModule.forRoot() - ], - declarations: [ - EmptyFolderComponent - ] - }) - .compileComponents(); - })); - - beforeEach(() => { - fixture = TestBed.createComponent(EmptyFolderComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/src/app/components/empty-folder/empty-folder.component.ts b/src/app/components/empty-folder/empty-folder.component.ts deleted file mode 100644 index 4e137d1de..000000000 --- a/src/app/components/empty-folder/empty-folder.component.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { Component, Input, ChangeDetectionStrategy, ViewEncapsulation } from '@angular/core'; - -@Component({ - selector: 'app-empty-folder', - templateUrl: './empty-folder.component.html', - styleUrls: ['./empty-folder.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush, - encapsulation: ViewEncapsulation.None, - // tslint:disable-next-line:use-host-property-decorator - host: { class: 'app-empty-folder' } -}) -export class EmptyFolderComponent { - - @Input() - icon = 'cake'; - - @Input() - title = ''; - - @Input() - subtitle = ''; -} diff --git a/src/app/components/favorites/favorites.component.html b/src/app/components/favorites/favorites.component.html index 8b54addb1..330b340d9 100644 --- a/src/app/components/favorites/favorites.component.html +++ b/src/app/components/favorites/favorites.component.html @@ -3,39 +3,36 @@ - + + [overlapTrigger]="false">
-
- + + [sorting]="[ 'modifiedAt', 'desc' ]" + (node-dblclick)="onNodeDoubleClick($event.detail?.node)"> - - + @@ -139,10 +126,10 @@ - + @@ -169,48 +156,12 @@ - - - - + +
-
- - - -
- -
- - - -
- - - - - - - -
- face - {{ 'VERSION.SELECTION.EMPTY' | translate }} -
-
-
-
+
+
diff --git a/src/app/components/favorites/favorites.component.spec.ts b/src/app/components/favorites/favorites.component.spec.ts index 13f571a1d..ae27594cd 100644 --- a/src/app/components/favorites/favorites.component.spec.ts +++ b/src/app/components/favorites/favorites.component.spec.ts @@ -25,40 +25,26 @@ import { Observable } from 'rxjs/Rx'; import { NO_ERRORS_SCHEMA } from '@angular/core'; -import { Router, ActivatedRoute } from '@angular/router'; -import { RouterTestingModule } from '@angular/router/testing'; -import { HttpClientModule } from '@angular/common/http'; -import { TestBed, async } from '@angular/core/testing'; +import { Router } from '@angular/router'; +import { TestBed, ComponentFixture } from '@angular/core/testing'; import { - NotificationService, TranslationService, TranslationMock, - NodesApiService, AlfrescoApiService, ContentService, - UserPreferencesService, LogService, AppConfigService, - StorageService, CookieService, ThumbnailService, - AuthenticationService, TimeAgoPipe, NodeNameTooltipPipe, - NodeFavoriteDirective, DataTableComponent + AlfrescoApiService, + TimeAgoPipe, NodeNameTooltipPipe, + NodeFavoriteDirective, DataTableComponent, AppConfigPipe } from '@alfresco/adf-core'; -import { DocumentListComponent, CustomResourcesService } from '@alfresco/adf-content-services'; - -import { TranslateModule } from '@ngx-translate/core'; -import { NoopAnimationsModule } from '@angular/platform-browser/animations'; -import { MatMenuModule, MatSnackBarModule, MatIconModule } from '@angular/material'; -import { DocumentListService } from '@alfresco/adf-content-services'; +import { DocumentListComponent } from '@alfresco/adf-content-services'; import { ContentManagementService } from '../../common/services/content-management.service'; -import { NodeInfoDirective } from '../../common/directives/node-info.directive'; -import { NodePermissionService } from '../../common/services/node-permission.service'; -import { AppConfigPipe } from '../../common/pipes/app-config.pipe'; import { FavoritesComponent } from './favorites.component'; +import { AppTestingModule } from '../../testing/app-testing.module'; +import { ContentApiService } from '../../services/content-api.service'; -describe('Favorites Routed Component', () => { - let fixture; +describe('FavoritesComponent', () => { + let fixture: ComponentFixture; let component: FavoritesComponent; - let nodesApi: NodesApiService; let alfrescoApi: AlfrescoApiService; - let alfrescoContentService: ContentService; let contentService: ContentManagementService; - let preferenceService: UserPreferencesService; - let notificationService: NotificationService; + let contentApi: ContentApiService; let router: Router; let page; let node; @@ -89,100 +75,68 @@ describe('Favorites Routed Component', () => { }; }); - beforeEach(async(() => { + beforeEach(() => { TestBed.configureTestingModule({ - imports: [ - MatMenuModule, - NoopAnimationsModule, - HttpClientModule, - TranslateModule.forRoot(), - RouterTestingModule, - MatSnackBarModule, MatIconModule - ], + imports: [ AppTestingModule ], declarations: [ DataTableComponent, TimeAgoPipe, NodeNameTooltipPipe, NodeFavoriteDirective, - NodeInfoDirective, DocumentListComponent, FavoritesComponent, AppConfigPipe ], - providers: [ - { provide: ActivatedRoute, useValue: { - snapshot: { data: { preferencePrefix: 'prefix' } } - } } , - { provide: TranslationService, useClass: TranslationMock }, - AuthenticationService, - UserPreferencesService, - AppConfigService, StorageService, CookieService, - AlfrescoApiService, - CustomResourcesService, - LogService, - NotificationService, - ContentManagementService, - ContentService, - NodesApiService, - NodePermissionService, - DocumentListService, - ThumbnailService - ], schemas: [ NO_ERRORS_SCHEMA ] - }) - .compileComponents().then(() => { - fixture = TestBed.createComponent(FavoritesComponent); - component = fixture.componentInstance; - - nodesApi = TestBed.get(NodesApiService); - notificationService = TestBed.get(NotificationService); - alfrescoApi = TestBed.get(AlfrescoApiService); - alfrescoApi.reset(); - alfrescoContentService = TestBed.get(ContentService); - contentService = TestBed.get(ContentManagementService); - preferenceService = TestBed.get(UserPreferencesService); - router = TestBed.get(Router); }); - })); - beforeEach(() => { + fixture = TestBed.createComponent(FavoritesComponent); + component = fixture.componentInstance; + + alfrescoApi = TestBed.get(AlfrescoApiService); + alfrescoApi.reset(); spyOn(alfrescoApi.favoritesApi, 'getFavorites').and.returnValue(Promise.resolve(page)); + + contentApi = TestBed.get(ContentApiService); + + contentService = TestBed.get(ContentManagementService); + router = TestBed.get(Router); }); describe('Events', () => { beforeEach(() => { - spyOn(component, 'refresh'); + spyOn(component, 'reload'); fixture.detectChanges(); }); it('should refresh on editing folder event', () => { - alfrescoContentService.folderEdit.next(null); + contentService.folderEdited.next(null); - expect(component.refresh).toHaveBeenCalled(); + expect(component.reload).toHaveBeenCalled(); }); it('should refresh on move node event', () => { - contentService.nodeMoved.next(null); + contentService.nodesMoved.next(null); - expect(component.refresh).toHaveBeenCalled(); + expect(component.reload).toHaveBeenCalled(); }); it('should refresh on node deleted event', () => { - contentService.nodeDeleted.next(null); + contentService.nodesDeleted.next(null); - expect(component.refresh).toHaveBeenCalled(); + expect(component.reload).toHaveBeenCalled(); }); it('should refresh on node restore event', () => { - contentService.nodeRestored.next(null); + contentService.nodesRestored.next(null); - expect(component.refresh).toHaveBeenCalled(); + expect(component.reload).toHaveBeenCalled(); }); }); describe('Node navigation', () => { beforeEach(() => { - spyOn(nodesApi, 'getNode').and.returnValue(Observable.of(node)); + spyOn(contentApi, 'getNode').and.returnValue(Observable.of({ entry: node})); spyOn(router, 'navigate'); fixture.detectChanges(); }); @@ -212,133 +166,14 @@ describe('Favorites Routed Component', () => { }); }); - describe('onNodeDoubleClick', () => { - beforeEach(() => { - spyOn(nodesApi, 'getNode').and.returnValue(Observable.of(node)); - fixture.detectChanges(); - }); - - it('navigates if node is a folder', () => { - node.isFolder = true; - spyOn(router, 'navigate'); - - component.onNodeDoubleClick(node); - - expect(router.navigate).toHaveBeenCalled(); - }); - - it('opens preview if node is a file', () => { - node.isFolder = false; - node.isFile = true; - spyOn(router, 'navigate').and.stub(); - - component.onNodeDoubleClick(node); - - expect(router.navigate['calls'].argsFor(0)[0]).toEqual(['./preview', 'folder-node']); - }); - }); - - describe('edit option', () => { - it('should return false if a file node is selected', () => { - const selection = [ - { - entry: { - isFolder: false, - isFile: true - } - } - ]; - - const result = component.showEditOption(selection); - expect(result).toBe(false); - }); - - it('should return false if multiple nodes are selected', () => { - const selection = [ - { - entry: { - isFolder: true, - isFile: false - } - }, - { - entry: { - isFolder: true, - isFile: false - } - } - ]; - - const result = component.showEditOption(selection); - expect(result).toBe(false); - }); - - it('should return true if selected node is a folder', () => { - const selection = [ - { - entry: { - isFolder: true, - isFile: false - } - } - ]; - - const result = component.showEditOption(selection); - expect(result).toBe(true); - }); - }); - describe('refresh', () => { it('should call document list reload', () => { spyOn(component.documentList, 'reload'); fixture.detectChanges(); - component.refresh(); + component.reload(); expect(component.documentList.reload).toHaveBeenCalled(); }); }); - - describe('onSortingChanged', () => { - it('should save sorting input', () => { - spyOn(preferenceService, 'set'); - - const event = { - detail: { - key: 'some-name', - direction: 'some-direction' - } - }; - - component.onSortingChanged(event); - - expect(preferenceService.set).toHaveBeenCalledWith('prefix.sorting.key', 'some-name'); - expect(preferenceService.set).toHaveBeenCalledWith('prefix.sorting.direction', 'some-direction'); - }); - - it('should save default sorting when no input', () => { - spyOn(preferenceService, 'set'); - - const event = { - detail: {} - }; - - component.onSortingChanged(event); - - expect(preferenceService.set).toHaveBeenCalledWith('prefix.sorting.key', 'modifiedAt'); - expect(preferenceService.set).toHaveBeenCalledWith('prefix.sorting.direction', 'desc'); - }); - }); - - describe('openSnackMessage', () => { - it('should call notification service', () => { - const message = 'notification message'; - - spyOn(notificationService, 'openSnackMessage'); - - component.openSnackMessage(message); - - expect(notificationService.openSnackMessage).toHaveBeenCalledWith(message, 4000); - }); - }); }); diff --git a/src/app/components/favorites/favorites.component.ts b/src/app/components/favorites/favorites.component.ts index a1c999a15..98336e2e7 100644 --- a/src/app/components/favorites/favorites.component.ts +++ b/src/app/components/favorites/favorites.component.ts @@ -23,116 +23,78 @@ * along with Alfresco. If not, see . */ -import { Component, ViewChild, OnInit, OnDestroy } from '@angular/core'; -import { Router, ActivatedRoute } from '@angular/router'; -import { Subscription } from 'rxjs/Rx'; - -import { MinimalNodeEntryEntity, MinimalNodeEntity, PathElementEntity, PathInfo } from 'alfresco-js-api'; -import { ContentService, NodesApiService, UserPreferencesService, NotificationService } from '@alfresco/adf-core'; -import { DocumentListComponent } from '@alfresco/adf-content-services'; - +import { Component, OnInit } from '@angular/core'; +import { Router } from '@angular/router'; +import { Store } from '@ngrx/store'; +import { + MinimalNodeEntity, + MinimalNodeEntryEntity, + PathElementEntity, + PathInfo +} from 'alfresco-js-api'; import { ContentManagementService } from '../../common/services/content-management.service'; import { NodePermissionService } from '../../common/services/node-permission.service'; +import { AppStore } from '../../store/states/app.state'; import { PageComponent } from '../page.component'; +import { ContentApiService } from '../../services/content-api.service'; @Component({ templateUrl: './favorites.component.html' }) -export class FavoritesComponent extends PageComponent implements OnInit, OnDestroy { - - @ViewChild(DocumentListComponent) - documentList: DocumentListComponent; - - private subscriptions: Subscription[] = []; - - sorting = [ 'modifiedAt', 'desc' ]; - - constructor(private router: Router, - private route: ActivatedRoute, - private nodesApi: NodesApiService, - private contentService: ContentService, - private content: ContentManagementService, - private notificationService: NotificationService, - public permission: NodePermissionService, - preferences: UserPreferencesService) { - super(preferences); - - const sortingKey = preferences.get(`${this.prefix}.sorting.key`) || 'modifiedAt'; - const sortingDirection = preferences.get(`${this.prefix}.sorting.direction`) || 'desc'; - - this.sorting = [sortingKey, sortingDirection]; +export class FavoritesComponent extends PageComponent implements OnInit { + constructor( + private router: Router, + store: Store, + private contentApi: ContentApiService, + private content: ContentManagementService, + public permission: NodePermissionService + ) { + super(store); } ngOnInit() { + super.ngOnInit(); + this.subscriptions = this.subscriptions.concat([ - this.content.nodeDeleted.subscribe(() => this.refresh()), - this.content.nodeRestored.subscribe(() => this.refresh()), - this.contentService.folderEdit.subscribe(() => this.refresh()), - this.content.nodeMoved.subscribe(() => this.refresh()) + this.content.nodesDeleted.subscribe(() => this.reload()), + this.content.nodesRestored.subscribe(() => this.reload()), + this.content.folderEdited.subscribe(() => this.reload()), + this.content.nodesMoved.subscribe(() => this.reload()) ]); } - ngOnDestroy() { - this.subscriptions.forEach(s => s.unsubscribe()); - } - - fetchNodes(): void { - // todo: remove once all views migrate to native data source - } - navigate(favorite: MinimalNodeEntryEntity) { const { isFolder, id } = favorite; // TODO: rework as it will fail on non-English setups const isSitePath = (path: PathInfo): boolean => { - return path.elements.some(({ name }: PathElementEntity) => (name === 'Sites')); + return path.elements.some( + ({ name }: PathElementEntity) => name === 'Sites' + ); }; if (isFolder) { - this.nodesApi + this.contentApi .getNode(id) + .map(node => node.entry) .subscribe(({ path }: MinimalNodeEntryEntity) => { - const routeUrl = isSitePath(path) ? '/libraries' : '/personal-files'; - this.router.navigate([ routeUrl, id ]); + const routeUrl = isSitePath(path) + ? '/libraries' + : '/personal-files'; + this.router.navigate([routeUrl, id]); }); } } - onNodeDoubleClick(node: MinimalNodeEntryEntity) { - if (node) { - if (node.isFolder) { - this.navigate(node); + onNodeDoubleClick(node: MinimalNodeEntity) { + if (node && node.entry) { + if (node.entry.isFolder) { + this.navigate(node.entry); } - if (node.isFile) { - this.router.navigate(['./preview', node.id], { relativeTo: this.route }); + if (node.entry.isFile) { + this.showPreview(node); } } } - - showEditOption(selection: MinimalNodeEntity[]) { - return selection && selection.length === 1 && selection[0].entry.isFolder; - } - - refresh(): void { - if (this.documentList) { - this.documentList.reload(); - } - } - - onSortingChanged(event: CustomEvent) { - this.preferences.set(`${this.prefix}.sorting.key`, event.detail.key || 'modifiedAt'); - this.preferences.set(`${this.prefix}.sorting.direction`, event.detail.direction || 'desc'); - } - - openSnackMessage(event: any) { - this.notificationService.openSnackMessage( - event, - 4000 - ); - } - - private get prefix() { - return this.route.snapshot.data.preferencePrefix; - } } diff --git a/src/app/components/files/files.component.html b/src/app/components/files/files.component.html index 27c2380e7..56515fd14 100644 --- a/src/app/components/files/files.component.html +++ b/src/app/components/files/files.component.html @@ -1,43 +1,40 @@
- + + [overlapTrigger]="false"> @@ -103,31 +94,23 @@
- +
-
+
- + (node-dblclick)="onNodeDoubleClick($event.detail?.node)"> - - - - + +
-
- - - -
- -
- - - -
- - - - - - - -
- face - {{ 'VERSION.SELECTION.EMPTY' | translate }} -
-
-
-
+
+
diff --git a/src/app/components/files/files.component.spec.ts b/src/app/components/files/files.component.spec.ts index 04c960c0b..88868b150 100644 --- a/src/app/components/files/files.component.spec.ts +++ b/src/app/components/files/files.component.spec.ts @@ -24,111 +24,70 @@ */ import { Observable } from 'rxjs/Rx'; -import { TestBed, async } from '@angular/core/testing'; +import { TestBed, fakeAsync, tick, ComponentFixture } from '@angular/core/testing'; import { NO_ERRORS_SCHEMA } from '@angular/core'; import { Router, ActivatedRoute } from '@angular/router'; -import { RouterTestingModule } from '@angular/router/testing'; -import { HttpClientModule } from '@angular/common/http'; -import { TranslateModule } from '@ngx-translate/core'; -import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { - NotificationService, TranslationService, TranslationMock, - NodesApiService, AlfrescoApiService, ContentService, - UserPreferencesService, LogService, AppConfigService, - StorageService, CookieService, ThumbnailService, AuthenticationService, TimeAgoPipe, NodeNameTooltipPipe, FileSizePipe, NodeFavoriteDirective, - DataTableComponent, UploadService + DataTableComponent, UploadService, AppConfigPipe } from '@alfresco/adf-core'; -import { DocumentListComponent, CustomResourcesService } from '@alfresco/adf-content-services'; -import { MatMenuModule, MatSnackBarModule, MatIconModule, MatDialogModule } from '@angular/material'; -import { DocumentListService } from '@alfresco/adf-content-services'; +import { DocumentListComponent } from '@alfresco/adf-content-services'; import { ContentManagementService } from '../../common/services/content-management.service'; import { BrowsingFilesService } from '../../common/services/browsing-files.service'; import { NodeActionsService } from '../../common/services/node-actions.service'; -import { NodePermissionService } from '../../common/services/node-permission.service'; -import { NodeInfoDirective } from '../../common/directives/node-info.directive'; -import { AppConfigPipe } from '../../common/pipes/app-config.pipe'; - import { FilesComponent } from './files.component'; +import { AppTestingModule } from '../../testing/app-testing.module'; +import { ContentApiService } from '../../services/content-api.service'; describe('FilesComponent', () => { let node; let page; - let fixture; + let fixture: ComponentFixture; let component: FilesComponent; let contentManagementService: ContentManagementService; - let alfrescoContentService: ContentService; let uploadService: UploadService; - let nodesApi: NodesApiService; let router: Router; let browsingFilesService: BrowsingFilesService; let nodeActionsService: NodeActionsService; - let preferenceService: UserPreferencesService; - let notificationService: NotificationService; + let contentApi: ContentApiService; - beforeEach(async(() => { + beforeAll(() => { + // testing only functional-wise not time-wise + Observable.prototype.debounceTime = function () { return this; }; + }); + + beforeEach(() => { TestBed.configureTestingModule({ - imports: [ - MatMenuModule, - NoopAnimationsModule, - HttpClientModule, - TranslateModule.forRoot(), - RouterTestingModule, - MatSnackBarModule, MatIconModule, - MatDialogModule - ], + imports: [ AppTestingModule ], declarations: [ FilesComponent, DataTableComponent, TimeAgoPipe, NodeNameTooltipPipe, NodeFavoriteDirective, - NodeInfoDirective, DocumentListComponent, FileSizePipe, AppConfigPipe ], providers: [ { provide: ActivatedRoute, useValue: { - params: Observable.of({ folderId: 'someId' }), - snapshot: { data: { preferencePrefix: 'prefix' } } - } } , - { provide: TranslationService, useClass: TranslationMock }, - AuthenticationService, - UserPreferencesService, - AppConfigService, StorageService, CookieService, - AlfrescoApiService, - LogService, - NotificationService, - ContentManagementService, - ContentService, - NodesApiService, - DocumentListService, - ThumbnailService, - NodeActionsService, - NodePermissionService, - UploadService, - BrowsingFilesService, - CustomResourcesService + snapshot: { data: { preferencePrefix: 'prefix' } }, + params: Observable.of({ folderId: 'someId' }) + } } ], schemas: [ NO_ERRORS_SCHEMA ] - }).compileComponents() - .then(() => { - - fixture = TestBed.createComponent(FilesComponent); - component = fixture.componentInstance; - - contentManagementService = TestBed.get(ContentManagementService); - uploadService = TestBed.get(UploadService); - nodesApi = TestBed.get(NodesApiService); - router = TestBed.get(Router); - alfrescoContentService = TestBed.get(ContentService); - browsingFilesService = TestBed.get(BrowsingFilesService); - notificationService = TestBed.get(NotificationService); - nodeActionsService = TestBed.get(NodeActionsService); - preferenceService = TestBed.get(UserPreferencesService); }); - })); + + fixture = TestBed.createComponent(FilesComponent); + component = fixture.componentInstance; + + contentManagementService = TestBed.get(ContentManagementService); + uploadService = TestBed.get(UploadService); + router = TestBed.get(Router); + browsingFilesService = TestBed.get(BrowsingFilesService); + nodeActionsService = TestBed.get(NodeActionsService); + contentApi = TestBed.get(ContentApiService); + }); beforeEach(() => { node = { id: 'node-id', isFolder: true }; @@ -138,11 +97,37 @@ describe('FilesComponent', () => { pagination: {} } }; + + spyOn(component.documentList, 'loadFolder').and.callFake(() => {}); + }); + + describe('Current page is valid', () => { + it('should be a valid current page', fakeAsync(() => { + spyOn(contentApi, 'getNode').and.returnValue(Observable.of({ entry: node })); + spyOn(component, 'fetchNodes').and.returnValue(Observable.throw(null)); + + component.ngOnInit(); + fixture.detectChanges(); + tick(); + + expect(component.isValidPath).toBe(false); + })); + + it('should set current page as invalid path', fakeAsync(() => { + spyOn(contentApi, 'getNode').and.returnValue(Observable.of({ entry: node })); + spyOn(component, 'fetchNodes').and.returnValue(Observable.of(page)); + + component.ngOnInit(); + tick(); + fixture.detectChanges(); + + expect(component.isValidPath).toBe(true); + })); }); describe('OnInit', () => { - it('set current node', () => { - spyOn(component, 'fetchNode').and.returnValue(Observable.of(node)); + it('should set current node', () => { + spyOn(contentApi, 'getNode').and.returnValue(Observable.of({ entry: node })); spyOn(component, 'fetchNodes').and.returnValue(Observable.of(page)); fixture.detectChanges(); @@ -150,17 +135,17 @@ describe('FilesComponent', () => { expect(component.node).toBe(node); }); - it('get current node children', () => { - spyOn(component, 'fetchNode').and.returnValue(Observable.of(node)); + it('should get current node children', () => { + spyOn(contentApi, 'getNode').and.returnValue(Observable.of({ entry: node })); spyOn(component, 'fetchNodes').and.returnValue(Observable.of(page)); fixture.detectChanges(); - expect(component.paging).toBe(page); + expect(component.fetchNodes).toHaveBeenCalled(); }); it('emits onChangeParent event', () => { - spyOn(component, 'fetchNode').and.returnValue(Observable.of(node)); + spyOn(contentApi, 'getNode').and.returnValue(Observable.of({ entry: node })); spyOn(component, 'fetchNodes').and.returnValue(Observable.of(page)); spyOn(browsingFilesService.onChangeParent, 'next').and.callFake((val) => val); @@ -170,29 +155,10 @@ describe('FilesComponent', () => { .toHaveBeenCalledWith(node); }); - it('raise error when fetchNode() fails', () => { - spyOn(component, 'fetchNode').and.returnValue(Observable.throw(null)); - spyOn(component, 'onFetchError'); - - fixture.detectChanges(); - - expect(component.onFetchError).toHaveBeenCalled(); - }); - - it('raise error when fetchNodes() fails', () => { - spyOn(component, 'fetchNode').and.returnValue(Observable.of(node)); - spyOn(component, 'fetchNodes').and.returnValue(Observable.throw(null)); - spyOn(component, 'onFetchError'); - - fixture.detectChanges(); - - expect(component.onFetchError).toHaveBeenCalled(); - }); - it('if should navigate to parent if node is not a folder', () => { node.isFolder = false; node.parentId = 'parent-id'; - spyOn(component, 'fetchNode').and.returnValue(Observable.of(node)); + spyOn(contentApi, 'getNode').and.returnValue(Observable.of({ entry: node })); spyOn(router, 'navigate'); fixture.detectChanges(); @@ -203,14 +169,14 @@ describe('FilesComponent', () => { describe('refresh on events', () => { beforeEach(() => { - spyOn(component, 'fetchNode').and.returnValue(Observable.of(node)); + spyOn(contentApi, 'getNode').and.returnValue(Observable.of(node)); spyOn(component, 'fetchNodes').and.returnValue(Observable.of(page)); - spyOn(component, 'load'); + spyOn(component.documentList, 'reload'); fixture.detectChanges(); }); - it('calls refresh onContentCopied event if parent is the same', () => { + it('should call refresh onContentCopied event if parent is the same', () => { const nodes = [ { entry: { parentId: '1' } }, { entry: { parentId: '2' } } @@ -220,10 +186,10 @@ describe('FilesComponent', () => { nodeActionsService.contentCopied.next(nodes); - expect(component.load).toHaveBeenCalled(); + expect(component.documentList.reload).toHaveBeenCalled(); }); - it('does not call refresh onContentCopied event when parent mismatch', () => { + it('should not call refresh onContentCopied event when parent mismatch', () => { const nodes = [ { entry: { parentId: '1' } }, { entry: { parentId: '2' } } @@ -233,215 +199,100 @@ describe('FilesComponent', () => { nodeActionsService.contentCopied.next(nodes); - expect(component.load).not.toHaveBeenCalled(); + expect(component.documentList.reload).not.toHaveBeenCalled(); }); - it('calls refresh onCreateFolder event', () => { - alfrescoContentService.folderCreate.next(); + it('should call refresh onCreateFolder event', () => { + contentManagementService.folderCreated.next(); - expect(component.load).toHaveBeenCalled(); + expect(component.documentList.reload).toHaveBeenCalled(); }); - it('calls refresh editFolder event', () => { - alfrescoContentService.folderEdit.next(); + it('should call refresh editFolder event', () => { + contentManagementService.folderEdited.next(); - expect(component.load).toHaveBeenCalled(); + expect(component.documentList.reload).toHaveBeenCalled(); }); - it('calls refresh deleteNode event', () => { - contentManagementService.nodeDeleted.next(); + it('should call refresh deleteNode event', () => { + contentManagementService.nodesDeleted.next(); - expect(component.load).toHaveBeenCalled(); + expect(component.documentList.reload).toHaveBeenCalled(); }); - it('calls refresh moveNode event', () => { - contentManagementService.nodeMoved.next(); + it('should call refresh moveNode event', () => { + contentManagementService.nodesMoved.next(); - expect(component.load).toHaveBeenCalled(); + expect(component.documentList.reload).toHaveBeenCalled(); }); - it('calls refresh restoreNode event', () => { - contentManagementService.nodeRestored.next(); + it('should call refresh restoreNode event', () => { + contentManagementService.nodesRestored.next(); - expect(component.load).toHaveBeenCalled(); + expect(component.documentList.reload).toHaveBeenCalled(); }); - it('calls refresh on fileUploadComplete event if parent node match', () => { + it('should call refresh on fileUploadComplete event if parent node match', () => { const file = { file: { options: { parentId: 'parentId' } } }; component.node = { id: 'parentId' }; uploadService.fileUploadComplete.next(file); - expect(component.load).toHaveBeenCalled(); + expect(component.documentList.reload).toHaveBeenCalled(); }); - it('does not call refresh on fileUploadComplete event if parent mismatch', () => { + it('should not call refresh on fileUploadComplete event if parent mismatch', () => { const file = { file: { options: { parentId: 'otherId' } } }; component.node = { id: 'parentId' }; uploadService.fileUploadComplete.next(file); - expect(component.load).not.toHaveBeenCalled(); + expect(component.documentList.reload).not.toHaveBeenCalled(); }); - it('calls refresh on fileUploadDeleted event if parent node match', () => { + it('should call refresh on fileUploadDeleted event if parent node match', () => { const file = { file: { options: { parentId: 'parentId' } } }; component.node = { id: 'parentId' }; uploadService.fileUploadDeleted.next(file); - expect(component.load).toHaveBeenCalled(); + expect(component.documentList.reload).toHaveBeenCalled(); }); - it('does not call refresh on fileUploadDeleted event if parent mismatch', () => { + it('should not call refresh on fileUploadDeleted event if parent mismatch', () => { const file = { file: { options: { parentId: 'otherId' } } }; component.node = { id: 'parentId' }; uploadService.fileUploadDeleted.next(file); - expect(component.load).not.toHaveBeenCalled(); - }); - }); - - describe('fetchNode()', () => { - beforeEach(() => { - spyOn(component, 'fetchNodes').and.returnValue(Observable.of(page)); - spyOn(nodesApi, 'getNode').and.returnValue(Observable.of(node)); - - fixture.detectChanges(); - }); - - it('calls getNode api with node id', () => { - component.fetchNode('nodeId'); - - expect(nodesApi.getNode).toHaveBeenCalledWith('nodeId'); + expect(component.documentList.reload).not.toHaveBeenCalled(); }); }); describe('fetchNodes()', () => { beforeEach(() => { - spyOn(component, 'fetchNode').and.returnValue(Observable.of(node)); - spyOn(nodesApi, 'getNodeChildren').and.returnValue(Observable.of(page)); + spyOn(contentApi, 'getNode').and.returnValue(Observable.of(node)); + spyOn(contentApi, 'getNodeChildren').and.returnValue(Observable.of(page)); fixture.detectChanges(); }); - it('calls getNode api with node id', () => { + it('should call getNode api with node id', () => { component.fetchNodes('nodeId'); - expect(nodesApi.getNodeChildren).toHaveBeenCalledWith('nodeId', jasmine.any(Object)); - }); - }); - - describe('onNodeDoubleClick()', () => { - beforeEach(() => { - spyOn(component, 'fetchNode').and.returnValue(Observable.of(node)); - spyOn(component, 'fetchNodes').and.returnValue(Observable.of(page)); - - fixture.detectChanges(); - }); - - it('opens preview if node is file', () => { - spyOn(router, 'navigate').and.stub(); - node.isFile = true; - node.isFolder = false; - - const event: any = { - detail: { - node: { - entry: node - } - } - }; - component.onNodeDoubleClick(event); - - expect(router.navigate['calls'].argsFor(0)[0]).toEqual(['./preview', node.id]); - }); - - it('navigate if node is folder', () => { - spyOn(component, 'navigate').and.stub(); - node.isFolder = true; - - - const event: any = { - detail: { - node: { - entry: node - } - } - }; - component.onNodeDoubleClick(event); - - expect(component.navigate).toHaveBeenCalledWith(node.id); - }); - }); - - describe('load()', () => { - let fetchNodesSpy; - - beforeEach(() => { - spyOn(component, 'fetchNode').and.returnValue(Observable.of(node)); - fetchNodesSpy = spyOn(component, 'fetchNodes'); - - fetchNodesSpy.and.returnValue(Observable.of(page)); - - fixture.detectChanges(); - }); - - afterEach(() => { - fetchNodesSpy.calls.reset(); - }); - - it('shows load indicator', () => { - spyOn(component, 'onPageLoaded'); - component.node = { id: 'currentNode' }; - - expect(component.isLoading).toBe(false); - - component.load(true); - - expect(component.isLoading).toBe(true); - }); - - it('does not show load indicator', () => { - spyOn(component, 'onPageLoaded'); - component.node = { id: 'currentNode' }; - - expect(component.isLoading).toBe(false); - - component.load(); - - expect(component.isLoading).toBe(false); - }); - - it('sets data on success', () => { - component.node = { id: 'currentNode' }; - - component.load(); - - expect(component.paging).toBe(page); - expect(component.pagination).toEqual(page.list.pagination); - }); - - it('raise error on fail', () => { - fetchNodesSpy.and.returnValue(Observable.throw(null)); - spyOn(component, 'onFetchError'); - - component.load(); - - expect(component.onFetchError).toHaveBeenCalled(); + expect(contentApi.getNodeChildren).toHaveBeenCalledWith('nodeId'); }); }); describe('onBreadcrumbNavigate()', () => { beforeEach(() => { - spyOn(component, 'fetchNode').and.returnValue(Observable.of(node)); + spyOn(contentApi, 'getNode').and.returnValue(Observable.of(node)); spyOn(component, 'fetchNodes').and.returnValue(Observable.of(page)); fixture.detectChanges(); }); - it('navigates to node id', () => { + it('should navigates to node id', () => { const routeData = { id: 'some-where-over-the-rainbow' }; spyOn(component, 'navigate'); @@ -453,26 +304,26 @@ describe('FilesComponent', () => { describe('Node navigation', () => { beforeEach(() => { - spyOn(component, 'fetchNode').and.returnValue(Observable.of(node)); + spyOn(contentApi, 'getNode').and.returnValue(Observable.of(node)); spyOn(component, 'fetchNodes').and.returnValue(Observable.of(page)); spyOn(router, 'navigate'); fixture.detectChanges(); }); - it('navigates to node when id provided', () => { + it('should navigates to node when id provided', () => { component.navigate(node.id); expect(router.navigate).toHaveBeenCalledWith(['./', node.id], jasmine.any(Object)); }); - it('navigates to home when id not provided', () => { + it('should navigates to home when id not provided', () => { component.navigate(); expect(router.navigate).toHaveBeenCalledWith(['./'], jasmine.any(Object)); }); - it('it navigate home if node is root', () => { + it('should navigate home if node is root', () => { (component).node = { path: { elements: [ {id: 'node-id'} ] @@ -504,47 +355,4 @@ describe('FilesComponent', () => { expect(component.isSiteContainer(mock)).toBe(true); }); }); - - describe('onSortingChanged', () => { - it('should save sorting input', () => { - spyOn(preferenceService, 'set'); - - const event = { - detail: { - key: 'some-name', - direction: 'some-direction' - } - }; - - component.onSortingChanged(event); - - expect(preferenceService.set).toHaveBeenCalledWith('prefix.sorting.key', 'some-name'); - expect(preferenceService.set).toHaveBeenCalledWith('prefix.sorting.direction', 'some-direction'); - }); - - it('should save default sorting when no input', () => { - spyOn(preferenceService, 'set'); - - const event = { - detail: {} - }; - - component.onSortingChanged(event); - - expect(preferenceService.set).toHaveBeenCalledWith('prefix.sorting.key', 'modifiedAt'); - expect(preferenceService.set).toHaveBeenCalledWith('prefix.sorting.direction', 'desc'); - }); - }); - - describe('openSnackMessage', () => { - it('should call notification service', () => { - const message = 'notification message'; - - spyOn(notificationService, 'openSnackMessage'); - - component.openSnackMessage(message); - - expect(notificationService.openSnackMessage).toHaveBeenCalledWith(message, 4000); - }); - }); }); diff --git a/src/app/components/files/files.component.ts b/src/app/components/files/files.component.ts index 8eab1d700..726c47567 100644 --- a/src/app/components/files/files.component.ts +++ b/src/app/components/files/files.component.ts @@ -23,21 +23,19 @@ * along with Alfresco. If not, see . */ -import { Observable, Subscription } from 'rxjs/Rx'; -import { Component, OnInit, OnDestroy, NgZone } from '@angular/core'; -import { Router, ActivatedRoute, Params } from '@angular/router'; -import { MinimalNodeEntity, MinimalNodeEntryEntity, PathElementEntity, NodePaging, PathElement } from 'alfresco-js-api'; -import { - UploadService, FileUploadEvent, NodesApiService, - ContentService, AlfrescoApiService, UserPreferencesService, NotificationService -} from '@alfresco/adf-core'; - +import { FileUploadEvent, UploadService } from '@alfresco/adf-core'; +import { Component, OnDestroy, OnInit } from '@angular/core'; +import { ActivatedRoute, Params, Router } from '@angular/router'; +import { Store } from '@ngrx/store'; +import { MinimalNodeEntity, MinimalNodeEntryEntity, NodePaging, PathElement, PathElementEntity } from 'alfresco-js-api'; +import { Observable } from 'rxjs/Rx'; import { BrowsingFilesService } from '../../common/services/browsing-files.service'; import { ContentManagementService } from '../../common/services/content-management.service'; import { NodeActionsService } from '../../common/services/node-actions.service'; import { NodePermissionService } from '../../common/services/node-permission.service'; - +import { AppStore } from '../../store/states/app.state'; import { PageComponent } from '../page.component'; +import { ContentApiService } from '../../services/content-api.service'; @Component({ templateUrl: './files.component.html' @@ -47,43 +45,33 @@ export class FilesComponent extends PageComponent implements OnInit, OnDestroy { isValidPath = true; private nodePath: PathElement[]; - private subscriptions: Subscription[] = []; - - sorting = [ 'modifiedAt', 'desc' ]; constructor(private router: Router, - private zone: NgZone, private route: ActivatedRoute, - private nodesApi: NodesApiService, + private contentApi: ContentApiService, + store: Store, private nodeActionsService: NodeActionsService, private uploadService: UploadService, private contentManagementService: ContentManagementService, private browsingFilesService: BrowsingFilesService, - private contentService: ContentService, - private apiService: AlfrescoApiService, - private notificationService: NotificationService, - public permission: NodePermissionService, - preferences: UserPreferencesService) { - super(preferences); - - const sortingKey = this.preferences.get(`${this.prefix}.sorting.key`) || 'modifiedAt'; - const sortingDirection = this.preferences.get(`${this.prefix}.sorting.direction`) || 'desc'; - - this.sorting = [sortingKey, sortingDirection]; + public permission: NodePermissionService) { + super(store); } ngOnInit() { - const { route, contentManagementService, contentService, nodeActionsService, uploadService } = this; + super.ngOnInit(); + + const { route, contentManagementService, nodeActionsService, uploadService } = this; const { data } = route.snapshot; - this.title = data.i18nTitle; + this.title = data.title; route.params.subscribe(({ folderId }: Params) => { const nodeId = folderId || data.defaultNodeId; - this.isLoading = true; - this.fetchNode(nodeId) - .do((node) => { + this.contentApi.getNode(nodeId) + .map(node => node.entry) + .do(node => { if (node.isFolder) { this.updateCurrentNode(node); } else { @@ -91,49 +79,33 @@ export class FilesComponent extends PageComponent implements OnInit, OnDestroy { } }) .skipWhile(node => !node.isFolder) - .flatMap((node) => this.fetchNodes(node.id)) + .flatMap(node => this.fetchNodes(node.id)) .subscribe( - (page) => { - this.isValidPath = true; - this.onPageLoaded(page); - }, - error => { - this.isValidPath = false; - this.onFetchError(error); - } + () => this.isValidPath = true, + () => this.isValidPath = false ); }); this.subscriptions = this.subscriptions.concat([ nodeActionsService.contentCopied.subscribe((nodes) => this.onContentCopied(nodes)), - contentService.folderCreate.subscribe(() => this.load(false, this.pagination)), - contentService.folderEdit.subscribe(() => this.load(false, this.pagination)), - contentManagementService.nodeDeleted.subscribe(() => this.load(false, this.pagination)), - contentManagementService.nodeMoved.subscribe(() => this.load(false, this.pagination)), - contentManagementService.nodeRestored.subscribe(() => this.load(false, this.pagination)), - uploadService.fileUploadComplete.subscribe(file => this.onFileUploadedEvent(file)), - uploadService.fileUploadDeleted.subscribe((file) => this.onFileUploadedEvent(file)) + contentManagementService.folderCreated.subscribe(() => this.documentList.reload()), + contentManagementService.folderEdited.subscribe(() => this.documentList.reload()), + contentManagementService.nodesDeleted.subscribe(() => this.documentList.reload()), + contentManagementService.nodesMoved.subscribe(() => this.documentList.reload()), + contentManagementService.nodesRestored.subscribe(() => this.documentList.reload()), + uploadService.fileUploadComplete.debounceTime(300).subscribe(file => this.onFileUploadedEvent(file)), + uploadService.fileUploadDeleted.debounceTime(300).subscribe((file) => this.onFileUploadedEvent(file)), + uploadService.fileUploadError.subscribe((error) => this.onFileUploadedError(error)) ]); } ngOnDestroy() { - this.subscriptions.forEach(s => s.unsubscribe()); - + super.ngOnDestroy(); this.browsingFilesService.onChangeParent.next(null); } - fetchNode(nodeId: string): Observable { - return this.nodesApi.getNode(nodeId); - } - - fetchNodes(parentNodeId?: string, options: { maxItems?: number, skipCount?: number } = {}): Observable { - const defaults = { - include: [ 'isLocked', 'path', 'properties', 'allowableOperations' ] - }; - - const queryOptions = Object.assign({}, defaults, options); - - return this.nodesApi.getNodeChildren(parentNodeId, queryOptions); + fetchNodes(parentNodeId?: string): Observable { + return this.contentApi.getNodeChildren(parentNodeId); } navigate(nodeId: string = null) { @@ -148,32 +120,21 @@ export class FilesComponent extends PageComponent implements OnInit, OnDestroy { }); } - onNodeDoubleClick(event) { - if (!!event.detail && !!event.detail.node) { + onNodeDoubleClick(node: MinimalNodeEntity) { + if (node && node.entry) { + const { id, isFolder } = node.entry; - const node: MinimalNodeEntryEntity = event.detail.node.entry; - if (node) { - - if (node.isFolder) { - this.navigate(node.id); - } - - if (PageComponent.isLockedNode(node)) { - event.preventDefault(); - - } else if (node.isFile) { - this.router.navigate(['./preview', node.id], { relativeTo: this.route }); - } + if (isFolder) { + this.navigate(id); + return; } - } - } - - showPreview(node: MinimalNodeEntryEntity) { - if (node) { - if (node.isFile) { - this.router.navigate(['./preview', node.id], { relativeTo: this.route }); + if (PageComponent.isLockedNode(node.entry)) { + event.preventDefault(); + return; } + + this.showPreview(node); } } @@ -188,9 +149,44 @@ export class FilesComponent extends PageComponent implements OnInit, OnDestroy { } onFileUploadedEvent(event: FileUploadEvent) { - if (event && event.file.options.parentId === this.getParentNodeId()) { - this.load(false, this.pagination); + const node: MinimalNodeEntity = event.file.data; + + // check root and child nodes + if (node && node.entry && node.entry.parentId === this.getParentNodeId()) { + this.documentList.reload(); + return; } + + // check the child nodes to show dropped folder + if (event && event.file.options.parentId === this.getParentNodeId()) { + this.displayFolderParent(event.file.options.path, 0); + return; + } + + if (event && event.file.options.parentId) { + if (this.nodePath) { + const correspondingNodePath = this.nodePath.find(pathItem => pathItem.id === event.file.options.parentId); + + // check if the current folder has the 'trigger-upload-folder' as one of its parents + if (correspondingNodePath) { + const correspondingIndex = this.nodePath.length - this.nodePath.indexOf(correspondingNodePath); + this.displayFolderParent(event.file.options.path, correspondingIndex); + } + } + } + } + + displayFolderParent(filePath = '', index) { + const parentName = filePath.split('/')[index]; + const currentFoldersDisplayed: any = this.documentList.data.getRows() || []; + + const alreadyDisplayedParentFolder = currentFoldersDisplayed.find( + row => row.node.entry.isFolder && row.node.entry.name === parentName); + + if (alreadyDisplayedParentFolder) { + return; + } + this.documentList.reload(); } onContentCopied(nodes: MinimalNodeEntity[]) { @@ -199,43 +195,10 @@ export class FilesComponent extends PageComponent implements OnInit, OnDestroy { return node && node.entry && node.entry.parentId === this.getParentNodeId(); }); if (newNode) { - this.load(false, this.pagination); + this.documentList.reload(); } } - load(showIndicator: boolean = false, options: { maxItems?: number, skipCount?: number } = {}) { - this.isLoading = showIndicator; - - this.fetchNodes(this.getParentNodeId(), options) - .flatMap((page) => { - if (this.isCurrentPageEmpty(page) && this.isNotFirstPage(page)) { - const newSkipCount = options.skipCount - options.maxItems; - - return this.fetchNodes(this.getParentNodeId(), { - skipCount: newSkipCount, maxItems: options.maxItems - }); - } - - return Observable.of(page); - }) - .subscribe( - (page) => this.zone.run(() => this.onPageLoaded(page)), - error => this.onFetchError(error) - ); - } - - isCurrentPageEmpty(page): boolean { - return !this.hasPageEntries(page); - } - - hasPageEntries(page): boolean { - return page && page.list && page.list.entries && page.list.entries.length > 0; - } - - isNotFirstPage(page): boolean { - return (page.list.pagination.skipCount >= page.list.pagination.maxItems); - } - // todo: review this approach once 5.2.3 is out private async updateCurrentNode(node: MinimalNodeEntryEntity) { this.nodePath = null; @@ -270,7 +233,7 @@ export class FilesComponent extends PageComponent implements OnInit, OnDestroy { if (this.isSiteContainer(node)) { // rename 'documentLibrary' entry to the target site display name // clicking on the breadcrumb entry loads the site content - const parentNode = await this.apiService.nodesApi.getNodeInfo(node.parentId); + const parentNode = await this.contentApi.getNodeInfo(node.parentId).toPromise(); node.name = parentNode.properties['cm:title'] || parentNode.name; // remove the site entry @@ -280,7 +243,7 @@ export class FilesComponent extends PageComponent implements OnInit, OnDestroy { const docLib = elements.findIndex(el => el.name === 'documentLibrary'); if (docLib > -1) { const siteFragment = elements[docLib - 1]; - const siteNode = await this.apiService.nodesApi.getNodeInfo(siteFragment.id); + const siteNode = await this.contentApi.getNodeInfo(siteFragment.id).toPromise(); // apply Site Name to the parent fragment siteFragment.name = siteNode.properties['cm:title'] || siteNode.name; @@ -302,20 +265,4 @@ export class FilesComponent extends PageComponent implements OnInit, OnDestroy { } return false; } - - onSortingChanged(event: CustomEvent) { - this.preferences.set(`${this.prefix}.sorting.key`, event.detail.key || 'modifiedAt'); - this.preferences.set(`${this.prefix}.sorting.direction`, event.detail.direction || 'desc'); - } - - openSnackMessage(event: any) { - this.notificationService.openSnackMessage( - event, - 4000 - ); - } - - private get prefix() { - return this.route.snapshot.data.preferencePrefix; - } } diff --git a/src/app/components/generic-error/generic-error.component.html b/src/app/components/generic-error/generic-error.component.html index 1525e9ba3..ac7157b63 100644 --- a/src/app/components/generic-error/generic-error.component.html +++ b/src/app/components/generic-error/generic-error.component.html @@ -1,4 +1,4 @@ -
- ic_error -

This file / folder no longer exists or you don't have permission to view it.

-
\ No newline at end of file +ic_error +

+ {{ 'APP.MESSAGES.ERRORS.MISSING_CONTENT' | translate }} +

diff --git a/src/app/components/generic-error/generic-error.component.scss b/src/app/components/generic-error/generic-error.component.scss deleted file mode 100644 index a3b5b67f1..000000000 --- a/src/app/components/generic-error/generic-error.component.scss +++ /dev/null @@ -1,25 +0,0 @@ -@import 'variables'; - -$alfresco-warn-color--hue-2: #ff5252; - -.generic-error { - color: $alfresco-secondary-text-color; - display: flex; - align-items: center; - justify-content: center; - flex-direction: column; - width: 100%; - height: 100%; - - &__title { - font-size: 16px; - } - - mat-icon { - color: $alfresco-warn-color--hue-2; - direction: rtl; - font-size: 52px; - height: 52px; - width: 52px; - } - } diff --git a/src/app/components/generic-error/generic-error.component.theme.scss b/src/app/components/generic-error/generic-error.component.theme.scss new file mode 100644 index 000000000..bbf0735e9 --- /dev/null +++ b/src/app/components/generic-error/generic-error.component.theme.scss @@ -0,0 +1,27 @@ +@mixin aca-generic-error-theme($theme) { + $warn: map-get($theme, warn); + $foreground: map-get($theme, foreground); + + .aca-generic-error { + color: mat-color($foreground, text, 0.54); + + display: flex; + align-items: center; + justify-content: center; + flex-direction: column; + width: 100%; + height: 100%; + + &__title { + font-size: 16px; + } + + mat-icon { + color: mat-color($warn); + direction: rtl; + font-size: 52px; + height: 52px; + width: 52px; + } + } +} diff --git a/src/app/components/generic-error/generic-error.component.ts b/src/app/components/generic-error/generic-error.component.ts index abfbf9a6d..dfae6b91e 100644 --- a/src/app/components/generic-error/generic-error.component.ts +++ b/src/app/components/generic-error/generic-error.component.ts @@ -23,12 +23,14 @@ * along with Alfresco. If not, see . */ -import { Component } from '@angular/core'; +import { Component, ViewEncapsulation, ChangeDetectionStrategy } from '@angular/core'; @Component({ - selector: 'app-generic-error', - styleUrls: ['./generic-error.component.scss'], - templateUrl: './generic-error.component.html' + selector: 'aca-generic-error', + templateUrl: './generic-error.component.html', + encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, + host: { class: 'aca-generic-error' } }) export class GenericErrorComponent {} diff --git a/src/app/components/header/header.component.html b/src/app/components/header/header.component.html index 1e033fe0a..2a4668d66 100644 --- a/src/app/components/header/header.component.html +++ b/src/app/components/header/header.component.html @@ -1,19 +1,24 @@ - + - - {{ appName }} + {{ appName$ | async }} - +
+ + - +
diff --git a/src/app/components/header/header.component.scss b/src/app/components/header/header.component.scss deleted file mode 100644 index 1ef205891..000000000 --- a/src/app/components/header/header.component.scss +++ /dev/null @@ -1,55 +0,0 @@ -@import 'variables'; - -$app-menu-height: 64px; - -.app-menu { - height: $app-menu-height; - - &.adf-toolbar { - .mat-toolbar { - background-color: inherit; - font-family: inherit; - min-height: $app-menu-height; - height: $app-menu-height; - - .mat-toolbar-layout { - height: $app-menu-height; - - .mat-toolbar-row { - height: $app-menu-height; - } - } - } - - .adf-toolbar-divider { - margin-left: 5px; - margin-right: 5px; - - & > div { - background-color: $alfresco-white !important; - } - } - - .adf-toolbar-title { - color: $alfresco-white; - display: flex; - flex-direction: row; - align-items: center; - justify-content: center; - } - } - - .app-menu__title { - width: 100px; - height: 50px; - margin-left: 40px; - display: flex; - justify-content: center; - align-items: stretch; - - &> img { - width: 100%; - object-fit: contain; - } - } -} diff --git a/src/app/components/header/header.component.spec.ts b/src/app/components/header/header.component.spec.ts index c32d91cfc..f2a3f88ec 100644 --- a/src/app/components/header/header.component.spec.ts +++ b/src/app/components/header/header.component.spec.ts @@ -23,33 +23,28 @@ * along with Alfresco. If not, see . */ -import { NO_ERRORS_SCHEMA } from '@angular/core'; -import { TestBed } from '@angular/core/testing'; -import { RouterTestingModule } from '@angular/router/testing'; import { AppConfigService, PeopleContentService } from '@alfresco/adf-core'; -import { HttpClientModule } from '@angular/common/http'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { Store } from '@ngrx/store'; import { Observable } from 'rxjs/Rx'; - +import { SetAppNameAction, SetHeaderColorAction } from '../../store/actions'; +import { AppStore } from '../../store/states/app.state'; +import { AppTestingModule } from '../../testing/app-testing.module'; import { HeaderComponent } from './header.component'; describe('HeaderComponent', () => { - let fixture; - let component; + let fixture: ComponentFixture; + let component: HeaderComponent; let appConfigService: AppConfigService; + let store: Store; beforeEach(() => { TestBed.configureTestingModule({ - imports: [ - HttpClientModule, - RouterTestingModule - ], + imports: [ AppTestingModule ], declarations: [ HeaderComponent ], - providers: [ - AppConfigService, - PeopleContentService - ], schemas: [ NO_ERRORS_SCHEMA ] }) .overrideProvider(PeopleContentService, { @@ -58,6 +53,10 @@ describe('HeaderComponent', () => { } }); + store = TestBed.get(Store); + store.dispatch(new SetAppNameAction('app-name')); + store.dispatch(new SetHeaderColorAction('some-color')); + fixture = TestBed.createComponent(HeaderComponent); component = fixture.componentInstance; appConfigService = TestBed.get(AppConfigService); @@ -79,11 +78,17 @@ describe('HeaderComponent', () => { fixture.detectChanges(); }); - it('it should set application name', () => { - expect(component.appName).toBe('app-name'); + it('it should set application name', done => { + component.appName$.subscribe(val => { + expect(val).toBe('app-name'); + done(); + }); }); - it('it should set header background color', () => { - expect(component.backgroundColor).toBe('some-color'); + it('it should set header background color', done => { + component.headerColor$.subscribe(val => { + expect(val).toBe('some-color'); + done(); + }); }); }); diff --git a/src/app/components/header/header.component.theme.scss b/src/app/components/header/header.component.theme.scss new file mode 100644 index 000000000..8b56a4224 --- /dev/null +++ b/src/app/components/header/header.component.theme.scss @@ -0,0 +1,59 @@ +@mixin aca-header-theme($theme) { + $background: map-get($theme, background); + $app-menu-height: 64px; + + .aca-header { + + .app-menu { + height: $app-menu-height; + + &.adf-toolbar { + .mat-toolbar { + background-color: inherit; + font-family: inherit; + min-height: $app-menu-height; + height: $app-menu-height; + + .mat-toolbar-layout { + height: $app-menu-height; + + .mat-toolbar-row { + height: $app-menu-height; + } + } + } + + .adf-toolbar-divider { + margin-left: 5px; + margin-right: 5px; + + & > div { + background-color: mat-color($background, card); + } + } + + .adf-toolbar-title { + color: mat-color($background, card); + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + } + } + + .app-menu__title { + width: 100px; + height: 50px; + margin-left: 40px; + display: flex; + justify-content: center; + align-items: stretch; + + &> img { + width: 100%; + object-fit: contain; + } + } + } + } +} diff --git a/src/app/components/header/header.component.ts b/src/app/components/header/header.component.ts index 235610db8..6ad498bb3 100644 --- a/src/app/components/header/header.component.ts +++ b/src/app/components/header/header.component.ts @@ -23,41 +23,32 @@ * along with Alfresco. If not, see . */ -import { DomSanitizer } from '@angular/platform-browser'; -import { Component, Output, EventEmitter, ViewEncapsulation, SecurityContext } from '@angular/core'; -import { AppConfigService } from '@alfresco/adf-core'; +import { Component, Output, EventEmitter, ViewEncapsulation } from '@angular/core'; +import { Store } from '@ngrx/store'; +import { Observable } from 'rxjs/Rx'; +import { AppStore } from '../../store/states/app.state'; +import { selectHeaderColor, selectAppName, selectLogoPath } from '../../store/selectors/app.selectors'; @Component({ - selector: 'app-header', + selector: 'aca-header', templateUrl: './header.component.html', - styleUrls: [ './header.component.scss' ], - encapsulation: ViewEncapsulation.None + encapsulation: ViewEncapsulation.None, + host: { class: 'aca-header' } }) export class HeaderComponent { @Output() menu: EventEmitter = new EventEmitter(); - private defaultPath = '/assets/images/alfresco-logo-white.svg'; - private defaultBackgroundColor = '#2196F3'; + appName$: Observable; + headerColor$: Observable; + logo$: Observable; - constructor( - private appConfig: AppConfigService, - private sanitizer: DomSanitizer - ) {} + constructor(store: Store) { + this.headerColor$ = store.select(selectHeaderColor); + this.appName$ = store.select(selectAppName); + this.logo$ = store.select(selectLogoPath); + } toggleMenu() { this.menu.emit(); } - - get appName(): string { - return this.appConfig.get('application.name'); - } - - get logo() { - return this.appConfig.get('application.logo', this.defaultPath); - } - - get backgroundColor() { - const color = this.appConfig.get('headerColor', this.defaultBackgroundColor); - return this.sanitizer.sanitize(SecurityContext.STYLE, color); - } } diff --git a/src/app/components/info-drawer/info-drawer.component.html b/src/app/components/info-drawer/info-drawer.component.html new file mode 100644 index 000000000..985ebec34 --- /dev/null +++ b/src/app/components/info-drawer/info-drawer.component.html @@ -0,0 +1,32 @@ +
+ +
+ + + + + + + + + + + + + + +
+ face + {{ 'VERSION.SELECTION.EMPTY' | translate }} +
+
+
+
+
diff --git a/src/app/components/info-drawer/info-drawer.component.ts b/src/app/components/info-drawer/info-drawer.component.ts new file mode 100644 index 000000000..d3ff5cfe5 --- /dev/null +++ b/src/app/components/info-drawer/info-drawer.component.ts @@ -0,0 +1,111 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { Component, Input, OnChanges, SimpleChanges } from '@angular/core'; +import { MinimalNodeEntity, MinimalNodeEntryEntity } from 'alfresco-js-api'; +import { NodePermissionService } from '../../common/services/node-permission.service'; +import { ContentApiService } from '../../services/content-api.service'; + +@Component({ + selector: 'aca-info-drawer', + templateUrl: './info-drawer.component.html' +}) +export class InfoDrawerComponent implements OnChanges { + @Input() nodeId: string; + + @Input() node: MinimalNodeEntity; + + isLoading = false; + displayNode: MinimalNodeEntryEntity; + + canUpdateNode(): boolean { + if (this.displayNode) { + return this.permission.check(this.displayNode, ['update']); + } + + return false; + } + + get isFileSelected(): boolean { + if (this.node && this.node.entry) { + // workaround for shared files type. + if (this.node.entry.nodeId) { + return true; + } else { + return this.node.entry.isFile; + } + } + return false; + } + + constructor( + public permission: NodePermissionService, + private contentApi: ContentApiService + ) {} + + ngOnChanges(changes: SimpleChanges) { + if (this.node) { + const entry = this.node.entry; + if (entry.nodeId) { + this.loadNodeInfo(entry.nodeId); + } else if ((entry).guid) { + // workaround for Favorite files + this.loadNodeInfo(entry.id); + } else { + // workaround Recent + if (this.isTypeImage(entry) && !this.hasAspectNames(entry)) { + this.loadNodeInfo(this.node.entry.id); + } else { + this.displayNode = this.node.entry; + } + } + } + } + + private hasAspectNames(entry: MinimalNodeEntryEntity): boolean { + return entry.aspectNames && entry.aspectNames.includes('exif:exif'); + } + + private isTypeImage(entry: MinimalNodeEntryEntity): boolean { + if (entry && entry.content && entry.content.mimeType) { + return entry.content.mimeType.includes('image/'); + } + return false; + } + + private loadNodeInfo(nodeId: string) { + if (nodeId) { + this.isLoading = true; + + this.contentApi.getNodeInfo(nodeId).subscribe( + entity => { + this.displayNode = entity; + this.isLoading = false; + }, + () => this.isLoading = false + ); + } + } +} diff --git a/src/app/components/layout/layout.component.html b/src/app/components/layout/layout.component.html index 958362bc2..52c37d768 100644 --- a/src/app/components/layout/layout.component.html +++ b/src/app/components/layout/layout.component.html @@ -4,17 +4,18 @@ [disabled]="!permission.check(node, ['create'])"> + [hideSidenav]="sidenavManager.hideSidenav" + [expandedSidenav]="expandedSidenav" + (expanded)="sidenavManager.setState($event)"> - - - + @@ -34,4 +35,4 @@ -
\ No newline at end of file +
diff --git a/src/app/components/layout/layout.component.spec.ts b/src/app/components/layout/layout.component.spec.ts index 166179c45..8a6e3a351 100644 --- a/src/app/components/layout/layout.component.spec.ts +++ b/src/app/components/layout/layout.component.spec.ts @@ -24,27 +24,22 @@ */ import { NO_ERRORS_SCHEMA } from '@angular/core'; -import { RouterTestingModule } from '@angular/router/testing'; import { TestBed, ComponentFixture } from '@angular/core/testing'; import { MinimalNodeEntryEntity } from 'alfresco-js-api'; -import { TranslateModule } from '@ngx-translate/core'; -import { HttpClientModule } from '@angular/common/http'; -import { - PeopleContentService, AppConfigService, - AuthenticationService, UserPreferencesService, TranslationService, - TranslationMock, StorageService, AlfrescoApiService, CookieService, - LogService -} from '@alfresco/adf-core'; +import { PeopleContentService, AppConfigService, UserPreferencesService } from '@alfresco/adf-core'; import { Observable } from 'rxjs/Observable'; - import { BrowsingFilesService } from '../../common/services/browsing-files.service'; -import { NodePermissionService } from '../../common/services/node-permission.service'; import { LayoutComponent } from './layout.component'; +import { SidenavViewsManagerDirective } from './sidenav-views-manager.directive'; +import { AppTestingModule } from '../../testing/app-testing.module'; describe('LayoutComponent', () => { let fixture: ComponentFixture; let component: LayoutComponent; let browsingFilesService: BrowsingFilesService; + let appConfig: AppConfigService; + let userPreference: UserPreferencesService; + const navItem = { label: 'some-label', route: { @@ -54,25 +49,12 @@ describe('LayoutComponent', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [ - HttpClientModule, - TranslateModule.forRoot(), - RouterTestingModule - ], + imports: [ AppTestingModule ], declarations: [ - LayoutComponent + LayoutComponent, + SidenavViewsManagerDirective ], providers: [ - { provide: TranslationService, useClass: TranslationMock }, - AlfrescoApiService, - StorageService, - CookieService, - LogService, - UserPreferencesService, - AuthenticationService, - AppConfigService, - NodePermissionService, - BrowsingFilesService, { provide: PeopleContentService, useValue: { @@ -86,18 +68,82 @@ describe('LayoutComponent', () => { fixture = TestBed.createComponent(LayoutComponent); component = fixture.componentInstance; browsingFilesService = TestBed.get(BrowsingFilesService); - - const appConfig = TestBed.get(AppConfigService); - spyOn(appConfig, 'get').and.returnValue([navItem]); - - fixture.detectChanges(); + appConfig = TestBed.get(AppConfigService); + userPreference = TestBed.get(UserPreferencesService); }); it('sets current node', () => { + appConfig.config = { + navigation: [navItem] + }; + const currentNode = { id: 'someId' }; + fixture.detectChanges(); + browsingFilesService.onChangeParent.next(currentNode); expect(component.node).toEqual(currentNode); }); + + describe('sidenav state', () => { + it('should get state from configuration', () => { + appConfig.config = { + sideNav: { + expandedSidenav: false, + preserveState: false + } + }; + + fixture.detectChanges(); + + expect(component.expandedSidenav).toBe(false); + }); + + it('should resolve state to true is no configuration', () => { + appConfig.config = {}; + + fixture.detectChanges(); + + expect(component.expandedSidenav).toBe(true); + }); + + it('should get state from user settings as true', () => { + appConfig.config = { + sideNav: { + expandedSidenav: false, + preserveState: true + } + }; + + spyOn(userPreference, 'get').and.callFake(key => { + if (key === 'expandedSidenav') { + return 'true'; + } + }); + + fixture.detectChanges(); + + expect(component.expandedSidenav).toBe(true); + }); + + it('should get state from user settings as false', () => { + appConfig.config = { + sideNav: { + expandedSidenav: false, + preserveState: true + } + }; + + spyOn(userPreference, 'get').and.callFake(key => { + if (key === 'expandedSidenav') { + return 'false'; + } + }); + + fixture.detectChanges(); + + expect(component.expandedSidenav).toBe(false); + }); + }); }); diff --git a/src/app/components/layout/layout.component.ts b/src/app/components/layout/layout.component.ts index 07f3fa06e..4f1cf43bc 100644 --- a/src/app/components/layout/layout.component.ts +++ b/src/app/components/layout/layout.component.ts @@ -23,12 +23,12 @@ * along with Alfresco. If not, see . */ -import { Component, OnInit, OnDestroy } from '@angular/core'; -import { Router, NavigationEnd } from '@angular/router'; +import { Component, OnInit, OnDestroy, ViewChild } from '@angular/core'; import { Subscription } from 'rxjs/Rx'; import { MinimalNodeEntryEntity } from 'alfresco-js-api'; import { BrowsingFilesService } from '../../common/services/browsing-files.service'; import { NodePermissionService } from '../../common/services/node-permission.service'; +import { SidenavViewsManagerDirective } from './sidenav-views-manager.directive'; @Component({ selector: 'app-layout', @@ -36,23 +36,26 @@ import { NodePermissionService } from '../../common/services/node-permission.ser styleUrls: ['./layout.component.scss'] }) export class LayoutComponent implements OnInit, OnDestroy { + @ViewChild(SidenavViewsManagerDirective) manager: SidenavViewsManagerDirective; + + expandedSidenav: boolean; node: MinimalNodeEntryEntity; - isPreview = false; private subscriptions: Subscription[] = []; constructor( - private router: Router, private browsingFilesService: BrowsingFilesService, - public permission: NodePermissionService) { - this.router.events - .filter(event => event instanceof NavigationEnd) - .subscribe( (event: any ) => { - this.isPreview = event.urlAfterRedirects.includes('preview'); - }); - } + public permission: NodePermissionService) {} ngOnInit() { + if (!this.manager.minimizeSidenav) { + this.expandedSidenav = this.manager.sidenavState; + } else { + this.expandedSidenav = false; + } + + this.manager.run(true); + this.subscriptions.concat([ this.browsingFilesService.onChangeParent.subscribe((node: MinimalNodeEntryEntity) => this.node = node) ]); diff --git a/src/app/components/layout/sidenav-views-manager.directive.ts b/src/app/components/layout/sidenav-views-manager.directive.ts new file mode 100644 index 000000000..513073faa --- /dev/null +++ b/src/app/components/layout/sidenav-views-manager.directive.ts @@ -0,0 +1,72 @@ +import { Directive, ContentChild } from '@angular/core'; +import { Router, NavigationEnd } from '@angular/router'; +import { UserPreferencesService, AppConfigService, SidenavLayoutComponent } from '@alfresco/adf-core'; + +@Directive({ + selector: '[acaSidenavManager]', + exportAs: 'acaSidenavManager' +}) +export class SidenavViewsManagerDirective { + + @ContentChild(SidenavLayoutComponent) sidenavLayout: SidenavLayoutComponent; + + minimizeSidenav = false; + hideSidenav = false; + + private _run = false; + private minimizeConditions: string[] = ['search']; + private hideConditions: string[] = ['preview']; + + constructor( + private router: Router, + private userPreferenceService: UserPreferencesService, + private appConfigService: AppConfigService + ) { + this.router.events + .filter(event => event instanceof NavigationEnd) + .subscribe( (event: any ) => { + this.minimizeSidenav = this.minimizeConditions.some(el => event.urlAfterRedirects.includes(el)); + this.hideSidenav = this.hideConditions.some(el => event.urlAfterRedirects.includes(el)); + + if (this._run) { + this.manageSidenavState(); + } + }); + + } + + run (shouldRun) { + this._run = shouldRun; + } + + manageSidenavState() { + if (this.minimizeSidenav && !this.sidenavLayout.isMenuMinimized) { + this.sidenavLayout.isMenuMinimized = true; + this.sidenavLayout.container.toggleMenu(); + } + + if (!this.minimizeSidenav) { + if (this.sidenavState && this.sidenavLayout.isMenuMinimized) { + this.sidenavLayout.isMenuMinimized = false; + this.sidenavLayout.container.toggleMenu(); + } + } + } + + setState(state) { + if (!this.minimizeSidenav && this.appConfigService.get('sideNav.preserveState')) { + this.userPreferenceService.set('expandedSidenav', state); + } + } + + get sidenavState(): boolean { + const expand = this.appConfigService.get('sideNav.expandedSidenav', true); + const preserveState = this.appConfigService.get('sideNav.preserveState', true); + + if (preserveState) { + return (this.userPreferenceService.get('expandedSidenav', expand.toString()) === 'true'); + } + + return expand; + } +} diff --git a/src/app/components/libraries/libraries.component.html b/src/app/components/libraries/libraries.component.html index aed07e6e6..133da22a2 100644 --- a/src/app/components/libraries/libraries.component.html +++ b/src/app/components/libraries/libraries.component.html @@ -2,30 +2,43 @@
- + + + + + + +
-
- + - - + @@ -66,13 +79,8 @@ - - - - + +
diff --git a/src/app/components/libraries/libraries.component.spec.ts b/src/app/components/libraries/libraries.component.spec.ts index a04008200..fe340e282 100644 --- a/src/app/components/libraries/libraries.component.spec.ts +++ b/src/app/components/libraries/libraries.component.spec.ts @@ -23,36 +23,27 @@ * along with Alfresco. If not, see . */ -import { TestBed, async } from '@angular/core/testing'; +import { TestBed, ComponentFixture } from '@angular/core/testing'; import { Observable } from 'rxjs/Rx'; import { NO_ERRORS_SCHEMA } from '@angular/core'; -import { Router, ActivatedRoute } from '@angular/router'; -import { RouterTestingModule } from '@angular/router/testing'; -import { HttpClientModule } from '@angular/common/http'; +import { Router } from '@angular/router'; import { - NotificationService, TranslationService, TranslationMock, - NodesApiService, AlfrescoApiService, ContentService, - UserPreferencesService, LogService, AppConfigService, - StorageService, CookieService, ThumbnailService, AuthenticationService, - TimeAgoPipe, NodeNameTooltipPipe, NodeFavoriteDirective, DataTableComponent + AlfrescoApiService, + TimeAgoPipe, NodeNameTooltipPipe, NodeFavoriteDirective, DataTableComponent, AppConfigPipe } from '@alfresco/adf-core'; -import { DocumentListComponent, CustomResourcesService } from '@alfresco/adf-content-services'; -import { TranslateModule } from '@ngx-translate/core'; -import { NoopAnimationsModule } from '@angular/platform-browser/animations'; -import { MatMenuModule, MatSnackBarModule, MatIconModule } from '@angular/material'; -import { DocumentListService } from '@alfresco/adf-content-services'; +import { DocumentListComponent } from '@alfresco/adf-content-services'; import { ShareDataTableAdapter } from '@alfresco/adf-content-services'; -import { AppConfigPipe } from '../../common/pipes/app-config.pipe'; - import { LibrariesComponent } from './libraries.component'; +import { ExperimentalDirective } from '../../directives/experimental.directive'; +import { AppTestingModule } from '../../testing/app-testing.module'; +import { ContentApiService } from '../../services/content-api.service'; -describe('Libraries Routed Component', () => { - let fixture; +describe('LibrariesComponent', () => { + let fixture: ComponentFixture; let component: LibrariesComponent; - let nodesApi: NodesApiService; + let contentApi: ContentApiService; let alfrescoApi: AlfrescoApiService; let router: Router; - let preferenceService: UserPreferencesService; let page; let node; @@ -72,16 +63,9 @@ describe('Libraries Routed Component', () => { }; }); - beforeEach(async(() => { + beforeEach(() => { TestBed.configureTestingModule({ - imports: [ - MatMenuModule, - NoopAnimationsModule, - HttpClientModule, - TranslateModule.forRoot(), - RouterTestingModule, - MatSnackBarModule, MatIconModule - ], + imports: [ AppTestingModule ], declarations: [ DataTableComponent, TimeAgoPipe, @@ -89,42 +73,23 @@ describe('Libraries Routed Component', () => { NodeFavoriteDirective, DocumentListComponent, LibrariesComponent, - AppConfigPipe - ], - providers: [ - { provide: ActivatedRoute, useValue: { - snapshot: { data: { preferencePrefix: 'prefix' } } - } } , - { provide: TranslationService, useClass: TranslationMock }, - AuthenticationService, - UserPreferencesService, - AppConfigService, StorageService, CookieService, - AlfrescoApiService, - LogService, - NotificationService, - ContentService, - NodesApiService, - DocumentListService, - ThumbnailService, - CustomResourcesService + AppConfigPipe, + ExperimentalDirective ], schemas: [ NO_ERRORS_SCHEMA ] - }) - .compileComponents().then(() => { - fixture = TestBed.createComponent(LibrariesComponent); - component = fixture.componentInstance; - - nodesApi = TestBed.get(NodesApiService); - alfrescoApi = TestBed.get(AlfrescoApiService); - alfrescoApi.reset(); - router = TestBed.get(Router); - preferenceService = TestBed.get(UserPreferencesService); }); - })); - beforeEach(() => { + fixture = TestBed.createComponent(LibrariesComponent); + component = fixture.componentInstance; + + alfrescoApi = TestBed.get(AlfrescoApiService); + alfrescoApi.reset(); + router = TestBed.get(Router); + spyOn(alfrescoApi.sitesApi, 'getSites').and.returnValue((Promise.resolve(page))); spyOn(alfrescoApi.peopleApi, 'getSiteMembership').and.returnValue((Promise.resolve({}))); + + contentApi = TestBed.get(ContentApiService); }); describe('makeLibraryTooltip()', () => { @@ -153,7 +118,7 @@ describe('Libraries Routed Component', () => { it('sets title with id when duplicate nodes title exists in list', () => { node.title = 'title'; - const data = new ShareDataTableAdapter(null); + const data = new ShareDataTableAdapter(null, null); data.setRows([{ node: { entry: { id: 'some-id', title: 'title' } } }]); component.documentList.data = data; @@ -165,7 +130,7 @@ describe('Libraries Routed Component', () => { it('sets title when no duplicate nodes title exists in list', () => { node.title = 'title'; - const data = new ShareDataTableAdapter(null); + const data = new ShareDataTableAdapter(null, null); data.setRows([{ node: { entry: { id: 'some-id', title: 'title-some-id' } } }]); component.documentList.data = data; @@ -180,7 +145,6 @@ describe('Libraries Routed Component', () => { beforeEach(() => { routerSpy = spyOn(router, 'navigate'); - spyOn(component, 'fetchNodes').and.callFake(val => val); }); it('does not navigate when id is not passed', () => { @@ -191,7 +155,7 @@ describe('Libraries Routed Component', () => { it('navigates to node id', () => { const document = { id: 'documentId' }; - spyOn(nodesApi, 'getNode').and.returnValue(Observable.of(document)); + spyOn(contentApi, 'getNode').and.returnValue(Observable.of({ entry: document })); component.navigate(node.id); @@ -232,35 +196,4 @@ describe('Libraries Routed Component', () => { expect(component.navigate).not.toHaveBeenCalled(); }); }); - - describe('onSortingChanged', () => { - it('should save sorting input', () => { - spyOn(preferenceService, 'set'); - - const event = { - detail: { - key: 'some-name', - direction: 'some-direction' - } - }; - - component.onSortingChanged(event); - - expect(preferenceService.set).toHaveBeenCalledWith('prefix.sorting.key', 'some-name'); - expect(preferenceService.set).toHaveBeenCalledWith('prefix.sorting.direction', 'some-direction'); - }); - - it('should save default sorting when no input', () => { - spyOn(preferenceService, 'set'); - - const event = { - detail: {} - }; - - component.onSortingChanged(event); - - expect(preferenceService.set).toHaveBeenCalledWith('prefix.sorting.key', 'modifiedAt'); - expect(preferenceService.set).toHaveBeenCalledWith('prefix.sorting.direction', 'desc'); - }); - }); }); diff --git a/src/app/components/libraries/libraries.component.ts b/src/app/components/libraries/libraries.component.ts index 6870d14e1..201bcec1f 100644 --- a/src/app/components/libraries/libraries.component.ts +++ b/src/app/components/libraries/libraries.component.ts @@ -23,33 +23,37 @@ * along with Alfresco. If not, see . */ -import { Component, ViewChild } from '@angular/core'; +import { Component, OnInit } from '@angular/core'; import { Router, ActivatedRoute } from '@angular/router'; -import { NodesApiService, UserPreferencesService } from '@alfresco/adf-core'; -import { DocumentListComponent, ShareDataRow } from '@alfresco/adf-content-services'; +import { ShareDataRow } from '@alfresco/adf-content-services'; import { PageComponent } from '../page.component'; +import { Store } from '@ngrx/store'; +import { AppStore } from '../../store/states/app.state'; +import { DeleteLibraryAction } from '../../store/actions'; +import { SiteEntry } from 'alfresco-js-api'; +import { ContentManagementService } from '../../common/services/content-management.service'; +import { ContentApiService } from '../../services/content-api.service'; @Component({ templateUrl: './libraries.component.html' }) -export class LibrariesComponent extends PageComponent { +export class LibrariesComponent extends PageComponent implements OnInit { - @ViewChild(DocumentListComponent) - documentList: DocumentListComponent; + constructor(private route: ActivatedRoute, + private content: ContentManagementService, + private contentApi: ContentApiService, + store: Store, + private router: Router) { + super(store); + } - sorting = [ 'title', 'asc' ]; + ngOnInit() { + super.ngOnInit(); - constructor(private nodesApi: NodesApiService, - private route: ActivatedRoute, - private router: Router, - preferences: UserPreferencesService) { - super(preferences); - - const sortingKey = preferences.get(`${this.prefix}.sorting.key`) || 'title'; - const sortingDirection = preferences.get(`${this.prefix}.sorting.direction`) || 'desc'; - - this.sorting = [sortingKey, sortingDirection]; + this.subscriptions.push( + this.content.siteDeleted.subscribe(() => this.reload()) + ); } makeLibraryTooltip(library: any): string { @@ -85,24 +89,18 @@ export class LibrariesComponent extends PageComponent { navigate(libraryId: string) { if (libraryId) { - this.nodesApi + this.contentApi .getNode(libraryId, { relativePath: '/documentLibrary' }) + .map(node => node.entry) .subscribe(documentLibrary => { this.router.navigate([ './', documentLibrary.id ], { relativeTo: this.route }); }); } } - fetchNodes(): void { - // todo: remove once all views migrate to native data source - } - - onSortingChanged(event: CustomEvent) { - this.preferences.set(`${this.prefix}.sorting.key`, event.detail.key || 'modifiedAt'); - this.preferences.set(`${this.prefix}.sorting.direction`, event.detail.direction || 'desc'); - } - - private get prefix() { - return this.route.snapshot.data.preferencePrefix; + deleteLibrary(node: SiteEntry) { + if (node && node.entry) { + this.store.dispatch(new DeleteLibraryAction(node.entry.id)); + } } } diff --git a/src/app/components/location-link/location-link.component.ts b/src/app/components/location-link/location-link.component.ts index dc198a35d..1f083351e 100644 --- a/src/app/components/location-link/location-link.component.ts +++ b/src/app/components/location-link/location-link.component.ts @@ -24,23 +24,24 @@ */ import { Component, Input, ChangeDetectionStrategy, OnInit, ViewEncapsulation } from '@angular/core'; -import { AlfrescoApiService, DataColumn, DataRow, DataTableAdapter } from '@alfresco/adf-core'; -import { PathInfoEntity } from 'alfresco-js-api'; +import { PathInfo, MinimalNodeEntity } from 'alfresco-js-api'; import { Observable } from 'rxjs/Rx'; +import { Store } from '@ngrx/store'; +import { AppStore } from '../../store/states/app.state'; +import { NavigateToParentFolder } from '../../store/actions'; +import { ContentApiService } from '../../services/content-api.service'; + @Component({ - selector: 'app-location-link', + selector: 'aca-location-link', template: ` - + {{ displayText | async }} `, changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, - // tslint:disable-next-line:use-host-property-decorator - host: { - 'class': 'app-location-link adf-location-cell' - } + host: { 'class': 'aca-location-link adf-location-cell' } }) export class LocationLinkComponent implements OnInit { @@ -56,44 +57,34 @@ export class LocationLinkComponent implements OnInit { @Input() tooltip: Observable; - constructor(private apiService: AlfrescoApiService) { + constructor( + private store: Store, + private contentApi: ContentApiService) { + } + + goToLocation() { + if (this.context) { + const node: MinimalNodeEntity = this.context.row.node; + this.store.dispatch(new NavigateToParentFolder(node)); + } } ngOnInit() { if (this.context) { - const data: DataTableAdapter = this.context.data; - const col: DataColumn = this.context.col; - const row: DataRow = this.context.row; - const value: PathInfoEntity = data.getValue(row, col); + const node: MinimalNodeEntity = this.context.row.node; + if (node && node.entry && node.entry.path) { + const path = node.entry.path; - if (value && value.name && value.elements) { - const isLibraryPath = this.isLibraryContent(value); - - this.displayText = this.getDisplayText(value); - this.tooltip = this.getTooltip(value); - - const parent = value.elements[value.elements.length - 1]; - const area = isLibraryPath ? '/libraries' : '/personal-files'; - - if (!isLibraryPath) { - this.link = [ area, parent.id ]; - } else { - // parent.id could be 'Site' folder or child as 'documentLibrary' - this.link = [ area, (parent.name === 'Sites' ? {} : parent.id) ]; + if (path && path.name && path.elements) { + this.displayText = this.getDisplayText(path); + this.tooltip = this.getTooltip(path); } } } } - private isLibraryContent(path: PathInfoEntity): boolean { - if (path && path.elements.length >= 2 && path.elements[1].name === 'Sites') { - return true; - } - return false; - } - // todo: review once 5.2.3 is out - private getDisplayText(path: PathInfoEntity): Observable { + private getDisplayText(path: PathInfo): Observable { const elements = path.elements.map(e => e.name); // for admin users @@ -112,12 +103,12 @@ export class LocationLinkComponent implements OnInit { const fragment = path.elements[path.elements.length - 2]; return new Observable(observer => { - this.apiService.nodesApi.getNodeInfo(fragment.id).then( - (node) => { + this.contentApi.getNodeInfo(fragment.id).subscribe( + node => { observer.next(node.properties['cm:title'] || node.name || fragment.name); observer.complete(); }, - (err) => { + () => { observer.next(fragment.name); observer.complete(); } @@ -129,7 +120,7 @@ export class LocationLinkComponent implements OnInit { } // todo: review once 5.2.3 is out - private getTooltip(path: PathInfoEntity): Observable { + private getTooltip(path: PathInfo): Observable { const elements = path.elements.map(e => Object.assign({}, e)); if (elements[0].name === 'Company Home') { @@ -140,8 +131,8 @@ export class LocationLinkComponent implements OnInit { const fragment = elements[2]; return new Observable(observer => { - this.apiService.nodesApi.getNodeInfo(fragment.id).then( - (node) => { + this.contentApi.getNodeInfo(fragment.id).subscribe( + node => { elements.splice(0, 2); elements[0].name = node.properties['cm:title'] || node.name || fragment.name; elements.splice(1, 1); @@ -150,7 +141,7 @@ export class LocationLinkComponent implements OnInit { observer.next(elements.map(e => e.name).join('/')); observer.complete(); }, - (err) => { + () => { elements.splice(0, 2); elements.unshift({ id: null, name: 'File Libraries' }); elements.splice(2, 1); diff --git a/src/app/components/login/login.component.html b/src/app/components/login/login.component.html index b23521bf6..e5a80250b 100644 --- a/src/app/components/login/login.component.html +++ b/src/app/components/login/login.component.html @@ -1,7 +1,7 @@ + [showLoginActions]="false"> diff --git a/src/app/components/login/login.component.spec.ts b/src/app/components/login/login.component.spec.ts index 18f006a8a..7db3e5a82 100644 --- a/src/app/components/login/login.component.spec.ts +++ b/src/app/components/login/login.component.spec.ts @@ -24,66 +24,46 @@ */ import { NO_ERRORS_SCHEMA } from '@angular/core'; -import { RouterTestingModule } from '@angular/router/testing'; import { Router } from '@angular/router'; -import { HttpClientModule } from '@angular/common/http'; -import { TranslateModule } from '@ngx-translate/core'; import { Location } from '@angular/common'; -import { TestBed, async } from '@angular/core/testing'; -import { - AuthenticationService, UserPreferencesService, TranslationService, - TranslationMock, AppConfigService, StorageService, AlfrescoApiService, - CookieService, LogService -} from '@alfresco/adf-core'; - +import { TestBed, ComponentFixture } from '@angular/core/testing'; +import { AuthenticationService, UserPreferencesService, AppConfigPipe } from '@alfresco/adf-core'; import { LoginComponent } from './login.component'; +import { AppTestingModule } from '../../testing/app-testing.module'; describe('LoginComponent', () => { - let component; - let fixture; - let router; - let userPreference; - let auth; - let location; + let fixture: ComponentFixture; + let router: Router; + let userPreference: UserPreferencesService; + let auth: AuthenticationService; + let location: Location; - beforeEach(async(() => { + beforeEach(() => { TestBed.configureTestingModule({ - imports: [ - HttpClientModule, - TranslateModule.forRoot(), - RouterTestingModule - ], + imports: [ AppTestingModule ], declarations: [ - LoginComponent + LoginComponent, + AppConfigPipe ], providers: [ - { provide: TranslationService, useClass: TranslationMock }, - Location, - CookieService, - LogService, - StorageService, - AlfrescoApiService, - AppConfigService, - AuthenticationService, - UserPreferencesService + Location ], schemas: [ NO_ERRORS_SCHEMA ] }); fixture = TestBed.createComponent(LoginComponent); - component = fixture.componentInstance; router = TestBed.get(Router); - location = TestBed.get(Location); - auth = TestBed.get(AuthenticationService); - userPreference = TestBed.get(UserPreferencesService); - })); - - beforeEach(() => { - spyOn(userPreference, 'setStoragePrefix'); spyOn(router, 'navigateByUrl'); - spyOn(auth, 'getRedirectUrl').and.returnValue('/some-url'); + + location = TestBed.get(Location); spyOn(location, 'forward'); + + auth = TestBed.get(AuthenticationService); + spyOn(auth, 'getRedirect').and.returnValue('/some-url'); + + userPreference = TestBed.get(UserPreferencesService); + spyOn(userPreference, 'setStoragePrefix'); }); describe('OnInit()', () => { @@ -101,23 +81,4 @@ describe('LoginComponent', () => { expect(location.forward).toHaveBeenCalled(); }); }); - - describe('onLoginSuccess()', () => { - beforeEach(() => { - spyOn(auth, 'isEcmLoggedIn').and.returnValue(false); - fixture.detectChanges(); - }); - - it('should redirect on success', () => { - component.onLoginSuccess(); - - expect(router.navigateByUrl).toHaveBeenCalledWith('/personal-files'); - }); - - it('should set user preference store prefix', () => { - component.onLoginSuccess({ username: 'bogus' }); - - expect(userPreference.setStoragePrefix).toHaveBeenCalledWith('bogus'); - }); - }); }); diff --git a/src/app/components/login/login.component.ts b/src/app/components/login/login.component.ts index c0e24091e..76aa0f722 100644 --- a/src/app/components/login/login.component.ts +++ b/src/app/components/login/login.component.ts @@ -25,18 +25,15 @@ import { Component, OnInit } from '@angular/core'; import { Location } from '@angular/common'; -import { Router } from '@angular/router'; -import { AuthenticationService, UserPreferencesService } from '@alfresco/adf-core'; +import { AuthenticationService } from '@alfresco/adf-core'; @Component({ templateUrl: './login.component.html' }) export class LoginComponent implements OnInit { constructor( - private router: Router, private location: Location, private auth: AuthenticationService, - private userPreferences: UserPreferencesService ) {} ngOnInit() { @@ -44,12 +41,4 @@ export class LoginComponent implements OnInit { this.location.forward(); } } - - onLoginSuccess(data) { - if (data && data.username) { - this.userPreferences.setStoragePrefix(data.username); - } - - this.router.navigateByUrl('/personal-files'); - } } diff --git a/src/app/components/page.component.spec.ts b/src/app/components/page.component.spec.ts index 0b1f78e95..d44f5a257 100644 --- a/src/app/components/page.component.spec.ts +++ b/src/app/components/page.component.spec.ts @@ -31,14 +31,10 @@ class TestClass extends PageComponent { constructor() { super(null); } - - fetchNodes(parentNodeId?: string, options?: any) { - // abstract - } } describe('PageComponent', () => { - let component; + let component: TestClass; beforeEach(() => { component = new TestClass(); @@ -57,263 +53,4 @@ describe('PageComponent', () => { expect(component.getParentNodeId()).toBe(null); }); }); - - describe('onFetchError()', () => { - it('sets isLoading state to false', () => { - component.isLoading = true; - - component.onFetchError(); - - expect(component.isLoading).toBe(false); - }); - }); - - describe('onPaginationChange()', () => { - it('fetch children nodes for current node id', () => { - component.node = { id: 'node-id' }; - spyOn(component, 'fetchNodes').and.stub(); - - component.onPaginationChange({pagination: 'pagination-data'}); - - expect(component.fetchNodes).toHaveBeenCalledWith('node-id', { pagination: 'pagination-data' }); - }); - }); - - describe('onPageLoaded()', () => { - let page; - - beforeEach(() => { - page = { - list: { - entries: ['a', 'b', 'c'], - pagination: {} - } - }; - - component.isLoading = true; - component.isEmpty = true; - component.onPageLoaded(page); - }); - - it('sets isLoading state to false', () => { - expect(component.isLoading).toBe(false); - }); - - it('sets component paging data', () => { - expect(component.paging).toBe(page); - }); - - it('sets component pagination data', () => { - expect(component.pagination).toEqual(page.list.pagination); - }); - - it('sets component isEmpty state', () => { - expect(component.isEmpty).toBe(false); - }); - }); - - describe('hasSelection()', () => { - it('returns true when it has nodes selected', () => { - const hasSelection = component.hasSelection([ {}, {} ]); - expect(hasSelection).toBe(true); - }); - - it('returns false when it has no selections', () => { - const hasSelection = component.hasSelection([]); - expect(hasSelection).toBe(false); - }); - }); - - describe('filesOnlySelected()', () => { - it('return true if only files are selected', () => { - const selected = [ { entry: { isFile: true } }, { entry: { isFile: true } } ]; - expect(component.filesOnlySelected(selected)).toBe(true); - }); - - it('return false if selection contains others types', () => { - const selected = [ { entry: { isFile: true } }, { entry: { isFolder: true } } ]; - expect(component.filesOnlySelected(selected)).toBe(false); - }); - - it('return false if selection contains no files', () => { - const selected = [ { entry: { isFolder: true } } ]; - expect(component.filesOnlySelected(selected)).toBe(false); - }); - - it('return false no selection', () => { - const selected = []; - expect(component.filesOnlySelected(selected)).toBe(false); - }); - }); - - describe('foldersOnlySelected()', () => { - it('return true if only folders are selected', () => { - const selected = [ { entry: { isFolder: true } }, { entry: { isFolder: true } } ]; - expect(component.foldersOnlySelected(selected)).toBe(true); - }); - - it('return false if selection contains others types', () => { - const selected = [ { entry: { isFile: true } }, { entry: { isFolder: true } } ]; - expect(component.foldersOnlySelected(selected)).toBe(false); - }); - - it('return false if selection contains no files', () => { - const selected = [ { entry: { isFile: true } } ]; - expect(component.foldersOnlySelected(selected)).toBe(false); - }); - - it('return false no selection', () => { - const selected = []; - expect(component.foldersOnlySelected(selected)).toBe(false); - }); - }); - - describe('isFileSelected()', () => { - it('returns true if selected node is file', () => { - const selection = [ { entry: { isFile: true } } ]; - expect(component.isFileSelected(selection)).toBe(true); - }); - - it('returns false if selected node is folder', () => { - const selection = [ { entry: { isFolder: true } } ]; - expect(component.isFileSelected(selection)).toBe(false); - }); - - it('returns false if there are more than one selections', () => { - const selection = [ { entry: { isFile: true } }, { entry: { isFile: true } } ]; - expect(component.isFileSelected(selection)).toBe(false); - }); - }); - - describe('canEditFolder()', () => { - it('returns true if selected node is folder', () => { - const selection = [ { entry: { isFolder: true } } ]; - spyOn(component, 'nodeHasPermission').and.returnValue(true); - - expect(component.canEditFolder(selection)).toBe(true); - }); - - it('returns false if selected node is file', () => { - const selection = [ { entry: { isFile: true } } ]; - expect(component.canEditFolder(selection)).toBe(false); - }); - - it('returns false if there are more than one selections', () => { - const selection = [ { entry: { isFolder: true } }, { entry: { isFolder: true } } ]; - expect(component.canEditFolder(selection)).toBe(false); - }); - - it('returns false folder dows not have edit permission', () => { - spyOn(component, 'nodeHasPermission').and.returnValue(false); - const selection = [ { entry: { isFolder: true } } ]; - - expect(component.canEditFolder(selection)).toBe(false); - }); - }); - - describe('canDelete()', () => { - it('returns false if node has no delete permission', () => { - const selection = [ { entry: { } } ]; - spyOn(component, 'nodeHasPermission').and.returnValue(false); - - expect(component.canDelete(selection)).toBe(false); - }); - - it('returns true if node has delete permission', () => { - const selection = [ { entry: { } } ]; - spyOn(component, 'nodeHasPermission').and.returnValue(true); - - expect(component.canDelete(selection)).toBe(true); - }); - }); - - describe('canMove()', () => { - it('returns true if node can be deleted', () => { - const selection = [ { entry: { } } ]; - spyOn(component, 'canDelete').and.returnValue(true); - - expect(component.canMove(selection)).toBe(true); - }); - - it('returns false if node can not be deleted', () => { - const selection = [ { entry: { } } ]; - spyOn(component, 'canDelete').and.returnValue(false); - - expect(component.canMove(selection)).toBe(false); - }); - }); - - describe('canPreviewFile()', () => { - it('it returns true if node is file', () => { - const selection = [{ entry: { isFile: true } }]; - - expect(component.canPreviewFile(selection)).toBe(true); - }); - - it('it returns false if node is folder', () => { - const selection = [{ entry: { isFolder: true } }]; - - expect(component.canPreviewFile(selection)).toBe(false); - }); - }); - - describe('canShareFile()', () => { - it('it returns true if node is file', () => { - const selection = [{ entry: { isFile: true } }]; - - expect(component.canShareFile(selection)).toBe(true); - }); - - it('it returns false if node is folder', () => { - const selection = [{ entry: { isFolder: true } }]; - - expect(component.canShareFile(selection)).toBe(false); - }); - }); - - describe('canDownloadFile()', () => { - it('it returns true if node is file', () => { - const selection = [{ entry: { isFile: true } }]; - - expect(component.canDownloadFile(selection)).toBe(true); - }); - - it('it returns false if node is folder', () => { - const selection = [{ entry: { isFolder: true } }]; - - expect(component.canDownloadFile(selection)).toBe(false); - }); - }); - - describe('nodeHasPermission()', () => { - it('returns true is has permission', () => { - const node = { allowableOperations: ['some-operation'] }; - - expect(component.nodeHasPermission(node, 'some-operation')).toBe(true); - }); - - it('returns true is has permission', () => { - const node = { allowableOperations: ['other-operation'] }; - - expect(component.nodeHasPermission(node, 'some-operation')).toBe(false); - }); - }); - - describe('canUpdate()', () => { - it('should return true if node can be edited', () => { - const selection = [ { entry: { - allowableOperations: ['update'] - } } ]; - - expect(component.canUpdate(selection)).toBe(true); - }); - - it(`should return false if node cannot be edited`, () => { - const selection = [ { entry: { - allowableOperations: ['other-permission'] - } } ]; - - expect(component.canUpdate(selection)).toBe(false); - }); - }); }); diff --git a/src/app/components/page.component.ts b/src/app/components/page.component.ts index 2c66c376b..765612193 100644 --- a/src/app/components/page.component.ts +++ b/src/app/components/page.component.ts @@ -23,176 +23,82 @@ * along with Alfresco. If not, see . */ -import { MinimalNodeEntity, MinimalNodeEntryEntity, NodePaging, Pagination } from 'alfresco-js-api'; -import { UserPreferencesService } from '@alfresco/adf-core'; -import { ShareDataRow } from '@alfresco/adf-content-services'; +import { DocumentListComponent, ShareDataRow } from '@alfresco/adf-content-services'; +import { FileUploadErrorEvent } from '@alfresco/adf-core'; +import { OnDestroy, OnInit, ViewChild } from '@angular/core'; +import { Store } from '@ngrx/store'; +import { MinimalNodeEntity, MinimalNodeEntryEntity } from 'alfresco-js-api'; +import { takeUntil } from 'rxjs/operators'; +import { Subject, Subscription } from 'rxjs/Rx'; +import { SnackbarErrorAction, ViewNodeAction, SetSelectedNodesAction } from '../store/actions'; +import { appSelection } from '../store/selectors/app.selectors'; +import { AppStore } from '../store/states/app.state'; +import { SelectionState } from '../store/states/selection.state'; -export abstract class PageComponent { +export abstract class PageComponent implements OnInit, OnDestroy { + + onDestroy$: Subject = new Subject(); + + @ViewChild(DocumentListComponent) + documentList: DocumentListComponent; title = 'Page'; - - isLoading = false; - isEmpty = true; infoDrawerOpened = false; - - paging: NodePaging; - pagination: Pagination; - node: MinimalNodeEntryEntity; + selection: SelectionState; + + protected subscriptions: Subscription[] = []; static isLockedNode(node) { return node.isLocked || (node.properties && node.properties['cm:lockType'] === 'READ_ONLY_LOCK'); } - abstract fetchNodes(parentNodeId?: string, options?: any): void; + constructor(protected store: Store) {} - constructor(protected preferences: UserPreferencesService) { + ngOnInit() { + this.store + .select(appSelection) + .pipe(takeUntil(this.onDestroy$)) + .subscribe(selection => { + this.selection = selection; + if (selection.isEmpty) { + this.infoDrawerOpened = false; + } + }); } - onFetchError(error: any) { - this.isLoading = false; + ngOnDestroy() { + this.subscriptions.forEach(subscription => subscription.unsubscribe()); + this.subscriptions = []; + + this.onDestroy$.next(true); + this.onDestroy$.complete(); + } + + showPreview(node: MinimalNodeEntity) { + if (node && node.entry) { + const { id, nodeId, name, isFile, isFolder } = node.entry; + const parentId = this.node ? this.node.id : null; + + this.store.dispatch(new ViewNodeAction({ + parentId, + id: nodeId || id, + name, + isFile, + isFolder + })); + } } getParentNodeId(): string { return this.node ? this.node.id : null; } - onPaginationChange(pagination: any) { - this.fetchNodes(this.getParentNodeId(), pagination); - } - - onPageLoaded(page: NodePaging) { - this.isLoading = false; - this.paging = page; - this.pagination = { ...page.list.pagination }; - this.isEmpty = !(page.list.entries && page.list.entries.length > 0); - } - - hasSelection(selection: Array): boolean { - return selection && selection.length > 0; - } - - filesOnlySelected(selection: Array): boolean { - if (this.hasSelection(selection)) { - return selection.every(entity => entity.entry && entity.entry.isFile); - } - return false; - } - - foldersOnlySelected(selection: Array): boolean { - if (this.hasSelection(selection)) { - return selection.every(entity => entity.entry && entity.entry.isFolder); - } - return false; - } - - isFileSelected(selection: Array): boolean { - if (selection && selection.length === 1) { - const entry = selection[0].entry; - - if (entry && entry.isFile) { - return true; - } - } - return false; - } - - canEditFolder(selection: Array): boolean { - if (selection && selection.length === 1) { - const entry = selection[0].entry; - - if (entry && entry.isFolder) { - return this.nodeHasPermission(entry, 'update'); - } - } - return false; - } - - canDelete(selection: Array = []): boolean { - return selection.every(node => node.entry && this.nodeHasPermission(node.entry, 'delete')); - } - - canMove(selection: Array): boolean { - return this.canDelete(selection); - } - - canUpdate(selection: Array = []): boolean { - return selection.every(node => node.entry && this.nodeHasPermission(node.entry, 'update')); - } - - canPreviewFile(selection: Array): boolean { - return this.isFileSelected(selection); - } - - canShareFile(selection: Array): boolean { - return this.isFileSelected(selection); - } - - canDownloadFile(selection: Array): boolean { - return this.isFileSelected(selection); - } - - canUpdateFile(selection: Array): boolean { - return this.isFileSelected(selection) && this.nodeHasPermission(selection[0].entry, 'update'); - } - - canManageVersions(selection: Array): boolean { - return this.canUpdateFile(selection); - } - - nodeHasPermission(node: MinimalNodeEntryEntity, permission: string): boolean { - if (node && permission) { - const { allowableOperations = [] } = (node || {}); - - if (allowableOperations.indexOf(permission) > -1) { - return true; - } - } - - return false; - } - - onChangePageSize(event: Pagination): void { - this.preferences.paginationSize = event.maxItems; - } - - onNodeSelect(event, documentList) { - if (!!event.detail && !!event.detail.node) { - - const node: MinimalNodeEntryEntity = event.detail.node.entry; - if (node && PageComponent.isLockedNode(node)) { - this.unSelectLockedNodes(documentList); - } - } - } - - unSelectLockedNodes(documentList) { - documentList.selection = documentList.selection.filter(item => !PageComponent.isLockedNode(item.entry)); - - const dataTable = documentList.dataTable; - if (dataTable && dataTable.data) { - const rows = dataTable.data.getRows(); - - if (rows && rows.length > 0) { - rows.forEach(r => { - if (this.isLockedRow(r)) { - r.isSelected = false; - } - }); - } - } - } - - isLockedRow(row) { - return row.getValue('isLocked') || - (row.getValue('properties') && row.getValue('properties')['cm:lockType'] === 'READ_ONLY_LOCK'); - } - imageResolver(row: ShareDataRow): string | null { const entry: MinimalNodeEntryEntity = row.node.entry; if (PageComponent.isLockedNode(entry)) { - return '/assets/images/ic_lock_black_24dp_1x.png'; + return 'assets/images/ic_lock_black_24dp_1x.png'; } return null; } @@ -204,4 +110,28 @@ export abstract class PageComponent { this.infoDrawerOpened = !this.infoDrawerOpened; } + + reload(): void { + if (this.documentList) { + this.documentList.resetSelection(); + this.store.dispatch(new SetSelectedNodesAction([])); + this.documentList.reload(); + } + } + + onFileUploadedError(error: FileUploadErrorEvent) { + let message = 'APP.MESSAGES.UPLOAD.ERROR.GENERIC'; + + if (error.error.status === 409) { + message = 'APP.MESSAGES.UPLOAD.ERROR.CONFLICT'; + } + + if (error.error.status === 500) { + message = 'APP.MESSAGES.UPLOAD.ERROR.500'; + } + + const action = new SnackbarErrorAction(message); + + this.store.dispatch(action); + } } diff --git a/src/app/components/preview/preview.component.html b/src/app/components/preview/preview.component.html index 585cec05b..4807b59a9 100644 --- a/src/app/components/preview/preview.component.html +++ b/src/app/components/preview/preview.component.html @@ -13,7 +13,11 @@ - + + @@ -35,29 +39,25 @@ @@ -65,15 +65,15 @@ mat-menu-item *ngIf="permission.check(node, ['delete'])" (click)="deleteFile()"> - delete + delete {{ 'APP.ACTIONS.DELETE' | translate }} diff --git a/src/app/components/preview/preview.component.spec.ts b/src/app/components/preview/preview.component.spec.ts index 75cff1615..e4c466510 100644 --- a/src/app/components/preview/preview.component.spec.ts +++ b/src/app/components/preview/preview.component.spec.ts @@ -25,20 +25,14 @@ import { NO_ERRORS_SCHEMA } from '@angular/core'; import { Router, ActivatedRoute } from '@angular/router'; -import { RouterTestingModule } from '@angular/router/testing'; -import { TestBed, async, ComponentFixture } from '@angular/core/testing'; -import { - AlfrescoApiService, UserPreferencesService, TranslationService, TranslationMock, - AppConfigService, StorageService, CookieService, NotificationService, NodeFavoriteDirective -} from '@alfresco/adf-core'; -import { TranslateModule } from '@ngx-translate/core'; -import { HttpClientModule } from '@angular/common/http'; - +import { TestBed, ComponentFixture } from '@angular/core/testing'; +import { UserPreferencesService, AppConfigPipe, NodeFavoriteDirective } from '@alfresco/adf-core'; import { PreviewComponent } from './preview.component'; import { Observable } from 'rxjs/Rx'; -import { NodePermissionService } from '../../common/services/node-permission.service'; -import { ContentManagementService } from '../../common/services/content-management.service'; -import { MatSnackBarModule } from '@angular/material'; +import { EffectsModule } from '@ngrx/effects'; +import { NodeEffects } from '../../store/effects/node.effects'; +import { AppTestingModule } from '../../testing/app-testing.module'; +import { ContentApiService } from '../../services/content-api.service'; describe('PreviewComponent', () => { @@ -46,45 +40,31 @@ describe('PreviewComponent', () => { let component: PreviewComponent; let router: Router; let route: ActivatedRoute; - let alfrescoApi: AlfrescoApiService; let preferences: UserPreferencesService; + let contentApi: ContentApiService; - beforeEach(async(() => { + beforeEach(() => { TestBed.configureTestingModule({ imports: [ - HttpClientModule, - RouterTestingModule, - TranslateModule.forRoot(), - MatSnackBarModule - ], - providers: [ - { provide: TranslationService, useClass: TranslationMock }, - AlfrescoApiService, - AppConfigService, - StorageService, - CookieService, - NotificationService, - UserPreferencesService, - NodePermissionService, - ContentManagementService + AppTestingModule, + EffectsModule.forRoot([NodeEffects]) ], declarations: [ + AppConfigPipe, PreviewComponent, NodeFavoriteDirective ], schemas: [ NO_ERRORS_SCHEMA ] - }) - .compileComponents().then(() => { - fixture = TestBed.createComponent(PreviewComponent); - component = fixture.componentInstance; - - router = TestBed.get(Router); - route = TestBed.get(ActivatedRoute); - alfrescoApi = TestBed.get(AlfrescoApiService); - alfrescoApi.reset(); - preferences = TestBed.get(UserPreferencesService); }); - })); + + fixture = TestBed.createComponent(PreviewComponent); + component = fixture.componentInstance; + + router = TestBed.get(Router); + route = TestBed.get(ActivatedRoute); + preferences = TestBed.get(UserPreferencesService); + contentApi = TestBed.get(ContentApiService); + }); it('should extract the property path root', () => { expect(component.getRootField('some.property.path')).toBe('some'); @@ -205,7 +185,7 @@ describe('PreviewComponent', () => { component.onVisibilityChanged(false); expect(router.navigate).toHaveBeenCalledWith( - ['libraries'] + ['libraries', {}] ); }); @@ -219,7 +199,7 @@ describe('PreviewComponent', () => { component.onVisibilityChanged(false); expect(router.navigate).toHaveBeenCalledWith( - ['libraries', 'site1'] + ['libraries', {}, 'site1'] ); }); @@ -233,7 +213,7 @@ describe('PreviewComponent', () => { component.onVisibilityChanged(false); expect(router.navigate).toHaveBeenCalledWith( - ['shared'] + ['shared', {}] ); }); @@ -358,35 +338,33 @@ describe('PreviewComponent', () => { it('should not display node when id is missing', async () => { spyOn(router, 'navigate').and.stub(); - spyOn(alfrescoApi.nodesApi, 'getNodeInfo').and.returnValue( - Promise.resolve(null) + spyOn(contentApi, 'getNodeInfo').and.returnValue( + Observable.of(null) ); await component.displayNode(null); - expect(alfrescoApi.nodesApi.getNodeInfo).not.toHaveBeenCalled(); + expect(contentApi.getNodeInfo).not.toHaveBeenCalled(); expect(router.navigate).not.toHaveBeenCalled(); }); it('should navigate to original location if node not found', async () => { spyOn(router, 'navigate').and.stub(); - spyOn(alfrescoApi.nodesApi, 'getNodeInfo').and.returnValue( - Promise.resolve(null) + spyOn(contentApi, 'getNodeInfo').and.returnValue( + Observable.of(null) ); component.previewLocation = 'personal-files'; await component.displayNode('folder1'); - expect(alfrescoApi.nodesApi.getNodeInfo).toHaveBeenCalledWith( - 'folder1', { include: [ 'allowableOperations' ] } - ); + expect(contentApi.getNodeInfo).toHaveBeenCalledWith('folder1'); expect(router.navigate).toHaveBeenCalledWith(['personal-files', 'folder1']); }); it('should navigate to original location if node is not a File', async () => { spyOn(router, 'navigate').and.stub(); - spyOn(alfrescoApi.nodesApi, 'getNodeInfo').and.returnValue( - Promise.resolve({ + spyOn(contentApi, 'getNodeInfo').and.returnValue( + Observable.of({ isFile: false }) ); @@ -394,31 +372,27 @@ describe('PreviewComponent', () => { component.previewLocation = 'personal-files'; await component.displayNode('folder1'); - expect(alfrescoApi.nodesApi.getNodeInfo).toHaveBeenCalledWith( - 'folder1', { include: [ 'allowableOperations' ] } - ); + expect(contentApi.getNodeInfo).toHaveBeenCalledWith('folder1'); expect(router.navigate).toHaveBeenCalledWith(['personal-files', 'folder1']); }); it('should navigate to original location in case of Alfresco API errors', async () => { spyOn(router, 'navigate').and.stub(); - spyOn(alfrescoApi.nodesApi, 'getNodeInfo').and.returnValue( - Promise.reject('error') + spyOn(contentApi, 'getNodeInfo').and.returnValue( + Observable.throw('error') ); component.previewLocation = 'personal-files'; await component.displayNode('folder1'); - expect(alfrescoApi.nodesApi.getNodeInfo).toHaveBeenCalledWith( - 'folder1', { include: [ 'allowableOperations' ] } - ); + expect(contentApi.getNodeInfo).toHaveBeenCalledWith('folder1'); expect(router.navigate).toHaveBeenCalledWith(['personal-files', 'folder1']); }); it('should navigate to original location in case of internal errors', async () => { spyOn(router, 'navigate').and.stub(); - spyOn(alfrescoApi.nodesApi, 'getNodeInfo').and.returnValue( - Promise.resolve({ + spyOn(contentApi, 'getNodeInfo').and.returnValue( + Observable.of({ isFile: true }) ); @@ -429,17 +403,15 @@ describe('PreviewComponent', () => { component.previewLocation = 'personal-files'; await component.displayNode('folder1'); - expect(alfrescoApi.nodesApi.getNodeInfo).toHaveBeenCalledWith( - 'folder1', { include: [ 'allowableOperations' ] } - ); + expect(contentApi.getNodeInfo).toHaveBeenCalledWith('folder1'); expect(router.navigate).toHaveBeenCalledWith(['personal-files', 'folder1']); }); it('should setup node for displaying', async () => { spyOn(router, 'navigate').and.stub(); spyOn(component, 'getNearestNodes').and.returnValue({ left: 'node1', right: 'node3' }); - spyOn(alfrescoApi.nodesApi, 'getNodeInfo').and.returnValue( - Promise.resolve({ + spyOn(contentApi, 'getNodeInfo').and.returnValue( + Observable.of({ id: 'node2', parentId: 'parent1', isFile: true @@ -458,8 +430,8 @@ describe('PreviewComponent', () => { preferences.set('personal-files.sorting.key', 'name'); preferences.set('personal-files.sorting.direction', 'desc'); - spyOn(alfrescoApi.nodesApi, 'getNodeChildren').and.returnValue( - Promise.resolve({ + spyOn(contentApi, 'getNodeChildren').and.returnValue( + Observable.of({ list: { entries: [ { entry: { id: 'node1', name: 'node 1' } }, @@ -477,8 +449,8 @@ describe('PreviewComponent', () => { preferences.set('personal-files.sorting.key', 'missing'); preferences.set('personal-files.sorting.direction', 'desc'); - spyOn(alfrescoApi.nodesApi, 'getNodeChildren').and.returnValue( - Promise.resolve({ + spyOn(contentApi, 'getNodeChildren').and.returnValue( + Observable.of({ list: { entries: [ { entry: { id: 'node1', name: 'node 1' } }, @@ -500,8 +472,8 @@ describe('PreviewComponent', () => { it('should sort file ids for personal-files with [modifiedAt desc]', async () => { spyOn(preferences, 'get').and.returnValue(null); - spyOn(alfrescoApi.nodesApi, 'getNodeChildren').and.returnValue( - Promise.resolve({ + spyOn(contentApi, 'getNodeChildren').and.returnValue( + Observable.of({ list: { entries: [ { entry: { id: 'node1', name: 'node 1', modifiedAt: 1 } }, @@ -519,8 +491,8 @@ describe('PreviewComponent', () => { preferences.set('personal-files.sorting.key', 'name'); preferences.set('personal-files.sorting.direction', 'desc'); - spyOn(alfrescoApi.nodesApi, 'getNodeChildren').and.returnValue( - Promise.resolve({ + spyOn(contentApi, 'getNodeChildren').and.returnValue( + Observable.of({ list: { entries: [ { entry: { id: 'node1', name: 'node 1' } }, @@ -542,8 +514,8 @@ describe('PreviewComponent', () => { it('should sort file ids for libraries with [modifiedAt desc]', async () => { spyOn(preferences, 'get').and.returnValue(null); - spyOn(alfrescoApi.nodesApi, 'getNodeChildren').and.returnValue( - Promise.resolve({ + spyOn(contentApi, 'getNodeChildren').and.returnValue( + Observable.of({ list: { entries: [ { entry: { id: 'node1', name: 'node 1', modifiedAt: new Date(1) } }, @@ -561,8 +533,8 @@ describe('PreviewComponent', () => { preferences.set('favorites.sorting.key', 'name'); preferences.set('favorites.sorting.direction', 'desc'); - spyOn(alfrescoApi.favoritesApi, 'getFavorites').and.returnValue( - Promise.resolve({ + spyOn(contentApi, 'getFavorites').and.returnValue( + Observable.of({ list: { entries: [ { entry: { target: { file: { id: 'file3', name: 'file 3' } } } }, @@ -580,8 +552,8 @@ describe('PreviewComponent', () => { it('should sort file ids for favorites with [modifiedAt desc]', async () => { spyOn(preferences, 'get').and.returnValue(null); - spyOn(alfrescoApi.favoritesApi, 'getFavorites').and.returnValue( - Promise.resolve({ + spyOn(contentApi, 'getFavorites').and.returnValue( + Observable.of({ list: { entries: [ { entry: { target: { file: { id: 'file3', modifiedAt: new Date(3) } } } }, @@ -600,8 +572,8 @@ describe('PreviewComponent', () => { preferences.set('shared.sorting.key', 'name'); preferences.set('shared.sorting.direction', 'asc'); - spyOn(alfrescoApi.sharedLinksApi, 'findSharedLinks').and.returnValue( - Promise.resolve({ + spyOn(contentApi, 'findSharedLinks').and.returnValue( + Observable.of({ list: { entries: [ { entry: { nodeId: 'node2', name: 'node 2', modifiedAt: new Date(2) } }, @@ -618,8 +590,8 @@ describe('PreviewComponent', () => { it('should sort file ids for favorites with [modifiedAt desc]', async () => { spyOn(preferences, 'get').and.returnValue(null); - spyOn(alfrescoApi.sharedLinksApi, 'findSharedLinks').and.returnValue( - Promise.resolve({ + spyOn(contentApi, 'findSharedLinks').and.returnValue( + Observable.of({ list: { entries: [ { entry: { nodeId: 'node2', name: 'node 2', modifiedAt: new Date(2) } }, @@ -637,14 +609,14 @@ describe('PreviewComponent', () => { preferences.set('recent-files.sorting.key', 'name'); preferences.set('recent-files.sorting.direction', 'asc'); - spyOn(alfrescoApi.peopleApi, 'getPerson').and.returnValue( - Promise.resolve({ + spyOn(contentApi, 'getPerson').and.returnValue( + Observable.of({ entry: { id: 'user' } }) ); - spyOn(alfrescoApi.searchApi, 'search').and.returnValue( - Promise.resolve({ + spyOn(contentApi, 'search').and.returnValue( + Observable.of({ list: { entries: [ { entry: { id: 'node2', name: 'node 2', modifiedAt: new Date(2) } }, @@ -661,14 +633,14 @@ describe('PreviewComponent', () => { it('should sort file ids for favorites with [modifiedAt desc]', async () => { spyOn(preferences, 'get').and.returnValue(null); - spyOn(alfrescoApi.peopleApi, 'getPerson').and.returnValue( - Promise.resolve({ + spyOn(contentApi, 'getPerson').and.returnValue( + Observable.of({ entry: { id: 'user' } }) ); - spyOn(alfrescoApi.searchApi, 'search').and.returnValue( - Promise.resolve({ + spyOn(contentApi, 'search').and.returnValue( + Observable.of({ list: { entries: [ { entry: { id: 'node2', name: 'node 2', modifiedAt: new Date(2) } }, diff --git a/src/app/components/preview/preview.component.ts b/src/app/components/preview/preview.component.ts index 6ff4e806e..81bbdfdb3 100644 --- a/src/app/components/preview/preview.component.ts +++ b/src/app/components/preview/preview.component.ts @@ -24,21 +24,23 @@ */ import { Component, OnInit, ViewEncapsulation } from '@angular/core'; -import { ActivatedRoute, Router } from '@angular/router'; -import { AlfrescoApiService, UserPreferencesService, ObjectUtils } from '@alfresco/adf-core'; +import { ActivatedRoute, Router, UrlTree, UrlSegmentGroup, UrlSegment, PRIMARY_OUTLET } from '@angular/router'; +import { UserPreferencesService, ObjectUtils, UploadService } from '@alfresco/adf-core'; import { Node, MinimalNodeEntity } from 'alfresco-js-api'; import { NodePermissionService } from '../../common/services/node-permission.service'; -import { ContentManagementService } from '../../common/services/content-management.service'; - +import { Store } from '@ngrx/store'; +import { AppStore } from '../../store/states/app.state'; +import { DeleteNodesAction } from '../../store/actions'; +import { PageComponent } from '../page.component'; +import { ContentApiService } from '../../services/content-api.service'; @Component({ selector: 'app-preview', templateUrl: 'preview.component.html', styleUrls: ['preview.component.scss'], encapsulation: ViewEncapsulation.None, - // tslint:disable-next-line:use-host-property-decorator host: { 'class': 'app-preview' } }) -export class PreviewComponent implements OnInit { +export class PreviewComponent extends PageComponent implements OnInit { node: Node; previewLocation: string = null; @@ -54,12 +56,16 @@ export class PreviewComponent implements OnInit { selectedEntities: MinimalNodeEntity[] = []; - constructor(private router: Router, - private route: ActivatedRoute, - private apiService: AlfrescoApiService, + constructor( + private contentApi: ContentApiService, + private uploadService: UploadService, private preferences: UserPreferencesService, - private content: ContentManagementService, + private route: ActivatedRoute, + private router: Router, + store: Store, public permission: NodePermissionService) { + + super(store); } ngOnInit() { @@ -87,6 +93,10 @@ export class PreviewComponent implements OnInit { this.displayNode(id); } }); + + this.subscriptions = this.subscriptions.concat([ + this.uploadService.fileUploadError.subscribe((error) => this.onFileUploadedError(error)) + ]); } /** @@ -96,9 +106,7 @@ export class PreviewComponent implements OnInit { async displayNode(id: string) { if (id) { try { - this.node = await this.apiService.nodesApi.getNodeInfo(id, { - include: ['allowableOperations'] - }); + this.node = await this.contentApi.getNodeInfo(id).toPromise(); this.selectedEntities = [{ entry: this.node }]; if (this.node && this.node.isFile) { @@ -124,7 +132,7 @@ export class PreviewComponent implements OnInit { const shouldSkipNavigation = this.routesSkipNavigation.includes(this.previewLocation); if (!isVisible) { - const route = [this.previewLocation]; + const route = this.getNavigationCommands(this.previewLocation); if ( !shouldSkipNavigation && this.folderId ) { route.push(this.folderId); @@ -213,11 +221,11 @@ export class PreviewComponent implements OnInit { if ((source === 'personal-files' || source === 'libraries') && folderId) { const sortKey = this.preferences.get('personal-files.sorting.key') || 'modifiedAt'; const sortDirection = this.preferences.get('personal-files.sorting.direction') || 'desc'; - const nodes = await this.apiService.nodesApi.getNodeChildren(folderId, { + const nodes = await this.contentApi.getNodeChildren(folderId, { // orderBy: `${sortKey} ${sortDirection}`, fields: ['id', this.getRootField(sortKey)], where: '(isFile=true)' - }); + }).toPromise(); const entries = nodes.list.entries.map(obj => obj.entry); this.sort(entries, sortKey, sortDirection); @@ -226,10 +234,10 @@ export class PreviewComponent implements OnInit { } if (source === 'favorites') { - const nodes = await this.apiService.favoritesApi.getFavorites('-me-', { + const nodes = await this.contentApi.getFavorites('-me-', { where: '(EXISTS(target/file))', fields: ['target'] - }); + }).toPromise(); const sortKey = this.preferences.get('favorites.sorting.key') || 'modifiedAt'; const sortDirection = this.preferences.get('favorites.sorting.direction') || 'desc'; @@ -243,9 +251,9 @@ export class PreviewComponent implements OnInit { const sortingKey = this.preferences.get('shared.sorting.key') || 'modifiedAt'; const sortingDirection = this.preferences.get('shared.sorting.direction') || 'desc'; - const nodes = await this.apiService.sharedLinksApi.findSharedLinks({ + const nodes = await this.contentApi.findSharedLinks({ fields: ['nodeId', this.getRootField(sortingKey)] - }); + }).toPromise(); const entries = nodes.list.entries.map(obj => obj.entry); this.sort(entries, sortingKey, sortingDirection); @@ -254,12 +262,12 @@ export class PreviewComponent implements OnInit { } if (source === 'recent-files') { - const person = await this.apiService.peopleApi.getPerson('-me-'); + const person = await this.contentApi.getPerson('-me-').toPromise(); const username = person.entry.id; const sortingKey = this.preferences.get('recent-files.sorting.key') || 'modifiedAt'; const sortingDirection = this.preferences.get('recent-files.sorting.direction') || 'desc'; - const nodes = await this.apiService.searchApi.search({ + const nodes = await this.contentApi.search({ query: { query: '*', language: 'afts' @@ -275,7 +283,7 @@ export class PreviewComponent implements OnInit { field: 'cm:modified', ascending: false }] - }); + }).toPromise(); const entries = nodes.list.entries.map(obj => obj.entry); this.sort(entries, sortingKey, sortingDirection); @@ -326,11 +334,29 @@ export class PreviewComponent implements OnInit { return path; } - async deleteFile() { - try { - await this.content.deleteNode(this.node); - this.onVisibilityChanged(false); - } catch { + deleteFile() { + this.store.dispatch(new DeleteNodesAction([ + { + id: this.node.nodeId || this.node.id, + name: this.node.name + } + ])); + this.onVisibilityChanged(false); + } + + private getNavigationCommands(url: string): any[] { + const urlTree: UrlTree = this.router.parseUrl(url); + const urlSegmentGroup: UrlSegmentGroup = urlTree.root.children[PRIMARY_OUTLET]; + + if (!urlSegmentGroup) { + return [url]; } + + const urlSegments: UrlSegment[] = urlSegmentGroup.segments; + + return urlSegments.reduce(function(acc, item) { + acc.push(item.path, item.parameters); + return acc; + }, []); } } diff --git a/src/app/components/recent-files/recent-files.component.html b/src/app/components/recent-files/recent-files.component.html index 18aa9a7db..0196a3a92 100644 --- a/src/app/components/recent-files/recent-files.component.html +++ b/src/app/components/recent-files/recent-files.component.html @@ -3,29 +3,27 @@ - + + [overlapTrigger]="false"> @@ -91,27 +83,22 @@
-
- + + (node-dblclick)="onNodeDoubleClick($event.detail?.node)"> - - + @@ -133,10 +120,10 @@ - + @@ -157,50 +144,12 @@ - - - - + +
-
- - - -
- -
- - - -
- - - - - - - -
- face - {{ 'VERSION.SELECTION.EMPTY' | translate }} -
-
-
-
+
+
diff --git a/src/app/components/recent-files/recent-files.component.spec.ts b/src/app/components/recent-files/recent-files.component.spec.ts index 62f681d2c..1f7865044 100644 --- a/src/app/components/recent-files/recent-files.component.spec.ts +++ b/src/app/components/recent-files/recent-files.component.spec.ts @@ -23,36 +23,23 @@ * along with Alfresco. If not, see . */ -import { TestBed, async } from '@angular/core/testing'; +import { TestBed, ComponentFixture } from '@angular/core/testing'; import { NO_ERRORS_SCHEMA } from '@angular/core'; -import { Router, ActivatedRoute } from '@angular/router'; -import { RouterTestingModule } from '@angular/router/testing'; -import { HttpClientModule } from '@angular/common/http'; import { - NotificationService, TranslationService, TranslationMock, - NodesApiService, AlfrescoApiService, ContentService, - UserPreferencesService, LogService, AppConfigService, - StorageService, CookieService, ThumbnailService, AuthenticationService, - TimeAgoPipe, NodeNameTooltipPipe, NodeFavoriteDirective, DataTableComponent + AlfrescoApiService, + TimeAgoPipe, NodeNameTooltipPipe, NodeFavoriteDirective, DataTableComponent, AppConfigPipe } from '@alfresco/adf-core'; -import { DocumentListComponent, CustomResourcesService } from '@alfresco/adf-content-services'; -import { TranslateModule } from '@ngx-translate/core'; -import { NoopAnimationsModule } from '@angular/platform-browser/animations'; -import { MatMenuModule, MatSnackBarModule, MatIconModule } from '@angular/material'; -import { DocumentListService } from '@alfresco/adf-content-services'; +import { DocumentListComponent } from '@alfresco/adf-content-services'; import { ContentManagementService } from '../../common/services/content-management.service'; -import { NodeInfoDirective } from '../../common/directives/node-info.directive'; -import { AppConfigPipe } from '../../common/pipes/app-config.pipe'; import { RecentFilesComponent } from './recent-files.component'; +import { AppTestingModule } from '../../testing/app-testing.module'; -describe('RecentFiles Routed Component', () => { - let fixture; - let component; - let router: Router; +describe('RecentFilesComponent', () => { + let fixture: ComponentFixture; + let component: RecentFilesComponent; let alfrescoApi: AlfrescoApiService; let contentService: ContentManagementService; - let preferenceService: UserPreferencesService; let page; beforeEach(() => { @@ -64,119 +51,64 @@ describe('RecentFiles Routed Component', () => { }; }); - beforeEach(async(() => { + beforeEach(() => { TestBed.configureTestingModule({ imports: [ - MatMenuModule, - NoopAnimationsModule, - HttpClientModule, - TranslateModule.forRoot(), - RouterTestingModule, - MatSnackBarModule, MatIconModule + AppTestingModule ], declarations: [ DataTableComponent, TimeAgoPipe, NodeNameTooltipPipe, NodeFavoriteDirective, - NodeInfoDirective, DocumentListComponent, RecentFilesComponent, AppConfigPipe ], - providers: [ - { provide: ActivatedRoute, useValue: { - snapshot: { data: { preferencePrefix: 'prefix' } } - } } , - { provide: TranslationService, useClass: TranslationMock }, - AuthenticationService, - UserPreferencesService, - AppConfigService, StorageService, CookieService, - AlfrescoApiService, - LogService, - NotificationService, - ContentManagementService, - ContentService, - NodesApiService, - DocumentListService, - ThumbnailService, - CustomResourcesService - ], schemas: [ NO_ERRORS_SCHEMA ] - }) - .compileComponents().then(() => { - fixture = TestBed.createComponent(RecentFilesComponent); - component = fixture.componentInstance; - - router = TestBed.get(Router); - contentService = TestBed.get(ContentManagementService); - preferenceService = TestBed.get(UserPreferencesService); - alfrescoApi = TestBed.get(AlfrescoApiService); - alfrescoApi.reset(); }); - })); - beforeEach(() => { + fixture = TestBed.createComponent(RecentFilesComponent); + component = fixture.componentInstance; + + contentService = TestBed.get(ContentManagementService); + alfrescoApi = TestBed.get(AlfrescoApiService); + alfrescoApi.reset(); + spyOn(alfrescoApi.peopleApi, 'getPerson').and.returnValue(Promise.resolve({ - entry: { id: 'personId' } - })); + entry: { id: 'personId' } + })); - spyOn(alfrescoApi.searchApi, 'search').and.returnValue(Promise.resolve(page)); + spyOn(alfrescoApi.searchApi, 'search').and.returnValue(Promise.resolve(page)); }); describe('OnInit()', () => { beforeEach(() => { - spyOn(component, 'refresh').and.stub(); + spyOn(component, 'reload').and.stub(); }); it('should reload nodes on onDeleteNode event', () => { fixture.detectChanges(); - contentService.nodeDeleted.next(); + contentService.nodesDeleted.next(); - expect(component.refresh).toHaveBeenCalled(); + expect(component.reload).toHaveBeenCalled(); }); it('should reload on onRestoreNode event', () => { fixture.detectChanges(); - contentService.nodeRestored.next(); + contentService.nodesRestored.next(); - expect(component.refresh).toHaveBeenCalled(); + expect(component.reload).toHaveBeenCalled(); }); it('should reload on move node event', () => { fixture.detectChanges(); - contentService.nodeMoved.next(); + contentService.nodesMoved.next(); - expect(component.refresh).toHaveBeenCalled(); - }); - }); - - describe('onNodeDoubleClick()', () => { - beforeEach(() => { - spyOn(component, 'fetchNodes').and.callFake(val => val); - }); - - it('open preview if node is file', () => { - spyOn(router, 'navigate').and.stub(); - const node: any = { id: 'node-id', isFile: true }; - - component.onNodeDoubleClick(node); - fixture.detectChanges(); - - expect(router.navigate['calls'].argsFor(0)[0]).toEqual(['./preview', node.id]); - }); - - it('does not open preview if node is folder', () => { - spyOn(router, 'navigate').and.stub(); - const node: any = { isFolder: true }; - - component.onNodeDoubleClick(node); - fixture.detectChanges(); - - expect(router.navigate).not.toHaveBeenCalled(); + expect(component.reload).toHaveBeenCalled(); }); }); @@ -185,40 +117,9 @@ describe('RecentFiles Routed Component', () => { spyOn(component.documentList, 'reload'); fixture.detectChanges(); - component.refresh(); + component.reload(); expect(component.documentList.reload).toHaveBeenCalled(); }); }); - - describe('onSortingChanged', () => { - it('should save sorting input', () => { - spyOn(preferenceService, 'set'); - - const event = { - detail: { - key: 'some-name', - direction: 'some-direction' - } - }; - - component.onSortingChanged(event); - - expect(preferenceService.set).toHaveBeenCalledWith('prefix.sorting.key', 'some-name'); - expect(preferenceService.set).toHaveBeenCalledWith('prefix.sorting.direction', 'some-direction'); - }); - - it('should save default sorting when no input', () => { - spyOn(preferenceService, 'set'); - - const event = { - detail: {} - }; - - component.onSortingChanged(event); - - expect(preferenceService.set).toHaveBeenCalledWith('prefix.sorting.key', 'modifiedAt'); - expect(preferenceService.set).toHaveBeenCalledWith('prefix.sorting.direction', 'desc'); - }); - }); }); diff --git a/src/app/components/recent-files/recent-files.component.ts b/src/app/components/recent-files/recent-files.component.ts index a62b3e4f2..f9581e608 100644 --- a/src/app/components/recent-files/recent-files.component.ts +++ b/src/app/components/recent-files/recent-files.component.ts @@ -23,78 +23,48 @@ * along with Alfresco. If not, see . */ -import { Subscription } from 'rxjs/Rx'; -import { Component, ViewChild, OnInit, OnDestroy } from '@angular/core'; -import { Router, ActivatedRoute } from '@angular/router'; -import { MinimalNodeEntryEntity } from 'alfresco-js-api'; -import { UserPreferencesService } from '@alfresco/adf-core'; -import { DocumentListComponent } from '@alfresco/adf-content-services'; +import { Component, OnInit } from '@angular/core'; +import { MinimalNodeEntity } from 'alfresco-js-api'; +import { UploadService } from '@alfresco/adf-core'; import { ContentManagementService } from '../../common/services/content-management.service'; import { PageComponent } from '../page.component'; +import { NodePermissionService } from '../../common/services/node-permission.service'; +import { Store } from '@ngrx/store'; +import { AppStore } from '../../store/states/app.state'; @Component({ templateUrl: './recent-files.component.html' }) -export class RecentFilesComponent extends PageComponent implements OnInit, OnDestroy { - - @ViewChild(DocumentListComponent) - documentList: DocumentListComponent; - - private subscriptions: Subscription[] = []; - - sorting = [ 'modifiedAt', 'desc' ]; +export class RecentFilesComponent extends PageComponent implements OnInit { constructor( - private router: Router, - private route: ActivatedRoute, + store: Store, + private uploadService: UploadService, private content: ContentManagementService, - preferences: UserPreferencesService) { - super(preferences); - - const sortingKey = preferences.get(`${this.prefix}.sorting.key`) || 'modifiedAt'; - const sortingDirection = preferences.get(`${this.prefix}.sorting.direction`) || 'desc'; - - this.sorting = [sortingKey, sortingDirection]; + public permission: NodePermissionService) { + super(store); } ngOnInit() { + super.ngOnInit(); + this.subscriptions = this.subscriptions.concat([ - this.content.nodeDeleted.subscribe(() => this.refresh()), - this.content.nodeMoved.subscribe(() => this.refresh()), - this.content.nodeRestored.subscribe(() => this.refresh()) + this.content.nodesDeleted.subscribe(() => this.reload()), + this.content.nodesMoved.subscribe(() => this.reload()), + this.content.nodesRestored.subscribe(() => this.reload()), + this.uploadService.fileUploadError.subscribe((error) => this.onFileUploadedError(error)) ]); } - ngOnDestroy() { - this.subscriptions.forEach(s => s.unsubscribe()); - } + onNodeDoubleClick(node: MinimalNodeEntity) { + if (node && node.entry) { + if (PageComponent.isLockedNode(node.entry)) { + event.preventDefault(); + return; + } - onNodeDoubleClick(node: MinimalNodeEntryEntity) { - if (node && PageComponent.isLockedNode(node)) { - event.preventDefault(); - - } else if (node && node.isFile) { - this.router.navigate(['./preview', node.id], { relativeTo: this.route }); + this.showPreview(node); } } - - fetchNodes(): void { - // todo: remove once all views migrate to native data source - } - - refresh(): void { - if (this.documentList) { - this.documentList.reload(); - } - } - - onSortingChanged(event: CustomEvent) { - this.preferences.set(`${this.prefix}.sorting.key`, event.detail.key || 'modifiedAt'); - this.preferences.set(`${this.prefix}.sorting.direction`, event.detail.direction || 'desc'); - } - - private get prefix() { - return this.route.snapshot.data.preferencePrefix; - } } diff --git a/src/app/components/search-input-control/search-input-control.component.html b/src/app/components/search-input-control/search-input-control.component.html new file mode 100644 index 000000000..13db9bdf2 --- /dev/null +++ b/src/app/components/search-input-control/search-input-control.component.html @@ -0,0 +1,92 @@ +
+
+ + + + + +
+ clear + +
+
+
+
+ + + + + + + + + +

+ {{ item?.entry.name }} +

+ +

+
+

{{item?.entry?.createdByUser?.displayName}}

+
+ + + + +

{{ 'SEARCH.RESULTS.NONE' | translate:{searchTerm: searchTerm} }}

+
+
+
+
+
diff --git a/src/app/components/search-input-control/search-input-control.component.scss b/src/app/components/search-input-control/search-input-control.component.scss new file mode 100644 index 000000000..cb1dd538d --- /dev/null +++ b/src/app/components/search-input-control/search-input-control.component.scss @@ -0,0 +1,8 @@ +.adf-clear-search-icon-wrapper { + width: 1em; + + .mat-icon { + font-size: 110%; + cursor: pointer; + } +} diff --git a/src/app/components/search-input-control/search-input-control.component.ts b/src/app/components/search-input-control/search-input-control.component.ts new file mode 100644 index 000000000..496cb1d5b --- /dev/null +++ b/src/app/components/search-input-control/search-input-control.component.ts @@ -0,0 +1,275 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { ThumbnailService } from '@alfresco/adf-core'; +import { animate, state, style, transition, trigger } from '@angular/animations'; +import { Component, EventEmitter, Input, OnDestroy, OnInit, Output, + QueryList, ViewEncapsulation, ViewChild, ViewChildren, ElementRef, TemplateRef, ContentChild } from '@angular/core'; +import { MinimalNodeEntity, QueryBody } from 'alfresco-js-api'; +import { Observable } from 'rxjs/Observable'; +import { Subject } from 'rxjs/Subject'; +import { MatListItem } from '@angular/material'; +import { debounceTime } from 'rxjs/operators'; +import { EmptySearchResultComponent, SearchComponent } from '@alfresco/adf-content-services'; + +@Component({ + selector: 'app-search-input-control', + templateUrl: './search-input-control.component.html', + styleUrls: ['./search-input-control.component.scss'], + animations: [ + trigger('transitionMessages', [ + state('active', style({ transform: 'translateX(0%)', 'margin-left': '13px' })), + state('inactive', style({ transform: 'translateX(81%)'})), + state('no-animation', style({ transform: 'translateX(0%)', width: '100%' })), + transition('inactive => active', + animate('300ms cubic-bezier(0.55, 0, 0.55, 0.2)')), + transition('active => inactive', + animate('300ms cubic-bezier(0.55, 0, 0.55, 0.2)')) + ]) + ], + encapsulation: ViewEncapsulation.None, + host: { class: 'adf-search-control' } +}) +export class SearchInputControlComponent implements OnInit, OnDestroy { + + /** Toggles whether to use an expanding search control. If false + * then a regular input is used. + */ + @Input() + expandable = true; + + /** Toggles highlighting of the search term in the results. */ + @Input() + highlight = false; + + /** Type of the input field to render, e.g. "search" or "text" (default). */ + @Input() + inputType = 'text'; + + /** Toggles auto-completion of the search input field. */ + @Input() + autocomplete = false; + + /** Toggles "find-as-you-type" suggestions for possible matches. */ + @Input() + liveSearchEnabled = true; + + /** Maximum number of results to show in the live search. */ + @Input() + liveSearchMaxResults = 5; + + /** @deprecated in 2.1.0 */ + @Input() + customQueryBody: QueryBody; + + /** Emitted when the search is submitted pressing ENTER button. + * The search term is provided as value of the event. + */ + @Output() + submit: EventEmitter = new EventEmitter(); + + /** Emitted when the search term is changed. The search term is provided + * in the 'value' property of the returned object. If the term is less + * than three characters in length then the term is truncated to an empty + * string. + */ + @Output() + searchChange: EventEmitter = new EventEmitter(); + + /** Emitted when a file item from the list of "find-as-you-type" results is selected. */ + @Output() + optionClicked: EventEmitter = new EventEmitter(); + + @ViewChild('search') + searchAutocomplete: SearchComponent; + + @ViewChild('searchInput') + searchInput: ElementRef; + + @ViewChildren(MatListItem) + private listResultElement: QueryList; + + @ContentChild(EmptySearchResultComponent) + emptySearchTemplate: EmptySearchResultComponent; + + searchTerm = ''; + subscriptAnimationState: string; + noSearchResultTemplate: TemplateRef = null; + skipToggle = false; + + private toggleSearch = new Subject(); + private focusSubject = new Subject(); + + constructor(private thumbnailService: ThumbnailService) { + + this.toggleSearch.asObservable().pipe(debounceTime(200)).subscribe(() => { + if (this.expandable && !this.skipToggle) { + this.subscriptAnimationState = this.subscriptAnimationState === 'inactive' ? 'active' : 'inactive'; + + if (this.subscriptAnimationState === 'inactive') { + this.searchTerm = ''; + this.searchAutocomplete.resetResults(); + if ( document.activeElement.id === this.searchInput.nativeElement.id) { + this.searchInput.nativeElement.blur(); + } + } + } + this.skipToggle = false; + }); + } + + applySearchFocus(animationDoneEvent) { + if (animationDoneEvent.toState === 'active') { + this.searchInput.nativeElement.focus(); + } + } + + ngOnInit() { + this.subscriptAnimationState = this.expandable ? 'inactive' : 'no-animation'; + this.setupFocusEventHandlers(); + } + + isNoSearchTemplatePresent(): boolean { + return this.emptySearchTemplate ? true : false; + } + + ngOnDestroy(): void { + if (this.focusSubject) { + this.focusSubject.unsubscribe(); + this.focusSubject = null; + } + + if (this.toggleSearch) { + this.toggleSearch.unsubscribe(); + this.toggleSearch = null; + } + } + + searchSubmit(event: any) { + this.submit.emit(event); + this.toggleSearchBar(); + } + + inputChange(event: any) { + this.searchChange.emit(event); + } + + getAutoComplete(): string { + return this.autocomplete ? 'on' : 'off'; + } + + getMimeTypeIcon(node: MinimalNodeEntity): string { + let mimeType; + + if (node.entry.content && node.entry.content.mimeType) { + mimeType = node.entry.content.mimeType; + } + if (node.entry.isFolder) { + mimeType = 'folder'; + } + + return this.thumbnailService.getMimeTypeIcon(mimeType); + } + + isSearchBarActive() { + return this.subscriptAnimationState === 'active' && this.liveSearchEnabled; + } + + toggleSearchBar() { + if (this.toggleSearch) { + this.toggleSearch.next(); + } + } + + elementClicked(item: any) { + if (item.entry) { + this.optionClicked.next(item); + this.toggleSearchBar(); + } + } + + onFocus($event): void { + this.focusSubject.next($event); + } + + onBlur($event): void { + this.focusSubject.next($event); + } + + activateToolbar() { + if (!this.isSearchBarActive()) { + this.toggleSearchBar(); + } + } + + selectFirstResult() { + if ( this.listResultElement && this.listResultElement.length > 0) { + const firstElement: MatListItem = this.listResultElement.first; + firstElement._getHostElement().focus(); + } + } + + onRowArrowDown($event: KeyboardEvent): void { + const nextElement: any = this.getNextElementSibling( $event.target); + if (nextElement) { + nextElement.focus(); + } + } + + onRowArrowUp($event: KeyboardEvent): void { + const previousElement: any = this.getPreviousElementSibling( $event.target); + if (previousElement) { + previousElement.focus(); + } else { + this.searchInput.nativeElement.focus(); + this.focusSubject.next(new FocusEvent('focus')); + } + } + + private setupFocusEventHandlers() { + const focusEvents: Observable = this.focusSubject.asObservable() + .debounceTime(50); + focusEvents.filter(($event: any) => { + return this.isSearchBarActive() && ($event.type === 'blur' || $event.type === 'focusout'); + }).subscribe(() => { + this.toggleSearchBar(); + }); + } + + clear(event: any) { + this.searchTerm = ''; + this.searchChange.emit(''); + this.skipToggle = true; + } + + private getNextElementSibling(node: Element): Element { + return node.nextElementSibling; + } + + private getPreviousElementSibling(node: Element): Element { + return node.previousElementSibling; + } + +} diff --git a/src/app/components/search-input/search-input.component.html b/src/app/components/search-input/search-input.component.html index d1ca1b18c..3b2ba7f96 100644 --- a/src/app/components/search-input/search-input.component.html +++ b/src/app/components/search-input/search-input.component.html @@ -1,5 +1,8 @@ - - + [expandable]="!onSearchResults" + [liveSearchEnabled]="!onSearchResults" + (submit)="onSearchSubmit($event)" + (searchChange)="onSearchChange($event)"> + diff --git a/src/app/components/search-input/search-input.component.scss b/src/app/components/search-input/search-input.component.scss deleted file mode 100644 index 01065f775..000000000 --- a/src/app/components/search-input/search-input.component.scss +++ /dev/null @@ -1,10 +0,0 @@ -@import 'variables'; - -// todo: remove once ADF 2.0 is out -:host { - overflow: hidden; -} - -adf-search-control { - color: $alfresco-white; -} diff --git a/src/app/components/search-input/search-input.component.spec.ts b/src/app/components/search-input/search-input.component.spec.ts index 640ed4ac6..82de63b77 100644 --- a/src/app/components/search-input/search-input.component.spec.ts +++ b/src/app/components/search-input/search-input.component.spec.ts @@ -24,21 +24,23 @@ */ import { NO_ERRORS_SCHEMA } from '@angular/core'; -import { TestBed, async } from '@angular/core/testing'; -import { Router } from '@angular/router'; -import { RouterTestingModule } from '@angular/router/testing'; +import { TestBed, async, ComponentFixture, fakeAsync, tick } from '@angular/core/testing'; import { SearchInputComponent } from './search-input.component'; +import { AppTestingModule } from '../../testing/app-testing.module'; +import { Actions, ofType } from '@ngrx/effects'; +import { ViewNodeAction, VIEW_NODE, NAVIGATE_FOLDER, NavigateToFolder } from '../../store/actions'; +import { map } from 'rxjs/operators'; describe('SearchInputComponent', () => { - let fixture; - let component; - let router: Router; + let fixture: ComponentFixture; + let component: SearchInputComponent; + let actions$: Actions; beforeEach(async(() => { TestBed.configureTestingModule({ imports: [ - RouterTestingModule + AppTestingModule ], declarations: [ SearchInputComponent @@ -47,32 +49,40 @@ describe('SearchInputComponent', () => { }) .compileComponents() .then(() => { + actions$ = TestBed.get(Actions); fixture = TestBed.createComponent(SearchInputComponent); component = fixture.componentInstance; - router = TestBed.get(Router); - fixture.detectChanges(); }); })); describe('onItemClicked()', () => { - it('opens preview if node is file', () => { - spyOn(router, 'navigate').and.stub(); + it('opens preview if node is file', fakeAsync(done => { + actions$.pipe( + ofType(VIEW_NODE), + map(action => { + expect(action.payload.id).toBe('node-id'); + done(); + }) + ); + const node = { entry: { isFile: true, id: 'node-id', parentId: 'parent-id' } }; component.onItemClicked(node); + tick(); + })); - expect(router.navigate['calls'].argsFor(0)[0]) - .toEqual([`/personal-files/${node.entry.parentId}/preview/`, node.entry.id]); - }); - - it('navigates if node is folder', () => { - const node = { entry: { isFolder: true } }; - spyOn(router, 'navigate'); - + it('navigates if node is folder', fakeAsync(done => { + actions$.pipe( + ofType(NAVIGATE_FOLDER), + map(action => { + expect(action.payload.entry.id).toBe('folder-id'); + done(); + }) + ); + const node = { entry: { id: 'folder-id', isFolder: true } }; component.onItemClicked(node); - - expect(router.navigate).toHaveBeenCalled(); - }); + tick(); + })); }); }); diff --git a/src/app/components/search-input/search-input.component.theme.scss b/src/app/components/search-input/search-input.component.theme.scss new file mode 100644 index 000000000..8be93e791 --- /dev/null +++ b/src/app/components/search-input/search-input.component.theme.scss @@ -0,0 +1,28 @@ +@mixin aca-search-input-theme($theme) { + $background: map-get($theme, background); + + .aca-search-input{ + display: flex; + box-sizing: border-box; + padding: 0; + flex-direction: row; + align-items: center; + white-space: nowrap; + + .adf-search-control { + color: mat-color($background, card); + + .mat-form-field-underline { + background-color: mat-color($background, card); + } + } + + .adf-search-button { + left: -15px; + margin-left: 15px; + align-items: flex-start; + font: 400 11px system-ui; + color: mat-color($background, card); + } + } +} diff --git a/src/app/components/search-input/search-input.component.ts b/src/app/components/search-input/search-input.component.ts index b0a14c365..2285cf376 100644 --- a/src/app/components/search-input/search-input.component.ts +++ b/src/app/components/search-input/search-input.component.ts @@ -23,27 +23,84 @@ * along with Alfresco. If not, see . */ -import { Component } from '@angular/core'; -import { Router } from '@angular/router'; +import { Component, OnInit, ViewChild, ViewEncapsulation } from '@angular/core'; +import { + NavigationEnd, PRIMARY_OUTLET, Router, RouterEvent, UrlSegment, UrlSegmentGroup, + UrlTree +} from '@angular/router'; import { MinimalNodeEntity } from 'alfresco-js-api'; +import { SearchInputControlComponent } from '../search-input-control/search-input-control.component'; +import { Store } from '@ngrx/store'; +import { AppStore } from '../../store/states/app.state'; +import { SearchByTermAction, ViewNodeAction, NavigateToFolder } from '../../store/actions'; @Component({ - selector: 'app-search-input', + selector: 'aca-search-input', templateUrl: 'search-input.component.html', - styleUrls: ['search-input.component.scss'] + encapsulation: ViewEncapsulation.None, + host: { class: 'aca-search-input' } }) -export class SearchInputComponent { +export class SearchInputComponent implements OnInit { - constructor( - private router: Router) { + hasOneChange = false; + hasNewChange = false; + navigationTimer: any; + + @ViewChild('searchInputControl') + searchInputControl: SearchInputControlComponent; + + constructor(private router: Router, private store: Store) { + } + + ngOnInit() { + this.showInputValue(); + + this.router.events.filter(e => e instanceof RouterEvent).subscribe(event => { + if (event instanceof NavigationEnd) { + this.showInputValue(); + } + }); + } + + showInputValue() { + if (this.onSearchResults) { + + let searchedWord = null; + const urlTree: UrlTree = this.router.parseUrl(this.router.url); + const urlSegmentGroup: UrlSegmentGroup = urlTree.root.children[PRIMARY_OUTLET]; + + if (urlSegmentGroup) { + const urlSegments: UrlSegment[] = urlSegmentGroup.segments; + searchedWord = urlSegments[0].parameters['q']; + } + + if (this.searchInputControl) { + this.searchInputControl.searchTerm = searchedWord; + this.searchInputControl.subscriptAnimationState = 'no-animation'; + } + + } else { + if (this.searchInputControl.subscriptAnimationState === 'no-animation') { + this.searchInputControl.subscriptAnimationState = 'active'; + this.searchInputControl.searchTerm = ''; + this.searchInputControl.toggleSearchBar(); + } + } } onItemClicked(node: MinimalNodeEntity) { if (node && node.entry) { - if (node.entry.isFile) { - this.router.navigate([`/personal-files/${node.entry.parentId}/preview/`, node.entry.id]); - } else if (node.entry.isFolder) { - this.router.navigate([ '/personal-files', node.entry.id ]); + const { id, nodeId, name, isFile, isFolder, parentId } = node.entry; + if (isFile) { + this.store.dispatch(new ViewNodeAction({ + parentId, + id: nodeId || id, + name, + isFile, + isFolder + })); + } else if (isFolder) { + this.store.dispatch(new NavigateToFolder(node)); } } } @@ -54,9 +111,36 @@ export class SearchInputComponent { * @param event Parameters relating to the search */ onSearchSubmit(event: KeyboardEvent) { - const value = (event.target as HTMLInputElement).value; - this.router.navigate(['/search', { - q: value - }]); + const searchTerm = (event.target as HTMLInputElement).value; + if (searchTerm) { + this.store.dispatch(new SearchByTermAction(searchTerm)); + } + } + + onSearchChange(searchTerm: string) { + if (this.onSearchResults) { + + if (this.hasOneChange) { + this.hasNewChange = true; + } else { + this.hasOneChange = true; + } + + if (this.hasNewChange) { + clearTimeout(this.navigationTimer); + this.hasNewChange = false; + } + + this.navigationTimer = setTimeout(() => { + if (searchTerm) { + this.store.dispatch(new SearchByTermAction(searchTerm)); + } + this.hasOneChange = false; + }, 1000); + } + } + + get onSearchResults() { + return this.router.url.indexOf('/search') === 0; } } diff --git a/src/app/components/search/search.component.html b/src/app/components/search/search.component.html index 18aa4ac04..5ef9c4e63 100644 --- a/src/app/components/search/search.component.html +++ b/src/app/components/search/search.component.html @@ -1,32 +1,135 @@ - - +
+
+ + + + -
- - -
+ -
- - + -
- - + - - + + + + + + + + +
+ +
+
+
+ + +
+
+
+
{{ 'APP.BROWSE.SEARCH.FOUND_RESULTS' | translate: { number: totalResults } }}
+ +
+ +
+
+ + +
+ + + + + + + + + + + + + + + + + +
+

Your search returned 0 results

+
+
+
+
+ + + +
+
+
+
+ +
diff --git a/src/app/components/search/search.component.scss b/src/app/components/search/search.component.scss index a650b0c02..6a201a3b1 100644 --- a/src/app/components/search/search.component.scss +++ b/src/app/components/search/search.component.scss @@ -5,11 +5,9 @@ &__facets { display: flex; - height: 35px; - flex-direction: column; - justify-content: center; - padding: 5px; - border-bottom: 1px solid #eee; + flex-direction: row; + margin-top: 5px; + margin-bottom: 5px; } &__content { @@ -17,10 +15,46 @@ border-left: 1px solid #eee; } + &__content-header { + display: flex; + padding: 0 25px 0 25px; + flex-direction: row; + align-items: center; + border-bottom: 1px solid #eee; + } + + &--info-text { + flex: 1; + font-size: 16px; + color: rgba(0, 0, 0, 0.54); + } + .adf-search-filter { min-width: 260px; padding: 5px; height: 100%; overflow: scroll; + + &--hidden { + display: none; + } + } + + .text--bold { + font-weight: 600; + } + + .content { + @include flex-row; + flex: unset; + height: unset; + padding-top: 8px; + padding-bottom: 8px; + flex-wrap: wrap; + + &__side--left { + @include flex-column; + height: unset; + } } } diff --git a/src/app/components/search/search.component.ts b/src/app/components/search/search.component.ts index 645668776..d965642bc 100644 --- a/src/app/components/search/search.component.ts +++ b/src/app/components/search/search.component.ts @@ -23,31 +23,40 @@ * along with Alfresco. If not, see . */ -import { Component, OnInit, Optional, ViewChild } from '@angular/core'; -import { NodePaging, Pagination } from 'alfresco-js-api'; -import { Router, ActivatedRoute, Params } from '@angular/router'; -import { SearchQueryBuilderService, SearchComponent as AdfSearchComponent } from '@alfresco/adf-content-services'; +import { Component, OnInit, ViewChild } from '@angular/core'; +import { NodePaging, Pagination, MinimalNodeEntity } from 'alfresco-js-api'; +import { ActivatedRoute, Params } from '@angular/router'; +import { SearchQueryBuilderService, SearchComponent as AdfSearchComponent, NodePermissionService } from '@alfresco/adf-content-services'; +import { PageComponent } from '../page.component'; +import { Store } from '@ngrx/store'; +import { AppStore } from '../../store/states/app.state'; +import { NavigateToFolder } from '../../store/actions'; @Component({ selector: 'app-search', templateUrl: './search.component.html', - styleUrls: ['./search.component.scss'] + styleUrls: ['./search.component.scss'], + providers: [SearchQueryBuilderService] }) -export class SearchComponent implements OnInit { +export class SearchComponent extends PageComponent implements OnInit { @ViewChild('search') search: AdfSearchComponent; + searchedWord: string; queryParamName = 'q'; - searchedWord = ''; data: NodePaging; - maxItems = 5; - skipCount = 0; + totalResults = 0; + sorting = ['name', 'asc']; constructor( - public router: Router, + public permission: NodePermissionService, private queryBuilder: SearchQueryBuilderService, - @Optional() private route: ActivatedRoute) { + private route: ActivatedRoute, + store: Store + ) { + super(store); + queryBuilder.paging = { skipCount: 0, maxItems: 25 @@ -55,27 +64,105 @@ export class SearchComponent implements OnInit { } ngOnInit() { + super.ngOnInit(); + + this.sorting = this.getSorting(); + this.resetSettings(); + + this.subscriptions.push( + this.queryBuilder.updated.subscribe(() => { + this.sorting = this.getSorting(); + }), + + this.queryBuilder.executed.subscribe(data => { + this.onSearchResultLoaded(data); + }) + ); + if (this.route) { this.route.params.forEach((params: Params) => { this.searchedWord = params.hasOwnProperty(this.queryParamName) ? params[this.queryParamName] : null; - this.queryBuilder.queryFragments['queryName'] = `cm:name:'${this.searchedWord}'`; - this.queryBuilder.update(); + const query = this.formatSearchQuery(this.searchedWord); + + if (query) { + this.queryBuilder.userQuery = query; + this.queryBuilder.update(); + } else { + this.queryBuilder.userQuery = null; + this.queryBuilder.executed.next( {list: { pagination: { totalItems: 0 }, entries: []}} ); + } }); } } - onSearchResultLoaded(nodePaging: NodePaging) { - this.data = nodePaging; + // TODO: workaround for ADF 2.4.0 bug + private resetSettings() { + this.queryBuilder.categories + .map(category => category.component) + .filter(component => component.selector === 'check-list') + .map(component => component.settings.options || []) + .reduce((acc, value) => acc.concat(value), []) + .forEach(value => { + if (value.hasOwnProperty('checked')) { + value.checked = false; + } + }); } - onRefreshPagination(pagination: Pagination) { - this.maxItems = pagination.maxItems; - this.skipCount = pagination.skipCount; + private formatSearchQuery(userInput: string) { + if (!userInput) { + return null; + } + const suffix = userInput.lastIndexOf('*') >= 0 ? '' : '*'; + const query = `${userInput}${suffix} OR name:${userInput}${suffix}`; + + return query; + } + + onSearchResultLoaded(nodePaging: NodePaging) { + this.data = nodePaging; + this.totalResults = this.getNumberOfResults(); + } + + getNumberOfResults() { + if (this.data && this.data.list && this.data.list.pagination) { + return this.data.list.pagination.totalItems; + } + return 0; + } + + onPaginationChanged(pagination: Pagination) { this.queryBuilder.paging = { maxItems: pagination.maxItems, skipCount: pagination.skipCount }; this.queryBuilder.update(); } + + private getSorting(): string[] { + const primary = this.queryBuilder.getPrimarySorting(); + + if (primary) { + return [primary.key, primary.ascending ? 'asc' : 'desc']; + } + + return ['name', 'asc']; + } + + onNodeDoubleClick(node: MinimalNodeEntity) { + if (node && node.entry) { + if (node.entry.isFolder) { + this.store.dispatch(new NavigateToFolder(node)); + return; + } + + if (PageComponent.isLockedNode(node.entry)) { + event.preventDefault(); + return; + } + + this.showPreview(node); + } + } } diff --git a/src/app/components/settings/settings.component.html b/src/app/components/settings/settings.component.html new file mode 100644 index 000000000..c17358f9e --- /dev/null +++ b/src/app/components/settings/settings.component.html @@ -0,0 +1,68 @@ + + + + {{ appName$ | async }} + + + + + + + + + {{ 'APP.SETTINGS.REPOSITORY-SETTINGS' | translate }} + +
+
+ + + + {{ 'APP.SETTINGS.INVALID-VALUE-FORMAT' | translate }} + + + {{ 'APP.SETTINGS.REQUIRED-FIELD' | translate }} + + +
+ +
+ + +
+
+
+ + + + + {{ 'APP.SETTINGS.APPLICATION-SETTINGS' | translate }} + + + + Language Picker + + + + + + + {{ 'APP.SETTINGS.EXPERIMENTAL-FEATURES' | translate }} + + + + Library Management + + +
diff --git a/src/app/components/settings/settings.component.theme.scss b/src/app/components/settings/settings.component.theme.scss new file mode 100644 index 000000000..11de98a19 --- /dev/null +++ b/src/app/components/settings/settings.component.theme.scss @@ -0,0 +1,70 @@ +@mixin aca-settings-theme($theme) { + $background: map-get($theme, background); + $app-menu-height: 64px; + + .aca-settings { + .settings-input { + width: 50%; + } + + .settings-buttons { + text-align: right; + + .mat-button { + text-transform: uppercase; + } + } + + .app-menu { + height: $app-menu-height; + + &.adf-toolbar { + .mat-toolbar { + background-color: inherit; + font-family: inherit; + min-height: $app-menu-height; + height: $app-menu-height; + + .mat-toolbar-layout { + height: $app-menu-height; + + .mat-toolbar-row { + height: $app-menu-height; + } + } + } + + .adf-toolbar-divider { + margin-left: 5px; + margin-right: 5px; + + & > div { + background-color: mat-color($background, card); + } + } + + .adf-toolbar-title { + color: mat-color($background, card); + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + } + } + + .app-menu__title { + width: 100px; + height: 50px; + margin-left: 40px; + display: flex; + justify-content: center; + align-items: stretch; + + &> img { + width: 100%; + object-fit: contain; + } + } + } + } +} diff --git a/src/app/components/settings/settings.component.ts b/src/app/components/settings/settings.component.ts new file mode 100644 index 000000000..e2ff08280 --- /dev/null +++ b/src/app/components/settings/settings.component.ts @@ -0,0 +1,100 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { Component, ViewEncapsulation, OnInit } from '@angular/core'; +import { AppConfigService, StorageService, SettingsService } from '@alfresco/adf-core'; +import { Validators, FormGroup, FormBuilder } from '@angular/forms'; +import { Observable } from 'rxjs/Rx'; +import { Store } from '@ngrx/store'; +import { AppStore } from '../../store/states'; +import { appLanguagePicker, selectHeaderColor, selectAppName } from '../../store/selectors/app.selectors'; +import { MatCheckboxChange } from '@angular/material'; +import { SetLanguagePickerAction } from '../../store/actions'; + +@Component({ + selector: 'aca-settings', + templateUrl: './settings.component.html', + encapsulation: ViewEncapsulation.None, + host: { class: 'aca-settings' } +}) +export class SettingsComponent implements OnInit { + + private defaultPath = '/assets/images/alfresco-logo-white.svg'; + + form: FormGroup; + + appName$: Observable; + headerColor$: Observable; + languagePicker$: Observable; + libraries: boolean; + + constructor( + private store: Store, + private appConfig: AppConfigService, + private settingsService: SettingsService, + private storage: StorageService, + private fb: FormBuilder) { + this.appName$ = store.select(selectAppName); + this.languagePicker$ = store.select(appLanguagePicker); + this.headerColor$ = store.select(selectHeaderColor); + } + + get logo() { + return this.appConfig.get('application.logo', this.defaultPath); + } + + ngOnInit() { + this.form = this.fb.group({ + ecmHost: ['', [Validators.required, Validators.pattern('^(http|https):\/\/.*[^/]$')]] + }); + + this.reset(); + + const libraries = this.appConfig.get('experimental.libraries'); + this.libraries = (libraries === true || libraries === 'true'); + } + + apply(model: any, isValid: boolean) { + if (isValid) { + this.storage.setItem('ecmHost', model.ecmHost); + // window.location.reload(true); + } + } + + reset() { + this.form.reset({ + ecmHost: this.storage.getItem('ecmHost') || this.settingsService.ecmHost + }); + } + + onLanguagePickerValueChanged(event: MatCheckboxChange) { + this.storage.setItem('languagePicker', event.checked.toString()); + this.store.dispatch(new SetLanguagePickerAction(event.checked)); + } + + onChangeLibrariesFeature(event: MatCheckboxChange) { + this.storage.setItem('experimental.libraries', event.checked.toString()); + } +} diff --git a/src/app/components/shared-files/shared-files.component.html b/src/app/components/shared-files/shared-files.component.html index a6705935f..8e74cdcf2 100644 --- a/src/app/components/shared-files/shared-files.component.html +++ b/src/app/components/shared-files/shared-files.component.html @@ -3,28 +3,26 @@ - + + [overlapTrigger]="false"> @@ -99,24 +90,20 @@
-
- + + [sorting]="[ 'modifiedAt', 'desc' ]" + (node-dblclick)="showPreview($event.detail?.node)"> - - + @@ -138,10 +125,10 @@ - + @@ -174,50 +161,12 @@ - - - - + +
-
- - - -
- -
- - - -
- - - - - - - -
- face - {{ 'VERSION.SELECTION.EMPTY' | translate }} -
-
-
-
+
+
diff --git a/src/app/components/shared-files/shared-files.component.spec.ts b/src/app/components/shared-files/shared-files.component.spec.ts index 8c4371f8f..4cd743077 100644 --- a/src/app/components/shared-files/shared-files.component.spec.ts +++ b/src/app/components/shared-files/shared-files.component.spec.ts @@ -23,38 +23,22 @@ * along with Alfresco. If not, see . */ -import { TestBed, async, fakeAsync, tick } from '@angular/core/testing'; +import { TestBed, ComponentFixture } from '@angular/core/testing'; import { NO_ERRORS_SCHEMA } from '@angular/core'; -import { Router, ActivatedRoute } from '@angular/router'; -import { RouterTestingModule } from '@angular/router/testing'; -import { HttpClientModule } from '@angular/common/http'; import { - NotificationService, TranslationService, TranslationMock, - NodesApiService, AlfrescoApiService, ContentService, - UserPreferencesService, LogService, AppConfigService, - StorageService, CookieService, ThumbnailService, AuthenticationService, - TimeAgoPipe, NodeNameTooltipPipe, NodeFavoriteDirective,DataTableComponent + AlfrescoApiService, + TimeAgoPipe, NodeNameTooltipPipe, NodeFavoriteDirective, DataTableComponent, AppConfigPipe } from '@alfresco/adf-core'; -import { DocumentListComponent, CustomResourcesService } from '@alfresco/adf-content-services'; -import { TranslateModule } from '@ngx-translate/core'; -import { NoopAnimationsModule } from '@angular/platform-browser/animations'; -import { MatMenuModule, MatSnackBarModule, MatIconModule } from '@angular/material'; -import { DocumentListService } from '@alfresco/adf-content-services'; +import { DocumentListComponent } from '@alfresco/adf-content-services'; import { ContentManagementService } from '../../common/services/content-management.service'; -import { NodeInfoDirective } from '../../common/directives/node-info.directive'; -import { NodePermissionService } from '../../common/services/node-permission.service'; -import { AppConfigPipe } from '../../common/pipes/app-config.pipe'; - import { SharedFilesComponent } from './shared-files.component'; +import { AppTestingModule } from '../../testing/app-testing.module'; describe('SharedFilesComponent', () => { - let fixture; + let fixture: ComponentFixture; let component: SharedFilesComponent; let contentService: ContentManagementService; - let nodeService; let alfrescoApi: AlfrescoApiService; - let preferenceService: UserPreferencesService; - let router: Router; let page; beforeEach(() => { @@ -66,133 +50,59 @@ describe('SharedFilesComponent', () => { }; }); - beforeEach(async(() => { + beforeEach(() => { TestBed .configureTestingModule({ - imports: [ - MatMenuModule, - NoopAnimationsModule, - HttpClientModule, - TranslateModule.forRoot(), - RouterTestingModule, - MatSnackBarModule, MatIconModule - ], + imports: [ AppTestingModule ], declarations: [ DataTableComponent, TimeAgoPipe, NodeNameTooltipPipe, NodeFavoriteDirective, - NodeInfoDirective, DocumentListComponent, SharedFilesComponent, AppConfigPipe ], - providers: [ - { provide: ActivatedRoute, useValue: { - snapshot: { data: { preferencePrefix: 'prefix' } } - } } , - { provide: TranslationService, useClass: TranslationMock }, - AuthenticationService, - UserPreferencesService, - AppConfigService, StorageService, CookieService, - AlfrescoApiService, - LogService, - NotificationService, - ContentManagementService, - NodePermissionService, - ContentService, - NodesApiService, - DocumentListService, - ThumbnailService, - CustomResourcesService - ], schemas: [ NO_ERRORS_SCHEMA ] - }) - .compileComponents() - .then(() => { - fixture = TestBed.createComponent(SharedFilesComponent); - component = fixture.componentInstance; - - contentService = TestBed.get(ContentManagementService); - alfrescoApi = TestBed.get(AlfrescoApiService); - alfrescoApi.reset(); - nodeService = alfrescoApi.getInstance().nodes; - preferenceService = TestBed.get(UserPreferencesService); - router = TestBed.get(Router); }); - })); + fixture = TestBed.createComponent(SharedFilesComponent); + component = fixture.componentInstance; - beforeEach(() => { - spyOn(alfrescoApi.sharedLinksApi, 'findSharedLinks').and.returnValue(Promise.resolve(page)); + contentService = TestBed.get(ContentManagementService); + alfrescoApi = TestBed.get(AlfrescoApiService); + alfrescoApi.reset(); + + spyOn(alfrescoApi.sharedLinksApi, 'findSharedLinks').and.returnValue(Promise.resolve(page)); }); describe('OnInit', () => { beforeEach(() => { - spyOn(component, 'refresh').and.callFake(val => val); + spyOn(component, 'reload').and.callFake(val => val); }); it('should refresh on deleteNode event', () => { fixture.detectChanges(); - contentService.nodeDeleted.next(); + contentService.nodesDeleted.next(); - expect(component.refresh).toHaveBeenCalled(); + expect(component.reload).toHaveBeenCalled(); }); it('should refresh on restoreNode event', () => { fixture.detectChanges(); - contentService.nodeRestored.next(); + contentService.nodesRestored.next(); - expect(component.refresh).toHaveBeenCalled(); + expect(component.reload).toHaveBeenCalled(); }); it('should reload on move node event', () => { fixture.detectChanges(); - contentService.nodeMoved.next(); + contentService.nodesMoved.next(); - expect(component.refresh).toHaveBeenCalled(); - }); - }); - - describe('onNodeDoubleClick()', () => { - beforeEach(() => { - spyOn(component, 'fetchNodes').and.callFake(val => val); - fixture.detectChanges(); - }); - - it('opens viewer if node is file', fakeAsync(() => { - spyOn(router, 'navigate').and.stub(); - const link = { nodeId: 'nodeId' }; - const node = { entry: { isFile: true, id: 'nodeId' } }; - - spyOn(nodeService, 'getNode').and.returnValue(Promise.resolve(node)); - component.onNodeDoubleClick(link); - tick(); - - expect(router.navigate['calls'].argsFor(0)[0]).toEqual(['./preview', node.entry.id]); - })); - - it('does nothing if node is folder', fakeAsync(() => { - spyOn(router, 'navigate').and.stub(); - spyOn(nodeService, 'getNode').and.returnValue(Promise.resolve({ entry: { isFile: false } })); - const link = { nodeId: 'nodeId' }; - - component.onNodeDoubleClick(link); - tick(); - - expect(router.navigate).not.toHaveBeenCalled(); - })); - - it('does nothing if link data is not passed', () => { - spyOn(router, 'navigate').and.stub(); - spyOn(nodeService, 'getNode').and.returnValue(Promise.resolve({ entry: { isFile: true } })); - - component.onNodeDoubleClick(null); - - expect(router.navigate).not.toHaveBeenCalled(); + expect(component.reload).toHaveBeenCalled(); }); }); @@ -201,40 +111,9 @@ describe('SharedFilesComponent', () => { spyOn(component.documentList, 'reload'); fixture.detectChanges(); - component.refresh(); + component.reload(); expect(component.documentList.reload).toHaveBeenCalled(); }); }); - - describe('onSortingChanged', () => { - it('should save sorting input', () => { - spyOn(preferenceService, 'set'); - - const event = { - detail: { - key: 'some-name', - direction: 'some-direction' - } - }; - - component.onSortingChanged(event); - - expect(preferenceService.set).toHaveBeenCalledWith('prefix.sorting.key', 'some-name'); - expect(preferenceService.set).toHaveBeenCalledWith('prefix.sorting.direction', 'some-direction'); - }); - - it('should save default sorting when no input', () => { - spyOn(preferenceService, 'set'); - - const event = { - detail: {} - }; - - component.onSortingChanged(event); - - expect(preferenceService.set).toHaveBeenCalledWith('prefix.sorting.key', 'modifiedAt'); - expect(preferenceService.set).toHaveBeenCalledWith('prefix.sorting.direction', 'desc'); - }); - }); }); diff --git a/src/app/components/shared-files/shared-files.component.ts b/src/app/components/shared-files/shared-files.component.ts index 89ab80765..01058f99b 100644 --- a/src/app/components/shared-files/shared-files.component.ts +++ b/src/app/components/shared-files/shared-files.component.ts @@ -23,89 +23,36 @@ * along with Alfresco. If not, see . */ -import { Component, OnInit, ViewChild, OnDestroy } from '@angular/core'; -import { Router, ActivatedRoute } from '@angular/router'; -import { Subscription } from 'rxjs/Rx'; -import { MinimalNodeEntity } from 'alfresco-js-api'; -import { AlfrescoApiService, UserPreferencesService } from '@alfresco/adf-core'; -import { DocumentListComponent } from '@alfresco/adf-content-services'; +import { Component, OnInit } from '@angular/core'; +import { UploadService } from '@alfresco/adf-core'; import { ContentManagementService } from '../../common/services/content-management.service'; import { NodePermissionService } from '../../common/services/node-permission.service'; import { PageComponent } from '../page.component'; +import { Store } from '@ngrx/store'; +import { AppStore } from '../../store/states/app.state'; @Component({ templateUrl: './shared-files.component.html' }) -export class SharedFilesComponent extends PageComponent implements OnInit, OnDestroy { +export class SharedFilesComponent extends PageComponent implements OnInit { - @ViewChild(DocumentListComponent) - documentList: DocumentListComponent; - - private subscriptions: Subscription[] = []; - - sorting = [ 'modifiedAt', 'desc' ]; - - constructor(private router: Router, - private route: ActivatedRoute, + constructor(store: Store, + private uploadService: UploadService, private content: ContentManagementService, - private apiService: AlfrescoApiService, - public permission: NodePermissionService, - preferences: UserPreferencesService) { - super(preferences); - - const sortingKey = preferences.get(`${this.prefix}.sorting.key`) || 'modifiedAt'; - const sortingDirection = preferences.get(`${this.prefix}.sorting.direction`) || 'desc'; - - this.sorting = [sortingKey, sortingDirection]; + public permission: NodePermissionService) { + super(store); } ngOnInit() { + super.ngOnInit(); + this.subscriptions = this.subscriptions.concat([ - this.content.nodeDeleted.subscribe(() => this.refresh()), - this.content.nodeMoved.subscribe(() => this.refresh()), - this.content.nodeRestored.subscribe(() => this.refresh()) + this.content.nodesDeleted.subscribe(() => this.reload()), + this.content.nodesMoved.subscribe(() => this.reload()), + this.content.nodesRestored.subscribe(() => this.reload()), + this.content.linksUnshared.subscribe(() => this.reload()), + this.uploadService.fileUploadError.subscribe((error) => this.onFileUploadedError(error)) ]); } - - ngOnDestroy() { - this.subscriptions.forEach(s => s.unsubscribe()); - } - - onNodeDoubleClick(link: { nodeId?: string }) { - if (link && link.nodeId) { - this.apiService.nodesApi.getNode(link.nodeId).then( - (node: MinimalNodeEntity) => { - if (node && node.entry && node.entry.isFile) { - this.router.navigate(['./preview', node.entry.id], { relativeTo: this.route }); - } - } - ); - } - } - - fetchNodes(parentNodeId?: string) { - // todo: remove once all views migrate to native data source - } - - /** @override */ - isFileSelected(selection: Array): boolean { - return selection && selection.length === 1; - } - - refresh(): void { - if (this.documentList) { - this.documentList.resetSelection(); - this.documentList.reload(); - } - } - - onSortingChanged(event: CustomEvent) { - this.preferences.set(`${this.prefix}.sorting.key`, event.detail.key || 'modifiedAt'); - this.preferences.set(`${this.prefix}.sorting.direction`, event.detail.direction || 'desc'); - } - - private get prefix() { - return this.route.snapshot.data.preferencePrefix; - } } diff --git a/src/app/components/sidenav/sidenav.component.html b/src/app/components/sidenav/sidenav.component.html index e5c288aca..65dbccf6e 100644 --- a/src/app/components/sidenav/sidenav.component.html +++ b/src/app/components/sidenav/sidenav.component.html @@ -1,64 +1,51 @@
-
- - {{ 'APP.NEW_MENU.LABEL' | translate }} - arrow_drop_down - + + - - - - - - - - - - - + + +
+
@@ -70,6 +57,7 @@
-
\ No newline at end of file +
diff --git a/src/app/components/sidenav/sidenav.component.scss b/src/app/components/sidenav/sidenav.component.scss index 724a6ee3f..48df44e8f 100644 --- a/src/app/components/sidenav/sidenav.component.scss +++ b/src/app/components/sidenav/sidenav.component.scss @@ -8,8 +8,10 @@ border-bottom: 0; } - .section--new--mini { + &_action-menu { display: flex; + padding: 16px 24px; + height: 40px; justify-content: center; align-items: center; } @@ -17,19 +19,6 @@ &__section { padding: 8px 14px; position: relative; - - &--new { - padding: 16px 24px; - height: 40px; - } - - &--new__button { - width: 100%; - } - - &--new__button.mat-raised-button { - box-shadow: none !important; - } } &-menu { diff --git a/src/app/components/sidenav/sidenav.component.spec.ts b/src/app/components/sidenav/sidenav.component.spec.ts index 07e53b2fc..b237127e5 100644 --- a/src/app/components/sidenav/sidenav.component.spec.ts +++ b/src/app/components/sidenav/sidenav.component.spec.ts @@ -24,27 +24,19 @@ */ import { NO_ERRORS_SCHEMA } from '@angular/core'; -import { TestBed, async } from '@angular/core/testing'; -import { RouterTestingModule } from '@angular/router/testing'; -import { TranslateModule } from '@ngx-translate/core'; -import { MatMenuModule, MatSnackBarModule } from '@angular/material'; -import { HttpClientModule } from '@angular/common/http'; -import { - AppConfigService, AuthenticationService, - UserPreferencesService, StorageService, AlfrescoApiService, - CookieService, LogService, NotificationService -} from '@alfresco/adf-core'; +import { TestBed, async, ComponentFixture } from '@angular/core/testing'; +import { AppConfigService } from '@alfresco/adf-core'; import { BrowsingFilesService } from '../../common/services/browsing-files.service'; -import { NodePermissionService } from '../../common/services/node-permission.service'; - import { SidenavComponent } from './sidenav.component'; +import { EffectsModule } from '@ngrx/effects'; +import { NodeEffects } from '../../store/effects/node.effects'; +import { AppTestingModule } from '../../testing/app-testing.module'; describe('SidenavComponent', () => { - let fixture; + let fixture: ComponentFixture; let component: SidenavComponent; let browsingService: BrowsingFilesService; let appConfig: AppConfigService; - let notificationService: NotificationService; let appConfigSpy; const navItem = { @@ -57,34 +49,18 @@ describe('SidenavComponent', () => { beforeEach(async(() => { TestBed.configureTestingModule({ imports: [ - HttpClientModule, - MatMenuModule, - MatSnackBarModule, - TranslateModule.forRoot(), - RouterTestingModule + AppTestingModule, + EffectsModule.forRoot([NodeEffects]) ], declarations: [ SidenavComponent ], - providers: [ - LogService, - CookieService, - AlfrescoApiService, - StorageService, - UserPreferencesService, - AuthenticationService, - NodePermissionService, - AppConfigService, - BrowsingFilesService, - NotificationService - ], schemas: [ NO_ERRORS_SCHEMA ] }) .compileComponents() .then(() => { browsingService = TestBed.get(BrowsingFilesService); appConfig = TestBed.get(AppConfigService); - notificationService = TestBed.get(NotificationService); fixture = TestBed.createComponent(SidenavComponent); component = fixture.componentInstance; @@ -117,16 +93,4 @@ describe('SidenavComponent', () => { expect(component.navigation).toEqual([[navItem, navItem], [navItem, navItem]]); }); }); - - describe('openSnackMessage', () => { - it('should call notification service', () => { - const message = 'notification message'; - - spyOn(notificationService, 'openSnackMessage'); - - component.openSnackMessage(message); - - expect(notificationService.openSnackMessage).toHaveBeenCalledWith(message, 4000); - }); - }); }); diff --git a/src/app/components/sidenav/sidenav.component.theme.scss b/src/app/components/sidenav/sidenav.component.theme.scss index 8941378c3..9ec93afae 100644 --- a/src/app/components/sidenav/sidenav.component.theme.scss +++ b/src/app/components/sidenav/sidenav.component.theme.scss @@ -10,7 +10,10 @@ @include angular-material-theme($theme); background-color: mat-color($background, background); - border-right: $border; + + .adf-sidebar-action-menu-button { + background-color: mat-color($accent); + } &__section { border-bottom: $border; diff --git a/src/app/components/sidenav/sidenav.component.ts b/src/app/components/sidenav/sidenav.component.ts index 84fe64749..b667661a7 100644 --- a/src/app/components/sidenav/sidenav.component.ts +++ b/src/app/components/sidenav/sidenav.component.ts @@ -26,7 +26,7 @@ import { Subscription } from 'rxjs/Rx'; import { Component, Input, OnInit, OnDestroy } from '@angular/core'; import { MinimalNodeEntryEntity } from 'alfresco-js-api'; -import { AppConfigService, NotificationService } from '@alfresco/adf-core'; +import { AppConfigService } from '@alfresco/adf-core'; import { BrowsingFilesService } from '../../common/services/browsing-files.service'; @@ -46,7 +46,6 @@ export class SidenavComponent implements OnInit, OnDestroy { private subscriptions: Subscription[] = []; constructor( - private notificationService: NotificationService, private browsingFilesService: BrowsingFilesService, private appConfig: AppConfigService, public permission: NodePermissionService @@ -61,13 +60,6 @@ export class SidenavComponent implements OnInit, OnDestroy { ]); } - openSnackMessage(event: any) { - this.notificationService.openSnackMessage( - event, - 4000 - ); - } - ngOnDestroy() { this.subscriptions.forEach(s => s.unsubscribe()); } diff --git a/src/app/components/trashcan/trashcan.component.html b/src/app/components/trashcan/trashcan.component.html index 109c57a0f..33406bf59 100644 --- a/src/app/components/trashcan/trashcan.component.html +++ b/src/app/components/trashcan/trashcan.component.html @@ -1,15 +1,13 @@
- + - + @@ -17,9 +15,7 @@ @@ -27,25 +23,21 @@
-
- + + [sorting]="[ 'archivedAt', 'desc' ]"> - -

{{ 'APP.BROWSE.TRASHCAN.EMPTY_STATE.FIRST_TEXT' | translate }}

-

{{ 'APP.BROWSE.TRASHCAN.EMPTY_STATE.SECOND_TEXT' | translate }}

-
+ [title]="'APP.BROWSE.TRASHCAN.EMPTY_STATE.TITLE'"> +

{{ 'APP.BROWSE.TRASHCAN.EMPTY_STATE.FIRST_TEXT' | translate }}

+

{{ 'APP.BROWSE.TRASHCAN.EMPTY_STATE.SECOND_TEXT' | translate }}

+
@@ -68,10 +60,10 @@ - + @@ -90,6 +82,7 @@ @@ -98,13 +91,8 @@
- - - - + +
diff --git a/src/app/components/trashcan/trashcan.component.spec.ts b/src/app/components/trashcan/trashcan.component.spec.ts index df6b8a078..111b3a632 100644 --- a/src/app/components/trashcan/trashcan.component.spec.ts +++ b/src/app/components/trashcan/trashcan.component.spec.ts @@ -23,35 +23,22 @@ * along with Alfresco. If not, see . */ import { NO_ERRORS_SCHEMA } from '@angular/core'; -import { ActivatedRoute } from '@angular/router'; -import { RouterTestingModule } from '@angular/router/testing'; -import { HttpClientModule } from '@angular/common/http'; -import { TestBed, async } from '@angular/core/testing'; +import { TestBed, ComponentFixture } from '@angular/core/testing'; import { - NotificationService, TranslationService, TranslationMock, - NodesApiService, AlfrescoApiService, ContentService, - UserPreferencesService, LogService, AppConfigService, - StorageService, CookieService, ThumbnailService, - AuthenticationService, TimeAgoPipe, NodeNameTooltipPipe, - NodeFavoriteDirective, DataTableComponent + AlfrescoApiService, + TimeAgoPipe, NodeNameTooltipPipe, + NodeFavoriteDirective, DataTableComponent, AppConfigPipe } from '@alfresco/adf-core'; -import { DocumentListComponent, CustomResourcesService } from '@alfresco/adf-content-services'; -import { TranslateModule } from '@ngx-translate/core'; -import { NoopAnimationsModule } from '@angular/platform-browser/animations'; -import { MatMenuModule, MatSnackBarModule, MatIconModule } from '@angular/material'; -import { DocumentListService } from '@alfresco/adf-content-services'; +import { DocumentListComponent } from '@alfresco/adf-content-services'; import { ContentManagementService } from '../../common/services/content-management.service'; -import { NodeInfoDirective } from '../../common/directives/node-info.directive'; -import { AppConfigPipe } from '../../common/pipes/app-config.pipe'; - import { TrashcanComponent } from './trashcan.component'; +import { AppTestingModule } from '../../testing/app-testing.module'; describe('TrashcanComponent', () => { - let fixture; - let component; + let fixture: ComponentFixture; + let component: TrashcanComponent; let alfrescoApi: AlfrescoApiService; let contentService: ContentManagementService; - let preferenceService: UserPreferencesService; let page; beforeEach(() => { @@ -63,62 +50,33 @@ describe('TrashcanComponent', () => { }; }); - beforeEach(async(() => { + beforeEach(() => { TestBed.configureTestingModule({ - imports: [ - MatMenuModule, - NoopAnimationsModule, - HttpClientModule, - TranslateModule.forRoot(), - RouterTestingModule, - MatSnackBarModule, MatIconModule - ], + imports: [ AppTestingModule ], declarations: [ DataTableComponent, TimeAgoPipe, NodeNameTooltipPipe, NodeFavoriteDirective, - NodeInfoDirective, DocumentListComponent, TrashcanComponent, AppConfigPipe ], - providers: [ - { provide: ActivatedRoute, useValue: { - snapshot: { data: { preferencePrefix: 'prefix' } } - } } , - { provide: TranslationService, useClass: TranslationMock }, - AuthenticationService, - UserPreferencesService, - AppConfigService, StorageService, CookieService, - AlfrescoApiService, - LogService, - NotificationService, - ContentManagementService, - ContentService, - NodesApiService, - DocumentListService, - ThumbnailService, - CustomResourcesService - ], schemas: [ NO_ERRORS_SCHEMA ] - }) - .compileComponents() - .then(() => { - fixture = TestBed.createComponent(TrashcanComponent); - component = fixture.componentInstance; - - alfrescoApi = TestBed.get(AlfrescoApiService); - alfrescoApi.reset(); - contentService = TestBed.get(ContentManagementService); - preferenceService = TestBed.get(UserPreferencesService); - - component.documentList = { - reload: jasmine.createSpy('reload'), - resetSelection: jasmine.createSpy('resetSelection') - }; }); - })); + + fixture = TestBed.createComponent(TrashcanComponent); + component = fixture.componentInstance; + + alfrescoApi = TestBed.get(AlfrescoApiService); + alfrescoApi.reset(); + contentService = TestBed.get(ContentManagementService); + + component.documentList = { + reload: jasmine.createSpy('reload'), + resetSelection: jasmine.createSpy('resetSelection') + }; + }); beforeEach(() => { spyOn(alfrescoApi.nodesApi, 'getDeletedNodes').and.returnValue(Promise.resolve(page)); @@ -126,55 +84,24 @@ describe('TrashcanComponent', () => { describe('onRestoreNode()', () => { it('should call refresh()', () => { - spyOn(component, 'refresh'); + spyOn(component, 'reload'); fixture.detectChanges(); - contentService.nodeRestored.next(); + contentService.nodesRestored.next(); - expect(component.refresh).toHaveBeenCalled(); + expect(component.reload).toHaveBeenCalled(); }); }); describe('refresh()', () => { it('calls child component to reload', () => { - component.refresh(); + component.reload(); expect(component.documentList.reload).toHaveBeenCalled(); }); it('calls child component to reset selection', () => { - component.refresh(); + component.reload(); expect(component.documentList.resetSelection).toHaveBeenCalled(); }); }); - - describe('onSortingChanged', () => { - it('should save sorting input', () => { - spyOn(preferenceService, 'set'); - - const event = { - detail: { - key: 'some-name', - direction: 'some-direction' - } - }; - - component.onSortingChanged(event); - - expect(preferenceService.set).toHaveBeenCalledWith('prefix.sorting.key', 'some-name'); - expect(preferenceService.set).toHaveBeenCalledWith('prefix.sorting.direction', 'some-direction'); - }); - - it('should save default sorting when no input', () => { - spyOn(preferenceService, 'set'); - - const event = { - detail: {} - }; - - component.onSortingChanged(event); - - expect(preferenceService.set).toHaveBeenCalledWith('prefix.sorting.key', 'archivedAt'); - expect(preferenceService.set).toHaveBeenCalledWith('prefix.sorting.direction', 'desc'); - }); - }); }); diff --git a/src/app/components/trashcan/trashcan.component.ts b/src/app/components/trashcan/trashcan.component.ts index 8c5429248..f754cf3f4 100644 --- a/src/app/components/trashcan/trashcan.component.ts +++ b/src/app/components/trashcan/trashcan.component.ts @@ -23,57 +23,33 @@ * along with Alfresco. If not, see . */ -import { Component, ViewChild, OnInit, OnDestroy } from '@angular/core'; -import { ActivatedRoute } from '@angular/router'; -import { Subscription } from 'rxjs/Rx'; -import { Pagination } from 'alfresco-js-api'; -import { UserPreferencesService } from '@alfresco/adf-core'; -import { DocumentListComponent } from '@alfresco/adf-content-services'; +import { Component, OnInit } from '@angular/core'; import { ContentManagementService } from '../../common/services/content-management.service'; +import { PageComponent } from '../page.component'; +import { Store } from '@ngrx/store'; +import { selectUser } from '../../store/selectors/app.selectors'; +import { AppStore } from '../../store/states/app.state'; +import { ProfileState } from '../../store/states/profile.state'; @Component({ templateUrl: './trashcan.component.html' }) -export class TrashcanComponent implements OnInit, OnDestroy { - private subscriptions: Subscription[] = []; - - @ViewChild(DocumentListComponent) documentList; - - sorting = [ 'archivedAt', 'desc' ]; +export class TrashcanComponent extends PageComponent implements OnInit { + user: ProfileState; constructor(private contentManagementService: ContentManagementService, - private preferences: UserPreferencesService, - private route: ActivatedRoute) { - - const sortingKey = preferences.get(`${this.prefix}.sorting.key`) || 'archivedAt'; - const sortingDirection = preferences.get(`${this.prefix}.sorting.direction`) || 'desc'; - - this.sorting = [sortingKey, sortingDirection]; + store: Store) { + super(store); } ngOnInit() { - this.subscriptions.push(this.contentManagementService.nodeRestored.subscribe(() => this.refresh())); - } + super.ngOnInit(); - refresh(): void { - this.documentList.reload(); - this.documentList.resetSelection(); + this.subscriptions.push( + this.contentManagementService.nodesRestored.subscribe(() => this.reload()), + this.contentManagementService.nodesPurged.subscribe(() => this.reload()), + this.contentManagementService.nodesRestored.subscribe(() => this.reload()), + this.store.select(selectUser).subscribe((user) => this.user = user) + ); } - - ngOnDestroy() { - this.subscriptions.forEach(s => s.unsubscribe()); - } - - onChangePageSize(event: Pagination): void { - this.preferences.paginationSize = event.maxItems; - } - - onSortingChanged(event: CustomEvent) { - this.preferences.set(`${this.prefix}.sorting.key`, event.detail.key || 'archivedAt'); - this.preferences.set(`${this.prefix}.sorting.direction`, event.detail.direction || 'desc'); - } - - private get prefix() { - return this.route.snapshot.data.preferencePrefix; - } -} + } diff --git a/src/app/components/versions-dialog/version-manager-dialog-adapter.component.html b/src/app/components/versions-dialog/version-manager-dialog-adapter.component.html deleted file mode 100644 index c1874f01d..000000000 --- a/src/app/components/versions-dialog/version-manager-dialog-adapter.component.html +++ /dev/null @@ -1,9 +0,0 @@ -
-
{{'VERSION.DIALOG.TITLE' | translate}}
-
- -
-
- -
-
diff --git a/src/app/components/versions-dialog/version-manager-dialog-adapter.component.scss b/src/app/components/versions-dialog/version-manager-dialog-adapter.component.scss deleted file mode 100644 index 9be059419..000000000 --- a/src/app/components/versions-dialog/version-manager-dialog-adapter.component.scss +++ /dev/null @@ -1,52 +0,0 @@ -.adf-version-manager-dialog { - .mat-dialog-container { - padding-left: 0; - padding-right: 0; - padding-bottom: 8px; - } - - .mat-dialog-title { - margin-left: 24px; - margin-right: 24px; - font-size: 20px; - font-weight: 600; - font-style: normal; - font-stretch: normal; - line-height: 1.6; - letter-spacing: -0.5px; - color: rgba(0, 0, 0, 0.87); - } - - .mat-dialog-content { - margin: 0; - } - - .mat-dialog-actions { - padding: 8px 8px 24px 8px; - display: -webkit-box; - display: -ms-flexbox; - display: flex; - -webkit-box-pack: end; - -ms-flex-pack: end; - justify-content: flex-end; - color: rgba(0, 0, 0, 0.54); - - button { - text-transform: uppercase; - font-weight: normal; - - &:enabled { - color: #ff9800; - } - } - } - - .adf-version-list { - height: 200px; - overflow: auto; - } -} - -.version-manager-dialog-adapter { - width: 100%; -} diff --git a/src/app/components/versions-dialog/version-manager-dialog-adapter.component.ts b/src/app/components/versions-dialog/version-manager-dialog-adapter.component.ts deleted file mode 100644 index 3a6e0e667..000000000 --- a/src/app/components/versions-dialog/version-manager-dialog-adapter.component.ts +++ /dev/null @@ -1,45 +0,0 @@ -/*! - * @license - * Copyright 2016 Alfresco Software, Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { Component, Inject, ViewEncapsulation } from '@angular/core'; -import { MAT_DIALOG_DATA, MatDialogRef, MatSnackBarConfig } from '@angular/material'; -import { MinimalNodeEntryEntity } from 'alfresco-js-api'; -import { MatSnackBar } from '@angular/material'; - -@Component({ - templateUrl: './version-manager-dialog-adapter.component.html', - styleUrls: ['./version-manager-dialog-adapter.component.scss'], - encapsulation: ViewEncapsulation.None -}) -export class VersionManagerDialogAdapterComponent { - - public contentEntry: MinimalNodeEntryEntity; - - constructor(@Inject(MAT_DIALOG_DATA) data: any, - private snackBar: MatSnackBar, - private containingDialog?: MatDialogRef) { - this.contentEntry = data.contentEntry; - } - - uploadError(errorMessage: string) { - this.snackBar.open(errorMessage, '', { duration: 4000 }); - } - - close() { - this.containingDialog.close(); - } -} diff --git a/src/app/dialogs/node-versions/node-versions.dialog.html b/src/app/dialogs/node-versions/node-versions.dialog.html new file mode 100644 index 000000000..829ea13b1 --- /dev/null +++ b/src/app/dialogs/node-versions/node-versions.dialog.html @@ -0,0 +1,12 @@ +
{{'VERSION.DIALOG.TITLE' | translate}}
+
+ + +
+
+ +
diff --git a/src/app/dialogs/node-versions/node-versions.dialog.theme.scss b/src/app/dialogs/node-versions/node-versions.dialog.theme.scss new file mode 100644 index 000000000..dbb69e9fc --- /dev/null +++ b/src/app/dialogs/node-versions/node-versions.dialog.theme.scss @@ -0,0 +1,83 @@ +@mixin aca-node-versions-dialog-theme($theme) { + $foreground: map-get($theme, foreground); + $accent: map-get($theme, accent); + + .adf-version-manager-dialog-panel { + height: 400px; + } + + .aca-node-versions-dialog { + .mat-dialog-title { + flex: 0 0 auto; + } + + .mat-dialog-content { + flex: 1 1 auto; + position: relative; + overflow-y: auto; + } + + .mat-dialog-actions { + flex: 0 0 auto; + } + + + .mat-dialog-title { + font-size: 20px; + font-weight: 600; + font-style: normal; + font-stretch: normal; + line-height: 1.6; + margin: 0; + letter-spacing: -0.5px; + color: mat-color($foreground, text, 0.87); + } + + + .mat-dialog-actions { + padding: 8px 8px 24px 8px; + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-pack: end; + -ms-flex-pack: end; + justify-content: flex-end; + color: mat-color($foreground, text, 0.54); + + button { + text-transform: uppercase; + font-weight: normal; + + &:enabled { + color: mat-color($accent); + } + } + } + + .adf-new-version-container { + height: 350px !important; + } + + .mat-dialog-content { + max-height: 36vh; + overflow: hidden; + } + + .mat-list-item-content { + padding: 0; + margin: 0 16px; + } + + .adf-version-list-container { + .adf-version-list { + height: 180px; + overflow: hidden; + padding: 0; + } + + .mat-list.adf-version-list { + overflow: auto; + } + } + } +} diff --git a/src/app/dialogs/node-versions/node-versions.dialog.ts b/src/app/dialogs/node-versions/node-versions.dialog.ts new file mode 100644 index 000000000..e99e07d38 --- /dev/null +++ b/src/app/dialogs/node-versions/node-versions.dialog.ts @@ -0,0 +1,51 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { Component, Inject, ViewEncapsulation } from '@angular/core'; +import { MAT_DIALOG_DATA } from '@angular/material'; +import { MinimalNodeEntryEntity } from 'alfresco-js-api'; +import { Store } from '@ngrx/store'; +import { AppStore } from '../../store/states/app.state'; +import { SnackbarErrorAction } from '../../store/actions'; + +@Component({ + templateUrl: './node-versions.dialog.html', + encapsulation: ViewEncapsulation.None, + host: { class: 'aca-node-versions-dialog' } +}) +export class NodeVersionsDialogComponent { + node: MinimalNodeEntryEntity; + + constructor( + @Inject(MAT_DIALOG_DATA) data: any, + private store: Store + ) { + this.node = data.node; + } + + uploadError(errorMessage: string) { + this.store.dispatch(new SnackbarErrorAction(errorMessage)); + } +} diff --git a/src/app/directives/create-folder.directive.ts b/src/app/directives/create-folder.directive.ts new file mode 100644 index 000000000..4dcb19015 --- /dev/null +++ b/src/app/directives/create-folder.directive.ts @@ -0,0 +1,89 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { Directive, HostListener, Input } from '@angular/core'; +import { MatDialog, MatDialogConfig } from '@angular/material'; +import { FolderDialogComponent } from '@alfresco/adf-content-services'; +import { Store } from '@ngrx/store'; +import { AppStore } from '../store/states/app.state'; +import { SnackbarErrorAction } from '../store/actions'; +import { ContentManagementService } from '../common/services/content-management.service'; + +@Directive({ + selector: '[acaCreateFolder]' +}) +export class CreateFolderDirective { + /** Parent folder where the new folder will be located after creation. */ + // tslint:disable-next-line:no-input-rename + @Input('acaCreateFolder') parentNodeId: string; + + /** Title of folder creation dialog. */ + @Input() dialogTitle: string = null; + + /** Type of node to create. */ + @Input() nodeType = 'cm:folder'; + + @HostListener('click', ['$event']) + onClick(event: Event) { + if (this.parentNodeId) { + event.preventDefault(); + this.openDialog(); + } + } + + constructor( + private store: Store, + private dialogRef: MatDialog, + private content: ContentManagementService + ) {} + + private get dialogConfig(): MatDialogConfig { + return { + data: { + parentNodeId: this.parentNodeId, + createTitle: this.dialogTitle, + nodeType: this.nodeType + }, + width: '400px' + }; + } + + private openDialog(): void { + const dialogInstance = this.dialogRef.open( + FolderDialogComponent, + this.dialogConfig + ); + + dialogInstance.componentInstance.error.subscribe(message => { + this.store.dispatch(new SnackbarErrorAction(message)); + }); + + dialogInstance.afterClosed().subscribe(node => { + if (node) { + this.content.folderCreated.next(node); + } + }); + } +} diff --git a/src/app/directives/document-list.directive.ts b/src/app/directives/document-list.directive.ts new file mode 100644 index 000000000..28ab6e701 --- /dev/null +++ b/src/app/directives/document-list.directive.ts @@ -0,0 +1,159 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { Directive, OnDestroy, OnInit, HostListener } from '@angular/core'; +import { DocumentListComponent } from '@alfresco/adf-content-services'; +import { ActivatedRoute } from '@angular/router'; +import { UserPreferencesService } from '@alfresco/adf-core'; +import { Subscription } from 'rxjs/Rx'; +import { Store } from '@ngrx/store'; +import { AppStore } from '../store/states/app.state'; +import { SetSelectedNodesAction } from '../store/actions'; +import { MinimalNodeEntryEntity } from 'alfresco-js-api'; + +@Directive({ + selector: '[acaDocumentList]' +}) +export class DocumentListDirective implements OnInit, OnDestroy { + private subscriptions: Subscription[] = []; + + get sortingPreferenceKey(): string { + return this.route.snapshot.data.sortingPreferenceKey; + } + + constructor( + private store: Store, + private documentList: DocumentListComponent, + private preferences: UserPreferencesService, + private route: ActivatedRoute + ) {} + + ngOnInit() { + this.documentList.includeFields = ['isFavorite', 'aspectNames']; + this.documentList.allowDropFiles = false; + + if (this.sortingPreferenceKey) { + const current = this.documentList.sorting; + + const key = this.preferences.get( + `${this.sortingPreferenceKey}.sorting.key`, + current[0] + ); + const direction = this.preferences.get( + `${this.sortingPreferenceKey}.sorting.direction`, + current[1] + ); + + this.documentList.sorting = [key, direction]; + // TODO: bug in ADF, the `sorting` binding is not updated when changed from code + this.documentList.data.setSorting({ key, direction }); + } + + this.subscriptions.push( + this.documentList.ready.subscribe(() => this.onReady()) + ); + } + + ngOnDestroy() { + this.subscriptions.forEach(subscription => subscription.unsubscribe()); + this.subscriptions = []; + } + + @HostListener('sorting-changed', ['$event']) + onSortingChanged(event: CustomEvent) { + if (this.sortingPreferenceKey) { + this.preferences.set( + `${this.sortingPreferenceKey}.sorting.key`, + event.detail.key + ); + this.preferences.set( + `${this.sortingPreferenceKey}.sorting.direction`, + event.detail.direction + ); + } + } + + @HostListener('node-select', ['$event']) + onNodeSelect(event: CustomEvent) { + if (!!event.detail && !!event.detail.node) { + const node: MinimalNodeEntryEntity = event.detail.node.entry; + if (node && this.isLockedNode(node)) { + this.unSelectLockedNodes(this.documentList); + } + + this.store.dispatch( + new SetSelectedNodesAction(this.documentList.selection) + ); + } + } + + @HostListener('node-unselect') + onNodeUnselect() { + this.store.dispatch( + new SetSelectedNodesAction(this.documentList.selection) + ); + } + + onReady() { + this.store.dispatch( + new SetSelectedNodesAction(this.documentList.selection) + ); + } + + private isLockedNode(node): boolean { + return ( + node.isLocked || + (node.properties && + node.properties['cm:lockType'] === 'READ_ONLY_LOCK') + ); + } + + private isLockedRow(row): boolean { + return ( + row.getValue('isLocked') || + (row.getValue('properties') && + row.getValue('properties')['cm:lockType'] === 'READ_ONLY_LOCK') + ); + } + + private unSelectLockedNodes(documentList: DocumentListComponent) { + documentList.selection = documentList.selection.filter( + item => !this.isLockedNode(item.entry) + ); + + const dataTable = documentList.dataTable; + if (dataTable && dataTable.data) { + const rows = dataTable.data.getRows(); + + if (rows && rows.length > 0) { + rows.forEach(r => { + if (this.isLockedRow(r)) { + r.isSelected = false; + } + }); + } + } + } +} diff --git a/src/app/directives/download-nodes.directive.ts b/src/app/directives/download-nodes.directive.ts new file mode 100644 index 000000000..ea2d61dc7 --- /dev/null +++ b/src/app/directives/download-nodes.directive.ts @@ -0,0 +1,58 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { Directive, HostListener, Input } from '@angular/core'; +import { Store } from '@ngrx/store'; +import { AppStore } from '../store/states/app.state'; +import { MinimalNodeEntity } from 'alfresco-js-api'; +import { DownloadNodesAction } from '../store/actions'; + +@Directive({ + selector: '[acaDownloadNodes]' +}) +export class DownloadNodesDirective { + // tslint:disable-next-line:no-input-rename + @Input('acaDownloadNodes') + nodes: Array | MinimalNodeEntity; + + constructor(private store: Store) {} + + @HostListener('click') + onClick() { + const targets = Array.isArray(this.nodes) ? this.nodes : [this.nodes]; + const toDownload = targets.map(node => { + const { id, nodeId, name, isFile, isFolder } = node.entry; + + return { + id: nodeId || id, + name, + isFile, + isFolder + }; + }); + + this.store.dispatch(new DownloadNodesAction(toDownload)); + } +} diff --git a/src/app/directives/edit-folder.directive.ts b/src/app/directives/edit-folder.directive.ts new file mode 100644 index 000000000..e532ec87d --- /dev/null +++ b/src/app/directives/edit-folder.directive.ts @@ -0,0 +1,82 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { Directive, Input, HostListener } from '@angular/core'; +import { MinimalNodeEntryEntity, MinimalNodeEntity } from 'alfresco-js-api'; +import { MatDialog, MatDialogConfig } from '@angular/material'; +import { FolderDialogComponent } from '@alfresco/adf-content-services'; +import { Store } from '@ngrx/store'; +import { AppStore } from '../store/states/app.state'; +import { SnackbarErrorAction } from '../store/actions'; +import { ContentManagementService } from '../common/services/content-management.service'; + +@Directive({ + selector: '[acaEditFolder]' +}) +export class EditFolderDirective { + + /** Folder node to edit. */ + // tslint:disable-next-line:no-input-rename + @Input('acaEditFolder') + folder: MinimalNodeEntity; + + @HostListener('click', [ '$event' ]) + onClick(event) { + event.preventDefault(); + + if (this.folder) { + this.openDialog(); + } + } + + constructor( + private store: Store, + private dialogRef: MatDialog, + private content: ContentManagementService + ) {} + + private get dialogConfig(): MatDialogConfig { + return { + data: { + folder: this.folder.entry + }, + width: '400px' + }; + } + + private openDialog(): void { + const dialog = this.dialogRef.open(FolderDialogComponent, this.dialogConfig); + + dialog.componentInstance.error.subscribe(message => { + this.store.dispatch(new SnackbarErrorAction(message)); + }); + + dialog.afterClosed().subscribe((node: MinimalNodeEntryEntity) => { + if (node) { + this.content.folderEdited.next(node); + } + }); + } +} diff --git a/src/app/directives/experimental.directive.ts b/src/app/directives/experimental.directive.ts new file mode 100644 index 000000000..3546f8c50 --- /dev/null +++ b/src/app/directives/experimental.directive.ts @@ -0,0 +1,61 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { Directive, TemplateRef, ViewContainerRef, Input } from '@angular/core'; +import { AppConfigService, StorageService } from '@alfresco/adf-core'; +import { environment } from '../../environments/environment'; + +@Directive({ + // tslint:disable-next-line:directive-selector + selector: '[ifExperimental]' +}) +export class ExperimentalDirective { + constructor( + private templateRef: TemplateRef, + private viewContainerRef: ViewContainerRef, + private storage: StorageService, + private config: AppConfigService + ) {} + + @Input() set ifExperimental(featureKey: string) { + const key = `experimental.${featureKey}`; + + const override = this.storage.getItem(key); + if (override === 'true') { + this.viewContainerRef.createEmbeddedView(this.templateRef); + return; + } + + if (!environment.production) { + const value = this.config.get(key); + if (value === true || value === 'true') { + this.viewContainerRef.createEmbeddedView(this.templateRef); + return; + } + } + + this.viewContainerRef.clear(); + } +} diff --git a/src/app/directives/pagination.directive.ts b/src/app/directives/pagination.directive.ts new file mode 100644 index 000000000..94155d892 --- /dev/null +++ b/src/app/directives/pagination.directive.ts @@ -0,0 +1,65 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { Directive, OnInit, OnDestroy } from '@angular/core'; +import { + PaginationComponent, + UserPreferencesService, + PaginationModel, + AppConfigService +} from '@alfresco/adf-core'; +import { Subscription } from 'rxjs/Rx'; + +@Directive({ + selector: '[acaPagination]' +}) +export class PaginationDirective implements OnInit, OnDestroy { + private subscriptions: Subscription[] = []; + + constructor( + private pagination: PaginationComponent, + private preferences: UserPreferencesService, + private config: AppConfigService + ) {} + + ngOnInit() { + this.pagination.supportedPageSizes = this.config.get( + 'pagination.supportedPageSizes' + ); + + this.subscriptions.push( + this.pagination.changePageSize.subscribe( + (event: PaginationModel) => { + this.preferences.paginationSize = event.maxItems; + } + ) + ); + } + + ngOnDestroy() { + this.subscriptions.forEach(subscription => subscription.unsubscribe()); + this.subscriptions = []; + } +} diff --git a/src/app/material.module.ts b/src/app/material.module.ts new file mode 100644 index 000000000..c0c3e2032 --- /dev/null +++ b/src/app/material.module.ts @@ -0,0 +1,54 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { NgModule } from '@angular/core'; +import { + MatMenuModule, + MatIconModule, + MatButtonModule, + MatDialogModule, + MatInputModule, + MatSnackBarModule +} from '@angular/material'; + +@NgModule({ + imports: [ + MatMenuModule, + MatIconModule, + MatButtonModule, + MatDialogModule, + MatInputModule, + MatSnackBarModule + ], + exports: [ + MatMenuModule, + MatIconModule, + MatButtonModule, + MatDialogModule, + MatInputModule, + MatSnackBarModule + ] +}) +export class MaterialModule {} diff --git a/src/app/services/content-api.service.ts b/src/app/services/content-api.service.ts new file mode 100644 index 000000000..a5ecff227 --- /dev/null +++ b/src/app/services/content-api.service.ts @@ -0,0 +1,229 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { Injectable } from '@angular/core'; +import { AlfrescoApiService, UserPreferencesService } from '@alfresco/adf-core'; +import { Observable } from 'rxjs/Observable'; +import { + MinimalNodeEntity, + NodePaging, + Node, + DeletedNodesPaging, + PersonEntry, + NodeEntry, + DiscoveryEntry, + FavoritePaging, + SharedLinkPaging, + SearchRequest, + ResultSetPaging +} from 'alfresco-js-api'; + +@Injectable() +export class ContentApiService { + constructor( + private api: AlfrescoApiService, + private preferences: UserPreferencesService + ) {} + + /** + * Moves a node to the trashcan. + * @param nodeId ID of the target node + * @param options Optional parameters supported by JS-API + * @returns Empty result that notifies when the deletion is complete + */ + deleteNode( + nodeId: string, + options: { permanent?: boolean } = {} + ): Observable { + return Observable.fromPromise( + this.api.nodesApi.deleteNode(nodeId, options) + ); + } + + /** + * Gets the stored information about a node. + * @param nodeId ID of the target node + * @param options Optional parameters supported by JS-API + * @returns Node information + */ + getNode(nodeId: string, options: any = {}): Observable { + const defaults = { + include: [ + 'path', + 'properties', + 'allowableOperations', + 'permissions' + ] + }; + const queryOptions = Object.assign(defaults, options); + + return Observable.fromPromise( + this.api.nodesApi.getNode(nodeId, queryOptions) + ); + } + + getNodeInfo(nodeId: string, options: any = {}): Observable { + const defaults = { + include: ['allowableOperations'] + }; + const queryOptions = Object.assign(defaults, options); + + return Observable.fromPromise( + this.api.nodesApi.getNodeInfo(nodeId, queryOptions) + ); + } + + /** + * Gets the items contained in a folder node. + * @param nodeId ID of the target node + * @param options Optional parameters supported by JS-API + * @returns List of child items from the folder + */ + getNodeChildren(nodeId: string, options: any = {}): Observable { + const defaults = { + maxItems: this.preferences.paginationSize, + skipCount: 0, + include: [ + 'isLocked', + 'path', + 'properties', + 'allowableOperations', + 'permissions' + ] + }; + const queryOptions = Object.assign(defaults, options); + + return Observable.fromPromise( + this.api.nodesApi.getNodeChildren(nodeId, queryOptions) + ); + } + + deleteSharedLink(linkId: string): Observable { + return Observable.fromPromise( + this.api.sharedLinksApi.deleteSharedLink(linkId) + ); + } + + getDeletedNodes(options: any = {}): Observable { + const defaults = { + include: ['path'] + }; + const queryOptions = Object.assign(defaults, options); + + return Observable.fromPromise( + this.api.nodesApi.getDeletedNodes(queryOptions) + ); + } + + restoreNode(nodeId: string): Observable { + return Observable.fromPromise(this.api.nodesApi.restoreNode(nodeId)); + } + + purgeDeletedNode(nodeId: string): Observable { + return Observable.fromPromise( + this.api.nodesApi.purgeDeletedNode(nodeId) + ); + } + + /** + * Gets information about a user identified by their username. + * @param personId ID of the target user + * @returns User information + */ + getPerson( + personId: string, + options?: { fields?: Array } + ): Observable { + return Observable.fromPromise( + this.api.peopleApi.getPerson(personId, options) + ); + } + + /** + * Copy a node to destination node + * + * @param nodeId The id of the node to be copied + * @param targetParentId The id of the folder-node where the node have to be copied to + * @param name The new name for the copy that would be added on the destination folder + */ + copyNode( + nodeId: string, + targetParentId: string, + name?: string, + opts?: { include?: Array; fields?: Array } + ): Observable { + return Observable.fromPromise( + this.api.nodesApi.copyNode(nodeId, { targetParentId, name }, opts) + ); + } + + /** + * Gets product information for Content Services. + * @returns ProductVersionModel containing product details + */ + getRepositoryInformation(): Observable { + return Observable.fromPromise( + this.api + .getInstance() + .discovery.discoveryApi.getRepositoryInformation() + ); + } + + getFavorites( + personId: string, + opts?: { + skipCount?: number; + maxItems?: number; + where?: string; + fields?: Array; + } + ): Observable { + return Observable.fromPromise( + this.api.favoritesApi.getFavorites(personId, opts) + ); + } + + findSharedLinks(opts?: any): Observable { + return Observable.fromPromise( + this.api.sharedLinksApi.findSharedLinks(opts) + ); + } + + search(request: SearchRequest): Observable { + return Observable.fromPromise( + this.api.searchApi.search(request) + ); + } + + getContentUrl(nodeId: string, attachment?: boolean): string { + return this.api.contentApi.getContentUrl(nodeId, attachment); + } + + deleteSite(siteId?: string, opts?: { permanent?: boolean }): Observable { + return Observable.fromPromise( + this.api.sitesApi.deleteSite(siteId, opts) + ); + } +} diff --git a/src/app/store/actions.ts b/src/app/store/actions.ts new file mode 100644 index 000000000..013545cb3 --- /dev/null +++ b/src/app/store/actions.ts @@ -0,0 +1,33 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +export * from './actions/app.actions'; +export * from './actions/node.actions'; +export * from './actions/snackbar.actions'; +export * from './actions/router.actions'; +export * from './actions/viewer.actions'; +export * from './actions/search.actions'; +export * from './actions/user.actions'; +export * from './actions/library.actions'; diff --git a/src/app/store/actions/app.actions.ts b/src/app/store/actions/app.actions.ts new file mode 100644 index 000000000..c73a019f5 --- /dev/null +++ b/src/app/store/actions/app.actions.ts @@ -0,0 +1,51 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { Action } from '@ngrx/store'; + +export const SET_APP_NAME = 'SET_APP_NAME'; +export const SET_HEADER_COLOR = 'SET_HEADER_COLOR'; +export const SET_LOGO_PATH = 'SET_LOGO_PATH'; +export const SET_LANGUAGE_PICKER = 'SET_LANGUAGE_PICKER'; + +export class SetAppNameAction implements Action { + readonly type = SET_APP_NAME; + constructor(public payload: string) {} +} + +export class SetHeaderColorAction implements Action { + readonly type = SET_HEADER_COLOR; + constructor(public payload: string) {} +} + +export class SetLogoPathAction implements Action { + readonly type = SET_LOGO_PATH; + constructor(public payload: string) {} +} + +export class SetLanguagePickerAction implements Action { + readonly type = SET_LANGUAGE_PICKER; + constructor(public payload: boolean) {} +} diff --git a/src/app/store/actions/library.actions.ts b/src/app/store/actions/library.actions.ts new file mode 100644 index 000000000..d2fad4aef --- /dev/null +++ b/src/app/store/actions/library.actions.ts @@ -0,0 +1,33 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { Action } from '@ngrx/store'; + +export const DELETE_LIBRARY = 'DELETE_LIBRARY'; + +export class DeleteLibraryAction implements Action { + readonly type = DELETE_LIBRARY; + constructor(public payload: string) {} +} diff --git a/src/app/store/actions/node.actions.ts b/src/app/store/actions/node.actions.ts new file mode 100644 index 000000000..c8b1a9063 --- /dev/null +++ b/src/app/store/actions/node.actions.ts @@ -0,0 +1,64 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { Action } from '@ngrx/store'; +import { NodeInfo } from '../models'; + +export const SET_SELECTED_NODES = 'SET_SELECTED_NODES'; +export const DELETE_NODES = 'DELETE_NODES'; +export const UNDO_DELETE_NODES = 'UNDO_DELETE_NODES'; +export const RESTORE_DELETED_NODES = 'RESTORE_DELETED_NODES'; +export const PURGE_DELETED_NODES = 'PURGE_DELETED_NODES'; +export const DOWNLOAD_NODES = 'DOWNLOAD_NODES'; + +export class SetSelectedNodesAction implements Action { + readonly type = SET_SELECTED_NODES; + constructor(public payload: any[] = []) {} +} + +export class DeleteNodesAction implements Action { + readonly type = DELETE_NODES; + constructor(public payload: NodeInfo[] = []) {} +} + +export class UndoDeleteNodesAction implements Action { + readonly type = UNDO_DELETE_NODES; + constructor(public payload: any[] = []) {} +} + +export class RestoreDeletedNodesAction implements Action { + readonly type = RESTORE_DELETED_NODES; + constructor(public payload: any[] = []) {} +} + +export class PurgeDeletedNodesAction implements Action { + readonly type = PURGE_DELETED_NODES; + constructor(public payload: NodeInfo[] = []) {} +} + +export class DownloadNodesAction implements Action { + readonly type = DOWNLOAD_NODES; + constructor(public payload: NodeInfo[] = []) {} +} diff --git a/src/app/store/actions/router.actions.ts b/src/app/store/actions/router.actions.ts new file mode 100644 index 000000000..2e3faa760 --- /dev/null +++ b/src/app/store/actions/router.actions.ts @@ -0,0 +1,47 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { Action } from '@ngrx/store'; +import { MinimalNodeEntity } from 'alfresco-js-api'; + +export const NAVIGATE_ROUTE = 'NAVIGATE_ROUTE'; +export const NAVIGATE_FOLDER = 'NAVIGATE_FOLDER'; +export const NAVIGATE_PARENT_FOLDER = 'NAVIGATE_PARENT_FOLDER'; + +export class NavigateRouteAction implements Action { + readonly type = NAVIGATE_ROUTE; + constructor(public payload: any[]) {} +} + +export class NavigateToFolder implements Action { + readonly type = NAVIGATE_FOLDER; + constructor(public payload: MinimalNodeEntity) {} +} + + +export class NavigateToParentFolder implements Action { + readonly type = NAVIGATE_PARENT_FOLDER; + constructor(public payload: MinimalNodeEntity) {} +} diff --git a/src/app/store/actions/search.actions.ts b/src/app/store/actions/search.actions.ts new file mode 100644 index 000000000..5d3632bb6 --- /dev/null +++ b/src/app/store/actions/search.actions.ts @@ -0,0 +1,33 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { Action } from '@ngrx/store'; + +export const SEARCH_BY_TERM = 'SEARCH_BY_TERM'; + +export class SearchByTermAction implements Action { + readonly type = SEARCH_BY_TERM; + constructor(public payload: string) {} +} diff --git a/src/app/store/actions/snackbar.actions.ts b/src/app/store/actions/snackbar.actions.ts new file mode 100644 index 000000000..baac9ef9e --- /dev/null +++ b/src/app/store/actions/snackbar.actions.ts @@ -0,0 +1,68 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { Action } from '@ngrx/store'; + +export const SNACKBAR_INFO = 'SNACKBAR_INFO'; +export const SNACKBAR_WARNING = 'SNACKBAR_WARNING'; +export const SNACKBAR_ERROR = 'SNACKBAR_ERROR'; + +export interface SnackbarAction extends Action { + payload: string; + params?: Object; + userAction?: SnackbarUserAction; + duration: number; +} + +export class SnackbarUserAction { + constructor(public title: string, public action: Action) {} +} + +export class SnackbarInfoAction implements SnackbarAction { + readonly type = SNACKBAR_INFO; + + userAction?: SnackbarUserAction; + duration = 4000; + + constructor(public payload: string, public params?: Object) {} +} + +export class SnackbarWarningAction implements SnackbarAction { + readonly type = SNACKBAR_WARNING; + + userAction?: SnackbarUserAction; + duration = 4000; + + constructor(public payload: string, public params?: Object) {} +} + +export class SnackbarErrorAction implements SnackbarAction { + readonly type = SNACKBAR_ERROR; + + userAction?: SnackbarUserAction; + duration = 4000; + + constructor(public payload: string, public params?: Object) {} +} diff --git a/src/app/store/actions/user.actions.ts b/src/app/store/actions/user.actions.ts new file mode 100644 index 000000000..a128e1511 --- /dev/null +++ b/src/app/store/actions/user.actions.ts @@ -0,0 +1,34 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { Action } from '@ngrx/store'; +import { Person } from 'alfresco-js-api'; + +export const SET_USER = 'SET_USER'; + +export class SetUserAction implements Action { + readonly type = SET_USER; + constructor(public payload: Person) { } +} diff --git a/src/app/store/actions/viewer.actions.ts b/src/app/store/actions/viewer.actions.ts new file mode 100644 index 000000000..048a408eb --- /dev/null +++ b/src/app/store/actions/viewer.actions.ts @@ -0,0 +1,34 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { Action } from '@ngrx/store'; +import { NodeInfo } from '../models'; + +export const VIEW_NODE = 'VIEW_NODE'; + +export class ViewNodeAction implements Action { + readonly type = VIEW_NODE; + constructor(public payload: NodeInfo) {} +} diff --git a/src/app/store/app-store.module.ts b/src/app/store/app-store.module.ts new file mode 100644 index 000000000..ecf31fd66 --- /dev/null +++ b/src/app/store/app-store.module.ts @@ -0,0 +1,65 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { NgModule } from '@angular/core'; +import { StoreModule } from '@ngrx/store'; +import { appReducer } from './reducers/app.reducer'; +import { INITIAL_STATE } from './states'; +import { StoreRouterConnectingModule } from '@ngrx/router-store'; +import { EffectsModule } from '@ngrx/effects'; +import { environment } from '../../environments/environment'; +import { StoreDevtoolsModule } from '@ngrx/store-devtools'; +import { + SnackbarEffects, + NodeEffects, + RouterEffects, + DownloadEffects, + ViewerEffects, + SearchEffects, + SiteEffects +} from './effects'; + +@NgModule({ + imports: [ + StoreModule.forRoot( + { app: appReducer }, + { initialState: INITIAL_STATE } + ), + StoreRouterConnectingModule.forRoot({ stateKey: 'router' }), + EffectsModule.forRoot([ + SnackbarEffects, + NodeEffects, + RouterEffects, + DownloadEffects, + ViewerEffects, + SearchEffects, + SiteEffects + ]), + !environment.production + ? StoreDevtoolsModule.instrument({ maxAge: 25 }) + : [] + ] +}) +export class AppStoreModule {} diff --git a/src/app/store/effects.ts b/src/app/store/effects.ts new file mode 100644 index 000000000..3c773fee2 --- /dev/null +++ b/src/app/store/effects.ts @@ -0,0 +1,32 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +export * from './effects/download.effects'; +export * from './effects/node.effects'; +export * from './effects/router.effects'; +export * from './effects/snackbar.effects'; +export * from './effects/viewer.effects'; +export * from './effects/search.effects'; +export * from './effects/library.effects'; diff --git a/src/app/store/effects/download.effects.ts b/src/app/store/effects/download.effects.ts new file mode 100644 index 000000000..72a6d1dbb --- /dev/null +++ b/src/app/store/effects/download.effects.ts @@ -0,0 +1,111 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { DownloadZipDialogComponent } from '@alfresco/adf-content-services'; +import { Injectable } from '@angular/core'; +import { MatDialog } from '@angular/material'; +import { Actions, Effect, ofType } from '@ngrx/effects'; +import { map } from 'rxjs/operators'; +import { DownloadNodesAction, DOWNLOAD_NODES } from '../actions'; +import { NodeInfo } from '../models'; +import { ContentApiService } from '../../services/content-api.service'; + +@Injectable() +export class DownloadEffects { + constructor( + private actions$: Actions, + private contentApi: ContentApiService, + private dialog: MatDialog + ) {} + + @Effect({ dispatch: false }) + downloadNode$ = this.actions$.pipe( + ofType(DOWNLOAD_NODES), + map(action => { + if (action.payload && action.payload.length > 0) { + this.downloadNodes(action.payload); + } + }) + ); + + private downloadNodes(nodes: Array) { + if (!nodes || nodes.length === 0) { + return; + } + + if (nodes.length === 1) { + this.downloadNode(nodes[0]); + } else { + this.downloadZip(nodes); + } + } + + private downloadNode(node: NodeInfo) { + if (node) { + if (node.isFolder) { + this.downloadZip([node]); + } else { + this.downloadFile(node); + } + } + } + + private downloadFile(node: NodeInfo) { + if (node) { + this.download( + this.contentApi.getContentUrl(node.id, true), + node.name + ); + } + } + + private downloadZip(nodes: Array) { + if (nodes && nodes.length > 0) { + const nodeIds = nodes.map(node => node.id); + + this.dialog.open(DownloadZipDialogComponent, { + width: '600px', + disableClose: true, + data: { + nodeIds + } + }); + } + } + + private download(url: string, fileName: string) { + if (url && fileName) { + const link = document.createElement('a'); + + link.style.display = 'none'; + link.download = fileName; + link.href = url; + + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + } + } +} diff --git a/src/app/store/effects/library.effects.ts b/src/app/store/effects/library.effects.ts new file mode 100644 index 000000000..a8df0f9bb --- /dev/null +++ b/src/app/store/effects/library.effects.ts @@ -0,0 +1,71 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { Effect, Actions, ofType } from '@ngrx/effects'; +import { Injectable } from '@angular/core'; +import { map } from 'rxjs/operators'; +import { DeleteLibraryAction, DELETE_LIBRARY } from '../actions'; +import { + SnackbarInfoAction, + SnackbarErrorAction +} from '../actions/snackbar.actions'; +import { Store } from '@ngrx/store'; +import { AppStore } from '../states/app.state'; +import { ContentManagementService } from '../../common/services/content-management.service'; +import { ContentApiService } from '../../services/content-api.service'; + +@Injectable() +export class SiteEffects { + constructor( + private actions$: Actions, + private store: Store, + private contentApi: ContentApiService, + private content: ContentManagementService + ) {} + + @Effect({ dispatch: false }) + deleteLibrary$ = this.actions$.pipe( + ofType(DELETE_LIBRARY), + map(action => { + this.contentApi.deleteSite(action.payload).subscribe( + () => { + this.content.siteDeleted.next(action.payload); + this.store.dispatch( + new SnackbarInfoAction( + 'APP.MESSAGES.INFO.LIBRARY_DELETED' + ) + ); + }, + () => { + this.store.dispatch( + new SnackbarErrorAction( + 'APP.MESSAGES.ERRORS.DELETE_LIBRARY_FAILED' + ) + ); + } + ); + }) + ); +} diff --git a/src/app/store/effects/node.effects.ts b/src/app/store/effects/node.effects.ts new file mode 100644 index 000000000..c0adad2a3 --- /dev/null +++ b/src/app/store/effects/node.effects.ts @@ -0,0 +1,371 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { Effect, Actions, ofType } from '@ngrx/effects'; +import { Injectable } from '@angular/core'; +import { map } from 'rxjs/operators'; +import { Store } from '@ngrx/store'; +import { AppStore } from '../states/app.state'; +import { + SnackbarWarningAction, + SnackbarInfoAction, + SnackbarErrorAction, + PurgeDeletedNodesAction, + PURGE_DELETED_NODES, + DeleteNodesAction, + DELETE_NODES, + SnackbarUserAction, + SnackbarAction, + UndoDeleteNodesAction, + UNDO_DELETE_NODES +} from '../actions'; +import { ContentManagementService } from '../../common/services/content-management.service'; +import { Observable } from 'rxjs/Rx'; +import { NodeInfo, DeleteStatus, DeletedNodeInfo } from '../models'; +import { ContentApiService } from '../../services/content-api.service'; + +@Injectable() +export class NodeEffects { + constructor( + private store: Store, + private actions$: Actions, + private contentManagementService: ContentManagementService, + private contentApi: ContentApiService + ) {} + + @Effect({ dispatch: false }) + purgeDeletedNodes$ = this.actions$.pipe( + ofType(PURGE_DELETED_NODES), + map(action => { + this.purgeNodes(action.payload); + }) + ); + + @Effect({ dispatch: false }) + deleteNodes$ = this.actions$.pipe( + ofType(DELETE_NODES), + map(action => { + if (action.payload.length > 0) { + this.deleteNodes(action.payload); + } + }) + ); + + @Effect({ dispatch: false }) + undoDeleteNodes$ = this.actions$.pipe( + ofType(UNDO_DELETE_NODES), + map(action => { + if (action.payload.length > 0) { + this.undoDeleteNodes(action.payload); + } + }) + ); + + private deleteNodes(items: NodeInfo[]): void { + const batch: Observable[] = []; + + items.forEach(node => { + batch.push(this.deleteNode(node)); + }); + + Observable.forkJoin(...batch).subscribe((data: DeletedNodeInfo[]) => { + const status = this.processStatus(data); + const message = this.getDeleteMessage(status); + + if (message && status.someSucceeded) { + message.duration = 10000; + message.userAction = new SnackbarUserAction( + 'APP.ACTIONS.UNDO', + new UndoDeleteNodesAction([...status.success]) + ); + } + + this.store.dispatch(message); + + if (status.someSucceeded) { + this.contentManagementService.nodesDeleted.next(); + } + }); + } + + private deleteNode(node: NodeInfo): Observable { + const { id, name } = node; + + return this.contentApi.deleteNode(id) + .map(() => { + return { + id, + name, + status: 1 + }; + }) + .catch((error: any) => { + return Observable.of({ + id, + name, + status: 0 + }); + }); + } + + private getDeleteMessage(status: DeleteStatus): SnackbarAction { + if (status.allFailed && !status.oneFailed) { + return new SnackbarErrorAction( + 'APP.MESSAGES.ERRORS.NODE_DELETION_PLURAL', + { number: status.fail.length } + ); + } + + if (status.allSucceeded && !status.oneSucceeded) { + return new SnackbarInfoAction( + 'APP.MESSAGES.INFO.NODE_DELETION.PLURAL', + { number: status.success.length } + ); + } + + if (status.someFailed && status.someSucceeded && !status.oneSucceeded) { + return new SnackbarWarningAction( + 'APP.MESSAGES.INFO.NODE_DELETION.PARTIAL_PLURAL', + { + success: status.success.length, + failed: status.fail.length + } + ); + } + + if (status.someFailed && status.oneSucceeded) { + return new SnackbarWarningAction( + 'APP.MESSAGES.INFO.NODE_DELETION.PARTIAL_SINGULAR', + { + success: status.success.length, + failed: status.fail.length + } + ); + } + + if (status.oneFailed && !status.someSucceeded) { + return new SnackbarErrorAction( + 'APP.MESSAGES.ERRORS.NODE_DELETION', + { name: status.fail[0].name } + ); + } + + if (status.oneSucceeded && !status.someFailed) { + return new SnackbarInfoAction( + 'APP.MESSAGES.INFO.NODE_DELETION.SINGULAR', + { name: status.success[0].name } + ); + } + + return null; + } + + private undoDeleteNodes(items: DeletedNodeInfo[]): void { + const batch: Observable[] = []; + + items.forEach(item => { + batch.push(this.undoDeleteNode(item)); + }); + + Observable.forkJoin(...batch).subscribe(data => { + const processedData = this.processStatus(data); + + if (processedData.fail.length) { + const message = this.getUndoDeleteMessage(processedData); + this.store.dispatch(message); + } + + if (processedData.someSucceeded) { + this.contentManagementService.nodesRestored.next(); + } + }); + } + + private undoDeleteNode(item: DeletedNodeInfo): Observable { + const { id, name } = item; + + return this.contentApi.restoreNode(id) + .map(() => { + return { + id, + name, + status: 1 + }; + }) + .catch((error: any) => { + return Observable.of({ + id, + name, + status: 0 + }); + }); + } + + private getUndoDeleteMessage(status: DeleteStatus): SnackbarAction { + if (status.someFailed && !status.oneFailed) { + return new SnackbarErrorAction( + 'APP.MESSAGES.ERRORS.NODE_RESTORE_PLURAL', + { number: status.fail.length } + ); + } + + if (status.oneFailed) { + return new SnackbarErrorAction('APP.MESSAGES.ERRORS.NODE_RESTORE', { + name: status.fail[0].name + }); + } + + return null; + } + + private purgeNodes(selection: NodeInfo[] = []) { + if (!selection.length) { + return; + } + + const batch = selection.map(node => this.purgeDeletedNode(node)); + + Observable.forkJoin(batch).subscribe(purgedNodes => { + const status = this.processStatus(purgedNodes); + + if (status.success.length) { + this.contentManagementService.nodesPurged.next(); + } + const message = this.getPurgeMessage(status); + if (message) { + this.store.dispatch(message); + } + }); + } + + private purgeDeletedNode(node: NodeInfo): Observable { + const { id, name } = node; + + return this.contentApi.purgeDeletedNode(id) + .map(() => ({ + status: 1, + id, + name + })) + .catch(error => { + return Observable.of({ + status: 0, + id, + name + }); + }); + } + + private processStatus(data: DeletedNodeInfo[] = []): DeleteStatus { + const status = { + fail: [], + success: [], + get someFailed() { + return !!this.fail.length; + }, + get someSucceeded() { + return !!this.success.length; + }, + get oneFailed() { + return this.fail.length === 1; + }, + get oneSucceeded() { + return this.success.length === 1; + }, + get allSucceeded() { + return this.someSucceeded && !this.someFailed; + }, + get allFailed() { + return this.someFailed && !this.someSucceeded; + }, + reset() { + this.fail = []; + this.success = []; + } + }; + + return data.reduce((acc, node) => { + if (node.status) { + acc.success.push(node); + } else { + acc.fail.push(node); + } + + return acc; + }, status); + } + + private getPurgeMessage(status: DeleteStatus): SnackbarAction { + if (status.oneSucceeded && status.someFailed && !status.oneFailed) { + return new SnackbarWarningAction( + 'APP.MESSAGES.INFO.TRASH.NODES_PURGE.PARTIAL_SINGULAR', + { + name: status.success[0].name, + failed: status.fail.length + } + ); + } + + if (status.someSucceeded && !status.oneSucceeded && status.someFailed) { + return new SnackbarWarningAction( + 'APP.MESSAGES.INFO.TRASH.NODES_PURGE.PARTIAL_PLURAL', + { + number: status.success.length, + failed: status.fail.length + } + ); + } + + if (status.oneSucceeded) { + return new SnackbarInfoAction( + 'APP.MESSAGES.INFO.TRASH.NODES_PURGE.SINGULAR', + { name: status.success[0].name } + ); + } + + if (status.oneFailed) { + return new SnackbarErrorAction( + 'APP.MESSAGES.ERRORS.TRASH.NODES_PURGE.SINGULAR', + { name: status.fail[0].name } + ); + } + + if (status.allSucceeded) { + return new SnackbarInfoAction( + 'APP.MESSAGES.INFO.TRASH.NODES_PURGE.PLURAL', + { number: status.success.length } + ); + } + + if (status.allFailed) { + return new SnackbarErrorAction( + 'APP.MESSAGES.ERRORS.TRASH.NODES_PURGE.PLURAL', + { number: status.fail.length } + ); + } + + return null; + } +} diff --git a/src/app/store/effects/router.effects.ts b/src/app/store/effects/router.effects.ts new file mode 100644 index 000000000..e0f66c4b3 --- /dev/null +++ b/src/app/store/effects/router.effects.ts @@ -0,0 +1,128 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { Injectable } from '@angular/core'; +import { Router } from '@angular/router'; +import { Actions, Effect, ofType } from '@ngrx/effects'; +import { MinimalNodeEntryEntity, PathInfoEntity } from 'alfresco-js-api'; +import { map } from 'rxjs/operators'; +import { + NavigateRouteAction, + NavigateToParentFolder, + NAVIGATE_PARENT_FOLDER, + NAVIGATE_ROUTE +} from '../actions'; +import { NavigateToFolder, NAVIGATE_FOLDER } from '../actions/router.actions'; + +@Injectable() +export class RouterEffects { + constructor(private actions$: Actions, private router: Router) {} + + @Effect({ dispatch: false }) + navigateRoute$ = this.actions$.pipe( + ofType(NAVIGATE_ROUTE), + map(action => { + this.router.navigate(action.payload); + }) + ); + + @Effect({ dispatch: false }) + navigateToFolder$ = this.actions$.pipe( + ofType(NAVIGATE_FOLDER), + map(action => { + if (action.payload && action.payload.entry) { + this.navigateToFolder(action.payload.entry); + } + }) + ); + + @Effect({ dispatch: false }) + navigateToParentFolder$ = this.actions$.pipe( + ofType(NAVIGATE_PARENT_FOLDER), + map(action => { + if (action.payload && action.payload.entry) { + this.navigateToParentFolder(action.payload.entry); + } + }) + ); + + private navigateToFolder(node: MinimalNodeEntryEntity) { + let link = null; + const { path, id } = node; + + if (path && path.name && path.elements) { + const isLibraryPath = this.isLibraryContent(path); + + const parent = path.elements[path.elements.length - 1]; + const area = isLibraryPath ? '/libraries' : '/personal-files'; + + if (!isLibraryPath) { + link = [area, id]; + } else { + // parent.id could be 'Site' folder or child as 'documentLibrary' + link = [area, parent.name === 'Sites' ? {} : id]; + } + } + + setTimeout(() => { + this.router.navigate(link); + }, 10); + } + + private navigateToParentFolder(node: MinimalNodeEntryEntity) { + let link = null; + const { path } = node; + + if (path && path.name && path.elements) { + const isLibraryPath = this.isLibraryContent(path); + + const parent = path.elements[path.elements.length - 1]; + const area = isLibraryPath ? '/libraries' : '/personal-files'; + + if (!isLibraryPath) { + link = [area, parent.id]; + } else { + // parent.id could be 'Site' folder or child as 'documentLibrary' + link = [area, parent.name === 'Sites' ? {} : parent.id]; + } + } + + setTimeout(() => { + this.router.navigate(link); + }, 10); + } + + private isLibraryContent(path: PathInfoEntity): boolean { + if ( + path && + path.elements.length >= 2 && + path.elements[1].name === 'Sites' + ) { + return true; + } + + return false; + } +} diff --git a/src/app/store/effects/search.effects.ts b/src/app/store/effects/search.effects.ts new file mode 100644 index 000000000..0d5dd150f --- /dev/null +++ b/src/app/store/effects/search.effects.ts @@ -0,0 +1,43 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { Effect, Actions, ofType } from '@ngrx/effects'; +import { Injectable } from '@angular/core'; +import { map } from 'rxjs/operators'; +import { SEARCH_BY_TERM, SearchByTermAction } from '../actions/search.actions'; +import { Router } from '@angular/router'; + +@Injectable() +export class SearchEffects { + constructor(private actions$: Actions, private router: Router) {} + + @Effect({ dispatch: false }) + searchByTerm$ = this.actions$.pipe( + ofType(SEARCH_BY_TERM), + map(action => { + this.router.navigateByUrl('/search;q=' + action.payload); + }) + ); +} diff --git a/src/app/store/effects/snackbar.effects.ts b/src/app/store/effects/snackbar.effects.ts new file mode 100644 index 000000000..99dd419fe --- /dev/null +++ b/src/app/store/effects/snackbar.effects.ts @@ -0,0 +1,99 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { TranslationService } from '@alfresco/adf-core'; +import { Injectable } from '@angular/core'; +import { MatSnackBar } from '@angular/material'; +import { Actions, Effect, ofType } from '@ngrx/effects'; +import { Store } from '@ngrx/store'; +import { map } from 'rxjs/operators'; +import { + SnackbarAction, + SnackbarErrorAction, + SnackbarInfoAction, + SnackbarWarningAction, + SNACKBAR_ERROR, + SNACKBAR_INFO, + SNACKBAR_WARNING +} from '../actions'; +import { AppStore } from '../states/app.state'; + +@Injectable() +export class SnackbarEffects { + constructor( + private store: Store, + private actions$: Actions, + private snackBar: MatSnackBar, + private translationService: TranslationService + ) {} + + @Effect({ dispatch: false }) + infoEffect = this.actions$.pipe( + ofType(SNACKBAR_INFO), + map((action: SnackbarInfoAction) => { + this.showSnackBar(action, 'info-snackbar'); + }) + ); + + @Effect({ dispatch: false }) + warningEffect = this.actions$.pipe( + ofType(SNACKBAR_WARNING), + map((action: SnackbarWarningAction) => { + this.showSnackBar(action, 'warning-snackbar'); + }) + ); + + @Effect({ dispatch: false }) + errorEffect = this.actions$.pipe( + ofType(SNACKBAR_ERROR), + map((action: SnackbarErrorAction) => { + this.showSnackBar(action, 'error-snackbar'); + }) + ); + + private showSnackBar(action: SnackbarAction, panelClass: string) { + const message = this.translate(action.payload, action.params); + + let actionName: string = null; + if (action.userAction) { + actionName = this.translate(action.userAction.title); + } + + const snackBarRef = this.snackBar.open(message, actionName, { + duration: action.duration, + panelClass: panelClass + }); + + if (action.userAction) { + snackBarRef.onAction().subscribe(() => { + this.store.dispatch(action.userAction.action); + }); + } + } + + private translate(message: string, params?: Object): string { + return this.translationService.instant(message, params); + } +} diff --git a/src/app/store/effects/viewer.effects.ts b/src/app/store/effects/viewer.effects.ts new file mode 100644 index 000000000..42a97bb2e --- /dev/null +++ b/src/app/store/effects/viewer.effects.ts @@ -0,0 +1,62 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { Effect, Actions, ofType } from '@ngrx/effects'; +import { Injectable } from '@angular/core'; +import { map } from 'rxjs/operators'; +import { ViewNodeAction, VIEW_NODE } from '../actions/viewer.actions'; +import { Router } from '@angular/router'; + +@Injectable() +export class ViewerEffects { + constructor(private actions$: Actions, private router: Router) {} + + @Effect({ dispatch: false }) + viewNode$ = this.actions$.pipe( + ofType(VIEW_NODE), + map(action => { + const node = action.payload; + if (!node) { + return; + } + + let previewLocation = this.router.url; + if (previewLocation.lastIndexOf('/') > 0) { + previewLocation = previewLocation.substr( + 0, + this.router.url.indexOf('/', 1) + ); + } + previewLocation = previewLocation.replace(/\//g, ''); + + const path = [previewLocation]; + if (node.parentId) { + path.push(node.parentId); + } + path.push('preview', node.id); + this.router.navigateByUrl(path.join('/')); + }) + ); +} diff --git a/src/app/store/models.ts b/src/app/store/models.ts new file mode 100644 index 000000000..2a4d65d9a --- /dev/null +++ b/src/app/store/models.ts @@ -0,0 +1,28 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +export * from './models/delete-status.model'; +export * from './models/deleted-node-info.model'; +export * from './models/node-info.model'; diff --git a/src/app/store/models/delete-status.model.ts b/src/app/store/models/delete-status.model.ts new file mode 100644 index 000000000..aa912b175 --- /dev/null +++ b/src/app/store/models/delete-status.model.ts @@ -0,0 +1,36 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +export interface DeleteStatus { + success: any[]; + fail: any[]; + someFailed: boolean; + someSucceeded: boolean; + oneFailed: boolean; + oneSucceeded: boolean; + allSucceeded: boolean; + allFailed: boolean; + reset(): void; +} diff --git a/src/app/store/models/deleted-node-info.model.ts b/src/app/store/models/deleted-node-info.model.ts new file mode 100644 index 000000000..3e6a432f6 --- /dev/null +++ b/src/app/store/models/deleted-node-info.model.ts @@ -0,0 +1,30 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +export interface DeletedNodeInfo { + id: string; + name: string; + status: number; +} diff --git a/src/app/store/models/node-info.model.ts b/src/app/store/models/node-info.model.ts new file mode 100644 index 000000000..057900403 --- /dev/null +++ b/src/app/store/models/node-info.model.ts @@ -0,0 +1,32 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +export interface NodeInfo { + parentId?: string; + id: string; + name: string; + isFile?: boolean; + isFolder?: boolean; +} diff --git a/src/app/store/reducers/app.reducer.ts b/src/app/store/reducers/app.reducer.ts new file mode 100644 index 000000000..38d559e25 --- /dev/null +++ b/src/app/store/reducers/app.reducer.ts @@ -0,0 +1,173 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { Action } from '@ngrx/store'; +import { AppState, INITIAL_APP_STATE } from '../states/app.state'; +import { + SET_HEADER_COLOR, + SetHeaderColorAction, + SET_APP_NAME, + SetAppNameAction, + SET_LOGO_PATH, + SetLogoPathAction, + SET_SELECTED_NODES, + SetSelectedNodesAction, + SET_USER, + SetUserAction +} from '../actions'; +import { + SET_LANGUAGE_PICKER, + SetLanguagePickerAction +} from '../actions/app.actions'; + +export function appReducer( + state: AppState = INITIAL_APP_STATE, + action: Action +): AppState { + let newState: AppState; + + switch (action.type) { + case SET_APP_NAME: + newState = updateAppName(state, action); + break; + case SET_HEADER_COLOR: + newState = updateHeaderColor(state, action); + break; + case SET_LOGO_PATH: + newState = updateLogoPath(state, action); + break; + case SET_SELECTED_NODES: + newState = updateSelectedNodes(state, ( + action + )); + break; + case SET_USER: + newState = updateUser(state, action); + break; + case SET_LANGUAGE_PICKER: + newState = updateLanguagePicker(state, ( + action + )); + break; + default: + newState = Object.assign({}, state); + } + + return newState; +} + +function updateHeaderColor( + state: AppState, + action: SetHeaderColorAction +): AppState { + const newState = Object.assign({}, state); + newState.headerColor = action.payload; + return newState; +} + +function updateLanguagePicker( + state: AppState, + action: SetLanguagePickerAction +): AppState { + const newState = Object.assign({}, state); + newState.languagePicker = action.payload; + return newState; +} + +function updateAppName(state: AppState, action: SetAppNameAction): AppState { + const newState = Object.assign({}, state); + newState.appName = action.payload; + return newState; +} + +function updateLogoPath(state: AppState, action: SetLogoPathAction): AppState { + const newState = Object.assign({}, state); + newState.logoPath = action.payload; + return newState; +} + +function updateUser(state: AppState, action: SetUserAction): AppState { + const newState = Object.assign({}, state); + const user = action.payload; + + const id = user.id; + const firstName = user.firstName || ''; + const lastName = user.lastName || ''; + const userName = `${firstName} ${lastName}`; + const initials = [firstName[0], lastName[0]].join(''); + + const capabilities = (user).capabilities; + const isAdmin = capabilities ? capabilities.isAdmin : true; + + newState.user = { + firstName, + lastName, + userName, + initials, + isAdmin, + id + }; + + return newState; +} + +function updateSelectedNodes( + state: AppState, + action: SetSelectedNodesAction +): AppState { + const newState = Object.assign({}, state); + const nodes = [...action.payload]; + const count = nodes.length; + const isEmpty = nodes.length === 0; + + let first = null; + let last = null; + let file = null; + let folder = null; + + if (nodes.length > 0) { + first = nodes[0]; + last = nodes[nodes.length - 1]; + + if (nodes.length === 1) { + file = nodes.find(entity => { + // workaround Shared + return entity.entry.isFile || entity.entry.nodeId; + }); + folder = nodes.find(entity => entity.entry.isFolder); + } + } + + newState.selection = { + count, + nodes, + isEmpty, + first, + last, + file, + folder + }; + return newState; +} diff --git a/src/app/store/selectors/app.selectors.ts b/src/app/store/selectors/app.selectors.ts new file mode 100644 index 000000000..8fe15c58c --- /dev/null +++ b/src/app/store/selectors/app.selectors.ts @@ -0,0 +1,35 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { createSelector } from '@ngrx/store'; +import { AppStore } from '../states/app.state'; + +export const selectApp = (state: AppStore) => state.app; +export const selectHeaderColor = createSelector(selectApp, state => state.headerColor); +export const selectAppName = createSelector(selectApp, state => state.appName); +export const selectLogoPath = createSelector(selectApp, state => state.logoPath); +export const appSelection = createSelector(selectApp, state => state.selection); +export const appLanguagePicker = createSelector(selectApp, state => state.languagePicker); +export const selectUser = createSelector(selectApp, state => state.user); diff --git a/src/app/store/states.ts b/src/app/store/states.ts new file mode 100644 index 000000000..7309f63a5 --- /dev/null +++ b/src/app/store/states.ts @@ -0,0 +1,28 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +export * from './states/app.state'; +export * from './states/profile.state'; +export * from './states/selection.state'; diff --git a/src/app/store/states/app.state.ts b/src/app/store/states/app.state.ts new file mode 100644 index 000000000..e63212685 --- /dev/null +++ b/src/app/store/states/app.state.ts @@ -0,0 +1,62 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { SelectionState } from './selection.state'; +import { ProfileState } from './profile.state'; + +export interface AppState { + appName: string; + headerColor: string; + logoPath: string; + languagePicker: boolean; + selection: SelectionState; + user: ProfileState; +} + +export const INITIAL_APP_STATE: AppState = { + appName: 'Alfresco Example Content Application', + headerColor: '#2196F3', + logoPath: 'assets/images/alfresco-logo-white.svg', + languagePicker: false, + user: { + isAdmin: true, // 5.2.x + id: null, + firstName: '', + lastName: '' + }, + selection: { + nodes: [], + isEmpty: true, + count: 0 + } +}; + +export interface AppStore { + app: AppState; +} + +export const INITIAL_STATE: AppStore = { + app: INITIAL_APP_STATE +}; diff --git a/src/app/store/states/profile.state.ts b/src/app/store/states/profile.state.ts new file mode 100644 index 000000000..2113d51c9 --- /dev/null +++ b/src/app/store/states/profile.state.ts @@ -0,0 +1,33 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +export interface ProfileState { + id: string; + isAdmin: boolean; + firstName: string; + lastName: string; + userName?: string; + initials?: string; +} diff --git a/src/app/store/states/selection.state.ts b/src/app/store/states/selection.state.ts new file mode 100644 index 000000000..9db206192 --- /dev/null +++ b/src/app/store/states/selection.state.ts @@ -0,0 +1,36 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { MinimalNodeEntity } from 'alfresco-js-api'; + +export interface SelectionState { + count: number; + nodes: MinimalNodeEntity[]; + isEmpty: boolean; + first?: MinimalNodeEntity; + last?: MinimalNodeEntity; + folder?: MinimalNodeEntity; + file?: MinimalNodeEntity; +} diff --git a/src/app/testing/app-testing.module.ts b/src/app/testing/app-testing.module.ts new file mode 100644 index 000000000..0ad459064 --- /dev/null +++ b/src/app/testing/app-testing.module.ts @@ -0,0 +1,118 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { NgModule } from '@angular/core'; +import { TranslateService, TranslatePipe } from '@ngx-translate/core'; +import { TranslatePipeMock } from './translate-pipe.directive'; +import { NoopAnimationsModule } from '@angular/platform-browser/animations'; +import { + TranslationService, + TranslationMock, + AuthenticationService, + UserPreferencesService, + AppConfigService, + StorageService, + CookieService, + AlfrescoApiService, + LogService, + NotificationService, + NodesApiService, + ContentService, + ThumbnailService, + UploadService, + PeopleContentService, + AlfrescoApiMock +} from '@alfresco/adf-core'; +import { HttpClientModule } from '@angular/common/http'; +import { TranslateServiceMock } from './translation.service'; +import { StoreModule } from '@ngrx/store'; +import { appReducer } from '../store/reducers/app.reducer'; +import { INITIAL_STATE } from '../store/states/app.state'; +import { RouterTestingModule } from '@angular/router/testing'; +import { EffectsModule } from '@ngrx/effects'; +import { + CustomResourcesService, + DocumentListService +} from '@alfresco/adf-content-services'; +import { MaterialModule } from '../material.module'; +import { ContentManagementService } from '../common/services/content-management.service'; +import { NodeActionsService } from '../common/services/node-actions.service'; +import { NodePermissionService } from '../common/services/node-permission.service'; +import { BrowsingFilesService } from '../common/services/browsing-files.service'; +import { ContentApiService } from '../services/content-api.service'; + +@NgModule({ + imports: [ + NoopAnimationsModule, + HttpClientModule, + RouterTestingModule, + MaterialModule, + StoreModule.forRoot( + { app: appReducer }, + { initialState: INITIAL_STATE } + ), + EffectsModule.forRoot([]) + ], + declarations: [TranslatePipeMock], + exports: [TranslatePipeMock, RouterTestingModule, MaterialModule], + providers: [ + { provide: AlfrescoApiService, useClass: AlfrescoApiMock }, + { provide: TranslationService, useClass: TranslationMock }, + { provide: TranslateService, useClass: TranslateServiceMock }, + { provide: TranslatePipe, useClass: TranslatePipeMock }, + { + provide: AuthenticationService, + useValue: { + isEcmLoggedIn(): boolean { + return true; + }, + getRedirect(): string { + return null; + } + } + }, + UserPreferencesService, + AppConfigService, + StorageService, + CookieService, + AlfrescoApiService, + LogService, + NotificationService, + NodesApiService, + ContentService, + ThumbnailService, + UploadService, + CustomResourcesService, + DocumentListService, + PeopleContentService, + + ContentManagementService, + NodeActionsService, + NodePermissionService, + BrowsingFilesService, + ContentApiService + ] +}) +export class AppTestingModule {} diff --git a/src/app/common/pipes/app-config.pipe.ts b/src/app/testing/translate-pipe.directive.ts similarity index 79% rename from src/app/common/pipes/app-config.pipe.ts rename to src/app/testing/translate-pipe.directive.ts index 46470d69a..727f85e8b 100644 --- a/src/app/common/pipes/app-config.pipe.ts +++ b/src/app/testing/translate-pipe.directive.ts @@ -24,16 +24,10 @@ */ import { Pipe, PipeTransform } from '@angular/core'; -import { AppConfigService } from '@alfresco/adf-core'; -@Pipe({ - name: 'appConfig', - pure: true -}) -export class AppConfigPipe implements PipeTransform { - constructor(private config: AppConfigService) {} - - transform(value: string, fallback: any): any { - return this.config.get(value, fallback); +@Pipe({ name: 'translate' }) +export class TranslatePipeMock implements PipeTransform { + transform(value: any, ...args: any[]) { + return value; } } diff --git a/src/app/testing/translation.service.ts b/src/app/testing/translation.service.ts new file mode 100644 index 000000000..1d6b30312 --- /dev/null +++ b/src/app/testing/translation.service.ts @@ -0,0 +1,43 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { Injectable } from '@angular/core'; +import { TranslateService } from '@ngx-translate/core'; +import { Observable } from 'rxjs/Observable'; + +@Injectable() +export class TranslateServiceMock extends TranslateService { + constructor() { + super(null, null, null, null, null); + } + + get(key: string | Array, interpolateParams?: Object): Observable { + return Observable.of(key); + } + + instant(key: string | Array, interpolateParams?: Object): string | any { + return key; + } +} diff --git a/src/app/ui/_layout.scss b/src/app/ui/_layout.scss deleted file mode 100644 index e64871faf..000000000 --- a/src/app/ui/_layout.scss +++ /dev/null @@ -1,55 +0,0 @@ -@import 'mixins'; - -$app-layout--header-height: 65px; -$app-layout--side-width: 320px; -$app-inner-layout--header-height: 48px; -$app-inner-layout--footer-height: 48px; -$alfresco-divider-color: rgba($alfresco-black, .07); -$alfresco-gray-background: #fafafa; - -.layout { - @include flex-column; -} - -.inner-layout { - @include flex-column; - - &__header { - display: flex; - align-items: center; - flex: 0 0 $app-layout--header-height; - flex-basis: $app-inner-layout--header-height; - background: $alfresco-gray-background; - border-bottom: 1px solid $alfresco-divider-color; - padding: 0 24px; - } - - &__content { - @include flex-row; - } - - &__content--hide { - display: none !important; - } - - &__panel { - @include flex-column; - border-right: 1px solid rgba(0, 0, 0, 0.07); - } - - &__side-panel { - display: block; - height: 100%; - overflow-y: scroll; - max-width: 320px; - } -} - -.content--scroll { - overflow: auto !important; -} - -app-generic-error { - height: 100%; - width: 100%; -} diff --git a/src/app/ui/_variables.scss b/src/app/ui/_variables.scss deleted file mode 100644 index 25054d318..000000000 --- a/src/app/ui/_variables.scss +++ /dev/null @@ -1,13 +0,0 @@ -// Primary color palette -// - please note that Hue 2 and Enhanced Hue 1 and 2 -// are missing from specs -$alfresco-app-color--default: #00bcd4; -$alfresco-app-color--hue-1: #e0f7fa; - -// Grayscale -$alfresco-white: #fff; -$alfresco-black: #000; - -// Dark -$alfresco-primary-text-color: rgba($alfresco-black, .87); -$alfresco-secondary-text-color: rgba($alfresco-black, .54); diff --git a/src/app/ui/application.scss b/src/app/ui/application.scss index b5d8072e6..70891f13f 100644 --- a/src/app/ui/application.scss +++ b/src/app/ui/application.scss @@ -1,13 +1,18 @@ @import 'mixins'; -@import 'variables'; @import 'theme'; +$foreground: map-get($theme, foreground); + html, body { @include flex-column; font-size: 14px; font-family: "Muli", sans-serif; - color: $alfresco-primary-text-color; + color: mat-color($foreground, text, 0.87); margin: 0; + + & > main { + @include flex-column; + } } app-root, @@ -18,17 +23,3 @@ app-search, ng-component { @include flex-column; } - -@import 'layout'; - -@import './overrides/adf-login'; -@import './overrides/adf-sidenav-layout'; -@import './overrides/adf-viewer'; -@import './overrides/alfresco-document-list'; -@import './overrides/alfresco-upload-drag-area'; -@import './overrides/alfresco-upload-button'; -@import './overrides/alfresco-upload-dialog'; -@import './overrides/toolbar'; -@import './overrides/adf-viewer-more-actions'; -@import './overrides/breadcrumb'; -@import './overrides/adf-info-drawer'; diff --git a/src/app/ui/custom-theme.scss b/src/app/ui/custom-theme.scss index 9cd3784d1..a652a54a5 100644 --- a/src/app/ui/custom-theme.scss +++ b/src/app/ui/custom-theme.scss @@ -2,8 +2,29 @@ @import '~@alfresco/adf-content-services/theming'; @import '../components/sidenav/sidenav.component.theme'; -@import './overrides/toolbar'; -@import './overrides/adf-viewer-more-actions'; +@import '../components/about/about.component.theme'; +@import '../components/generic-error/generic-error.component.theme'; +@import '../components/search-input/search-input.component.theme'; +@import '../components/settings/settings.component.theme'; +@import '../components/current-user/current-user.component.theme'; +@import '../components/header/header.component.theme'; +@import '../dialogs/node-versions/node-versions.dialog.theme'; + +@import './overrides/adf-toolbar.theme'; +@import './overrides/adf-search-filter.theme'; +@import './overrides/adf-info-drawer.theme'; +@import './overrides/adf-upload-button.theme'; +@import './overrides/adf-sidebar-action-menu.theme'; +@import './overrides/adf-upload-dialog.theme'; +@import './overrides/adf-pagination.theme'; +@import './overrides/adf-sidenav-layout.theme'; +@import './overrides/adf-document-list.theme'; +@import './overrides/adf-upload-drag-area.theme'; +@import './overrides/adf-search-sorting-picker.theme'; +@import './overrides/adf-content-node-selector.theme'; + +@import 'layout'; +@import 'snackbar'; $grey-scale: ( 50 : #e0e0e0, @@ -46,7 +67,26 @@ $custom-theme-warn: mat-palette($alfresco-warn); $custom-theme: mat-light-theme($custom-theme-primary, $custom-theme-accent); @mixin custom-theme($theme) { - @include sidenav-component-theme($custom-theme); - @include toolbar-component-theme($custom-theme); - @include viewer-more-actions-component-theme($custom-theme); + @include adf-toolbar-theme($theme); + @include adf-search-filter-theme($theme); + @include adf-info-drawer-theme($theme); + @include adf-upload-button-theme($theme); + @include adf-sidebar-action-menu-theme($theme); + @include adf-pagination-theme($theme); + @include adf-sidenav-layout-theme($theme); + @include adf-document-list-theme($theme); + @include adf-upload-drag-area-theme($theme); + @include adf-search-sorting-picker-theme($theme); + @include adf-content-node-selector-theme($theme); + + @include aca-layout-theme($theme); + @include aca-header-theme($theme); + @include aca-search-input-theme($theme); + @include aca-generic-error-theme($theme); + @include aca-node-versions-dialog-theme($theme); + @include aca-settings-theme($theme); + @include snackbar-theme($theme); + @include sidenav-component-theme($theme); + @include aca-about-component-theme($theme); + @include aca-current-user-theme($theme); } diff --git a/src/app/ui/layout.scss b/src/app/ui/layout.scss new file mode 100644 index 000000000..bb38d0e07 --- /dev/null +++ b/src/app/ui/layout.scss @@ -0,0 +1,55 @@ +@import 'mixins'; + +@mixin aca-layout-theme($theme) { + $foreground: map-get($theme, foreground); + + $app-layout--header-height: 65px; + $app-layout--side-width: 320px; + $app-inner-layout--header-height: 48px; + $app-inner-layout--footer-height: 48px; + $alfresco-divider-color: mat-color($foreground, text, 0.07); + $alfresco-gray-background: #fafafa; + + .layout { + @include flex-column; + } + + .inner-layout { + @include flex-column; + + &__header { + display: flex; + align-items: center; + flex: 0 0 $app-layout--header-height; + flex-basis: $app-inner-layout--header-height; + background: $alfresco-gray-background; + border-bottom: 1px solid $alfresco-divider-color; + padding: 0 24px; + } + + &__content { + @include flex-row; + } + + &__content--hide { + display: none !important; + } + + &__panel { + @include flex-column; + border-right: 1px solid mat-color($foreground, text, 0.07); + } + + &__side-panel { + display: block; + height: 100%; + overflow-y: scroll; + max-width: 350px; + width: 350px; + } + } + + .content--scroll { + overflow: auto !important; + } +} diff --git a/src/app/ui/overrides/_adf-info-drawer.scss b/src/app/ui/overrides/_adf-info-drawer.scss deleted file mode 100644 index dbec2ec4f..000000000 --- a/src/app/ui/overrides/_adf-info-drawer.scss +++ /dev/null @@ -1,50 +0,0 @@ -$icon-size: 48px; - -.adf-info-drawer-layout { - height: 100%; - - .adf-info-drawer-layout-content .adf-info-drawer-tabs .mat-tab-body-content { - .adf-manage-versions-empty, - .adf-manage-versions-no-permission { - margin: 24px; - color: rgba(0, 0, 0, 0.54); - text-align: justify; - display: flex; - flex-direction: column; - - &-icon { - width: $icon-size; - height: $icon-size; - font-size: $icon-size; - margin: auto; - } - } - } -} - -.adf-version-list-container, -.adf-version-manager-dialog .adf-version-list-container { - .adf-version-list { - height: auto; - } - - .mat-list .mat-list-item { - &.mat-3-line { - display: flex; - align-items: center; - height: 100%; - min-height: 88px; - } - - .mat-list-item-content { - padding-top: 10px; - padding-bottom: 10px; - width: 100%; - } - - .mat-line.adf-version-list-item-comment { - overflow: visible; - white-space: pre-wrap; - } - } -} diff --git a/src/app/ui/overrides/_adf-login.scss b/src/app/ui/overrides/_adf-login.scss deleted file mode 100644 index 3d8305f0a..000000000 --- a/src/app/ui/overrides/_adf-login.scss +++ /dev/null @@ -1,5 +0,0 @@ -@import 'mixins'; - -adf-login { - @include flex-column; -} \ No newline at end of file diff --git a/src/app/ui/overrides/_adf-sidenav-layout.scss b/src/app/ui/overrides/_adf-sidenav-layout.scss deleted file mode 100644 index cdec99c34..000000000 --- a/src/app/ui/overrides/_adf-sidenav-layout.scss +++ /dev/null @@ -1,18 +0,0 @@ -@import 'mixins'; - -adf-sidenav-layout { - @include flex-column; - - .mat-drawer-content { - @include flex-column; - overflow: auto; - } -} - -.sidenav-layout { - @include flex-column; -} - -.mat-drawer-content>div, .mat-drawer-content>div>div { - @include flex-column; -} diff --git a/src/app/ui/overrides/_adf-viewer-more-actions.scss b/src/app/ui/overrides/_adf-viewer-more-actions.scss deleted file mode 100644 index fc9a7cc62..000000000 --- a/src/app/ui/overrides/_adf-viewer-more-actions.scss +++ /dev/null @@ -1,18 +0,0 @@ -@mixin viewer-more-actions-component-theme($theme) { - $primary: map-get($theme, primary); - $accent: map-get($theme, accent); - $background: map-get($theme, background); - - .adf-viewer-more-actions { - @include angular-material-theme($theme); - - .toolbar__option--active { - color: mat-color($accent) !important; - } - - .toolbar__option--default { - color: mat-color($primary, .87) !important; - } - } - -} diff --git a/src/app/ui/overrides/_adf-viewer.scss b/src/app/ui/overrides/_adf-viewer.scss deleted file mode 100644 index b8e549685..000000000 --- a/src/app/ui/overrides/_adf-viewer.scss +++ /dev/null @@ -1,6 +0,0 @@ - -@import 'mixins'; - -.adf-viewer-content>div { - min-height: 0px !important; -} \ No newline at end of file diff --git a/src/app/ui/overrides/_alfresco-document-list.scss b/src/app/ui/overrides/_alfresco-document-list.scss deleted file mode 100644 index 43846d3a4..000000000 --- a/src/app/ui/overrides/_alfresco-document-list.scss +++ /dev/null @@ -1,101 +0,0 @@ -@import 'variables'; -@import 'mixins'; - -adf-document-list { - @include flex-column; - background-color: white; // TODO: remove when ADF 2.4.0 is out. -} - -.adf-document-list--loading { - .adf-data-table { - @include flex-column; - justify-content: center; - align-items: center; - } - - .adf-datatable-table-cell { - border: none !important; - } -} - -adf-datatable { - @include flex-column; - overflow-y: scroll; -} - -.adf-data-table { - border: none !important; - - .adf-datatable-header, .adf-datatable-row, .adf-data-table-cell { - color: $alfresco-secondary-text-color; - &:focus { - outline: none !important; - } - } - - .adf-datatable-table-cell-header:focus { - outline: none !important; - } - - .adf-datatable-body .adf-datatable-row { - &:hover, &:focus { - background-color: $alfresco-app-color--hue-1; - } - - &.is-selected, &.is-selected:hover { - background-color: $alfresco-app-color--hue-1; - } - } - - .adf-data-table-cell, .adf-datatable-header { - width: 100%; - text-align: left; - } - - .adf-datatable-body .adf-data-table-cell--image { - padding-left: 24px; - padding-right: 0; - width: 10px; - } - - .adf-data-table-cell--ellipsis .cell-value, - .adf-data-table-cell--ellipsis__name .cell-value { - display: flex; - align-items: center; - } - - .adf-data-table-cell--ellipsis .adf-datatable-cell, - .adf-data-table-cell--ellipsis__name .adf-datatable-cell { - white-space: nowrap; - display: block; - overflow: hidden; - text-overflow: ellipsis; - } - - .adf-data-table-cell--ellipsis .adf-datatable-cell { - max-width: 7vw; - } - - .adf-data-table-cell--ellipsis__name .adf-datatable-cell { - position: absolute; - max-width: calc(100% - 2em); - } - - .adf-datatable-row:last-child .adf-datatable-table-cell { - border-bottom: 1px solid rgba(0, 0, 0, 0.07); - } -} - -.adf-document-list--empty { - .adf-data-table { - @include flex-column; - justify-content: center; - align-items: center; - } - - .adf-data-table .adf-datatable-row:hover, - .adf-data-table .adf-datatable-row:focus { - background-color: unset; - cursor: default; - } -} diff --git a/src/app/ui/overrides/_alfresco-upload-button.scss b/src/app/ui/overrides/_alfresco-upload-button.scss deleted file mode 100644 index e3dbbfd00..000000000 --- a/src/app/ui/overrides/_alfresco-upload-button.scss +++ /dev/null @@ -1,44 +0,0 @@ -@import '../_variables.scss'; - -adf-upload-button { - .mat-raised-button.mat-primary { - width: 100%; - border-radius: 0; - text-align: left; - line-height: 48px; - box-shadow: none; - transform: none; - transition: unset; - background-color: $alfresco-white; - } - - .mat-raised-button.mat-primary:hover:not([disabled]) { - background-color: rgba(0, 0, 0, 0.04); - } - - .mat-raised-button.mat-primary[disabled] { - background: none; - } - - .mat-raised-button.mat-primary[disabled] label { - color: rgba(0, 0, 0, 0.38); - } - - .mat-raised-button:not([disabled]):active { - box-shadow: none; - } - - mat-icon { - color: rgba(0, 0, 0, 0.54); - } - - label { - text-transform: capitalize; - font-family: Muli; - font-size: 16px; - font-weight: normal; - text-align: left; - margin-left: 18px; - color: $alfresco-primary-text-color; - } -} diff --git a/src/app/ui/overrides/_alfresco-upload-dialog.scss b/src/app/ui/overrides/_alfresco-upload-dialog.scss deleted file mode 100644 index 76d9a1382..000000000 --- a/src/app/ui/overrides/_alfresco-upload-dialog.scss +++ /dev/null @@ -1,31 +0,0 @@ -@import 'variables'; - -$alfresco-primary-accent--hue-3: #ff6d00; -$alfresco-warn-color--hue-3: #d50000; -$alfresco-dark-color--hue-3: #546e7a; - -.upload-dialog { - z-index: 999; -} - -.adf-file-uploading-row { - &__status { - &--done { - color: $alfresco-app-color--default !important; - } - - &--error { - color: $alfresco-primary-accent--hue-3 !important; - } - } - - &__action { - &--cancel { - color: $alfresco-warn-color--hue-3 !important; - } - - &--remove { - color: $alfresco-dark-color--hue-3 !important; - } - } -} diff --git a/src/app/ui/overrides/_alfresco-upload-drag-area.scss b/src/app/ui/overrides/_alfresco-upload-drag-area.scss deleted file mode 100644 index afafe9d43..000000000 --- a/src/app/ui/overrides/_alfresco-upload-drag-area.scss +++ /dev/null @@ -1,76 +0,0 @@ -@import 'mixins'; -@import 'variables.scss'; - -@mixin file-draggable__input-focus { - color: $alfresco-secondary-text-color !important; - border: 1px solid $alfresco-app-color--default !important; - margin-left: 0 !important; -} - -adf-upload-drag-area { - @include flex-column; - - .upload-border { - @include flex-column; - - vertical-align: unset; - text-align: unset; - } -} - -adf-upload-drag-area:first-child { - & > div { - adf-upload-drag-area { - .file-draggable__input-focus { - @include file-draggable__input-focus; - } - } - } - - .upload-border { - vertical-align: inherit !important; - text-align: inherit !important; - } - - .file-draggable__input-focus { - color: none !important; - border: none !important; - margin-left: 0 !important; - - adf-upload-drag-area { - & > div { - @include file-draggable__input-focus; - } - } - } -} - -adf-upload-drag-area { - .file-draggable__input-focus { - adf-document-list { - background: $alfresco-app-color--hue-1; - - adf-datatable > table { - background: inherit; - } - } - } - - .adf-upload__dragging { - background: $alfresco-app-color--hue-1; - color: $alfresco-secondary-text-color !important; - } - - .adf-upload__dragging td { - border-top: 1px solid $alfresco-app-color--default !important; - border-bottom: 1px solid $alfresco-app-color--default !important; - - &:first-child { - border-left: 1px solid $alfresco-app-color--default !important; - } - - &:last-child { - border-right: 1px solid $alfresco-app-color--default !important; - } - } -} diff --git a/src/app/ui/overrides/_breadcrumb.scss b/src/app/ui/overrides/_breadcrumb.scss deleted file mode 100644 index 010468714..000000000 --- a/src/app/ui/overrides/_breadcrumb.scss +++ /dev/null @@ -1,10 +0,0 @@ -@import 'variables'; - -.adf-breadcrumb { - color: $alfresco-secondary-text-color; - width: 0; - - &-item:first-child:nth-last-child(1) { - opacity: 1; - } -} diff --git a/src/app/ui/overrides/_toolbar.scss b/src/app/ui/overrides/_toolbar.scss deleted file mode 100644 index a9bbe406b..000000000 --- a/src/app/ui/overrides/_toolbar.scss +++ /dev/null @@ -1,47 +0,0 @@ -@import 'variables'; - -.adf-toolbar { - .mat-toolbar-single-row { - padding: 0 14px; - height: 48px; - } - - &.inline { - .mat-toolbar { - border: none !important; - padding: 0; - } - } -} - -@mixin toolbar-component-theme($theme) { - $primary: map-get($theme, primary); - $accent: map-get($theme, accent); - $background: map-get($theme, background); - - .adf-toolbar { - @include angular-material-theme($theme); - - &.inline { - .mat-toolbar { - background-color: mat-color($background, background); - } - } - } - - .secondary-options { - @include angular-material-theme($theme); - - .toolbar__option--active { - color: mat-color($accent) !important; - } - - .toolbar__option--default { - color: mat-color($primary, .87) !important; - } - - button span { - color: mat-color($primary, .87); - } - } -} diff --git a/src/app/ui/overrides/adf-content-node-selector.theme.scss b/src/app/ui/overrides/adf-content-node-selector.theme.scss new file mode 100644 index 000000000..1a8dd5c82 --- /dev/null +++ b/src/app/ui/overrides/adf-content-node-selector.theme.scss @@ -0,0 +1,18 @@ +@mixin adf-content-node-selector-theme($theme) { + + adf-content-node-selector { + .adf-content-node-selector-panel { + .cell-container { + display: flex !important; + align-items: center; + } + } + + .adf-content-node-selector-content-breadcrumb { + .adf-current-folder { + font-weight: 600; + } + } + } + +} diff --git a/src/app/ui/overrides/adf-document-list.theme.scss b/src/app/ui/overrides/adf-document-list.theme.scss new file mode 100644 index 000000000..799a25c82 --- /dev/null +++ b/src/app/ui/overrides/adf-document-list.theme.scss @@ -0,0 +1,97 @@ +@import 'mixins'; + +@mixin adf-document-list-theme($theme) { + $foreground: map-get($theme, foreground); + $background: map-get($theme, background); + $primary: map-get($theme, primary); + + adf-document-list { + @include flex-column; + background-color: mat-color($background, card); + } + + adf-datatable { + @include flex-column; + overflow-y: scroll; + } + + .adf-data-table { + border: none !important; + + .adf-datatable-selected > svg { + // fill: mat-color($primary); + fill: #2196f3; + } + + .adf-datatable-header, .adf-datatable-row, .adf-data-table-cell { + color: mat-color($foreground, text, 0.54); + &:focus { + outline: none !important; + } + } + + .adf-datatable-table-cell-header:focus { + outline: none !important; + } + + .adf-datatable-body .adf-datatable-row { + &:hover, &:focus { + background-color: #e0f7fa; + } + + &.is-selected, &.is-selected:hover { + background-color: #e0f7fa; + } + } + + .adf-data-table-cell, .adf-datatable-header { + width: 100%; + text-align: left; + } + + .adf-datatable-body .adf-data-table-cell--image { + padding-left: 24px; + padding-right: 0; + width: 10px; + } + + .adf-data-table-cell--ellipsis .cell-value, + .adf-data-table-cell--ellipsis__name .cell-value { + display: flex; + align-items: center; + } + + .adf-data-table-cell--ellipsis .adf-datatable-cell, + .adf-data-table-cell--ellipsis__name .adf-datatable-cell { + white-space: nowrap; + display: block; + overflow: hidden; + text-overflow: ellipsis; + } + + .adf-data-table-cell--ellipsis .adf-datatable-cell { + max-width: 7vw; + } + + .adf-data-table-cell--ellipsis__name .adf-datatable-cell { + position: absolute; + max-width: calc(100% - 2em); + } + + .adf-datatable-row:last-child .adf-datatable-table-cell { + border-bottom: 1px solid mat-color($foreground, text, 0.07); + } + + &.adf-data-table--empty { + .adf-datatable-row { + &:hover, &:focus { + background-color: unset; + } + + .adf-datatable-table-cell { + border: none + } + } + } + } +} diff --git a/src/app/ui/overrides/adf-info-drawer.theme.scss b/src/app/ui/overrides/adf-info-drawer.theme.scss new file mode 100644 index 000000000..7cf3146e6 --- /dev/null +++ b/src/app/ui/overrides/adf-info-drawer.theme.scss @@ -0,0 +1,59 @@ +@mixin adf-info-drawer-theme($theme) { + $foreground: map-get($theme, foreground); + $accent: map-get($theme, accent); + $icon-size: 48px; + + .adf-info-drawer { + .adf-info-drawer-layout { + height: 100%; + + .mat-tab-label { + color: mat-color($accent); + } + + .adf-info-drawer-layout-content .adf-info-drawer-tabs .mat-tab-body-content { + .adf-manage-versions-empty, + .adf-manage-versions-no-permission { + margin: 24px; + color: mat-color($foreground, text, 0.54); + text-align: justify; + display: flex; + flex-direction: column; + + &-icon { + width: $icon-size; + height: $icon-size; + font-size: $icon-size; + margin: auto; + } + } + } + } + + .adf-version-list-container { + .adf-version-list { + height: auto; + } + + .mat-list .mat-list-item { + &.mat-3-line { + display: flex; + align-items: center; + height: 100%; + min-height: 88px; + } + + .mat-list-item-content { + padding-top: 10px; + padding-bottom: 10px; + width: 100%; + } + + .mat-line.adf-version-list-item-comment { + overflow: visible; + white-space: pre-wrap; + } + } + } + } +} diff --git a/src/app/ui/overrides/adf-pagination.theme.scss b/src/app/ui/overrides/adf-pagination.theme.scss new file mode 100644 index 000000000..47060f65b --- /dev/null +++ b/src/app/ui/overrides/adf-pagination.theme.scss @@ -0,0 +1,7 @@ +@mixin adf-pagination-theme($theme) { + .adf-pagination { + &.adf-pagination__empty { + display: none; + } + } +} diff --git a/src/app/ui/overrides/adf-search-filter.theme.scss b/src/app/ui/overrides/adf-search-filter.theme.scss new file mode 100644 index 000000000..2c3320180 --- /dev/null +++ b/src/app/ui/overrides/adf-search-filter.theme.scss @@ -0,0 +1,16 @@ +@mixin adf-search-filter-theme($theme) { + $foreground: map-get($theme, foreground); + + .adf-search-filter { + .mat-expansion-panel-header-title { + font-size: 14px; + color: mat-color($foreground, text, 0.87); + } + + .mat-checkbox-label, + .mat-radio-label { + color: mat-color($foreground, text, 0.54); + } + } + +} diff --git a/src/app/ui/overrides/adf-search-sorting-picker.theme.scss b/src/app/ui/overrides/adf-search-sorting-picker.theme.scss new file mode 100644 index 000000000..f83b1b64e --- /dev/null +++ b/src/app/ui/overrides/adf-search-sorting-picker.theme.scss @@ -0,0 +1,9 @@ +@mixin adf-search-sorting-picker-theme($theme) { + $foreground: map-get($theme, foreground); + + .adf-search-sorting-picker { + .mat-icon-button { + color: mat-color($foreground, text, 0.54); + } + } +} diff --git a/src/app/ui/overrides/adf-sidebar-action-menu.theme.scss b/src/app/ui/overrides/adf-sidebar-action-menu.theme.scss new file mode 100644 index 000000000..b245c1b02 --- /dev/null +++ b/src/app/ui/overrides/adf-sidebar-action-menu.theme.scss @@ -0,0 +1,70 @@ +@mixin adf-sidebar-action-menu-theme($theme) { + $foreground: map-get($theme, foreground); + $accent: map-get($theme, accent); + $primary: map-get($theme, primary); + + .adf-sidebar-action-menu { + .adf-sidebar-action-menu-button { + font-size: 12.7px; + font-weight: normal; + text-transform: uppercase; + } + } + + .mat-menu-panel.adf-sidebar-action-menu-panel { + max-width: 290px !important; + } + + .adf-sidebar-action-menu-panel { + width: 290px; + display: flex; + align-items: center; + justify-content: center; + } + + .adf-sidebar-action-menu-panel .mat-menu-content { + width: 100%; + } + + .adf-sidebar-action-menu-icon { + margin: 0; + } + + .adf-sidebar-action-menu-icon div[sidebar-menu-expand-icon] { + display: flex; + align-items: center; + justify-content: center; + } + + .adf-sidebar-action-menu { + width: 100%; + } + + .adf-sidebar-action-menu-options { + width: 100% !important; + + .mat-menu-item { + display: flex; + flex-direction: row; + align-items: center; + font-size: 14px; + color: mat-color($foreground, text, 0.54); + line-height: 48px; + box-shadow: none; + transform: none; + transition: unset; + font-weight: normal; + text-transform: capitalize; + color: mat-color($primary); + + &:hover { + color: mat-color($accent); + } + } + + .mat-menu-item[disabled], + .mat-menu-item[disabled]:hover { + color: mat-color($foreground, text, 0.38); + } + } +} diff --git a/src/app/ui/overrides/adf-sidenav-layout.theme.scss b/src/app/ui/overrides/adf-sidenav-layout.theme.scss new file mode 100644 index 000000000..06b2490e6 --- /dev/null +++ b/src/app/ui/overrides/adf-sidenav-layout.theme.scss @@ -0,0 +1,20 @@ +@import 'mixins'; + +@mixin adf-sidenav-layout-theme($theme) { + adf-sidenav-layout { + @include flex-column; + + .mat-drawer-content { + @include flex-column; + overflow: auto; + } + } + + .sidenav-layout { + @include flex-column; + } + + .mat-drawer-content>div, .mat-drawer-content>div>div { + @include flex-column; + } +} diff --git a/src/app/ui/overrides/adf-toolbar.theme.scss b/src/app/ui/overrides/adf-toolbar.theme.scss new file mode 100644 index 000000000..b2fa28225 --- /dev/null +++ b/src/app/ui/overrides/adf-toolbar.theme.scss @@ -0,0 +1,19 @@ +@mixin adf-toolbar-theme($theme) { + + .adf-toolbar { + @include angular-material-theme($theme); + + .mat-toolbar-single-row { + padding: 0 14px; + height: 48px; + } + + &.inline { + .mat-toolbar { + background-color: inherit; + border: none !important; + padding: 0; + } + } + } +} diff --git a/src/app/ui/overrides/adf-upload-button.theme.scss b/src/app/ui/overrides/adf-upload-button.theme.scss new file mode 100644 index 000000000..8ee04c650 --- /dev/null +++ b/src/app/ui/overrides/adf-upload-button.theme.scss @@ -0,0 +1,53 @@ +@mixin adf-upload-button-theme($theme) { + $foreground: map-get($theme, foreground); + $accent: map-get($theme, accent); + $primary: map-get($theme, primary); + + adf-upload-button { + .mat-raised-button.mat-primary { + width: 100%; + border-radius: 0; + text-align: left; + line-height: 48px; + box-shadow: none; + transform: none; + transition: unset; + background-color: transparent; + } + + .mat-raised-button.mat-primary:hover:not([disabled]) { + background-color: mat-color($foreground, text, 0.04); + } + + .mat-raised-button.mat-primary[disabled] { + background: none; + } + + .mat-raised-button.mat-primary[disabled] label { + color: mat-color($foreground, text, 0.38); + } + + .mat-raised-button:not([disabled]):active { + box-shadow: none; + } + + mat-icon { + color: mat-color($foreground, text, 0.54); + } + + label { + text-transform: capitalize; + font-family: Muli; + font-size: 14px; + font-weight: normal; + text-align: left; + margin-left: 12px; + color: mat-color($primary); + } + + &:hover label { + color: mat-color($accent); + opacity: inherit; + } + } +} diff --git a/src/app/ui/overrides/adf-upload-dialog.theme.scss b/src/app/ui/overrides/adf-upload-dialog.theme.scss new file mode 100644 index 000000000..1b232ee3e --- /dev/null +++ b/src/app/ui/overrides/adf-upload-dialog.theme.scss @@ -0,0 +1,31 @@ +@mixin adf-upload-dialog-theme($theme) { + $primary: map-get($theme, primary); + $accent: map-get($theme, accent); + $warn: map-get($theme, warn); + + .upload-dialog { + z-index: 999; + } + + .adf-file-uploading-row { + &__status { + &--done { + color: mat-color($accent); + } + + &--error { + color: mat-color($warn); + } + } + + &__action { + &--cancel { + color: mat-color($warn); + } + + &--remove { + color: mat-color($warn); + } + } + } +} diff --git a/src/app/ui/overrides/adf-upload-drag-area.theme.scss b/src/app/ui/overrides/adf-upload-drag-area.theme.scss new file mode 100644 index 000000000..ad5d4d35d --- /dev/null +++ b/src/app/ui/overrides/adf-upload-drag-area.theme.scss @@ -0,0 +1,83 @@ +@import 'mixins'; + +$alfresco-app-color--default: #00bcd4; + +@mixin file-draggable__input-focus($theme) { + $foreground: map-get($theme, foreground); + + color: mat-color($foreground, text, 0.54); + border: 1px solid $alfresco-app-color--default !important; + margin-left: 0 !important; +} + +@mixin adf-upload-drag-area-theme($theme) { + $foreground: map-get($theme, foreground); + + adf-upload-drag-area { + @include flex-column; + + .upload-border { + @include flex-column; + + vertical-align: unset; + text-align: unset; + } + } + + adf-upload-drag-area:first-child { + & > div { + adf-upload-drag-area { + .file-draggable__input-focus { + @include file-draggable__input-focus($theme); + } + } + } + + .upload-border { + vertical-align: inherit !important; + text-align: inherit !important; + } + + .file-draggable__input-focus { + color: none !important; + border: none !important; + margin-left: 0 !important; + + adf-upload-drag-area { + & > div { + @include file-draggable__input-focus($theme); + } + } + } + } + + adf-upload-drag-area { + .file-draggable__input-focus { + adf-document-list { + background: #e0f7fa; + + adf-datatable > table { + background: inherit; + } + } + } + + .adf-upload__dragging { + background: #e0f7fa; + color: mat-color($foreground, text, 0.54); + } + + .adf-upload__dragging td { + border-top: 1px solid $alfresco-app-color--default !important; + border-bottom: 1px solid $alfresco-app-color--default !important; + + &:first-child { + border-left: 1px solid $alfresco-app-color--default !important; + } + + &:last-child { + border-right: 1px solid $alfresco-app-color--default !important; + } + } + } +} diff --git a/src/app/ui/snackbar.scss b/src/app/ui/snackbar.scss new file mode 100644 index 000000000..929fe461f --- /dev/null +++ b/src/app/ui/snackbar.scss @@ -0,0 +1,29 @@ +@mixin snackbar-theme($theme) { + $warn: map-get($theme, warn); + $accent: map-get($theme, accent); + $primary: map-get($theme, primary); + + .error-snackbar { + background-color: mat-color($warn); + + .mat-simple-snackbar-action { + color: white; + } + } + + .warning-snackbar { + background-color: mat-color($accent); + + .mat-simple-snackbar-action { + color: white; + } + } + + .info-snackbar { + background-color: mat-color($primary); + + .mat-simple-snackbar-action { + color: white; + } + } +} diff --git a/src/app/ui/theme.scss b/src/app/ui/theme.scss index 96302e8aa..9deea7cda 100644 --- a/src/app/ui/theme.scss +++ b/src/app/ui/theme.scss @@ -5,7 +5,7 @@ @import 'custom-theme'; -@include mat-core(); +@include mat-core($alfresco-typography); $primary: mat-palette($alfresco-accent-orange); $accent: mat-palette($alfresco-ecm-blue); diff --git a/src/assets/i18n/de.json b/src/assets/i18n/de.json index 6cd4118fd..35f234fde 100644 --- a/src/assets/i18n/de.json +++ b/src/assets/i18n/de.json @@ -3,11 +3,21 @@ "LANGUAGE": "Sprache", "SIGN_IN": "Anmelden", "SIGN_OUT": "Abmelden", + "SETTINGS": { + "APPLICATION-SETTINGS": "Anwendungseinstellungen", + "REPOSITORY-SETTINGS": "Repository-Einstellungen", + "EXPERIMENTAL-FEATURES": "Testfunktionen", + "INVALID-VALUE-FORMAT": "Wert in ungültigem Format", + "REQUIRED-FIELD": "Dieses Feld ist erforderlich.", + "RESET": "Zurücksetzen", + "APPLY": "Anwenden" + }, "PREVIEW": { "TITLE": "Vorschau" }, "NEW_MENU": { "LABEL": "Neu", + "TOOLTIP": "Neue Dateien oder Ordner hinzufügen", "MENU_ITEMS": { "CREATE_FOLDER": "Ordner erstellen", "UPLOAD_FILE": "Datei hochladen", @@ -88,6 +98,15 @@ }, "ABOUT": { "TITLE": "Info" + }, + "SEARCH": { + "TITLE": "Ergebnisse durchsuchen", + "FOUND_RESULTS": "{{ number }} Ergebnisse gefunden", + "CUSTOM_ROW": { + "MODIFIED": "Bearbeitet", + "LOCATION": "Speicherort", + "SIZE": "Größe" + } } }, "ACTIONS": { @@ -104,7 +123,8 @@ "FAVORITE": "Zu Favoriten", "UNSHARE": "Freigabe aufheben", "DETAILS": "Details anzeigen", - "VERSIONS": "Versionen verwalten" + "VERSIONS": "Versionen verwalten", + "TOGGLE-SIDENAV": "Navigationsseitenleiste umschalten" }, "DIALOGS": { "CONFIRM_PURGE": { @@ -135,6 +155,7 @@ }, "MESSAGES": { "ERRORS": { + "MISSING_CONTENT": "Diese Datei oder diesen Ordner gibt es nicht mehr oder Sie verfügen nicht über die für die Anzeige nötigen Benutzerrechte.", "GENERIC": "Die Aktion war nicht erfolgreich. Versuchen Sie es noch einmal oder wenden Sie sich an Ihr IT-Team.", "CONFLICT": "Dieser Name wird bereits verwendet. Versuchen Sie es mit einem anderen Namen.", "NODE_MOVE": "Verschiebung nicht erfolgreich. Es gibt bereits eine Datei mit demselben Namen.", @@ -155,6 +176,14 @@ "LOCATION_MISSING": "{{ name }} kann nicht wiederhergestellt werden, weil es den ursprünglichen Speicherort nicht mehr gibt", "GENERIC": "Beim Wiederherstellen von {{ name }} ist ein Problem aufgetreten" } + }, + "DELETE_LIBRARY_FAILED": "Bibliothek kann nicht gelöscht werden" + }, + "UPLOAD": { + "ERROR": { + "GENERIC": "Es ist ein Problem aufgetreten.", + "CONFLICT": "Neue Version nicht hochgeladen. Es gibt bereits eine Datei mit demselben Namen.", + "500": "Beim Hochladen ist ein Problem aufgetreten." } }, "INFO": { @@ -192,7 +221,8 @@ "PLURAL": "{{ partially }} Elemente teilweise verschoben.", "FAIL": "{{ failed }} konnte nicht verschoben werden." } - } + }, + "LIBRARY_DELETED": "Bibliothek gelöscht" } }, "CONTENT_METADATA": { @@ -222,5 +252,48 @@ "EMPTY": "Wählen Sie ein Dokument aus, um die Versionen davon anzuzeigen.", "NO_PERMISSION": "Sie verfügen nicht über die nötigen Benutzerrechte, um Versionen dieser Inhalte anzuzeigen." } + }, + "SEARCH": { + "SORT": { + "RELEVANCE": "Relevanz", + "FILENAME": "Dateiname", + "TITLE": "Titel", + "MODIFIED_DATE": "Datum der Änderung", + "SIZE": "Größe", + "TYPE": "Typ", + "MODIFIER": "Bearbeiter", + "CREATE_DATE": "Datum der Erstellung" + }, + "FACET_FIELDS": { + "FILE_TYPE": "Dateityp", + "CREATOR": "Ersteller", + "MODIFIER": "Bearbeiter", + "FILE_LIBRARY": "Dateibibliothek" + }, + "CATEGORIES": { + "MODIFIED_DATE": "Datum der Änderung", + "SIZE": "Größe", + "CREATED_DATE": "Datum der Erstellung" + } + }, + "CORE": { + "METADATA": { + "ACTIONS": { + "SAVE": "Änderungen speichern", + "CANCEL": "Änderungen verwerfen" + } + } + }, + "ADF_VIEWER": { + "ACTIONS": { + "INFO": "Details anzeigen" + } + }, + "ADF_VERSION_LIST": { + "ACTIONS": { + "UPLOAD": { + "TOOLTIP": "Neue Version hochladen" + } + } } -} \ No newline at end of file +} diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json index d3f9b478e..189470c16 100644 --- a/src/assets/i18n/en.json +++ b/src/assets/i18n/en.json @@ -3,11 +3,21 @@ "LANGUAGE": "Language", "SIGN_IN": "Sign in", "SIGN_OUT": "Sign out", + "SETTINGS": { + "APPLICATION-SETTINGS": "Application Settings", + "REPOSITORY-SETTINGS": "Repository Settings", + "EXPERIMENTAL-FEATURES": "Experimental Features", + "INVALID-VALUE-FORMAT": "Invalid value format", + "REQUIRED-FIELD": "This field is required", + "RESET": "Reset", + "APPLY": "Apply" + }, "PREVIEW": { "TITLE": "Preview" }, "NEW_MENU": { "LABEL": "New", + "TOOLTIP": "Add new files or folders", "MENU_ITEMS": { "CREATE_FOLDER": "Create folder", "UPLOAD_FILE": "Upload file", @@ -88,6 +98,15 @@ }, "ABOUT": { "TITLE": "About" + }, + "SEARCH": { + "TITLE": "Search Results", + "FOUND_RESULTS": "{{ number }} results found", + "CUSTOM_ROW": { + "MODIFIED": "Modified", + "LOCATION": "Location", + "SIZE": "Size" + } } }, "ACTIONS": { @@ -104,7 +123,8 @@ "FAVORITE": "Favorite", "UNSHARE": "Unshare", "DETAILS": "View details", - "VERSIONS": "Manage Versions" + "VERSIONS": "Manage Versions", + "TOGGLE-SIDENAV": "Toggle side navigation bar" }, "DIALOGS": { "CONFIRM_PURGE": { @@ -135,6 +155,7 @@ }, "MESSAGES": { "ERRORS":{ + "MISSING_CONTENT": "This file or folder no longer exists or you don't have permission to view it.", "GENERIC": "The action was unsuccessful. Try again or contact your IT Team.", "CONFLICT": "This name is already in use, try a different name.", "NODE_MOVE": "Move unsuccessful, a file with the same name already exists.", @@ -155,6 +176,14 @@ "LOCATION_MISSING": "Can't restore {{ name }}, the original location no longer exists", "GENERIC": "There was a problem restoring {{ name }}" } + }, + "DELETE_LIBRARY_FAILED": "Cannot delete the library" + }, + "UPLOAD": { + "ERROR": { + "GENERIC": "There was a problem", + "CONFLICT": "New version not uploaded, another file with the same name already exists", + "500": "There was a problem while uploading" } }, "INFO": { @@ -192,7 +221,8 @@ "PLURAL": "Partially moved {{ partially }} items.", "FAIL": "{{ failed }} couldn't be moved." } - } + }, + "LIBRARY_DELETED": "Library deleted" } }, "CONTENT_METADATA": { @@ -222,5 +252,48 @@ "EMPTY": "Please choose a document to see the versions of it.", "NO_PERMISSION": "You don't have permission to manage the versions of this content." } + }, + "SEARCH": { + "SORT": { + "RELEVANCE": "Relevance", + "FILENAME": "Filename", + "TITLE": "Title", + "MODIFIED_DATE": "Modified Date", + "SIZE": "Size", + "TYPE": "Type", + "MODIFIER": "Modifier", + "CREATE_DATE": "Created date" + }, + "FACET_FIELDS": { + "FILE_TYPE": "File Type", + "CREATOR": "Creator", + "MODIFIER": "Modifier", + "FILE_LIBRARY": "File Library" + }, + "CATEGORIES": { + "MODIFIED_DATE": "Modified date", + "SIZE": "Size", + "CREATED_DATE": "Created Date" + } + }, + "CORE": { + "METADATA": { + "ACTIONS": { + "SAVE": "Save changes", + "CANCEL": "Discard changes" + } + } + }, + "ADF_VIEWER": { + "ACTIONS": { + "INFO": "View details" + } + }, + "ADF_VERSION_LIST": { + "ACTIONS": { + "UPLOAD": { + "TOOLTIP": "Upload new version" + } + } } } diff --git a/src/assets/i18n/es.json b/src/assets/i18n/es.json index 1f1c46f73..1552320c6 100644 --- a/src/assets/i18n/es.json +++ b/src/assets/i18n/es.json @@ -3,11 +3,21 @@ "LANGUAGE": "Idioma", "SIGN_IN": "Iniciar sesión", "SIGN_OUT": "Cerrar sesión", + "SETTINGS": { + "APPLICATION-SETTINGS": "Configuración de la aplicación", + "REPOSITORY-SETTINGS": "Configuración del repositorio", + "EXPERIMENTAL-FEATURES": "Funciones experimentales", + "INVALID-VALUE-FORMAT": "Formato de valor no válido", + "REQUIRED-FIELD": "Este campo es obligatorio", + "RESET": "Reiniciar", + "APPLY": "Aplicar" + }, "PREVIEW": { "TITLE": "Vista previa" }, "NEW_MENU": { "LABEL": "Nuevo", + "TOOLTIP": "Añadir nuevos ficheros o carpetas", "MENU_ITEMS": { "CREATE_FOLDER": "Crear carpeta", "UPLOAD_FILE": "Añadir fichero", @@ -88,6 +98,15 @@ }, "ABOUT": { "TITLE": "Acerca de" + }, + "SEARCH": { + "TITLE": "Resultados de la búsqueda", + "FOUND_RESULTS": "{{ number }} resultados encontrados", + "CUSTOM_ROW": { + "MODIFIED": "Modificado", + "LOCATION": "Ubicación", + "SIZE": "Tamaño" + } } }, "ACTIONS": { @@ -104,7 +123,8 @@ "FAVORITE": "Favorito", "UNSHARE": "No compartir", "DETAILS": "Ver los detalles", - "VERSIONS": "Gestionar versiones" + "VERSIONS": "Gestionar versiones", + "TOGGLE-SIDENAV": "Alternar barra de navegación lateral" }, "DIALOGS": { "CONFIRM_PURGE": { @@ -135,6 +155,7 @@ }, "MESSAGES": { "ERRORS": { + "MISSING_CONTENT": "Este fichero o carpeta ya no existe o no tiene permiso para verlo.", "GENERIC": "La acción no ha sido satisfactoria. Vuelva a intentarlo o póngase en contacto con el equipo de TI.", "CONFLICT": "Este nombre ya está en uso; pruebe con un nombre diferente.", "NODE_MOVE": "Error al mover el fichero; ya existe un fichero con el mismo nombre.", @@ -155,6 +176,14 @@ "LOCATION_MISSING": "No se ha podido restaurar {{ name }}, la ubicación original ya no existe", "GENERIC": "Error al restaurar {{ name }}" } + }, + "DELETE_LIBRARY_FAILED": "No se puede eliminar la biblioteca" + }, + "UPLOAD": { + "ERROR": { + "GENERIC": "Se ha producido un problema", + "CONFLICT": "No se ha cargado la nueva versión, ya existe otro fichero con el mismo nombre", + "500": "Se ha producido un problema durante la carga" } }, "INFO": { @@ -192,7 +221,8 @@ "PLURAL": "Se han movido parcialmente {{ partially }} elementos.", "FAIL": "{{ failed }} no se ha podido mover." } - } + }, + "LIBRARY_DELETED": "Biblioteca eliminada" } }, "CONTENT_METADATA": { @@ -222,5 +252,48 @@ "EMPTY": "Por favor, seleccione un documento para ver sus versiones.", "NO_PERMISSION": "No tiene permiso para gestionar las versiones de este contenido." } + }, + "SEARCH": { + "SORT": { + "RELEVANCE": "Relevancia", + "FILENAME": "Nombre de fichero", + "TITLE": "Título", + "MODIFIED_DATE": "Fecha de modificación", + "SIZE": "Tamaño", + "TYPE": "Tipo", + "MODIFIER": "Modificador", + "CREATE_DATE": "Fecha de creación" + }, + "FACET_FIELDS": { + "FILE_TYPE": "Tipo de fichero", + "CREATOR": "Creador", + "MODIFIER": "Modificador", + "FILE_LIBRARY": "Biblioteca de ficheros" + }, + "CATEGORIES": { + "MODIFIED_DATE": "Fecha de modificación", + "SIZE": "Tamaño", + "CREATED_DATE": "Fecha de creación" + } + }, + "CORE": { + "METADATA": { + "ACTIONS": { + "SAVE": "Guardar cambios", + "CANCEL": "Descartar cambios" + } + } + }, + "ADF_VIEWER": { + "ACTIONS": { + "INFO": "Ver los detalles" + } + }, + "ADF_VERSION_LIST": { + "ACTIONS": { + "UPLOAD": { + "TOOLTIP": "Cargar nueva versión" + } + } } -} \ No newline at end of file +} diff --git a/src/assets/i18n/fr.json b/src/assets/i18n/fr.json index 5bb55010d..17a776dac 100644 --- a/src/assets/i18n/fr.json +++ b/src/assets/i18n/fr.json @@ -3,11 +3,21 @@ "LANGUAGE": "Langue", "SIGN_IN": "Connexion", "SIGN_OUT": "Déconnexion", + "SETTINGS": { + "APPLICATION-SETTINGS": "Paramètres de l'application", + "REPOSITORY-SETTINGS": "Paramètres de l'entrepôt", + "EXPERIMENTAL-FEATURES": "Fonctionnalités expérimentales", + "INVALID-VALUE-FORMAT": "Format de valeur non valide", + "REQUIRED-FIELD": "Ce champ doit être renseigné", + "RESET": "Réinitialiser", + "APPLY": "Appliquer" + }, "PREVIEW": { "TITLE": "Aperçu" }, "NEW_MENU": { "LABEL": "Nouveau", + "TOOLTIP": "Ajouter de nouveaux fichiers ou dossiers", "MENU_ITEMS": { "CREATE_FOLDER": "Créer un dossier", "UPLOAD_FILE": "Importer le fichier", @@ -88,6 +98,15 @@ }, "ABOUT": { "TITLE": "A propos de" + }, + "SEARCH": { + "TITLE": "Résultats de la recherche", + "FOUND_RESULTS": "{{ number }} résultats trouvés", + "CUSTOM_ROW": { + "MODIFIED": "Modifié", + "LOCATION": "Emplacement", + "SIZE": "Taille" + } } }, "ACTIONS": { @@ -104,7 +123,8 @@ "FAVORITE": "Favori", "UNSHARE": "Ne pas partager", "DETAILS": "Afficher les détails", - "VERSIONS": "Gérer les versions" + "VERSIONS": "Gérer les versions", + "TOGGLE-SIDENAV": "Activer/désactiver la barre de navigation latérale" }, "DIALOGS": { "CONFIRM_PURGE": { @@ -135,6 +155,7 @@ }, "MESSAGES": { "ERRORS": { + "MISSING_CONTENT": "Ce fichier ou dossier n'existe plus ou vous n'avez pas les droits pour le consulter.", "GENERIC": "L'action a échoué. Réessayez ou contactez le service informatique.", "CONFLICT": "Ce nom est déjà utilisé, essayez avec un nom différent.", "NODE_MOVE": "Echec du déplacement, un fichier du même nom existe déjà.", @@ -155,6 +176,14 @@ "LOCATION_MISSING": "Impossible de restaurer {{ name }}, l'emplacement d'origine n'existe plus", "GENERIC": "Un problème est survenu pendant la restauration de {{ name }}" } + }, + "DELETE_LIBRARY_FAILED": "Impossible de supprimer la bibliothèque" + }, + "UPLOAD": { + "ERROR": { + "GENERIC": "Une erreur est survenue", + "CONFLICT": "La nouvelle version n'a pas été importée, un autre fichier du même nom existe déjà", + "500": "Une erreur est survenue lors de l'importation" } }, "INFO": { @@ -192,7 +221,8 @@ "PLURAL": "{{ partially }} éléments partiellement déplacés.", "FAIL": "{{ failed }} n'a/n'ont pas pu être déplacé(s)." } - } + }, + "LIBRARY_DELETED": "Bibliothèque supprimée" } }, "CONTENT_METADATA": { @@ -222,5 +252,48 @@ "EMPTY": "Choisissez un document pour en voir les versions.", "NO_PERMISSION": "Vous n'êtes pas autorisé(e) à gérer les versions de ce contenu." } + }, + "SEARCH": { + "SORT": { + "RELEVANCE": "Pertinence", + "FILENAME": "Nom de fichier", + "TITLE": "Titre", + "MODIFIED_DATE": "Date de Modification", + "SIZE": "Taille", + "TYPE": "Type", + "MODIFIER": "Modificateur", + "CREATE_DATE": "Date de création" + }, + "FACET_FIELDS": { + "FILE_TYPE": "Type de fichier", + "CREATOR": "Créateur", + "MODIFIER": "Modificateur", + "FILE_LIBRARY": "Bibliothèque de fichiers" + }, + "CATEGORIES": { + "MODIFIED_DATE": "Date de Modification", + "SIZE": "Taille", + "CREATED_DATE": "Date de création" + } + }, + "CORE": { + "METADATA": { + "ACTIONS": { + "SAVE": "Enregistrer les modifications", + "CANCEL": "Ignorer les modifications" + } + } + }, + "ADF_VIEWER": { + "ACTIONS": { + "INFO": "Afficher les détails" + } + }, + "ADF_VERSION_LIST": { + "ACTIONS": { + "UPLOAD": { + "TOOLTIP": "Importer une nouvelle version" + } + } } -} \ No newline at end of file +} diff --git a/src/assets/i18n/it.json b/src/assets/i18n/it.json index 0a9c58603..c77b64fd0 100644 --- a/src/assets/i18n/it.json +++ b/src/assets/i18n/it.json @@ -3,11 +3,21 @@ "LANGUAGE": "Lingua", "SIGN_IN": "Accedi", "SIGN_OUT": "Disconnetti", + "SETTINGS": { + "APPLICATION-SETTINGS": "Impostazioni applicazione", + "REPOSITORY-SETTINGS": "Impostazioni repository", + "EXPERIMENTAL-FEATURES": "Funzioni sperimentali", + "INVALID-VALUE-FORMAT": "Formato valore non valido", + "REQUIRED-FIELD": "Questo campo è obbligatorio", + "RESET": "Reimposta", + "APPLY": "Applica" + }, "PREVIEW": { "TITLE": "Anteprima" }, "NEW_MENU": { "LABEL": "Nuovo", + "TOOLTIP": "Aggiungi nuovi file o cartelle", "MENU_ITEMS": { "CREATE_FOLDER": "Crea cartella", "UPLOAD_FILE": "Carica file", @@ -88,6 +98,15 @@ }, "ABOUT": { "TITLE": "Informazioni su" + }, + "SEARCH": { + "TITLE": "Risultati della ricerca", + "FOUND_RESULTS": "{{ number }} risultati trovati", + "CUSTOM_ROW": { + "MODIFIED": "Modificato", + "LOCATION": "Località", + "SIZE": "Dimensione" + } } }, "ACTIONS": { @@ -104,7 +123,8 @@ "FAVORITE": "Preferito", "UNSHARE": "Rimuovi condivisione", "DETAILS": "Visualizza dettagli", - "VERSIONS": "Gestione versioni" + "VERSIONS": "Gestione versioni", + "TOGGLE-SIDENAV": "Attiva/disattiva barra di navigazione laterale" }, "DIALOGS": { "CONFIRM_PURGE": { @@ -135,6 +155,7 @@ }, "MESSAGES": { "ERRORS": { + "MISSING_CONTENT": "Il file o la cartella non esistono più o non si dispone delle autorizzazioni per visualizzarli.", "GENERIC": "Azione non eseguita correttamente. Riprovare o contattare il team IT.", "CONFLICT": "Nome già in uso, provare un nome diverso.", "NODE_MOVE": "Spostamento non eseguito correttamente, file con lo stesso nome già esistente.", @@ -155,6 +176,14 @@ "LOCATION_MISSING": "Impossibile ripristinare {{ name }}. Il percorso originale non esiste più.", "GENERIC": "Si è verificato un problema durante il ripristino di {{ name }}" } + }, + "DELETE_LIBRARY_FAILED": "Impossibile eliminare la raccolta" + }, + "UPLOAD": { + "ERROR": { + "GENERIC": "Si è verificato un problema", + "CONFLICT": "Nuova versione non caricata. Esiste già un fil con lo stesso nome.", + "500": "Si è verificato un problema durante il caricamento" } }, "INFO": { @@ -192,7 +221,8 @@ "PLURAL": "Elementi {{ partially }} parzialmente spostati.", "FAIL": "Impossibile spostare {{ failed }}." } - } + }, + "LIBRARY_DELETED": "Raccolta eliminata" } }, "CONTENT_METADATA": { @@ -222,5 +252,48 @@ "EMPTY": "Selezionare un documento per vedere le versioni.", "NO_PERMISSION": "Non hai il permesso per gestire le versioni di questo documento." } + }, + "SEARCH": { + "SORT": { + "RELEVANCE": "Pertinenza", + "FILENAME": "Nome file", + "TITLE": "Titolo", + "MODIFIED_DATE": "Data di modifica", + "SIZE": "Dimensione", + "TYPE": "Tipo", + "MODIFIER": "Modificatore", + "CREATE_DATE": "Data di creazione" + }, + "FACET_FIELDS": { + "FILE_TYPE": "Tipo di file", + "CREATOR": "Creatore", + "MODIFIER": "Modificatore", + "FILE_LIBRARY": "Raccolta di file" + }, + "CATEGORIES": { + "MODIFIED_DATE": "Data di modifica", + "SIZE": "Dimensione", + "CREATED_DATE": "Data di creazione" + } + }, + "CORE": { + "METADATA": { + "ACTIONS": { + "SAVE": "Salva modifiche", + "CANCEL": "Ignora modifiche" + } + } + }, + "ADF_VIEWER": { + "ACTIONS": { + "INFO": "Visualizza dettagli" + } + }, + "ADF_VERSION_LIST": { + "ACTIONS": { + "UPLOAD": { + "TOOLTIP": "Utilizza nuova versione" + } + } } -} \ No newline at end of file +} diff --git a/src/assets/i18n/ja.json b/src/assets/i18n/ja.json index 8e30017ef..fff2ce951 100644 --- a/src/assets/i18n/ja.json +++ b/src/assets/i18n/ja.json @@ -3,11 +3,21 @@ "LANGUAGE": "言語", "SIGN_IN": "サインイン", "SIGN_OUT": "サインアウト", + "SETTINGS": { + "APPLICATION-SETTINGS": "アプリケーションの設定", + "REPOSITORY-SETTINGS": "リポジトリの設定", + "EXPERIMENTAL-FEATURES": "試験的な機能", + "INVALID-VALUE-FORMAT": "無効な値形式", + "REQUIRED-FIELD": "このフィールドは必須です", + "RESET": "リセット", + "APPLY": "適用" + }, "PREVIEW": { "TITLE": "プレビュー" }, "NEW_MENU": { "LABEL": "新規", + "TOOLTIP": "新しいフィールドまたはフォルダを追加します", "MENU_ITEMS": { "CREATE_FOLDER": "フォルダの作成", "UPLOAD_FILE": "ファイルのアップロード", @@ -88,6 +98,15 @@ }, "ABOUT": { "TITLE": "バージョン情報" + }, + "SEARCH": { + "TITLE": "検索結果", + "FOUND_RESULTS": "{{ number }} 件見つかりました", + "CUSTOM_ROW": { + "MODIFIED": "変更日", + "LOCATION": "場所", + "SIZE": "サイズ" + } } }, "ACTIONS": { @@ -104,7 +123,8 @@ "FAVORITE": "お気に入り", "UNSHARE": "共有の解除", "DETAILS": "詳細の表示", - "VERSIONS": "バージョンの管理" + "VERSIONS": "バージョンの管理", + "TOGGLE-SIDENAV": "サイドナビゲーションバーの切り替え" }, "DIALOGS": { "CONFIRM_PURGE": { @@ -135,6 +155,7 @@ }, "MESSAGES": { "ERRORS": { + "MISSING_CONTENT": "このファイルまたはフォルダがなくなったか、表示する権限がありません。", "GENERIC": "処理が失敗しました。もう一度操作をやり直すか、IT 担当者に連絡してください。", "CONFLICT": "この名前は既に使用されています。別の名前を使用してください。", "NODE_MOVE": "移動できません。同じ名前のファイルが既に存在します。", @@ -155,6 +176,14 @@ "LOCATION_MISSING": "{{ name }} を復元できません。元の場所が削除されています。", "GENERIC": "{{ name }} の復元中に問題が発生しました" } + }, + "DELETE_LIBRARY_FAILED": "このライブラリは削除できません" + }, + "UPLOAD": { + "ERROR": { + "GENERIC": "問題が発生しました", + "CONFLICT": "新しいバージョンはアップロードされていません。同じ名前のファイルが既に存在します", + "500": "アップロード中に問題が発生しました" } }, "INFO": { @@ -192,7 +221,8 @@ "PLURAL": "一部のアイテム ({{ partially }} 件) を移動しました。", "FAIL": "{{ failed }} 件を移動できませんでした。" } - } + }, + "LIBRARY_DELETED": "ライブラリが削除されました" } }, "CONTENT_METADATA": { @@ -222,5 +252,48 @@ "EMPTY": "バージョンを表示する文書を選択してください。", "NO_PERMISSION": "このコンテンツのバージョンを管理するための権限がありません。" } + }, + "SEARCH": { + "SORT": { + "RELEVANCE": "関連性", + "FILENAME": "ファイル名", + "TITLE": "タイトル", + "MODIFIED_DATE": "変更日", + "SIZE": "サイズ", + "TYPE": "種類", + "MODIFIER": "変更者", + "CREATE_DATE": "作成日" + }, + "FACET_FIELDS": { + "FILE_TYPE": "ファイルの種類", + "CREATOR": "作成者", + "MODIFIER": "変更者", + "FILE_LIBRARY": "ファイルライブラリ" + }, + "CATEGORIES": { + "MODIFIED_DATE": "変更日", + "SIZE": "サイズ", + "CREATED_DATE": "作成日" + } + }, + "CORE": { + "METADATA": { + "ACTIONS": { + "SAVE": "変更を保存する", + "CANCEL": "変更を破棄する" + } + } + }, + "ADF_VIEWER": { + "ACTIONS": { + "INFO": "詳細の表示" + } + }, + "ADF_VERSION_LIST": { + "ACTIONS": { + "UPLOAD": { + "TOOLTIP": "新しいバージョンのアップロード" + } + } } -} \ No newline at end of file +} diff --git a/src/assets/i18n/nb.json b/src/assets/i18n/nb.json index f1f3b3376..a212ebcb4 100644 --- a/src/assets/i18n/nb.json +++ b/src/assets/i18n/nb.json @@ -3,11 +3,21 @@ "LANGUAGE": "Språk", "SIGN_IN": "Logg på", "SIGN_OUT": "Logg ut", + "SETTINGS": { + "APPLICATION-SETTINGS": "Programinnstillinger", + "REPOSITORY-SETTINGS": "Databaseinnstillinger", + "EXPERIMENTAL-FEATURES": "Eksperimentelle funksjoner", + "INVALID-VALUE-FORMAT": "Ugyldig verdiformat", + "REQUIRED-FIELD": "Dette feltet er obligatorisk", + "RESET": "Tilbakestill", + "APPLY": "Bruk" + }, "PREVIEW": { "TITLE": "Forhåndsvis" }, "NEW_MENU": { "LABEL": "Ny", + "TOOLTIP": "Legg til nye filer eller mapper", "MENU_ITEMS": { "CREATE_FOLDER": "Opprett mappe", "UPLOAD_FILE": "Last opp fil", @@ -88,6 +98,15 @@ }, "ABOUT": { "TITLE": "Om" + }, + "SEARCH": { + "TITLE": "Søkeresultater", + "FOUND_RESULTS": "{{ number }} resultater funnet", + "CUSTOM_ROW": { + "MODIFIED": "Endret", + "LOCATION": "Sted", + "SIZE": "Størrelse" + } } }, "ACTIONS": { @@ -104,7 +123,8 @@ "FAVORITE": "Favoritt", "UNSHARE": "Opphev deling", "DETAILS": "Visningsdetaljer", - "VERSIONS": "Administrer versjoner" + "VERSIONS": "Administrer versjoner", + "TOGGLE-SIDENAV": "Aktiver/deaktiver sidenavigasjonsfelt" }, "DIALOGS": { "CONFIRM_PURGE": { @@ -135,6 +155,7 @@ }, "MESSAGES": { "ERRORS": { + "MISSING_CONTENT": "Denne filen eller mappen finnes ikke lenger, eller du har ikke tillatelse til å se den.", "GENERIC": "Handlingen var mislykket. Prøv på nytt, eller kontakt IT-teamet.", "CONFLICT": "Dette navnet er allerede i bruk, prøv et annet navn.", "NODE_MOVE": "Mislykket flytting, det finnes allerede en fil med samme navn.", @@ -155,6 +176,14 @@ "LOCATION_MISSING": "Kan ikke gjenopprette {{ name }}, det opprinnelige stedet finnes ikke lenger", "GENERIC": "Problem med å gjenopprette {{ name }}" } + }, + "DELETE_LIBRARY_FAILED": "Kan ikke slette biblioteket" + }, + "UPLOAD": { + "ERROR": { + "GENERIC": "Det oppstod et problem", + "CONFLICT": "Ny versjon er ikke lastet opp, en annen fil med samme navn finnes alt", + "500": "Det oppstod et problem under opplasting" } }, "INFO": { @@ -192,7 +221,8 @@ "PLURAL": "Flyttet elementer {{ partially }} delvis.", "FAIL": "{{ failed }} kunne ikke flyttes." } - } + }, + "LIBRARY_DELETED": "Bibliotek slettet" } }, "CONTENT_METADATA": { @@ -222,5 +252,48 @@ "EMPTY": "Velg et dokument for å se versjonene av det.", "NO_PERMISSION": "Du har ikke tillatelse til å administrere versjonene av dette innholdet." } + }, + "SEARCH": { + "SORT": { + "RELEVANCE": "Relevans", + "FILENAME": "Filnavn", + "TITLE": "Tittel", + "MODIFIED_DATE": "Endringsdato", + "SIZE": "Størrelse", + "TYPE": "Type", + "MODIFIER": "Modifikator", + "CREATE_DATE": "Opprettelsesdato" + }, + "FACET_FIELDS": { + "FILE_TYPE": "Filtype", + "CREATOR": "Oppretter", + "MODIFIER": "Modifikator", + "FILE_LIBRARY": "Filbibliotek" + }, + "CATEGORIES": { + "MODIFIED_DATE": "Endringsdato", + "SIZE": "Størrelse", + "CREATED_DATE": "Opprettelsesdato" + } + }, + "CORE": { + "METADATA": { + "ACTIONS": { + "SAVE": "Lagre endringer", + "CANCEL": "Forkast endringer" + } + } + }, + "ADF_VIEWER": { + "ACTIONS": { + "INFO": "Visningsdetaljer" + } + }, + "ADF_VERSION_LIST": { + "ACTIONS": { + "UPLOAD": { + "TOOLTIP": "Last opp ny versjon" + } + } } -} \ No newline at end of file +} diff --git a/src/assets/i18n/nl.json b/src/assets/i18n/nl.json index 3c33f6251..2cee0f894 100644 --- a/src/assets/i18n/nl.json +++ b/src/assets/i18n/nl.json @@ -3,11 +3,21 @@ "LANGUAGE": "Taal", "SIGN_IN": "Aanmelden", "SIGN_OUT": "Afmelden", + "SETTINGS": { + "APPLICATION-SETTINGS": "Toepassingsinstellingen", + "REPOSITORY-SETTINGS": "Opslagplaatsinstellingen", + "EXPERIMENTAL-FEATURES": "Experimentele onderdelen", + "INVALID-VALUE-FORMAT": "Ongeldige indeling van waarde", + "REQUIRED-FIELD": "Dit veld is vereist", + "RESET": "Opnieuw instellen", + "APPLY": "Toepassen" + }, "PREVIEW": { "TITLE": "Preview" }, "NEW_MENU": { "LABEL": "Nieuw", + "TOOLTIP": "Nieuwe bestanden of mappen toevoegen", "MENU_ITEMS": { "CREATE_FOLDER": "Map maken", "UPLOAD_FILE": "Bestand uploaden", @@ -88,6 +98,15 @@ }, "ABOUT": { "TITLE": "Info" + }, + "SEARCH": { + "TITLE": "Zoekresultaten", + "FOUND_RESULTS": "{{ number }} resultaten gevonden", + "CUSTOM_ROW": { + "MODIFIED": "Aangepast", + "LOCATION": "Locatie", + "SIZE": "Grootte" + } } }, "ACTIONS": { @@ -104,7 +123,8 @@ "FAVORITE": "Favoriet", "UNSHARE": "Delen ongedaan maken", "DETAILS": "Details weergeven", - "VERSIONS": "Versies beheren" + "VERSIONS": "Versies beheren", + "TOGGLE-SIDENAV": "Zij-navigatiebalk in- of uitschakelen" }, "DIALOGS": { "CONFIRM_PURGE": { @@ -135,6 +155,7 @@ }, "MESSAGES": { "ERRORS": { + "MISSING_CONTENT": "Dit bestand of deze map bestaat niet meer of u hebt geen rechten om het bestand of de map weer te geven.", "GENERIC": "De actie is mislukt. Probeer het opnieuw of neem contact op met het IT-team.", "CONFLICT": "Deze naam wordt al gebruikt, probeer een andere naam.", "NODE_MOVE": "Verplaatsen is mislukt, er bestaat al een bestand met dezelfde naam.", @@ -155,6 +176,14 @@ "LOCATION_MISSING": "Kan {{ name }} niet herstellen, de oorspronkelijke locatie bestaat niet meer", "GENERIC": "Er is een probleem opgetreden bij het herstellen van {{ name }}" } + }, + "DELETE_LIBRARY_FAILED": "De bibliotheek kan niet worden verwijderd" + }, + "UPLOAD": { + "ERROR": { + "GENERIC": "Er is een probleem opgetreden", + "CONFLICT": "Nieuwe versie is niet geüpload, er bestaat al een ander bestand met dezelfde naam", + "500": "Er is een probleem opgetreden tijdens het uploaden" } }, "INFO": { @@ -192,7 +221,8 @@ "PLURAL": "{{ partially }} items gedeeltelijk verplaatst.", "FAIL": "Kan {{ failed }} niet verplaatsen." } - } + }, + "LIBRARY_DELETED": "Bibliotheek verwijderd" } }, "CONTENT_METADATA": { @@ -222,5 +252,48 @@ "EMPTY": "Kies een document om de versies ervan weer te geven.", "NO_PERMISSION": "U hebt geen rechten voor het beheren van de versies van deze content." } + }, + "SEARCH": { + "SORT": { + "RELEVANCE": "Relevantie", + "FILENAME": "Bestandsnaam", + "TITLE": "Titel", + "MODIFIED_DATE": "Datum gewijzigd", + "SIZE": "Grootte", + "TYPE": "Type", + "MODIFIER": "Gewijzigd door", + "CREATE_DATE": "Datum gemaakt" + }, + "FACET_FIELDS": { + "FILE_TYPE": "Bestandstype", + "CREATOR": "Maker", + "MODIFIER": "Gewijzigd door", + "FILE_LIBRARY": "Bestandsbibliotheek" + }, + "CATEGORIES": { + "MODIFIED_DATE": "Datum gewijzigd", + "SIZE": "Grootte", + "CREATED_DATE": "Datum gemaakt" + } + }, + "CORE": { + "METADATA": { + "ACTIONS": { + "SAVE": "Wijzigingen opslaan", + "CANCEL": "Wijzigingen negeren" + } + } + }, + "ADF_VIEWER": { + "ACTIONS": { + "INFO": "Details weergeven" + } + }, + "ADF_VERSION_LIST": { + "ACTIONS": { + "UPLOAD": { + "TOOLTIP": "Nieuwe versie uploaden" + } + } } -} \ No newline at end of file +} diff --git a/src/assets/i18n/pt-BR.json b/src/assets/i18n/pt-BR.json index 659ddcd41..1b760c763 100644 --- a/src/assets/i18n/pt-BR.json +++ b/src/assets/i18n/pt-BR.json @@ -3,11 +3,21 @@ "LANGUAGE": "Idioma", "SIGN_IN": "Entrar", "SIGN_OUT": "Sair", + "SETTINGS": { + "APPLICATION-SETTINGS": "Configurações do aplicativo", + "REPOSITORY-SETTINGS": "Configurações do repositório", + "EXPERIMENTAL-FEATURES": "Recursos Experimentais", + "INVALID-VALUE-FORMAT": "Formato de valor inválido", + "REQUIRED-FIELD": "Este campo é obrigatório", + "RESET": "Redefinir", + "APPLY": "Aplicar" + }, "PREVIEW": { "TITLE": "Visualizar" }, "NEW_MENU": { "LABEL": "Novo", + "TOOLTIP": "Adicionar novos arquivos ou pastas", "MENU_ITEMS": { "CREATE_FOLDER": "Criar pasta", "UPLOAD_FILE": "Carregar arquivo", @@ -88,6 +98,15 @@ }, "ABOUT": { "TITLE": "Sobre" + }, + "SEARCH": { + "TITLE": "Resultados da pesquisa", + "FOUND_RESULTS": "{{ number }} resultados encontrados", + "CUSTOM_ROW": { + "MODIFIED": "Modificado", + "LOCATION": "Localização", + "SIZE": "Tamanho" + } } }, "ACTIONS": { @@ -104,7 +123,8 @@ "FAVORITE": "Favoritos", "UNSHARE": "Descompartilhar", "DETAILS": "Exibir detalhes", - "VERSIONS": "Gerenciar versões" + "VERSIONS": "Gerenciar versões", + "TOGGLE-SIDENAV": "Alternar a barra de navegação lateral" }, "DIALOGS": { "CONFIRM_PURGE": { @@ -135,6 +155,7 @@ }, "MESSAGES": { "ERRORS": { + "MISSING_CONTENT": "Este arquivo ou pasta não existe, ou você não tem permissão de visualização.", "GENERIC": "A ação não teve êxito. Tente novamente ou entre em contato com a Equipe de TI.", "CONFLICT": "Este nome já está em uso, tente outro nome.", "NODE_MOVE": "Falha ao mover, já existe um arquivo com o mesmo nome.", @@ -155,6 +176,14 @@ "LOCATION_MISSING": "Não foi possível restaurar {{ name }}, o local original não existe mais", "GENERIC": "Houve um problema ao restaurar {{ name }}" } + }, + "DELETE_LIBRARY_FAILED": "Não foi possível apagar a biblioteca" + }, + "UPLOAD": { + "ERROR": { + "GENERIC": "Houve um problema", + "CONFLICT": "Nova versão não carregada, já existe um arquivo com o mesmo nome", + "500": "Houve um problema ao carregar" } }, "INFO": { @@ -192,7 +221,8 @@ "PLURAL": "Itens {{ success }} parcialmente movidos.", "FAIL": "Não foi possível mover {{ failed }}." } - } + }, + "LIBRARY_DELETED": "Biblioteca apagada" } }, "CONTENT_METADATA": { @@ -222,5 +252,48 @@ "EMPTY": "Escolha um documento para ver suas versões.", "NO_PERMISSION": "Você não tem permissão para gerenciar as versões deste conteúdo." } + }, + "SEARCH": { + "SORT": { + "RELEVANCE": "Relevância", + "FILENAME": "Nome do arquivo", + "TITLE": "Título", + "MODIFIED_DATE": "Data de modificação", + "SIZE": "Tamanho", + "TYPE": "Tipo", + "MODIFIER": "Modificador", + "CREATE_DATE": "Data de criação" + }, + "FACET_FIELDS": { + "FILE_TYPE": "Tipo de arquivo", + "CREATOR": "Criador", + "MODIFIER": "Modificador", + "FILE_LIBRARY": "Biblioteca de arquivo" + }, + "CATEGORIES": { + "MODIFIED_DATE": "Data de modificação", + "SIZE": "Tamanho", + "CREATED_DATE": "Data de criação" + } + }, + "CORE": { + "METADATA": { + "ACTIONS": { + "SAVE": "Salvar alterações", + "CANCEL": "Descartar alterações" + } + } + }, + "ADF_VIEWER": { + "ACTIONS": { + "INFO": "Exibir detalhes" + } + }, + "ADF_VERSION_LIST": { + "ACTIONS": { + "UPLOAD": { + "TOOLTIP": "Carregar nova versão" + } + } } -} \ No newline at end of file +} diff --git a/src/assets/i18n/ru.json b/src/assets/i18n/ru.json index ee51a24ca..3da7094e1 100644 --- a/src/assets/i18n/ru.json +++ b/src/assets/i18n/ru.json @@ -3,11 +3,21 @@ "LANGUAGE": "Язык", "SIGN_IN": "Войти", "SIGN_OUT": "Выйти", + "SETTINGS": { + "APPLICATION-SETTINGS": "Настройки приложений", + "REPOSITORY-SETTINGS": "Настройки репозитория", + "EXPERIMENTAL-FEATURES": "Экспериментальные функции", + "INVALID-VALUE-FORMAT": "Недопустимый формат значения", + "REQUIRED-FIELD": "Это обязательное поле", + "RESET": "Сброс", + "APPLY": "Применить" + }, "PREVIEW": { "TITLE": "Предварительный просмотр" }, "NEW_MENU": { "LABEL": "Создать", + "TOOLTIP": "Добавить новые файлы или папки", "MENU_ITEMS": { "CREATE_FOLDER": "Создать папку", "UPLOAD_FILE": "Загрузить файл", @@ -88,6 +98,15 @@ }, "ABOUT": { "TITLE": "Информация" + }, + "SEARCH": { + "TITLE": "Результаты поиска", + "FOUND_RESULTS": "Найдено результатов: {{ number }}", + "CUSTOM_ROW": { + "MODIFIED": "Изменено", + "LOCATION": "Местоположение", + "SIZE": "Размер" + } } }, "ACTIONS": { @@ -104,7 +123,8 @@ "FAVORITE": "Избранное", "UNSHARE": "Снять с публикации", "DETAILS": "Подробно", - "VERSIONS": "Управление версиями" + "VERSIONS": "Управление версиями", + "TOGGLE-SIDENAV": "Переключить боковую панель навигации" }, "DIALOGS": { "CONFIRM_PURGE": { @@ -135,6 +155,7 @@ }, "MESSAGES": { "ERRORS": { + "MISSING_CONTENT": "Этот файл или папка больше не существует, или у вас нет разрешения на просмотр.", "GENERIC": "Действие не выполнено. Повторите попытку или обратитесь к IT-специалистам.", "CONFLICT": "Это имя уже используется, попробуйте другое.", "NODE_MOVE": "Перемещение не выполнено, файл с таким же именем уже существует.", @@ -155,6 +176,14 @@ "LOCATION_MISSING": "Невозможно восстановить {{ name }}, исходное местоположение больше не существует", "GENERIC": "Возникла проблема с восстановлением {{ name }}" } + }, + "DELETE_LIBRARY_FAILED": "Не удается удалить библиотеку" + }, + "UPLOAD": { + "ERROR": { + "GENERIC": "Возникла проблема", + "CONFLICT": "Новая версия не загружена, существует другой файл с таким же именем", + "500": "Возникла проблема во время загрузки" } }, "INFO": { @@ -192,7 +221,8 @@ "PLURAL": "Частично перемещено элементов: {{ partially }}.", "FAIL": "Не удалось переместить: {{ failed }}." } - } + }, + "LIBRARY_DELETED": "Библиотека удалена" } }, "CONTENT_METADATA": { @@ -222,5 +252,48 @@ "EMPTY": "Пожалуйста, выберите документ чтобы просмотреть его версии.", "NO_PERMISSION": "У вас нет разрешения на управление версиями этого контента." } + }, + "SEARCH": { + "SORT": { + "RELEVANCE": "Релевантность", + "FILENAME": "Имя файла", + "TITLE": "Название", + "MODIFIED_DATE": "Дата изменения", + "SIZE": "Размер", + "TYPE": "Тип", + "MODIFIER": "Редактор", + "CREATE_DATE": "Дата создания" + }, + "FACET_FIELDS": { + "FILE_TYPE": "Тип файла", + "CREATOR": "Создатель", + "MODIFIER": "Редактор", + "FILE_LIBRARY": "Библиотека файлов" + }, + "CATEGORIES": { + "MODIFIED_DATE": "Дата изменения", + "SIZE": "Размер", + "CREATED_DATE": "Дата создания" + } + }, + "CORE": { + "METADATA": { + "ACTIONS": { + "SAVE": "Сохранить изменения", + "CANCEL": "Отменить изменения" + } + } + }, + "ADF_VIEWER": { + "ACTIONS": { + "INFO": "Подробно" + } + }, + "ADF_VERSION_LIST": { + "ACTIONS": { + "UPLOAD": { + "TOOLTIP": "Отправить новую версию" + } + } } -} \ No newline at end of file +} diff --git a/src/assets/i18n/zh-CN.json b/src/assets/i18n/zh-CN.json index 7b363d4c8..4645a3e43 100644 --- a/src/assets/i18n/zh-CN.json +++ b/src/assets/i18n/zh-CN.json @@ -3,11 +3,21 @@ "LANGUAGE": "语言", "SIGN_IN": "登录", "SIGN_OUT": "退出", + "SETTINGS": { + "APPLICATION-SETTINGS": "应用程序设置", + "REPOSITORY-SETTINGS": "存储库设置", + "EXPERIMENTAL-FEATURES": "试验性功能", + "INVALID-VALUE-FORMAT": "值格式无效", + "REQUIRED-FIELD": "此字段为必填字段", + "RESET": "重置", + "APPLY": "应用" + }, "PREVIEW": { "TITLE": "预览" }, "NEW_MENU": { "LABEL": "新建", + "TOOLTIP": "添加新文件或文件夹", "MENU_ITEMS": { "CREATE_FOLDER": "创建文件夹", "UPLOAD_FILE": "上传文件", @@ -88,6 +98,15 @@ }, "ABOUT": { "TITLE": "关于" + }, + "SEARCH": { + "TITLE": "搜索结果", + "FOUND_RESULTS": "找到 {{ number }} 个结果", + "CUSTOM_ROW": { + "MODIFIED": "已修改", + "LOCATION": "位置", + "SIZE": "字号(&S)" + } } }, "ACTIONS": { @@ -104,7 +123,8 @@ "FAVORITE": "收藏", "UNSHARE": "取消共享", "DETAILS": "查看详细信息", - "VERSIONS": "管理版本" + "VERSIONS": "管理版本", + "TOGGLE-SIDENAV": "切换侧导航栏" }, "DIALOGS": { "CONFIRM_PURGE": { @@ -135,6 +155,7 @@ }, "MESSAGES": { "ERRORS": { + "MISSING_CONTENT": "此文件或文件夹不再存在,或者您无权对其进行查看。", "GENERIC": "此操作未成功,请重试或联系您的 IT 团队。", "CONFLICT": "此名称已使用,请尝试其他名称。", "NODE_MOVE": "未成功移动,存在有相同名称的文件。", @@ -155,6 +176,14 @@ "LOCATION_MISSING": "无法恢复 {{ name }},原位置不复存在", "GENERIC": "恢复 {{ name }} 时出现问题" } + }, + "DELETE_LIBRARY_FAILED": "无法删除库" + }, + "UPLOAD": { + "ERROR": { + "GENERIC": "发生了问题", + "CONFLICT": "未上传新版本,已存在另一个同名文件", + "500": "上传时发生了问题" } }, "INFO": { @@ -192,7 +221,8 @@ "PLURAL": "部分移动 {{ partially }} 项目。", "FAIL": "{{ failed }} 无法移动。" } - } + }, + "LIBRARY_DELETED": "库已删除" } }, "CONTENT_METADATA": { @@ -222,5 +252,48 @@ "EMPTY": "请选定一个文件去看它的版本。", "NO_PERMISSION": "你没有权限管理此内容的版本。" } + }, + "SEARCH": { + "SORT": { + "RELEVANCE": "相关性", + "FILENAME": "文件名", + "TITLE": "标题", + "MODIFIED_DATE": "修改日期", + "SIZE": "字号(&S)", + "TYPE": "类型", + "MODIFIER": "修改者", + "CREATE_DATE": "创建日期" + }, + "FACET_FIELDS": { + "FILE_TYPE": "文件类型", + "CREATOR": "创建者", + "MODIFIER": "修改者", + "FILE_LIBRARY": "文件库" + }, + "CATEGORIES": { + "MODIFIED_DATE": "修改日期", + "SIZE": "字号(&S)", + "CREATED_DATE": "创建日期" + } + }, + "CORE": { + "METADATA": { + "ACTIONS": { + "SAVE": "保存更改", + "CANCEL": "丢弃更改" + } + } + }, + "ADF_VIEWER": { + "ACTIONS": { + "INFO": "查看详细信息" + } + }, + "ADF_VERSION_LIST": { + "ACTIONS": { + "UPLOAD": { + "TOOLTIP": "上传新版本" + } + } } -} \ No newline at end of file +} diff --git a/src/index.html b/src/index.html index 688fda628..ac09ad06b 100644 --- a/src/index.html +++ b/src/index.html @@ -7,8 +7,50 @@ + + - +
+ +
+
+
+
+
+
+
diff --git a/tslint.json b/tslint.json index b6e591b2b..12d8eaf91 100644 --- a/tslint.json +++ b/tslint.json @@ -103,7 +103,6 @@ "variable-declaration": "nospace" } ], - "typeof-compare": true, "unified-signatures": true, "variable-name": false, "whitespace": [ @@ -117,24 +116,23 @@ "directive-selector": [ true, "attribute", - "app", + "aca", "camelCase" ], "component-selector": [ true, "element", - "app", + [ "app", "aca"], "kebab-case" ], "use-input-property-decorator": true, "use-output-property-decorator": true, - "use-host-property-decorator": true, + "use-host-property-decorator": false, "no-input-rename": true, "no-output-rename": true, "use-life-cycle-interface": true, "use-pipe-transform-interface": true, "component-class-suffix": true, - "directive-class-suffix": true, - "invoke-injectable": true + "directive-class-suffix": true } }