Merge pull request #226 from Alfresco/development

v.1.1.0
This commit is contained in:
Denys Vuika
2018-03-12 11:29:45 +00:00
committed by GitHub
103 changed files with 5881 additions and 2801 deletions
+3
View File
@@ -11,3 +11,6 @@ trim_trailing_whitespace = true
[*.md]
max_line_length = off
trim_trailing_whitespace = false
[*.yml]
indent_size = 2
+18 -8
View File
@@ -1,5 +1,5 @@
dist: trusty
sudo: false
sudo: required
language: node_js
node_js:
@@ -9,15 +9,25 @@ cache:
directories:
- ./node_modules
before_install:
- export CHROME_BIN=chromium-browser
- "export DISPLAY=:99.0"
- "sh -e /etc/init.d/xvfb start"
services:
- docker
install:
- npm install
script:
- xvfb-run -a npm run test -- --single-run --no-progress --browser=ChromeNoSandbox
#- xvfb-run -a npm run e2e -- --no-progress --config=protractor-ci.conf.js
- npm run build
- npm run test -- --single-run --no-progress && cat ./coverage/lcov.info | ./node_modules/.bin/codacy-coverage && rm -rf ./coverage
# Send coverage data to codecov
after_success:
- bash <(curl -s https://codecov.io/bash) -X gcov
- export BRANCH=$(if [ "$TRAVIS_PULL_REQUEST" == "false" ]; then echo $TRAVIS_BRANCH; else echo $TRAVIS_PULL_REQUEST_BRANCH; fi)
- echo "TRAVIS_BRANCH=$TRAVIS_BRANCH, PR=$PR, BRANCH=$BRANCH"
- export TAG=`if [ "$BRANCH" == "master" ]; then echo "latest"; else echo $BRANCH ; fi`
- sed -i 's/{:port}/:8080/1' ./dist/app.config.json
- docker build -t $DOCKER_REPO:$TAG .
# Publish extra image based on Travis build number
- docker tag $DOCKER_REPO:$TAG $DOCKER_REPO:travis-$TRAVIS_BUILD_NUMBER
- docker login -u "$DOCKER_USERNAME" -p "$DOCKER_PASSWORD"
- docker push $DOCKER_REPO
+1 -1
View File
@@ -1,4 +1,4 @@
FROM nginx
FROM nginx:alpine
COPY nginx.conf /etc/nginx/nginx.conf
+8
View File
@@ -19,6 +19,14 @@ with a simple and easy to use interface for working with files stored in the Alf
[Public documentation](https://alfresco.github.io/alfresco-content-app/)
### Raising issues and feature requests
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.
## Development server
Run `npm start` for a dev server. Navigate to `http://localhost:3000/` (opens by default).
+3 -55
View File
@@ -17,9 +17,9 @@ 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.0](https://community.alfresco.com/community/application-development-framework/pages/get-started)
- [Alfresco Content Services version 5.2.2](https://www.alfresco.com/platform/content-services-ecm)
- [Alfresco Community Edition 201707](https://www.alfresco.com/products/community/download)
- [Alfresco ADF version 2.2](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)
<p class="warning">
You also need <a href="https://nodejs.org/en/" target="_blank">node.js</a> (8.9.1 or later) installed to build it locally from source code.
@@ -28,58 +28,6 @@ You also need <a href="https://nodejs.org/en/" target="_blank">node.js</a> (8.9.
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.
## Where to get help
There are several ways to get help with building applications using the Alfresco Application Development Framework:
### Alfresco DevCon 2018
DevCon 2018 is an international developer conference entirely dedicated to Alfresco technology.
With the support of our community, customers, and partners, DevCon will increase your technical know-how,
connect you with other Alfresco developers, and let you collaborate with our team and each other.
Register now to avoid disappointment, places are limited:
http://devcon.alfresco.com/
### Alfresco Community
Visit the Alfresco Community space where you can find many resources to help you get started building your application,
along with blog posts from the Alfresco developers and much more:
https://community.alfresco.com/community/application-development-framework
### Alfresco ADF Gitter
Join the vibrant community in Gitter where you can chat with experienced developers,
including the Alfresco employees working directly on the ADF and this example application project:
https://gitter.im/Alfresco/alfresco-ng2-components
### Alfresco Developer Support
Developer Support is a subscription-based support offering delivered remotely
by a dedicated team of development-focused, senior Support Engineers.
This support offering is best for customers and partners that require on-going support
for their development teams and customized code.
Visit the Developer Support Services section on the Alfresco website for more information:
https://www.alfresco.com/alfresco-developer-support-services
### Alfresco University
Training with Alfresco University is the best way to acquire the right skills for your team to deliver a successful Alfresco implementation.
The most cost-effective way to take advantage of this valuable training is through Alfresco University Passport.
Visit the Alfresco University section on the Alfresco website for more information:
https://www.alfresco.com/alfresco-university
## Building and running locally
Please refer to the [developer docs](/build) to get more details on building and running application on your local machine.
## Using with Docker
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.
## Contribution Policy
### How to contribute
+1 -1
View File
@@ -121,7 +121,7 @@ You can change the default settings of the pagination that gets applied to all t
```json
{
...,
"document-list": {
"pagination": {
"supportedPageSizes": [
25,
50,
+24 -2
View File
@@ -6,6 +6,28 @@ This article assumes you are familiar with Docker and know how to create images
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:
@@ -35,10 +57,10 @@ docker image build -t content-app .
To run the image locally, you can use the following command:
```sh
docker container run -p 80:80 --rm content-app
docker container run -p 8888:80 --rm content-app
```
Navigate to "http://localhost" to access the running application.
Navigate to "http://localhost:8888" to access the running application.
## Docker Compose file
+43
View File
@@ -0,0 +1,43 @@
### 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 and delete.
#### 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
+42
View File
@@ -0,0 +1,42 @@
# Where to get help
There are several ways to get help with building applications using the Alfresco Application Development Framework:
## Alfresco Community
Visit the Alfresco Community space where you can find many resources to help you get started building your application,
along with blog posts from the Alfresco developers and much more:
https://community.alfresco.com/community/application-development-framework
## Alfresco ADF Gitter
Join the vibrant community in Gitter where you can chat with experienced developers,
including the Alfresco employees working directly on the ADF and this example application project:
https://gitter.im/Alfresco/alfresco-ng2-components
## Alfresco Developer Support
Developer Support is a subscription-based support offering delivered remotely
by a dedicated team of development-focused, senior Support Engineers.
This support offering is best for customers and partners that require on-going support
for their development teams and customized code.
Visit the Developer Support Services section on the Alfresco website for more information:
https://www.alfresco.com/alfresco-developer-support-services
## Alfresco University
Training with Alfresco University is the best way to acquire the right skills for your team to deliver a successful Alfresco implementation.
The most cost-effective way to take advantage of this valuable training is through Alfresco University Passport.
Visit the Alfresco University section on the Alfresco website for more information:
https://www.alfresco.com/alfresco-university
# Building and running locally
Please refer to the [developer docs](/build) to get more details on building and running application on your local machine.
# Using with Docker
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.
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

+8
View File
@@ -37,6 +37,10 @@
{
title: 'Document List',
path: 'doc-list'
},
{
title: 'File Viewer',
path: 'file-viewer'
}
]
},
@@ -77,6 +81,10 @@
path: 'navigation'
}
]
},
{
title: 'Get Help',
path: 'help'
}
],
icons: [
+12 -4
View File
@@ -38,11 +38,16 @@ module.exports = function (config) {
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['Chrome'],
browsers: ['ChromeHeadless'],
customLaunchers: {
ChromeNoSandbox: {
ChromeHeadless: {
base: 'Chrome',
flags: ['--no-sandbox']
flags: [
'--no-sandbox',
'--headless',
'--disable-gpu',
'--remote-debugging-port=9222'
]
}
},
singleRun: false,
@@ -50,6 +55,9 @@ module.exports = function (config) {
captureTimeout: 180000,
browserDisconnectTimeout: 180000,
browserDisconnectTolerance: 3,
browserNoActivityTimeout: 300000
browserNoActivityTimeout: 300000,
// workaround for alfresco-js-api builds
webpack: { node: { fs: 'empty', } }
});
};
+2153 -1241
View File
File diff suppressed because it is too large Load Diff
+26 -22
View File
@@ -1,6 +1,6 @@
{
"name": "alfresco-content-app",
"version": "1.0.1",
"version": "1.1.0",
"license": "LGPL-3.0",
"scripts": {
"ng": "ng",
@@ -9,44 +9,48 @@
"build:prod": "npm run server-versions && ng build --prod",
"build:dev": "npm run server-versions && ng build && node postbuild-dev.js",
"build:tomcat": "npm run server-versions && ng build --base-href ./",
"test": "ng test",
"test": "ng test --code-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"
},
"private": true,
"dependencies": {
"@alfresco/adf-content-services": "2.0.0",
"@alfresco/adf-core": "2.0.0",
"@angular/animations": "5.0.0",
"@angular/cdk": "5.0.0-rc0",
"@angular/common": "5.0.0",
"@angular/compiler": "5.0.0",
"@angular/core": "5.0.0",
"@angular/flex-layout": "2.0.0-beta.10",
"@angular/forms": "5.0.0",
"@angular/http": "5.0.0",
"@angular/material": "5.0.0-rc0",
"@angular/platform-browser": "5.0.0",
"@angular/platform-browser-dynamic": "5.0.0",
"@angular/router": "5.0.0",
"@ngx-translate/core": "8.0.0",
"alfresco-js-api": "2.0.0",
"@alfresco/adf-content-services": "2.2.0",
"@alfresco/adf-core": "2.2.0",
"@angular/animations": "5.1.1",
"@angular/cdk": "5.0.1",
"@angular/common": "5.1.1",
"@angular/compiler": "5.1.1",
"@angular/core": "5.1.1",
"@angular/flex-layout": "2.0.0-beta.12",
"@angular/forms": "5.1.1",
"@angular/http": "5.1.1",
"@angular/material": "5.0.1",
"@angular/material-moment-adapter": "5.0.1",
"@angular/platform-browser": "5.1.1",
"@angular/platform-browser-dynamic": "5.1.1",
"@angular/router": "5.1.1",
"@mat-datetimepicker/core": "1.0.1",
"@mat-datetimepicker/moment": "1.0.1",
"@ngx-translate/core": "9.1.1",
"alfresco-js-api": "2.2.0",
"core-js": "2.5.3",
"hammerjs": "2.0.8",
"moment-es6": "1.0.0",
"pdfjs-dist": "2.0.301",
"pdfjs-dist": "2.0.303",
"rxjs": "5.5.2",
"web-animations-js": "^2.3.1",
"web-animations-js": "2.3.1",
"zone.js": "0.8.20"
},
"devDependencies": {
"@angular/cli": "1.6.6",
"@angular/compiler-cli": "5.2.2",
"@angular/language-service": "5.2.2",
"@angular/compiler-cli": "5.1.1",
"@angular/language-service": "5.1.1",
"@types/jasmine": "^2.5.53",
"@types/jasminewd2": "^2.0.2",
"@types/node": "9.3.0",
"codacy-coverage": "^2.0.3",
"codelyzer": "^4.0.1",
"jasmine-core": "~2.8.0",
"jasmine-reporters": "^2.2.1",
+1 -1
View File
@@ -2,7 +2,7 @@
* @license
* Alfresco Example Content Application
*
* Copyright (C) 2005 - 2017 Alfresco Software Limited
* 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
+1 -1
View File
@@ -6,7 +6,7 @@
},
"headerColor": "#2196F3",
"languagePicker": false,
"document-list": {
"pagination": {
"supportedPageSizes": [
25,
50,
+1 -1
View File
@@ -2,7 +2,7 @@
* @license
* Alfresco Example Content Application
*
* Copyright (C) 2005 - 2017 Alfresco Software Limited
* 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
+8 -4
View File
@@ -2,7 +2,7 @@
* @license
* Alfresco Example Content Application
*
* Copyright (C) 2005 - 2017 Alfresco Software Limited
* 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
@@ -25,7 +25,7 @@
import { Component, OnInit } from '@angular/core';
import { Router, ActivatedRoute, NavigationEnd } from '@angular/router';
import { TranslationService, PageTitleService } from '@alfresco/adf-core';
import { TranslationService, PageTitleService, UserPreferencesService, AppConfigService } from '@alfresco/adf-core';
@Component({
selector: 'app-root',
@@ -37,11 +37,15 @@ export class AppComponent implements OnInit {
private route: ActivatedRoute,
private router: Router,
private pageTitle: PageTitleService,
private translateService: TranslationService) {
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');
}
ngOnInit() {
const { router, pageTitle, route, translateService } = this;
const { router, pageTitle, route } = this;
router
.events
+4 -2
View File
@@ -2,7 +2,7 @@
* @license
* Alfresco Example Content Application
*
* Copyright (C) 2005 - 2017 Alfresco Software Limited
* 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
@@ -51,6 +51,7 @@ import { SearchComponent } from './components/search/search.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';
@NgModule({
imports: [
@@ -80,7 +81,8 @@ import { LocationLinkComponent } from './components/location-link/location-link.
TrashcanComponent,
PreviewComponent,
AboutComponent,
LocationLinkComponent
LocationLinkComponent,
EmptyFolderComponent
],
providers: [
{
+117 -34
View File
@@ -2,7 +2,7 @@
* @license
* Alfresco Example Content Application
*
* Copyright (C) 2005 - 2017 Alfresco Software Limited
* 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
@@ -41,13 +41,6 @@ import { PreviewComponent } from './components/preview/preview.component';
import { GenericErrorComponent } from './components/generic-error/generic-error.component';
export const APP_ROUTES: Routes = [
{
path: 'preview/:nodeId',
component: PreviewComponent,
data: {
i18nTitle: 'APP.PREVIEW.TITLE'
}
},
{
path: 'login',
component: LoginComponent,
@@ -66,13 +59,33 @@ export const APP_ROUTES: Routes = [
},
{
path: 'favorites',
component: FavoritesComponent,
data: {
i18nTitle: 'APP.BROWSE.FAVORITES.TITLE'
}
preferencePrefix: 'favorites'
},
children: [
{
path: '',
component: FavoritesComponent,
data: {
i18nTitle: 'APP.BROWSE.FAVORITES.TITLE'
}
},
{
path: 'preview/:nodeId',
component: PreviewComponent,
data: {
i18nTitle: 'APP.PREVIEW.TITLE',
navigateMultiple: true,
navigateSource: 'favorites'
}
}
]
},
{
path: 'libraries',
data: {
preferencePrefix: 'libraries'
},
children: [{
path: '',
component: LibrariesComponent,
@@ -80,49 +93,119 @@ export const APP_ROUTES: Routes = [
i18nTitle: 'APP.BROWSE.LIBRARIES.TITLE'
}
}, {
path: ':id',
path: ':folderId',
component: FilesComponent,
data: {
i18nTitle: 'APP.BROWSE.LIBRARIES.TITLE'
i18nTitle: 'APP.BROWSE.LIBRARIES.TITLE',
preferencePrefix: 'libraries-files'
}
}]
},
{
path: ':folderId/preview/:nodeId',
component: PreviewComponent,
data: {
i18nTitle: 'APP.PREVIEW.TITLE',
navigateMultiple: true,
navigateSource: 'libraries'
}
}
]
},
{
path: 'personal-files',
children: [{
path: '',
component: FilesComponent,
data: {
i18nTitle: 'APP.BROWSE.PERSONAL.TITLE',
defaultNodeId: '-my-'
data: {
preferencePrefix: 'personal-files'
},
children: [
{
path: '',
component: FilesComponent,
data: {
i18nTitle: 'APP.BROWSE.PERSONAL.TITLE',
defaultNodeId: '-my-'
}
},
{
path: ':folderId',
component: FilesComponent,
data: {
i18nTitle: 'APP.BROWSE.PERSONAL.TITLE'
}
},
{
path: 'preview/:nodeId',
component: PreviewComponent,
data: {
i18nTitle: 'APP.PREVIEW.TITLE',
navigateMultiple: true,
navigateSource: 'personal-files'
}
},
{
path: ':folderId/preview/:nodeId',
component: PreviewComponent,
data: {
i18nTitle: 'APP.PREVIEW.TITLE',
navigateMultiple: true,
navigateSource: 'personal-files'
}
}
}, {
path: ':id',
component: FilesComponent,
data: {
i18nTitle: 'APP.BROWSE.PERSONAL.TITLE'
}
}]
]
},
{
path: 'recent-files',
component: RecentFilesComponent,
data: {
i18nTitle: 'APP.BROWSE.RECENT.TITLE'
}
preferencePrefix: 'recent-files'
},
children: [
{
path: '',
component: RecentFilesComponent,
data: {
i18nTitle: 'APP.BROWSE.RECENT.TITLE'
}
},
{
path: 'preview/:nodeId',
component: PreviewComponent,
data: {
i18nTitle: 'APP.PREVIEW.TITLE',
navigateMultiple: true,
navigateSource: 'recent-files'
}
}
]
},
{
path: 'shared',
component: SharedFilesComponent,
data: {
i18nTitle: 'APP.BROWSE.SHARED.TITLE'
}
preferencePrefix: 'shared-files'
},
children: [
{
path: '',
component: SharedFilesComponent,
data: {
i18nTitle: 'APP.BROWSE.SHARED.TITLE'
}
},
{
path: 'preview/:nodeId',
component: PreviewComponent,
data: {
i18nTitle: 'APP.PREVIEW.TITLE',
navigateMultiple: true,
navigateSource: 'shared'
}
}
]
},
{
path: 'trashcan',
component: TrashcanComponent,
data: {
i18nTitle: 'APP.BROWSE.TRASHCAN.TITLE'
i18nTitle: 'APP.BROWSE.TRASHCAN.TITLE',
preferencePrefix: 'trashcan'
}
},
{
+6 -4
View File
@@ -2,7 +2,7 @@
* @license
* Alfresco Example Content Application
*
* Copyright (C) 2005 - 2017 Alfresco Software Limited
* 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
@@ -27,6 +27,7 @@ import { NgModule } from '@angular/core';
import { DatePipe } from '@angular/common';
import { RouterModule } from '@angular/router';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { AdfModule } from '../adf.module';
import { MaterialModule } from './material.module';
@@ -34,9 +35,9 @@ import { MaterialModule } from './material.module';
import { NodeCopyDirective } from './directives/node-copy.directive';
import { NodeDeleteDirective } from './directives/node-delete.directive';
import { NodeMoveDirective } from './directives/node-move.directive';
import { DownloadFileDirective } from './directives/node-download.directive';
import { NodeRestoreDirective } from './directives/node-restore.directive';
import { NodePermanentDeleteDirective } from './directives/node-permanent-delete.directive';
import { NodeUnshareDirective } from './directives/node-unshare.directive';
import { ContentManagementService } from './services/content-management.service';
import { BrowsingFilesService } from './services/browsing-files.service';
@@ -44,6 +45,7 @@ import { NodeActionsService } from './services/node-actions.service';
export function modules() {
return [
BrowserAnimationsModule,
FormsModule,
ReactiveFormsModule,
MaterialModule,
@@ -57,9 +59,9 @@ export function declarations() {
NodeCopyDirective,
NodeDeleteDirective,
NodeMoveDirective,
DownloadFileDirective,
NodeRestoreDirective,
NodePermanentDeleteDirective
NodePermanentDeleteDirective,
NodeUnshareDirective
];
}
@@ -2,7 +2,7 @@
* @license
* Alfresco Example Content Application
*
* Copyright (C) 2005 - 2017 Alfresco Software Limited
* 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
@@ -29,12 +29,20 @@ import { By } from '@angular/platform-browser';
import { Observable } from 'rxjs/Rx';
import { CoreModule, TranslationService, NodesApiService, NotificationService } from '@alfresco/adf-core';
import { DocumentListModule } from '@alfresco/adf-content-services';
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 { NodeActionsService } from '../services/node-actions.service';
import { ContentManagementService } from '../services/content-management.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';
@Component({
template: '<div [app-copy-node]="selection"></div>'
@@ -55,16 +63,33 @@ describe('NodeCopyDirective', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [
CoreModule,
DocumentListModule
NoopAnimationsModule,
HttpClientModule,
TranslateModule.forRoot(),
MatSnackBarModule,
MatDialogModule,
MatIconModule
],
declarations: [
TestComponent,
NodeCopyDirective
],
providers: [
AlfrescoApiService,
AuthenticationService,
AppConfigService,
StorageService,
ContentService,
UserPreferencesService,
LogService,
CookieService,
NotificationService,
NodesApiService,
NodeActionsService,
{ provide: TranslationService, useClass: TranslationMock },
ContentManagementService,
NodeActionsService
DocumentListService,
ThumbnailService
]
});
@@ -2,7 +2,7 @@
* @license
* Alfresco Example Content Application
*
* Copyright (C) 2005 - 2017 Alfresco Software Limited
* 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
@@ -104,7 +104,7 @@ export class NodeCopyDirective {
} catch (err) { /* Do nothing, keep the original message */ }
}
const undo = (numberOfCopiedItems > 0) ? this.translation.translate.instant('APP.ACTIONS.UNDO') : '';
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 => {
@@ -122,7 +122,7 @@ export class NodeCopyDirective {
Observable.forkJoin(...batch)
.subscribe(
() => {
this.content.deleteNode.next(null);
this.content.nodeDeleted.next(null);
},
(error) => {
let i18nMessageString = 'APP.MESSAGES.ERRORS.GENERIC';
@@ -2,7 +2,7 @@
* @license
* Alfresco Example Content Application
*
* Copyright (C) 2005 - 2017 Alfresco Software Limited
* 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
@@ -23,12 +23,13 @@
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
import { TestBed, ComponentFixture, fakeAsync } from '@angular/core/testing';
import { TestBed, ComponentFixture } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { CoreModule, TranslationService, NodesApiService, NotificationService } from '@alfresco/adf-core';
import { TranslationService, NodesApiService, NotificationService } from '@alfresco/adf-core';
import { Component, DebugElement } from '@angular/core';
import { Observable } from 'rxjs/Rx';
import { CommonModule } from '../common.module';
import { NodeDeleteDirective } from './node-delete.directive';
import { ContentManagementService } from '../services/content-management.service';
@@ -52,14 +53,10 @@ describe('NodeDeleteDirective', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [
CoreModule
CommonModule
],
declarations: [
TestComponent,
NodeDeleteDirective
],
providers: [
ContentManagementService
TestComponent
]
});
@@ -96,7 +93,7 @@ describe('NodeDeleteDirective', () => {
);
});
it('notifies faild file deletion', () => {
it('notifies failed file deletion', () => {
spyOn(nodeApiService, 'deleteNode').and.returnValue(Observable.throw(null));
component.selection = [{ entry: { id: '1', name: 'name1' } }];
@@ -125,7 +122,7 @@ describe('NodeDeleteDirective', () => {
);
});
it('notifies faild files deletion', () => {
it('notifies failed files deletion', () => {
spyOn(nodeApiService, 'deleteNode').and.returnValue(Observable.throw(null));
component.selection = [
@@ -202,7 +199,7 @@ describe('NodeDeleteDirective', () => {
});
});
it('notifies faild file on on restore', () => {
it('notifies failed file on on restore', () => {
spyOn(nodeApiService, 'restoreNode').and.returnValue(Observable.throw(null));
component.selection = [
@@ -216,7 +213,7 @@ describe('NodeDeleteDirective', () => {
.toEqual((['APP.MESSAGES.ERRORS.NODE_RESTORE', '', 3000]));
});
it('notifies faild files on on restore', () => {
it('notifies failed files on on restore', () => {
spyOn(nodeApiService, 'restoreNode').and.returnValue(Observable.throw(null));
component.selection = [
@@ -232,7 +229,7 @@ describe('NodeDeleteDirective', () => {
});
it('signals files restored', () => {
spyOn(contentService.restoreNode, 'next');
spyOn(contentService.nodeRestored, 'next');
spyOn(nodeApiService, 'restoreNode').and.callFake((id) => {
if (id === '1') {
return Observable.of(null);
@@ -249,7 +246,7 @@ describe('NodeDeleteDirective', () => {
fixture.detectChanges();
element.triggerEventHandler('click', null);
expect(contentService.restoreNode.next).toHaveBeenCalled();
expect(contentService.nodeRestored.next).toHaveBeenCalled();
});
});
});
@@ -2,7 +2,7 @@
* @license
* Alfresco Example Content Application
*
* Copyright (C) 2005 - 2017 Alfresco Software Limited
* 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
@@ -74,7 +74,7 @@ export class NodeDeleteDirective {
.subscribe(() => this.restore(processedData.success));
if (processedData.someSucceeded) {
this.content.deleteNode.next(null);
this.content.nodeDeleted.next(null);
}
});
}
@@ -103,7 +103,7 @@ export class NodeDeleteDirective {
}
if (processedData.someSucceeded) {
this.content.restoreNode.next(null);
this.content.nodeRestored.next(null);
}
}
);
@@ -1,149 +0,0 @@
/*!
* @license
* Alfresco Example Content Application
*
* Copyright (C) 2005 - 2017 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 <http://www.gnu.org/licenses/>.
*/
import { TestBed, ComponentFixture } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { CoreModule, AlfrescoApiService } from '@alfresco/adf-core';
import { MatDialog } from '@angular/material';
import { Component, DebugElement } from '@angular/core';
import { DownloadFileDirective } from './node-download.directive';
@Component({
template: '<div [app-download-node]="selection"></div>'
})
class TestComponent {
selection;
}
describe('DownloadFileDirective', () => {
let component: TestComponent;
let fixture: ComponentFixture<TestComponent>;
let element: DebugElement;
let dialog: MatDialog;
let apiService: AlfrescoApiService;
let contentService;
let dialogSpy;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [
CoreModule
],
declarations: [
TestComponent,
DownloadFileDirective
],
providers: [
AlfrescoApiService
]
});
fixture = TestBed.createComponent(TestComponent);
component = fixture.componentInstance;
element = fixture.debugElement.query(By.directive(DownloadFileDirective));
dialog = TestBed.get(MatDialog);
apiService = TestBed.get(AlfrescoApiService);
contentService = apiService.getInstance().content;
dialogSpy = spyOn(dialog, 'open');
});
it('should not download node when selection is empty', () => {
spyOn(apiService, 'getInstance');
component.selection = [];
fixture.detectChanges();
element.triggerEventHandler('click', null);
expect(apiService.getInstance).not.toHaveBeenCalled();
});
it('should not download zip when selection has no nodes', () => {
component.selection = [];
fixture.detectChanges();
element.triggerEventHandler('click', null);
expect(dialogSpy).not.toHaveBeenCalled();
});
it('should download selected node as file', () => {
spyOn(contentService, 'getContentUrl');
const node = { entry: { id: 'node-id', isFile: true } };
component.selection = [node];
fixture.detectChanges();
element.triggerEventHandler('click', null);
expect(contentService.getContentUrl).toHaveBeenCalledWith(node.entry.id, true);
});
it('should download selected files nodes as zip', () => {
const node1 = { entry: { id: 'node-1' } };
const node2 = { entry: { id: 'node-2' } };
component.selection = [node1, node2];
fixture.detectChanges();
element.triggerEventHandler('click', null);
expect(dialogSpy.calls.argsFor(0)[1].data).toEqual({ nodeIds: [ 'node-1', 'node-2' ] });
});
it('should download selected folder node as zip', () => {
const node = { entry: { isFolder: true, id: 'node-id' } };
component.selection = [node];
fixture.detectChanges();
element.triggerEventHandler('click', null);
expect(dialogSpy.calls.argsFor(0)[1].data).toEqual({ nodeIds: [ 'node-id' ] });
});
it('should create link element to download file node', () => {
const dummyLinkElement = {
download: null,
href: null,
click: () => null,
style: {
display: null
}
};
const node = { entry: { name: 'dummy', isFile: true, id: 'node-id' } };
spyOn(contentService, 'getContentUrl').and.returnValue('somewhere-over-the-rainbow');
spyOn(document, 'createElement').and.returnValue(dummyLinkElement);
spyOn(document.body, 'appendChild').and.stub();
spyOn(document.body, 'removeChild').and.stub();
component.selection = [node];
fixture.detectChanges();
element.triggerEventHandler('click', null);
expect(document.createElement).toHaveBeenCalled();
expect(dummyLinkElement.download).toBe('dummy');
expect(dummyLinkElement.href).toContain('somewhere-over-the-rainbow');
});
});
@@ -1,120 +0,0 @@
/*!
* @license
* Alfresco Example Content Application
*
* Copyright (C) 2005 - 2017 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 <http://www.gnu.org/licenses/>.
*/
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';
@Directive({
selector: '[app-download-node]'
})
export class DownloadFileDirective {
@Input('app-download-node')
nodes: MinimalNodeEntity[];
@HostListener('click')
onClick() {
this.downloadNodes(this.nodes);
}
constructor(
private apiService: AlfrescoApiService,
private dialog: MatDialog
) {}
private downloadNodes(selection: Array<MinimalNodeEntity>) {
if (!selection || selection.length === 0) {
return;
}
if (selection.length === 1) {
this.downloadNode(selection[0]);
} else {
this.downloadZip(selection);
}
}
private 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 && (<any>entry).nodeId) {
this.downloadFile(node);
}
}
}
private downloadFile(node: MinimalNodeEntity) {
if (node && node.entry) {
const contentApi = this.apiService.getInstance().content;
const url = contentApi.getContentUrl(node.entry.id, true);
const fileName = node.entry.name;
this.download(url, fileName);
}
}
private downloadZip(selection: Array<MinimalNodeEntity>) {
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);
}
}
}
@@ -2,7 +2,7 @@
* @license
* Alfresco Example Content Application
*
* Copyright (C) 2005 - 2017 Alfresco Software Limited
* 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
@@ -26,14 +26,12 @@
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 { CoreModule, TranslationService, NodesApiService, NotificationService } from '@alfresco/adf-core';
import { DocumentListModule } from '@alfresco/adf-content-services';
import { TranslationService, NodesApiService, NotificationService } from '@alfresco/adf-core';
import { CommonModule } from '../common.module';
import { NodeActionsService } from '../services/node-actions.service';
import { ContentManagementService } from '../services/content-management.service';
import { NodeMoveDirective } from './node-move.directive';
@Component({
@@ -55,16 +53,10 @@ describe('NodeMoveDirective', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [
CoreModule,
DocumentListModule
CommonModule
],
declarations: [
TestComponent,
NodeMoveDirective
],
providers: [
ContentManagementService,
NodeActionsService
TestComponent
]
});
@@ -2,7 +2,7 @@
* @license
* Alfresco Example Content Application
*
* Copyright (C) 2005 - 2017 Alfresco Software Limited
* 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
@@ -64,7 +64,7 @@ export class NodeMoveDirective {
const [ operationResult, moveResponse ] = result;
this.toastMessage(operationResult, moveResponse);
this.content.moveNode.next(null);
this.content.nodeMoved.next(null);
},
(error) => {
this.toastMessage(error);
@@ -125,7 +125,7 @@ export class NodeMoveDirective {
const beforePartialSuccessMessage = (successMessage && partialSuccessMessage) ? ' ' : '';
const beforeFailedMessage = ((successMessage || partialSuccessMessage) && failedMessage) ? ' ' : '';
const initialParentId = this.nodeActionsService.getFirstParentId(this.selection);
const initialParentId = this.nodeActionsService.getEntryParentId(this.selection[0].entry);
this.translation.get(
[successMessage, partialSuccessMessage, failedMessage],
@@ -190,7 +190,7 @@ export class NodeMoveDirective {
})
.subscribe(
() => {
this.content.moveNode.next(null);
this.content.nodeMoved.next(null);
},
(error) => {
@@ -2,7 +2,7 @@
* @license
* Alfresco Example Content Application
*
* Copyright (C) 2005 - 2017 Alfresco Software Limited
* 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
@@ -27,8 +27,9 @@ import { Component, DebugElement } from '@angular/core';
import { TestBed, ComponentFixture, async, fakeAsync, tick } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { Observable } from 'rxjs/Rx';
import { CoreModule, AlfrescoApiService, TranslationService, NotificationService } from '@alfresco/adf-core';
import { AlfrescoApiService, TranslationService, NotificationService } from '@alfresco/adf-core';
import { CommonModule } from '../common.module';
import { NodePermanentDeleteDirective } from './node-permanent-delete.directive';
@Component({
@@ -51,11 +52,10 @@ describe('NodePermanentDeleteDirective', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
CoreModule
CommonModule
],
declarations: [
TestComponent,
NodePermanentDeleteDirective
TestComponent
]
})
.compileComponents()
@@ -2,7 +2,7 @@
* @license
* Alfresco Example Content Application
*
* Copyright (C) 2005 - 2017 Alfresco Software Limited
* 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
@@ -27,7 +27,7 @@ import { Directive, ElementRef, HostListener, Input } from '@angular/core';
import { Observable } from 'rxjs/Rx';
import { TranslationService, AlfrescoApiService, NotificationService } from '@alfresco/adf-core';
import { MinimalNodeEntity, DeletedNodeEntry, PathInfoEntity } from 'alfresco-js-api';
import { MinimalNodeEntity } from 'alfresco-js-api';
@Directive({
selector: '[app-permanent-delete-node]'
@@ -2,7 +2,7 @@
* @license
* Alfresco Example Content Application
*
* Copyright (C) 2005 - 2017 Alfresco Software Limited
* 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
@@ -29,7 +29,9 @@ import { RouterTestingModule } from '@angular/router/testing';
import { TestBed, ComponentFixture, async, fakeAsync, tick } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { Observable } from 'rxjs/Rx';
import { CoreModule, AlfrescoApiService, TranslationService, NotificationService } from '@alfresco/adf-core';
import { CommonModule } from '../common.module';
import { AlfrescoApiService, TranslationService, NotificationService } from '@alfresco/adf-core';
import { NodeRestoreDirective } from './node-restore.directive';
@@ -55,12 +57,11 @@ describe('NodeRestoreDirective', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
CoreModule,
CommonModule,
RouterTestingModule
],
declarations: [
TestComponent,
NodeRestoreDirective
TestComponent
]
})
.compileComponents()
@@ -2,7 +2,7 @@
* @license
* Alfresco Example Content Application
*
* Copyright (C) 2005 - 2017 Alfresco Software Limited
* 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
@@ -28,7 +28,7 @@ import { Router } from '@angular/router';
import { Observable } from 'rxjs/Rx';
import { TranslationService, AlfrescoApiService, NotificationService } from '@alfresco/adf-core';
import { MinimalNodeEntity, DeletedNodeEntry, PathInfoEntity, DeletedNodesPaging } from 'alfresco-js-api';
import { MinimalNodeEntity, PathInfoEntity, DeletedNodesPaging } from 'alfresco-js-api';
@Directive({
selector: '[app-restore-node]'
@@ -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 <http://www.gnu.org/licenses/>.
*/
import { Directive, HostListener, Input, ElementRef } from '@angular/core';
import { AlfrescoApiService } from '@alfresco/adf-core';
import { MinimalNodeEntity } from 'alfresco-js-api';
@Directive({
selector: '[appUnshareNode]'
})
export class NodeUnshareDirective {
// tslint:disable-next-line:no-input-rename
@Input('appUnshareNode')
selection: MinimalNodeEntity[];
constructor(
private apiService: AlfrescoApiService,
private el: ElementRef) {
}
@HostListener('click')
onClick() {
if (this.selection.length > 0) {
this.unshareLinks(this.selection);
}
}
private async unshareLinks(links: MinimalNodeEntity[]) {
const promises = links.map(link => this.apiService.sharedLinksApi.deleteSharedLink(link.entry.id));
await Promise.all(promises);
this.emitDone();
}
private emitDone() {
const e = new CustomEvent('links-unshared', { bubbles: true });
this.el.nativeElement.dispatchEvent(e);
}
}
+1 -1
View File
@@ -2,7 +2,7 @@
* @license
* Alfresco Example Content Application
*
* Copyright (C) 2005 - 2017 Alfresco Software Limited
* 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
@@ -2,7 +2,7 @@
* @license
* Alfresco Example Content Application
*
* Copyright (C) 2005 - 2017 Alfresco Software Limited
* 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
@@ -2,7 +2,7 @@
* @license
* Alfresco Example Content Application
*
* Copyright (C) 2005 - 2017 Alfresco Software Limited
* 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
@@ -2,7 +2,7 @@
* @license
* Alfresco Example Content Application
*
* Copyright (C) 2005 - 2017 Alfresco Software Limited
* 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
@@ -25,10 +25,83 @@
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 {
deleteNode = new Subject<string>();
moveNode = new Subject<string>();
restoreNode = new Subject<string>();
nodeDeleted = new Subject<string>();
nodeMoved = new Subject<string>();
nodeRestored = new Subject<string>();
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
);
}
}
}
}
@@ -2,7 +2,7 @@
* @license
* Alfresco Example Content Application
*
* Copyright (C) 2005 - 2017 Alfresco Software Limited
* 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
@@ -24,13 +24,21 @@
*/
import { TestBed, async } from '@angular/core/testing';
import { MatDialog } from '@angular/material';
import { MatDialog, MatDialogModule, MatIconModule } from '@angular/material';
import { OverlayModule } from '@angular/cdk/overlay';
import { Observable } from 'rxjs/Rx';
import { CoreModule, AlfrescoApiService, NodesApiService } from '@alfresco/adf-core';
import {
TranslationMock, AlfrescoApiService, NodesApiService,
TranslationService, ContentService, AuthenticationService,
UserPreferencesService, AppConfigService, StorageService,
CookieService, LogService, ThumbnailService
} 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';
class TestNode {
entry?: MinimalNodeEntryEntity;
@@ -102,15 +110,27 @@ describe('NodeActionsService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [
CoreModule,
MatDialogModule,
MatIconModule,
HttpClientModule,
TranslateModule.forRoot(),
OverlayModule
],
providers: [
MatDialog,
DocumentListService,
AlfrescoApiService,
NodesApiService,
NodeActionsService]
{ provide: TranslationService, useClass: TranslationMock },
AuthenticationService,
UserPreferencesService,
AppConfigService,
CookieService,
LogService,
ThumbnailService,
StorageService,
ContentService,
DocumentListService,
NodeActionsService
]
});
service = TestBed.get(NodeActionsService);
@@ -203,36 +223,6 @@ describe('NodeActionsService', () => {
}));
});
describe('getFirstParentId', () => {
it('should give the parentId, if that exists on the node entry', () => {
const parentID = 'parent-id';
const contentEntities = [ {entry: {nodeId: '1234', parentId: parentID}} ];
expect(service.getFirstParentId(contentEntities)).toBe(parentID);
});
it('should give the last element in path property, if parentId is missing and path exists on the node entry', () => {
const firstParentId = 'parent-0-id';
const contentEntities = [ {entry: {nodeId: '1234', path: {elements: [ {id: 'parent-1-id'}, { id: firstParentId} ]} }} ];
expect(service.getFirstParentId(contentEntities)).toBe(firstParentId);
});
it('should give the id of the first node entry, if none of nodes has either parentId, or path properties', () => {
const nodeID = '1234';
const contentEntities = [ {entry: {id: nodeID}}, {entry: {id: `${nodeID}-2`}} ];
expect(service.getFirstParentId(contentEntities)).toBe(nodeID);
});
it('should give the nodeId of the first node entry, if none of nodes has either parentId, or path properties', () => {
const nodeID = '1234';
const contentEntities = [ {entry: {nodeId: nodeID}}, {entry: {id: `${nodeID}-2`}} ];
expect(service.getFirstParentId(contentEntities)).toBe(nodeID);
});
});
describe('getEntryParentId', () => {
it('should return the parentId, if that exists on the node entry', () => {
const parentID = 'parent-id';
@@ -258,7 +248,7 @@ describe('NodeActionsService', () => {
fileToCopy = new TestNode(fileId, isFile, 'file-name');
folderToCopy = new TestNode();
spyOn(service, 'getFirstParentId').and.returnValue('parent-id');
spyOn(service, 'getEntryParentId').and.returnValue('parent-id');
const dialog = TestBed.get(MatDialog);
spyOn(dialog, 'open').and.callFake((contentNodeSelectorComponent: any, data: any) => {
@@ -289,12 +279,17 @@ describe('NodeActionsService', () => {
let fileToCopy;
let folderToCopy;
let destinationFolder;
let translationService: TranslationService;
beforeEach(() => {
fileToCopy = new TestNode(fileId, isFile, 'file-name');
folderToCopy = new TestNode();
destinationFolder = new TestNode(folderDestinationId);
translationService = TestBed.get(TranslationService);
spyOn(translationService, 'instant').and.callFake(key => {
return key;
});
});
it('should be called', () => {
@@ -310,7 +305,7 @@ describe('NodeActionsService', () => {
it('should use the custom data object with custom rowFilter & imageResolver & title with destination picker', () => {
const spyOnBatchOperation = spyOn(service, 'doBatchOperation').and.callThrough();
const spyOnDestinationPicker = spyOn(service, 'getContentNodeSelection').and.callThrough();
spyOn(service, 'getFirstParentId').and.returnValue('parent-id');
spyOn(service, 'getEntryParentId').and.returnValue('parent-id');
let testContentNodeSelectorComponentData;
const dialog = TestBed.get(MatDialog);
@@ -328,7 +323,8 @@ describe('NodeActionsService', () => {
expect(testContentNodeSelectorComponentData).toBeDefined();
expect(testContentNodeSelectorComponentData.data.rowFilter({node: destinationFolder})).toBeDefined();
expect(testContentNodeSelectorComponentData.data.imageResolver({node: destinationFolder})).toBeDefined();
expect(testContentNodeSelectorComponentData.data.title).toBe('copy to ...');
expect(testContentNodeSelectorComponentData.data.title).toBe('NODE_SELECTOR.COPY_ITEMS');
expect(translationService.instant).toHaveBeenCalledWith('NODE_SELECTOR.COPY_ITEMS', {name: '', number: 2});
destinationFolder.entry['allowableOperations'] = ['update'];
expect(testContentNodeSelectorComponentData.data.imageResolver({node: destinationFolder})).toBeDefined();
@@ -337,7 +333,7 @@ describe('NodeActionsService', () => {
it('should use the ContentNodeSelectorComponentData object with file name in title', () => {
const spyOnBatchOperation = spyOn(service, 'doBatchOperation').and.callThrough();
spyOn(service, 'getContentNodeSelection').and.callThrough();
spyOn(service, 'getFirstParentId').and.returnValue('parent-id');
spyOn(service, 'getEntryParentId').and.returnValue('parent-id');
let testContentNodeSelectorComponentData;
const dialog = TestBed.get(MatDialog);
@@ -350,13 +346,14 @@ describe('NodeActionsService', () => {
expect(spyOnBatchOperation).toHaveBeenCalled();
expect(testContentNodeSelectorComponentData).toBeDefined();
expect(testContentNodeSelectorComponentData.data.title).toBe('copy \'entry-name\' to ...');
expect(testContentNodeSelectorComponentData.data.title).toBe('NODE_SELECTOR.COPY_ITEM');
expect(translationService.instant).toHaveBeenCalledWith('NODE_SELECTOR.COPY_ITEM', {name: 'entry-name', number: 1});
});
it('should use the ContentNodeSelectorComponentData object without file name in title, if no name exists', () => {
const spyOnBatchOperation = spyOn(service, 'doBatchOperation').and.callThrough();
spyOn(service, 'getContentNodeSelection').and.callThrough();
spyOn(service, 'getFirstParentId').and.returnValue('parent-id');
spyOn(service, 'getEntryParentId').and.returnValue('parent-id');
let testContentNodeSelectorComponentData;
const dialog = TestBed.get(MatDialog);
@@ -369,7 +366,8 @@ describe('NodeActionsService', () => {
expect(spyOnBatchOperation).toHaveBeenCalled();
expect(testContentNodeSelectorComponentData).toBeDefined();
expect(testContentNodeSelectorComponentData.data.title).toBe('copy to ...');
expect(testContentNodeSelectorComponentData.data.title).toBe('NODE_SELECTOR.COPY_ITEMS');
expect(translationService.instant).toHaveBeenCalledWith('NODE_SELECTOR.COPY_ITEMS', {name: '', number: 1});
});
});
@@ -700,9 +698,7 @@ describe('NodeActionsService', () => {
});
it('should call the documentListService moveNode directly for moving a file that has permission to be moved', () => {
const spyOnDestinationPicker =
spyOn(service, 'getContentNodeSelection')
.and.returnValue(Observable.of([destinationFolder.entry]));
spyOn(service, 'getContentNodeSelection').and.returnValue(Observable.of([destinationFolder.entry]));
fileToMove.entry['allowableOperations'] = [permissionToMove];
spyOnDocumentListServiceAction = spyOn(documentListService, 'moveNode').and.returnValue(Observable.of([fileToMove]));
spyOn(service, 'moveNodeAction');
+147 -46
View File
@@ -2,7 +2,7 @@
* @license
* Alfresco Example Content Application
*
* Copyright (C) 2005 - 2017 Alfresco Software Limited
* 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
@@ -23,13 +23,13 @@
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
import { EventEmitter, Injectable } from '@angular/core';
import { Injectable } from '@angular/core';
import { MatDialog } from '@angular/material';
import { Observable, Subject } from 'rxjs/Rx';
import { AlfrescoApiService, ContentService, NodesApiService, DataColumn, DataSorting } from '@alfresco/adf-core';
import { AlfrescoApiService, ContentService, NodesApiService, DataColumn, TranslationService } from '@alfresco/adf-core';
import { DocumentListService, ContentNodeSelectorComponent, ContentNodeSelectorComponentData } from '@alfresco/adf-content-services';
import { MinimalNodeEntity, MinimalNodeEntryEntity } from 'alfresco-js-api';
import { MinimalNodeEntity, MinimalNodeEntryEntity, SitePaging } from 'alfresco-js-api';
@Injectable()
export class NodeActionsService {
@@ -39,12 +39,14 @@ export class NodeActionsService {
contentCopied: Subject<MinimalNodeEntity[]> = new Subject<MinimalNodeEntity[]>();
contentMoved: Subject<any> = new Subject<any>();
moveDeletedEntries: any[] = [];
isSitesDestinationAvailable = false;
constructor(private contentService: ContentService,
private dialog: MatDialog,
private documentListService: DocumentListService,
private apiService: AlfrescoApiService,
private nodesApi: NodesApiService) {}
private nodesApi: NodesApiService,
private translation: TranslationService) {}
/**
* Copy node list
@@ -121,7 +123,6 @@ export class NodeActionsService {
},
observable.error.bind(observable)
);
this.dialog.closeAll();
});
} else {
@@ -144,23 +145,7 @@ export class NodeActionsService {
return !notAllowedNode;
}
getFirstParentId(nodeEntities: any[]): string {
for (let i = 0; i < nodeEntities.length; i++) {
const nodeEntry = nodeEntities[i].entry;
if (nodeEntry.parentId) {
return nodeEntry.parentId;
} else if (nodeEntry.path && nodeEntry.path.elements && nodeEntry.path.elements.length) {
return nodeEntry.path.elements[nodeEntry.path.elements.length - 1].id;
}
}
// if no parent data is found, return the id of first item / the nodeId in case of Shared Files
return nodeEntities[0].entry.nodeId || nodeEntities[0].entry.id;
}
getEntryParentId(nodeEntry: any) {
getEntryParentId(nodeEntry: MinimalNodeEntryEntity) {
let entryParentId = '';
if (nodeEntry.parentId) {
@@ -173,33 +158,52 @@ export class NodeActionsService {
return entryParentId;
}
getContentNodeSelection(action: string, contentEntities: MinimalNodeEntity[]): EventEmitter<MinimalNodeEntryEntity[]> {
const currentParentFolderId = this.getFirstParentId(contentEntities);
getContentNodeSelection(action: string, contentEntities: MinimalNodeEntity[]): Subject<MinimalNodeEntryEntity[]> {
const currentParentFolderId = this.getEntryParentId(contentEntities[0].entry);
let nodeEntryName = '';
if (contentEntities.length === 1 && contentEntities[0].entry.name) {
nodeEntryName = `'${contentEntities[0].entry.name}' `;
}
const customDropdown: SitePaging = {
list: {
entries: [
{
entry: {
guid: '-my-',
title: 'APP.BROWSE.PERSONAL.SIDENAV_LINK.LABEL'
}
},
{
entry: {
guid: '-mysites-',
title: 'APP.BROWSE.LIBRARIES.SIDENAV_LINK.LABEL'
}
}
]
}
};
const title = this.getTitleTranslation(action, contentEntities);
this.isSitesDestinationAvailable = false;
const data: ContentNodeSelectorComponentData = {
title: `${action} ${nodeEntryName}to ...`,
title: title,
currentFolderId: currentParentFolderId,
actionName: action,
dropdownHideMyFiles: true,
dropdownSiteList: [
{title: 'APP.BROWSE.PERSONAL.SIDENAV_LINK.LABEL', guid: '-my-'},
{title: 'APP.BROWSE.LIBRARIES.SIDENAV_LINK.LABEL', guid: '-mysites-'}],
dropdownSiteList: customDropdown,
rowFilter: this.rowFilter.bind(this),
imageResolver: this.imageResolver.bind(this),
select: new EventEmitter<MinimalNodeEntryEntity[]>()
isSelectionValid: this.canCopyMoveInsideIt.bind(this),
breadcrumbTransform: this.customizeBreadcrumb.bind(this),
select: new Subject<MinimalNodeEntryEntity[]>()
};
const matDialogRef = this.dialog.open(ContentNodeSelectorComponent, <any>{
/*const matDialogRef =*/ this.dialog.open(ContentNodeSelectorComponent, <any>{
data,
panelClass: 'adf-content-node-selector-dialog',
width: '630px'
});
const destinationPicker = matDialogRef.componentInstance;
// todo: add back the fix for [ACA-1054]:
/* const destinationPicker = matDialogRef.componentInstance;
const initialSiteChanged = destinationPicker.siteChanged;
destinationPicker.siteChanged = (chosenSite) => {
@@ -210,21 +214,116 @@ export class NodeActionsService {
} else {
destinationPicker.documentList.data.setSorting(new DataSorting('name', 'asc'));
}
};
};*/
const initialGetNextPageOfSearch = destinationPicker.getNextPageOfSearch;
destinationPicker.getNextPageOfSearch = (event) => {
destinationPicker.infiniteScroll = true;
destinationPicker.skipCount = event.skipCount;
if (destinationPicker.searchTerm.length > 0) {
initialGetNextPageOfSearch.call(destinationPicker, event);
}
};
data.select.subscribe({
complete: this.close.bind(this)
});
return data.select;
}
getTitleTranslation(action: string, nodes: MinimalNodeEntity[] = []): string {
let keyPrefix = 'ITEMS';
let name = '';
if (nodes.length === 1 && nodes[0].entry.name) {
name = nodes[0].entry.name;
keyPrefix = 'ITEM';
}
const number = nodes.length;
return this.translation.instant(`NODE_SELECTOR.${action.toUpperCase()}_${keyPrefix}`, {name, number});
}
private canCopyMoveInsideIt(entry: MinimalNodeEntryEntity): boolean {
return this.hasEntityCreatePermission(entry) && !this.isSite(entry);
}
private hasEntityCreatePermission(entry: MinimalNodeEntryEntity): boolean {
return this.contentService.hasPermission(entry, 'create');
}
private isSite(entry) {
return !!entry.guid || entry.nodeType === 'st:site' || entry.nodeType === 'st:sites';
}
close() {
this.dialog.closeAll();
}
// todo: review this approach once 5.2.3 is out
private customizeBreadcrumb(node: MinimalNodeEntryEntity) {
if (node && node.path && node.path.elements) {
const elements = node.path.elements;
if (elements.length > 1) {
if (elements[1].name === 'User Homes') {
elements.splice(0, 2);
// make sure first item is 'Personal Files'
if (elements[0]) {
elements[0].name = this.translation.instant('APP.BROWSE.PERSONAL.TITLE');
elements[0].id = '-my-';
} else {
node.name = this.translation.instant('APP.BROWSE.PERSONAL.TITLE');
}
} else if (elements[1].name === 'Sites') {
this.normalizeSitePath(node);
}
} else if (elements.length === 1) {
if (node.name === 'Sites') {
node.name = this.translation.instant('APP.BROWSE.LIBRARIES.TITLE');
elements.splice(0, 1);
}
}
} else if (node === null && this.isSitesDestinationAvailable) {
node = {
name: this.translation.instant('APP.BROWSE.LIBRARIES.TITLE'),
path: { elements: [] }
};
}
return node;
}
// todo: review this approach once 5.2.3 is out
private normalizeSitePath(node: MinimalNodeEntryEntity) {
const elements = node.path.elements;
// remove 'Company Home'
elements.splice(0, 1);
// replace first item with 'File Libraries'
elements[0].name = this.translation.instant('APP.BROWSE.LIBRARIES.TITLE');
// elements[0].id = '-mysites-'; // commented this until navigation on custom sources is enabled on document-list
if (this.isSiteContainer(node)) {
// rename 'documentLibrary' entry to the target site display name
// clicking on the breadcrumb entry loads the site content
node.name = elements[1].name;
// remove the site entry
elements.splice(1, 1);
} else {
// remove 'documentLibrary' in the middle of the path
const docLib = elements.findIndex(el => el.name === 'documentLibrary');
if (docLib > -1) {
elements.splice(docLib, 1);
}
}
}
isSiteContainer(node: MinimalNodeEntryEntity): boolean {
if (node && node.aspectNames && node.aspectNames.length > 0) {
return node.aspectNames.indexOf('st:siteContainer') >= 0;
}
return false;
}
copyNodeAction(nodeEntry, selectionId): Observable<any> {
if (nodeEntry.isFolder) {
return this.copyFolderAction(nodeEntry, selectionId);
@@ -454,6 +553,8 @@ export class NodeActionsService {
// todo: review once 1.10-beta6 is out
private rowFilter(row: /*ShareDataRow*/ any): boolean {
const node: MinimalNodeEntryEntity = row.node.entry;
this.isSitesDestinationAvailable = !!node['guid'];
return (!node.isFile && (node.nodeType !== 'app:folderlink'));
}
+1 -1
View File
@@ -2,7 +2,7 @@
* @license
* Alfresco Example Content Application
*
* Copyright (C) 2005 - 2017 Alfresco Software Limited
* 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
@@ -2,7 +2,7 @@
* @license
* Alfresco Example Content Application
*
* Copyright (C) 2005 - 2017 Alfresco Software Limited
* 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
@@ -23,11 +23,18 @@
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
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 { MaterialModule } from '../../common/material.module';
import { CoreModule, PeopleContentService } from '@alfresco/adf-core';
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';
@@ -44,13 +51,23 @@ describe('CurrentUserComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
MaterialModule,
CoreModule,
HttpClientModule,
TranslateModule.forRoot(),
NoopAnimationsModule,
MatMenuModule,
RouterTestingModule
],
declarations: [
CurrentUserComponent
]
],
providers: [
{ provide: TranslationService, useClass: TranslationMock },
AlfrescoApiService,
AppConfigService,
StorageService,
PeopleContentService
],
schemas: [ NO_ERRORS_SCHEMA ]
})
.compileComponents()
.then(() => {
@@ -2,7 +2,7 @@
* @license
* Alfresco Example Content Application
*
* Copyright (C) 2005 - 2017 Alfresco Software Limited
* 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
@@ -0,0 +1,6 @@
<div class="app-empty-folder">
<mat-icon class="app-empty-folder__icon">{{ icon }}</mat-icon>
<p class="app-empty-folder__title">{{ title | translate }}</p>
<p class="app-empty-folder__subtitle">{{ subtitle | translate }}</p>
<ng-content></ng-content>
</div>
@@ -0,0 +1,29 @@
@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;
}
}
@@ -0,0 +1,33 @@
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<EmptyFolderComponent>;
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();
});
});
@@ -0,0 +1,22 @@
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 = '';
}
@@ -17,7 +17,7 @@
mat-icon-button
*ngIf="hasSelection(documentList.selection)"
title="{{ 'APP.ACTIONS.DOWNLOAD' | translate }}"
[app-download-node]="documentList.selection">
[adfNodeDownload]="documentList.selection">
<mat-icon>get_app</mat-icon>
</button>
@@ -42,6 +42,7 @@
<button
mat-menu-item
#selection="adfFavorite"
(toggle)="refresh()"
[adf-node-favorite]="documentList.selection">
<mat-icon [ngClass]="{ 'icon-highlight': selection.hasFavorites() }">
{{ selection.hasFavorites() ? 'star' :'star_border' }}
@@ -79,18 +80,19 @@
currentFolderId="-favorites-"
selectionMode="multiple"
[navigate]="false"
[sorting]="[ 'modifiedAt', 'desc' ]"
[sorting]="sorting"
[contextMenuActions]="true"
[contentActions]="false"
(sorting-changed)="onSortingChanged($event)"
(node-dblclick)="onNodeDoubleClick($event.detail?.node?.entry)">
<empty-folder-content>
<ng-template>
<div class="empty-list__block">
<mat-icon>star_rate</mat-icon>
<p class="empty-list__title">{{ 'APP.BROWSE.FAVORITES.EMPTY_STATE.TITLE' | translate }}</p>
<p class="empty-list__text">{{ 'APP.BROWSE.FAVORITES.EMPTY_STATE.TEXT' | translate }}</p>
</div>
<app-empty-folder
icon="star_rate"
title="APP.BROWSE.FAVORITES.EMPTY_STATE.TITLE"
subtitle="APP.BROWSE.FAVORITES.EMPTY_STATE.TEXT">
</app-empty-folder>
</ng-template>
</empty-folder-content>
@@ -2,7 +2,7 @@
* @license
* Alfresco Example Content Application
*
* Copyright (C) 2005 - 2017 Alfresco Software Limited
* 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
@@ -23,14 +23,26 @@
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
import { Router } from '@angular/router';
import { RouterTestingModule } from '@angular/router/testing';
import { TestBed, async } from '@angular/core/testing';
import { Observable } from 'rxjs/Rx';
import { CoreModule, NodesApiService, AlfrescoApiService, ContentService } from '@alfresco/adf-core';
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 {
NotificationService, TranslationService, TranslationMock,
NodesApiService, AlfrescoApiService, ContentService,
UserPreferencesService, LogService, AppConfigService,
StorageService, CookieService, ThumbnailService,
AuthenticationService, TimeAgoPipe, NodeNameTooltipPipe,
NodeFavoriteDirective, DataTableComponent
} from '@alfresco/adf-core';
import { DocumentListComponent } from '@alfresco/adf-content-services';
import { CommonModule } from '../../common/common.module';
import { LocationLinkComponent } from '../location-link/location-link.component';
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 { ContentManagementService } from '../../common/services/content-management.service';
import { FavoritesComponent } from './favorites.component';
@@ -42,6 +54,7 @@ describe('Favorites Routed Component', () => {
let alfrescoApi: AlfrescoApiService;
let alfrescoContentService: ContentService;
let contentService: ContentManagementService;
let preferenceService: UserPreferencesService;
let router: Router;
let page;
let node;
@@ -75,14 +88,39 @@ describe('Favorites Routed Component', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
CoreModule,
CommonModule,
RouterTestingModule
MatMenuModule,
NoopAnimationsModule,
HttpClientModule,
TranslateModule.forRoot(),
RouterTestingModule,
MatSnackBarModule, MatIconModule
],
declarations: [
LocationLinkComponent,
DataTableComponent,
TimeAgoPipe,
NodeNameTooltipPipe,
NodeFavoriteDirective,
DocumentListComponent,
FavoritesComponent
]
],
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
],
schemas: [ NO_ERRORS_SCHEMA ]
})
.compileComponents().then(() => {
fixture = TestBed.createComponent(FavoritesComponent);
@@ -92,6 +130,7 @@ describe('Favorites Routed Component', () => {
alfrescoApi = TestBed.get(AlfrescoApiService);
alfrescoContentService = TestBed.get(ContentService);
contentService = TestBed.get(ContentManagementService);
preferenceService = TestBed.get(UserPreferencesService);
router = TestBed.get(Router);
});
}));
@@ -101,20 +140,31 @@ describe('Favorites Routed Component', () => {
});
describe('Events', () => {
it('should refresh on editing folder event', () => {
beforeEach(() => {
spyOn(component, 'refresh');
fixture.detectChanges();
});
it('should refresh on editing folder event', () => {
alfrescoContentService.folderEdit.next(null);
expect(component.refresh).toHaveBeenCalled();
});
it('should refresh on move node event', () => {
spyOn(component, 'refresh');
fixture.detectChanges();
contentService.nodeMoved.next(null);
contentService.moveNode.next(null);
expect(component.refresh).toHaveBeenCalled();
});
it('should refresh on node deleted event', () => {
contentService.nodeDeleted.next(null);
expect(component.refresh).toHaveBeenCalled();
});
it('should refresh on node restore event', () => {
contentService.nodeRestored.next(null);
expect(component.refresh).toHaveBeenCalled();
});
@@ -174,7 +224,7 @@ describe('Favorites Routed Component', () => {
component.onNodeDoubleClick(node);
expect(router.navigate).toHaveBeenCalledWith(['/preview', node.id]);
expect(router.navigate['calls'].argsFor(0)[0]).toEqual(['./preview', 'folder-node']);
});
});
@@ -238,4 +288,35 @@ describe('Favorites Routed Component', () => {
expect(component.documentList.reload).toHaveBeenCalled();
});
});
describe('onSortingChanged', () => {
it('should save sorting input', () => {
spyOn(preferenceService, 'set');
const event = <any>{
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 = <any>{
detail: {}
};
component.onSortingChanged(event);
expect(preferenceService.set).toHaveBeenCalledWith('prefix.sorting.key', 'modifiedAt');
expect(preferenceService.set).toHaveBeenCalledWith('prefix.sorting.direction', 'desc');
});
});
});
@@ -2,7 +2,7 @@
* @license
* Alfresco Example Content Application
*
* Copyright (C) 2005 - 2017 Alfresco Software Limited
* 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
@@ -24,7 +24,7 @@
*/
import { Component, ViewChild, OnInit, OnDestroy } from '@angular/core';
import { Router } from '@angular/router';
import { Router, ActivatedRoute } from '@angular/router';
import { Subscription } from 'rxjs/Rx';
import { MinimalNodeEntryEntity, MinimalNodeEntity, PathElementEntity, PathInfo } from 'alfresco-js-api';
@@ -44,21 +44,28 @@ export class FavoritesComponent extends PageComponent implements OnInit, OnDestr
private subscriptions: Subscription[] = [];
constructor(
private router: Router,
private nodesApi: NodesApiService,
private contentService: ContentService,
private content: ContentManagementService,
preferences: UserPreferencesService) {
sorting = [ 'modifiedAt', 'desc' ];
constructor(private router: Router,
private route: ActivatedRoute,
private nodesApi: NodesApiService,
private contentService: ContentService,
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];
}
ngOnInit() {
this.subscriptions = this.subscriptions.concat([
this.content.deleteNode.subscribe(() => this.refresh()),
this.content.restoreNode.subscribe(() => this.refresh()),
this.content.nodeDeleted.subscribe(() => this.refresh()),
this.content.nodeRestored.subscribe(() => this.refresh()),
this.contentService.folderEdit.subscribe(() => this.refresh()),
this.content.moveNode.subscribe(() => this.refresh())
this.content.nodeMoved.subscribe(() => this.refresh())
]);
}
@@ -95,7 +102,7 @@ export class FavoritesComponent extends PageComponent implements OnInit, OnDestr
}
if (node.isFile) {
this.router.navigate(['/preview', node.id]);
this.router.navigate(['./preview', node.id], { relativeTo: this.route });
}
}
}
@@ -109,4 +116,13 @@ export class FavoritesComponent extends PageComponent implements OnInit, OnDestr
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;
}
}
@@ -19,7 +19,7 @@
mat-icon-button
*ngIf="hasSelection(documentList.selection)"
title="{{ 'APP.ACTIONS.DOWNLOAD' | translate }}"
[app-download-node]="documentList.selection">
[adfNodeDownload]="documentList.selection">
<mat-icon>get_app</mat-icon>
</button>
@@ -91,7 +91,7 @@
[attr.class]="documentList.isEmpty() ? 'empty-list' : ''"
[loading]="isLoading"
[node]="paging"
[sorting]="[ 'modifiedAt', 'desc' ]"
[sorting]="sorting"
[allowDropFiles]="true"
[contextMenuActions]="true"
@@ -100,6 +100,7 @@
[selectionMode]="'multiple'"
[imageResolver]="imageResolver"
(sorting-changed)="onSortingChanged($event)"
(node-dblclick)="onNodeDoubleClick($event)"
(node-select)="onNodeSelect($event, documentList)">
+124 -16
View File
@@ -2,7 +2,7 @@
* @license
* Alfresco Example Content Application
*
* Copyright (C) 2005 - 2017 Alfresco Software Limited
* 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
@@ -24,17 +24,28 @@
*/
import { Observable } from 'rxjs/Rx';
import { Router } from '@angular/router';
import { RouterTestingModule } from '@angular/router/testing';
import { TestBed, async } from '@angular/core/testing';
import { UploadService, NodesApiService, FileUploadCompleteEvent,
FileUploadDeleteEvent, FileModel, ContentService } from '@alfresco/adf-core';
import { CommonModule } from '../../common/common.module';
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
} from '@alfresco/adf-core';
import { DocumentListComponent } from '@alfresco/adf-content-services';
import { MatMenuModule, MatSnackBarModule, MatIconModule, MatDialogModule } from '@angular/material';
import { DocumentListService } 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 { GenericErrorComponent } from '../generic-error/generic-error.component';
import { FilesComponent } from './files.component';
describe('FilesComponent', () => {
@@ -49,17 +60,50 @@ describe('FilesComponent', () => {
let router: Router;
let browsingFilesService: BrowsingFilesService;
let nodeActionsService: NodeActionsService;
let preferenceService: UserPreferencesService;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
MatMenuModule,
NoopAnimationsModule,
HttpClientModule,
TranslateModule.forRoot(),
RouterTestingModule,
CommonModule
MatSnackBarModule, MatIconModule,
MatDialogModule
],
declarations: [
FilesComponent,
GenericErrorComponent
]
DataTableComponent,
TimeAgoPipe,
NodeNameTooltipPipe,
NodeFavoriteDirective,
DocumentListComponent,
FileSizePipe
],
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,
UploadService,
BrowsingFilesService
],
schemas: [ NO_ERRORS_SCHEMA ]
}).compileComponents()
.then(() => {
@@ -73,11 +117,12 @@ describe('FilesComponent', () => {
alfrescoContentService = TestBed.get(ContentService);
browsingFilesService = TestBed.get(BrowsingFilesService);
nodeActionsService = TestBed.get(NodeActionsService);
preferenceService = TestBed.get(UserPreferencesService);
});
}));
beforeEach(() => {
node = { id: 'node-id' };
node = { id: 'node-id', isFolder: true };
page = {
list: {
entries: ['a', 'b', 'c'],
@@ -134,6 +179,17 @@ describe('FilesComponent', () => {
expect(component.onFetchError).toHaveBeenCalled();
});
it('if should navigate to parent if node is not a folder', () => {
node.isFolder = false;
node.parentId = 'parent-id';
spyOn(component, 'fetchNode').and.returnValue(Observable.of(node));
spyOn(router, 'navigate');
fixture.detectChanges();
expect(router.navigate['calls'].argsFor(0)[0]).toEqual(['/personal-files', 'parent-id']);
});
});
describe('refresh on events', () => {
@@ -184,19 +240,19 @@ describe('FilesComponent', () => {
});
it('calls refresh deleteNode event', () => {
contentManagementService.deleteNode.next();
contentManagementService.nodeDeleted.next();
expect(component.load).toHaveBeenCalled();
});
it('calls refresh moveNode event', () => {
contentManagementService.moveNode.next();
contentManagementService.nodeMoved.next();
expect(component.load).toHaveBeenCalled();
});
it('calls refresh restoreNode event', () => {
contentManagementService.restoreNode.next();
contentManagementService.nodeRestored.next();
expect(component.load).toHaveBeenCalled();
});
@@ -304,6 +360,7 @@ describe('FilesComponent', () => {
it('opens preview if node is file', () => {
spyOn(router, 'navigate').and.stub();
node.isFile = true;
node.isFolder = false;
const event: any = {
detail: {
@@ -314,7 +371,7 @@ describe('FilesComponent', () => {
};
component.onNodeDoubleClick(event);
expect(router.navigate).toHaveBeenCalledWith(['/preview', node.id]);
expect(router.navigate['calls'].argsFor(0)[0]).toEqual(['./preview', node.id]);
});
it('navigate if node is folder', () => {
@@ -443,4 +500,55 @@ describe('FilesComponent', () => {
expect(router.navigate).toHaveBeenCalledWith(['./'], jasmine.any(Object));
});
});
describe('isSiteContainer', () => {
it('should return false if node has no aspectNames', () => {
const mock = { aspectNames: [] };
expect(component.isSiteContainer(mock)).toBe(false);
});
it('should return false if node is not site container', () => {
const mock = { aspectNames: ['something-else'] };
expect(component.isSiteContainer(mock)).toBe(false);
});
it('should return true if node is a site container', () => {
const mock = { aspectNames: [ 'st:siteContainer' ] };
expect(component.isSiteContainer(mock)).toBe(true);
});
});
describe('onSortingChanged', () => {
it('should save sorting input', () => {
spyOn(preferenceService, 'set');
const event = <any>{
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 = <any>{
detail: {}
};
component.onSortingChanged(event);
expect(preferenceService.set).toHaveBeenCalledWith('prefix.sorting.key', 'modifiedAt');
expect(preferenceService.set).toHaveBeenCalledWith('prefix.sorting.direction', 'desc');
});
});
});
+43 -21
View File
@@ -2,7 +2,7 @@
* @license
* Alfresco Example Content Application
*
* Copyright (C) 2005 - 2017 Alfresco Software Limited
* 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
@@ -49,19 +49,25 @@ export class FilesComponent extends PageComponent implements OnInit, OnDestroy {
private nodePath: PathElement[];
private subscriptions: Subscription[] = [];
constructor(
private router: Router,
private zone: NgZone,
private route: ActivatedRoute,
private nodesApi: NodesApiService,
private nodeActionsService: NodeActionsService,
private uploadService: UploadService,
private contentManagementService: ContentManagementService,
private browsingFilesService: BrowsingFilesService,
private contentService: ContentService,
private apiService: AlfrescoApiService,
preferences: UserPreferencesService) {
sorting = [ 'modifiedAt', 'desc' ];
constructor(private router: Router,
private zone: NgZone,
private route: ActivatedRoute,
private nodesApi: NodesApiService,
private nodeActionsService: NodeActionsService,
private uploadService: UploadService,
private contentManagementService: ContentManagementService,
private browsingFilesService: BrowsingFilesService,
private contentService: ContentService,
private apiService: AlfrescoApiService,
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];
}
ngOnInit() {
@@ -71,12 +77,19 @@ export class FilesComponent extends PageComponent implements OnInit, OnDestroy {
this.routeData = data;
this.title = data.i18nTitle;
route.params.subscribe(({ id }: Params) => {
const nodeId = id || data.defaultNodeId;
route.params.subscribe(({ folderId }: Params) => {
const nodeId = folderId || data.defaultNodeId;
this.isLoading = true;
this.fetchNode(nodeId)
.do((node) => this.updateCurrentNode(node))
.do((node) => {
if (node.isFolder) {
this.updateCurrentNode(node);
} else {
this.router.navigate(['/personal-files', node.parentId], { replaceUrl: true });
}
})
.skipWhile(node => !node.isFolder)
.flatMap((node) => this.fetchNodes(node.id))
.subscribe(
(page) => {
@@ -94,9 +107,9 @@ export class FilesComponent extends PageComponent implements OnInit, OnDestroy {
nodeActionsService.contentCopied.subscribe((nodes) => this.onContentCopied(nodes)),
contentService.folderCreate.subscribe(() => this.load(false, this.pagination)),
contentService.folderEdit.subscribe(() => this.load(false, this.pagination)),
contentManagementService.deleteNode.subscribe(() => this.load(false, this.pagination)),
contentManagementService.moveNode.subscribe(() => this.load(false, this.pagination)),
contentManagementService.restoreNode.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))
]);
@@ -148,7 +161,7 @@ export class FilesComponent extends PageComponent implements OnInit, OnDestroy {
event.preventDefault();
} else if (node.isFile) {
this.router.navigate(['/preview', node.id]);
this.router.navigate(['./preview', node.id], { relativeTo: this.route });
}
}
@@ -158,7 +171,7 @@ export class FilesComponent extends PageComponent implements OnInit, OnDestroy {
showPreview(node: MinimalNodeEntryEntity) {
if (node) {
if (node.isFile) {
this.router.navigate(['/preview', node.id]);
this.router.navigate(['./preview', node.id], { relativeTo: this.route });
}
}
}
@@ -294,4 +307,13 @@ 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');
}
private get prefix() {
return this.route.snapshot.data.preferencePrefix;
}
}
@@ -2,7 +2,7 @@
* @license
* Alfresco Example Content Application
*
* Copyright (C) 2005 - 2017 Alfresco Software Limited
* 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
@@ -2,7 +2,7 @@
* @license
* Alfresco Example Content Application
*
* Copyright (C) 2005 - 2017 Alfresco Software Limited
* 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
@@ -23,15 +23,14 @@
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { TestBed } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { CoreModule, AppConfigService, PeopleContentService } from '@alfresco/adf-core';
import { AppConfigService, PeopleContentService } from '@alfresco/adf-core';
import { HttpClientModule } from '@angular/common/http';
import { Observable } from 'rxjs/Rx';
import { CommonModule } from './../../common/common.module';
import { HeaderComponent } from './header.component';
import { SearchComponent } from '../search/search.component';
import { CurrentUserComponent } from '../current-user/current-user.component';
describe('HeaderComponent', () => {
let fixture;
@@ -41,15 +40,17 @@ describe('HeaderComponent', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [
CoreModule,
RouterTestingModule,
CommonModule
HttpClientModule,
RouterTestingModule
],
declarations: [
HeaderComponent,
SearchComponent,
CurrentUserComponent
]
HeaderComponent
],
providers: [
AppConfigService,
PeopleContentService
],
schemas: [ NO_ERRORS_SCHEMA ]
})
.overrideProvider(PeopleContentService, {
useValue: {
@@ -65,16 +66,24 @@ describe('HeaderComponent', () => {
if (val === 'application.name') {
return 'app-name';
}
if (val === 'headerColor') {
return 'some-color';
}
if (val === 'application.logo') {
return '';
}
});
fixture.detectChanges();
});
it('should get application name from configuration file', () => {
expect(appConfigService.get).toHaveBeenCalledWith('application.name');
});
it('it should set application name', () => {
expect(component.appName).toBe('app-name');
});
it('it should set header background color', () => {
expect(component.backgroundColor).toBe('some-color');
});
});
@@ -2,7 +2,7 @@
* @license
* Alfresco Example Content Application
*
* Copyright (C) 2005 - 2017 Alfresco Software Limited
* 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
@@ -2,7 +2,7 @@
* @license
* Alfresco Example Content Application
*
* Copyright (C) 2005 - 2017 Alfresco Software Limited
* 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
@@ -23,19 +23,22 @@
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
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 { CoreModule, ContentService, PeopleContentService, AppConfigService } from '@alfresco/adf-core';
import { TranslateModule } from '@ngx-translate/core';
import { HttpClientModule } from '@angular/common/http';
import {
ContentService, PeopleContentService, AppConfigService,
AuthenticationService, UserPreferencesService, TranslationService,
TranslationMock, StorageService, AlfrescoApiService, CookieService,
LogService
} from '@alfresco/adf-core';
import { Observable } from 'rxjs/Observable';
import { BrowsingFilesService } from '../../common/services/browsing-files.service';
import { LayoutComponent } from './layout.component';
import { CommonModule } from './../../common/common.module';
import { HeaderComponent } from '../header/header.component';
import { SidenavComponent } from '../sidenav/sidenav.component';
import { SearchComponent } from '../search/search.component';
import { CurrentUserComponent } from '../current-user/current-user.component';
describe('LayoutComponent', () => {
let fixture: ComponentFixture<LayoutComponent>;
@@ -55,26 +58,32 @@ describe('LayoutComponent', () => {
TestBed.configureTestingModule({
imports: [
CoreModule,
RouterTestingModule,
CommonModule
HttpClientModule,
TranslateModule.forRoot(),
RouterTestingModule
],
declarations: [
LayoutComponent,
HeaderComponent,
SidenavComponent,
SearchComponent,
CurrentUserComponent
LayoutComponent
],
providers: [
{ provide: TranslationService, useClass: TranslationMock },
AlfrescoApiService,
StorageService,
CookieService,
LogService,
UserPreferencesService,
AuthenticationService,
ContentService,
AppConfigService,
BrowsingFilesService,
{
provide: PeopleContentService,
useValue: {
getCurrentPerson: () => Observable.of({ entry: {} })
}
}
]
],
schemas: [ NO_ERRORS_SCHEMA ]
});
fixture = TestBed.createComponent(LayoutComponent);
@@ -2,7 +2,7 @@
* @license
* Alfresco Example Content Application
*
* Copyright (C) 2005 - 2017 Alfresco Software Limited
* 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
@@ -13,18 +13,19 @@
currentFolderId="-mysites-"
selectionMode="none"
[navigate]="false"
[sorting]="[ 'name', 'asc' ]"
[sorting]="sorting"
[contextMenuActions]="true"
[contentActions]="false"
(sorting-changed)="onSortingChanged($event)"
(node-dblclick)="onNodeDoubleClick($event)">
<empty-folder-content>
<ng-template>
<div class="empty-list__block">
<mat-icon>group_work</mat-icon>
<p class="empty-list__title">{{ 'APP.BROWSE.LIBRARIES.EMPTY_STATE.TITLE' | translate }}</p>
<p class="empty-list__text">{{ 'APP.BROWSE.LIBRARIES.EMPTY_STATE.TEXT' | translate }}</p>
</div>
<app-empty-folder
icon="group_work"
title="APP.BROWSE.LIBRARIES.EMPTY_STATE.TITLE"
subtitle="APP.BROWSE.LIBRARIES.EMPTY_STATE.TEXT">
</app-empty-folder>
</ng-template>
</empty-folder-content>
@@ -2,7 +2,7 @@
* @license
* Alfresco Example Content Application
*
* Copyright (C) 2005 - 2017 Alfresco Software Limited
* 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
@@ -23,15 +23,26 @@
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
import { Router } from '@angular/router';
import { RouterTestingModule } from '@angular/router/testing';
import { TestBed, async } from '@angular/core/testing';
import { Observable } from 'rxjs/Rx';
import { CoreModule , NodesApiService, AlfrescoApiService} from '@alfresco/adf-core';
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
} from '@alfresco/adf-core';
import { DocumentListComponent } 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 { ShareDataTableAdapter } from '@alfresco/adf-content-services';
import { CommonModule } from '../../common/common.module';
import { LibrariesComponent } from './libraries.component';
describe('Libraries Routed Component', () => {
@@ -40,6 +51,7 @@ describe('Libraries Routed Component', () => {
let nodesApi: NodesApiService;
let alfrescoApi: AlfrescoApiService;
let router: Router;
let preferenceService: UserPreferencesService;
let page;
let node;
@@ -62,13 +74,38 @@ describe('Libraries Routed Component', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
CoreModule,
MatMenuModule,
NoopAnimationsModule,
HttpClientModule,
TranslateModule.forRoot(),
RouterTestingModule,
CommonModule
MatSnackBarModule, MatIconModule
],
declarations: [
DataTableComponent,
TimeAgoPipe,
NodeNameTooltipPipe,
NodeFavoriteDirective,
DocumentListComponent,
LibrariesComponent
]
],
providers: [
{ provide: ActivatedRoute, useValue: {
snapshot: { data: { preferencePrefix: 'prefix' } }
} } ,
{ provide: TranslationService, useClass: TranslationMock },
AuthenticationService,
UserPreferencesService,
AppConfigService, StorageService, CookieService,
AlfrescoApiService,
LogService,
NotificationService,
ContentService,
NodesApiService,
DocumentListService,
ThumbnailService
],
schemas: [ NO_ERRORS_SCHEMA ]
})
.compileComponents().then(() => {
fixture = TestBed.createComponent(LibrariesComponent);
@@ -77,11 +114,13 @@ describe('Libraries Routed Component', () => {
nodesApi = TestBed.get(NodesApiService);
alfrescoApi = TestBed.get(AlfrescoApiService);
router = TestBed.get(Router);
preferenceService = TestBed.get(UserPreferencesService);
});
}));
beforeEach(() => {
spyOn(alfrescoApi.sitesApi, 'getSites').and.returnValue((Promise.resolve(page)));
spyOn(alfrescoApi.peopleApi, 'getSiteMembership').and.returnValue((Promise.resolve({})));
});
describe('makeLibraryTooltip()', () => {
@@ -191,4 +230,35 @@ describe('Libraries Routed Component', () => {
expect(component.navigate).not.toHaveBeenCalled();
});
});
describe('onSortingChanged', () => {
it('should save sorting input', () => {
spyOn(preferenceService, 'set');
const event = <any>{
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 = <any>{
detail: {}
};
component.onSortingChanged(event);
expect(preferenceService.set).toHaveBeenCalledWith('prefix.sorting.key', 'modifiedAt');
expect(preferenceService.set).toHaveBeenCalledWith('prefix.sorting.direction', 'desc');
});
});
});
@@ -2,7 +2,7 @@
* @license
* Alfresco Example Content Application
*
* Copyright (C) 2005 - 2017 Alfresco Software Limited
* 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
@@ -38,12 +38,18 @@ export class LibrariesComponent extends PageComponent {
@ViewChild(DocumentListComponent)
documentList: DocumentListComponent;
constructor(
private nodesApi: NodesApiService,
private route: ActivatedRoute,
private router: Router,
preferences: UserPreferencesService) {
sorting = [ 'title', 'asc' ];
constructor(private nodesApi: NodesApiService,
private route: ActivatedRoute,
private router: Router,
preferences: UserPreferencesService) {
super(preferences);
const sortingKey = preferences.get(`${this.prefix}.sorting.key`) || 'title';
const sortingDirection = preferences.get(`${this.prefix}.sorting.direction`) || 'desc';
this.sorting = [sortingKey, sortingDirection];
}
makeLibraryTooltip(library: any): string {
@@ -90,4 +96,13 @@ export class LibrariesComponent extends PageComponent {
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;
}
}
@@ -2,7 +2,7 @@
* @license
* Alfresco Example Content Application
*
* Copyright (C) 2005 - 2017 Alfresco Software Limited
* 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
@@ -25,7 +25,7 @@
import { Component, Input, ChangeDetectionStrategy, OnInit, ViewEncapsulation } from '@angular/core';
import { AlfrescoApiService, DataColumn, DataRow, DataTableAdapter } from '@alfresco/adf-core';
import { PathInfoEntity, AlfrescoApi } from 'alfresco-js-api';
import { PathInfoEntity } from 'alfresco-js-api';
import { Observable } from 'rxjs/Rx';
@Component({
@@ -1,5 +1,5 @@
<adf-login
copyrightText="&#169; 2017 Alfresco Software, Inc. All rights reserved."
copyrightText="&#169; 2017 - 2018 Alfresco Software, Inc. All rights reserved."
providers="ECM"
[showRememberMe]="false"
[showLoginActions]="false"
@@ -2,7 +2,7 @@
* @license
* Alfresco Example Content Application
*
* Copyright (C) 2005 - 2017 Alfresco Software Limited
* 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
@@ -23,11 +23,18 @@
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
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 { CoreModule, AuthenticationService, UserPreferencesService } from '@alfresco/adf-core';
import {
AuthenticationService, UserPreferencesService, TranslationService,
TranslationMock, AppConfigService, StorageService, AlfrescoApiService,
CookieService, LogService
} from '@alfresco/adf-core';
import { LoginComponent } from './login.component';
@@ -42,15 +49,25 @@ describe('LoginComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
RouterTestingModule,
CoreModule
HttpClientModule,
TranslateModule.forRoot(),
RouterTestingModule
],
declarations: [
LoginComponent
],
providers: [
Location
]
{ provide: TranslationService, useClass: TranslationMock },
Location,
CookieService,
LogService,
StorageService,
AlfrescoApiService,
AppConfigService,
AuthenticationService,
UserPreferencesService
],
schemas: [ NO_ERRORS_SCHEMA ]
});
fixture = TestBed.createComponent(LoginComponent);
+1 -1
View File
@@ -2,7 +2,7 @@
* @license
* Alfresco Example Content Application
*
* Copyright (C) 2005 - 2017 Alfresco Software Limited
* 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
+49 -3
View File
@@ -2,7 +2,7 @@
* @license
* Alfresco Example Content Application
*
* Copyright (C) 2005 - 2017 Alfresco Software Limited
* 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
@@ -23,8 +23,6 @@
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
import { TestBed } from '@angular/core/testing';
import { PageComponent } from './page.component';
class TestClass extends PageComponent {
@@ -300,4 +298,52 @@ describe('PageComponent', () => {
expect(component.nodeHasPermission(node, 'some-operation')).toBe(false);
});
});
describe('canDeleteShared()', () => {
it('should return true if shared node can be deleted', () => {
const selection = [ { entry: {
allowableOperationsOnTarget: ['delete']
} } ];
expect(component.canDeleteShared(selection)).toBe(true);
});
it(`should return false if shared node doesn't have permission`, () => {
const selection = [ { entry: {
allowableOperationsOnTarget: ['something']
} } ];
expect(component.canDeleteShared(selection)).toBe(false);
});
it(`should return false if shared node doesn't have permissions property`, () => {
const selection = [ { entry: { } } ];
expect(component.canDeleteShared(selection)).toBe(false);
});
});
describe('canMoveShared()', () => {
it('should return true if shared node can be moved', () => {
const selection = [ { entry: {
allowableOperationsOnTarget: ['delete']
} } ];
expect(component.canMoveShared(selection)).toBe(true);
});
it(`should return false if shared node doesn't have permission`, () => {
const selection = [ { entry: {
allowableOperationsOnTarget: ['something']
} } ];
expect(component.canMoveShared(selection)).toBe(false);
});
it(`should return false if shared node doesn't have permissions property`, () => {
const selection = [ { entry: { } } ];
expect(component.canMoveShared(selection)).toBe(false);
});
});
});
+23 -3
View File
@@ -2,7 +2,7 @@
* @license
* Alfresco Example Content Application
*
* Copyright (C) 2005 - 2017 Alfresco Software Limited
* 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
@@ -45,7 +45,7 @@ export abstract class PageComponent {
abstract fetchNodes(parentNodeId?: string, options?: any): void;
constructor(private preferences: UserPreferencesService) {
constructor(protected preferences: UserPreferencesService) {
}
onFetchError(error: any) {
@@ -111,10 +111,18 @@ export abstract class PageComponent {
return selection.every(node => node.entry && this.nodeHasPermission(node.entry, 'delete'));
}
canDeleteShared(selection: Array<MinimalNodeEntity> = []): boolean {
return selection.every(node => node.entry && this.nodeSharedHasPermission(node.entry, 'delete'));
}
canMove(selection: Array<MinimalNodeEntity>): boolean {
return this.canDelete(selection);
}
canMoveShared(selection: Array<MinimalNodeEntity> = []): boolean {
return selection.every(node => node.entry && this.nodeSharedHasPermission(node.entry, 'delete'));
}
canPreviewFile(selection: Array<MinimalNodeEntity>): boolean {
return this.isFileSelected(selection);
}
@@ -127,7 +135,7 @@ export abstract class PageComponent {
return this.isFileSelected(selection);
}
nodeHasPermission(node: MinimalNodeEntryEntity, permission: string) {
nodeHasPermission(node: MinimalNodeEntryEntity, permission: string): boolean {
if (node && permission) {
const { allowableOperations = [] } = <any>(node || {});
@@ -139,6 +147,18 @@ export abstract class PageComponent {
return false;
}
nodeSharedHasPermission(node: MinimalNodeEntryEntity, permission: string): boolean {
if (node && permission) {
const { allowableOperationsOnTarget } = <any>(node || {});
if (allowableOperationsOnTarget && allowableOperationsOnTarget.indexOf(permission) > -1) {
return true;
}
}
return false;
}
onChangePageSize(event: Pagination): void {
this.preferences.paginationSize = event.maxItems;
}
@@ -1,3 +1,50 @@
<ng-container *ngIf="nodeId">
<adf-viewer [fileNodeId]="nodeId"></adf-viewer>
<adf-viewer
[fileNodeId]="nodeId"
[allowNavigate]="navigateMultiple"
[canNavigateBefore]="previousNodeId"
[canNavigateNext]="nextNodeId"
[overlayMode]="true"
(showViewerChange)="onVisibilityChanged($event)"
(navigateBefore)="onNavigateBefore()"
(navigateNext)="onNavigateNext()">
<adf-viewer-more-actions>
<button
mat-menu-item
#selection="adfFavorite"
[adf-node-favorite]="selectedEntities">
<mat-icon [ngClass]="{ 'icon-highlight': selection.hasFavorites() }">
{{ selection.hasFavorites() ? 'star' :'star_border' }}
</mat-icon>
<span>{{ 'APP.ACTIONS.FAVORITE' | translate }}</span>
</button>
<button
mat-menu-item
*ngIf="canCopyFile()"
[app-copy-node]="selectedEntities">
<mat-icon>content_copy</mat-icon>
<span>{{ 'APP.ACTIONS.COPY' | translate }}</span>
</button>
<button
mat-menu-item
*ngIf="canMoveFile()"
[app-move-node]="selectedEntities">
<mat-icon>library_books</mat-icon>
<span>{{ 'APP.ACTIONS.MOVE' | translate }}</span>
</button>
<button
mat-menu-item
*ngIf="canDeleteFile()"
(click)="deleteFile()">
<mat-icon>delete</mat-icon>
<span>{{ 'APP.ACTIONS.DELETE' | translate }}</span>
</button>
</adf-viewer-more-actions>
</adf-viewer>
</ng-container>
@@ -0,0 +1,681 @@
/*!
* @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 <http://www.gnu.org/licenses/>.
*/
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 { PreviewComponent } from './preview.component';
import { Observable } from 'rxjs/Rx';
import { ContentManagementService } from '../../common/services/content-management.service';
import { MatSnackBarModule } from '@angular/material';
describe('PreviewComponent', () => {
let fixture: ComponentFixture<PreviewComponent>;
let component: PreviewComponent;
let router: Router;
let route: ActivatedRoute;
let alfrescoApi: AlfrescoApiService;
let preferences: UserPreferencesService;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
HttpClientModule,
RouterTestingModule,
TranslateModule.forRoot(),
MatSnackBarModule
],
providers: [
{ provide: TranslationService, useClass: TranslationMock },
AlfrescoApiService,
AppConfigService,
StorageService,
CookieService,
NotificationService,
UserPreferencesService,
ContentManagementService
],
declarations: [
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);
preferences = TestBed.get(UserPreferencesService);
});
}));
it('should extract the property path root', () => {
expect(component.getRootField('some.property.path')).toBe('some');
expect(component.getRootField('some')).toBe('some');
expect(component.getRootField('')).toBe('');
expect(component.getRootField(null)).toBe(null);
});
it('should navigate to previous node in sub-folder', () => {
spyOn(router, 'navigate').and.stub();
component.previewLocation = 'personal-files';
component.folderId = 'folder1';
component.previousNodeId = 'previous1';
component.onNavigateBefore();
expect(router.navigate).toHaveBeenCalledWith(
['personal-files', 'folder1', 'preview', 'previous1']
);
});
it('should navigate back to previous node in the root path', () => {
spyOn(router, 'navigate').and.stub();
component.previewLocation = 'personal-files';
component.folderId = null;
component.previousNodeId = 'previous1';
component.onNavigateBefore();
expect(router.navigate).toHaveBeenCalledWith(
['personal-files', 'preview', 'previous1']
);
});
it('should not navigate back if node unset', () => {
spyOn(router, 'navigate').and.stub();
component.previousNodeId = null;
component.onNavigateBefore();
expect(router.navigate).not.toHaveBeenCalled();
});
it('should navigate to next node in sub-folder', () => {
spyOn(router, 'navigate').and.stub();
component.previewLocation = 'personal-files';
component.folderId = 'folder1';
component.nextNodeId = 'next1';
component.onNavigateNext();
expect(router.navigate).toHaveBeenCalledWith(
['personal-files', 'folder1', 'preview', 'next1']
);
});
it('should navigate to next node in the root path', () => {
spyOn(router, 'navigate').and.stub();
component.previewLocation = 'personal-files';
component.folderId = null;
component.nextNodeId = 'next1';
component.onNavigateNext();
expect(router.navigate).toHaveBeenCalledWith(
['personal-files', 'preview', 'next1']
);
});
it('should not navigate back if node unset', () => {
spyOn(router, 'navigate').and.stub();
component.nextNodeId = null;
component.onNavigateNext();
expect(router.navigate).not.toHaveBeenCalled();
});
it('should generate preview path for a folder only', () => {
component.previewLocation = 'personal-files';
expect(component.getPreviewPath('folder1', null)).toEqual(
['personal-files', 'folder1']
);
});
it('should generate preview path for a folder and a node', () => {
component.previewLocation = 'personal-files';
expect(component.getPreviewPath('folder1', 'node1')).toEqual(
['personal-files', 'folder1', 'preview', 'node1']
);
});
it('should generate preview path for a node only', () => {
component.previewLocation = 'personal-files';
expect(component.getPreviewPath(null, 'node1')).toEqual(
['personal-files', 'preview', 'node1']
);
});
it('should generate preview for the location only', () => {
component.previewLocation = 'personal-files';
expect(component.getPreviewPath(null, null)).toEqual(
['personal-files']
);
});
it('should navigate back to root path upon close', () => {
spyOn(router, 'navigate').and.stub();
component.routesSkipNavigation = [];
component.previewLocation = 'libraries';
component.folderId = null;
component.onVisibilityChanged(false);
expect(router.navigate).toHaveBeenCalledWith(
['libraries']
);
});
it('should navigate back to folder path upon close', () => {
spyOn(router, 'navigate').and.stub();
component.routesSkipNavigation = [];
component.previewLocation = 'libraries';
component.folderId = 'site1';
component.onVisibilityChanged(false);
expect(router.navigate).toHaveBeenCalledWith(
['libraries', 'site1']
);
});
it('should not navigate to root path for certain routes upon close', () => {
spyOn(router, 'navigate').and.stub();
component.routesSkipNavigation = ['shared'];
component.previewLocation = 'shared';
component.folderId = 'folder1';
component.onVisibilityChanged(false);
expect(router.navigate).toHaveBeenCalledWith(
['shared']
);
});
it('should not navigate back if viewer is still visible', () => {
spyOn(router, 'navigate').and.stub();
component.routesSkipNavigation = [];
component.previewLocation = 'shared';
component.onVisibilityChanged(true);
expect(router.navigate).not.toHaveBeenCalled();
});
it('should enable multiple document navigation from route data', () => {
route.snapshot.data = {
navigateMultiple: true
};
component.ngOnInit();
expect(component.navigateMultiple).toBeTruthy();
});
it('should not enable multiple document navigation from route data', () => {
route.snapshot.data = {};
component.ngOnInit();
expect(component.navigateMultiple).toBeFalsy();
});
it('should fetch navigation source from route', () => {
route.snapshot.data = {
navigateSource: 'personal-files'
};
component.ngOnInit();
expect(component.navigateSource).toBe('personal-files');
});
it('should fetch case-insensitive source from route', () => {
route.snapshot.data = {
navigateSource: 'PERSONAL-FILES'
};
component.navigationSources = ['personal-files'];
component.ngOnInit();
expect(component.navigateSource).toBe('PERSONAL-FILES');
});
it('should fetch only permitted navigation source from route', () => {
route.snapshot.data = {
navigateSource: 'personal-files'
};
component.navigationSources = ['shared'];
component.ngOnInit();
expect(component.navigateSource).toBeNull();
});
it('should display document upon init', () => {
route.params = Observable.of({
folderId: 'folder1',
nodeId: 'node1'
});
spyOn(component, 'displayNode').and.stub();
component.ngOnInit();
expect(component.folderId).toBe('folder1');
expect(component.displayNode).toHaveBeenCalledWith('node1');
});
it('should return empty nearest nodes for missing node id', async () => {
const nearest = await component.getNearestNodes(null, 'folder1');
expect(nearest).toEqual({ left: null, right: null });
});
it('should return empty nearest nodes for missing folder id', async () => {
const nearest = await component.getNearestNodes('node1', null);
expect(nearest).toEqual({ left: null, right: null });
});
it('should return empty nearest nodes for crashed fields id request', async () => {
spyOn(component, 'getFileIds').and.returnValue(Promise.reject('err'));
const nearest = await component.getNearestNodes('node1', 'folder1');
expect(nearest).toEqual({ left: null, right: null });
});
it('should return nearest nodes', async () => {
spyOn(component, 'getFileIds').and.returnValue(Promise.resolve(
[ 'node1', 'node2', 'node3', 'node4', 'node5' ]
));
let nearest = await component.getNearestNodes('node1', 'folder1');
expect(nearest).toEqual({ left: null, right: 'node2' });
nearest = await component.getNearestNodes('node3', 'folder1');
expect(nearest).toEqual({ left: 'node2', right: 'node4' });
nearest = await component.getNearestNodes('node5', 'folder1');
expect(nearest).toEqual({ left: 'node4', right: null });
});
it('should return empty nearest nodes if node is already deleted', async () => {
spyOn(component, 'getFileIds').and.returnValue(Promise.resolve(
[ 'node1', 'node2', 'node3', 'node4', 'node5' ]
));
const nearest = await component.getNearestNodes('node9', 'folder1');
expect(nearest).toEqual({ left: null, right: null });
});
it('should not display node when id is missing', async () => {
spyOn(router, 'navigate').and.stub();
spyOn(alfrescoApi.nodesApi, 'getNodeInfo').and.returnValue(
Promise.resolve(null)
);
await component.displayNode(null);
expect(alfrescoApi.nodesApi.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)
);
component.previewLocation = 'personal-files';
await component.displayNode('folder1');
expect(alfrescoApi.nodesApi.getNodeInfo).toHaveBeenCalledWith(
'folder1', { include: [ 'allowableOperations' ] }
);
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({
isFile: false
})
);
component.previewLocation = 'personal-files';
await component.displayNode('folder1');
expect(alfrescoApi.nodesApi.getNodeInfo).toHaveBeenCalledWith(
'folder1', { include: [ 'allowableOperations' ] }
);
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')
);
component.previewLocation = 'personal-files';
await component.displayNode('folder1');
expect(alfrescoApi.nodesApi.getNodeInfo).toHaveBeenCalledWith(
'folder1', { include: [ 'allowableOperations' ] }
);
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({
isFile: true
})
);
spyOn(component, 'getNearestNodes').and.returnValue(
Promise.reject('error')
);
component.previewLocation = 'personal-files';
await component.displayNode('folder1');
expect(alfrescoApi.nodesApi.getNodeInfo).toHaveBeenCalledWith(
'folder1', { include: [ 'allowableOperations' ] }
);
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({
id: 'node2',
parentId: 'parent1',
isFile: true
})
);
await component.displayNode('node2');
expect(component.previousNodeId).toBe('node1');
expect(component.nextNodeId).toBe('node3');
expect(component.nodeId).toBe('node2');
expect(router.navigate).not.toHaveBeenCalled();
});
it('should fetch and sort file ids for personal-files', async () => {
preferences.set('personal-files.sorting.key', 'name');
preferences.set('personal-files.sorting.direction', 'desc');
spyOn(alfrescoApi.nodesApi, 'getNodeChildren').and.returnValue(
Promise.resolve({
list: {
entries: [
{ entry: { id: 'node1', name: 'node 1' } },
{ entry: { id: 'node2', name: 'node 2' } }
]
}
})
);
const ids = await component.getFileIds('personal-files', 'folder1');
expect(ids).toEqual(['node2', 'node1']);
});
it('should fetch file ids for personal-files with default sorting for missing key', async () => {
preferences.set('personal-files.sorting.key', 'missing');
preferences.set('personal-files.sorting.direction', 'desc');
spyOn(alfrescoApi.nodesApi, 'getNodeChildren').and.returnValue(
Promise.resolve({
list: {
entries: [
{ entry: { id: 'node1', name: 'node 1' } },
{ entry: { id: 'node2', name: 'node 2' } }
]
}
})
);
const ids = await component.getFileIds('personal-files', 'folder1');
expect(ids).toEqual(['node1', 'node2']);
});
it('should require folder id to fetch ids for personal-files', async () => {
const ids = await component.getFileIds('personal-files', null);
expect(ids).toEqual([]);
});
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({
list: {
entries: [
{ entry: { id: 'node1', name: 'node 1', modifiedAt: 1 } },
{ entry: { id: 'node2', name: 'node 2', modifiedAt: 2 } }
]
}
})
);
const ids = await component.getFileIds('personal-files', 'folder1');
expect(ids).toEqual(['node2', 'node1']);
});
it('should fetch and sort file ids for libraries', async () => {
preferences.set('personal-files.sorting.key', 'name');
preferences.set('personal-files.sorting.direction', 'desc');
spyOn(alfrescoApi.nodesApi, 'getNodeChildren').and.returnValue(
Promise.resolve({
list: {
entries: [
{ entry: { id: 'node1', name: 'node 1' } },
{ entry: { id: 'node2', name: 'node 2' } }
]
}
})
);
const ids = await component.getFileIds('libraries', 'site1');
expect(ids).toEqual(['node2', 'node1']);
});
it('should require folder id to fetch ids for libraries', async () => {
const ids = await component.getFileIds('libraries', null);
expect(ids).toEqual([]);
});
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({
list: {
entries: [
{ entry: { id: 'node1', name: 'node 1', modifiedAt: new Date(1) } },
{ entry: { id: 'node2', name: 'node 2', modifiedAt: new Date(2) } }
]
}
})
);
const ids = await component.getFileIds('libraries', 'folder1');
expect(ids).toEqual(['node2', 'node1']);
});
it('should fetch and sort ids for favorites', async () => {
preferences.set('favorites.sorting.key', 'name');
preferences.set('favorites.sorting.direction', 'desc');
spyOn(alfrescoApi.favoritesApi, 'getFavorites').and.returnValue(
Promise.resolve({
list: {
entries: [
{ entry: { target: { file: { id: 'file3', name: 'file 3' } } } },
{ entry: { target: { file: { id: 'file1', name: 'file 1' } } } },
{ entry: { target: { file: { id: 'file2', name: 'file 2' } } } }
]
}
})
);
const ids = await component.getFileIds('favorites');
expect(ids).toEqual(['file3', 'file2', 'file1']);
});
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({
list: {
entries: [
{ entry: { target: { file: { id: 'file3', modifiedAt: new Date(3) } } } },
{ entry: { target: { file: { id: 'file1', modifiedAt: new Date(1) } } } },
{ entry: { target: { file: { id: 'file2', modifiedAt: new Date(2) } } } }
]
}
})
);
const ids = await component.getFileIds('favorites');
expect(ids).toEqual(['file3', 'file2', 'file1']);
});
it('should fetch and sort file ids for shared files', async () => {
preferences.set('shared.sorting.key', 'name');
preferences.set('shared.sorting.direction', 'asc');
spyOn(alfrescoApi.sharedLinksApi, 'findSharedLinks').and.returnValue(
Promise.resolve({
list: {
entries: [
{ entry: { nodeId: 'node2', name: 'node 2', modifiedAt: new Date(2) } },
{ entry: { nodeId: 'node1', name: 'node 1', modifiedAt: new Date(1) } }
]
}
})
);
const ids = await component.getFileIds('shared');
expect(ids).toEqual(['node1', 'node2']);
});
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({
list: {
entries: [
{ entry: { nodeId: 'node2', name: 'node 2', modifiedAt: new Date(2) } },
{ entry: { nodeId: 'node1', name: 'node 1', modifiedAt: new Date(1) } }
]
}
})
);
const ids = await component.getFileIds('shared');
expect(ids).toEqual(['node2', 'node1']);
});
it('should fetch and sort ids for recent-files', async () => {
preferences.set('recent-files.sorting.key', 'name');
preferences.set('recent-files.sorting.direction', 'asc');
spyOn(alfrescoApi.peopleApi, 'getPerson').and.returnValue(
Promise.resolve({
entry: { id: 'user' }
})
);
spyOn(alfrescoApi.searchApi, 'search').and.returnValue(
Promise.resolve({
list: {
entries: [
{ entry: { id: 'node2', name: 'node 2', modifiedAt: new Date(2) } },
{ entry: { id: 'node1', name: 'node 1', modifiedAt: new Date(1) } }
]
}
})
);
const ids = await component.getFileIds('recent-files');
expect(ids).toEqual(['node1', 'node2']);
});
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({
entry: { id: 'user' }
})
);
spyOn(alfrescoApi.searchApi, 'search').and.returnValue(
Promise.resolve({
list: {
entries: [
{ entry: { id: 'node2', name: 'node 2', modifiedAt: new Date(2) } },
{ entry: { id: 'node1', name: 'node 1', modifiedAt: new Date(1) } }
]
}
})
);
const ids = await component.getFileIds('recent-files');
expect(ids).toEqual(['node2', 'node1']);
});
});
+298 -17
View File
@@ -2,7 +2,7 @@
* @license
* Alfresco Example Content Application
*
* Copyright (C) 2005 - 2017 Alfresco Software Limited
* 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
@@ -25,7 +25,9 @@
import { Component, OnInit, ViewEncapsulation } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { AlfrescoApiService } from '@alfresco/adf-core';
import { AlfrescoApiService, UserPreferencesService, ObjectUtils } from '@alfresco/adf-core';
import { Node, MinimalNodeEntity } from 'alfresco-js-api';
import { ContentManagementService } from '../../common/services/content-management.service';
@Component({
selector: 'app-preview',
@@ -37,29 +39,308 @@ import { AlfrescoApiService } from '@alfresco/adf-core';
})
export class PreviewComponent implements OnInit {
nodeId: string = null;
node: Node;
previewLocation: string = null;
routesSkipNavigation = [ 'shared', 'recent-files', 'favorites' ];
navigateSource: string = null;
navigationSources = ['favorites', 'libraries', 'personal-files', 'recent-files', 'shared'];
constructor(
private router: Router,
private route: ActivatedRoute,
private apiService: AlfrescoApiService) {}
folderId: string = null;
nodeId: string = null;
previousNodeId: string;
nextNodeId: string;
navigateMultiple = false;
selectedEntities: MinimalNodeEntity[] = [];
constructor(private router: Router,
private route: ActivatedRoute,
private apiService: AlfrescoApiService,
private preferences: UserPreferencesService,
private content: ContentManagementService) {
}
ngOnInit() {
this.previewLocation = this.router.url
.substr(0, this.router.url.indexOf('/', 1))
.replace(/\//g, '');
const routeData = this.route.snapshot.data;
if (routeData.navigateMultiple) {
this.navigateMultiple = true;
}
if (routeData.navigateSource) {
const source = routeData.navigateSource.toLowerCase();
if (this.navigationSources.includes(source)) {
this.navigateSource = routeData.navigateSource;
}
}
this.route.params.subscribe(params => {
this.folderId = params.folderId;
const id = params.nodeId;
if (id) {
this.apiService.getInstance().nodes.getNodeInfo(id).then(
(node) => {
if (node && node.isFile) {
this.nodeId = id;
return;
}
this.router.navigate(['/personal-files', id]);
},
() => this.router.navigate(['/personal-files', id])
);
this.displayNode(id);
}
});
}
/**
* Loads the particular node into the Viewer
* @param id Unique identifier for the Node to display
*/
async displayNode(id: string) {
if (id) {
try {
this.node = await this.apiService.nodesApi.getNodeInfo(id, {
include: ['allowableOperations']
});
this.selectedEntities = [{ entry: this.node }];
if (this.node && this.node.isFile) {
const nearest = await this.getNearestNodes(this.node.id, this.node.parentId);
this.previousNodeId = nearest.left;
this.nextNodeId = nearest.right;
this.nodeId = this.node.id;
return;
}
this.router.navigate([this.previewLocation, id]);
} catch {
this.router.navigate([this.previewLocation, id]);
}
}
}
/**
* Handles the visibility change of the Viewer component.
* @param isVisible Indicator whether Viewer is visible or hidden.
*/
onVisibilityChanged(isVisible: boolean): void {
const shouldSkipNavigation = this.routesSkipNavigation.includes(this.previewLocation);
if (!isVisible) {
const route = [this.previewLocation];
if ( !shouldSkipNavigation && this.folderId ) {
route.push(this.folderId);
}
this.router.navigate(route);
}
}
/** Handles navigation to a previous document */
onNavigateBefore(): void {
if (this.previousNodeId) {
this.router.navigate(
this.getPreviewPath(this.folderId, this.previousNodeId)
);
}
}
/** Handles navigation to a next document */
onNavigateNext(): void {
if (this.nextNodeId) {
this.router.navigate(
this.getPreviewPath(this.folderId, this.nextNodeId)
);
}
}
/**
* Generates a node preview route based on folder and node IDs.
* @param folderId Folder ID
* @param nodeId Node ID
*/
getPreviewPath(folderId: string, nodeId: string): any[] {
const route = [this.previewLocation];
if (folderId) {
route.push(folderId);
}
if (nodeId) {
route.push('preview', nodeId);
}
return route;
}
/**
* Retrieves nearest node information for the given node and folder.
* @param nodeId Unique identifier of the document node
* @param folderId Unique identifier of the containing folder node.
*/
async getNearestNodes(nodeId: string, folderId: string): Promise<{ left: string, right: string }> {
const empty = {
left: null,
right: null
};
if (nodeId && folderId) {
try {
const ids = await this.getFileIds(this.navigateSource, folderId);
const idx = ids.indexOf(nodeId);
if (idx >= 0) {
return {
left: ids[idx - 1] || null,
right: ids[idx + 1] || null
};
} else {
return empty;
}
} catch {
return empty;
}
} else {
return empty;
}
}
/**
* Retrieves a list of node identifiers for the folder and data source.
* @param source Data source name. Allowed values are: personal-files, libraries, favorites, shared, recent-files.
* @param folderId Containing folder node identifier for 'personal-files' and 'libraries' sources.
*/
async getFileIds(source: string, folderId?: string): Promise<string[]> {
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, {
// orderBy: `${sortKey} ${sortDirection}`,
fields: ['id', this.getRootField(sortKey)],
where: '(isFile=true)'
});
const entries = nodes.list.entries.map(obj => obj.entry);
this.sort(entries, sortKey, sortDirection);
return entries.map(obj => obj.id);
}
if (source === 'favorites') {
const nodes = await this.apiService.favoritesApi.getFavorites('-me-', {
where: '(EXISTS(target/file))',
fields: ['target']
});
const sortKey = this.preferences.get('favorites.sorting.key') || 'modifiedAt';
const sortDirection = this.preferences.get('favorites.sorting.direction') || 'desc';
const files = nodes.list.entries.map(obj => obj.entry.target.file);
this.sort(files, sortKey, sortDirection);
return files.map(f => f.id);
}
if (source === 'shared') {
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({
fields: ['nodeId', this.getRootField(sortingKey)]
});
const entries = nodes.list.entries.map(obj => obj.entry);
this.sort(entries, sortingKey, sortingDirection);
return entries.map(obj => obj.nodeId);
}
if (source === 'recent-files') {
const person = await this.apiService.peopleApi.getPerson('-me-');
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({
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"` }
],
fields: ['id', this.getRootField(sortingKey)],
sort: [{
type: 'FIELD',
field: 'cm:modified',
ascending: false
}]
});
const entries = nodes.list.entries.map(obj => obj.entry);
this.sort(entries, sortingKey, sortingDirection);
return entries.map(obj => obj.id);
}
return [];
}
private sort(items: any[], key: string, direction: string) {
const options: Intl.CollatorOptions = {};
if (key.includes('sizeInBytes') || key === 'name') {
options.numeric = true;
}
items.sort((a: any, b: any) => {
let left = ObjectUtils.getValue(a, key);
if (left) {
left = (left instanceof Date) ? left.valueOf().toString() : left.toString();
} else {
left = '';
}
let right = ObjectUtils.getValue(b, key);
if (right) {
right = (right instanceof Date) ? right.valueOf().toString() : right.toString();
} else {
right = '';
}
return direction === 'asc'
? left.localeCompare(right, undefined, options)
: right.localeCompare(left, undefined, options);
});
}
/**
* Get the root field name from the property path.
* Example: 'property1.some.child.property' => 'property1'
* @param path Property path
*/
getRootField(path: string) {
if (path) {
return path.split('.')[0];
}
return path;
}
canDeleteFile(): boolean {
return this.content.canDeleteNode(this.node);
}
async deleteFile() {
try {
await this.content.deleteNode(this.node);
this.onVisibilityChanged(false);
} catch {
}
}
canMoveFile(): boolean {
return this.content.canMoveNode(this.node);
}
canCopyFile(): boolean {
return this.content.canCopyNode(this.node);
}
}
@@ -17,7 +17,7 @@
mat-icon-button
*ngIf="hasSelection(documentList.selection)"
title="{{ 'APP.ACTIONS.DOWNLOAD' | translate }}"
[app-download-node]="documentList.selection">
[adfNodeDownload]="documentList.selection">
<mat-icon>get_app</mat-icon>
</button>
@@ -74,20 +74,21 @@
currentFolderId="-recent-"
selectionMode="multiple"
[navigate]="false"
[sorting]="[ 'modifiedAt', 'desc' ]"
[sorting]="sorting"
[contextMenuActions]="true"
[contentActions]="false"
[imageResolver]="imageResolver"
(sorting-changed)="onSortingChanged($event)"
(node-dblclick)="onNodeDoubleClick($event.detail?.node?.entry)"
(node-select)="onNodeSelect($event, documentList)">
<empty-folder-content>
<ng-template>
<div class="empty-list__block">
<mat-icon>access_time</mat-icon>
<p class="empty-list__title">{{ 'APP.BROWSE.RECENT.EMPTY_STATE.TITLE' | translate }}</p>
<p class="empty-list__text">{{ 'APP.BROWSE.RECENT.EMPTY_STATE.TEXT' | translate }}</p>
</div>
<app-empty-folder
icon="access_time"
title="APP.BROWSE.RECENT.EMPTY_STATE.TITLE"
subtitle="APP.BROWSE.RECENT.EMPTY_STATE.TEXT">
</app-empty-folder>
</ng-template>
</empty-folder-content>
@@ -2,7 +2,7 @@
* @license
* Alfresco Example Content Application
*
* Copyright (C) 2005 - 2017 Alfresco Software Limited
* 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
@@ -23,16 +23,25 @@
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
import { Router } from '@angular/router';
import { RouterTestingModule } from '@angular/router/testing';
import { TestBed, async } from '@angular/core/testing';
import { Observable } from 'rxjs/Rx';
import { CoreModule, AlfrescoApiService } from '@alfresco/adf-core';
import { CommonModule } from '../../common/common.module';
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
} from '@alfresco/adf-core';
import { DocumentListComponent } 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 { ContentManagementService } from '../../common/services/content-management.service';
import { LocationLinkComponent } from '../location-link/location-link.component';
import { RecentFilesComponent } from './recent-files.component';
describe('RecentFiles Routed Component', () => {
@@ -41,6 +50,7 @@ describe('RecentFiles Routed Component', () => {
let router: Router;
let alfrescoApi: AlfrescoApiService;
let contentService: ContentManagementService;
let preferenceService: UserPreferencesService;
let page;
let person;
@@ -58,14 +68,39 @@ describe('RecentFiles Routed Component', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
CoreModule,
MatMenuModule,
NoopAnimationsModule,
HttpClientModule,
TranslateModule.forRoot(),
RouterTestingModule,
CommonModule
MatSnackBarModule, MatIconModule
],
declarations: [
LocationLinkComponent,
DataTableComponent,
TimeAgoPipe,
NodeNameTooltipPipe,
NodeFavoriteDirective,
DocumentListComponent,
RecentFilesComponent
]
],
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
],
schemas: [ NO_ERRORS_SCHEMA ]
})
.compileComponents().then(() => {
fixture = TestBed.createComponent(RecentFilesComponent);
@@ -73,6 +108,7 @@ describe('RecentFiles Routed Component', () => {
router = TestBed.get(Router);
contentService = TestBed.get(ContentManagementService);
preferenceService = TestBed.get(UserPreferencesService);
alfrescoApi = TestBed.get(AlfrescoApiService);
});
}));
@@ -93,7 +129,7 @@ describe('RecentFiles Routed Component', () => {
it('should reload nodes on onDeleteNode event', () => {
fixture.detectChanges();
contentService.deleteNode.next();
contentService.nodeDeleted.next();
expect(component.refresh).toHaveBeenCalled();
});
@@ -101,7 +137,7 @@ describe('RecentFiles Routed Component', () => {
it('should reload on onRestoreNode event', () => {
fixture.detectChanges();
contentService.restoreNode.next();
contentService.nodeRestored.next();
expect(component.refresh).toHaveBeenCalled();
});
@@ -109,7 +145,7 @@ describe('RecentFiles Routed Component', () => {
it('should reload on move node event', () => {
fixture.detectChanges();
contentService.moveNode.next();
contentService.nodeMoved.next();
expect(component.refresh).toHaveBeenCalled();
});
@@ -122,12 +158,12 @@ describe('RecentFiles Routed Component', () => {
it('open preview if node is file', () => {
spyOn(router, 'navigate').and.stub();
const node: any = { isFile: true };
const node: any = { id: 'node-id', isFile: true };
component.onNodeDoubleClick(node);
fixture.detectChanges();
expect(router.navigate).toHaveBeenCalledWith(['/preview', node.id]);
expect(router.navigate['calls'].argsFor(0)[0]).toEqual(['./preview', node.id]);
});
it('does not open preview if node is folder', () => {
@@ -151,4 +187,35 @@ describe('RecentFiles Routed Component', () => {
expect(component.documentList.reload).toHaveBeenCalled();
});
});
describe('onSortingChanged', () => {
it('should save sorting input', () => {
spyOn(preferenceService, 'set');
const event = <any>{
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 = <any>{
detail: {}
};
component.onSortingChanged(event);
expect(preferenceService.set).toHaveBeenCalledWith('prefix.sorting.key', 'modifiedAt');
expect(preferenceService.set).toHaveBeenCalledWith('prefix.sorting.direction', 'desc');
});
});
});
@@ -2,7 +2,7 @@
* @license
* Alfresco Example Content Application
*
* Copyright (C) 2005 - 2017 Alfresco Software Limited
* 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
@@ -25,7 +25,7 @@
import { Subscription } from 'rxjs/Rx';
import { Component, ViewChild, OnInit, OnDestroy } from '@angular/core';
import { Router } from '@angular/router';
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';
@@ -43,18 +43,26 @@ export class RecentFilesComponent extends PageComponent implements OnInit, OnDes
private subscriptions: Subscription[] = [];
sorting = [ 'modifiedAt', 'desc' ];
constructor(
private router: Router,
private route: ActivatedRoute,
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];
}
ngOnInit() {
this.subscriptions = this.subscriptions.concat([
this.content.deleteNode.subscribe(() => this.refresh()),
this.content.moveNode.subscribe(() => this.refresh()),
this.content.restoreNode.subscribe(() => this.refresh())
this.content.nodeDeleted.subscribe(() => this.refresh()),
this.content.nodeMoved.subscribe(() => this.refresh()),
this.content.nodeRestored.subscribe(() => this.refresh())
]);
}
@@ -67,7 +75,7 @@ export class RecentFilesComponent extends PageComponent implements OnInit, OnDes
event.preventDefault();
} else if (node && node.isFile) {
this.router.navigate(['/preview', node.id]);
this.router.navigate(['./preview', node.id], { relativeTo: this.route });
}
}
@@ -80,4 +88,13 @@ export class RecentFilesComponent extends PageComponent implements OnInit, OnDes
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;
}
}
@@ -2,7 +2,7 @@
* @license
* Alfresco Example Content Application
*
* Copyright (C) 2005 - 2017 Alfresco Software Limited
* 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
@@ -23,13 +23,12 @@
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { TestBed, async } from '@angular/core/testing';
import { CoreModule, AppConfigService } from '@alfresco/adf-core';
import { Router } from '@angular/router';
import { RouterTestingModule } from '@angular/router/testing';
import { SearchComponent } from './search.component';
import { CommonModule } from './../../common/common.module';
describe('SearchComponent', () => {
let fixture;
@@ -39,13 +38,12 @@ describe('SearchComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
CoreModule,
RouterTestingModule,
CommonModule
RouterTestingModule
],
declarations: [
SearchComponent
]
],
schemas: [ NO_ERRORS_SCHEMA ]
})
.compileComponents()
.then(() => {
@@ -60,11 +58,12 @@ describe('SearchComponent', () => {
describe('onItemClicked()', () => {
it('opens preview if node is file', () => {
spyOn(router, 'navigate').and.stub();
const node = { entry: { isFile: true, id: 'node-id' } };
const node = { entry: { isFile: true, id: 'node-id', parentId: 'parent-id' } };
component.onItemClicked(node);
expect(router.navigate).toHaveBeenCalledWith(['/preview', node.entry.id]);
expect(router.navigate['calls'].argsFor(0)[0])
.toEqual([`/personal-files/${node.entry.parentId}/preview/`, node.entry.id]);
});
it('navigates if node is folder', () => {
@@ -2,7 +2,7 @@
* @license
* Alfresco Example Content Application
*
* Copyright (C) 2005 - 2017 Alfresco Software Limited
* 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
@@ -41,7 +41,7 @@ export class SearchComponent {
onItemClicked(node: MinimalNodeEntity) {
if (node && node.entry) {
if (node.entry.isFile) {
this.router.navigate(['/preview', node.entry.id]);
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 ]);
}
@@ -17,7 +17,7 @@
mat-icon-button
*ngIf="hasSelection(documentList.selection)"
title="{{ 'APP.ACTIONS.DOWNLOAD' | translate }}"
[app-download-node]="documentList.selection">
[adfNodeDownload]="documentList.selection">
<mat-icon>get_app</mat-icon>
</button>
@@ -50,7 +50,7 @@
<button
mat-menu-item
*ngIf="canMove(documentList.selection)"
*ngIf="canMoveShared(documentList.selection)"
[app-move-node]="documentList.selection">
<mat-icon>library_books</mat-icon>
<span>{{ 'APP.ACTIONS.MOVE' | translate }}</span>
@@ -59,6 +59,15 @@
<button
mat-menu-item
*ngIf="canDelete(documentList.selection)"
[appUnshareNode]="documentList.selection"
(links-unshared)="refresh()">
<mat-icon>stop_screen_share</mat-icon>
<span>{{ 'APP.ACTIONS.UNSHARE' | translate }}</span>
</button>
<button
mat-menu-item
*ngIf="canDeleteShared(documentList.selection)"
[app-delete-node]="documentList.selection">
<mat-icon>delete</mat-icon>
<span>{{ 'APP.ACTIONS.DELETE' | translate }}</span>
@@ -72,18 +81,19 @@
[attr.class]="documentList.isEmpty() ? 'empty-list' : ''"
currentFolderId="-sharedlinks-"
selectionMode="multiple"
[sorting]="[ 'modifiedAt', 'desc' ]"
[sorting]="sorting"
[contextMenuActions]="true"
[contentActions]="false"
(sorting-changed)="onSortingChanged($event)"
(node-dblclick)="onNodeDoubleClick($event.detail?.node?.entry)">
<empty-folder-content>
<ng-template>
<div class="empty-list__block">
<mat-icon>people</mat-icon>
<p class="empty-list__title">{{ 'APP.BROWSE.SHARED.EMPTY_STATE.TITLE' | translate }}</p>
<p class="empty-list__text">{{ 'APP.BROWSE.SHARED.EMPTY_STATE.TEXT' | translate }}</p>
</div>
<app-empty-folder
icon="people"
title="APP.BROWSE.SHARED.EMPTY_STATE.TITLE"
subtitle="APP.BROWSE.SHARED.EMPTY_STATE.TEXT">
</app-empty-folder>
</ng-template>
</empty-folder-content>
@@ -2,7 +2,7 @@
* @license
* Alfresco Example Content Application
*
* Copyright (C) 2005 - 2017 Alfresco Software Limited
* 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
@@ -23,15 +23,25 @@
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
import { Router } from '@angular/router';
import { TestBed, async, fakeAsync, tick } from '@angular/core/testing';
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { Router, ActivatedRoute } from '@angular/router';
import { RouterTestingModule } from '@angular/router/testing';
import { AlfrescoApiService } from '@alfresco/adf-core';
import { CommonModule } from '../../common/common.module';
import { HttpClientModule } from '@angular/common/http';
import {
NotificationService, TranslationService, TranslationMock,
NodesApiService, AlfrescoApiService, ContentService,
UserPreferencesService, LogService, AppConfigService,
StorageService, CookieService, ThumbnailService, AuthenticationService,
TimeAgoPipe, NodeNameTooltipPipe, NodeFavoriteDirective,DataTableComponent
} from '@alfresco/adf-core';
import { DocumentListComponent } 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 { ContentManagementService } from '../../common/services/content-management.service';
import { LocationLinkComponent } from '../location-link/location-link.component';
import { SharedFilesComponent } from './shared-files.component';
describe('SharedFilesComponent', () => {
@@ -40,6 +50,7 @@ describe('SharedFilesComponent', () => {
let contentService: ContentManagementService;
let nodeService;
let alfrescoApi: AlfrescoApiService;
let preferenceService: UserPreferencesService;
let router: Router;
let page;
@@ -56,13 +67,39 @@ describe('SharedFilesComponent', () => {
TestBed
.configureTestingModule({
imports: [
MatMenuModule,
NoopAnimationsModule,
HttpClientModule,
TranslateModule.forRoot(),
RouterTestingModule,
CommonModule
MatSnackBarModule, MatIconModule
],
declarations: [
LocationLinkComponent,
DataTableComponent,
TimeAgoPipe,
NodeNameTooltipPipe,
NodeFavoriteDirective,
DocumentListComponent,
SharedFilesComponent
]
],
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
],
schemas: [ NO_ERRORS_SCHEMA ]
})
.compileComponents()
.then(() => {
@@ -72,6 +109,7 @@ describe('SharedFilesComponent', () => {
contentService = TestBed.get(ContentManagementService);
alfrescoApi = TestBed.get(AlfrescoApiService);
nodeService = alfrescoApi.getInstance().nodes;
preferenceService = TestBed.get(UserPreferencesService);
router = TestBed.get(Router);
});
@@ -89,7 +127,7 @@ describe('SharedFilesComponent', () => {
it('should refresh on deleteNode event', () => {
fixture.detectChanges();
contentService.deleteNode.next();
contentService.nodeDeleted.next();
expect(component.refresh).toHaveBeenCalled();
});
@@ -97,7 +135,7 @@ describe('SharedFilesComponent', () => {
it('should refresh on restoreNode event', () => {
fixture.detectChanges();
contentService.restoreNode.next();
contentService.nodeRestored.next();
expect(component.refresh).toHaveBeenCalled();
});
@@ -105,7 +143,7 @@ describe('SharedFilesComponent', () => {
it('should reload on move node event', () => {
fixture.detectChanges();
contentService.moveNode.next();
contentService.nodeMoved.next();
expect(component.refresh).toHaveBeenCalled();
});
@@ -126,7 +164,7 @@ describe('SharedFilesComponent', () => {
component.onNodeDoubleClick(link);
tick();
expect(router.navigate).toHaveBeenCalledWith(['/preview', node.entry.id]);
expect(router.navigate['calls'].argsFor(0)[0]).toEqual(['./preview', node.entry.id]);
}));
it('does nothing if node is folder', fakeAsync(() => {
@@ -160,4 +198,35 @@ describe('SharedFilesComponent', () => {
expect(component.documentList.reload).toHaveBeenCalled();
});
});
describe('onSortingChanged', () => {
it('should save sorting input', () => {
spyOn(preferenceService, 'set');
const event = <any>{
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 = <any>{
detail: {}
};
component.onSortingChanged(event);
expect(preferenceService.set).toHaveBeenCalledWith('prefix.sorting.key', 'modifiedAt');
expect(preferenceService.set).toHaveBeenCalledWith('prefix.sorting.direction', 'desc');
});
});
});
@@ -2,7 +2,7 @@
* @license
* Alfresco Example Content Application
*
* Copyright (C) 2005 - 2017 Alfresco Software Limited
* 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
@@ -24,7 +24,7 @@
*/
import { Component, OnInit, ViewChild, OnDestroy } from '@angular/core';
import { Router } from '@angular/router';
import { Router, ActivatedRoute } from '@angular/router';
import { Subscription } from 'rxjs/Rx';
import { MinimalNodeEntity } from 'alfresco-js-api';
import { AlfrescoApiService, UserPreferencesService } from '@alfresco/adf-core';
@@ -43,19 +43,26 @@ export class SharedFilesComponent extends PageComponent implements OnInit, OnDes
private subscriptions: Subscription[] = [];
constructor(
private router: Router,
private content: ContentManagementService,
private apiService: AlfrescoApiService,
preferences: UserPreferencesService) {
sorting = [ 'modifiedAt', 'desc' ];
constructor(private router: Router,
private route: ActivatedRoute,
private content: ContentManagementService,
private apiService: AlfrescoApiService,
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];
}
ngOnInit() {
this.subscriptions = this.subscriptions.concat([
this.content.deleteNode.subscribe(() => this.refresh()),
this.content.moveNode.subscribe(() => this.refresh()),
this.content.restoreNode.subscribe(() => this.refresh())
this.content.nodeDeleted.subscribe(() => this.refresh()),
this.content.nodeMoved.subscribe(() => this.refresh()),
this.content.nodeRestored.subscribe(() => this.refresh())
]);
}
@@ -68,7 +75,7 @@ export class SharedFilesComponent extends PageComponent implements OnInit, OnDes
this.apiService.nodesApi.getNode(link.nodeId).then(
(node: MinimalNodeEntity) => {
if (node && node.entry && node.entry.isFile) {
this.router.navigate(['/preview', node.entry.id]);
this.router.navigate(['./preview', node.entry.id], { relativeTo: this.route });
}
}
);
@@ -86,7 +93,17 @@ export class SharedFilesComponent extends PageComponent implements OnInit, OnDes
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;
}
}
@@ -2,7 +2,7 @@
* @license
* Alfresco Example Content Application
*
* Copyright (C) 2005 - 2017 Alfresco Software Limited
* 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
@@ -23,13 +23,20 @@
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { TestBed, async } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { ContentService, AppConfigService } from '@alfresco/adf-core';
import { TranslateModule } from '@ngx-translate/core';
import { MatMenuModule } from '@angular/material';
import { HttpClientModule } from '@angular/common/http';
import {
ContentService, AppConfigService, AuthenticationService,
UserPreferencesService, StorageService, AlfrescoApiService,
CookieService, LogService
} from '@alfresco/adf-core';
import { BrowsingFilesService } from '../../common/services/browsing-files.service';
import { SidenavComponent } from './sidenav.component';
import { CommonModule } from './../../common/common.module';
describe('SidenavComponent', () => {
let fixture;
@@ -49,12 +56,26 @@ describe('SidenavComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
RouterTestingModule,
CommonModule
HttpClientModule,
MatMenuModule,
TranslateModule.forRoot(),
RouterTestingModule
],
declarations: [
SidenavComponent
]
],
providers: [
LogService,
CookieService,
AlfrescoApiService,
StorageService,
UserPreferencesService,
AuthenticationService,
ContentService,
AppConfigService,
BrowsingFilesService
],
schemas: [ NO_ERRORS_SCHEMA ]
})
.compileComponents()
.then(() => {
@@ -2,7 +2,7 @@
* @license
* Alfresco Example Content Application
*
* Copyright (C) 2005 - 2017 Alfresco Software Limited
* 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
@@ -31,18 +31,19 @@
currentFolderId="-trashcan-"
selectionMode="multiple"
[navigate]="false"
[sorting]="[ 'archivedAt', 'desc' ]"
[sorting]="sorting"
[contextMenuActions]="true"
[contentActions]="false">
[contentActions]="false"
(sorting-changed)="onSortingChanged($event)">
<empty-folder-content>
<ng-template>
<div class="empty-list__block">
<mat-icon>delete</mat-icon>
<p class="empty-list__title">{{ 'APP.BROWSE.TRASHCAN.EMPTY_STATE.TITLE' | translate }}</p>
<p class="empty-list__text">{{ 'APP.BROWSE.TRASHCAN.EMPTY_STATE.FIRST_TEXT' | translate }}</p>
<p class="empty-list__text">{{ 'APP.BROWSE.TRASHCAN.EMPTY_STATE.SECOND_TEXT' | translate }}</p>
</div>
<app-empty-folder
icon="delete"
title="APP.BROWSE.TRASHCAN.EMPTY_STATE.TITLE">
<p class="app-empty-folder__text">{{ 'APP.BROWSE.TRASHCAN.EMPTY_STATE.FIRST_TEXT' | translate }}</p>
<p class="app-empty-folder__text">{{ 'APP.BROWSE.TRASHCAN.EMPTY_STATE.SECOND_TEXT' | translate }}</p>
</app-empty-folder>
</ng-template>
</empty-folder-content>
@@ -2,7 +2,7 @@
* @license
* Alfresco Example Content Application
*
* Copyright (C) 2005 - 2017 Alfresco Software Limited
* 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
@@ -22,19 +22,34 @@
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
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 { CoreModule, AlfrescoApiService } from '@alfresco/adf-core';
import { TrashcanComponent } from './trashcan.component';
import { CommonModule } from '../../common/common.module';
import { LocationLinkComponent } from '../location-link/location-link.component';
import {
NotificationService, TranslationService, TranslationMock,
NodesApiService, AlfrescoApiService, ContentService,
UserPreferencesService, LogService, AppConfigService,
StorageService, CookieService, ThumbnailService,
AuthenticationService, TimeAgoPipe, NodeNameTooltipPipe,
NodeFavoriteDirective, DataTableComponent
} from '@alfresco/adf-core';
import { DocumentListComponent } 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 { ContentManagementService } from '../../common/services/content-management.service';
import { TrashcanComponent } from './trashcan.component';
describe('TrashcanComponent', () => {
let fixture;
let component;
let alfrescoApi: AlfrescoApiService;
let contentService: ContentManagementService;
let preferenceService: UserPreferencesService;
let page;
beforeEach(() => {
@@ -49,16 +64,39 @@ describe('TrashcanComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
CoreModule,
CommonModule
MatMenuModule,
NoopAnimationsModule,
HttpClientModule,
TranslateModule.forRoot(),
RouterTestingModule,
MatSnackBarModule, MatIconModule
],
declarations: [
LocationLinkComponent,
DataTableComponent,
TimeAgoPipe,
NodeNameTooltipPipe,
NodeFavoriteDirective,
DocumentListComponent,
TrashcanComponent
],
providers: [
ContentManagementService
]
{ provide: ActivatedRoute, useValue: {
snapshot: { data: { preferencePrefix: 'prefix' } }
} } ,
{ provide: TranslationService, useClass: TranslationMock },
AuthenticationService,
UserPreferencesService,
AppConfigService, StorageService, CookieService,
AlfrescoApiService,
LogService,
NotificationService,
ContentManagementService,
ContentService,
NodesApiService,
DocumentListService,
ThumbnailService
],
schemas: [ NO_ERRORS_SCHEMA ]
})
.compileComponents()
.then(() => {
@@ -67,6 +105,7 @@ describe('TrashcanComponent', () => {
alfrescoApi = TestBed.get(AlfrescoApiService);
contentService = TestBed.get(ContentManagementService);
preferenceService = TestBed.get(UserPreferencesService);
component.documentList = {
loadTrashcan: jasmine.createSpy('loadTrashcan'),
@@ -84,7 +123,7 @@ describe('TrashcanComponent', () => {
spyOn(component, 'refresh');
fixture.detectChanges();
contentService.restoreNode.next();
contentService.nodeRestored.next();
expect(component.refresh).toHaveBeenCalled();
});
@@ -101,4 +140,35 @@ describe('TrashcanComponent', () => {
expect(component.documentList.resetSelection).toHaveBeenCalled();
});
});
describe('onSortingChanged', () => {
it('should save sorting input', () => {
spyOn(preferenceService, 'set');
const event = <any>{
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 = <any>{
detail: {}
};
component.onSortingChanged(event);
expect(preferenceService.set).toHaveBeenCalledWith('prefix.sorting.key', 'archivedAt');
expect(preferenceService.set).toHaveBeenCalledWith('prefix.sorting.direction', 'desc');
});
});
});
@@ -2,7 +2,7 @@
* @license
* Alfresco Example Content Application
*
* Copyright (C) 2005 - 2017 Alfresco Software Limited
* 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
@@ -24,6 +24,7 @@
*/
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';
@@ -38,12 +39,20 @@ export class TrashcanComponent implements OnInit, OnDestroy {
@ViewChild(DocumentListComponent) documentList;
constructor(
private contentManagementService: ContentManagementService,
private preferences: UserPreferencesService) {}
sorting = [ 'archivedAt', 'desc' ];
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];
}
ngOnInit() {
this.subscriptions.push(this.contentManagementService.restoreNode.subscribe(() => this.refresh()));
this.subscriptions.push(this.contentManagementService.nodeRestored.subscribe(() => this.refresh()));
}
refresh(): void {
@@ -58,4 +67,13 @@ export class TrashcanComponent implements OnInit, OnDestroy {
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;
}
}
@@ -15,14 +15,18 @@ adf-document-list {
.adf-data-table {
border: none !important;
th, tr, td {
.adf-datatable-header, .adf-datatable-row, .adf-data-table-cell {
color: $alfresco-secondary-text-color;
&:focus {
outline: none !important;
}
}
tbody tr {
.adf-datatable-table-cell-header:focus {
outline: none !important;
}
.adf-datatable-body .adf-datatable-row {
&:hover, &:focus {
background-color: $alfresco-app-color--hue-1;
}
@@ -32,20 +36,11 @@ adf-document-list {
}
}
td, th {
.adf-data-table-cell, .adf-datatable-header {
width: 100%;
text-align: left;
}
.adf-data-table__header--sorted-asc,
.adf-data-table__header--sorted-desc {
&:hover {
&:before {
color: inherit !important;
}
}
}
.app-location-link,
.adf-location-cell {
a {
@@ -59,11 +54,8 @@ adf-document-list {
}
}
.adf-no-content-container {
border: none !important;
}
td:first-of-type, th:first-of-type {
.adf-datatable-header:first-of-type, .adf-datatable-header.adf-data-table-cell--image,
.adf-data-table-cell:first-of-type, .adf-data-table-cell.adf-data-table-cell--image {
padding-left: 24px;
padding-right: 0;
width: 10px;
@@ -91,42 +83,19 @@ adf-document-list {
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);
}
}
.empty-list {
.adf-data-table {
height: 100%;
tr:hover, tr:focus {
.adf-datatable-row:hover, .adf-datatable-row:focus {
background-color: unset;
cursor: default;
}
}
&__block {
color: $alfresco-secondary-text-color;
display: flex;
flex-direction: column;
align-items: center;
p {
line-height: 0;
}
}
&__title {
font-size: 18px;
font-weight: 600;
}
&__subtitle {
font-size: 14px;
font-weight: 300;
}
&__block > mat-icon {
font-size: 52px;
height: 52px;
width: 52px;
}
}
+10 -2
View File
@@ -101,7 +101,8 @@
"MORE": "Weitere Aktionen",
"UNDO": "Rückgängig machen",
"RESTORE": "Wiederherstellen",
"FAVORITE": "Zu Favoriten"
"FAVORITE": "Zu Favoriten",
"UNSHARE": "Freigabe aufheben"
},
"DOCUMENT_LIST": {
"COLUMNS": {
@@ -184,5 +185,12 @@
}
}
}
},
"NODE_SELECTOR": {
"COPY_ITEM": "'{{ name }}' kopieren nach...",
"COPY_ITEMS": "{{ number }} Elemente kopieren nach...",
"MOVE_ITEM": "'{{ name }}' verschieben nach...",
"MOVE_ITEMS": "{{ number }} Elemente verschieben nach...",
"SEARCH": "Suchen"
}
}
}
+9 -1
View File
@@ -101,7 +101,8 @@
"MORE": "More actions",
"UNDO": "Undo",
"RESTORE": "Restore",
"FAVORITE": "Favorite"
"FAVORITE": "Favorite",
"UNSHARE": "Unshare"
},
"DOCUMENT_LIST": {
"COLUMNS": {
@@ -184,5 +185,12 @@
}
}
}
},
"NODE_SELECTOR": {
"COPY_ITEM": "Copy '{{ name }}' to...",
"COPY_ITEMS": "Copy {{ number }} items to...",
"MOVE_ITEM": "Move '{{ name }}' to...",
"MOVE_ITEMS": "Move {{ number }} items to...",
"SEARCH": "Search"
}
}
+10 -2
View File
@@ -101,7 +101,8 @@
"MORE": "Más acciones",
"UNDO": "Deshacer",
"RESTORE": "Restaurar",
"FAVORITE": "Favorito"
"FAVORITE": "Favorito",
"UNSHARE": "No compartir"
},
"DOCUMENT_LIST": {
"COLUMNS": {
@@ -184,5 +185,12 @@
}
}
}
},
"NODE_SELECTOR": {
"COPY_ITEM": "Copiar '{{ name }}' a...",
"COPY_ITEMS": "Copiar {{ number }} elementos a...",
"MOVE_ITEM": "Mover '{{ name }}' a...",
"MOVE_ITEMS": "Mover {{ number }} elementos a...",
"SEARCH": "Buscar"
}
}
}
+10 -2
View File
@@ -101,7 +101,8 @@
"MORE": "Plus d'actions",
"UNDO": "Annuler",
"RESTORE": "Restaurer",
"FAVORITE": "Favori"
"FAVORITE": "Favori",
"UNSHARE": "Ne pas partager"
},
"DOCUMENT_LIST": {
"COLUMNS": {
@@ -184,5 +185,12 @@
}
}
}
},
"NODE_SELECTOR": {
"COPY_ITEM": "Copier '{{ name }}' dans...",
"COPY_ITEMS": "Copier {{ number }} éléments vers...",
"MOVE_ITEM": "Déplacer '{{ name }}' vers...",
"MOVE_ITEMS": "Déplacer {{ number }} éléments vers...",
"SEARCH": "Rechercher"
}
}
}
+10 -2
View File
@@ -101,7 +101,8 @@
"MORE": "Altre azioni",
"UNDO": "Annulla",
"RESTORE": "Ripristina",
"FAVORITE": "Preferito"
"FAVORITE": "Preferito",
"UNSHARE": "Rimuovi condivisione"
},
"DOCUMENT_LIST": {
"COLUMNS": {
@@ -184,5 +185,12 @@
}
}
}
},
"NODE_SELECTOR": {
"COPY_ITEM": "Copia '{{ name }}' in...",
"COPY_ITEMS": "Copia {{ number }} elementi in...",
"MOVE_ITEM": "Sposta '{{ name }}' in...",
"MOVE_ITEMS": "Sposta {{ number }} elementi in...",
"SEARCH": "Cerca"
}
}
}
+10 -2
View File
@@ -101,7 +101,8 @@
"MORE": "その他の処理",
"UNDO": "元に戻す",
"RESTORE": "復元",
"FAVORITE": "お気に入り"
"FAVORITE": "お気に入り",
"UNSHARE": "共有の解除"
},
"DOCUMENT_LIST": {
"COLUMNS": {
@@ -184,5 +185,12 @@
}
}
}
},
"NODE_SELECTOR": {
"COPY_ITEM": "'{{ name }}' のコピー先...",
"COPY_ITEMS": "{{ number }} 件のアイテムのコピー先...",
"MOVE_ITEM": "'{{ name }}' の移動先...",
"MOVE_ITEMS": "{{ number }} 件のアイテムの移動先...",
"SEARCH": "検索"
}
}
}
+10 -2
View File
@@ -101,7 +101,8 @@
"MORE": "Flere handlinger",
"UNDO": "Angre",
"RESTORE": "Gjenopprett",
"FAVORITE": "Favoritt"
"FAVORITE": "Favoritt",
"UNSHARE": "Opphev deling"
},
"DOCUMENT_LIST": {
"COLUMNS": {
@@ -184,5 +185,12 @@
}
}
}
},
"NODE_SELECTOR": {
"COPY_ITEM": "Kopier {{ name }} til...",
"COPY_ITEMS": "Kopier {{ number }} elementer til...",
"MOVE_ITEM": "Flytt {{ name }}'til...",
"MOVE_ITEMS": "Flytt {{ number }} elementer til...",
"SEARCH": "Søk"
}
}
}
+10 -2
View File
@@ -101,7 +101,8 @@
"MORE": "Meer acties",
"UNDO": "Ongedaan maken",
"RESTORE": "Herstellen",
"FAVORITE": "Favoriet"
"FAVORITE": "Favoriet",
"UNSHARE": "Delen ongedaan maken"
},
"DOCUMENT_LIST": {
"COLUMNS": {
@@ -184,5 +185,12 @@
}
}
}
},
"NODE_SELECTOR": {
"COPY_ITEM": "'{{ name }}' kopiëren naar...",
"COPY_ITEMS": "{{ number }} items kopiëren naar...",
"MOVE_ITEM": "'{{ name }}' verplaatsen naar...",
"MOVE_ITEMS": "{{ number }} items verplaatsen naar...",
"SEARCH": "Zoeken"
}
}
}
+10 -2
View File
@@ -101,7 +101,8 @@
"MORE": "Mais ações",
"UNDO": "Desfazer",
"RESTORE": "Restaurar",
"FAVORITE": "Favoritos"
"FAVORITE": "Favoritos",
"UNSHARE": "Descompartilhar"
},
"DOCUMENT_LIST": {
"COLUMNS": {
@@ -184,5 +185,12 @@
}
}
}
},
"NODE_SELECTOR": {
"COPY_ITEM": "Copiar '{{ name }}' para...",
"COPY_ITEMS": "Copiar {{ number }} itens para...",
"MOVE_ITEM": "Mover '{{ name }}' para...",
"MOVE_ITEMS": "Mover {{ number }} itens para...",
"SEARCH": "Pesquisar"
}
}
}
+10 -2
View File
@@ -101,7 +101,8 @@
"MORE": "Больше действий",
"UNDO": "Отменить",
"RESTORE": "Восстановить",
"FAVORITE": "Избранное"
"FAVORITE": "Избранное",
"UNSHARE": "Снять с публикации"
},
"DOCUMENT_LIST": {
"COLUMNS": {
@@ -184,5 +185,12 @@
}
}
}
},
"NODE_SELECTOR": {
"COPY_ITEM": "Копировать '{{ name }}' в...",
"COPY_ITEMS": "Скопировать элементы {{ number }} в...",
"MOVE_ITEM": "Переместить '{{ name }}' в...",
"MOVE_ITEMS": "Переместить элементы {{ number }} в...",
"SEARCH": "Поиск"
}
}
}
+13 -5
View File
@@ -19,7 +19,7 @@
"UPLOAD_FILES": "选择要上传的文件",
"UPLOAD_FILES_NOT_ALLOWED": "您需要在此上传的权限,请与您的 IT 团队联系。",
"UPLOAD_FOLDERS": "选择要上传的文件夹",
"UPLOAD_FOLDERS_NOT_ALLOWED": "您需要在此上传的权限,请与您的 IT 团队联系。."
"UPLOAD_FOLDERS_NOT_ALLOWED": "您需要在此上传的权限,请与您的 IT 团队联系。"
}
},
"BROWSE": {
@@ -101,7 +101,8 @@
"MORE": "更多操作",
"UNDO": "撤消",
"RESTORE": "恢复",
"FAVORITE": "收藏"
"FAVORITE": "收藏",
"UNSHARE": "取消共享"
},
"DOCUMENT_LIST": {
"COLUMNS": {
@@ -124,8 +125,8 @@
},
"MESSAGES": {
"ERRORS": {
"GENERIC": "此操作未成功,请重试或联系您的 IT 团队.",
"CONFLICT": "此名称已使用,请尝试其他名称.",
"GENERIC": "此操作未成功,请重试或联系您的 IT 团队",
"CONFLICT": "此名称已使用,请尝试其他名称",
"NODE_MOVE": "未成功移动,存在有相同名称的文件。",
"EXISTENT_FOLDER": "存在使用此名称的文件夹。请尝试使用其他名称。",
"NODE_DELETION": "{{ name }} 无法删除",
@@ -184,5 +185,12 @@
}
}
}
},
"NODE_SELECTOR": {
"COPY_ITEM": "将 '{{ name }}' 复制到...",
"COPY_ITEMS": "将 {{ number }} 项复制到...",
"MOVE_ITEM": "将 '{{ name }}' 移动到...",
"MOVE_ITEMS": "将 {{ number }} 项移动到...",
"SEARCH": "搜索"
}
}
}
+1 -1
View File
@@ -2,7 +2,7 @@
* @license
* Alfresco Example Content Application
*
* Copyright (C) 2005 - 2017 Alfresco Software Limited
* 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
+1 -1
View File
@@ -2,7 +2,7 @@
* @license
* Alfresco Example Content Application
*
* Copyright (C) 2005 - 2017 Alfresco Software Limited
* 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
+1 -1
View File
@@ -2,7 +2,7 @@
* @license
* Alfresco Example Content Application
*
* Copyright (C) 2005 - 2017 Alfresco Software Limited
* 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
+1 -1
View File
@@ -2,7 +2,7 @@
* @license
* Alfresco Example Content Application
*
* Copyright (C) 2005 - 2017 Alfresco Software Limited
* 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
+1 -1
View File
@@ -2,7 +2,7 @@
* @license
* Alfresco Example Content Application
*
* Copyright (C) 2005 - 2017 Alfresco Software Limited
* 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

Some files were not shown because too many files have changed in this diff Show More