* [ADF-2914] support for number range patterns (#3282)

* support for number range patterns

* fix memory leak for tag actions

* [ADF-2824] Reviewed docs for core and content services (#3290)

* [ADF-2824] Reviewed card view docs

* [ADF-2824] Reviewed doc for core and content services

* [ADF-2824] Added class name exception to prop script

* [ADF-2843] added tooltip for create new folder and edit folder icons … (#3286)

* [ADF-2843] added tooltip for create new folder and edit folder icons from Content Service

* [ADF-2843] added translation for tooltip

* Update content-metadata.component.md

* [ADF_NOISSUE] Fix styling and sidenav layout fixes (#3291)

* Fix styling and sidenav layout fixes

* Revert packagr conflicting mods

* [ADF-2905] Added JSDocs for process services (#3292)

* [ADF-2905] Added JSDocs for process services

* [ADF-2905] Added missing return value in tasklist service

* [ADF-2916] number range form validator added (#3279)

* number range form validator added

* tests added

* [ADF-2626] added preserved state functionality for sidenav component (#3278)

* [ADF-2626] added preserved state functionality for sidenav component

* [ADF-2626] changed logic for preserving the sidenav component state

* [ADF-2626] added missing curly brace }

* [ADF-2626] small changes on logic based on pr comments

* [ADF-2843] added tooltip for create folder and edit folder icons from Content Services

* Revert "[ADF-2843] added tooltip for create folder and edit folder icons from Content Services"

This reverts commit d5a7abb65b.

* [ADF-2626] changed casting

* [ADF-2626] updated documentation with event

* [ADF-2626] removed app-config pipe because it was not being used

* [ADF-2328] filtering support for facets and categories (#3293)

* filtering support for facets and categories

* fix tests

* update variable names

* [ADF-2973] Angular pipe to get application configuration settings (#3301)

* app config pipe

* update docs

* [ADF-2849] Search Date Range - Set the format of the date from config (#3288)

* format date chosen from the datePicker's calendar

* format date on focusout event

* fix tests & some code refactoring

* more validation messages

* unit tests

* fix typecast error

* move "dateFormat" to be part of the "date range" widget settings block

* fix error on Moment

"...Type 'moment.Moment' is not assignable to type 'moment.Moment'. Two different types with this name exist, but they are unrelated.
          Property 'isLocal' is missing in type 'Moment'..."

* moment - use old version

* change script - use recent version of moment

* Added the working-with-nodes-api-service tutorial. (#3295)

* [ADF-2826] added a check for duplicate permission add (#3265)

* [ADF-2556] first step to create add people or group to permissions

* [ADF-2556] creating a dialog with user results

* [ADF-2556]
integrated service for add and remove permission from node

* [ADF-2556] fixed behaviour and style for add user group

* [ADF-2556] added some refactoring for dialog service

* [ADF-2556] refactoring the dependencies of the components

* [ADF-2556] added some fix and a new key for dialog

* [ADF-2556] start adding test for node permission service

* [ADF-2556] added test for add permission panel component

* [ADf-2556] adding tests for new add permission component

* [ADF-2556] fixed tests and added documentation

* [ADF-2556] fixed documentation for add-node components

* [ADF-2556] added peer review changes

* [ADF-2826] added a check for duplicate permission add

* [ADF-2826] removed fdescribe

* [ADF-2826] applied peer review changes

* [ADF-2930] general purpose "Empty Page" component (#3296)

* empty content component

* fix selector name

* style fixes

* cleanup code

* docs for empty content

* update docs

* update docs

* use typography for icon

* layout fixes for Login (ported from ACA)

* fix aot crash

* fix tslint error and add and block if tslint give error in the pipeline

* [ADF-2892] Sidenav now prevents closing itself when ESC key is pressed (#3302)

* [ADF-2892] Sidenav now closes when clicking outside of it

* [ADF-2892] Sidevan now prevents closing itself when ESC key is pressed

* Fix for a technical bug, which prevents the proper usage of the expanded output event. (#3304)

* [ADF-NO-PRIVATE] renoved private property which breaks AOT (#3305)

* [ADF-2975] New input for File Upload Button component (#3299)

* new nodeType input

* unit tests

* content type fix

* remove package-lock.json files

* [ADF-2699] added localisation for time ago pipe (#3298)

* [ADF-2699] added localisation to the time-ago pipe

* [ADF-2699] added lang to time ago pipe

* [ADF-2699] added localisation for time ago pipe

* [ADF-2699] removed fdescribe

* [ADF-2699] removed comments

* [ADF-2699] removed useless default values

* fix css scope for search filter

* fix aot error

* fix ID show rancher script update

* Addition to the preparation of the environment. (#3306)

* Added the working-with-nodes-api-service tutorial.

* Added the 'Alfresco Example Content Application' paragraph to the 'Preparing the development environment' tutorial.

* [ADF-2843] added tooltips for all the buttons in toolbar (#3297)

* [ADF-2843] added tooltip for create new folder and edit folder icons from Content Service

* [ADF-2843] added translation for tooltip

* [ADF-2843] added tooltips for all the buttons in toolbar

* [ADF-2843] discard package-lock

* [ADF-2832] discard changes to package-lock

* [ADF-2843] discarded changes from package-lock

* [ADF-2843] added tooltip for list view button

* [ADF-2912] added group everyone as constant result for add permissions (#3266)

* [ADF-2556] first step to create add people or group to permissions

* [ADF-2556] creating a dialog with user results

* [ADF-2556]
integrated service for add and remove permission from node

* [ADF-2556] fixed behaviour and style for add user group

* [ADF-2556] added some refactoring for dialog service

* [ADF-2556] refactoring the dependencies of the components

* [ADF-2556] added some fix and a new key for dialog

* [ADF-2556] start adding test for node permission service

* [ADF-2556] added test for add permission panel component

* [ADf-2556] adding tests for new add permission component

* [ADF-2556] fixed tests and added documentation

* [ADF-2556] fixed documentation for add-node components

* [ADF-2556] added peer review changes

* [ADF-2912] added group everyone as constant result for add permissions

* [ADF-2912] readded jsdoc

* fix search filter styles (#3313)

* [ADF-2978] added a check when locally set permission is undefined (#3307)

* [ADF-2969] Moved doc tools to new tools folder (#3314)

* [ADF-2969] Moved doc tools to new tools folder

* [ADF-2969] Added files missing from schematic

* [ADF-2969] Added missing files to schematic

* [ADF-2813] set default sorting (#3311)

* [ADF-2891] decoupled search service love (#3312)

* Added the tutorial 'Creating your JavaScript application using alfresco-js-api'. (#3310)

* Added the working-with-nodes-api-service tutorial.

* Added the 'Alfresco Example Content Application' paragraph to the 'Preparing the development environment' tutorial.

* Added the tutorial 'Creating your JavaScript application using alfresco-js-api'.

* Added the image of the tutorial.

* Typo in 'creating-javascript-app-using-alfresco-js-api.md'

Typo in 'Prerequisites'.

* Typos in 'creating-javascript-app-using-alfresco-js-api.md'

Two small typos.

* Wrong sentence in 'creating-javascript-app-using-alfresco-js-api.md'

...into Angular applications, but it is "framework agnostic".

* Typo on 'creating-javascript-app-using-alfresco-js-api.md'.

...and Angular...

* [ADF-2888] UX doesn't respect the Sidenav specifiactions (#3303)

* [ADF-2771] Sidebar action menu component - UX review

* Add a input property to set the width of sidebar-action-menu.

* [ADF-2888]  UX doesn't respect the Sidenav specifiactions

* Add a input property to set the width of sidebar-action-menu.

* remove search configuration enforcement (#3315)

* [ADF-2986] try to pick the start value from local storage before the standard default (#3318)

* [ADF-2912] group everyone is always visible even for no search result (#3316)

* [ADF-2826] fixed wrong parsing for error message (#3317)

* [ADF-2739] Improved breadcrumb logic (#3287)

* [ADF-2739] Long names in breadcrumb fixed

* [ADF-2739] Updated styles

* [ADF-2739] Fixing @mixin for breadcrumb

* [ADF-2739] Waiting for changes in demo-shell

* [ADF-2739] Fixed @mixin

* [ADF-2739] Fixed issue related to breadcrumb position

* [ADF-2739] Improved ngOnChanges call for breadcrumb

* [ADF-2739] Fixed issues with lint

* [ADF-2739] Removed comment in dropdown breadcrumb component file

* [ADF-2739] Changed recalculateNodes method from public to protected

* pipeline-update (#3309)

* deploy PR script

* [ADF-2967] fixed create new task navigation (#3322)

* [ADF-2967] fixed create new task navigation

* [ADF-2967] removed extra variables

* [ADF-2967] removed unused methods

* [ADF-2961] support for protocol substition in app config files (#3324)

* support for protocol substition in app config files

* Update app-config.service.spec.ts

* Update app-config.service.spec.ts

* [ADF-2925] Required and invalid validators added (#3277)

* test added

* tests clean

* test added

* tslint clean

* test name changed

* update tests

* Added the tutorial 'Working with the Nodes API Service'. (#3319)

* [ADF-2979] Updated props script to avoid copying missing JSDocs (#3327)

* [ADF-2979] Updated tools to ignore blank JSDocs for inputs/outputs

* [ADF-2979] Bug fixes for handling missing tables, etc

* [ADF-2989] updated documentation with multiple node example (#3328)

* [ADF-2503] conditional visibility for content actions (#3325)

* conditional visibility for content actions

* fix typo

* workaround for "target: all"

* [ADF-2821] move module sidebar action menu (#3321)

* move module sidebar action menu

* fix core import test

* remove sidebar module

* skip error

* skiperror old versions

* [ADF-2995] Permissions - Consumer should be able to upload a new version for his file on a private site (#3326)

* check node permission instead of parent

* tests

* Fix sidenav action icons (#3331)

* Only the username form control should be emitted (#3333)

* about component (#3337)

* [ADF-2563] Improve versioning functionality (#3335)

* change input with textarea

* update file version use now the update content API

* provide way to test read only mode version list

* fix test

* test fix

* [ADF-2997] The meaning of the range fields is not clear (#3338)

* [ADF-2997] restrict 'to' field not to allow dates in the future

* [ADF-2997] more clear labels for start and end date fields

the translation team will decide what will be the best values for the date fields

* [ADF-3000] Update the documentation by adding the steps of date format custom configuration. (#3339)

* [ADF-3028] i18n support for title service (#3342)

* i18n support for title service

* cleanup tests

* update tests

* fixed constraint for viewer name width (#3343)

* (demo shell) login css fixes (#3344)

* [ADF-1997] override custom form widgets by default (#3346)

* override custom form widgets by default

* update tests

* [ADF-2979] Updated doc tools to avoid erasing MD docs with blank JSDocs in services (#3347)

* [ADF-2979] Update to ignore blank JSDocs for method signature

* [ADF-2979] Finished adding blank JSDoc check for services

* [ADF-2131] Search sorting (#3334)

* sorting configuration

* detect primary sorting and use with document list

* search results sorting

* docs update

* unit tests and code updates

* update code

* update code

* generic sorting picker, test updates

* ability to switch off client side sorting

* update docs for document list

* [ADF-2884] Task List - Custom apps display all tasks (#3329)

* [ADF-2884] Handled filterParam input change
Changed component selector

* [ADF-2884] Modified/Added tests

* [ADF-2884] Deprecated old selectors

* [ADF-2884] Added deprecated version

* [ADF-2884] Improved filter finding condition

* update project permissions change

* [ADF-2984] Show date invalid message on search date range picker (#3323)

* [ADF-2984] Show date invalid message on search date range picker

* [ADF-2984] test that required format is displayed when date input is invalid

* [ADF-2984] More space above buttons

* dummy commit

* restore deleted breadcrumb code

* [ADF-2753] New error component (#3336)

* [ADF-1938] Overflowing text in reports section fidex

* [ADF-1938] Long names in report section now fit

* [ADF-1938] Reverted changes in container widget

* [ADF-2753] New error component created

* [ADF-2753] Unit test for Error Content Component

* Deleting unused files

* Deleting unused files

* Deleting unused files

* [ADF-2753] Documentation added

* [ADF-2753] Fixed minor bugs

* [ADF-2753] Authentication not needed to view error

* add error handler

* tslint fix

* router app component

* remove unused import

* fix import modules

* limit to 404

* destroy fixture after any test

* misspelling error

* breadcrumb bug fixes (#3348)

* [ADF-2726] fixed save content for external repository (#3341)

* [ADF-2726] start fixing the show of files loaded from CS

* [ADF-2726] start fixing the show of files loaded from CS

* [ADF-2726] fixed save content for external repository|

* [ADF-2726] fixed save content for external repository|

* [ADF-2726] reeanabled and fixed the tests

* [ADF-2726] reeanabled and fixed the tests

* [ADF-2726] added tests for attach file widget and activiti alfresco service

* [ADF-2726] added tests for attach file widget and activiti alfresco service

* [ADF-2726] fixed test

* fix like test

* fix typo in the resource key (#3352)

* fix failing tests replace ajax with spy

* remove fdescribe

* [ADF-3040] Replaced doc template engine to fix whitespace issues (#3356)

* replace jasmine ajax with spy in rating tests

* fix breadcrumb align in object picker (#3353)

* fix no content node display (#3351)

* Broken links and missing images for two tutorials. (#3357)

* Broken links and missing images for two tutorials.

* Replaced .html with .md extensions

Replaced .html with .md extensions.

* Typo in Prerequi(si)tes

Typo in Prerequi(si)tes.

* [ADF-2988] fix logo change (#3362)

* [ADF-3055] fix bug with page size for facet fields (#3359)

* fix bug with page size for facet fields

* move page size to a constant

* move const inside the scope

* test fixes

* [ADF-2923] button type added (#3358)

* button type added

* remove unnecessary code

* [ADF-2608] added a fix to handle selection on slow connection problem (#3350)

* [ADF-2608] added a fix to handle selection on slow connection problem

* [ADF-2608] fixed test for datatable

* Added an import command to the 'Adding a new view' tutorial. (#3366)

* remove redundant logic (#3361)

* [ADF-2679] Reviewed latest tutorial content (#3371)

* [ADF-2679] Initial review of new tutorial content

* [ADF-2679] Reviewed the latest tutorials

* [ADF-2679] Initial review of new tutorial content

* [ADF-2679] Reviewed the latest tutorials

* [ADF-2832] Demo-shell, Fix Task filter dropdown menu alignment (#3294)

* [ADF-2832] changed height to auto for task filter dropdown header

* [ADF-2832] change selector with classes

* [ADF-2832] changed selector

* [ADF-2832] removed unnecessary css

* [ADF-2832] added class and changed selector

* [ADF-2832] added adf-prefix

* [ADF-2927] search filter enhancements (#3365)

* search filter enhancements

* reset button for facet queries

* update code and tests

* remove unused type

* restore code missing after rebase

* [ADF-2764] Updated doc files with latest script features (#3368)

* [ADF-2764] Updated doc files with latest script features

* [ADF-2764] Rebuilt full index instead of just content services index

* [ADF-2985] Demo shell - Provide an easy way to test the security issue (#3354)

* Provide an easy way to test the security issue

* use logService

* first part random test fix (#3376)

fixing random test executions first part

* fix form radio test

* [ADF-3065] TaskList component - Deprecate the processDefinitionKey (#3369)

* Deprecate the processDefinitionKey property.
remove unused tasklist code. It was needed with the old pagination component

* Remove nosense test

* Use adf version

* [ADF-2999] readded form validation icon (#3367)

* [ADF-2999] readded form validation icon

* [ADF-2999] fixed broken test

* Added the tutorial 'Basic theming'. (#3378)

* [ADF-2854] Fix demoshell task/process filter and routing (#3360)

* Fix demoshell task/process filter and routing

* Use the correct js api model

* Fix unit test

* search filter now remembers original user query (#3384)

* [ADF-2319] added a check for delete checklist button (#3382)

* [ADF-2238] removed created by property (#3380)

* [ADF-2238] removed created by property

* [ADF-2238] added a condition to avoid checklist delete for completed task

* Revert "[ADF-2238] added a condition to avoid checklist delete for completed task"

This reverts commit fc2227ea04.

* bold font for checked boxes (#3381)

* [ADF-2774] fixed message bus, background and language dropdowns that were hidden by the toolbar (#3379)

* [ADF-2764] Applied new doc tool features to core library (#3383)

* [ADF-2824] Added and reviewed some content services docs (#3385)

* revert changes (#3387)

* Added the 'Content metadata component' tutorial. (#3386)

* update package settings and lock file (#3389)

* minor layout fix for search facet buttons

* [ADF-2857] fixed message for date and date time widget (#3370)

* [ADF-2764] Updated process services docs with latest features (#3390)

* [ADF-2977] made language change persistenr (#3373)

* [ADF-3041] TaskList Component - Empty State issue. (#3345)

* [DW-635] Empty State Component

* [DW-635] Empty State Component

* [DW-635] Empty state Issue

* [DW-635] Use empty state component and custom empty directive

* [ADF-3041] Documentation for TaskList Component - Empty State issue.

* [ADF-3088] move sorting picker to a separate module (#3396)

* move sorting picker to a separate module

* update translation mock

* [ADF-3087] Clarified section about adding/replacing keys in i18n guide (#3392)

* [ADF-3087] Clarified section about adding/replacing keys

* [ADF-3087] Corrected information in i18n and translation service docs

* [ADF-3068] ADF Sidenav/Toolbar - Ux background colour review (#3401)

* [ADF-2824] Reviewed docs for new components in 2.4 (#3400)

* [ADF-3042] Use the custom date adapter from adf-core on Search Date Range widget (#3394)

-fix localization
-fix tests
-show parse error

* [ADF-3053] breadcrumb fixes (#3406)

* translate breadcrumb root in demo shell

* optional "max items" feature and style fixes

* should not be restricted by default

* style updates

* toolbar and breadcrumb layout fixes

* breadcrumb demo and testing page

* full toolbar scenario

* fix translation issue with the dropdown and custom root

* a11y fixes

* fix issue with duplicate id, remove unused attribute

* [ADF-2627] Icons-only mode for Info Drawer (#3398)

* tab with icon

* docs

* test

* add option without login docker publish

* fix publish script

* move docker push outside login

* fix random test failing part 2 (#3395)

* fix random failing test core search/comment/auth/user

* fix node delete directive

* fix lint issues

* node restore fix

* fix comment test

* remove fdescribe

* fix tests and tslint

* fix upload test

* unsubscribe success event task test

* copy comment object during test

* use the data pipe performance improvement and standard usage

* uncomment random test

* fix comment date random failing test

* disposable unsubscribe

* fix start process

* remove fdescribe

* change start process test and remove commented code

* fix error event check double click

* clone object form test

* refactor date time test

* fix service mock

* fix test dropdown and context

* git hook lint

* fix language test

* unsubscribe documentlist event test

* fix disposable error

* fix console log service error document list

* unusbscribe search test

* clear input field

* remove wrong test

* [ADF-2754] People Widget - Refactoring style and logic (#3349)

* observable logic added

* tests added & bug fixed

* onkeyup removed

* isValidUser method removed

* tslint fix

* test added

* comments fixed

* check if input value is string

* remove console log err from document list test

* [ADF-3082] Task Filter - Custom Task filters don't work (#3402)

* [ADF-3082] Custom Task filters don't work.

* Added an sorting input to the datatable.
* Updated documentation for the recent changes.
* Added testcases for the recent changes.

* [ADF-3082] Custom Task filters don't work

* Added a sorting input to the datatable
* Added testcases to the recent changes.
* Updated doc for the recent changes.

* * Refactored task/process list dataSort.

* * Refactored process/task list datasort method

* remove console log error from version list and content node selector test stubbing necessary method

* [ADF-3041] - Task List Empty State Issue (#3408)

* fix demo shell gallery view switch (#3413)

* [ADF-2541] reset datatable selection when rows are changed from code (#3410)

* reset selection when rows are replaced from code

* code fixes

* unit test updates

* visualise selection count for testing purposes

* make row selection api public

* remove question mark from the event name

* remove pdfviewer thumbnail console log test error

* fix conflict link creation id (#3412)

*  [ADF-2753] Error Component throwing default error fixed (#3364)

* [ADF-1938] Overflowing text in reports section fidex

* [ADF-1938] Long names in report section now fit

* [ADF-1938] Reverted changes in container widget

* [ADF-2753] New error component created

* [ADF-2753] Unit test for Error Content Component

* Deleting unused files

* Deleting unused files

* Deleting unused files

* [ADF-2753] Documentation added

* [ADF-2753] Error Component throwing default error fixed

* [ADF-2753] White space removed

* [ADF-2753] Removed unnecessary files and updated trnaslation file

* [ADF-2753] Changed link for button in error component

* [ADF-2753] Updated doc file

* [ADF-2753] Removed fdescribe

* [ADF-2753] Improved code coverage

* fix error AppTitle service test log

* fix gallery view demo shell route

* [ADF-3095] ability to intercept, pause and resume upload process (#3416)

* prevent and resume upload process

* upload fixes and confirmation dialog demo

* ability to toggle the upload confirmation demo

* fix tests

* unit tests

* docs update

* remove deprecation

* fix test name

* [ADF-2698] Date and time Widget - UTC vs local time issue (#3393)

* [ADF-2698] Date and time Widget - UTC vs local time issue

* [ADF-2698] add unit test to check that the format for min and max values is correct

* [ADF-2698] test behavior

* [ADF-3102] added lazy loading for tab content to fix animations (#3418)

* [ADF-2764] Fixed union type display glitches in property tables (#3419)

* [ADF-2764] Test commit to fix handling of union types in Github docs

* [ADF-2764] Fixed union type error in component docs

* [ADF-2593] z-index fix for search bar (#3409)

* [ADF-2593]

* [ADF-2593] Removed unnecessary style attributes

* [ADF-3111] pagination is not showed when automation test are running (#3420)

* [ADF-3108] Search - facetQueries panel renders when not defined in configuration (#3417)

* show facetQueries based on configuration definition

* tests

* FacetQueries is defined

* [ADF-3039] Task List - Enanchement (#3404)

* * Created DataColumnSchemaAssembler component to get column schema from html and app.config.json
* Removed column related  method  from tasklist.

* * Removed data property from the tasklist component
* Using rows input property instead of data input property of the datatable

* *  Renamed  DataColumnSchemaAssembler to DataTableSchema
* Refactored DataTableSchema component

* * Changed schem property into an  input schemaColumns property  in dataTable component

* * Added selectFirstRow input property to select a first row of datatable
* Removed unnecessary method from tasklist component

* * Added test case for the recent changes
* Added mock object for the tasklist spec

* * Added testcases for recent changes in the datatable component

* * Updated datatable and tasklist document for the recent changes

* * Refactored process-service and task list component
* Updated datatable document.

* [ADF-3039] Task List - Enanchement
* Changed schemaColumn name to columns
* Updated datatable documentation.
*  data input Annotated  with @deprecated in the tasklist component

* * Added an sorting input to the datatable.
* Updated datatable and tasklist documentation
* Added method to get current sorting order.

* * After rebasing

* * Revert  sorting changes

* * After rebase

* * fixed conflicts

* * Fixed failing testcase after rebased.

* update toolbar docs (#3423)

* Add custom data holder for CardViewItems (#3422)

* [ADF-3115] a11y fixes (#3424)

* a11y fixes for search input

* a11y fixes for pagination

* a11y fixes for content actions

* [ADF-3118] translation support for notification service (snackbars) (#3427)

* translation support for snackbar

* unit tests

* [ADF-2438] fixed thumbnails height (#3407)

* [ADF-2438] calculate thumbnail height

* [ADF-2438] send height to parent element

* [ADF-2438] add width to css

* [ADF-2438] moved height and width logic to parent component

* [ADF-2438] added height and width logic to parent component

* [ADF-2438] fixed failing test

* demo translation for search radio

* [ADF-2710] removed default shared link creation at the opening (#3430)

* [ADF-2999] added a way to test the validation icon on demo shell (#3431)

* [ADF-2754] People component tests fixed and style improvement (#3414)

* tests fixed and style

* show people test fixed

* error message test fixed

* [ADF-3131] fix selection order for DT/DL (#3433)

* fix selection order for DT/DL

* remove fit

* [ADF-3066] ProcessList Component - Empty State issue. (#3434)

* [ADF-3093] Added style checking tool for en.json translation file (#3428)

* [ADF-3093] Started li18nt VS Code extension

* [ADF-3093] Started work on UI style lint tool for VSCode

* [ADF-3093] Added UI style rules up to sg0006

* [ADF-3093] Added remaining style rules

* [ADF-3093] Added docs and command line tool

* [ADF-3093] Removed Microsoft notices and updated licences to Apache-2.0

* [ADF-3133] Fixed inconsistency in doc example (#3436)

* [ADF-2753] Fixed routing and second button showing up (#3421)

* [ADF-1938] Overflowing text in reports section fidex

* [ADF-1938] Long names in report section now fit

* [ADF-1938] Reverted changes in container widget

* [ADF-2753] New error component created

* [ADF-2753] Unit test for Error Content Component

* Deleting unused files

* Deleting unused files

* Deleting unused files

* [ADF-2753] Documentation added

* [ADF-2753] Removed unnecessary files and updated trnaslation file

* []

* [ADF-2753] Fixed routing and second button showing up

* [ADF-2753] Fixed typo

* [ADF-2753] Fixed view loading before variables

* [ADF-2753] Missing whitespace

* [ADF-2753] Added test for route params

* [ADF-2753] Changed getData function name to getTranslations

* [ADF-fix date widget test] Fixing a randomly failing test (#3439)

* group uploaded files into single batch, IE fixes (#3438)

* [ADF-3067] App-list Empty State. (#3432)

* extend demo shell upload example to button

* [ADF-2764] Added new type linker features and applied them to core docs (#3442)

* [ADF-2764] Added basic support for composite and external types

* [ADF-2764] Added new type linker features and applied to core docs

* notification service demo and testing area (#3443)

* notification service demo and testing area

* fix typo

* fix viewer title style (#3445)

* [ADF-3132] TaskList empty state doesn't respect the specification (#3440)

* [ADF-3134] fix increasing search calls issue (#3444)

* [ADF-2764] Applied new type linker features to content services docs (#3446)

* [ADF-3062] dual api support for Favorites (#3447)

* dual api support for Favorites

* unit test

* ADF-2974] New Buttons Menu component version (#3429)

* [ADF-2974] Buttons injected from parent html component

* [ADF-2974] New version of buttons menu component

* [ADF-2974] Updated unit tests

* [ADF-2974] Updated documentation

* [ADF-2974] Removed unused variable

* [ADF-2974] Fixed failing test at analytics report parameters

* [ADF-2974] Removed fdescribe

* [ADF-2974] Moved mock inside testing file for buttons menu component

* extend breadcrumb testing page (demo shell)

* [ADF-3142] Added missing info about 'remember me' in login docs (#3448)

* [ADF-3120] fixed sorting for tasklist and process list (#3435)

* [ADF-3120] fixed sorting for tasklist and process list

* [ADF-3120] removed commented code

* [ADF-3120] fixing another randomly failing test

* [ADF-2764] Applied new type linker features to proc services docs (#3449)

* (demo shell) fix memory leak with search results

* [ADF-2795] SSO implicitflow (#3332)

* Enable OAUTH2

* Create SSO services

* SSO improvements

* Rollback sso login change

* Add SSO configuration from Setting component

* Refactoring

* Remove login ECM/BPM toggle and move use the userpreference instead of store

* fix host setting unit test

* Fix unit test missing instance

* use the Js api oauth

* add logout component and clean sso not used class

* fix dependencies cicle

* add translation settings

* fix style setting page

* clean

* JS APi should receive the oauth config from the userPreference and not from the config file

* change login if SSO is present

* missing spaces

* add sso test in login component

* add logout directive new properties test

* Improve host setting and remove library reference

* fix login test

* Remove unused code

* Fix authentication unit test

* fix authguard unit test

* fix csrf check login component

* fix unit test core and demo shell

* remove

* simple filtering for datatable (#3453)

* [ADF-3150] Moved all doc tool config settings to doctool.config.json (#3455)

* [ADF-3150] Moved undoc stoplist to doc config file

* [ADF-3150] Moved config to doctools.config.json and removed obsolete scripts

* fixes for memory leaks and multiple subscriptions (#3456)

* fix content action memory leaks

* memory leak fixes for demo shell

* [no-issue] error image resolver mimetype should not be part of datatable (#3415)

* move error image custom logic form datatable to documentlist

* change travis

* [fix-test-log] added optional function to data row interface

* [no-issue] fixed datatable tests

* [no-issue] fixing tests

* [ADF-2859] conditional evaluation of disabled state for content actions (#3450)

* react on [disabled] binding changes

* [disabled] binding updates for context menu items

* evaluating disabled state with a function

* unit test

* restore original description

* remove irrelevant test

* fix tests

* fix test

* remove not used events in demo shell host setting components

* [ADF-3156] App config default ALL (BPM and ECM) provider (#3460)

* Use as default provider ALL (BPM and ECM)

* Default host OAUTH

* removing wrong fdescribe (#3461)

* [ADF-3123] removed default choice for the first radio button (#3462)

* Fix host component (#3463)

* [ADF-2824] Doc review and minor changes to review checker tool (#3466)

* restore historical selection order (#3467)

* restore historical selection order

* fix test

* [ADF-2859] fixes for the conditional visibility and disabled states (#3465)

* fixes for the conditional visibility and disabled states

* update docs

* cleanup code

* remove unused code

* [LOC-61] i18n 2.4.0 (#3468)

* i18n 2.4.0

* missing part

* revert development config

* [ADF-3144] Error component displayed when closing file viewer in process tab (#3469)

* [ADF-3173] [Task List] - Empty list message is not translated (#3471)

* Added a missing translation keys.

* Fix dropdown panel in viewer toolbar  because of z-index (#3472)

* [ADF-3162] Setting component - Should be able to render the providers passed as input (#3464)

* Be able to change the available providers values

* Add deprecated tag

* add default providers

* Fix empty OAuth and selected providers
Improve the documentation

* Fix BPM guard to check SSO condition

* Add the oauthConfig again

* SSO or Basic otpion auth setting change

* fix host settings sso/basic
add login documentation

* remove test string

* fix auth guard test

* dispose upload emitter test events

* remove space

* exclude failing test

* Update ADF packages version 2.4.0-beta13 (#3475)

* [ADF-2824] Reviewed search docs for 2.4 (#3477)

* [ADF-2824] Moved search widget docs into separate pages

* [ADF-2824] Separated search widget page and added search filter service docs

* [ADF-2824] Updated new search docs with doc tools

* Remove unused field (#3480)

* Fix default SSO config not filled in (#3479)

* [ADF-3175] renabling upload on single target folder row (#3473)

* [ADF-3191] Not able to login only to BPM or ECM (#3478)

* disable random test

* host setting component reset JS-API

* reload api after user prefrence change

* remove replicate reset

* missing semicolon

* deprecate provider property

* remove trailing space

* check new value before to set

* [ADF-3198] Updated index files + minor doc updates (#3482)

* [ADF-3200] custom Extension filter - file with different extension is uploaded with drag and drop

* [ADF-3181] Auth Guard - redirect by string url (#3474)

* redirect by string url

* auth guard tests fix

* revise

* [ADF-3196] fixed task selection and double click (#3484)

* [ADF-3191] Fix userprefrence oauth2 (#3488)

* remove user preference setting save

* fix host setting test

* remove userPreferences test

* fix title service test

* remove unused imports

* restore input align

* fix service import in test tag rating

* [ADF-3210] Typo People Search (#3487)

* remove vscode changes

* [ADF-3196] [Task list / Process list] - The 'Task details' are not diplayed for the selected Task (#3483)

* [ADF-3196] [Task list / Process list] - The 'Task details' are not displayed for the selected Task

* * After rebase

* [ADF-3176] Fixed permissionsissue when editing metadata (#3476)

* [ADF-3217] reset previous search settings (#3490)

* reset previous search settings

* unit test

* remove fit

* [ADF-3196] fixed selection of first tasklist element (#3491)

* [ADF-3196] [Task list / Process list] - The 'Task details' are not displayed for the selected Task

* * After rebase

* [ADF-3196] fixed selection for fist element

* [ADF-3212] Upload version buttons wrapping fixed (#3492)

* Update ADF packages version 2.4.0-beta14 (#3493)

* demo shell host setting component login redirect after setting

* remove old auth test not  valid anymore

* fix translations (#3497)

* [ADF-3203] Fixed doc tools bugs from Linux run and added verbose option (#3496)

* [ADF-3912] Setting Component - Return as default BASIC authType (#3495)

* Return as default BASIC authType

* remove the commented line

* [ADF-3190] Fix rerendering issue with content-meta-data (#3494)

* Fix rerendering issue with content-meta-data

* Fix indentation

* [ADF-3198] Index and tutorial content review (#3500)

* [ADF-3198] Fixed glitches in doc index files and tools

* [ADF-3198] Reviewed new tutorial content and fixed index glitches

* [ADF-3198] Fixed host settings component brief description

* [ADF-3223] Restore message directive files fix (#3499)

* fix restore notification

* improve path specification property

* fix lint and document issues

* fix test

* remove unused import

* [ADF-3226] remove Double behaviour setting allow download and comment (#3502)

* fix restore notification

* improve path specification property

* fix lint and document issues

* fix test

* remove unused import

* remove double behavior allowComments and allowDonload can be set only thorough input property

* [ADF-3233] fixed demo shell filtering for files on drag&drop] (#3504)

* [ADF-3232] fixing retrieving for ecm user via site service (#3506)

* [ADF-3176] file content reload fixed (#3501)

* file content reload fixed

* refactoring & layout fix

* resolve indentation problems

* missing semicolon

* [ADF-3224] move ticket store logic in js-api (#3505)

* move ticket store logic in js-api

* remove space

* fix test

* fix tests

* [ADF-3212] Fixed permission issues when updating version (#3503)

* [ADF-3213] Fixed permission issues when updating version

* [ADF-3212] fixed test for PR

* allowableOperations include viewer get node

* update demo shell with metadata demo

* undo ADF-3176 changes

* fix login oauth cookie problem (#3508)

* fix login don't check the cookie in oauth2

* fix test

* check boolean storage

* fix provider check

* clean local storage before auth test

* fix missing pdf viewer update when scale is the same (#3510)

* [ADF-3239] Fixed deprecated property descriptions (#3509)

* [ADF-3239] Changed deprecated properties to include full description

* [ADF-3239] Updated deprecated properties of affected doc files

* [ADF-3239] Fixed badly formatted doc comment in tasklist

* [ADF-3229] people widget list reseted if empty input (#3507)

* list reseted when input is empty

* test added

* [ADF-3242] removed filter for string in example log component (#3511)

* [ADF-3242] removed filter for string in example log component

* [ADF-3242] added try catch for circular dependency objects

* [ADF-3242] added try catch and removed error message

* invalidate session in host setting internal component instead to logout

* remove unused import

* use i18n key for "Permission" label

* [ADF-3198] Final doc checks for release (#3513)

* [ADF-3198] Doc link check for release

* [ADF-3198] Updated main index doc page

* remove invalidation session because is concern of the js-api

* check 401 and redirect demo shell (#3516)

* [ADF-3248] fixed readonly status for people widget (#3512)

* [ADF-3248] fixed readonly status for people widget

* [ADF-3248] fixed test people widget

* [ADF-3248] added readonly test

* [ADF-3251] Authentication Service - Check if used loggedin based on the provider (#3515)

* check if loggedin based on the provider

* Fix unit tests

* remove 404 check in demo shell

* [ADF-3262] Remove avoidable css style from task/process list component (#3518)

* [ADF-3230] Changes to stop doc tools from updating files unnecessarily (#3519)

* [ADF-3230] Added basic change detector to doc tools

* [ADF-3230] Updates to type linker to fix before/after inconsistencies

* [ADF-3230] Fixed comparison error caused by adjacent text blocks

* [ADF-3230] Added basic change detector to doc tools

* [ADF-3230] Updates to type linker to fix before/after inconsistencies

* [ADF-3230] Fixed comparison error caused by adjacent text blocks

* [ADF-3230] Modified props tool to remove spaces from union types

* [ADF-3230] Made ToC tool before/after state consistent

* [ADF-3265] Updated index script to remove brief desc links (#3520)

* bump 2.4.0 version
This commit is contained in:
Eugenio Romano
2018-06-25 11:24:26 +01:00
committed by GitHub
parent c3db1c2ff8
commit f9658e2879
1054 changed files with 70758 additions and 16578 deletions
-35
View File
@@ -1,40 +1,5 @@
# Alfresco Angular Components
<!-- markdown-toc start - Don't edit this section. npm run toc to generate it-->
<!-- toc -->
- [Documentation](#documentation)
- [Build from sources](#build-from-sources)
- [NPM scripts](#npm-scripts)
<!-- tocstop -->
<!-- markdown-toc end -->
## Documentation
The [docs index](../docs/README.md) lists all available documentation for components and
also includes a user guide that explains techniques in greater detail.
## Build from sources
You can build component from sources with the following commands:
```sh
npm install
npm run build
```
> The `build` task rebuilds all the code, runs tslint, license checks
> and other quality check tools before performing unit testing.
## NPM scripts
| Command | Description |
| --- | --- |
| npm run build | Build component |
| npm run test | Run unit tests in the console |
| npm run test-browser | Run unit tests in the browser
| npm run coverage | Run unit tests and display code coverage report |
@@ -1,27 +0,0 @@
# Review checker guide
The review checker tool queries the Github repo to look for recent
commits to the component source files. The dates of these commits
are compared against against a review date stored in the Markdown doc
file for each component. The time and the number of commits since the
last review are then combined into a "score" that gives an indication
of how urgently the doc file needs a review.
## Review date metadata
The review date is kept in the YAML metadata section at the top of each
Markdown file. The key is "Last reviewed" and the date is in the form
YYYY-MM-DD.
## Commit message stoplist
The checker will ignore any commits that match regular expressions stored
in the `commitStoplist.json` file in the `DocProcessor` folder. You could
use this, for example, to filter out JIRA tasks that don't involve any
changes in functionality (and therefore don't need documenting).
## Output format
The script sends comma-separated text to the command line. You can copy/paste
this into a spreadsheet or redirect the output to a text file with a ".csv"
suffix.
@@ -1 +0,0 @@
["ADF-1769"]
-177
View File
@@ -1,177 +0,0 @@
var fs = require("fs");
var path = require("path");
var program = require("commander");
var remark = require("remark");
var parse = require("remark-parse");
var stringify = require("remark-stringify");
var frontMatter = require("remark-frontmatter");
// "Aggregate" data collected over the whole file set.
var aggData = {};
var toolsFolderName = "tools";
var configFileName = "doctool.config.json";
var defaultFolder = path.resolve("..", "docs");
function initPhase(aggData) {
toolList.forEach(toolName => {
toolModules[toolName].initPhase(aggData);
});
}
function readPhase(filenames, aggData) {
for (var i = 0; i < filenames.length; i++) {
var pathname = filenames[i];//path.resolve(srcFolder, filenames[i]);
var src = fs.readFileSync(pathname);
var tree = remark().use(frontMatter, ["yaml"]).parse(src);
toolList.forEach(toolName => {
toolModules[toolName].readPhase(tree, pathname, aggData);
});
}
//console.log(JSON.stringify(aggData.mdFileData));
}
function aggPhase(aggData) {
toolList.forEach(toolName => {
toolModules[toolName].aggPhase(aggData);
});
}
function updatePhase(filenames, aggData) {
for (var i = 0; i < filenames.length; i++) {
var pathname = filenames[i]; // path.resolve(srcFolder, filenames[i]);
var src = fs.readFileSync(pathname);
var tree = remark().use(frontMatter, ["yaml"]).parse(src)
var modified = false;
toolList.forEach(toolName => {
modified |= toolModules[toolName].updatePhase(tree, pathname, aggData);
});
if (modified)
fs.writeFileSync(filenames[i], remark().use(frontMatter, {type: 'yaml', fence: '---'}).data("settings", {paddedTable: false, gfm: false}).stringify(tree));
//console.log(JSON.stringify(tree));
}
}
function loadToolModules() {
var mods = {};
var toolsFolderPath = path.resolve(__dirname, toolsFolderName);
var modFiles = fs.readdirSync(toolsFolderPath);
for (var i = 0; i < modFiles.length; i++) {
var modPath = path.resolve(toolsFolderPath, modFiles[i])
if (path.extname(modPath) === ".js") {
var toolName = path.basename(modPath, ".js");
mods[toolName] = require(modPath);
}
}
return mods;
}
function loadToolConfig(configFilePath) {
var config = JSON.parse(fs.readFileSync(configFilePath));
return config.enabledTools;
}
function getAllDocFilePaths(docFolder, files) {
var items = fs.readdirSync(docFolder);
for (var i = 0; i < items.length; i++) {
var itemPath = path.resolve(docFolder, items[i]);
var itemInfo = fs.statSync(itemPath);
if (itemInfo.isFile()){
files.push(itemPath);
} else if (itemInfo.isDirectory()) {
getAllDocFilePaths(itemPath, files);
}
}
}
program
.usage("[options] <source>")
.parse(process.argv);
if (program.args.length === 0) {
console.log("Error: source argument required");
return 0;
}
var sourcePath = path.resolve(program.args[0]);
var sourceInfo = fs.statSync(sourcePath);
/*
var destPath;
var destInfo;
if (program.args.length >= 2) {
destPath = path.resolve(program.args[1]);
destInfo = fs.statSync(sourcePath);
} else {
destPath = sourcePath;
destInfo = sourceInfo;
}
if (sourceInfo.isDirectory() && !destInfo.isDirectory()) {
console.log("Error: The <dest> argument must be a directory");
return 0;
}
*/
var toolModules = loadToolModules();
var toolList = loadToolConfig(path.resolve(__dirname, configFileName));
var files = [];
if (sourceInfo.isDirectory()) {
getAllDocFilePaths(sourcePath, files);
} else if (sourceInfo.isFile()) {
files = [ sourcePath ];
//files = [ path.basename(sourcePath) ];
//sourcePath = path.dirname(sourcePath);
//destPath = path.dirname(destPath);
}
files = files.filter(filename =>
(path.extname(filename) === ".md") &&
(filename !== "README.md")
);
//files.forEach(element => console.log(element));
console.log("Initialising...");
initPhase(aggData);
console.log("Analysing Markdown files...");
readPhase(files, aggData);
console.log("Computing aggregate data...");
aggPhase(aggData);
console.log("Updating Markdown files...");
updatePhase(files, aggData);
@@ -1,7 +0,0 @@
{
"enabledTools": [
"index",
"versionIndex",
"tutorialIndex"
]
}
-31
View File
@@ -1,31 +0,0 @@
var fs = require("fs");
var path = require("path");
module.exports = searchLibraryRecursive;
var angFilenameRegex = /([a-zA-Z0-9\-]+)\.((component)|(directive)|(model)|(pipe)|(service)|(widget))\.ts/;
var searchFolderOmitRegex = /(config)|(mock)|(i18n)|(assets)|(styles)/;
// Search source folders for .ts files to discover all components, directives, etc.
function searchLibraryRecursive(srcData, folderPath) {
var items = fs.readdirSync(folderPath);
for (var i = 0; i < items.length; i++) {
var itemPath = path.resolve(folderPath, items[i]);
var info = fs.statSync(itemPath);
if (info.isFile() && (items[i].match(angFilenameRegex))) {
var nameNoSuffix = path.basename(items[i], '.ts');
var displayPath = itemPath.replace(/\\/g, '/');
displayPath = displayPath.substr(displayPath.indexOf("lib") + 4);
// Type == "component", "directive", etc.
var itemType = nameNoSuffix.split('.')[1];
srcData[nameNoSuffix] = { "path": displayPath, "type": itemType };
} else if (info.isDirectory() && !items[i].match(searchFolderOmitRegex)) {
searchLibraryRecursive(srcData, itemPath);
}
}
}
-19
View File
@@ -1,19 +0,0 @@
module.exports = {
"ngNameToDisplayName": ngNameToDisplayName,
"dekebabifyName": dekebabifyName,
"classTypes": ["component", "directive", "model", "pipe", "service", "widget"]
}
function ngNameToDisplayName(ngName) {
var mainSections = ngName.split(".");
mainSections[0] = dekebabifyName(mainSections[0]);
return mainSections.join(" ");
}
function dekebabifyName(name) {
var result = name.replace(/-/g, " ");
result = result.substr(0, 1).toUpperCase() + result.substr(1);
return result;
}
-103
View File
@@ -1,103 +0,0 @@
"use strict";
exports.__esModule = true;
var path = require("path");
var fs = require("fs");
var process = require("process");
var graphql_request_1 = require("graphql-request");
var remark = require("remark");
var frontMatter = require("remark-frontmatter");
var yaml = require("js-yaml");
var moment = require("moment");
var Rx_1 = require("rxjs/Rx");
var libsearch = require("./libsearch");
var stoplist_1 = require("./stoplist");
var adf20StartDate = "2017-11-20";
var commitWeight = 0.1;
var scoreTimeBase = 60;
var rootFolder = ".";
var stoplistFilePath = path.resolve("config", "DocProcessor", "commitStoplist.json");
var angFilePattern = /(component)|(directive)|(model)|(pipe)|(service)|(widget)/;
var srcData = {};
var stoplist = new stoplist_1.Stoplist(stoplistFilePath);
var docsFolderPath = path.resolve("..", "docs");
var libFolders = ["core", "content-services", "process-services", "insights"];
libsearch(srcData, path.resolve(rootFolder));
/*
let keys = Object.keys(srcData);
for (let i = 0; i < keys.length; i++) {
console.log(keys[i]);
}
*/
var authToken = process.env.graphAuthToken;
var client = new graphql_request_1.GraphQLClient('https://api.github.com/graphql', {
headers: {
Authorization: 'Bearer ' + authToken
}
});
var query = "query commitHistory($path: String) {\n repository(name: \"alfresco-ng2-components\", owner: \"alfresco\") {\n ref(qualifiedName: \"development\") {\n target {\n ... on Commit {\n history(first: 15, path: $path) {\n nodes {\n pushedDate\n message\n }\n }\n }\n }\n }\n }\n}";
var docFiles = getDocFilePaths(docsFolderPath);
var docNames = Rx_1.Observable.from(docFiles);
console.log("'Name','Review date','Commits since review','Score'");
docNames.subscribe(function (x) {
var key = path.basename(x, ".md");
if (!srcData[key])
return;
var vars = {
"path": "lib/" + srcData[key].path
};
client.request(query, vars).then(function (data) {
var nodes = data["repository"].ref.target.history.nodes;
var lastReviewDate = getDocReviewDate(x); //(key + ".md");
var numUsefulCommits = extractCommitInfo(nodes, lastReviewDate, stoplist);
var dateString = lastReviewDate.format("YYYY-MM-DD");
var score = priorityScore(lastReviewDate, numUsefulCommits).toPrecision(3);
console.log("'" + key + "','" + dateString + "','" + numUsefulCommits + "','" + score + "'");
});
});
function priorityScore(reviewDate, numCommits) {
var daysSinceReview = moment().diff(reviewDate, 'days');
var commitScore = 2 + numCommits * commitWeight;
return Math.pow(commitScore, daysSinceReview / scoreTimeBase);
}
function getDocReviewDate(docFileName) {
var mdFilePath = path.resolve(docsFolderPath, docFileName);
var mdText = fs.readFileSync(mdFilePath);
var tree = remark().use(frontMatter, ["yaml"]).parse(mdText);
var lastReviewDate = moment(adf20StartDate);
if (tree.children[0].type == "yaml") {
var metadata = yaml.load(tree.children[0].value);
if (metadata["Last reviewed"])
lastReviewDate = moment(metadata["Last reviewed"]);
}
return lastReviewDate;
}
function extractCommitInfo(commitNodes, cutOffDate, stoplist) {
var numUsefulCommits = 0;
commitNodes.forEach(function (element) {
if (!stoplist.isRejected(element.message)) {
var abbr = element.message.substr(0, 15);
var commitDate = moment(element.pushedDate);
if (commitDate.isAfter(cutOffDate)) {
numUsefulCommits++;
}
}
});
return numUsefulCommits;
}
function getDocFilePaths(folderPath) {
var result = [];
libFolders.forEach(function (element) {
var libPath = path.resolve(folderPath, element);
var files = fs.readdirSync(libPath);
files = files.filter(function (filename) {
return (path.extname(filename) === ".md") &&
(filename !== "README.md") &&
(filename.match(angFilePattern));
});
files.forEach(function (element) {
result.push(path.join(libPath, element));
});
});
return result;
}
-164
View File
@@ -1,164 +0,0 @@
import * as path from "path";
import * as fs from "fs";
import * as process from "process"
import { GraphQLClient } from "graphql-request";
import * as remark from "remark";
import * as frontMatter from "remark-frontmatter";
import * as yaml from "js-yaml";
import * as moment from "moment";
import { Observable } from 'rxjs/Rx';
import * as libsearch from "./libsearch";
import { Stoplist } from "./stoplist";
import { last } from "rxjs/operator/last";
const adf20StartDate = "2017-11-20";
const commitWeight = 0.1;
const scoreTimeBase = 60;
const rootFolder = ".";
const stoplistFilePath = path.resolve("config", "DocProcessor", "commitStoplist.json");
const angFilePattern = /(component)|(directive)|(model)|(pipe)|(service)|(widget)/;
let srcData = {};
let stoplist = new Stoplist(stoplistFilePath);
let docsFolderPath = path.resolve("..", "docs");
let libFolders = ["core", "content-services", "process-services", "insights"];
libsearch(srcData, path.resolve(rootFolder));
/*
let keys = Object.keys(srcData);
for (let i = 0; i < keys.length; i++) {
console.log(keys[i]);
}
*/
const authToken = process.env.graphAuthToken;
const client = new GraphQLClient('https://api.github.com/graphql', {
headers: {
Authorization: 'Bearer ' + authToken
}
});
const query = `query commitHistory($path: String) {
repository(name: "alfresco-ng2-components", owner: "alfresco") {
ref(qualifiedName: "development") {
target {
... on Commit {
history(first: 15, path: $path) {
nodes {
pushedDate
message
}
}
}
}
}
}
}`;
let docFiles = getDocFilePaths(docsFolderPath);
let docNames = Observable.from(docFiles);
console.log("'Name','Review date','Commits since review','Score'");
docNames.subscribe(x => {
let key = path.basename(x, ".md");
if (!srcData[key])
return;
let vars = {
"path": "lib/" + srcData[key].path
};
client.request(query, vars).then(data => {
let nodes = data["repository"].ref.target.history.nodes;
let lastReviewDate = getDocReviewDate(x);//(key + ".md");
let numUsefulCommits = extractCommitInfo(nodes, lastReviewDate, stoplist);
let dateString = lastReviewDate.format("YYYY-MM-DD");
let score = priorityScore(lastReviewDate, numUsefulCommits).toPrecision(3);
console.log(`'${key}','${dateString}','${numUsefulCommits}','${score}'`);
});
});
function priorityScore(reviewDate, numCommits) {
let daysSinceReview = moment().diff(reviewDate, 'days');
let commitScore = 2 + numCommits * commitWeight;
return Math.pow(commitScore, daysSinceReview / scoreTimeBase);
}
function getDocReviewDate(docFileName) {
let mdFilePath = path.resolve(docsFolderPath, docFileName);
let mdText = fs.readFileSync(mdFilePath);
let tree = remark().use(frontMatter, ["yaml"]).parse(mdText);
let lastReviewDate = moment(adf20StartDate);
if (tree.children[0].type == "yaml") {
let metadata = yaml.load(tree.children[0].value);
if (metadata["Last reviewed"])
lastReviewDate = moment(metadata["Last reviewed"]);
}
return lastReviewDate;
}
function extractCommitInfo(commitNodes, cutOffDate, stoplist) {
let numUsefulCommits = 0;
commitNodes.forEach(element => {
if (!stoplist.isRejected(element.message)) {
let abbr = element.message.substr(0, 15);
let commitDate = moment(element.pushedDate);
if (commitDate.isAfter(cutOffDate)) {
numUsefulCommits++;
}
}
});
return numUsefulCommits;
}
function getDocFilePaths(folderPath) {
let result = [];
libFolders.forEach(element => {
let libPath = path.resolve(folderPath, element);
let files = fs.readdirSync(libPath);
files = files.filter(filename =>
(path.extname(filename) === ".md") &&
(filename !== "README.md") &&
(filename.match(angFilePattern))
);
files.forEach(element => {
result.push(path.join(libPath, element));
});
});
return result;
}
-29
View File
@@ -1,29 +0,0 @@
"use strict";
exports.__esModule = true;
var fs = require("fs");
/* "Stoplist" of regular expressions to match against strings. */
var Stoplist = /** @class */ (function () {
function Stoplist(slFilePath) {
var listExpressions = JSON.parse(fs.readFileSync(slFilePath, 'utf8'));
this.regexes = [];
if (listExpressions) {
for (var i = 0; i < listExpressions.length; i++) {
this.regexes.push(new RegExp(listExpressions[i]));
}
}
else {
this.regexes = [];
}
}
// Check if an item is covered by the stoplist and reject it if so.
Stoplist.prototype.isRejected = function (itemName) {
for (var i = 0; i < this.regexes.length; i++) {
if (this.regexes[i].test(itemName)) {
return true;
}
}
return false;
};
return Stoplist;
}());
exports.Stoplist = Stoplist;
-31
View File
@@ -1,31 +0,0 @@
import * as fs from "fs";
/* "Stoplist" of regular expressions to match against strings. */
export class Stoplist {
regexes: RegExp[];
constructor(slFilePath: string) {
let listExpressions = JSON.parse(fs.readFileSync(slFilePath, 'utf8'));
this.regexes = [];
if (listExpressions) {
for (var i = 0; i < listExpressions.length; i++) {
this.regexes.push(new RegExp(listExpressions[i]));
}
} else {
this.regexes = [];
}
}
// Check if an item is covered by the stoplist and reject it if so.
isRejected(itemName: string) {
for (var i = 0; i < this.regexes.length; i++) {
if (this.regexes[i].test(itemName)) {
return true;
}
}
return false;
}
}
@@ -1,23 +0,0 @@
{% if hasInputs %}
### Properties
| Name | Type | Default value | Description |
| -- | -- | -- | -- |
{% each properties as prop %}
{% if prop.isInput %}
| {{{prop.name}}} | `{{{prop.type}}}` | {{{prop.defaultValue}}} | {{{prop.docText}}} |
{% endif %}
{% endeach %}
{% endif %}
{% if hasOutputs%}
### Events
| Name | Type | Description |
| -- | -- | -- |
{% each properties as prop %}
{% if prop.isOutput %}
| {{prop.name}} | `{{{prop.type}}}` | {{{prop.docText}}} |
{% endif %}
{% endeach %}
{% endif %}
@@ -1,23 +0,0 @@
{% if hasInputs %}
### Properties
| Name | Type | Default value | Description |
| -- | -- | -- | -- |
{% each properties as prop %}
{% if prop.isInput %}
| {{{prop.name}}} | `{{{prop.type}}}` | {{{prop.defaultValue}}} | {{{prop.docText}}} |
{% endif %}
{% endeach %}
{% endif %}
{% if hasOutputs%}
### Events
| Name | Type | Description |
| -- | -- | -- |
{% each properties as prop %}
{% if prop.isOutput %}
| {{prop.name}} | `{{{prop.type}}}` | {{{prop.docText}}} |
{% endif %}
{% endeach %}
{% endif %}
@@ -1,14 +0,0 @@
{% if hasMethods %}
### Methods
{% each methods as meth %}- `{{meth.name}}{{{meth.signature}}{% if meth.returnsSomething %}: {{{meth.returnType}}}{% endif %}`<br/>
{{meth.docText}}
{% each meth.params as param %}
- `{{{param.combined}}}` - {% if param.isOptional %}(Optional){% endif %}{{{param.docText}}}
{% endeach %}
{% if meth.returnsSomething %}
- **Returns** `{{{meth.returnType}}}` - {{{meth.returnDocText}}}
{% endif %}
{% endeach %}
{% endif %}
@@ -1,5 +0,0 @@
| Name | Level | Abstract |
| -- | -- | -- |
{% each tuts as tut %}
| [**{{tut.title}}**]({{tut.link}}) | {{tut.level}} | {{tut.briefDesc}} |
{% endeach %}
-401
View File
@@ -1,401 +0,0 @@
var fs = require("fs");
var path = require("path");
var remark = require("remark");
var stringify = require("remark-stringify");
var zone = require("mdast-zone");
var yaml = require("js-yaml");
var unist = require("../unistHelpers");
var ngHelpers = require("../ngHelpers");
var searchLibraryRecursive = require("../libsearch");
module.exports = {
"initPhase": initPhase,
"readPhase": readPhase,
"aggPhase": aggPhase,
"updatePhase": updatePhase
}
var angFilenameRegex = /([a-zA-Z0-9\-]+)\.((component)|(directive)|(model)|(pipe)|(service)|(widget))\.ts/;
var searchFolderOmitRegex = /(config)|(mock)|(i18n)|(assets)|(styles)/;
var docsFolderPath = path.resolve("..", "docs");
var undocStoplistFileName = path.resolve(docsFolderPath, "undocStoplist.json");
var rootFolder = ".";
var indexMdFilePath = path.resolve(docsFolderPath, "README.md");
var guideFolderName = "user-guide";
var guideSummaryFileName = path.resolve(docsFolderPath, guideFolderName, "summary.json");
var maxBriefDescLength = 180;
var adfLibNames = ["core", "content-services", "insights", "process-services"];
var deprecatedIconURL = "docassets/images/DeprecatedIcon.png";
var experimentalIconURL = "docassets/images/ExperimentalIcon.png";
function initPhase(aggData) {
aggData.stoplist = makeStoplist(undocStoplistFileName);
aggData.srcData = {};
aggData.mdFileDesc = [];
aggData.mdFileStatus = [];
aggData.mdFilePath = [];
searchLibraryRecursive(aggData.srcData, path.resolve(rootFolder));
//console.log(JSON.stringify(aggData.srcData));
}
function readPhase(tree, pathname, aggData) {
var itemName = path.basename(pathname, ".md");
// Look for the first paragraph in the file by skipping other items.
// Should usually be at position 1 in the tree.
var s;
var briefDesc;
if (tree.children[0].type == "yaml")
s = 1;
else
s = 0;
for (;
(s < tree.children.length) && !unist.isParagraph(tree.children[s]);
s++
);
if (s < tree.children.length) {
briefDesc = tree.children[s];
}
aggData.mdFileDesc[itemName] = briefDesc;
if (tree.children[0].type == "yaml") {
var metadata = yaml.load(tree.children[0].value);
var status = metadata["Status"];
if (status) {
var compName = path.basename(pathname, ".md");
aggData.mdFileStatus[compName] = status;
}
}
var linkPath = pathname.replace(/\\/g, '/');
linkPath = linkPath.substr(linkPath.indexOf("docs") + 5);
aggData.mdFilePath[itemName] = linkPath;
}
function aggPhase(aggData) {
var sections = prepareIndexSections(aggData);
//console.log(JSON.stringify(sections));
var indexFileText = fs.readFileSync(indexMdFilePath, "utf8");
var indexFileTree = remark().parse(indexFileText);
for (var l = 0; l < adfLibNames.length; l++) {
var libName = adfLibNames[l];
var libSection = sections[libName];
var md = makeLibSectionMD(libSection, false);
zone(indexFileTree, libName, (startComment, oldSection, endComment) => {
md.unshift(startComment);
md.push(endComment);
return md;
});
md = makeLibSectionMD(libSection, true);
var subIndexFilePath = path.resolve(docsFolderPath, libName, "README.md");
var subIndexText = fs.readFileSync(subIndexFilePath, "utf8");
var subIndexTree = remark().parse(subIndexText);
zone(subIndexTree, libName, (startComment, oldSection, endComment) => {
md.unshift(startComment);
md.push(endComment);
return md;
});
subIndexText = remark().data("settings", {paddedTable: false}).stringify(subIndexTree);
fs.writeFileSync(subIndexFilePath, subIndexText);
}
var guideSection = buildGuideSection(guideSummaryFileName, false);
zone(indexFileTree, "guide", (startComment, oldSection, endComment) => {
return [
startComment, guideSection, endComment
]
});
fs.writeFileSync(path.resolve("..", "docs", "README.md"), remark().data("settings", {paddedTable: false}).stringify(indexFileTree));
guideSection = buildGuideSection(guideSummaryFileName, true);
subIndexFilePath = path.resolve(docsFolderPath, "user-guide", "README.md");
subIndexText = fs.readFileSync(subIndexFilePath, "utf8");
subIndexTree = remark().parse(subIndexText);
zone(subIndexTree, "guide", (startComment, oldSection, endComment) => {
return [
startComment, guideSection, endComment
]
});
subIndexText = remark().data("settings", {paddedTable: false}).stringify(subIndexTree);
fs.writeFileSync(subIndexFilePath, subIndexText);
//fs.writeFileSync(indexMdFilePath, remark().stringify(indexFileTree));
}
function updatePhase(tree, pathname, aggData) {
return false;
}
// Create a stoplist of regular expressions.
function makeStoplist(slFilePath) {
var listExpressions = JSON.parse(fs.readFileSync(slFilePath, 'utf8'));
var result = [];
for (var i = 0; i < listExpressions.length; i++) {
result.push(new RegExp(listExpressions[i]));
}
return result;
}
// Check if an item is covered by the stoplist and reject it if so.
function rejectItemViaStoplist(stoplist, itemName) {
for (var i = 0; i < stoplist.length; i++) {
if (stoplist[i].test(itemName)) {
return true;
}
}
return false;
}
function prepareIndexSections(aggData) {
var srcNames = Object.keys(aggData.srcData);
var sections = initEmptySections();
for (var i = 0; i < srcNames.length; i++) {
var itemName = srcNames[i];
var srcData = aggData.srcData[itemName];
var libName = srcData.path.substr(0, srcData.path.indexOf("/"));
var briefDesc = aggData.mdFileDesc[itemName];
var displayName = ngHelpers.ngNameToDisplayName(itemName);
var pathname = aggData.mdFilePath[itemName];
var status = "";
if (aggData.mdFileStatus[itemName])
status = aggData.mdFileStatus[itemName];
if (briefDesc) {
sections[libName][srcData.type].documented.push({
"displayName": displayName,
"mdName": itemName + ".md",
"mdPath": pathname,
"srcPath": srcData.path,
"briefDesc": briefDesc,
"status": status
});
} else if (!rejectItemViaStoplist(aggData.stoplist, itemName)) {
sections[libName][srcData.type].undocumented.push({
"displayName": displayName,
"mdName": itemName + ".md",
"srcPath": srcData.path
});
}
}
return sections;
}
function initEmptySections() {
var result = {};
for (var l = 0; l < adfLibNames.length; l++) {
var lib = result[adfLibNames[l]] = {};
for (var c = 0; c < ngHelpers.classTypes.length; c++) {
var classType = lib[ngHelpers.classTypes[c]] = {};
classType.undocumented = [];
classType.documented = [];
}
}
return result;
}
function buildMDDocumentedTable(docItems, forSubFolder) {
var rows = [
];
for (var i = 0; i < docItems.length; i++) {
rows.push(makeMDDocumentedTableRow(docItems[i], forSubFolder));
}
return rows;
//return unist.makeTable([null, null, null, null], rows);
}
function buildMDUndocumentedTable(docItems, forSubFolder) {
var rows = [
];
for (var i = 0; i < docItems.length; i++) {
rows.push(makeMDUndocumentedTableRow(docItems[i], forSubFolder));
}
return rows;
//return unist.makeTable([null, null, null, null], rows);
}
function makeMDDocumentedTableRow(docItem, forSubFolder) {
var mdPath = docItem.mdPath;
if (forSubFolder) {
mdPath = path.basename(mdPath);
}
var mdFileLink = unist.makeLink(unist.makeText(docItem.displayName), mdPath);
var srcPath = "../lib/" + docItem.srcPath;
if (forSubFolder) {
srcPath = "../" + srcPath;
}
var srcFileLink = unist.makeLink(unist.makeText("Source"), srcPath);
var desc = docItem.briefDesc;
var linkCellItems = [mdFileLink];
var finalDepIconURL = deprecatedIconURL;
var finalExIconURL = experimentalIconURL;
if (forSubFolder) {
finalDepIconURL = "../" + finalDepIconURL;
finalExIconURL = "../" + finalExIconURL;
}
if (docItem.status) {
if (docItem.status === "Deprecated") {
linkCellItems.push(unist.makeText(" "));
linkCellItems.push(unist.makeImage(finalDepIconURL, "Deprecated"));
} else if (docItem.status === "Experimental") {
linkCellItems.push(unist.makeText(" "));
linkCellItems.push(unist.makeImage(finalExIconURL, "Experimental"));
}
}
return unist.makeTableRow([
unist.makeTableCell(linkCellItems),
unist.makeTableCell([desc]),
unist.makeTableCell([srcFileLink])
]);
}
function makeMDUndocumentedTableRow(docItem, forSubFolder) {
var itemName = unist.makeText(docItem.displayName);
var srcPath = "../lib/" + docItem.srcPath;
if (forSubFolder) {
srcPath = "../" + srcPath;
}
var srcFileLink = unist.makeLink(unist.makeText("Source"), srcPath);
return unist.makeTableRow([
unist.makeTableCell([unist.makeEmphasis([itemName])]),
unist.makeTableCell([unist.makeEmphasis([unist.makeText("Not currently documented")])]),
unist.makeTableCell([srcFileLink])
]);
}
function makeLibSectionMD(libSection, forSubFolder){
var md = [];
var libClassTypes = Object.keys(libSection);
for (var i = 0; i < libClassTypes.length; i++) {
var classType = libClassTypes[i];
var classSection = libSection[classType];
if (!classSection)
continue;
var displayNameNode;
if ((classSection.documented.length > 0) || (classSection.undocumented.length > 0)) {
displayNameNode = unist.makeText(ngHelpers.dekebabifyName(classType + "s"));
md.push(unist.makeHeading(displayNameNode, 2));
var tableRows = [
unist.makeTableRow([
unist.makeTableCell([unist.makeText("Name")]),
unist.makeTableCell([unist.makeText("Description")]),
unist.makeTableCell([unist.makeText("Source link")])
])
];
if (classSection.documented.length > 0) {
//md.push(buildMDDocumentedSection(classSection.documented));
tableRows = tableRows.concat(buildMDDocumentedTable(classSection.documented, forSubFolder));
}
if (classSection.undocumented.length > 0) {
// md.push(buildMDUndocumentedSection(classSection.undocumented));
tableRows = tableRows.concat(buildMDUndocumentedTable(classSection.undocumented, forSubFolder));
}
md.push(unist.makeTable([null, null, null, null], tableRows));
}
}
return md;
}
function buildGuideSection(guideJsonFilename, forSubFolder) {
var summary = JSON.parse(fs.readFileSync(guideJsonFilename, "utf8"));
var listItems = [];
for (var i = 0; i < summary.length; i++) {
var filePath = summary[i].file;
if (!forSubFolder) {
filePath = guideFolderName + "/" + filePath;
}
if (summary[i].title !== "Tutorials") {
var link = unist.makeLink(unist.makeText(summary[i].title), filePath);
listItems.push(unist.makeListItem(link));
}
}
return unist.makeListUnordered(listItems);
}
-175
View File
@@ -1,175 +0,0 @@
var path = require("path");
var getSection = require("mdast-util-heading-range");
var unist = require("../unistHelpers");
var seeAlsoHeading = "See Also";
module.exports = {
"initPhase": initPhase,
"readPhase": readPhase,
"aggPhase": aggPhase,
"updatePhase": updatePhase
}
function initPhase(aggData) {
aggData.saGraph = {};
aggData.saUpdateGraph = {};
}
function readPhase(tree, pathname, aggData) {
var saHeadingOffset = findSeeAlsoSection(tree);
var saNode = [];
if (saHeadingOffset !== -1) {
// Skip over non-list parts.
var s;
for (
s = saHeadingOffset;
(s < tree.children.length) && !unist.isListUnordered(tree.children[s]);
s++
);
if ((s < tree.children.length) && unist.isListUnordered(tree.children[s])) {
var list = tree.children[s];
for (var i = 0; i < list.children.length; i++) {
var itemLink = getItemLinkInfo(list.children[i]);
if (itemLink) {
saNode.push(itemLink);
}
}
}
}
aggData.saGraph[path.basename(pathname, ".md")] = saNode;
}
function aggPhase(aggData) {
aggData.saUpdateGraph = tidyGraph(aggData.saGraph);
}
function updatePhase(tree, pathname, aggData) {
var currNodeName = path.basename(pathname, ".md");
var currNodeArcs = aggData.saUpdateGraph[currNodeName];
if (currNodeArcs.length > 0) {
var saListItems = [];
for (var i = 0; i < currNodeArcs.length; i++) {
var linkText = graphKeyToLinkName(currNodeArcs[i]);
var linkTarget = currNodeArcs[i] + ".md";
var link = unist.makeLink(unist.makeText(linkText), linkTarget);
saListItems.push(unist.makeListItem(link));
}
var saHeadingOffset = findSeeAlsoSection(tree);
if (saHeadingOffset !== -1) {
// Skip over non-list parts.
var s;
for (
s = saHeadingOffset;
(s < tree.children.length) && !unist.isListUnordered(tree.children[s]);
s++
);
// Push all elements of the items array as if they were separate elements.
Array.prototype.push.apply(tree.children[s].children, saListItems);
} else {
// This file doesn't currently have a See Also section, so add one at the end.
tree.children.push(unist.makeHeading(unist.makeText(seeAlsoHeading), 2));
tree.children.push(unist.makeListUnordered(saListItems));
}
}
return true;
}
function graphKeyToLinkName(key) {
var mainSections = key.split(".");
mainSections[0] = tidyName(mainSections[0]);
return mainSections.join(" ");
}
// Convert an Angular-style name (eg, "card-view") into one with correct spaces and uppercase (eg, "Card view").
function tidyName(name) {
var result = name.replace(/-/g, " ");
result = result.substr(0, 1).toUpperCase() + result.substr(1);
return result;
}
function makeEmptySAList() {
var result = [];
}
// Makes link symmetrical between items (ie, if A links to B but not the other way
// around then it adds the missing link).
function tidyGraph(graph) {
var nodeNames = Object.keys(graph);
var result = {};
for (var n = 0; n < nodeNames.length; n++) {
result[nodeNames[n]] = [];
}
for (var n = 0; n < nodeNames.length; n++) {
var currNodeName = nodeNames[n];
var currNodeArcs = graph[currNodeName];
for (var a = 0; a < currNodeArcs.length; a++) {
var linkedNode = graph[currNodeArcs[a]];
var resultNode = result[currNodeArcs[a]];
if (!linkedNode) {
console.log(`Warning: item '${currNodeArcs[a]}' (in See Also section of '${currNodeName}') has no corresponding file`);
} else if (linkedNode.indexOf(currNodeName) === -1) {
linkedNode.push(currNodeName);
resultNode.push(currNodeName);
}
}
}
return result;
}
function findSeeAlsoSection(tree) {
var i;
for (i = 0; i < tree.children.length; i++) {
var child = tree.children[i];
if (unist.isHeading(child) && (child.children[0].value.toLowerCase() === seeAlsoHeading.toLowerCase()))
return i;
}
return -1;
}
function getItemLinkInfo(listItem) {
var linkTarget = listItem.children[0].children[0].url;
if (linkTarget.startsWith("http:") ||
linkTarget.startsWith("#") ||
!linkTarget.endsWith(".md"))
return null;
else
return path.basename(linkTarget, ".md");
}
-93
View File
@@ -1,93 +0,0 @@
var remark = require("remark");
var tocGenerator = require("mdast-util-toc");
var replaceSection = require("mdast-util-heading-range");
var unist = require("../unistHelpers");
const contentsHeading = "Contents";
const minHeadingsForToc = 8;
const maxTocHeadingDepth = 3;
module.exports = {
"initPhase": initPhase,
"readPhase": readPhase,
"aggPhase": aggPhase,
"updatePhase": updatePhase
}
function initPhase(aggData) {}
function readPhase(tree, pathname, aggData) {}
function aggPhase(aggData) {}
// Find an existing Contents section or add a new empty one if needed.
// Returns true if section is present/needed, false if not needed.
function establishContentsSection(mdTree) {
var firstL2HeadingPos = -1;
var numTocHeadings = 0;
var foundContentsHeading = false;
for (var i = 0; i < mdTree.children.length; i++) {
var child = mdTree.children[i];
// Look through all headings.
if (child.type === "heading") {
if ((child.depth > 1) && (child.depth <= maxTocHeadingDepth)) {
numTocHeadings++;
}
if (child.depth === 2) {
// Note where the first L2 heading is.
if (firstL2HeadingPos === -1) {
firstL2HeadingPos = i;
}
// If it is also a Contents heading then we're done. We don't include the
// Contents heading itself within the ToC, so decrement the count for that.
if ((child.children[0].value === contentsHeading) && !foundContentsHeading) {
foundContentsHeading = true;
numTocHeadings--;
}
}
}
}
// If we get here then a level 2 Contents heading was not found.
// If there are enough headings for a ToC to be necessary then
// add one in the right place.
if (!foundContentsHeading) {
var newContsHeading = unist.makeHeading(unist.makeText(contentsHeading), 2);
// If we found another L2 heading then add the Contents in just before it.
if (firstL2HeadingPos != -1) {
mdTree.children.splice(firstL2HeadingPos, 0, newContsHeading);
} else {
// Otherwise, the unlikely situation where a ToC is required but there
// are no L2 headings! Add it as the second element in the document.
mdTree.children.splice(1, 0, newContsHeading);
}
}
return numTocHeadings;
}
function updatePhase(tree, pathname, aggData) {
// If we need a contents section then add one or update the existing one.
var numTocHeadings = establishContentsSection(tree);
if (numTocHeadings >= minHeadingsForToc) {
var newToc = tocGenerator(tree, {heading: contentsHeading, maxDepth: 3});
replaceSection(tree, contentsHeading, function(before, oldSection, after) {
return [before, newToc.map, after];
});
} else {
// Otherwise, we don't need one, so remove any existing one.
replaceSection(tree, contentsHeading, function(before, oldSection, after) {
return [after];
});
}
return true;
}
-225
View File
@@ -1,225 +0,0 @@
"use strict";
exports.__esModule = true;
var fs = require("fs");
var path = require("path");
var replaceSection = require("mdast-util-heading-range");
var remark = require("remark");
var combyne = require("combyne");
var typedoc_1 = require("typedoc");
var libFolders = ["core", "content-services", "process-services", "insights"];
var templateFolder = path.resolve(".", "config", "DocProcessor", "templates");
var excludePatterns = [
"**/*.spec.ts"
];
var nameExceptions = {
"datatable.component": "DataTableComponent",
"tasklist.service": "TaskListService",
"text-mask.component": "InputMaskDirective"
};
var PropInfo = /** @class */ (function () {
function PropInfo(rawProp) {
var _this = this;
this.name = rawProp.name;
this.docText = rawProp.comment ? rawProp.comment.shortText : "";
this.docText = this.docText.replace(/[\n\r]+/g, " ").trim();
this.defaultValue = rawProp.defaultValue || "";
this.defaultValue = this.defaultValue.replace(/\|/, "\\|");
this.type = rawProp.type ? rawProp.type.toString() : "";
this.isDeprecated = rawProp.comment && rawProp.comment.hasTag("deprecated");
if (this.isDeprecated) {
this.docText = "**Deprecated:** " + rawProp.comment.getTag("deprecated").text.replace(/[\n\r]+/g, " ").trim();
}
if (rawProp.decorators) {
rawProp.decorators.forEach(function (dec) {
//console.log(dec);
if (dec.name === "Input") {
_this.isInput = true;
if (dec.arguments) {
var bindingName = dec.arguments["bindingPropertyName"];
//console.log(JSON.stringify(dec.arguments));
if (bindingName && (bindingName !== ""))
_this.name = bindingName.replace(/['"]/g, "");
}
if (!_this.docText && !_this.isDeprecated)
console.log("Warning: Input \"" + rawProp.getFullName() + "\" has no doc text.");
}
if (dec.name === "Output") {
_this.isOutput = true;
if (!_this.docText && !_this.isDeprecated)
console.log("Warning: Output \"" + rawProp.getFullName() + "\" has no doc text.");
}
});
}
}
return PropInfo;
}());
;
var ParamInfo = /** @class */ (function () {
function ParamInfo(rawParam) {
this.name = rawParam.name;
this.type = rawParam.type.toString();
this.defaultValue = rawParam.defaultValue;
this.docText = rawParam.comment ? rawParam.comment.text : "";
this.docText = this.docText.replace(/[\n\r]+/g, " ").trim();
this.isOptional = rawParam.flags.isOptional;
this.combined = this.name;
if (this.isOptional)
this.combined += "?";
this.combined += ": " + this.type;
if (this.defaultValue !== "")
this.combined += " = " + this.defaultValue;
}
return ParamInfo;
}());
var MethodSigInfo = /** @class */ (function () {
function MethodSigInfo(rawSig) {
var _this = this;
this.name = rawSig.name;
this.returnType = rawSig.type ? rawSig.type.toString() : "";
this.returnsSomething = this.returnType != "void";
if (rawSig.hasComment()) {
this.docText = rawSig.comment.shortText + rawSig.comment.text;
this.docText = this.docText.replace(/[\n\r]+/g, " ").trim();
if (!this.docText)
console.log("Warning: method \"" + rawSig.name + "\" has no doc text.");
this.returnDocText = rawSig.comment.returns;
this.returnDocText = this.returnDocText ? this.returnDocText.replace(/[\n\r]+/g, " ").trim() : "";
if (this.returnDocText.toLowerCase() === "nothing")
this.returnsSomething = false;
if (this.returnsSomething && !this.returnDocText)
console.log("Warning: Return value of method \"" + rawSig.name + "\" has no doc text.");
this.isDeprecated = rawSig.comment.hasTag("deprecated");
}
this.params = [];
var paramStrings = [];
if (rawSig.parameters) {
rawSig.parameters.forEach(function (rawParam) {
if (!rawParam.comment || !rawParam.comment.text)
console.log("Warning: parameter \"" + rawParam.name + "\" of method \"" + rawSig.name + "\" has no doc text.");
var param = new ParamInfo(rawParam);
_this.params.push(param);
paramStrings.push(param.combined);
});
}
this.signature = "(" + paramStrings.join(", ") + ")";
}
return MethodSigInfo;
}());
var ComponentInfo = /** @class */ (function () {
function ComponentInfo(classRef) {
var _this = this;
var props = classRef.getChildrenByKind(typedoc_1.ReflectionKind.Property);
var accessors = classRef.getChildrenByKind(typedoc_1.ReflectionKind.Accessor);
this.properties = props.concat(accessors).map(function (item) {
return new PropInfo(item);
});
var methods = classRef.getChildrenByKind(typedoc_1.ReflectionKind.Method);
this.methods = [];
methods.forEach(function (method) {
if (!(method.flags.isPrivate || method.flags.isProtected)) {
method.signatures.forEach(function (sig) {
_this.methods.push(new MethodSigInfo(sig));
});
}
});
this.properties.forEach(function (prop) {
if (prop.isInput)
_this.hasInputs = true;
if (prop.isOutput)
_this.hasOutputs = true;
});
this.hasMethods = methods.length > 0;
}
return ComponentInfo;
}());
function initPhase(aggData) {
var app = new typedoc_1.Application({
exclude: excludePatterns,
ignoreCompilerErrors: true,
experimentalDecorators: true,
tsconfig: "tsconfig.json"
});
var sources = app.expandInputFiles(libFolders);
aggData.projData = app.convert(sources);
/*
aggData.liq = liquid({
root: templateFolder
});
*/
}
exports.initPhase = initPhase;
function readPhase(tree, pathname, aggData) {
}
exports.readPhase = readPhase;
function aggPhase(aggData) {
}
exports.aggPhase = aggPhase;
function updatePhase(tree, pathname, aggData) {
var compName = angNameToClassName(path.basename(pathname, ".md"));
var classRef = aggData.projData.findReflectionByName(compName);
var compData = new ComponentInfo(classRef);
var classType = compName.match(/component|directive|service/i);
if (classType) {
var templateName = path.resolve(templateFolder, classType + ".combyne");
var templateSource = fs.readFileSync(templateName, "utf8");
var template = combyne(templateSource);
var mdText = template.render(compData);
mdText = mdText.replace(/^ +\|/mg, "|");
var newSection_1 = remark().data("settings", { paddedTable: false, gfm: false }).parse(mdText.trim()).children;
replaceSection(tree, "Class members", function (before, section, after) {
newSection_1.unshift(before);
newSection_1.push(after);
return newSection_1;
});
/*
let templateName = classType[0] + ".liquid";
aggData.liq
.renderFile(templateName, compData)
.then(mdText => {
let newSection = remark().parse(mdText).children;
replaceSection(tree, "Class members", (before, section, after) => {
newSection.unshift(before);
newSection.push(after);
return newSection;
});
fs.writeFileSync(pathname, remark().use(frontMatter, {type: 'yaml', fence: '---'}).data("settings", {paddedTable: false}).stringify(tree));
});
*/
}
return true;
}
exports.updatePhase = updatePhase;
/*
function renderInputs(comp: ComponentInfo): string {
var result = "";
comp.properties.forEach(prop => {
result += `| ${prop.name} | \`${prop.type}\` | ${prop.defaultValue} | ${prop.docText} |\n`;
});
return result;
}
*/
function initialCap(str) {
return str[0].toUpperCase() + str.substr(1);
}
function angNameToClassName(rawName) {
if (nameExceptions[rawName])
return nameExceptions[rawName];
var name = rawName.replace(/\]|\(|\)/g, '');
var fileNameSections = name.split('.');
var compNameSections = fileNameSections[0].split('-');
var outCompName = '';
for (var i = 0; i < compNameSections.length; i++) {
outCompName = outCompName + initialCap(compNameSections[i]);
}
var itemTypeIndicator = '';
if (fileNameSections.length > 1) {
itemTypeIndicator = initialCap(fileNameSections[1]);
}
var finalName = outCompName + itemTypeIndicator;
return finalName;
}
-326
View File
@@ -1,326 +0,0 @@
import * as fs from "fs";
import * as path from "path";
import * as replaceSection from "mdast-util-heading-range";
import * as remark from "remark";
import * as stringify from "remark-stringify";
import * as frontMatter from "remark-frontmatter";
import * as combyne from "combyne";
import {
Application,
ProjectReflection,
Reflection,
DeclarationReflection,
SignatureReflection,
ParameterReflection,
ReflectionKind,
TraverseProperty,
Decorator
} from "typedoc";
import { CommentTag } from "typedoc/dist/lib/models";
let libFolders = ["core", "content-services", "process-services", "insights"];
let templateFolder = path.resolve(".", "config", "DocProcessor", "templates");
let excludePatterns = [
"**/*.spec.ts"
];
let nameExceptions = {
"datatable.component": "DataTableComponent",
"tasklist.service": "TaskListService",
"text-mask.component": "InputMaskDirective"
}
class PropInfo {
name: string;
type: string;
typeLink: string;
defaultValue: string;
docText: string;
isInput: boolean;
isOutput: boolean;
isDeprecated: boolean;
constructor(rawProp: DeclarationReflection) {
this.name = rawProp.name;
this.docText = rawProp.comment ? rawProp.comment.shortText : "";
this.docText = this.docText.replace(/[\n\r]+/g, " ").trim();
this.defaultValue = rawProp.defaultValue || "";
this.defaultValue = this.defaultValue.replace(/\|/, "\\|");
this.type = rawProp.type ? rawProp.type.toString() : "";
this.isDeprecated = rawProp.comment && rawProp.comment.hasTag("deprecated");
if (this.isDeprecated) {
this.docText = "**Deprecated:** " + rawProp.comment.getTag("deprecated").text.replace(/[\n\r]+/g, " ").trim();
}
if (rawProp.decorators) {
rawProp.decorators.forEach(dec => {
//console.log(dec);
if (dec.name === "Input") {
this.isInput = true;
if (dec.arguments) {
let bindingName = dec.arguments["bindingPropertyName"];
//console.log(JSON.stringify(dec.arguments));
if (bindingName && (bindingName !== ""))
this.name = bindingName.replace(/['"]/g, "");
}
if (!this.docText && !this.isDeprecated)
console.log(`Warning: Input "${rawProp.getFullName()}" has no doc text.`);
}
if (dec.name === "Output") {
this.isOutput = true;
if (!this.docText && !this.isDeprecated)
console.log(`Warning: Output "${rawProp.getFullName()}" has no doc text.`);
}
});
}
}
};
class ParamInfo {
name: string;
type: string;
defaultValue: string;
docText: string;
combined: string;
isOptional: boolean;
constructor(rawParam: ParameterReflection) {
this.name = rawParam.name;
this.type = rawParam.type.toString();
this.defaultValue = rawParam.defaultValue;
this.docText = rawParam.comment ? rawParam.comment.text : "";
this.docText = this.docText.replace(/[\n\r]+/g, " ").trim();
this.isOptional = rawParam.flags.isOptional;
this.combined = this.name;
if (this.isOptional)
this.combined += "?";
this.combined += `: ${this.type}`;
if (this.defaultValue !== "")
this.combined += ` = ${this.defaultValue}`;
}
}
class MethodSigInfo {
name: string;
docText: string;
returnType: string;
returnDocText: string;
returnsSomething: boolean;
signature: string;
params: ParamInfo[];
isDeprecated: boolean;
constructor(rawSig: SignatureReflection) {
this.name = rawSig.name;
this.returnType = rawSig.type ? rawSig.type.toString() : "";
this.returnsSomething = this.returnType != "void";
if (rawSig.hasComment()) {
this.docText = rawSig.comment.shortText + rawSig.comment.text;
this.docText = this.docText.replace(/[\n\r]+/g, " ").trim();
if (!this.docText)
console.log(`Warning: method "${rawSig.name}" has no doc text.`);
this.returnDocText = rawSig.comment.returns;
this.returnDocText = this.returnDocText ? this.returnDocText.replace(/[\n\r]+/g, " ").trim() : "";
if (this.returnDocText.toLowerCase() === "nothing")
this.returnsSomething = false;
if (this.returnsSomething && !this.returnDocText)
console.log(`Warning: Return value of method "${rawSig.name}" has no doc text.`);
this.isDeprecated = rawSig.comment.hasTag("deprecated");
}
this.params = [];
let paramStrings = [];
if (rawSig.parameters) {
rawSig.parameters.forEach(rawParam => {
if (!rawParam.comment || !rawParam.comment.text)
console.log(`Warning: parameter "${rawParam.name}" of method "${rawSig.name}" has no doc text.`);
let param = new ParamInfo(rawParam);
this.params.push(param);
paramStrings.push(param.combined);
});
}
this.signature = "(" + paramStrings.join(", ") + ")";
}
}
class ComponentInfo {
properties: PropInfo[];
methods: MethodSigInfo[];
hasInputs: boolean;
hasOutputs: boolean;
hasMethods: boolean;
constructor(classRef: DeclarationReflection) {
let props = classRef.getChildrenByKind(ReflectionKind.Property);
let accessors = classRef.getChildrenByKind(ReflectionKind.Accessor);
this.properties = [...props, ...accessors].map(item => {
return new PropInfo(item);
});
let methods = classRef.getChildrenByKind(ReflectionKind.Method);
this.methods = [];
methods.forEach(method =>{
if (!(method.flags.isPrivate || method.flags.isProtected)) {
method.signatures.forEach(sig => {
this.methods.push(new MethodSigInfo(sig));
});
}
});
this.properties.forEach(prop => {
if (prop.isInput)
this.hasInputs = true;
if (prop.isOutput)
this.hasOutputs = true;
});
this.hasMethods = methods.length > 0;
}
}
export function initPhase(aggData) {
let app = new Application({
exclude: excludePatterns,
ignoreCompilerErrors: true,
experimentalDecorators: true,
tsconfig: "tsconfig.json"
});
let sources = app.expandInputFiles(libFolders);
aggData.projData = app.convert(sources);
/*
aggData.liq = liquid({
root: templateFolder
});
*/
}
export function readPhase(tree, pathname, aggData) {
}
export function aggPhase(aggData) {
}
export function updatePhase(tree, pathname, aggData) {
let compName = angNameToClassName(path.basename(pathname, ".md"));
let classRef = aggData.projData.findReflectionByName(compName);
let compData = new ComponentInfo(classRef);
let classType = compName.match(/component|directive|service/i);
if (classType) {
let templateName = path.resolve(templateFolder, classType + ".combyne");
let templateSource = fs.readFileSync(templateName, "utf8");
let template = combyne(templateSource);
let mdText = template.render(compData);
mdText = mdText.replace(/^ +\|/mg, "|");
let newSection = remark().data("settings", {paddedTable: false, gfm: false}).parse(mdText.trim()).children;
replaceSection(tree, "Class members", (before, section, after) => {
newSection.unshift(before);
newSection.push(after);
return newSection;
});
/*
let templateName = classType[0] + ".liquid";
aggData.liq
.renderFile(templateName, compData)
.then(mdText => {
let newSection = remark().parse(mdText).children;
replaceSection(tree, "Class members", (before, section, after) => {
newSection.unshift(before);
newSection.push(after);
return newSection;
});
fs.writeFileSync(pathname, remark().use(frontMatter, {type: 'yaml', fence: '---'}).data("settings", {paddedTable: false}).stringify(tree));
});
*/
}
return true;
}
/*
function renderInputs(comp: ComponentInfo): string {
var result = "";
comp.properties.forEach(prop => {
result += `| ${prop.name} | \`${prop.type}\` | ${prop.defaultValue} | ${prop.docText} |\n`;
});
return result;
}
*/
function initialCap(str: string) {
return str[0].toUpperCase() + str.substr(1);
}
function angNameToClassName(rawName: string) {
if (nameExceptions[rawName])
return nameExceptions[rawName];
var name = rawName.replace(/\]|\(|\)/g, '');
var fileNameSections = name.split('.');
var compNameSections = fileNameSections[0].split('-');
var outCompName = '';
for (var i = 0; i < compNameSections.length; i++) {
outCompName = outCompName + initialCap(compNameSections[i]);
}
var itemTypeIndicator = '';
if (fileNameSections.length > 1) {
itemTypeIndicator = initialCap(fileNameSections[1]);
}
var finalName = outCompName + itemTypeIndicator;
return finalName;
}
-357
View File
@@ -1,357 +0,0 @@
"use strict";
exports.__esModule = true;
var ts = require("typescript");
var path = require("path");
var heading = require("mdast-util-heading-range");
var remark = require("remark");
var unist = require("../unistHelpers");
var typescript_1 = require("typescript");
// Max number of characters in the text for the default value column.
var maxDefaultTextLength = 20;
var nameExceptions = {
"datatable.component": "DataTableComponent",
"tasklist.service": "TaskListService"
};
function initPhase(aggData) {
}
exports.initPhase = initPhase;
function readPhase(tree, pathname, aggData) {
}
exports.readPhase = readPhase;
function aggPhase(aggData) {
}
exports.aggPhase = aggPhase;
var PropData = /** @class */ (function () {
function PropData() {
}
return PropData;
}());
var ParamData = /** @class */ (function () {
function ParamData() {
}
ParamData.prototype.getSignature = function () {
var sig = this.name;
if (this.optional)
sig += "?";
if (this.type)
sig += ": " + this.type;
if (this.initializer)
sig += " = " + this.initializer;
return sig;
};
return ParamData;
}());
var MethodData = /** @class */ (function () {
function MethodData() {
this.params = [];
}
MethodData.prototype.getSignature = function () {
var sig = this.name + "(";
if (this.params.length > 0) {
sig += this.params[0].getSignature();
}
for (var i = 1; i < this.params.length; i++) {
sig += ", " + this.params[i].getSignature();
}
sig += ")";
if (this.returnType !== "void") {
sig += ": " + this.returnType;
}
return sig;
};
return MethodData;
}());
var ComponentDocAutoContent = /** @class */ (function () {
function ComponentDocAutoContent() {
this.inputs = [];
this.outputs = [];
}
ComponentDocAutoContent.prototype.extractClassInfoFromSource = function (checker, classDec) {
var sourceFile = classDec.getSourceFile();
for (var i = 0; i < classDec.members.length; i++) {
var member = classDec.members[i];
if (ts.isPropertyDeclaration(member) ||
ts.isGetAccessorDeclaration(member) ||
ts.isSetAccessorDeclaration(member)) {
var prop = member;
var mods = ts.getCombinedModifierFlags(prop);
var nonPrivate = (mods & ts.ModifierFlags.Private) === 0;
var memSymbol = checker.getSymbolAtLocation(prop.name);
if (nonPrivate && memSymbol && prop.decorators) {
var name_1 = memSymbol.getName();
var initializer = "";
if (prop.initializer) {
initializer = prop.initializer.getText(sourceFile);
}
var doc = ts.displayPartsToString(memSymbol.getDocumentationComment(checker));
doc = doc.replace(/\r\n/g, " ");
var propType = checker.typeToString(checker.getTypeOfSymbolAtLocation(memSymbol, memSymbol.valueDeclaration));
var dec = prop.decorators[0].getText(sourceFile);
if (dec.match(/@Input/)) {
this.inputs.push({
"name": name_1,
"type": propType,
"initializer": initializer,
"docText": doc
});
}
else if (dec.match(/@Output/)) {
this.outputs.push({
"name": name_1,
"type": propType,
"initializer": "",
"docText": doc
});
}
}
}
}
};
ComponentDocAutoContent.prototype.addContentToDoc = function (tree) {
var inTable = buildPropsTable(this.inputs);
var outTable = buildPropsTable(this.outputs, false);
if (inTable) {
heading(tree, "Properties", function (before, section, after) {
return [before, inTable, after];
});
}
if (outTable) {
heading(tree, "Events", function (before, section, after) {
return [before, outTable, after];
});
}
};
return ComponentDocAutoContent;
}());
var ServiceDocAutoContent = /** @class */ (function () {
function ServiceDocAutoContent() {
this.props = [];
this.methods = [];
}
ServiceDocAutoContent.prototype.extractClassInfoFromSource = function (checker, classDec) {
var sourceFile = classDec.getSourceFile();
for (var i = 0; i < classDec.members.length; i++) {
var member = classDec.members[i];
if (ts.isMethodDeclaration(member)) {
var method = member;
var mods = ts.getCombinedModifierFlags(method);
var nonPrivate = (mods & ts.ModifierFlags.Private) === 0;
var memSymbol = checker.getSymbolAtLocation(method.name);
if (nonPrivate && memSymbol) {
var methData = new MethodData();
methData.name = memSymbol.getName();
var doc = ts.displayPartsToString(memSymbol.getDocumentationComment());
if (!doc)
console.log("Warning: Method " + classDec.name.escapedText + "." + methData.name + " is not documented");
methData.docText = doc.replace(/\r\n/g, " ");
var sig = checker.getSignatureFromDeclaration(method);
var returnType = sig.getReturnType();
methData.returnType = checker.typeToString(returnType);
var returnSymbol = returnType.getSymbol();
var params = method.parameters;
for (var p = 0; p < params.length; p++) {
var pData = new ParamData();
pData.name = params[p].name.getText();
if (params[p].type)
pData.type = params[p].type.getText();
else
pData.type = "";
var paramSymbol = checker.getSymbolAtLocation(params[p].name);
pData.docText = ts.displayPartsToString(paramSymbol.getDocumentationComment());
if (!pData.docText)
console.log("Warning: Parameter \"" + pData.name + "\" of " + classDec.name.escapedText + "." + methData.name + " is not documented");
pData.optional = params[p].questionToken ? true : false;
if (params[p].initializer) {
var initText = params[p].initializer.getText();
if (initText !== "undefined")
pData.initializer = initText;
}
methData.params.push(pData);
}
this.methods.push(methData);
}
}
}
};
ServiceDocAutoContent.prototype.addContentToDoc = function (tree) {
var propsTable = buildPropsTable(this.props);
var methodsList = buildMethodsList(this.methods);
if (propsTable) {
heading(tree, "Properties", function (before, section, after) {
return [before, propsTable, after];
});
}
if (methodsList) {
heading(tree, "Methods", function (before, section, after) {
return [before, methodsList, after];
});
}
};
return ServiceDocAutoContent;
}());
function updatePhase(tree, pathname, aggData) {
var fileNameNoSuffix = path.basename(pathname, ".md");
var itemType = fileNameNoSuffix.match(/component|directive|service/);
if (itemType) {
var srcData = aggData.srcData[fileNameNoSuffix];
if (srcData) {
var srcPath = srcData.path;
var className = fixAngularFilename(fileNameNoSuffix);
var classData = void 0;
if ((itemType[0] === "component") || (itemType[0] === "directive")) {
classData = new ComponentDocAutoContent();
}
else if (itemType[0] === "service") {
classData = new ServiceDocAutoContent();
}
getDocSourceData(path.resolve(".", srcPath), className, classData);
classData.addContentToDoc(tree);
}
return true;
}
else {
return false;
}
}
exports.updatePhase = updatePhase;
function initialCap(str) {
return str[0].toUpperCase() + str.substr(1);
}
function fixAngularFilename(rawName) {
if (nameExceptions[rawName])
return nameExceptions[rawName];
var name = rawName.replace(/\]|\(|\)/g, '');
var fileNameSections = name.split('.');
var compNameSections = fileNameSections[0].split('-');
var outCompName = '';
for (var i = 0; i < compNameSections.length; i++) {
outCompName = outCompName + initialCap(compNameSections[i]);
}
var itemTypeIndicator = '';
if (fileNameSections.length > 1) {
itemTypeIndicator = initialCap(fileNameSections[1]);
}
var finalName = outCompName + itemTypeIndicator;
return finalName;
}
function getDocSourceData(srcPath, docClassName, classData) {
var prog = ts.createProgram([srcPath], {
target: ts.ScriptTarget.ES5, module: ts.ModuleKind.CommonJS
});
var sourceFiles = prog.getSourceFiles();
var checker = prog.getTypeChecker();
for (var i = 0; i < sourceFiles.length; i++) {
if (!sourceFiles[i].isDeclarationFile)
ts.forEachChild(sourceFiles[i], visit);
}
function visit(node) {
if (!isNodeExported(node))
return;
if (ts.isClassDeclaration(node) && node.name) {
var classDec = node;
var sourceFile = classDec.getSourceFile();
if (classDec.name.escapedText === docClassName) {
getPropDataFromClassChain(checker, classDec, classData);
}
}
}
}
// Get properties/events from main class and all inherited classes.
function getPropDataFromClassChain(checker, classDec, classData) {
// Main class
classData.extractClassInfoFromSource(checker, classDec);
// Inherited classes
if (classDec.heritageClauses) {
for (var _i = 0, _a = classDec.heritageClauses; _i < _a.length; _i++) {
var hc = _a[_i];
var hcType = checker.getTypeFromTypeNode(hc.types[0]);
for (var _b = 0, _c = hcType.symbol.declarations; _b < _c.length; _b++) {
var dec = _c[_b];
if (typescript_1.isClassDeclaration(dec)) {
getPropDataFromClassChain(checker, dec, classData);
}
}
}
}
}
function buildPropsTable(props, includeInitializer) {
if (includeInitializer === void 0) { includeInitializer = true; }
if (props.length === 0) {
return null;
}
var headerCells = [
unist.makeTableCell([unist.makeText("Name")]),
unist.makeTableCell([unist.makeText("Type")])
];
if (includeInitializer)
headerCells.push(unist.makeTableCell([unist.makeText("Default value")]));
headerCells.push(unist.makeTableCell([unist.makeText("Description")]));
var rows = [
unist.makeTableRow(headerCells)
];
for (var i = 0; i < props.length; i++) {
var pName = props[i].name;
var pType = props[i].type;
var pDesc = props[i].docText || "";
if (pDesc) {
pDesc = pDesc.replace(/[\n\r]+/, " ");
}
var descCellContent = remark().parse(pDesc).children;
var pDefault = props[i].initializer || "";
var defaultCellContent;
if (pDefault) {
if (pDefault.length > maxDefaultTextLength) {
defaultCellContent = unist.makeText("See description");
console.log("Warning: property \"" + pName + "\" default value substituted (> " + maxDefaultTextLength + " chars)");
}
else
defaultCellContent = unist.makeInlineCode(pDefault);
}
else {
defaultCellContent = unist.makeText("");
}
var cells = [
unist.makeTableCell([unist.makeText(pName)]),
unist.makeTableCell([unist.makeInlineCode(pType)])
];
if (includeInitializer)
cells.push(unist.makeTableCell([defaultCellContent]));
cells.push(unist.makeTableCell(descCellContent));
rows.push(unist.makeTableRow(cells));
}
var spacers = [null, null, null];
if (includeInitializer)
spacers.push(null);
return unist.makeTable(spacers, rows);
}
function buildMethodsList(methods) {
if (methods.length === 0)
return null;
var listItems = [];
for (var _i = 0, methods_1 = methods; _i < methods_1.length; _i++) {
var method = methods_1[_i];
var itemLines = [];
itemLines.push(unist.makeInlineCode(method.getSignature()));
itemLines.push(unist.makeBreak());
itemLines.push(unist.makeParagraph(remark().parse(method.docText).children));
itemLines.push(unist.makeBreak());
var paramListItems = [];
for (var _a = 0, _b = method.params; _a < _b.length; _a++) {
var param = _b[_a];
var currParamSections = [];
if (param.docText !== "") {
currParamSections.push(unist.makeInlineCode(param.name));
var optionalPart = param.optional ? "(Optional) " : "";
currParamSections.push(unist.makeText(" - " + optionalPart + param.docText));
//currParamSections.push(unist.makeBreak());
paramListItems.push(unist.makeListItem(unist.makeParagraph(currParamSections)));
}
}
itemLines.push(unist.makeListUnordered(paramListItems));
listItems.push(unist.makeListItem(unist.makeParagraph(itemLines)));
}
return unist.makeListUnordered(listItems);
}
function isNodeExported(node) {
return (ts.getCombinedModifierFlags(node) & ts.ModifierFlags.Export) !== 0 || (!!node.parent && node.parent.kind === ts.SyntaxKind.SourceFile);
}
-498
View File
@@ -1,498 +0,0 @@
import * as ts from "typescript";
import * as fs from "fs";
import * as path from "path";
import * as heading from "mdast-util-heading-range";
import * as remark from "remark";
import * as unist from "../unistHelpers";
import { JsxEmit, isClassDeclaration, PropertyDeclaration } from "typescript";
// Max number of characters in the text for the default value column.
const maxDefaultTextLength = 20;
let nameExceptions = {
"datatable.component": "DataTableComponent",
"tasklist.service": "TaskListService"
}
export function initPhase(aggData) {
}
export function readPhase(tree, pathname, aggData) {
}
export function aggPhase(aggData) {
}
interface NgDocAutoContent {
extractClassInfoFromSource(checker: ts.TypeChecker, classDec: ts.ClassDeclaration);
addContentToDoc(tree);
}
class PropData {
name: string;
type: string;
initializer: string;
docText: string;
}
class ParamData {
name: string;
type: string;
docText: string;
initializer: string;
optional: boolean;
getSignature() {
let sig = this.name;
if (this.optional)
sig += "?";
if (this.type)
sig += ": " + this.type;
if (this.initializer)
sig += " = " + this.initializer;
return sig;
}
}
class MethodData {
name: string;
docText: string;
params: ParamData[];
returnType: string;
constructor() {
this.params = [];
}
getSignature() {
let sig = this.name + "(";
if (this.params.length > 0) {
sig += this.params[0].getSignature();
}
for (let i = 1; i < this.params.length; i++) {
sig += ", " + this.params[i].getSignature();
}
sig += ")";
if (this.returnType !== "void") {
sig += ": " + this.returnType;
}
return sig;
}
}
class ComponentDocAutoContent implements NgDocAutoContent {
inputs: PropData[];
outputs: PropData[];
constructor() {
this.inputs = [];
this.outputs = [];
}
extractClassInfoFromSource(checker: ts.TypeChecker, classDec: ts.ClassDeclaration) {
let sourceFile = classDec.getSourceFile();
for (var i = 0; i < classDec.members.length; i++) {
let member = classDec.members[i];
if (ts.isPropertyDeclaration(member) ||
ts.isGetAccessorDeclaration(member) ||
ts.isSetAccessorDeclaration(member)) {
let prop: ts.PropertyDeclaration = member;
let mods = ts.getCombinedModifierFlags(prop);
let nonPrivate = (mods & ts.ModifierFlags.Private) === 0;
let memSymbol = checker.getSymbolAtLocation(prop.name);
if (nonPrivate && memSymbol && prop.decorators) {
let name = memSymbol.getName();
let initializer = "";
if (prop.initializer) {
initializer = prop.initializer.getText(sourceFile);
}
let doc = ts.displayPartsToString(memSymbol.getDocumentationComment(checker));
doc = doc.replace(/\r\n/g, " ");
let propType = checker.typeToString(checker.getTypeOfSymbolAtLocation(memSymbol, memSymbol.valueDeclaration!));
let dec = prop.decorators[0].getText(sourceFile);
if (dec.match(/@Input/)) {
this.inputs.push({
"name": name,
"type": propType,
"initializer": initializer,
"docText": doc
});
} else if (dec.match(/@Output/)) {
this.outputs.push({
"name": name,
"type": propType,
"initializer": "",
"docText": doc
});
}
}
}
}
}
addContentToDoc(tree) {
let inTable = buildPropsTable(this.inputs);
let outTable = buildPropsTable(this.outputs, false);
if (inTable) {
heading(tree, "Properties", (before, section, after) => {
return [before, inTable, after];
});
}
if (outTable) {
heading(tree, "Events", (before, section, after) => {
return [before, outTable, after];
});
}
}
}
class ServiceDocAutoContent implements NgDocAutoContent {
props: PropData[];
methods: MethodData[];
constructor() {
this.props = [];
this.methods = [];
}
extractClassInfoFromSource(checker: ts.TypeChecker, classDec: ts.ClassDeclaration) {
let sourceFile = classDec.getSourceFile();
for (var i = 0; i < classDec.members.length; i++) {
let member = classDec.members[i];
if (ts.isMethodDeclaration(member)) {
let method: ts.MethodDeclaration = member;
let mods = ts.getCombinedModifierFlags(method);
let nonPrivate = (mods & ts.ModifierFlags.Private) === 0;
let memSymbol = checker.getSymbolAtLocation(method.name);
if (nonPrivate && memSymbol) {
let methData = new MethodData();
methData.name = memSymbol.getName();
let doc = ts.displayPartsToString(memSymbol.getDocumentationComment());
if (!doc)
console.log(`Warning: Method ${classDec.name.escapedText}.${methData.name} is not documented`);
methData.docText = doc.replace(/\r\n/g, " ");
let sig = checker.getSignatureFromDeclaration(method);
let returnType = sig.getReturnType();
methData.returnType = checker.typeToString(returnType);
let returnSymbol = returnType.getSymbol();
let params = method.parameters;
for (let p = 0; p < params.length; p++){
let pData = new ParamData();
pData.name = params[p].name.getText();
if (params[p].type)
pData.type = params[p].type.getText();
else
pData.type = "";
let paramSymbol = checker.getSymbolAtLocation(params[p].name);
pData.docText = ts.displayPartsToString(paramSymbol.getDocumentationComment());
if (!pData.docText)
console.log(`Warning: Parameter "${pData.name}" of ${classDec.name.escapedText}.${methData.name} is not documented`);
pData.optional = params[p].questionToken ? true : false;
if (params[p].initializer) {
let initText = params[p].initializer.getText();
if (initText !== "undefined")
pData.initializer = initText;
}
methData.params.push(pData);
}
this.methods.push(methData);
}
}
}
}
addContentToDoc(tree) {
let propsTable = buildPropsTable(this.props);
let methodsList = buildMethodsList(this.methods);
if (propsTable) {
heading(tree, "Properties", (before, section, after) => {
return [before, propsTable, after];
});
}
if (methodsList) {
heading(tree, "Methods", (before, section, after) => {
return [before, methodsList, after];
});
}
}
}
export function updatePhase(tree, pathname, aggData) {
let fileNameNoSuffix = path.basename(pathname, ".md");
let itemType = fileNameNoSuffix.match(/component|directive|service/);
if (itemType) {
let srcData = aggData.srcData[fileNameNoSuffix];
if (srcData) {
let srcPath = srcData.path;
let className = fixAngularFilename(fileNameNoSuffix);
let classData: NgDocAutoContent;
if ((itemType[0] === "component") || (itemType[0] === "directive")) {
classData = new ComponentDocAutoContent();
} else if (itemType[0] === "service") {
classData = new ServiceDocAutoContent();
}
getDocSourceData(path.resolve(".", srcPath), className, classData);
classData.addContentToDoc(tree);
}
return true;
} else {
return false;
}
}
function initialCap(str: string) {
return str[0].toUpperCase() + str.substr(1);
}
function fixAngularFilename(rawName: string) {
if (nameExceptions[rawName])
return nameExceptions[rawName];
var name = rawName.replace(/\]|\(|\)/g, '');
var fileNameSections = name.split('.');
var compNameSections = fileNameSections[0].split('-');
var outCompName = '';
for (var i = 0; i < compNameSections.length; i++) {
outCompName = outCompName + initialCap(compNameSections[i]);
}
var itemTypeIndicator = '';
if (fileNameSections.length > 1) {
itemTypeIndicator = initialCap(fileNameSections[1]);
}
var finalName = outCompName + itemTypeIndicator;
return finalName;
}
function getDocSourceData(srcPath: string, docClassName: string, classData: NgDocAutoContent) {
let prog = ts.createProgram([srcPath], {
target: ts.ScriptTarget.ES5, module: ts.ModuleKind.CommonJS
});
let sourceFiles = prog.getSourceFiles();
let checker = prog.getTypeChecker();
for (var i = 0; i < sourceFiles.length; i++) {
if (!sourceFiles[i].isDeclarationFile)
ts.forEachChild(sourceFiles[i], visit);
}
function visit(node: ts.Node) {
if (!isNodeExported(node))
return;
if (ts.isClassDeclaration(node) && node.name) {
let classDec: ts.ClassDeclaration = node;
let sourceFile = classDec.getSourceFile();
if (classDec.name.escapedText === docClassName) {
getPropDataFromClassChain(checker, classDec, classData);
}
}
}
}
// Get properties/events from main class and all inherited classes.
function getPropDataFromClassChain(
checker: ts.TypeChecker,
classDec: ts.ClassDeclaration,
classData: NgDocAutoContent
){
// Main class
classData.extractClassInfoFromSource(checker, classDec);
// Inherited classes
if (classDec.heritageClauses) {
for(const hc of classDec.heritageClauses) {
let hcType = checker.getTypeFromTypeNode(hc.types[0]);
for (const dec of hcType.symbol.declarations) {
if (isClassDeclaration(dec)) {
getPropDataFromClassChain(checker, dec, classData);
}
}
}
}
}
function buildPropsTable(props: PropData[], includeInitializer: boolean = true) {
if (props.length === 0) {
return null;
}
var headerCells = [
unist.makeTableCell([unist.makeText("Name")]),
unist.makeTableCell([unist.makeText("Type")])
];
if (includeInitializer)
headerCells.push(unist.makeTableCell([unist.makeText("Default value")]));
headerCells.push(unist.makeTableCell([unist.makeText("Description")]));
var rows = [
unist.makeTableRow(headerCells)
];
for (var i = 0; i < props.length; i++) {
var pName = props[i].name;
var pType = props[i].type;
var pDesc = props[i].docText || "";
if (pDesc) {
pDesc = pDesc.replace(/[\n\r]+/, " ");
}
var descCellContent = remark().parse(pDesc).children;
var pDefault = props[i].initializer || "";
var defaultCellContent;
if (pDefault) {
if (pDefault.length > maxDefaultTextLength) {
defaultCellContent = unist.makeText("See description");
console.log(`Warning: property "${pName}" default value substituted (> ${maxDefaultTextLength} chars)`);
} else
defaultCellContent = unist.makeInlineCode(pDefault);
} else {
defaultCellContent = unist.makeText("");
}
var cells = [
unist.makeTableCell([unist.makeText(pName)]),
unist.makeTableCell([unist.makeInlineCode(pType)])
];
if (includeInitializer)
cells.push(unist.makeTableCell([defaultCellContent]));
cells.push(unist.makeTableCell(descCellContent));
rows.push(unist.makeTableRow(cells));
}
let spacers = [null, null, null];
if (includeInitializer)
spacers.push(null);
return unist.makeTable(spacers, rows);
}
function buildMethodsList(methods: MethodData[]) {
if (methods.length === 0)
return null;
let listItems = [];
for (let method of methods) {
let itemLines = [];
itemLines.push(unist.makeInlineCode(method.getSignature()));
itemLines.push(unist.makeBreak());
itemLines.push(unist.makeParagraph(remark().parse(method.docText).children));
itemLines.push(unist.makeBreak());
let paramListItems = [];
for (let param of method.params) {
let currParamSections = [];
if (param.docText !== "") {
currParamSections.push(unist.makeInlineCode(param.name));
let optionalPart = param.optional ? "(Optional) " : "";
currParamSections.push(unist.makeText(" - " + optionalPart + param.docText));
//currParamSections.push(unist.makeBreak());
paramListItems.push(unist.makeListItem(unist.makeParagraph(currParamSections)));
}
}
itemLines.push(unist.makeListUnordered(paramListItems));
listItems.push(unist.makeListItem(unist.makeParagraph(itemLines)));
}
return unist.makeListUnordered(listItems);
}
function isNodeExported(node: ts.Node): boolean {
return (ts.getCombinedModifierFlags(node) & ts.ModifierFlags.Export) !== 0 || (!!node.parent && node.parent.kind === ts.SyntaxKind.SourceFile);
}
@@ -1,108 +0,0 @@
"use strict";
exports.__esModule = true;
var fs = require("fs");
var path = require("path");
var replaceSection = require("mdast-util-heading-range");
var remark = require("remark");
var frontMatter = require("remark-frontmatter");
var yaml = require("js-yaml");
var combyne = require("combyne");
var unist = require("../unistHelpers");
var tutFolder = path.resolve("..", "docs", "tutorials");
var templateFolder = path.resolve(".", "config", "DocProcessor", "templates");
var userGuideFolder = path.resolve("..", "docs", "user-guide");
function initPhase(aggData) { }
exports.initPhase = initPhase;
function readPhase(tree, pathname, aggData) { }
exports.readPhase = readPhase;
function aggPhase(aggData) {
var indexDocData = getIndexDocData();
var templateName = path.resolve(templateFolder, "tutIndex.combyne");
var templateSource = fs.readFileSync(templateName, "utf8");
var template = combyne(templateSource);
var mdText = template.render(indexDocData);
mdText = mdText.replace(/^ +\|/mg, "|");
var newSection = remark().data("settings", { paddedTable: false, gfm: false }).parse(mdText.trim()).children;
var tutIndexFile = path.resolve(tutFolder, "README.md");
var tutIndexText = fs.readFileSync(tutIndexFile, "utf8");
var tutIndexMD = remark().data("settings", { paddedTable: false, gfm: false }).parse(tutIndexText);
replaceSection(tutIndexMD, "Tutorials", function (before, section, after) {
newSection.unshift(before);
newSection.push(after);
return newSection;
});
fs.writeFileSync(tutIndexFile, remark().use(frontMatter, { type: 'yaml', fence: '---' }).data("settings", { paddedTable: false, gfm: false }).stringify(tutIndexMD));
}
exports.aggPhase = aggPhase;
function updatePhase(tree, pathname, aggData) {
return false;
}
exports.updatePhase = updatePhase;
function getIndexDocData() {
var indexFile = path.resolve(userGuideFolder, "summary.json");
var summaryArray = JSON.parse(fs.readFileSync(indexFile, "utf8"));
var indexArray = [];
summaryArray.forEach(function (element) {
if (element["title"] === "Tutorials") {
indexArray = element["children"];
}
});
var result = {
tuts: []
};
indexArray.forEach(function (element) {
var tutData = { link: element["file"] };
var tutFile = path.resolve(tutFolder, element["file"]);
var tutFileText = fs.readFileSync(tutFile, "utf8");
var tutMD = remark().use(frontMatter, ["yaml"]).parse(tutFileText);
var metadata = getDocMetadata(tutMD);
if (metadata["Level"]) {
tutData["level"] = metadata["Level"];
}
else {
tutData["level"] = "";
}
var briefDesc = getFirstParagraph(tutMD);
var briefDescText = remark()
.use(frontMatter, { type: 'yaml', fence: '---' })
.data("settings", { paddedTable: false, gfm: false })
.stringify(briefDesc);
tutData["briefDesc"] = briefDescText;
var title = getFirstHeading(tutMD);
var titleText = remark()
.use(frontMatter, { type: 'yaml', fence: '---' })
.data("settings", { paddedTable: false, gfm: false })
.stringify(title.children[0]);
tutData["title"] = titleText;
result.tuts.push(tutData);
});
return result;
}
function getDocMetadata(tree) {
if (tree.children[0].type == "yaml") {
return yaml.load(tree.children[0].value);
}
else {
return {};
}
}
function getFirstParagraph(tree) {
var s = 0;
for (; (s < tree.children.length) && !unist.isParagraph(tree.children[s]); s++) { }
if (s < tree.children.length) {
return tree.children[s];
}
else {
return null;
}
}
function getFirstHeading(tree) {
var s = 0;
for (; (s < tree.children.length) && !unist.isHeading(tree.children[s]); s++) { }
if (s < tree.children.length) {
return tree.children[s];
}
else {
return null;
}
}
@@ -1,142 +0,0 @@
import * as fs from "fs";
import * as path from "path";
import * as replaceSection from "mdast-util-heading-range";
import * as remark from "remark";
import * as stringify from "remark-stringify";
import * as frontMatter from "remark-frontmatter";
import * as yaml from "js-yaml";
import * as combyne from "combyne";
import * as unist from "../unistHelpers";
const tutFolder = path.resolve("..", "docs", "tutorials");
const templateFolder = path.resolve(".", "config", "DocProcessor", "templates");
const userGuideFolder = path.resolve("..", "docs", "user-guide");
export function initPhase(aggData) {}
export function readPhase(tree, pathname, aggData) {}
export function aggPhase(aggData) {
let indexDocData = getIndexDocData();
let templateName = path.resolve(templateFolder, "tutIndex.combyne");
let templateSource = fs.readFileSync(templateName, "utf8");
let template = combyne(templateSource);
let mdText = template.render(indexDocData);
mdText = mdText.replace(/^ +\|/mg, "|");
let newSection = remark().data("settings", {paddedTable: false, gfm: false}).parse(mdText.trim()).children;
let tutIndexFile = path.resolve(tutFolder, "README.md");
let tutIndexText = fs.readFileSync(tutIndexFile, "utf8");
let tutIndexMD = remark().data("settings", {paddedTable: false, gfm: false}).parse(tutIndexText);
replaceSection(tutIndexMD, "Tutorials", (before, section, after) => {
newSection.unshift(before);
newSection.push(after);
return newSection;
});
fs.writeFileSync(tutIndexFile, remark().use(frontMatter, {type: 'yaml', fence: '---'}).data("settings", {paddedTable: false, gfm: false}).stringify(tutIndexMD));
}
export function updatePhase(tree, pathname, aggData) {
return false;
}
function getIndexDocData() {
let indexFile = path.resolve(userGuideFolder, "summary.json");
let summaryArray = JSON.parse(fs.readFileSync(indexFile, "utf8"));
let indexArray = [];
summaryArray.forEach(element => {
if (element["title"] === "Tutorials") {
indexArray = element["children"];
}
});
let result = {
tuts: []
};
indexArray.forEach(element => {
let tutData = { link: element["file"] };
let tutFile = path.resolve(tutFolder, element["file"]);
let tutFileText = fs.readFileSync(tutFile, "utf8");
let tutMD = remark().use(frontMatter, ["yaml"]).parse(tutFileText);
let metadata = getDocMetadata(tutMD);
if (metadata["Level"]){
tutData["level"] = metadata["Level"];
} else {
tutData["level"] = "";
}
let briefDesc = getFirstParagraph(tutMD);
let briefDescText = remark()
.use(frontMatter, {type: 'yaml', fence: '---'})
.data("settings", {paddedTable: false, gfm: false})
.stringify(briefDesc);
tutData["briefDesc"] = briefDescText;
let title = getFirstHeading(tutMD);
let titleText = remark()
.use(frontMatter, {type: 'yaml', fence: '---'})
.data("settings", {paddedTable: false, gfm: false})
.stringify(title.children[0]);
tutData["title"] = titleText;
result.tuts.push(tutData);
});
return result;
}
function getDocMetadata(tree) {
if (tree.children[0].type == "yaml") {
return yaml.load(tree.children[0].value);
} else {
return {};
}
}
function getFirstParagraph(tree) {
let s = 0;
for (;(s < tree.children.length) && !unist.isParagraph(tree.children[s]); s++) {}
if (s < tree.children.length) {
return tree.children[s];
} else {
return null;
}
}
function getFirstHeading(tree) {
let s = 0;
for (;(s < tree.children.length) && !unist.isHeading(tree.children[s]); s++) {}
if (s < tree.children.length) {
return tree.children[s];
} else {
return null;
}
}
@@ -1,128 +0,0 @@
var fs = require("fs");
var path = require("path");
var yaml = require("js-yaml");
var remark = require("remark");
var stringify = require("remark-stringify");
var zone = require("mdast-zone");
var unist = require("../unistHelpers");
var ngHelpers = require("../ngHelpers");
module.exports = {
"initPhase": initPhase,
"readPhase": readPhase,
"aggPhase": aggPhase,
"updatePhase": updatePhase
}
var angFilenameRegex = /([a-zA-Z0-9\-]+)\.((component)|(directive)|(model)|(pipe)|(service)|(widget))\.ts/;
var docsFolderPath = path.resolve("..", "docs");
var histFilePath = path.resolve(docsFolderPath, "versionIndex.md");
var histSectionName = "history";
var initialVersion = "v2.0.0";
function initPhase(aggData) {
aggData.versions = { "v2.0.0":[] };
}
function readPhase(tree, pathname, aggData) {
var compName = pathname; //path.basename(pathname, ".md");
var angNameRegex = /([a-zA-Z0-9\-]+)\.((component)|(directive)|(model)|(pipe)|(service)|(widget))/;
if (!compName.match(angNameRegex))
return;
if (compName.match(/boilerplate/))
return;
if (tree.children[0].type == "yaml") {
var metadata = yaml.load(tree.children[0].value);
var version = metadata["Added"];
if (version) {
if (aggData.versions[version]) {
aggData.versions[version].push(compName);
} else {
aggData.versions[version] = [compName];
}
} else {
aggData.versions[initialVersion].push(compName);
}
} else {
aggData.versions[initialVersion].push(compName);
}
}
function aggPhase(aggData) {
var histFileText = fs.readFileSync(histFilePath, "utf8");
var histFileTree = remark().parse(histFileText);
var keys = Object.keys(aggData.versions);
keys.sort((a, b) => {
if (a > b)
return -1;
else if (b > a)
return 1;
else
return 0;
});
var sections = [unist.makeHeading(unist.makeText("Versions"), 2)];
var vListItems = [];
for (var i = 0; i < keys.length; i++) {
var keyAnchor = keys[i].replace(/\./g, "");
vListItems.push(unist.makeListItem(unist.makeLink(unist.makeText(keys[i]), `#${keyAnchor}`)));
}
sections.push(unist.makeListUnordered(vListItems));
for (var i = 0; i < keys.length; i++) {
var version = keys[i];
var versionItems = aggData.versions[version];
versionItems.sort((a, b) => {
var aa = path.basename(a, ".md");
var bb = path.basename(b, ".md");
return aa.localeCompare(bb);
});
var versListItems = [];
for (var v = 0; v < versionItems.length; v++) {
var displayName = ngHelpers.ngNameToDisplayName(path.basename(versionItems[v], ".md"));
var pageLink = versionItems[v];// + ".md";
pageLink = pageLink.replace(/\\/g, '/');
pageLink = pageLink.substr(pageLink.indexOf("docs") + 5);
versListItems.push(
unist.makeListItem(
unist.makeLink(unist.makeText(displayName), pageLink)
)
);
}
sections.push(unist.makeHeading(unist.makeText(version), 2));
sections.push(unist.makeListUnordered(versListItems));
}
zone(histFileTree, histSectionName, (startComment, oldSection, endComment) => {
sections.unshift(startComment);
sections.push(endComment);
return sections;
});
//console.log(JSON.stringify(histFileTree));
fs.writeFileSync(histFilePath, remark().stringify(histFileTree));
}
function updatePhase(tree, pathname, aggData) {
return false;
}
-136
View File
@@ -1,136 +0,0 @@
module.exports = {
makeRoot: function (children) {
return {
"type": "root",
"children": children
};
},
makeText: function (textVal) {
return {
"type": "text",
"value": textVal
};
},
makeEmphasis: function (content) {
return {
"type": "emphasis",
"children": content
};
},
makeStrong: function (content) {
return {
"type": "strong",
"children": content
};
},
makeHeading: function (caption, depth) {
return {
"type": "heading",
"depth": depth,
"children": [caption]
};
},
makeLink: function (caption, url) {
return {
"type": "link",
"url": url,
"children": [ caption ]
};
},
makeListItem: function (itemValue) {
return {
"type": "listItem",
"loose": false,
"children": [ itemValue ]
};
},
makeListUnordered: function (itemsArray) {
return {
"type": "list",
"ordered": false,
"children": itemsArray,
"loose": false
};
},
makeParagraph: function (itemsArray) {
return {
"type": "paragraph",
"children": itemsArray
}
},
makeTable: function (colAlignArray, rowArray) {
return {
"type": "table",
"align": colAlignArray,
"children": rowArray
};
},
makeTableRow: function (cellArray) {
return {
"type": "tableRow",
"children": cellArray
};
},
makeTableCell: function (content) {
return {
"type": "tableCell",
"children": content
};
},
makeInlineCode: function (codeText) {
return {
"type": "inlineCode",
"value": codeText
}
},
makeHTML: function (htmlText) {
return {
"type": "html",
"value": htmlText
};
},
makeBreak: function () {
return {
"type": "break"
}
},
makeImage: function (url, alt) {
return {
"type": "image",
"url": url,
"alt": alt
}
},
isHeading: function (node) {
return node.type === "heading";
},
isListUnordered: function (node) {
return (node.type === "list") && !node.ordered;
},
isParagraph: function (node) {
return node.type === "paragraph";
},
isText: function (node) {
return node.type === "text";
}
}
-240
View File
@@ -1,240 +0,0 @@
var fs = require('fs');
var path = require('path');
var angFilenameRegex = /([a-zA-Z0-9\-]+)\.((component)|(directive)|(model)|(pipe)|(service)|(widget))\.ts/;
var searchFolderOmitRegex = /(mock)|(i18n)|(assets)|(styles)/;
var libFolderOmitRegex = /config/;
var indexFileName = path.resolve('..', 'docs', 'README.md');
var summaryFileName = path.resolve('..', 'docs', 'summary.json');
var undocStoplistFileName = path.resolve('..', 'docs', 'undocStoplist.json');
// Search source folders for .ts files to discover all components, directives, etc,
// that are in the supplied library.
function searchLibraryRecursive(libData, folderPath) {
var items = fs.readdirSync(folderPath);
for (var i = 0; i < items.length; i++) {
var itemPath = path.resolve(folderPath, items[i]);
var info = fs.statSync(itemPath);
if (info.isFile() && (items[i].match(angFilenameRegex))) {
var nameNoSuffix = path.basename(items[i], '.ts');
var itemCategory = nameNoSuffix.split('.')[1];
if(nameNoSuffix in docDict) {
switch (itemCategory) {
case "component":
libData.componentsWithDocs.push(itemPath);
break;
case "directive":
libData.directivesWithDocs.push(itemPath);
break;
case "model":
libData.modelsWithDocs.push(itemPath);
break;
case "pipe":
libData.pipesWithDocs.push(itemPath);
break;
case "service":
libData.servicesWithDocs.push(itemPath);
break;
case "widget":
libData.widgetsWithDocs.push(itemPath);
break;
default:
break;
}
} else if (!rejectItemViaStoplist(undocStoplist, items[i])) {
switch (itemCategory) {
case "component":
libData.componentsWithoutDocs.push(itemPath);
break;
case "directive":
libData.directivesWithoutDocs.push(itemPath);
break;
case "model":
libData.modelsWithoutDocs.push(itemPath);
break;
case "pipe":
libData.pipesWithoutDocs.push(itemPath);
break;
case "service":
libData.servicesWithoutDocs.push(itemPath);
break;
case "widget":
libData.widgetsWithoutDocs.push(itemPath);
break;
default:
break;
}
}
} else if (info.isDirectory() && !items[i].match(searchFolderOmitRegex)) {
searchLibraryRecursive(libData, itemPath);
}
}
}
// Get a list of all items that have a file in the docs folder.
function getDocFolderItems(docFolderPath) {
var result = {};
var items = fs.readdirSync(path.resolve(docFolderPath));
for (var i = 0; i < items.length; i++) {
if (items[i].endsWith('.md')) {
var nameNoSuffix = path.basename(items[i], '.md');
result[nameNoSuffix] = 1;
}
}
return result;
}
// Convert an Angular-style name (eg, "card-view") into one with correct spaces and uppercase (eg, "Card View").
function tidyName(name) {
var result = name.replace(/-/g, " ");
result = result.substr(0, 1).toUpperCase() + result.substr(1);
return result;
}
// Generate the Markdown index for the files from the guide summary.
function makeSummaryIndex() {
var summaryJson = fs.readFileSync(summaryFileName, 'utf8');
var summary = JSON.parse(summaryJson);
var result = '';
for (var i = 0; i < summary.length; i++) {
var item = summary[i];
result += '- [' + item.title + '](' + item.file + ')\n';
}
return result;
}
// Create a stoplist of regular expressions.
function makeStoplist(slFilePath) {
var listExpressions = JSON.parse(fs.readFileSync(slFilePath, 'utf8'));
var result = [];
for (var i = 0; i < listExpressions.length; i++) {
result.push(new RegExp(listExpressions[i]));
}
return result;
}
// Check if an item is covered by the stoplist and reject it if so.
function rejectItemViaStoplist(stoplist, itemName) {
for (var i = 0; i < stoplist.length; i++) {
if (stoplist[i].test(itemName)) {
return true;
}
}
return false;
}
function buildIndexSection(name, documented, undocumented) {
var listItems = [];
if ((documented.length > 0) || (undocumented.length > 0)) {
listItems.push('\n### ' + name + '\n');
}
for (var i = 0; i < documented.length; i++) {
var libFilePath = documented[i];
var libFileName = path.basename(libFilePath, '.ts');
var nameSections = libFileName.split('.');
var visibleName = tidyName(nameSections[0]) + ' ' + nameSections[1];
var mdListItem = '- [' + visibleName + '](' + libFileName + '.md)';
listItems.push(mdListItem);
}
for (var i = 0; i < undocumented.length; i++) {
var libFilePath = undocumented[i].replace(/\\/g, '/');
var libFileName = path.basename(libFilePath, '.ts');
var nameSections = libFileName.split('.');
var visibleName = tidyName(nameSections[0]) + ' ' + nameSections[1];
var relPath = libFilePath.substr(libFilePath.indexOf('/lib'));
var mdListItem = '- [*' + visibleName + '](..' + relPath + ')';
listItems.push(mdListItem);
}
return listItems;
}
var undocStoplist = makeStoplist(undocStoplistFileName);
var docDict = getDocFolderItems(path.resolve('..', 'docs'));
var rootItems = fs.readdirSync(path.resolve('.'));
var libs = {}
for (var i = 0; i < rootItems.length; i++) {
var itemPath = path.resolve(rootItems[i]);
var info = fs.statSync(itemPath);
if (info.isDirectory() && !rootItems[i].match(libFolderOmitRegex)) {
libs[rootItems[i]] = {
componentsWithDocs: [], componentsWithoutDocs: [],
directivesWithDocs: [], directivesWithoutDocs: [],
modelsWithDocs: [], modelsWithoutDocs: [],
pipesWithDocs: [], pipesWithoutDocs: [],
servicesWithDocs: [], servicesWithoutDocs: [],
widgetsWithDocs: [], widgetsWithoutDocs: [],
};
searchLibraryRecursive(libs[rootItems[i]], path.resolve(itemPath));
}
}
var indexFileText = fs.readFileSync(indexFileName, 'utf8');
var libNames = Object.keys(libs);
for (var i = 0; i < libNames.length; i++) {
var libName = libNames[i];
var libData = libs[libName];
var listItems = buildIndexSection('Components', libData.componentsWithDocs, libData.componentsWithoutDocs);
listItems = listItems.concat(buildIndexSection('Directives', libData.directivesWithDocs, libData.directivesWithoutDocs));
listItems = listItems.concat(buildIndexSection('Models', libData.modelsWithDocs, libData.modelsWithoutDocs));
listItems = listItems.concat(buildIndexSection('Pipes', libData.pipesWithDocs, libData.pipesWithoutDocs));
listItems = listItems.concat(buildIndexSection('Services', libData.servicesWithDocs, libData.servicesWithoutDocs));
listItems = listItems.concat(buildIndexSection('Widgets', libData.widgetsWithDocs, libData.widgetsWithoutDocs));
var libText = listItems.join('\n');
var libStartMarker = '<!-- ' + libName + ' start -->';
var libEndMarker = '<!-- ' + libName + ' end -->';
var contentRegex = new RegExp('(?:' + libStartMarker + ')([\\s\\S]*)(?:' + libEndMarker + ')');
indexFileText = indexFileText.replace(contentRegex, libStartMarker + '\n' + libText + '\n' + libEndMarker);
}
var guideStartMarker = '<!-- guide start -->';
var guideEndMarker = '<!-- guide end -->';
var contentRegex = new RegExp('(?:' + guideStartMarker + ')([\\s\\S]*)(?:' + guideEndMarker + ')');
indexFileText = indexFileText.replace(contentRegex, guideStartMarker + '\n' + makeSummaryIndex() + '\n' + guideEndMarker);
fs.writeFileSync(indexFileName, indexFileText, 'utf-8');
+8 -8
View File
@@ -2,24 +2,24 @@ var Bundler = require('scss-bundle').Bundler;
var writeFileSync = require('fs-extra').writeFileSync;
var mkdirpSync = require('fs-extra').mkdirpSync;
new Bundler().Bundle('./core/styles/_index.scss', '**/*.scss').then(result => {
new Bundler().Bundle('./lib/core/styles/_index.scss', '**/*.scss').then(result => {
mkdirpSync('core');
writeFileSync('dist/core/_theming.scss', result.bundledContent);
writeFileSync('./lib/dist/core/_theming.scss', result.bundledContent);
});
new Bundler().Bundle('./insights/styles/_index.scss', '**/*.scss').then(result => {
new Bundler().Bundle('./lib/insights/styles/_index.scss', '**/*.scss').then(result => {
mkdirpSync('insights');
writeFileSync('dist/insights/_theming.scss', result.bundledContent);
writeFileSync('./lib/dist/insights/_theming.scss', result.bundledContent);
});
new Bundler().Bundle('./process-services/styles/_index.scss', '**/*.scss').then(result => {
new Bundler().Bundle('./lib/process-services/styles/_index.scss', '**/*.scss').then(result => {
mkdirpSync('process-services');
writeFileSync('dist/process-services/_theming.scss', result.bundledContent);
writeFileSync('./lib/dist/process-services/_theming.scss', result.bundledContent);
});
new Bundler().Bundle('./content-services/styles/_index.scss', '**/*.scss').then(result => {
new Bundler().Bundle('./lib/content-services/styles/_index.scss', '**/*.scss').then(result => {
mkdirpSync('content-services');
writeFileSync('dist/content-services/_theming.scss', result.bundledContent);
writeFileSync('./lib/dist/content-services/_theming.scss', result.bundledContent);
});
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -1,8 +0,0 @@
{
"export-last-version": "export-new.json",
"export-versions": [
"export-2.0.0.json",
"export-2.1.0.json",
"export-2.2.0.json"
]
}
-373
View File
@@ -1,373 +0,0 @@
import * as ts from "typescript";
import * as fs from "fs";
import chalk from "chalk";
var nconf = require('nconf');
var baseParh = "./config/export-check/";
nconf.add('config', { type: 'file', file: `${baseParh}export-check-config.json` });
var newLibExports = baseParh + nconf.get('export-last-version');
var exportFilesVersions = nconf.get('export-versions').map((currentFile) => {
return baseParh + currentFile;
});
interface DocEntry {
position?: {
line: number,
character: number,
fileName: string
},
name?: string,
skipError?: boolean
};
let error_array = [];
let warning_array = [];
let exportedAllPath: Array<string> = [];
let classList: Array<any> = [];
let add_error = function (error: string, nameClass: string) {
let findErrorClass = false;
error_array.forEach((currentError) => {
if (currentError.nameClass === nameClass) {
findErrorClass = true;
return;
}
});
if (!findErrorClass) {
error_array.push({
error: error,
nameClass: nameClass
});
}
}
let count_error = 0;
let count_warning = 0;
let print_errors = function () {
error_array.forEach((current_error) => {
console.log(chalk.red(`[${++count_error}] ${current_error.error}\n`));
});
}
let add_warning = function (warning: string, nameClass: string, arrayCall: string[]) {
let findWarningClass = false;
warning_array.forEach((currentWarning) => {
if (currentWarning.nameClass === nameClass) {
findWarningClass = true;
return;
}
});
if (!findWarningClass) {
warning_array.push({
warning: warning,
nameClass: nameClass,
arrayCall: arrayCall
});
}
}
let print_warnings = function () {
warning_array.forEach((current_warning) => {
console.log(chalk.yellow(`[${++count_warning}] ${current_warning.warning} \n ${current_warning.arrayCall} \n`));
});
}
let currentErrorPostion = function (exportEntry) {
return ` ${exportEntry.position.fileName} (${exportEntry.position.line},${exportEntry.position.character})`
}
let check_export = function (exportLastMajor: any, exportNew: any) {
exportLastMajor.forEach((currentexportLastMajor) => {
let currentexportNew = exportNew.filter((currentexportNew) => {
return currentexportNew.name === currentexportLastMajor.name;
});
if (currentexportNew.length > 1) {
let arrayCall = [];
currentexportNew.forEach((error) => {
arrayCall.push(`${currentErrorPostion(error)}`);
})
add_warning(`Multiple export ${currentexportNew[0].name} times ${currentexportNew.length}`, currentexportNew[0].name, arrayCall);
} else if (currentexportNew.length === 0) {
if (!currentexportLastMajor.skipError) {
add_error(`Not find export ${currentexportLastMajor.name} , old path: [${currentErrorPostion(currentexportLastMajor)}]`, currentexportLastMajor.name);
}
}
});
};
let expandStarExport = function (node: ts.Node): ts.ExportDeclaration {
const ed = node as ts.Node as ts.ExportDeclaration;
const exports = [{ name: "x" }];
const exportSpecifiers = exports.map(e => ts.createExportSpecifier(e.name, e.name));
const exportClause = ts.createNamedExports(exportSpecifiers);
const newEd = ts.updateExportDeclaration(ed, ed.decorators, ed.modifiers, exportClause, ed.moduleSpecifier);
return newEd as ts.ExportDeclaration
};
/** Generate documentation for all classes in a set of .ts files */
function generatExportList(fileNames: string[], options: ts.CompilerOptions): void {
// Build a program using the set of root file names in fileNames
let program = ts.createProgram(fileNames, options);
// Get the checker, we will use it to find more about classes
let checker = program.getTypeChecker();
let exportCurrentVersion: DocEntry[] = [];
// Visit every sourceFile in the program
for (const sourceFile of program.getSourceFiles()) {
if (!sourceFile.isDeclarationFile) {
// Walk the tree to search for classes
ts.forEachChild(sourceFile, visit);
}
}
classList.forEach((classNode) => {
if (classNode.symbol.parent) {
let pathClass = classNode.symbol.parent.escapedName.replace(/"/g, "");
exportedAllPath.forEach((currenPath) => {
let pathNoExtension = currenPath.replace(/\.[^/.]+$/, "");
if (pathNoExtension === pathClass) {
// console.log('pathClass'+ pathClass);
// console.log('pathNoExtension '+ pathNoExtension);
extractExport(classNode);
return;
}
});
}
});
exportCurrentVersion.sort((nameA, nameB) => nameA.name.localeCompare(nameB.name));
console.log(chalk.green(`Saving new export in ${newLibExports}`));
fs.writeFileSync(newLibExports, JSON.stringify(exportCurrentVersion, undefined, 4));
var exportNewJSON = JSON.parse(JSON.stringify(exportCurrentVersion));
exportFilesVersions.forEach((currentExportVersionFile) => {
error_array = [];
warning_array = [];
try {
var currentExportVersionJSON = JSON.parse(fs.readFileSync(`${currentExportVersionFile}`, 'utf8'));
} catch (error) {
console.log(chalk.red(`${currentExportVersionFile} json not present`));
throw new Error(`Undetected export comapring file ${currentExportVersionFile}`);
}
console.log(chalk.green(`Comparing ${newLibExports} and ${currentExportVersionFile}`));
check_export(currentExportVersionJSON, exportNewJSON);
print_warnings();
print_errors();
if (error_array.length > 0) {
throw new Error('Export problems detected');
} else {
return;
}
})
function extractExport(node: ts.Node) {
//skip file with export-check: exclude comment
if (node.getFullText(node.getSourceFile()).indexOf('export-check: exclude') > 0) {
return;
}
let { line, character } = node.getSourceFile().getLineAndCharacterOfPosition(node.getStart());
//console.log(line + " " + character + " " + node.getSourceFile().fileName);
let symbol = checker.getSymbolAtLocation(node);
if (symbol) {
let arryCalls = recursiveStackSave(node);
let className: any = symbol.escapedName;
let filename = node.getSourceFile().fileName.substring(node.getSourceFile().fileName.indexOf('lib'), node.getSourceFile().fileName.length);
exportCurrentVersion.push(serializeClass(className, line, character, filename, arryCalls));
// if (className === "ContentMetadataService") {
// console.log(chalk.red("exportedAllPath" + exportedAllPath));
// console.log(chalk.red("ContentMetadataService"));
// recursiveStack(node);
// }
} else {
let arryCalls = recursiveStackSave(node);
let className: any = (node as ts.ClassDeclaration).name.escapedText;
let filename = node.getSourceFile().fileName.substring(node.getSourceFile().fileName.indexOf('lib'), node.getSourceFile().fileName.length);
exportCurrentVersion.push(serializeClass(className, line, character, filename, arryCalls));
// if (className === "ContentMetadataService") {
// console.log(chalk.greenBright("exportedAllPath" + exportedAllPath));
// console.log(chalk.greenBright("ContentMetadataService"));
// recursiveStack(node);
// }
}
}
function recursiveStackSave(node: ts.Node, arrayCalls?: string[]) {
if (!arrayCalls) {
arrayCalls = [];
}
let filename = node.getSourceFile().fileName.substring(node.getSourceFile().fileName.indexOf('lib'), node.getSourceFile().fileName.length);
let { line, character } = node.getSourceFile().getLineAndCharacterOfPosition(node.getStart());
arrayCalls.push(node.getSourceFile().fileName);
if (node.parent) {
recursiveStackSave(node.parent, arrayCalls)
}
return arrayCalls;
}
function recursiveStack(node: ts.Node) {
let filename = node.getSourceFile().fileName.substring(node.getSourceFile().fileName.indexOf('lib'), node.getSourceFile().fileName.length);
let { line, character } = node.getSourceFile().getLineAndCharacterOfPosition(node.getStart());
console.log(chalk.bgCyan(line + " " + character + " " + node.getSourceFile().fileName));
if (node.parent) {
recursiveStack(node.parent)
}
}
/** visit nodes finding exported classes */
function visit(node: ts.Node) {
// Only consider exported nodes
if (node.kind === ts.SyntaxKind.ClassDeclaration) {
if (node.decorators) {
node.decorators.forEach((decorator) => {
visit(decorator as ts.Node);
});
}
classList.push(node);
}
if (node.kind === ts.SyntaxKind.PropertyAssignment) {
const initializer = (node as ts.PropertyAssignment).initializer;
visit(initializer as ts.Node);
}
if (node.kind === ts.SyntaxKind.Identifier) {
extractExport(node);
}
if (node.kind === ts.SyntaxKind.ArrayLiteralExpression) {
(node as ts.ArrayLiteralExpression).elements.forEach((element) => {
visit(element as ts.Node);
});
}
if (node.kind === ts.SyntaxKind.Decorator &&
((node as ts.Decorator).expression as any).expression.text === "NgModule") {
((node as ts.Decorator).expression as any).arguments.forEach((argument) => {
argument.properties.forEach((property) => {
if (property.name.escapedText === "exports") {
visit(property as ts.Node);
}
});
});
}
if (ts.isExportDeclaration(node)) {
if (node.exportClause) {
node.exportClause.elements.forEach(exportCurrent => {
extractExport(exportCurrent as ts.Node);
});
} else {
(node.parent as any).resolvedModules.forEach((currentModule) => {
if (currentModule) {
let find;
exportedAllPath.forEach((currentExported) => {
if (currentModule.resolvedFileName === currentExported) {
find = currentExported;
}
})
if (!find) {
exportedAllPath.push(currentModule.resolvedFileName);
}
}
})
visit(node.moduleSpecifier);
}
}
if (ts.isModuleDeclaration(node)) {
// This is a namespace, visit its children
ts.forEachChild(node, visit);
}
}
/** Serialize a symbol into a json object */
function serializeSymbol(className: string, line?: number, character?: number, fileName?: string, arryCalls?: string[]): DocEntry {
return {
position: {
line: line,
character: character,
fileName: fileName
},
name: className
};
}
/** Serialize a class symbol information */
function serializeClass(className: string, line?: number, character?: number, fileName?: string, arryCalls?: string[]) {
let details = serializeSymbol(className, line, character, fileName, arryCalls);
return details;
}
/** True if this is visible outside this file, false otherwise */
function isNodeExported(node: ts.Node): boolean {
return (ts.getCombinedModifierFlags(node) & ts.ModifierFlags.Export) !== 0 || (!!node.parent && node.parent.kind === ts.SyntaxKind.SourceFile);
}
}
generatExportList(process.argv.slice(2), {
target: ts.ScriptTarget.ES5, module: ts.ModuleKind.CommonJS, removeComments: false
});
-96
View File
@@ -1,96 +0,0 @@
var fs = require('fs');
var path = require('path');
var docFolderPath = path.resolve("..", "docs");
var graphFileName = path.resolve(docFolderPath, "seeAlsoGraph.json");
// Get a list of all items that have a file in the docs folder.
function getDocFolderItems(docFolderPath) {
var result = {};
var items = fs.readdirSync(path.resolve(docFolderPath));
for (var i = 0; i < items.length; i++) {
if (items[i].endsWith('.md')) {
var nameNoSuffix = path.basename(items[i], '.md');
result[nameNoSuffix] = 1;
}
}
return result;
}
// Convert an Angular-style name (eg, "card-view") into one with correct spaces and uppercase (eg, "Card view").
function tidyName(name) {
var result = name.replace(/-/g, " ");
result = result.substr(0, 1).toUpperCase() + result.substr(1);
return result;
}
function buildSeeAlsoList(arcs) {
var listItems = [];
for (var i = 0; i < arcs.length; i++) {
var parts = arcs[i].split('.');
var itemName = tidyName(parts[0]);
if (parts[1]) {
itemName += ' ' + parts[1];
}
listItems.push('- [' + itemName + '](' + arcs[i] + '.md)');
}
return listItems.join('\n');
}
// If item is not in the arcs array then add it at
// the end.
function fixArcs(arcsArray, item) {
if (arcsArray.indexOf(item) == -1) {
arcsArray.push(item);
}
}
// Makes link symmetrical between items (ie, if A links to B but not the other way
// around then it adds the missing link).
function tidyGraph(graph) {
var nodeNames = Object.keys(graph);
for (var n = 0; n < nodeNames.length; n++) {
var currNodeName = nodeNames[n];
var currNodeArcs = graph[currNodeName];
for (var a = 0; a < currNodeArcs.length; a++) {
var linkedNode = graph[currNodeArcs[a]];
fixArcs(linkedNode, currNodeName);
}
}
}
var graphJson = fs.readFileSync(graphFileName, 'utf8');
var graph = JSON.parse(graphJson);
tidyGraph(graph);
var nodeNames = Object.keys(graph);
for (var i = 0; i < nodeNames.length; i++) {
var seeAlsoText = '## See also\n\n' + buildSeeAlsoList(graph[nodeNames[i]]);
var docFileName = path.resolve(docFolderPath, nodeNames[i] + '.md');
var docFileText = fs.readFileSync(docFileName, 'utf8');
var seeAlsoStartMarker = '<!-- seealso start -->';
var seeAlsoEndMarker = '<!-- seealso end -->';
var seeAlsoRegex = new RegExp('(?:' + seeAlsoStartMarker + ')([\\s\\S]*)(?:' + seeAlsoEndMarker + ')');
docFileText = docFileText.replace(seeAlsoRegex, seeAlsoStartMarker + '\n' + seeAlsoText + '\n' + seeAlsoEndMarker);
fs.writeFileSync(docFileName, docFileText, 'utf-8');
}
+57 -29
View File
@@ -1,9 +1,9 @@
const webpackCoverage = require('./webpack.coverage');
const webpackTest= require('./webpack.test');
const webpackTest = require('./webpack.test');
module.exports = function (config) {
var _config = {
basePath: './',
basePath: '../',
frameworks: ['jasmine-ajax', 'jasmine'],
@@ -13,6 +13,11 @@ module.exports = function (config) {
{pattern: './node_modules/hammerjs/hammer.min.js', included: true, watched: false},
{pattern: './node_modules/hammerjs/hammer.min.js.map', included: false, watched: false},
// pdf-js
{pattern: './node_modules/pdfjs-dist/build/pdf.js', included: true, watched: false},
{pattern: './node_modules/pdfjs-dist/build/pdf.worker.js', included: true, watched: false},
{pattern: './node_modules/pdfjs-dist/web/pdf_viewer.js', included: true, watched: false},
{
pattern: './node_modules/@angular/material/prebuilt-themes/indigo-pink.css',
included: true,
@@ -31,23 +36,23 @@ module.exports = function (config) {
watched: false
},
// pdf-js
{pattern: './node_modules/pdfjs-dist/build/pdf.js', included: true, watched: false},
{pattern: './node_modules/pdfjs-dist/build/pdf.worker.js', included: true, watched: false},
{pattern: './node_modules/pdfjs-dist/web/pdf_viewer.js', included: true, watched: false},
{pattern: './lib/core/i18n/**/en.json', included: false, served: true, watched: false},
{pattern: './lib/content-services/i18n/**/en.json', included: false, served: true, watched: false},
{pattern: './lib/process-services/i18n/**/en.json', included: false, served: true, watched: false},
{pattern: config.component + '/karma-test-shim.js', watched: false},
{pattern: './lib/**/*.ts', included: false, served: true, watched: false},
{pattern: './core/i18n/**/en.json', included: false, served: true, watched: false},
{pattern: './content-services/i18n/**/en.json', included: false, served: true, watched: false},
{pattern: './process-services/i18n/**/en.json', included: false, served: true, watched: false},
{pattern: './lib/config/app.config.json', included: false, served: true, watched: false},
{pattern: './lib/core/viewer/assets/fake-test-file.pdf', included: false, served: true, watched: false},
{pattern: './lib/core/viewer/assets/fake-test-file.txt', included: false, served: true, watched: false},
{
pattern: './lib/core/viewer/assets/fake-test-password-file.pdf',
included: false,
served: true,
watched: false
},
{pattern: './**/*.ts', included: false, served: true, watched: false},
{pattern: './config/app.config.json', included: false, served: true, watched: false},
{pattern: './core/viewer/assets/fake-test-file.pdf', included: false, served: true, watched: false},
{pattern: './core/viewer/assets/fake-test-file.txt', included: false, served: true, watched: false},
{pattern: './core/viewer/assets/fake-test-password-file.pdf', included: false, served: true, watched: false}
{pattern: './lib/' + config.component + '/karma-test-shim.js', watched: false}
],
webpack: (config.mode === 'coverage') ? webpackCoverage(config) : webpackTest(config),
@@ -62,10 +67,10 @@ module.exports = function (config) {
port: 9876,
proxies: {
'/app.config.json': '/base/config/app.config.json',
'/fake-test-file.pdf': '/base/core/viewer/assets/fake-test-file.pdf',
'/fake-test-file.txt': '/base/core/viewer/assets/fake-test-file.txt',
'/fake-test-password-file.pdf': '/base/core/viewer/assets/fake-test-password-file.pdf'
'/app.config.json': '/base/lib/config/app.config.json',
'/fake-test-file.pdf': '/base/lib/core/viewer/assets/fake-test-file.pdf',
'/fake-test-file.txt': '/base/lib/core/viewer/assets/fake-test-file.txt',
'/fake-test-password-file.pdf': '/base/lib/core/viewer/assets/fake-test-password-file.pdf'
},
// level of logging
@@ -80,17 +85,30 @@ module.exports = function (config) {
browserNoActivityTimeout: 2400000,
captureTimeout: 1200000,
browsers: ['Chrome'],
// browsers: ['ChromeHeadless'],
customLaunchers: {
ChromeHeadless: {
base: 'Chrome',
flags: [
'--no-sandbox',
'--headless',
'--disable-gpu',
'--remote-debugging-port=9222'
]
}
},
// Karma plugins loaded
plugins: [
require('../node_modules/karma-jasmine'),
require('../node_modules/karma-coverage'),
require('../node_modules/karma-sourcemap-loader'),
require('../node_modules/karma-jasmine-ajax'),
require('../node_modules/karma-chrome-launcher'),
require('../node_modules/karma-webpack'),
require('../node_modules/karma-jasmine-html-reporter'),
require('../node_modules/karma-mocha-reporter')
require('../../node_modules/karma-jasmine'),
require('../../node_modules/karma-coverage'),
require('../../node_modules/karma-sourcemap-loader'),
require('../../node_modules/karma-jasmine-ajax'),
require('../../node_modules/karma-chrome-launcher'),
require('../../node_modules/karma-webpack'),
require('../../node_modules/karma-jasmine-html-reporter'),
require('../../node_modules/karma-mocha-reporter')
],
webpackServer: {
@@ -100,6 +118,10 @@ module.exports = function (config) {
// Coverage reporter generates the coverage
reporters: ['mocha', 'coverage', 'kjhtml'],
mochaReporter: {
ignoreSkipped: true
},
preprocessors: {
'**/karma-test-shim.js': ['webpack'],
'(core|content-services|process-services)/**/!(*spec|index|*mock|*model|*event).js': 'coverage'
@@ -107,7 +129,7 @@ module.exports = function (config) {
coverageReporter: {
includeAllSources: true,
dir: './coverage/' + config.component + '/',
dir: './lib/coverage/' + config.component + '/',
subdir: 'report',
reporters: [
{type: 'text'},
@@ -117,6 +139,12 @@ module.exports = function (config) {
{type: 'lcov'}
]
}
// client: {
// jasmine: {
// random: true
// }
// }
};
config.set(_config);
+3 -3
View File
@@ -37,7 +37,7 @@ module.exports = {
"@alfresco/adf-insights": path.resolve(__dirname, '../insights/index.ts')
},
extensions: ['.ts', '.js', '.scss'],
modules: [helpers.root('node_modules')]
modules: [helpers.root('../node_modules')]
},
module: {
@@ -149,8 +149,8 @@ module.exports = {
new CopyWebpackPlugin([
... alfrescoLibs.map(lib => {
return {
from: `${lib}/i18n/`,
to: `${lib}/bundles/assets/adf-${lib}/i18n/`
from: `lib/${lib}/i18n/`,
to: `lib/${lib}/bundles/assets/adf-${lib}/i18n/`
}
})
]),
+10 -10
View File
@@ -1,19 +1,19 @@
const ExtractTextPlugin = require("extract-text-webpack-plugin");
const extractScss = new ExtractTextPlugin('./dist/core/prebuilt-themes/[name].css');
const extractScss = new ExtractTextPlugin('./lib/dist/core/prebuilt-themes/[name].css');
module.exports = {
entry: {
'adf-blue-orange': './core/styles/prebuilt/adf-blue-orange.scss',
'adf-blue-purple': './core/styles/prebuilt/adf-blue-purple.scss',
'adf-cyan-orange': './core/styles/prebuilt/adf-cyan-orange.scss',
'adf-cyan-purple': './core/styles/prebuilt/adf-cyan-purple.scss',
'adf-green-purple': './core/styles/prebuilt/adf-green-purple.scss',
'adf-green-orange': './core/styles/prebuilt/adf-green-orange.scss',
'adf-pink-bluegrey': './core/styles/prebuilt/adf-pink-bluegrey.scss',
'adf-indigo-pink': './core/styles/prebuilt/adf-indigo-pink.scss',
'adf-purple-green': './core/styles/prebuilt/adf-purple-green.scss'
'adf-blue-orange': './lib/core/styles/prebuilt/adf-blue-orange.scss',
'adf-blue-purple': './lib/core/styles/prebuilt/adf-blue-purple.scss',
'adf-cyan-orange': './lib/core/styles/prebuilt/adf-cyan-orange.scss',
'adf-cyan-purple': './lib/core/styles/prebuilt/adf-cyan-purple.scss',
'adf-green-purple': './lib/core/styles/prebuilt/adf-green-purple.scss',
'adf-green-orange': './lib/core/styles/prebuilt/adf-green-orange.scss',
'adf-pink-bluegrey': './lib/core/styles/prebuilt/adf-pink-bluegrey.scss',
'adf-indigo-pink': './lib/core/styles/prebuilt/adf-indigo-pink.scss',
'adf-purple-green': './lib/core/styles/prebuilt/adf-purple-green.scss'
},
output: {
@@ -1,13 +1,40 @@
<div *ngIf="folderNode" data-automation-id="breadcrumb" class="adf-breadcrumb-container">
<li *ngFor="let item of route; let last = last"
<nav *ngIf="folderNode" data-automation-id="breadcrumb" class="adf-breadcrumb-container" role="list">
<button
*ngIf="hasPreviousNodes()"
tabindex="0"
class="adf-breadcrumb-dropdown-trigger"
(click)="open()">
<div class="adf-breadcrumb-dropdown-trigger-icon">
<mat-icon [class.isRoot]="!hasPreviousNodes()">folder</mat-icon>
<mat-icon [class.isRoot]="!hasPreviousNodes()" class="adf-breadcrumb-dropdown-trigger-arrow">arrow_drop_down</mat-icon>
</div>
</button>
<mat-select
#dropdown
*ngIf="hasPreviousNodes()"
class="adf-breadcrumb-dropdown-path"
tabindex="0">
<mat-option
*ngFor="let node of previousNodes;"
(click)="onRoutePathClick(node, $event)"
class="adf-breadcrumb-path-option"
tabindex="0">
{{ node.name | translate }}
</mat-option>
</mat-select>
<div *ngFor="let item of lastNodes; let last = last"
[class.active]="last"
[ngSwitch]="last"
title="{{ item.name | translate }}"
class="adf-breadcrumb-item">
class="adf-breadcrumb-item"
role="listitem">
<a *ngSwitchDefault href="#" [attr.data-automation-id]="'breadcrumb_' + item.name"
class="adf-breadcrumb-item-anchor"
(click)="onRoutePathClick(item, $event)">
class="adf-breadcrumb-item-anchor"
(click)="onRoutePathClick(item, $event)">
{{ item.name | translate }}
</a>
@@ -18,12 +45,13 @@
<mat-icon class="adf-breadcrumb-item-chevron" *ngIf="!last">
chevron_right
</mat-icon>
</li>
</div>
<div *ngIf="!folderNode && hasRoot">
<li class="adf-breadcrumb-item">
</div>
</nav>
<nav *ngIf="!folderNode && hasRoot" data-automation-id="breadcrumb" role="navigation">
<div class="adf-breadcrumb-item active" role="listitem">
<div class="adf-breadcrumb-item-current">
{{ root | translate }}
</div>
</li>
</div>
</div>
</nav>
@@ -1,17 +1,21 @@
$breadcrumb-chevron-spacer: 2px;
@mixin adf-breadcrumb-theme($theme) {
$primary: map-get($theme, primary);
$accent: map-get($theme, accent);
$warn: map-get($theme, warn);
$foreground: map-get($theme, foreground);
$background: map-get($theme, background);
$breadcrumb-chevron-spacer: 2px;
.adf-breadcrumb {
display: flex;
display: flex;
flex: 1;
line-height: 24px;
font-size: 14px;
font-weight: 600;
letter-spacing: -0.2px;
letter-spacing: -0.2px;
color: mat-color($foreground, text, 0.54);
overflow: hidden;
&-container {
margin: 0;
@@ -20,32 +24,92 @@ $breadcrumb-chevron-spacer: 2px;
cursor: default;
display: flex;
overflow: hidden;
height: 25px;
}
&-dropdown {
&-path {
width: 0;
height: 0;
overflow: hidden;
margin-top: 35px;
&.mat-select {
width: 0;
}
}
&-trigger {
cursor: pointer;
padding: 0;
border: none;
background: transparent;
width: 30px;
margin-top: 2px;
margin-right: 5px;
&:focus {
color: mat-color($primary);
outline: none;
}
&-icon {
position: relative;
}
&-arrow {
font-size: 17px;
position: absolute;
left: 4px;
top: 4px;
color: white;
z-index: 2;
}
&-arrow.isRoot {
visibility: hidden;
}
&-arrow.focus {
border: none;
}
}
&-trigger.isRoot {
cursor: not-allowed;
}
}
&-item {
padding-right: $breadcrumb-chevron-spacer;
overflow: hidden;
display: flex;
line-height: 24px;
line-height: 33px;
font-size: 14px;
font-weight: 600;
letter-spacing: -0.2px;
text-align: left;
opacity: 0.6;
flex: 0 10 auto;
min-width: 35px;
text-overflow: ellipsis;
&:hover,
&.active {
opacity: 1;
}
&.active {
flex: 1 0 auto;
}
flex: 1 1 auto;
color: mat-color($foreground, text, 0.87);
min-width: initial;
width: auto;
}
&-chevron {
opacity: 1;
margin-top: 9px;
font-size: 17px;
}
&.mat-primary {
@@ -61,17 +125,27 @@ $breadcrumb-chevron-spacer: 2px;
}
&-anchor {
box-sizing: border-box;
color: inherit;
text-decoration: none;
display: block;
display: inline-block;
width: 100%;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
flex: 0 1 auto;
}
&.active {
display: block;
&-current {
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
}
&-path-option {
&.mat-option {
background-color: mat-color($background, card);
}
}
}
@@ -16,14 +16,13 @@
*/
import { CUSTOM_ELEMENTS_SCHEMA, SimpleChange } from '@angular/core';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { PathElementEntity } from 'alfresco-js-api';
import { DataTableModule } from '@alfresco/adf-core';
import { setupTestBed } from '@alfresco/adf-core';
import { fakeNodeWithCreatePermission } from '../mock';
import { CustomResourcesService, DocumentListService, DocumentListComponent } from '../document-list';
import { DocumentListComponent } from '../document-list';
import { BreadcrumbComponent } from './breadcrumb.component';
declare let jasmine: any;
import { ContentTestingModule } from '../testing/content.testing.module';
describe('Breadcrumb', () => {
@@ -31,33 +30,21 @@ describe('Breadcrumb', () => {
let fixture: ComponentFixture<BreadcrumbComponent>;
let documentList: DocumentListComponent;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
DataTableModule
],
declarations: [
DocumentListComponent,
BreadcrumbComponent
],
providers: [
DocumentListService,
CustomResourcesService
],
schemas: [
CUSTOM_ELEMENTS_SCHEMA
]
}).compileComponents();
}));
setupTestBed({
imports: [ContentTestingModule],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
});
beforeEach(() => {
fixture = TestBed.createComponent(BreadcrumbComponent);
component = fixture.componentInstance;
documentList = TestBed.createComponent<DocumentListComponent>(DocumentListComponent).componentInstance;
});
afterEach(() => {
fixture.destroy();
});
it('should prevent default click behavior', () => {
let event = jasmine.createSpyObj('event', ['preventDefault']);
component.onRoutePathClick(null, event);
@@ -15,16 +15,8 @@
* limitations under the License.
*/
import {
Component,
EventEmitter,
Input,
OnChanges,
Output,
SimpleChanges,
ViewEncapsulation,
OnInit
} from '@angular/core';
import { Component, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges, ViewChild, ViewEncapsulation } from '@angular/core';
import { MatSelect } from '@angular/material';
import { MinimalNodeEntryEntity, PathElementEntity } from 'alfresco-js-api';
import { DocumentListComponent } from '../document-list';
@@ -70,6 +62,16 @@ export class BreadcrumbComponent implements OnInit, OnChanges {
@Input()
transform: (node) => any;
@ViewChild('dropdown')
dropdown: MatSelect;
/** Maximum number of nodes to display before wrapping them with a dropdown element. */
@Input()
maxItems: number;
previousNodes: PathElementEntity[];
lastNodes: PathElementEntity[];
route: PathElementEntity[] = [];
get hasRoot(): boolean {
@@ -78,7 +80,7 @@ export class BreadcrumbComponent implements OnInit, OnChanges {
/** Emitted when the user clicks on a breadcrumb. */
@Output()
navigate: EventEmitter<PathElementEntity> = new EventEmitter<PathElementEntity>();
navigate = new EventEmitter<PathElementEntity>();
ngOnInit() {
this.transform = this.transform ? this.transform : null;
@@ -95,6 +97,28 @@ export class BreadcrumbComponent implements OnInit, OnChanges {
let node = this.transform ? this.transform(this.folderNode) : this.folderNode;
this.route = this.parseRoute(node);
}
this.recalculateNodes();
}
protected recalculateNodes(): void {
if (this.maxItems && this.route.length > this.maxItems) {
this.lastNodes = this.route.slice(this.route.length - this.maxItems);
this.previousNodes = this.route.slice(0, this.route.length - this.maxItems);
this.previousNodes.reverse();
} else {
this.lastNodes = this.route;
this.previousNodes = null;
}
}
open(): void {
if (this.dropdown) {
this.dropdown.open();
}
}
hasPreviousNodes(): boolean {
return this.previousNodes ? true : false;
}
parseRoute(node: MinimalNodeEntryEntity): PathElementEntity[] {
@@ -2,7 +2,7 @@
<button
tabindex="0"
class="adf-dropdown-breadcumb-trigger"
class="adf-dropdown-breadcrumb-trigger"
(click)="open()"
data-automation-id="dropdown-breadcrumb-trigger">
<mat-icon [class.isRoot]="!hasPreviousNodes()">folder</mat-icon>
@@ -10,7 +10,7 @@
<mat-icon class="adf-dropdown-breadcrumb-item-chevron">chevron_right</mat-icon>
<mat-select
#select
#dropdown
*ngIf="hasPreviousNodes()"
class="adf-dropdown-breadcrumb-path"
tabindex="0"
@@ -22,7 +22,7 @@
class="adf-dropdown-breadcrumb-path-option"
tabindex="0"
data-automation-class="dropdown-breadcrumb-path-option">
{{ node.name }}
{{ node.name | translate }}
</mat-option>
</mat-select>
@@ -1,4 +1,3 @@
@mixin adf-breadcrumb-dropdown-theme($theme) {
$primary: map-get($theme, primary);
$dropdownHorizontalOffset: 30px;
@@ -10,18 +9,23 @@
width: 100%;
}
&-dropdown-breadcumb-trigger {
&-dropdown-breadcrumb-trigger {
cursor: pointer;
padding: 0;
border: none;
background: transparent;
&:focus {
color: mat-color($primary);
outline: none;
}
}
&-dropdown-breadcrumb-item-chevron {
margin-top: 5px;
}
&-dropdown-breadcumb-trigger.isRoot {
&-dropdown-breadcrumb-trigger.isRoot {
cursor: not-allowed;
}
@@ -39,7 +43,6 @@
&-current-folder {
text-align: left;
line-height: 33px;
margin-left: $dropdownHorizontalOffset;
white-space: nowrap;
overflow: hidden;
@@ -18,10 +18,11 @@
import { CUSTOM_ELEMENTS_SCHEMA, SimpleChange } from '@angular/core';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { DataTableModule } from '@alfresco/adf-core';
import { setupTestBed } from '@alfresco/adf-core';
import { fakeNodeWithCreatePermission } from '../mock';
import { CustomResourcesService, DocumentListComponent, DocumentListService } from '../document-list';
import { DocumentListComponent } from '../document-list';
import { DropdownBreadcrumbComponent } from './dropdown-breadcrumb.component';
import { ContentTestingModule } from '../testing/content.testing.module';
describe('DropdownBreadcrumb', () => {
@@ -29,126 +30,156 @@ describe('DropdownBreadcrumb', () => {
let fixture: ComponentFixture<DropdownBreadcrumbComponent>;
let documentList: DocumentListComponent;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
DataTableModule
],
declarations: [
DocumentListComponent,
DropdownBreadcrumbComponent
],
providers: [
DocumentListService,
CustomResourcesService
],
schemas: [
CUSTOM_ELEMENTS_SCHEMA
]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(DropdownBreadcrumbComponent);
component = fixture.componentInstance;
documentList = TestBed.createComponent<DocumentListComponent>(DocumentListComponent).componentInstance;
setupTestBed({
imports: [ContentTestingModule],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
});
beforeEach(async(() => {
fixture = TestBed.createComponent(DropdownBreadcrumbComponent);
component = fixture.componentInstance;
documentList = TestBed.createComponent<DocumentListComponent>(DocumentListComponent).componentInstance;
}));
afterEach(async(() => {
fixture.destroy();
}));
function openSelect() {
const folderIcon = fixture.debugElement.query(By.css('[data-automation-id="dropdown-breadcrumb-trigger"]'));
folderIcon.triggerEventHandler('click', null);
const folderIcon = fixture.debugElement.nativeElement.querySelector('[data-automation-id="dropdown-breadcrumb-trigger"]');
folderIcon.click();
fixture.detectChanges();
}
function triggerComponentChange(fakeNodeData) {
const change = new SimpleChange(null, fakeNodeData, true);
component.ngOnChanges({'folderNode': change});
component.ngOnChanges({ 'folderNode': change });
fixture.detectChanges();
}
function clickOnTheFirstOption() {
const option = fixture.debugElement.query(By.css('[data-automation-class="dropdown-breadcrumb-path-option"]'));
option.triggerEventHandler('click', null);
const option: any = document.querySelector('[id^="mat-option"]');
option.click();
}
it('should display only the current folder name if there is no previous folders', () => {
fakeNodeWithCreatePermission.path.elements = [];
it('should display only the current folder name if there is no previous folders', (done) => {
let fakeNodeWithCreatePermissionInstance = JSON.parse(JSON.stringify(fakeNodeWithCreatePermission));
fakeNodeWithCreatePermissionInstance.path.elements = [];
triggerComponentChange(fakeNodeWithCreatePermission);
openSelect();
triggerComponentChange(fakeNodeWithCreatePermissionInstance);
const currentFolder = fixture.debugElement.query(By.css('[data-automation-id="current-folder"]'));
const path = fixture.debugElement.query(By.css('[data-automation-id="dropdown-breadcrumb-path"]'));
expect(path).toBeNull();
expect(currentFolder).not.toBeNull();
expect(currentFolder.nativeElement.innerText.trim()).toEqual('Test');
fixture.whenStable().then(() => {
openSelect();
const currentFolder = fixture.debugElement.query(By.css('[data-automation-id="current-folder"]'));
const path = fixture.debugElement.query(By.css('[data-automation-id="dropdown-breadcrumb-path"]'));
expect(path).toBeNull();
expect(currentFolder).not.toBeNull();
expect(currentFolder.nativeElement.innerText.trim()).toEqual('Test');
done();
});
});
it('should display only the path in the selectbox', () => {
fakeNodeWithCreatePermission.path.elements = [
it('should display only the path in the selectbox', (done) => {
let fakeNodeWithCreatePermissionInstance = JSON.parse(JSON.stringify(fakeNodeWithCreatePermission));
fakeNodeWithCreatePermissionInstance.path.elements = [
{ id: '1', name: 'Stark Industries' },
{ id: '2', name: 'User Homes' },
{ id: '3', name: 'J.A.R.V.I.S' }
];
triggerComponentChange(fakeNodeWithCreatePermission);
openSelect();
triggerComponentChange(fakeNodeWithCreatePermissionInstance);
const path = fixture.debugElement.query(By.css('[data-automation-id="dropdown-breadcrumb-path"]'));
const options = fixture.debugElement.queryAll(By.css('[data-automation-class="dropdown-breadcrumb-path-option"]'));
expect(path).not.toBeNull();
expect(options.length).toBe(3);
fixture.whenStable().then(() => {
openSelect();
const path = fixture.debugElement.query(By.css('[data-automation-id="dropdown-breadcrumb-path"]'));
const options = fixture.debugElement.queryAll(By.css('[data-automation-class="dropdown-breadcrumb-path-option"]'));
expect(path).not.toBeNull();
expect(options.length).toBe(3);
done();
});
});
it('should display the path in reverse order', () => {
fakeNodeWithCreatePermission.path.elements = [
it('should display the path in reverse order', (done) => {
let fakeNodeWithCreatePermissionInstance = JSON.parse(JSON.stringify(fakeNodeWithCreatePermission));
fakeNodeWithCreatePermissionInstance.path.elements = [
{ id: '1', name: 'Stark Industries' },
{ id: '2', name: 'User Homes' },
{ id: '3', name: 'J.A.R.V.I.S' }
];
triggerComponentChange(fakeNodeWithCreatePermission);
openSelect();
triggerComponentChange(fakeNodeWithCreatePermissionInstance);
const options = fixture.debugElement.queryAll(By.css('[data-automation-class="dropdown-breadcrumb-path-option"]'));
expect(options.length).toBe(3);
expect(options[0].nativeElement.innerText.trim()).toBe('J.A.R.V.I.S');
expect(options[1].nativeElement.innerText.trim()).toBe('User Homes');
expect(options[2].nativeElement.innerText.trim()).toBe('Stark Industries');
fixture.whenStable().then(() => {
openSelect();
fixture.whenStable().then(() => {
const options = fixture.debugElement.queryAll(By.css('[data-automation-class="dropdown-breadcrumb-path-option"]'));
expect(options.length).toBe(3);
expect(options[0].nativeElement.innerText.trim()).toBe('J.A.R.V.I.S');
expect(options[1].nativeElement.innerText.trim()).toBe('User Homes');
expect(options[2].nativeElement.innerText.trim()).toBe('Stark Industries');
done();
});
});
});
it('should emit navigation event when clicking on an option', (done) => {
fakeNodeWithCreatePermission.path.elements = [{ id: '1', name: 'Stark Industries' }];
component.navigate.subscribe(val => {
expect(val).toEqual({ id: '1', name: 'Stark Industries' });
done();
let fakeNodeWithCreatePermissionInstance = JSON.parse(JSON.stringify(fakeNodeWithCreatePermission));
fakeNodeWithCreatePermissionInstance.path.elements = [{ id: '1', name: 'Stark Industries' }];
triggerComponentChange(fakeNodeWithCreatePermissionInstance);
fixture.whenStable().then(() => {
openSelect();
fixture.whenStable().then(() => {
component.navigate.subscribe(val => {
expect(val).toEqual({ id: '1', name: 'Stark Industries' });
done();
});
clickOnTheFirstOption();
});
});
triggerComponentChange(fakeNodeWithCreatePermission);
openSelect();
clickOnTheFirstOption();
});
it('should update document list when clicking on an option', () => {
it('should update document list when clicking on an option', (done) => {
spyOn(documentList, 'loadFolderByNodeId').and.stub();
component.target = documentList;
fakeNodeWithCreatePermission.path.elements = [{ id: '1', name: 'Stark Industries' }];
triggerComponentChange(fakeNodeWithCreatePermission);
openSelect();
let fakeNodeWithCreatePermissionInstance = JSON.parse(JSON.stringify(fakeNodeWithCreatePermission));
fakeNodeWithCreatePermissionInstance.path.elements = [{ id: '1', name: 'Stark Industries' }];
triggerComponentChange(fakeNodeWithCreatePermissionInstance);
clickOnTheFirstOption();
fixture.whenStable().then(() => {
openSelect();
fixture.whenStable().then(() => {
expect(documentList.loadFolderByNodeId).toHaveBeenCalledWith('1');
clickOnTheFirstOption();
expect(documentList.loadFolderByNodeId).toHaveBeenCalledWith('1');
done();
});
});
});
it('should open the selectbox when clicking on the folder icon', async(() => {
triggerComponentChange(fakeNodeWithCreatePermission);
spyOn(component.selectbox, 'open');
it('should open the selectbox when clicking on the folder icon', (done) => {
triggerComponentChange(JSON.parse(JSON.stringify(fakeNodeWithCreatePermission)));
spyOn(component.dropdown, 'open');
openSelect();
fixture.whenStable().then(() => {
openSelect();
expect(component.selectbox.open).toHaveBeenCalled();
}));
fixture.whenStable().then(() => {
expect(component.dropdown.open).toHaveBeenCalled();
done();
});
});
});
});
@@ -17,7 +17,7 @@
import { Component, OnChanges, SimpleChanges, ViewChild, ViewEncapsulation } from '@angular/core';
import { MatSelect } from '@angular/material';
import { PathElementEntity } from 'alfresco-js-api';
import { PathElementEntity, MinimalNodeEntryEntity } from 'alfresco-js-api';
import { BreadcrumbComponent } from './breadcrumb.component';
@Component({
@@ -31,31 +31,40 @@ import { BreadcrumbComponent } from './breadcrumb.component';
})
export class DropdownBreadcrumbComponent extends BreadcrumbComponent implements OnChanges {
@ViewChild('select')
selectbox: MatSelect;
@ViewChild('dropdown')
dropdown: MatSelect;
currentNode: PathElementEntity;
previousNodes: PathElementEntity[];
ngOnChanges(changes: SimpleChanges): void {
super.ngOnChanges(changes);
if (changes.folderNode) {
let node: MinimalNodeEntryEntity = null;
node = this.transform ? this.transform(changes.folderNode.currentValue) : changes.folderNode.currentValue;
this.route = this.parseRoute(node);
}
if (changes.transform) {
let node = this.transform ? this.transform(this.folderNode) : this.folderNode;
this.route = this.parseRoute(node);
}
this.recalculateNodes();
}
/**
* Calculate the current and previous nodes from the route array
*/
private recalculateNodes(): void {
protected recalculateNodes(): void {
this.currentNode = this.route[this.route.length - 1];
this.previousNodes = this.route.slice(0, this.route.length - 1).reverse();
}
/**
* Opens the selectbox overlay
* Opens the node picker menu
*/
open(): void {
if (this.selectbox) {
this.selectbox.open();
if (this.dropdown) {
this.dropdown.open();
}
}
@@ -11,7 +11,7 @@
</mat-card-content>
<mat-card-footer class="adf-content-metadata-card-footer" fxLayout="row" fxLayoutAlign="space-between stretch">
<div>
<button *ngIf="!readOnly"
<button *ngIf="!readOnly && hasPermission()"
mat-icon-button
(click)="toggleEdit()"
[attr.title]="'CORE.METADATA.ACTIONS.EDIT' | translate"
@@ -17,46 +17,24 @@
/*tslint:disable: ban*/
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { MinimalNodeEntryEntity } from 'alfresco-js-api';
import { ContentMetadataCardComponent } from './content-metadata-card.component';
import { ContentMetadataComponent } from '../content-metadata/content-metadata.component';
import { MatExpansionModule, MatCardModule, MatButtonModule, MatIconModule } from '@angular/material';
import { ContentMetadataService } from '../../services/content-metadata.service';
import { BasicPropertiesService } from '../../services/basic-properties.service';
import { PropertyGroupTranslatorService } from '../../services/property-groups-translator.service';
import { PropertyDescriptorsService } from '../../services/property-descriptors.service';
import { ContentMetadataConfigFactory } from '../../services/config/content-metadata-config.factory';
import { setupTestBed, PermissionsEnum } from '@alfresco/adf-core';
import { ContentTestingModule } from '../../../testing/content.testing.module';
describe('ContentMetadataCardComponent', () => {
let component: ContentMetadataCardComponent,
fixture: ComponentFixture<ContentMetadataCardComponent>,
node: MinimalNodeEntryEntity,
preset = 'custom-preset';
let component: ContentMetadataCardComponent;
let fixture: ComponentFixture<ContentMetadataCardComponent>;
let node: MinimalNodeEntryEntity;
let preset = 'custom-preset';
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
MatExpansionModule,
MatCardModule,
MatButtonModule,
MatIconModule
],
declarations: [
ContentMetadataCardComponent,
ContentMetadataComponent
],
providers: [
ContentMetadataService,
BasicPropertiesService,
PropertyGroupTranslatorService,
ContentMetadataConfigFactory,
PropertyDescriptorsService
]
}).compileComponents();
}));
setupTestBed({
imports: [ContentTestingModule]
});
beforeEach(() => {
fixture = TestBed.createComponent(ContentMetadataCardComponent);
@@ -139,6 +117,7 @@ describe('ContentMetadataCardComponent', () => {
it('should toggle editable by clicking on the button', () => {
component.editable = true;
component.node.allowableOperations = [PermissionsEnum.UPDATE];
fixture.detectChanges();
const button = fixture.debugElement.query(By.css('[data-automation-id="mata-data-card-toggle-edit"]'));
@@ -177,16 +156,29 @@ describe('ContentMetadataCardComponent', () => {
expect(buttonLabel.nativeElement.innerText.trim()).toBe('ADF_VIEWER.SIDEBAR.METADATA.LESS_INFORMATION');
});
it('should show the edit button by default', () => {
const button = fixture.debugElement.query(By.css('[data-automation-id="mata-data-card-toggle-edit"]'));
expect(button).not.toBeNull();
});
it('should hode the edit button in readOnly is true', () => {
it('should hide the edit button in readOnly is true', () => {
component.readOnly = true;
fixture.detectChanges();
const button = fixture.debugElement.query(By.css('[data-automation-id="mata-data-card-toggle-edit"]'));
expect(button).toBeNull();
});
it('should hide the edit button if node does not have `update` permissions', () => {
component.readOnly = false;
component.node.allowableOperations = null;
fixture.detectChanges();
const button = fixture.debugElement.query(By.css('[data-automation-id="mata-data-card-toggle-edit"]'));
expect(button).toBeNull();
});
it('should show the edit button if node does has `update` permissions', () => {
component.readOnly = false;
component.node.allowableOperations = [PermissionsEnum.UPDATE];
fixture.detectChanges();
const button = fixture.debugElement.query(By.css('[data-automation-id="mata-data-card-toggle-edit"]'));
expect(button).not.toBeNull();
});
});
@@ -17,6 +17,7 @@
import { Component, Input, ViewEncapsulation } from '@angular/core';
import { MinimalNodeEntryEntity } from 'alfresco-js-api';
import { ContentService, PermissionsEnum } from '@alfresco/adf-core';
@Component({
selector: 'adf-content-metadata-card',
@@ -44,6 +45,9 @@ export class ContentMetadataCardComponent {
editable: boolean = false;
expanded: boolean = false;
constructor(private contentService: ContentService) {
}
toggleEdit(): void {
this.editable = !this.editable;
}
@@ -51,4 +55,8 @@ export class ContentMetadataCardComponent {
toggleExpanded(): void {
this.expanded = !this.expanded;
}
hasPermission() {
return this.contentService.hasPermission(this.node, PermissionsEnum.UPDATE);
}
}
@@ -22,51 +22,23 @@ import { SimpleChange } from '@angular/core';
import { By } from '@angular/platform-browser';
import { MinimalNodeEntryEntity } from 'alfresco-js-api';
import { ContentMetadataComponent } from './content-metadata.component';
import { MatExpansionModule, MatButtonModule, MatIconModule } from '@angular/material';
import { ContentMetadataService } from '../../services/content-metadata.service';
import { BasicPropertiesService } from '../../services/basic-properties.service';
import { PropertyGroupTranslatorService } from '../../services/property-groups-translator.service';
import { PropertyDescriptorsService } from '../../services/property-descriptors.service';
import {
CardViewBaseItemModel,
CardViewComponent,
CardViewUpdateService,
NodesApiService,
LogService
} from '@alfresco/adf-core';
import { CardViewBaseItemModel, CardViewComponent, CardViewUpdateService, NodesApiService, LogService, setupTestBed } from '@alfresco/adf-core';
import { ErrorObservable } from 'rxjs/observable/ErrorObservable';
import { Observable } from 'rxjs/Observable';
import { ContentMetadataConfigFactory } from '../../services/config/content-metadata-config.factory';
import { ContentTestingModule } from '../../../testing/content.testing.module';
describe('ContentMetadataComponent', () => {
let component: ContentMetadataComponent;
let fixture: ComponentFixture<ContentMetadataComponent>;
let node: MinimalNodeEntryEntity;
let folderNode: MinimalNodeEntryEntity;
let preset = 'custom-preset';
let component: ContentMetadataComponent,
fixture: ComponentFixture<ContentMetadataComponent>,
node: MinimalNodeEntryEntity,
folderNode: MinimalNodeEntryEntity,
preset = 'custom-preset';
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
MatExpansionModule,
MatButtonModule,
MatIconModule
],
declarations: [
ContentMetadataComponent
],
providers: [
ContentMetadataService,
BasicPropertiesService,
PropertyGroupTranslatorService,
PropertyDescriptorsService,
ContentMetadataConfigFactory,
NodesApiService,
{ provide: LogService, useValue: { error: jasmine.createSpy('error') } }
]
}).compileComponents();
}));
setupTestBed({
imports: [ContentTestingModule],
providers: [{ provide: LogService, useValue: { error: jasmine.createSpy('error') } }]
});
beforeEach(() => {
fixture = TestBed.createComponent(ContentMetadataComponent);
@@ -96,11 +68,9 @@ describe('ContentMetadataComponent', () => {
afterEach(() => {
fixture.destroy();
TestBed.resetTestingModule();
});
describe('Default input param values', () => {
it('should have editable input param as false by default', () => {
expect(component.editable).toBe(false);
});
@@ -115,7 +85,6 @@ describe('ContentMetadataComponent', () => {
});
describe('Folder', () => {
it('should show the folder node', () => {
component.expanded = false;
fixture.detectChanges();
@@ -131,7 +100,6 @@ describe('ContentMetadataComponent', () => {
});
describe('Saving', () => {
it('should save the node on itemUpdate', () => {
const property = <CardViewBaseItemModel> { key: 'property-key', value: 'original-value' },
updateService: CardViewUpdateService = fixture.debugElement.injector.get(CardViewUpdateService),
@@ -158,7 +126,7 @@ describe('ContentMetadataComponent', () => {
updateService.update(property, 'updated-value');
fixture.whenStable().then(() => {
expect(component.node).toBe(expectedNode);
expect(component.node).toEqual(expectedNode);
});
}));
@@ -179,9 +147,7 @@ describe('ContentMetadataComponent', () => {
});
describe('Properties loading', () => {
let expectedNode,
contentMetadataService: ContentMetadataService;
let expectedNode, contentMetadataService: ContentMetadataService;
beforeEach(() => {
expectedNode = Object.assign({}, node, { name: 'some-modified-value' });
@@ -267,22 +233,5 @@ describe('ContentMetadataComponent', () => {
expect(basicPropertiesComponent.displayEmpty).toBe(false);
});
}));
it('should be performed again if property updating occured, since the originally passed node has changed, so the previously calculated properties', () => {
const property = <CardViewBaseItemModel> { key: 'property-key', value: 'original-value' },
updateService = fixture.debugElement.injector.get(CardViewUpdateService),
nodesApiService = TestBed.get(NodesApiService);
spyOn(nodesApiService, 'updateNode').and.callFake(() => Observable.of(node));
spyOn(contentMetadataService, 'getBasicProperties');
component.ngOnChanges({ node: new SimpleChange(null, node, true) });
updateService.update(property, 'updated-value');
component.ngOnChanges({ expanded: new SimpleChange(false, true, false) });
component.ngOnChanges({ expanded: new SimpleChange(true, false, false) });
component.ngOnChanges({ expanded: new SimpleChange(false, true, false) });
expect(contentMetadataService.getBasicProperties).toHaveBeenCalledTimes(2);
});
});
});
@@ -15,12 +15,13 @@
* limitations under the License.
*/
import { Component, Input, OnChanges, OnInit, SimpleChanges, SimpleChange, ViewEncapsulation } from '@angular/core';
import { Component, Input, OnChanges, OnDestroy, OnInit, SimpleChanges, ViewEncapsulation } from '@angular/core';
import { MinimalNodeEntryEntity } from 'alfresco-js-api';
import { Observable } from 'rxjs/Observable';
import { CardViewItem, NodesApiService, LogService, CardViewUpdateService, AlfrescoApiService } from '@alfresco/adf-core';
import { ContentMetadataService } from '../../services/content-metadata.service';
import { CardViewGroup } from '../../interfaces/content-metadata.interfaces';
import { Subscription } from 'rxjs/Rx';
@Component({
selector: 'adf-content-metadata',
@@ -29,61 +30,78 @@ import { CardViewGroup } from '../../interfaces/content-metadata.interfaces';
host: { 'class': 'adf-content-metadata' },
encapsulation: ViewEncapsulation.None
})
export class ContentMetadataComponent implements OnChanges, OnInit {
export class ContentMetadataComponent implements OnChanges, OnInit, OnDestroy {
/** (required) The node entity to fetch metadata about */
@Input()
node: MinimalNodeEntryEntity;
/** Toggles whether the edit button should be shown */
@Input()
editable: boolean = false;
/** Toggles whether to display empty values in the card view */
@Input()
displayEmpty: boolean = false;
/** Toggles between expanded (ie, full information) and collapsed
* (ie, reduced information) in the display
*/
@Input()
expanded: boolean = false;
/** The multi parameter of the underlying material expansion panel */
@Input()
multi = false;
/** Name of the metadata preset, which defines aspects and their properties */
@Input()
preset: string;
nodeHasBeenUpdated: boolean = false;
basicProperties$: Observable<CardViewItem[]>;
groupedProperties$: Observable<CardViewGroup[]>;
disposableNodeUpdate: Subscription;
constructor(private contentMetadataService: ContentMetadataService,
private cardViewUpdateService: CardViewUpdateService,
private nodesApi: NodesApiService,
private logService: LogService,
private apiService: AlfrescoApiService) {}
constructor(
private contentMetadataService: ContentMetadataService,
private cardViewUpdateService: CardViewUpdateService,
private nodesApiService: NodesApiService,
private logService: LogService,
private alfrescoApiService: AlfrescoApiService
) {}
ngOnInit() {
this.cardViewUpdateService.itemUpdated$
this.disposableNodeUpdate = this.cardViewUpdateService.itemUpdated$
.switchMap(this.saveNode.bind(this))
.subscribe(
(node) => {
this.nodeHasBeenUpdated = true;
this.node = node;
this.apiService.nodeUpdated.next(node);
updatedNode => {
Object.assign(this.node, updatedNode);
this.alfrescoApiService.nodeUpdated.next(this.node);
},
error => this.logService.error(error)
);
this.loadProperties(this.node);
}
ngOnChanges(changes: SimpleChanges) {
const nodeChange: SimpleChange = changes['node'];
if (nodeChange || this.nodeHasBeenUpdated) {
const node = nodeChange && nodeChange.currentValue || this.node;
this.nodeHasBeenUpdated = false;
if (changes.node && !changes.node.firstChange) {
this.loadProperties(changes.node.currentValue);
}
}
private loadProperties(node: MinimalNodeEntryEntity) {
if (node) {
this.basicProperties$ = this.contentMetadataService.getBasicProperties(node);
this.groupedProperties$ = this.contentMetadataService.getGroupedProperties(node, this.preset);
}
}
private saveNode({ changed: nodeBody }): Observable<MinimalNodeEntryEntity> {
return this.nodesApi.updateNode(this.node.id, nodeBody);
return this.nodesApiService.updateNode(this.node.id, nodeBody);
}
ngOnDestroy() {
this.disposableNodeUpdate.unsubscribe();
}
}
@@ -16,102 +16,116 @@
*/
import { async, TestBed } from '@angular/core/testing';
import { AppConfigService, LogService } from '@alfresco/adf-core';
import { AppConfigService, LogService, setupTestBed } from '@alfresco/adf-core';
import { IndifferentConfigService } from './indifferent-config.service';
import { AspectOrientedConfigService } from './aspect-oriented-config.service';
import { LayoutOrientedConfigService } from './layout-oriented-config.service';
import { ContentMetadataConfigFactory } from './content-metadata-config.factory';
import { ContentMetadataConfig } from '../../interfaces/content-metadata.interfaces';
import { HttpClientModule } from '@angular/common/http';
describe('ContentMetadataConfigFactory', () => {
let factory: ContentMetadataConfigFactory,
appConfig: AppConfigService,
config: ContentMetadataConfig;
let factory: ContentMetadataConfigFactory;
let appConfig: AppConfigService;
let config: ContentMetadataConfig;
setupTestBed({
imports: [
HttpClientModule
],
providers: [
ContentMetadataConfigFactory,
AppConfigService,
{
provide: LogService, useValue: {
error: () => {
}
}
}
]
});
beforeEach(async(() => {
TestBed.configureTestingModule({
providers: [
ContentMetadataConfigFactory,
AppConfigService,
{ provide: LogService, useValue: { error: () => {} }}
]
}).compileComponents();
}));
beforeEach(() => {
factory = TestBed.get(ContentMetadataConfigFactory);
appConfig = TestBed.get(AppConfigService);
});
function setConfig(presetName, presetConfig) {
appConfig.config['content-metadata'] = {
presets: {
[presetName]: presetConfig
}
};
}
afterEach(() => {
TestBed.resetTestingModule();
});
}));
describe('get', () => {
let logService;
beforeEach(() => {
beforeEach(async(() => {
logService = TestBed.get(LogService);
spyOn(logService, 'error').and.stub();
}));
afterEach(() => {
TestBed.resetTestingModule();
});
it('should get back to default preset if no preset is provided as parameter', () => {
config = factory.get();
describe('get', () => {
expect(config).toEqual(jasmine.any(IndifferentConfigService));
it('should get back to default preset if no preset is provided as parameter', async(() => {
config = factory.get();
expect(config).toEqual(jasmine.any(IndifferentConfigService));
}));
it('should get back to default preset if no preset is set', async(() => {
config = factory.get('default');
expect(config).toEqual(jasmine.any(IndifferentConfigService));
expect(logService.error).not.toHaveBeenCalled();
}));
it('should get back to the default preset if the requested preset does not exist', async(() => {
config = factory.get('not-existing-preset');
expect(config).toEqual(jasmine.any(IndifferentConfigService));
}));
it('should log an error message if the requested preset does not exist', async(() => {
config = factory.get('not-existing-preset');
expect(logService.error).toHaveBeenCalledWith('No content-metadata preset for: not-existing-preset');
}));
});
it('should get back to default preset if no preset is set', () => {
config = factory.get('default');
xdescribe('set', () => {
expect(config).toEqual(jasmine.any(IndifferentConfigService));
expect(logService.error).not.toHaveBeenCalled();
function setConfig(presetName, presetConfig) {
appConfig.config['content-metadata'] = {
presets: {
[presetName]: presetConfig
}
};
}
it('should get back the IndifferentConfigService preset if the preset config is indifferent', async(() => {
setConfig('default', '*');
config = factory.get('default');
expect(config).toEqual(jasmine.any(IndifferentConfigService));
}));
it('should get back the AspectOrientedConfigService preset if the preset config is aspect oriented', async(() => {
setConfig('default', { 'exif:exif': '*' });
config = factory.get('default');
expect(config).toEqual(jasmine.any(AspectOrientedConfigService));
}));
it('should get back the LayoutOrientedConfigService preset if the preset config is layout oriented', async(() => {
setConfig('default', []);
config = factory.get('default');
expect(config).toEqual(jasmine.any(LayoutOrientedConfigService));
}));
});
it('should get back to the default preset if the requested preset does not exist', () => {
config = factory.get('not-existing-preset');
expect(config).toEqual(jasmine.any(IndifferentConfigService));
});
it('should log an error message if the requested preset does not exist', () => {
config = factory.get('not-existing-preset');
expect(logService.error).toHaveBeenCalledWith('No content-metadata preset for: not-existing-preset');
});
it('should get back the IndifferentConfigService preset if the preset config is indifferent', () => {
setConfig('default', '*');
config = factory.get('default');
expect(config).toEqual(jasmine.any(IndifferentConfigService));
});
it('should get back the AspectOrientedConfigService preset if the preset config is aspect oriented', () => {
setConfig('default', { 'exif:exif' : '*'});
config = factory.get('default');
expect(config).toEqual(jasmine.any(AspectOrientedConfigService));
});
it('should get back the LayoutOrientedConfigService preset if the preset config is layout oriented', () => {
setConfig('default', []);
config = factory.get('default');
expect(config).toEqual(jasmine.any(LayoutOrientedConfigService));
});
});
});
@@ -15,25 +15,22 @@
* limitations under the License.
*/
import { async, TestBed } from '@angular/core/testing';
import { TestBed } from '@angular/core/testing';
import { PropertyDescriptorsService } from './property-descriptors.service';
import { AlfrescoApiService } from '@alfresco/adf-core';
import { AlfrescoApiService, setupTestBed } from '@alfresco/adf-core';
import { Observable } from 'rxjs/Observable';
import { ClassesApi } from 'alfresco-js-api';
import { PropertyGroup } from '../interfaces/content-metadata.interfaces';
import { ContentTestingModule } from '../../testing/content.testing.module';
describe('PropertyDescriptorLoaderService', () => {
let service: PropertyDescriptorsService,
classesApi: ClassesApi;
let service: PropertyDescriptorsService;
let classesApi: ClassesApi;
beforeEach(async(() => {
TestBed.configureTestingModule({
providers: [
PropertyDescriptorsService
]
}).compileComponents();
}));
setupTestBed({
imports: [ContentTestingModule]
});
beforeEach(() => {
service = TestBed.get(PropertyDescriptorsService);
@@ -41,10 +38,6 @@ describe('PropertyDescriptorLoaderService', () => {
classesApi = alfrescoApiService.classesApi;
});
afterEach(() => {
TestBed.resetTestingModule();
});
it('should load the groups passed by paramter', () => {
spyOn(classesApi, 'getClass');
@@ -15,7 +15,7 @@
* limitations under the License.
*/
import { async, TestBed } from '@angular/core/testing';
import { TestBed } from '@angular/core/testing';
import { PropertyGroupTranslatorService } from './property-groups-translator.service';
import { Property, OrganisedPropertyGroup } from '../interfaces/content-metadata.interfaces';
import {
@@ -25,25 +25,30 @@ import {
CardViewFloatItemModel,
LogService,
CardViewBoolItemModel,
CardViewDatetimeItemModel
CardViewDatetimeItemModel,
setupTestBed
} from '@alfresco/adf-core';
import { ContentTestingModule } from '../../testing/content.testing.module';
describe('PropertyGroupTranslatorService', () => {
let service: PropertyGroupTranslatorService,
propertyGroups: OrganisedPropertyGroup[],
propertyGroup: OrganisedPropertyGroup,
property: Property,
propertyValues: { [key: string]: any };
let service: PropertyGroupTranslatorService;
let propertyGroups: OrganisedPropertyGroup[];
let propertyGroup: OrganisedPropertyGroup;
let property: Property;
let propertyValues: { [key: string]: any };
beforeEach(async(() => {
TestBed.configureTestingModule({
providers: [
PropertyGroupTranslatorService,
{ provide: LogService, useValue: { error: () => {} }}
]
}).compileComponents();
}));
setupTestBed({
imports: [ContentTestingModule],
providers: [
{
provide: LogService, useValue: {
error: () => {
}
}
}
]
});
beforeEach(() => {
service = TestBed.get(PropertyGroupTranslatorService);
@@ -56,10 +61,12 @@ describe('PropertyGroupTranslatorService', () => {
mandatory: false,
multiValued: false
};
propertyGroup = {
title: 'Faro Automated Solutions',
properties: [property]
};
propertyGroups = [];
});
@@ -78,14 +85,14 @@ describe('PropertyGroupTranslatorService', () => {
mandatory: false,
multiValued: false
},
{
name: 'FAS:ALOY',
title: 'title',
dataType: 'd:text',
defaultValue: 'defaultValue',
mandatory: false,
multiValued: false
}];
{
name: 'FAS:ALOY',
title: 'title',
dataType: 'd:text',
defaultValue: 'defaultValue',
mandatory: false,
multiValued: false
}];
propertyGroups.push(propertyGroup);
propertyValues = { 'FAS:PLAGUE': 'The Chariot Line' };
@@ -136,7 +143,10 @@ describe('PropertyGroupTranslatorService', () => {
property.title = 'The Faro Plague';
property.dataType = 'daemonic:scorcher';
property.defaultValue = 'Daemonic beast';
propertyGroups.push(propertyGroup);
propertyValues = { 'FAS:PLAGUE': 'The Chariot Line' };
propertyGroups.push(Object.assign({}, propertyGroup));
service.translateToCardViewGroups(propertyGroups, propertyValues);
expect(logService.error).toHaveBeenCalledWith('Unknown type for mapping: daemonic:scorcher');
@@ -147,7 +157,12 @@ describe('PropertyGroupTranslatorService', () => {
property.title = 'The Faro Plague';
property.dataType = 'daemonic:scorcher';
property.defaultValue = 'Daemonic beast';
propertyGroups.push(propertyGroup);
propertyGroups.push({
title: 'Faro Automated Solutions',
properties: [property]
});
propertyValues = { 'FAS:PLAGUE': 'The Chariot Line' };
const cardViewGroup = service.translateToCardViewGroups(propertyGroups, propertyValues);
const cardViewProperty: CardViewTextItemModel = <CardViewTextItemModel> cardViewGroup[0].properties[0];
@@ -15,15 +15,15 @@
* limitations under the License.
*/
import { async, TestBed, fakeAsync, tick } from '@angular/core/testing';
import { TestBed, fakeAsync, tick } from '@angular/core/testing';
import { MinimalNodeEntryEntity, SitePaging } from 'alfresco-js-api';
import { AppConfigService, SitesService } from '@alfresco/adf-core';
import { AppConfigService, SitesService, setupTestBed } from '@alfresco/adf-core';
import { DocumentListService } from '../document-list/services/document-list.service';
import { CustomResourcesService } from '../document-list/services/custom-resources.service';
import { ContentNodeDialogService } from './content-node-dialog.service';
import { MatDialog } from '@angular/material';
import { Observable } from 'rxjs/Observable';
import { Subject } from 'rxjs/Subject';
import { ContentTestingModule } from '../testing/content.testing.module';
const fakeNode: MinimalNodeEntryEntity = <MinimalNodeEntryEntity> {
id: 'fake',
@@ -60,18 +60,9 @@ describe('ContentNodeDialogService', () => {
let spyOnDialogOpen: jasmine.Spy;
let afterOpenObservable: Subject<any>;
beforeEach(async(() => {
TestBed.configureTestingModule({
providers: [
ContentNodeDialogService,
DocumentListService,
CustomResourcesService,
SitesService,
MatDialog
]
}).compileComponents();
}));
setupTestBed({
imports: [ContentTestingModule]
});
beforeEach(() => {
let appConfig: AppConfigService = TestBed.get(AppConfigService);
@@ -89,7 +80,6 @@ describe('ContentNodeDialogService', () => {
error: new Subject<any>()
}
});
});
it('should not open the lock node dialog if have no permission', () => {
@@ -32,6 +32,7 @@ import 'rxjs/operator/switchMap';
@Injectable()
export class ContentNodeDialogService {
/** Emitted when an error occurs. */
@Output()
error: EventEmitter<any> = new EventEmitter<any>();
@@ -42,8 +43,11 @@ export class ContentNodeDialogService {
private translation: TranslationService) {
}
/** Opens a file browser at a chosen folder location. */
/** @param folderNodeId ID of the folder to use */
/**
* Opens a file browser at a chosen folder location.
* @param folderNodeId ID of the folder to use
* @returns Information about the selected file(s)
*/
openFileBrowseDialogByFolderId(folderNodeId: string): Observable<MinimalNodeEntryEntity[]> {
return this.documentListService.getFolderNode(folderNodeId).switchMap((node: MinimalNodeEntryEntity) => {
return this.openUploadFileDialog('Choose', node);
@@ -51,9 +55,9 @@ export class ContentNodeDialogService {
}
/**
* Opens a lock node dialog
*
* Opens a lock node dialog.
* @param contentEntry Node to lock
* @returns Error/status message (if any)
*/
public openLockNodeDialog(contentEntry: MinimalNodeEntryEntity): Subject<string> {
const observable: Subject<string> = new Subject<string>();
@@ -76,33 +80,44 @@ export class ContentNodeDialogService {
return observable;
}
/** Opens a file browser at a chosen site location. */
/**
* Opens a file browser at a chosen site location.
* @returns Information about the selected file(s)
*/
openFileBrowseDialogBySite(): Observable<MinimalNodeEntryEntity[]> {
return this.siteService.getSites().switchMap((response: SitePaging) => {
return this.openFileBrowseDialogByFolderId(response.list.entries[0].entry.guid);
});
}
/** Opens a folder browser at a chosen site location. */
/**
* Opens a folder browser at a chosen site location.
* @returns Information about the selected folder(s)
*/
openFolderBrowseDialogBySite(): Observable<MinimalNodeEntryEntity[]> {
return this.siteService.getSites().switchMap((response: SitePaging) => {
return this.openFolderBrowseDialogByFolderId(response.list.entries[0].entry.guid);
});
}
/** Opens a folder browser at a chosen folder location. */
/** @param folderNodeId ID of the folder to use */
/**
* Opens a folder browser at a chosen folder location.
* @param folderNodeId ID of the folder to use
* @returns Information about the selected folder(s)
*/
openFolderBrowseDialogByFolderId(folderNodeId: string): Observable<MinimalNodeEntryEntity[]> {
return this.documentListService.getFolderNode(folderNodeId).switchMap((node: MinimalNodeEntryEntity) => {
return this.openUploadFolderDialog('Choose', node);
});
}
/** Opens a dialog to copy or move an item to a new location. */
/** @param action Name of the action (eg, "Copy" or "Move") to show in the title */
/** @param contentEntry Item to be copied or moved */
/** @param permission Permission for the operation */
/**
* Opens a dialog to copy or move an item to a new location.
* @param action Name of the action (eg, "Copy" or "Move") to show in the title
* @param contentEntry Item to be copied or moved
* @param permission Permission for the operation
* @returns Information about files that were copied/moved
*/
openCopyMoveDialog(action: string, contentEntry: MinimalNodeEntryEntity, permission?: string): Observable<MinimalNodeEntryEntity[]> {
if (this.contentService.hasPermission(contentEntry, permission)) {
@@ -132,18 +147,22 @@ export class ContentNodeDialogService {
}
}
/** Gets the translation of the dialog title. */
/** @param action Name of the action to display in the dialog title */
/** @param name Name of the item on which the action is being performed */
/**
* Gets the translation of the dialog title.
* @param action Name of the action to display in the dialog title
* @param name Name of the item on which the action is being performed
* @returns Translated version of the title
*/
getTitleTranslation(action: string, name: string): string {
return this.translation.instant(`NODE_SELECTOR.${action.toUpperCase()}_ITEM`, { name });
}
/** Opens a dialog to choose a folder to upload. */
/** @param action Name of the action to show in the title */
/** @param contentEntry Item to upload */
/**
* Opens a dialog to choose folders to upload.
* @param action Name of the action to show in the title
* @param contentEntry Item to upload
* @returns Information about the chosed folder(s)
*/
openUploadFolderDialog(action: string, contentEntry: MinimalNodeEntryEntity): Observable<MinimalNodeEntryEntity[]> {
const select = new Subject<MinimalNodeEntryEntity[]>();
select.subscribe({
@@ -164,10 +183,12 @@ export class ContentNodeDialogService {
return select;
}
/** Opens a dialog to choose a file to upload. */
/** @param action Name of the action to show in the title */
/** @param contentEntry Item to upload */
/**
* Opens a dialog to choose a file to upload.
* @param action Name of the action to show in the title
* @param contentEntry Item to upload
* @returns Information about the chosen file(s)
*/
openUploadFileDialog(action: string, contentEntry: MinimalNodeEntryEntity): Observable<MinimalNodeEntryEntity[]> {
const select = new Subject<MinimalNodeEntryEntity[]>();
select.subscribe({
@@ -64,7 +64,7 @@
}
&-breadcrumb {
.adf-dropdown-breadcumb-trigger {
.adf-dropdown-breadcrumb-trigger {
outline: none;
.mat-icon {
color: mat-color($foreground, base, 0.45);
@@ -19,21 +19,16 @@ import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { async, fakeAsync, tick, ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { MinimalNodeEntryEntity, SiteEntry, SitePaging } from 'alfresco-js-api';
import { SearchService, SitesService } from '@alfresco/adf-core';
import { DataTableModule } from '@alfresco/adf-core';
import { SearchService, SitesService, setupTestBed } from '@alfresco/adf-core';
import { Observable } from 'rxjs/Observable';
import { Observer } from 'rxjs/Observer';
import {
CustomResourcesService,
EmptyFolderContentDirective,
DocumentListComponent,
DocumentListService
} from '../document-list';
import { DropdownSitesComponent } from '../site-dropdown';
import { DropdownBreadcrumbComponent } from '../breadcrumb';
import { ContentNodeSelectorPanelComponent } from './content-node-selector-panel.component';
import { ContentNodeSelectorService } from './content-node-selector.service';
import { NodePaging } from 'alfresco-js-api';
import { ContentTestingModule } from '../testing/content.testing.module';
import { DocumentListService } from '../document-list/services/document-list.service';
import { DocumentListComponent } from '../document-list/components/document-list.component';
const ONE_FOLDER_RESULT = {
list: {
@@ -74,37 +69,13 @@ describe('ContentNodeSelectorComponent', () => {
_observer.next(result);
}
afterEach(() => {
fixture.destroy();
TestBed.resetTestingModule();
setupTestBed({
imports: [ContentTestingModule],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
});
describe('General component features', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
DataTableModule
],
declarations: [
DocumentListComponent,
EmptyFolderContentDirective,
DropdownSitesComponent,
DropdownBreadcrumbComponent,
ContentNodeSelectorPanelComponent
],
providers: [
CustomResourcesService,
SearchService,
DocumentListService,
SitesService,
ContentNodeSelectorService
],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
});
TestBed.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(ContentNodeSelectorPanelComponent);
component = fixture.componentInstance;
@@ -120,6 +91,10 @@ describe('ContentNodeSelectorComponent', () => {
});
});
afterEach(() => {
fixture.destroy();
});
describe('Parameters', () => {
it('should trigger the select event when selection has been made', (done) => {
@@ -518,7 +493,7 @@ describe('ContentNodeSelectorComponent', () => {
expect(component.searchTerm).toBe('');
expect(component.folderIdToShow).toBe('namek');
});
}));
it('should show the current folder\'s content instead of search results if search was not performed', () => {
let documentList = fixture.debugElement.query(By.directive(DocumentListComponent));
@@ -18,74 +18,53 @@
import { MAT_DIALOG_DATA } from '@angular/material';
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { EventEmitter } from '@angular/core';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ContentNodeSelectorComponent } from './content-node-selector.component';
import { ContentNodeSelectorPanelComponent } from './content-node-selector-panel.component';
import { ContentNodeSelectorService } from './content-node-selector.service';
import { MinimalNodeEntryEntity } from 'alfresco-js-api';
import { By } from '@angular/platform-browser';
import {
EmptyFolderContentDirective,
DocumentListComponent,
DocumentListService,
CustomResourcesService
} from '../document-list';
import { ContentService } from '@alfresco/adf-core';
import { setupTestBed, SitesService } from '@alfresco/adf-core';
import { Observable } from 'rxjs/Observable';
import { ContentTestingModule } from '../testing/content.testing.module';
import { DocumentListService } from '../document-list/services/document-list.service';
import { DocumentListComponent } from '../document-list/components/document-list.component';
describe('ContentNodeSelectorDialogComponent', () => {
let component: ContentNodeSelectorComponent;
let fixture: ComponentFixture<ContentNodeSelectorComponent>;
let data: any;
let data: any = {
title: 'Move along citizen...',
actionName: 'move',
select: new EventEmitter<MinimalNodeEntryEntity>(),
rowFilter: () => {
},
imageResolver: () => 'piccolo',
currentFolderId: 'cat-girl-nuku-nuku'
};
function setupTestbed(plusProviders) {
TestBed.configureTestingModule({
imports: [
],
declarations: [
ContentNodeSelectorComponent,
ContentNodeSelectorPanelComponent,
DocumentListComponent,
EmptyFolderContentDirective
],
providers: [
CustomResourcesService,
ContentNodeSelectorService,
ContentNodeSelectorPanelComponent,
DocumentListService,
ContentService,
DocumentListService,
ContentNodeSelectorService,
...plusProviders
],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
});
}
setupTestBed({
imports: [ContentTestingModule],
providers: [
{ provide: MAT_DIALOG_DATA, useValue: data }
],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
});
beforeEach(() => {
const documentListService: DocumentListService = TestBed.get(DocumentListService);
const sitesService: SitesService = TestBed.get(SitesService);
spyOn(documentListService, 'getFolder').and.returnValue(Observable.of({ list: [] }));
spyOn(documentListService, 'getFolderNode').and.returnValue(Observable.of({}));
spyOn(sitesService, 'getSites').and.returnValue(Observable.of({ list: { entries: [] } }));
fixture = TestBed.createComponent(ContentNodeSelectorComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
afterEach(() => {
fixture.destroy();
TestBed.resetTestingModule();
});
beforeEach(async(() => {
data = {
title: 'Move along citizen...',
actionName: 'move',
select: new EventEmitter<MinimalNodeEntryEntity>(),
rowFilter: () => {
},
imageResolver: () => 'piccolo',
currentFolderId: 'cat-girl-nuku-nuku'
};
setupTestbed([{ provide: MAT_DIALOG_DATA, useValue: data }]);
TestBed.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(ContentNodeSelectorComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
describe('Data injecting with the "Material dialog way"', () => {
@@ -98,6 +77,7 @@ describe('ContentNodeSelectorDialogComponent', () => {
it('should have the INJECTED actionName on the name of the choose button', () => {
const actionButton = fixture.debugElement.query(By.css('[data-automation-id="content-node-selector-actions-choose"]'));
expect(component.buttonActionName).toBe('NODE_SELECTOR.MOVE');
expect(actionButton).not.toBeNull();
expect(actionButton.nativeElement.innerText).toBe('NODE_SELECTOR.MOVE');
});
@@ -108,10 +88,14 @@ describe('ContentNodeSelectorDialogComponent', () => {
expect(documentList.componentInstance.currentFolderId).toBe('cat-girl-nuku-nuku');
});
it('should pass through the injected rowFilter to the documentlist', () => {
let documentList = fixture.debugElement.query(By.directive(DocumentListComponent));
expect(documentList).not.toBeNull('Document list should be shown');
expect(documentList.componentInstance.rowFilter).toBe(data.rowFilter);
it('should pass through the injected rowFilter to the documentlist', (done) => {
fixture.whenStable().then(() => {
let documentList = fixture.debugElement.query(By.directive(DocumentListComponent));
expect(documentList).not.toBeNull('Document list should be shown');
expect(documentList.componentInstance.rowFilter).toBe(data.rowFilter);
done();
});
});
it('should pass through the injected imageResolver to the documentlist', () => {
@@ -127,8 +111,10 @@ describe('ContentNodeSelectorDialogComponent', () => {
it('should complete the data stream when user click "CANCEL"', () => {
let cancelButton;
data.select.subscribe(
() => { },
() => { },
() => {
},
() => {
},
() => {
cancelButton = fixture.debugElement.query(By.css('[data-automation-id="content-node-selector-actions-cancel"]'));
expect(cancelButton).not.toBeNull();
@@ -15,10 +15,11 @@
* limitations under the License.
*/
import { async, TestBed } from '@angular/core/testing';
import { TestBed } from '@angular/core/testing';
import { QueryBody } from 'alfresco-js-api';
import { SearchService } from '@alfresco/adf-core';
import { SearchService, setupTestBed } from '@alfresco/adf-core';
import { ContentNodeSelectorService } from './content-node-selector.service';
import { ContentTestingModule } from '../testing/content.testing.module';
class SearchServiceMock {
public query: QueryBody;
@@ -29,17 +30,15 @@ class SearchServiceMock {
describe('ContentNodeSelectorService', () => {
let service: ContentNodeSelectorService,
search: SearchServiceMock;
let service: ContentNodeSelectorService;
let search: SearchServiceMock;
beforeEach(async(() => {
TestBed.configureTestingModule({
providers: [
ContentNodeSelectorService,
{ provide: SearchService, useClass: SearchServiceMock }
]
}).compileComponents();
}));
setupTestBed({
imports: [ContentTestingModule],
providers: [
{ provide: SearchService, useClass: SearchServiceMock }
]
});
beforeEach(() => {
service = TestBed.get(ContentNodeSelectorService);
+3 -1
View File
@@ -44,6 +44,7 @@ import { ContentMetadataConfigFactory } from './content-metadata/services/config
import { BasicPropertiesService } from './content-metadata/services/basic-properties.service';
import { PropertyGroupTranslatorService } from './content-metadata/services/property-groups-translator.service';
import { SearchQueryBuilderService } from './search/search-query-builder.service';
import { SearchFilterService } from './search/components/search-filter/search-filter.service';
@NgModule({
imports: [
@@ -83,7 +84,8 @@ import { SearchQueryBuilderService } from './search/search-query-builder.service
ContentMetadataConfigFactory,
BasicPropertiesService,
PropertyGroupTranslatorService,
SearchQueryBuilderService
SearchQueryBuilderService,
SearchFilterService
],
exports: [
CoreModule,
@@ -1,10 +1,5 @@
<h2 mat-dialog-title>
{{
(editing
? 'CORE.FOLDER_DIALOG.EDIT_FOLDER_TITLE'
: 'CORE.FOLDER_DIALOG.CREATE_FOLDER_TITLE'
) | translate
}}
{{ (editing ? editTitle : createTitle) | translate }}
</h2>
<mat-dialog-content>
@@ -6,6 +6,22 @@
width: 100%;
}
.adf-lock-file-name {
.mat-checkbox-layout{
width: 100%;
}
.mat-checkbox-label{
text-overflow: ellipsis;
overflow: hidden;
}
.mat-checkbox-inner-container{
margin: auto 0;
margin-right: 8px;
}
}
@mixin adf-dialog-theme($theme) {
$primary: map-get($theme, primary);
@@ -15,60 +15,40 @@
* limitations under the License.
*/
import { async, TestBed } from '@angular/core/testing';
import { ComponentFixture } from '@angular/core/testing';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { TestBed } from '@angular/core/testing';
import { async, ComponentFixture } from '@angular/core/testing';
import { MatDialogRef } from '@angular/material';
import { BrowserDynamicTestingModule } from '@angular/platform-browser-dynamic/testing';
import { Observable } from 'rxjs/Observable';
import { NodesApiService, TranslationService } from '@alfresco/adf-core';
import { NodesApiService, setupTestBed } from '@alfresco/adf-core';
import { FolderDialogComponent } from './folder.dialog';
import { Observable } from 'rxjs/Observable';
import { ContentTestingModule } from '../testing/content.testing.module';
import { By } from '@angular/platform-browser';
describe('FolderDialogComponent', () => {
let fixture: ComponentFixture<FolderDialogComponent>;
let component: FolderDialogComponent;
let translationService: TranslationService;
let nodesApi: NodesApiService;
let dialogRef;
let dialogRef = {
close: jasmine.createSpy('close')
};
beforeEach(async(() => {
dialogRef = {
close: jasmine.createSpy('close')
};
TestBed.configureTestingModule({
imports: [
FormsModule,
ReactiveFormsModule,
BrowserDynamicTestingModule
],
declarations: [
FolderDialogComponent
],
providers: [
{ provide: MatDialogRef, useValue: dialogRef }
]
});
// entryComponents are not supported yet on TestBed, that is why this ugly workaround:
// https://github.com/angular/angular/issues/10760
TestBed.overrideModule(BrowserDynamicTestingModule, {
set: { entryComponents: [FolderDialogComponent] }
});
TestBed.compileComponents();
}));
setupTestBed({
imports: [ContentTestingModule],
providers: [
{ provide: MatDialogRef, useValue: dialogRef }
]
});
beforeEach(() => {
dialogRef.close.calls.reset();
fixture = TestBed.createComponent(FolderDialogComponent);
component = fixture.componentInstance;
nodesApi = TestBed.get(NodesApiService);
});
translationService = TestBed.get(TranslationService);
spyOn(translationService, 'get').and.returnValue(Observable.of('message'));
afterEach(() => {
fixture.destroy();
});
describe('Edit', () => {
@@ -91,6 +71,12 @@ describe('FolderDialogComponent', () => {
expect(component.description).toBe('folder-description');
});
it('should have the proper title', () => {
const title = fixture.debugElement.query(By.css('[mat-dialog-title]'));
expect(title === null).toBe(false);
expect(title.nativeElement.innerText.trim()).toBe('CORE.FOLDER_DIALOG.EDIT_FOLDER_TITLE');
});
it('should update form input', () => {
component.form.controls['name'].setValue('folder-name-update');
component.form.controls['description'].setValue('folder-description-update');
@@ -119,7 +105,7 @@ describe('FolderDialogComponent', () => {
);
});
it('should call dialog to close with form data when submit is succesfluly', () => {
it('should call dialog to close with form data when submit is successfully', () => {
const folder = {
data: 'folder-data'
};
@@ -131,6 +117,20 @@ describe('FolderDialogComponent', () => {
expect(dialogRef.close).toHaveBeenCalledWith(folder);
});
it('should emit success output event with folder when submit is succesfull', async(() => {
const folder = { data: 'folder-data' };
let expectedNode = null;
spyOn(nodesApi, 'updateNode').and.returnValue(Observable.of(folder));
component.success.subscribe((node) => { expectedNode = node; });
component.submit();
fixture.whenStable().then(() => {
expect(expectedNode).toBe(folder);
});
}));
it('should not submit if form is invalid', () => {
spyOn(nodesApi, 'updateNode');
@@ -163,6 +163,12 @@ describe('FolderDialogComponent', () => {
fixture.detectChanges();
});
it('should have the proper title', () => {
const title = fixture.debugElement.query(By.css('[mat-dialog-title]'));
expect(title === null).toBe(false);
expect(title.nativeElement.innerText.trim()).toBe('CORE.FOLDER_DIALOG.CREATE_FOLDER_TITLE');
});
it('should init form with empty inputs', () => {
expect(component.name).toBe('');
expect(component.description).toBe('');
@@ -191,12 +197,35 @@ describe('FolderDialogComponent', () => {
properties: {
'cm:title': 'folder-name-update',
'cm:description': 'folder-description-update'
}
},
nodeType: 'cm:folder'
}
);
});
it('should call dialog to close with form data when submit is succesfluly', () => {
it('should submit updated values if form is valid (with custom nodeType)', () => {
spyOn(nodesApi, 'createFolder').and.returnValue(Observable.of({}));
component.form.controls['name'].setValue('folder-name-update');
component.form.controls['description'].setValue('folder-description-update');
component.nodeType = 'cm:sushi';
component.submit();
expect(nodesApi.createFolder).toHaveBeenCalledWith(
'parentNodeId',
{
name: 'folder-name-update',
properties: {
'cm:title': 'folder-name-update',
'cm:description': 'folder-description-update'
},
nodeType: 'cm:sushi'
}
);
});
it('should call dialog to close with form data when submit is successfully', () => {
const folder = {
data: 'folder-data'
};
+22 -4
View File
@@ -42,6 +42,15 @@ export class FolderDialogComponent implements OnInit {
@Output()
error: EventEmitter<any> = new EventEmitter<any>();
/** Emitted when the edit/create folder is successfully created/mmodified
*/
@Output()
success: EventEmitter<any> = new EventEmitter<MinimalNodeEntryEntity>();
editTitle = 'CORE.FOLDER_DIALOG.EDIT_FOLDER_TITLE';
createTitle = 'CORE.FOLDER_DIALOG.CREATE_FOLDER_TITLE';
nodeType = 'cm:folder';
constructor(
private formBuilder: FormBuilder,
private dialog: MatDialogRef<FolderDialogComponent>,
@@ -50,7 +59,13 @@ export class FolderDialogComponent implements OnInit {
@Optional()
@Inject(MAT_DIALOG_DATA)
public data: any
) {}
) {
if (data) {
this.editTitle = data.editTitle || this.editTitle;
this.createTitle = data.createTitle || this.createTitle;
this.nodeType = data.nodeType || this.nodeType;
}
}
get editing(): boolean {
return !!this.data.folder;
@@ -105,8 +120,8 @@ export class FolderDialogComponent implements OnInit {
}
private create(): Observable<MinimalNodeEntryEntity> {
const { name, properties, nodesApi, data: { parentNodeId} } = this;
return nodesApi.createFolder(parentNodeId, { name, properties });
const { name, properties, nodeType, nodesApi, data: { parentNodeId} } = this;
return nodesApi.createFolder(parentNodeId, { name, properties, nodeType });
}
private edit(): Observable<MinimalNodeEntryEntity> {
@@ -121,7 +136,10 @@ export class FolderDialogComponent implements OnInit {
(editing ? this.edit() : this.create())
.subscribe(
(folder: MinimalNodeEntryEntity) => dialog.close(folder),
(folder: MinimalNodeEntryEntity) => {
this.success.emit(folder);
dialog.close(folder);
},
(error) => this.handleError(error)
);
}
@@ -5,20 +5,20 @@
<mat-dialog-content>
<br />
<form [formGroup]="form" (submit)="submit()">
<mat-checkbox [formControl]="form.controls['isLocked']" ngDefaultControl>
<mat-checkbox class="adf-lock-file-name" [formControl]="form.controls['isLocked']" ngDefaultControl>
{{ 'CORE.FILE_DIALOG.FILE_LOCK_CHECKBOX' | translate }} <strong>"{{ nodeName }}"</strong>
</mat-checkbox>
<br />
<div *ngIf="form.value.isLocked">
<mat-checkbox [formControl]="form.controls['allowOwner']" ngDefaultControl>
<mat-checkbox class="adf-lock-file-name" [formControl]="form.controls['allowOwner']" ngDefaultControl>
{{ 'CORE.FILE_DIALOG.ALLOW_OTHERS_CHECKBOX' | translate }}
</mat-checkbox>
<br />
<mat-checkbox [formControl]="form.controls['isTimeLock']" ngDefaultControl>
<mat-checkbox class="adf-lock-file-name" [formControl]="form.controls['isTimeLock']" ngDefaultControl>
{{ 'CORE.FILE_DIALOG.TIME_LOCK_CHECKBOX' | translate }}
</mat-checkbox>
@@ -17,57 +17,38 @@
import moment from 'moment-es6';
import { async, TestBed, fakeAsync, tick } from '@angular/core/testing';
import { TestBed, fakeAsync, tick } from '@angular/core/testing';
import { ComponentFixture } from '@angular/core/testing';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MatDialogRef } from '@angular/material';
import { BrowserDynamicTestingModule } from '@angular/platform-browser-dynamic/testing';
import { Observable } from 'rxjs/Observable';
import { AlfrescoApiService, TranslationService } from '@alfresco/adf-core';
import { AlfrescoApiService, setupTestBed } from '@alfresco/adf-core';
import { NodeLockDialogComponent } from './node-lock.dialog';
import { ContentTestingModule } from '../testing/content.testing.module';
describe('NodeLockDialogComponent', () => {
let fixture: ComponentFixture<NodeLockDialogComponent>;
let component: NodeLockDialogComponent;
let translationService: TranslationService;
let alfrescoApi: AlfrescoApiService;
let expiryDate;
const dialogRef = {
close: jasmine.createSpy('close')
};
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
FormsModule,
ReactiveFormsModule,
BrowserDynamicTestingModule
],
declarations: [
NodeLockDialogComponent
],
providers: [
{ provide: MatDialogRef, useValue: dialogRef }
]
});
TestBed.overrideModule(BrowserDynamicTestingModule, {
set: { entryComponents: [NodeLockDialogComponent] }
});
TestBed.compileComponents();
}));
setupTestBed({
imports: [ContentTestingModule],
providers: [
{ provide: MatDialogRef, useValue: dialogRef }
]
});
beforeEach(() => {
fixture = TestBed.createComponent(NodeLockDialogComponent);
component = fixture.componentInstance;
alfrescoApi = TestBed.get(AlfrescoApiService);
});
translationService = TestBed.get(TranslationService);
spyOn(translationService, 'get').and.returnValue(Observable.of('message'));
afterEach(() => {
fixture.destroy();
});
describe('Node lock dialog component', () => {
@@ -1,22 +1,22 @@
<h1 mat-dialog-title id="adf-share-title" >{{ 'SHARE.TITLE' | translate }} {{fileName}}</h1>
<h1 mat-dialog-title id="adf-share-title">{{ 'SHARE.TITLE' | translate }} {{fileName}}</h1>
<mat-dialog-content>
<span class="spacer"></span>
<mat-slide-toggle [checked]="isFileShared" [disabled]="isDisabled" (change)="onSlideShareChange($event)">
<mat-slide-toggle id="adf-share-toggle" [checked]="isFileShared" [disabled]="isDisabled"
(change)="onSlideShareChange($event)">
{{ 'SHARE.ACTIONS.SHARE' | translate }}
</mat-slide-toggle>
<span class="spacer"></span>
.
<h2 *ngIf="isFileShared">{{ 'SHARE.DESCRIPTION' | translate }}</h2>
<div *ngIf="isFileShared">{{ 'SHARE.ALERT' | translate }}</div>
<form *ngIf="isFileShared">
<mat-form-field class="full-width">
<input class="adf-share-link" matInput placeholder="{{ 'SHARE.PUBLIC-LINK' | translate }}" [value]="baseShareUrl + sharedId">
<input id="adf-share-link" lass="adf-share-link" matInput placeholder="{{ 'SHARE.PUBLIC-LINK' | translate }}"
[value]="baseShareUrl + sharedId">
<mat-icon matPrefix>link</mat-icon>
</mat-form-field>
</form>
@@ -17,93 +17,112 @@
import { async, TestBed } from '@angular/core/testing';
import { ComponentFixture } from '@angular/core/testing';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material';
import { BrowserDynamicTestingModule } from '@angular/platform-browser-dynamic/testing';
import { Observable } from 'rxjs/Observable';
import { ShareDialogComponent } from './share.dialog';
import { ContentTestingModule } from '../testing/content.testing.module';
import { SharedLinksApiService, setupTestBed } from '@alfresco/adf-core';
describe('ShareDialogComponent', () => {
let fixture: ComponentFixture<ShareDialogComponent>;
let spyCreate: any;
let spyDelete: any;
let component: ShareDialogComponent;
let dialogRef;
let sharedLinksApiService: SharedLinksApiService;
const dialogRef = {
close: jasmine.createSpy('close')
};
let data: any = {
node: { entry: { properties: { 'qshare:sharedId': 'example-link' }, name: 'example-name' } }
node: { entry: { properties: { 'qshare:sharedId': 'example-link' }, name: 'example-name' } },
baseShareUrl: 'baseShareUrl-example'
};
beforeEach(async(() => {
dialogRef = {
close: jasmine.createSpy('close')
};
TestBed.configureTestingModule({
imports: [
FormsModule,
ReactiveFormsModule,
BrowserDynamicTestingModule
],
declarations: [
ShareDialogComponent
],
providers: [
{ provide: MatDialogRef, useValue: dialogRef },
{
provide: MAT_DIALOG_DATA,
useValue: data
}
]
});
TestBed.overrideModule(BrowserDynamicTestingModule, {
set: { entryComponents: [ShareDialogComponent] }
});
TestBed.compileComponents();
}));
setupTestBed({
imports: [ContentTestingModule],
providers: [
{ provide: MatDialogRef, useValue: dialogRef },
{ provide: MAT_DIALOG_DATA, useValue: data }
]
});
beforeEach(() => {
fixture = TestBed.createComponent(ShareDialogComponent);
sharedLinksApiService = TestBed.get(SharedLinksApiService);
component = fixture.componentInstance;
fixture.detectChanges();
spyCreate = spyOn(sharedLinksApiService, 'createSharedLinks').and.returnValue(Observable.of({ entry: { id: 'test-sharedId' } }));
spyDelete = spyOn(sharedLinksApiService, 'deleteSharedLink').and.returnValue(Observable.of({}));
});
it('should init the dialog with the file name and baseShareUrl', () => {
expect(component.fileName).toBe('example-name');
expect(component.baseShareUrl).toBe('baseShareUrl-example');
});
it('should init the dialog with the file name and baseShareUrl', async(() => {
component.data = {
baseShareUrl: 'base-url/',
node: { entry: { properties: { 'qshare:sharedId': 'example-link' }, name: 'example-name' } }
};
component.ngOnInit();
fixture.detectChanges();
fixture.whenStable().then(() => {
expect(fixture.nativeElement.querySelector('#adf-share-link').value).toBe('base-url/example-link');
});
}));
describe('public link creation', () => {
it('should not create the public link if it alredy present in the node', () => {
let spyCreate = spyOn(component, 'createSharedLinks').and.returnValue(Observable.of(''));
it('should not create the public link if it already present in the node', () => {
component.data = {
node: { entry: { properties: { 'qshare:sharedId': 'example-link' }, name: 'example-name' } }
};
component.ngOnInit();
expect(spyCreate).not.toHaveBeenCalled();
});
it('should not create the public link if it alredy present in the node', () => {
it('should not create the public link if is not present in the node', () => {
component.data = {
node: { entry: { name: 'example-name' } }
baseShareUrl: 'baseShareUrl-example'
};
let spyCreate = spyOn(component, 'createSharedLinks').and.returnValue(Observable.of(''));
data = {
node: { entry: { name: 'example-name' } }
node: { entry: { properties: {}, name: 'example-name' } },
baseShareUrl: 'baseShareUrl-example'
};
component.ngOnInit();
expect(spyCreate).toHaveBeenCalled();
expect(spyCreate).not.toHaveBeenCalled();
});
it('should be able to delete the shared link', async(() => {
component.data = {
node: { entry: { name: 'example-name', properties: { 'qshare:sharedId': 'example-link' } } },
baseShareUrl: 'baseShareUrl-example'
};
component.ngOnInit();
fixture.detectChanges();
fixture.nativeElement.querySelector('#adf-share-toggle-input').click();
fixture.detectChanges();
fixture.whenStable().then(() => {
expect(spyDelete).toHaveBeenCalled();
expect(component.data.node.entry.properties['qshare:sharedId']).toBeNull();
});
}));
it('should show the toggle disabled when the shareid property is null', async(() => {
component.data = {
node: { entry: { name: 'example-name', properties: { 'qshare:sharedId': null } } },
baseShareUrl: 'baseShareUrl-example'
};
component.ngOnInit();
fixture.detectChanges();
fixture.whenStable().then(() => {
expect(fixture.nativeElement.querySelector('#adf-share-toggle')).not.toBeNull();
expect(fixture.nativeElement.querySelector('#adf-share-toggle.mat-checked')).toBeNull();
});
}));
});
});
+4 -1
View File
@@ -52,7 +52,8 @@ export class ShareDialogComponent implements OnInit {
this.sharedId = this.data.node.entry.properties['qshare:sharedId'];
this.isFileShared = true;
} else {
this.createSharedLinks(this.data.node.entry.id);
this.isFileShared = false;
this.isDisabled = false;
}
}
}
@@ -74,6 +75,7 @@ export class ShareDialogComponent implements OnInit {
this.sharedLinksApiService.createSharedLinks(nodeId).subscribe((sharedLink: SharedLinkEntry) => {
if (sharedLink.entry) {
this.sharedId = sharedLink.entry.id;
this.data.node.entry.properties['qshare:sharedId'] = this.sharedId;
this.isFileShared = true;
this.isDisabled = false;
}
@@ -86,6 +88,7 @@ export class ShareDialogComponent implements OnInit {
deleteSharedLink(sharedId: string) {
this.sharedLinksApiService.deleteSharedLink(sharedId).subscribe(() => {
this.data.node.entry.properties['qshare:sharedId'] = null;
this.isFileShared = false;
this.isDisabled = false;
},
@@ -19,7 +19,7 @@ import { TestBed, ComponentFixture } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { MatDialog } from '@angular/material';
import { Component, DebugElement } from '@angular/core';
import { AlfrescoApiService } from '@alfresco/adf-core';
import { AlfrescoApiService, setupTestBed, CoreModule, AlfrescoApiServiceMock } from '@alfresco/adf-core';
import { DialogModule } from '../dialogs/dialog.module';
import { NodeDownloadDirective } from './node-download.directive';
@@ -40,17 +40,22 @@ describe('NodeDownloadDirective', () => {
let contentService;
let dialogSpy;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [
DialogModule
],
declarations: [
TestComponent,
NodeDownloadDirective
]
});
setupTestBed({
imports: [
CoreModule.forRoot(),
DialogModule
],
providers: [
{ provide: AlfrescoApiService, useClass: AlfrescoApiServiceMock }
// { provide: AppConfigService, useValue: AppConfigServiceMock },
],
declarations: [
TestComponent,
NodeDownloadDirective
]
});
beforeEach(() => {
fixture = TestBed.createComponent(TestComponent);
component = fixture.componentInstance;
element = fixture.debugElement.query(By.directive(NodeDownloadDirective));
@@ -90,6 +95,17 @@ describe('NodeDownloadDirective', () => {
expect(contentService.getContentUrl).toHaveBeenCalledWith(node.entry.id, true);
});
it('should download selected shared node as file', () => {
spyOn(contentService, 'getContentUrl');
const node = { entry: { nodeId: 'shared-node-id', isFile: true } };
component.selection = [node];
fixture.detectChanges();
element.triggerEventHandler('click', null);
expect(contentService.getContentUrl).toHaveBeenCalledWith(node.entry.nodeId, true);
});
it('should download selected files nodes as zip', () => {
const node1 = { entry: { id: 'node-1' } };
const node2 = { entry: { id: 'node-2' } };
@@ -101,6 +117,17 @@ describe('NodeDownloadDirective', () => {
expect(dialogSpy.calls.argsFor(0)[1].data).toEqual({ nodeIds: [ 'node-1', 'node-2' ] });
});
it('should download selected shared files nodes as zip', () => {
const node1 = { entry: { nodeId: 'shared-node-1' } };
const node2 = { entry: { nodeId: 'shared-node-2' } };
component.selection = [node1, node2];
fixture.detectChanges();
element.triggerEventHandler('click', null);
expect(dialogSpy.calls.argsFor(0)[1].data).toEqual({ nodeIds: [ 'shared-node-1', 'shared-node-2' ] });
});
it('should download selected folder node as zip', () => {
const node = { entry: { isFolder: true, id: 'node-id' } };
component.selection = [node];
@@ -86,8 +86,10 @@ export class NodeDownloadDirective {
private downloadFile(node: MinimalNodeEntity) {
if (node && node.entry) {
const contentApi = this.apiService.getInstance().content;
// nodeId for Shared node
const id = (<any> node.entry).nodeId || node.entry.id;
const url = contentApi.getContentUrl(node.entry.id, true);
const url = contentApi.getContentUrl(id, true);
const fileName = node.entry.name;
this.download(url, fileName);
@@ -15,7 +15,7 @@
* limitations under the License.
*/
import { TestBed, ComponentFixture, async, fakeAsync } from '@angular/core/testing';
import { TestBed, ComponentFixture, fakeAsync } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { Component, DebugElement } from '@angular/core';
@@ -24,6 +24,8 @@ import { MinimalNodeEntryEntity } from 'alfresco-js-api';
import { NodeActionsService } from '../document-list/services/node-actions.service';
import { ContentNodeDialogService } from '../content-node-selector/content-node-dialog.service';
import { DocumentListService } from '../document-list/services/document-list.service';
import { setupTestBed } from '../../core/testing';
import { CoreModule } from '@alfresco/adf-core';
const fakeNode: MinimalNodeEntryEntity = <MinimalNodeEntryEntity> {
id: 'fake',
@@ -44,22 +46,20 @@ describe('NodeLock Directive', () => {
let element: DebugElement;
let contentNodeDialogService: ContentNodeDialogService;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [],
providers: [
NodeActionsService,
ContentNodeDialogService,
DocumentListService
],
declarations: [
TestComponent,
NodeLockDirective
]
});
TestBed.compileComponents();
}));
setupTestBed({
imports: [
CoreModule.forRoot()
],
providers: [
NodeActionsService,
ContentNodeDialogService,
DocumentListService
],
declarations: [
TestComponent,
NodeLockDirective
]
});
beforeEach(() => {
fixture = TestBed.createComponent(TestComponent);
@@ -27,6 +27,7 @@ import { ContentNodeDialogService } from '../content-node-selector/content-node-
})
export class NodeLockDirective implements AfterViewInit {
/** Node to lock/unlock. */
@Input('adf-node-lock')
node: MinimalNodeEntryEntity;
@@ -16,36 +16,22 @@
*/
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { async, TestBed } from '@angular/core/testing';
import { DataTableModule } from '@alfresco/adf-core';
import { DocumentListService } from '../../services/document-list.service';
import { CustomResourcesService } from '../../services/custom-resources.service';
import { TestBed } from '@angular/core/testing';
import { setupTestBed } from '@alfresco/adf-core';
import { ContentActionModel } from './../../models/content-action.model';
import { DocumentListComponent } from './../document-list.component';
import { ContentActionListComponent } from './content-action-list.component';
import { ContentTestingModule } from '../../../testing/content.testing.module';
describe('ContentColumnList', () => {
let documentList: DocumentListComponent;
let actionList: ContentActionListComponent;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
DataTableModule
],
declarations: [
DocumentListComponent
],
providers: [
DocumentListService,
CustomResourcesService
],
schemas: [
CUSTOM_ELEMENTS_SCHEMA
]
}).compileComponents();
}));
setupTestBed({
imports: [ContentTestingModule],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
});
beforeEach(() => {
documentList = (TestBed.createComponent(DocumentListComponent).componentInstance as DocumentListComponent);
@@ -42,4 +42,15 @@ export class ContentActionListComponent {
}
return false;
}
unregisterAction(action: ContentActionModel): boolean {
if (this.documentList && action) {
const idx = this.documentList.actions.indexOf(action);
if (idx >= 0) {
this.documentList.actions.splice(idx, 1);
return true;
}
}
return false;
}
}
@@ -15,14 +15,11 @@
* limitations under the License.
*/
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { CUSTOM_ELEMENTS_SCHEMA, SimpleChange } from '@angular/core';
import { EventEmitter } from '@angular/core';
import { async, TestBed } from '@angular/core/testing';
import { ContentService } from '@alfresco/adf-core';
import { DataTableModule } from '@alfresco/adf-core';
import { ContentService, setupTestBed } from '@alfresco/adf-core';
import { FileNode } from '../../../mock';
import { DocumentListService } from '../../services/document-list.service';
import { CustomResourcesService } from '../../services/custom-resources.service';
import { ContentActionHandler } from './../../models/content-action.model';
import { DocumentActionsService } from './../../services/document-actions.service';
import { FolderActionsService } from './../../services/folder-actions.service';
@@ -31,6 +28,7 @@ import { DocumentListComponent } from './../document-list.component';
import { ContentActionListComponent } from './content-action-list.component';
import { ContentActionComponent } from './content-action.component';
import { ContentActionModel } from './../../models/content-action.model';
import { ContentTestingModule } from '../../../testing/content.testing.module';
describe('ContentAction', () => {
@@ -42,23 +40,10 @@ describe('ContentAction', () => {
let contentService: ContentService;
let nodeActionsService: NodeActionsService;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
DataTableModule
],
providers: [
DocumentListService,
CustomResourcesService
],
declarations: [
DocumentListComponent
],
schemas: [
CUSTOM_ELEMENTS_SCHEMA
]
}).compileComponents();
}));
setupTestBed({
imports: [ContentTestingModule],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
});
beforeEach(() => {
contentService = TestBed.get(ContentService);
@@ -96,6 +81,24 @@ describe('ContentAction', () => {
expect(model.icon).toBe(action.icon);
});
it('should update visibility binding', () => {
let action = new ContentActionComponent(actionList, null, null);
action.target = 'document';
action.title = '<title>';
action.icon = '<icon>';
action.visible = true;
action.ngOnInit();
expect(action.documentActionModel.visible).toBeTruthy();
action.visible = false;
action.ngOnChanges({
'visible': new SimpleChange(true, false, false)
});
expect(action.documentActionModel.visible).toBeFalsy();
});
it('should get action handler from document actions service', () => {
let handler = function () {
@@ -17,13 +17,14 @@
/* tslint:disable:component-selector */
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { Component, EventEmitter, Input, OnInit, Output, OnChanges, SimpleChanges, OnDestroy } from '@angular/core';
import { ContentActionHandler } from '../../models/content-action.model';
import { DocumentActionsService } from '../../services/document-actions.service';
import { FolderActionsService } from '../../services/folder-actions.service';
import { ContentActionModel, ContentActionTarget } from './../../models/content-action.model';
import { ContentActionListComponent } from './content-action-list.component';
import { Subscription } from 'rxjs/Subscription';
@Component({
selector: 'content-action',
@@ -33,7 +34,7 @@ import { ContentActionListComponent } from './content-action-list.component';
FolderActionsService
]
})
export class ContentActionComponent implements OnInit {
export class ContentActionComponent implements OnInit, OnChanges, OnDestroy {
/** The title of the action as shown in the menu. */
@Input()
@@ -43,11 +44,15 @@ export class ContentActionComponent implements OnInit {
@Input()
icon: string;
/** Visibility state (see examples). */
@Input()
visible: boolean | Function = true;
/** System actions. Can be "delete", "download", "copy" or "move". */
@Input()
handler: string;
/** Type of item that the action appies to. Can be "document" or "folder" */
/** Type of item that the action applies to. Can be "document" or "folder" */
@Input()
target: string = ContentActionTarget.All;
@@ -61,7 +66,7 @@ export class ContentActionComponent implements OnInit {
/** Is the menu item disabled? */
@Input()
disabled: boolean = false;
disabled: boolean | Function = false;
/** Emitted when the user selects the action from the menu. */
@Output()
@@ -83,6 +88,11 @@ export class ContentActionComponent implements OnInit {
@Output()
success = new EventEmitter();
documentActionModel: ContentActionModel;
folderActionModel: ContentActionModel;
private subscriptions: Subscription[] = [];
constructor(
private list: ContentActionListComponent,
private documentActions: DocumentActionsService,
@@ -91,10 +101,45 @@ export class ContentActionComponent implements OnInit {
ngOnInit() {
if (this.target === ContentActionTarget.All) {
this.generateAction(ContentActionTarget.Folder);
this.generateAction(ContentActionTarget.Document);
this.folderActionModel = this.generateAction(ContentActionTarget.Folder);
this.documentActionModel = this.generateAction(ContentActionTarget.Document);
} else {
this.generateAction(this.target);
this.documentActionModel = this.generateAction(this.target);
}
}
ngOnChanges(changes: SimpleChanges) {
if (changes.visible && !changes.visible.firstChange) {
if (this.documentActionModel) {
this.documentActionModel.visible = changes.visible.currentValue;
}
if (this.folderActionModel) {
this.folderActionModel.visible = changes.visible.currentValue;
}
}
if (changes.disabled && !changes.disabled.firstChange) {
if (this.documentActionModel) {
this.documentActionModel.disabled = changes.disabled.currentValue;
}
if (this.folderActionModel) {
this.folderActionModel.disabled = changes.disabled.currentValue;
}
}
}
ngOnDestroy() {
this.subscriptions.forEach(subscription => subscription.unsubscribe());
this.subscriptions = [];
if (this.documentActionModel) {
this.unregister(this.documentActionModel);
this.documentActionModel = null;
}
if (this.folderActionModel) {
this.unregister(this.folderActionModel);
this.folderActionModel = null;
}
}
@@ -105,14 +150,22 @@ export class ContentActionComponent implements OnInit {
return false;
}
private generateAction(target: string) {
let model = new ContentActionModel({
unregister(model: ContentActionModel): boolean {
if (this.list) {
return this.list.unregisterAction(model);
}
return false;
}
private generateAction(target: string): ContentActionModel {
const model = new ContentActionModel({
title: this.title,
icon: this.icon,
permission: this.permission,
disableWithNoPermission: this.disableWithNoPermission,
target: target,
disabled: this.disabled
disabled: this.disabled,
visible: this.visible
});
if (this.handler) {
model.handler = this.getSystemHandler(target, this.handler);
@@ -125,44 +178,45 @@ export class ContentActionComponent implements OnInit {
}
this.register(model);
return model;
}
getSystemHandler(target: string, name: string): ContentActionHandler {
if (target) {
let ltarget = target.toLowerCase();
target = target.toLowerCase();
if (ltarget === ContentActionTarget.Document) {
if (target === ContentActionTarget.Document) {
if (this.documentActions) {
this.documentActions.permissionEvent.subscribe((permission) => {
this.permissionEvent.emit(permission);
});
this.documentActions.error.subscribe((errors) => {
this.error.emit(errors);
});
this.documentActions.success.subscribe((message) => {
this.success.emit(message);
});
this.subscriptions.push(
this.documentActions.permissionEvent.subscribe(permission => {
this.permissionEvent.emit(permission);
}),
this.documentActions.error.subscribe(errors => {
this.error.emit(errors);
}),
this.documentActions.success.subscribe(message => {
this.success.emit(message);
})
);
return this.documentActions.getHandler(name);
}
return null;
}
if (ltarget === ContentActionTarget.Folder) {
if (target === ContentActionTarget.Folder) {
if (this.folderActions) {
this.folderActions.permissionEvent.subscribe((permission) => {
this.permissionEvent.emit(permission);
});
this.folderActions.error.subscribe((errors) => {
this.error.emit(errors);
});
this.folderActions.success.subscribe((message) => {
this.success.emit(message);
});
this.subscriptions.push(
this.folderActions.permissionEvent.subscribe(permission => {
this.permissionEvent.emit(permission);
}),
this.folderActions.error.subscribe(errors => {
this.error.emit(errors);
}),
this.folderActions.success.subscribe(message => {
this.success.emit(message);
})
);
return this.folderActions.getHandler(name);
}
@@ -16,14 +16,12 @@
*/
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { async, TestBed } from '@angular/core/testing';
import { DataColumn, DataTableModule } from '@alfresco/adf-core';
import { TestBed } from '@angular/core/testing';
import { DataColumn, setupTestBed } from '@alfresco/adf-core';
import { LogService } from '@alfresco/adf-core';
import { DocumentListService } from '../../services/document-list.service';
import { CustomResourcesService } from '../../services/custom-resources.service';
import { DocumentListComponent } from './../document-list.component';
import { ContentColumnListComponent } from './content-column-list.component';
import { ContentTestingModule } from '../../../testing/content.testing.module';
describe('ContentColumnList', () => {
@@ -31,23 +29,10 @@ describe('ContentColumnList', () => {
let columnList: ContentColumnListComponent;
let logService: LogService;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
DataTableModule
],
declarations: [
DocumentListComponent
],
providers: [
CustomResourcesService,
DocumentListService
],
schemas: [
CUSTOM_ELEMENTS_SCHEMA
]
}).compileComponents();
}));
setupTestBed({
imports: [ContentTestingModule],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
});
beforeEach(() => {
documentList = (TestBed.createComponent(DocumentListComponent).componentInstance as DocumentListComponent);
@@ -16,14 +16,12 @@
*/
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { async, TestBed } from '@angular/core/testing';
import { LogService } from '@alfresco/adf-core';
import { DataTableModule } from '@alfresco/adf-core';
import { DocumentListService } from '../../services/document-list.service';
import { CustomResourcesService } from '../../services/custom-resources.service';
import { TestBed } from '@angular/core/testing';
import { LogService, setupTestBed } from '@alfresco/adf-core';
import { DocumentListComponent } from './../document-list.component';
import { ContentColumnListComponent } from './content-column-list.component';
import { ContentColumnComponent } from './content-column.component';
import { ContentTestingModule } from '../../../testing/content.testing.module';
describe('ContentColumn', () => {
@@ -31,24 +29,10 @@ describe('ContentColumn', () => {
let columnList: ContentColumnListComponent;
let logService: LogService;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
DataTableModule
],
declarations: [
DocumentListComponent
],
providers: [
CustomResourcesService,
DocumentListService,
LogService
],
schemas: [
CUSTOM_ELEMENTS_SCHEMA
]
}).compileComponents();
}));
setupTestBed({
imports: [ContentTestingModule],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
});
beforeEach(() => {
documentList = (TestBed.createComponent(DocumentListComponent).componentInstance as DocumentListComponent);
@@ -13,6 +13,7 @@
[display]="display"
[noPermission]="noPermission"
[showHeader]="!isEmpty() && showHeader"
[rowMenuCacheEnabled]="false"
(showRowContextMenu)="onShowRowContextMenu($event)"
(showRowActionsMenu)="onShowRowActionsMenu($event)"
(executeRowAction)="onExecuteRowAction($event)"
@@ -15,11 +15,10 @@
* limitations under the License.
*/
import { CUSTOM_ELEMENTS_SCHEMA, NgZone, SimpleChange, TemplateRef } from '@angular/core';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { AlfrescoApiService, TranslationService } from '@alfresco/adf-core';
import { CUSTOM_ELEMENTS_SCHEMA, SimpleChange, TemplateRef, QueryList } from '@angular/core';
import { ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing';
import { AlfrescoApiService, DataColumnListComponent, DataColumnComponent } from '@alfresco/adf-core';
import { DataColumn, DataTableComponent } from '@alfresco/adf-core';
import { DataTableModule } from '@alfresco/adf-core';
import { Observable } from 'rxjs/Observable';
import { Subject } from 'rxjs/Subject';
import { FileNode, FolderNode } from '../../mock';
@@ -38,8 +37,8 @@ import { RowFilter } from './../data/row-filter.model';
import { DocumentListService } from './../services/document-list.service';
import { CustomResourcesService } from './../services/custom-resources.service';
import { DocumentListComponent } from './document-list.component';
declare let jasmine: any;
import { setupTestBed } from '@alfresco/adf-core';
import { ContentTestingModule } from '../../testing/content.testing.module';
describe('DocumentList', () => {
@@ -50,25 +49,13 @@ describe('DocumentList', () => {
let fixture: ComponentFixture<DocumentListComponent>;
let element: HTMLElement;
let eventMock: any;
let spyGetSites: any;
let spyFavorite: any;
beforeEach(async(() => {
let zone = new NgZone({ enableLongStackTrace: false });
TestBed.configureTestingModule({
imports: [
DataTableModule
],
declarations: [
DocumentListComponent
],
providers: [
DocumentListService,
CustomResourcesService,
{ provide: NgZone, useValue: zone }
],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
}).compileComponents();
}));
setupTestBed({
imports: [ContentTestingModule],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
});
beforeEach(() => {
eventMock = {
@@ -78,35 +65,47 @@ describe('DocumentList', () => {
fixture = TestBed.createComponent(DocumentListComponent);
let translateService = TestBed.get(TranslationService);
spyOn(translateService, 'get').and.callFake((key) => {
return Observable.of(key);
});
element = fixture.nativeElement;
documentList = fixture.componentInstance;
documentListService = TestBed.get(DocumentListService);
apiService = TestBed.get(AlfrescoApiService);
customResourcesService = TestBed.get(CustomResourcesService);
fixture.detectChanges();
});
documentList.ngOnInit();
beforeEach(() => {
jasmine.Ajax.install();
spyGetSites = spyOn(apiService.sitesApi, 'getSites').and.returnValue(Promise.resolve(fakeGetSitesAnswer));
spyFavorite = spyOn(apiService.favoritesApi, 'getFavorites').and.returnValue(Promise.resolve({list: []}));
});
afterEach(() => {
jasmine.Ajax.uninstall();
fixture.destroy();
});
it('should update schema if columns change', fakeAsync(() => {
documentList.columnList = new DataColumnListComponent();
documentList.columnList.columns = new QueryList<DataColumnComponent>();
spyOn(documentList.data, 'setColumns').and.callThrough();
documentList.ngAfterContentInit();
documentList.columnList.columns.reset([new DataColumnComponent()]);
documentList.columnList.columns.notifyOnChanges();
tick(100);
expect(documentList.data.setColumns).toHaveBeenCalled();
}));
it('should setup default columns', () => {
fixture.detectChanges();
documentList.ngAfterContentInit();
expect(documentList.data.getColumns().length).not.toBe(0);
});
it('should add the custom columns', () => {
fixture.detectChanges();
let column = <DataColumn> {
title: 'title',
key: 'source',
@@ -178,41 +177,29 @@ describe('DocumentList', () => {
expect(action.execute).toHaveBeenCalledWith(node);
});
it('should show the loading state during the loading of new elements', (done) => {
it('should show the loading state during the loading of new elements', () => {
documentList.ngOnInit();
documentList.ngAfterContentInit();
documentList.node = new NodePaging();
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(element.querySelector('.adf-document-list-loading')).toBeDefined();
done();
});
expect(element.querySelector('.adf-document-list-loading')).toBeDefined();
});
it('should hide the header if showHeader is false', (done) => {
it('should hide the header if showHeader is false', () => {
documentList.showHeader = false;
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(element.querySelector('.adf-datatable-header')).toBe(null);
done();
});
expect(element.querySelector('.adf-datatable-header')).toBe(null);
});
it('should show the header if showHeader is true', (done) => {
it('should show the header if showHeader is true', () => {
documentList.showHeader = true;
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(element.querySelector('.adf-datatable-header')).toBeDefined();
done();
});
expect(element.querySelector('.adf-datatable-header')).toBeDefined();
});
it('should reset selection upon reload', () => {
@@ -224,29 +211,21 @@ describe('DocumentList', () => {
expect(documentList.resetSelection).toHaveBeenCalled();
});
it('should use the cardview style if cardview is true', (done) => {
it('should use the cardview style if cardview is true', () => {
documentList.display = 'gallery';
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(element.querySelector('.adf-data-table-card')).toBeDefined();
done();
});
expect(element.querySelector('.adf-data-table-card')).toBeDefined();
});
it('should use the base document list style if cardview is false', (done) => {
it('should use the base document list style if cardview is false', () => {
documentList.display = 'list';
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(element.querySelector('.adf-data-table-card')).toBe(null);
expect(element.querySelector('.adf-data-table')).toBeDefined();
done();
});
expect(element.querySelector('.adf-data-table-card')).toBe(null);
expect(element.querySelector('.adf-data-table')).toBeDefined();
});
it('should reset selection upon reload', () => {
@@ -258,7 +237,7 @@ describe('DocumentList', () => {
expect(documentList.resetSelection).toHaveBeenCalled();
});
it('should reset when a prameter changes', () => {
it('should reset when a parameter changes', () => {
spyOn(documentList.dataTable, 'resetSelection').and.callThrough();
documentList.ngOnChanges({});
@@ -266,25 +245,19 @@ describe('DocumentList', () => {
});
it('should empty template be present when no element are present', (done) => {
documentList.currentFolderId = '1d26e465-dea3-42f3-b415-faa8364b9692';
fixture.detectChanges();
documentList.folderNode = new NodeMinimal();
documentList.folderNode.id = '1d26e465-dea3-42f3-b415-faa8364b9692';
documentList.reload();
spyOn(documentListService, 'getFolder').and.returnValue(Observable.of(fakeNodeAnswerWithNOEntries));
fixture.detectChanges();
documentList.ready.subscribe(() => {
fixture.detectChanges();
let disposableReady = documentList.ready.subscribe(() => {
expect(element.querySelector('#adf-document-list-empty')).toBeDefined();
disposableReady.unsubscribe();
done();
});
jasmine.Ajax.requests.at(0).respondWith({
status: 200,
contentType: 'application/json',
responseText: JSON.stringify(fakeNodeAnswerWithNOEntries)
});
documentList.reload();
});
it('should not execute action without node provided', () => {
@@ -344,6 +317,70 @@ describe('DocumentList', () => {
});
it('should not display hidden content actions', () => {
documentList.actions = [
new ContentActionModel({
target: 'document',
title: 'Action1',
visible: false
}),
new ContentActionModel({
target: 'document',
title: 'Action2',
visible: true
})
];
const nodeFile = { entry: { isFile: true, name: 'xyz' } };
const actions = documentList.getNodeActions(nodeFile);
expect(actions.length).toBe(1);
expect(actions[0].title).toBe('Action2');
});
it('should evaluate conditional visibility for content actions', () => {
documentList.actions = [
new ContentActionModel({
target: 'document',
title: 'Action1',
visible: (): boolean => true
}),
new ContentActionModel({
target: 'document',
title: 'Action2',
visible: (): boolean => false
})
];
const nodeFile = { entry: { isFile: true, name: 'xyz' } };
const actions = documentList.getNodeActions(nodeFile);
expect(actions.length).toBe(1);
expect(actions[0].title).toBe('Action1');
});
it('should evaluate conditional disabled state for content action', () => {
documentList.actions = [
new ContentActionModel({
target: 'document',
title: 'Action1',
disabled: (): boolean => true
}),
new ContentActionModel({
target: 'document',
title: 'Action2',
disabled: (): boolean => false
})
];
const nodeFile = { entry: { isFile: true, name: 'xyz' } };
const actions = documentList.getNodeActions(nodeFile);
expect(actions.length).toBe(2);
expect(actions[0].disabled).toBeTruthy();
expect(actions[1].disabled).toBeFalsy();
});
it('should not disable the action if there is copy permission', () => {
let documentMenu = new ContentActionModel({
disableWithNoPermission: true,
@@ -386,46 +423,6 @@ describe('DocumentList', () => {
});
it('should disable the action if there is no permission for the file and disableWithNoPermission false', () => {
let documentMenu = new ContentActionModel({
disableWithNoPermission: false,
permission: 'delete',
target: 'document',
title: 'FileAction'
});
documentList.actions = [
documentMenu
];
let nodeFile = { entry: { isFile: true, name: 'xyz', allowableOperations: ['create', 'update'] } };
let actions = documentList.getNodeActions(nodeFile);
expect(actions.length).toBe(1);
expect(actions[0].title).toEqual('FileAction');
expect(actions[0].disabled).toBe(true);
});
it('should disable the action if there is no permission for the folder and disableWithNoPermission false', () => {
let documentMenu = new ContentActionModel({
disableWithNoPermission: false,
permission: 'delete',
target: 'folder',
title: 'FolderAction'
});
documentList.actions = [
documentMenu
];
let nodeFile = { entry: { isFolder: true, name: 'xyz', allowableOperations: ['create', 'update'] } };
let actions = documentList.getNodeActions(nodeFile);
expect(actions.length).toBe(1);
expect(actions[0].title).toEqual('FolderAction');
expect(actions[0].disabled).toBe(true);
});
it('should not disable the action if there is the right permission for the file', () => {
let documentMenu = new ContentActionModel({
disableWithNoPermission: true,
@@ -443,7 +440,7 @@ describe('DocumentList', () => {
let actions = documentList.getNodeActions(nodeFile);
expect(actions.length).toBe(1);
expect(actions[0].title).toEqual('FileAction');
expect(actions[0].disabled).toBeUndefined();
expect(actions[0].disabled).toBeFalsy();
});
it('should not disable the action if there is the right permission for the folder', () => {
@@ -463,14 +460,15 @@ describe('DocumentList', () => {
let actions = documentList.getNodeActions(nodeFile);
expect(actions.length).toBe(1);
expect(actions[0].title).toEqual('FolderAction');
expect(actions[0].disabled).toBeUndefined();
expect(actions[0].disabled).toBeFalsy();
});
it('should not disable the action if there are no permissions for the file', () => {
it('should not disable the action if there are no permissions for the file and disable with no permission is false', () => {
let documentMenu = new ContentActionModel({
permission: 'delete',
target: 'document',
title: 'FileAction'
title: 'FileAction',
disableWithNoPermission: false
});
documentList.actions = [
@@ -482,14 +480,15 @@ describe('DocumentList', () => {
let actions = documentList.getNodeActions(nodeFile);
expect(actions.length).toBe(1);
expect(actions[0].title).toEqual('FileAction');
expect(actions[0].disabled).toBeUndefined();
expect(actions[0].disabled).toBeFalsy();
});
it('should not disable the action if there are no permissions for the folder', () => {
it('should not disable the action if there are no permissions for the folder and disable with no permission is false', () => {
let documentMenu = new ContentActionModel({
permission: 'delete',
target: 'folder',
title: 'FolderAction'
title: 'FolderAction',
disableWithNoPermission: false
});
documentList.actions = [
@@ -501,7 +500,49 @@ describe('DocumentList', () => {
let actions = documentList.getNodeActions(nodeFile);
expect(actions.length).toBe(1);
expect(actions[0].title).toEqual('FolderAction');
expect(actions[0].disabled).toBeUndefined();
expect(actions[0].disabled).toBeFalsy();
});
it('should disable the action if there are no permissions for the file and disable with no permission is true', () => {
let documentMenu = new ContentActionModel({
permission: 'delete',
target: 'document',
title: 'FileAction',
disableWithNoPermission: true
});
documentList.actions = [
documentMenu
];
let nodeFile = { entry: { isFile: true, name: 'xyz', allowableOperations: null } };
let actions = documentList.getNodeActions(nodeFile);
expect(actions.length).toBe(1);
expect(actions[0].title).toEqual('FileAction');
expect(actions[0].disabled).toBeDefined();
expect(actions[0].disabled).toBeTruthy();
});
it('should disable the action if there are no permissions for the folder and disable with no permission is true', () => {
let documentMenu = new ContentActionModel({
permission: 'delete',
target: 'folder',
title: 'FolderAction',
disableWithNoPermission: true
});
documentList.actions = [
documentMenu
];
let nodeFile = { entry: { isFolder: true, name: 'xyz', allowableOperations: null } };
let actions = documentList.getNodeActions(nodeFile);
expect(actions.length).toBe(1);
expect(actions[0].title).toEqual('FolderAction');
expect(actions[0].disabled).toBeDefined();
expect(actions[0].disabled).toBeTruthy();
});
it('should find no content actions', () => {
@@ -520,8 +561,9 @@ describe('DocumentList', () => {
it('should emit nodeClick event', (done) => {
let node = new FileNode();
documentList.nodeClick.subscribe(e => {
let disposableClick = documentList.nodeClick.subscribe(e => {
expect(e.value).toBe(node);
disposableClick.unsubscribe();
done();
});
documentList.onNodeClick(node);
@@ -609,8 +651,9 @@ describe('DocumentList', () => {
it('should emit file preview event on single click', (done) => {
let file = new FileNode();
documentList.preview.subscribe(e => {
let disposablePreview = documentList.preview.subscribe(e => {
expect(e.value).toBe(file);
disposablePreview.unsubscribe();
done();
});
documentList.navigationMode = DocumentListComponent.SINGLE_CLICK_NAVIGATION;
@@ -619,8 +662,9 @@ describe('DocumentList', () => {
it('should emit file preview event on double click', (done) => {
let file = new FileNode();
documentList.preview.subscribe(e => {
let disposablePreview = documentList.preview.subscribe(e => {
expect(e.value).toBe(file);
disposablePreview.unsubscribe();
done();
});
documentList.navigationMode = DocumentListComponent.DOUBLE_CLICK_NAVIGATION;
@@ -670,6 +714,8 @@ describe('DocumentList', () => {
let folder = new FolderNode();
let file = new FileNode();
spyOn(documentList, 'loadFolder').and.stub();
expect(documentList.performNavigation(folder)).toBeTruthy();
expect(documentList.performNavigation(file)).toBeFalsy();
expect(documentList.performNavigation(null)).toBeFalsy();
@@ -708,6 +754,7 @@ describe('DocumentList', () => {
it('should display folder content from loadFolder on reload if folderNode defined', () => {
documentList.folderNode = new NodeMinimal();
spyOn(documentList, 'loadFolderNodesByFolderNodeId').and.returnValue(Observable.of(''));
spyOn(documentList, 'loadFolder').and.callThrough();
documentList.reload();
expect(documentList.loadFolder).toHaveBeenCalled();
@@ -715,7 +762,7 @@ describe('DocumentList', () => {
it('should display folder content from loadFolderByNodeId on reload if currentFolderId defined', () => {
documentList.currentFolderId = 'id-folder';
spyOn(documentList, 'loadFolderByNodeId').and.callThrough();
spyOn(documentList, 'loadFolderByNodeId').and.stub();
documentList.reload();
expect(documentList.loadFolderByNodeId).toHaveBeenCalled();
});
@@ -829,6 +876,7 @@ describe('DocumentList', () => {
});
it('should set row filter and reload contents if currentFolderId is set when setting rowFilter', () => {
fixture.detectChanges();
let filter = <RowFilter> {};
documentList.currentFolderId = 'id';
spyOn(documentList.data, 'setFilter').and.callThrough();
@@ -850,6 +898,7 @@ describe('DocumentList', () => {
});
it('should set image resolver for underlying adapter', () => {
fixture.detectChanges();
let resolver = <ImageResolver> {};
spyOn(documentList.data, 'setImageResolver').and.callThrough();
@@ -869,8 +918,7 @@ describe('DocumentList', () => {
it('should emit node-click DOM event', (done) => {
let node = new NodeMinimalEntry();
const htmlElement = fixture.debugElement.nativeElement as HTMLElement;
htmlElement.addEventListener('node-click', (e: CustomEvent) => {
document.addEventListener('node-click', (e: CustomEvent) => {
done();
});
@@ -888,8 +936,7 @@ describe('DocumentList', () => {
it('should emit node-dblclick DOM event', (done) => {
let node = new NodeMinimalEntry();
const htmlElement = fixture.debugElement.nativeElement as HTMLElement;
htmlElement.addEventListener('node-dblclick', (e: CustomEvent) => {
document.addEventListener('node-dblclick', (e: CustomEvent) => {
done();
});
@@ -908,8 +955,9 @@ describe('DocumentList', () => {
const error = { message: '{ "error": { "statusCode": 501 } }' };
spyOn(documentListService, 'getFolderNode').and.returnValue(Observable.throw(error));
documentList.error.subscribe(val => {
let disposableError = documentList.error.subscribe(val => {
expect(val).toBe(error);
disposableError.unsubscribe();
done();
});
@@ -921,21 +969,23 @@ describe('DocumentList', () => {
spyOn(documentListService, 'getFolderNode').and.returnValue(Observable.of(fakeNodeWithCreatePermission));
spyOn(documentList, 'loadFolderNodesByFolderNodeId').and.returnValue(Promise.reject(error));
documentList.error.subscribe(val => {
let disposableError = documentList.error.subscribe(val => {
expect(val).toBe(error);
disposableError.unsubscribe();
done();
});
documentList.loadFolderByNodeId('123');
});
it('should set no permision when getFolderNode fails with 403', (done) => {
it('should set no permission when getFolderNode fails with 403', (done) => {
const error = { message: '{ "error": { "statusCode": 403 } }' };
spyOn(documentListService, 'getFolderNode').and.returnValue(Observable.throw(error));
documentList.error.subscribe(val => {
let disposableError = documentList.error.subscribe(val => {
expect(val).toBe(error);
expect(documentList.noPermission).toBe(true);
disposableError.unsubscribe();
done();
});
@@ -953,6 +1003,7 @@ describe('DocumentList', () => {
});
it('should reload contents if node data changes after previously got noPermission error', () => {
fixture.detectChanges();
spyOn(documentList.data, 'loadPage').and.callThrough();
documentList.noPermission = true;
@@ -964,7 +1015,7 @@ describe('DocumentList', () => {
expect(documentList.noPermission).toBeFalsy();
});
it('should noPermission be true if navigate to a folder with no permission', (done) => {
it('should noPermission be true if navigate to a folder with no permission', () => {
const error = { message: '{ "error": { "statusCode": 403 } }' };
documentList.currentFolderId = '1d26e465-dea3-42f3-b415-faa8364b9692';
@@ -977,12 +1028,8 @@ describe('DocumentList', () => {
documentList.loadFolder();
let clickedFolderNode = new FolderNode('fake-folder-node');
documentList.onNodeDblClick(clickedFolderNode);
fixture.detectChanges();
fixture.whenStable().then(() => {
expect(documentList.noPermission).toBeTruthy();
done();
});
expect(documentList.noPermission).toBeTruthy();
});
it('should not perform navigation for virtual sources', () => {
@@ -1008,8 +1055,9 @@ describe('DocumentList', () => {
it('should emit error when fetch trashcan fails', (done) => {
spyOn(apiService.nodesApi, 'getDeletedNodes').and.returnValue(Promise.reject('error'));
documentList.error.subscribe(val => {
let disposableError = documentList.error.subscribe(val => {
expect(val).toBe('error');
disposableError.unsubscribe();
done();
});
@@ -1028,8 +1076,9 @@ describe('DocumentList', () => {
spyOn(apiService.getInstance().core.sharedlinksApi, 'findSharedLinks')
.and.returnValue(Promise.reject('error'));
documentList.error.subscribe(val => {
let disposableError = documentList.error.subscribe(val => {
expect(val).toBe('error');
disposableError.unsubscribe();
done();
});
@@ -1038,18 +1087,17 @@ describe('DocumentList', () => {
it('should fetch sites', () => {
const sitesApi = apiService.getInstance().core.sitesApi;
spyOn(sitesApi, 'getSites').and.returnValue(Promise.resolve(null));
documentList.loadFolderByNodeId('-sites-');
expect(sitesApi.getSites).toHaveBeenCalled();
});
it('should emit error when fetch sites fails', (done) => {
spyOn(apiService.getInstance().core.sitesApi, 'getSites')
.and.returnValue(Promise.reject('error'));
spyGetSites.and.returnValue(Promise.reject('error'));
documentList.error.subscribe(val => {
let disposableError = documentList.error.subscribe(val => {
expect(val).toBe('error');
disposableError.unsubscribe();
done();
});
@@ -1057,36 +1105,34 @@ describe('DocumentList', () => {
});
it('should assure that sites have name property set', (done) => {
const sitesApi = apiService.getInstance().core.sitesApi;
spyOn(sitesApi, 'getSites').and.returnValue(Promise.resolve(fakeGetSitesAnswer));
fixture.detectChanges();
documentList.loadFolderByNodeId('-sites-');
expect(sitesApi.getSites).toHaveBeenCalled();
documentList.ready.subscribe((page) => {
let disposableReady = documentList.ready.subscribe((page) => {
const entriesWithoutName = page.list.entries.filter(item => !item.entry.name);
expect(entriesWithoutName.length).toBe(0);
disposableReady.unsubscribe();
done();
});
documentList.loadFolderByNodeId('-sites-');
});
it('should assure that sites have name property set correctly', (done) => {
const sitesApi = apiService.getInstance().core.sitesApi;
spyOn(sitesApi, 'getSites').and.returnValue(Promise.resolve(fakeGetSitesAnswer));
fixture.detectChanges();
documentList.loadFolderByNodeId('-sites-');
expect(sitesApi.getSites).toHaveBeenCalled();
documentList.ready.subscribe((page) => {
let disposableReady = documentList.ready.subscribe((page) => {
const wrongName = page.list.entries.filter(item => (item.entry.name !== item.entry.title));
expect(wrongName.length).toBe(0);
disposableReady.unsubscribe();
done();
});
documentList.loadFolderByNodeId('-sites-');
});
it('should fetch user membership sites', () => {
const peopleApi = apiService.getInstance().core.peopleApi;
spyOn(peopleApi, 'getSiteMembership').and.returnValue(Promise.resolve());
spyOn(peopleApi, 'getSiteMembership').and.returnValue(Promise.resolve(fakeGetSiteMembership));
documentList.loadFolderByNodeId('-mysites-');
expect(peopleApi.getSiteMembership).toHaveBeenCalled();
@@ -1096,8 +1142,9 @@ describe('DocumentList', () => {
spyOn(apiService.getInstance().core.peopleApi, 'getSiteMembership')
.and.returnValue(Promise.reject('error'));
documentList.error.subscribe(val => {
let disposableError = documentList.error.subscribe(val => {
expect(val).toBe('error');
disposableError.unsubscribe();
done();
});
@@ -1105,47 +1152,51 @@ describe('DocumentList', () => {
});
it('should assure that user membership sites have name property set', (done) => {
fixture.detectChanges();
const peopleApi = apiService.getInstance().core.peopleApi;
spyOn(peopleApi, 'getSiteMembership').and.returnValue(Promise.resolve(fakeGetSiteMembership));
documentList.loadFolderByNodeId('-mysites-');
expect(peopleApi.getSiteMembership).toHaveBeenCalled();
documentList.ready.subscribe((page) => {
let disposableReady = documentList.ready.subscribe((page) => {
const entriesWithoutName = page.list.entries.filter(item => !item.entry.name);
expect(entriesWithoutName.length).toBe(0);
disposableReady.unsubscribe();
done();
});
});
it('should assure that user membership sites have name property set correctly', (done) => {
fixture.detectChanges();
const peopleApi = apiService.getInstance().core.peopleApi;
spyOn(peopleApi, 'getSiteMembership').and.returnValue(Promise.resolve(fakeGetSiteMembership));
documentList.loadFolderByNodeId('-mysites-');
expect(peopleApi.getSiteMembership).toHaveBeenCalled();
documentList.ready.subscribe((page) => {
let disposableReady = documentList.ready.subscribe((page) => {
const wrongName = page.list.entries.filter(item => (item.entry.name !== item.entry.title));
expect(wrongName.length).toBe(0);
disposableReady.unsubscribe();
done();
});
});
it('should fetch favorites', () => {
const favoritesApi = apiService.getInstance().core.favoritesApi;
spyOn(favoritesApi, 'getFavorites').and.returnValue(Promise.resolve(null));
spyFavorite.and.returnValue(Promise.resolve(null));
documentList.loadFolderByNodeId('-favorites-');
expect(favoritesApi.getFavorites).toHaveBeenCalled();
});
it('should emit error when fetch favorites fails', (done) => {
spyOn(apiService.getInstance().core.favoritesApi, 'getFavorites')
.and.returnValue(Promise.reject('error'));
spyFavorite.and.returnValue(Promise.reject('error'));
documentList.error.subscribe(val => {
let disposableError = documentList.error.subscribe(val => {
expect(val).toBe('error');
disposableError.unsubscribe();
done();
});
@@ -1165,19 +1216,21 @@ describe('DocumentList', () => {
it('should emit error when fetch recent fails on getPerson call', (done) => {
spyOn(apiService.peopleApi, 'getPerson').and.returnValue(Promise.reject('error'));
documentList.error.subscribe(val => {
let disposableError = documentList.error.subscribe(val => {
expect(val).toBe('error');
disposableError.unsubscribe();
done();
});
documentList.loadFolderByNodeId('-recent-');
});
it('should emit error when fetch recent fails on search call', (done) => {
xit('should emit error when fetch recent fails on search call', (done) => {
spyOn(customResourcesService, 'loadFolderByNodeId').and.returnValue(Observable.throw('error'));
documentList.error.subscribe(val => {
let disposableError = documentList.error.subscribe(val => {
expect(val).toBe('error');
disposableError.unsubscribe();
done();
});
@@ -1195,9 +1248,6 @@ describe('DocumentList', () => {
it('should reset folder node on loading folder by node id', () => {
documentList.folderNode = <any> {};
const sitesApi = apiService.getInstance().core.sitesApi;
spyOn(sitesApi, 'getSites').and.returnValue(Promise.resolve(null));
documentList.loadFolderByNodeId('-sites-');
expect(documentList.folderNode).toBeNull();
@@ -1207,7 +1257,7 @@ describe('DocumentList', () => {
documentList.currentFolderId = '12345-some-id-6789';
const peopleApi = apiService.getInstance().core.peopleApi;
spyOn(peopleApi, 'getSiteMembership').and.returnValue(Promise.resolve());
spyOn(peopleApi, 'getSiteMembership').and.returnValue(Promise.resolve(fakeGetSiteMembership));
documentList.loadFolderByNodeId('-mysites-');
expect(documentList.currentFolderId).toBe('-mysites-');
@@ -1263,9 +1313,10 @@ describe('DocumentList', () => {
});
it('should add includeFields in the server request when present', () => {
fixture.detectChanges();
documentList.currentFolderId = 'fake-id';
documentList.includeFields = ['test-include'];
spyOn(documentListService, 'getFolder');
spyOn(documentListService, 'getFolder').and.stub();
documentList.ngOnChanges({ rowFilter: new SimpleChange(null, <RowFilter> {}, true) });
@@ -23,7 +23,7 @@ import {
import {
ContentService, DataCellEvent, DataColumn, DataRowActionEvent, DataSorting, DataTableComponent,
DisplayMode, ObjectDataColumn, PaginatedComponent, AppConfigService, DataColumnListComponent,
UserPreferencesService, PaginationModel
UserPreferencesService, PaginationModel, ThumbnailService
} from '@alfresco/adf-core';
import { MinimalNodeEntity, MinimalNodeEntryEntity, NodePaging } from 'alfresco-js-api';
@@ -136,7 +136,11 @@ export class DocumentListComponent implements OnInit, OnChanges, OnDestroy, Afte
* override the default sorting detected by the component based on columns.
*/
@Input()
sorting: string[];
sorting = ['name', 'asc'];
/** Defines sorting mode. Can be either `client` or `server`. */
@Input()
sortingMode = 'client';
/** The inline style to apply to every row. See
* the Angular NgStyle
@@ -231,8 +235,8 @@ export class DocumentListComponent implements OnInit, OnChanges, OnDestroy, Afte
private _pagination: BehaviorSubject<PaginationModel>;
private layoutPresets = {};
private contextActionHandlerSubscription: Subscription;
private subscriptions: Subscription[] = [];
private rowMenuCache: { [key: string]: ContentActionModel[] } = {};
constructor(private documentListService: DocumentListService,
private ngZone: NgZone,
@@ -240,7 +244,8 @@ export class DocumentListComponent implements OnInit, OnChanges, OnDestroy, Afte
private appConfig: AppConfigService,
private preferences: UserPreferencesService,
private customResourcesService: CustomResourcesService,
private contentService: ContentService) {
private contentService: ContentService,
private thumbnailService: ThumbnailService) {
}
getContextActions(node: MinimalNodeEntity) {
@@ -329,8 +334,9 @@ export class DocumentListComponent implements OnInit, OnChanges, OnDestroy, Afte
}
ngOnInit() {
this.rowMenuCache = {};
this.loadLayoutPresets();
this.data = new ShareDataTableAdapter(this.documentListService, null, this.getDefaultSorting());
this.data = new ShareDataTableAdapter(this.documentListService, this.thumbnailService, null, this.getDefaultSorting(), this.sortingMode);
this.data.thumbnails = this.thumbnails;
this.data.permissionsStyle = this.permissionsStyle;
@@ -342,12 +348,25 @@ export class DocumentListComponent implements OnInit, OnChanges, OnDestroy, Afte
this.data.setImageResolver(this.imageResolver);
}
this.contextActionHandlerSubscription = this.contextActionHandler.subscribe(val => this.contextActionCallback(val));
this.subscriptions.push(
this.contextActionHandler.subscribe(val => this.contextActionCallback(val))
);
this.enforceSingleClickNavigationForMobile();
}
ngAfterContentInit() {
if (this.columnList) {
this.subscriptions.push(
this.columnList.columns.changes.subscribe(() => {
this.setTableSchema();
})
);
}
this.setTableSchema();
}
private setTableSchema() {
let schema: DataColumn[] = [];
if (this.hasCustomLayout) {
@@ -355,7 +374,7 @@ export class DocumentListComponent implements OnInit, OnChanges, OnDestroy, Afte
}
if (!this.data) {
this.data = new ShareDataTableAdapter(this.documentListService, schema, this.getDefaultSorting());
this.data = new ShareDataTableAdapter(this.documentListService, this.thumbnailService, schema, this.getDefaultSorting(), this.sortingMode);
} else if (schema && schema.length > 0) {
this.data.setColumns(schema);
}
@@ -368,6 +387,21 @@ export class DocumentListComponent implements OnInit, OnChanges, OnDestroy, Afte
ngOnChanges(changes: SimpleChanges) {
this.resetSelection();
if (this.data) {
this.data.thumbnails = this.thumbnails;
}
if (changes.sortingMode && !changes.sortingMode.firstChange && this.data) {
this.data.sortingMode = changes.sortingMode.currentValue;
}
if (changes.sorting && !changes.sorting.firstChange && this.data) {
const newValue = changes.sorting.currentValue;
if (newValue && newValue.length > 0) {
const [key, direction] = newValue;
this.data.setSorting(new DataSorting(key, direction));
}
}
if (changes.folderNode && changes.folderNode.currentValue) {
this.currentFolderId = changes.folderNode.currentValue.id;
@@ -380,9 +414,6 @@ export class DocumentListComponent implements OnInit, OnChanges, OnDestroy, Afte
this.loadFolder();
} else if (this.data) {
if (changes.node && changes.node.currentValue) {
if (changes.node.currentValue.list.pagination) {
changes.node.currentValue.list.pagination.skipCount = 0;
}
this.data.loadPage(changes.node.currentValue);
this.onDataReady(changes.node.currentValue);
} else if (changes.rowFilter) {
@@ -415,9 +446,10 @@ export class DocumentListComponent implements OnInit, OnChanges, OnDestroy, Afte
}
getNodeActions(node: MinimalNodeEntity | any): ContentActionModel[] {
let target = null;
if (node && node.entry) {
let target = null;
if (node.entry.isFile) {
target = 'document';
} else if (node.entry.isFolder) {
@@ -425,14 +457,29 @@ export class DocumentListComponent implements OnInit, OnChanges, OnDestroy, Afte
}
if (target) {
let actionsByTarget = this.actions.filter(entry => {
return entry.target.toLowerCase() === target;
}).map(action => new ContentActionModel(action));
const actions = this.rowMenuCache[node.entry.id];
if (actions) {
actions.forEach(action => {
this.refreshAction(action, node);
});
return actions;
}
let actionsByTarget = this.actions
.filter(entry => {
const isVisible = (typeof entry.visible === 'function')
? entry.visible(node)
: entry.visible;
return isVisible && entry.target.toLowerCase() === target;
})
.map(action => new ContentActionModel(action));
actionsByTarget.forEach((action) => {
this.disableActionsWithNoPermissions(node, action);
this.refreshAction(action, node);
});
this.rowMenuCache[node.entry.id] = actionsByTarget;
return actionsByTarget;
}
}
@@ -440,10 +487,29 @@ export class DocumentListComponent implements OnInit, OnChanges, OnDestroy, Afte
return [];
}
disableActionsWithNoPermissions(node: MinimalNodeEntity, action: ContentActionModel) {
if (action.permission && node.entry.allowableOperations && !this.contentService.hasPermission(node.entry, action.permission)) {
action.disabled = true;
private refreshAction(action: ContentActionModel, node: MinimalNodeEntity) {
action.disabled = this.isActionDisabled(action, node);
action.visible = this.isActionVisible(action, node);
}
private isActionVisible(action: ContentActionModel, node: MinimalNodeEntity): boolean {
if (typeof action.visible === 'function') {
return action.visible(node);
}
return action.visible;
}
private isActionDisabled(action: ContentActionModel, node: MinimalNodeEntity): boolean {
if (typeof action.disabled === 'function') {
return action.disabled(node);
}
if (action.permission && action.disableWithNoPermission && !this.contentService.hasPermission(node.entry, action.permission)) {
return true;
}
return action.disabled;
}
@HostListener('contextmenu', ['$event'])
@@ -570,7 +636,7 @@ export class DocumentListComponent implements OnInit, OnChanges, OnDestroy, Afte
this.noPermission = false;
}
private onPageLoaded(nodePaging: NodePaging) {
onPageLoaded(nodePaging: NodePaging) {
if (nodePaging) {
this.data.loadPage(nodePaging, this.pagination.getValue().merge);
this.loading = false;
@@ -738,6 +804,7 @@ export class DocumentListComponent implements OnInit, OnChanges, OnDestroy, Afte
}
updatePagination(pagination: PaginationModel) {
this.pagination.next(pagination);
this.reload();
}
@@ -770,10 +837,8 @@ export class DocumentListComponent implements OnInit, OnChanges, OnDestroy, Afte
}
ngOnDestroy() {
if (this.contextActionHandlerSubscription) {
this.contextActionHandlerSubscription.unsubscribe();
this.contextActionHandlerSubscription = null;
}
this.subscriptions.forEach(s => s.unsubscribe());
this.subscriptions = [];
}
private handleError(err: any) {
@@ -16,36 +16,21 @@
*/
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { async, TestBed } from '@angular/core/testing';
import { DataTableComponent, DataTableModule } from '@alfresco/adf-core';
import { DocumentListService } from '../../services/document-list.service';
import { CustomResourcesService } from '../../services/custom-resources.service';
import { TestBed } from '@angular/core/testing';
import { DataTableComponent, setupTestBed } from '@alfresco/adf-core';
import { DocumentListComponent } from './../document-list.component';
import { EmptyFolderContentDirective } from './empty-folder-content.directive';
import { ContentTestingModule } from '../../../testing/content.testing.module';
describe('EmptyFolderContent', () => {
let emptyFolderContent: EmptyFolderContentDirective;
let documentList: DocumentListComponent;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
DataTableModule
],
declarations: [
DocumentListComponent
],
providers: [
DocumentListService,
CustomResourcesService
],
schemas: [
CUSTOM_ELEMENTS_SCHEMA
]
}).compileComponents();
}));
setupTestBed({
imports: [ContentTestingModule],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
});
beforeEach(() => {
documentList = (TestBed.createComponent(DocumentListComponent).componentInstance as DocumentListComponent);
@@ -15,35 +15,20 @@
* limitations under the License.
*/
import { async, TestBed } from '@angular/core/testing';
import { MatProgressSpinnerModule } from '@angular/material';
import { DataTableComponent, DataTableModule } from '@alfresco/adf-core';
import { DocumentListService } from '../../services/document-list.service';
import { CustomResourcesService } from '../../services/custom-resources.service';
import { TestBed } from '@angular/core/testing';
import { DataTableComponent, setupTestBed } from '@alfresco/adf-core';
import { DocumentListComponent } from './../document-list.component';
import { NoPermissionContentDirective } from './no-permission-content.directive';
import { ContentTestingModule } from '../../../testing/content.testing.module';
describe('NoPermissionContentDirective', () => {
let noPermissionContent: NoPermissionContentDirective;
let documentList: DocumentListComponent;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
DataTableModule,
MatProgressSpinnerModule
],
declarations: [
DocumentListComponent
],
providers: [
DocumentListService,
CustomResourcesService
]
}).compileComponents();
}));
setupTestBed({
imports: [ContentTestingModule]
});
beforeEach(() => {
documentList = (TestBed.createComponent(DocumentListComponent).componentInstance as DocumentListComponent);
@@ -15,8 +15,7 @@
* limitations under the License.
*/
import { DataRow } from '@alfresco/adf-core';
import { ObjectUtils } from '@alfresco/adf-core';
import { DataRow, ObjectUtils, ThumbnailService } from '@alfresco/adf-core';
import { MinimalNode, MinimalNodeEntity } from 'alfresco-js-api';
import { PermissionStyleModel } from './../models/permissions-style.model';
import { DocumentListService } from './../services/document-list.service';
@@ -34,7 +33,10 @@ export class ShareDataRow implements DataRow {
return this.obj;
}
constructor(private obj: MinimalNodeEntity, private documentListService: DocumentListService, private permissionsStyle: PermissionStyleModel[]) {
constructor(private obj: MinimalNodeEntity,
private documentListService: DocumentListService,
private permissionsStyle: PermissionStyleModel[],
private thumbnailService?: ThumbnailService) {
if (!obj) {
throw new Error(ShareDataRow.ERR_OBJECT_NOT_FOUND);
}
@@ -91,6 +93,10 @@ export class ShareDataRow implements DataRow {
return ObjectUtils.getValue(this.obj.entry, key);
}
imageErrorResolver(event: Event): any {
return this.thumbnailService.getMimeTypeIcon(this.obj.entry.content.mimeType);
}
hasValue(key: string): boolean {
return this.getValue(key) !== undefined;
}
@@ -32,30 +32,54 @@ describe('ShareDataTableAdapter', () => {
spyOn(documentListService, 'getDocumentThumbnailUrl').and.returnValue(imageUrl);
});
it('should use client sorting by default', () => {
const adapter = new ShareDataTableAdapter(documentListService, null, []);
expect(adapter.sortingMode).toBe('client');
});
it('should not be case sensitive for sorting mode value', () => {
const adapter = new ShareDataTableAdapter(documentListService, null, []);
adapter.sortingMode = 'CLIENT';
expect(adapter.sortingMode).toBe('client');
adapter.sortingMode = 'SeRvEr';
expect(adapter.sortingMode).toBe('server');
});
it('should fallback to client sorting for unknown values', () => {
const adapter = new ShareDataTableAdapter(documentListService, null, []);
adapter.sortingMode = 'SeRvEr';
expect(adapter.sortingMode).toBe('server');
adapter.sortingMode = 'quantum';
expect(adapter.sortingMode).toBe('client');
});
it('should setup rows and columns with constructor', () => {
let schema = [<DataColumn> {}];
let adapter = new ShareDataTableAdapter(documentListService, schema);
let adapter = new ShareDataTableAdapter(documentListService, null, schema);
expect(adapter.getRows()).toEqual([]);
expect(adapter.getColumns()).toEqual(schema);
});
it('should setup columns when constructor is missing schema', () => {
let adapter = new ShareDataTableAdapter(documentListService, null);
const adapter = new ShareDataTableAdapter(documentListService, null, null);
expect(adapter.getColumns()).toEqual([]);
});
it('should set new columns', () => {
let columns = [<DataColumn> {}, <DataColumn> {}];
let adapter = new ShareDataTableAdapter(documentListService, null);
let adapter = new ShareDataTableAdapter(documentListService, null, null);
adapter.setColumns(columns);
expect(adapter.getColumns()).toEqual(columns);
});
it('should reset columns', () => {
let columns = [<DataColumn> {}, <DataColumn> {}];
let adapter = new ShareDataTableAdapter(documentListService, columns);
let adapter = new ShareDataTableAdapter(documentListService, null, columns);
expect(adapter.getColumns()).toEqual(columns);
adapter.setColumns(null);
@@ -64,7 +88,7 @@ describe('ShareDataTableAdapter', () => {
it('should set new rows', () => {
let rows = [<DataRow> {}, <DataRow> {}];
let adapter = new ShareDataTableAdapter(documentListService, null);
let adapter = new ShareDataTableAdapter(documentListService, null, null);
expect(adapter.getRows()).toEqual([]);
adapter.setRows(rows);
@@ -73,7 +97,7 @@ describe('ShareDataTableAdapter', () => {
it('should reset rows', () => {
let rows = [<DataRow> {}, <DataRow> {}];
let adapter = new ShareDataTableAdapter(documentListService, null);
let adapter = new ShareDataTableAdapter(documentListService, null, null);
adapter.setRows(rows);
expect(adapter.getRows()).toEqual(rows);
@@ -83,7 +107,7 @@ describe('ShareDataTableAdapter', () => {
});
it('should sort new rows', () => {
let adapter = new ShareDataTableAdapter(documentListService, null);
let adapter = new ShareDataTableAdapter(documentListService, null, null);
spyOn(adapter, 'sort').and.callThrough();
let rows = [<DataRow> {}];
@@ -93,7 +117,7 @@ describe('ShareDataTableAdapter', () => {
});
it('should fail when getting value for missing row', () => {
let adapter = new ShareDataTableAdapter(documentListService, null);
let adapter = new ShareDataTableAdapter(documentListService, null, null);
let check = () => {
return adapter.getValue(null, <DataColumn> {});
};
@@ -101,53 +125,13 @@ describe('ShareDataTableAdapter', () => {
});
it('should fail when getting value for missing column', () => {
let adapter = new ShareDataTableAdapter(documentListService, null);
let adapter = new ShareDataTableAdapter(documentListService, null, null);
let check = () => {
return adapter.getValue(<DataRow> {}, null);
};
expect(check).toThrowError(adapter.ERR_COL_NOT_FOUND);
});
it('should covert cell value to formatted date', () => {
let rawValue = new Date(2015, 6, 15, 21, 43, 11); // Wed Jul 15 2015 21:43:11 GMT+0100 (BST);
let dateValue = 'Jul 15, 2015, 9:43:11 PM';
let file = new FileNode();
file.entry.createdAt = rawValue;
let col = <DataColumn> {
key: 'createdAt',
type: 'date',
format: 'medium' // Jul 15, 2015, 9:43:11 PM
};
let row = new ShareDataRow(file, documentListService, null);
let adapter = new ShareDataTableAdapter(documentListService, null);
let value = adapter.getValue(row, col);
expect(value).toBe(dateValue);
});
it('should use default date format as fallback', () => {
let rawValue = new Date(2015, 6, 15, 21, 43, 11); // Wed Jul 15 2015 21:43:11 GMT+0100 (BST);
let dateValue = 'Jul 15, 2015, 9:43:11 PM';
let file = new FileNode();
file.entry.createdAt = rawValue;
let col = <DataColumn> {
key: 'createdAt',
type: 'date',
format: null
};
let row = new ShareDataRow(file, documentListService, null);
let adapter = new ShareDataTableAdapter(documentListService, null);
let value = adapter.getValue(row, col);
expect(value).toBe(dateValue);
});
it('should return date value as string', () => {
let rawValue = new Date(2015, 6, 15, 21, 43, 11); // Wed Jul 15 2015 21:43:11 GMT+0100 (BST);
@@ -160,36 +144,16 @@ describe('ShareDataTableAdapter', () => {
};
let row = new ShareDataRow(file, documentListService, null);
let adapter = new ShareDataTableAdapter(documentListService, null);
let adapter = new ShareDataTableAdapter(documentListService, null, null);
let value = adapter.getValue(row, col);
expect(value).toBe(rawValue);
});
it('should log error when having date conversion issues', () => {
let dateValue = <Date> {};
let file = new FileNode();
file.entry.createdAt = <any> dateValue;
let col = <DataColumn> {
key: 'createdAt',
type: 'date',
format: 'medium'
};
let row = new ShareDataRow(file, documentListService, null);
let adapter = new ShareDataTableAdapter(documentListService, null);
spyOn(console, 'error').and.stub();
let value = adapter.getValue(row, col);
expect(value).toBe('Error');
expect(console.error).toHaveBeenCalled();
});
it('should generate fallback icon for a file thumbnail with missing mime type', () => {
spyOn(documentListService, 'getDefaultMimeTypeIcon').and.returnValue(`assets/images/ft_ic_miscellaneouse.svg`);
let adapter = new ShareDataTableAdapter(documentListService, null);
let adapter = new ShareDataTableAdapter(documentListService, null, null);
let file = new FileNode();
file.entry.content.mimeType = null;
@@ -205,7 +169,7 @@ describe('ShareDataTableAdapter', () => {
it('should generate fallback icon for a file with no content entry', () => {
spyOn(documentListService, 'getDefaultMimeTypeIcon').and.returnValue(`assets/images/ft_ic_miscellaneouse.svg`);
let adapter = new ShareDataTableAdapter(documentListService, null);
let adapter = new ShareDataTableAdapter(documentListService, null, null);
let file = new FileNode();
file.entry.content = null;
@@ -224,7 +188,7 @@ describe('ShareDataTableAdapter', () => {
let file = new FileNode();
file.entry['icon'] = imageUrl;
let adapter = new ShareDataTableAdapter(documentListService, null);
let adapter = new ShareDataTableAdapter(documentListService, null, null);
let row = new ShareDataRow(file, documentListService, null);
let col = <DataColumn> { type: 'image', key: 'icon' };
@@ -235,7 +199,7 @@ describe('ShareDataTableAdapter', () => {
it('should resolve folder icon', () => {
spyOn(documentListService, 'getMimeTypeIcon').and.returnValue(`assets/images/ft_ic_folder.svg`);
let adapter = new ShareDataTableAdapter(documentListService, null);
let adapter = new ShareDataTableAdapter(documentListService, null, null);
let row = new ShareDataRow(new FolderNode(), documentListService, null);
let col = <DataColumn> { type: 'image', key: '$thumbnail' };
@@ -247,7 +211,7 @@ describe('ShareDataTableAdapter', () => {
it('should resolve file thumbnail', () => {
let imageUrl = 'http://<addresss>';
let adapter = new ShareDataTableAdapter(documentListService, null);
let adapter = new ShareDataTableAdapter(documentListService, null, null);
adapter.thumbnails = true;
let file = new FileNode();
@@ -262,7 +226,7 @@ describe('ShareDataTableAdapter', () => {
it('should resolve fallback file icon for unknown node', () => {
spyOn(documentListService, 'getDefaultMimeTypeIcon').and.returnValue(`assets/images/ft_ic_miscellaneous.svg`);
let adapter = new ShareDataTableAdapter(documentListService, null);
let adapter = new ShareDataTableAdapter(documentListService, null, null);
let file = new FileNode();
file.entry.isFile = false;
@@ -279,7 +243,7 @@ describe('ShareDataTableAdapter', () => {
it('should resolve file icon for content type', () => {
spyOn(documentListService, 'getMimeTypeIcon').and.returnValue(`assets/images/ft_ic_raster_image.svg`);
let adapter = new ShareDataTableAdapter(documentListService, null);
let adapter = new ShareDataTableAdapter(documentListService, null, null);
let file = new FileNode();
file.entry.isFile = false;
@@ -300,7 +264,7 @@ describe('ShareDataTableAdapter', () => {
let folder = new FolderNode();
let col = <DataColumn> { key: 'name' };
let adapter = new ShareDataTableAdapter(documentListService, [col]);
let adapter = new ShareDataTableAdapter(documentListService, null, [col]);
adapter.setSorting(new DataSorting('name', 'asc'));
adapter.setRows([
@@ -323,7 +287,7 @@ describe('ShareDataTableAdapter', () => {
file2.entry['dateProp'] = new Date(2016, 6, 30, 13, 14, 2);
let col = <DataColumn> { key: 'dateProp' };
let adapter = new ShareDataTableAdapter(documentListService, [col]);
let adapter = new ShareDataTableAdapter(documentListService, null, [col]);
adapter.setRows([
new ShareDataRow(file2, documentListService, null),
@@ -353,7 +317,7 @@ describe('ShareDataTableAdapter', () => {
file4.entry.content.sizeInBytes = 2852791665; // 2.66 GB
let col = <DataColumn> { key: 'content.sizeInBytes' };
let adapter = new ShareDataTableAdapter(documentListService, [col]);
let adapter = new ShareDataTableAdapter(documentListService, null, [col]);
adapter.setRows([
new ShareDataRow(file3, documentListService, null),
@@ -386,7 +350,7 @@ describe('ShareDataTableAdapter', () => {
let file6 = new FileNode('b');
let col = <DataColumn> { key: 'name' };
let adapter = new ShareDataTableAdapter(documentListService, [col]);
let adapter = new ShareDataTableAdapter(documentListService, null, [col]);
adapter.setRows([
new ShareDataRow(file4, documentListService, null),
@@ -15,9 +15,7 @@
* limitations under the License.
*/
import { DataColumn, DataRow, DataSorting, DataTableAdapter } from '@alfresco/adf-core';
import { TimeAgoPipe } from '@alfresco/adf-core';
import { DatePipe } from '@angular/common';
import { DataColumn, DataRow, DataSorting, DataTableAdapter, ThumbnailService } from '@alfresco/adf-core';
import { NodePaging } from 'alfresco-js-api';
import { PermissionStyleModel } from './../models/permissions-style.model';
import { DocumentListService } from './../services/document-list.service';
@@ -28,6 +26,7 @@ export class ShareDataTableAdapter implements DataTableAdapter {
ERR_ROW_NOT_FOUND: string = 'Row not found';
ERR_COL_NOT_FOUND: string = 'Column not found';
private _sortingMode: string;
private sorting: DataSorting;
private rows: DataRow[];
private columns: DataColumn[];
@@ -39,12 +38,27 @@ export class ShareDataTableAdapter implements DataTableAdapter {
permissionsStyle: PermissionStyleModel[];
selectedRow: DataRow;
set sortingMode(value: string) {
let newValue = (value || 'client').toLowerCase();
if (newValue !== 'client' && newValue !== 'server') {
newValue = 'client';
}
this._sortingMode = newValue;
}
get sortingMode(): string {
return this._sortingMode;
}
constructor(private documentListService: DocumentListService,
private thumbnailService: ThumbnailService,
schema: DataColumn[] = [],
sorting?: DataSorting) {
sorting?: DataSorting,
sortingMode: string = 'client') {
this.rows = [];
this.columns = schema || [];
this.sorting = sorting;
this.sortingMode = sortingMode;
}
getRows(): Array<DataRow> {
@@ -78,16 +92,6 @@ export class ShareDataTableAdapter implements DataTableAdapter {
return dataRow.cache[col.key];
}
if (col.type === 'date') {
try {
const result = this.formatDate(col, value);
return dataRow.cacheValue(col.key, result);
} catch (err) {
console.error(`Error parsing date ${value} to format ${col.format}`);
return 'Error';
}
}
if (col.key === '$thumbnail') {
if (this.imageResolver) {
@@ -132,21 +136,6 @@ export class ShareDataTableAdapter implements DataTableAdapter {
return dataRow.cacheValue(col.key, value);
}
formatDate(col: DataColumn, value: any): string {
if (col.type === 'date') {
const format = col.format || 'medium';
if (format === 'timeAgo') {
const timeAgoPipe = new TimeAgoPipe();
return timeAgoPipe.transform(value);
} else {
const datePipe = new DatePipe('en-US');
return datePipe.transform(value, format);
}
}
return value;
}
getSorting(): DataSorting {
return this.sorting;
}
@@ -175,6 +164,10 @@ export class ShareDataTableAdapter implements DataTableAdapter {
}
private sortRows(rows: DataRow[], sorting: DataSorting) {
if (this.sortingMode === 'server') {
return;
}
const options: Intl.CollatorOptions = {};
if (sorting && sorting.key && rows && rows.length > 0) {
@@ -215,23 +208,25 @@ export class ShareDataTableAdapter implements DataTableAdapter {
if (page && page.list) {
let data = page.list.entries;
if (data && data.length > 0) {
rows = data.map(item => new ShareDataRow(item, this.documentListService, this.permissionsStyle));
rows = data.map(item => new ShareDataRow(item, this.documentListService, this.permissionsStyle, this.thumbnailService));
if (this.filter) {
rows = rows.filter(this.filter);
}
// Sort by first sortable or just first column
if (this.columns && this.columns.length > 0) {
let sorting = this.getSorting();
if (sorting) {
this.sortRows(rows, sorting);
} else {
let sortable = this.columns.filter(c => c.sortable);
if (sortable.length > 0) {
this.sort(sortable[0].key, 'asc');
if (this.sortingMode !== 'server') {
// Sort by first sortable or just first column
if (this.columns && this.columns.length > 0) {
let sorting = this.getSorting();
if (sorting) {
this.sortRows(rows, sorting);
} else {
this.sort(this.columns[0].key, 'asc');
let sortable = this.columns.filter(c => c.sortable);
if (sortable.length > 0) {
this.sort(sortable[0].key, 'asc');
} else {
this.sort(this.columns[0].key, 'asc');
}
}
}
}
@@ -23,7 +23,8 @@ export class ContentActionModel {
target: string;
permission: string;
disableWithNoPermission: boolean = false;
disabled: boolean = false;
disabled: boolean | Function = false;
visible: boolean | Function = true;
constructor(obj?: any) {
if (obj) {
@@ -34,7 +35,14 @@ export class ContentActionModel {
this.target = obj.target;
this.permission = obj.permission;
this.disableWithNoPermission = obj.disableWithNoPermission;
this.disabled = obj.disabled;
if (obj.hasOwnProperty('disabled')) {
this.disabled = obj.disabled;
}
if (obj.hasOwnProperty('visible')) {
this.visible = obj.visible;
}
}
}
}
@@ -25,7 +25,8 @@ import {
NodePaging,
PersonEntry,
SitePaging,
DeletedNodesPaging
DeletedNodesPaging,
SearchRequest
} from 'alfresco-js-api';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Observable';
@@ -39,12 +40,18 @@ export class CustomResourcesService {
private logService: LogService) {
}
/**
* Gets files recently accessed by a user.
* @param personId ID of the user
* @param pagination Specifies how to paginate the results
* @returns List of nodes for the recently used files
*/
getRecentFiles(personId: string, pagination: PaginationModel): Observable<NodePaging> {
return new Observable(observer => {
this.apiService.peopleApi.getPerson(personId)
.then((person: PersonEntry) => {
const username = person.entry.id;
const query = {
const query: SearchRequest = {
query: {
query: '*',
language: 'afts'
@@ -66,8 +73,8 @@ export class CustomResourcesService {
}
};
return this.apiService.searchApi.search(query)
.then((serachResult) => {
observer.next(serachResult);
.then((searchResult) => {
observer.next(searchResult);
observer.complete();
},
(err) => {
@@ -82,6 +89,12 @@ export class CustomResourcesService {
}).catch(err => this.handleError(err));
}
/**
* Gets favorite files for the current user.
* @param pagination Specifies how to paginate the results
* @param includeFields List of data field names to include in the results
* @returns List of favorite files
*/
loadFavorites(pagination: PaginationModel, includeFields: string[] = []): Observable<NodePaging> {
let includeFieldsRequest = this.getIncludesFields(includeFields);
@@ -122,6 +135,11 @@ export class CustomResourcesService {
}).catch(err => this.handleError(err));
}
/**
* Gets sites that the current user is a member of.
* @param pagination Specifies how to paginate the results
* @returns List of sites
*/
loadMemberSites(pagination: PaginationModel): Observable<NodePaging> {
const options = {
include: ['properties'],
@@ -156,6 +174,11 @@ export class CustomResourcesService {
}).catch(err => this.handleError(err));
}
/**
* Gets all sites in the respository.
* @param pagination Specifies how to paginate the results
* @returns List of sites
*/
loadSites(pagination: PaginationModel): Observable<NodePaging> {
const options = {
include: ['properties'],
@@ -182,6 +205,12 @@ export class CustomResourcesService {
}).catch(err => this.handleError(err));
}
/**
* Gets all items currently in the trash.
* @param pagination Specifies how to paginate the results
* @param includeFields List of data field names to include in the results
* @returns List of deleted items
*/
loadTrashcan(pagination: PaginationModel, includeFields: string[] = []): Observable<DeletedNodesPaging> {
let includeFieldsRequest = this.getIncludesFields(includeFields);
@@ -195,6 +224,12 @@ export class CustomResourcesService {
}
/**
* Gets shared links for the current user.
* @param pagination Specifies how to paginate the results
* @param includeFields List of data field names to include in the results
* @returns List of shared links
*/
loadSharedLinks(pagination: PaginationModel, includeFields: string[] = []): Observable<NodePaging> {
let includeFieldsRequest = this.getIncludesFields(includeFields);
@@ -207,6 +242,11 @@ export class CustomResourcesService {
return Observable.fromPromise(this.apiService.sharedLinksApi.findSharedLinks(options)).catch(err => this.handleError(err));
}
/**
* Is the folder ID one of the well-known aliases?
* @param folderId Folder ID name to check
* @returns True if the ID is a well-known name, false otherwise
*/
isCustomSource(folderId: string): boolean {
let isCustomSources = false;
const sources = ['-trashcan-', '-sharedlinks-', '-sites-', '-mysites-', '-favorites-', '-recent-'];
@@ -218,6 +258,13 @@ export class CustomResourcesService {
return isCustomSources;
}
/**
* Gets a folder's contents.
* @param nodeId ID of the target folder node
* @param pagination Specifies how to paginate the results
* @param includeFields List of data field names to include in the results
* @returns List of items contained in the folder
*/
loadFolderByNodeId(nodeId: string, pagination: PaginationModel, includeFields: string[]): Observable<NodePaging> {
if (nodeId === '-trashcan-') {
return this.loadTrashcan(pagination, includeFields);
@@ -235,6 +282,13 @@ export class CustomResourcesService {
}
// TODO: remove it from here
/**
* Gets the contents of one of the well-known aliases in the form of node ID strings.
* @param nodeId ID of the target folder node
* @param pagination Specifies how to paginate the results
* @returns List of node IDs
*/
getCorrespondingNodeIds(nodeId: string, pagination: PaginationModel): Observable<string[]> {
if (nodeId === '-trashcan-') {
return Observable.fromPromise(this.apiService.nodesApi.getDeletedNodes()
@@ -15,12 +15,8 @@
* limitations under the License.
*/
import {
AlfrescoApiServiceMock,
AppConfigService,
ContentService,
StorageService,
TranslationMock
import { AlfrescoApiServiceMock, AppConfigService, ContentService,
StorageService, setupTestBed, CoreModule, TranslationMock
} from '@alfresco/adf-core';
import { FileNode, FolderNode } from '../../mock';
import { ContentActionHandler } from '../models/content-action.model';
@@ -35,6 +31,12 @@ describe('DocumentActionsService', () => {
let documentListService: DocumentListService;
let nodeActionsService: NodeActionsService;
setupTestBed({
imports: [
CoreModule.forRoot()
]
});
beforeEach(() => {
let contentService = new ContentService(null, null, null, null);
let alfrescoApiService = new AlfrescoApiServiceMock(new AppConfigService(null), new StorageService());
@@ -16,7 +16,7 @@
*/
import { AlfrescoApiServiceMock, AlfrescoApiService,
AppConfigService, StorageService, ContentService } from '@alfresco/adf-core';
AppConfigService, StorageService, ContentService, setupTestBed, CoreModule } from '@alfresco/adf-core';
import { DocumentListService } from './document-list.service';
declare let jasmine: any;
@@ -87,6 +87,12 @@ describe('DocumentListService', () => {
}
};
setupTestBed({
imports: [
CoreModule.forRoot()
]
});
beforeEach(() => {
let contentService = new ContentService(null, null, null, null);
alfrescoApiService = new AlfrescoApiServiceMock(new AppConfigService(null), new StorageService());
@@ -90,7 +90,7 @@ export class DocumentListService {
}
/**
* Move a node to destination node
* Moves a node to destination node.
*
* @param nodeId The id of the node to be moved
* @param targetParentId The id of the folder where the node will be moved
@@ -102,7 +102,7 @@ export class DocumentListService {
}
/**
* Create a new folder in the path.
* Creates a new folder in the path.
* @param name Folder name
* @param parentId Parent folder ID
* @returns Details of the created folder node
@@ -128,7 +128,7 @@ export class DocumentListService {
/**
* Gets a node via its node ID.
* @param nodeId
* @param nodeId ID of the target node
* @param includeFields Extra information to include (available options are "aspectNames", "isLink" and "association")
* @returns Details of the folder
*/
@@ -16,7 +16,7 @@
*/
import { TestBed } from '@angular/core/testing';
import { AlfrescoApiServiceMock, AppConfigService, StorageService, ContentService, TranslationMock } from '@alfresco/adf-core';
import { AlfrescoApiServiceMock, AppConfigService, StorageService, ContentService, setupTestBed, CoreModule, TranslationMock } from '@alfresco/adf-core';
import { Observable } from 'rxjs/Observable';
import { FileNode, FolderNode } from '../../mock';
import { ContentActionHandler } from '../models/content-action.model';
@@ -28,6 +28,12 @@ describe('FolderActionsService', () => {
let service: FolderActionsService;
let documentListService: DocumentListService;
setupTestBed({
imports: [
CoreModule.forRoot()
]
});
beforeEach(() => {
let appConfig: AppConfigService = TestBed.get(AppConfigService);
appConfig.config.ecmHost = 'http://localhost:9876/ecm';
@@ -17,14 +17,13 @@
import { async, TestBed } from '@angular/core/testing';
import { MinimalNodeEntryEntity } from 'alfresco-js-api';
import { AppConfigService } from '@alfresco/adf-core';
import { AppConfigService, setupTestBed, CoreModule } from '@alfresco/adf-core';
import { DocumentListService } from './document-list.service';
import { NodeActionsService } from './node-actions.service';
import { ContentNodeDialogService } from '../../content-node-selector/content-node-dialog.service';
import { Observable } from 'rxjs/Observable';
import { MatDialogRef } from '@angular/material';
import { NodeLockDialogComponent } from '../../dialogs/node-lock.dialog';
import { BrowserDynamicTestingModule } from '@angular/platform-browser-dynamic/testing';
import { DialogModule } from '../../dialogs/dialog.module';
const fakeNode: MinimalNodeEntryEntity = <MinimalNodeEntryEntity> {
id: 'fake'
@@ -39,24 +38,18 @@ describe('NodeActionsService', () => {
open: jasmine.createSpy('open')
};
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [
NodeLockDialogComponent
],
providers: [
NodeActionsService,
DocumentListService,
ContentNodeDialogService,
{ provide: MatDialogRef, useValue: dialogRef }
]
});
TestBed.overrideModule(BrowserDynamicTestingModule, {
set: { entryComponents: [ NodeLockDialogComponent ] }
}).compileComponents();
}));
setupTestBed({
imports: [
CoreModule.forRoot(),
DialogModule
],
providers: [
NodeActionsService,
DocumentListService,
ContentNodeDialogService,
{ provide: MatDialogRef, useValue: dialogRef }
]
});
beforeEach(() => {
let appConfig: AppConfigService = TestBed.get(AppConfigService);
@@ -76,7 +69,7 @@ describe('NodeActionsService', () => {
spyOn(contentDialogService, 'openCopyMoveDialog').and.returnValue(Observable.of([fakeNode]));
service.copyContent(fakeNode, 'allowed').subscribe((value) => {
expect(value).toBe('OPERATION.SUCCES.CONTENT.COPY');
expect(value).toBe('OPERATION.SUCCESS.CONTENT.COPY');
});
}));
@@ -85,7 +78,7 @@ describe('NodeActionsService', () => {
spyOn(contentDialogService, 'openCopyMoveDialog').and.returnValue(Observable.of([fakeNode]));
service.moveContent(fakeNode, 'allowed').subscribe((value) => {
expect(value).toBe('OPERATION.SUCCES.CONTENT.MOVE');
expect(value).toBe('OPERATION.SUCCESS.CONTENT.MOVE');
});
}));
@@ -94,7 +87,7 @@ describe('NodeActionsService', () => {
spyOn(contentDialogService, 'openCopyMoveDialog').and.returnValue(Observable.of([fakeNode]));
service.moveFolder(fakeNode, 'allowed').subscribe((value) => {
expect(value).toBe('OPERATION.SUCCES.FOLDER.MOVE');
expect(value).toBe('OPERATION.SUCCESS.FOLDER.MOVE');
});
}));
@@ -103,7 +96,7 @@ describe('NodeActionsService', () => {
spyOn(contentDialogService, 'openCopyMoveDialog').and.returnValue(Observable.of([fakeNode]));
service.copyFolder(fakeNode, 'allowed').subscribe((value) => {
expect(value).toBe('OPERATION.SUCCES.FOLDER.COPY');
expect(value).toBe('OPERATION.SUCCESS.FOLDER.COPY');
});
}));
@@ -100,7 +100,7 @@ export class NodeActionsService {
const selection = selections[0];
this.documentListService[`${action}Node`].call(this.documentListService, contentEntry.id, selection.id)
.subscribe(
observable.next.bind(observable, `OPERATION.SUCCES.${type.toUpperCase()}.${action.toUpperCase()}`),
observable.next.bind(observable, `OPERATION.SUCCESS.${type.toUpperCase()}.${action.toUpperCase()}`),
observable.error.bind(observable)
);
},
@@ -15,61 +15,70 @@
* limitations under the License.
*/
import { HttpClientModule } from '@angular/common/http';
import { Component } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MatDialog, MatDialogModule } from '@angular/material';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { MatDialog } from '@angular/material';
import { By } from '@angular/platform-browser';
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
import { Observable } from 'rxjs/Observable';
import { FolderDialogComponent } from '../dialogs/folder.dialog';
import { DirectiveModule, ContentService, TranslateLoaderService } from '@alfresco/adf-core';
import { ContentService, setupTestBed, CoreModule } from '@alfresco/adf-core';
import { FolderCreateDirective } from './folder-create.directive';
import { Subject } from 'rxjs/Subject';
import { MinimalNodeEntryEntity } from 'alfresco-js-api';
@Component({
template: '<div [adf-create-folder]="parentNode"></div>'
template: `
<div
[adf-create-folder]="parentNode"
(success)="success($event)"
title="create-title"
[nodeType]="'cm:my-litte-pony'">
</div>`
})
class TestTypeComponent {
parentNode = '';
public successParameter: MinimalNodeEntryEntity = null;
success(node: MinimalNodeEntryEntity) {
this.successParameter = node;
}
}
@Component({
template: `<div [adf-create-folder]="parentNode"></div>`
})
class TestComponent {
parentNode = '';
public successParameter: MinimalNodeEntryEntity = null;
}
describe('FolderCreateDirective', () => {
let fixture: ComponentFixture<TestComponent>;
let fixture: ComponentFixture<TestTypeComponent | TestComponent>;
let element;
let node: any;
let dialog: MatDialog;
let contentService: ContentService;
let dialogRefMock;
const event = { type: 'click', preventDefault: () => null };
setupTestBed({
imports: [
CoreModule.forRoot()
],
declarations: [
TestTypeComponent,
TestComponent,
FolderDialogComponent,
FolderCreateDirective
],
providers: [
ContentService
]
});
beforeEach(() => {
TestBed.configureTestingModule({
imports: [
HttpClientModule,
MatDialogModule,
FormsModule,
DirectiveModule,
ReactiveFormsModule,
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
useClass: TranslateLoaderService
}
})
],
declarations: [
TestComponent,
FolderDialogComponent,
FolderCreateDirective
],
providers: [
ContentService
]
});
TestBed.compileComponents();
fixture = TestBed.createComponent(TestComponent);
element = fixture.debugElement.query(By.directive(FolderCreateDirective));
dialog = TestBed.get(MatDialog);
@@ -80,37 +89,101 @@ describe('FolderCreateDirective', () => {
node = { entry: { id: 'nodeId' } };
dialogRefMock = {
afterClosed: val => Observable.of(val)
afterClosed: val => Observable.of(val),
componentInstance: {
error: new Subject<any>(),
success: new Subject<MinimalNodeEntryEntity>()
}
};
spyOn(dialog, 'open').and.returnValue(dialogRefMock);
});
xit('should emit folderCreate event when input value is not undefined', (done) => {
spyOn(dialogRefMock, 'afterClosed').and.returnValue(Observable.of(node));
spyOn(contentService.folderCreate, 'next');
describe('With overrides', () => {
contentService.folderCreate.subscribe((val) => {
expect(val).toBe(node);
done();
beforeEach(() => {
fixture = TestBed.createComponent(TestTypeComponent);
element = fixture.debugElement.query(By.directive(FolderCreateDirective));
dialog = TestBed.get(MatDialog);
contentService = TestBed.get(ContentService);
spyOn(dialog, 'open').and.returnValue(dialogRefMock);
});
fixture.detectChanges();
xit('should emit folderCreate event when input value is not undefined', (done) => {
spyOn(dialogRefMock, 'afterClosed').and.returnValue(Observable.of(node));
spyOn(contentService.folderCreate, 'next');
fixture.whenStable().then(() => {
element.nativeElement.click();
contentService.folderCreate.subscribe((val) => {
expect(val).toBe(node);
done();
});
fixture.detectChanges();
fixture.whenStable().then(() => {
element.nativeElement.click();
});
});
it('should not emit folderCreate event when input value is undefined', () => {
spyOn(dialogRefMock, 'afterClosed').and.returnValue(Observable.of(null));
spyOn(contentService.folderCreate, 'next');
fixture.detectChanges();
fixture.whenStable().then(() => {
element.nativeElement.click();
expect(contentService.folderCreate.next).not.toHaveBeenCalled();
});
});
it('should emit success event with node if the folder creation was successful', async(() => {
const testNode = <MinimalNodeEntryEntity> {};
fixture.detectChanges();
element.triggerEventHandler('click', event);
dialogRefMock.componentInstance.success.next(testNode);
fixture.whenStable().then(() => {
expect(fixture.componentInstance.successParameter).toBe(testNode);
});
}));
it('should open the dialog with the proper title and nodeType', async(() => {
fixture.detectChanges();
element.triggerEventHandler('click', event);
expect(dialog.open).toHaveBeenCalledWith(jasmine.any(Function), {
data: {
parentNodeId: jasmine.any(String),
createTitle: 'create-title',
nodeType: 'cm:my-litte-pony'
},
width: jasmine.any(String)
});
}));
});
it('should not emit folderCreate event when input value is undefined', () => {
spyOn(dialogRefMock, 'afterClosed').and.returnValue(Observable.of(null));
spyOn(contentService.folderCreate, 'next');
describe('Without overrides', () => {
fixture.detectChanges();
fixture.whenStable().then(() => {
element.nativeElement.click();
expect(contentService.folderCreate.next).not.toHaveBeenCalled();
beforeEach(() => {
fixture = TestBed.createComponent(TestComponent);
element = fixture.debugElement.query(By.directive(FolderCreateDirective));
dialog = TestBed.get(MatDialog);
contentService = TestBed.get(ContentService);
spyOn(dialog, 'open').and.returnValue(dialogRefMock);
});
it('should open the dialog with the default title and nodeType', async(() => {
fixture.detectChanges();
element.triggerEventHandler('click', event);
expect(dialog.open).toHaveBeenCalledWith(jasmine.any(Function), {
data: {
parentNodeId: jasmine.any(String),
createTitle: null,
nodeType: 'cm:folder'
},
width: jasmine.any(String)
});
}));
});
});
@@ -35,10 +35,22 @@ export class FolderCreateDirective {
@Input('adf-create-folder')
parentNodeId: string = DEFAULT_FOLDER_PARENT_ID;
/** Emitted when the create folder give error for example a folder with same name already exist */
/** Title of folder creation dialog. */
@Input()
title: string = null;
/** Type of node to create. */
@Input()
nodeType = 'cm:folder';
/** Emitted when an error occurs (eg, a folder with same name already exists). */
@Output()
error: EventEmitter<any> = new EventEmitter<any>();
/** Emitted when the folder is created successfully. */
@Output()
success: EventEmitter<MinimalNodeEntryEntity> = new EventEmitter<MinimalNodeEntryEntity>();
@HostListener('click', [ '$event' ])
onClick(event) {
event.preventDefault();
@@ -52,10 +64,10 @@ export class FolderCreateDirective {
private get dialogConfig(): MatDialogConfig {
const { DIALOG_WIDTH: width } = FolderCreateDirective;
const { parentNodeId } = this;
const { parentNodeId, title: createTitle, nodeType } = this;
return {
data: { parentNodeId },
data: { parentNodeId, createTitle, nodeType },
width: `${width}px`
};
}
@@ -68,6 +80,10 @@ export class FolderCreateDirective {
this.error.emit(error);
});
dialogInstance.componentInstance.success.subscribe((node: MinimalNodeEntryEntity) => {
this.success.emit(node);
});
dialogInstance.afterClosed().subscribe((node: MinimalNodeEntryEntity) => {
if (node) {
content.folderCreate.next(node);
@@ -15,24 +15,27 @@
* limitations under the License.
*/
import { HttpClientModule } from '@angular/common/http';
import { Component } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { MatDialog, MatDialogModule } from '@angular/material';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { MatDialog } from '@angular/material';
import { By } from '@angular/platform-browser';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
import { Observable } from 'rxjs/Observable';
import { ContentService, TranslateLoaderService, DirectiveModule } from '@alfresco/adf-core';
import { ContentService, setupTestBed, CoreModule } from '@alfresco/adf-core';
import { FolderEditDirective } from './folder-edit.directive';
import { MinimalNodeEntryEntity } from 'alfresco-js-api';
import { Subject } from 'rxjs/Subject';
@Component({
template: '<div [adf-edit-folder]="folder"></div>'
template: '<div [adf-edit-folder]="folder" (success)="success($event)" title="edit-title"></div>'
})
class TestComponent {
folder = {};
public successParameter: MinimalNodeEntryEntity = null;
success(node: MinimalNodeEntryEntity) {
this.successParameter = node;
}
}
describe('FolderEditDirective', () => {
@@ -48,33 +51,17 @@ describe('FolderEditDirective', () => {
preventDefault: () => null
};
setupTestBed({
imports: [
CoreModule.forRoot()
],
declarations: [
TestComponent,
FolderEditDirective
]
});
beforeEach(() => {
TestBed.configureTestingModule({
imports: [
HttpClientModule,
MatDialogModule,
FormsModule,
ReactiveFormsModule,
DirectiveModule,
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
useClass: TranslateLoaderService
}
})
],
declarations: [
TestComponent,
FolderEditDirective
]
,
providers: [
ContentService
]
});
TestBed.compileComponents();
fixture = TestBed.createComponent(TestComponent);
element = fixture.debugElement.query(By.directive(FolderEditDirective));
dialog = TestBed.get(MatDialog);
@@ -85,7 +72,11 @@ describe('FolderEditDirective', () => {
node = { entry: { id: 'folderId' } };
dialogRefMock = {
afterClosed: val => Observable.of(val)
afterClosed: val => Observable.of(val),
componentInstance: {
error: new Subject<any>(),
success: new Subject<MinimalNodeEntryEntity>()
}
};
spyOn(dialog, 'open').and.returnValue(dialogRefMock);
@@ -114,4 +105,29 @@ describe('FolderEditDirective', () => {
expect(contentService.folderEdit.next).not.toHaveBeenCalled();
});
});
it('should emit success event with node if the folder creation was successful', async(() => {
const testNode = <MinimalNodeEntryEntity> {};
fixture.detectChanges();
element.triggerEventHandler('click', event);
dialogRefMock.componentInstance.success.next(testNode);
fixture.whenStable().then(() => {
expect(fixture.componentInstance.successParameter).toBe(testNode);
});
}));
it('should open the dialog with the proper title', async(() => {
fixture.detectChanges();
element.triggerEventHandler('click', event);
expect(dialog.open).toHaveBeenCalledWith(jasmine.any(Function), {
data: {
folder: jasmine.any(Object),
editTitle: 'edit-title'
},
width: jasmine.any(String)
});
}));
});
@@ -35,10 +35,18 @@ export class FolderEditDirective {
@Input('adf-edit-folder')
folder: MinimalNodeEntryEntity;
/** Emitted when the edit/create folder give error for example a folder with same name already exist */
/** Emitted when an error occurs (eg, a folder with same name already exists). */
@Output()
error: EventEmitter<any> = new EventEmitter<any>();
/** Title of folder edit dialog. */
@Input()
title: string = null;
/** Emitted when the folder has been edited successfully. */
@Output()
success: EventEmitter<MinimalNodeEntryEntity> = new EventEmitter<MinimalNodeEntryEntity>();
@HostListener('click', [ '$event' ])
onClick(event) {
event.preventDefault();
@@ -58,7 +66,10 @@ export class FolderEditDirective {
const { folder } = this;
return {
data: { folder },
data: {
folder,
editTitle: this.title
},
width: `${width}px`
};
}
@@ -71,6 +82,10 @@ export class FolderEditDirective {
this.error.emit(error);
});
dialogInstance.componentInstance.success.subscribe((node: MinimalNodeEntryEntity) => {
this.success.emit(node);
});
dialogInstance.afterClosed().subscribe((node: MinimalNodeEntryEntity) => {
if (node) {
content.folderEdit.next(node);
+51 -3
View File
@@ -6,7 +6,12 @@
"DOWNLOAD": "Herunterladen",
"UPLOAD": {
"TITLE": "Neue Version hochladen",
"TOOLTIP": "Einschränkung: Sie müssen eine Datei mit dem gleichen Namen hochladen, wenn Sie eine neue Version der Datei erstellen möchten."
"TOOLTIP": "Einschränkung: Sie müssen eine Datei mit dem gleichen Namen hochladen, wenn Sie eine neue Version der Datei erstellen möchten.",
"MAJOR": "bedeutende Änderungen (2.0)",
"MINOR": "kleine Änderungen (1.1)",
"COMMENT": "Kommentar abgeben",
"ADD": "Neue Version hinzufügen",
"CANCEL": "Abbrechen"
}
},
"CONFIRM_DELETE": {
@@ -72,7 +77,7 @@
"SELECT_LOCATION": "Speicherort auswählen"
},
"OPERATION": {
"SUCCES": {
"SUCCESS": {
"CONTENT": {
"COPY": "Kopieren erfolgreich",
"MOVE": "Verschieben erfolgreich"
@@ -155,6 +160,13 @@
},
"SEARCH": {
"CONTROL": {},
"BUTTON": {
"TOOLTIP": "Suchen",
"ARIA-LABEL": "Suchschaltfläche"
},
"INPUT": {
"ARIA-LABEL": "Sucheingabe"
},
"RESULTS": {
"SUMMARY": "{{numResults}} Ergebnis gefunden für {{searchTerm}}",
"NONE": "Keine Ergebnisse gefunden für {{searchTerm}}",
@@ -165,6 +177,29 @@
"MODIFIED_AT": "Bearbeitet"
}
},
"FILTER": {
"ACTIONS": {
"CLEAR": "Löschen",
"APPLY": "Anwenden",
"CLEAR-ALL": "Alle löschen",
"SHOW-MORE": "Mehr anzeigen",
"SHOW-LESS": "Weniger anzeigen",
"FILTER-CATEGORY": "Kategorie filtern"
},
"RANGE": {
"FROM": "Von",
"TO": "Bis",
"FROM-DATE": "Von",
"TO-DATE": "Bis"
},
"VALIDATION": {
"REQUIRED-VALUE": "Erforderlicher Wert",
"NO-DAYS": "Keine Tage ausgewählt.",
"INVALID-FORMAT": "Unzulässiges Format",
"INVALID-DATE": "Unzulässiges Datum. Das Datum muss das Format '{{ requiredFormat }}' haben.",
"BEYOND-MAX-DATE": "Das Datum liegt nach dem maximal zulässigen Datum."
}
},
"ICONS": {
"ft_ic_raster_image": "Bilddatei",
"ft_ic_pdf": "PDF-Dokument",
@@ -235,7 +270,20 @@
"INHERITED": "Geerbt",
"AUTHORITY_ID": "Autoritäts-ID",
"ROLE": "Rolle",
"LOCALLY_SET": "Lokal festgelegt"
"LOCALLY_SET": "Lokal festgelegt",
"NO_PERMISSIONS": "Keine Berechtigungen"
},
"ADD-PERMISSION": {
"SEARCH": "Suchen",
"TYPE-MESSAGE": "Geben Sie etwas ein, um nach Gruppen oder Personen zu suchen",
"NO-RESULT": "Für diese Suchanfrage wurden keine Ergebnisse gefunden",
"ADD-ACTION": "Hinzufügen",
"CLOSE-ACTION": "Schließen",
"BASE-DIALOG-TITLE": "Es wird nach Gruppen oder Personen gesucht, die hinzugefügt werden können...",
"EVERYONE": "JEDER"
},
"ERROR": {
"DUPLICATE-PERMISSION": "Mindestens eine der von Ihnen bestimmten Berechtigungen ist bereits vorhanden: {{list}}"
}
}
}

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