Merge pull request #6284 from opf/housekeeping/remove-unused-ui-components

Remove unused frontend UI components

[ci skip]
pull/6285/head
Oliver Günther 7 years ago committed by GitHub
commit 057a288a5f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      app/assets/javascripts/styleguide.js
  2. 5
      app/assets/stylesheets/content/_forms.sass
  3. 23
      frontend/app/angular-modules.ts
  4. 14
      frontend/app/angular4-modules.ts
  5. 3
      frontend/app/angular4-transition-utils.ts
  6. 36
      frontend/app/components/common/filters/html-escape.filter.ts
  7. 110
      frontend/app/components/common/focus/focus-helper.ts
  8. 2
      frontend/app/components/common/model-auth/model-auth.service.test.ts
  9. 2
      frontend/app/components/filters/wp-filters/wp-filters.service.test.ts
  10. 4
      frontend/app/components/filters/wp-filters/wp-filters.service.ts
  11. 105
      frontend/app/components/modals/wp-destroy-modal/wp-destroy-modal.controller.ts
  12. 76
      frontend/app/components/modals/wp-destroy-modal/wp-destroy-modal.html
  13. 39
      frontend/app/components/modals/wp-destroy-modal/wp-destroy-modal.ts
  14. 78
      frontend/app/components/modals/wp-destroy-modal/wp-destroy.modal.html
  15. 118
      frontend/app/components/modals/wp-destroy-modal/wp-destroy.modal.ts
  16. 5
      frontend/app/components/op-context-menu/op-context-menu.service.ts
  17. 11
      frontend/app/components/op-context-menu/wp-context-menu/wp-single-context-menu.ts
  18. 8
      frontend/app/components/op-context-menu/wp-context-menu/wp-table-context-menu.directive.ts
  19. 5
      frontend/app/components/op-modals/op-modal.service.ts
  20. 3
      frontend/app/components/projects/current-project.service.test.ts
  21. 33
      frontend/app/components/work-packages/work-package.service.ts
  22. 5
      frontend/app/components/wp-edit-form/single-view-edit-context.ts
  23. 5
      frontend/app/components/wp-edit-form/table-row-edit-context.ts
  24. 5
      frontend/app/components/wp-edit-form/work-package-edit-field-handler.ts
  25. 4
      frontend/app/components/wp-fast-table/handlers/state/selection-transformer.ts
  26. 5
      frontend/app/components/wp-inline-create/wp-inline-create.component.ts
  27. 2
      frontend/app/components/wp-query-select/wp-query-select-dropdown.component.ts
  28. 4
      frontend/app/components/wp-table/external-configuration/external-query-configuration.service.ts
  29. 94
      frontend/app/helpers/custom-field-helper.js
  30. 35
      frontend/app/helpers/index.js
  31. 179
      frontend/app/helpers/svg-helper.js
  32. 64
      frontend/app/helpers/work-package-loading-helper.js
  33. 2
      frontend/app/init-app.ts
  34. 29
      frontend/app/models/index.js
  35. 2
      frontend/app/openproject-tests.js
  36. 56
      frontend/app/services/conversion-service.js
  37. 5
      frontend/app/services/index.js
  38. 72
      frontend/app/services/sort-service.js
  39. 45
      frontend/app/services/status-service.js
  40. 8
      frontend/app/templates/components/accessible_checkbox.html
  41. 4
      frontend/app/templates/components/accessible_element.html
  42. 2
      frontend/app/templates/components/empty_element.html
  43. 3
      frontend/app/templates/components/icon_wrapper.html
  44. 4
      frontend/app/templates/components/modal_loading.html
  45. 5
      frontend/app/templates/components/notifications.html
  46. 1
      frontend/app/templates/components/sort_link.html
  47. 14
      frontend/app/templates/components/user_field.html
  48. 5
      frontend/app/templates/components/zoom_slider.html
  49. 52
      frontend/app/templates/work_packages/query_columns.html
  50. 43
      frontend/app/ui_components/accessible-checkbox-directive.js
  51. 38
      frontend/app/ui_components/accessible-element-directive.js
  52. 56
      frontend/app/ui_components/attachment-icon-directive.js
  53. 43
      frontend/app/ui_components/confirm-popup-directive.js
  54. 34
      frontend/app/ui_components/empty-element-directive.js
  55. 48
      frontend/app/ui_components/filters/ancestors-expanded-filter.js
  56. 26
      frontend/app/ui_components/focus-directive.js
  57. 131
      frontend/app/ui_components/focus-helper.js
  58. 37
      frontend/app/ui_components/icon-wrapper-directive.js
  59. 58
      frontend/app/ui_components/inaccessible-by-tab-directive.js
  60. 33
      frontend/app/ui_components/index.js
  61. 47
      frontend/app/ui_components/modal-directive.js
  62. 38
      frontend/app/ui_components/modal-loading-directive.js
  63. 41
      frontend/app/ui_components/selectable-title-directive.js
  64. 56
      frontend/app/ui_components/single-click.js
  65. 46
      frontend/app/ui_components/slide-toggle.js
  66. 88
      frontend/app/ui_components/sort-link-directive.js
  67. 45
      frontend/app/ui_components/user-field-directive.js
  68. 73
      frontend/app/ui_components/zoom-slider-directive.js
  69. 3
      frontend/app/vendors.js
  70. 493
      frontend/npm-shrinkwrap.json
  71. 5
      frontend/package.json
  72. 71
      frontend/tests/unit/tests/directives/components/inaccessible-by-tab-directive-test.js
  73. 166
      frontend/tests/unit/tests/helpers/custom-field-helper-test.js
  74. 62
      frontend/tests/unit/tests/services/conversion-service-test.js
  75. 95
      frontend/tests/unit/tests/services/sort-service-test.js
  76. 84
      frontend/tests/unit/tests/ui_components/accessible-checkbox-directive-test.js
  77. 76
      frontend/tests/unit/tests/ui_components/focus-directive-test.js
  78. 64
      frontend/tests/unit/tests/ui_components/modal-directive-test.js
  79. 73
      frontend/tests/unit/tests/ui_components/slide-toggle-directive-test.js
  80. 121
      frontend/tests/unit/tests/ui_components/sort-link-directive-test.js
  81. 96
      frontend/tests/unit/tests/ui_components/zoom-slider-directive-test.js

