diff --git a/.angular-cli.json b/.angular-cli.json index 0e3fb2b064..035ee99398 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 bef30d0312..309640f2de 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 c0235eaaba..0000000000 --- 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 7f29dd9c02..5ccd541087 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 c5d885dad0..e2df279f2a 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 11e39bebb6..f97f4facde 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 acadd47def..831fd7c983 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 423c400990..3ce9f8c7bc 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 fa4065e229..35743a0f9b 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. + +#### 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. + +### 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) + +## 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.| -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. - -## Want to help? - -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 7f6326b0b3..fc2bcdeccb 100644 Binary files a/alfresco.png and b/alfresco.png differ diff --git a/cspell.json b/cspell.json new file mode 100644 index 0000000000..6401bf8bda --- /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 0129115965..4d2ac0f0f1 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 c0235eaaba..0000000000 --- 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 b6153d7185..9e553cb29d 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 472b78e364..48833ac89f 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 0d486c4790..0000000000 --- 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 258ddb4884..0000000000 --- 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 6ea79e47ad..0000000000 --- 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 e3e2433473..0000000000 --- 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 86d5ce13db..0000000000 --- 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 68d192eb76..0000000000 --- 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 39de383ac9..0000000000 --- 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 9ce7d51073..0000000000 --- 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 0000000000..07e42cd369 --- /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 cf77f6c998..0000000000 --- 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 f547b977e9..54652ee0fc 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 52d700fefc..0000000000 --- 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 0000000000..0a0e026033 Binary files /dev/null and b/docs/images/aca-search-results.png differ diff --git a/docs/index.html b/docs/index.html index d9acb344ec..1ea7e81e39 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 6d22172cc0..0000000000 --- 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 4a283a2725..0000000000 --- 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 98ac4258d3..0000000000 --- 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 c291c84a89..0000000000 --- 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 0000000000..134287c0c5 --- /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 0000000000..604be941d1 --- /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 0000000000..c8ec0d9c7d --- /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 0000000000..696746e63d --- /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 0000000000..c5e5f944a2 --- /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 0000000000..36a14b186c --- /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 0000000000..5b6a306a84 --- /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 0000000000..ba6796a8c0 --- /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 0000000000..47a07dd853 --- /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 0000000000..3c14615b6a --- /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 0000000000..6847a306de --- /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 0000000000..7b939d120a --- /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 0000000000..7bfbcfba05 --- /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 0000000000..3ac50a40db --- /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 0000000000..d5eab3de17 --- /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 0000000000..87fcf6981c --- /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 0000000000..b78cb04430 --- /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 0000000000..570b40c007 --- /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 0000000000..196228230b --- /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 0000000000..d3b4024d84 --- /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 0000000000..f3a7b4c2f8 --- /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 0000000000..137dcfe3fa --- /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 0000000000..cc861d0224 --- /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 0000000000..1025feefd5 --- /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 0000000000..c6f3794072 --- /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 0000000000..56c39453b5 --- /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 0000000000..d97d7f65b0 --- /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 0000000000..a780644bce --- /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 0000000000..7789d43662 --- /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 0000000000..07197ca50a --- /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 0000000000..e17028248e --- /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 0000000000..dfb79669a3 --- /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 0000000000..dd9ea40cd5 --- /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 0000000000..3cd8ae546e --- /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 0000000000..3f6567dddb --- /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 0000000000..9b637babb8 --- /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 0000000000..88bdd95dee --- /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 0000000000..0902c7c70f --- /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 0000000000..b81fc56a99 --- /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 0000000000..43e12985b2 --- /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 0000000000..df378e765f --- /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 0000000000..974c62df7f --- /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 0000000000..531fc36d23 --- /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 0000000000..2244be6e85 --- /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 0000000000..beb5806384 --- /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 0000000000..bf3baa066c --- /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 0000000000..ae0b47511d --- /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 0000000000..35452d2c35 --- /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 0000000000..e2de4f8c4d --- /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 0000000000..d82201ab2c --- /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 0000000000..65915d4800 --- /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 0000000000..bde0a2a110 --- /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 0000000000..93be7980c6 --- /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 0000000000..2ede559824 --- /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 0000000000..ee61ce6dfe --- /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 0000000000..bb09fabd49 --- /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 0000000000..6140dcd74f --- /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 0000000000..598aad9003 --- /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 0000000000..a824893ba0 --- /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 0000000000..0f94d5ee6a --- /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 0000000000..a352eb8824 --- /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 0000000000..e3ffc0107d --- /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 0000000000..76d6683e59 --- /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 0000000000..ad08110be3 --- /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 0000000000..6b5098d402 --- /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 0000000000..49b724a9b4 --- /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 0000000000..92da973bff --- /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 f998d1faef..b1061ba326 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,47 +2557,246 @@ "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" - } - }, - "css-parse": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/css-parse/-/css-parse-1.7.0.tgz", - "integrity": "sha1-Mh9s9zeCpv91ERE5D8BeLGV9jJs=", - "dev": true - }, - "css-select": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", - "integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=", - "dev": true, - "requires": { - "boolbase": "1.0.0", - "css-what": "2.1.0", - "domutils": "1.5.1", - "nth-check": "1.0.1" + "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" } }, - "css-selector-tokenizer": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/css-selector-tokenizer/-/css-selector-tokenizer-0.7.0.tgz", - "integrity": "sha1-5piEdK6MlTR3v15+/s/OzNnPTIY=", - "dev": true, - "requires": { - "cssesc": "0.1.0", - "fastparse": "1.1.1", - "regexpu-core": "1.0.0" - } + "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": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/css-parse/-/css-parse-1.7.0.tgz", + "integrity": "sha1-Mh9s9zeCpv91ERE5D8BeLGV9jJs=", + "dev": true + }, + "css-select": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", + "integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=", + "dev": true, + "requires": { + "boolbase": "~1.0.0", + "css-what": "2.1", + "domutils": "1.5.1", + "nth-check": "~1.0.1" + } + }, + "css-selector-tokenizer": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/css-selector-tokenizer/-/css-selector-tokenizer-0.7.0.tgz", + "integrity": "sha1-5piEdK6MlTR3v15+/s/OzNnPTIY=", + "dev": true, + "requires": { + "cssesc": "^0.1.0", + "fastparse": "^1.1.1", + "regexpu-core": "^1.0.0" + } }, "css-what": { "version": "2.1.0", @@ -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,244 +4266,92 @@ "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", + "balanced-match": { + "version": "1.0.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, - "asynckit": { - "version": "0.4.0", + "brace-expansion": { + "version": "1.1.11", "bundled": true, "dev": true, - "optional": true + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } }, - "aws-sign2": { - "version": "0.6.0", + "chownr": { + "version": "1.0.1", "bundled": true, "dev": true, "optional": true }, - "aws4": { - "version": "1.6.0", + "code-point-at": { + "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, - "balanced-match": { - "version": "0.4.2", + "concat-map": { + "version": "0.0.1", "bundled": true, "dev": true }, - "bcrypt-pbkdf": { - "version": "1.0.1", + "console-control-strings": { + "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true, - "requires": { - "tweetnacl": "0.14.5" - } + "dev": true }, - "block-stream": { - "version": "0.0.9", + "core-util-is": { + "version": "1.0.2", "bundled": true, "dev": true, - "requires": { - "inherits": "2.0.3" - } + "optional": true }, - "boom": { - "version": "2.10.1", + "debug": { + "version": "2.6.9", "bundled": true, "dev": true, + "optional": true, "requires": { - "hoek": "2.16.3" + "ms": "2.0.0" } }, - "brace-expansion": { - "version": "1.1.7", + "deep-extend": { + "version": "0.4.2", "bundled": true, "dev": true, - "requires": { - "balanced-match": "0.4.2", - "concat-map": "0.0.1" - } + "optional": true }, - "buffer-shims": { + "delegates": { "version": "1.0.0", "bundled": true, - "dev": true - }, - "caseless": { - "version": "0.12.0", - "bundled": true, "dev": true, "optional": true }, - "co": { - "version": "4.6.0", + "detect-libc": { + "version": "1.0.3", "bundled": true, "dev": true, "optional": true }, - "code-point-at": { - "version": "1.1.0", - "bundled": true, - "dev": true - }, - "combined-stream": { - "version": "1.0.5", + "fs-minipass": { + "version": "1.2.5", "bundled": true, "dev": true, + "optional": true, "requires": { - "delayed-stream": "1.0.0" - } - }, - "concat-map": { - "version": "0.0.1", - "bundled": true, - "dev": true - }, - "console-control-strings": { - "version": "1.1.0", - "bundled": true, - "dev": true - }, - "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 - } - } - }, - "debug": { - "version": "2.6.8", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "ms": "2.0.0" - } - }, - "deep-extend": { - "version": "0.4.2", - "bundled": true, - "dev": true, - "optional": true - }, - "delayed-stream": { - "version": "1.0.0", - "bundled": true, - "dev": true - }, - "delegates": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "detect-libc": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "ecc-jsbn": { - "version": "0.1.1", - "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", + "iconv-lite": { + "version": "0.4.21", "bundled": true, "dev": true, + "optional": true, "requires": { - "boom": "2.10.1", - "cryptiles": "2.0.5", - "hoek": "2.16.3", - "sntp": "1.0.9" + "safer-buffer": "^2.1.0" } }, - "hoek": { - "version": "2.16.3", - "bundled": true, - "dev": true - }, - "http-signature": { - "version": "1.1.1", + "ignore-walk": { + "version": "3.0.1", "bundled": true, "dev": true, "optional": true, "requires": { - "assert-plus": "0.2.0", - "jsprim": "1.4.0", - "sshpk": "1.13.0" + "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,167 +4433,125 @@ "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", + "minimatch": { + "version": "3.0.4", "bundled": true, "dev": true, - "optional": true, "requires": { - "jsbn": "0.1.1" + "brace-expansion": "^1.1.7" } }, - "jsbn": { - "version": "0.1.1", + "minimist": { + "version": "0.0.8", "bundled": true, - "dev": true, - "optional": true + "dev": true }, - "json-schema": { - "version": "0.2.3", + "minipass": { + "version": "2.2.4", "bundled": true, "dev": true, - "optional": true + "requires": { + "safe-buffer": "^5.1.1", + "yallist": "^3.0.0" + } }, - "json-stable-stringify": { - "version": "1.0.1", + "minizlib": { + "version": "1.1.0", "bundled": true, "dev": true, "optional": true, "requires": { - "jsonify": "0.0.0" + "minipass": "^2.2.1" } }, - "json-stringify-safe": { - "version": "5.0.1", + "mkdirp": { + "version": "0.5.1", "bundled": true, "dev": true, - "optional": true + "requires": { + "minimist": "0.0.8" + } }, - "jsonify": { - "version": "0.0.0", + "ms": { + "version": "2.0.0", "bundled": true, "dev": true, "optional": true }, - "jsprim": { - "version": "1.4.0", + "needle": { + "version": "2.2.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" + "debug": "^2.1.2", + "iconv-lite": "^0.4.4", + "sax": "^1.2.4" } }, - "minimatch": { - "version": "3.0.4", + "node-pre-gyp": { + "version": "0.9.1", "bundled": true, "dev": true, + "optional": true, "requires": { - "brace-expansion": "1.1.7" + "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" } }, - "minimist": { - "version": "0.0.8", - "bundled": true, - "dev": true - }, - "mkdirp": { - "version": "0.5.1", + "nopt": { + "version": "4.0.1", "bundled": true, "dev": true, + "optional": true, "requires": { - "minimist": "0.0.8" + "abbrev": "1", + "osenv": "^0.1.4" } }, - "ms": { - "version": "2.0.0", + "npm-bundled": { + "version": "1.0.3", "bundled": true, "dev": true, "optional": true }, - "node-pre-gyp": { - "version": "0.6.39", + "npm-packlist": { + "version": "1.1.10", "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" - } - }, - "nopt": { - "version": "4.0.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "abbrev": "1.1.0", - "osenv": "0.1.4" + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1" } }, "npmlog": { - "version": "4.1.0", + "version": "4.1.2", "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" + "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,143 +4628,89 @@ } }, "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 }, - "semver": { - "version": "5.3.0", + "safer-buffer": { + "version": "2.1.2", "bundled": true, "dev": true, "optional": true }, - "set-blocking": { - "version": "2.0.0", + "sax": { + "version": "1.2.4", "bundled": true, "dev": true, "optional": true }, - "signal-exit": { - "version": "3.0.2", + "semver": { + "version": "5.5.0", "bundled": true, "dev": true, "optional": true }, - "sntp": { - "version": "1.0.9", + "set-blocking": { + "version": "2.0.0", "bundled": true, "dev": true, - "requires": { - "hoek": "2.16.3" - } + "optional": true }, - "sshpk": { - "version": "1.13.0", + "signal-exit": { + "version": "3.0.2", "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 - } - } + "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", - "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" - } - }, - "tough-cookie": { - "version": "2.3.2", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "punycode": "1.4.1" - } - }, - "tunnel-agent": { - "version": "0.6.0", + "version": "4.4.1", "bundled": true, "dev": true, "optional": true, "requires": { - "safe-buffer": "5.0.1" + "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" } }, - "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": { @@ -5881,38 +5529,25 @@ "dev": true }, "https-browserify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", - "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=", - "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=", - "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" - } - } + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", + "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=", + "dev": true + }, + "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" } }, "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==", - "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", + "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.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=", - "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", + "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.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 }, + "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": { + "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.5.0", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.5.0.tgz", - "integrity": "sha512-RSlipNQB1u48cq0wH/BNfCu1tD/cJ8ydFIkNYhp9o+3d+8unClkIovpW5qpFPgmL9OE48wfAnlZydXByWP82AA==", + "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.3", - "graceful-fs": "4.1.11", - "neo-async": "2.5.1" + "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 331c14da86..f38992d7b3 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 9a1a1b39c8..6b6fbb0d47 --- 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 6db201ac8d..0673d08960 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 - }, - "filterQueries": [ - { "query": "TYPE:'cm:folder' OR TYPE:'cm:content'" }, - { "query": "NOT cm:creator:System" } - ], - "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" } - ], - "query": { - "categories": [ + "include": ["path", "allowableOperations", "properties"], + "sorting": { + "options": [ { - "id": "broken", - "name": "Broken Facet", - "enabled": false, - "expanded": false, - "component": { - "selector": "adf-search-text", - "settings": { - "field": "fieldname" - } - } + "key": "score", + "label": "SEARCH.SORT.RELEVANCE", + "type": "FIELD", + "field": "score", + "ascending": true }, { - "id": "queryName", - "name": "Name", - "enabled": true, - "expanded": true, - "component": { - "selector": "adf-search-text", - "settings": { - "pattern": "cm:name:'(.*?)'", - "field": "cm:name", - "placeholder": "Enter the name" - } - } + "key": "name", + "label": "SEARCH.SORT.FILENAME", + "type": "FIELD", + "field": "cm:name", + "ascending": true }, { - "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 } - ] - } - } + "key": "title", + "label": "SEARCH.SORT.TITLE", + "type": "FIELD", + "field": "cm:title", + "ascending": 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'" } - ] - } - } + "key": "modified", + "label": "SEARCH.SORT.MODIFIED_DATE", + "type": "FIELD", + "field": "cm:modified", + "ascending": true }, { - "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" } - ] - } - } + "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 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": [ + { "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" } + ], + "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 4399601878..74d5e6aa43 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 8c17e443a7..f5f07237df 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 3a098acb5b..0a4b610a06 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 e32cd3c9a6..c95b985469 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 91c61268e5..c483da02dc 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 05cce99df6..a9087766b6 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('notifies file deletion', () => { - spyOn(nodeApiService, 'deleteNode').and.returnValue(Observable.of(null)); + it('should raise info message on successful single file 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' } }]; 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('should raise info message on successful multiple files deletion', fakeAsync(done => { + spyOn(contentApi, 'deleteNode').and.returnValue(Observable.of(null)); - it('notifies files deletion', () => { - spyOn(nodeApiService, '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 dc76f3ab89..c66cc4562a 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(); - } - - 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); - } + 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; - 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 d2a6f4275c..0000000000 --- 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 c063e2e809..0000000000 --- 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 754b1a7df6..0000000000 --- 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 e696998493..e23209b5a8 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 812e687ec9..a1b645a47e 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 f08ffd5d82..efde1230f3 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('raises warning on multiple success and multiple fail', fakeAsync(done => { + actions$.pipe( + ofType(SNACKBAR_WARNING), + map((action: SnackbarWarningAction) => { + done(); + }) ); - })); - it('notifies on multiple success and multiple fail', fakeAsync(() => { - spyOn(nodesService, 'purgeDeletedNode').and.callFake((id) => { + 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('raises info on one selected node success', fakeAsync(done => { + actions$.pipe( + ofType(SNACKBAR_INFO), + map((action: SnackbarInfoAction) => { + done(); + }) ); - })); - it('notifies on one selected node success', fakeAsync(() => { - spyOn(nodesService, 'purgeDeletedNode').and.returnValue(Promise.resolve()); + 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('raises error on one selected node fail', fakeAsync(done => { + actions$.pipe( + ofType(SNACKBAR_ERROR), + map((action: SnackbarErrorAction) => { + done(); + }) ); - })); - it('notifies on one selected node fail', fakeAsync(() => { - spyOn(nodesService, 'purgeDeletedNode').and.returnValue(Promise.reject({})); + spyOn(contentApi, 'purgeDeletedNode').and.returnValue(Observable.throw({})); component.selection = [ { entry: { id: '1', name: 'name1' } } @@ -209,49 +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) => { - if (id === '1') { - return Promise.resolve(); - } - - 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(notificationService.openSnackMessage).toHaveBeenCalled(); - expect(translation.get).toHaveBeenCalledWith( - 'APP.MESSAGES.INFO.TRASH.NODES_PURGE.PLURAL', - { number: 2 } + it('raises info on all nodes success', fakeAsync(done => { + actions$.pipe( + ofType(SNACKBAR_INFO), + map((action: SnackbarInfoAction) => { + done(); + }) ); - })); - - it('notifies on selected nodes fail', fakeAsync(() => { - spyOn(nodesService, 'purgeDeletedNode').and.callFake((id) => { + spyOn(contentApi, 'purgeDeletedNode').and.callFake((id) => { if (id === '1') { - return Promise.reject({}); + return Observable.of({}); } if (id === '2') { - return Promise.reject({}); + return Observable.of({}); } }); @@ -263,68 +240,22 @@ 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) => { + 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.resolve(); + return Observable.throw({}); } }); @@ -336,23 +267,6 @@ describe('NodePermanentDeleteDirective', () => { 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 a9aa7f90ad..90f1e9f405 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(); - } - }); - } - - 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 + const nodesToDelete: NodeInfo[] = this.selection.map(node => { + const { name } = node.entry; + const id = node.entry.nodeId || node.entry.id; + + return { + 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 = []; + this.store.dispatch(new PurgeDeletedNodesAction(nodesToDelete)); } - }; - - 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 68b6cab5f1..620c22ec9c 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 { 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'; +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'; @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; - - beforeEach(async(() => { + let directiveInstance: NodeRestoreDirective; + let contentManagementService: ContentManagementService; + let actions$: Actions; + let contentApi: ContentApiService; + + 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); + + fixture = TestBed.createComponent(TestComponent); + component = fixture.componentInstance; + element = fixture.debugElement.query(By.directive(NodeRestoreDirective)); + directiveInstance = element.injector.get(NodeRestoreDirective); - spyOn(translation, 'get').and.returnValue(Observable.of('message')); + 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,46 +108,16 @@ describe('NodeRestoreDirective', () => { element.triggerEventHandler('click', null); tick(); - expect(nodesService.restoreNode).toHaveBeenCalled(); + expect(contentApi.restoreNode).toHaveBeenCalled(); })); describe('refresh()', () => { - it('reset selection', fakeAsync(() => { - spyOn(directiveInstance, 'restoreNotification').and.callFake(() => null); - spyOn(nodesService, 'restoreNode').and.returnValue(Promise.resolve()); - spyOn(coreApi.nodesApi, 'getDeletedNodes').and.returnValue(Promise.resolve({ - 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(() => { + 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: [] } })); - spyOn(element.nativeElement, 'dispatchEvent'); component.selection = [{ entry: { id: '1', path: ['somewhere-over-the-rainbow'] } }]; @@ -169,33 +125,36 @@ describe('NodeRestoreDirective', () => { 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 8d16d8a675..ddacb0b284 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; } - private restoreNotification(): void { - const status = Object.assign({}, this.restoreProcessStatus); - const action = (status.oneSucceeded && !status.someFailed) ? this.translation.translate.instant('APP.ACTIONS.VIEW') : ''; + restoreNotification(status: DeleteStatus): void { + const message = this.getRestoreMessage(status); - this.getRestoreMessage() - .subscribe((message) => { - this.notification.openSnackMessageAction(message, action, 3000) - .onAction() - .subscribe(() => this.navigateLocation(status.success[0].entry.path)); - }); + 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 refresh(): void { - this.restoreProcessStatus.reset(); - this.selection = []; - this.emitDone(); + private isSite(entry: MinimalNodeEntryEntity): boolean { + return entry.nodeType === 'st:site'; } - private emitDone() { - const e = new CustomEvent('selection-node-restored', { bubbles: true }); - this.el.nativeElement.dispatchEvent(e); + private refresh(): void { + 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 abd6f2b71c..19fdedfa74 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 f822e1b828..55c862ef23 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 48e71eb93f..0d9e7284a5 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 2942e578ed..9902a7ce39 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 68e52b0df6..1d49bf082a 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 0000000000..c1a723c3a4 --- /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 0000000000..429bd3c443 --- /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 ba1e413f5d..057ba3e45c 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 6e5d684cdd..0000000000 --- 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 0000000000..71be08396d --- /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 8bcad5897c..3b5cb988f8 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 638f9230be..46858be8c0 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 b301ce7bc1..0000000000 --- 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 5eef09d371..0000000000 --- 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 0000000000..6a22fa6658 --- /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 6ef40663ae..2f10576f67 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 0000000000..be695e0868 --- /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 0000000000..39b45278b4 --- /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 0000000000..51a8b4514a --- /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 43ecf6bf83..0000000000 --- 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 2da90ab1ca..0000000000 --- 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 32e4d5571a..0000000000 --- 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 4e137d1de1..0000000000 --- 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 8b54addb12..330b340d93 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 13f571a1d0..ae27594cd8 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 a1c999a154..98336e2e78 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 27c2380e74..56515fd14b 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 04c960c0bd..88868b1508 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; + + beforeAll(() => { + // testing only functional-wise not time-wise + Observable.prototype.debounceTime = function () { return this; }; + }); - beforeEach(async(() => { + 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,61 +97,68 @@ describe('FilesComponent', () => { pagination: {} } }; + + spyOn(component.documentList, 'loadFolder').and.callFake(() => {}); }); - describe('OnInit', () => { - it('set current node', () => { - spyOn(component, 'fetchNode').and.returnValue(Observable.of(node)); - spyOn(component, 'fetchNodes').and.returnValue(Observable.of(page)); + 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.node).toBe(node); - }); + expect(component.isValidPath).toBe(false); + })); - it('get current node children', () => { - spyOn(component, 'fetchNode').and.returnValue(Observable.of(node)); + 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.paging).toBe(page); - }); + expect(component.isValidPath).toBe(true); + })); + }); - it('emits onChangeParent event', () => { - spyOn(component, 'fetchNode').and.returnValue(Observable.of(node)); + describe('OnInit', () => { + it('should set current 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); fixture.detectChanges(); - expect(browsingFilesService.onChangeParent.next) - .toHaveBeenCalledWith(node); + expect(component.node).toBe(node); }); - it('raise error when fetchNode() fails', () => { - spyOn(component, 'fetchNode').and.returnValue(Observable.throw(null)); - spyOn(component, 'onFetchError'); + 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.onFetchError).toHaveBeenCalled(); + expect(component.fetchNodes).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'); + it('emits onChangeParent event', () => { + 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); fixture.detectChanges(); - expect(component.onFetchError).toHaveBeenCalled(); + expect(browsingFilesService.onChangeParent.next) + .toHaveBeenCalledWith(node); }); 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 8eab1d700c..726c47567e 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) { - - const node: MinimalNodeEntryEntity = event.detail.node.entry; - if (node) { + onNodeDoubleClick(node: MinimalNodeEntity) { + if (node && node.entry) { + const { id, isFolder } = node.entry; - 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,52 +149,54 @@ 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; - onContentCopied(nodes: MinimalNodeEntity[]) { - const newNode = nodes - .find((node) => { - return node && node.entry && node.entry.parentId === this.getParentNodeId(); - }); - if (newNode) { - this.load(false, this.pagination); + // check root and child nodes + if (node && node.entry && node.entry.parentId === this.getParentNodeId()) { + this.documentList.reload(); + return; } - } - load(showIndicator: boolean = false, options: { maxItems?: number, skipCount?: number } = {}) { - this.isLoading = showIndicator; + // check the child nodes to show dropped folder + if (event && event.file.options.parentId === this.getParentNodeId()) { + this.displayFolderParent(event.file.options.path, 0); + return; + } - this.fetchNodes(this.getParentNodeId(), options) - .flatMap((page) => { - if (this.isCurrentPageEmpty(page) && this.isNotFirstPage(page)) { - const newSkipCount = options.skipCount - options.maxItems; + if (event && event.file.options.parentId) { + if (this.nodePath) { + const correspondingNodePath = this.nodePath.find(pathItem => pathItem.id === event.file.options.parentId); - return this.fetchNodes(this.getParentNodeId(), { - skipCount: newSkipCount, maxItems: options.maxItems - }); + // 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); } - - return Observable.of(page); - }) - .subscribe( - (page) => this.zone.run(() => this.onPageLoaded(page)), - error => this.onFetchError(error) - ); + } + } } - isCurrentPageEmpty(page): boolean { - return !this.hasPageEntries(page); - } + 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); - hasPageEntries(page): boolean { - return page && page.list && page.list.entries && page.list.entries.length > 0; + if (alreadyDisplayedParentFolder) { + return; + } + this.documentList.reload(); } - isNotFirstPage(page): boolean { - return (page.list.pagination.skipCount >= page.list.pagination.maxItems); + onContentCopied(nodes: MinimalNodeEntity[]) { + const newNode = nodes + .find((node) => { + return node && node.entry && node.entry.parentId === this.getParentNodeId(); + }); + if (newNode) { + this.documentList.reload(); + } } // todo: review this approach once 5.2.3 is out @@ -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 1525e9ba33..ac7157b634 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 a3b5b67f1c..0000000000 --- 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 0000000000..bbf0735e99 --- /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 abfbf9a6df..dfae6b91e4 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 1e033fe0a8..2a4668d66f 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 1ef2058915..0000000000 --- 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 c32d91cfc8..f2a3f88ec6 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 0000000000..8b56a42247 --- /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 235610db8d..6ad498bb3c 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 0000000000..985ebec34e --- /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 0000000000..d3ff5cfe58 --- /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 958362bc2a..52c37d768a 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 166179c456..8a6e3a3513 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 07f3fa06ec..4f1cf43bcb 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 0000000000..513073faa2 --- /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 aed07e6e6a..133da22a22 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 a040082009..fe340e2820 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 6870d14e1b..201bcec1f4 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 { - - @ViewChild(DocumentListComponent) - documentList: DocumentListComponent; - - sorting = [ 'title', 'asc' ]; - - constructor(private nodesApi: NodesApiService, - private route: ActivatedRoute, - private router: Router, - preferences: UserPreferencesService) { - super(preferences); +export class LibrariesComponent extends PageComponent implements OnInit { + + constructor(private route: ActivatedRoute, + private content: ContentManagementService, + private contentApi: ContentApiService, + store: Store, + private router: Router) { + super(store); + } - const sortingKey = preferences.get(`${this.prefix}.sorting.key`) || 'title'; - const sortingDirection = preferences.get(`${this.prefix}.sorting.direction`) || 'desc'; + ngOnInit() { + super.ngOnInit(); - 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 dc198a35de..1f083351ed 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) { } - ngOnInit() { + goToLocation() { 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); - - if (value && value.name && value.elements) { - const isLibraryPath = this.isLibraryContent(value); - - this.displayText = this.getDisplayText(value); - this.tooltip = this.getTooltip(value); + const node: MinimalNodeEntity = this.context.row.node; + this.store.dispatch(new NavigateToParentFolder(node)); + } + } - const parent = value.elements[value.elements.length - 1]; - const area = isLibraryPath ? '/libraries' : '/personal-files'; + ngOnInit() { + if (this.context) { + const node: MinimalNodeEntity = this.context.row.node; + if (node && node.entry && node.entry.path) { + const path = node.entry.path; - 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 b23521bf6c..e5a80250b8 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 18f006a8a4..7db3e5a826 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); + spyOn(router, 'navigateByUrl'); + location = TestBed.get(Location); + spyOn(location, 'forward'); + auth = TestBed.get(AuthenticationService); - userPreference = TestBed.get(UserPreferencesService); - })); + spyOn(auth, 'getRedirect').and.returnValue('/some-url'); - beforeEach(() => { + userPreference = TestBed.get(UserPreferencesService); spyOn(userPreference, 'setStoragePrefix'); - spyOn(router, 'navigateByUrl'); - spyOn(auth, 'getRedirectUrl').and.returnValue('/some-url'); - spyOn(location, 'forward'); }); 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 c0e24091e9..76aa0f7229 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 0b1f78e95c..d44f5a257f 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 2c66c376bd..7656121932 100644 --- a/src/app/components/page.component.ts +++ b/src/app/components/page.component.ts @@ -23,185 +23,115 @@ * 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 { - title = 'Page'; - - isLoading = false; - isEmpty = true; - infoDrawerOpened = false; + onDestroy$: Subject = new Subject(); - paging: NodePaging; - pagination: Pagination; + @ViewChild(DocumentListComponent) + documentList: DocumentListComponent; + title = 'Page'; + infoDrawerOpened = false; 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 = []; - getParentNodeId(): string { - return this.node ? this.node.id : null; + this.onDestroy$.next(true); + this.onDestroy$.complete(); } - 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; - } + showPreview(node: MinimalNodeEntity) { + if (node && node.entry) { + const { id, nodeId, name, isFile, isFolder } = node.entry; + const parentId = this.node ? this.node.id : null; - foldersOnlySelected(selection: Array): boolean { - if (this.hasSelection(selection)) { - return selection.every(entity => entity.entry && entity.entry.isFolder); + this.store.dispatch(new ViewNodeAction({ + parentId, + id: nodeId || id, + name, + isFile, + 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; + getParentNodeId(): string { + return this.node ? this.node.id : null; } - canEditFolder(selection: Array): boolean { - if (selection && selection.length === 1) { - const entry = selection[0].entry; + imageResolver(row: ShareDataRow): string | null { + const entry: MinimalNodeEntryEntity = row.node.entry; - if (entry && entry.isFolder) { - return this.nodeHasPermission(entry, 'update'); - } + if (PageComponent.isLockedNode(entry)) { + return 'assets/images/ic_lock_black_24dp_1x.png'; } - 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); + return null; } - nodeHasPermission(node: MinimalNodeEntryEntity, permission: string): boolean { - if (node && permission) { - const { allowableOperations = [] } = (node || {}); - - if (allowableOperations.indexOf(permission) > -1) { - return true; - } + toggleSidebar(event) { + if (event) { + return; } - return false; - } - - onChangePageSize(event: Pagination): void { - this.preferences.paginationSize = event.maxItems; + this.infoDrawerOpened = !this.infoDrawerOpened; } - onNodeSelect(event, documentList) { - if (!!event.detail && !!event.detail.node) { - - const node: MinimalNodeEntryEntity = event.detail.node.entry; - if (node && PageComponent.isLockedNode(node)) { - this.unSelectLockedNodes(documentList); - } + reload(): void { + if (this.documentList) { + this.documentList.resetSelection(); + this.store.dispatch(new SetSelectedNodesAction([])); + this.documentList.reload(); } } - unSelectLockedNodes(documentList) { - documentList.selection = documentList.selection.filter(item => !PageComponent.isLockedNode(item.entry)); + onFileUploadedError(error: FileUploadErrorEvent) { + let message = 'APP.MESSAGES.UPLOAD.ERROR.GENERIC'; - 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; - } - }); - } + if (error.error.status === 409) { + message = 'APP.MESSAGES.UPLOAD.ERROR.CONFLICT'; } - } - - 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 null; - } + if (error.error.status === 500) { + message = 'APP.MESSAGES.UPLOAD.ERROR.500'; + } - toggleSidebar(event) { - if (event) { - return; - } + const action = new SnackbarErrorAction(message); - this.infoDrawerOpened = !this.infoDrawerOpened; + this.store.dispatch(action); } } diff --git a/src/app/components/preview/preview.component.html b/src/app/components/preview/preview.component.html index 585cec05b8..4807b59a95 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 75cff1615e..e4c4665100 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 6ff4e806eb..81bbdfdb37 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 18aa9a7dba..0196a3a92f 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 62f681d2cd..1f78650442 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(); - - 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(); + contentService.nodesMoved.next(); - 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 a62b3e4f20..f9581e608f 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: MinimalNodeEntryEntity) { - if (node && PageComponent.isLockedNode(node)) { - event.preventDefault(); + onNodeDoubleClick(node: MinimalNodeEntity) { + if (node && node.entry) { + if (PageComponent.isLockedNode(node.entry)) { + event.preventDefault(); + return; + } - } 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 0000000000..13db9bdf2b --- /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 0000000000..cb1dd538d3 --- /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 0000000000..496cb1d5b0 --- /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 d1ca1b18cc..3b2ba7f960 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 01065f7759..0000000000 --- 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 640ed4ac61..82de63b77d 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 0000000000..8be93e791c --- /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 b0a14c365f..2285cf376f 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 18aa4ac044..5ef9c4e630 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 a650b0c025..6a201a3b10 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 645668776a..d965642bc7 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: []}} ); + } }); } } + // 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; + } + }); + } + + 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(); } - onRefreshPagination(pagination: Pagination) { - this.maxItems = pagination.maxItems; - this.skipCount = pagination.skipCount; + 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 0000000000..c17358f9ef --- /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 0000000000..11de98a196 --- /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 0000000000..e2ff08280a --- /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 a6705935f5..8e74cdcf2c 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 8c4371f8ff..4cd7430773 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(); - - 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]); - })); + contentService.nodesMoved.next(); - 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 89ab807659..01058f99b2 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 e5c288acad..65dbccf6e3 100644 --- a/src/app/components/sidenav/sidenav.component.html +++ b/src/app/components/sidenav/sidenav.component.html @@ -1,64 +1,51 @@
-
- - - - - - +
+ + arrow_drop_down +
+ queue +
+
+ - - + + - - - + + +
+
@@ -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 724a6ee3ff..48df44e8fe 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 07e53b2fc7..b237127e53 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 8941378c3c..9ec93afaec 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 84fe64749c..b667661a7f 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 109c57a0fa..33406bf592 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 df6b8a0781..111b3a632f 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); + fixture = TestBed.createComponent(TrashcanComponent); + component = fixture.componentInstance; - component.documentList = { - reload: jasmine.createSpy('reload'), - resetSelection: jasmine.createSpy('resetSelection') - }; - }); - })); + 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 8c5429248c..f754cf3f48 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())); - } - - refresh(): void { - this.documentList.reload(); - this.documentList.resetSelection(); - } - - 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; + super.ngOnInit(); + + 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) + ); } -} + } 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 c1874f01d6..0000000000 --- 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 9be0594199..0000000000 --- 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 3a6e0e667e..0000000000 --- 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 0000000000..829ea13b13 --- /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 0000000000..dbb69e9fcf --- /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 0000000000..e99e07d380 --- /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 0000000000..4dcb190159 --- /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 0000000000..28ab6e7015 --- /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 0000000000..ea2d61dc71 --- /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 0000000000..e532ec87d3 --- /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 0000000000..3546f8c50d --- /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 0000000000..94155d8924 --- /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 0000000000..c0c3e20320 --- /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 0000000000..a5ecff227a --- /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 0000000000..013545cb3e --- /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 0000000000..c73a019f51 --- /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 0000000000..d2fad4aef3 --- /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 0000000000..c8b1a90631 --- /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 0000000000..2e3faa760b --- /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 0000000000..5d3632bb62 --- /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 0000000000..baac9ef9e7 --- /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 0000000000..a128e15112 --- /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 0000000000..048a408ebd --- /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 0000000000..ecf31fd66f --- /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 0000000000..3c773fee26 --- /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 0000000000..72a6d1dbb2 --- /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 0000000000..a8df0f9bbb --- /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 0000000000..c0adad2a33 --- /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 0000000000..e0f66c4b37 --- /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 0000000000..0d5dd150ff --- /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 0000000000..99dd419fed --- /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 0000000000..42a97bb2e9 --- /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 0000000000..2a4d65d9af --- /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 0000000000..aa912b1754 --- /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 0000000000..3e6a432f6e --- /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 0000000000..057900403f --- /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 0000000000..38d559e253 --- /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 0000000000..8fe15c58ce --- /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 0000000000..7309f63a59 --- /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 0000000000..e63212685a --- /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 0000000000..2113d51c96 --- /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 0000000000..9db2061926 --- /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 0000000000..0ad4590642 --- /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 46470d69a9..727f85e8b9 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 0000000000..1d6b30312f --- /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 e64871faf9..0000000000 --- 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 25054d3183..0000000000 --- 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 b5d8072e62..70891f13f7 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 9cd3784d1c..a652a54a58 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 0000000000..bb38d0e07f --- /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 dbec2ec4f7..0000000000 --- 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 3d8305f0a2..0000000000 --- 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 cdec99c348..0000000000 --- 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 fc9a7cc62f..0000000000 --- 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 b8e549685c..0000000000 --- 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 43846d3a4a..0000000000 --- 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 e3dbbfd004..0000000000 --- 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 76d9a1382f..0000000000 --- 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 afafe9d43e..0000000000 --- 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 010468714c..0000000000 --- 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 a9bbe406b3..0000000000 --- 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 0000000000..1a8dd5c821 --- /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 0000000000..799a25c82b --- /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 0000000000..7cf3146e6c --- /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 0000000000..47060f65bf --- /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 0000000000..2c33201808 --- /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 0000000000..f83b1b64e2 --- /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 0000000000..b245c1b025 --- /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 0000000000..06b2490e67 --- /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 0000000000..b2fa28225d --- /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 0000000000..8ee04c650b --- /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 0000000000..1b232ee3e1 --- /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 0000000000..ad5d4d35d5 --- /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 0000000000..929fe461f3 --- /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 96302e8aac..9deea7cda5 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 6cd4118fd5..35f234fdeb 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 d3f9b478ed..189470c163 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 1f1c46f731..1552320c61 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 5bb55010d5..17a776dac7 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 0a9c586033..c77b64fd0e 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 8e30017ef9..fff2ce9516 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 f1f3b33765..a212ebcb4b 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 3c33f62513..2cee0f8941 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 659ddcd411..1b760c7637 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 ee51a24ca2..3da7094e1b 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 7b363d4c81..4645a3e43d 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 688fda628a..ac09ad06bc 100644 --- a/src/index.html +++ b/src/index.html @@ -7,8 +7,50 @@ + + - +
+ +
+
+
+
+
+
+
diff --git a/tslint.json b/tslint.json index b6e591b2b7..12d8eaf915 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 } }