Compare commits
2 Commits
3693c53d13
...
505f4dffc0
| Author | SHA1 | Date | |
|---|---|---|---|
| 505f4dffc0 | |||
| 49a67accd4 |
3
.browserslistrc
Normal file
3
.browserslistrc
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
last 2 versions
|
||||||
|
> 1%
|
||||||
|
IE 9
|
||||||
239
.gitignore
vendored
Normal file
239
.gitignore
vendored
Normal file
@@ -0,0 +1,239 @@
|
|||||||
|
.idea/
|
||||||
|
package-lock.json
|
||||||
|
|
||||||
|
### Node template
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||||
|
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||||
|
|
||||||
|
# Runtime data
|
||||||
|
pids
|
||||||
|
*.pid
|
||||||
|
*.seed
|
||||||
|
*.pid.lock
|
||||||
|
|
||||||
|
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||||
|
lib-cov
|
||||||
|
|
||||||
|
# Coverage directory used by tools like istanbul
|
||||||
|
coverage
|
||||||
|
*.lcov
|
||||||
|
|
||||||
|
# nyc test coverage
|
||||||
|
.nyc_output
|
||||||
|
|
||||||
|
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||||
|
.grunt
|
||||||
|
|
||||||
|
# Bower dependency directory (https://bower.io/)
|
||||||
|
bower_components
|
||||||
|
|
||||||
|
# node-waf configuration
|
||||||
|
.lock-wscript
|
||||||
|
|
||||||
|
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||||
|
build/Release
|
||||||
|
|
||||||
|
# Dependency directories
|
||||||
|
node_modules/
|
||||||
|
jspm_packages/
|
||||||
|
|
||||||
|
# Snowpack dependency directory (https://snowpack.dev/)
|
||||||
|
web_modules/
|
||||||
|
|
||||||
|
# TypeScript cache
|
||||||
|
*.tsbuildinfo
|
||||||
|
|
||||||
|
# Optional npm cache directory
|
||||||
|
.npm
|
||||||
|
|
||||||
|
# Optional eslint cache
|
||||||
|
.eslintcache
|
||||||
|
|
||||||
|
# Microbundle cache
|
||||||
|
.rpt2_cache/
|
||||||
|
.rts2_cache_cjs/
|
||||||
|
.rts2_cache_es/
|
||||||
|
.rts2_cache_umd/
|
||||||
|
|
||||||
|
# Optional REPL history
|
||||||
|
.node_repl_history
|
||||||
|
|
||||||
|
# Output of 'npm pack'
|
||||||
|
*.tgz
|
||||||
|
|
||||||
|
# Yarn Integrity file
|
||||||
|
.yarn-integrity
|
||||||
|
|
||||||
|
# dotenv environment variables file
|
||||||
|
.env
|
||||||
|
.env.test
|
||||||
|
|
||||||
|
# parcel-bundler cache (https://parceljs.org/)
|
||||||
|
.cache
|
||||||
|
.parcel-cache
|
||||||
|
|
||||||
|
# Next.js build output
|
||||||
|
.next
|
||||||
|
out
|
||||||
|
|
||||||
|
# Nuxt.js build / generate output
|
||||||
|
.nuxt
|
||||||
|
dist
|
||||||
|
|
||||||
|
# Gatsby files
|
||||||
|
.cache/
|
||||||
|
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||||
|
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||||
|
# public
|
||||||
|
|
||||||
|
# vuepress build output
|
||||||
|
.vuepress/dist
|
||||||
|
|
||||||
|
# Serverless directories
|
||||||
|
.serverless/
|
||||||
|
|
||||||
|
# FuseBox cache
|
||||||
|
.fusebox/
|
||||||
|
|
||||||
|
# DynamoDB Local files
|
||||||
|
.dynamodb/
|
||||||
|
|
||||||
|
# TernJS port file
|
||||||
|
.tern-port
|
||||||
|
|
||||||
|
# Stores VSCode versions used for testing VSCode extensions
|
||||||
|
.vscode-test
|
||||||
|
|
||||||
|
# yarn v2
|
||||||
|
.yarn/cache
|
||||||
|
.yarn/unplugged
|
||||||
|
.yarn/build-state.yml
|
||||||
|
.yarn/install-state.gz
|
||||||
|
.pnp.*
|
||||||
|
|
||||||
|
### Node template
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||||
|
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||||
|
|
||||||
|
# Runtime data
|
||||||
|
pids
|
||||||
|
*.pid
|
||||||
|
*.seed
|
||||||
|
*.pid.lock
|
||||||
|
|
||||||
|
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||||
|
lib-cov
|
||||||
|
|
||||||
|
# Coverage directory used by tools like istanbul
|
||||||
|
coverage
|
||||||
|
*.lcov
|
||||||
|
|
||||||
|
# nyc test coverage
|
||||||
|
.nyc_output
|
||||||
|
|
||||||
|
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||||
|
.grunt
|
||||||
|
|
||||||
|
# Bower dependency directory (https://bower.io/)
|
||||||
|
bower_components
|
||||||
|
|
||||||
|
# node-waf configuration
|
||||||
|
.lock-wscript
|
||||||
|
|
||||||
|
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||||
|
build/Release
|
||||||
|
|
||||||
|
# Dependency directories
|
||||||
|
node_modules/
|
||||||
|
jspm_packages/
|
||||||
|
|
||||||
|
# Snowpack dependency directory (https://snowpack.dev/)
|
||||||
|
web_modules/
|
||||||
|
|
||||||
|
# TypeScript cache
|
||||||
|
*.tsbuildinfo
|
||||||
|
|
||||||
|
# Optional npm cache directory
|
||||||
|
.npm
|
||||||
|
|
||||||
|
# Optional eslint cache
|
||||||
|
.eslintcache
|
||||||
|
|
||||||
|
# Microbundle cache
|
||||||
|
.rpt2_cache/
|
||||||
|
.rts2_cache_cjs/
|
||||||
|
.rts2_cache_es/
|
||||||
|
.rts2_cache_umd/
|
||||||
|
|
||||||
|
# Optional REPL history
|
||||||
|
.node_repl_history
|
||||||
|
|
||||||
|
# Output of 'npm pack'
|
||||||
|
*.tgz
|
||||||
|
|
||||||
|
# Yarn Integrity file
|
||||||
|
.yarn-integrity
|
||||||
|
|
||||||
|
# dotenv environment variables file
|
||||||
|
.env
|
||||||
|
.env.test
|
||||||
|
|
||||||
|
# parcel-bundler cache (https://parceljs.org/)
|
||||||
|
.cache
|
||||||
|
.parcel-cache
|
||||||
|
|
||||||
|
# Next.js build output
|
||||||
|
.next
|
||||||
|
out
|
||||||
|
|
||||||
|
# Nuxt.js build / generate output
|
||||||
|
.nuxt
|
||||||
|
dist
|
||||||
|
|
||||||
|
# Gatsby files
|
||||||
|
.cache/
|
||||||
|
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||||
|
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||||
|
# public
|
||||||
|
|
||||||
|
# vuepress build output
|
||||||
|
.vuepress/dist
|
||||||
|
|
||||||
|
# Serverless directories
|
||||||
|
.serverless/
|
||||||
|
|
||||||
|
# FuseBox cache
|
||||||
|
.fusebox/
|
||||||
|
|
||||||
|
# DynamoDB Local files
|
||||||
|
.dynamodb/
|
||||||
|
|
||||||
|
# TernJS port file
|
||||||
|
.tern-port
|
||||||
|
|
||||||
|
# Stores VSCode versions used for testing VSCode extensions
|
||||||
|
.vscode-test
|
||||||
|
|
||||||
|
# yarn v2
|
||||||
|
.yarn/cache
|
||||||
|
.yarn/unplugged
|
||||||
|
.yarn/build-state.yml
|
||||||
|
.yarn/install-state.gz
|
||||||
|
.pnp.*
|
||||||
|
|
||||||
3
babel.config.js
Normal file
3
babel.config.js
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
module.exports = {
|
||||||
|
presets: ["@babel/preset-env"]
|
||||||
|
}
|
||||||
31
package.json
Normal file
31
package.json
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"name": "radency_hometask_1_js",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "index.js",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"start": "cross-env SOURCE_MAP_ENV=true webpack serve --open",
|
||||||
|
"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"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"devDependencies": {
|
||||||
|
"@babel/core": "^7.15.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",
|
||||||
|
"html-webpack-plugin": "^5.3.2",
|
||||||
|
"mini-css-extract-plugin": "^2.3.0",
|
||||||
|
"terser-webpack-plugin": "^5.2.4",
|
||||||
|
"webpack": "^5.56.1",
|
||||||
|
"webpack-cli": "^4.8.0",
|
||||||
|
"webpack-dev-server": "^4.3.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
46
src/env.json
Normal file
46
src/env.json
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
{
|
||||||
|
"localStorageKey": "notes-home-task-1",
|
||||||
|
"prePopulateAmount": 7,
|
||||||
|
"lists": {
|
||||||
|
"monthsFull": ["January","February","March","April","May","June","July", "August","September","October","November","December"]
|
||||||
|
},
|
||||||
|
"alertTypes": [
|
||||||
|
"primary", "success", "warning", "danger"
|
||||||
|
],
|
||||||
|
"querySelectors": {
|
||||||
|
"console": "#appConsole",
|
||||||
|
"modal": ".modal",
|
||||||
|
"notesGrid": "#notesGrid",
|
||||||
|
"noteEditForm": "#noteEditForm",
|
||||||
|
"btnCreateNote": "#btnCreateNote",
|
||||||
|
"btnDestroyModal": "#btnDestroyModal",
|
||||||
|
"saveNote": "#saveNote",
|
||||||
|
"templates": {
|
||||||
|
"gridRow": "#noteRowTemplate",
|
||||||
|
"editForm": "#noteEditFormTemplate"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"gridOrder": {
|
||||||
|
"notes": [
|
||||||
|
"title",
|
||||||
|
"createdAt",
|
||||||
|
"category",
|
||||||
|
"content",
|
||||||
|
"dates",
|
||||||
|
"edit",
|
||||||
|
"archive",
|
||||||
|
"delete"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"schemas": {
|
||||||
|
"note": {
|
||||||
|
"id": [ false, "^[0-9]{6}$" ],
|
||||||
|
"title": [ true, "^(.{1,128})$" ],
|
||||||
|
"category": [ true, "^(task|random_thought|idea|quote)$" ],
|
||||||
|
"content": [ false, "^(.{1,1024}$)" ],
|
||||||
|
"archive": [ false, "^(on|)$" ],
|
||||||
|
"createdAt": [ false, "^202[1-9]-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\\.[0-9]{3}Z$" ],
|
||||||
|
"updatedAt": [ false, "^202[1-9]-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\\.[0-9]{3}Z$" ]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
1
src/images/archive_black_48dp.svg
Normal file
1
src/images/archive_black_48dp.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" height="48px" viewBox="0 0 24 24" width="48px" fill="#999"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M20.54 5.23l-1.39-1.68C18.88 3.21 18.47 3 18 3H6c-.47 0-.88.21-1.16.55L3.46 5.23C3.17 5.57 3 6.02 3 6.5V19c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V6.5c0-.48-.17-.93-.46-1.27zm-8.89 11.92L6.5 12H10v-2h4v2h3.5l-5.15 5.15c-.19.19-.51.19-.7 0zM5.12 5l.81-1h12l.94 1H5.12z"/></svg>
|
||||||
|
After Width: | Height: | Size: 415 B |
1
src/images/delete_black_48dp.svg
Normal file
1
src/images/delete_black_48dp.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" height="48px" viewBox="0 0 24 24" width="48px" fill="#999"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V9c0-1.1-.9-2-2-2H8c-1.1 0-2 .9-2 2v10zM18 4h-2.5l-.71-.71c-.18-.18-.44-.29-.7-.29H9.91c-.26 0-.52.11-.7.29L8.5 4H6c-.55 0-1 .45-1 1s.45 1 1 1h12c.55 0 1-.45 1-1s-.45-1-1-1z"/></svg>
|
||||||
|
After Width: | Height: | Size: 365 B |
1
src/images/edit_black_48dp.svg
Normal file
1
src/images/edit_black_48dp.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" height="48px" viewBox="0 0 24 24" width="48px" fill="#999"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM21.41 6.34l-3.75-3.75-2.53 2.54 3.75 3.75 2.53-2.54z"/></svg>
|
||||||
|
After Width: | Height: | Size: 256 B |
13
src/index.html
Normal file
13
src/index.html
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Notes in JS (Home task 1)</title>
|
||||||
|
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="appConsole" class="uk-container"></div>
|
||||||
|
<div id="app" class="uk-container"></div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
50
src/index.js
Normal file
50
src/index.js
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
import env from './env.json'
|
||||||
|
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)
|
||||||
|
|
||||||
|
//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()
|
||||||
|
|
||||||
|
/*NotesAPI.saveNote({
|
||||||
|
//id: 441512,
|
||||||
|
title: 'Note 1234',
|
||||||
|
category: 'category 1234',
|
||||||
|
content: 'contents 1234',
|
||||||
|
updatedAt: null
|
||||||
|
})*/
|
||||||
|
|
||||||
|
//NotesAPI.deleteNote(441512)
|
||||||
|
//console.log(NotesAPI.getNotes())
|
||||||
197
src/modules/DOMController.js
Normal file
197
src/modules/DOMController.js
Normal file
@@ -0,0 +1,197 @@
|
|||||||
|
import Templates from '../templates/Views'
|
||||||
|
import NotesAPI from './NotesAPI'
|
||||||
|
|
||||||
|
export default class DOMController {
|
||||||
|
|
||||||
|
constructor (root) {
|
||||||
|
this.root = root
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.root.innerHTML = Templates.grid
|
||||||
|
} catch (e) {
|
||||||
|
DOMController.alert(e.toString(), 0, 3)
|
||||||
|
}
|
||||||
|
|
||||||
|
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',
|
||||||
|
}],
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param msg
|
||||||
|
* @param fadeOut
|
||||||
|
* @param type
|
||||||
|
*/
|
||||||
|
static alert (msg = '', fadeOut = 0, type = 0) {
|
||||||
|
const alertTypes = window.env.alertTypes
|
||||||
|
const alert = this.createNode('div', { class: `uk-alert-${alertTypes[type]}`, 'uk-alert': null },
|
||||||
|
`<a class="uk-alert-close" uk-close></a>\n` + msg)
|
||||||
|
alert.style.display = 'block'
|
||||||
|
document.getElementById(window.env.querySelectors.console.slice(1)).appendChild(alert)
|
||||||
|
}
|
||||||
|
|
||||||
|
createNotesGrid (len, 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')
|
||||||
|
row.dataset.id = el.id
|
||||||
|
|
||||||
|
window.env.gridOrder.notes.forEach(key => {
|
||||||
|
let attrs = {}
|
||||||
|
let value = el[key] || ''
|
||||||
|
|
||||||
|
switch (key) {
|
||||||
|
case 'createdAt':
|
||||||
|
value = ___formatDate(value)
|
||||||
|
break
|
||||||
|
case 'content':
|
||||||
|
attrs.class = 'uk-text-truncate'
|
||||||
|
break
|
||||||
|
case 'dates':
|
||||||
|
attrs.class = 'uk-text-truncate'
|
||||||
|
value = ___findDates(el.content).join(', ')
|
||||||
|
break
|
||||||
|
case 'edit' :
|
||||||
|
attrs.class = 'icon icon-edit grid-control'
|
||||||
|
attrs['data-action'] = 'getEditForm'
|
||||||
|
break
|
||||||
|
case 'archive' :
|
||||||
|
attrs.class = 'icon icon-archive grid-control'
|
||||||
|
attrs['data-action'] = 'toArchive'
|
||||||
|
break
|
||||||
|
case 'delete' :
|
||||||
|
attrs.class = 'icon icon-delete grid-control'
|
||||||
|
attrs['data-action'] = 'toTrash'
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
const td = DOMController.createNode('td', attrs, value)
|
||||||
|
|
||||||
|
if (key.match(/^(edit|archive|delete)$/)) {
|
||||||
|
nodeToListen.push(td)
|
||||||
|
this.listener([td, 'click', td.dataset.action])
|
||||||
|
}
|
||||||
|
|
||||||
|
row.appendChild(td)
|
||||||
|
|
||||||
|
})
|
||||||
|
notesGrid.appendChild(row)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
* @returns {{}}
|
||||||
|
*/
|
||||||
|
static getFormData (selector = window.env.querySelectors.noteEditForm) {
|
||||||
|
const formData = new FormData(document.querySelector(selector))
|
||||||
|
const data = {}
|
||||||
|
|
||||||
|
for (let key of formData.keys()) {
|
||||||
|
data[key] = formData.get(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
}
|
||||||
96
src/modules/NotesAPI.js
Normal file
96
src/modules/NotesAPI.js
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
import DOMController from './DOMController'
|
||||||
|
import Validation from './ValidationController'
|
||||||
|
|
||||||
|
export default class NotesAPI{
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param n
|
||||||
|
* @param archive
|
||||||
|
* @param key
|
||||||
|
* @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' )
|
||||||
|
}
|
||||||
|
|
||||||
|
notes.sort((a, b) => {
|
||||||
|
return new Date(a.createdAt) > new Date(b.createdAt) ? -1 : 1
|
||||||
|
})
|
||||||
|
|
||||||
|
return n > 0 ? notes.slice(0, n) : notes
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param n
|
||||||
|
* @param archive
|
||||||
|
* @returns {*[]|[]}
|
||||||
|
*/
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param noteToStore
|
||||||
|
*/
|
||||||
|
static saveNote (noteToStore = null) {
|
||||||
|
|
||||||
|
if( Object.keys(noteToStore).length === 0 ){
|
||||||
|
noteToStore = DOMController.getFormData()
|
||||||
|
}
|
||||||
|
|
||||||
|
const isValid = Validation.againstSchema( noteToStore, window.env.schemas.note )
|
||||||
|
|
||||||
|
if( isValid.status === true ){
|
||||||
|
|
||||||
|
noteToStore = isValid.data
|
||||||
|
|
||||||
|
const notes = NotesAPI.getNotes()
|
||||||
|
const noteExists = notes.find(note => note.id === noteToStore.id)
|
||||||
|
|
||||||
|
if (noteExists) {
|
||||||
|
noteExists.title = noteToStore.title
|
||||||
|
noteExists.content = noteToStore.content
|
||||||
|
noteExists.category = noteToStore.category
|
||||||
|
noteExists.archive = noteToStore.archive || ''
|
||||||
|
noteExists.updatedAt = new Date().toISOString()
|
||||||
|
} else {
|
||||||
|
noteToStore.id = Math.floor(100000 + Math.random() * 900000)
|
||||||
|
noteToStore.archive = noteToStore.archive || ''
|
||||||
|
noteToStore.createdAt = new Date().toISOString()
|
||||||
|
notes.push(noteToStore)
|
||||||
|
}
|
||||||
|
NotesAPI.saveNotesToStorage(notes)
|
||||||
|
} else {
|
||||||
|
DOMController.alert( 'Could save the note! ' + isValid.errors.join(', '),0, 2 )
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(NotesAPI.getNotes())
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param id
|
||||||
|
*/
|
||||||
|
static deleteNote (id) {
|
||||||
|
const notes = NotesAPI.getNotes()
|
||||||
|
const filteredNotesByID = notes.filter(note => note.id !== id)
|
||||||
|
NotesAPI.saveNotesToStorage(filteredNotesByID)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param notes
|
||||||
|
* @param key
|
||||||
|
*/
|
||||||
|
static saveNotesToStorage (notes, key = window.env.localStorageKey) {
|
||||||
|
localStorage.setItem(key, JSON.stringify(notes, null, 2))
|
||||||
|
}
|
||||||
|
}
|
||||||
74
src/modules/ValidationController.js
Normal file
74
src/modules/ValidationController.js
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
export default class ValidationController {
|
||||||
|
|
||||||
|
|
||||||
|
static allAgainstSchema(array, schema) {
|
||||||
|
const objs = []
|
||||||
|
|
||||||
|
array.forEach( obj => {
|
||||||
|
const res = this.againstSchema(obj, schema)
|
||||||
|
|
||||||
|
if( res.status === true ){
|
||||||
|
objs.push( res.data )
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
return objs
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param obj
|
||||||
|
* @param obj.key => schema.key
|
||||||
|
* @param obj.values => [ <required>: boolean, <regexp_pattern>: string ]
|
||||||
|
* @param schema
|
||||||
|
* @returns {{error_text: *[], data: {}, errors: *[], status: boolean}}
|
||||||
|
*/
|
||||||
|
static againstSchema (obj, schema) {
|
||||||
|
|
||||||
|
const out = {
|
||||||
|
status: false,
|
||||||
|
error_text: [],
|
||||||
|
data: {},
|
||||||
|
errors: [],
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
for (const [key, value] of Object.entries(schema)) {
|
||||||
|
const field = (obj[key] || '').toString().trim()
|
||||||
|
|
||||||
|
if (field !== '') {
|
||||||
|
const regex = new RegExp(value[1].toString(), 'i')
|
||||||
|
|
||||||
|
if (regex.exec(field)) {
|
||||||
|
out.data[key] = field
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
if (value[0]) {
|
||||||
|
out.errors.push(key)
|
||||||
|
out.error_text.push(`${key} is required`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
if (value[0]) {
|
||||||
|
out.errors.push(key)
|
||||||
|
out.error_text.push(`${key} is empty`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
|
||||||
|
console.error(e.toString())
|
||||||
|
out.error_text.push(`Critical error! See console`)
|
||||||
|
}
|
||||||
|
|
||||||
|
out.status = !(out.error_text.length > 0 && out.errors.length > 0)
|
||||||
|
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
79
src/styles/main.css
Normal file
79
src/styles/main.css
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
body{
|
||||||
|
background-color: aliceblue;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon{
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: center;
|
||||||
|
background-size: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon.grid-control{
|
||||||
|
cursor: pointer;
|
||||||
|
transition: transform 250ms;
|
||||||
|
}
|
||||||
|
.icon.grid-control:hover{
|
||||||
|
filter: brightness(0.1) sepia(1) hue-rotate(90deg) saturate(1);
|
||||||
|
transform: translateY(5%);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.icon.as-transition:hover{
|
||||||
|
transition: ease-in-out 100ms;
|
||||||
|
background-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-edit{
|
||||||
|
background-image: url(../images/edit_black_48dp.svg)
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-archive{
|
||||||
|
background-image: url(../images/archive_black_48dp.svg)
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-delete{
|
||||||
|
background-image: url(../images/delete_black_48dp.svg)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The Modal (background) */
|
||||||
|
#noteEditFormModal, .modal {
|
||||||
|
display: none; /* Hidden by default */
|
||||||
|
position: fixed; /* Stay in place */
|
||||||
|
z-index: 1; /* Sit on top */
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 100%; /* Full width */
|
||||||
|
height: 100%; /* Full height */
|
||||||
|
overflow: auto; /* Enable scroll if needed */
|
||||||
|
background-color: rgb(0,0,0); /* Fallback color */
|
||||||
|
background-color: rgba(0,0,0,0.6); /* Black w/ opacity */
|
||||||
|
}
|
||||||
|
|
||||||
|
#alertMsg{
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Modal Content/Box */
|
||||||
|
.modal-content {
|
||||||
|
background-color: #fefefe;
|
||||||
|
margin: 15% auto; /* 15% from the top and centered */
|
||||||
|
padding: 20px;
|
||||||
|
border: 1px solid #888;
|
||||||
|
width: 80%; /* Could be more or less, depending on screen size */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The Close Button */
|
||||||
|
.close {
|
||||||
|
color: #aaa;
|
||||||
|
float: right;
|
||||||
|
font-size: 28px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close:hover,
|
||||||
|
.close:focus {
|
||||||
|
color: black;
|
||||||
|
text-decoration: none;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
1
src/styles/uikit.min.css
vendored
Normal file
1
src/styles/uikit.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
12
src/templates/Views.js
Normal file
12
src/templates/Views.js
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import grid from './grid'
|
||||||
|
import editForm from './editForm'
|
||||||
|
import gridRow from './gridRow'
|
||||||
|
|
||||||
|
|
||||||
|
export default {
|
||||||
|
templates: {
|
||||||
|
editForm: editForm,
|
||||||
|
gridRow: gridRow
|
||||||
|
},
|
||||||
|
grid: grid
|
||||||
|
}
|
||||||
52
src/templates/editForm.js
Normal file
52
src/templates/editForm.js
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
export default `<form id="noteEditForm" class="uk-form-horizontal uk-margin-large modal-content" style="background: ghostwhite">
|
||||||
|
<fieldset class="uk-fieldset">
|
||||||
|
<legend class="uk-legend">Edit Note</legend>
|
||||||
|
|
||||||
|
<div class="uk-margin">
|
||||||
|
<label class="uk-form-label" for="formTitle">Title</label>
|
||||||
|
<div class="uk-form-controls">
|
||||||
|
<input id="formTitle" name="title" class="uk-input" type="text" maxlength="127" placeholder="Add a title...">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="uk-margin" style="background: aquamarine">
|
||||||
|
<label class="uk-form-label uk-float-right" for="formCategory">Category</label>
|
||||||
|
<div class="uk-form-controls">
|
||||||
|
<select id="formCategory" name="category" class="uk-select">
|
||||||
|
<option disabled value="" selected>Select a category...</option>
|
||||||
|
<option disabled value=""></option>
|
||||||
|
<option value="task">Task</option>
|
||||||
|
<option value="random_thought">Random Thought</option>
|
||||||
|
<option value="idea">Idea</option>
|
||||||
|
<option value="quote">Quote</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="uk-margin">
|
||||||
|
<textarea name="content" class="uk-textarea" rows="6" placeholder="Description..."></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="uk-margin">
|
||||||
|
<label class="uk-form-label uk-float-right" for="formArchived">Archived</label>
|
||||||
|
<div class="uk-form-controls">
|
||||||
|
<input id="formArchived" name="archive" class="uk-checkbox" type="checkbox">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<div class="uk-clearfix">
|
||||||
|
<div class="uk-float-right">
|
||||||
|
<button type="submit" class="uk-button uk-button-default">Sumbit</button>
|
||||||
|
</div>
|
||||||
|
<div class="uk-float-left">
|
||||||
|
<button type="button" id="btnDestroyModal" class="uk-button uk-button-danger">Close</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</fieldset>
|
||||||
|
</form>`
|
||||||
21
src/templates/grid.js
Normal file
21
src/templates/grid.js
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
export default `<table id="notesGrid" class="uk-table uk-table-small uk-table-responsive1 uk-table-striped uk-table-hover uk-table-divider">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Title</th>
|
||||||
|
<th>Created</th>
|
||||||
|
<th>Category</th>
|
||||||
|
<th>Content</th>
|
||||||
|
<th>Dates</th>
|
||||||
|
<th></th>
|
||||||
|
<th class="icon icon-archive"></th>
|
||||||
|
<th class="icon icon-delete"></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
</tbody>
|
||||||
|
<tr>
|
||||||
|
<td colspan="8" class="uk-margin uk-text-right">
|
||||||
|
<button id="btnCreateNote" class="uk-button uk-button-primary" data-action="getEditForm">Create Note</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>`
|
||||||
13
src/templates/gridRow.js
Normal file
13
src/templates/gridRow.js
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
export default `
|
||||||
|
<td></td>
|
||||||
|
<td></td>
|
||||||
|
<td></td>
|
||||||
|
<td></td>
|
||||||
|
<td></td>
|
||||||
|
<td class="uk-text-right">
|
||||||
|
<div class="uk-button-group">
|
||||||
|
<button class="uk-button uk-button-default icon icon-archive"></button>
|
||||||
|
<button class="uk-button uk-button-default"></button>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
`
|
||||||
20
styles.css
Normal file
20
styles.css
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
.icon{
|
||||||
|
display: table-cell;
|
||||||
|
min-height: 24px;
|
||||||
|
max-width: 24px;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: center;
|
||||||
|
background-size: 24px;
|
||||||
|
color: green;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon:hover{
|
||||||
|
transition: ease-in-out 100ms;
|
||||||
|
background-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.icon-archive{
|
||||||
|
/*background-image: url(https://upload.wikimedia.org/wikipedia/commons/thumb/f/fb/Ic_archive_48px.svg/48px-Ic_archive_48px.svg.png)*/
|
||||||
|
background-image: url(https://upload.wikimedia.org/wikipedia/commons/f/fb/Ic_archive_48px.svg)
|
||||||
|
}
|
||||||
91
webpack.config.js
Normal file
91
webpack.config.js
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
const path = require('path')
|
||||||
|
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
|
||||||
|
const HtmlWebpackPlugin = require('html-webpack-plugin')
|
||||||
|
const {CleanWebpackPlugin} = require('clean-webpack-plugin')
|
||||||
|
const TerserPlugin = require("terser-webpack-plugin")
|
||||||
|
|
||||||
|
let mode = 'development', target = 'web'
|
||||||
|
|
||||||
|
if (process.env.NODE_ENV === 'production') {
|
||||||
|
mode = 'development'
|
||||||
|
target = 'browserslist'
|
||||||
|
}
|
||||||
|
|
||||||
|
const plugins = [
|
||||||
|
new CleanWebpackPlugin(),
|
||||||
|
new MiniCssExtractPlugin(),
|
||||||
|
new HtmlWebpackPlugin({
|
||||||
|
template: './src/index.html',
|
||||||
|
inject: 'body'
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
|
||||||
|
console.log( process.env.SOURCE_MAP_ENV )
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
mode: mode,
|
||||||
|
target: target,
|
||||||
|
module: {
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
test: /\.(jpe?g|png|gif|svg)$/i,
|
||||||
|
type: "asset",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.css$/i,
|
||||||
|
use: [
|
||||||
|
{
|
||||||
|
loader: MiniCssExtractPlugin.loader,
|
||||||
|
// This is required for asset imports in CSS, such as url()
|
||||||
|
options: { publicPath: '' },
|
||||||
|
},
|
||||||
|
'css-loader',
|
||||||
|
///"postcss-loader",
|
||||||
|
// according to the docs, sass-loader should be at the bottom, which
|
||||||
|
// loads it first to avoid prefixes in your sourcemaps and other issues.
|
||||||
|
//"sass-loader",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.js$/,
|
||||||
|
exclude: /node_modules/,
|
||||||
|
use: {
|
||||||
|
loader: 'babel-loader',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
entry: { app: './src/index' },
|
||||||
|
output: {
|
||||||
|
path: path.resolve(__dirname, 'dist'),
|
||||||
|
filename: '[name].[hash].js',
|
||||||
|
},
|
||||||
|
plugins: plugins,
|
||||||
|
devtool: mode === 'production' ? 'source-map' : ( process.env.SOURCE_MAP_ENV ? 'source-map' : false ),
|
||||||
|
optimization: {
|
||||||
|
minimize: true,
|
||||||
|
minimizer: [
|
||||||
|
new TerserPlugin({
|
||||||
|
extractComments: true,
|
||||||
|
terserOptions: {
|
||||||
|
ecma: 5,
|
||||||
|
compress: {
|
||||||
|
drop_console: mode === 'production',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
|
||||||
|
],
|
||||||
|
},
|
||||||
|
devServer: {
|
||||||
|
static: path.resolve(__dirname, 'dist'),
|
||||||
|
compress: true,
|
||||||
|
port: 1976,
|
||||||
|
liveReload: true,
|
||||||
|
/*overlay: {
|
||||||
|
warnings: true,
|
||||||
|
errors: true,
|
||||||
|
}*/
|
||||||
|
|
||||||
|
},
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user