diff --git a/.babelrc b/.babelrc index 8aa924d..0c36458 100644 --- a/.babelrc +++ b/.babelrc @@ -1,3 +1,7 @@ { - "presets": ["@babel/preset-env"] + "presets": ["@babel/preset-env"], + "plugins": [ + "@babel/plugin-syntax-class-properties", + "@babel/plugin-proposal-class-properties" + ] } \ No newline at end of file diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..b245e57 --- /dev/null +++ b/.eslintignore @@ -0,0 +1,6 @@ +/node_modules +/dist +/out +.vscode +.idea +webpack.config.js \ No newline at end of file diff --git a/.eslintrc.js b/.eslintrc.json similarity index 62% rename from .eslintrc.js rename to .eslintrc.json index 88bf0c3..5744803 100644 --- a/.eslintrc.js +++ b/.eslintrc.json @@ -1,4 +1,5 @@ -module.exports = { +{ + "parser": "babel-eslint", "env": { "browser": true, "es2020": true @@ -9,5 +10,8 @@ module.exports = { "sourceType": "module" }, "rules": { + "no-console": "warn", + "semi": "error", + "quotes": ["error", "single"] } -}; +} diff --git a/.gitignore b/.gitignore index d021d2c..3be1f7a 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,4 @@ /dist /node_modules -.DS_Store +**.DS_Store diff --git a/package-lock.json b/package-lock.json index bb080bd..fb0de76 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1638,6 +1638,20 @@ } } }, + "babel-eslint": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.1.0.tgz", + "integrity": "sha512-ifWaTHQ0ce+448CYop8AdrQiBsGrnC+bMgfyKFdi6EsPLTAWG+QfyDeM6OH+FmWnKvEq5NnBMLvlBUPKQZoDSg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/parser": "^7.7.0", + "@babel/traverse": "^7.7.0", + "@babel/types": "^7.7.0", + "eslint-visitor-keys": "^1.0.0", + "resolve": "^1.12.0" + } + }, "babel-loader": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.1.0.tgz", @@ -2551,6 +2565,15 @@ "integrity": "sha512-ufHZNtMaDEuRBpTbqD93tIQnngmJ+oBknjvr0IbFympSdtFpAUFmNv4mVKbb53qltxFx0nK3iy32S9AqkLzUNA==", "dev": true }, + "catharsis": { + "version": "0.8.11", + "resolved": "https://registry.npmjs.org/catharsis/-/catharsis-0.8.11.tgz", + "integrity": "sha512-a+xUyMV7hD1BrDQA/3iPV7oc+6W26BgVJO05PGEoatMyIuPScQKsde6i3YorWX1qs+AZjnJ18NqdKoCtKiNh1g==", + "dev": true, + "requires": { + "lodash": "^4.17.14" + } + }, "caw": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/caw/-/caw-2.0.1.tgz", @@ -6137,6 +6160,51 @@ "esprima": "^4.0.0" } }, + "js2xmlparser": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-4.0.1.tgz", + "integrity": "sha512-KrPTolcw6RocpYjdC7pL7v62e55q7qOMHvLX1UCLc5AAS8qeJ6nukarEJAF2KL2PZxlbGueEbINqZR2bDe/gUw==", + "dev": true, + "requires": { + "xmlcreate": "^2.0.3" + } + }, + "jsdoc": { + "version": "3.6.4", + "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-3.6.4.tgz", + "integrity": "sha512-3G9d37VHv7MFdheviDCjUfQoIjdv4TC5zTTf5G9VODLtOnVS6La1eoYBDlbWfsRT3/Xo+j2MIqki2EV12BZfwA==", + "dev": true, + "requires": { + "@babel/parser": "^7.9.4", + "bluebird": "^3.7.2", + "catharsis": "^0.8.11", + "escape-string-regexp": "^2.0.0", + "js2xmlparser": "^4.0.1", + "klaw": "^3.0.0", + "markdown-it": "^10.0.0", + "markdown-it-anchor": "^5.2.7", + "marked": "^0.8.2", + "mkdirp": "^1.0.4", + "requizzle": "^0.2.3", + "strip-json-comments": "^3.1.0", + "taffydb": "2.6.2", + "underscore": "~1.10.2" + }, + "dependencies": { + "escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true + }, + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true + } + } + }, "jsesc": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", @@ -6211,6 +6279,15 @@ "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", "dev": true }, + "klaw": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/klaw/-/klaw-3.0.0.tgz", + "integrity": "sha512-0Fo5oir+O9jnXu5EefYbVK+mHMBeEVEy2cmctR1O1NECcCkPRreJKrS6Qt/j3KC2C148Dfo9i3pCmCMsdqGr0g==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.9" + } + }, "leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", @@ -6236,6 +6313,15 @@ "type-check": "~0.4.0" } }, + "linkify-it": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-2.2.0.tgz", + "integrity": "sha512-GnAl/knGn+i1U/wjBz3akz2stz+HrHLsxMwHQGofCDfPvlf+gDKN58UtfmUquTY4/MXeE2x7k19KQmeoZi94Iw==", + "dev": true, + "requires": { + "uc.micro": "^1.0.1" + } + }, "load-json-file": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", @@ -6406,6 +6492,31 @@ "object-visit": "^1.0.0" } }, + "markdown-it": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-10.0.0.tgz", + "integrity": "sha512-YWOP1j7UbDNz+TumYP1kpwnP0aEa711cJjrAQrzd0UXlbJfc5aAq0F/PZHjiioqDC1NKgvIMX+o+9Bk7yuM2dg==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "entities": "~2.0.0", + "linkify-it": "^2.0.0", + "mdurl": "^1.0.1", + "uc.micro": "^1.0.5" + } + }, + "markdown-it-anchor": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-5.3.0.tgz", + "integrity": "sha512-/V1MnLL/rgJ3jkMWo84UR+K+jF1cxNG1a+KwqeXqTIJ+jtA8aWSHuigx8lTzauiIjBDbwF3NcWQMotd0Dm39jA==", + "dev": true + }, + "marked": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/marked/-/marked-0.8.2.tgz", + "integrity": "sha512-EGwzEeCcLniFX51DhTpmTom+dSA/MG/OBUDjnWtHbEnjAH180VzUeAw+oE4+Zv+CoYBWyRlYOTR0N8SO9R1PVw==", + "dev": true + }, "md5.js": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", @@ -6424,6 +6535,12 @@ "dev": true, "optional": true }, + "mdurl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", + "integrity": "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=", + "dev": true + }, "media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -8031,6 +8148,15 @@ "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", "dev": true }, + "requizzle": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/requizzle/-/requizzle-0.2.3.tgz", + "integrity": "sha512-YanoyJjykPxGHii0fZP0uUPEXpvqfBDxWV7s6GKAiiOsiqhX6vHNyW3Qzdmqp/iq/ExbhaGbVrjB4ruEVSM4GQ==", + "dev": true, + "requires": { + "lodash": "^4.17.14" + } + }, "resolve": { "version": "1.17.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", @@ -9106,6 +9232,12 @@ "string-width": "^3.0.0" } }, + "taffydb": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/taffydb/-/taffydb-2.6.2.tgz", + "integrity": "sha1-fLy2S1oUG2ou/CxdLGe04VCyomg=", + "dev": true + }, "tapable": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", @@ -9382,6 +9514,12 @@ "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", "dev": true }, + "uc.micro": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", + "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==", + "dev": true + }, "unbzip2-stream": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", @@ -9406,6 +9544,12 @@ } } }, + "underscore": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.10.2.tgz", + "integrity": "sha512-N4P+Q/BuyuEKFJ43B9gYuOj4TQUHXX+j2FqguVOpjkssLUUrnJofCcBccJSCoeturDoZU6GorDTHSvUDlSQbTg==", + "dev": true + }, "unicode-canonical-property-names-ecmascript": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz", @@ -10223,6 +10367,12 @@ "async-limiter": "~1.0.0" } }, + "xmlcreate": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-2.0.3.tgz", + "integrity": "sha512-HgS+X6zAztGa9zIK3Y3LXuJes33Lz9x+YyTxgrkIdabu2vqcGOWwdfCpf1hWLRrd553wd4QCDf6BBO6FfdsRiQ==", + "dev": true + }, "xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", diff --git a/package.json b/package.json index 00f1171..f143081 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,10 @@ "homepage": "https://github.com/vigdorov/storage-service-ui#readme", "devDependencies": { "@babel/core": "^7.10.4", + "@babel/plugin-proposal-class-properties": "^7.10.4", + "@babel/plugin-syntax-class-properties": "^7.10.4", "@babel/preset-env": "^7.10.4", + "babel-eslint": "^10.1.0", "babel-loader": "^8.1.0", "clean-webpack-plugin": "^3.0.0", "css-loader": "^3.6.0", @@ -28,6 +31,7 @@ "file-loader": "^6.0.0", "html-webpack-plugin": "^4.3.0", "image-webpack-loader": "^6.0.0", + "jsdoc": "^3.6.4", "mini-css-extract-plugin": "^0.9.0", "url-loader": "^4.1.0", "webpack": "^4.43.0", diff --git a/src/.DS_Store b/src/.DS_Store deleted file mode 100644 index cd3a2cf..0000000 Binary files a/src/.DS_Store and /dev/null differ diff --git a/src/api/StorageListAPI.js b/src/api/StorageListAPI.js index 7ca839a..37da939 100644 --- a/src/api/StorageListAPI.js +++ b/src/api/StorageListAPI.js @@ -1,9 +1,16 @@ +import {v4 as uuidv4} from 'uuid'; + import StorageServiceApi from './StorageServiceAPI'; +/** + * @typedef Element + * @type {object} + * @property {string} _id - уникальный id элемента + */ + /** * Класс создания api для списков данных * @class - * @public */ class StorageListApi { /** @@ -16,33 +23,47 @@ class StorageListApi { /** * @private + * + * @param {Array.} list - список элементов по которым осуществялется поиск * @param {string} _id - _id искомого элемента * - - * @returns {Promise} + * @returns {number} - Возвращает индекс искомого эллемента по _id */ - async _findIndex(_id) { - const list = await this.request(); + _findIndex = (list, _id) => { return list.findIndex(item => item._id === _id); } /** - * @public - * @returns {Promise} - возвращает все элементы списка + * @private + * + * @param {Array} list - новый список элементов + * + * @returns {Promise>} - возвращает обновленный список элементов */ - async request() { + _updateList = async (list) => { + return await this.api.createOrUpdate(this.key, list); + } + + /** + * @public + * + * @returns {Promise>} - возвращает все элементы списка + */ + request = async () => { const data = await this.api.find(this.key); return (data && data.value) || []; } /** + * @public + * * @param {string} _id - _id искомого элемента списка * - * @returns {Promise} - возвращает элемент списка или генерит ошибку + * @returns {Promise} - возвращает элемент списка или генерит ошибку */ - async find(_id) { + find = async (_id) => { const list = await this.request(); - const findIndex = LocalStorageListApi.findIndex(list, _id); + const findIndex = this._findIndex(list, _id); if (findIndex === -1) { throw new Error(`Not Found _id: ${_id}`); } @@ -50,51 +71,58 @@ class StorageListApi { } /** + * @public + * * @param {Object} data - элемент списка * - * @returns {Promise} - Возвращает вновь созданный элемент с уникальным полем _id + * @returns {Promise} - Возвращает вновь созданный элемент с уникальным полем _id */ - async create(data) { + create = async (data) => { const list = await this.request(); const _id = uuidv4(); const newData = { ...data, _id, }; - await this.api.createOrUpdate(this.key, list.concat(newData)); + await this._updateList(list.concat(newData)); return newData; } /** - * @param {Object} data - элемент списка - * @param {string} data._id - наличие _id обязательно + * @public * - * @returns {Promise} - Возвращает обновленный элемент списка + * @param {Element} data - элемент списка + * + * @returns {Promise} - Возвращает обновленный элемент списка */ - async update(data) { + update = async (data) => { const list = await this.request(); - const findIndex = LocalStorageListApi.findIndex(list, data._id); + const findIndex = this._findIndex(list, data._id); if (findIndex === -1) { throw new Error(`Not Found _id: ${data._id}`); } list.splice(findIndex, 1, data); - await this.api.createOrUpdate(this.key, list); + await this._updateList(list); return data; } /** + * @public + * * @param {string} _id - _id удаляемого элемента * - * @returns {Promise} - Возвращает _id удаленного элемента или ошибку + * @returns {Promise} - Возвращает _id удаленного элемента или ошибку */ - async remove(_id) { + remove = async (_id) => { const list = await this.request(); - const findIndex = LocalStorageListApi.findIndex(list, _id); + const findIndex = this._findIndex(list, _id); if (findIndex === -1) { throw new Error(`Not Found _id: ${_id}`); } list.splice(findIndex, 1); - await this.api.createOrUpdate(this.key, list); + await this._updateList(list); return _id; } -} \ No newline at end of file +} + +export default StorageListApi; diff --git a/src/api/StorageServiceAPI.js b/src/api/StorageServiceAPI.js index f88bf61..edf19c1 100644 --- a/src/api/StorageServiceAPI.js +++ b/src/api/StorageServiceAPI.js @@ -1,25 +1,64 @@ +import axios from 'axios'; + import {API_URL, ENDPOINT} from './consts'; +/** + * @typedef Store + * @type {object} + * @property {string} key + * @property {unknown} value + */ +/** + * Класс для работы с store-service api + * @class + */ class StorageServiceApi { URL = `${API_URL}${ENDPOINT}`; - async request() { + /** + * @public + * + * @returns {Promise>} - Возвращает список всех пар ключ-значение + */ + request = async () => { const {data} = await axios.get(this.URL); return data; } - async find(key) { - const {data} = await axios.get(`${this.URL}/${key}`) + /** + * @public + * + * @param {string} key - ключ хранилища в api + * + * @returns {Promise} - Возвращает значение по указанному ключу + */ + find = async (key) => { + const {data} = await axios.get(`${this.URL}/${key}`); return data; } - async createOrUpdate(key, value) { - const {data} = await axios.post(this.URL, {key, value}) + /** + * @public + * + * @param {string} key - ключ хранилища в api + * @param {unknown} value - значение, которое будет хранится под указанным ключом + * + * @returns {Promise} - возвращает вновь созданный элемент + */ + createOrUpdate = async (key, value) => { + const {data} = await axios.post(this.URL, {key, value}); return data; } - async remove(key) { + /** + * @public + * + * @param {string} key - ключ хранилища api + * + * @returns {Promise} - возвращает 'ok', если удаление было выполнено + */ + remove = async (key) => { const {data} = await axios.delete(`${this.URL}/${key}`); return data; } diff --git a/src/api/consts.js b/src/api/consts.js index 92e3dbd..b19da15 100644 --- a/src/api/consts.js +++ b/src/api/consts.js @@ -1,5 +1,15 @@ +/** + * @type {string} + */ export const API_URL = 'http://vigdorov.ru:4001'; -export const ENDPOINT = '/store'; -export const API_KEYS = { +/** + * @type {string} + */ +export const ENDPOINT = '/store'; + +/** + * @type {Object} + */ +export const API_KEYS = { }; \ No newline at end of file diff --git a/src/app.html b/src/app.html index 69763a7..cb00b24 100644 --- a/src/app.html +++ b/src/app.html @@ -1,11 +1,26 @@ + - - - Document + + + Document + - + + + + + + + \ No newline at end of file diff --git a/src/app.js b/src/app.js index 872424e..8eb0c9a 100644 --- a/src/app.js +++ b/src/app.js @@ -1,3 +1,14 @@ import './app.css'; import 'bootstrap/dist/css/bootstrap.min.css'; -import 'bootstrap'; \ No newline at end of file +import 'bootstrap'; + +// ! TODO: 5-14 строчки удалить, после теста компонента +import TestModal from './components/test-modal'; +import TestButton from './components/test-button'; + +const testModal = new TestModal(); +const testButton = new TestButton(); + +testButton.subscribe('click', () => { + testModal.show(); +}); diff --git a/src/components/component/Component.js b/src/components/component/Component.js new file mode 100644 index 0000000..197fe8d --- /dev/null +++ b/src/components/component/Component.js @@ -0,0 +1,118 @@ +/** + * @typedef Listener + * @type {Object} + * @property {Node} element + * @property {string} eventName + * @property {function} listener + */ + +/** + * @function EventListener + * @param {unknown[]} args - аргументы функции + */ + +/** + * @typedef Events + * @type {Object} + */ + +/** + * Класс для создания компонентов приложения. Необходим для нследования. + * @class + */ +class Component { + /** + * @private + * @type {Listener[]} + */ + _listeners; + + /** + * @private + * @type {Events} + */ + _events; + + /** + * @public + * @type {Node} - корневой элемент компонента + */ + mainNode; + + /** + * @param {string} mainNodeSelector - селектор, с помощью которого извлекается шаблон компонента + * @param {Node} parentNode - родительский Node, в который следует положить созданный элемент + */ + constructor(mainNodeSelector, parentNode) { + /** + * @type {DocumentFragment} + */ + const content = document.querySelector(mainNodeSelector).content; + if (content.children.length > 1) { + const message = '