diff --git a/gulpfile.js b/gulpfile.js new file mode 100644 index 0000000..1fe13be --- /dev/null +++ b/gulpfile.js @@ -0,0 +1,38 @@ +const gulp = require('gulp'); +const sass = require('gulp-sass'); +const sourcemaps = require('gulp-sourcemaps'); +const clean = require('gulp-clean'); +const rebaseUrls = require('gulp-css-rebase-urls'); + + +gulp.task('images-clean-dist', () => + gulp.src('./public/dist/images', {read: false}) + .pipe(clean()) +); + +gulp.task('images', ['images-clean-dist'], () => + gulp.src('./src/images/**/*') + .pipe(gulp.dest('./public/dist/images')) +); + +gulp.task('images:watch', ['images'], () => + gulp.watch('./src/images/**/*', ['images']) +); + +gulp.task('sass', () => + gulp.src('./src/styles/default.scss') + .pipe(sourcemaps.init()) + .pipe(sass().on('error', sass.logError)) + .pipe(rebaseUrls()) + .pipe(sourcemaps.write('./')) + .pipe(gulp.dest('./public/dist/styles')) +); + +gulp.task('sass:watch', ['sass'], () => + gulp.watch('./src/styles/**/*.scss', ['sass']) +); + + +// common +gulp.task('build', ['sass', 'images']); +gulp.task('default', ['sass:watch', 'images:watch']); \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..0379342 --- /dev/null +++ b/package.json @@ -0,0 +1,29 @@ +{ + "name": "todo-app-with-css", + "version": "0.0.1", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "build": "gulp build", + "watch": "gulp watch", + "start": "gulp" + }, + "author": "", + "license": "MIT", + "dependencies": { + "gulp": "^3.9.1", + "gulp-clean": "^0.3.2", + "gulp-css-rebase-urls": "0.0.5", + "gulp-sass": "^3.1.0", + "gulp-sourcemaps": "^2.5.1" + }, + "devDependencies": { + "assert": "^1.4.1", + "babel": "^6.23.0", + "babel-plugin-transform-es2015-modules-commonjs": "^6.24.1", + "babel-register": "^6.24.0", + "mocha": "^3.4.2", + "webpack": "^2.6.1" + } +} diff --git a/public/dist/scripts/bundle.js b/public/dist/scripts/bundle.js new file mode 100644 index 0000000..f004638 --- /dev/null +++ b/public/dist/scripts/bundle.js @@ -0,0 +1,898 @@ +/******/ (function(modules) { // webpackBootstrap +/******/ // The module cache +/******/ var installedModules = {}; +/******/ +/******/ // The require function +/******/ function __webpack_require__(moduleId) { +/******/ +/******/ // Check if module is in cache +/******/ if(installedModules[moduleId]) { +/******/ return installedModules[moduleId].exports; +/******/ } +/******/ // Create a new module (and put it into the cache) +/******/ var module = installedModules[moduleId] = { +/******/ i: moduleId, +/******/ l: false, +/******/ exports: {} +/******/ }; +/******/ +/******/ // Execute the module function +/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); +/******/ +/******/ // Flag the module as loaded +/******/ module.l = true; +/******/ +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } +/******/ +/******/ +/******/ // expose the modules object (__webpack_modules__) +/******/ __webpack_require__.m = modules; +/******/ +/******/ // expose the module cache +/******/ __webpack_require__.c = installedModules; +/******/ +/******/ // identity function for calling harmony imports with the correct context +/******/ __webpack_require__.i = function(value) { return value; }; +/******/ +/******/ // define getter function for harmony exports +/******/ __webpack_require__.d = function(exports, name, getter) { +/******/ if(!__webpack_require__.o(exports, name)) { +/******/ Object.defineProperty(exports, name, { +/******/ configurable: false, +/******/ enumerable: true, +/******/ get: getter +/******/ }); +/******/ } +/******/ }; +/******/ +/******/ // getDefaultExport function for compatibility with non-harmony modules +/******/ __webpack_require__.n = function(module) { +/******/ var getter = module && module.__esModule ? +/******/ function getDefault() { return module['default']; } : +/******/ function getModuleExports() { return module; }; +/******/ __webpack_require__.d(getter, 'a', getter); +/******/ return getter; +/******/ }; +/******/ +/******/ // Object.prototype.hasOwnProperty.call +/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; +/******/ +/******/ // __webpack_public_path__ +/******/ __webpack_require__.p = ""; +/******/ +/******/ // Load entry module and return exports +/******/ return __webpack_require__(__webpack_require__.s = 9); +/******/ }) +/************************************************************************/ +/******/ ([ +/* 0 */ +/***/ (function(module, exports) { + +function EventableConstructor() {} + +var eventablePrototype = EventableConstructor.prototype; + +eventablePrototype._initEventable = function () { + this._eventable_registry = {}; +}; + +function getEventSubscribers(eventable, eventName, needCreate) { + var registry = eventable._eventable_registry; + + if (eventName in registry) { + return registry[eventName]; + + } else if (needCreate) { + return registry[eventName] = []; + } + + return null; +} + +eventablePrototype.on = function (eventName, handler, ctx) { + var subscribers = getEventSubscribers(this, eventName, true); + + subscribers.push({ + handler: handler, + ctx: ctx + }); + + return this; +}; + +eventablePrototype.off = function (eventName, handler, ctx) { + var subscribers = getEventSubscribers(this, eventName); + + if (subscribers) { + for (var i = subscribers.length; i-- ;) { + if ((subscribers[i].handler === handler) + && (subscribers[i].ctx === ctx) + ) { + subscribers.splice(i, 1); + return this; + } + } + } + + return this; +}; + +eventablePrototype.trigger = function (eventName, data) { + var subscribers = getEventSubscribers(this, eventName); + + if (subscribers) { + var subscribersCopy = subscribers.slice(); + for (var i = 0, l = subscribersCopy.length; i !== l; i += 1) { + subscribersCopy[i].handler.call(subscribersCopy[i].ctx, data); + } + } + + return this; +}; + +module.exports = EventableConstructor; + +/***/ }), +/* 1 */ +/***/ (function(module, exports) { + +/** + * @param {Function} Extendable + * @param {Function} Extension + * @return {Function} Extendable + */ +function extendConstructor(Extendable, Extension) { + var extendablePrototype = Extendable.prototype; + var extensionPrototype = Extension.prototype; + + for (var p in extensionPrototype) { + extendablePrototype[p] = extensionPrototype[p]; + } + + return Extendable; +} + +module.exports = extendConstructor; + +/***/ }), +/* 2 */ +/***/ (function(module, exports) { + +function EAST_SLAVIC_ALGORITHM(number) { + var value = Math.abs(number); + + if ((value % 10 === 1) && (value % 100 !== 11)) { + return 0; + } + if ((2 <= (value % 10)) + && ((value % 10) <= 4) + && (Math.floor((value % 100) / 10) !== 1) + ) { + return 1; + } + + return 2; +} + +function ROMANO_GERMANIC_ALGORITHM(number) { + return (Math.abs(number) === 1) ? 0 : 1; +} + +var pluralAlgorithms = { + 'ru': EAST_SLAVIC_ALGORITHM, + 'en': ROMANO_GERMANIC_ALGORITHM +}; + +var l10n = { + language: 'ru', + _dictionaries: {}, + + /** + * @param {String} key + * @return {String} + * @private + */ + _getDictValue: function (key) { + var language = this.language; + + if (!(language in this._dictionaries) + || !(key in this._dictionaries[language]) + ) { + return null; + } + + return this._dictionaries[language][key]; + }, + + /** + * @param {String} languageCode + * @param {Object} dict + * @return {l10n} + */ + provideDict: function (languageCode, dict) { + this._dictionaries[languageCode] = dict; + return this; + }, + + /** + * @param {String} key + * @return {String} + */ + simple: function (key) { + var dictValue = this._getDictValue(key); + if (dictValue !== null) { + return dictValue; + } + return key; + }, + + /** + * @param {String} key + * @param {Number} number + * @return {String} + */ + plural: function (key, number) { + var dictValue = this._getDictValue(key); + if (dictValue === null) { + return key; + } + + var currentPluralAlgorithm = pluralAlgorithms[this.language] || pluralAlgorithms.ru; + var result = dictValue[currentPluralAlgorithm(number)]; + + if (result != null) { + return result; + } + return key; + + } +}; + +module.exports = l10n; + +/***/ }), +/* 3 */ +/***/ (function(module, exports, __webpack_require__) { + +var extendConstructor = __webpack_require__(1); +var Eventable = __webpack_require__(0); + +var ENTER_KEY_CODE = 13; + +var TODOS_TODO_INPUT_SELECTOR = '.js-todos-todo-input'; +var TODOS_SELECT_ALL_SELECTOR = '.js-todos-select-all'; + +/** + * @implements {EventListener} + * @extends {Eventable} + * @constructor + */ +function AddTodosConstructor() { + this._todoInput = document.querySelector(TODOS_TODO_INPUT_SELECTOR); + this._todoSelectAll = document.querySelector(TODOS_SELECT_ALL_SELECTOR); + + this._todoInput.addEventListener('keypress', this); + this._todoSelectAll.addEventListener('click', this); + + this._initEventable(); +} + +extendConstructor(AddTodosConstructor, Eventable); + +var addTodosConstructorPrototype = AddTodosConstructor.prototype; + +addTodosConstructorPrototype._markAsReadyAll = function () { + return this.trigger('markAsReadyAll'); +}; + +addTodosConstructorPrototype._addItem = function () { + var todoInputValue = this._todoInput.value.trim(); + + if (todoInputValue.length !== 0) { + this._todoInput.value = ''; + } + + return this.trigger('newTodo', { + text: todoInputValue + }); +}; + +addTodosConstructorPrototype.handleEvent = function (e) { + switch (e.type) { + case 'click': + this._markAsReadyAll(); + break; + case 'keypress': + if (e.keyCode === ENTER_KEY_CODE) { + this._addItem(); + } + break; + } +}; + +module.exports = AddTodosConstructor; + +/***/ }), +/* 4 */ +/***/ (function(module, exports, __webpack_require__) { + +var extendConstructor = __webpack_require__(1); +var getTextNode = __webpack_require__(11); +var Eventable = __webpack_require__(0); +var l10n = __webpack_require__(2); +var Filter = __webpack_require__(7); + +/** + * @constructor + * @implements {EventListener} + */ +function TodoActionsBarConstructor() { + this._initEventable(); + + this._counterNode = document.querySelector('.js-todos-actions-bar_counter'); + this._counterNodeText = getTextNode(this._counterNode); + this._clearCompletedNode = document.querySelector('.js-todos-actions-bar_clear-completed'); + + this._clearCompletedNode.addEventListener('click', this); + + this._filters = new Filter(document.querySelector('.js-todos-actions-bar_filter')); + + this._filters.on('filterSelected', this._onFilterSelected, this); +} + +extendConstructor(TodoActionsBarConstructor, Eventable); + +var todoActionsBarConstructorPrototype = TodoActionsBarConstructor.prototype; + +todoActionsBarConstructorPrototype._onFilterSelected = function (filterId) { + this.trigger('filterSelected', filterId); +}; + +/** + * @return {TodoActionsBarConstructor} + * @private + */ +todoActionsBarConstructorPrototype._clearCompleted = function () { + this.trigger('clearCompleted'); + return this; +}; + +/** + * @param {Number} count + * @return {TodoActionsBarConstructor} + */ +todoActionsBarConstructorPrototype.setItemsCount = function (count) { + this._counterNodeText.nodeValue = count + ' ' + l10n.plural('todosCountLabel', count); + return this; +}; + +/** + * @param {Event} e + */ +todoActionsBarConstructorPrototype.handleEvent = function (e) { + switch (e.type) { + case 'click': + this._clearCompleted(); + break; + } +}; + +module.exports = TodoActionsBarConstructor; + +/***/ }), +/* 5 */ +/***/ (function(module, exports, __webpack_require__) { + +var Eventable = __webpack_require__(0); +var extendConstructor = __webpack_require__(1); + +var TodoItem = __webpack_require__(8); + +var TODO_LIST_SELECTOR = '.js-todos-list'; +var itemsIdIterator = 0; + +/** + * @extends {Eventable} + * @constructor + */ +function TodoListConstructor() { + /** + * @type {Array.} + * @private + */ + this._items = []; + this._todosList = document.querySelector(TODO_LIST_SELECTOR); + this._currentFilter = 'all'; + + this._initEventable(); +} + +extendConstructor(TodoListConstructor, Eventable); + +var todoListConstructorPrototype = TodoListConstructor.prototype; + +/** + * @return {Number} + */ +todoListConstructorPrototype.getItemsCount =function () { + return this._items.length; +}; + +/** + * @param {Object} todoItemData + * @return {TodoListConstructor} + */ +todoListConstructorPrototype.createItem = function (todoItemData) { + var item = new TodoItem(Object.assign( + { + id: itemsIdIterator++, + }, + todoItemData + )); + + this._items.push(item); + + item.on('change', this._onItemChange, this) + .on('remove', this._onItemRemove, this) + .render(this._todosList); + + this.trigger('itemAdd', item); + + return this; +}; + +/** + * @return {TodoListConstructor} + */ +todoListConstructorPrototype.removeCompletedItems = function () { + var items = this._items; + var i = items.length; + + for (; i-- ;) { + if (items[i].model.isReady) { + items[i].remove(); + } + } + + return this; +}; + +/** + * @param {Number} itemId + * @return {TodoItem|null} + * @private + */ +todoListConstructorPrototype._getItemById = function (itemId) { + var items = this._items; + + for (var i = items.length; i-- ;) { + if (items[i].model.id === itemId) { + return items[i]; + } + } + + return null; +}; + +todoListConstructorPrototype._onItemChange = function (itemModel) { + this.filterShowedItems(this._currentFilter); +}; + +todoListConstructorPrototype._onItemRemove = function (itemId) { + var todoItemComponent = this._getItemById(itemId); + + if (todoItemComponent) { + todoItemComponent.off('change', this._onItemChange, this); + todoItemComponent.off('remove', this._onItemRemove, this); + var todoItemComponentIndex = this._items.indexOf(todoItemComponent); + this._items.splice(todoItemComponentIndex, 1); + this.trigger('itemDelete', todoItemComponent.model); + } + + return this; +}; + +/** + * @return {TodoListConstructor} + */ +todoListConstructorPrototype.markAsReadyAll = function () { + this._items.forEach(function (todoItem) { + todoItem.setReady(true); + }); + return this; +}; + +/** + * @param {String} filterId + * @return {TodoListConstructor} + */ +todoListConstructorPrototype.setFilter = function (filterId) { + this._currentFilter = filterId; + return this.filterShowedItems(filterId); +}; + +/** + * @param {String} filterId + * @return {TodoListConstructor} + */ +todoListConstructorPrototype.filterShowedItems = function (filterId) { + this._items.forEach(function (item) { + switch (filterId) { + case 'all': + item.show(); + break; + case 'ready': + if (item.model.isReady) { + item.show(); + } else { + item.hide(); + } + break; + case 'unready': + if (!item.model.isReady) { + item.show(); + } else { + item.hide(); + } + break; + } + }); + return this; +}; + +module.exports = TodoListConstructor; + +/***/ }), +/* 6 */ +/***/ (function(module, exports) { + +var TODOS_MAIN_SELECTOR = '.js-todos-main'; +var FULL_INTERFACE_MODIFICATOR = '__has-todos'; + +function TodoMainConstructor() { + this._todosMain = document.querySelector(TODOS_MAIN_SELECTOR); +} + +var todoMainComponentConstructorPrototype = TodoMainConstructor.prototype; + +todoMainComponentConstructorPrototype.showFullInterface = function () { + this._todosMain.classList.add(FULL_INTERFACE_MODIFICATOR); + return this; +}; + +todoMainComponentConstructorPrototype.hideFullInterface = function () { + this._todosMain.classList.remove(FULL_INTERFACE_MODIFICATOR); + return this; +}; + +module.exports = TodoMainConstructor; + +/***/ }), +/* 7 */ +/***/ (function(module, exports, __webpack_require__) { + +var Eventable = __webpack_require__(0); +var extendConstructor = __webpack_require__(1); + +var ACTIVE_FILTER_MODIFICATOR = '__active'; + +/** + * @param {HTMLElement} domRoot + * @constructor + * @implements {EventListener} + */ +function FilterConstructor(domRoot) { + this._initEventable(); + + var filters = this._filters = domRoot.querySelectorAll('.filter'); + this._currentActive = null; + + for (var i = filters.length; i-- ;) { + filters[i].addEventListener('click', this); + if (filters[i].classList.contains(ACTIVE_FILTER_MODIFICATOR)) { + this._currentActive = filters[i]; + } + } +} + +extendConstructor(FilterConstructor, Eventable); + +var filterConstructorPrototype = FilterConstructor.prototype; + +/** + * @param {HTMLElement} filterElement + * @return {FilterConstructor} + * @private + */ +filterConstructorPrototype._setFilter = function (filterElement) { + if (this._currentActive !== filterElement) { + this._currentActive.classList.remove(ACTIVE_FILTER_MODIFICATOR); + filterElement.classList.add(ACTIVE_FILTER_MODIFICATOR); + this._currentActive = filterElement; + this.trigger('filterSelected', filterElement.getAttribute('data-filter')); + } + return this; +}; + +filterConstructorPrototype.handleEvent = function (e) { + switch (e.type) { + case 'click': + this._setFilter(e.target); + break; + } +}; + +module.exports = FilterConstructor; + +/***/ }), +/* 8 */ +/***/ (function(module, exports, __webpack_require__) { + +var Eventable = __webpack_require__(0); +var extendConstructor = __webpack_require__(1); +var templatesEngine = __webpack_require__(10); + +var READY_MODIFICATOR = '__ready'; +var HIDDEN_MODIFICATOR = '__hide'; + +/** + * @param itemData + * @implements {EventListener} + * @constructor + */ +function TodoItemConstructor(itemData) { + this._initEventable(); + + var templateResult = templatesEngine.todoItem({ + text: itemData.text + }); + + this._root = templateResult.root; + this._markReady = templateResult.markReady; + this._removeAction = templateResult.removeAction; + this._text = templateResult.text; + + this.model = { + id: itemData.id, + isReady: itemData.isReady || false, + text: itemData.text + }; + + if (itemData.isReady) { + this._setReadyModificator(true); + } + + this._markReady.addEventListener('change', this); + this._removeAction.addEventListener('click', this); + this._text.addEventListener('input', this); +} + +extendConstructor(TodoItemConstructor, Eventable); + +var todoItemConstructorPrototype = TodoItemConstructor.prototype; + +/** + * @param {HTMLElement} parent + * @return {TodoItemConstructor} + */ +todoItemConstructorPrototype.render = function (parent) { + parent.appendChild(this._root); + return this; +}; + +/** + * @param {Event} e + */ +todoItemConstructorPrototype.handleEvent = function (e) { + switch (e.type) { + case 'change': + this.setReady(this._markReady.checked); + break; + case 'click': + if (e.target === this._removeAction) { + this.remove(); + } + break; + case 'input': + this.setText(this._text.innerText); + break; + } +}; + +/** + * @param {String} text + * @return {TodoItemConstructor} + */ +todoItemConstructorPrototype.setText = function (text) { + if (this.model.text !== text) { + this._text.innerHTML = text; + this.model.text = text; + this.trigger('change', this.model); + } + return this; +}; + +/** + * @param {Boolean} isReady + * @return {TodoItemConstructor} + * @private + */ +todoItemConstructorPrototype._setReadyModificator = function (isReady) { + if (isReady) { + this._root.classList.add(READY_MODIFICATOR); + } else { + this._root.classList.remove(READY_MODIFICATOR); + } + return this; +}; + +/** + * @param {Boolean} isReady + * @return {TodoItemConstructor} + */ +todoItemConstructorPrototype.setReady = function (isReady) { + if (isReady !== this.model.isReady) { + this._markReady.checked = isReady; + this.model.isReady = isReady; + this._setReadyModificator(isReady); + this.trigger('change', this.model); + } + return this; +}; + +/** + * @return {TodoItemConstructor} + */ +todoItemConstructorPrototype.remove = function () { + this._root.parentNode.removeChild(this._root); + this.trigger('remove', this.model.id); + return this; +}; + +/** + * @return {TodoItemConstructor} + */ +todoItemConstructorPrototype.show = function () { + this._root.classList.remove(HIDDEN_MODIFICATOR); + return this; +}; + +/** + * @return {TodoItemConstructor} + */ +todoItemConstructorPrototype.hide = function () { + this._root.classList.add(HIDDEN_MODIFICATOR); + return this; +}; + +module.exports = TodoItemConstructor; + +/***/ }), +/* 9 */ +/***/ (function(module, exports, __webpack_require__) { + +var l10n = __webpack_require__(2); + +var TodoMain = __webpack_require__(6); +var AddTodos = __webpack_require__(3); +var TodoList = __webpack_require__(5); +var TodoActionsBar = __webpack_require__(4); + +function init() { + var rusDictionary = { + 'todosCountLabel': ['задача', 'задачи', 'задач'] + }; + l10n.provideDict('ru', rusDictionary); + + var todoMain = new TodoMain(); + var addTodos = new AddTodos(); + var todoList = new TodoList(); + var todoActionsBar = new TodoActionsBar(); + + + addTodos + .on('newTodo', + function (todoData) { todoList.createItem(todoData); } + ) + .on('markAsReadyAll', + function () { todoList.markAsReadyAll();} + ); + + function itemsCountWatcher () { + var itemsCount = todoList.getItemsCount(); + + if (itemsCount !== 0) { + todoMain.showFullInterface(); + } + + todoActionsBar.setItemsCount(itemsCount); + } + + todoList.on('itemAdd', itemsCountWatcher) + .on('itemDelete', itemsCountWatcher); + + todoActionsBar.on( + 'clearCompleted', + function () { todoList.removeCompletedItems(); } + ); + + todoActionsBar.on('filterSelected', function (filterId) { + todoList.setFilter(filterId); + }); + +} + +document.addEventListener('DOMContentLoaded', init); + +/***/ }), +/* 10 */ +/***/ (function(module, exports) { + + +var div = document.createElement('div'); + +function getTemplateRootNode(scriptId) { + var scriptTag = document.getElementById(scriptId); + div.innerHTML = scriptTag.innerHTML; + var result = div.children[0]; + div.removeChild(result); + return result; +} + +var templatesEngine = { + todoItem: function (data) { + var root = getTemplateRootNode('todoItemTemplate'); + + var markReady = root.querySelector('.js-todo-item_mark-ready'); + var removeAction = root.querySelector('.js-todo-item_remove-action'); + var text = root.querySelector('.js-todo-item_text'); + + if (data.text) { + text.innerText = data.text; + } + + if (data.isReady) { + markReady.checked = true; + } + + return { + root: root, + text: text, + markReady: markReady, + removeAction: removeAction + }; + } +}; + +module.exports = templatesEngine; + +/***/ }), +/* 11 */ +/***/ (function(module, exports) { + +/** + * @param {HTMLElement} node + * @return {Node} + */ +function getTextNode(node) { + var childs = node.childNodes; + var i = 0; + var l = childs.length; + + for (; i !== l; i += 1) { + if (childs[i].nodeName === '#text') { + return childs[i]; + } + } + + var result = document.createTextNode(''); + node.appendChild(result); + return result; +} + +module.exports = getTextNode; + +/***/ }) +/******/ ]); \ No newline at end of file diff --git a/public/dist/styles/default.css b/public/dist/styles/default.css new file mode 100644 index 0000000..bc6d7bd --- /dev/null +++ b/public/dist/styles/default.css @@ -0,0 +1,509 @@ +/* Reset.css +---------------------------------------------------------------------------------- */ + +html, +body { + font-size: 15px; +} + +textarea, +input, +select { + font-size: 15px; +} + +body, +table, +td, +ul, +menu, +li, +div, +span, +img, +a, +form, +p, +h1 { + margin: 0; + padding: 0; +} + +body, +textarea, +input, +select, +button { + color: #333; + font-family: "Helvetica Neue", Arial, sans-serif; +} + +body { + background: #fff; +} + +ul, +menu { + list-style: none; +} + +img { + border: 0; +} + +table { + border-collapse: collapse; + border-width: 0; +} + +a { + cursor: pointer; + color: #333; +} + +/* /Reset.css +---------------------------------------------------------------------------------- */ + +body { + background: #f5f5f5; +} + +.main-wrapper { + max-width: 592px; + min-width: 212px; + margin: 0 auto; +} + +.action { + position: relative; + display: inline-block; +} + +.action_target { + position: absolute; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; +} + +button.action_target, +input.action_target { + margin: 0; + padding: 0; + background: none; + border: 0; + outline: none; + opacity: 0; +} + +.input-checkbox { + display: inline-block; + position: relative; + width: 36px; + min-width: 36px; + height: 36px; +} + +.input-checkbox_target { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + margin: 0; + padding: 0; + background: none; + border: 0; + outline: none; + opacity: 0; + z-index: 1; +} + +.input-checkbox_visual { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + border-radius: 18px; + box-sizing: border-box; + border: 1px solid #ccc; + transition: border-color 0.2s; +} + +.input-checkbox_target:checked ~ .input-checkbox_visual { + border-color: #5dc2af; +} + +.input-checkbox_visual_icon { + position: absolute; + width: 19px; + height: 20px; + top: 50%; + left: 50%; + margin: -9px -9px; + opacity: 0; + transition: opacity 0.2s; +} + +.input-checkbox_target:checked ~ .input-checkbox_visual .input-checkbox_visual_icon { + opacity: 1; +} + +.input-checkbox_visual_icon:before, +.input-checkbox_visual_icon:after { + content: ''; + position: absolute; +} + +.input-checkbox_visual_icon:before { + width: 11px; + height: 2px; + background: #5dc2af; + top: 13px; + left: 0; + transform: rotate(45deg); +} + +.input-checkbox_visual_icon:after { + width: 19px; + height: 2px; + background: #5dc2af; + top: 8px; + left: 3px; + transform: rotate(-65deg); +} + +.filter { + font-family: "Helvetica Neue", Arial, sans-serif; + font-size: 14px; + line-height: 18px; + background: none; + outline: none; + padding: 2px 8px; + border-radius: 4px; + font-weight: 200; + border: 0; + box-sizing: border-box; +} + +.filter.__active { + border: 1px solid #ebdfdf; + padding-top: 1px; + padding-bottom: 1px; + padding-left: 7px; + padding-right: 7px; +} + +.todos-title { + font-family: 'Helvetica Neue', Arial, sans-serif; + font-size: 100px; + font-weight: 100; + text-transform: lowercase; + text-align: center; + color: rgba(175, 47, 47, 0.15); +} + +.todos-main { + background: #fff; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); + position: relative; + margin: 0 16px 16px; +} + +.todos-main:before, +.todos-main:after { + position: absolute; + background: #fff; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); + top: 100%; + z-index: -1; +} + +.todos-main:before { + height: 8px; + left: 4px; + right: 4px; +} + +.todos-main:after { + height: 4px; + left: 2px; + right: 2px; +} + +.todos-main.__has-todos:before, +.todos-main.__has-todos:after { + content: ''; +} + +.todos-main .todos-list, +.todos-main .todos-actions-bar { + display: none; +} + +.todos-main.__has-todos .todos-list, +.todos-main.__has-todos .todos-actions-bar { + display: block; +} + +.todos-add { + height: 64px; +} + +.todos-add:before, +.todos-add:after { + content: ''; + display: table; +} + +.todos-add:after { + clear: both; +} + +.todo-add_select-all-action { + width: 24px; + height: 24px; + float: left; + margin: 20px 0 0 16px; + opacity: 0; + transition: opacity .2s; +} + +.todos-main .todo-add_select-all-action { + visibility: hidden; +} + +.todos-main.__has-todos .todo-add_select-all-action { + visibility: visible; + opacity: 1; +} + +.todo-add_select-all-action .action_visual { + width: 24px; + height: 12px; + overflow: hidden; + position: absolute; + top: 50%; + margin-top: -6px; +} + +.todo-add_select-all-action .action_visual:before, +.todo-add_select-all-action .action_visual:after { + content: ''; + position: absolute; + height: 3px; + background: #737373; + width: 100%; +} + +.todo-add_select-all-action .action_visual:before { + transform: rotate(32.5deg); + left: -9px; + top: 2px; +} + +.todo-add_select-all-action .action_visual:after { + transform: rotate(-32.5deg); + left: 9px; + top: 2px; +} + +.todo-add_input-w { + overflow: hidden; +} + +.todo-add_input { + width: 100%; + box-sizing: border-box; + font-size: 24px; + height: 64px; + padding: 16px; + border: 0; + outline: 0; + font-weight: 300; + color: #666; +} + +.todo-add_input::-webkit-input-placeholder { + color: #ccc; + font-style: italic; +} + +.todo-add_input:-moz-placeholder { + color: #ccc; + font-style: italic; +} + +.todo-add_input::-moz-placeholder { + color: #ccc; + font-style: italic; +} + +.todo-add_input:-ms-input-placeholder { + color: #ccc; + font-style: italic; +} + +.todo-item { + padding: 12px; + border-bottom: 1px solid #e6e6e6; + background: #fff; + display: flex; +} + +.todo-item:before, +.todo-item:after { + content: ''; + display: table; +} + +.todo-item:after { + clear: both; +} + +.hover .todo-item .todo-item_remove-action { + opacity: 0; + transition: opacity .2s; +} + +.hover .todo-item:hover { + background: #fafafa; +} + +.hover .todo-item:hover .todo-item_remove-action { + opacity: 1; +} + +.todo-item.__hide { + display: none; +} + +.todo-item:first-child, +.todo-item.__hide:first-child ~ .todo-item { + border-top: 1px solid #e6e6e6; +} + +.todo-item.__hide:first-child ~ .todo-item:not(.__hide) ~ .todo-item { + border-top: 0; +} + +.todo-item_ready-checker { + float: left; + align-self: center; +} + +.todo-item_remove-action { + float: right; + margin-top: 12px; + margin-bottom: 12px; + order: 1; + align-self: flex-end; +} + +.todo-item_remove-action .action_visual { + width: 16px; + height: 16px; + min-width: 16px; + position: relative; + overflow: hidden; +} + +.todo-item_remove-action .action_visual:before, +.todo-item_remove-action .action_visual:after { + content: ''; + position: absolute; + width: 21px; + height: 2px; + background: #ebdfdf; + transform-origin: 0 0; +} + +.todo-item_remove-action .action_visual:before { + transform: rotate(45deg); + left: 1px; +} + +.todo-item_remove-action .action_visual:after { + transform: rotate(-45deg); + top: 15px; +} + +.todo-item_text-w { + overflow: hidden; + flex: 1 0; +} + +.todo-item_text { + outline: none; + border: 0; + font-family: "Helvetica Neue", Arial, sans-serif; + font-size: 24px; + line-height: 28px; + font-weight: 300; + padding: 4px 12px; + overflow: hidden; +} + +.todo-item.__ready .todo-item_text { + text-decoration: line-through; +} + +.todos-actions-bar { + padding: 4px 12px; + text-align: center; +} + +.todos-actions-bar:before, +.todos-actions-bar:after { + content: ''; + display: table; +} + +.todos-actions-bar:after { + clear: both; +} + +.todos-actions-bar_counter { + font-family: "Helvetica Neue", Arial, sans-serif; + font-size: 14px; + line-height: 18px; + font-weight: 200; + padding: 2px 0; + float: left; + margin: 8px 16px 8px 0; +} + +.todos-filters { + white-space: nowrap; + display: inline-block; + margin: 8px 0; +} + +.todos-filters .filter { + margin: 0 4px; +} + +.todos-filters .filter:first-child { + margin-left: 0; +} + +.todos-filters .filter:last-child { + margin-right: 0; +} + +.todos-actions-bar_clear-completed { + font-family: "Helvetica Neue", Arial, sans-serif; + font-size: 14px; + line-height: 18px; + font-weight: 200; + margin: 8px 0 8px 16px; + padding: 2px 0; + border: none; + background: 0; + outline: none; + float: right; +} +/*# sourceMappingURL=default.css.map */ diff --git a/public/dist/styles/default.css.map b/public/dist/styles/default.css.map new file mode 100644 index 0000000..2e9b35b --- /dev/null +++ b/public/dist/styles/default.css.map @@ -0,0 +1 @@ +{"version":3,"file":"default.css","sources":["default.scss","reset.scss","config/globals.scss","config/vars/color.scss","config/vars/fonts.scss","config/vars/font-size.scss","config/vars/layout.scss","config/vars/offset.scss","config/mixins/clearfix.scss","config/mixins/text.scss","config/mixins/input.scss","config/mixins/positin.scss","config/mixins/media.scss","config/mixins/hover.scss","layout.scss","base/action/action.scss","base/input-checkbox/input-checkbox.scss","base/filter/filter.scss","project/todos-title/todos-title.scss","project/todos-main/todos-main.scss","project/todos-add/todos-add.scss","project/todos-list/todos-list.scss","project/todos-list/todo-item.scss","project/todos-actions-bar/todos-actions-bar.scss"],"sourcesContent":["@import \"reset\";\n\n\n@import \"layout\";\n\n\n@import \"base/action/action\";\n@import \"base/input-checkbox/input-checkbox\";\n@import \"base/filter/filter\";\n\n\n@import \"project/todos-title/todos-title\";\n@import \"project/todos-main/todos-main\";\n@import \"project/todos-add/todos-add\";\n\n@import \"project/todos-list/todos-list\";\n@import \"project/todos-list/todo-item\";\n\n@import \"project/todos-actions-bar/todos-actions-bar\";","/* Reset.css\n---------------------------------------------------------------------------------- */\n\n@import \"config/globals\";\n\nhtml, body { font-size: $fs_normal; }\n\ntextarea,\ninput,\nselect {\n font-size: $fs_normal;\n }\n\nbody, table, td, ul, menu, li, div, span, img, a, form, p, h1 {\n margin: 0;\n padding: 0;\n }\n\nbody, textarea, input, select, button {\n color: $c-gray-dark;\n font-family: $font-main;\n }\n\nbody { background: $c-white; }\n\n\nul, menu { list-style: none; }\n\nimg { border: 0; }\n\ntable {\n border-collapse: collapse;\n border-width: 0;\n }\n\na {\n cursor: pointer;\n color: $c-gray-dark;\n }\n\n/* /Reset.css\n---------------------------------------------------------------------------------- */","@import \"vars/color\";\r\n@import \"vars/fonts\";\r\n@import \"vars/font-size\";\r\n@import \"vars/layout\";\r\n@import \"vars/offset\";\r\n\r\n@import \"mixins/clearfix\";\r\n@import \"mixins/text\";\r\n@import \"mixins/input\";\r\n@import \"mixins/positin\";\r\n@import \"mixins/media\";\r\n@import \"mixins/hover\";\r\n","$c-white: #fff;\r\n$c-gray: #666;\r\n$c-gray-dark: #333;\r\n$c-gray-light: #ddd;\r\n$c-light: #999;\r\n$c-ultra-light: #ccc;\r\n$c-orange: #eb722e;\r\n$c-error: #ff5756;\r\n\r\n$c-main-bg: #f5f5f5;\r\n$c-title: rgba(#af2f2f, .15);\r\n$c-todos-main-bg: $c-white;\r\n$c-common-border: #e6e6e6;\r\n\r\n$c-micro-elements: #ebdfdf;","$font-main: 'Helvetica Neue', Arial, sans-serif;","$fs_normal: 15px;\r\n$fs_little: 14px;\r\n$fs_large: 18px;\r\n$fs_x-large: 20px;\r\n$fs_xx-large: 24px;\r\n$fs_small: 13px;\r\n$fs_x-small: 12px;\r\n$fs_xx-small: 11px;\r\n\r\n$lh_normal: 20px;\r\n$lh_little: 18px;\r\n$lh_large: 22px;\r\n$lh_x-large: 24px;\r\n$lh_xx-large: 28px;\r\n$lh_small: 17px;\r\n$lh_x-small: 17px;\r\n$lh_xx-small: 15px;\r\n","$main-wrapper-width: 592px;\n$main-wrapper-min-width: 212px;","$offset: 4px;\r\n$offset_half: $offset / 2;\r\n$offset_2x: $offset * 2;\r\n$offset_3x: $offset * 3;\r\n$offset_4x: $offset * 4;\r\n$offset_5x: $offset * 5;\r\n$offset_6x: $offset * 6;\r\n$offset_7x: $offset * 7;\r\n$offset_8x: $offset * 8;\r\n$offset_9x: $offset * 9;\r\n$offset_10x: $offset * 10;\r\n","//\r\n// clearfix - modern way\r\n\r\n@mixin clearfix() {\r\n &:before,\r\n &:after {\r\n content: '';\r\n display: table;\r\n }\r\n\r\n &:after {\r\n clear: both;\r\n }\r\n }","@mixin ellipsisTextBlock() {\r\n max-width: 100%;\r\n overflow: hidden;\r\n white-space: nowrap;\r\n text-overflow: ellipsis;\r\n box-sizing: border-box;\r\n }","@mixin reset-input() {\n margin: 0;\n padding: 0;\n background: none;\n border: 0;\n outline: none;\n }\n\n@mixin placeholder() {\n &::-webkit-input-placeholder {\n @content\n }\n\n &:-moz-placeholder {\n @content\n }\n\n &::-moz-placeholder {\n @content\n }\n\n &:-ms-input-placeholder {\n @content\n }\n }","@mixin absolute-full() {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n }\n","@mixin retinaMedia {\n @media (-webkit-min-device-pixel-ratio: 1.5), (min-resolution: 120dpi), (min-resolution: 1.5dppx) {\n @content;\n }\n }\n","@mixin not-hovered() {\n .hover & {\n @content\n }\n }\n\n@mixin hover() {\n .hover &:hover {\n @content\n }\n }","@import \"config/globals\";\n\nbody {\n background: $c-main-bg;\n }\n\n.main-wrapper {\n max-width: $main-wrapper-width;\n min-width: $main-wrapper-min-width;\n margin: 0 auto;\n }","@import \"../../config/globals\";\n\n\n.action {\n position: relative;\n display: inline-block;\n }\n\n .action_target {\n position: absolute;\n @include absolute-full();\n }\n button.action_target, input.action_target {\n @include reset-input();\n opacity: 0;\n }","@import \"../../config/globals\";\n\n\n$checkbox-size: 36px;\n\n$c-checkbox-active: #5dc2af;\n$c-checkbox-animation-time: .2s;\n\n\n.input-checkbox {\n display: inline-block;\n position: relative;\n width: $checkbox-size;\n min-width: $checkbox-size;\n height: $checkbox-size;\n }\n\n .input-checkbox_target {\n @include absolute-full();\n @include reset-input();\n opacity: 0;\n z-index: 1;\n }\n\n .input-checkbox_visual {\n @include absolute-full();\n border-radius: $checkbox-size / 2;\n box-sizing: border-box;\n border: 1px solid $c-ultra-light;\n transition: border-color $c-checkbox-animation-time;\n }\n .input-checkbox_target:checked ~ .input-checkbox_visual {\n border-color: $c-checkbox-active;\n }\n\n .input-checkbox_visual_icon {\n position: absolute;\n width: 19px;\n height: 20px;\n top: 50%;\n left: 50%;\n margin: -9px -9px;\n opacity: 0;\n transition: opacity $c-checkbox-animation-time;\n }\n .input-checkbox_target:checked ~ .input-checkbox_visual .input-checkbox_visual_icon {\n opacity: 1;\n }\n .input-checkbox_visual_icon:before,\n .input-checkbox_visual_icon:after {\n content: '';\n position: absolute;\n }\n\n .input-checkbox_visual_icon:before {\n width: 11px;\n height: 2px;\n background: $c-checkbox-active;\n\n top: 13px;\n left: 0;\n transform: rotate(45deg);\n }\n\n .input-checkbox_visual_icon:after {\n width: 19px;\n height: 2px;\n background: $c-checkbox-active;\n\n top: 8px;\n left: 3px;\n transform: rotate(-65deg);\n }\n","@import \"../../config/globals\";\n\n\n.filter {\n font-family: $font-main;\n font-size: $fs_little;\n line-height: $lh_little;\n background: none;\n outline: none;\n padding: $offset_half $offset_2x;\n border-radius: $offset;\n font-weight: 200;\n border: 0;\n box-sizing: border-box;\n }\n .filter.__active {\n border: 1px solid $c-micro-elements;\n padding-top: ($offset_half - 1px);\n padding-bottom: ($offset_half - 1px);\n padding-left: ($offset_2x - 1px);\n padding-right: ($offset_2x - 1px);\n }\n\n","@import \"../../config/globals\";\n\n.todos-title {\n font-family: 'Helvetica Neue', Arial, sans-serif;\n font-size: 100px;\n font-weight: 100;\n text-transform: lowercase;\n text-align: center;\n color: $c-title;\n }","@import \"../../config/globals\";\n\n.todos-main {\n background: $c-todos-main-bg;\n box-shadow: 0 2px 4px rgba(#000, .2);\n position: relative;\n margin: 0 $offset_4x $offset_4x;\n }\n .todos-main:before,\n .todos-main:after {\n position: absolute;\n background: #fff;\n box-shadow: 0 2px 4px rgba(#000, .2);\n top: 100%;\n z-index: -1;\n }\n .todos-main:before {\n height: 8px;\n left: 4px;\n right: 4px;\n }\n .todos-main:after {\n height: 4px;\n left: 2px;\n right: 2px;\n }\n .todos-main.__has-todos:before,\n .todos-main.__has-todos:after {\n content: '';\n }\n\n .todos-main .todos-list,\n .todos-main .todos-actions-bar {\n display: none;\n }\n .todos-main.__has-todos .todos-list,\n .todos-main.__has-todos .todos-actions-bar {\n display: block;\n }\n\n","@import \"../../config/globals\";\n\n.todos-add {\n height: 64px;\n @include clearfix();\n }\n\n .todo-add_select-all-action {\n width: 24px;\n height: 24px;\n float: left;\n margin: $offset_5x 0 0 $offset_4x;\n opacity: 0;\n transition: opacity .2s;\n }\n .todos-main .todo-add_select-all-action {\n visibility: hidden;\n }\n .todos-main.__has-todos .todo-add_select-all-action {\n visibility: visible;\n opacity: 1;\n }\n\n .todo-add_select-all-action .action_visual {\n width: 24px;\n height: 12px;\n overflow: hidden;\n position: absolute;\n top: 50%;\n margin-top: -6px;\n }\n .todo-add_select-all-action .action_visual:before,\n .todo-add_select-all-action .action_visual:after {\n content: '';\n position: absolute;\n height: 3px;\n background: #737373;\n width: 100%;\n }\n .todo-add_select-all-action .action_visual:before {\n transform: rotate(32.5deg);\n left: -9px;\n top: 2px;\n }\n .todo-add_select-all-action .action_visual:after {\n transform: rotate(-32.5deg);\n left: 9px;\n top: 2px;\n }\n\n .todo-add_input-w {\n overflow: hidden;\n }\n\n .todo-add_input {\n width: 100%;\n box-sizing: border-box;\n font-size: $fs_xx-large;\n height: 64px;\n padding: $offset_4x;\n border: 0;\n outline: 0;\n font-weight: 300;\n color: $c-gray;\n\n @include placeholder() {\n color: $c-ultra-light;\n font-style: italic;\n }\n }\n\n\n","@import \"../../config/globals\";\n\n\n.todos-list {\n\n }\n\n\n\n","@import \"../../config/globals\";\r\n\r\n\r\n.todo-item {\r\n padding: $offset_3x;\r\n border-bottom: 1px solid $c-common-border;\r\n background: #fff;\r\n display: flex;\r\n\r\n @include clearfix();\r\n\r\n @include not-hovered() {\r\n .todo-item_remove-action {\r\n opacity: 0;\r\n transition: opacity .2s;\r\n }\r\n }\r\n @include hover() {\r\n background: #fafafa;\r\n\r\n .todo-item_remove-action {\r\n opacity: 1;\r\n }\r\n }\r\n }\r\n .todo-item.__hide {\r\n display: none;\r\n }\r\n .todo-item:first-child,\r\n .todo-item.__hide:first-child ~ .todo-item {\r\n border-top: 1px solid $c-common-border;\r\n }\r\n .todo-item.__hide:first-child ~ .todo-item:not(.__hide) ~ .todo-item {\r\n border-top: 0;\r\n }\r\n\r\n .todo-item_ready-checker {\r\n float: left;\r\n align-self: center;\r\n }\r\n\r\n\r\n .todo-item_remove-action {\r\n float: right;\r\n margin-top: $offset_3x;\r\n margin-bottom: $offset_3x;\r\n order: 1;\r\n align-self: flex-end;\r\n }\r\n\r\n .todo-item_remove-action .action_visual {\r\n width: 16px;\r\n height: 16px;\r\n min-width: 16px;\r\n position: relative;\r\n overflow: hidden;\r\n }\r\n .todo-item_remove-action .action_visual:before,\r\n .todo-item_remove-action .action_visual:after {\r\n content: '';\r\n position: absolute;\r\n width: 21px;\r\n height: 2px;\r\n background: $c-micro-elements;\r\n transform-origin: 0 0;\r\n }\r\n .todo-item_remove-action .action_visual:before {\r\n transform: rotate(45deg);\r\n left: 1px;\r\n }\r\n .todo-item_remove-action .action_visual:after {\r\n transform: rotate(-45deg);\r\n top: 15px;\r\n }\r\n\r\n .todo-item_text-w {\r\n overflow: hidden;\r\n flex: 1 0;\r\n }\r\n .todo-item_text {\r\n outline: none;\r\n border: 0;\r\n font-family: $font-main;\r\n font-size: $fs_xx-large;\r\n line-height: $lh_xx-large;\r\n font-weight: 300;\r\n padding: $offset $offset_3x;\r\n overflow: hidden;\r\n }\r\n .todo-item.__ready .todo-item_text {\r\n text-decoration: line-through;\r\n }\r\n","@import \"../../config/globals\";\n\n.todos-actions-bar {\n padding: $offset $offset_3x;\n @include clearfix();\n text-align: center;\n }\n\n .todos-actions-bar_counter {\n font-family: $font-main;\n font-size: $fs_little;\n line-height: $lh_little;\n font-weight: 200;\n padding: $offset_half 0;\n float: left;\n margin: $offset_2x $offset_4x $offset_2x 0;\n }\n\n .todos-filters {\n white-space: nowrap;\n display: inline-block;\n margin: $offset_2x 0;\n }\n .todos-actions-bar .todos-filters {\n //text-align: center;\n }\n\n .todos-filters .filter {\n margin: 0 $offset;\n }\n .todos-filters .filter:first-child {\n margin-left: 0;\n }\n .todos-filters .filter:last-child {\n margin-right: 0;\n }\n\n .todos-actions-bar_clear-completed {\n font-family: $font-main;\n font-size: $fs_little;\n line-height: $lh_little;\n font-weight: 200;\n margin: $offset_2x 0 $offset_2x $offset_4x;\n padding: $offset_half 0;\n border: none;\n background: 0;\n outline: none;\n float: right;\n }\n"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/public/index.html b/public/index.html new file mode 100644 index 0000000..5c1aae4 --- /dev/null +++ b/public/index.html @@ -0,0 +1,83 @@ + + + + + + Todos + + + + + +
+
+

