diff --git a/.debts b/.debts
index 4099407..7273c0f 100644
--- a/.debts
+++ b/.debts
@@ -1 +1 @@
-23
+25
diff --git a/.eslintignore b/.eslintignore
new file mode 100644
index 0000000..ae7617a
--- /dev/null
+++ b/.eslintignore
@@ -0,0 +1 @@
+purity-modules/
\ No newline at end of file
diff --git a/.prettierrc.yaml b/.prettierrc.yaml
index 6b9c164..0cce527 100644
--- a/.prettierrc.yaml
+++ b/.prettierrc.yaml
@@ -1,4 +1,5 @@
-trailingComma: 'es5'
+trailingComma: es5
tabWidth: 2
semi: false
singleQuote: true
+arrowParens: avoid
diff --git a/README.md b/README.md
index ddbc4df..b04872f 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,7 @@
# Purity ToDo Application
+[](https://app.netlify.com/sites/reactive-todo-app/deploys)
+
The app is built using [Purity](https://github.com/tatomyr/purity) reactive store & rendering library
and bare native ES modules.
The aim is mostly proof of concept.
diff --git a/cypress/integration/examples/todo.spec.js b/cypress/integration/examples/todo.spec.js
index fa7ab7a..4e306b5 100644
--- a/cypress/integration/examples/todo.spec.js
+++ b/cypress/integration/examples/todo.spec.js
@@ -15,25 +15,15 @@ context('Basic flow', () => {
// a:
cy.get('#newTask').type('One')
cy.get('form#newTask-form').submit()
- cy.get('ol#tasks-list li')
- .should('have.length', 1)
- .contains('One')
+ cy.get('ol#tasks-list li').should('have.length', 1).contains('One')
cy.wait(0)
- cy.get('#newTask')
- .should('have.value', '')
- .type('Two')
+ cy.get('#newTask').should('have.value', '').type('Two')
cy.get('form#newTask-form').submit()
- cy.get('ol#tasks-list li')
- .should('have.length', 2)
- .contains('Two')
+ cy.get('ol#tasks-list li').should('have.length', 2).contains('Two')
cy.wait(0)
- cy.get('#newTask')
- .should('have.value', '')
- .type('Three')
+ cy.get('#newTask').should('have.value', '').type('Three')
cy.get('form#newTask-form').submit()
- cy.get('ol#tasks-list li')
- .should('have.length', 3)
- .contains('Three')
+ cy.get('ol#tasks-list li').should('have.length', 3).contains('Three')
cy.wait(0)
cy.get('#all .counter').should('have.text', '3')
@@ -41,17 +31,13 @@ context('Basic flow', () => {
cy.get('#completed .counter').should('have.text', '0')
// b:
- cy.get('ol#tasks-list li:nth-child(2)')
- .contains('Two')
- .click()
+ cy.get('ol#tasks-list li:nth-child(2)').contains('Two').click()
cy.get('.task-details__controls a:first-child').click()
cy.get('ol#tasks-list li').should('have.length', 2)
cy.get('#nav-button-completed').click()
cy.get('#completed').should('have.class', 'active')
- cy.get('ol#tasks-list li')
- .should('have.length', 1)
- .contains('Two')
+ cy.get('ol#tasks-list li').should('have.length', 1).contains('Two')
cy.wait(0)
cy.get('#all .counter').should('have.text', '3')
@@ -75,9 +61,7 @@ context('Basic flow', () => {
cy.get('#clear').click()
cy.wait(0)
- cy.get('#newTask')
- .should('have.value', '')
- .should('not.have.focus')
+ cy.get('#newTask').should('have.value', '').should('not.have.focus')
cy.get('#completed').should('have.class', 'active')
cy.get('#all .counter').should('have.text', '3')
cy.get('#active .counter').should('have.text', '2')
@@ -96,12 +80,8 @@ context('Basic flow', () => {
cy.get('#all .counter').should('have.text', '4')
cy.get('#active .counter').should('have.text', '3')
cy.get('#completed .counter').should('have.text', '1')
- cy.get('#newTask')
- .should('have.value', '')
- .should('not.have.focus')
- cy.get('ol#tasks-list li')
- .should('have.length', 3)
- .contains('Four')
+ cy.get('#newTask').should('have.value', '').should('not.have.focus')
+ cy.get('ol#tasks-list li').should('have.length', 3).contains('Four')
// e:
cy.get('#nav-button-completed').click()
@@ -119,9 +99,7 @@ context('Basic flow', () => {
cy.get('#active .counter').should('have.text', '2')
cy.get('#completed .counter').should('have.text', '1')
- cy.get('ol#tasks-list li:last-child')
- .contains('One')
- .click()
+ cy.get('ol#tasks-list li:last-child').contains('One').click()
cy.get('.task-details__controls a:first-child').click()
cy.wait(0)
cy.get('#active').should('have.class', 'active')
@@ -134,8 +112,23 @@ context('Basic flow', () => {
cy.get('#all .counter').should('have.text', '4')
cy.get('#active .counter').should('have.text', '2')
cy.get('#completed .counter').should('have.text', '2')
- cy.get('#newTask')
- .should('have.text', '')
- .should('not.have.focus')
+ cy.get('#newTask').should('have.text', '').should('not.have.focus')
+ })
+})
+
+context('Security', () => {
+ beforeEach(() => {
+ cy.visit('/#/active')
+ })
+ // TODO: make test fail first
+ it('sanitizes quotes', async () => {
+ cy.get('#newTask').type('test" onclick="event.target.value = \'OOPS\'"')
+ // FIXME: why click doesn't work??
+ // TODO: [!MAJOR] investigate why can't add multiple event handlers !
+ cy.get('#newTask').click()
+ cy.get('#newTask').should(
+ 'have.value',
+ 'test" onclick="event.target.value = \'OOPS\'"'
+ )
})
})
diff --git a/src/config/version b/src/config/version
index 37ad5c8..a13e7b9 100644
--- a/src/config/version
+++ b/src/config/version
@@ -1 +1 @@
-9.0.1
+10.0.0
diff --git a/src/hashrouter.js b/src/hashrouter.js
index 237b82c..795276d 100644
--- a/src/hashrouter.js
+++ b/src/hashrouter.js
@@ -3,18 +3,20 @@ let match
// FIXME: I believe the common `match` will cause data inferring issues. Work this out.
export const router = component => props => component({ ...match, ...props })
-export const Switch = props => {
- for (const [path, component] of Object.entries(props)) {
+export const getParams = () => match
+
+export const Switch = routes => {
+ for (const path in routes) {
const params = (path.match(/:(\w+)/g) || []).map(param => param.slice(1))
const matchRe = new RegExp(path.replace(/:\w+/g, '(\\w+[\\w\\-\\.]*)'))
const matches = window.location.hash.match(matchRe)
+ console.log(matches, path, ':', routes[path])
if (!matches) {
continue
}
- const [_, ...args] = window.location.hash.match(matchRe)
+ const [_, ...args] = matches
match = params.reduce(($, param, i) => ({ ...$, [param]: args[i] }), {})
- return (props => component({ ...props, ...match }))()
- // return router(component)(props)
+ return routes[path](match)
}
}
diff --git a/src/index.html b/src/index.html
index 80cc921..7189800 100644
--- a/src/index.html
+++ b/src/index.html
@@ -6,8 +6,8 @@
Purity ToDo
-
-
+
+
diff --git a/src/index.js b/src/index.js
index e41bc89..3448b0d 100644
--- a/src/index.js
+++ b/src/index.js
@@ -7,8 +7,9 @@ mount(App)
// Registering service worker
// TODO: provide a maintainable DEV/PROD flagging
const stage = location.hostname === 'localhost' ? 'DEV' : 'PROD'
-console.log(stage, stage !== 'DEV', 'serviceWorker' in navigator)
-if (stage !== 'DEV' && 'serviceWorker' in navigator) {
+console.log('STAGE:', stage)
+console.log('SERVICE WORKER:', 'serviceWorker' in navigator)
+if (stage === 'PROD' && 'serviceWorker' in navigator) {
navigator.serviceWorker.register('./service-worker.js').then(() => {
console.log('Service Worker Registered')
})
diff --git a/src/manifest.json b/src/manifest.json
index abe0549..d31476d 100644
--- a/src/manifest.json
+++ b/src/manifest.json
@@ -28,7 +28,7 @@
"type": "image/png"
}
],
- "start_url": "/index.html",
+ "start_url": "/#/active",
"display": "standalone",
"background_color": "#5554AB",
"theme_color": "#37366E"
diff --git a/src/modules.js b/src/modules.js
index 8ab016e..727f595 100644
--- a/src/modules.js
+++ b/src/modules.js
@@ -1,17 +1,13 @@
-export {
- createStore,
- render,
-} from 'https://tatomyr.github.io/purity/core.min.js'
-export { registerAsync } from 'https://tatomyr.github.io/purity/utils/register-async.min.js'
-export { debounce } from 'https://tatomyr.github.io/purity/lib/debounce.min.js'
-// TODO: use minified version after fixing md5.min.js
-export { md5 } from 'https://tatomyr.github.io/purity/lib/md5.js'
-export { sanitize } from 'https://tatomyr.github.io/purity/lib/sanitize.min.js'
+export { createStore, render } from './purity-modules/core.js'
+export { registerAsync } from './purity-modules/register-async.js'
+export { debounce } from './purity-modules/debounce.js'
+export { md5 } from './purity-modules/md5.js'
+export { sanitize } from './purity-modules/sanitize.js'
// Local:
// export { createStore, render } from 'http://192.168.1.3:8081/core.js'
// export { registerAsync } from 'http://192.168.1.3:8081/utils/register-async.js'
-// export { debounce } from 'http://192.168.1.3:8081/lib/debounce.js'
-// export { md5 } from 'http://192.168.1.3:8081/lib/md5.js'
-// export { sanitize } from 'http://192.168.1.3:8081/lib/sanitize.js'
+// export { debounce } from 'http://192.168.1.3:8081/utils/debounce.js'
+// export { md5 } from 'http://192.168.1.3:8081/utils/md5.js'
+// export { sanitize } from 'http://192.168.1.3:8081/utils/sanitize.js'
diff --git a/src/purity-modules/README.md b/src/purity-modules/README.md
new file mode 100644
index 0000000..618d119
--- /dev/null
+++ b/src/purity-modules/README.md
@@ -0,0 +1,105 @@
+# Useful utils
+
+A handful of algorithms from different sources for using with **Purity** or without it.
+
+## MD5 hashing algorithm
+
+Borrowed [here](http://www.myersdaily.org/joseph/javascript/md5-text.html).
+
+Usage:
+
+```javascript
+import { md5 } from 'https://tatomyr.github.io/purity/lib/md5.js'
+
+console.log(md5('some string'))
+```
+
+## Visibility Sensor
+
+Taken [here](https://vanillajstoolkit.com/helpers/isinviewport/)
+
+Usage:
+
+```javascript
+import { trackVisibility } from 'https://tatomyr.github.io/purity/lib/visibility-sensor.js'
+
+…
+trackVisibility($element, isInViewport => {
+ console.log($element + ' is in viewport:' + isInViewport)
+})
+…
+```
+
+
+
+## Debounce
+
+Usage:
+
+```javascript
+import { debounce } from 'https://tatomyr.github.io/purity/lib/debounce.js'
+
+…
+render`
+
+`
+…
+```
+
+Use a positive `timeout` for triggering the callback on the leading edge and a negative one for triggering the callback on the trailing edge.
+
+## Delay
+
+Usage:
+
+```javascript
+import { delay } from 'https://tatomyr.github.io/purity/lib/delay.js'
+
+…
+delay(