diff --git a/app/controllers/my_controller.rb b/app/controllers/my_controller.rb
index 0547ab32894..8f5a4d33e00 100644
--- a/app/controllers/my_controller.rb
+++ b/app/controllers/my_controller.rb
@@ -46,6 +46,7 @@ class MyController < ApplicationController
menu_item :password, only: [:password]
menu_item :access_token, only: [:access_token]
menu_item :notifications, only: [:notifications]
+ menu_item :reminders, only: [:reminders]
def account; end
@@ -81,23 +82,30 @@ class MyController < ApplicationController
# Administer access tokens
def access_token; end
- # Configure user's mail notifications
+ # Configure user's in app notifications
def notifications
render html: '',
layout: 'angular',
locals: { menu_name: :my_menu }
end
+ # Configure user's mail reminders
+ def reminders
+ render html: '',
+ layout: 'angular',
+ locals: { menu_name: :my_menu }
+ end
+
# Create a new feeds key
def generate_rss_key
- if request.post?
- token = Token::RSS.create!(user: current_user)
- flash[:info] = [
- t('my.access_token.notice_reset_token', type: 'RSS').html_safe,
- content_tag(:strong, token.plain_value),
- t('my.access_token.token_value_warning')
- ]
- end
+ token = Token::RSS.create!(user: current_user)
+ flash[:info] = [
+ # rubocop:disable Rails/OutputSafety
+ t('my.access_token.notice_reset_token', type: 'RSS').html_safe,
+ # rubocop:enable Rails/OutputSafety
+ content_tag(:strong, token.plain_value),
+ t('my.access_token.token_value_warning')
+ ]
rescue StandardError => e
Rails.logger.error "Failed to reset user ##{current_user.id} RSS key: #{e}"
flash[:error] = t('my.access_token.failed_to_reset_token', error: e.message)
@@ -107,14 +115,14 @@ class MyController < ApplicationController
# Create a new API key
def generate_api_key
- if request.post?
- token = Token::API.create!(user: current_user)
- flash[:info] = [
- t('my.access_token.notice_reset_token', type: 'API').html_safe,
- content_tag(:strong, token.plain_value),
- t('my.access_token.token_value_warning')
- ]
- end
+ token = Token::API.create!(user: current_user)
+ flash[:info] = [
+ # rubocop:disable Rails/OutputSafety
+ t('my.access_token.notice_reset_token', type: 'API').html_safe,
+ # rubocop:enable Rails/OutputSafety
+ content_tag(:strong, token.plain_value),
+ t('my.access_token.token_value_warning')
+ ]
rescue StandardError => e
Rails.logger.error "Failed to reset user ##{current_user.id} API key: #{e}"
flash[:error] = t('my.access_token.failed_to_reset_token', error: e.message)
diff --git a/config/initializers/menus.rb b/config/initializers/menus.rb
index 46dd2450b18..b5e76a346af 100644
--- a/config/initializers/menus.rb
+++ b/config/initializers/menus.rb
@@ -137,6 +137,10 @@ Redmine::MenuManager.map :my_menu do |menu|
{ controller: '/my', action: 'notifications' },
caption: I18n.t('js.notifications.settings.title'),
icon: 'icon2 icon-bell'
+ menu.push :reminders,
+ { controller: '/my', action: 'reminders' },
+ caption: I18n.t('js.reminders.settings.title'),
+ icon: 'icon2 icon-email-alert'
menu.push :delete_account, :delete_my_account_info_path,
caption: I18n.t('account.delete'),
diff --git a/config/locales/js-en.yml b/config/locales/js-en.yml
index cef93364866..65a9478fa1a 100644
--- a/config/locales/js-en.yml
+++ b/config/locales/js-en.yml
@@ -627,6 +627,15 @@ en:
autocompleter:
label: 'Project autocompletion'
+ reminders:
+ settings:
+ daily:
+ title: 'Send me daily email reminders for unread notifications'
+ explanation: 'You will receive these reminders only for unread notifications and only at hours you specify.'
+ label: 'Time %{counter}'
+ title: 'Email reminders'
+
+
text_are_you_sure: "Are you sure?"
text_data_lost: "All entered data will be lost."
diff --git a/config/routes.rb b/config/routes.rb
index 42fb85a6fb3..ca4ea7e6a44 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -552,6 +552,7 @@ OpenProject::Application.routes.draw do
get '/my/account', action: 'account'
get '/my/settings', action: 'settings'
get '/my/notifications', action: 'notifications'
+ get '/my/reminders', action: 'reminders'
patch '/my/account', action: 'update_account'
patch '/my/settings', action: 'update_settings'
diff --git a/frontend/src/app/features/user-preferences/reminder-settings/page/reminder-settings-page.component.html b/frontend/src/app/features/user-preferences/reminder-settings/page/reminder-settings-page.component.html
new file mode 100644
index 00000000000..5cb11bff6ba
--- /dev/null
+++ b/frontend/src/app/features/user-preferences/reminder-settings/page/reminder-settings-page.component.html
@@ -0,0 +1,19 @@
+
+
+
+
+
diff --git a/frontend/src/app/features/user-preferences/reminder-settings/page/reminder-settings-page.component.ts b/frontend/src/app/features/user-preferences/reminder-settings/page/reminder-settings-page.component.ts
new file mode 100644
index 00000000000..eacbe2f035c
--- /dev/null
+++ b/frontend/src/app/features/user-preferences/reminder-settings/page/reminder-settings-page.component.ts
@@ -0,0 +1,55 @@
+import {
+ ChangeDetectionStrategy, Component, Input, OnInit,
+} from '@angular/core';
+import { I18nService } from 'core-app/core/i18n/i18n.service';
+import { CurrentUserService } from 'core-app/core/current-user/current-user.service';
+import { take } from 'rxjs/internal/operators/take';
+import { UIRouterGlobals } from '@uirouter/core';
+import { UserPreferencesService } from 'core-app/features/user-preferences/state/user-preferences.service';
+import { UserPreferencesQuery } from 'core-app/features/user-preferences/state/user-preferences.query';
+
+export const myReminderPageComponentSelector = 'op-reminders-page';
+
+@Component({
+ selector: myReminderPageComponentSelector,
+ templateUrl: './reminder-settings-page.component.html',
+ changeDetection: ChangeDetectionStrategy.OnPush,
+})
+export class ReminderSettingsPageComponent implements OnInit {
+ @Input() userId:string;
+
+ text = {
+ title: this.I18n.t('js.reminders.settings.title'),
+ save: this.I18n.t('js.button_save'),
+ daily: {
+ title: this.I18n.t('js.reminders.settings.daily.title'),
+ explanation: this.I18n.t('js.reminders.settings.daily.explanation'),
+ },
+ };
+
+ constructor(
+ private I18n:I18nService,
+ private stateService:UserPreferencesService,
+ private query:UserPreferencesQuery,
+ private currentUserService:CurrentUserService,
+ private uiRouterGlobals:UIRouterGlobals,
+ ) {
+ }
+
+ ngOnInit():void {
+ this.userId = this.userId || this.uiRouterGlobals.params.userId;
+ this
+ .currentUserService
+ .user$
+ .pipe(take(1))
+ .subscribe((user) => {
+ this.userId = this.userId || user.id!;
+ this.stateService.get(this.userId);
+ });
+ }
+
+ public saveChanges():void {
+ const prefs = this.query.getValue();
+ this.stateService.update(this.userId, prefs);
+ }
+}
diff --git a/frontend/src/app/features/user-preferences/reminder-settings/reminder-time/reminder-settings-daily-time.component.html b/frontend/src/app/features/user-preferences/reminder-settings/reminder-time/reminder-settings-daily-time.component.html
new file mode 100644
index 00000000000..5b3e178fa6a
--- /dev/null
+++ b/frontend/src/app/features/user-preferences/reminder-settings/reminder-time/reminder-settings-daily-time.component.html
@@ -0,0 +1,20 @@
+
+
+
+
+ {{text.label(i + 1)}}
+
+
diff --git a/frontend/src/app/features/user-preferences/reminder-settings/reminder-time/reminder-settings-daily-time.component.ts b/frontend/src/app/features/user-preferences/reminder-settings/reminder-time/reminder-settings-daily-time.component.ts
new file mode 100644
index 00000000000..72944ba93d5
--- /dev/null
+++ b/frontend/src/app/features/user-preferences/reminder-settings/reminder-time/reminder-settings-daily-time.component.ts
@@ -0,0 +1,30 @@
+import { Component, Input, OnInit } from "@angular/core";
+import { ChangeDetectionStrategy } from "@angular/core";
+import { I18nService } from 'core-app/core/i18n/i18n.service';
+import { UserPreferencesStore } from 'core-app/features/user-preferences/state/user-preferences.store';
+
+@Component({
+ selector: 'op-reminder-settings-daily-time',
+ templateUrl: './reminder-settings-daily-time.component.html',
+ changeDetection: ChangeDetectionStrategy.OnPush,
+})
+export class ReminderSettingsDailyTimeComponent implements OnInit {
+ public dailyReminderTimes = ["08:00", "12:00", "16:00"]
+
+ text = {
+ label: (counter:number):string => this.I18n.t('js.reminders.settings.daily.label', { counter: counter }),
+ };
+
+ constructor(
+ private I18n:I18nService,
+ private store:UserPreferencesStore,
+ ) {
+ }
+
+ ngOnInit():void {
+
+ }
+
+ public saveChanges():void {
+ }
+}
diff --git a/frontend/src/app/features/user-preferences/user-preferences.lazy-routes.ts b/frontend/src/app/features/user-preferences/user-preferences.lazy-routes.ts
index c5c41633822..51a75249c0f 100644
--- a/frontend/src/app/features/user-preferences/user-preferences.lazy-routes.ts
+++ b/frontend/src/app/features/user-preferences/user-preferences.lazy-routes.ts
@@ -39,4 +39,9 @@ export const MY_ACCOUNT_LAZY_ROUTES:Ng2StateDeclaration[] = [
url: '/users/:userId/edit/notifications',
loadChildren: () => import('./user-preferences.module').then((m) => m.OpenProjectMyAccountModule),
},
+ {
+ name: 'my_reminders.**',
+ url: '/my/reminders',
+ loadChildren: () => import('./user-preferences.module').then((m) => m.OpenProjectMyAccountModule),
+ },
];
diff --git a/frontend/src/app/features/user-preferences/user-preferences.module.ts b/frontend/src/app/features/user-preferences/user-preferences.module.ts
index 3b31d787a96..b65caa0559e 100644
--- a/frontend/src/app/features/user-preferences/user-preferences.module.ts
+++ b/frontend/src/app/features/user-preferences/user-preferences.module.ts
@@ -13,6 +13,8 @@ import { NotificationSettingInlineCreateComponent } from 'core-app/features/user
import { MY_ACCOUNT_ROUTES } from 'core-app/features/user-preferences/user-preferences.routes';
import { NotificationsSettingsToolbarComponent } from './notifications-settings/toolbar/notifications-settings-toolbar.component';
import { NotificationSettingsTableComponent } from './notifications-settings/table/notification-settings-table.component';
+import { ReminderSettingsPageComponent } from './reminder-settings/page/reminder-settings-page.component';
+import { ReminderSettingsDailyTimeComponent } from 'core-app/features/user-preferences/reminder-settings/reminder-time/reminder-settings-daily-time.component';
@NgModule({
providers: [
@@ -26,6 +28,8 @@ import { NotificationSettingsTableComponent } from './notifications-settings/tab
NotificationSettingInlineCreateComponent,
NotificationsSettingsToolbarComponent,
NotificationSettingsTableComponent,
+ ReminderSettingsPageComponent,
+ ReminderSettingsDailyTimeComponent
],
imports: [
CommonModule,
diff --git a/frontend/src/app/features/user-preferences/user-preferences.routes.ts b/frontend/src/app/features/user-preferences/user-preferences.routes.ts
index 19cd25e5776..9b6ec82df99 100644
--- a/frontend/src/app/features/user-preferences/user-preferences.routes.ts
+++ b/frontend/src/app/features/user-preferences/user-preferences.routes.ts
@@ -28,6 +28,7 @@
import { Ng2StateDeclaration } from '@uirouter/angular';
import { NotificationsSettingsPageComponent } from 'core-app/features/user-preferences/notifications-settings/page/notifications-settings-page.component';
+import { ReminderSettingsPageComponent } from './reminder-settings/page/reminder-settings-page.component';
export const MY_ACCOUNT_ROUTES:Ng2StateDeclaration[] = [
{
@@ -40,4 +41,9 @@ export const MY_ACCOUNT_ROUTES:Ng2StateDeclaration[] = [
url: '/users/:userId/edit/notifications',
component: NotificationsSettingsPageComponent,
},
+ {
+ name: 'my_reminders',
+ url: '/my/reminders',
+ component: ReminderSettingsPageComponent,
+ },
];
diff --git a/frontend/src/assets/fonts/openproject_icon/openproject-icon-font.svg b/frontend/src/assets/fonts/openproject_icon/openproject-icon-font.svg
index b21c0578ef4..8336ce11080 100644
--- a/frontend/src/assets/fonts/openproject_icon/openproject-icon-font.svg
+++ b/frontend/src/assets/fonts/openproject_icon/openproject-icon-font.svg
@@ -232,563 +232,566 @@
-
+
-
-
+
-
-
+
diff --git a/frontend/src/assets/fonts/openproject_icon/openproject-icon-font.ttf b/frontend/src/assets/fonts/openproject_icon/openproject-icon-font.ttf
index 5807107f7ed..9cb3ca62c0e 100644
Binary files a/frontend/src/assets/fonts/openproject_icon/openproject-icon-font.ttf and b/frontend/src/assets/fonts/openproject_icon/openproject-icon-font.ttf differ
diff --git a/frontend/src/assets/fonts/openproject_icon/openproject-icon-font.woff b/frontend/src/assets/fonts/openproject_icon/openproject-icon-font.woff
index bf4bfe152e8..23c0bf27a1f 100644
Binary files a/frontend/src/assets/fonts/openproject_icon/openproject-icon-font.woff and b/frontend/src/assets/fonts/openproject_icon/openproject-icon-font.woff differ
diff --git a/frontend/src/assets/fonts/openproject_icon/openproject-icon-font.woff2 b/frontend/src/assets/fonts/openproject_icon/openproject-icon-font.woff2
index b8ce478fb70..2a32699dc84 100644
Binary files a/frontend/src/assets/fonts/openproject_icon/openproject-icon-font.woff2 and b/frontend/src/assets/fonts/openproject_icon/openproject-icon-font.woff2 differ
diff --git a/frontend/src/global_styles/fonts/_openproject_icon_definitions.scss b/frontend/src/global_styles/fonts/_openproject_icon_definitions.scss
index 840e9d15126..ed1ebced572 100644
--- a/frontend/src/global_styles/fonts/_openproject_icon_definitions.scss
+++ b/frontend/src/global_styles/fonts/_openproject_icon_definitions.scss
@@ -448,1119 +448,1125 @@
.icon-edit:before {
content: "\f14b";
}
-@mixin icon-mixin-enterprise {
+@mixin icon-mixin-email-alert {
content: "\f14c";
}
-.icon-enterprise:before {
+.icon-email-alert:before {
content: "\f14c";
}
-@mixin icon-mixin-enumerations {
+@mixin icon-mixin-enterprise {
content: "\f14d";
}
-.icon-enumerations:before {
+.icon-enterprise:before {
content: "\f14d";
}
-@mixin icon-mixin-error {
+@mixin icon-mixin-enumerations {
content: "\f14e";
}
-.icon-error:before {
+.icon-enumerations:before {
content: "\f14e";
}
-@mixin icon-mixin-export-atom {
+@mixin icon-mixin-error {
content: "\f14f";
}
-.icon-export-atom:before {
+.icon-error:before {
content: "\f14f";
}
-@mixin icon-mixin-export-bcf {
+@mixin icon-mixin-export-atom {
content: "\f150";
}
-.icon-export-bcf:before {
+.icon-export-atom:before {
content: "\f150";
}
-@mixin icon-mixin-export-csv {
+@mixin icon-mixin-export-bcf {
content: "\f151";
}
-.icon-export-csv:before {
+.icon-export-bcf:before {
content: "\f151";
}
-@mixin icon-mixin-export-pdf-descr {
+@mixin icon-mixin-export-csv {
content: "\f152";
}
-.icon-export-pdf-descr:before {
+.icon-export-csv:before {
content: "\f152";
}
-@mixin icon-mixin-export-pdf-with-descriptions {
+@mixin icon-mixin-export-pdf-descr {
content: "\f153";
}
-.icon-export-pdf-with-descriptions:before {
+.icon-export-pdf-descr:before {
content: "\f153";
}
-@mixin icon-mixin-export-pdf {
+@mixin icon-mixin-export-pdf-with-descriptions {
content: "\f154";
}
-.icon-export-pdf:before {
+.icon-export-pdf-with-descriptions:before {
content: "\f154";
}
-@mixin icon-mixin-export-xls-descr {
+@mixin icon-mixin-export-pdf {
content: "\f155";
}
-.icon-export-xls-descr:before {
+.icon-export-pdf:before {
content: "\f155";
}
-@mixin icon-mixin-export-xls-with-descriptions {
+@mixin icon-mixin-export-xls-descr {
content: "\f156";
}
-.icon-export-xls-with-descriptions:before {
+.icon-export-xls-descr:before {
content: "\f156";
}
-@mixin icon-mixin-export-xls-with-relations {
+@mixin icon-mixin-export-xls-with-descriptions {
content: "\f157";
}
-.icon-export-xls-with-relations:before {
+.icon-export-xls-with-descriptions:before {
content: "\f157";
}
-@mixin icon-mixin-export-xls {
+@mixin icon-mixin-export-xls-with-relations {
content: "\f158";
}
-.icon-export-xls:before {
+.icon-export-xls-with-relations:before {
content: "\f158";
}
-@mixin icon-mixin-export {
+@mixin icon-mixin-export-xls {
content: "\f159";
}
-.icon-export:before {
+.icon-export-xls:before {
content: "\f159";
}
-@mixin icon-mixin-external-link {
+@mixin icon-mixin-export {
content: "\f15a";
}
-.icon-external-link:before {
+.icon-export:before {
content: "\f15a";
}
-@mixin icon-mixin-faq {
+@mixin icon-mixin-external-link {
content: "\f15b";
}
-.icon-faq:before {
+.icon-external-link:before {
content: "\f15b";
}
-@mixin icon-mixin-filter {
+@mixin icon-mixin-faq {
content: "\f15c";
}
-.icon-filter:before {
+.icon-faq:before {
content: "\f15c";
}
-@mixin icon-mixin-flag {
+@mixin icon-mixin-filter {
content: "\f15d";
}
-.icon-flag:before {
+.icon-filter:before {
content: "\f15d";
}
-@mixin icon-mixin-folder-add {
+@mixin icon-mixin-flag {
content: "\f15e";
}
-.icon-folder-add:before {
+.icon-flag:before {
content: "\f15e";
}
-@mixin icon-mixin-folder-locked {
+@mixin icon-mixin-folder-add {
content: "\f15f";
}
-.icon-folder-locked:before {
+.icon-folder-add:before {
content: "\f15f";
}
-@mixin icon-mixin-folder-open {
+@mixin icon-mixin-folder-locked {
content: "\f160";
}
-.icon-folder-open:before {
+.icon-folder-locked:before {
content: "\f160";
}
-@mixin icon-mixin-folder-remove {
+@mixin icon-mixin-folder-open {
content: "\f161";
}
-.icon-folder-remove:before {
+.icon-folder-open:before {
content: "\f161";
}
-@mixin icon-mixin-folder {
+@mixin icon-mixin-folder-remove {
content: "\f162";
}
-.icon-folder:before {
+.icon-folder-remove:before {
content: "\f162";
}
-@mixin icon-mixin-forums {
+@mixin icon-mixin-folder {
content: "\f163";
}
-.icon-forums:before {
+.icon-folder:before {
content: "\f163";
}
-@mixin icon-mixin-from-fullscreen {
+@mixin icon-mixin-forums {
content: "\f164";
}
-.icon-from-fullscreen:before {
+.icon-forums:before {
content: "\f164";
}
-@mixin icon-mixin-getting-started {
+@mixin icon-mixin-from-fullscreen {
content: "\f165";
}
-.icon-getting-started:before {
+.icon-from-fullscreen:before {
content: "\f165";
}
-@mixin icon-mixin-glossar {
+@mixin icon-mixin-getting-started {
content: "\f166";
}
-.icon-glossar:before {
+.icon-getting-started:before {
content: "\f166";
}
-@mixin icon-mixin-google-plus {
+@mixin icon-mixin-glossar {
content: "\f167";
}
-.icon-google-plus:before {
+.icon-glossar:before {
content: "\f167";
}
-@mixin icon-mixin-group-by {
+@mixin icon-mixin-google-plus {
content: "\f168";
}
-.icon-group-by:before {
+.icon-google-plus:before {
content: "\f168";
}
-@mixin icon-mixin-group {
+@mixin icon-mixin-group-by {
content: "\f169";
}
-.icon-group:before {
+.icon-group-by:before {
content: "\f169";
}
-@mixin icon-mixin-hamburger {
+@mixin icon-mixin-group {
content: "\f16a";
}
-.icon-hamburger:before {
+.icon-group:before {
content: "\f16a";
}
-@mixin icon-mixin-headline1 {
+@mixin icon-mixin-hamburger {
content: "\f16b";
}
-.icon-headline1:before {
+.icon-hamburger:before {
content: "\f16b";
}
-@mixin icon-mixin-headline2 {
+@mixin icon-mixin-headline1 {
content: "\f16c";
}
-.icon-headline2:before {
+.icon-headline1:before {
content: "\f16c";
}
-@mixin icon-mixin-headline3 {
+@mixin icon-mixin-headline2 {
content: "\f16d";
}
-.icon-headline3:before {
+.icon-headline2:before {
content: "\f16d";
}
-@mixin icon-mixin-headset {
+@mixin icon-mixin-headline3 {
content: "\f16e";
}
-.icon-headset:before {
+.icon-headline3:before {
content: "\f16e";
}
-@mixin icon-mixin-help {
+@mixin icon-mixin-headset {
content: "\f16f";
}
-.icon-help:before {
+.icon-headset:before {
content: "\f16f";
}
-@mixin icon-mixin-help1 {
+@mixin icon-mixin-help {
content: "\f170";
}
-.icon-help1:before {
+.icon-help:before {
content: "\f170";
}
-@mixin icon-mixin-help2 {
+@mixin icon-mixin-help1 {
content: "\f171";
}
-.icon-help2:before {
+.icon-help1:before {
content: "\f171";
}
-@mixin icon-mixin-hierarchy {
+@mixin icon-mixin-help2 {
content: "\f172";
}
-.icon-hierarchy:before {
+.icon-help2:before {
content: "\f172";
}
-@mixin icon-mixin-home {
+@mixin icon-mixin-hierarchy {
content: "\f173";
}
-.icon-home:before {
+.icon-hierarchy:before {
content: "\f173";
}
-@mixin icon-mixin-hosting {
+@mixin icon-mixin-home {
content: "\f174";
}
-.icon-hosting:before {
+.icon-home:before {
content: "\f174";
}
-@mixin icon-mixin-ifc {
+@mixin icon-mixin-hosting {
content: "\f175";
}
-.icon-ifc:before {
+.icon-hosting:before {
content: "\f175";
}
-@mixin icon-mixin-image1 {
+@mixin icon-mixin-ifc {
content: "\f176";
}
-.icon-image1:before {
+.icon-ifc:before {
content: "\f176";
}
-@mixin icon-mixin-image2 {
+@mixin icon-mixin-image1 {
content: "\f177";
}
-.icon-image2:before {
+.icon-image1:before {
content: "\f177";
}
-@mixin icon-mixin-import {
+@mixin icon-mixin-image2 {
content: "\f178";
}
-.icon-import:before {
+.icon-image2:before {
content: "\f178";
}
-@mixin icon-mixin-info1 {
+@mixin icon-mixin-import {
content: "\f179";
}
-.icon-info1:before {
+.icon-import:before {
content: "\f179";
}
-@mixin icon-mixin-info2 {
+@mixin icon-mixin-info1 {
content: "\f17a";
}
-.icon-info2:before {
+.icon-info1:before {
content: "\f17a";
}
-@mixin icon-mixin-input-disabled {
+@mixin icon-mixin-info2 {
content: "\f17b";
}
-.icon-input-disabled:before {
+.icon-info2:before {
content: "\f17b";
}
-@mixin icon-mixin-installation-services {
+@mixin icon-mixin-input-disabled {
content: "\f17c";
}
-.icon-installation-services:before {
+.icon-input-disabled:before {
content: "\f17c";
}
-@mixin icon-mixin-italic {
+@mixin icon-mixin-installation-services {
content: "\f17d";
}
-.icon-italic:before {
+.icon-installation-services:before {
content: "\f17d";
}
-@mixin icon-mixin-key {
+@mixin icon-mixin-italic {
content: "\f17e";
}
-.icon-key:before {
+.icon-italic:before {
content: "\f17e";
}
-@mixin icon-mixin-link {
+@mixin icon-mixin-key {
content: "\f17f";
}
-.icon-link:before {
+.icon-key:before {
content: "\f17f";
}
-@mixin icon-mixin-loading1 {
+@mixin icon-mixin-link {
content: "\f180";
}
-.icon-loading1:before {
+.icon-link:before {
content: "\f180";
}
-@mixin icon-mixin-loading2 {
+@mixin icon-mixin-loading1 {
content: "\f181";
}
-.icon-loading2:before {
+.icon-loading1:before {
content: "\f181";
}
-@mixin icon-mixin-location {
+@mixin icon-mixin-loading2 {
content: "\f182";
}
-.icon-location:before {
+.icon-loading2:before {
content: "\f182";
}
-@mixin icon-mixin-locked {
+@mixin icon-mixin-location {
content: "\f183";
}
-.icon-locked:before {
+.icon-location:before {
content: "\f183";
}
-@mixin icon-mixin-logout {
+@mixin icon-mixin-locked {
content: "\f184";
}
-.icon-logout:before {
+.icon-locked:before {
content: "\f184";
}
-@mixin icon-mixin-mail1 {
+@mixin icon-mixin-logout {
content: "\f185";
}
-.icon-mail1:before {
+.icon-logout:before {
content: "\f185";
}
-@mixin icon-mixin-mail2 {
+@mixin icon-mixin-mail1 {
content: "\f186";
}
-.icon-mail2:before {
+.icon-mail1:before {
content: "\f186";
}
-@mixin icon-mixin-maintenance-support {
+@mixin icon-mixin-mail2 {
content: "\f187";
}
-.icon-maintenance-support:before {
+.icon-mail2:before {
content: "\f187";
}
-@mixin icon-mixin-mark-all-read {
+@mixin icon-mixin-maintenance-support {
content: "\f188";
}
-.icon-mark-all-read:before {
+.icon-maintenance-support:before {
content: "\f188";
}
-@mixin icon-mixin-mark-read {
+@mixin icon-mixin-mark-all-read {
content: "\f189";
}
-.icon-mark-read:before {
+.icon-mark-all-read:before {
content: "\f189";
}
-@mixin icon-mixin-meetings {
+@mixin icon-mixin-mark-read {
content: "\f18a";
}
-.icon-meetings:before {
+.icon-mark-read:before {
content: "\f18a";
}
-@mixin icon-mixin-menu {
+@mixin icon-mixin-meetings {
content: "\f18b";
}
-.icon-menu:before {
+.icon-meetings:before {
content: "\f18b";
}
-@mixin icon-mixin-merge-branch {
+@mixin icon-mixin-menu {
content: "\f18c";
}
-.icon-merge-branch:before {
+.icon-menu:before {
content: "\f18c";
}
-@mixin icon-mixin-microphone {
+@mixin icon-mixin-merge-branch {
content: "\f18d";
}
-.icon-microphone:before {
+.icon-merge-branch:before {
content: "\f18d";
}
-@mixin icon-mixin-milestone {
+@mixin icon-mixin-microphone {
content: "\f18e";
}
-.icon-milestone:before {
+.icon-microphone:before {
content: "\f18e";
}
-@mixin icon-mixin-minus1 {
+@mixin icon-mixin-milestone {
content: "\f18f";
}
-.icon-minus1:before {
+.icon-milestone:before {
content: "\f18f";
}
-@mixin icon-mixin-minus2 {
+@mixin icon-mixin-minus1 {
content: "\f190";
}
-.icon-minus2:before {
+.icon-minus1:before {
content: "\f190";
}
-@mixin icon-mixin-mobile {
+@mixin icon-mixin-minus2 {
content: "\f191";
}
-.icon-mobile:before {
+.icon-minus2:before {
content: "\f191";
}
-@mixin icon-mixin-modules {
+@mixin icon-mixin-mobile {
content: "\f192";
}
-.icon-modules:before {
+.icon-mobile:before {
content: "\f192";
}
-@mixin icon-mixin-more {
+@mixin icon-mixin-modules {
content: "\f193";
}
-.icon-more:before {
+.icon-modules:before {
content: "\f193";
}
-@mixin icon-mixin-move {
+@mixin icon-mixin-more {
content: "\f194";
}
-.icon-move:before {
+.icon-more:before {
content: "\f194";
}
-@mixin icon-mixin-movie {
+@mixin icon-mixin-move {
content: "\f195";
}
-.icon-movie:before {
+.icon-move:before {
content: "\f195";
}
-@mixin icon-mixin-music {
+@mixin icon-mixin-movie {
content: "\f196";
}
-.icon-music:before {
+.icon-movie:before {
content: "\f196";
}
-@mixin icon-mixin-new-planning-element {
+@mixin icon-mixin-music {
content: "\f197";
}
-.icon-new-planning-element:before {
+.icon-music:before {
content: "\f197";
}
-@mixin icon-mixin-news {
+@mixin icon-mixin-new-planning-element {
content: "\f198";
}
-.icon-news:before {
+.icon-new-planning-element:before {
content: "\f198";
}
-@mixin icon-mixin-no-hierarchy {
+@mixin icon-mixin-news {
content: "\f199";
}
-.icon-no-hierarchy:before {
+.icon-news:before {
content: "\f199";
}
-@mixin icon-mixin-no-zen-mode {
+@mixin icon-mixin-no-hierarchy {
content: "\f19a";
}
-.icon-no-zen-mode:before {
+.icon-no-hierarchy:before {
content: "\f19a";
}
-@mixin icon-mixin-not-supported {
+@mixin icon-mixin-no-zen-mode {
content: "\f19b";
}
-.icon-not-supported:before {
+.icon-no-zen-mode:before {
content: "\f19b";
}
-@mixin icon-mixin-notes {
+@mixin icon-mixin-not-supported {
content: "\f19c";
}
-.icon-notes:before {
+.icon-not-supported:before {
content: "\f19c";
}
-@mixin icon-mixin-openid {
+@mixin icon-mixin-notes {
content: "\f19d";
}
-.icon-openid:before {
+.icon-notes:before {
content: "\f19d";
}
-@mixin icon-mixin-openproject {
+@mixin icon-mixin-openid {
content: "\f19e";
}
-.icon-openproject:before {
+.icon-openid:before {
content: "\f19e";
}
-@mixin icon-mixin-ordered-list {
+@mixin icon-mixin-openproject {
content: "\f19f";
}
-.icon-ordered-list:before {
+.icon-openproject:before {
content: "\f19f";
}
-@mixin icon-mixin-outline {
+@mixin icon-mixin-ordered-list {
content: "\f1a0";
}
-.icon-outline:before {
+.icon-ordered-list:before {
content: "\f1a0";
}
-@mixin icon-mixin-paragraph-left {
+@mixin icon-mixin-outline {
content: "\f1a1";
}
-.icon-paragraph-left:before {
+.icon-outline:before {
content: "\f1a1";
}
-@mixin icon-mixin-paragraph-right {
+@mixin icon-mixin-paragraph-left {
content: "\f1a2";
}
-.icon-paragraph-right:before {
+.icon-paragraph-left:before {
content: "\f1a2";
}
-@mixin icon-mixin-paragraph {
+@mixin icon-mixin-paragraph-right {
content: "\f1a3";
}
-.icon-paragraph:before {
+.icon-paragraph-right:before {
content: "\f1a3";
}
-@mixin icon-mixin-payment-history {
+@mixin icon-mixin-paragraph {
content: "\f1a4";
}
-.icon-payment-history:before {
+.icon-paragraph:before {
content: "\f1a4";
}
-@mixin icon-mixin-phone {
+@mixin icon-mixin-payment-history {
content: "\f1a5";
}
-.icon-phone:before {
+.icon-payment-history:before {
content: "\f1a5";
}
-@mixin icon-mixin-pin {
+@mixin icon-mixin-phone {
content: "\f1a6";
}
-.icon-pin:before {
+.icon-phone:before {
content: "\f1a6";
}
-@mixin icon-mixin-play {
+@mixin icon-mixin-pin {
content: "\f1a7";
}
-.icon-play:before {
+.icon-pin:before {
content: "\f1a7";
}
-@mixin icon-mixin-plugins {
+@mixin icon-mixin-play {
content: "\f1a8";
}
-.icon-plugins:before {
+.icon-play:before {
content: "\f1a8";
}
-@mixin icon-mixin-plus {
+@mixin icon-mixin-plugins {
content: "\f1a9";
}
-.icon-plus:before {
+.icon-plugins:before {
content: "\f1a9";
}
-@mixin icon-mixin-pre {
+@mixin icon-mixin-plus {
content: "\f1aa";
}
-.icon-pre:before {
+.icon-plus:before {
content: "\f1aa";
}
-@mixin icon-mixin-presentation {
+@mixin icon-mixin-pre {
content: "\f1ab";
}
-.icon-presentation:before {
+.icon-pre:before {
content: "\f1ab";
}
-@mixin icon-mixin-preview {
+@mixin icon-mixin-presentation {
content: "\f1ac";
}
-.icon-preview:before {
+.icon-presentation:before {
content: "\f1ac";
}
-@mixin icon-mixin-print {
+@mixin icon-mixin-preview {
content: "\f1ad";
}
-.icon-print:before {
+.icon-preview:before {
content: "\f1ad";
}
-@mixin icon-mixin-priority {
+@mixin icon-mixin-print {
content: "\f1ae";
}
-.icon-priority:before {
+.icon-print:before {
content: "\f1ae";
}
-@mixin icon-mixin-project-types {
+@mixin icon-mixin-priority {
content: "\f1af";
}
-.icon-project-types:before {
+.icon-priority:before {
content: "\f1af";
}
-@mixin icon-mixin-projects {
+@mixin icon-mixin-project-types {
content: "\f1b0";
}
-.icon-projects:before {
+.icon-project-types:before {
content: "\f1b0";
}
-@mixin icon-mixin-publish {
+@mixin icon-mixin-projects {
content: "\f1b1";
}
-.icon-publish:before {
+.icon-projects:before {
content: "\f1b1";
}
-@mixin icon-mixin-pulldown-up {
+@mixin icon-mixin-publish {
content: "\f1b2";
}
-.icon-pulldown-up:before {
+.icon-publish:before {
content: "\f1b2";
}
-@mixin icon-mixin-pulldown {
+@mixin icon-mixin-pulldown-up {
content: "\f1b3";
}
-.icon-pulldown:before {
+.icon-pulldown-up:before {
content: "\f1b3";
}
-@mixin icon-mixin-quote {
+@mixin icon-mixin-pulldown {
content: "\f1b4";
}
-.icon-quote:before {
+.icon-pulldown:before {
content: "\f1b4";
}
-@mixin icon-mixin-quote2 {
+@mixin icon-mixin-quote {
content: "\f1b5";
}
-.icon-quote2:before {
+.icon-quote:before {
content: "\f1b5";
}
-@mixin icon-mixin-redo {
+@mixin icon-mixin-quote2 {
content: "\f1b6";
}
-.icon-redo:before {
+.icon-quote2:before {
content: "\f1b6";
}
-@mixin icon-mixin-relation-follows {
+@mixin icon-mixin-redo {
content: "\f1b7";
}
-.icon-relation-follows:before {
+.icon-redo:before {
content: "\f1b7";
}
-@mixin icon-mixin-relation-new-child {
+@mixin icon-mixin-relation-follows {
content: "\f1b8";
}
-.icon-relation-new-child:before {
+.icon-relation-follows:before {
content: "\f1b8";
}
-@mixin icon-mixin-relation-precedes {
+@mixin icon-mixin-relation-new-child {
content: "\f1b9";
}
-.icon-relation-precedes:before {
+.icon-relation-new-child:before {
content: "\f1b9";
}
-@mixin icon-mixin-relations {
+@mixin icon-mixin-relation-precedes {
content: "\f1ba";
}
-.icon-relations:before {
+.icon-relation-precedes:before {
content: "\f1ba";
}
-@mixin icon-mixin-reload {
+@mixin icon-mixin-relations {
content: "\f1bb";
}
-.icon-reload:before {
+.icon-relations:before {
content: "\f1bb";
}
-@mixin icon-mixin-reminder {
+@mixin icon-mixin-reload {
content: "\f1bc";
}
-.icon-reminder:before {
+.icon-reload:before {
content: "\f1bc";
}
-@mixin icon-mixin-remove {
+@mixin icon-mixin-reminder {
content: "\f1bd";
}
-.icon-remove:before {
+.icon-reminder:before {
content: "\f1bd";
}
-@mixin icon-mixin-rename {
+@mixin icon-mixin-remove {
content: "\f1be";
}
-.icon-rename:before {
+.icon-remove:before {
content: "\f1be";
}
-@mixin icon-mixin-reported-by-me {
+@mixin icon-mixin-rename {
content: "\f1bf";
}
-.icon-reported-by-me:before {
+.icon-rename:before {
content: "\f1bf";
}
-@mixin icon-mixin-resizer-bottom-right {
+@mixin icon-mixin-reported-by-me {
content: "\f1c0";
}
-.icon-resizer-bottom-right:before {
+.icon-reported-by-me:before {
content: "\f1c0";
}
-@mixin icon-mixin-resizer-vertical-lines {
+@mixin icon-mixin-resizer-bottom-right {
content: "\f1c1";
}
-.icon-resizer-vertical-lines:before {
+.icon-resizer-bottom-right:before {
content: "\f1c1";
}
-@mixin icon-mixin-roadmap {
+@mixin icon-mixin-resizer-vertical-lines {
content: "\f1c2";
}
-.icon-roadmap:before {
+.icon-resizer-vertical-lines:before {
content: "\f1c2";
}
-@mixin icon-mixin-rss {
+@mixin icon-mixin-roadmap {
content: "\f1c3";
}
-.icon-rss:before {
+.icon-roadmap:before {
content: "\f1c3";
}
-@mixin icon-mixin-rubber {
+@mixin icon-mixin-rss {
content: "\f1c4";
}
-.icon-rubber:before {
+.icon-rss:before {
content: "\f1c4";
}
-@mixin icon-mixin-save {
+@mixin icon-mixin-rubber {
content: "\f1c5";
}
-.icon-save:before {
+.icon-rubber:before {
content: "\f1c5";
}
-@mixin icon-mixin-search {
+@mixin icon-mixin-save {
content: "\f1c6";
}
-.icon-search:before {
+.icon-save:before {
content: "\f1c6";
}
-@mixin icon-mixin-send-mail {
+@mixin icon-mixin-search {
content: "\f1c7";
}
-.icon-send-mail:before {
+.icon-search:before {
content: "\f1c7";
}
-@mixin icon-mixin-server-key {
+@mixin icon-mixin-send-mail {
content: "\f1c8";
}
-.icon-server-key:before {
+.icon-send-mail:before {
content: "\f1c8";
}
-@mixin icon-mixin-settings {
+@mixin icon-mixin-server-key {
content: "\f1c9";
}
-.icon-settings:before {
+.icon-server-key:before {
content: "\f1c9";
}
-@mixin icon-mixin-settings2 {
+@mixin icon-mixin-settings {
content: "\f1ca";
}
-.icon-settings2:before {
+.icon-settings:before {
content: "\f1ca";
}
-@mixin icon-mixin-settings3 {
+@mixin icon-mixin-settings2 {
content: "\f1cb";
}
-.icon-settings3:before {
+.icon-settings2:before {
content: "\f1cb";
}
-@mixin icon-mixin-settings4 {
+@mixin icon-mixin-settings3 {
content: "\f1cc";
}
-.icon-settings4:before {
+.icon-settings3:before {
content: "\f1cc";
}
-@mixin icon-mixin-shortcuts {
+@mixin icon-mixin-settings4 {
content: "\f1cd";
}
-.icon-shortcuts:before {
+.icon-settings4:before {
content: "\f1cd";
}
-@mixin icon-mixin-show-all-projects {
+@mixin icon-mixin-shortcuts {
content: "\f1ce";
}
-.icon-show-all-projects:before {
+.icon-shortcuts:before {
content: "\f1ce";
}
-@mixin icon-mixin-show-more-horizontal {
+@mixin icon-mixin-show-all-projects {
content: "\f1cf";
}
-.icon-show-more-horizontal:before {
+.icon-show-all-projects:before {
content: "\f1cf";
}
-@mixin icon-mixin-show-more {
+@mixin icon-mixin-show-more-horizontal {
content: "\f1d0";
}
-.icon-show-more:before {
+.icon-show-more-horizontal:before {
content: "\f1d0";
}
-@mixin icon-mixin-slack {
+@mixin icon-mixin-show-more {
content: "\f1d1";
}
-.icon-slack:before {
+.icon-show-more:before {
content: "\f1d1";
}
-@mixin icon-mixin-sort-ascending {
+@mixin icon-mixin-slack {
content: "\f1d2";
}
-.icon-sort-ascending:before {
+.icon-slack:before {
content: "\f1d2";
}
-@mixin icon-mixin-sort-by {
+@mixin icon-mixin-sort-ascending {
content: "\f1d3";
}
-.icon-sort-by:before {
+.icon-sort-ascending:before {
content: "\f1d3";
}
-@mixin icon-mixin-sort-descending {
+@mixin icon-mixin-sort-by {
content: "\f1d4";
}
-.icon-sort-descending:before {
+.icon-sort-by:before {
content: "\f1d4";
}
-@mixin icon-mixin-sort-down {
+@mixin icon-mixin-sort-descending {
content: "\f1d5";
}
-.icon-sort-down:before {
+.icon-sort-descending:before {
content: "\f1d5";
}
-@mixin icon-mixin-sort-up {
+@mixin icon-mixin-sort-down {
content: "\f1d6";
}
-.icon-sort-up:before {
+.icon-sort-down:before {
content: "\f1d6";
}
-@mixin icon-mixin-square {
+@mixin icon-mixin-sort-up {
content: "\f1d7";
}
-.icon-square:before {
+.icon-sort-up:before {
content: "\f1d7";
}
-@mixin icon-mixin-star {
+@mixin icon-mixin-square {
content: "\f1d8";
}
-.icon-star:before {
+.icon-square:before {
content: "\f1d8";
}
-@mixin icon-mixin-status-reporting {
+@mixin icon-mixin-star {
content: "\f1d9";
}
-.icon-status-reporting:before {
+.icon-star:before {
content: "\f1d9";
}
-@mixin icon-mixin-status {
+@mixin icon-mixin-status-reporting {
content: "\f1da";
}
-.icon-status:before {
+.icon-status-reporting:before {
content: "\f1da";
}
-@mixin icon-mixin-strike-through {
+@mixin icon-mixin-status {
content: "\f1db";
}
-.icon-strike-through:before {
+.icon-status:before {
content: "\f1db";
}
-@mixin icon-mixin-text {
+@mixin icon-mixin-strike-through {
content: "\f1dc";
}
-.icon-text:before {
+.icon-strike-through:before {
content: "\f1dc";
}
-@mixin icon-mixin-ticket-checked {
+@mixin icon-mixin-text {
content: "\f1dd";
}
-.icon-ticket-checked:before {
+.icon-text:before {
content: "\f1dd";
}
-@mixin icon-mixin-ticket-down {
+@mixin icon-mixin-ticket-checked {
content: "\f1de";
}
-.icon-ticket-down:before {
+.icon-ticket-checked:before {
content: "\f1de";
}
-@mixin icon-mixin-ticket-edit {
+@mixin icon-mixin-ticket-down {
content: "\f1df";
}
-.icon-ticket-edit:before {
+.icon-ticket-down:before {
content: "\f1df";
}
-@mixin icon-mixin-ticket-minus {
+@mixin icon-mixin-ticket-edit {
content: "\f1e0";
}
-.icon-ticket-minus:before {
+.icon-ticket-edit:before {
content: "\f1e0";
}
-@mixin icon-mixin-ticket-note {
+@mixin icon-mixin-ticket-minus {
content: "\f1e1";
}
-.icon-ticket-note:before {
+.icon-ticket-minus:before {
content: "\f1e1";
}
-@mixin icon-mixin-ticket {
+@mixin icon-mixin-ticket-note {
content: "\f1e2";
}
-.icon-ticket:before {
+.icon-ticket-note:before {
content: "\f1e2";
}
-@mixin icon-mixin-time {
+@mixin icon-mixin-ticket {
content: "\f1e3";
}
-.icon-time:before {
+.icon-ticket:before {
content: "\f1e3";
}
-@mixin icon-mixin-to-fullscreen {
+@mixin icon-mixin-time {
content: "\f1e4";
}
-.icon-to-fullscreen:before {
+.icon-time:before {
content: "\f1e4";
}
-@mixin icon-mixin-training-consulting {
+@mixin icon-mixin-to-fullscreen {
content: "\f1e5";
}
-.icon-training-consulting:before {
+.icon-to-fullscreen:before {
content: "\f1e5";
}
-@mixin icon-mixin-two-factor-authentication {
+@mixin icon-mixin-training-consulting {
content: "\f1e6";
}
-.icon-two-factor-authentication:before {
+.icon-training-consulting:before {
content: "\f1e6";
}
-@mixin icon-mixin-types {
+@mixin icon-mixin-two-factor-authentication {
content: "\f1e7";
}
-.icon-types:before {
+.icon-two-factor-authentication:before {
content: "\f1e7";
}
-@mixin icon-mixin-underline {
+@mixin icon-mixin-types {
content: "\f1e8";
}
-.icon-underline:before {
+.icon-types:before {
content: "\f1e8";
}
-@mixin icon-mixin-undo {
+@mixin icon-mixin-underline {
content: "\f1e9";
}
-.icon-undo:before {
+.icon-underline:before {
content: "\f1e9";
}
-@mixin icon-mixin-unit {
+@mixin icon-mixin-undo {
content: "\f1ea";
}
-.icon-unit:before {
+.icon-undo:before {
content: "\f1ea";
}
-@mixin icon-mixin-unlocked {
+@mixin icon-mixin-unit {
content: "\f1eb";
}
-.icon-unlocked:before {
+.icon-unit:before {
content: "\f1eb";
}
-@mixin icon-mixin-unordered-list {
+@mixin icon-mixin-unlocked {
content: "\f1ec";
}
-.icon-unordered-list:before {
+.icon-unlocked:before {
content: "\f1ec";
}
-@mixin icon-mixin-unwatched {
+@mixin icon-mixin-unordered-list {
content: "\f1ed";
}
-.icon-unwatched:before {
+.icon-unordered-list:before {
content: "\f1ed";
}
-@mixin icon-mixin-upload {
+@mixin icon-mixin-unwatched {
content: "\f1ee";
}
-.icon-upload:before {
+.icon-unwatched:before {
content: "\f1ee";
}
-@mixin icon-mixin-user-minus {
+@mixin icon-mixin-upload {
content: "\f1ef";
}
-.icon-user-minus:before {
+.icon-upload:before {
content: "\f1ef";
}
-@mixin icon-mixin-user-plus {
+@mixin icon-mixin-user-minus {
content: "\f1f0";
}
-.icon-user-plus:before {
+.icon-user-minus:before {
content: "\f1f0";
}
-@mixin icon-mixin-user {
+@mixin icon-mixin-user-plus {
content: "\f1f1";
}
-.icon-user:before {
+.icon-user-plus:before {
content: "\f1f1";
}
-@mixin icon-mixin-view-card {
+@mixin icon-mixin-user {
content: "\f1f2";
}
-.icon-view-card:before {
+.icon-user:before {
content: "\f1f2";
}
-@mixin icon-mixin-view-fullscreen {
+@mixin icon-mixin-view-card {
content: "\f1f3";
}
-.icon-view-fullscreen:before {
+.icon-view-card:before {
content: "\f1f3";
}
-@mixin icon-mixin-view-list {
+@mixin icon-mixin-view-fullscreen {
content: "\f1f4";
}
-.icon-view-list:before {
+.icon-view-fullscreen:before {
content: "\f1f4";
}
-@mixin icon-mixin-view-model {
+@mixin icon-mixin-view-list {
content: "\f1f5";
}
-.icon-view-model:before {
+.icon-view-list:before {
content: "\f1f5";
}
-@mixin icon-mixin-view-split-viewer-table {
+@mixin icon-mixin-view-model {
content: "\f1f6";
}
-.icon-view-split-viewer-table:before {
+.icon-view-model:before {
content: "\f1f6";
}
-@mixin icon-mixin-view-split {
+@mixin icon-mixin-view-split-viewer-table {
content: "\f1f7";
}
-.icon-view-split:before {
+.icon-view-split-viewer-table:before {
content: "\f1f7";
}
-@mixin icon-mixin-view-split2 {
+@mixin icon-mixin-view-split {
content: "\f1f8";
}
-.icon-view-split2:before {
+.icon-view-split:before {
content: "\f1f8";
}
-@mixin icon-mixin-view-timeline {
+@mixin icon-mixin-view-split2 {
content: "\f1f9";
}
-.icon-view-timeline:before {
+.icon-view-split2:before {
content: "\f1f9";
}
-@mixin icon-mixin-warning {
+@mixin icon-mixin-view-timeline {
content: "\f1fa";
}
-.icon-warning:before {
+.icon-view-timeline:before {
content: "\f1fa";
}
-@mixin icon-mixin-watched {
+@mixin icon-mixin-warning {
content: "\f1fb";
}
-.icon-watched:before {
+.icon-warning:before {
content: "\f1fb";
}
-@mixin icon-mixin-wiki-edit {
+@mixin icon-mixin-watched {
content: "\f1fc";
}
-.icon-wiki-edit:before {
+.icon-watched:before {
content: "\f1fc";
}
-@mixin icon-mixin-wiki {
+@mixin icon-mixin-wiki-edit {
content: "\f1fd";
}
-.icon-wiki:before {
+.icon-wiki-edit:before {
content: "\f1fd";
}
-@mixin icon-mixin-wiki2 {
+@mixin icon-mixin-wiki {
content: "\f1fe";
}
-.icon-wiki2:before {
+.icon-wiki:before {
content: "\f1fe";
}
-@mixin icon-mixin-work-packages {
+@mixin icon-mixin-wiki2 {
content: "\f1ff";
}
-.icon-work-packages:before {
+.icon-wiki2:before {
content: "\f1ff";
}
-@mixin icon-mixin-workflow {
+@mixin icon-mixin-work-packages {
content: "\f200";
}
-.icon-workflow:before {
+.icon-work-packages:before {
content: "\f200";
}
-@mixin icon-mixin-yes {
+@mixin icon-mixin-workflow {
content: "\f201";
}
-.icon-yes:before {
+.icon-workflow:before {
content: "\f201";
}
-@mixin icon-mixin-zen-mode {
+@mixin icon-mixin-yes {
content: "\f202";
}
-.icon-zen-mode:before {
+.icon-yes:before {
content: "\f202";
}
-@mixin icon-mixin-zoom-auto {
+@mixin icon-mixin-zen-mode {
content: "\f203";
}
-.icon-zoom-auto:before {
+.icon-zen-mode:before {
content: "\f203";
}
-@mixin icon-mixin-zoom-in {
+@mixin icon-mixin-zoom-auto {
content: "\f204";
}
-.icon-zoom-in:before {
+.icon-zoom-auto:before {
content: "\f204";
}
-@mixin icon-mixin-zoom-out {
+@mixin icon-mixin-zoom-in {
content: "\f205";
}
-.icon-zoom-out:before {
+.icon-zoom-in:before {
content: "\f205";
}
+@mixin icon-mixin-zoom-out {
+ content: "\f206";
+}
+.icon-zoom-out:before {
+ content: "\f206";
+}
diff --git a/frontend/src/global_styles/fonts/_openproject_icon_font.lsg b/frontend/src/global_styles/fonts/_openproject_icon_font.lsg
index 85800e8208b..699aa85cc49 100644
--- a/frontend/src/global_styles/fonts/_openproject_icon_font.lsg
+++ b/frontend/src/global_styles/fonts/_openproject_icon_font.lsg
@@ -78,6 +78,7 @@
drag-handle
duplicate
edit
+ email-alert
enterprise
enumerations
error
diff --git a/spec/features/notifications/digest_mail_spec.rb b/spec/features/notifications/digest_mail_spec.rb
index 4672736f649..50ac533594a 100644
--- a/spec/features/notifications/digest_mail_spec.rb
+++ b/spec/features/notifications/digest_mail_spec.rb
@@ -1,6 +1,7 @@
require 'spec_helper'
require 'support/pages/my/notifications'
+# TODO: This feature spec is to be replaced by the reminder_mail_spec.rb in the same directory.
describe "Digest email", type: :feature, js: true do
let!(:project) { FactoryBot.create :project, members: { current_user => role } }
let!(:mute_project) { FactoryBot.create :project, members: { current_user => role } }
diff --git a/spec/features/notifications/reminder_mail_spec.rb b/spec/features/notifications/reminder_mail_spec.rb
new file mode 100644
index 00000000000..e33935e933c
--- /dev/null
+++ b/spec/features/notifications/reminder_mail_spec.rb
@@ -0,0 +1,65 @@
+require 'spec_helper'
+require 'support/pages/my/notifications'
+
+describe "Reminder email", type: :feature, js: true do
+ let!(:project) { FactoryBot.create :project, members: { current_user => role } }
+ let!(:mute_project) { FactoryBot.create :project, members: { current_user => role } }
+ let(:reminders_settings_page) { Pages::My::Reminders.new(current_user) }
+ let(:role) { FactoryBot.create(:role, permissions: %i[view_work_packages]) }
+ let(:other_user) { FactoryBot.create(:user) }
+ let(:work_package) { FactoryBot.create(:work_package, project: project) }
+ let(:watched_work_package) { FactoryBot.create(:work_package, project: project, watcher_users: [current_user]) }
+ let(:involved_work_package) { FactoryBot.create(:work_package, project: project, assigned_to: current_user) }
+
+ current_user do
+ FactoryBot.create :user,
+ notification_settings: [
+ FactoryBot.build(:mail_notification_setting,
+ involved: false,
+ watched: false,
+ mentioned: false,
+ work_package_commented: false,
+ work_package_created: false,
+ work_package_processed: false,
+ work_package_prioritized: false,
+ work_package_scheduled: false,
+ all: false),
+ FactoryBot.build(:in_app_notification_setting,
+ involved: false,
+ watched: false,
+ mentioned: false,
+ work_package_commented: false,
+ work_package_created: false,
+ work_package_processed: false,
+ work_package_prioritized: false,
+ work_package_scheduled: false,
+ all: false),
+ FactoryBot.build(:mail_digest_notification_setting,
+ involved: true,
+ watched: true,
+ mentioned: true,
+ work_package_commented: true,
+ work_package_created: true,
+ work_package_processed: true,
+ work_package_prioritized: true,
+ work_package_scheduled: true,
+ all: false)
+ ]
+ end
+
+ before do
+ watched_work_package
+ work_package
+ involved_work_package
+
+ ActiveJob::Base.queue_adapter.enqueued_jobs.clear
+ end
+
+ it 'sends a reminder mail based on the configuration', with_settings: { journal_aggregation_time_minutes: 0 } do
+ # Configure the digest
+ reminders_settings_page.visit!
+
+ # By default a reminder timed for 8:00 should be configured
+ reminders_settings_page.expect_active_daily_times("08:00")
+ end
+end
diff --git a/spec/routing/my_spec.rb b/spec/routing/my_spec.rb
index 110c13ed16e..be6c3526897 100644
--- a/spec/routing/my_spec.rb
+++ b/spec/routing/my_spec.rb
@@ -45,6 +45,14 @@ describe 'my routes', type: :routing do
expect(patch('/my/settings')).to route_to('my#update_settings')
end
+ it '/my/notifications GET routes to my#notifications' do
+ expect(get('/my/notifications')).to route_to('my#notifications')
+ end
+
+ it '/my/reminders GET routes to my#notifications' do
+ expect(get('/my/reminders')).to route_to('my#reminders')
+ end
+
it '/my/generate_rss_key POST routes to my#generate_rss_key' do
expect(post('/my/generate_rss_key')).to route_to('my#generate_rss_key')
end
@@ -53,8 +61,8 @@ describe 'my routes', type: :routing do
expect(post('/my/generate_api_key')).to route_to('my#generate_api_key')
end
- it {
+ it '/my/deletion_info GET routes to users#deletion_info' do
expect(get('/my/deletion_info')).to route_to(controller: 'users',
action: 'deletion_info')
- }
+ end
end
diff --git a/spec/support/pages/my/reminders.rb b/spec/support/pages/my/reminders.rb
new file mode 100644
index 00000000000..13daca47d69
--- /dev/null
+++ b/spec/support/pages/my/reminders.rb
@@ -0,0 +1,39 @@
+#-- 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.
+#++
+
+require 'support/pages/reminders/settings'
+
+module Pages
+ module My
+ class Reminders < ::Pages::Reminders::Settings
+ def path
+ my_reminders_path
+ end
+ end
+ end
+end
diff --git a/spec/support/pages/reminders/settings.rb b/spec/support/pages/reminders/settings.rb
new file mode 100644
index 00000000000..747993ae8f0
--- /dev/null
+++ b/spec/support/pages/reminders/settings.rb
@@ -0,0 +1,64 @@
+#-- 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.
+#++
+
+require 'support/pages/page'
+
+module Pages
+ module Reminders
+ class Settings < ::Pages::Page
+ attr_reader :user
+
+ def initialize(user)
+ super()
+ @user = user
+ end
+
+ def path
+ edit_user_path(user, tab: :reminders)
+ end
+
+ def expect_active_daily_times(*times)
+ times.each_with_index do |time, index|
+ expect(page)
+ .to have_checked_field "Time #{index + 1}"
+
+ expect(page)
+ .to have_css("input[data-qa-selector='op-settings-daily-time--time-#{index + 1}']")
+
+ expect(page.find("input[data-qa-selector='op-settings-daily-time--time-#{index + 1}']").value)
+ .to eql(time)
+ end
+ end
+
+ def save
+ click_button 'Save'
+ end
+
+ end
+ end
+end
diff --git a/vendor/openproject-icon-font/src/email-alert.svg b/vendor/openproject-icon-font/src/email-alert.svg
new file mode 100644
index 00000000000..92bb4f1d80e
--- /dev/null
+++ b/vendor/openproject-icon-font/src/email-alert.svg
@@ -0,0 +1,4 @@
+
+
+
+