mirror of
https://github.com/opf/openproject.git
synced 2026-06-14 03:30:14 +00:00
Remove accessible-by-keyboard a11y component (#9331)
* Replace accessible-by-keyboard * Replace messed up inplace styles with work-package-comment bem block The styles are not used anymore except for work package comments anyway * Replace split view icons with buttons * Make back-button its own bem block * Replace button in hide-section component * Replace activity buttons * Replace notification icon * Replace collapsible-section * Replace config-menu * Make attribute help text trigger a button * Ensure all buttons are role=button * Replace inplace save controls * Replace relations buttons * Replace relation toggle button * Replace relation row buttons * Replace breadcrumb parent * Replace activities toggler * Remove accessible-by-keyboard component * Fix button role to button type * Fix indent * Bemmify media query * Fix specs
This commit is contained in:
@@ -446,6 +446,7 @@ en:
|
||||
label_quote_comment: "Quote this comment"
|
||||
label_recent: "Recent"
|
||||
label_reset: "Reset"
|
||||
label_remove: "Remove"
|
||||
label_remove_column: "Remove column"
|
||||
label_remove_columns: "Remove selected columns"
|
||||
label_remove_row: "Remove row"
|
||||
|
||||
@@ -11,8 +11,7 @@ OpenProject is a hybrid application with most parts being Rails, along with some
|
||||
|
||||
|
||||
|
||||
The Angular frontend services and components can be tested with frontend specs. A good isolated example on how to set up an Angular TestBed to test components is `frontend/src/app/modules/a11y/accessible-by-keyboard.component.spec.ts`
|
||||
|
||||
The Angular frontend services and components can be tested with frontend specs.
|
||||
|
||||
|
||||
If you want to test services that have no dependencies, a simple instantiation of that class is sufficient to test the service in isolation. A good example is ` frontend/src/app/components/projects/current-project.service.spec.ts`
|
||||
@@ -1,87 +1,115 @@
|
||||
<ng-container>
|
||||
<!-- Name -->
|
||||
<label for="operators-{{filter.id}}"
|
||||
class="advanced-filters--filter-name"
|
||||
[textContent]="filter.name"
|
||||
[attr.title]="filter.name">
|
||||
<label
|
||||
for="operators-{{filter.id}}"
|
||||
class="advanced-filters--filter-name"
|
||||
[textContent]="filter.name"
|
||||
[attr.title]="filter.name"
|
||||
>
|
||||
</label>
|
||||
|
||||
<!-- Operator -->
|
||||
<div class="advanced-filters--filter-operator">
|
||||
|
||||
<label for="operators-{{filter.id}}"
|
||||
class="hidden-for-sighted">
|
||||
<label
|
||||
for="operators-{{filter.id}}"
|
||||
class="hidden-for-sighted"
|
||||
>
|
||||
{{ filter.name }}
|
||||
{{ text.open_filter }}
|
||||
</label>
|
||||
|
||||
<select required
|
||||
*ngIf="valueType !== '[1]Boolean'"
|
||||
class="advanced-filters--select"
|
||||
id="operators-{{filter.id}}"
|
||||
name="op[{{filter.id}}]"
|
||||
[(ngModel)]="filter.operator"
|
||||
(ngModelChange)="onFilterUpdated(filter)"
|
||||
[compareWith]="compareByHref"
|
||||
style="vertical-align: top;">
|
||||
<option *ngFor="let operator of availableOperators"
|
||||
[textContent]="operator.name"
|
||||
[ngValue]="operator">
|
||||
<select
|
||||
required
|
||||
*ngIf="valueType !== '[1]Boolean'"
|
||||
class="advanced-filters--select"
|
||||
id="operators-{{filter.id}}"
|
||||
name="op[{{filter.id}}]"
|
||||
[(ngModel)]="filter.operator"
|
||||
(ngModelChange)="onFilterUpdated(filter)"
|
||||
[compareWith]="compareByHref"
|
||||
style="vertical-align: top;"
|
||||
>
|
||||
<option
|
||||
*ngFor="let operator of availableOperators"
|
||||
[textContent]="operator.name"
|
||||
[ngValue]="operator"
|
||||
>
|
||||
</option>
|
||||
</select>
|
||||
|
||||
<filter-boolean-value *ngIf="valueType == '[1]Boolean'"
|
||||
(filterChanged)="onFilterUpdated($event)"
|
||||
[shouldFocus]="shouldFocus"
|
||||
[filter]="filter"></filter-boolean-value>
|
||||
|
||||
<filter-boolean-value
|
||||
*ngIf="valueType == '[1]Boolean'"
|
||||
(filterChanged)="onFilterUpdated($event)"
|
||||
[shouldFocus]="shouldFocus"
|
||||
[filter]="filter"
|
||||
></filter-boolean-value>
|
||||
</div>
|
||||
<!-- Values -->
|
||||
<ng-container *ngIf="showValuesInput && valueType">
|
||||
<div class="advanced-filters--filter-value" [ngSwitch]="valueType">
|
||||
<filter-integer-value *ngSwitchCase="'[1]Integer'"
|
||||
(filterChanged)="onFilterUpdated($event)"
|
||||
[shouldFocus]="shouldFocus"
|
||||
[filter]="filter"></filter-integer-value>
|
||||
<filter-integer-value
|
||||
*ngSwitchCase="'[1]Integer'"
|
||||
(filterChanged)="onFilterUpdated($event)"
|
||||
[shouldFocus]="shouldFocus"
|
||||
[filter]="filter"
|
||||
></filter-integer-value>
|
||||
|
||||
<filter-date-value *ngSwitchCase="'[1]Date'"
|
||||
(filterChanged)="onFilterUpdated($event)"
|
||||
[shouldFocus]="shouldFocus"
|
||||
[filter]="filter"></filter-date-value>
|
||||
<filter-date-value
|
||||
*ngSwitchCase="'[1]Date'"
|
||||
(filterChanged)="onFilterUpdated($event)"
|
||||
[shouldFocus]="shouldFocus"
|
||||
[filter]="filter"
|
||||
></filter-date-value>
|
||||
|
||||
<filter-dates-value *ngSwitchCase="'[2]Date'"
|
||||
(filterChanged)="onFilterUpdated($event)"
|
||||
[shouldFocus]="shouldFocus"
|
||||
[filter]="filter"></filter-dates-value>
|
||||
<filter-dates-value
|
||||
*ngSwitchCase="'[2]Date'"
|
||||
(filterChanged)="onFilterUpdated($event)"
|
||||
[shouldFocus]="shouldFocus"
|
||||
[filter]="filter"
|
||||
></filter-dates-value>
|
||||
|
||||
<filter-date-time-value *ngSwitchCase="'[1]DateTime'"
|
||||
(filterChanged)="onFilterUpdated($event)"
|
||||
[shouldFocus]="shouldFocus"
|
||||
[filter]="filter"></filter-date-time-value>
|
||||
<filter-date-time-value
|
||||
*ngSwitchCase="'[1]DateTime'"
|
||||
(filterChanged)="onFilterUpdated($event)"
|
||||
[shouldFocus]="shouldFocus"
|
||||
[filter]="filter"
|
||||
></filter-date-time-value>
|
||||
|
||||
<filter-date-times-value *ngSwitchCase="'[2]DateTime'"
|
||||
[shouldFocus]="shouldFocus"
|
||||
(filterChanged)="onFilterUpdated($event)"
|
||||
[filter]="filter"></filter-date-times-value>
|
||||
<filter-date-times-value
|
||||
*ngSwitchCase="'[2]DateTime'"
|
||||
[shouldFocus]="shouldFocus"
|
||||
(filterChanged)="onFilterUpdated($event)"
|
||||
[filter]="filter"
|
||||
></filter-date-times-value>
|
||||
|
||||
<filter-string-value *ngSwitchCase="'[1]String'"
|
||||
(filterChanged)="onFilterUpdated($event)"
|
||||
[shouldFocus]="shouldFocus"
|
||||
[filter]="filter"></filter-string-value>
|
||||
<filter-string-value
|
||||
*ngSwitchCase="'[1]String'"
|
||||
(filterChanged)="onFilterUpdated($event)"
|
||||
[shouldFocus]="shouldFocus"
|
||||
[filter]="filter"
|
||||
></filter-string-value>
|
||||
|
||||
<filter-string-value *ngSwitchCase="'[1]Float'"
|
||||
(filterChanged)="onFilterUpdated($event)"
|
||||
[shouldFocus]="shouldFocus"
|
||||
[filter]="filter"></filter-string-value>
|
||||
<filter-string-value
|
||||
*ngSwitchCase="'[1]Float'"
|
||||
(filterChanged)="onFilterUpdated($event)"
|
||||
[shouldFocus]="shouldFocus"
|
||||
[filter]="filter"
|
||||
></filter-string-value>
|
||||
|
||||
<filter-searchable-multiselect-value *ngSwitchCase="'[]WorkPackage'"
|
||||
(filterChanged)="onFilterUpdated($event)"
|
||||
[shouldFocus]="shouldFocus"
|
||||
[filter]="filter"></filter-searchable-multiselect-value>
|
||||
<filter-searchable-multiselect-value
|
||||
*ngSwitchCase="'[]WorkPackage'"
|
||||
(filterChanged)="onFilterUpdated($event)"
|
||||
[shouldFocus]="shouldFocus"
|
||||
[filter]="filter"
|
||||
></filter-searchable-multiselect-value>
|
||||
|
||||
<filter-toggled-multiselect-value *ngSwitchDefault
|
||||
(filterChanged)="onFilterUpdated($event)"
|
||||
[shouldFocus]="shouldFocus"
|
||||
[filter]="filter"></filter-toggled-multiselect-value>
|
||||
<filter-toggled-multiselect-value
|
||||
*ngSwitchDefault
|
||||
(filterChanged)="onFilterUpdated($event)"
|
||||
[shouldFocus]="shouldFocus"
|
||||
[filter]="filter"
|
||||
></filter-toggled-multiselect-value>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
@@ -89,9 +117,13 @@
|
||||
</div>
|
||||
|
||||
<div class="advanced-filters--remove-filter">
|
||||
<accessible-by-keyboard (execute)="removeThisFilter()">
|
||||
<op-icon icon-classes="icon-close advanced-filters--remove-filter-icon"
|
||||
[icon-title]="text.button_delete"></op-icon>
|
||||
</accessible-by-keyboard>
|
||||
<button
|
||||
type="button"
|
||||
class="op-link"
|
||||
[title]="text.button_delete"
|
||||
(click)="removeThisFilter()"
|
||||
>
|
||||
<op-icon icon-classes="icon-close advanced-filters--remove-filter-icon"></op-icon>
|
||||
</button>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
+30
-24
@@ -3,36 +3,42 @@
|
||||
|
||||
<ng-container *ngIf="!showAbove" [ngTemplateOutlet]="template"></ng-container>
|
||||
|
||||
<div class="work-package--details--long-field work-packages--activity--add-comment hide-when-print"
|
||||
#commentContainer
|
||||
*ngIf="canAddComment">
|
||||
<div
|
||||
class="work-package--details--long-field work-packages--activity--add-comment hide-when-print"
|
||||
#commentContainer
|
||||
*ngIf="canAddComment"
|
||||
>
|
||||
<div class="inline-edit--container inplace-edit">
|
||||
<edit-form-portal *ngIf="active"
|
||||
[schemaInput]="schema"
|
||||
[changeInput]="change"
|
||||
[editFieldHandler]="handler">
|
||||
<edit-form-portal
|
||||
*ngIf="active"
|
||||
[schemaInput]="schema"
|
||||
[changeInput]="change"
|
||||
[editFieldHandler]="handler"
|
||||
>
|
||||
</edit-form-portal>
|
||||
<div *ngIf="!active"
|
||||
(dragover)="startDragOverActivation($event)"
|
||||
class="inplace-edit--read">
|
||||
<accessible-by-keyboard
|
||||
class="inplace-editing--trigger-container"
|
||||
spanClass="inplace-editing--container inline-edit--display-field"
|
||||
linkClass="inplace-editing--trigger-link"
|
||||
[linkAriaLabel]="text.editTitle"
|
||||
(execute)="activate()">
|
||||
|
||||
<span class="inplace-editing--trigger-text inline-edit--formattable-display-text -default">
|
||||
<span [textContent]="text.placeholder"></span>
|
||||
<div
|
||||
*ngIf="!active"
|
||||
(dragover)="startDragOverActivation($event)"
|
||||
>
|
||||
<button
|
||||
class="work-package-comment inline-edit--display-field"
|
||||
[title]="text.editTitle"
|
||||
(click)="activate()"
|
||||
>
|
||||
<span
|
||||
class="work-package-comment--text"
|
||||
[textContent]="text.placeholder"
|
||||
>
|
||||
</span>
|
||||
|
||||
<span class="inplace-editing--trigger-icon">
|
||||
<op-icon icon-classes="icon-edit" [icon-title]="text.editTitle"></op-icon>
|
||||
</span>
|
||||
</accessible-by-keyboard>
|
||||
<op-icon
|
||||
class="work-package-comment--icon"
|
||||
icon-classes="icon-edit" [icon-title]="text.editTitle"
|
||||
></op-icon>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ng-container *ngIf="showAbove" [ngTemplateOutlet]="template"></ng-container>
|
||||
</div>
|
||||
|
||||
@@ -6,19 +6,25 @@
|
||||
class="wp-breadcrumb-parent breadcrumb-project-title nocut">
|
||||
<span [textContent]="parent.name"></span>
|
||||
</a>
|
||||
<accessible-by-keyboard
|
||||
(execute)="open()"
|
||||
*ngIf="canModifyParent()"
|
||||
[linkTitle]="parent ? text.edit_parent : text.set_parent"
|
||||
linkClass="wp-relation--parent-change -no-decoration hide-when-print icon-small {{ parent ? 'icon-edit icon5' : 'icon-add icon4' }}">
|
||||
<button
|
||||
*ngIf="canModifyParent()"
|
||||
type="button"
|
||||
class="op-link wp-relation--parent-change -no-decoration hide-when-print"
|
||||
[title]="parent ? text.edit_parent : text.set_parent"
|
||||
(click)="open()"
|
||||
>
|
||||
<span *ngIf="!parent" [textContent]="text.set_parent"></span>
|
||||
</accessible-by-keyboard>
|
||||
<accessible-by-keyboard
|
||||
(execute)="updateParent(null)"
|
||||
<op-icon icon-classes="icon-small {{ parent ? 'icon-edit icon5 icon-no-color' : 'icon-add icon4' }}"></op-icon>
|
||||
</button>
|
||||
<button
|
||||
*ngIf="canModifyParent() && parent"
|
||||
[linkTitle]="text.remove_parent"
|
||||
linkClass="wp-relation--parent-remove hide-when-print -no-decoration icon-small icon-remove icon4">
|
||||
</accessible-by-keyboard>
|
||||
type="button"
|
||||
class="op-link wp-relation--parent-remove hide-when-print -no-decoration"
|
||||
[title]="text.remove_parent"
|
||||
(click)="updateParent(null)"
|
||||
>
|
||||
<op-icon icon-classes="icon-small icon-no-color icon-remove icon4"></op-icon>
|
||||
</button>
|
||||
</ng-container>
|
||||
|
||||
<wp-relations-autocomplete
|
||||
|
||||
@@ -25,17 +25,25 @@
|
||||
</activity-link>
|
||||
<div class="comments-icons"
|
||||
[hidden]="shouldHideIcons()">
|
||||
<accessible-by-keyboard *ngIf="userCanQuote"
|
||||
(execute)="quoteComment()"
|
||||
[linkTitle]="text.quote_comment">
|
||||
<op-icon icon-classes="action-icon icon-quote" [icon-title]="text.quote_comment"></op-icon>
|
||||
</accessible-by-keyboard>
|
||||
<accessible-by-keyboard *ngIf="userCanEdit"
|
||||
(execute)="activate()"
|
||||
[linkTitle]="text.edit_comment"
|
||||
class="edit-activity--{{activityNo}}">
|
||||
<op-icon icon-classes="action-icon icon-edit" [icon-title]="text.edit_comment"></op-icon>
|
||||
</accessible-by-keyboard>
|
||||
<button
|
||||
type="button"
|
||||
*ngIf="userCanQuote"
|
||||
class="op-link"
|
||||
[title]="text.quote_comment"
|
||||
(click)="quoteComment()"
|
||||
>
|
||||
<op-icon icon-classes="action-icon icon-quote"></op-icon>
|
||||
</button>
|
||||
<button
|
||||
*ngIf="userCanEdit"
|
||||
type="button"
|
||||
class="op-link"
|
||||
[ngClass]="'edit-activity--' + activityNo"
|
||||
[title]="text.edit_comment"
|
||||
(click)="activate()"
|
||||
>
|
||||
<op-icon icon-classes="action-icon icon-edit"></op-icon>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<img *ngIf="bcfSnapshotUrl"
|
||||
|
||||
+7
-6
@@ -11,12 +11,13 @@
|
||||
</wp-relations-autocomplete>
|
||||
</div>
|
||||
<div class="wp-relations-controls-section relation-row">
|
||||
<accessible-by-keyboard
|
||||
linkClass="wp-create-relation--cancel"
|
||||
(execute)="cancel()"
|
||||
aria-hidden="false">
|
||||
<op-icon icon-classes="icon-remove" [icon-title]="text.abort"></op-icon>
|
||||
</accessible-by-keyboard>
|
||||
<button
|
||||
type="button"
|
||||
class="op-link wp-create-relation--cancel"
|
||||
(click)="cancel()"
|
||||
>
|
||||
<op-icon icon-classes="icon-remove icon-no-color" [icon-title]="text.abort"></op-icon>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+21
-17
@@ -1,6 +1,6 @@
|
||||
<div *ngIf="workPackage && relatedWorkPackage"
|
||||
class="relation-row relation-row-{{ relatedWorkPackage.id }}"
|
||||
focus-within="'.wp-relations-controls-section accessible-by-keyboard'">
|
||||
focus-within="'.wp-relations-controls-section button'">
|
||||
<div class="grid-block hierarchy-item">
|
||||
|
||||
<div class="grid-content medium-2 collapse">
|
||||
@@ -49,22 +49,26 @@
|
||||
|
||||
<div class="grid-content medium-2 collapse wp-relations-controls-section"
|
||||
ng-class="{'-expanded': userInputs.showRelationInfo }">
|
||||
<accessible-by-keyboard [linkAriaLabel]="text.description_label"
|
||||
[linkTitle]="text.description_label"
|
||||
linkClass="wp-relations--description-btn"
|
||||
[ngClass]="{'-visible': showDescriptionInfo }"
|
||||
(execute)="userInputs.showRelationInfo = !userInputs.showRelationInfo">
|
||||
<op-icon icon-classes="icon-info1 -padded wp-relations--icon wp-relations--description-icon"
|
||||
icon-title="text.toggleDescription"></op-icon>
|
||||
</accessible-by-keyboard>
|
||||
<accessible-by-keyboard *ngIf="relation.delete"
|
||||
(execute)="removeRelation(relation)"
|
||||
aria-hidden="false"
|
||||
[linkAriaLabel]="text.removeButton"
|
||||
[linkTitle]="text.removeButton"
|
||||
linkClass="relation-row--remove-btn">
|
||||
<op-icon icon-classes="icon-remove -padded wp-relations--icon" [icon-title]="text.removeButton"></op-icon>
|
||||
</accessible-by-keyboard>
|
||||
<button
|
||||
type="button"
|
||||
class="op-link wp-relations--description-btn"
|
||||
[ngClass]="{'-visible': showDescriptionInfo }"
|
||||
[title]="text.description_label"
|
||||
(click)="userInputs.showRelationInfo = !userInputs.showRelationInfo"
|
||||
>
|
||||
<op-icon icon-classes="icon-info1 icon-no-color -padded wp-relations--icon wp-relations--description-icon"
|
||||
[icon-title]="text.toggleDescription"></op-icon>
|
||||
</button>
|
||||
<button
|
||||
*ngIf="!!relation.delete"
|
||||
type="button"
|
||||
class="op-link relation-row--remove-btn"
|
||||
[ngClass]="{'-visible': showDescriptionInfo }"
|
||||
[title]="text.removeButton"
|
||||
(click)="removeRelation()"
|
||||
>
|
||||
<op-icon icon-classes="icon-remove icon-no-color -padded wp-relations--icon" [icon-title]="text.removeButton"></op-icon>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
+7
-6
@@ -42,12 +42,13 @@
|
||||
</wp-relations-autocomplete>
|
||||
</div>
|
||||
<div class="grid-content medium-1 collapse wp-relations-controls-section relation-row">
|
||||
<accessible-by-keyboard
|
||||
(execute)="toggleRelationsCreateForm()"
|
||||
linkClass="wp-create-relation--cancel"
|
||||
aria-hidden="false">
|
||||
<op-icon icon-classes="icon-remove -padded" [icon-title]="text.abort"></op-icon>
|
||||
</accessible-by-keyboard>
|
||||
<button
|
||||
type="button"
|
||||
class="op-link wp-create-relation--cancel"
|
||||
(click)="toggleRelationsCreateForm()"
|
||||
>
|
||||
<op-icon icon-classes="icon-remove icon-no-color -padded" [icon-title]="text.abort"></op-icon>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+8
-6
@@ -7,13 +7,15 @@
|
||||
</div>
|
||||
<div class="attributes-group--header-toggle"
|
||||
*ngIf="firstGroup">
|
||||
<accessible-by-keyboard id="wp-relation-group-by-toggle"
|
||||
#wpRelationGroupByToggler
|
||||
(execute)="toggleButton()"
|
||||
linkClass="button -small -transparent -with-icon icon-group-by icon-small hide-when-print"
|
||||
[linkAriaLabel]="togglerText">
|
||||
<button
|
||||
#wpRelationGroupByToggler
|
||||
id="wp-relation-group-by-toggle"
|
||||
type="button"
|
||||
class="button -small -transparent -with-icon icon-group-by icon-small hide-when-print"
|
||||
(click)="toggleButton()"
|
||||
>
|
||||
<span [textContent]="togglerText"></span>
|
||||
</accessible-by-keyboard>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -8,12 +8,14 @@
|
||||
[ngClass]="{'-with-toggler': first && showToggler}"
|
||||
*ngIf="first || inf.isNextDate">
|
||||
<span class="activity-date--label" [textContent]="inf.date"></span>
|
||||
<accessible-by-keyboard (execute)="toggleComments()"
|
||||
*ngIf="first && showToggler"
|
||||
linkClass="activity-comments--toggler button -small -transparent -with-icon icon-filter icon-small hide-when-print"
|
||||
[linkAriaLabel]="togglerText">
|
||||
<span [textContent]="togglerText"></span>
|
||||
</accessible-by-keyboard>
|
||||
<button
|
||||
*ngIf="first && showToggler"
|
||||
type="button"
|
||||
class="activity-comments--toggler button -small -transparent -with-icon icon-filter icon-small hide-when-print"
|
||||
[textContent]="togglerText"
|
||||
(click)="toggleComments()"
|
||||
>
|
||||
</button>
|
||||
</h3>
|
||||
|
||||
<activity-entry [workPackage]="workPackage"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<div class="form--selected-value--container" focus-within="accessible-by-keyboard">
|
||||
<div class="form--selected-value--container" focus-within="button">
|
||||
<span class="form--selected-value">
|
||||
<span [ngClass]="{'deleting': deleting }">
|
||||
<a [attr.href]="watcher.showUser.href">
|
||||
@@ -13,13 +13,14 @@
|
||||
</a>
|
||||
</span>
|
||||
</span>
|
||||
<accessible-by-keyboard (execute)="remove()"
|
||||
[linkAriaLabel]="text.remove"
|
||||
[linkTitle]="text.remove"
|
||||
linkClass="form--selected-value--remover">
|
||||
<op-icon icon-classes="icon-remove"
|
||||
[icon-title]="text.remove"></op-icon>
|
||||
</accessible-by-keyboard>
|
||||
<button
|
||||
type="button"
|
||||
class="op-link form--selected-value--remover"
|
||||
[title]="text.remove"
|
||||
(click)="remove()"
|
||||
>
|
||||
<op-icon icon-classes="icon-remove"></op-icon>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<accessible-by-keyboard
|
||||
(execute)="openTableConfigurationModal()"
|
||||
linkClass="wp-table--columns-selection"
|
||||
[linkTitle]="text.configureTable"
|
||||
[linkAriaLabel]="text.configureTable">
|
||||
<op-icon icon-classes="icon-button icon-small icon-settings"></op-icon>
|
||||
</accessible-by-keyboard>
|
||||
<button
|
||||
type="button"
|
||||
class="op-link wp-table--columns-selection"
|
||||
[title]="text.configureTable"
|
||||
(click)="openTableConfigurationModal()"
|
||||
>
|
||||
<op-icon icon-classes="icon-button icon-small icon-no-color icon-settings"></op-icon>
|
||||
</button>
|
||||
|
||||
@@ -3,24 +3,24 @@
|
||||
class="op-work-package-tabs">
|
||||
<ng-container slot="actions" *ngIf="view === 'split'">
|
||||
<li class="tab-icon">
|
||||
<accessible-by-keyboard
|
||||
(execute)="switchToFullscreen()"
|
||||
[linkAriaLabel]="text.details.goToFullScreen"
|
||||
[linkTitle]="text.details.goToFullScreen"
|
||||
linkClass="work-packages--details-fullscreen-icon"
|
||||
spanClass="icon-context icon-to-fullscreen"
|
||||
<button
|
||||
type="button"
|
||||
class="op-link work-packages--details-fullscreen-icon"
|
||||
[title]="text.details.goToFullScreen"
|
||||
(click)="switchToFullscreen()"
|
||||
>
|
||||
</accessible-by-keyboard>
|
||||
<op-icon icon-classes="icon-context icon-no-color icon-to-fullscreen"></op-icon>
|
||||
</button>
|
||||
</li>
|
||||
<li class="tab-icon">
|
||||
<accessible-by-keyboard
|
||||
(execute)="close()"
|
||||
[linkAriaLabel]="text.details.close"
|
||||
[linkTitle]="text.details.close"
|
||||
linkClass="work-packages--details-close-icon"
|
||||
spanClass="icon-context icon-close"
|
||||
<button
|
||||
type="button"
|
||||
class="op-link work-packages--details-close-icon"
|
||||
[title]="text.details.close"
|
||||
(click)="close()"
|
||||
>
|
||||
</accessible-by-keyboard>
|
||||
<op-icon icon-classes="icon-context icon-no-color icon-close"></op-icon>
|
||||
</button>
|
||||
</li>
|
||||
</ng-container>
|
||||
</op-scrollable-tabs>
|
||||
|
||||
@@ -6,6 +6,7 @@ import { WpTabWrapperComponent } from "core-components/wp-tabs/components/wp-tab
|
||||
import { DynamicModule } from "ng-dynamic-component";
|
||||
import { OpenprojectAccessibilityModule } from "core-app/modules/a11y/openproject-a11y.module";
|
||||
import { OpenprojectTabsModule } from "core-app/modules/common/tabs/openproject-tabs.module";
|
||||
import { IconModule } from "core-app/modules/icon/icon.module";
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
@@ -17,7 +18,8 @@ import { OpenprojectTabsModule } from "core-app/modules/common/tabs/openproject-
|
||||
UIRouterModule,
|
||||
DynamicModule,
|
||||
OpenprojectAccessibilityModule,
|
||||
OpenprojectTabsModule
|
||||
OpenprojectTabsModule,
|
||||
IconModule
|
||||
],
|
||||
exports: [
|
||||
WpTabsComponent,
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
//-- copyright
|
||||
// OpenProject is an open source project management software.
|
||||
// Copyright (C) 2012-2021 the OpenProject GmbH
|
||||
//
|
||||
// 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 docs/COPYRIGHT.rdoc for more details.
|
||||
//++
|
||||
|
||||
import { AccessibleByKeyboardComponent } from "core-app/modules/a11y/accessible-by-keyboard.component";
|
||||
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
|
||||
describe('accessibleByKeyboard component', () => {
|
||||
beforeEach((() => {
|
||||
|
||||
// noinspection JSIgnoredPromiseFromCall
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [
|
||||
AccessibleByKeyboardComponent
|
||||
]
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
describe('inner element', function() {
|
||||
let app:AccessibleByKeyboardComponent;
|
||||
let fixture:ComponentFixture<AccessibleByKeyboardComponent>;
|
||||
let element:HTMLElement;
|
||||
|
||||
it('should render an inner link with specified classes', function() {
|
||||
fixture = TestBed.createComponent(AccessibleByKeyboardComponent);
|
||||
app = fixture.debugElement.componentInstance;
|
||||
element = fixture.elementRef.nativeElement;
|
||||
|
||||
app.linkClass = 'a-link-class';
|
||||
app.spanClass = 'a-span-class';
|
||||
fixture.detectChanges();
|
||||
|
||||
const link = element.querySelector('a')!;
|
||||
const span = element.querySelector('a > span')!;
|
||||
expect(link.classList.contains('a-link-class')).toBeTruthy();
|
||||
expect(span.classList.contains('a-span-class')).toBeTruthy();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
//-- copyright
|
||||
// OpenProject is an open source project management software.
|
||||
// Copyright (C) 2012-2021 the OpenProject GmbH
|
||||
//
|
||||
// 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 docs/COPYRIGHT.rdoc for more details.
|
||||
//++
|
||||
|
||||
import { Component, EventEmitter, Input, Output } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'accessible-by-keyboard',
|
||||
template: `
|
||||
<a (accessibleClick)="handleClick($event)"
|
||||
role="link"
|
||||
[ngClass]="linkClass"
|
||||
[attr.disabled]="isDisabled || undefined"
|
||||
[attr.title]="linkTitle"
|
||||
[attr.aria-label]="linkAriaLabel"
|
||||
href>
|
||||
<span [ngClass]="spanClass">
|
||||
<ng-content></ng-content>
|
||||
</span>
|
||||
</a>
|
||||
`
|
||||
})
|
||||
export class AccessibleByKeyboardComponent {
|
||||
@Output() execute = new EventEmitter<JQuery.TriggeredEvent>();
|
||||
@Input() isDisabled:boolean;
|
||||
@Input() linkClass:string;
|
||||
@Input() linkTitle:string;
|
||||
@Input() spanClass:string;
|
||||
@Input() linkAriaLabel:string;
|
||||
|
||||
public handleClick(event:JQuery.TriggeredEvent) {
|
||||
if (!this.isDisabled) {
|
||||
this.execute.emit(event);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -31,7 +31,6 @@ import { NgModule } from "@angular/core";
|
||||
import { FormsModule } from "@angular/forms";
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { AccessibleClickDirective } from "core-app/modules/a11y/accessible-click.directive";
|
||||
import { AccessibleByKeyboardComponent } from "core-app/modules/a11y/accessible-by-keyboard.component";
|
||||
import { DoubleClickOrTapDirective } from "core-app/modules/a11y/double-click-or-tap.directive";
|
||||
|
||||
@NgModule({
|
||||
@@ -42,12 +41,10 @@ import { DoubleClickOrTapDirective } from "core-app/modules/a11y/double-click-or
|
||||
exports: [
|
||||
AccessibleClickDirective,
|
||||
DoubleClickOrTapDirective,
|
||||
AccessibleByKeyboardComponent,
|
||||
],
|
||||
declarations: [
|
||||
AccessibleClickDirective,
|
||||
DoubleClickOrTapDirective,
|
||||
AccessibleByKeyboardComponent,
|
||||
]
|
||||
})
|
||||
export class OpenprojectAccessibilityModule {
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
<accessible-by-keyboard (execute)="handleClick()"
|
||||
*ngIf="exists"
|
||||
[linkAriaLabel]="text.open_dialog"
|
||||
linkClass="icon help-text--entry help-text--for-{{ attribute }}">
|
||||
<op-icon icon-classes="icon icon-help1"></op-icon>
|
||||
{{ additionalLabel }}
|
||||
</accessible-by-keyboard>
|
||||
<button
|
||||
type="button"
|
||||
*ngIf="exists"
|
||||
class="op-link help-text--entry"
|
||||
[attr.data-qa-help-text-for]="attribute"
|
||||
[title]="text.open_dialog"
|
||||
(click)="handleClick($event)"
|
||||
>
|
||||
<span *ngIf="additionalLabel" [textContent]="additionalLabel"></span>
|
||||
<op-icon icon-classes="icon icon-no-color icon-help1"></op-icon>
|
||||
</button>
|
||||
|
||||
@@ -93,10 +93,12 @@ export class AttributeHelpTextComponent implements OnInit {
|
||||
}
|
||||
}
|
||||
|
||||
public handleClick() {
|
||||
public handleClick(event:Event):void {
|
||||
this.load().then((resource) => {
|
||||
this.opModalService.show(AttributeHelpTextModal, this.injector, { helpText: resource });
|
||||
});
|
||||
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
private load() {
|
||||
|
||||
@@ -91,11 +91,13 @@
|
||||
<op-date-time [dateTimeValue]="board.createdAt"></op-date-time>
|
||||
</td>
|
||||
<td class="buttons">
|
||||
<accessible-by-keyboard *ngIf="board.editable"
|
||||
(execute)="destroyBoard(board)"
|
||||
linkClass="icon icon-delete">
|
||||
{{ text.delete }}
|
||||
</accessible-by-keyboard>
|
||||
<button class="op-link"
|
||||
type="button"
|
||||
(click)="destroyBoard(board)"
|
||||
>
|
||||
<op-icon icon-classes="icon icon-delete"></op-icon>
|
||||
<span [textContent]="text.delete"></span>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
<button
|
||||
class="op-back-button button hide-when-print"
|
||||
[ngClass]="linkClass"
|
||||
[title]="text.goBack"
|
||||
(click)="goBack()"
|
||||
>
|
||||
<op-icon icon-classes="op-back-button--icon button--icon icon-back-up"></op-icon>
|
||||
</button>
|
||||
@@ -0,0 +1,18 @@
|
||||
.op-back-button
|
||||
padding: 0
|
||||
line-height: 32px
|
||||
margin: 0 1rem 0 0
|
||||
width: 75px
|
||||
height: 34px
|
||||
|
||||
&--icon
|
||||
font-size: 1rem
|
||||
line-height: 22px
|
||||
|
||||
@media only screen and (max-width: 679px)
|
||||
// Move button in the current reverse order to front
|
||||
position: absolute
|
||||
top: 0
|
||||
left: 0
|
||||
z-index: 1
|
||||
width: 100%
|
||||
@@ -31,22 +31,14 @@ import { BackRoutingService } from "core-app/modules/common/back-routing/back-ro
|
||||
import { I18nService } from "core-app/modules/common/i18n/i18n.service";
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<div class="wp-show--back-button hide-when-print">
|
||||
<accessible-by-keyboard (execute)="goBack()"
|
||||
[linkClass]="classes()"
|
||||
[linkAriaLabel]="text.goBack"
|
||||
[linkTitle]="text.goBack">
|
||||
<op-icon icon-classes="button--icon icon-back-up"></op-icon>
|
||||
</accessible-by-keyboard>
|
||||
</div>
|
||||
`,
|
||||
templateUrl: './back-button.component.html',
|
||||
styleUrls: ['./back-button.component.sass'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
selector: 'back-button',
|
||||
selector: 'op-back-button',
|
||||
})
|
||||
export class BackButtonComponent {
|
||||
@Input() public linkClass:string;
|
||||
@Input() public customBackMethod:Function;
|
||||
@Input() public customBackMethod:() => unknown;
|
||||
|
||||
public text = {
|
||||
goBack: this.I18n.t('js.button_back')
|
||||
@@ -56,18 +48,11 @@ export class BackButtonComponent {
|
||||
readonly I18n:I18nService) {
|
||||
}
|
||||
|
||||
public goBack() {
|
||||
public goBack():void {
|
||||
if (this.customBackMethod) {
|
||||
this.customBackMethod();
|
||||
} else {
|
||||
this.backRoutingService.goBack();
|
||||
}
|
||||
}
|
||||
|
||||
public classes():string {
|
||||
let classes = 'button ';
|
||||
classes += this.linkClass ? this.linkClass : '';
|
||||
|
||||
return classes;
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -57,7 +57,8 @@ export class CollapsibleSectionComponent implements OnInit {
|
||||
target.removeAttribute('hidden');
|
||||
}
|
||||
|
||||
public toggle() {
|
||||
public toggle(event:Event) {
|
||||
this.expanded = !this.expanded;
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
<section class="collapsible-section"
|
||||
[ngClass]="{ '-expanded': expanded }">
|
||||
|
||||
<accessible-by-keyboard (execute)="toggle()"
|
||||
[linkAriaLabel]="sectionTitle"
|
||||
linkClass="collapsible-section--toggle-link"
|
||||
spanClass="collapsible-section--legend">
|
||||
<span [textContent]="sectionTitle"></span>
|
||||
</accessible-by-keyboard>
|
||||
<button
|
||||
class="collapsible-section--toggle-link"
|
||||
(click)="toggle($event)"
|
||||
>
|
||||
<span class="collapsible-section--legend"
|
||||
[textContent]="sectionTitle">
|
||||
</span>
|
||||
</button>
|
||||
<div class="collapsible-section--body toggle-slide-animation"
|
||||
#sectionBody
|
||||
[hidden]="!expanded">
|
||||
|
||||
+8
-6
@@ -1,6 +1,8 @@
|
||||
<accessible-by-keyboard
|
||||
(execute)="hideSection()"
|
||||
linkTitle="Remove"
|
||||
linkAriaLabel="Remove">
|
||||
<op-icon icon-classes="icon-close icon-small" icon-title="Remove"></op-icon>
|
||||
</accessible-by-keyboard>
|
||||
<button
|
||||
type="button"
|
||||
class="op-link"
|
||||
[title]="text.remove"
|
||||
(click)="hideSection()"
|
||||
>
|
||||
<op-icon icon-classes="icon-close icon-small"></op-icon>
|
||||
</button>
|
||||
+7
-1
@@ -28,6 +28,7 @@
|
||||
|
||||
import { Component, ElementRef, OnInit } from "@angular/core";
|
||||
import { HideSectionService } from "core-app/modules/common/hide-section/hide-section.service";
|
||||
import { I18nService } from "core-app/modules/common/i18n/i18n.service";
|
||||
|
||||
export const hideSectionLinkSelector = 'hide-section-link';
|
||||
|
||||
@@ -40,8 +41,13 @@ export class HideSectionLinkComponent implements OnInit {
|
||||
|
||||
public sectionName:string;
|
||||
|
||||
text = {
|
||||
remove: this.I18n.t('js.label_remove')
|
||||
}
|
||||
|
||||
constructor(protected elementRef:ElementRef,
|
||||
protected hideSectionService:HideSectionService) {}
|
||||
protected hideSectionService:HideSectionService,
|
||||
protected I18n:I18nService) {}
|
||||
|
||||
ngOnInit():void {
|
||||
this.sectionName = this.elementRef.nativeElement.dataset.sectionName;
|
||||
|
||||
@@ -16,4 +16,8 @@
|
||||
|
||||
&:focus
|
||||
color: var(--content-link-hover-active-color)
|
||||
|
||||
|
||||
// Ensure op-icon within a link gets inline-block
|
||||
// to avoid it receiving any text-decoration on hover
|
||||
.op-icon--wrapper
|
||||
display: inline-block
|
||||
|
||||
@@ -39,9 +39,13 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<accessible-by-keyboard *ngIf="removable()"
|
||||
(execute)="remove()"
|
||||
[linkTitle]="text.close_popup"
|
||||
linkClass="notification-box--close icon-context icon-close">
|
||||
</accessible-by-keyboard>
|
||||
<button
|
||||
*ngIf="removable()"
|
||||
type="button"
|
||||
class="op-link notification-box--close"
|
||||
[title]="text.close_popup"
|
||||
(click)="remove()"
|
||||
>
|
||||
<op-icon icon-classes="icon-context icon-close icon-no-color"></op-icon>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
+17
-11
@@ -1,15 +1,21 @@
|
||||
<div class="inplace-edit--dashboard">
|
||||
<div class="inplace-edit--controls">
|
||||
<accessible-by-keyboard (execute)="save()"
|
||||
[attr.disabled]="(field.required && field.isEmpty()) || undefined"
|
||||
[linkTitle]="saveTitle"
|
||||
class="inplace-edit--control inplace-edit--control--save">
|
||||
<op-icon icon-classes="icon-checkmark" [icon-title]="saveTitle"></op-icon>
|
||||
</accessible-by-keyboard>
|
||||
<accessible-by-keyboard (execute)="cancel()"
|
||||
[linkTitle]="cancelTitle"
|
||||
class="inplace-edit--control inplace-edit--control--cancel">
|
||||
<op-icon icon-classes="icon-close" [icon-title]="cancelTitle"></op-icon>
|
||||
</accessible-by-keyboard>
|
||||
<button
|
||||
type="button"
|
||||
class="inplace-edit--control inplace-edit--control--save"
|
||||
[disabled]="(field.required && field.isEmpty()) || undefined"
|
||||
[title]="saveTitle"
|
||||
(click)="save()"
|
||||
>
|
||||
<op-icon icon-classes="icon-checkmark icon-no-color" [icon-title]="saveTitle"></op-icon>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="inplace-edit--control inplace-edit--control--cancel"
|
||||
[title]="cancelTitle"
|
||||
(click)="cancel()"
|
||||
>
|
||||
<op-icon icon-classes="icon-close icon-no-color" [icon-title]="cancelTitle"></op-icon>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
>
|
||||
<b>{{ text.inviteUser }}</b> {{ principal.name }}
|
||||
<button
|
||||
type="button"
|
||||
class="op-link"
|
||||
(click)="principalControl?.setValue(null)"
|
||||
>{{ text.changeUserSelection }}</button>
|
||||
@@ -35,6 +36,7 @@
|
||||
>
|
||||
<b>{{ text.createNewPlaceholder }}</b> {{ principal.name }}
|
||||
<button
|
||||
type="button"
|
||||
class="op-link"
|
||||
(click)="principalControl?.setValue(null)"
|
||||
>{{ text.changePlaceholderSelection }}</button>
|
||||
|
||||
+2
-2
@@ -2,10 +2,10 @@
|
||||
[ngClass]="currentPartition">
|
||||
<div class="toolbar-container -editable">
|
||||
<div class="toolbar">
|
||||
<back-button *ngIf="backButtonCallback"
|
||||
<op-back-button *ngIf="backButtonCallback"
|
||||
linkClass="back-button"
|
||||
[customBackMethod]="backButtonCallback">
|
||||
</back-button>
|
||||
</op-back-button>
|
||||
|
||||
<editable-toolbar-title [title]="selectedTitle"
|
||||
[inFlight]="toolbarDisabled"
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<div id="toolbar">
|
||||
<div class="wp-show--header-container">
|
||||
|
||||
<back-button linkClass="work-packages-back-button"></back-button>
|
||||
<op-back-button linkClass="work-packages-back-button"></op-back-button>
|
||||
|
||||
<div class="subject-header">
|
||||
<wp-subject></wp-subject>
|
||||
|
||||
@@ -10,10 +10,10 @@
|
||||
|
||||
<edit-form [resource]="workPackage">
|
||||
<div class="wp-show--header-container">
|
||||
<back-button *ngIf="showBackButton()"
|
||||
<op-back-button *ngIf="showBackButton()"
|
||||
linkClass="work-packages-back-button"
|
||||
[customBackMethod]="backToList.bind(this)">
|
||||
</back-button>
|
||||
</op-back-button>
|
||||
|
||||
<div class="subject-header">
|
||||
<wp-subject></wp-subject>
|
||||
|
||||
@@ -13,6 +13,10 @@
|
||||
|
||||
.collapsible-section--toggle-link
|
||||
@include without-link-styling
|
||||
background: none
|
||||
border: none
|
||||
width: 100%
|
||||
text-align: left
|
||||
display: block
|
||||
cursor: pointer
|
||||
color: var(--body-font-color)
|
||||
|
||||
@@ -17,201 +17,3 @@
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
|
||||
## In place editing: Attributes
|
||||
|
||||
```
|
||||
<div class="attributes-group">
|
||||
<div class="attributes-group--header">
|
||||
<div class="attributes-group--header-container">
|
||||
<h3 class="attributes-group--header-text">
|
||||
Details
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
<div class="attributes-key-value">
|
||||
<div class="attributes-key-value--key">
|
||||
Custom Field
|
||||
</div>
|
||||
<div class="attributes-key-value--value-container">
|
||||
<editable-attribute-field>
|
||||
<div class="inline-edit--container -small">
|
||||
<span title="Editable text field" class="inline-edit--display-field inplace-edit -editable">
|
||||
Editable text field
|
||||
</span>
|
||||
</div>
|
||||
</editable-attribute-field>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
## In place editing: Single line of text
|
||||
|
||||
### Read mode
|
||||
|
||||
```
|
||||
<div class="work-package--details--long-field work-packages--activity--add-comment">
|
||||
<div class="inplace-edit--read">
|
||||
<accessible-by-keyboard class="inplace-editing--trigger-container">
|
||||
<a href="" class="inplace-editing--trigger-link">
|
||||
<span class="inplace-editing--container inline-edit--display-field">
|
||||
<span>Comment and type @ to notify other people</span>
|
||||
<span class="inplace-editing--trigger-icon">
|
||||
<op-icon class="op-icon--wrapper">
|
||||
<i aria-hidden="true" class="icon-edit" title="Comment and type @ to notify other people"></i>
|
||||
</op-icon>
|
||||
</span>
|
||||
</span>
|
||||
</a>
|
||||
</accessible-by-keyboard>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
|
||||
## In place editing: Multiple lines of text
|
||||
|
||||
### Read mode
|
||||
|
||||
```
|
||||
<div class="attributes-group">
|
||||
<div class="attributes-group--header">
|
||||
<div class="attributes-group--header-container">
|
||||
<div class="attributes-group--header-container">
|
||||
<h3 class="attributes-group--header-text">Description</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<editable-attribute-field>
|
||||
<div class="inline-edit--container description -no-label">
|
||||
<div>
|
||||
<span class="inline-edit--display-field inplace-edit -editable">
|
||||
<div class="read-value--html wiki highlight -multiline">
|
||||
<p>
|
||||
Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor.
|
||||
Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.
|
||||
Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim
|
||||
</p>
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</editable-attribute-field>
|
||||
</div>
|
||||
```
|
||||
|
||||
### Edit mode
|
||||
|
||||
```
|
||||
<div class="attributes-group ">
|
||||
|
||||
<div class="attributes-group--header">
|
||||
<div class="attributes-group--header-container">
|
||||
<h3 class="attributes-group--header-text ">
|
||||
Description
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="single-attribute wiki">
|
||||
<span inplace-editor="" placeholder="Click to enter description..." autocomplete-path="/work_packages/auto_complete.json?project_id=undefined" >
|
||||
<div class="inplace-edit type-wiki_textarea attribute-description textarea-wrapper" aria-busy="false" >
|
||||
|
||||
<div class="inplace-edit--write " >
|
||||
<form class="inplace-edit--form " name="editForm" novalidate="">
|
||||
<div class="inplace-edit--write-value">
|
||||
<div class="jstElements">
|
||||
<button type="button" class="jstb_strong" title="Strong">
|
||||
<span>Strong</span>
|
||||
</button>
|
||||
<button type="button" class="jstb_em" title="Italic">
|
||||
<span>Italic</span>
|
||||
</button>
|
||||
<button type="button" class="jstb_ins" title="Underline">
|
||||
<span>Underline</span>
|
||||
</button>
|
||||
<button type="button" class="jstb_del" title="Deleted">
|
||||
<span>Deleted</span></button><button type="button" class="jstb_code" title="Inline Code">
|
||||
<span>Inline Code</span></button><span id="space1" class="jstSpacer"> </span>
|
||||
<button type="button" class="jstb_h1" title="Heading 1">
|
||||
<span>Heading 1</span>
|
||||
</button>
|
||||
<button type="button" class="jstb_h2" title="Heading 2">
|
||||
<span>Heading 2</span>
|
||||
</button>
|
||||
<button type="button" class="jstb_h3" title="Heading 3">
|
||||
<span>Heading 3</span>
|
||||
</button>
|
||||
<span id="space2" class="jstSpacer"> </span>
|
||||
<button type="button" class="jstb_ul" title="Unordered List">
|
||||
<span>Unordered List</span>
|
||||
</button>
|
||||
<button type="button" class="jstb_ol" title="Ordered List">
|
||||
<span>Ordered List</span>
|
||||
</button>
|
||||
<span id="space3" class="jstSpacer"> </span>
|
||||
<button type="button" class="jstb_bq" title="Quote">
|
||||
<span>Quote</span></button>
|
||||
<button type="button" class="jstb_unbq" title="Unquote">
|
||||
<span>Unquote</span>
|
||||
</button>
|
||||
<button type="button" class="jstb_pre" title="Preformatted Text">
|
||||
<span>Preformatted Text</span></button><span id="space4" class="jstSpacer"> </span>
|
||||
<button type="button" class="jstb_link" title="Link to a Wiki page">
|
||||
<span>Link to a Wiki page</span>
|
||||
</button>
|
||||
<button type="button" class="jstb_img" title="Image">
|
||||
<span>Image</span>
|
||||
</button>
|
||||
<div class="jstb_help">
|
||||
<a href="/help/wiki_syntax" title="Text formatting" class="icon icon-help1">
|
||||
<span class="hidden-for-sighted">
|
||||
Text formatting
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
<button class="jstb_preview icon-preview" type="button" title="Preview">
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="jstEditor">
|
||||
<textarea wiki-toolbar="" class="focus-input inplace-edit--textarea " name="value" title="Description: Edit" aria-multiline="true" tabindex="0" aria-hidden="false" aria-disabled="false" aria-invalid="false" rows="5">
|
||||
</textarea>
|
||||
</div>
|
||||
|
||||
<div class="jstHandle"></div>
|
||||
</div>
|
||||
<div class="inplace-edit--dashboard">
|
||||
<div class="inplace-edit--controls">
|
||||
<accessible-by-keyboard class="inplace-edit--control inplace-edit--control--save">
|
||||
<a href="" role="link" title="Description: Save">
|
||||
<span>
|
||||
<op-icon class="op-icon--wrapper">
|
||||
<i class="icon-checkmark" title="Description: Save"></i>
|
||||
<span class="hidden-for-sighted">Description: Save</span>
|
||||
</op-icon>
|
||||
</span>
|
||||
</a>
|
||||
</accessible-by-keyboard>
|
||||
<accessible-by-keyboard class="inplace-edit--control inplace-edit--control--cancel">
|
||||
<a href="" role="link" title="Description: Cancel">
|
||||
<span>
|
||||
<op-icon class="op-icon--wrapper">
|
||||
<i class="icon-close" title="Description: Cancel"></i>
|
||||
<span class="hidden-for-sighted">Description: Cancel</span>
|
||||
</op-icon>
|
||||
</span>
|
||||
</a>
|
||||
</accessible-by-keyboard>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
```
|
||||
|
||||
@@ -47,11 +47,10 @@
|
||||
// WP single view styles
|
||||
@import work_packages/single_view/single_view
|
||||
@import work_packages/single_view/attachments
|
||||
@import work_packages/single_view/inplace_esque_fields
|
||||
@import work_packages/single_view/work_package_comment
|
||||
|
||||
// WP fullscreen specific styles
|
||||
@import work_packages/fullscreen/fullscreen
|
||||
@import work_packages/fullscreen/back_button
|
||||
|
||||
// Tabs
|
||||
@import work_packages/tabs/activities
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
.wp-show--back-button
|
||||
min-width: 90px
|
||||
height: 34px
|
||||
a
|
||||
padding: 0
|
||||
line-height: 32px
|
||||
margin: 0
|
||||
width: 75px
|
||||
height: 34px
|
||||
|
||||
.button--icon:before
|
||||
font-size: 1rem
|
||||
line-height: 22px
|
||||
|
||||
@media only screen and (max-width: 679px)
|
||||
.router--work-packages-base &
|
||||
// Move button in the current reverse order to front
|
||||
position: absolute
|
||||
top: 0
|
||||
left: 0
|
||||
z-index: 1
|
||||
|
||||
a
|
||||
width: 100%
|
||||
@@ -55,11 +55,12 @@
|
||||
text-align: center
|
||||
// Align controls via flex
|
||||
display: flex
|
||||
justify-content: space-evenly
|
||||
align-items: center
|
||||
float: right
|
||||
|
||||
// Disabled submit styles when not applicable
|
||||
.inplace-edit--control--save[disabled] a,
|
||||
.inplace-edit--control--send[disabled] a
|
||||
.inplace-edit--control[disabled]
|
||||
background-color: var(--inplace-edit--bg-color--disabled)
|
||||
color: var(--inplace-edit--color--disabled)
|
||||
cursor: not-allowed
|
||||
@@ -67,19 +68,19 @@
|
||||
// A single save/cancel control
|
||||
.inplace-edit--control
|
||||
font-size: 0.9rem
|
||||
flex: 1
|
||||
padding: 5px
|
||||
a
|
||||
// Center save/cancel links
|
||||
width: 100%
|
||||
line-height: 27px
|
||||
border: 1px solid transparent
|
||||
display: inline-block
|
||||
color: var(--body-font-color)
|
||||
text-decoration: none
|
||||
width: 29px
|
||||
height: 29px
|
||||
background: none
|
||||
|
||||
&:hover, &:active
|
||||
border-color: var(--inplace-edit--border-color)
|
||||
// Center save/cancel links
|
||||
line-height: 27px
|
||||
border: 1px solid transparent
|
||||
display: inline-block
|
||||
color: var(--body-font-color)
|
||||
text-decoration: none
|
||||
|
||||
&:hover, &:active
|
||||
border-color: var(--inplace-edit--border-color)
|
||||
|
||||
.icon-context:before
|
||||
padding: 0
|
||||
|
||||
-76
@@ -1,76 +0,0 @@
|
||||
// Add a border at all times to the inplace container
|
||||
.inplace-editing--container
|
||||
position: relative
|
||||
|
||||
// need to specify the a explicitly as otherwise
|
||||
// the default class will win
|
||||
a.inplace-editing--trigger-link,
|
||||
.inplace-editing--trigger-link
|
||||
&:hover,
|
||||
&:focus,
|
||||
&.-focus
|
||||
text-decoration: none
|
||||
color: var(--body-font-color)
|
||||
|
||||
&.-active
|
||||
border-color: transparent
|
||||
|
||||
.inplace-editing--container
|
||||
border-color: var(--inplace-edit--border-color)
|
||||
|
||||
.inplace-editing--trigger-icon
|
||||
visibility: visible
|
||||
|
||||
// Do not hover trigger-link when element is read-only
|
||||
.-read-only
|
||||
.inplace-editing--trigger-link:hover .inplace-editing--container
|
||||
border-color: transparent
|
||||
|
||||
// Explicit styles for input-esque trigger appearance
|
||||
.work-packages--activity--add-comment,
|
||||
.work-package--watchers-lookup
|
||||
.inplace-editing--container
|
||||
border: 1px solid #eee
|
||||
|
||||
// Explicit styling for the comment and watcher container
|
||||
.work-packages--activity--add-comment,
|
||||
.work-package--watchers-lookup
|
||||
margin: 20px 0
|
||||
|
||||
// Styles the comment trigger link similar to attachment
|
||||
// but without icon and actual link
|
||||
a.inplace-editing--trigger-link
|
||||
color: var(--body-font-color)
|
||||
font-style: italic
|
||||
|
||||
// The trigger text left to the edit icon
|
||||
.inplace-editing--trigger-text
|
||||
width: calc(100% - 42px)
|
||||
padding: 3px
|
||||
line-height: 2
|
||||
display: inline-block
|
||||
|
||||
// The edit icon to the right of the trigger text
|
||||
.inplace-editing--trigger-icon
|
||||
position: absolute
|
||||
height: 100%
|
||||
top: 0
|
||||
right: 0
|
||||
text-align: center
|
||||
width: 32px
|
||||
background: $gray-light
|
||||
border-left: 1px solid var(--inplace-edit--border-color)
|
||||
color: var(--body-font-color)
|
||||
visibility: hidden
|
||||
|
||||
i
|
||||
position: relative
|
||||
// Position the icon in the middle
|
||||
top: calc(50% - 0.5rem)
|
||||
|
||||
.icon-context:before
|
||||
// HACK: overriding default padding here
|
||||
padding-right: 0
|
||||
|
||||
&:hover
|
||||
text-decoration: none
|
||||
@@ -95,15 +95,6 @@ i
|
||||
vertical-align: -2px
|
||||
padding: 0 0 0 4px
|
||||
|
||||
.work-packages--details-close-icon span,
|
||||
.work-packages--details-fullscreen-icon span
|
||||
display: inline-block
|
||||
padding-top: 2px
|
||||
font-size: 0.9rem
|
||||
|
||||
&:before
|
||||
color: var(--body-font-color)
|
||||
|
||||
// -------------------- Attribute groups --------------------
|
||||
// Special styling for project context area
|
||||
.attributes-group.-project-context
|
||||
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
.work-package-comment
|
||||
display: flex
|
||||
align-items: center
|
||||
width: 100%
|
||||
color: var(--body-font-color)
|
||||
padding: 0
|
||||
margin: 0
|
||||
background: none
|
||||
border: 1px solid #eee
|
||||
cursor: pointer
|
||||
height: 42px
|
||||
|
||||
|
||||
&:hover &,
|
||||
&:focus &
|
||||
border-color: var(--inplace-edit--border-color)
|
||||
text-decoration: none
|
||||
color: var(--body-font-color)
|
||||
|
||||
&--icon
|
||||
visibility: visible
|
||||
|
||||
// The trigger text left to the edit icon
|
||||
&--text
|
||||
flex: 1
|
||||
padding: 3px
|
||||
|
||||
// The edit icon to the right of the trigger text
|
||||
&--icon
|
||||
height: 100%
|
||||
text-align: center
|
||||
width: 32px
|
||||
background: var(--gray-light)
|
||||
border-left: 1px solid var(--inplace-edit--border-color)
|
||||
color: var(--body-font-color)
|
||||
visibility: hidden
|
||||
// Align the icon centered
|
||||
display: flex
|
||||
align-items: center
|
||||
justify-content: center
|
||||
|
||||
@@ -248,4 +248,4 @@
|
||||
|
||||
// Icon required for documents activity
|
||||
.icon-document
|
||||
@extend .icon-notes
|
||||
@extend .icon-notes
|
||||
@@ -52,7 +52,7 @@ module Components
|
||||
|
||||
def open!
|
||||
SeleniumHubWaiter.wait
|
||||
container.find(".help-text--for-#{help_text.attribute_name}").click
|
||||
container.find("[data-qa-help-text-for='#{help_text.attribute_name}']").click
|
||||
expect(page).to have_selector('.attribute-help-text--modal h1', text: help_text.attribute_caption)
|
||||
end
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ describe 'Work package attribute help texts', type: :feature, js: true do
|
||||
|
||||
shared_examples 'allows to view help texts' do
|
||||
it 'shows an indicator for whatever help text exists' do
|
||||
expect(page).to have_selector('.work-package--single-view .help-text--for-status')
|
||||
expect(page).to have_selector('.work-package--single-view [data-qa-help-text-for="status"]')
|
||||
|
||||
# Open help text modal
|
||||
modal.open!
|
||||
|
||||
@@ -242,11 +242,11 @@ describe 'Work package relations tab', js: true, selenium: true do
|
||||
textarea.set 'my description!'
|
||||
|
||||
# Save description
|
||||
created_row.find('.inplace-edit--control--save a').click
|
||||
created_row.find('.inplace-edit--control--save').click
|
||||
|
||||
loading_indicator_saveguard
|
||||
|
||||
# Wait for the relations table to be present
|
||||
# Wait for the relations table to be presen
|
||||
sleep 2
|
||||
expect(page).to have_selector('.wp-relations--subject-field')
|
||||
|
||||
@@ -264,7 +264,7 @@ describe 'Work package relations tab', js: true, selenium: true do
|
||||
text: 'my description!').click
|
||||
|
||||
# Cancel edition
|
||||
created_row.find('.inplace-edit--control--cancel a').click
|
||||
created_row.find('.inplace-edit--control--cancel').click
|
||||
created_row.find('.wp-relation--description-read-value',
|
||||
text: 'my description!').click
|
||||
|
||||
|
||||
@@ -78,14 +78,12 @@ describe 'Wysiwyg work package user mentions',
|
||||
expect(page)
|
||||
.to have_selector('a.mention', text: '@Foo Bar')
|
||||
|
||||
retry_block do
|
||||
comment_field.submit_by_click if comment_field.active?
|
||||
comment_field.submit_by_click if comment_field.active?
|
||||
|
||||
wp_page.expect_and_dismiss_notification message: "The comment was successfully added."
|
||||
wp_page.expect_and_dismiss_notification message: "The comment was successfully added."
|
||||
|
||||
expect(page)
|
||||
.to have_selector('a.user-mention', text: 'Foo Bar')
|
||||
end
|
||||
expect(page)
|
||||
.to have_selector('a.user-mention', text: 'Foo Bar')
|
||||
|
||||
# Mentioning a group works
|
||||
comment_field.activate!
|
||||
@@ -99,21 +97,19 @@ describe 'Wysiwyg work package user mentions',
|
||||
expect(page)
|
||||
.to have_selector('a.mention', text: '@Foogroup')
|
||||
|
||||
retry_block do
|
||||
comment_field.submit_by_click if comment_field.active?
|
||||
comment_field.submit_by_click if comment_field.active?
|
||||
|
||||
wp_page.expect_and_dismiss_notification message: "The comment was successfully added."
|
||||
wp_page.expect_and_dismiss_notification message: "The comment was successfully added."
|
||||
|
||||
expect(page)
|
||||
.to have_selector('a.user-mention', text: 'Foogroup')
|
||||
end
|
||||
expect(page)
|
||||
.to have_selector('a.user-mention', text: 'Foogroup')
|
||||
|
||||
# The mention is still displayed as such when reentering the comment field
|
||||
find('#activity-1')
|
||||
find('#activity-1 .op-user-activity')
|
||||
.hover
|
||||
|
||||
within('#activity-1') do
|
||||
click_link("Edit this comment")
|
||||
click_button("Edit this comment")
|
||||
end
|
||||
|
||||
expect(page)
|
||||
|
||||
@@ -125,7 +125,7 @@ class EditField
|
||||
end
|
||||
|
||||
def submit_by_dashboard
|
||||
field_container.find('.inplace-edit--control--save a', wait: 5).click
|
||||
field_container.find('.inplace-edit--control--save', wait: 5).click
|
||||
end
|
||||
|
||||
##
|
||||
|
||||
@@ -42,7 +42,7 @@ class TextEditorField < EditField
|
||||
end
|
||||
|
||||
def submit_by_click
|
||||
target = field_container.find(control_link)
|
||||
target = field_container.find(control_link, wait: 10)
|
||||
scroll_to_element(target)
|
||||
target.click
|
||||
end
|
||||
@@ -52,7 +52,7 @@ class TextEditorField < EditField
|
||||
end
|
||||
|
||||
def cancel_by_click
|
||||
target = field_container.find(control_link(:cancel))
|
||||
target = field_container.find(control_link(:cancel), wait: 10)
|
||||
scroll_to_element(target)
|
||||
target.click
|
||||
end
|
||||
@@ -64,6 +64,6 @@ class TextEditorField < EditField
|
||||
def control_link(action = :save)
|
||||
raise 'Invalid link' unless %i[save cancel].include?(action)
|
||||
|
||||
".inplace-edit--control--#{action}"
|
||||
".inplace-edit--control--#{action}:not([disabled])"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -245,7 +245,7 @@ module Pages
|
||||
end
|
||||
|
||||
def trigger_edit_comment
|
||||
add_comment_container.find('.inplace-editing--trigger-link').click
|
||||
add_comment_container.find('.work-package-comment').click
|
||||
end
|
||||
|
||||
def update_comment(comment)
|
||||
@@ -255,7 +255,7 @@ module Pages
|
||||
|
||||
def save_comment
|
||||
label = 'Comment: Save'
|
||||
add_comment_container.find(:xpath, "//a[@title='#{label}']").click
|
||||
add_comment_container.find(:xpath, "//button[@title='#{label}']").click
|
||||
end
|
||||
|
||||
def save!
|
||||
|
||||
Reference in New Issue
Block a user