diff --git a/.gitignore b/.gitignore index ad1b1f4..5989668 100644 --- a/.gitignore +++ b/.gitignore @@ -86,7 +86,6 @@ out # Nuxt.js build / generate output .nuxt -dist # Gatsby files .cache/ @@ -204,7 +203,6 @@ out # Nuxt.js build / generate output .nuxt -dist # Gatsby files .cache/ @@ -237,3 +235,4 @@ dist .yarn/install-state.gz .pnp.* +/.trash/ diff --git a/package.json b/package.json index 9d48e6f..d57c51f 100644 --- a/package.json +++ b/package.json @@ -9,21 +9,29 @@ "srv": "cross-env SOURCE_MAP_ENV=true webpack serve", "watch": "webpack --watch", "build": "cross-env NODE_ENV=production webpack", - "build-dev": "cross-env SOURCE_MAP_ENV=true webpack" + "build-dev": "cross-env SOURCE_MAP_ENV=true NODE_ENV=production webpack" }, "keywords": [], "author": "", "license": "ISC", "devDependencies": { "@babel/core": "^7.15.5", + "@babel/plugin-proposal-class-properties": "^7.14.5", "@babel/preset-env": "^7.15.6", "babel-loader": "^8.2.2", "clean-webpack-plugin": "^4.0.0", "cross-env": "^7.0.3", "css-loader": "^6.3.0", + "css-minimizer-webpack-plugin": "^3.1.1", + "css-mqpacker-webpack-plugin": "^0.12.1", + "glob": "^7.2.0", "html-webpack-plugin": "^5.3.2", "mini-css-extract-plugin": "^2.3.0", - "terser-webpack-plugin": "^5.2.4", + "mini-svg-data-uri": "^1.3.3", + "postcss-loader": "^6.1.1", + "postcss-preset-env": "^6.7.0", + "purgecss-webpack-plugin": "^4.0.3", + "style-loader": "^3.3.0", "webpack": "^5.56.1", "webpack-cli": "^4.8.0", "webpack-dev-server": "^4.3.1" diff --git a/src/env.json b/src/env.json index cde9308..4510c06 100644 --- a/src/env.json +++ b/src/env.json @@ -9,6 +9,7 @@ ], "querySelectors": { "console": "#appConsole", + "navTabs": "#asSwitcher", "modal": ".modal", "notesGrid": "#notesGrid", "noteEditForm": "#noteEditForm", @@ -22,8 +23,8 @@ }, "gridOrder": { "notes": [ - "title", "createdAt", + "title", "category", "content", "dates", diff --git a/src/helpers.js b/src/helpers.js new file mode 100644 index 0000000..32a69d9 --- /dev/null +++ b/src/helpers.js @@ -0,0 +1,80 @@ +export const env = require('./env.json') +export const msgBox = document.getElementById(env.querySelectors.console.slice(1)) + +/** + * + * @type {{}} + */ +const monthsNamesFull = env.lists.monthsFull + +/** + * + * @param string + * @returns {string} + */ +export const ucWords = string => { + return string.split('_').map(word => word.charAt(0).toUpperCase() + word.substr(1).toLowerCase()).join(' ') +} + + +/** + * + * @param date + * @returns {string} + */ +export const formatDate = date => { + const dt = new Date(date) + return [monthsNamesFull[dt.getMonth()], dt.getDate(), `, ${dt.getFullYear()}`].join(' ') +} + +/** + * + * @param str + * @returns {*[]} + */ +export const findDates = str => { + const dates = [] + const regex = new RegExp('[0-3]?[0-9].[0-3]?[0-9].(?:[0-9]{2})?[0-9]{2}', 'mg') + + for (const match of (str || '').matchAll(regex)) { + dates.push(match[0]) + } + + return dates +} + +/** + * + * @param args + * @returns {*} + */ +export const createNode = (...args) => { + const [tag, attrs, content, callback] = args + const node = document.createElement(tag) + + for (const [key, value] of Object.entries(attrs || [])) { + node.setAttribute(key, value) + } + + node.innerHTML = content || '' + + return node +} + +/** + * + * @param node + */ +export const destroyNode = (node) => { + + if (!(node instanceof Node) && typeof node === 'string') { + node = document.querySelector(node) + } + + try { + node.remove() + } catch (e) {} + +} + + diff --git a/src/index.html b/src/index.html index 0454386..7d95e6a 100644 --- a/src/index.html +++ b/src/index.html @@ -8,6 +8,5 @@
- \ No newline at end of file diff --git a/src/index.js b/src/index.js index a41b0e6..2cded74 100644 --- a/src/index.js +++ b/src/index.js @@ -1,42 +1,13 @@ -import env from './env.json' +import { env } from './helpers' import './styles/uikit.min.css' import './styles/main.css' import DOMController from './modules/DOMController' -//localStorage.clear() - -window.env = env -const monthsNamesFull = window.env.lists.monthsFull - -/** - * - * @param str - * @returns {*[]} - * @private - */ -window.___findDates = str => { - const dates = [] - const regex = new RegExp('[0-3]?[0-9].[0-3]?[0-9].(?:[0-9]{2})?[0-9]{2}', 'mg') - - for (const match of (str || '').matchAll(regex)) { - dates.push(match[0]) - } - - return dates -} - -window.___formatDate = date => { - const dt = new Date(date) - return [monthsNamesFull[dt.getMonth()], dt.getDate(), `, ${dt.getFullYear()}`].join(' ') -} - const app = document.getElementById('app') -const view = new DOMController(app) +window.env = env -//console.log(dt, dt.match(/^(((2000|2400|2800|((19|2[0-9])(0[48]|[2468][048]|[13579][26])))-02-29)|(((19|2[0-9])[0-9]{2})-02-(0[1-9]|1[0-9]|2[0-8]))|(((19|2[0-9])[0-9]{2})-(0[13578]|10|12)-(0[1-9]|[12][0-9]|3[01]))|(((19|2[0-9])[0-9]{2})-(0[469]|11)-(0[1-9]|[12][0-9]|30)))T([01][0-9]|[2][0-3]):[0-5][0-9]:[0-5][0-9]\.[0-9]{3}Z$/) ) - -//localStorage.clear() +new DOMController(app) /*NotesAPI.saveNote({ //id: 441512, @@ -47,4 +18,7 @@ const view = new DOMController(app) })*/ //NotesAPI.deleteNote(441512) -//console.log(NotesAPI.getNotes()) \ No newline at end of file +//console.log(NotesAPI.getNotes()) + +/* +1/1/11 or 1.1.11 or 1-1-11 01/01/11 or 01.01.11 or 01-01-11 01/01/2011 or 01.01.2011 or 01-01-2011 : true 01/1/2011 or 01.1.2011 or 01-1-2011 : true 1/11/2011 or 1.11.2011 or 1-11-2011 1/11/11 or 1.11.11 or 1-11-11 11/1/11 or 11.1.11 or 11-1-11*/ diff --git a/src/modules/DOMController.js b/src/modules/DOMController.js index 0a62de7..233f06a 100644 --- a/src/modules/DOMController.js +++ b/src/modules/DOMController.js @@ -1,4 +1,5 @@ import Templates from '../templates/Views' +import { msgBox, createNode, destroyNode, findDates, formatDate, ucWords } from '../helpers' import NotesAPI from './NotesAPI' export default class DOMController { @@ -13,15 +14,29 @@ export default class DOMController { } this.btnCreateNote = this.root.querySelector(window.env.querySelectors.btnCreateNote) - this.createNotesGrid(window.env.prePopulateAmount || 0) this.listener([ - [ - this.btnCreateNote, 'click', (this.btnCreateNote.dataset.action || 'showError'), { - legend: 'Create Note', - }], + [this.btnCreateNote, 'click', (this.btnCreateNote.dataset.action || 'showError'), { legend: 'Create Note' }], ]) + + this.navTabs() + + } + + navTabs () { + const navTabs = document.querySelectorAll(window.env.querySelectors.navTabs + ' > li > a') + + for (let i = 0; i < navTabs.length; ++i) { + this.listener([navTabs[i], 'click', 'createNotesGridByRoute', navTabs[i]]) + } + } + + /** + * + */ + showError () { + DOMController.alert('Error occurred :(', 3000, 3) } /** @@ -29,28 +44,68 @@ export default class DOMController { * @param msg * @param fadeOut * @param type + * @param womb */ - static alert (msg = '', fadeOut = 0, type = 0) { + static alert (msg = 'Alert without message', fadeOut = 0, type = 0, womb = null) { const alertTypes = window.env.alertTypes - const alert = this.createNode('div', { class: `uk-alert-${alertTypes[type]}`, 'uk-alert': null }, - `\n` + msg) + const alert = createNode('div', { class: `uk-alert-${alertTypes[type]} uk-padding-small`, 'uk-alert': null }, msg) alert.style.display = 'block' - document.getElementById(window.env.querySelectors.console.slice(1)).appendChild(alert) + womb instanceof Element ? womb.appendChild(alert) : msgBox.appendChild(alert) + + if (fadeOut > 0) { + setTimeout(() => { + alert.remove() + }, fadeOut) + } } - createNotesGrid (len, archived = false) { + /** + * + * @param node + */ + noteToTrash (node) { + const row = node.closest('tr') + NotesAPI.deleteNote(parseInt(row.dataset.id, 10)) + row.classList.add('as-removing') + + setTimeout(() => { + row.remove() + this.createNotesGrid(window.env.prePopulateAmount || 0) + }, 3000) + } + + createNotesGridByRoute (navTab) { + const route = new URL( navTab.href ).pathname.slice(1).split('\/')[0] + document.querySelector('li.nav-tab.uk-active').classList.remove('uk-active') + navTab.closest('li').classList.add( 'uk-active' ) + + switch ( route ){ + case 'all': this.createNotesGrid( 0, null ); break; + case '': this.createNotesGrid( window.env.prePopulateAmount || 0 ); break; + case 'archived': this.createNotesGrid( window.env.prePopulateAmount || 0, true ); break; + default: console.log(route) + } + } + + /** + * + * @param len + * @param archived + */ + createNotesGrid (len = window.env.prePopulateAmount || 0, archived = false) { const notes = NotesAPI.getNotesSanitized(len, archived) if (notes.length === 0) { return } - const nodeToListen = [] + + const notesGrid = document.querySelector(window.env.querySelectors.notesGrid + ' tbody') notesGrid.innerHTML = '' notes.forEach(el => { - const row = DOMController.createNode('tr') + const row = createNode('tr') row.dataset.id = el.id window.env.gridOrder.notes.forEach(key => { @@ -59,14 +114,24 @@ export default class DOMController { switch (key) { case 'createdAt': - value = ___formatDate(value) + value = formatDate(value) + break + case 'category': + value = ucWords( value ) + break + case 'title': + //attrs.class = 'uk-text-truncate' + attrs.class = 'uk-text-bold' break case 'content': - attrs.class = 'uk-text-truncate' + + //attrs.class = 'uk-text-truncate' + //attrs.title = value break case 'dates': - attrs.class = 'uk-text-truncate' - value = ___findDates(el.content).join(', ') + value = findDates(el.content).join(', ') + //attrs.class = 'uk-text-truncate' + //attrs.title = value break case 'edit' : attrs.class = 'icon icon-edit grid-control' @@ -78,15 +143,14 @@ export default class DOMController { break case 'delete' : attrs.class = 'icon icon-delete grid-control' - attrs['data-action'] = 'toTrash' + attrs['data-action'] = 'noteToTrash' break } - const td = DOMController.createNode('td', attrs, value) + const td = createNode('td', attrs, value) if (key.match(/^(edit|archive|delete)$/)) { - nodeToListen.push(td) - this.listener([td, 'click', td.dataset.action]) + this.listener([td, 'click', td.dataset.action, td]) } row.appendChild(td) @@ -96,89 +160,6 @@ export default class DOMController { }) } - showError () { - console.error('Error occurred :(') - } - - static createNode (...args) { - const [tag, attrs, content, callback] = args - const node = document.createElement(tag) - - for (const [key, value] of Object.entries(attrs || [])) { - node.setAttribute(key, value) - } - - node.innerHTML = content || '' - - return node - } - - destroyNode (selector) { - - if (!(selector instanceof Node) && typeof selector === 'string') { - selector = document.querySelector(selector) - } - - try { - selector.remove() - } catch (e) {} - - } - - getEditForm (args = {}) { - - const node = DOMController.createNode('div', { class: window.env.querySelectors.modal.slice(1) }, - Templates.templates.editForm) - - if (args.legend) { - node.querySelector('legend').innerHTML = args.legend - } - - this.root.appendChild(node) - node.style.display = 'block' - - this.listener([ - [node.querySelector(window.env.querySelectors.btnDestroyModal), 'click', 'destroyNode', node], - [node.querySelector('form'), 'submit', NotesAPI.saveNote], - ]) - } - - listener (els) { - els[0] instanceof Element ? this.on(els) : els.forEach(el => this.on(el)) - } - - /** - * - * @param args //selector, event, method, params - */ - on (args) { - - const [selector, event, method] = args - - if (!(selector instanceof Element)) { - return - } - - try { - selector.addEventListener(event, evt => { - const params = args[3] || {} - - if (evt.type === 'submit') { - evt.preventDefault() - } - - if (typeof this[method] === 'function') { - this[method](params) - } else if (typeof method === 'function') { - method(params) - } - }) - - } catch (err) { - console.error(err) - } - } - /** * * @param selector @@ -194,4 +175,69 @@ export default class DOMController { return data } -} \ No newline at end of file + + /** + * + * @param els + * @returns {void|*} + */ + listener (els) { return els[0] instanceof Element ? this.on(els) : els.forEach(el => this.on(el)) } + + /** + * + * @param args //selector, event, method, params + */ + on (args) { + + const [selector, event, method] = args + + if (!(selector instanceof Element)) { + return + } + + try { + selector.addEventListener(event, evt => { + const params = args[3] || {} + + if (selector.tagName === 'A') { + evt.preventDefault() + } + + if (evt.type === 'submit') { + evt.preventDefault() + + setTimeout(() => { + this.createNotesGrid(window.env.prePopulateAmount || 0) + }, 300) + } + + if (typeof this[method] === 'function') { + this[method](params) + } else if (typeof method === 'function') { + method(params) + } + }) + + } catch (err) { + console.error(err) + } + } + + getEditForm (args = {}) { + + const node = createNode('div', { class: window.env.querySelectors.modal.slice(1) }, + Templates.templates.editForm) + + if (args.legend) { + node.querySelector('legend').innerHTML = args.legend + } + + this.root.appendChild(node) + node.style.display = 'block' + + this.listener([ + [node.querySelector(window.env.querySelectors.btnDestroyModal), 'click', destroyNode, node], //Remove modal dialog + [node.querySelector('form'), 'submit', NotesAPI.saveNote], + ]) + } +} \ No newline at end of file diff --git a/src/modules/NotesAPI.js b/src/modules/NotesAPI.js index 04b5829..bfad6ba 100644 --- a/src/modules/NotesAPI.js +++ b/src/modules/NotesAPI.js @@ -11,12 +11,14 @@ export default class NotesAPI{ * @returns {*} */ static getNotes (n, archive = false, key = (window.env.localStorageKey || '') ) { + let notes = JSON.parse(localStorage.getItem(key) || '[]') - if( archive === false ){ - notes = notes.filter( el => el.archive !== 'on' ) + if( typeof archive === 'boolean' ){ + notes = notes.filter( el => ( archive ? el.archive === 'on' : el.archive !== 'on' ) ) } + notes.sort((a, b) => { return new Date(a.createdAt) > new Date(b.createdAt) ? -1 : 1 }) @@ -32,6 +34,7 @@ export default class NotesAPI{ */ static getNotesSanitized( n = 0, archive = false){ let notes = NotesAPI.getNotes(0, archive) + notes = Validation.allAgainstSchema(notes, window.env.schemas.note ) return n > 0 ? notes.slice(0, n) : notes } @@ -68,11 +71,13 @@ export default class NotesAPI{ notes.push(noteToStore) } NotesAPI.saveNotesToStorage(notes) + + document.querySelector( window.env.querySelectors.noteEditForm ).closest('.modal').remove() + } else { - DOMController.alert( 'Could save the note! ' + isValid.errors.join(', '),0, 2 ) + DOMController.alert( 'Could save the note! ' + isValid.errors.join(', '),6666, 2 ) //, document.getElementById('modalMsgBox') } - console.log(NotesAPI.getNotes()) } /** diff --git a/src/styles/main.css b/src/styles/main.css index cda3675..a3c68eb 100644 --- a/src/styles/main.css +++ b/src/styles/main.css @@ -1,5 +1,26 @@ body{ - background-color: aliceblue; + background-color: ghostwhite; + margin-top: 100px; +} + +tbody td{ + font-size: 14px; +} + +#as-navbar{ + overflow: hidden; position: fixed; left: 0; top: 0; width: 100% +} + +.uk-text-truncate{ + cursor: text; +} + +#appConsole{ + display: flex; + z-index: 2; + position: fixed; + bottom: 2rem; + right: 2rem; } .icon{ @@ -12,12 +33,26 @@ body{ cursor: pointer; transition: transform 250ms; } + .icon.grid-control:hover{ filter: brightness(0.1) sepia(1) hue-rotate(90deg) saturate(1); transform: translateY(5%); } +@keyframes fadeInAnimation { + 0% { + opacity: 1; + } + 100% { + opacity: 0; + } +} +.as-removing{ + animation: fadeInAnimation ease 3s; + animation-iteration-count: 1; + animation-fill-mode: forwards; +} .icon.as-transition:hover{ transition: ease-in-out 100ms; diff --git a/src/templates/Views.js b/src/templates/Views.js index e7bc5c0..b1f1f5e 100644 --- a/src/templates/Views.js +++ b/src/templates/Views.js @@ -1,12 +1,12 @@ import grid from './grid' import editForm from './editForm' -import gridRow from './gridRow' +//import gridRow from '../../.trash/gridRow' export default { templates: { editForm: editForm, - gridRow: gridRow + // gridRow: gridRow }, grid: grid } \ No newline at end of file diff --git a/src/templates/editForm.js b/src/templates/editForm.js index 35d69a1..6b21355 100644 --- a/src/templates/editForm.js +++ b/src/templates/editForm.js @@ -1,6 +1,7 @@ export default `