diff --git a/src/actions/board.js b/src/actions/board.js
index cb518e05..344ed3bf 100644
--- a/src/actions/board.js
+++ b/src/actions/board.js
@@ -17,6 +17,10 @@ export const BoardActionTypes = {
POPUP_BOARD_DELETE_SHOW: 'board/popup/delete/show',
// Закрытие диалога подтверждения удаления доски (нажато да/нет)
POPUP_BOARD_DELETE_HIDE: 'board/popup/delete/hide',
+ BOARD_ADD_MEMBER_SHOW: 'board/member/show',
+ BOARD_ADD_MEMBER_CLOSE: 'board/member/close',
+ BOARD_ADD_MEMBER_INPUT: 'board/member/input',
+ BOARD_ADD_MEMBER_USER_CLICKED: 'board/member/clicked',
};
/**
@@ -78,4 +82,44 @@ export const boardActions = {
},
});
},
+
+ /**
+ * Отобразить popup добавления пользователя в доску
+ */
+ showAddBoardMemberPopUp() {
+ Dispatcher.dispatch({
+ actionName: BoardActionTypes.BOARD_ADD_MEMBER_SHOW,
+ });
+ },
+
+ /**
+ * Скрыть popup добавления пользователя в доску
+ */
+ hideAddBoardMemberPopUp() {
+ Dispatcher.dispatch({
+ actionName: BoardActionTypes.BOARD_ADD_MEMBER_CLOSE,
+ });
+ },
+
+ /**
+ * Обновить список пользователей на основании ввода пользователя
+ * @param {String} searchString - строка для поиска
+ */
+ refreshUserSearchList(searchString) {
+ Dispatcher.dispatch({
+ actionName: BoardActionTypes.BOARD_ADD_MEMBER_INPUT,
+ data: {searchString},
+ });
+ },
+
+ /**
+ * Добаваить/исключить пользователя из доски
+ * @param {Number} uid - id пользователя
+ */
+ toggleUserInSearchList(uid) {
+ Dispatcher.dispatch({
+ actionName: BoardActionTypes.BOARD_ADD_MEMBER_USER_CLICKED,
+ data: {uid},
+ });
+ },
};
diff --git a/src/actions/boards.js b/src/actions/boards.js
index 1e03d4d9..5aa64305 100644
--- a/src/actions/boards.js
+++ b/src/actions/boards.js
@@ -12,6 +12,10 @@ export const BoardsActionTypes = {
BOARDS_CREATE: 'boards/create',
BOARDS_POPUP_SHOW: 'boards/popup/show',
BOARDS_POPUP_HIDE: 'boards/popup/hide',
+ BOARDS_ADD_MEMBER_SHOW: 'boards/member/show',
+ BOARDS_ADD_MEMBER_CLOSE: 'boards/member/close',
+ BOARDS_ADD_MEMBER_INPUT: 'boards/member/input',
+ BOARDS_ADD_MEMBER_USER_CLICKED: 'boards/member/clicked',
};
/**
@@ -41,27 +45,27 @@ export const boardsActions = {
/**
* Действие: создание доски.
* @param {String} name - название доски
- * @param {int} teamID - номер команды
+ * @param {Number} tid - номер команды
*/
- createBoard(name, teamID) {
+ createBoard(name, tid) {
Dispatcher.dispatch({
actionName: BoardsActionTypes.BOARDS_CREATE,
data: {
name,
- teamID,
+ tid,
},
});
},
/**
* Действие: открытие модального окна для команды.
- * @param {int} teamID - номер команды
+ * @param {Number} tid - номер команды
*/
- showModal(teamID) {
+ showModal(tid) {
Dispatcher.dispatch({
actionName: BoardsActionTypes.BOARDS_POPUP_SHOW,
data: {
- teamID,
+ tid,
},
});
},
@@ -71,4 +75,48 @@ export const boardsActions = {
actionName: BoardsActionTypes.BOARDS_POPUP_HIDE,
});
},
+
+ /**
+ * Отобразить popup добавления пользователя в команду
+ * @param {Number} tid - id команды
+ */
+ showAddTeamMemberPopUp(tid) {
+ Dispatcher.dispatch({
+ actionName: BoardsActionTypes.BOARDS_ADD_MEMBER_SHOW,
+ data: {
+ tid,
+ },
+ });
+ },
+
+ /**
+ * Скрыть popup добавления пользователя на карточку
+ */
+ hideAddTeamMemberPopUp() {
+ Dispatcher.dispatch({
+ actionName: BoardsActionTypes.BOARDS_ADD_MEMBER_CLOSE,
+ });
+ },
+
+ /**
+ * Обновить список пользователей на основании ввода пользователя
+ * @param {String} searchString - строка для поиска
+ */
+ refreshTeamSearchList(searchString) {
+ Dispatcher.dispatch({
+ actionName: BoardsActionTypes.BOARDS_ADD_MEMBER_INPUT,
+ data: {searchString},
+ });
+ },
+
+ /**
+ * Добаваить/исключить пользователя из доски
+ * @param {Number} uid - id пользователя
+ */
+ toggleUserInSearchList(uid) {
+ Dispatcher.dispatch({
+ actionName: BoardsActionTypes.BOARDS_ADD_MEMBER_USER_CLICKED,
+ data: {uid},
+ });
+ },
};
diff --git a/src/actions/card.js b/src/actions/card.js
index 49decab3..de6034a4 100644
--- a/src/actions/card.js
+++ b/src/actions/card.js
@@ -16,6 +16,10 @@ export const CardActionTypes = {
CARD_DELETE_CHOOSE: 'card/delete/choose',
CARD_DELETE_HIDE: 'card/delete/hide',
CARD_UPDATE_STATUS: 'card/update/deadline',
+ CARD_ADD_ASSIGNEE_SHOW: 'card/assignee/show',
+ CARD_ADD_ASSIGNEE_CLOSE: 'card/assignee/close',
+ CARD_ADD_ASSIGNEE_INPUT: 'card/assignee/input',
+ CARD_ADD_ASSIGNEE_USER_CLICKED: 'card/assignee/clicked',
};
/**
@@ -135,4 +139,45 @@ export const cardActions = {
actionName: CardActionTypes.CARD_DELETE_HIDE,
});
},
+
+ /**
+ * Отобразить popup добавления пользователя на карточку
+ */
+ showAddCardAssigneePopUp() {
+ Dispatcher.dispatch({
+ actionName: CardActionTypes.CARD_ADD_ASSIGNEE_SHOW,
+ });
+ },
+
+ /**
+ * Скрыть popup добавления пользователя на карточку
+ */
+ hideAddCardAssigneePopUp() {
+ Dispatcher.dispatch({
+ actionName: CardActionTypes.CARD_ADD_ASSIGNEE_CLOSE,
+ });
+ },
+
+ /**
+ * Обновить список пользователей на основании ввода пользователя
+ * @param {String} searchString - строка для поиска
+ */
+ refreshUserSearchList(searchString) {
+ Dispatcher.dispatch({
+ actionName: CardActionTypes.CARD_ADD_ASSIGNEE_INPUT,
+ data: {searchString},
+ });
+ },
+
+ /**
+ * Добаваить/исключить пользователя из карточки
+ * @param {Number} uid - id пользователя
+ */
+ toggleUserInSearchList(uid) {
+ Dispatcher.dispatch({
+ actionName: CardActionTypes.CARD_ADD_ASSIGNEE_USER_CLICKED,
+ data: {uid},
+ });
+ },
+
};
diff --git a/src/components/BaseComponent.js b/src/components/BaseComponent.js
index 73ffb7d9..920ea1a2 100644
--- a/src/components/BaseComponent.js
+++ b/src/components/BaseComponent.js
@@ -40,6 +40,15 @@ export default class BaseComponent {
});
}
+ /**
+ * Метод, обновляет контекст у компонента с заданным именем.
+ * @param {String} componentName - название компонента
+ * @param {Object} context - новый контекст
+ */
+ _setContextByComponentName(componentName, context) {
+ this.subComponents.get(componentName)._setContext(context);
+ }
+
/**
* Метод, отрисовывающий HTML компонента.
* @return {String} HTML-код компонента
diff --git a/src/components/CardList/CardList.scss b/src/components/CardList/CardList.scss
index dec23493..09b60419 100644
--- a/src/components/CardList/CardList.scss
+++ b/src/components/CardList/CardList.scss
@@ -5,7 +5,6 @@ $scrollbar-width: 1600px;
/* Стили списков карточек: */
.columns {
- //background: darkred;
flex-grow: 1;
max-height: $max-height-politic;
margin: 10px;
diff --git a/src/components/Navbar/Navbar.hbs b/src/components/Navbar/Navbar.hbs
index 33734950..e99afecf 100644
--- a/src/components/Navbar/Navbar.hbs
+++ b/src/components/Navbar/Navbar.hbs
@@ -9,7 +9,7 @@
{{#if isAuthorized}}
-
{{userName}}
diff --git a/src/components/Navbar/Navbar.scss b/src/components/Navbar/Navbar.scss
index de0a96f8..5f1232d9 100644
--- a/src/components/Navbar/Navbar.scss
+++ b/src/components/Navbar/Navbar.scss
@@ -75,13 +75,6 @@
}
}
-img.avatar {
- width: 35px;
- height: 35px;
- border: 2px solid $tertiary-bg-color;
- margin-right: 10px;
-}
-
.navbar__link__title {
color: white;
text-decoration: none;
diff --git a/src/constants/constants.js b/src/constants/constants.js
index 3f42992c..2d475abd 100644
--- a/src/constants/constants.js
+++ b/src/constants/constants.js
@@ -44,7 +44,7 @@ export const BackendAddress = {
export const HttpStatusCodes = {
Ok: 200,
Created: 201,
- NotMofidied: 304,
+ NotModified: 304,
BadRequest: 400,
Unauthorized: 401,
InternalServerError: 500,
@@ -90,6 +90,10 @@ export const ConstantMessages = {
UnsuccessfulRequest: 'Неудачный запрос, попробуйте позднее :]',
};
+export const BoardStoreConstants = {
+ MinUserNameSearchLength: 3,
+};
+
export const CheckLists = {
CheckListDefaultTitle: 'Check List',
CheckListItemDefaultTitle: 'Item',
diff --git a/src/modules/Network/Network.js b/src/modules/Network/Network.js
index 4673bac4..11d84321 100644
--- a/src/modules/Network/Network.js
+++ b/src/modules/Network/Network.js
@@ -20,6 +20,12 @@ class Network {
board: 'api/boards',
card: 'api/cards',
cardlist: 'api/cardLists',
+ usersearch: {
+ card: 'api/usersearch/card',
+ board: 'api/usersearch/board',
+ team: 'api/usersearch/team',
+ },
+ team: 'api/teams',
checklists: 'api/checkLists',
checklistsItems: 'api/checkListItems',
};
@@ -153,16 +159,11 @@ class Network {
/**
* Метод, реализующий запрос GET /api/boards.
- * @param {object} data полезная нагрузка запроса
* @return {Promise} промис запроса
*/
- async getBoards(data) {
+ async getBoards() {
const options = {
method: 'get',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: JSON.stringify(data),
};
return this.httpRequest(
`http://${this.BackendUrl}:${this.BackendPort}/${this._endpoints.board}`,
@@ -359,6 +360,101 @@ class Network {
options);
}
+ /**
+ * Метод, реализующий GET /api/usersearch/card/:cid/:search_text
+ * @param {String} searchString - строка для поиска
+ * @param {Number} cid - id карточки
+ */
+ async searchCardMembers(searchString, cid) {
+ const options = {
+ method: 'get',
+ };
+ return this.httpRequest(
+ `http://${this.BackendUrl}:${this.BackendPort}/${this._endpoints.usersearch.card}` +
+ `/${cid}/${searchString}`,
+ options);
+ }
+
+ /**
+ * Метод, реализующий GET /api/usersearch/board/:bid/:search_text
+ * @param {String} searchString - строка для поиска
+ * @param {Number} bid - id доски
+ */
+ async searchBoardMembers(searchString, bid) {
+ const options = {
+ method: 'get',
+ };
+ return this.httpRequest(
+ `http://${this.BackendUrl}:${this.BackendPort}/${this._endpoints.usersearch.board}` +
+ `/${bid}/${searchString}`,
+ options);
+ }
+
+ /**
+ * Метод, реализующий GET /api/usersearch/team/:tid/:search_text
+ * @param {String} searchString - строка для поиска
+ * @param {Number} tid - id команды
+ */
+ async searchTeamMembers(searchString, tid) {
+ const options = {
+ method: 'get',
+ };
+ return this.httpRequest(
+ `http://${this.BackendUrl}:${this.BackendPort}/${this._endpoints.usersearch.team}` +
+ `/${tid}/${searchString}`,
+ options);
+ }
+
+ /**
+ * Метод, реализующий PUT /api/teams/:tid/toggleuser/:uid
+ * @param {Number} tid - id команды
+ * @param {Number} uid - id переключаемого пользователя
+ */
+ async toggleTeamMember(tid, uid) {
+ const options = {
+ method: 'put',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ };
+ return this.httpRequest(
+ `http://${this.BackendUrl}:${this.BackendPort}/${this._endpoints.team}/${tid}` +
+ `/toggleuser/${uid}`, options);
+ }
+
+ /**
+ * Метод, реализующий PUT /api/boards/:bid/toggleuser/:uid
+ * @param {Number} bid - id доски
+ * @param {Number} uid - id переключаемого пользователя
+ */
+ async toggleBoardMember(bid, uid) {
+ const options = {
+ method: 'put',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ };
+ return this.httpRequest(
+ `http://${this.BackendUrl}:${this.BackendPort}/${this._endpoints.board}/${bid}` +
+ `/toggleuser/${uid}`, options);
+ }
+
+ /**
+ * Метод, реализующий PUT /api/cards/:cid/toggleuser/:uid
+ * @param {Number} cid - id карточки
+ * @param {Number} uid - id переключаемого пользователя
+ */
+ async toggleCardMember(cid, uid) {
+ const options = {
+ method: 'put',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ };
+ return this.httpRequest(
+ `http://${this.BackendUrl}:${this.BackendPort}/${this._endpoints.card}/${cid}` +
+ `/toggleuser/${uid}`, options);
+ }
/**
* Метод, реализующий запрос POST /api/checkLists.
* @param {object} data полезная нагрузка запроса
diff --git a/src/modules/Validator/Validator.js b/src/modules/Validator/Validator.js
index 357f821f..0e345674 100644
--- a/src/modules/Validator/Validator.js
+++ b/src/modules/Validator/Validator.js
@@ -145,9 +145,6 @@ export default class Validator {
* @return {String} 'invalid' или 'valid'
*/
validateDeadline(deadline, deadlineCheck) {
- /* if (!deadline) {
- throw new Error('DeadlinePreview: некорректный deadline.');
- } */
const deadlineDate = new Date(deadline);
const timeNow = new Date();
@@ -159,4 +156,20 @@ export default class Validator {
'invalid' :
'valid';
}
+
+ /**
+ * Метод, валидирующий дедлайн
+ * @param {String} deadline
+ * @return {String} дедлайн, максимальный дедлайн или пустая строка.
+ */
+ validateDeadlineInput(deadline) {
+ if (deadline) {
+ const date = new Date(deadline);
+ if (isNaN(date)) {
+ return '3000-12-31T23:59';
+ }
+ return deadline;
+ }
+ return '';
+ }
}
diff --git a/src/popups/AddUser/AddUserPopUp.hbs b/src/popups/AddUser/AddUserPopUp.hbs
new file mode 100644
index 00000000..cae49ef6
--- /dev/null
+++ b/src/popups/AddUser/AddUserPopUp.hbs
@@ -0,0 +1,34 @@
+{{# if visible }}
+
+{{/if}}
diff --git a/src/popups/AddUser/AddUserPopUp.js b/src/popups/AddUser/AddUserPopUp.js
new file mode 100644
index 00000000..621f9180
--- /dev/null
+++ b/src/popups/AddUser/AddUserPopUp.js
@@ -0,0 +1,80 @@
+// BaseComponent
+import BaseComponent from '../../components/BaseComponent.js';
+
+// Шаблон
+import template from './AddUserPopUp.hbs';
+
+// Стили
+import './AddUserPopUp.scss';
+
+/**
+ * Класс popup окна добавления пользователя.
+ */
+export default class AddUserPopUp extends BaseComponent {
+ /**
+ * Конструирует объект AddUserPopUp
+ * @param {Object} callbacks - набор callback'ов, вызываемых на события попапа
+ */
+ constructor(callbacks) {
+ super(null, template);
+ this._callbacks = callbacks;
+ this._elements = {};
+ }
+
+ /**
+ * Метод сохраняет ссылки на элементы popup'a
+ * @private
+ */
+ _registerPopUpElements() {
+ if (!this.context.visible) {
+ this._elements = {};
+ return;
+ }
+ this._elements = {
+ wrapper: document.getElementById('addUserPopUpWrapperId'),
+ input: document.getElementById('addUserPopUpSearchInputId'),
+ users: document.querySelectorAll('.search-result'),
+ closeBtn: document.getElementById('addUserPopUpCloseId'),
+ };
+ }
+
+ /**
+ * Метод регестрирует callback
+ * @private
+ */
+ addEventListeners() {
+ this._registerPopUpElements();
+ this._setUpSearchInput();
+ super.addEventListeners();
+ this._elements.wrapper?.addEventListener('click', this._callbacks.onClose);
+ this._elements.closeBtn?.addEventListener('click', this._callbacks.onClose);
+ this._elements.input?.addEventListener('input', this._callbacks.onInput);
+ this._elements.users?.forEach((user)=>{
+ user.addEventListener('click', this._callbacks.onUserClick);
+ });
+ };
+
+ /**
+ * Метод удаляет все ранее зарегестрированные callback
+ * @private
+ */
+ removeEventListeners() {
+ super.removeEventListeners();
+ this._elements.wrapper?.removeEventListener('click', this._callbacks.onClose);
+ this._elements.closeBtn?.removeEventListener('click', this._callbacks.onClose);
+ this._elements.input?.removeEventListener('click', this._callbacks.onInput);
+ this._elements.users?.forEach((user)=>{
+ user.removeEventListener('click', this._callbacks.onUserClick);
+ });
+ }
+
+ /**
+ * Метод устанавливает курсор в конце строки внутри input тэга
+ * @private
+ */
+ _setUpSearchInput() {
+ this._elements.input?.focus();
+ this._elements.input?.setSelectionRange(this._elements.input.value.length,
+ this._elements.input.value.length);
+ }
+}
diff --git a/src/popups/AddUser/AddUserPopUp.scss b/src/popups/AddUser/AddUserPopUp.scss
new file mode 100644
index 00000000..c2e99d0f
--- /dev/null
+++ b/src/popups/AddUser/AddUserPopUp.scss
@@ -0,0 +1,56 @@
+@import '../../styles/scss/Fonts';
+@import '../../styles/scss/Common';
+@import '../../styles/scss/Constants';
+
+.search-result-wrapper {
+ max-height: 200px;
+ display: flex;
+ flex-direction: column;
+ row-gap: 2px;
+ overflow-x: hidden;
+ overflow-y: auto;
+}
+
+.search-result {
+
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ padding: 2px 5px;
+ border-radius: $common-radius;
+ background-color: $secondary-bg-color;
+ border: 4px solid $tertiary-bg-color;
+
+ &:focus,
+ &:hover {
+ cursor: pointer;
+ box-shadow: 0 0 10px 2px $default-box-shadow-color;
+ }
+}
+
+.user-name {
+ margin-left: 5px;
+ flex-grow: 1;
+}
+
+.check {
+ @extend .material-icon-base;
+ &::before {
+ content: 'check';
+ }
+
+ &_padded {
+ padding: 0 10px;
+ }
+}
+
+.add {
+ @extend .material-icon-base;
+ &::before {
+ content: 'add';
+ }
+
+ &_padded {
+ padding: 0 10px;
+ }
+}
diff --git a/src/popups/BoardSetting/BoardSettingPopUp.scss b/src/popups/BoardSetting/BoardSettingPopUp.scss
index f3b52d2a..9163e055 100644
--- a/src/popups/BoardSetting/BoardSettingPopUp.scss
+++ b/src/popups/BoardSetting/BoardSettingPopUp.scss
@@ -9,7 +9,7 @@
width: 100%;
gap: 20px;
}
-
-.popup-content_sized {
- min-width: 450px;
-}
+//
+//.popup-content_sized {
+// min-width: 450px;
+//}
diff --git a/src/popups/Card/CardPopUp.hbs b/src/popups/Card/CardPopUp.hbs
index 20c68aa5..221e05c4 100644
--- a/src/popups/Card/CardPopUp.hbs
+++ b/src/popups/Card/CardPopUp.hbs
@@ -89,6 +89,7 @@
{{errors}}
{{/if}}
{{# if edit }}
+
{{ else }}
diff --git a/src/popups/Card/CardPopUp.js b/src/popups/Card/CardPopUp.js
index a40c498c..026eca57 100644
--- a/src/popups/Card/CardPopUp.js
+++ b/src/popups/Card/CardPopUp.js
@@ -38,6 +38,7 @@ export default class CardPopUp extends BaseComponent {
card_name: document.getElementById('cardPopUpTitleId'),
description: document.getElementById('cardPopUpDescriptionId'),
deadline: document.getElementById('cardPopUpDeadlineId'),
+ assigneeBtn: document.getElementById('cardPopUpAddAssigneeBtnId'),
checkList: {
createBtn: document.getElementById('cardPopUpAddCheckListBtnId'),
editBtn: document.querySelectorAll('.checklist-edit'),
@@ -66,6 +67,7 @@ export default class CardPopUp extends BaseComponent {
this._elements.createBtn?.addEventListener('click', this._onCreate);
this._elements.saveBtn?.addEventListener('click', this._onSave);
this._elements.deadline?.addEventListener('click', this._onDeadlineClick);
+ this._elements.assigneeBtn?.addEventListener('click', this._onAssigneeClick);
/* Check List */
this._elements.checkList.createBtn?.addEventListener('click', this._onCreateCheckList);
@@ -108,6 +110,7 @@ export default class CardPopUp extends BaseComponent {
this._elements.createBtn?.removeEventListener('click', this._onCreate);
this._elements.saveBtn?.removeEventListener('click', this._onSave);
this._elements.deadline?.removeEventListener('click', this._onDeadlineClick);
+ this._elements.assigneeBtn?.removeEventListener('click', this._onAssigneeClick);
/* Check List */
this._elements.checkList.createBtn?.removeEventListener('click', this._onCreateCheckList);
@@ -145,6 +148,7 @@ export default class CardPopUp extends BaseComponent {
this._onCreate = this._onCreate.bind(this);
this._onSave = this._onSave.bind(this);
this._onDeadlineClick = this._onDeadlineClick.bind(this);
+ this._onAssigneeClick = this._onAssigneeClick.bind(this);
/* CheckList */
this._onCreateCheckList = this._onCreateCheckList.bind(this);
@@ -179,11 +183,6 @@ export default class CardPopUp extends BaseComponent {
*/
_onSave(event) {
event.preventDefault();
- const date = new Date(this._elements.deadline.value);
- if (isNaN(date)) {
- this._elements.deadline.value = '3000-12-31T23:59';
- }
-
const data = {
position: parseInt(this._elements.positionSelect.value, 10),
card_name: this._elements.card_name.value,
@@ -198,15 +197,11 @@ export default class CardPopUp extends BaseComponent {
/**
* Callback, вызываемый при нажатии "Создать"
- * @param {Event} event объект события
+ * @param {Event} event - объект события
* @private
*/
_onCreate(event) {
event.preventDefault();
- const date = new Date(this._elements.deadline.value);
- if (isNaN(date)) {
- this._elements.deadline.value = '3000-12-31T23:59';
- }
cardActions.createCard(
this._elements.card_name.value,
this._elements.description.value,
@@ -231,6 +226,15 @@ export default class CardPopUp extends BaseComponent {
}
}
+ /**
+ * Callback, вызываемый при нажатии "Участники"
+ * @param {Event} event - объект события
+ * @private
+ */
+ _onAssigneeClick(event) {
+ event.preventDefault();
+ cardActions.showAddCardAssigneePopUp();
+ }
/* CheckList */
/**
* CallBack на создание чеклиста
diff --git a/src/popups/CreateBoard/CreateBoardPopUp.hbs b/src/popups/CreateBoard/CreateBoardPopUp.hbs
index e12c2e3b..937bdad4 100644
--- a/src/popups/CreateBoard/CreateBoardPopUp.hbs
+++ b/src/popups/CreateBoard/CreateBoardPopUp.hbs
@@ -17,7 +17,7 @@