@ -38,5 +38,5 @@ jQuery(document).ready(function () {
angular.element(document).ready(function() {
angular.bootstrap(document, ['openproject']);
});
angular.module('openproject-style-guide', ['ui.select', 'ngSanitize']);
angular.module('openproject-style-guide', ['ui.select']);

@ -161,7 +161,10 @@ $form--field-types: (text-field, text-area, select, check-box, radio-button, ran
border-bottom-width: 0px
.form--space
padding-top: 10px
padding-top: 1rem
&.-left-spacing
padding-left: 1rem
&.-big
padding-top: 20px

@ -44,20 +44,10 @@ export const opServicesModule = angular.module('openproject.services', [
'openproject.helpers',
'openproject.workPackages.config',
'openproject.workPackages.helpers',
'openproject.api',
'angular-cache',
'openproject.filters'
'openproject.api'
]);
angular.module('openproject.helpers', ['openproject.services']);
export const opModelsModule = angular.module('openproject.models', [
'openproject.workPackages.config',
'openproject.services'
]);
export const opViewModelsModule = angular.module('openproject.viewModels', [
'openproject.services'
]);
// work packages
export const opWorkPackagesModule = angular.module('openproject.workPackages', [
'openproject.workPackages.activities',
@ -81,13 +71,10 @@ angular.module('openproject.workPackages.filters', [
]);
angular.module('openproject.workPackages.config', []);
export const wpControllersModule = angular.module('openproject.workPackages.controllers', [
'openproject.models',
'openproject.viewModels',
'openproject.workPackages.helpers',
'openproject.services',
'openproject.workPackages.config',
'openproject.layout',
'btford.modal'
'openproject.layout'
]);
angular.module('openproject.workPackages.models', []);
export const wpDirectivesModule = angular.module('openproject.workPackages.directives', [
@ -133,10 +120,6 @@ export const opNotificationsModule = angular.module('openproject.notifications',
angular.module('openproject.inplace-edit', []);
angular.module('openproject.responsive', []);
export const filtersModule = angular.module('openproject.filters', [
'openproject.models'
]);
export const wpButtonsModule = angular.module('openproject.wpButtons',
['ui.router', 'ui.router.upgrade', 'openproject.services']);
@ -154,7 +137,6 @@ export const openprojectModule = angular.module('openproject', [
'openproject.timeEntries',
'ngAnimate',
'ngAria',
'ngSanitize',
angularDragula(angular),
'openproject.layout',
'openproject.api',
@ -163,7 +145,6 @@ export const openprojectModule = angular.module('openproject', [
'openproject.inplace-edit',
wpButtonsModule.name,
'openproject.responsive',
filtersModule.name
]);
export default openprojectModule;

@ -26,7 +26,7 @@
// See doc/COPYRIGHT.rdoc for more details.
// ++
import {APP_INITIALIZER, NgModule} from '@angular/core';
import {NgModule} from '@angular/core';
import {BrowserModule} from '@angular/platform-browser';
import {UpgradeModule} from '@angular/upgrade/static';
import {FormsModule} from '@angular/forms';
@ -69,13 +69,11 @@ import {
$stateToken,
$timeoutToken,
AutoCompleteHelperServiceToken,
FocusHelperToken,
HookServiceToken,
I18nToken,
TextileServiceToken,
upgradeService,
upgradeServiceWithToken,
wpDestroyModalToken,
upgradeServiceWithToken, WorkPackageServiceToken,
wpMoreMenuServiceToken
} from './angular4-transition-utils';
import {WpCustomActionComponent} from 'core-components/wp-custom-actions/wp-custom-actions/wp-custom-action.component';
@ -220,6 +218,8 @@ import {QuerySharingModal} from "core-components/modals/share-modal/query-sharin
import {SaveQueryModal} from "core-components/modals/save-modal/save-query.modal";
import {QuerySharingForm} from "core-components/modals/share-modal/query-sharing-form.component";
import {RenameQueryModal} from "core-components/modals/rename-query-modal/rename-query.modal";
import {FocusHelperService} from 'core-components/common/focus/focus-helper';
import {WpDestroyModal} from "core-components/modals/wp-destroy-modal/wp-destroy.modal";
@NgModule({
imports: [
@ -243,13 +243,13 @@ import {RenameQueryModal} from "core-components/modals/rename-query-modal/rename
upgradeServiceWithToken('$timeout', $timeoutToken),
upgradeServiceWithToken('$locale', $localeToken),
upgradeServiceWithToken('textileService', TextileServiceToken),
upgradeServiceWithToken('WorkPackageService', WorkPackageServiceToken),
upgradeServiceWithToken('AutoCompleteHelper', AutoCompleteHelperServiceToken),
NotificationsService,
upgradeServiceWithToken('FocusHelper', FocusHelperToken),
FocusHelperService,
PathHelperService,
upgradeServiceWithToken('wpMoreMenuService', wpMoreMenuServiceToken),
TimezoneService,
upgradeServiceWithToken('wpDestroyModal', wpDestroyModalToken),
upgradeService('wpRelations', WorkPackageRelationsService),
UrlParamsHelperService,
WorkPackageCacheService,
@ -452,6 +452,7 @@ import {RenameQueryModal} from "core-components/modals/rename-query-modal/rename
SaveQueryModal,
QuerySharingForm,
RenameQueryModal,
WpDestroyModal,
// Notifications
NotificationsContainerComponent,
@ -521,6 +522,7 @@ import {RenameQueryModal} from "core-components/modals/rename-query-modal/rename
QuerySharingModal,
SaveQueryModal,
RenameQueryModal,
WpDestroyModal,
// Notifications
NotificationsContainerComponent,

@ -41,13 +41,12 @@ export const I18nToken = new InjectionToken<op.I18n>('I18n');
export const AutoCompleteHelperServiceToken = new InjectionToken<any>('AutoCompleteHelperServiceToken');
export const TextileServiceToken = new InjectionToken<any>('TextileServiceToken');
export const FocusHelperToken = new InjectionToken<any>('FocusHelper');
export const wpMoreMenuServiceToken = new InjectionToken<any>('wpMoreMenuService');
export const $httpToken = new InjectionToken<any>('$http');
export const wpDestroyModalToken = new InjectionToken<any>('wpDestroyModal');
export const OpContextMenuLocalsToken = new InjectionToken<any>('CONTEXT_MENU_LOCALS');
export const OpModalLocalsToken = new InjectionToken<any>('OP_MODAL_LOCALS');
export const HookServiceToken = new InjectionToken<any>('HookService');
export const WorkPackageServiceToken = new InjectionToken<any>('WorkPackageService');
export function upgradeService(ng1InjectorName:string, providedType:any) {
return {

@ -1,36 +0,0 @@
// -- copyright
// OpenProject is a project management system.
// Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License version 3.
//
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
// Copyright (C) 2006-2013 Jean-Philippe Lang
// Copyright (C) 2010-2013 the ChiliProject Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// See doc/COPYRIGHT.rdoc for more details.
// ++
import {filtersModule} from './../../../angular-modules';
function htmlEscape() {
return function(string:string) {
return _.escape(string);
};
}
filtersModule.filter('htmlEscape', htmlEscape);

@ -0,0 +1,110 @@
//-- copyright
// OpenProject is a project management system.
// Copyright (C) 2012-2018 the OpenProject Foundation (OPF)
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License version 3.
//
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
// Copyright (C) 2006-2017 Jean-Philippe Lang
// Copyright (C) 2010-2013 the ChiliProject Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// See docs/COPYRIGHT.rdoc for more details.
//++
import {opServicesModule} from 'core-app/angular-modules';
import {downgradeInjectable} from '@angular/upgrade/static';
import {Injectable} from '@angular/core';
@Injectable()
export class FocusHelperService {
private minimumOffsetForNewSwitchInMs = 100;
private lastFocusSwitch = -this.minimumOffsetForNewSwitchInMs;
private lastPriority = -1;
private static FOCUSABLE_SELECTORS = ['a, button, :input, [tabindex], select'];
public throttleAndCheckIfAllowedFocusChangeBasedOnTimeout() {
var allowFocusSwitch = (Date.now() - this.lastFocusSwitch) >= this.minimumOffsetForNewSwitchInMs;
// Always update so that a chain of focus-change-requests gets considered as one
this.lastFocusSwitch = Date.now();
return allowFocusSwitch;
}
public checkIfAllowedFocusChange(priority?:any) {
var checkTimeout = this.throttleAndCheckIfAllowedFocusChangeBasedOnTimeout();
if (checkTimeout) {
// new timeout window -> reset priority
this.lastPriority = -1;
} else {
// within timeout window
if (priority > this.lastPriority) {
this.lastPriority = priority;
return true;
}
}
return checkTimeout;
}
public getFocusableElement(element:JQuery) {
var focusser = element.find('input.ui-select-focusser');
if (focusser.length > 0) {
return focusser[0];
}
var focusable = element;
if (!element.is(FocusHelperService.FOCUSABLE_SELECTORS)) {
focusable = element.find(FocusHelperService.FOCUSABLE_SELECTORS);
}
return focusable[0];
}
public focus(element:JQuery) {
var focusable = angular.element(this.getFocusableElement(element)),
$focusable = angular.element(focusable),
isDisabled = $focusable.is('[disabled]');
if (isDisabled && !$focusable.attr('ng-disabled')) {
$focusable.prop('disabled', false);
}
focusable.focus();
if (isDisabled && !$focusable.attr('ng-disabled')) {
$focusable.prop('disabled', true);
}
}
public focusElement(element:JQuery, priority?:any) {
if (!this.checkIfAllowedFocusChange(priority)) {
return;
}
setTimeout(() => {
this.focus(element);
});
}
}
opServicesModule.service('FocusHelper', downgradeInjectable(FocusHelperService));

@ -34,7 +34,7 @@ describe('authorisationService', function() {
var authorisationService:AuthorisationService, $rootScope:ng.IRootScopeService, query:any;
beforeEach(angular.mock.module('openproject.services', 'openproject.models'));
beforeEach(angular.mock.module('openproject.services'));
beforeEach(inject(function(_authorisationService_:AuthorisationService, _$rootScope_:ng.IRootScopeService){
authorisationService = _authorisationService_;

@ -32,7 +32,7 @@ const expect = chai.expect;
describe('wpFiltersService', () => {
var wpFiltersService:WorkPackageFiltersService;
beforeEach(angular.mock.module('openproject.filters'));
beforeEach(angular.mock.module('openproject.services'));
beforeEach(angular.mock.inject((_wpFiltersService_:any) => {
wpFiltersService = _wpFiltersService_;
}));

@ -26,7 +26,7 @@
// See doc/COPYRIGHT.rdoc for more details.
// ++
import {filtersModule} from '../../../angular-modules';
import {opServicesModule} from '../../../angular-modules';
export default class WorkPackageFiltersService {
public visible:boolean = false;
@ -36,4 +36,4 @@ export default class WorkPackageFiltersService {
}
}
filtersModule.service('wpFiltersService', WorkPackageFiltersService);
opServicesModule.service('wpFiltersService', WorkPackageFiltersService);

@ -1,105 +0,0 @@
//-- copyright
// OpenProject is a project management system.
// Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License version 3.
//
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
// Copyright (C) 2006-2013 Jean-Philippe Lang
// Copyright (C) 2010-2013 the ChiliProject Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// See doc/COPYRIGHT.rdoc for more details.
//++
import {wpControllersModule} from '../../../angular-modules';
import {States} from '../../states.service';
import {WorkPackageResource} from 'core-app/modules/hal/resources/work-package-resource';
import {WorkPackageTableFocusService} from 'core-components/wp-fast-table/state/wp-table-focus.service';
import {StateService} from '@uirouter/core';
export class WorkPackageDestroyModalController {
public text:any;
public workPackages:WorkPackageResource[];
public workPackageLabel:string;
constructor(private $scope:any,
private $state:StateService,
private states:States,
private WorkPackageService:any,
private wpTableFocus:WorkPackageTableFocusService,
private I18n:op.I18n,
private wpDestroyModal:any) {
this.workPackages = $scope.workPackages;
this.workPackageLabel = I18n.t('js.units.workPackage', { count: this.workPackages.length });
this.text = {
close: I18n.t('js.close_popup_title'),
cancel: I18n.t('js.button_cancel'),
confirm: I18n.t('js.button_confirm'),
warning: I18n.t('js.label_warning'),
title: I18n.t('js.modals.destroy_work_package.title', { label: this.workPackageLabel }),
text: I18n.t('js.modals.destroy_work_package.text', { label: this.workPackageLabel, count: this.workPackages.length }),
childCount: (wp:WorkPackageResource) => {
const count = this.children(wp).length;
return this.I18n.t('js.units.child_work_packages', {count: count});
},
hasChildren: (wp:WorkPackageResource) =>
I18n.t('js.modals.destroy_work_package.has_children', {childUnits: this.text.childCount(wp) }),
deletesChildren: I18n.t('js.modals.destroy_work_package.deletes_children')
};
}
public $onInit() {
// Created for interface compliance
}
public close() {
try {
this.wpDestroyModal.deactivate();
} catch(e) {
console.error("Failed to close deletion modal: " + e);
}
}
public confirmDeletion() {
this.wpDestroyModal.deactivate();
this.WorkPackageService.performBulkDelete(this.workPackages.map(el => el.id), true)
.then(() => {
this.close();
this.wpTableFocus.clear();
this.$state.go('work-packages.list');
});
}
public childLabel (workPackage:WorkPackageResource) {
}
public children(workPackage:WorkPackageResource) {
if (workPackage.hasOwnProperty('children')) {
return workPackage.children;
} else {
return [];
}
}
}
wpControllersModule.controller('WorkPackageDestroyModalController', WorkPackageDestroyModalController);

@ -1,76 +0,0 @@
<div class="op-modal--portal" id="wp_destroy_modal">
<div class="op-modal--modal-container">
<div class="op-modal--modal-header">
<a>
<i class="icon-close"
ng-click="$ctrl.close()"
title="{{ ::$ctrl.text.close }}">
</i>
</a>
</div>
<form name="modalWpDestroyForm" class="form danger-zone">
<section class="form--section -inner-scrolling">
<h3 class="form--section-title" ng-bind="::$ctrl.text.title"></h3>
<div ng-if="$ctrl.workPackages.length === 1"
class="modal-inner-scrolling-container"
ng-init="workPackage = $ctrl.workPackages[0]; children = $ctrl.children(workPackage)">
<p>
<span ng-bind="$ctrl.text.text"></span>
<br/>
<strong>
{{ workPackage.type.name }}
#{{ workPackage.id }}
{{ workPackage.subject }}
</strong>
</p>
<div ng-if="children.length > 0">
<p class="danger-zone--warning">
<span class="icon icon-error"></span>
<strong ng-bind="$ctrl.text.warning"></strong>:
<span ng-bind="$ctrl.text.hasChildren(workPackage)"></span>
</p>
<ul>
<li ng-repeat="child in children track by $index">
#<span ng-bind="child.id"></span>
<span ng-bind="child.subject"></span>
</li>
</ul>
<p>
<span ng-bind="$ctrl.text.deletesChildren"></span>
</p>
</div>
</div>
<div class="modal-inner-scrolling-container" ng-if="$ctrl.workPackages.length > 1">
<p class="danger-zone--warning">
<span class="icon icon-error"></span>
<strong ng-bind="$ctrl.text.text"></strong>
</p>
<ul>
<li ng-repeat="wp in $ctrl.workPackages track by $index">
#<span ng-bind="wp.id"></span>
<span ng-bind="wp.subject"></span>
<strong ng-if="$ctrl.children(wp).length > 0">
(&plus; {{ $ctrl.text.childCount(wp) }})
</strong>
</li>
</ul>
</div>
</section>
<div class="form--space -left-spacing">
<button class="button -danger"
ng-bind="::$ctrl.text.confirm"
ng-click="$ctrl.confirmDeletion()">
</button>
<button class="button"
ng-bind="::$ctrl.text.cancel"
ng-click="$ctrl.close()">
</button>
</div>
</form>
</div>
</div>

@ -1,39 +0,0 @@
//-- copyright
// OpenProject is a project management system.
// Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License version 3.
//
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
// Copyright (C) 2006-2013 Jean-Philippe Lang
// Copyright (C) 2010-2013 the ChiliProject Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// See doc/COPYRIGHT.rdoc for more details.
//++
import {wpControllersModule} from '../../../angular-modules';
function wpDestroyService(btfModal:any) {
return btfModal({
controller: 'WorkPackageDestroyModalController',
controllerAs: '$ctrl',
templateUrl: '/components/modals/wp-destroy-modal/wp-destroy-modal.html'
});
}
wpControllersModule.factory('wpDestroyModal', wpDestroyService);

@ -0,0 +1,78 @@
<div class="op-modal--portal ">
<div id="wp_destroy_modal"
class="op-modal--modal-container wp-table--configuration-modal loading-indicator--location"
data-indicator-name="modal"
tabindex="0">
<div class="op-modal--modal-header">
<a class="op-modal--modal-close-button">
<i
class="icon-close"
(click)="closeMe($event)"
[attr.title]="text.close">
</i>
</a>
</div>
<form name="modalWpDestroyForm" class="form danger-zone">
<section class="form--section -inner-scrolling">
<h3 class="form--section-title" [textContent]="text.title"></h3>
<div *ngIf="singleWorkPackage"
class="modal-inner-scrolling-container">
<p>
<span [textContent]="text.text"></span>
<br/>
<strong>
{{ singleWorkPackage.type.name }}
#{{ singleWorkPackage.id }}
{{ singleWorkPackage.subject }}
</strong>
</p>
<div *ngIf="singleWorkPackageChildren && singleWorkPackageChildren.length > 0">
<p class="danger-zone--warning">
<span class="icon icon-error"></span>
<strong [textContent]="text.warning"></strong>:
<span [textContent]="text.hasChildren(singleWorkPackage)"></span>
</p>
<ul>
<li *ngFor="let child of singleWorkPackageChildren">
#<span [textContent]="child.id"></span>
<span [textContent]="child.subject"></span>
</li>
</ul>
<p>
<span [textContent]="text.deletesChildren"></span>
</p>
</div>
</div>
<div class="modal-inner-scrolling-container" *ngIf="workPackages.length > 1">
<p class="danger-zone--warning">
<span class="icon icon-error"></span>
<strong [textContent]="text.text"></strong>
</p>
<ul>
<li *ngFor="let wp of workPackages">
#<span [textContent]="wp.id"></span>
<span [textContent]="wp.subject"></span>
<strong *ngIf="children(wp).length > 0">
(+ {{ text.childCount(wp) }})
</strong>
</li>
</ul>
</div>
</section>
<div class="form--space -left-spacing">
<button class="button -danger"
[textContent]="text.confirm"
(click)="confirmDeletion($event)">
</button>
<button class="button"
[textContent]="text.cancel"
(click)="closeMe($event)">
</button>
</div>
</form>
</div>
</div>

@ -0,0 +1,118 @@
//-- copyright
// OpenProject is a project management system.
// Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License version 3.
//
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
// Copyright (C) 2006-2013 Jean-Philippe Lang
// Copyright (C) 2010-2013 the ChiliProject Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// See doc/COPYRIGHT.rdoc for more details.
//++
import {WorkPackagesListService} from '../../wp-list/wp-list.service';
import {States} from '../../states.service';
import {WorkPackageNotificationService} from '../../wp-edit/wp-notification.service';
import {NotificationsService} from "core-components/common/notifications/notifications.service";
import {OpModalComponent} from "core-components/op-modals/op-modal.component";
import {Component, ElementRef, Inject, OnInit} from "@angular/core";
import {$stateToken, I18nToken, OpModalLocalsToken, WorkPackageServiceToken} from "core-app/angular4-transition-utils";
import {OpModalLocalsMap} from "core-components/op-modals/op-modal.types";
import {WorkPackageResource} from 'core-app/modules/hal/resources/work-package-resource';
import {WorkPackageTableFocusService} from 'core-components/wp-fast-table/state/wp-table-focus.service';
import {StateService} from '@uirouter/core';
@Component({
template: require('!!raw-loader!./wp-destroy.modal.html')
})
export class WpDestroyModal extends OpModalComponent implements OnInit {
// When deleting multiple
public workPackages:WorkPackageResource[];
public workPackageLabel:string;
// Single work package
public singleWorkPackage:WorkPackageResource;
public singleWorkPackageChildren:WorkPackageResource[];
public text:{ [key:string]:any } = {
label_visibility_settings: this.I18n.t('js.label_visibility_settings'),
button_save: this.I18n.t('js.modals.button_save'),
confirm: this.I18n.t('js.button_confirm'),
warning: this.I18n.t('js.label_warning'),
cancel: this.I18n.t('js.button_cancel'),
close: this.I18n.t('js.close_popup_title'),
};
constructor(readonly elementRef:ElementRef,
@Inject(WorkPackageServiceToken) readonly WorkPackageService:any,
@Inject(OpModalLocalsToken) public locals:OpModalLocalsMap,
@Inject(I18nToken) readonly I18n:op.I18n,
@Inject($stateToken) readonly $state:StateService,
readonly states:States,
readonly wpTableFocus:WorkPackageTableFocusService,
readonly wpListService:WorkPackagesListService,
readonly wpNotificationsService:WorkPackageNotificationService,
readonly notificationsService:NotificationsService) {
super(locals, elementRef);
}
ngOnInit() {
super.ngOnInit();
this.workPackages = this.locals.workPackages;
this.workPackageLabel = this.I18n.t('js.units.workPackage', { count: this.workPackages.length });
// Ugly way to provide the same view bindings as the ng-init in the previous template.
if (this.workPackages.length === 1) {
this.singleWorkPackage = this.workPackages[0];
this.singleWorkPackageChildren = this.singleWorkPackage.children;
}
this.text.title = this.I18n.t('js.modals.destroy_work_package.title', { label: this.workPackageLabel }),
this.text.text = this.I18n.t('js.modals.destroy_work_package.text', { label: this.workPackageLabel, count: this.workPackages.length }),
this.text.childCount = (wp:WorkPackageResource) => {
const count = this.children(wp).length;
return this.I18n.t('js.units.child_work_packages', {count: count});
};
this.text.hasChildren = (wp:WorkPackageResource) =>
this.I18n.t('js.modals.destroy_work_package.has_children', {childUnits: this.text.childCount(wp) }),
this.text.deletesChildren = this.I18n.t('js.modals.destroy_work_package.deletes_children');
}
public confirmDeletion($event:JQueryEventObject) {
this.WorkPackageService.performBulkDelete(this.workPackages.map(el => el.id), true)
.then(() => {
this.closeMe($event);
this.wpTableFocus.clear();
this.$state.go('work-packages.list');
});
}
public children(workPackage:WorkPackageResource) {
if (workPackage.hasOwnProperty('children')) {
return workPackage.children;
} else {
return [];
}
}
}

@ -5,10 +5,11 @@ import {
import {ComponentPortal, DomPortalOutlet, PortalInjector} from "@angular/cdk/portal";
import {TransitionService} from "@uirouter/core";
import {OpContextMenuHandler} from "core-components/op-context-menu/op-context-menu-handler";
import {FocusHelperToken, OpContextMenuLocalsToken} from "core-app/angular4-transition-utils";
import {OpContextMenuLocalsToken} from "core-app/angular4-transition-utils";
import {OpContextMenuLocalsMap} from "core-components/op-context-menu/op-context-menu.types";
import {OPContextMenuComponent} from "core-components/op-context-menu/op-context-menu.component";
import {keyCodes} from 'core-components/common/keyCodes.enum';
import {FocusHelperService} from 'core-components/common/focus/focus-helper';
@Injectable()
export class OPContextMenuService {
@ -23,7 +24,7 @@ export class OPContextMenuService {
private isOpening = false;
constructor(private componentFactoryResolver:ComponentFactoryResolver,
@Inject(FocusHelperToken) readonly FocusHelper:any,
readonly FocusHelper:FocusHelperService,
private appRef:ApplicationRef,
private $transitions:TransitionService,
private injector:Injector) {

@ -2,8 +2,7 @@ import {Directive, ElementRef, Inject, Input} from "@angular/core";
import {WorkPackageAction} from "core-components/wp-table/context-menu-helper/wp-context-menu-helper.service";
import {
$stateToken,
HookServiceToken,
wpDestroyModalToken
HookServiceToken
} from "core-app/angular4-transition-utils";
import {LinkHandling} from "core-components/common/link-handling/link-handling";
import {OPContextMenuService} from "core-components/op-context-menu/op-context-menu.service";
@ -13,6 +12,8 @@ import {OpContextMenuTrigger} from "core-components/op-context-menu/handlers/op-
import {WorkPackageAuthorization} from "core-components/work-packages/work-package-authorization.service";
import {AuthorisationService} from "core-components/common/model-auth/model-auth.service";
import {StateService} from "@uirouter/core";
import {OpModalService} from "core-components/op-modals/op-modal.service";
import {WpDestroyModal} from "core-components/modals/wp-destroy-modal/wp-destroy.modal";
@Directive({
selector: '[wpSingleContextMenu]'
@ -21,9 +22,9 @@ export class WorkPackageSingleContextMenuDirective extends OpContextMenuTrigger
@Input('wpSingleContextMenu-workPackage') public workPackage:WorkPackageResource;
constructor(@Inject(HookServiceToken) readonly HookService:any,
@Inject(wpDestroyModalToken) readonly wpDestroyModal:any,
@Inject($stateToken) readonly $state:StateService,
readonly elementRef:ElementRef,
readonly opModalService:OpModalService,
readonly opContextMenuService:OPContextMenuService,
readonly authorisationService:AuthorisationService) {
super(elementRef, opContextMenuService);
@ -50,7 +51,7 @@ export class WorkPackageSingleContextMenuDirective extends OpContextMenuTrigger
this.$state.go('work-packages.copy', {copiedFromWorkPackageId: this.workPackage.id});
break;
case 'delete':
this.wpDestroyModal.activate({workPackages: [this.workPackage]});
this.opModalService.show(WpDestroyModal, {workPackages: [this.workPackage]});
break;
default:
@ -78,7 +79,7 @@ export class WorkPackageSingleContextMenuDirective extends OpContextMenuTrigger
private getPermittedPluginActions(authorization:WorkPackageAuthorization) {
var pluginActions:WorkPackageAction[] = [];
angular.forEach(this.HookService.call('workPackageDetailsMoreMenu'), function (action) {
angular.forEach(this.HookService.call('workPackageDetailsMoreMenu'), function(action) {
pluginActions = pluginActions.concat(action);
});

@ -6,7 +6,7 @@ import {
import {WorkPackageTable} from "core-components/wp-fast-table/wp-fast-table";
import {States} from "core-components/states.service";
import {WorkPackageRelationsHierarchyService} from "core-components/wp-relations/wp-relations-hierarchy/wp-relations-hierarchy.service";
import {$stateToken, wpDestroyModalToken} from "core-app/angular4-transition-utils";
import {$stateToken} from "core-app/angular4-transition-utils";
import {WorkPackageTableSelection} from "core-components/wp-fast-table/state/wp-table-selection.service";
import {LinkHandling} from "core-components/common/link-handling/link-handling";
import {OpContextMenuHandler} from "core-components/op-context-menu/op-context-menu-handler";
@ -16,12 +16,14 @@ import {
OpContextMenuLocalsMap
} from "core-components/op-context-menu/op-context-menu.types";
import {PERMITTED_CONTEXT_MENU_ACTIONS} from "core-components/op-context-menu/wp-context-menu/wp-static-context-menu-actions";
import {OpModalService} from "core-components/op-modals/op-modal.service";
import {WpDestroyModal} from "core-components/modals/wp-destroy-modal/wp-destroy.modal";
export class OpWorkPackageContextMenu extends OpContextMenuHandler {
private states = this.injector.get(States);
private wpRelationsHierarchyService = this.injector.get(WorkPackageRelationsHierarchyService);
private wpDestroyModal = this.injector.get(wpDestroyModalToken);
private opModalService:OpModalService = this.injector.get(OpModalService);
private $state = this.injector.get($stateToken);
private wpTableSelection = this.injector.get(WorkPackageTableSelection);
private WorkPackageContextMenuHelper = this.injector.get(WorkPackageContextMenuHelperService);
@ -89,7 +91,7 @@ export class OpWorkPackageContextMenu extends OpContextMenuHandler {
private deleteSelectedWorkPackages() {
var selected = this.getSelectedWorkPackages();
this.wpDestroyModal.activate({workPackages: selected});
this.opModalService.show(WpDestroyModal, {workPackages: selected});
}
private editSelectedWorkPackages(link:any) {

@ -7,11 +7,12 @@ import {
} from '@angular/core';
import {ComponentPortal, ComponentType, DomPortalOutlet, PortalInjector} from '@angular/cdk/portal';
import {TransitionService} from '@uirouter/core';
import {FocusHelperToken, OpModalLocalsToken} from 'core-app/angular4-transition-utils';
import {OpModalLocalsToken} from 'core-app/angular4-transition-utils';
import {OpModalComponent} from 'core-components/op-modals/op-modal.component';
import {keyCodes} from 'core-components/common/keyCodes.enum';
import {opServicesModule} from "core-app/angular-modules";
import {downgradeInjectable} from "@angular/upgrade/static";
import {FocusHelperService} from 'core-components/common/focus/focus-helper';
@Injectable()
export class OpModalService {
@ -26,7 +27,7 @@ export class OpModalService {
private opening:boolean = false;
constructor(private componentFactoryResolver:ComponentFactoryResolver,
@Inject(FocusHelperToken) readonly FocusHelper:any,
readonly FocusHelper:FocusHelperService,
private appRef:ApplicationRef,
private $transitions:TransitionService,
private injector:Injector) {

@ -34,8 +34,7 @@ describe('currentProject service', function() {
var element:ng.IAugmentedJQuery;
var currentProject:CurrentProjectService;
beforeEach(angular.mock.module('openproject.filters',
'openproject.templates',
beforeEach(angular.mock.module('openproject.templates',
'openproject.services'));
beforeEach(angular.mock.inject((_currentProject_:CurrentProjectService) => {

@ -36,7 +36,6 @@ angular
function WorkPackageService($http:ng.IHttpService,
$window:ng.IWindowService,
$cacheFactory:any,
$state:StateService,
states:States,
I18n:op.I18n,
@ -45,35 +44,8 @@ function WorkPackageService($http:ng.IHttpService,
NotificationsService:any,
wpTableRefresh:WorkPackageTableRefreshService) {
var workPackageCache = $cacheFactory('workPackageCache');
var WorkPackageService = {
doQuery: function (url:string, params:any) {
return $http({
method: 'GET',
url: url,
params: params,
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
}).then(function (response) {
return response.data;
},
function (failedResponse) {
var error = '';
if (failedResponse.status === 404) {
error = I18n.t('js.work_packages.query.errors.not_found');
}
else {
error = I18n.t('js.work_packages.query.errors.unretrievable_query');
}
NotificationsService.addError(error);
}
);
},
performBulkDelete: function (ids:any, defaultHandling:any) {
var params = {
'ids[]': ids
@ -104,11 +76,6 @@ function WorkPackageService($http:ng.IHttpService,
return promise;
},
cache: function () {
return workPackageCache;
}
};
return WorkPackageService;

@ -36,18 +36,19 @@ import {EditField} from '../wp-edit/wp-edit-field/wp-edit-field.module';
import {WorkPackageNotificationService} from '../wp-edit/wp-notification.service';
import {WorkPackageTableSelection} from '../wp-fast-table/state/wp-table-selection.service';
import {Injector} from '@angular/core';
import {$stateToken, FocusHelperToken} from 'core-app/angular4-transition-utils';
import {$stateToken} from 'core-app/angular4-transition-utils';
import {WorkPackageEditContext} from 'core-components/wp-edit-form/work-package-edit-context';
import {WorkPackageTableRefreshService} from 'core-components/wp-table/wp-table-refresh-request.service';
import {WorkPackageEditForm} from 'core-components/wp-edit-form/work-package-edit-form';
import {WorkPackageEditFieldHandler} from 'core-components/wp-edit-form/work-package-edit-field-handler';
import {FocusHelperService} from 'core-components/common/focus/focus-helper';
export class SingleViewEditContext implements WorkPackageEditContext {
// Injections
public wpTableRefresh:WorkPackageTableRefreshService = this.injector.get(WorkPackageTableRefreshService);
public states:States = this.injector.get(States);
public FocusHelper:any = this.injector.get(FocusHelperToken);
public FocusHelper:FocusHelperService = this.injector.get(FocusHelperService);
public templateRenderer:SimpleTemplateRenderer = this.injector.get(SimpleTemplateRenderer);
public $state:StateService = this.injector.get($stateToken);
public wpNotificationsService:WorkPackageNotificationService = this.injector.get(WorkPackageNotificationService);

@ -27,7 +27,7 @@
// ++
import {Injector} from '@angular/core';
import {$qToken, $timeoutToken, FocusHelperToken} from 'core-app/angular4-transition-utils';
import {$qToken, $timeoutToken} from 'core-app/angular4-transition-utils';
import {SimpleTemplateRenderer} from '../angular/simple-template-renderer';
import {WorkPackageResource} from 'core-app/modules/hal/resources/work-package-resource';
import {States} from '../states.service';
@ -38,6 +38,7 @@ import {WorkPackageTableRefreshService} from '../wp-table/wp-table-refresh-reque
import {WorkPackageEditContext} from './work-package-edit-context';
import {WorkPackageEditFieldHandler} from './work-package-edit-field-handler';
import {WorkPackageEditForm} from './work-package-edit-form';
import {FocusHelperService} from 'core-components/common/focus/focus-helper';
export class TableRowEditContext implements WorkPackageEditContext {
@ -46,7 +47,7 @@ export class TableRowEditContext implements WorkPackageEditContext {
public wpTableRefresh:WorkPackageTableRefreshService = this.injector.get(WorkPackageTableRefreshService);
public wpTableColumns:WorkPackageTableColumnsService = this.injector.get(WorkPackageTableColumnsService);
public states:States = this.injector.get(States);
public FocusHelper:any = this.injector.get(FocusHelperToken);
public FocusHelper:FocusHelperService = this.injector.get(FocusHelperService);
public $q:ng.IQService = this.injector.get($qToken);
public $timeout:ng.ITimeoutService = this.injector.get($timeoutToken);

@ -30,13 +30,14 @@ import {WorkPackageEditForm} from './work-package-edit-form';
import {EditField} from '../wp-edit/wp-edit-field/wp-edit-field.module';
import {WorkPackageEditContext} from './work-package-edit-context';
import {keyCodes} from '../common/keyCodes.enum';
import {FocusHelperToken, I18nToken} from 'core-app/angular4-transition-utils';
import {I18nToken} from 'core-app/angular4-transition-utils';
import {ConfigurationService} from 'core-components/common/config/configuration.service';
import {Injector} from '@angular/core';
import {FocusHelperService} from 'core-components/common/focus/focus-helper';
export class WorkPackageEditFieldHandler {
// Injections
readonly FocusHelper = this.injector.get(FocusHelperToken)
readonly FocusHelper:FocusHelperService = this.injector.get(FocusHelperService)
readonly ConfigurationService = this.injector.get(ConfigurationService);
readonly I18n:op.I18n = this.injector.get(I18nToken);

@ -1,5 +1,4 @@
import {Injector} from '@angular/core';
import {FocusHelperToken} from 'core-app/angular4-transition-utils';
import {WorkPackageTableFocusService} from 'core-components/wp-fast-table/state/wp-table-focus.service';
import {takeUntil} from 'rxjs/operators';
import {tableRowClassName} from '../../builders/rows/single-row-builder';
@ -10,13 +9,14 @@ import {WorkPackageTable} from '../../wp-fast-table';
import {WPTableRowSelectionState} from '../../wp-table.interfaces';
import {OPContextMenuService} from "core-components/op-context-menu/op-context-menu.service";
import {TableState} from 'core-components/wp-table/table-state/table-state';
import {FocusHelperService} from 'core-components/common/focus/focus-helper';
export class SelectionTransformer {
public wpTableSelection:WorkPackageTableSelection = this.injector.get(WorkPackageTableSelection);
public wpTableFocus:WorkPackageTableFocusService = this.injector.get(WorkPackageTableFocusService);
public tableState:TableState = this.injector.get(TableState);
public FocusHelper:any = this.injector.get(FocusHelperToken);
public FocusHelper:FocusHelperService = this.injector.get(FocusHelperService);
public opContextMenu:OPContextMenuService = this.injector.get(OPContextMenuService);
constructor(public readonly injector:Injector,

@ -59,7 +59,8 @@ import {
} from './inline-create-row-builder';
import {TableState} from 'core-components/wp-table/table-state/table-state';
import {componentDestroyed} from 'ng2-rx-componentdestroyed';
import {FocusHelperToken, I18nToken} from 'core-app/angular4-transition-utils';
import {I18nToken} from 'core-app/angular4-transition-utils';
import {FocusHelperService} from 'core-components/common/focus/focus-helper';
@Component({
selector: '[wpInlineCreate]',
@ -92,7 +93,7 @@ export class WorkPackageInlineCreateComponent implements OnInit, OnChanges, OnDe
constructor(readonly elementRef:ElementRef,
readonly injector:Injector,
@Inject(FocusHelperToken) readonly FocusHelper:any,
readonly FocusHelper:FocusHelperService,
@Inject(I18nToken) readonly I18n:op.I18n,
readonly tableState:TableState,
readonly wpCacheService:WorkPackageCacheService,

@ -34,7 +34,7 @@ import {WorkPackagesListChecksumService} from '../wp-list/wp-list-checksum.servi
import {StateService} from '@uirouter/core';
import {Component, Inject, OnInit} from "@angular/core";
import {
$stateToken, FocusHelperToken, I18nToken,
$stateToken, I18nToken,
OpContextMenuLocalsToken
} from "core-app/angular4-transition-utils";
import {OpContextMenuLocalsMap} from "core-components/op-context-menu/op-context-menu.types";

@ -1,11 +1,11 @@
import {ApplicationRef, ComponentFactoryResolver, Inject, Injectable, InjectionToken, Injector} from '@angular/core';
import {ComponentPortal, DomPortalOutlet, PortalInjector} from '@angular/cdk/portal';
import {TransitionService} from '@uirouter/core';
import {FocusHelperToken} from 'core-app/angular4-transition-utils';
import {OpModalComponent} from 'core-components/op-modals/op-modal.component';
import {ExternalQueryConfigurationComponent} from 'core-components/wp-table/external-configuration/external-query-configuration.component';
import {downgradeInjectable} from '@angular/upgrade/static';
import {opServicesModule} from 'core-app/angular-modules';
import {FocusHelperService} from 'core-components/common/focus/focus-helper';
export const external_table_trigger_class = 'external-table-configuration--container';
export const OpQueryConfigurationLocals = new InjectionToken<any>('OpQueryConfigurationLocals');
@ -20,7 +20,7 @@ export class ExternalQueryConfigurationService {
private _bodyPortalHost:DomPortalOutlet;
constructor(private componentFactoryResolver:ComponentFactoryResolver,
@Inject(FocusHelperToken) readonly FocusHelper:any,
readonly FocusHelper:FocusHelperService,
private appRef:ApplicationRef,
private $transitions:TransitionService,
private injector:Injector) {

@ -1,94 +0,0 @@
//-- copyright
// OpenProject is a project management system.
// Copyright (C) 2012-2018 the OpenProject Foundation (OPF)
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License version 3.
//
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
// Copyright (C) 2006-2017 Jean-Philippe Lang
// Copyright (C) 2010-2013 the ChiliProject Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// See docs/COPYRIGHT.rdoc for more details.
//++
module.exports = function(CUSTOM_FIELD_PREFIX, I18n) {
var CustomFieldHelper = {
isCustomFieldKey: function(key) {
return key.substr(0, CUSTOM_FIELD_PREFIX.length) === CUSTOM_FIELD_PREFIX;
},
getCustomFieldId: function(cfKey) {
return parseInt(cfKey.substr(CUSTOM_FIELD_PREFIX.length, 10), 10);
},
booleanCustomFieldValue: function(value) {
switch(value) {
case ('f'):
return I18n.t('js.general_text_No');
case ('t'):
return I18n.t('js.general_text_Yes');
default:
return '';
}
},
userCustomFieldValue: function(value, users) {
if (users && users[value] && users[value].name) {
// try to look up users, assume value is an id
return users[value].name;
}
return CustomFieldHelper.nestedCustomFieldValue(value);
},
versionCustomFieldValue: function(value) {
// I am pretty sure that we need to have the same behavior here as
// we have for the user custom fields.
// However, there is no code that would be using it so ...
return CustomFieldHelper.nestedCustomFieldValue(value);
},
nestedCustomFieldValue: function(value) {
if (value && value.name) {
return value.name;
}
return '';
},
parseNumeric: function(value, parseMethod){
if(value && ((typeof(value) == "string" && value.length > 0) || typeof(value) == "number") && !isNaN(value)){
return parseMethod(value);
}
return '';
},
formatCustomFieldValue: function(value, fieldFormat, users) {
switch(fieldFormat) {
case 'bool':
return CustomFieldHelper.booleanCustomFieldValue(value);
case 'user':
return CustomFieldHelper.userCustomFieldValue(value, users);
case 'version':
return CustomFieldHelper.versionCustomFieldValue(value);
case 'int':
return CustomFieldHelper.parseNumeric(value, parseInt);
case 'float':
return CustomFieldHelper.parseNumeric(value, parseFloat);
default:
return value;
}
}
};
return CustomFieldHelper;
};

@ -1,35 +0,0 @@
//-- copyright
// OpenProject is a project management system.
// Copyright (C) 2012-2018 the OpenProject Foundation (OPF)
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License version 3.
//
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
// Copyright (C) 2006-2017 Jean-Philippe Lang
// Copyright (C) 2010-2013 the ChiliProject Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// See docs/COPYRIGHT.rdoc for more details.
//++
angular.module('openproject.helpers')
.constant('CUSTOM_FIELD_PREFIX', 'cf_')
.service('CustomFieldHelper', ['CUSTOM_FIELD_PREFIX', 'I18n', require(
'./custom-field-helper')])
.factory('SvgHelper', require('./svg-helper'))
.service('WorkPackageLoadingHelper', ['$timeout', require(
'./work-package-loading-helper')]);

@ -1,179 +0,0 @@
//-- copyright
// OpenProject is a project management system.
// Copyright (C) 2012-2018 the OpenProject Foundation (OPF)
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License version 3.
//
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
// Copyright (C) 2006-2017 Jean-Philippe Lang
// Copyright (C) 2010-2013 the ChiliProject Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// See docs/COPYRIGHT.rdoc for more details.
//++
// ╭───────────────────────────────────────────────────────────────╮
// │ _____ _ _ _ │
// │ |_ _(_)_ __ ___ ___| (_)_ __ ___ ___ │
// │ | | | | '_ ` _ \ / _ \ | | '_ \ / _ \/ __| │
// │ | | | | | | | | | __/ | | | | | __/\__ \ │
// │ |_| |_|_| |_| |_|\___|_|_|_| |_|\___||___/ │
// ├───────────────────────────────────────────────────────────────┤
// │ Javascript library that fetches and plots timelines for the │
// │ OpenProject timelines module. │
// ╰───────────────────────────────────────────────────────────────╯
module.exports = function() {
var SvgHelper = function(node) {
this.root = this.provideNode('svg').attr({
'width': 640,
'height': 480
});
jQuery(node).append(this.root);
};
SvgHelper.prototype.toString = function() {
return "SvgHelper";
};
SvgHelper.prototype.provideNode = function(elementName) {
return document.createElementNS(
'http://www.w3.org/2000/svg',
elementName
);
};
SvgHelper.prototype.clear = function() {
var node = this.root;
while (node.hasChildNodes() ){
node.removeChild(node.lastChild);
}
return this;
};
SvgHelper.prototype.setSize = function(w, h) {
this.root.attr({
'width': w,
'height': h,
});
};
SvgHelper.prototype.rect = function(x, y, w, h, r) {
var node = this.provideNode('rect').attr({
'x': x,
'y': y,
'width': w,
'height': h
});
if (r) {
node.round(r);
}
this.root.appendChild(node);
return node;
};
SvgHelper.prototype.path = function(direction) {
var node = this.provideNode('path').attr({
'd': direction
});
this.root.appendChild(node);
return node;
};
SvgHelper.prototype.text = function(x, y, text) {
var node = this.provideNode('text');
node.translate(x, y);
node.textContent = text;
this.root.appendChild(node);
return node;
};
SvgHelper.prototype.gradient = function(id, stops) {
var svg = this.root;
var svgNS = svg.namespaceURI;
var gradient = document.createElementNS(svgNS, 'linearGradient');
gradient.setAttribute('id', id);
for (var i=0; i < stops.length; i++){
var attrs = stops[i];
var stop = document.createElementNS(svgNS, 'stop');
for (var attr in attrs) {
if (attrs.hasOwnProperty(attr)) stop.setAttribute(attr, attrs[attr]);
}
gradient.appendChild(stop);
}
var defs = svg.querySelector('defs') || svg.insertBefore(document.createElementNS(svgNS, 'defs'), svg.firstChild);
return defs.appendChild(gradient);
};
jQuery.each([SVGSVGElement, SVGRectElement, SVGPathElement,
SVGTextElement], function (i, klass) {
klass.prototype.attr = function(attributeHash) {
for (var key in attributeHash) {
if (attributeHash.hasOwnProperty(key)) {
this.setAttribute(key, attributeHash[key]);
}
}
// allow chaining.
return this;
};
});
jQuery.each([SVGRectElement, SVGTextElement], function (i, klass) {
klass.prototype.translate = function(x, y) {
return this.attr({
'x': x,
'y': y
});
};
klass.prototype.insertAfter = function(node) {
this.parentNode.insertBefore(node, this.nextSibling);
};
});
SVGRectElement.prototype.round = function(r) {
this.attr({
'rx': r,
'ry': r
});
};
SVGRectElement.prototype.hover = function(f_in, f_out) {
this.addEventListener("mouseover", f_in);
this.addEventListener("mouseout", f_out);
};
SVGRectElement.prototype.unhover = function() {
// TODO (not sure if we even need this)
};
SVGRectElement.prototype.click = function(cb) {
this.addEventListener("click", cb);
};
SVGPathElement.prototype.transform = function(transform) {
return this.attr({'transform': transform});
};
return SvgHelper;
};

@ -1,64 +0,0 @@
//-- copyright
// OpenProject is a project management system.
// Copyright (C) 2012-2018 the OpenProject Foundation (OPF)
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License version 3.
//
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
// Copyright (C) 2006-2017 Jean-Philippe Lang
// Copyright (C) 2010-2013 the ChiliProject Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// See docs/COPYRIGHT.rdoc for more details.
//++
// TODO move to UI components
module.exports = function($timeout) {
var currentRun;
return {
withDelay: function(delay, callback, params) {
$timeout.cancel(currentRun);
currentRun = $timeout(function() {
return callback.apply(this, params);
}, delay);
return currentRun;
},
/**
* @name withLoading
*
* @description Wraps a data-loading function and manages the loading state within the scope
* @param {scope} a scope on which an isLoading flag is set
* @param {function} callback Function returning a promise
* @param {array} params Params forwarded to the callback
* @returns {promise} Promise returned by the callback
*/
withLoading: function(scope, callback, params, errorCallback) {
scope.isLoading = true;
return callback.apply(this, params)
.then(function(results){
scope.isLoading = false;
return results;
}, errorCallback);
}
};
};

@ -151,9 +151,7 @@ openprojectModule
}
]);
require('./helpers');
require('./layout');
require('./models');
require('./services');
require('./ui_components');
require('./work_packages');

@ -1,29 +0,0 @@
//-- copyright
// OpenProject is a project management system.
// Copyright (C) 2012-2018 the OpenProject Foundation (OPF)
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License version 3.
//
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
// Copyright (C) 2006-2017 Jean-Philippe Lang
// Copyright (C) 2010-2013 the ChiliProject Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// See docs/COPYRIGHT.rdoc for more details.
//++
angular.module('openproject.models')

@ -33,7 +33,7 @@ require('jquery-mockjax')(jQuery, window);
var requireComponent;
window.$injector = angular.injector(['ng', 'ngMock', 'openproject.uiComponents', 'openproject.models', 'openproject.services']);
window.$injector = angular.injector(['ng', 'ngMock', 'openproject.uiComponents', 'openproject.services']);
requireComponent = require.context('../tests/unit/tests/', true, /test\.(js|ts)$/);
requireComponent.keys().forEach(requireComponent);

@ -1,56 +0,0 @@
//-- copyright
// OpenProject is a project management system.
// Copyright (C) 2012-2018 the OpenProject Foundation (OPF)
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License version 3.
//
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
// Copyright (C) 2006-2017 Jean-Philippe Lang
// Copyright (C) 2010-2013 the ChiliProject Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// See docs/COPYRIGHT.rdoc for more details.
//++
module.exports = function() {
var ConversionService = {
//TODO: not sure if we need GigaBytes
megabytes: function(bytes) {
if (angular.isNumber(bytes)) {
return Number((bytes / 1000000).toFixed(1));
}
return bytes;
},
kilobytes: function(bytes) {
if (angular.isNumber(bytes)) {
return Number((bytes / 1000).toFixed(1));
}
return bytes;
},
fileSize: function(bytes) {
if (1000 <= bytes && bytes < 1000000) {
return ConversionService.kilobytes(bytes) + 'kB';
}
if (bytes >= 1000000) {
return ConversionService.megabytes(bytes) + 'MB';
}
return bytes + 'B';
}
};
return ConversionService;
};

@ -33,7 +33,4 @@ angular.module('openproject.services')
'$rootScope',
'$timeout',
'PathHelper',
require('./keyboard-shortcut-service')])
.service('SortService', require('./sort-service'))
.service('StatusService', ['$http', 'PathHelper', require('./status-service')])
.service('ConversionService', require('./conversion-service.js'));
require('./keyboard-shortcut-service')]);

@ -1,72 +0,0 @@
//-- copyright
// OpenProject is a project management system.
// Copyright (C) 2012-2018 the OpenProject Foundation (OPF)
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License version 3.
//
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
// Copyright (C) 2006-2017 Jean-Philippe Lang
// Copyright (C) 2010-2013 the ChiliProject Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// See docs/COPYRIGHT.rdoc for more details.
//++
module.exports = function() {
var sortOptions = {
column: "",
direction: ""
};
var SortService = {
getColumn: function() {
return sortOptions.column;
},
setColumn: function(column) {
sortOptions.column = column;
},
getDirection: function() {
return sortOptions.direction;
},
setDirection: function(direction) {
var sortDirection = direction;
if (sortDirection == '-') {
sortDirection = 'desc';
}
if (sortDirection != '' && sortDirection != 'asc' && sortDirection != 'desc') {
throw new Error("Parameter does not match possible values 'asc' or 'desc'!");
}
sortOptions.direction = sortDirection;
},
isDescending: function() {
return sortOptions.direction == 'desc';
},
getSortParam: function() {
var direction = sortOptions.direction;
if (!direction) {
direction = 'asc';
}
return sortOptions.column + ':' + direction;
}
};
return SortService;
};

@ -1,45 +0,0 @@
//-- copyright
// OpenProject is a project management system.
// Copyright (C) 2012-2018 the OpenProject Foundation (OPF)
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License version 3.
//
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
// Copyright (C) 2006-2017 Jean-Philippe Lang
// Copyright (C) 2010-2013 the ChiliProject Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// See docs/COPYRIGHT.rdoc for more details.
//++
module.exports = function($http, PathHelper) {
var StatusService = {
getStatuses: function() {
return StatusService.doQuery(PathHelper.apiV3StatusesPath());
},
doQuery: function(url, params) {
return $http.get(url, { params: params })
.then(function(response){
return response.data._embedded.elements;
});
}
};
return StatusService;
};

@ -1,8 +0,0 @@
<span class="accessible-checkbox">
<label for="{{ checkboxId }}" class="hidden-for-sighted">{{ checkboxTitle }}</label>
<input type="checkbox"
ng-model="model"
name="{{ name }}"
value="{{ checkboxValue }}"
id="{{ checkboxId }}"/>
</span>

@ -1,4 +0,0 @@
<span>
<span aria-hidden="true" role="presentation" tabindex="-1" ng-bind="visibleText"></span>
<span class="hidden-for-sighted" ng-bind="readableText"></span>
</span>

@ -1,2 +0,0 @@
<accessible-element visible-text="'-'" readable-text="I18n.t('js.timelines.empty')">
</accessible-element>

@ -1,3 +0,0 @@
<span class="icon-context icon-button icon-{{iconName}} {{cssClass}}" title="{{title}}">
<span class="hidden-for-sighted">{{title}}</span>
</span>

@ -1,4 +0,0 @@
<div ng-if="isLoading"
id="ajax-indicator">
<span>{{ I18n.t('js.label_loading') }}</span>
</div>

@ -1,5 +0,0 @@
<div class="notification-box--wrapper">
<div class="notification-box--casing">
<notification-box data-content="notification" data-ng-repeat="notification in stack"></notification-box>
</div>
</div>

@ -1 +0,0 @@
<a href="" ng-click="sort()" ng-transclude class="sort" ng-class="sortDirection" title="{{sortTitle}}"></a>

@ -1,14 +0,0 @@
<span class="user-avatar--container">
<img class="user-avatar--avatar"
ng-if="userName && user.avatar"
ng-src="{{user.avatar}}" alt="Avatar" title="{{userName}}" />
<span class="user-avatar--user-with-role">
<span class="user-avatar--user" ng-if="userName">
<a ng-if="user.type !== 'Group'" ng-href="{{ userPath(user.id) }}" ng-bind="user.name"
class="user-field-user-link"/>
<span ng-if="user.type == 'Group'" ng-bind="user.name" class="user-field-user-link"/>
</span>
<span class="user-avatar--user" ng-if="!userName"> - </span>
<span class="user-avatar--role" ng-if="user.role" ng-bind="user.role"/>
</span>
</span>

@ -1,5 +0,0 @@
<label for="{{ ::ctrl.sliderId }}" class="hidden-for-sighted"
ng-bind="::labelText"></label>
<input type="range" id="{{ ::ctrl.sliderId }}"
min="{{ ::ctrl.minValue }}" max="{{ ::ctrl.maxValue }}"
value="{{ ctrl.selectedScaleIndex + 1 }}">

@ -1,52 +0,0 @@
<table style="border-collapse: collapse; border:0;">
<tbody>
<tr>
<td style="padding-left:3px">
<label for="available_columns">{{ I18n.t('js.description_available_columns') }}</label>
<br/>
<select id="available_columns"
multiple="multiple"
name="available_columns[]"
ng-model="markedAvailableColumns"
ng-options="column.name as column.title for column in availableUnusedColumns"
size="10"
style="width:150px">
</select>
</td>
<td class="table-buttons" align="center" valign="middle">
<label class="hidden-for-sighted" for="add_selected_columns">{{ I18n.t('js.label_add_selected_columns') }}</label>
<input id="add_selected_columns"
type="button"
value="→"
ng-click="showColumns(markedAvailableColumns)"
title="{{ I18n.t('js.label_add_selected_columns') }}"
ng-disabled="loading"><br>
<label class="hidden-for-sighted" for="add_selected_columns">{{ I18n.t('js.label_remove_columns') }}</label>
<input type="button"
value="←"
ng-click="hideColumns(markedSelectedColumns)"
title="{{ I18n.t('js.label_remove_columns') }}"
ng-disabled="loading">
</td>
<td>
<label for="selected_columns">{{ I18n.t('js.description_selected_columns') }}</label>
<br/>
<select id="selected_columns"
name="c[]"
multiple="multiple"
ng-model="markedSelectedColumns"
name="c[]"
size="10"
style="width:150px">
<option ng-repeat="column in columns" value="{{ column.name }}">{{ column.title }}</option>
</select>
</td>
<td class="table-buttons" align="center" valign="middle">
<label class="hidden-for-sighted" for="move_up">{{ I18n.t('js.label_sort_higher') }}</label>
<input id="move_up" type="button" value="↑" ng-click="moveSelectedColumnBy(-1);" title="{{ I18n.t('js.label_sort_higher') }}" ng-disabled="loading"><br>
<label class="hidden-for-sighted" for="move_up">{{ I18n.t('js.label_sort_lower') }}</label>
<input id="move_down" type="button" value="↓" ng-click="moveSelectedColumnBy(1);" title="{{ I18n.t('js.label_sort_lower') }}" ng-disabled="loading">
</td>
</tr>
</tbody>
</table>

@ -1,43 +0,0 @@
//-- copyright
// OpenProject is a project management system.
// Copyright (C) 2012-2018 the OpenProject Foundation (OPF)
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License version 3.
//
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
// Copyright (C) 2006-2017 Jean-Philippe Lang
// Copyright (C) 2010-2013 the ChiliProject Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// See docs/COPYRIGHT.rdoc for more details.
//++
// TODO move to UI components
module.exports = function() {
return {
restrict: 'EA',
replace: true,
scope: {
name: '@',
checkboxId: '@',
checkboxTitle: '@',
checkboxValue: '=',
model: '='
},
templateUrl: '/templates/components/accessible_checkbox.html'
};
};

@ -1,38 +0,0 @@
//-- copyright
// OpenProject is a project management system.
// Copyright (C) 2012-2018 the OpenProject Foundation (OPF)
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License version 3.
//
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
// Copyright (C) 2006-2017 Jean-Philippe Lang
// Copyright (C) 2010-2013 the ChiliProject Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// See docs/COPYRIGHT.rdoc for more details.
//++
module.exports = function() {
return {
restrict: 'E',
scope: {
visibleText: '=',
readableText: '=',
},
templateUrl: "/templates/components/accessible_element.html"
};
};

@ -1,56 +0,0 @@
//-- copyright
// OpenProject is a project management system.
// Copyright (C) 2012-2018 the OpenProject Foundation (OPF)
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License version 3.
//
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
// Copyright (C) 2006-2017 Jean-Philippe Lang
// Copyright (C) 2010-2013 the ChiliProject Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// See docs/COPYRIGHT.rdoc for more details.
//++
module.exports = function() {
var imageRegExp = new RegExp(/^image\/.*$/),
excelRegExp = new RegExp(/.*excel$/);
return {
restrict: 'E',
replace: true,
scope: {
type: '&'
},
template: '<op-icon icon-classes="icon-{{icon}}"></op-icon>',
link: function(scope) {
var icon = 'ticket',
type = scope.type();
if (imageRegExp.test(type)) {
icon = 'image1';
}
if (excelRegExp.test(type)) {
icon = 'page-xls';
}
if (type === 'application/pdf') {
icon = 'page-pdf';
}
scope.icon = icon;
}
};
};

@ -1,43 +0,0 @@
//-- copyright
// OpenProject is a project management system.
// Copyright (C) 2012-2018 the OpenProject Foundation (OPF)
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License version 3.
//
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
// Copyright (C) 2006-2017 Jean-Philippe Lang
// Copyright (C) 2010-2013 the ChiliProject Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// See docs/COPYRIGHT.rdoc for more details.
//++
module.exports = function($window) {
return {
priority: -1,
restrict: 'A',
link: function(scope, element, attrs){
element.bind('click', function(e){
var message = attrs.confirmPopup;
if(message && !$window.confirm(message)){
e.stopImmediatePropagation();
e.preventDefault();
}
});
}
};
};

@ -1,34 +0,0 @@
//-- copyright
// OpenProject is a project management system.
// Copyright (C) 2012-2018 the OpenProject Foundation (OPF)
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License version 3.
//
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
// Copyright (C) 2006-2017 Jean-Philippe Lang
// Copyright (C) 2010-2013 the ChiliProject Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// See docs/COPYRIGHT.rdoc for more details.
//++
module.exports = function() {
return {
restrict: 'E',
templateUrl: "/templates/components/empty_element.html"
};
};

@ -1,48 +0,0 @@
//-- copyright
// OpenProject is a project management system.
// Copyright (C) 2012-2018 the OpenProject Foundation (OPF)
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License version 3.
//
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
// Copyright (C) 2006-2017 Jean-Philippe Lang
// Copyright (C) 2010-2013 the ChiliProject Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// See docs/COPYRIGHT.rdoc for more details.
//++
module.exports = function() {
return function(ancestors) {
if(!ancestors) return true;
var directAncestors;
if(ancestors.length > 1 && ancestors[0].payload.objectType === 'Project' && ancestors[1].payload.objectType === 'Project') {
// discard expansion state of root if there's another project ancestor
directAncestors = ancestors.slice(1);
} else {
directAncestors = ancestors;
}
return directAncestors.map(function(ancestor){
return ancestor.expanded;
}).reduce(function(a,b){
return a && b;
});
};
};

@ -38,28 +38,20 @@ module.exports = function(FocusHelper) {
}
function updateFocus(scope, element, attrs) {
if (element.hasClass('ui-select-multiple')) {
return element.find('input.ui-select-search').focus();
}
var condition = (attrs.focus) ? scope.$eval(attrs.focus) : true;
if (condition) {
if (isSelect2Element(attrs)) {
FocusHelper.focusSelect2Element(element);
} else {
var prio = 0;
if (attrs.focusPriority) {
prio = scope.$eval(attrs.focusPriority);
// Special case: Treat 'true' as 1 for convenience
if (prio === true) {
prio = 1;
} else {
prio = Number.parseInt(prio);
}
var prio = 0;
if (attrs.focusPriority) {
prio = scope.$eval(attrs.focusPriority);
// Special case: Treat 'true' as 1 for convenience
if (prio === true) {
prio = 1;
} else {
prio = Number.parseInt(prio);
}
FocusHelper.focusElement(element, prio);
}
FocusHelper.focusElement(element, prio);
}
}

@ -1,131 +0,0 @@
//-- copyright
// OpenProject is a project management system.
// Copyright (C) 2012-2018 the OpenProject Foundation (OPF)
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License version 3.
//
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
// Copyright (C) 2006-2017 Jean-Philippe Lang
// Copyright (C) 2010-2013 the ChiliProject Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// See docs/COPYRIGHT.rdoc for more details.
//++
// TODO move to UI components
module.exports = function ($timeout, FOCUSABLE_SELECTOR) {
var minimumOffsetForNewSwitchInMs = 100;
var lastFocusSwitch = -minimumOffsetForNewSwitchInMs;
var lastPriority = -1;
function throttleAndCheckIfAllowedFocusChangeBasedOnTimeout() {
var allowFocusSwitch = (Date.now() - lastFocusSwitch) >= minimumOffsetForNewSwitchInMs;
// Always update so that a chain of focus-change-requests gets considered as one
lastFocusSwitch = Date.now();
return allowFocusSwitch;
}
function checkIfAllowedFocusChange(priority) {
var checkTimeout = throttleAndCheckIfAllowedFocusChangeBasedOnTimeout();
if (checkTimeout) {
// new timeout window -> reset priority
lastPriority = -1;
} else {
// within timeout window
if (priority > lastPriority) {
lastPriority = priority;
return true;
}
}
return checkTimeout;
}
var FocusHelper = {
getFocusableElement: function (element) {
var focusser = element.find('input.ui-select-focusser');
if (focusser.length > 0) {
return focusser[0];
}
var focusable = element;
if (!element.is(FOCUSABLE_SELECTOR)) {
focusable = element.find(FOCUSABLE_SELECTOR);
}
return focusable[0];
},
focus: function (element) {
var focusable = angular.element(FocusHelper.getFocusableElement(element)),
$focusable = angular.element(focusable),
isDisabled = $focusable.is('[disabled]');
if (isDisabled && !$focusable.attr('ng-disabled')) {
$focusable.prop('disabled', false);
}
focusable.focus();
if (isDisabled && !$focusable.attr('ng-disabled')) {
$focusable.prop('disabled', true);
}
},
focusElement: function (element, priority) {
if (!checkIfAllowedFocusChange(priority)) {
return;
}
$timeout(function () {
FocusHelper.focus(element);
});
},
focusUiSelect: function (element) {
$timeout(function () {
element.find('.ui-select-match').trigger('click');
});
},
// TODO: remove when select2 is not used
focusSelect2Element: function (element) {
var focusSelect2ElementRecursiv = function (retries) {
$timeout(function () {
element.select2('focus');
var isSelect2Focused = angular.element(document.activeElement).hasClass('select2-input');
if (!isSelect2Focused && retries > 0) {
focusSelect2ElementRecursiv(--retries);
}
});
};
focusSelect2ElementRecursiv(3);
},
};
return FocusHelper;
};

@ -1,37 +0,0 @@
//-- copyright
// OpenProject is a project management system.
// Copyright (C) 2012-2018 the OpenProject Foundation (OPF)
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License version 3.
//
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
// Copyright (C) 2006-2017 Jean-Philippe Lang
// Copyright (C) 2010-2013 the ChiliProject Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// See docs/COPYRIGHT.rdoc for more details.
//++
// TODO move to UI components
module.exports = function(){
return {
restrict: 'EA',
replace: true,
scope: { iconName: '@', title: '@iconTitle', cssClass: '@' },
templateUrl: '/templates/components/icon_wrapper.html'
};
};

@ -1,58 +0,0 @@
//-- copyright
// OpenProject is a project management system.
// Copyright (C) 2012-2018 the OpenProject Foundation (OPF)
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License version 3.
//
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
// Copyright (C) 2006-2017 Jean-Philippe Lang
// Copyright (C) 2010-2013 the ChiliProject Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// See docs/COPYRIGHT.rdoc for more details.
//++
module.exports = function() {
return {
restrict: 'A',
scope: {
isInaccessible: "=inaccessibleByTab"
},
link: function(scope, element, attrs) {
scope.oldTabIndex = element.attr("tabindex");
scope.$watch('isInaccessible',
function(isInaccessible) {
if (isInaccessible) {
var currentTabIndex = element.attr("tabindex");
if (currentTabIndex != "-1") {
scope.oldTabIndex = currentTabIndex;
}
element.attr("tabindex", -1);
element.attr('aria-disabled', true);
} else {
element.attr('aria-disabled', false);
if (scope.oldTabIndex) {
element.attr("tabindex", scope.oldTabIndex);
} else {
element.removeAttr("tabindex");
}
}
}
);
}
};
};

@ -27,8 +27,6 @@
//++
angular.module('openproject.uiComponents')
.directive('accessibleCheckbox', [require('./accessible-checkbox-directive')])
.directive('accessibleElement', [require('./accessible-element-directive')])
.directive('copyToClipboard', [
'I18n',
'$timeout',
@ -36,48 +34,17 @@ angular.module('openproject.uiComponents')
'ConfigurationService',
require('./copy-to-clipboard-directive')
])
.directive('emptyElement', [require('./empty-element-directive')])
.constant('ENTER_KEY', 13)
.directive('expandableSearch', ['ENTER_KEY', require('./expandable-search')])
.directive('focus', [
'FocusHelper',
require('./focus-directive')
])
.constant('FOCUSABLE_SELECTOR', 'a, button, :input, [tabindex], select')
.service('FocusHelper', ['$timeout', 'FOCUSABLE_SELECTOR', require(
'./focus-helper')])
.service('I18n', [require('./i18n')])
.directive('inaccessibleByTab', [require('./inaccessible-by-tab-directive')])
.directive('modal', [require('./modal-directive')])
.directive('modalLoading', ['I18n', require('./modal-loading-directive')])
.directive('persistentToggle', [
'$timeout',
require('./persistent-toggle-directive')]
)
.constant('LABEL_MAX_CHARS', 40)
.constant('KEY_CODES', {
enter: 13,
up: 38,
down: 40
})
.directive('selectableTitle', [require('./selectable-title-directive')])
.constant('DOUBLE_CLICK_DELAY', 300)
// Thanks to http://stackoverflow.com/a/20445344
.directive('singleClick', [
'DOUBLE_CLICK_DELAY',
'$parse',
'$timeout',
require('./single-click')
])
.directive('slideToggle', [require('./slide-toggle')])
.directive('sortLink', ['I18n', 'SortService', require(
'./sort-link-directive')])
.constant('ESC_KEY', 27)
.directive('userField', ['PathHelper', require('./user-field-directive')])
.directive('wikiToolbar', [require('./wiki-toolbar-directive')])
.directive('zoomSlider', ['I18n', require('./zoom-slider-directive')])
.directive('attachmentIcon', [require('./attachment-icon-directive')])
.filter('ancestorsExpanded', require('./filters/ancestors-expanded-filter'))
.directive('highlightCol', [require('./highlight-col-directive')])
.directive('confirmPopup', ['$window', require('./confirm-popup-directive')])
.directive('clickOnKeypress', [require('./click-on-keypress-directive')]);

@ -1,47 +0,0 @@
//-- copyright
// OpenProject is a project management system.
// Copyright (C) 2012-2018 the OpenProject Foundation (OPF)
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License version 3.
//
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
// Copyright (C) 2006-2017 Jean-Philippe Lang
// Copyright (C) 2010-2013 the ChiliProject Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// See docs/COPYRIGHT.rdoc for more details.
//++
module.exports = function() {
return {
restrict: 'A',
scope: {
target: '='
},
link: function(scope, element, attributes) {
element.on('click', function(e) {
e.preventDefault();
if (e.which == 1 && !e.ctrlKey && !e.metaKey) {
modalHelperInstance.createModal(scope.target || attributes['href'], function (modalDiv) {});
} else {
window.open(scope.target || attributes['href'], '_blank');
}
});
}
};
};

@ -1,38 +0,0 @@
//-- copyright
// OpenProject is a project management system.
// Copyright (C) 2012-2018 the OpenProject Foundation (OPF)
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License version 3.
//
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
// Copyright (C) 2006-2017 Jean-Philippe Lang
// Copyright (C) 2010-2013 the ChiliProject Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// See docs/COPYRIGHT.rdoc for more details.
//++
module.exports = function(I18n) {
return {
restrict: 'E',
templateUrl: '/templates/components/modal_loading.html',
scope: true,
link: function(scope, element, attributes) {
scope.I18n = I18n;
}
};
};

@ -1,41 +0,0 @@
//-- copyright
// OpenProject is a project management system.
// Copyright (C) 2012-2018 the OpenProject Foundation (OPF)
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License version 3.
//
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
// Copyright (C) 2006-2017 Jean-Philippe Lang
// Copyright (C) 2010-2013 the ChiliProject Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// See docs/COPYRIGHT.rdoc for more details.
//++
module.exports = function() {
return {
restrict: 'E',
replace: true,
scope: {
selectedTitle: '='
},
templateUrl: '/templates/components/selectable_title.html',
link: function(scope) {
scope.I18n = I18n;
}
};
};

@ -1,56 +0,0 @@
//-- copyright
// OpenProject is a project management system.
// Copyright (C) 2012-2018 the OpenProject Foundation (OPF)
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License version 3.
//
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
// Copyright (C) 2006-2017 Jean-Philippe Lang
// Copyright (C) 2010-2013 the ChiliProject Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// See docs/COPYRIGHT.rdoc for more details.
//++
// TODO move to UI components
// Thanks to http://stackoverflow.com/a/20445344
module.exports = function(DOUBLE_CLICK_DELAY, $parse, $timeout) {
return {
restrict: 'A',
link: function(scope, element, attrs) {
var fn = $parse(attrs.singleClick);
var clicks = 0, timer = null;
if (fn) {
element.on('click', function (event) {
clicks++; //count clicks
if(clicks === 1) {
timer = $timeout(function() {
fn(scope, { $event: event });
clicks = 0; //after action performed, reset counter
}, DOUBLE_CLICK_DELAY);
} else {
$timeout.cancel(timer); //prevent single-click action
clicks = 0; //after action performed, reset counter
}
});
}
}
};
};

@ -1,46 +0,0 @@
//-- copyright
// OpenProject is a project management system.
// Copyright (C) 2012-2018 the OpenProject Foundation (OPF)
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License version 3.
//
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
// Copyright (C) 2006-2017 Jean-Philippe Lang
// Copyright (C) 2010-2013 the ChiliProject Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// See docs/COPYRIGHT.rdoc for more details.
//++
module.exports = function() {
return {
restrict: 'A',
scope: {
speed: '@',
collapsed: '='
},
link: function(scope, element) {
if (scope.collapsed) element.hide();
var defaultSpeed = 'fast';
scope.$watch('collapsed', function(state, formerState) {
if (state !== formerState) element.slideToggle(scope.speed || defaultSpeed, null);
});
}
};
};

@ -1,88 +0,0 @@
//-- copyright
// OpenProject is a project management system.
// Copyright (C) 2012-2018 the OpenProject Foundation (OPF)
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License version 3.
//
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
// Copyright (C) 2006-2017 Jean-Philippe Lang
// Copyright (C) 2010-2013 the ChiliProject Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// See docs/COPYRIGHT.rdoc for more details.
//++
module.exports = function(I18n, SortService) {
return {
restrict: 'E',
transclude: true,
scope: { sortAttr: '@', sortFunc: '&' },
templateUrl: '/templates/components/sort_link.html',
link: function(scope, element, attrs) {
var getSortTitle = function() {
var title = "";
var attribute = angular.element(element[0]).find('span.ng-scope').text();
if (SortService.getColumn().indexOf(scope.sortAttr) >= 0) {
if (SortService.isDescending()) {
title = I18n.t('js.label_descending');
} else {
title = I18n.t('js.label_ascending');
}
title += ' ' + I18n.t('js.label_sorted_by') + ' ' + attribute;
} else {
title = I18n.t('js.label_sort_by') + ' ' + attribute;
}
return title;
};
var getSortCss = function() {
var sortDirection = 'asc';
if (SortService.isDescending()) {
sortDirection = 'desc';
}
return sortDirection;
};
scope.sortDirection = getSortCss();
scope.sortTitle = getSortTitle();
scope.$watch(SortService.getColumn, function() {
if (SortService.getColumn().indexOf(scope.sortAttr) < 0) {
scope.sortDirection = "";
scope.sortTitle = getSortTitle();
}
});
scope.sort = function() {
var sortPrefix = SortService.isDescending() ? '' : 'desc';
SortService.setColumn(scope.sortAttr);
SortService.setDirection(sortPrefix);
scope.sortDirection = getSortCss();
scope.sortTitle = getSortTitle();
scope.sortFunc();
};
}
};
};

@ -1,45 +0,0 @@
//-- copyright
// OpenProject is a project management system.
// Copyright (C) 2012-2018 the OpenProject Foundation (OPF)
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License version 3.
//
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
// Copyright (C) 2006-2017 Jean-Philippe Lang
// Copyright (C) 2010-2013 the ChiliProject Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// See docs/COPYRIGHT.rdoc for more details.
//++
module.exports = function(PathHelper) {
return {
restrict: 'E',
replace: true,
templateUrl: '/templates/components/user_field.html',
scope: { user: '=' },
link: function(scope) {
scope.$watch('user', function() {
if (scope.user && scope.user.name) {
scope.userName = scope.user.name;
}
});
scope.userPath = PathHelper.userPath;
}
};
};

@ -1,73 +0,0 @@
//-- copyright
// OpenProject is a project management system.
// Copyright (C) 2012-2018 the OpenProject Foundation (OPF)
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License version 3.
//
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
// Copyright (C) 2006-2017 Jean-Philippe Lang
// Copyright (C) 2010-2013 the ChiliProject Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// See docs/COPYRIGHT.rdoc for more details.
//++
module.exports = function(I18n) {
var latestId = 0;
return {
restrict: 'E',
scope: {
scales: '=',
selectedScale: '='
},
controller: function() {
var vm = this;
vm.minValue = 1;
vm.maxValue = vm.scales.length;
vm.sliderId = 'zoom-slider-' + latestId++;
vm.setSelectedScaleIndex = function(index) {
vm.selectedScaleIndex = index;
vm.selectedScale = vm.scales[vm.selectedScaleIndex];
};
},
controllerAs: 'ctrl',
bindToController: true,
templateUrl: '/templates/components/zoom_slider.html',
link: function(scope, element, attributes, ctrl) {
scope.labelText = I18n.t('js.timelines.zoom.slider');
var slider = element.find('input');
slider.on('change', function() {
ctrl.setSelectedScaleIndex(slider.val() - 1);
scope.$apply();
});
scope.$watch('ctrl.selectedScale', function(newScale) {
var newIndex = ctrl.scales.indexOf(newScale);
if (ctrl.selectedScaleIndex !== newIndex) {
ctrl.selectedScaleIndex = newIndex;
}
});
}
};
};

@ -49,11 +49,8 @@ require('expose-loader?angular!angular');
require('expose-loader?dragula!dragula/dist/dragula.min.js');
require('angular-animate/angular-animate.min.js');
require('angular-aria/angular-aria.min.js');
require('angular-cache/dist/angular-cache.min.js');
require('angular-dragula/dist/angular-dragula.min.js');
require('angular-elastic');
require('angular-modal/modal.min.js');
require('angular-sanitize/angular-sanitize.min.js');
require('@uirouter/angular-hybrid');
require('ng-file-upload/dist/ng-file-upload.min.js');

@ -337,11 +337,6 @@
"resolved": "https://registry.npmjs.org/angular-aria/-/angular-aria-1.5.11.tgz",
"integrity": "sha1-JpY6wzaJHO17GZ5Zr9fpsl5T1qY="
},
"angular-cache": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/angular-cache/-/angular-cache-4.6.0.tgz",
"integrity": "sha1-XMkK0Sc/sVAgXcHHG4FBrjnki/8="
},
"angular-dragula": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/angular-dragula/-/angular-dragula-1.2.8.tgz",
@ -370,19 +365,6 @@
"integrity": "sha1-oOHdDqVf137np1fXVTbF6WTIb4E=",
"dev": true
},
"angular-modal": {
"version": "git://github.com/finnlabs/angular-modal.git#d45eb9ceb720b8785613ba89ba0f14f8ab197569"
},
"angular-sanitize": {
"version": "1.3.20",
"resolved": "https://registry.npmjs.org/angular-sanitize/-/angular-sanitize-1.3.20.tgz",
"integrity": "sha1-WxyqQXa42yA519HBUnU3phvjzWc="
},
"angular-ui-bootstrap": {
"version": "2.5.6",
"resolved": "https://registry.npmjs.org/angular-ui-bootstrap/-/angular-ui-bootstrap-2.5.6.tgz",
"integrity": "sha512-yzcHpPMLQl0232nDzm5P4iAFTFQ9dMw0QgFLuKYbDj9M0xJ62z0oudYD/Lvh1pWfRsukiytP4Xj6BHOSrSXP8A=="
},
"animation-frame-polyfill": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/animation-frame-polyfill/-/animation-frame-polyfill-1.0.1.tgz",
@ -874,6 +856,7 @@
"requires": {
"anymatch": "1.3.2",
"async-each": "1.0.1",
"fsevents": "1.2.3",
"glob-parent": "2.0.0",
"inherits": "2.0.3",
"is-binary-path": "1.0.1",
@ -2177,6 +2160,468 @@
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
},
"fsevents": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.3.tgz",
"integrity": "sha512-X+57O5YkDTiEQGiw8i7wYc2nQgweIekqkepI8Q3y4wVlurgBt2SuwxTeYUYMZIGpLZH3r/TsMjczCMXE5ZOt7Q==",
"optional": true,
"requires": {
"nan": "2.10.0",
"node-pre-gyp": "0.9.1"
},
"dependencies": {
"abbrev": {
"version": "1.1.1",
"bundled": true,
"optional": true
},
"ansi-regex": {
"version": "2.1.1",
"bundled": true
},
"aproba": {
"version": "1.2.0",
"bundled": true,
"optional": true
},
"are-we-there-yet": {
"version": "1.1.4",
"bundled": true,
"optional": true,
"requires": {
"delegates": "1.0.0",
"readable-stream": "2.3.6"
}
},
"balanced-match": {
"version": "1.0.0",
"bundled": true
},
"brace-expansion": {
"version": "1.1.11",
"bundled": true,
"requires": {
"balanced-match": "1.0.0",
"concat-map": "0.0.1"
}
},
"chownr": {
"version": "1.0.1",
"bundled": true,
"optional": true
},
"code-point-at": {
"version": "1.1.0",
"bundled": true
},
"concat-map": {
"version": "0.0.1",
"bundled": true
},
"console-control-strings": {
"version": "1.1.0",
"bundled": true
},
"core-util-is": {
"version": "1.0.2",
"bundled": true,
"optional": true
},
"debug": {
"version": "2.6.9",
"bundled": true,
"optional": true,
"requires": {
"ms": "2.0.0"
}
},
"deep-extend": {
"version": "0.4.2",
"bundled": true,
"optional": true
},
"delegates": {
"version": "1.0.0",
"bundled": true,
"optional": true
},
"detect-libc": {
"version": "1.0.3",
"bundled": true,
"optional": true
},
"fs-minipass": {
"version": "1.2.5",
"bundled": true,
"optional": true,
"requires": {
"minipass": "2.2.4"
}
},
"fs.realpath": {
"version": "1.0.0",
"bundled": true,
"optional": true
},
"gauge": {
"version": "2.7.4",
"bundled": true,
"optional": true,
"requires": {
"aproba": "1.2.0",
"console-control-strings": "1.1.0",
"has-unicode": "2.0.1",
"object-assign": "4.1.1",
"signal-exit": "3.0.2",
"string-width": "1.0.2",
"strip-ansi": "3.0.1",
"wide-align": "1.1.2"
}
},
"glob": {
"version": "7.1.2",
"bundled": true,
"optional": true,
"requires": {
"fs.realpath": "1.0.0",
"inflight": "1.0.6",
"inherits": "2.0.3",
"minimatch": "3.0.4",
"once": "1.4.0",
"path-is-absolute": "1.0.1"
}
},
"has-unicode": {
"version": "2.0.1",
"bundled": true,
"optional": true
},
"iconv-lite": {
"version": "0.4.21",
"bundled": true,
"optional": true,
"requires": {
"safer-buffer": "2.1.2"
}
},
"ignore-walk": {
"version": "3.0.1",
"bundled": true,
"optional": true,
"requires": {
"minimatch": "3.0.4"
}
},
"inflight": {
"version": "1.0.6",
"bundled": true,
"optional": true,
"requires": {
"once": "1.4.0",
"wrappy": "1.0.2"
}
},
"inherits": {
"version": "2.0.3",
"bundled": true
},
"ini": {
"version": "1.3.5",
"bundled": true,
"optional": true
},
"is-fullwidth-code-point": {
"version": "1.0.0",
"bundled": true,
"requires": {
"number-is-nan": "1.0.1"
}
},
"isarray": {
"version": "1.0.0",
"bundled": true,
"optional": true
},
"minimatch": {
"version": "3.0.4",
"bundled": true,
"requires": {
"brace-expansion": "1.1.11"
}
},
"minimist": {
"version": "0.0.8",
"bundled": true
},
"minipass": {
"version": "2.2.4",
"bundled": true,
"requires": {
"safe-buffer": "5.1.1",
"yallist": "3.0.2"
}
},
"minizlib": {
"version": "1.1.0",
"bundled": true,
"optional": true,
"requires": {
"minipass": "2.2.4"
}
},
"mkdirp": {
"version": "0.5.1",
"bundled": true,
"requires": {
"minimist": "0.0.8"
}
},
"ms": {
"version": "2.0.0",
"bundled": true,
"optional": true
},
"needle": {
"version": "2.2.0",
"bundled": true,
"optional": true,
"requires": {
"debug": "2.6.9",
"iconv-lite": "0.4.21",
"sax": "1.2.4"
}
},
"node-pre-gyp": {
"version": "0.9.1",
"bundled": true,
"optional": true,
"requires": {
"detect-libc": "1.0.3",
"mkdirp": "0.5.1",
"needle": "2.2.0",
"nopt": "4.0.1",
"npm-packlist": "1.1.10",
"npmlog": "4.1.2",
"rc": "1.2.6",
"rimraf": "2.6.2",
"semver": "5.5.0",
"tar": "4.4.1"
}
},
"nopt": {
"version": "4.0.1",
"bundled": true,
"optional": true,
"requires": {
"abbrev": "1.1.1",
"osenv": "0.1.5"
}
},
"npm-bundled": {
"version": "1.0.3",
"bundled": true,
"optional": true
},
"npm-packlist": {
"version": "1.1.10",
"bundled": true,
"optional": true,
"requires": {
"ignore-walk": "3.0.1",
"npm-bundled": "1.0.3"
}
},
"npmlog": {
"version": "4.1.2",
"bundled": true,
"optional": true,
"requires": {
"are-we-there-yet": "1.1.4",
"console-control-strings": "1.1.0",
"gauge": "2.7.4",
"set-blocking": "2.0.0"
}
},
"number-is-nan": {
"version": "1.0.1",
"bundled": true
},
"object-assign": {
"version": "4.1.1",
"bundled": true,
"optional": true
},
"once": {
"version": "1.4.0",
"bundled": true,
"requires": {
"wrappy": "1.0.2"
}
},
"os-homedir": {
"version": "1.0.2",
"bundled": true,
"optional": true
},
"os-tmpdir": {
"version": "1.0.2",
"bundled": true,
"optional": true
},
"osenv": {
"version": "0.1.5",
"bundled": true,
"optional": true,
"requires": {
"os-homedir": "1.0.2",
"os-tmpdir": "1.0.2"
}
},
"path-is-absolute": {
"version": "1.0.1",
"bundled": true,
"optional": true
},
"process-nextick-args": {
"version": "2.0.0",
"bundled": true,
"optional": true
},
"rc": {
"version": "1.2.6",
"bundled": true,
"optional": true,
"requires": {
"deep-extend": "0.4.2",
"ini": "1.3.5",
"minimist": "1.2.0",
"strip-json-comments": "2.0.1"
},
"dependencies": {
"minimist": {
"version": "1.2.0",
"bundled": true,
"optional": true
}
}
},
"readable-stream": {
"version": "2.3.6",
"bundled": true,
"optional": true,
"requires": {
"core-util-is": "1.0.2",
"inherits": "2.0.3",
"isarray": "1.0.0",
"process-nextick-args": "2.0.0",
"safe-buffer": "5.1.1",
"string_decoder": "1.1.1",
"util-deprecate": "1.0.2"
}
},
"rimraf": {
"version": "2.6.2",
"bundled": true,
"optional": true,
"requires": {
"glob": "7.1.2"
}
},
"safe-buffer": {
"version": "5.1.1",
"bundled": true
},
"safer-buffer": {
"version": "2.1.2",
"bundled": true,
"optional": true
},
"sax": {
"version": "1.2.4",
"bundled": true,
"optional": true
},
"semver": {
"version": "5.5.0",
"bundled": true,
"optional": true
},
"set-blocking": {
"version": "2.0.0",
"bundled": true,
"optional": true
},
"signal-exit": {
"version": "3.0.2",
"bundled": true,
"optional": true
},
"string-width": {
"version": "1.0.2",
"bundled": true,
"requires": {
"code-point-at": "1.1.0",
"is-fullwidth-code-point": "1.0.0",
"strip-ansi": "3.0.1"
}
},
"string_decoder": {
"version": "1.1.1",
"bundled": true,
"optional": true,
"requires": {
"safe-buffer": "5.1.1"
}
},
"strip-ansi": {
"version": "3.0.1",
"bundled": true,
"requires": {
"ansi-regex": "2.1.1"
}
},
"strip-json-comments": {
"version": "2.0.1",
"bundled": true,
"optional": true
},
"tar": {
"version": "4.4.1",
"bundled": true,
"optional": true,
"requires": {
"chownr": "1.0.1",
"fs-minipass": "1.2.5",
"minipass": "2.2.4",
"minizlib": "1.1.0",
"mkdirp": "0.5.1",
"safe-buffer": "5.1.1",
"yallist": "3.0.2"
}
},
"util-deprecate": {
"version": "1.0.2",
"bundled": true,
"optional": true
},
"wide-align": {
"version": "1.1.2",
"bundled": true,
"optional": true,
"requires": {
"string-width": "1.0.2"
}
},
"wrappy": {
"version": "1.0.2",
"bundled": true
},
"yallist": {
"version": "3.0.2",
"bundled": true
}
}
},
"fuse.js": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-3.2.0.tgz",
@ -3349,6 +3794,12 @@
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
},
"nan": {
"version": "2.10.0",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.10.0.tgz",
"integrity": "sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA==",
"optional": true
},
"negotiator": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz",
@ -4633,12 +5084,6 @@
}
}
},
"sorted-object": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/sorted-object/-/sorted-object-1.0.0.tgz",
"integrity": "sha1-XR9PnB+yzUiWWWcwTiEutEz7bQU=",
"dev": true
},
"source-list-map": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.0.tgz",

@ -22,7 +22,6 @@
"mocha": "^3.2.0",
"sinon": "^1.17.5",
"sinon-chai": "^2.8.0",
"sorted-object": "^1.0.0",
"tslint": "5.9.1",
"webpack-bundle-analyzer": "2.11.1",
"webpack-dev-server": "^1.6.5"
@ -64,13 +63,9 @@
"angular": "~1.5.9",
"angular-animate": "~1.5.9",
"angular-aria": "~1.5.9",
"angular-cache": "~4.6.0",
"angular-dragula": "~1.2.8",
"angular-elastic": "2.5.0",
"angular-i18n": "~1.3.0",
"angular-modal": "git://github.com/finnlabs/angular-modal#d45eb9ceb720b8785613ba89ba0f14f8ab197569",
"angular-sanitize": "~1.3.14",
"angular-ui-bootstrap": "^2.2.0",
"at.js": "^1.5.3",
"atoa": "^1.0.0",
"autoprefixer": "^6.5.3",

@ -1,71 +0,0 @@
//-- copyright
// OpenProject is a project management system.
// Copyright (C) 2012-2018 the OpenProject Foundation (OPF)
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License version 3.
//
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
// Copyright (C) 2006-2017 Jean-Philippe Lang
// Copyright (C) 2010-2013 the ChiliProject Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// See docs/COPYRIGHT.rdoc for more details.
//++
describe('inaccessibleByTab Directive', function() {
var compile, element, rootScope, scope;
beforeEach(angular.mock.module('openproject.uiComponents'));
beforeEach(angular.mock.module('openproject.templates'));
beforeEach(inject(function($rootScope, $compile) {
var html =
'<a inaccessible-by-tab="boolValue"></a>';
element = angular.element(html);
rootScope = $rootScope;
scope = $rootScope.$new();
compile = function() {
$compile(element)(scope);
scope.$digest();
};
}));
describe('tabindex', function() {
it('should be -1 for true', function() {
scope.boolValue = true;
compile();
expect(element.attr('tabindex')).to.eq("-1");
});
it('should not be -1 for false', function() {
scope.boolValue = false;
compile();
expect(element.attr('tabindex')).to.be.undefined;
});
it('should change if the directive attr changes', function() {
scope.boolValue = false;
compile();
expect(element.attr('tabindex')).to.be.undefined;
scope.boolValue = true;
scope.$digest();
expect(element.attr('tabindex')).to.eq("-1");
});
});
});

@ -1,166 +0,0 @@
//-- copyright
// OpenProject is a project management system.
// Copyright (C) 2012-2018 the OpenProject Foundation (OPF)
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License version 3.
//
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
// Copyright (C) 2006-2017 Jean-Philippe Lang
// Copyright (C) 2010-2013 the ChiliProject Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// See docs/COPYRIGHT.rdoc for more details.
//++
/*jshint expr: true*/
describe('Custom field helper', function() {
var CustomFieldHelper;
beforeEach(angular.mock.module('openproject.helpers'));
beforeEach(inject(function(_CustomFieldHelper_) {
CustomFieldHelper = _CustomFieldHelper_;
}));
describe('formatCustomFieldValue', function() {
var formatCustomFieldValue;
beforeEach(function() {
formatCustomFieldValue = CustomFieldHelper.formatCustomFieldValue;
});
describe('with a boolean type', function() {
var I18n, t;
beforeEach(inject(function(_I18n_){
I18n = _I18n_;
t = sinon.stub(I18n, 't');
t.withArgs('js.general_text_No').returns('No');
t.withArgs('js.general_text_Yes').returns('Yes');
}));
afterEach(inject(function() {
I18n.t.restore();
}));
it('should handle undefined and null values', function() {
expect(formatCustomFieldValue(null, 'bool')).to.not.be.ok;
expect(formatCustomFieldValue(undefined, 'bool')).to.not.be.ok;
});
it('should parse a false value', function() {
expect(formatCustomFieldValue('f', 'bool')).to.equal('No');
});
it('should parse a true value', function() {
expect(formatCustomFieldValue('t', 'bool')).to.equal('Yes');
});
});
describe('with an integer type', function() {
it('should handle undefined and null values', function() {
expect(formatCustomFieldValue(null, 'int')).to.equal('');
expect(formatCustomFieldValue(undefined, 'int')).to.equal('');
});
it('should parse a float, displaying as integer', function() {
expect(formatCustomFieldValue(200000.49, 'int')).to.equal(200000);
expect(formatCustomFieldValue(200000.51, 'int')).to.equal(200000);
});
it('should parse a string, displaying as integer', function() {
expect(formatCustomFieldValue('49', 'int')).to.equal(49);
expect(formatCustomFieldValue('49.99', 'int')).to.equal(49);
expect(formatCustomFieldValue('49.BLAH', 'int')).to.equal('');
});
it('should preserve an integer', function() {
expect(formatCustomFieldValue(49, 'int')).to.equal(49);
});
it('should handle a meaningless string', function() {
expect(formatCustomFieldValue('BLAHBLAH','int')).to.equal('');
});
});
describe('with a float type', function() {
it('should handle undefined and null values', function() {
expect(formatCustomFieldValue(null, 'int')).to.equal('');
expect(formatCustomFieldValue(undefined, 'int')).to.equal('');
});
xit('should parse an integer, displaying with decimal places', function() {
expect(formatCustomFieldValue(99, 'float')).to.equal(99);
});
it('should parse a string, displaying as float', function() {
expect(formatCustomFieldValue('49.99', 'float')).to.equal(49.99);
expect(formatCustomFieldValue('49.99BLAH','float')).to.equal('');
});
it('should preserve a float', function() {
expect(formatCustomFieldValue(11.11, 'float')).to.equal(11.11);
});
it('should handle a meaningless string', function() {
expect(formatCustomFieldValue('BLAHBLAH','int')).to.equal('');
});
});
describe('with a user type', function() {
it('should return the value of value.name', function() {
expect(formatCustomFieldValue({ name: 'blubs' }, 'user')).to.equal('blubs');
});
it('should handle undefined and null values', function() {
expect(formatCustomFieldValue(undefined, 'user')).to.equal('');
expect(formatCustomFieldValue(null, 'user')).to.equal('');
expect(formatCustomFieldValue({ name: undefined }, 'user')).to.equal('');
expect(formatCustomFieldValue({ name: null }, 'user')).to.equal('');
});
it('should return the name of the user out of a list of provided users', function() {
expect(formatCustomFieldValue( 5 , 'user', { '5': { name: 'blubs' }})).to.equal('blubs');
});
it('should return empty string if the user does' +
'not exists in list of provided users', function() {
expect(formatCustomFieldValue( 4 , 'user', { '5': { name: 'blubs' }})).to.equal('');
});
it('should handle undefined and null values in list of provided users', function() {
expect(formatCustomFieldValue( 5 , 'user', { '5': { name: null }})).to.equal('');
expect(formatCustomFieldValue( 5 , 'user', { '5': { name: undefined }})).to.equal('');
expect(formatCustomFieldValue( 5 , 'user', { '5': undefined })).to.equal('');
expect(formatCustomFieldValue( 5 , 'user', { '5': null })).to.equal('');
});
});
describe('with a version type', function() {
it('should return the value of value.name', function() {
expect(formatCustomFieldValue({ name: 'blubs' }, 'version')).to.equal('blubs');
});
it('should handle undefined and null values', function() {
expect(formatCustomFieldValue(undefined, 'version')).to.equal('');
expect(formatCustomFieldValue(null, 'version')).to.equal('');
expect(formatCustomFieldValue({ name: undefined }, 'version')).to.equal('');
expect(formatCustomFieldValue({ name: null }, 'version')).to.equal('');
});
});
});
});

@ -1,62 +0,0 @@
//-- copyright
// OpenProject is a project management system.
// Copyright (C) 2012-2018 the OpenProject Foundation (OPF)
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License version 3.
//
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
// Copyright (C) 2006-2017 Jean-Philippe Lang
// Copyright (C) 2010-2013 the ChiliProject Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// See docs/COPYRIGHT.rdoc for more details.
//++
describe('ConversionService', function() {
'use strict';
var ConversionService;
beforeEach(angular.mock.module('openproject.services'));
beforeEach(inject(function(_ConversionService_){
ConversionService = _ConversionService_;
}));
it('be able to turn bytes into KiloBytes', function() {
var kiloBytes = ConversionService.kilobytes(1000);
expect(kiloBytes).to.eql(1);
});
it('be able to turn bytes into MegaBytes', function() {
var megabytes = ConversionService.megabytes(1000000);
expect(megabytes).to.eql(1);
});
it('should dynamically convert bytes into Mega- and Kilobytes', function() {
var result = ConversionService.fileSize(1000000);
expect(result).to.eql('1MB');
result = ConversionService.fileSize(1000);
expect(result).to.eql('1kB');
result = ConversionService.fileSize(1234);
expect(result).to.eql('1.2kB');
result = ConversionService.fileSize(1874234);
expect(result).to.eql('1.9MB');
});
});

@ -1,95 +0,0 @@
//-- copyright
// OpenProject is a project management system.
// Copyright (C) 2012-2018 the OpenProject Foundation (OPF)
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License version 3.
//
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
// Copyright (C) 2006-2017 Jean-Philippe Lang
// Copyright (C) 2010-2013 the ChiliProject Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// See docs/COPYRIGHT.rdoc for more details.
//++
/*jshint expr: true*/
describe('SortService', function() {
var SortService;
beforeEach(angular.mock.module('openproject.services'));
beforeEach(inject(function(_SortService_){
SortService = _SortService_;
}));
describe('#setDirection', function() {
describe('invalid parameter', function() {
it('throws error', function() {
expect(SortService.setDirection).to.throw(Error, /Parameter does not match/);
});
});
describe('AngularJS sort operator', function() {
beforeEach(function() {
SortService.setDirection('-');
});
it('sets direction descending', function() {
expect(SortService.getDirection()).to.equal('desc');
});
});
});
describe('#isDescending', function() {
it('sort is descending', function() {
expect(SortService.isDescending()).to.be.false;
});
describe('set sort direction to descending', function() {
beforeEach(function() {
SortService.setDirection('desc');
});
it('sort is descending', function() {
expect(SortService.isDescending()).to.be.true;
});
});
});
describe('#getSortParam', function() {
beforeEach(function() {
SortService.setColumn('id');
});
it('is valid sort param', function() {
expect(SortService.getSortParam()).to.equal('id:asc');
});
describe('set sort direction to descending', function() {
beforeEach(function() {
SortService.setDirection('desc');
});
it('is valid sort param', function() {
expect(SortService.getSortParam()).to.equal('id:desc');
});
});
});
});

@ -1,84 +0,0 @@
//-- copyright
// OpenProject is a project management system.
// Copyright (C) 2012-2018 the OpenProject Foundation (OPF)
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License version 3.
//
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
// Copyright (C) 2006-2017 Jean-Philippe Lang
// Copyright (C) 2010-2013 the ChiliProject Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// See docs/COPYRIGHT.rdoc for more details.
//++
/*jshint expr: true*/
describe('accessibleCheckbox Directive', function() {
var compile, element, rootScope, scope;
beforeEach(angular.mock.module('openproject.uiComponents'));
beforeEach(angular.mock.module('openproject.templates'));
beforeEach(inject(function($rootScope, $compile) {
var html;
html = '<accessible-checkbox name="check-it-out" checkbox-id="check-it-out" checkbox-title="{{ title }}" ' +
'checkbox-value="simpleValue" model="carModel"></accessible-checkbox>';
element = angular.element(html);
rootScope = $rootScope;
scope = $rootScope.$new();
scope.simpleValue = 'BAR';
scope.carModel = {
nice: 'people'
};
compile = function() {
$compile(element)(scope);
scope.$digest();
};
}));
describe('element', function() {
beforeEach(function() {
compile();
});
it('should render a surrounding span', function() {
expect(element.prop('tagName')).to.equal('SPAN');
});
it('should render a label', function() {
var label = element.find('label');
expect(label.length).to.equal(1);
expect(label.text()).to.equal('');
scope.title = 'New Label';
scope.$apply();
expect(label.text()).to.equal('New Label');
});
it('should render a checkbox', function() {
var input = element.find('input');
expect(input.length).to.equal(1);
expect(input.attr('name')).to.equal('check-it-out');
expect(input.val()).to.equal('BAR');
});
});
});

@ -1,76 +0,0 @@
//-- copyright
// OpenProject is a project management system.
// Copyright (C) 2012-2018 the OpenProject Foundation (OPF)
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License version 3.
//
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
// Copyright (C) 2006-2017 Jean-Philippe Lang
// Copyright (C) 2010-2013 the ChiliProject Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// See docs/COPYRIGHT.rdoc for more details.
//++
/*jshint expr: true*/
describe('focus Directive', function() {
var doc, compile, element, rootScope, scope, timeout, body;
var input = '<input type="text" name="testInput" focus id="focusTest"></input>',
element = angular.element(input);
beforeEach(angular.mock.module('openproject.uiComponents'));
beforeEach(angular.mock.module('openproject.templates'));
beforeEach(angular.mock.module('openproject.uiComponents', function($provide) {
var configurationService = {};
configurationService.isTimezoneSet = sinon.stub().returns(false);
configurationService.accessibilityModeEnabled = sinon.stub().returns(false);
$provide.constant('ConfigurationService', configurationService);
}));
beforeEach(inject(function($compile, $rootScope, $document, $timeout) {
doc = $document[0];
rootScope = $rootScope;
scope = $rootScope.$new();
body = angular.element(doc.body);
body.append(element);
compile = $compile;
timeout = $timeout;
}));
afterEach(function() {
var body = angular.element(doc.body);
body.find('#focusTest').remove();
});
describe('element', function() {
it('should focus the element', function() {
compile(element)(scope);
scope.$digest();
timeout.flush();
expect(doc.activeElement).to.equal(element[0]);
});
});
});

@ -1,64 +0,0 @@
//-- copyright
// OpenProject is a project management system.
// Copyright (C) 2012-2018 the OpenProject Foundation (OPF)
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License version 3.
//
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
// Copyright (C) 2006-2017 Jean-Philippe Lang
// Copyright (C) 2010-2013 the ChiliProject Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// See docs/COPYRIGHT.rdoc for more details.
//++
/*jshint expr: true*/
describe('modal Directive', function() {
var compile, element, rootScope, scope;
beforeEach(angular.mock.module('openproject.uiComponents'));
beforeEach(angular.mock.module('openproject.templates'));
beforeEach(inject(function($rootScope, $compile) {
var html;
html = '<div modal></div>';
element = angular.element(html);
rootScope = $rootScope;
scope = $rootScope.$new();
compile = function() {
$compile(element)(scope);
scope.$digest();
};
}));
describe('element', function() {
beforeEach(function() {
compile();
});
it('should preserve its div', function() {
expect(element.prop('tagName')).to.equal('DIV');
});
xit('should be clickable', function() {
expect(element.click()).to.be.ok;
});
});
});

@ -1,73 +0,0 @@
//-- copyright
// OpenProject is a project management system.
// Copyright (C) 2012-2018 the OpenProject Foundation (OPF)
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License version 3.
//
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
// Copyright (C) 2006-2017 Jean-Philippe Lang
// Copyright (C) 2010-2013 the ChiliProject Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// See docs/COPYRIGHT.rdoc for more details.
//++
/*jshint expr: true*/
describe('slideToggle Directive', function() {
var compile, element, rootScope, scope;
beforeEach(angular.mock.module('openproject.uiComponents'));
beforeEach(angular.mock.module('openproject.templates'));
beforeEach(inject(function($rootScope, $compile) {
var html;
html = '<div slide-toggle speed="10" collapsed="doNotShow"></div>';
element = angular.element(html);
rootScope = $rootScope;
scope = $rootScope.$new();
scope.doNotShow = true;
compile = function() {
$compile(element)(scope);
scope.$digest();
};
}));
describe('element', function() {
beforeEach(function() {
compile();
});
it('should preserve its div', function() {
expect(element.prop('tagName')).to.equal('DIV');
});
it('should be in a collapsed state', function() {
expect(element.is(":visible")).to.be.false;
});
xit('should toggle', function() {
scope.doNotShow = true;
scope.$apply(function() {
expect(element.is(":visible")).to.be.true;
});
});
});
});

@ -1,121 +0,0 @@
//-- copyright
// OpenProject is a project management system.
// Copyright (C) 2012-2018 the OpenProject Foundation (OPF)
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License version 3.
//
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
// Copyright (C) 2006-2017 Jean-Philippe Lang
// Copyright (C) 2010-2013 the ChiliProject Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// See docs/COPYRIGHT.rdoc for more details.
//++
/*jshint expr: true*/
describe('sort link Directive', function() {
var I18n, t, compile, element, scope, sortService;
beforeEach(angular.mock.module('openproject.uiComponents'));
beforeEach(angular.mock.module('openproject.templates', function($provide) {
sortService = {};
sortService.isDescending = sinon.stub().returns(false);
sortService.getColumn = sinon.stub().returns('');
$provide.constant('SortService', sortService);
}));
beforeEach(inject(function($rootScope, $compile, _I18n_) {
var html = '<sort-link sort-attr="id" sort-func="sortFunc()">Id</sort-link>';
scope = $rootScope.$new();
scope.sortFunc = sinon.spy();
compile = function() {
element = $compile(html)(scope);
scope.$digest();
};
I18n = _I18n_;
t = sinon.stub(I18n, 't');
t.withArgs('js.label_descending').returns('desc');
t.withArgs('js.label_ascending').returns('down');
t.withArgs('js.label_sorted_by').returns('sorted by');
t.withArgs('js.label_sort_by').returns('sort by');
}));
afterEach(inject(function() {
I18n.t.restore();
}));
var link;
describe('inital state', function() {
beforeEach(function() {
compile();
link = element.children();
});
it('should render a link', function() {
expect(link.prop('tagName')).to.equal('A');
});
it('should render title', function() {
expect(link.prop('title')).to.equal("sort by Id");
});
it('should set sort css', function() {
var directiveScope = element.isolateScope();
expect(directiveScope.sortDirection).to.empty;
});
describe('callback', function() {
it('should call callback', function() {
var directiveScope = element.isolateScope();
directiveScope.sortFunc();
expect(scope.sortFunc.calledOnce).to.be.true;
});
});
});
describe('changing sort arguments', function() {
beforeEach(function() {
sortService.getColumn = sinon.stub().returns('id');
sortService.getDirection = sinon.stub().returns('desc');
sortService.isDescending = sinon.stub().returns(false);
compile();
link = element.children();
});
it('should render title', function() {
expect(link.prop('title')).to.equal("down sorted by Id");
});
it('should set sort css', function() {
var directiveScope = element.isolateScope();
expect(directiveScope.sortDirection).to.equal('asc');
});
});
});

@ -1,96 +0,0 @@
//-- copyright
// OpenProject is a project management system.
// Copyright (C) 2012-2018 the OpenProject Foundation (OPF)
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License version 3.
//
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
// Copyright (C) 2006-2017 Jean-Philippe Lang
// Copyright (C) 2010-2013 the ChiliProject Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// See docs/COPYRIGHT.rdoc for more details.
//++
/*jshint expr: true*/
describe('zoomSlider Directive', function() {
var I18n, compile, element, scope;
beforeEach(angular.mock.module('openproject.uiComponents'));
beforeEach(angular.mock.module('openproject.templates'));
beforeEach(inject(function($rootScope, $compile, _I18n_) {
var html = '<zoom-slider scales="scales" selected-scale="scale"></zoom-slider>';
element = angular.element(html);
scope = $rootScope.$new();
scope.scales = ['yearly', 'quarterly', 'monthly', 'weekly', 'daily'];
compile = function() {
$compile(element)(scope);
scope.$digest();
};
I18n = _I18n_;
sinon.stub(I18n, 't').returns('Zoom Test');
}));
afterEach(function() {
I18n.t.restore();
});
describe('label element', function() {
beforeEach(function() {
compile();
});
it('provides an accessible label for the slider', function() {
var slider = element.find('input[type="range"]');
var label = element.find('label[for=' + slider.attr('id') + ']');
expect(label.text().trim()).to.equal('Zoom Test');
});
});
describe('slider element', function() {
var slider;
beforeEach(function() {
compile();
slider = element.find('input[type="range"]');
});
it('has an initial value set', function() {
expect(slider.val()).to.eq('1');
});
it('has its value set based on selectedScale', function() {
scope.scale = 'monthly';
scope.$apply();
expect(slider.val()).to.eq('3');
});
it('updates selectedScale when its value changes', function() {
slider.val('2').change();
expect(scope.scale).to.eq('quarterly');
slider.val('5').change();
expect(scope.scale).to.eq('daily');
});
});
});
Loading…
Cancel
Save