Todos

+
+ +
+ +
+
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+ 2 задачи +
+ + + +
+ + + +
+ +
+ +
+
+ + + diff --git a/src/scripts/components/AddTodos.js b/src/scripts/components/AddTodos.js new file mode 100644 index 0000000..1425704 --- /dev/null +++ b/src/scripts/components/AddTodos.js @@ -0,0 +1,57 @@ +var extendConstructor = require('../utils/extendConstructor'); +var Eventable = require('../modules/Eventable'); + +var ENTER_KEY_CODE = 13; + +var TODOS_TODO_INPUT_SELECTOR = '.js-todos-todo-input'; +var TODOS_SELECT_ALL_SELECTOR = '.js-todos-select-all'; + +/** + * @implements {EventListener} + * @extends {Eventable} + * @constructor + */ +function AddTodosConstructor() { + this._todoInput = document.querySelector(TODOS_TODO_INPUT_SELECTOR); + this._todoSelectAll = document.querySelector(TODOS_SELECT_ALL_SELECTOR); + + this._todoInput.addEventListener('keypress', this); + this._todoSelectAll.addEventListener('click', this); + + this._initEventable(); +} + +extendConstructor(AddTodosConstructor, Eventable); + +var addTodosConstructorPrototype = AddTodosConstructor.prototype; + +addTodosConstructorPrototype._markAsReadyAll = function () { + return this.trigger('markAsReadyAll'); +}; + +addTodosConstructorPrototype._addItem = function () { + var todoInputValue = this._todoInput.value.trim(); + + if (todoInputValue.length !== 0) { + this._todoInput.value = ''; + } + + return this.trigger('newTodo', { + text: todoInputValue + }); +}; + +addTodosConstructorPrototype.handleEvent = function (e) { + switch (e.type) { + case 'click': + this._markAsReadyAll(); + break; + case 'keypress': + if (e.keyCode === ENTER_KEY_CODE) { + this._addItem(); + } + break; + } +}; + +module.exports = AddTodosConstructor; \ No newline at end of file diff --git a/src/scripts/components/Filter.js b/src/scripts/components/Filter.js new file mode 100644 index 0000000..9e543a7 --- /dev/null +++ b/src/scripts/components/Filter.js @@ -0,0 +1,52 @@ +var Eventable = require('../modules/Eventable'); +var extendConstructor = require('../utils/extendConstructor'); + +var ACTIVE_FILTER_MODIFICATOR = '__active'; + +/** + * @param {HTMLElement} domRoot + * @constructor + * @implements {EventListener} + */ +function FilterConstructor(domRoot) { + this._initEventable(); + + var filters = this._filters = domRoot.querySelectorAll('.filter'); + this._currentActive = null; + + for (var i = filters.length; i-- ;) { + filters[i].addEventListener('click', this); + if (filters[i].classList.contains(ACTIVE_FILTER_MODIFICATOR)) { + this._currentActive = filters[i]; + } + } +} + +extendConstructor(FilterConstructor, Eventable); + +var filterConstructorPrototype = FilterConstructor.prototype; + +/** + * @param {HTMLElement} filterElement + * @return {FilterConstructor} + * @private + */ +filterConstructorPrototype._setFilter = function (filterElement) { + if (this._currentActive !== filterElement) { + this._currentActive.classList.remove(ACTIVE_FILTER_MODIFICATOR); + filterElement.classList.add(ACTIVE_FILTER_MODIFICATOR); + this._currentActive = filterElement; + this.trigger('filterSelected', filterElement.getAttribute('data-filter')); + } + return this; +}; + +filterConstructorPrototype.handleEvent = function (e) { + switch (e.type) { + case 'click': + this._setFilter(e.target); + break; + } +}; + +module.exports = FilterConstructor; \ No newline at end of file diff --git a/src/scripts/components/TodoActionsBar.js b/src/scripts/components/TodoActionsBar.js new file mode 100644 index 0000000..c552692 --- /dev/null +++ b/src/scripts/components/TodoActionsBar.js @@ -0,0 +1,62 @@ +var extendConstructor = require('../utils/extendConstructor'); +var getTextNode = require('../utils/getTextNode'); +var Eventable = require('../modules/Eventable'); +var l10n = require('../modules/l10n'); +var Filter = require('../components/Filter'); + +/** + * @constructor + * @implements {EventListener} + */ +function TodoActionsBarConstructor() { + this._initEventable(); + + this._counterNode = document.querySelector('.js-todos-actions-bar_counter'); + this._counterNodeText = getTextNode(this._counterNode); + this._clearCompletedNode = document.querySelector('.js-todos-actions-bar_clear-completed'); + + this._clearCompletedNode.addEventListener('click', this); + + this._filters = new Filter(document.querySelector('.js-todos-actions-bar_filter')); + + this._filters.on('filterSelected', this._onFilterSelected, this); +} + +extendConstructor(TodoActionsBarConstructor, Eventable); + +var todoActionsBarConstructorPrototype = TodoActionsBarConstructor.prototype; + +todoActionsBarConstructorPrototype._onFilterSelected = function (filterId) { + this.trigger('filterSelected', filterId); +}; + +/** + * @return {TodoActionsBarConstructor} + * @private + */ +todoActionsBarConstructorPrototype._clearCompleted = function () { + this.trigger('clearCompleted'); + return this; +}; + +/** + * @param {Number} count + * @return {TodoActionsBarConstructor} + */ +todoActionsBarConstructorPrototype.setItemsCount = function (count) { + this._counterNodeText.nodeValue = count + ' ' + l10n.plural('todosCountLabel', count); + return this; +}; + +/** + * @param {Event} e + */ +todoActionsBarConstructorPrototype.handleEvent = function (e) { + switch (e.type) { + case 'click': + this._clearCompleted(); + break; + } +}; + +module.exports = TodoActionsBarConstructor; \ No newline at end of file diff --git a/src/scripts/components/TodoItem.js b/src/scripts/components/TodoItem.js new file mode 100644 index 0000000..24bb432 --- /dev/null +++ b/src/scripts/components/TodoItem.js @@ -0,0 +1,138 @@ +var Eventable = require('../modules/Eventable'); +var extendConstructor = require('../utils/extendConstructor'); +var templatesEngine = require('../modules/templatesEngine'); + +var READY_MODIFICATOR = '__ready'; +var HIDDEN_MODIFICATOR = '__hide'; + +/** + * @param itemData + * @implements {EventListener} + * @constructor + */ +function TodoItemConstructor(itemData) { + this._initEventable(); + + var templateResult = templatesEngine.todoItem({ + text: itemData.text + }); + + this._root = templateResult.root; + this._markReady = templateResult.markReady; + this._removeAction = templateResult.removeAction; + this._text = templateResult.text; + + this.model = { + id: itemData.id, + isReady: itemData.isReady || false, + text: itemData.text + }; + + if (itemData.isReady) { + this._setReadyModificator(true); + } + + this._markReady.addEventListener('change', this); + this._removeAction.addEventListener('click', this); + this._text.addEventListener('input', this); +} + +extendConstructor(TodoItemConstructor, Eventable); + +var todoItemConstructorPrototype = TodoItemConstructor.prototype; + +/** + * @param {HTMLElement} parent + * @return {TodoItemConstructor} + */ +todoItemConstructorPrototype.render = function (parent) { + parent.appendChild(this._root); + return this; +}; + +/** + * @param {Event} e + */ +todoItemConstructorPrototype.handleEvent = function (e) { + switch (e.type) { + case 'change': + this.setReady(this._markReady.checked); + break; + case 'click': + if (e.target === this._removeAction) { + this.remove(); + } + break; + case 'input': + this.setText(this._text.innerText); + break; + } +}; + +/** + * @param {String} text + * @return {TodoItemConstructor} + */ +todoItemConstructorPrototype.setText = function (text) { + if (this.model.text !== text) { + this._text.innerHTML = text; + this.model.text = text; + this.trigger('change', this.model); + } + return this; +}; + +/** + * @param {Boolean} isReady + * @return {TodoItemConstructor} + * @private + */ +todoItemConstructorPrototype._setReadyModificator = function (isReady) { + if (isReady) { + this._root.classList.add(READY_MODIFICATOR); + } else { + this._root.classList.remove(READY_MODIFICATOR); + } + return this; +}; + +/** + * @param {Boolean} isReady + * @return {TodoItemConstructor} + */ +todoItemConstructorPrototype.setReady = function (isReady) { + if (isReady !== this.model.isReady) { + this._markReady.checked = isReady; + this.model.isReady = isReady; + this._setReadyModificator(isReady); + this.trigger('change', this.model); + } + return this; +}; + +/** + * @return {TodoItemConstructor} + */ +todoItemConstructorPrototype.remove = function () { + this._root.parentNode.removeChild(this._root); + this.trigger('remove', this.model.id); + return this; +}; + +/** + * @return {TodoItemConstructor} + */ +todoItemConstructorPrototype.show = function () { + this._root.classList.remove(HIDDEN_MODIFICATOR); + return this; +}; + +/** + * @return {TodoItemConstructor} + */ +todoItemConstructorPrototype.hide = function () { + this._root.classList.add(HIDDEN_MODIFICATOR); + return this; +}; + +module.exports = TodoItemConstructor; \ No newline at end of file diff --git a/src/scripts/components/TodoList.js b/src/scripts/components/TodoList.js new file mode 100644 index 0000000..5627f45 --- /dev/null +++ b/src/scripts/components/TodoList.js @@ -0,0 +1,158 @@ +var Eventable = require('../modules/Eventable'); +var extendConstructor = require('../utils/extendConstructor'); + +var TodoItem = require('../components/TodoItem'); + +var TODO_LIST_SELECTOR = '.js-todos-list'; +var itemsIdIterator = 0; + +/** + * @extends {Eventable} + * @constructor + */ +function TodoListConstructor() { + /** + * @type {Array.} + * @private + */ + this._items = []; + this._todosList = document.querySelector(TODO_LIST_SELECTOR); + this._currentFilter = 'all'; + + this._initEventable(); +} + +extendConstructor(TodoListConstructor, Eventable); + +var todoListConstructorPrototype = TodoListConstructor.prototype; + +/** + * @return {Number} + */ +todoListConstructorPrototype.getItemsCount =function () { + return this._items.length; +}; + +/** + * @param {Object} todoItemData + * @return {TodoListConstructor} + */ +todoListConstructorPrototype.createItem = function (todoItemData) { + var item = new TodoItem(Object.assign( + { + id: itemsIdIterator++, + }, + todoItemData + )); + + this._items.push(item); + + item.on('change', this._onItemChange, this) + .on('remove', this._onItemRemove, this) + .render(this._todosList); + + this.trigger('itemAdd', item); + + return this; +}; + +/** + * @return {TodoListConstructor} + */ +todoListConstructorPrototype.removeCompletedItems = function () { + var items = this._items; + var i = items.length; + + for (; i-- ;) { + if (items[i].model.isReady) { + items[i].remove(); + } + } + + return this; +}; + +/** + * @param {Number} itemId + * @return {TodoItem|null} + * @private + */ +todoListConstructorPrototype._getItemById = function (itemId) { + var items = this._items; + + for (var i = items.length; i-- ;) { + if (items[i].model.id === itemId) { + return items[i]; + } + } + + return null; +}; + +todoListConstructorPrototype._onItemChange = function (itemModel) { + this.filterShowedItems(this._currentFilter); +}; + +todoListConstructorPrototype._onItemRemove = function (itemId) { + var todoItemComponent = this._getItemById(itemId); + + if (todoItemComponent) { + todoItemComponent.off('change', this._onItemChange, this); + todoItemComponent.off('remove', this._onItemRemove, this); + var todoItemComponentIndex = this._items.indexOf(todoItemComponent); + this._items.splice(todoItemComponentIndex, 1); + this.trigger('itemDelete', todoItemComponent.model); + } + + return this; +}; + +/** + * @return {TodoListConstructor} + */ +todoListConstructorPrototype.markAsReadyAll = function () { + this._items.forEach(function (todoItem) { + todoItem.setReady(true); + }); + return this; +}; + +/** + * @param {String} filterId + * @return {TodoListConstructor} + */ +todoListConstructorPrototype.setFilter = function (filterId) { + this._currentFilter = filterId; + return this.filterShowedItems(filterId); +}; + +/** + * @param {String} filterId + * @return {TodoListConstructor} + */ +todoListConstructorPrototype.filterShowedItems = function (filterId) { + this._items.forEach(function (item) { + switch (filterId) { + case 'all': + item.show(); + break; + case 'ready': + if (item.model.isReady) { + item.show(); + } else { + item.hide(); + } + break; + case 'unready': + if (!item.model.isReady) { + item.show(); + } else { + item.hide(); + } + break; + } + }); + return this; +}; + +module.exports = TodoListConstructor; \ No newline at end of file diff --git a/src/scripts/components/TodoMain.js b/src/scripts/components/TodoMain.js new file mode 100644 index 0000000..e6f9b79 --- /dev/null +++ b/src/scripts/components/TodoMain.js @@ -0,0 +1,20 @@ +var TODOS_MAIN_SELECTOR = '.js-todos-main'; +var FULL_INTERFACE_MODIFICATOR = '__has-todos'; + +function TodoMainConstructor() { + this._todosMain = document.querySelector(TODOS_MAIN_SELECTOR); +} + +var todoMainComponentConstructorPrototype = TodoMainConstructor.prototype; + +todoMainComponentConstructorPrototype.showFullInterface = function () { + this._todosMain.classList.add(FULL_INTERFACE_MODIFICATOR); + return this; +}; + +todoMainComponentConstructorPrototype.hideFullInterface = function () { + this._todosMain.classList.remove(FULL_INTERFACE_MODIFICATOR); + return this; +}; + +module.exports = TodoMainConstructor; \ No newline at end of file diff --git a/src/scripts/main.js b/src/scripts/main.js new file mode 100644 index 0000000..dd5f0e9 --- /dev/null +++ b/src/scripts/main.js @@ -0,0 +1,52 @@ +var l10n = require('./modules/l10n'); + +var TodoMain = require('./components/TodoMain'); +var AddTodos = require('./components/AddTodos'); +var TodoList = require('./components/TodoList'); +var TodoActionsBar = require('./components/TodoActionsBar'); + +function init() { + var rusDictionary = { + 'todosCountLabel': ['задача', 'задачи', 'задач'] + }; + l10n.provideDict('ru', rusDictionary); + + var todoMain = new TodoMain(); + var addTodos = new AddTodos(); + var todoList = new TodoList(); + var todoActionsBar = new TodoActionsBar(); + + + addTodos + .on('newTodo', + function (todoData) { todoList.createItem(todoData); } + ) + .on('markAsReadyAll', + function () { todoList.markAsReadyAll();} + ); + + function itemsCountWatcher () { + var itemsCount = todoList.getItemsCount(); + + if (itemsCount !== 0) { + todoMain.showFullInterface(); + } + + todoActionsBar.setItemsCount(itemsCount); + } + + todoList.on('itemAdd', itemsCountWatcher) + .on('itemDelete', itemsCountWatcher); + + todoActionsBar.on( + 'clearCompleted', + function () { todoList.removeCompletedItems(); } + ); + + todoActionsBar.on('filterSelected', function (filterId) { + todoList.setFilter(filterId); + }); + +} + +document.addEventListener('DOMContentLoaded', init); \ No newline at end of file diff --git a/src/scripts/modules/Eventable.js b/src/scripts/modules/Eventable.js new file mode 100644 index 0000000..18ca827 --- /dev/null +++ b/src/scripts/modules/Eventable.js @@ -0,0 +1,63 @@ +function Eventable() {} + +var eventablePrototype = Eventable.prototype; + +eventablePrototype._initEventable = function () { + this._eventable_registry = {}; +}; + +function getEventSubscribers(eventable, eventName, needCreate) { + var registry = eventable._eventable_registry; + + if (eventName in registry) { + return registry[eventName]; + + } else if (needCreate) { + return registry[eventName] = []; + } + + return null; +} + +eventablePrototype.on = function (eventName, handler, ctx) { + var subscribers = getEventSubscribers(this, eventName, true); + + subscribers.push({ + handler: handler, + ctx: ctx + }); + + return this; +}; + +eventablePrototype.off = function (eventName, handler, ctx) { + var subscribers = getEventSubscribers(this, eventName); + + if (subscribers) { + for (var i = subscribers.length; i-- ;) { + if ((subscribers[i].handler === handler) + && (subscribers[i].ctx === ctx) + ) { + subscribers.splice(i, 1); + return this; + } + } + } + + return this; +}; + +eventablePrototype.trigger = function (eventName, data) { + var subscribers = getEventSubscribers(this, eventName); + + if (subscribers) { + var subscribersCopy = subscribers.slice(); + for (var i = 0, l = subscribersCopy.length; i !== l; i += 1) { + subscribersCopy[i].handler.call(subscribersCopy[i].ctx, data); + } + } + + return this; +}; + +module.exports = Eventable; \ No newline at end of file diff --git a/src/scripts/modules/l10n.js b/src/scripts/modules/l10n.js new file mode 100644 index 0000000..62bcde6 --- /dev/null +++ b/src/scripts/modules/l10n.js @@ -0,0 +1,91 @@ +function EAST_SLAVIC_ALGORITHM(number) { + var value = Math.abs(number); + + if ((value % 10 === 1) && (value % 100 !== 11)) { + return 0; + } + if ((2 <= (value % 10)) + && ((value % 10) <= 4) + && (Math.floor((value % 100) / 10) !== 1) + ) { + return 1; + } + + return 2; +} + +function ROMANO_GERMANIC_ALGORITHM(number) { + return (Math.abs(number) === 1) ? 0 : 1; +} + +var pluralAlgorithms = { + 'ru': EAST_SLAVIC_ALGORITHM, + 'en': ROMANO_GERMANIC_ALGORITHM +}; + +var l10n = { + language: 'ru', + _dictionaries: {}, + + /** + * @param {String} key + * @return {String} + * @private + */ + _getDictValue: function (key) { + var language = this.language; + + if (!(language in this._dictionaries) + || !(key in this._dictionaries[language]) + ) { + return null; + } + + return this._dictionaries[language][key]; + }, + + /** + * @param {String} languageCode + * @param {Object} dict + * @return {l10n} + */ + provideDict: function (languageCode, dict) { + this._dictionaries[languageCode] = dict; + return this; + }, + + /** + * @param {String} key + * @return {String} + */ + simple: function (key) { + var dictValue = this._getDictValue(key); + if (dictValue !== null) { + return dictValue; + } + return key; + }, + + /** + * @param {String} key + * @param {Number} number + * @return {String} + */ + plural: function (key, number) { + var dictValue = this._getDictValue(key); + if (dictValue === null) { + return key; + } + + var currentPluralAlgorithm = pluralAlgorithms[this.language] || pluralAlgorithms.ru; + var result = dictValue[currentPluralAlgorithm(number)]; + + if (result != null) { + return result; + } + return key; + + } +}; + +module.exports = l10n; \ No newline at end of file diff --git a/src/scripts/modules/templatesEngine.js b/src/scripts/modules/templatesEngine.js new file mode 100644 index 0000000..5d7bccc --- /dev/null +++ b/src/scripts/modules/templatesEngine.js @@ -0,0 +1,37 @@ + +var div = document.createElement('div'); + +function getTemplateRootNode(scriptId) { + var scriptTag = document.getElementById(scriptId); + div.innerHTML = scriptTag.innerHTML; + var result = div.children[0]; + div.removeChild(result); + return result; +} + +var templatesEngine = { + todoItem: function (data) { + var root = getTemplateRootNode('todoItemTemplate'); + + var markReady = root.querySelector('.js-todo-item_mark-ready'); + var removeAction = root.querySelector('.js-todo-item_remove-action'); + var text = root.querySelector('.js-todo-item_text'); + + if (data.text) { + text.innerText = data.text; + } + + if (data.isReady) { + markReady.checked = true; + } + + return { + root: root, + text: text, + markReady: markReady, + removeAction: removeAction + }; + } +}; + +module.exports = templatesEngine; \ No newline at end of file diff --git a/src/scripts/utils/extendConstructor.js b/src/scripts/utils/extendConstructor.js new file mode 100644 index 0000000..339fa5a --- /dev/null +++ b/src/scripts/utils/extendConstructor.js @@ -0,0 +1,17 @@ +/** + * @param {Function} Extendable + * @param {Function} Extension + * @return {Function} Extendable + */ +function extendConstructor(Extendable, Extension) { + var extendablePrototype = Extendable.prototype; + var extensionPrototype = Extension.prototype; + + for (var p in extensionPrototype) { + extendablePrototype[p] = extensionPrototype[p]; + } + + return Extendable; +} + +module.exports = extendConstructor; \ No newline at end of file diff --git a/src/scripts/utils/getTextNode.js b/src/scripts/utils/getTextNode.js new file mode 100644 index 0000000..67a928f --- /dev/null +++ b/src/scripts/utils/getTextNode.js @@ -0,0 +1,21 @@ +/** + * @param {HTMLElement} node + * @return {Node} + */ +function getTextNode(node) { + var childs = node.childNodes; + var i = 0; + var l = childs.length; + + for (; i !== l; i += 1) { + if (childs[i].nodeName === '#text') { + return childs[i]; + } + } + + var result = document.createTextNode(''); + node.appendChild(result); + return result; +} + +module.exports = getTextNode; \ No newline at end of file diff --git a/src/styles/base/action/action.scss b/src/styles/base/action/action.scss new file mode 100644 index 0000000..aa43579 --- /dev/null +++ b/src/styles/base/action/action.scss @@ -0,0 +1,16 @@ +@import "../../config/globals"; + + +.action { + position: relative; + display: inline-block; + } + + .action_target { + position: absolute; + @include absolute-full(); + } + button.action_target, input.action_target { + @include reset-input(); + opacity: 0; + } \ No newline at end of file diff --git a/src/styles/base/filter/filter.scss b/src/styles/base/filter/filter.scss new file mode 100644 index 0000000..a50b9b0 --- /dev/null +++ b/src/styles/base/filter/filter.scss @@ -0,0 +1,23 @@ +@import "../../config/globals"; + + +.filter { + font-family: $font-main; + font-size: $fs_little; + line-height: $lh_little; + background: none; + outline: none; + padding: $offset_half $offset_2x; + border-radius: $offset; + font-weight: 200; + border: 0; + box-sizing: border-box; + } + .filter.__active { + border: 1px solid $c-micro-elements; + padding-top: ($offset_half - 1px); + padding-bottom: ($offset_half - 1px); + padding-left: ($offset_2x - 1px); + padding-right: ($offset_2x - 1px); + } + diff --git a/src/styles/base/input-checkbox/input-checkbox.scss b/src/styles/base/input-checkbox/input-checkbox.scss new file mode 100644 index 0000000..4b158d1 --- /dev/null +++ b/src/styles/base/input-checkbox/input-checkbox.scss @@ -0,0 +1,73 @@ +@import "../../config/globals"; + + +$checkbox-size: 36px; + +$c-checkbox-active: #5dc2af; +$c-checkbox-animation-time: .2s; + + +.input-checkbox { + display: inline-block; + position: relative; + width: $checkbox-size; + min-width: $checkbox-size; + height: $checkbox-size; + } + + .input-checkbox_target { + @include absolute-full(); + @include reset-input(); + opacity: 0; + z-index: 1; + } + + .input-checkbox_visual { + @include absolute-full(); + border-radius: $checkbox-size / 2; + box-sizing: border-box; + border: 1px solid $c-ultra-light; + transition: border-color $c-checkbox-animation-time; + } + .input-checkbox_target:checked ~ .input-checkbox_visual { + border-color: $c-checkbox-active; + } + + .input-checkbox_visual_icon { + position: absolute; + width: 19px; + height: 20px; + top: 50%; + left: 50%; + margin: -9px -9px; + opacity: 0; + transition: opacity $c-checkbox-animation-time; + } + .input-checkbox_target:checked ~ .input-checkbox_visual .input-checkbox_visual_icon { + opacity: 1; + } + .input-checkbox_visual_icon:before, + .input-checkbox_visual_icon:after { + content: ''; + position: absolute; + } + + .input-checkbox_visual_icon:before { + width: 11px; + height: 2px; + background: $c-checkbox-active; + + top: 13px; + left: 0; + transform: rotate(45deg); + } + + .input-checkbox_visual_icon:after { + width: 19px; + height: 2px; + background: $c-checkbox-active; + + top: 8px; + left: 3px; + transform: rotate(-65deg); + } diff --git a/src/styles/config/globals.scss b/src/styles/config/globals.scss new file mode 100644 index 0000000..1606d24 --- /dev/null +++ b/src/styles/config/globals.scss @@ -0,0 +1,12 @@ +@import "vars/color"; +@import "vars/fonts"; +@import "vars/font-size"; +@import "vars/layout"; +@import "vars/offset"; + +@import "mixins/clearfix"; +@import "mixins/text"; +@import "mixins/input"; +@import "mixins/positin"; +@import "mixins/media"; +@import "mixins/hover"; diff --git a/src/styles/config/mixins/clearfix.scss b/src/styles/config/mixins/clearfix.scss new file mode 100644 index 0000000..c2a8424 --- /dev/null +++ b/src/styles/config/mixins/clearfix.scss @@ -0,0 +1,14 @@ +// +// clearfix - modern way + +@mixin clearfix() { + &:before, + &:after { + content: ''; + display: table; + } + + &:after { + clear: both; + } + } \ No newline at end of file diff --git a/src/styles/config/mixins/hover.scss b/src/styles/config/mixins/hover.scss new file mode 100644 index 0000000..7325d92 --- /dev/null +++ b/src/styles/config/mixins/hover.scss @@ -0,0 +1,11 @@ +@mixin not-hovered() { + .hover & { + @content + } + } + +@mixin hover() { + .hover &:hover { + @content + } + } \ No newline at end of file diff --git a/src/styles/config/mixins/input.scss b/src/styles/config/mixins/input.scss new file mode 100644 index 0000000..5a2ab62 --- /dev/null +++ b/src/styles/config/mixins/input.scss @@ -0,0 +1,25 @@ +@mixin reset-input() { + margin: 0; + padding: 0; + background: none; + border: 0; + outline: none; + } + +@mixin placeholder() { + &::-webkit-input-placeholder { + @content + } + + &:-moz-placeholder { + @content + } + + &::-moz-placeholder { + @content + } + + &:-ms-input-placeholder { + @content + } + } \ No newline at end of file diff --git a/src/styles/config/mixins/media.scss b/src/styles/config/mixins/media.scss new file mode 100644 index 0000000..d5523e5 --- /dev/null +++ b/src/styles/config/mixins/media.scss @@ -0,0 +1,5 @@ +@mixin retinaMedia { + @media (-webkit-min-device-pixel-ratio: 1.5), (min-resolution: 120dpi), (min-resolution: 1.5dppx) { + @content; + } + } diff --git a/src/styles/config/mixins/positin.scss b/src/styles/config/mixins/positin.scss new file mode 100644 index 0000000..7a0e94f --- /dev/null +++ b/src/styles/config/mixins/positin.scss @@ -0,0 +1,7 @@ +@mixin absolute-full() { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + } diff --git a/src/styles/config/mixins/text.scss b/src/styles/config/mixins/text.scss new file mode 100644 index 0000000..b7c64cd --- /dev/null +++ b/src/styles/config/mixins/text.scss @@ -0,0 +1,7 @@ +@mixin ellipsisTextBlock() { + max-width: 100%; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + box-sizing: border-box; + } \ No newline at end of file diff --git a/src/styles/config/vars/color.scss b/src/styles/config/vars/color.scss new file mode 100644 index 0000000..23d3410 --- /dev/null +++ b/src/styles/config/vars/color.scss @@ -0,0 +1,15 @@ +$c-white: #fff; +$c-gray: #666; +$c-gray-dark: #333; +$c-gray-light: #ddd; +$c-light: #999; +$c-ultra-light: #ccc; +$c-orange: #eb722e; +$c-error: #ff5756; + +$c-main-bg: #f5f5f5; +$c-title: rgba(#af2f2f, .15); +$c-todos-main-bg: $c-white; +$c-common-border: #e6e6e6; + +$c-micro-elements: #ebdfdf; \ No newline at end of file diff --git a/src/styles/config/vars/font-size.scss b/src/styles/config/vars/font-size.scss new file mode 100644 index 0000000..4343632 --- /dev/null +++ b/src/styles/config/vars/font-size.scss @@ -0,0 +1,17 @@ +$fs_normal: 15px; +$fs_little: 14px; +$fs_large: 18px; +$fs_x-large: 20px; +$fs_xx-large: 24px; +$fs_small: 13px; +$fs_x-small: 12px; +$fs_xx-small: 11px; + +$lh_normal: 20px; +$lh_little: 18px; +$lh_large: 22px; +$lh_x-large: 24px; +$lh_xx-large: 28px; +$lh_small: 17px; +$lh_x-small: 17px; +$lh_xx-small: 15px; diff --git a/src/styles/config/vars/fonts.scss b/src/styles/config/vars/fonts.scss new file mode 100644 index 0000000..ea1b32c --- /dev/null +++ b/src/styles/config/vars/fonts.scss @@ -0,0 +1 @@ +$font-main: 'Helvetica Neue', Arial, sans-serif; \ No newline at end of file diff --git a/src/styles/config/vars/layout.scss b/src/styles/config/vars/layout.scss new file mode 100644 index 0000000..6909d41 --- /dev/null +++ b/src/styles/config/vars/layout.scss @@ -0,0 +1,2 @@ +$main-wrapper-width: 592px; +$main-wrapper-min-width: 212px; \ No newline at end of file diff --git a/src/styles/config/vars/offset.scss b/src/styles/config/vars/offset.scss new file mode 100644 index 0000000..0ccc2c8 --- /dev/null +++ b/src/styles/config/vars/offset.scss @@ -0,0 +1,11 @@ +$offset: 4px; +$offset_half: $offset / 2; +$offset_2x: $offset * 2; +$offset_3x: $offset * 3; +$offset_4x: $offset * 4; +$offset_5x: $offset * 5; +$offset_6x: $offset * 6; +$offset_7x: $offset * 7; +$offset_8x: $offset * 8; +$offset_9x: $offset * 9; +$offset_10x: $offset * 10; diff --git a/src/styles/default.scss b/src/styles/default.scss new file mode 100644 index 0000000..9e0c5c5 --- /dev/null +++ b/src/styles/default.scss @@ -0,0 +1,19 @@ +@import "reset"; + + +@import "layout"; + + +@import "base/action/action"; +@import "base/input-checkbox/input-checkbox"; +@import "base/filter/filter"; + + +@import "project/todos-title/todos-title"; +@import "project/todos-main/todos-main"; +@import "project/todos-add/todos-add"; + +@import "project/todos-list/todos-list"; +@import "project/todos-list/todo-item"; + +@import "project/todos-actions-bar/todos-actions-bar"; \ No newline at end of file diff --git a/src/styles/layout.scss b/src/styles/layout.scss new file mode 100644 index 0000000..5a2deed --- /dev/null +++ b/src/styles/layout.scss @@ -0,0 +1,11 @@ +@import "config/globals"; + +body { + background: $c-main-bg; + } + +.main-wrapper { + max-width: $main-wrapper-width; + min-width: $main-wrapper-min-width; + margin: 0 auto; + } \ No newline at end of file diff --git a/src/styles/project/todos-actions-bar/todos-actions-bar.scss b/src/styles/project/todos-actions-bar/todos-actions-bar.scss new file mode 100644 index 0000000..f2a9740 --- /dev/null +++ b/src/styles/project/todos-actions-bar/todos-actions-bar.scss @@ -0,0 +1,49 @@ +@import "../../config/globals"; + +.todos-actions-bar { + padding: $offset $offset_3x; + @include clearfix(); + text-align: center; + } + + .todos-actions-bar_counter { + font-family: $font-main; + font-size: $fs_little; + line-height: $lh_little; + font-weight: 200; + padding: $offset_half 0; + float: left; + margin: $offset_2x $offset_4x $offset_2x 0; + } + + .todos-filters { + white-space: nowrap; + display: inline-block; + margin: $offset_2x 0; + } + .todos-actions-bar .todos-filters { + //text-align: center; + } + + .todos-filters .filter { + margin: 0 $offset; + } + .todos-filters .filter:first-child { + margin-left: 0; + } + .todos-filters .filter:last-child { + margin-right: 0; + } + + .todos-actions-bar_clear-completed { + font-family: $font-main; + font-size: $fs_little; + line-height: $lh_little; + font-weight: 200; + margin: $offset_2x 0 $offset_2x $offset_4x; + padding: $offset_half 0; + border: none; + background: 0; + outline: none; + float: right; + } diff --git a/src/styles/project/todos-add/todos-add.scss b/src/styles/project/todos-add/todos-add.scss new file mode 100644 index 0000000..52ffa57 --- /dev/null +++ b/src/styles/project/todos-add/todos-add.scss @@ -0,0 +1,72 @@ +@import "../../config/globals"; + +.todos-add { + height: 64px; + @include clearfix(); + } + + .todo-add_select-all-action { + width: 24px; + height: 24px; + float: left; + margin: $offset_5x 0 0 $offset_4x; + opacity: 0; + transition: opacity .2s; + } + .todo-add_select-all-action { + visibility: hidden; + } + .todos-main.__has-todos .todo-add_select-all-action { + visibility: visible; + opacity: 1; + } + + .todo-add_select-all-action .action_visual { + width: 24px; + height: 12px; + overflow: hidden; + position: absolute; + top: 50%; + margin-top: -6px; + } + .todo-add_select-all-action .action_visual:before, + .todo-add_select-all-action .action_visual:after { + content: ''; + position: absolute; + height: 3px; + background: #737373; + width: 100%; + } + .todo-add_select-all-action .action_visual:before { + transform: rotate(32.5deg); + left: -9px; + top: 2px; + } + .todo-add_select-all-action .action_visual:after { + transform: rotate(-32.5deg); + left: 9px; + top: 2px; + } + + .todo-add_input-w { + overflow: hidden; + } + + .todo-add_input { + width: 100%; + box-sizing: border-box; + font-size: $fs_xx-large; + height: 64px; + padding: $offset_4x; + border: 0; + outline: 0; + font-weight: 300; + color: $c-gray; + + @include placeholder() { + color: $c-ultra-light; + font-style: italic; + } + } + + diff --git a/src/styles/project/todos-list/todo-item.scss b/src/styles/project/todos-list/todo-item.scss new file mode 100644 index 0000000..7194b98 --- /dev/null +++ b/src/styles/project/todos-list/todo-item.scss @@ -0,0 +1,92 @@ +@import "../../config/globals"; + + +.todo-item { + padding: $offset_3x; + border-bottom: 1px solid $c-common-border; + background: #fff; + display: flex; + + @include clearfix(); + + @include not-hovered() { + .todo-item_remove-action { + opacity: 0; + transition: opacity .2s; + } + } + @include hover() { + background: #fafafa; + + .todo-item_remove-action { + opacity: 1; + } + } + } + .todo-item.__hide { + display: none; + } + .todo-item:first-child, + .todo-item.__hide:first-child ~ .todo-item { + border-top: 1px solid $c-common-border; + } + .todo-item.__hide:first-child ~ .todo-item:not(.__hide) ~ .todo-item { + border-top: 0; + } + + .todo-item_ready-checker { + float: left; + align-self: center; + } + + + .todo-item_remove-action { + float: right; + margin-top: $offset_3x; + margin-bottom: $offset_3x; + order: 1; + align-self: flex-end; + } + + .todo-item_remove-action .action_visual { + width: 16px; + height: 16px; + min-width: 16px; + position: relative; + overflow: hidden; + } + .todo-item_remove-action .action_visual:before, + .todo-item_remove-action .action_visual:after { + content: ''; + position: absolute; + width: 21px; + height: 2px; + background: $c-micro-elements; + transform-origin: 0 0; + } + .todo-item_remove-action .action_visual:before { + transform: rotate(45deg); + left: 1px; + } + .todo-item_remove-action .action_visual:after { + transform: rotate(-45deg); + top: 15px; + } + + .todo-item_text-w { + overflow: hidden; + flex: 1 0; + } + .todo-item_text { + outline: none; + border: 0; + font-family: $font-main; + font-size: $fs_xx-large; + line-height: $lh_xx-large; + font-weight: 300; + padding: $offset $offset_3x; + overflow: hidden; + } + .todo-item.__ready .todo-item_text { + text-decoration: line-through; + } diff --git a/src/styles/project/todos-list/todos-list.scss b/src/styles/project/todos-list/todos-list.scss new file mode 100644 index 0000000..e463538 --- /dev/null +++ b/src/styles/project/todos-list/todos-list.scss @@ -0,0 +1,9 @@ +@import "../../config/globals"; + + +.todos-list { + + } + + + diff --git a/src/styles/project/todos-main/todos-main.scss b/src/styles/project/todos-main/todos-main.scss new file mode 100644 index 0000000..be647fb --- /dev/null +++ b/src/styles/project/todos-main/todos-main.scss @@ -0,0 +1,40 @@ +@import "../../config/globals"; + +.todos-main { + background: $c-todos-main-bg; + box-shadow: 0 2px 4px rgba(#000, .2); + position: relative; + margin: 0 $offset_4x $offset_4x; + } + .todos-main:before, + .todos-main:after { + position: absolute; + background: #fff; + box-shadow: 0 2px 4px rgba(#000, .2); + top: 100%; + z-index: -1; + } + .todos-main:before { + height: 8px; + left: 4px; + right: 4px; + } + .todos-main:after { + height: 4px; + left: 2px; + right: 2px; + } + .todos-main.__has-todos:before, + .todos-main.__has-todos:after { + content: ''; + } + + .todos-main .todos-list, + .todos-main .todos-actions-bar { + display: none; + } + .todos-main.__has-todos .todos-list, + .todos-main.__has-todos .todos-actions-bar { + display: block; + } + diff --git a/src/styles/project/todos-title/todos-title.scss b/src/styles/project/todos-title/todos-title.scss new file mode 100644 index 0000000..8b3bbfa --- /dev/null +++ b/src/styles/project/todos-title/todos-title.scss @@ -0,0 +1,10 @@ +@import "../../config/globals"; + +.todos-title { + font-family: 'Helvetica Neue', Arial, sans-serif; + font-size: 100px; + font-weight: 100; + text-transform: lowercase; + text-align: center; + color: $c-title; + } \ No newline at end of file diff --git a/src/styles/reset.scss b/src/styles/reset.scss new file mode 100644 index 0000000..87c8641 --- /dev/null +++ b/src/styles/reset.scss @@ -0,0 +1,42 @@ +/* Reset.css +---------------------------------------------------------------------------------- */ + +@import "config/globals"; + +html, body { font-size: $fs_normal; } + +textarea, +input, +select { + font-size: $fs_normal; + } + +body, table, td, ul, menu, li, div, span, img, a, form, p, h1 { + margin: 0; + padding: 0; + } + +body, textarea, input, select, button { + color: $c-gray-dark; + font-family: $font-main; + } + +body { background: $c-white; } + + +ul, menu { list-style: none; } + +img { border: 0; } + +table { + border-collapse: collapse; + border-width: 0; + } + +a { + cursor: pointer; + color: $c-gray-dark; + } + +/* /Reset.css +---------------------------------------------------------------------------------- */ \ No newline at end of file diff --git a/tests/spec/module/Eventable.test.js b/tests/spec/module/Eventable.test.js new file mode 100644 index 0000000..31cae2a --- /dev/null +++ b/tests/spec/module/Eventable.test.js @@ -0,0 +1,70 @@ +const assert = require('assert'); +const Eventable = require('../../../src/scripts/modules/Eventable'); +const extendConstructor = require('../../../src/scripts/utils/extendConstructor'); + +/** + * @constructor + * @extends {Eventable} + */ +function EventableStub() { + this._initEventable(); +} + +extendConstructor(EventableStub, Eventable); + +describe('Eventable', () => { + + describe('.on()', () => { + + it('single listener', () => { + const eventableStubInstance = new EventableStub(); + let counter = 0; + + eventableStubInstance.on('test', function () { + counter += 1; + }); + eventableStubInstance.trigger('test'); + + assert.strictEqual(counter, 1); + }); + + it('correct event argument', () => { + const eventableStubInstance = new EventableStub(); + let triggerArgument = {}; + let listenerArgument; + + eventableStubInstance.on('test', function (arg) { + listenerArgument = arg; + }); + eventableStubInstance.trigger('test', triggerArgument); + + assert.strictEqual(listenerArgument, triggerArgument); + }); + + it('correct context', () => { + const eventableStubInstance = new EventableStub(); + let context = {}; + let contextOfListenerCalling; + + eventableStubInstance.on( + 'test', + function (arg) { + contextOfListenerCalling = context; + }, + context + ); + eventableStubInstance.trigger('test'); + + assert.strictEqual(contextOfListenerCalling, context); + }); + + }); + + describe('.off()', () => { + + }); + + describe('.trigger()', () => { + + }); +}); \ No newline at end of file diff --git a/tests/spec/wallabyShow.test.js b/tests/spec/wallabyShow.test.js new file mode 100644 index 0000000..456a04f --- /dev/null +++ b/tests/spec/wallabyShow.test.js @@ -0,0 +1,44 @@ +const assert = require('assert'); + + +function normalizeFloatPart(number, floatSize) { + if (number === 0) { + return number; + } + + var cof = number < 0 + ? -1 + : 1; + var preparedNumber = cof * number; + + var intPart = Math.floor(preparedNumber); + var floatPart = preparedNumber - intPart; + + if (floatPart) { + if (floatSize == null) { + floatSize = 2; + } + var tenPow = Math.pow(10, floatSize); + var rounded = Math.round(floatPart * tenPow) / tenPow; + + return cof * (intPart + rounded); + } + + return number; +} + +describe('normalizeFloatPart', () => { + + it('base', () => { + assert.strictEqual(normalizeFloatPart(.3 + .6), 0.9); + }); + + it('negative', () => { + assert.strictEqual(normalizeFloatPart(-.3 + -.6), -0.9); + }); + + it('zero', () => { + assert.strictEqual(normalizeFloatPart(0), 1); + }); + +}); \ No newline at end of file diff --git a/wallaby.js b/wallaby.js new file mode 100644 index 0000000..ddd1a01 --- /dev/null +++ b/wallaby.js @@ -0,0 +1,22 @@ +module.exports = function (wallaby) { + return { + files: [ + 'src/scripts/**/*.js', + '!src/scripts/main.js', + ], + + tests: [ + 'tests/spec/**/*test.js' + ], + + env: { + type: 'node' + }, + + testFramework: 'mocha', + + compilers: { + '**/*.js': wallaby.compilers.babel() + } + }; +}; \ No newline at end of file diff --git a/webpack.config.js b/webpack.config.js new file mode 100644 index 0000000..8291fad --- /dev/null +++ b/webpack.config.js @@ -0,0 +1,9 @@ +const path = require('path'); + +module.exports = { + entry: './src/scripts/main.js', + output: { + filename: 'bundle.js', + path: path.resolve(__dirname, './public/dist/scripts') + } +}; \ No newline at end of file