├── .babelrc ├── .eslintignore ├── .eslintrc ├── .eslintrc.json ├── .gitignore ├── AUTHORS ├── README.md ├── assets ├── index.js ├── index.js.map └── sven.browser.js ├── examples ├── browser │ └── src │ │ ├── assets │ │ ├── app.css │ │ ├── index.js │ │ ├── sven.browser.js │ │ ├── sven.js │ │ ├── todomvc-app-css │ │ │ ├── index.css │ │ │ ├── package.json │ │ │ └── readme.md │ │ └── todomvc-common │ │ │ ├── base.css │ │ │ ├── base.js │ │ │ ├── package.json │ │ │ └── readme.md │ │ ├── index.html │ │ ├── todomovc.js │ │ └── todomovc.jsx ├── clickmeapp │ ├── dist │ │ ├── app.css │ │ ├── index.html │ │ ├── index.js │ │ └── index.min.js │ ├── package-lock.json │ ├── package.json │ ├── run.sh │ ├── src │ │ ├── app.css │ │ ├── app.jsx │ │ ├── get-json.js │ │ ├── index.html │ │ ├── index.jsx │ │ └── store.js │ ├── webpack.config.js │ └── yarn.lock ├── composable │ ├── .babelrc │ ├── package-lock.json │ ├── package.json │ ├── src │ │ ├── app.jsx │ │ ├── counter.jsx │ │ ├── get-json.js │ │ ├── index.html │ │ ├── index.jsx │ │ ├── store.js │ │ ├── welcome.jsx │ │ └── welcome2.jsx │ └── webpack.config.js └── todomvc │ ├── .babelrc │ ├── .gitignore │ ├── docs │ ├── app.css │ ├── bundle.js │ ├── index.html │ ├── todomvc-app-css │ │ ├── index.css │ │ ├── package.json │ │ └── readme.md │ └── todomvc-common │ │ ├── base.css │ │ ├── base.js │ │ ├── package.json │ │ └── readme.md │ ├── package.json │ ├── src │ ├── app.css │ ├── app.jsx │ ├── get-json.js │ ├── index.html │ ├── index.jsx │ ├── store.js │ ├── todomvc-app-css │ │ ├── index.css │ │ ├── package.json │ │ └── readme.md │ └── todomvc-common │ │ ├── base.css │ │ ├── base.js │ │ ├── package.json │ │ └── readme.md │ ├── todomvc-app-css │ ├── index.css │ ├── package.json │ └── readme.md │ ├── todomvc-common │ ├── base.css │ ├── base.js │ ├── package.json │ └── readme.md │ ├── webpack.config.js │ └── yarn.lock ├── npmbuild ├── README.md ├── index.js ├── index.js.map └── package.json ├── package-lock.json ├── package.json ├── rollup.config.js ├── scripts ├── authors.sh ├── build.sh ├── lint.sh ├── prod.sh └── push.sh ├── src ├── .DS_Store ├── core │ ├── create.js │ ├── render.js │ └── version.js ├── index.js ├── lib │ ├── deep-clone.js │ ├── deep-freeze.js │ ├── shallow-copy.js │ ├── uuid.js │ └── validations.js ├── store │ └── create-store.js └── web │ ├── life-cycle.js │ ├── save-state.js │ └── set-state.js ├── tests └── render-to-html.js ├── webpack.config.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "env":{ 3 | "development": { 4 | "presets": ["@babel/preset-env"] 5 | }, 6 | "production": { 7 | "presets": ["@babel/preset-env"] 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | examples 3 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "env": { 4 | "browser": true, 5 | "mocha": true, 6 | "node": true 7 | }, 8 | "rules": { 9 | "block-scoped-var": 0, 10 | "padded-blocks": 0, 11 | "strict": 0 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "parserOptions": { 3 | "ecmaVersion": 6, 4 | "sourceType": "module" 5 | }, 6 | "env": { 7 | "browser": true, 8 | "mocha": true, 9 | "es6": true 10 | }, 11 | "rules": { 12 | "semi": 2, 13 | "no-console": 1 14 | }, 15 | "extends": "eslint:recommended" 16 | } 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea 3 | dist 4 | build 5 | node_modules 6 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Sven Anders Robbestad 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SvenJS 2 | 3 | A JavaScript framework for composable web apps 4 | 5 | # Demos 6 | 7 | - [TodoMVC](http://svenanders.github.io/svenjs-todomvc/) ([Source](https://github.com/svenanders/svenjs-todomvc)) 8 | 9 | # Releases 10 | 11 | - 2.0.2: ES modules, bug fixes and more! 12 | - 0.3.2: Added *_beforeMount* life cycle method. 13 | - 0.3.1: Added composition (importing components and referencing them in JSX by name). For instance: _const SecondComponent = require("SecondComponent")_. Referenced in _render_ like this: __ 14 | - 0.3.0: Renamed life cycle methods. New names: *_didMount* & *_didUpdate* 15 | 16 | # Features 17 | 18 | - Enforced state immutability 19 | 20 | - Minimal file size 21 | 22 | # Goals 23 | 24 | - Can be used in a browser script tag (almost there) 25 | 26 | - A web library that enables you to write code that can be accessed both serverside and clientside 27 | 28 | - Built in store implementation (todo) 29 | 30 | # Install 31 | 32 | Use the npm version: 33 | 34 | ```bash 35 | npm install svenjs 36 | ``` 37 | 38 | Build youself. Clone this repo and run 39 | 40 | ```bash 41 | npm run build 42 | ``` 43 | 44 | # How to use 45 | 46 | ```html 47 | import SvenJs from "svenjs"; 48 | 49 | SvenJs.create({ 50 | initialState: { 51 | clicks: 0 52 | }, 53 | render() { 54 | const clickFunc = () =>{ 55 | let clicks=this.state.clicks; 56 | this.setState({clicks: ++clicks }); 57 | } 58 | return (
59 |

The Click App

60 |
61 | 62 |
63 |
64 |

Click stats

65 |

You have clicked on the button {this.state.clicks} times

66 |
67 |
) 68 | } 69 | }); 70 | SvenJs.render(App, document.getElementById("app")) 71 | ``` 72 | 73 | ## Related Modules 74 | 75 | * [svenjsx](https://github.com/svenanders/svenjsx) - JSX as used by SvenJS. 76 | 77 | * [svenjsx-loader](https://github.com/svenanders/svenjsx-loader) - Webpack loader for SvenJS. 78 | 79 | -------------------------------------------------------------------------------- /assets/index.js: -------------------------------------------------------------------------------- 1 | module.exports=function(t){var n={};function e(o){if(n[o])return n[o].exports;var r=n[o]={i:o,l:!1,exports:{}};return t[o].call(r.exports,r,r.exports,e),r.l=!0,r.exports}return e.m=t,e.c=n,e.d=function(t,n,o){e.o(t,n)||Object.defineProperty(t,n,{enumerable:!0,get:o})},e.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},e.t=function(t,n){if(1&n&&(t=e(t)),8&n)return t;if(4&n&&"object"==typeof t&&t&&t.__esModule)return t;var o=Object.create(null);if(e.r(o),Object.defineProperty(o,"default",{enumerable:!0,value:t}),2&n&&"string"!=typeof t)for(var r in t)e.d(o,r,function(n){return t[n]}.bind(null,r));return o},e.n=function(t){var n=t&&t.__esModule?function(){return t.default}:function(){return t};return e.d(n,"a",n),n},e.o=function(t,n){return Object.prototype.hasOwnProperty.call(t,n)},e.p="",e(e.s=0)}([function(t,n,e){t.exports=e(1)},function(t,n,e){"use strict";e.r(n);function o(t){return(o="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}var r=function t(n){if(!n||"object"!==o(n))return n;var e={};return Array.isArray(n)?e=n.map(function(n){return t(n)}):Object.keys(n).forEach(function(o){return e[o]=t(n[o])}),e};function i(t){return(i="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}var u=function t(n){return Object.freeze(n),Object.getOwnPropertyNames(n).forEach(function(e){!n.hasOwnProperty(e)||null===n[e]||"object"!==i(n[e])&&"function"!=typeof n[e]||Object.isFrozen(n[e])||t(n[e])}),n},c=function(t,n){var e=r(n);return u(e),e},f={}.toString,a=function(t){return"function"==typeof t},s=function(t){return"[object Object]"===f.call(t)},l=function(t){return"[object Array]"===f.call(t)},p=function(){var t=function(){return Math.floor(65536*(1+Math.random())).toString(16).substring(1)};return"".concat(t()+t(),"-").concat(t())};function d(t){return(d="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}var y=function(t,n){return n.appendChild(t)},b=Object.prototype.hasOwnProperty,m=function(t,n){if(b.call(t,"children")&&l(t.children)&&t.children.forEach(function(t){"string"!=typeof t&&"number"!=typeof t||n.appendChild(document.createTextNode(t))}),b.call(t,"attrs")){var e=t.attrs;for(var o in e)if("config"!==o&&"key"!==o&&("disabled"!==o||!1!==e[o]))if("class"==o||"className"==o)n.className=e[o].toString();else if(a(e[o])&&"on"==o.slice(0,2))n[o.toLowerCase()]=e[o];else{if("checked"===o&&(!1===e[o]||""===e[o]))continue;try{n.setAttribute(""+o,e[o].toString())}catch(t){console.error("e",t)}}}return n},v=function(t){void 0===t.tag&&(t.tag="span",t.attrs={sjxid:p()});var n=document.createElement(t.tag);return m(t,n),n},j=function(t,n){var e=document.createDocumentFragment(),o=document.createElement(t.tag);m(t,n.rootNode);var r=function t(n,e){var o;return b.call(n,"children")?l(n.children)&&n.children.forEach(function(n){null!==n&&"object"===d(n)&&(o=v(n),t(n,o),y(o,e)),l(n)&&n.forEach(function(n){b.call(n,"render")||(o=v(n),t(n,o),y(o,e))})}):"object"===d(n)&&b.call(n,"render")&&t(n.render(),e),e}(t,o);return e.appendChild(r),e},h=function(t,n){var e,o=arguments.length>2&&void 0!==arguments[2]&&arguments[2];if(!n)return"Error: No node to attach";s(t)&&(b.call(t,"_svenjs")||(t._svenjs={rootNode:!1}),t._svenjs.rootNode=n),n.innerHTML="",e=s(o)?o:t.render(),n.appendChild(j(e,t._svenjs))},S=function(t){var n;t._svenjs.rootNode&&(n=t._svenjs.rootNode),t.hasOwnProperty("attrs")&&t.attrs.hasOwnProperty("sjxid")&&(n||(n=document.querySelector("[sjxid='"+t.attrs.sjxid+"']"))),t.isMounted&&(h(t,n),t.hasOwnProperty("_didUpdate")&&t._didUpdate.apply(t))},g=function(t,n){n.state=c(n,t),S(n)},O=function(t,n){var e=r(t);return e._svenjs={rootNode:!1},e.isBound=!1,e.isMounted=!1,e.props={},n&&(e._jsxid=e.props.sjxid,e.props=n,setTimeout(function(){return S(e)},0),delete e.props.sjxid),e.hasOwnProperty("attrs")||e.hasOwnProperty("attrs")||(e.attrs={sjxid:p()}),e.isBound||(e.version="2.0.1",e.isBound=!0,e.setState=function(t){return g(t,this)},"function"==typeof e._beforeMount&&e._beforeMount.apply(e)),e.isMounted||(e.isMounted=!0,void 0!==e.initialState&&(e.state=e.initialState),"function"==typeof e._didMount&&(e._didMount.apply(e),"function"==typeof S&&setTimeout(function(){return S(e)},100))),e},_=[],M=function(t){return t.isMounted||(t.listenTo=function(t){_.push(t)},t.emit=function(t){_.forEach(function(n){n(t)})},"function"==typeof t.init&&t.init.apply(t)),t};console.info("Running svenjs version ".concat("2.0.1"));var x={version:"2.0.1",create:O,setState:g,createStore:M,render:h,renderToString:function(t,n){return j(t,n).innerHTML},lifeCycle:S};n.default=x}]); -------------------------------------------------------------------------------- /examples/browser/src/assets/app.css: -------------------------------------------------------------------------------- 1 | h3 {margin: 0; font-size: 1em; text-decoration: underline} 2 | 3 | #row {width: 19em; margin: 0 auto} 4 | 5 | #app {width: 45%; float: left; border-right: 1px solid grey; } 6 | #app small {color: #afafaf;display: block; padding: 4px;} 7 | #myapp {width: 100%; float: left; border-right: 1px solid grey; } 8 | #myapp small {color: #afafaf;display: block; padding: 4px;} 9 | #ui, button {margin-top: 1em;display: block; padding: 4px} 10 | #ui ul {margin-top: 0.3em;display: block; } 11 | #ui li {cursor: pointer} 12 | #ui li:hover {text-decoration: line-through} 13 | 14 | -------------------------------------------------------------------------------- /examples/browser/src/assets/index.js: -------------------------------------------------------------------------------- 1 | module.exports=function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}return n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(r,o,function(t){return e[t]}.bind(null,o));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=0)}([function(e,t,n){e.exports=n(3)},function(e){e.exports={name:"svenjs",version:"0.4.0",description:"A micro javascript framework for creating composable web apps",scripts:{test:"tape tests/*",build:"NODE_ENV=production webpack -p",postbuild:"cp dist/index.js npmbuild && cp dist/*.js examples/browser/src/assets/","build:watch":"sane 'NODE_ENV=development npm run build' --glob='src/**/*.js' --watchman",watch:"sane 'npm run build' --glob='src/**/*.js' --watchman"},repository:{type:"git",url:"https://github.com/svenanders/svenjs.git"},main:"dist/index.js",directories:{example:"example"},devDependencies:{"@babel/core":"^7.0.0-rc.1","@babel/preset-env":"^7.0.0-rc.1","@babel/preset-react":"^7.0.0-rc.1","babel-loader":"^8.0.0-beta.4",eslint:"^5.4.0",jsdom:"^12.0.0",jstransform:"^11.0.3",lodash:"^4.17.10","lodash-es":"^4.17.10",rimraf:"^2.3.4",rollup:"^0.64.1",sane:"^3.0.0",tape:"~4.9.1",webpack:"^4.17.0","webpack-cli":"^3.1.0","webpack-dev-server":"^3.1.5","webpack-serve":"^2.0.2"},author:"",license:"ISC",dependencies:{svenjsx:"^0.3.1","svenjsx-loader":"^0.3.1"}}},function(e,t,n){"use strict";var r={}.toString;t.isFunction=function(e){return"function"==typeof e},t.isObject=function(e){return"[object Object]"===r.call(e)},t.isString=function(e){return"[object String]"===r.call(e)},t.isArray=function(e){return"[object Array]"===r.call(e)},t.isDefined=function(e){return"undefined"!==r.call(e)}},function(e,t,n){"use strict";n.r(t);var r=n(1).version,o=function(e){return JSON.parse(JSON.stringify(e))};function i(e){return(i="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}var c=exports.deepFreeze=function(e){return Object.freeze(e),Object.getOwnPropertyNames(e).forEach(function(t){!e.hasOwnProperty(t)||null===e[t]||"object"!==i(e[t])&&"function"!=typeof e[t]||Object.isFrozen(e[t])||c(e[t])}),e},s=c,u=function(e,t){var n=o(t);return s(n),n};function a(e){return(a="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}var l=n(2),f=l.isFunction,p=l.isObject,d=(l.isString,l.isArray),y=function(e,t){return t.appendChild(e)},b=Object.prototype.hasOwnProperty,m=function(e,t){if(b.call(e,"children")&&d(e.children)&&e.children.forEach(function(e){"string"!=typeof e&&"number"!=typeof e||t.appendChild(document.createTextNode(e))}),b.call(e,"attrs")){var n=e.attrs;for(var r in n)if("config"!==r&&"key"!==r&&("disabled"!==r||!1!==n[r]))if("class"==r||"className"==r)t.className=n[r].toString();else if(f(n[r])&&"on"==r.slice(0,2))t[r.toLowerCase()]=n[r];else{if("checked"===r&&(!1===n[r]||""===n[r]))continue;try{t.setAttribute(""+r,n[r].toString())}catch(e){console.error("e",e)}}}return t},v=function(e){void 0===e.tag&&(e.tag="span",e.attrs={sjxid:Math.floor(Math.random()*(new Date).getTime())});var t=document.createElement(e.tag);return m(e,t),t},j=function(e,t){var n=document.createDocumentFragment(),r=document.createElement(e.tag);m(e,t.rootNode);var o=function e(t,n){var r;return b.call(t,"children")?d(t.children)&&t.children.forEach(function(t,o){null!==t&&"object"===a(t)&&(r=v(t),e(t,r),y(r,n)),d(t)&&(t.tag,t.forEach(function(t,o){b.call(t,"render")||(r=v(t),e(t,r),y(r,n))}))}):"object"===a(t)&&b.call(t,"render")&&e(t.render(),n),n}(e,r);return n.appendChild(o),n},h=function(e,t){var n,r=arguments.length>2&&void 0!==arguments[2]&&arguments[2];if(console.log("node",t),console.log("remder",e.render()),!t)return"Error: No node to attach";p(e)&&(b.call(e,"_svenjs")||(e._svenjs={rootNode:!1}),e._svenjs.rootNode=t),t.innerHTML="",n=p(r)?r:e.render(),console.log(n),t.appendChild(j(n,e._svenjs)),console.log(t)},g=function(e){var t;e._svenjs.rootNode&&(t=e._svenjs.rootNode),e.hasOwnProperty("attrs")&&e.attrs.hasOwnProperty("sjxid")&&(t||(t=document.querySelector("[sjxid='"+e.attrs.sjxid+"']"))),e.isMounted&&(h(e,t),e.hasOwnProperty("_didUpdate")&&e._didUpdate.apply(e))},S=function(e,t){t.state=u(t,e),g(t)};function O(e){return(O="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}var x=function(e,t){var n=function e(t){if(!t||"object"!==O(t))return t;var n={};return Array.isArray(t)?n=t.map(function(t){return e(t)}):Object.keys(t).forEach(function(r){return n[r]=e(t[r])}),n}(e);return n._svenjs={rootNode:!1},n.isBound=!1,n.isMounted=!1,n.props={},t&&(n._jsxid=n.props.sjxid,n.props=t,setTimeout(function(){return g(n)},0),delete n.props.sjxid),n.hasOwnProperty("attrs")||n.hasOwnProperty("attrs")||(n.attrs={sjxid:function(){var e=function(){return Math.floor(65536*(1+Math.random())).toString(16).substring(1)};return"".concat(e()+e())}()}),n.isBound||(n.version=r,n.isBound=!0,n.setState=function(e){return S(e,this)},"function"==typeof n._beforeMount&&n._beforeMount.apply(n)),n.isMounted||(n.isMounted=!0,void 0!==n.initialState&&(n.state=n.initialState),"function"==typeof n._didMount&&(n._didMount.apply(n),"function"==typeof g&&setTimeout(function(){return g(n)},100))),n},w=[],_=function(e){return e.isMounted||(e.listenTo=function(e){w.push(e)},e.emit=function(e){w.forEach(function(t){t(e)})},"function"==typeof e.init&&e.init.apply(e)),e};console.log("running svenjs version ".concat(r));var M={version:r,create:x,setState:S,createStore:_,render:h,renderToString:function(e,t){return j(e,t).innerHTML},lifeCycle:g};t.default=M}]); -------------------------------------------------------------------------------- /examples/browser/src/assets/sven.browser.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @module core/version 3 | * @see module:svenjs 4 | * @author Sven A Robbestad 5 | */ 6 | 7 | // import {version} from "../../package.json" 8 | // export default version; 9 | var version = "2.0.1"; 10 | 11 | const deepClone = obj => { 12 | if (!obj || typeof obj !== 'object') { 13 | return obj; 14 | } 15 | let newObj = {}; 16 | if (Array.isArray(obj)) { 17 | newObj = obj.map(item => deepClone(item)); 18 | } else { 19 | Object.keys(obj).forEach((key) => { 20 | return newObj[key] = deepClone(obj[key]); 21 | }); 22 | } 23 | return newObj; 24 | }; 25 | 26 | const deepFreeze = function (o) { 27 | Object.freeze(o); 28 | 29 | Object.getOwnPropertyNames(o).forEach(function (prop) { 30 | if (o.hasOwnProperty(prop) 31 | && o[prop] !== null 32 | && (typeof o[prop] === "object" || typeof o[prop] === "function") 33 | && !Object.isFrozen(o[prop])) { 34 | deepFreeze(o[prop]); 35 | } 36 | }); 37 | return o; 38 | }; 39 | 40 | const saveState = (spec,diff_state)=> { 41 | const state = deepClone(diff_state); 42 | deepFreeze(state); 43 | return state; 44 | }; 45 | 46 | // define common functions used in this module 47 | var type = ({}).toString; 48 | const isFunction = function (object) { 49 | return typeof object === "function"; 50 | }; 51 | const isObject = function (object) { 52 | return type.call(object) === "[object Object]"; 53 | }; 54 | const isArray = function (object) { 55 | return type.call(object) === "[object Array]"; 56 | }; 57 | 58 | const uuid = () => { 59 | const s = () => Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1); 60 | return `${s() + s()}-${s()}`; 61 | }; 62 | 63 | /** 64 | * render module. 65 | * @module core/render 66 | * @see module:svenjs 67 | * @author Sven A Robbestad 68 | */ 69 | 70 | const appendChild = (child, parent) => { 71 | return parent.appendChild(child); 72 | }; 73 | 74 | // Speed up calls to hasOwnProperty 75 | var hasOwnProperty = Object.prototype.hasOwnProperty; 76 | 77 | //const voidElems = /^(AREA|BASE|BR|COL|COMMAND|EMBED|HR|IMG|INPUT|KEYGEN|LINK|META|PARAM|SOURCE|TRACK|WBR)$/; 78 | 79 | /** 80 | * setAttrs. This sets all attributes on the node tag, like class names, event handlers etc. 81 | * @param {tag} a tag structure (e.g {tag: "div", attrs: {class:"test"}, children: []}) 82 | * @param {node} a DOM Node the children should be added to 83 | * @returns {Object} a DOM Node 84 | */ 85 | const setAttrs = (tag, node) => { 86 | if ((hasOwnProperty.call(tag, 'children'))) { 87 | if (isArray(tag.children)) { 88 | tag.children.forEach((childTag) => { 89 | if (typeof childTag == "string" || typeof childTag == "number") { 90 | node.appendChild(document.createTextNode(childTag)); 91 | } 92 | }); 93 | } 94 | } 95 | 96 | if ((hasOwnProperty.call(tag, 'attrs'))) { 97 | const attr = tag.attrs; 98 | for (var attrName in attr) { 99 | if (attrName === "config" || attrName === "key") continue; 100 | if (attrName === "disabled" && attr[attrName] === false) continue; 101 | else if (attrName == "class" || attrName == "className") node.className = attr[attrName].toString(); 102 | else if (isFunction(attr[attrName]) && attrName.slice(0, 2) == "on") { 103 | node[attrName.toLowerCase()] = attr[attrName]; 104 | } 105 | else if (attrName === "checked" && (attr[attrName] === false || attr[attrName] === "")) continue; 106 | else { 107 | try { 108 | node.setAttribute('' + attrName, attr[attrName].toString()); 109 | } catch (e) { 110 | console.error('e', e); 111 | } 112 | } 113 | } 114 | 115 | } 116 | return node; 117 | }; 118 | 119 | /** 120 | * buildElement 121 | * @param {tag} a tag structure (e.g {tag: "div", attrs: {class:"test"}, children: []}) 122 | * @returns {Object} a DOM Node 123 | */ 124 | const buildElement = (tag) => { 125 | if ("undefined" === typeof tag.tag) { 126 | tag.tag = "span"; 127 | tag.attrs = {"sjxid": uuid()}; 128 | } 129 | let child = document.createElement(tag.tag); 130 | setAttrs(tag, child); 131 | return child; 132 | }; 133 | 134 | /** 135 | * buildChildren 136 | * @param {tags} a tag structure (e.g {tag: "div", attrs: {class:"test"}, children: []}) 137 | * @param {parent} a DOM Node the children should be added to 138 | * @returns {Object} a DOM Node 139 | */ 140 | const buildChildren = (tags, parent) => { 141 | let childNode; 142 | if ((hasOwnProperty.call(tags, 'children'))) { 143 | if (isArray(tags.children)) { 144 | tags.children.forEach(tag => { 145 | if (null !== tag && 'object' === typeof tag) { 146 | childNode = buildElement(tag); 147 | buildChildren(tag, childNode); 148 | appendChild(childNode, parent); 149 | } 150 | if (isArray(tag)) { 151 | tag.forEach(childtag => { 152 | if (!(hasOwnProperty.call(childtag, 'render'))) { 153 | childNode = buildElement(childtag); 154 | buildChildren(childtag, childNode); 155 | appendChild(childNode, parent); 156 | } 157 | }); 158 | } 159 | }); 160 | } 161 | } else { 162 | // Components inside render 163 | if ('object' === typeof tags) { 164 | if ((hasOwnProperty.call(tags, 'render'))) { 165 | buildChildren(tags.render(), parent); 166 | } 167 | } 168 | } 169 | return parent; 170 | }; 171 | 172 | 173 | const renderToString = (tags, data) => { 174 | return vDom(tags, data).innerHTML; 175 | }; 176 | 177 | /** 178 | * vDom 179 | * @param {tags} a tag structure (e.g {tag: "div", attrs: {class:"test"}, children: []}) 180 | * @returns {Object} a DOM Node 181 | */ 182 | const vDom = (tags, data) => { 183 | var docFragment = document.createDocumentFragment(); 184 | 185 | // Root node 186 | var root = document.createElement(tags.tag); 187 | setAttrs(tags, data.rootNode); 188 | 189 | // Build children 190 | let childrenTree = buildChildren(tags, root); 191 | docFragment.appendChild(childrenTree); 192 | 193 | return docFragment; 194 | }; 195 | 196 | /** 197 | * Render 198 | * @alias svenjs.render 199 | * @param {spec} a svenjs component with a render method. Optional, set to false if not used 200 | * @param {node} a document node (e.g from document.getElementById()). 201 | * @param {tags} optional pre-rendered tags 202 | * @returns {undefined} 203 | */ 204 | const render = (spec, node, preRendered = false) => { 205 | if (node) { 206 | 207 | if (isObject(spec)) { 208 | // Set internal ref 209 | if (!(hasOwnProperty.call(spec, '_svenjs'))) { 210 | spec._svenjs = {rootNode: false}; 211 | } 212 | spec._svenjs.rootNode = node; 213 | } 214 | 215 | // reset HTML 216 | node.innerHTML = ""; 217 | // Get the converted tags 218 | let tags; 219 | 220 | if (isObject(preRendered)) { 221 | tags = preRendered; 222 | } else { 223 | tags = spec.render(); 224 | } 225 | 226 | // Append to window 227 | node.appendChild(vDom(tags, spec._svenjs)); 228 | } else { 229 | return 'Error: No node to attach'; 230 | } 231 | }; 232 | 233 | const lifeCycle = (spec) => { 234 | let rootNode; 235 | if (spec._svenjs.rootNode) { 236 | rootNode = spec._svenjs.rootNode; 237 | } 238 | if (spec.hasOwnProperty("attrs") && spec.attrs.hasOwnProperty("sjxid")) { 239 | if (!rootNode) rootNode = document.querySelector("[sjxid='" + spec.attrs.sjxid + "']"); 240 | } 241 | 242 | if (spec.isMounted) { 243 | render(spec, rootNode); 244 | if (spec.hasOwnProperty('_didUpdate')) spec._didUpdate.apply(spec); 245 | } 246 | }; 247 | 248 | const setState = (state, spec)=> { 249 | spec.state = saveState(spec, state); 250 | lifeCycle(spec); 251 | }; 252 | 253 | /** 254 | * @module core/create 255 | * @see module:svenjs 256 | * @author Sven A Robbestad 257 | */ 258 | 259 | const create = (_spec, props) => { 260 | const spec = deepClone(_spec); 261 | spec._svenjs = {rootNode: false}; 262 | spec.isBound = false; 263 | spec.isMounted = false; 264 | spec.props = {}; 265 | 266 | if (props) { 267 | spec._jsxid = spec.props.sjxid; 268 | spec.props = props; 269 | setTimeout(() => lifeCycle(spec), 0); 270 | delete spec.props.sjxid; 271 | } 272 | if (!spec.hasOwnProperty("attrs")) { 273 | if (!spec.hasOwnProperty("attrs")) { 274 | spec.attrs = {sjxid: uuid()}; 275 | } 276 | } 277 | if (!spec.isBound) { 278 | spec.version = version; 279 | spec.isBound = true; 280 | spec.setState = function (state) { 281 | return setState(state, this); 282 | }; 283 | if ("function" === typeof spec._beforeMount) { 284 | spec._beforeMount.apply(spec); 285 | } 286 | } 287 | if (!spec.isMounted) { 288 | spec.isMounted = true; 289 | 290 | if (undefined !== spec.initialState) { 291 | spec.state = spec.initialState; 292 | } 293 | if ("function" === typeof spec._didMount) { 294 | spec._didMount.apply(spec); 295 | if ("function" === typeof lifeCycle) 296 | setTimeout(() => lifeCycle(spec), 100); 297 | } 298 | } 299 | return spec; 300 | }; 301 | 302 | let _callbacks=[]; 303 | const createStore = (spec)=> { 304 | if(!spec.isMounted){ 305 | spec.listenTo=function(cb){ 306 | _callbacks.push(cb); 307 | }; 308 | spec.emit=(data)=>{ 309 | _callbacks.forEach((cb)=>{ 310 | cb(data); 311 | }); 312 | }; 313 | 314 | if("function" === typeof spec.init){ 315 | spec.init.apply(spec); 316 | } 317 | } 318 | return spec; 319 | }; 320 | 321 | console.info(`Running svenjs version ${version}`); 322 | 323 | const Svenjs = { 324 | version, 325 | create, 326 | setState, 327 | createStore, 328 | render, 329 | renderToString, 330 | lifeCycle 331 | }; 332 | 333 | export default Svenjs; 334 | -------------------------------------------------------------------------------- /examples/browser/src/assets/sven.js: -------------------------------------------------------------------------------- 1 | module.exports=function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}return n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(r,o,function(t){return e[t]}.bind(null,o));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=5)}([function(e,t,n){t.version=n(6).version},function(e,t,n){"use strict";function r(e){return(r="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}var o=n(7),i=o.isFunction,c=o.isObject,s=(o.isString,o.isArray),u=function(e,t){return t.appendChild(e)},a=Object.prototype.hasOwnProperty,f=function(e,t){if(a.call(e,"children")&&s(e.children)&&e.children.forEach(function(e){"string"!=typeof e&&"number"!=typeof e||t.appendChild(document.createTextNode(e))}),a.call(e,"attrs")){var n=e.attrs;for(var r in n)if("config"!==r&&"key"!==r&&("disabled"!==r||!1!==n[r]))if("class"==r||"className"==r)t.className=n[r].toString();else if(i(n[r])&&"on"==r.slice(0,2))t[r.toLowerCase()]=n[r];else{if("checked"===r&&(!1===n[r]||""===n[r]))continue;try{t.setAttribute(""+r,n[r].toString())}catch(e){console.error("e",e)}}}return t},l=function(e){void 0===e.tag&&(e.tag="span",e.attrs={sjxid:Math.floor(Math.random()*(new Date).getTime())});var t=document.createElement(e.tag);return f(e,t),t};t.renderToString=function(e,t){return d(e,t).innerHTML};var d=function(e,t){var n=document.createDocumentFragment(),o=document.createElement(e.tag);f(e,t.rootNode);var i=function e(t,n){var o;return a.call(t,"children")?s(t.children)&&t.children.forEach(function(t,i){null!==t&&"object"===r(t)&&(o=l(t),e(t,o),u(o,n)),s(t)&&(t.tag,t.forEach(function(t,r){a.call(t,"render")||(o=l(t),e(t,o),u(o,n))}))}):"object"===r(t)&&a.call(t,"render")&&e(t.render(),n),n}(e,o);return n.appendChild(i),n};t.render=function(e,t){var n,r=arguments.length>2&&void 0!==arguments[2]&&arguments[2];if(console.log("node",t),console.log("remder",e.render()),!t)return"Error: No node to attach";c(e)&&(a.call(e,"_svenjs")||(e._svenjs={rootNode:!1}),e._svenjs.rootNode=t),t.innerHTML="",n=c(r)?r:e.render(),console.log(n),t.appendChild(d(n,e._svenjs)),console.log(t)}},function(e,t,n){"use strict";t.deepCopy=function(e){return JSON.parse(JSON.stringify(e))}},function(e,t,n){"use strict";function r(e){return(r="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}var o=t.deepFreeze=function(e){return Object.freeze(e),Object.getOwnPropertyNames(e).forEach(function(t){!e.hasOwnProperty(t)||null===e[t]||"object"!==r(e[t])&&"function"!=typeof e[t]||Object.isFrozen(e[t])||o(e[t])}),e}},function(e,t){var n=[];t.createStore=function(e){return e.isMounted||(e.listenTo=function(e){n.push(e)},e.emit=function(e){n.forEach(function(t){t(e)})},"function"==typeof e.init&&e.init.apply(e)),e}},function(e,t,n){e.exports=n(8)},function(e){e.exports={name:"svenjs",version:"0.4.0",description:"A micro javascript framework for creating composable web apps",scripts:{test:"tape tests/*",build:"NODE_ENV=production webpack -p",postbuild:"cp dist/index.js npmbuild && cp dist/index.js examples/browser/src/assets/sven.js","build:watch":"sane 'NODE_ENV=development npm run build' --glob='src/**/*.js' --watchman",watch:"sane 'npm run build' --glob='src/**/*.js' --watchman"},repository:{type:"git",url:"https://github.com/svenanders/svenjs.git"},main:"dist/index.js",directories:{example:"example"},devDependencies:{"@babel/core":"^7.0.0-rc.1","@babel/preset-env":"^7.0.0-rc.1","@babel/preset-react":"^7.0.0-rc.1","babel-loader":"^8.0.0-beta.4",eslint:"^5.4.0",jsdom:"^12.0.0",jstransform:"^11.0.3",lodash:"^4.17.10","lodash-es":"^4.17.10",rimraf:"^2.3.4",sane:"^3.0.0",tape:"~4.9.1",webpack:"^4.17.0","webpack-cli":"^3.1.0","webpack-dev-server":"^3.1.5","webpack-serve":"^2.0.2"},author:"",license:"ISC",dependencies:{svenjsx:"^0.3.1","svenjsx-loader":"^0.3.1"}}},function(e,t,n){"use strict";var r={}.toString;t.isFunction=function(e){return"function"==typeof e},t.isObject=function(e){return"[object Object]"===r.call(e)},t.isString=function(e){return"[object String]"===r.call(e)},t.isArray=function(e){return"[object Array]"===r.call(e)},t.isDefined=function(e){return"undefined"!==r.call(e)}},function(e,t,n){"use strict";n.r(t);var r=n(0),o=n(2),i=n(3),c=function(e,t){var n=Object(o.deepCopy)(t);return Object(i.deepFreeze)(n),n},s=n(1),u=function(e){var t;e._svenjs.rootNode&&(t=e._svenjs.rootNode),e.hasOwnProperty("attrs")&&e.attrs.hasOwnProperty("sjxid")&&(t||(t=document.querySelector("[sjxid='"+e.attrs.sjxid+"']"))),e.isMounted&&(Object(s.render)(e,t),e.hasOwnProperty("_didUpdate")&&e._didUpdate.apply(e))},a=function(e,t){t.state=c(t,e),u(t)};function f(e){return(f="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}var l=function(e,t){var n=function e(t){if(!t||"object"!==f(t))return t;var n={};return Array.isArray(t)?n=t.map(function(t){return e(t)}):Object.keys(t).forEach(function(r){return n[r]=e(t[r])}),n}(e);return n._svenjs={rootNode:!1},n.isBound=!1,n.isMounted=!1,n.props={},t&&(n._jsxid=n.props.sjxid,n.props=t,setTimeout(function(){return u(n)},0),delete n.props.sjxid),n.hasOwnProperty("attrs")||n.hasOwnProperty("attrs")||(n.attrs={sjxid:function(){var e=function(){return Math.floor(65536*(1+Math.random())).toString(16).substring(1)};return"".concat(e()+e())}()}),n.isBound||(n.version=r.version,n.isBound=!0,n.setState=function(e){return a(e,this)},"function"==typeof n._beforeMount&&n._beforeMount.apply(n)),n.isMounted||(n.isMounted=!0,void 0!==n.initialState&&(n.state=n.initialState),"function"==typeof n._didMount&&(n._didMount.apply(n),"function"==typeof u&&setTimeout(function(){return u(n)},100))),n},d=n(4);console.log("running svenjs version ".concat(r.version));var p={version:r.version,create:l,setState:a,createStore:d.createStore,render:s.render,renderToString:s.renderToString,lifeCycle:u};t.default=p}]); -------------------------------------------------------------------------------- /examples/browser/src/assets/todomvc-app-css/index.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | margin: 0; 4 | padding: 0; 5 | } 6 | 7 | button { 8 | margin: 0; 9 | padding: 0; 10 | border: 0; 11 | background: none; 12 | font-size: 100%; 13 | vertical-align: baseline; 14 | font-family: inherit; 15 | font-weight: inherit; 16 | color: inherit; 17 | -webkit-appearance: none; 18 | appearance: none; 19 | -webkit-font-smoothing: antialiased; 20 | -moz-font-smoothing: antialiased; 21 | font-smoothing: antialiased; 22 | } 23 | 24 | body { 25 | font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif; 26 | line-height: 1.4em; 27 | background: #f5f5f5; 28 | color: #4d4d4d; 29 | min-width: 230px; 30 | max-width: 550px; 31 | margin: 0 auto; 32 | -webkit-font-smoothing: antialiased; 33 | -moz-font-smoothing: antialiased; 34 | font-smoothing: antialiased; 35 | font-weight: 300; 36 | } 37 | 38 | button, 39 | input[type="checkbox"] { 40 | outline: none; 41 | } 42 | 43 | .hidden { 44 | display: none; 45 | } 46 | 47 | .todoapp { 48 | background: #fff; 49 | margin: 130px 0 40px 0; 50 | position: relative; 51 | box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 52 | 0 25px 50px 0 rgba(0, 0, 0, 0.1); 53 | } 54 | 55 | .todoapp input::-webkit-input-placeholder { 56 | font-style: italic; 57 | font-weight: 300; 58 | color: #e6e6e6; 59 | } 60 | 61 | .todoapp input::-moz-placeholder { 62 | font-style: italic; 63 | font-weight: 300; 64 | color: #e6e6e6; 65 | } 66 | 67 | .todoapp input::input-placeholder { 68 | font-style: italic; 69 | font-weight: 300; 70 | color: #e6e6e6; 71 | } 72 | 73 | .todoapp h1 { 74 | position: absolute; 75 | top: -155px; 76 | width: 100%; 77 | font-size: 100px; 78 | font-weight: 100; 79 | text-align: center; 80 | color: rgba(175, 47, 47, 0.15); 81 | -webkit-text-rendering: optimizeLegibility; 82 | -moz-text-rendering: optimizeLegibility; 83 | text-rendering: optimizeLegibility; 84 | } 85 | 86 | .new-todo, 87 | .edit { 88 | position: relative; 89 | margin: 0; 90 | width: 100%; 91 | font-size: 24px; 92 | font-family: inherit; 93 | font-weight: inherit; 94 | line-height: 1.4em; 95 | border: 0; 96 | outline: none; 97 | color: inherit; 98 | padding: 6px; 99 | border: 1px solid #999; 100 | box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2); 101 | box-sizing: border-box; 102 | -webkit-font-smoothing: antialiased; 103 | -moz-font-smoothing: antialiased; 104 | font-smoothing: antialiased; 105 | } 106 | 107 | .new-todo { 108 | padding: 16px 16px 16px 60px; 109 | border: none; 110 | background: rgba(0, 0, 0, 0.003); 111 | box-shadow: inset 0 -2px 1px rgba(0,0,0,0.03); 112 | } 113 | 114 | .main { 115 | position: relative; 116 | z-index: 2; 117 | border-top: 1px solid #e6e6e6; 118 | } 119 | 120 | label[for='toggle-all'] { 121 | display: none; 122 | } 123 | 124 | .toggle-all { 125 | position: absolute; 126 | top: -55px; 127 | left: -12px; 128 | width: 60px; 129 | height: 34px; 130 | text-align: center; 131 | border: none; /* Mobile Safari */ 132 | } 133 | 134 | .toggle-all:before { 135 | content: '❯'; 136 | font-size: 22px; 137 | color: #e6e6e6; 138 | padding: 10px 27px 10px 27px; 139 | } 140 | 141 | .toggle-all:checked:before { 142 | color: #737373; 143 | } 144 | 145 | .todo-list { 146 | margin: 0; 147 | padding: 0; 148 | list-style: none; 149 | } 150 | 151 | .todo-list li { 152 | position: relative; 153 | font-size: 24px; 154 | border-bottom: 1px solid #ededed; 155 | } 156 | 157 | .todo-list li:last-child { 158 | border-bottom: none; 159 | } 160 | 161 | .todo-list li.editing { 162 | border-bottom: none; 163 | padding: 0; 164 | } 165 | 166 | .todo-list li.editing .edit { 167 | display: block; 168 | width: 506px; 169 | padding: 13px 17px 12px 17px; 170 | margin: 0 0 0 43px; 171 | } 172 | 173 | .todo-list li.editing .view { 174 | display: none; 175 | } 176 | 177 | .todo-list li .toggle { 178 | text-align: center; 179 | width: 40px; 180 | /* auto, since non-WebKit browsers doesn't support input styling */ 181 | height: auto; 182 | position: absolute; 183 | top: 0; 184 | bottom: 0; 185 | margin: auto 0; 186 | border: none; /* Mobile Safari */ 187 | -webkit-appearance: none; 188 | appearance: none; 189 | } 190 | 191 | .todo-list li .toggle:after { 192 | content: url('data:image/svg+xml;utf8,'); 193 | } 194 | 195 | .todo-list li .toggle:checked:after { 196 | content: url('data:image/svg+xml;utf8,'); 197 | } 198 | 199 | .todo-list li label { 200 | white-space: pre; 201 | word-break: break-word; 202 | padding: 15px 60px 15px 15px; 203 | margin-left: 45px; 204 | display: block; 205 | line-height: 1.2; 206 | transition: color 0.4s; 207 | } 208 | 209 | .todo-list li.completed label { 210 | color: #d9d9d9; 211 | text-decoration: line-through; 212 | } 213 | 214 | .todo-list li .destroy { 215 | display: none; 216 | position: absolute; 217 | top: 0; 218 | right: 10px; 219 | bottom: 0; 220 | width: 40px; 221 | height: 40px; 222 | margin: auto 0; 223 | font-size: 30px; 224 | color: #cc9a9a; 225 | margin-bottom: 11px; 226 | transition: color 0.2s ease-out; 227 | } 228 | 229 | .todo-list li .destroy:hover { 230 | color: #af5b5e; 231 | } 232 | 233 | .todo-list li .destroy:after { 234 | content: '×'; 235 | } 236 | 237 | .todo-list li:hover .destroy { 238 | display: block; 239 | } 240 | 241 | .todo-list li .edit { 242 | display: none; 243 | } 244 | 245 | .todo-list li.editing:last-child { 246 | margin-bottom: -1px; 247 | } 248 | 249 | .footer { 250 | color: #777; 251 | padding: 10px 15px; 252 | height: 20px; 253 | text-align: center; 254 | border-top: 1px solid #e6e6e6; 255 | } 256 | 257 | .footer:before { 258 | content: ''; 259 | position: absolute; 260 | right: 0; 261 | bottom: 0; 262 | left: 0; 263 | height: 50px; 264 | overflow: hidden; 265 | box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2), 266 | 0 8px 0 -3px #f6f6f6, 267 | 0 9px 1px -3px rgba(0, 0, 0, 0.2), 268 | 0 16px 0 -6px #f6f6f6, 269 | 0 17px 2px -6px rgba(0, 0, 0, 0.2); 270 | } 271 | 272 | .todo-count { 273 | float: left; 274 | text-align: left; 275 | } 276 | 277 | .todo-count strong { 278 | font-weight: 300; 279 | } 280 | 281 | .filters { 282 | margin: 0; 283 | padding: 0; 284 | list-style: none; 285 | position: absolute; 286 | right: 0; 287 | left: 0; 288 | } 289 | 290 | .filters li { 291 | display: inline; 292 | } 293 | 294 | .filters li a { 295 | color: inherit; 296 | margin: 3px; 297 | padding: 3px 7px; 298 | text-decoration: none; 299 | border: 1px solid transparent; 300 | border-radius: 3px; 301 | } 302 | 303 | .filters li a.selected, 304 | .filters li a:hover { 305 | border-color: rgba(175, 47, 47, 0.1); 306 | } 307 | 308 | .filters li a.selected { 309 | border-color: rgba(175, 47, 47, 0.2); 310 | } 311 | 312 | .clear-completed, 313 | html .clear-completed:active { 314 | float: right; 315 | position: relative; 316 | line-height: 20px; 317 | text-decoration: none; 318 | cursor: pointer; 319 | position: relative; 320 | } 321 | 322 | .clear-completed:hover { 323 | text-decoration: underline; 324 | } 325 | 326 | .info { 327 | margin: 65px auto 0; 328 | color: #bfbfbf; 329 | font-size: 10px; 330 | text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); 331 | text-align: center; 332 | } 333 | 334 | .info p { 335 | line-height: 1; 336 | } 337 | 338 | .info a { 339 | color: inherit; 340 | text-decoration: none; 341 | font-weight: 400; 342 | } 343 | 344 | .info a:hover { 345 | text-decoration: underline; 346 | } 347 | 348 | /* 349 | Hack to remove background from Mobile Safari. 350 | Can't use it globally since it destroys checkboxes in Firefox 351 | */ 352 | @media screen and (-webkit-min-device-pixel-ratio:0) { 353 | .toggle-all, 354 | .todo-list li .toggle { 355 | background: none; 356 | } 357 | 358 | .todo-list li .toggle { 359 | height: 40px; 360 | } 361 | 362 | .toggle-all { 363 | -webkit-transform: rotate(90deg); 364 | transform: rotate(90deg); 365 | -webkit-appearance: none; 366 | appearance: none; 367 | } 368 | } 369 | 370 | @media (max-width: 430px) { 371 | .footer { 372 | height: 50px; 373 | } 374 | 375 | .filters { 376 | bottom: 10px; 377 | } 378 | } 379 | -------------------------------------------------------------------------------- /examples/browser/src/assets/todomvc-app-css/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "todomvc-app-css", 3 | "version": "2.0.1", 4 | "description": "CSS for TodoMVC apps", 5 | "license": "CC-BY-4.0", 6 | "repository": { 7 | "type": "git", 8 | "url": "git+https://github.com/tastejs/todomvc-app-css.git" 9 | }, 10 | "author": { 11 | "name": "Sindre Sorhus", 12 | "email": "sindresorhus@gmail.com", 13 | "url": "sindresorhus.com" 14 | }, 15 | "files": [ 16 | "index.css" 17 | ], 18 | "keywords": [ 19 | "todomvc", 20 | "tastejs", 21 | "app", 22 | "todo", 23 | "template", 24 | "css", 25 | "style", 26 | "stylesheet" 27 | ], 28 | "gitHead": "f1bb1aa9b19888f339055418374a9b3a2d4c6fc5", 29 | "bugs": { 30 | "url": "https://github.com/tastejs/todomvc-app-css/issues" 31 | }, 32 | "homepage": "https://github.com/tastejs/todomvc-app-css", 33 | "_id": "todomvc-app-css@2.0.1", 34 | "scripts": {}, 35 | "_shasum": "f64d50b744a8a83c1151a08055b88f3aa5ccb052", 36 | "_from": "todomvc-app-css@>=2.0.1 <3.0.0", 37 | "_npmVersion": "2.5.1", 38 | "_nodeVersion": "0.12.0", 39 | "_npmUser": { 40 | "name": "sindresorhus", 41 | "email": "sindresorhus@gmail.com" 42 | }, 43 | "maintainers": [ 44 | { 45 | "name": "sindresorhus", 46 | "email": "sindresorhus@gmail.com" 47 | }, 48 | { 49 | "name": "addyosmani", 50 | "email": "addyosmani@gmail.com" 51 | }, 52 | { 53 | "name": "passy", 54 | "email": "phartig@rdrei.net" 55 | }, 56 | { 57 | "name": "stephenplusplus", 58 | "email": "sawchuk@gmail.com" 59 | } 60 | ], 61 | "dist": { 62 | "shasum": "f64d50b744a8a83c1151a08055b88f3aa5ccb052", 63 | "tarball": "http://registry.npmjs.org/todomvc-app-css/-/todomvc-app-css-2.0.1.tgz" 64 | }, 65 | "directories": {}, 66 | "_resolved": "https://registry.npmjs.org/todomvc-app-css/-/todomvc-app-css-2.0.1.tgz", 67 | "readme": "ERROR: No README data found!" 68 | } 69 | -------------------------------------------------------------------------------- /examples/browser/src/assets/todomvc-app-css/readme.md: -------------------------------------------------------------------------------- 1 | # todomvc-app-css 2 | 3 | > CSS for TodoMVC apps 4 | 5 | ![](screenshot.png) 6 | 7 | 8 | ## Install 9 | 10 | 11 | ``` 12 | $ npm install --save todomvc-app-css 13 | ``` 14 | 15 | 16 | ## Getting started 17 | 18 | ```html 19 | 20 | ``` 21 | 22 | See the [TodoMVC app template](https://github.com/tastejs/todomvc-app-template). 23 | 24 | 25 | 26 | ## License 27 | 28 | Creative Commons License
This work by Sindre Sorhus is licensed under a Creative Commons Attribution 4.0 International License. 29 | -------------------------------------------------------------------------------- /examples/browser/src/assets/todomvc-common/base.css: -------------------------------------------------------------------------------- 1 | hr { 2 | margin: 20px 0; 3 | border: 0; 4 | border-top: 1px dashed #c5c5c5; 5 | border-bottom: 1px dashed #f7f7f7; 6 | } 7 | 8 | .learn a { 9 | font-weight: normal; 10 | text-decoration: none; 11 | color: #b83f45; 12 | } 13 | 14 | .learn a:hover { 15 | text-decoration: underline; 16 | color: #787e7e; 17 | } 18 | 19 | .learn h3, 20 | .learn h4, 21 | .learn h5 { 22 | margin: 10px 0; 23 | font-weight: 500; 24 | line-height: 1.2; 25 | color: #000; 26 | } 27 | 28 | .learn h3 { 29 | font-size: 24px; 30 | } 31 | 32 | .learn h4 { 33 | font-size: 18px; 34 | } 35 | 36 | .learn h5 { 37 | margin-bottom: 0; 38 | font-size: 14px; 39 | } 40 | 41 | .learn ul { 42 | padding: 0; 43 | margin: 0 0 30px 25px; 44 | } 45 | 46 | .learn li { 47 | line-height: 20px; 48 | } 49 | 50 | .learn p { 51 | font-size: 15px; 52 | font-weight: 300; 53 | line-height: 1.3; 54 | margin-top: 0; 55 | margin-bottom: 0; 56 | } 57 | 58 | #issue-count { 59 | display: none; 60 | } 61 | 62 | .quote { 63 | border: none; 64 | margin: 20px 0 60px 0; 65 | } 66 | 67 | .quote p { 68 | font-style: italic; 69 | } 70 | 71 | .quote p:before { 72 | content: '“'; 73 | font-size: 50px; 74 | opacity: .15; 75 | position: absolute; 76 | top: -20px; 77 | left: 3px; 78 | } 79 | 80 | .quote p:after { 81 | content: '”'; 82 | font-size: 50px; 83 | opacity: .15; 84 | position: absolute; 85 | bottom: -42px; 86 | right: 3px; 87 | } 88 | 89 | .quote footer { 90 | position: absolute; 91 | bottom: -40px; 92 | right: 0; 93 | } 94 | 95 | .quote footer img { 96 | border-radius: 3px; 97 | } 98 | 99 | .quote footer a { 100 | margin-left: 5px; 101 | vertical-align: middle; 102 | } 103 | 104 | .speech-bubble { 105 | position: relative; 106 | padding: 10px; 107 | background: rgba(0, 0, 0, .04); 108 | border-radius: 5px; 109 | } 110 | 111 | .speech-bubble:after { 112 | content: ''; 113 | position: absolute; 114 | top: 100%; 115 | right: 30px; 116 | border: 13px solid transparent; 117 | border-top-color: rgba(0, 0, 0, .04); 118 | } 119 | 120 | .learn-bar > .learn { 121 | position: absolute; 122 | width: 272px; 123 | top: 8px; 124 | left: -300px; 125 | padding: 10px; 126 | border-radius: 5px; 127 | background-color: rgba(255, 255, 255, .6); 128 | transition-property: left; 129 | transition-duration: 500ms; 130 | } 131 | 132 | @media (min-width: 899px) { 133 | .learn-bar { 134 | width: auto; 135 | padding-left: 300px; 136 | } 137 | 138 | .learn-bar > .learn { 139 | left: 8px; 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /examples/browser/src/assets/todomvc-common/base.js: -------------------------------------------------------------------------------- 1 | /* global _ */ 2 | (function () { 3 | 'use strict'; 4 | 5 | /* jshint ignore:start */ 6 | // Underscore's Template Module 7 | // Courtesy of underscorejs.org 8 | var _ = (function (_) { 9 | _.defaults = function (object) { 10 | if (!object) { 11 | return object; 12 | } 13 | for (var argsIndex = 1, argsLength = arguments.length; argsIndex < argsLength; argsIndex++) { 14 | var iterable = arguments[argsIndex]; 15 | if (iterable) { 16 | for (var key in iterable) { 17 | if (object[key] == null) { 18 | object[key] = iterable[key]; 19 | } 20 | } 21 | } 22 | } 23 | return object; 24 | } 25 | 26 | // By default, Underscore uses ERB-style template delimiters, change the 27 | // following template settings to use alternative delimiters. 28 | _.templateSettings = { 29 | evaluate : /<%([\s\S]+?)%>/g, 30 | interpolate : /<%=([\s\S]+?)%>/g, 31 | escape : /<%-([\s\S]+?)%>/g 32 | }; 33 | 34 | // When customizing `templateSettings`, if you don't want to define an 35 | // interpolation, evaluation or escaping regex, we need one that is 36 | // guaranteed not to match. 37 | var noMatch = /(.)^/; 38 | 39 | // Certain characters need to be escaped so that they can be put into a 40 | // string literal. 41 | var escapes = { 42 | "'": "'", 43 | '\\': '\\', 44 | '\r': 'r', 45 | '\n': 'n', 46 | '\t': 't', 47 | '\u2028': 'u2028', 48 | '\u2029': 'u2029' 49 | }; 50 | 51 | var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g; 52 | 53 | // JavaScript micro-templating, similar to John Resig's implementation. 54 | // Underscore templating handles arbitrary delimiters, preserves whitespace, 55 | // and correctly escapes quotes within interpolated code. 56 | _.template = function(text, data, settings) { 57 | var render; 58 | settings = _.defaults({}, settings, _.templateSettings); 59 | 60 | // Combine delimiters into one regular expression via alternation. 61 | var matcher = new RegExp([ 62 | (settings.escape || noMatch).source, 63 | (settings.interpolate || noMatch).source, 64 | (settings.evaluate || noMatch).source 65 | ].join('|') + '|$', 'g'); 66 | 67 | // Compile the template source, escaping string literals appropriately. 68 | var index = 0; 69 | var source = "__p+='"; 70 | text.replace(matcher, function(match, escape, interpolate, evaluate, offset) { 71 | source += text.slice(index, offset) 72 | .replace(escaper, function(match) { return '\\' + escapes[match]; }); 73 | 74 | if (escape) { 75 | source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'"; 76 | } 77 | if (interpolate) { 78 | source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'"; 79 | } 80 | if (evaluate) { 81 | source += "';\n" + evaluate + "\n__p+='"; 82 | } 83 | index = offset + match.length; 84 | return match; 85 | }); 86 | source += "';\n"; 87 | 88 | // If a variable is not specified, place data values in local scope. 89 | if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n'; 90 | 91 | source = "var __t,__p='',__j=Array.prototype.join," + 92 | "print=function(){__p+=__j.call(arguments,'');};\n" + 93 | source + "return __p;\n"; 94 | 95 | try { 96 | render = new Function(settings.variable || 'obj', '_', source); 97 | } catch (e) { 98 | e.source = source; 99 | throw e; 100 | } 101 | 102 | if (data) return render(data, _); 103 | var template = function(data) { 104 | return render.call(this, data, _); 105 | }; 106 | 107 | // Provide the compiled function source as a convenience for precompilation. 108 | template.source = 'function(' + (settings.variable || 'obj') + '){\n' + source + '}'; 109 | 110 | return template; 111 | }; 112 | 113 | return _; 114 | })({}); 115 | 116 | if (location.hostname === 'todomvc.com') { 117 | (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ 118 | (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), 119 | m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) 120 | })(window,document,'script','https://www.google-analytics.com/analytics.js','ga'); 121 | ga('create', 'UA-31081062-1', 'auto'); 122 | ga('send', 'pageview'); 123 | } 124 | /* jshint ignore:end */ 125 | 126 | function redirect() { 127 | if (location.hostname === 'tastejs.github.io') { 128 | location.href = location.href.replace('tastejs.github.io/todomvc', 'todomvc.com'); 129 | } 130 | } 131 | 132 | function findRoot() { 133 | var base = location.href.indexOf('examples/'); 134 | return location.href.substr(0, base); 135 | } 136 | 137 | function getFile(file, callback) { 138 | if (!location.host) { 139 | return console.info('Miss the info bar? Run TodoMVC from a server to avoid a cross-origin error.'); 140 | } 141 | 142 | var xhr = new XMLHttpRequest(); 143 | 144 | xhr.open('GET', findRoot() + file, true); 145 | xhr.send(); 146 | 147 | xhr.onload = function () { 148 | if (xhr.status === 200 && callback) { 149 | callback(xhr.responseText); 150 | } 151 | }; 152 | } 153 | 154 | function Learn(learnJSON, config) { 155 | if (!(this instanceof Learn)) { 156 | return new Learn(learnJSON, config); 157 | } 158 | 159 | var template, framework; 160 | 161 | if (typeof learnJSON !== 'object') { 162 | try { 163 | learnJSON = JSON.parse(learnJSON); 164 | } catch (e) { 165 | return; 166 | } 167 | } 168 | 169 | if (config) { 170 | template = config.template; 171 | framework = config.framework; 172 | } 173 | 174 | if (!template && learnJSON.templates) { 175 | template = learnJSON.templates.todomvc; 176 | } 177 | 178 | if (!framework && document.querySelector('[data-framework]')) { 179 | framework = document.querySelector('[data-framework]').dataset.framework; 180 | } 181 | 182 | this.template = template; 183 | 184 | if (learnJSON.backend) { 185 | this.frameworkJSON = learnJSON.backend; 186 | this.frameworkJSON.issueLabel = framework; 187 | this.append({ 188 | backend: true 189 | }); 190 | } else if (learnJSON[framework]) { 191 | this.frameworkJSON = learnJSON[framework]; 192 | this.frameworkJSON.issueLabel = framework; 193 | this.append(); 194 | } 195 | 196 | this.fetchIssueCount(); 197 | } 198 | 199 | Learn.prototype.append = function (opts) { 200 | var aside = document.createElement('aside'); 201 | aside.innerHTML = _.template(this.template, this.frameworkJSON); 202 | aside.className = 'learn'; 203 | 204 | if (opts && opts.backend) { 205 | // Remove demo link 206 | var sourceLinks = aside.querySelector('.source-links'); 207 | var heading = sourceLinks.firstElementChild; 208 | var sourceLink = sourceLinks.lastElementChild; 209 | // Correct link path 210 | var href = sourceLink.getAttribute('href'); 211 | sourceLink.setAttribute('href', href.substr(href.lastIndexOf('http'))); 212 | sourceLinks.innerHTML = heading.outerHTML + sourceLink.outerHTML; 213 | } else { 214 | // Localize demo links 215 | var demoLinks = aside.querySelectorAll('.demo-link'); 216 | Array.prototype.forEach.call(demoLinks, function (demoLink) { 217 | if (demoLink.getAttribute('href').substr(0, 4) !== 'http') { 218 | demoLink.setAttribute('href', findRoot() + demoLink.getAttribute('href')); 219 | } 220 | }); 221 | } 222 | 223 | document.body.className = (document.body.className + ' learn-bar').trim(); 224 | document.body.insertAdjacentHTML('afterBegin', aside.outerHTML); 225 | }; 226 | 227 | Learn.prototype.fetchIssueCount = function () { 228 | var issueLink = document.getElementById('issue-count-link'); 229 | if (issueLink) { 230 | var url = issueLink.href.replace('https://github.com', 'https://api.github.com/repos'); 231 | var xhr = new XMLHttpRequest(); 232 | xhr.open('GET', url, true); 233 | xhr.onload = function (e) { 234 | var parsedResponse = JSON.parse(e.target.responseText); 235 | if (parsedResponse instanceof Array) { 236 | var count = parsedResponse.length; 237 | if (count !== 0) { 238 | issueLink.innerHTML = 'This app has ' + count + ' open issues'; 239 | document.getElementById('issue-count').style.display = 'inline'; 240 | } 241 | } 242 | }; 243 | xhr.send(); 244 | } 245 | }; 246 | 247 | redirect(); 248 | //getFile('learn.json', Learn); 249 | })(); 250 | -------------------------------------------------------------------------------- /examples/browser/src/assets/todomvc-common/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "todomvc-common", 3 | "version": "1.0.2", 4 | "description": "Common TodoMVC utilities used by our apps", 5 | "license": "MIT", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/tastejs/todomvc-common" 9 | }, 10 | "author": { 11 | "name": "TasteJS" 12 | }, 13 | "main": "base.js", 14 | "files": [ 15 | "base.js", 16 | "base.css" 17 | ], 18 | "keywords": [ 19 | "todomvc", 20 | "tastejs", 21 | "util", 22 | "utilities" 23 | ], 24 | "gitHead": "e82d0c79e01687ce7407df786cc784ad82166cb3", 25 | "bugs": { 26 | "url": "https://github.com/tastejs/todomvc-common/issues" 27 | }, 28 | "homepage": "https://github.com/tastejs/todomvc-common", 29 | "_id": "todomvc-common@1.0.2", 30 | "scripts": {}, 31 | "_shasum": "eb3ab61281ac74809f5869c917c7b08bc84234e0", 32 | "_from": "todomvc-common@>=1.0.1 <2.0.0", 33 | "_npmVersion": "2.7.4", 34 | "_nodeVersion": "0.12.2", 35 | "_npmUser": { 36 | "name": "sindresorhus", 37 | "email": "sindresorhus@gmail.com" 38 | }, 39 | "dist": { 40 | "shasum": "eb3ab61281ac74809f5869c917c7b08bc84234e0", 41 | "tarball": "http://registry.npmjs.org/todomvc-common/-/todomvc-common-1.0.2.tgz" 42 | }, 43 | "maintainers": [ 44 | { 45 | "name": "sindresorhus", 46 | "email": "sindresorhus@gmail.com" 47 | }, 48 | { 49 | "name": "addyosmani", 50 | "email": "addyosmani@gmail.com" 51 | }, 52 | { 53 | "name": "passy", 54 | "email": "phartig@rdrei.net" 55 | }, 56 | { 57 | "name": "stephenplusplus", 58 | "email": "sawchuk@gmail.com" 59 | } 60 | ], 61 | "directories": {}, 62 | "_resolved": "https://registry.npmjs.org/todomvc-common/-/todomvc-common-1.0.2.tgz" 63 | } 64 | -------------------------------------------------------------------------------- /examples/browser/src/assets/todomvc-common/readme.md: -------------------------------------------------------------------------------- 1 | # todomvc-common 2 | 3 | > Common TodoMVC utilities used by our apps 4 | 5 | 6 | ## Install 7 | 8 | ``` 9 | $ npm install --save todomvc-common 10 | ``` 11 | 12 | 13 | ## License 14 | 15 | MIT © [TasteJS](http://tastejs.com) 16 | -------------------------------------------------------------------------------- /examples/browser/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SvenJS - browser-app 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 258 | -------------------------------------------------------------------------------- /examples/browser/src/todomovc.js: -------------------------------------------------------------------------------- 1 | import Svenjs from "./assets/sven.browser.js"; 2 | 3 | let ENTER_KEY = 13; 4 | let ESCAPE_KEY = 27; 5 | let _toggled = false; 6 | let _prevEditing = false; 7 | let _currentEdit = 0; 8 | 9 | function deepCopy(o) { 10 | return JSON.parse(JSON.stringify(o)); 11 | }; 12 | 13 | 14 | let todoMVCApp = SvenJs.create({ 15 | initialState: { 16 | messages: [ 17 | {id: 1, message: "Answer all the mail", complete: false, editing: false}, 18 | {id: 2, message: "Get a cup of coffee", complete: false, editing: false} 19 | ] 20 | }, 21 | _didUpdate() { 22 | let node = document.getElementById('new-todo'); 23 | if (node !== null && _prevEditing) { 24 | _prevEditing = false; 25 | node.focus(); 26 | node.setSelectionRange(node.value.length, node.value.length); 27 | } 28 | }, 29 | _didMount() { 30 | var url = self.history === true ? self.getPath() : window.location.hash.replace(/.*#\//, ''); 31 | this.setState({messages: this.state.messages, url: url}); 32 | window.addEventListener("hashchange", this.onHashChange.bind(this), false); 33 | }, 34 | handleEditTodoKeyDown(e) { 35 | 36 | if (e.keyCode === ESCAPE_KEY) { 37 | this.simpleResetEditing(); 38 | return; 39 | } 40 | if (e.keyCode !== ENTER_KEY) { 41 | return; 42 | } 43 | this.saveTodo(e); 44 | this.resetEditing(); 45 | e.preventDefault(); 46 | }, 47 | handleNewTodoKeyDown(id, e) { 48 | if (e.keyCode !== ENTER_KEY) { 49 | return; 50 | } 51 | this.addTodo(e); 52 | e.innerHTML = ""; 53 | document.getElementById(id).focus(); 54 | e.preventDefault(); 55 | }, 56 | onHashChange() { 57 | var url = self.history === true ? self.getPath() : window.location.hash.replace(/.*#\//, ''); 58 | this.resetEditing(); 59 | this.setState({messages: this.state.messages, url: url}); 60 | }, 61 | saveTodo(e) { 62 | let val = "undefined" === typeof e.srcElement ? e.target.value : e.srcElement.value; 63 | let messages = this.state.messages.filter((msg) => { 64 | if (msg.id === _currentEdit) msg.message = val; 65 | return msg 66 | }) 67 | this.setState({messages: messages, url: this.state.url}); 68 | }, 69 | addTodo(e) { 70 | let messages = deepCopy(this.state.messages); 71 | let lastId; 72 | let val = "undefined" === typeof e.srcElement ? e.target.value : e.srcElement.value; 73 | if (messages.length === 0) lastId = 1; 74 | else lastId = messages[messages.length - 1].id; 75 | messages.push({id: lastId + 1, message: val, complete: false, editing: false}); 76 | this.setState({messages: messages, url: this.state.url}); 77 | }, 78 | destroyMessage(item) { 79 | let messages = this.state.messages.filter((msg) => { 80 | return msg.id !== item.id 81 | }) 82 | this.setState({messages: messages}); 83 | }, 84 | destroyCompleted() { 85 | let _messages = deepCopy(this.state.messages); 86 | let messages = _messages.filter((msg) => { 87 | return msg.complete === false 88 | }) 89 | this.resetEditing(); 90 | this.setState({messages: messages, url: this.state.url}); 91 | }, 92 | toggleOne(item, e) { 93 | let _messages = deepCopy(this.state.messages); 94 | let messages = _messages.filter((msg) => { 95 | if (msg.id === item.id) msg.complete = !msg.complete; 96 | return msg 97 | }) 98 | this.resetEditing(); 99 | this.setState({messages: messages, url: this.state.url}) 100 | }, 101 | simpleResetEditing() { 102 | let _messages = deepCopy(this.state.messages); 103 | let messages = _messages.map((msg) => { 104 | msg.editing = false; 105 | return msg 106 | }); 107 | _prevEditing = false; 108 | this.setState({messages: messages, url: this.state.url}); 109 | }, 110 | resetEditing(e) { 111 | let update = false; 112 | let _messages = deepCopy(this.state.messages); 113 | 114 | let messages = _messages.map((msg) => { 115 | if (msg.editing) update = true; 116 | msg.editing = false; 117 | return msg 118 | }) 119 | if (update) { 120 | _prevEditing = true; 121 | this.setState({messages: messages, url: this.state.url}); 122 | } else { 123 | _prevEditing = false; 124 | } 125 | }, 126 | onDoubleClick(todo, e) { 127 | _currentEdit = todo.id; 128 | let _messages = deepCopy(this.state.messages); 129 | 130 | if (!todo.complete) { 131 | let messages = _messages.map((msg) => { 132 | msg.editing = msg.id === todo.id ? !msg.editing : false; 133 | return msg 134 | }) 135 | this.setState({messages: messages, url: this.state.url}); 136 | let node = document.getElementsByClassName('edit active')[0]; 137 | node.focus(); 138 | node.setSelectionRange(node.value.length, node.value.length); 139 | } 140 | }, 141 | toggleAll() { 142 | _toggled = !_toggled; 143 | let _messages = deepCopy(this.state.messages); 144 | 145 | let messages = _messages.map((msg) => { 146 | msg.complete = _toggled; 147 | return msg; 148 | }) 149 | this.resetEditing(); 150 | this.setState({messages: messages}); 151 | }, 152 | listTodos() { 153 | let _messages = deepCopy(this.state.messages); 154 | 155 | let shownTodos = _messages.filter((todo) => { 156 | switch (this.state.url) { 157 | case "active": 158 | return !todo.complete; 159 | case "completed": 160 | return todo.complete; 161 | default: 162 | return true; 163 | } 164 | }, this); 165 | 166 | return shownTodos.map((todo) => { 167 | let label = todo.message; 168 | let checked = false; 169 | let className = "todo"; 170 | let editClassName = "edit"; 171 | if (todo.editing) { 172 | className = "todo editing"; 173 | editClassName = "edit active"; 174 | } 175 | if (todo.complete) { 176 | label = {tag: "del", attrs: {sjxid: "1222.595115120892"}, children: [todo.message]}; 177 | checked = true; 178 | } 179 | return {tag: "li", attrs: {sjxid: "1405.0809556733816", className:className}, children: [ 180 | {tag: "div", attrs: {sjxid: "1981.417695621729", className:"view"}, children: [ 181 | {tag: "input", attrs: {sjxid: "1591.9679358835772", className:"toggle", type:"checkbox", checked:checked, onClick:this.toggleOne.bind(this, todo)}}, 182 | {tag: "label", attrs: {sjxid: "1413.0949864339284", ondblclick:this.onDoubleClick.bind(this, todo)}, children: [label]}, 183 | {tag: "button", attrs: {sjxid: "1464.726366737478", className:"destroy", onClick:this.destroyMessage.bind(this, todo)}} 184 | ]}, 185 | {tag: "input", attrs: {sjxid: "550.0186024273787", className:editClassName, 186 | type:"text", 187 | onKeyDown:this.handleEditTodoKeyDown.bind(this), 188 | value:todo.message}} 189 | ]} 190 | }) 191 | }, 192 | render() { 193 | var state = this.state; 194 | let selected_all = "", selected_active = "", selected_completed = ""; 195 | if (this.state.url === "" || this.state.url === "all") selected_all = 'selected'; 196 | if (this.state.url === "active") selected_active = 'selected'; 197 | if (this.state.url === "completed") selected_completed = 'selected'; 198 | 199 | return ({tag: "section", attrs: {sjxid: "1128.2683506240464", class:"todoapp"}, children: [ 200 | {tag: "header", attrs: {sjxid: "857.4076023295851", class:"header"}, children: [ 201 | {tag: "h1", attrs: {sjxid: "496.0860361085362"}, children: ["todos"]}, 202 | {tag: "input", attrs: {sjxid: "451.10573650802934", className:"new-todo", 203 | id:"new-todo", 204 | onClick:this.resetEditing.bind(this), 205 | onKeyDown:this.handleNewTodoKeyDown.bind(this, "new-todo"), 206 | placeholder:"What needs to be done?", autofocus:true}} 207 | ]}, 208 | {tag: "section", attrs: {sjxid: "1121.9020961367999", className:"main"}, children: [ 209 | {tag: "input", attrs: {sjxid: "1212.6006707260694", className:"toggle-all", type:"checkbox", onClick:this.toggleAll.bind(this)}}, 210 | {tag: "label", attrs: {sjxid: "1099.8503021133588", for:"toggle-all"}, children: ["Mark all as complete"]}, 211 | {tag: "ul", attrs: {sjxid: "783.2873843576813", className:"todo-list"}, children: [ 212 | this.listTodos() 213 | ]} 214 | ]}, 215 | 216 | {tag: "footer", attrs: {sjxid: "220.7633598493457", class:"footer"}, children: [ 217 | {tag: "span", attrs: {sjxid: "526.4140235647093", class:"todo-count"}, children: [this.state.messages.length, 218 | this.state.messages.length === 1 ? " item" : " items", " remaining"]}, 219 | 220 | {tag: "ul", attrs: {sjxid: "1300.7775582596", class:"filters"}, children: [ 221 | {tag: "li", attrs: {sjxid: "273.1071093783215"}, children: [ 222 | {tag: "a", attrs: {sjxid: "1532.2045614556946", href:"#/all", class:selected_all}, children: ["All"]} 223 | ]}, 224 | {tag: "li", attrs: {sjxid: "1969.2213916277628"}, children: [ 225 | {tag: "a", attrs: {sjxid: "1815.6839176872713", href:"#/active", class:selected_active}, children: ["Active"]} 226 | ]}, 227 | {tag: "li", attrs: {sjxid: "1969.6586309694237"}, children: [ 228 | {tag: "a", attrs: {sjxid: "1526.0339520846337", href:"#/completed", class:selected_completed}, children: ["Completed"]} 229 | ]} 230 | ]}, 231 | {tag: "button", attrs: {sjxid: "103.73083651869264", class:"clear-completed", onClick:this.destroyCompleted.bind(this)}, children: ["Clear completed"]} 232 | ]} 233 | ]}); 234 | } 235 | 236 | }); 237 | Svenjs.render( 238 | App, 239 | document.getElementById("myapp") 240 | ); 241 | -------------------------------------------------------------------------------- /examples/browser/src/todomovc.jsx: -------------------------------------------------------------------------------- 1 | import Svenjs from "./assets/sven.browser.js"; 2 | 3 | let ENTER_KEY = 13; 4 | let ESCAPE_KEY = 27; 5 | let _toggled = false; 6 | let _prevEditing = false; 7 | let _currentEdit = 0; 8 | 9 | function deepCopy(o) { 10 | return JSON.parse(JSON.stringify(o)); 11 | }; 12 | 13 | 14 | let todoMVCApp = SvenJs.create({ 15 | initialState: { 16 | messages: [ 17 | {id: 1, message: "Answer all the mail", complete: false, editing: false}, 18 | {id: 2, message: "Get a cup of coffee", complete: false, editing: false} 19 | ] 20 | }, 21 | _didUpdate() { 22 | let node = document.getElementById('new-todo'); 23 | if (node !== null && _prevEditing) { 24 | _prevEditing = false; 25 | node.focus(); 26 | node.setSelectionRange(node.value.length, node.value.length); 27 | } 28 | }, 29 | _didMount() { 30 | var url = self.history === true ? self.getPath() : window.location.hash.replace(/.*#\//, ''); 31 | this.setState({messages: this.state.messages, url: url}); 32 | window.addEventListener("hashchange", this.onHashChange.bind(this), false); 33 | }, 34 | handleEditTodoKeyDown(e) { 35 | 36 | if (e.keyCode === ESCAPE_KEY) { 37 | this.simpleResetEditing(); 38 | return; 39 | } 40 | if (e.keyCode !== ENTER_KEY) { 41 | return; 42 | } 43 | this.saveTodo(e); 44 | this.resetEditing(); 45 | e.preventDefault(); 46 | }, 47 | handleNewTodoKeyDown(id, e) { 48 | if (e.keyCode !== ENTER_KEY) { 49 | return; 50 | } 51 | this.addTodo(e); 52 | e.innerHTML = ""; 53 | document.getElementById(id).focus(); 54 | e.preventDefault(); 55 | }, 56 | onHashChange() { 57 | var url = self.history === true ? self.getPath() : window.location.hash.replace(/.*#\//, ''); 58 | this.resetEditing(); 59 | this.setState({messages: this.state.messages, url: url}); 60 | }, 61 | saveTodo(e) { 62 | let val = "undefined" === typeof e.srcElement ? e.target.value : e.srcElement.value; 63 | let messages = this.state.messages.filter((msg) => { 64 | if (msg.id === _currentEdit) msg.message = val; 65 | return msg 66 | }) 67 | this.setState({messages: messages, url: this.state.url}); 68 | }, 69 | addTodo(e) { 70 | let messages = deepCopy(this.state.messages); 71 | let lastId; 72 | let val = "undefined" === typeof e.srcElement ? e.target.value : e.srcElement.value; 73 | if (messages.length === 0) lastId = 1; 74 | else lastId = messages[messages.length - 1].id; 75 | messages.push({id: lastId + 1, message: val, complete: false, editing: false}); 76 | this.setState({messages: messages, url: this.state.url}); 77 | }, 78 | destroyMessage(item) { 79 | let messages = this.state.messages.filter((msg) => { 80 | return msg.id !== item.id 81 | }) 82 | this.setState({messages: messages}); 83 | }, 84 | destroyCompleted() { 85 | let _messages = deepCopy(this.state.messages); 86 | let messages = _messages.filter((msg) => { 87 | return msg.complete === false 88 | }) 89 | this.resetEditing(); 90 | this.setState({messages: messages, url: this.state.url}); 91 | }, 92 | toggleOne(item, e) { 93 | let _messages = deepCopy(this.state.messages); 94 | let messages = _messages.filter((msg) => { 95 | if (msg.id === item.id) msg.complete = !msg.complete; 96 | return msg 97 | }) 98 | this.resetEditing(); 99 | this.setState({messages: messages, url: this.state.url}) 100 | }, 101 | simpleResetEditing() { 102 | let _messages = deepCopy(this.state.messages); 103 | let messages = _messages.map((msg) => { 104 | msg.editing = false; 105 | return msg 106 | }); 107 | _prevEditing = false; 108 | this.setState({messages: messages, url: this.state.url}); 109 | }, 110 | resetEditing(e) { 111 | let update = false; 112 | let _messages = deepCopy(this.state.messages); 113 | 114 | let messages = _messages.map((msg) => { 115 | if (msg.editing) update = true; 116 | msg.editing = false; 117 | return msg 118 | }) 119 | if (update) { 120 | _prevEditing = true; 121 | this.setState({messages: messages, url: this.state.url}); 122 | } else { 123 | _prevEditing = false; 124 | } 125 | }, 126 | onDoubleClick(todo, e) { 127 | _currentEdit = todo.id; 128 | let _messages = deepCopy(this.state.messages); 129 | 130 | if (!todo.complete) { 131 | let messages = _messages.map((msg) => { 132 | msg.editing = msg.id === todo.id ? !msg.editing : false; 133 | return msg 134 | }) 135 | this.setState({messages: messages, url: this.state.url}); 136 | let node = document.getElementsByClassName('edit active')[0]; 137 | node.focus(); 138 | node.setSelectionRange(node.value.length, node.value.length); 139 | } 140 | }, 141 | toggleAll() { 142 | _toggled = !_toggled; 143 | let _messages = deepCopy(this.state.messages); 144 | 145 | let messages = _messages.map((msg) => { 146 | msg.complete = _toggled; 147 | return msg; 148 | }) 149 | this.resetEditing(); 150 | this.setState({messages: messages}); 151 | }, 152 | listTodos() { 153 | let _messages = deepCopy(this.state.messages); 154 | 155 | let shownTodos = _messages.filter((todo) => { 156 | switch (this.state.url) { 157 | case "active": 158 | return !todo.complete; 159 | case "completed": 160 | return todo.complete; 161 | default: 162 | return true; 163 | } 164 | }, this); 165 | 166 | return shownTodos.map((todo) => { 167 | let label = todo.message; 168 | let checked = false; 169 | let className = "todo"; 170 | let editClassName = "edit"; 171 | if (todo.editing) { 172 | className = "todo editing"; 173 | editClassName = "edit active"; 174 | } 175 | if (todo.complete) { 176 | label = {todo.message}; 177 | checked = true; 178 | } 179 | return
  • 180 |
    181 | 182 | 183 | 184 |
    185 | 189 |
  • 190 | }) 191 | }, 192 | render() { 193 | var state = this.state; 194 | let selected_all = "", selected_active = "", selected_completed = ""; 195 | if (this.state.url === "" || this.state.url === "all") selected_all = 'selected'; 196 | if (this.state.url === "active") selected_active = 'selected'; 197 | if (this.state.url === "completed") selected_completed = 'selected'; 198 | 199 | return (
    200 |
    201 |

    todos

    202 | 207 |
    208 |
    209 | 210 | 211 |
      212 | {this.listTodos()} 213 |
    214 |
    215 | 216 |
    217 | {this.state.messages.length} 218 | {this.state.messages.length === 1 ? " item" : " items"} remaining 219 | 220 | 231 | 232 |
    233 |
    ); 234 | } 235 | 236 | }); 237 | Svenjs.render( 238 | App, 239 | document.getElementById("myapp") 240 | ); 241 | -------------------------------------------------------------------------------- /examples/clickmeapp/dist/app.css: -------------------------------------------------------------------------------- 1 | h3 {margin: 0; font-size: 1em; text-decoration: underline} 2 | 3 | #row {width: 19em; margin: 0 auto} 4 | 5 | #app {width: 45%; float: left; border-right: 1px solid grey; } 6 | #app small {color: #afafaf;display: block; padding: 4px;} 7 | #myapp {width: 100%; float: left; border-right: 1px solid grey; } 8 | #myapp small {color: #afafaf;display: block; padding: 4px;} 9 | #ui, button {margin-top: 1em;display: block; padding: 4px} 10 | #ui ul {margin-top: 0.3em;display: block; } 11 | #ui li {cursor: pointer} 12 | #ui li:hover {text-decoration: line-through} 13 | 14 | #time-travel {width: 40%; float: left; padding-left: 1em;display: block;} 15 | -------------------------------------------------------------------------------- /examples/clickmeapp/dist/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | The Click App 8 | 9 | 10 |
    11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /examples/clickmeapp/dist/index.min.js: -------------------------------------------------------------------------------- 1 | !function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):"object"==typeof exports?exports.Svenjs=e():t.Svenjs=e()}(this,function(){return function(t){function e(o){if(n[o])return n[o].exports;var r=n[o]={exports:{},id:o,loaded:!1};return t[o].call(r.exports,r,r.exports,e),r.loaded=!0,r.exports}var n={};return e.m=t,e.c=n,e.p="",e(0)}([function(t,e,n){"use strict";var o=n(1),r=n(2),i=document.getElementById("ui");o.render(r,i)},function(t,e,n){"use strict";!function(e,n){t.exports=n()}(void 0,function(){return function(t){function e(o){if(n[o])return n[o].exports;var r=n[o]={exports:{},id:o,loaded:!1};return t[o].call(r.exports,r,r.exports,e),r.loaded=!0,r.exports}var n={};return e.m=t,e.c=n,e.p="",e(0)}([function(t,e,n){var o;(function(t){var r=n(9),i=n(2),c=n(5),a=n(8),s=n(6),u=n(7),p=n(1),d=n(4),l=n(10),f=n(3),v={version:r.version,updateUI:i.updateUI,setState:s.setState,createStore:l.createStore,create:u.create,render:d.render,lifeCycle:p.lifeCycle,timeTravel:a.timeTravel,saveState:c.saveState,deepCopy:f.deepCopy};"object"==typeof t&&null!=t&&t.exports?t.exports=v:(o=function(){return v}.call(e,n,e,t),!(void 0!==o&&(t.exports=o)))}).call(e,n(11)(t))},function(t,e,n){var o=n(4);e.lifeCycle=function(t){t.isMounted&&(t.hasOwnProperty("componentDidUpdate")&&t.componentDidUpdate.apply(t),o.render(t,t._svenjs.rootNode))}},function(t,e){e.updateUI=function(t,e,n){}},function(t,e){var n=e.deepCopy=function(t){if(null===t||"object"!=typeof t||"isActiveClone"in t)return t;var e=t.constructor();for(var o in t)Object.prototype.hasOwnProperty.call(t,o)&&(t.isActiveClone=null,e[o]=n(t[o]),delete t.isActiveClone);return e}},function(t,e){var n=function(t,e){return e.appendChild(t)},o=function(t,e){return t.hasOwnProperty("children")&&t.children.forEach(function(t){("string"==typeof t||"number"==typeof t)&&e.appendChild(document.createTextNode(t))}),t.hasOwnProperty("attrs")&&(t.attrs.hasOwnProperty("id")&&(e.id=t.attrs.id),t.attrs.hasOwnProperty("onClick")&&(e.onclick=t.attrs.onClick)),e},r=function(t,e){return"object"!=typeof t.children?!1:(t.children.forEach(function(t){var r=document.createElement(t.tag);if(n(o(t,r),e),null!=t.children&&"object"==typeof t.children){var i=t.children;i.forEach(function(t){var e=document.createElement(t.tag);o(t,e),n(e,r)})}}),e)};e.render=function(t,e){t._svenjs.rootNode=e,e.innerHTML="";var n=t.render(),o=document.createDocumentFragment(),i=document.createElement(n.tag);n.attrs.hasOwnProperty("id")&&(i.id=n.attrs.id);var c=r(n,i);o.appendChild(c),e.appendChild(o)}},function(t,e,n){var o=n(3);e.saveState=function(t,e){t.state=o.deepCopy(e);var n=t.time||{history:[],pos:-1};n.history.splice(n.pos+1),n.history.push(o.deepCopy(e)),n.pos++}},function(t,e,n){var o=(n(2),n(5)),r=n(1);e.setState=function(t,e){o.saveState(e,t),r.lifeCycle(e)}},function(t,e,n){var o=n(6);e.create=function(t){return console.log(t.displayName),t._svenjs={rootNode:{}},t.isBound||(t.isBound=!0,t.setState=function(t){return o.setState(t,this)}),t.isMounted||(t.time={history:[],pos:-1},t.isMounted=!0,void 0!==t.initialState&&(t.state=t.initialState),"function"==typeof t.componentDidMount&&t.componentDidMount.apply(t)),t}},function(t,e,n){var o=n(3),r=n(2),i=n(1);e.timeTravel=function(t,e){var n=t.time,c=t.state;n.pos+=e,t.state=c,c=o.deepCopy(n.history[n.pos]),t.state=c,r.updateUI(t,t.render(c),n),i.lifeCycle(t)}},function(t,e){e.version=function(){return"0.0.2-alpha"}},function(t,e){var n=[];e.createStore=function(t){return t.isMounted||(t.listenTo=function(t){n.push(t)},t.emit=function(t){n.forEach(function(e){e(t)})},"function"==typeof t.init&&t.init.apply(t)),t}},function(t,e){t.exports=function(t){return t.webpackPolyfill||(t.deprecate=function(){},t.paths=[],t.children=[],t.webpackPolyfill=1),t}}])})},function(t,e,n){"use strict";var o=n(1),r=o.create({displayName:"Clicky App",initialState:{clicks:0},render:function(){var t=this,e=(this.state,function(){t.setState({clicks:t.state.clicks?++t.state.clicks:1})});return{tag:"div",attrs:{id:"row"},children:[{tag:"div",attrs:{id:"app"},children:[{tag:"h3",attrs:{},children:["The Click App"]},{tag:"button",attrs:{onClick:e},children:["Why not click me?"]}]},{tag:"div",attrs:{id:"time-travel"},children:[{tag:"h3",attrs:{},children:["Click stats"]},{tag:"p",attrs:{},children:["You have clicked on the button ",this.state.clicks," times"]}]}]}}});t.exports=r}])}); 2 | -------------------------------------------------------------------------------- /examples/clickmeapp/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "clickmeapp", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "webpack.config.js", 6 | "scripts": { 7 | "start": "NODE_ENV=development webpack-serve", 8 | "build": "webpack", 9 | "test": "echo \"Error: no test specified\" && exit 1" 10 | }, 11 | "author": "", 12 | "license": "ISC", 13 | "dependencies": { 14 | "connect-history-api-fallback": "^1.5.0", 15 | "html-webpack-plugin": "^3.2.0", 16 | "koa-connect": "^2.0.1", 17 | "@babel/core": "^7.0.0-rc.1", 18 | "@babel/preset-env": "^7.0.0-rc.1", 19 | "@babel/preset-react": "^7.0.0-rc.1", 20 | "babel-loader": "^8.0.0-beta.4", 21 | "connect-history-api-fallback": "^1.5.0", 22 | "html-webpack-plugin": "^3.2.0", 23 | "jstransform": "^11.0.3", 24 | "koa-connect": "^2.0.1", 25 | "webpack": "^4.17.0", 26 | "webpack-cli": "^3.1.0", 27 | "webpack-serve": "^2.0.2" 28 | }, 29 | "devDependencies": { 30 | "svenjs": "^2.0.0", 31 | "svenjsx": "^2.0.1", 32 | "svenjsx-loader": "^2.0.2" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /examples/clickmeapp/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | NODE_ENV=development ../../node_modules/.bin/webpack-dev-server --devtool eval --progress --colors --hot --port 3000 --config webpack.config.dev.js 3 | -------------------------------------------------------------------------------- /examples/clickmeapp/src/app.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | margin: 20px; 4 | padding: 0; 5 | } 6 | 7 | button { 8 | margin: 0; 9 | padding: 0; 10 | border: 1px solid grey; 11 | background: none; 12 | font-size: 100%; 13 | vertical-align: baseline; 14 | font-family: inherit; 15 | font-weight: inherit; 16 | color: inherit; 17 | -webkit-appearance: none; 18 | appearance: none; 19 | -webkit-font-smoothing: antialiased; 20 | -moz-font-smoothing: antialiased; 21 | font-smoothing: antialiased; 22 | } 23 | 24 | body { 25 | font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif; 26 | line-height: 1.4em; 27 | background: #f5f5f5; 28 | color: #4d4d4d; 29 | min-width: 230px; 30 | max-width: 550px; 31 | margin: 0 auto; 32 | -webkit-font-smoothing: antialiased; 33 | -moz-font-smoothing: antialiased; 34 | font-smoothing: antialiased; 35 | font-weight: 300; 36 | } 37 | 38 | h3 {margin: 0; font-size: 1.2em; text-decoration: none} 39 | 40 | #row {width: 80%; margin: 0 auto} 41 | 42 | #app {width: 40%; float: left; padding: 1em} 43 | #app small {color: #afafaf;display: block; padding: 4px;} 44 | #myapp {width: 100%; float: left; } 45 | #myapp small {color: #afafaf;display: block; padding: 8px;} 46 | #ui, button {margin-top: 1em;display: block; padding: 8px} 47 | #time-travel {width: 40%; float: left; padding: 1em;display: block;} 48 | -------------------------------------------------------------------------------- /examples/clickmeapp/src/app.jsx: -------------------------------------------------------------------------------- 1 | import Svenjs from "dist/index.js" 2 | var clickyApp = Svenjs.create({ 3 | displayName: "Clicky App", 4 | initialState: { 5 | clicks: 0 6 | }, 7 | render(){ 8 | var state=this.state; 9 | let handleClick = () =>{ 10 | this.setState( 11 | {clicks: (this.state.clicks ? this.state.clicks + 1 : 1) 12 | }) 13 | } 14 | return (
    15 |
    16 |

    The Click App

    17 | 18 |
    19 |
    20 |

    Click stats

    21 |

    You have clicked on the button {this.state.clicks} times

    22 |
    23 |
    ) 24 | 25 | } 26 | 27 | }); 28 | export default clickyApp; 29 | -------------------------------------------------------------------------------- /examples/clickmeapp/src/get-json.js: -------------------------------------------------------------------------------- 1 | exports.getJSON=(url) =>{ 2 | return new Promise((resolve, reject) =>{ 3 | const xhr = new XMLHttpRequest(); 4 | xhr.onreadystatechange = () => { 5 | if (xhr.readyState === 4) { 6 | if (xhr.status === 200) { 7 | resolve(JSON.parse(xhr.responseText)); 8 | } else { 9 | reject(xhr.responseText); 10 | } 11 | } 12 | }; 13 | xhr.open('GET', url); 14 | xhr.send(); 15 | }); 16 | } 17 | -------------------------------------------------------------------------------- /examples/clickmeapp/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | The Clickety click App 8 | 9 | 10 |
    11 | 12 | 13 | -------------------------------------------------------------------------------- /examples/clickmeapp/src/index.jsx: -------------------------------------------------------------------------------- 1 | import Svenjs from "dist/index.js" 2 | import App from "./app.jsx" 3 | const rootNode = document.getElementById('myapp'); 4 | Svenjs.render( 5 | App, 6 | rootNode 7 | ); 8 | -------------------------------------------------------------------------------- /examples/clickmeapp/src/store.js: -------------------------------------------------------------------------------- 1 | const Svenjs = require('dist/index.js'); 2 | const getJSON = require('./get-json').getJSON; 3 | 4 | module.exports = Svenjs.createStore({ 5 | init(){ 6 | getJSON('http://jsonplaceholder.typicode.com/posts/1') 7 | .then((data)=>{ 8 | this.emit(data); 9 | }) 10 | .catch(console.log.bind(console)); 11 | } 12 | }); 13 | -------------------------------------------------------------------------------- /examples/clickmeapp/webpack.config.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | var path = require('path'); 3 | const HtmlWebpackPlugin = require("html-webpack-plugin"); 4 | const history = require('connect-history-api-fallback'); 5 | const convert = require('koa-connect'); 6 | 7 | module.exports = { 8 | mode: "development", 9 | devtool: "eval-source-map", 10 | module: { 11 | rules: [ 12 | {test: /\.js$/, loaders: ['babel-loader']}, 13 | {test: /\.jsx?$/, enforce: "pre", loaders: ['svenjsx-loader']} 14 | ] 15 | }, 16 | plugins: [ 17 | new webpack.DefinePlugin({ 18 | 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV) 19 | }), 20 | new HtmlWebpackPlugin( 21 | { 22 | template: path.resolve(__dirname, "src/index.html") 23 | } 24 | ) 25 | ], 26 | entry: [path.resolve(__dirname, 'src/index.jsx')], 27 | output: { 28 | path: path.resolve(__dirname, "build"), 29 | filename: "bundle.js" 30 | }, 31 | resolve: { 32 | extensions: [".js",".jsx"], 33 | alias: { 34 | root: path.resolve(__dirname, "../../src"), 35 | dist: path.resolve(__dirname, "../../dist"), 36 | assets: path.resolve(__dirname, "../../assets") 37 | } 38 | } 39 | }; 40 | module.exports.serve = { 41 | content: [__dirname], 42 | add: (app, middleware, options) => { 43 | const historyOptions = { 44 | rewrites: [ 45 | {from: /\/*bundle.js$/, to: '/bundle.js'} 46 | ], 47 | index: '/index.html', 48 | logger: console.log.bind(console) 49 | }; 50 | app.use(convert(history(historyOptions))); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /examples/composable/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "env":{ 3 | "development": { 4 | "presets": ["@babel/preset-env"] 5 | }, 6 | "production": { 7 | "presets": ["@babel/preset-env"] 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /examples/composable/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "composable-svenjs", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "webpack.config.js", 6 | "scripts": { 7 | "start": "NODE_ENV=development webpack-serve", 8 | "build": "webpack", 9 | "test": "echo \"Error: no test specified\" && exit 1" 10 | }, 11 | "author": "", 12 | "license": "ISC", 13 | "dependencies": { 14 | "connect-history-api-fallback": "^1.5.0", 15 | "html-webpack-plugin": "^3.2.0", 16 | "koa-connect": "^2.0.1", 17 | "webpack": "^4.17.0", 18 | "webpack-cli": "^3.1.0", 19 | "webpack-serve": "^2.0.2", 20 | "@babel/core": "^7.0.0-rc.1", 21 | "@babel/preset-env": "^7.0.0-rc.1", 22 | "@babel/preset-react": "^7.0.0-rc.1", 23 | "babel-loader": "^8.0.0-beta.4" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /examples/composable/src/app.jsx: -------------------------------------------------------------------------------- 1 | const Svenjs = require('dist/index.js').default; 2 | const Welcome = require('./welcome'); 3 | const Welcome2 = require('./welcome2'); 4 | const Counter = require('./counter'); 5 | module.exports = Svenjs.create({ 6 | render(){ 7 | return (
    8 | 9 |

    Hello

    10 |
    11 | 12 |

    Hello

    13 |
    14 | 15 |

    Hello

    16 |
    17 | 18 |

    Counter

    19 |
    20 |
    ); 21 | } 22 | }); 23 | -------------------------------------------------------------------------------- /examples/composable/src/counter.jsx: -------------------------------------------------------------------------------- 1 | const Svenjs = require('dist/index.js').default; 2 | 3 | const App = Svenjs.create({ 4 | initialState: {count: 0}, 5 | _didMount() { 6 | setInterval(() => { 7 | this.upCount() 8 | }, 500); 9 | }, 10 | upCount() { 11 | this.setState({count: this.state.count + 1}); 12 | }, 13 | _didUpdate() { 14 | }, 15 | render() { 16 | let count = this.state.count; 17 | return (
    Hello from subcomponent 2 -- I can update my counter ({count})!
    ); 18 | } 19 | }); 20 | module.exports = App; 21 | -------------------------------------------------------------------------------- /examples/composable/src/get-json.js: -------------------------------------------------------------------------------- 1 | exports.getJSON=(url) =>{ 2 | 'use strict'; 3 | return new Promise((resolve, reject) =>{ 4 | const xhr = new XMLHttpRequest(); 5 | xhr.onreadystatechange = () => { 6 | if (xhr.readyState === 4) { 7 | if (xhr.status === 200) { 8 | resolve(JSON.parse(xhr.responseText)); 9 | } else { 10 | reject(xhr.responseText); 11 | } 12 | } 13 | }; 14 | xhr.open('GET', url); 15 | xhr.send(); 16 | }); 17 | } 18 | -------------------------------------------------------------------------------- /examples/composable/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SvenJS • Composable Demo 6 | 7 | 8 |
    Composable demo app
    9 |
    ...123...
    10 |
    Footer
    11 | 12 | 13 | -------------------------------------------------------------------------------- /examples/composable/src/index.jsx: -------------------------------------------------------------------------------- 1 | const Svenjs = require('dist/index.js').default; 2 | 3 | const App = require("./app"); 4 | const rootNode = document.getElementById('myapp'); 5 | Svenjs.render( 6 | App, 7 | rootNode 8 | ); 9 | -------------------------------------------------------------------------------- /examples/composable/src/store.js: -------------------------------------------------------------------------------- 1 | const Svenjs = require('dist/index.js').default; 2 | // const Svenjs = require('./sven.js'); 3 | let _data = ['Do this','Then do this']; 4 | module.exports = Svenjs.createStore({ 5 | init(){ 6 | this.emit(_data); 7 | } 8 | }); 9 | -------------------------------------------------------------------------------- /examples/composable/src/welcome.jsx: -------------------------------------------------------------------------------- 1 | const Svenjs = require('dist/index.js').default; 2 | 3 | const Welcome = Svenjs.create({ 4 | initialState: {welcomeString: "Hello World!"}, 5 | _didMount() { 6 | setTimeout(() => { 7 | this.setState({ 8 | welcomeString: "I am Santa Claus" 9 | }) 10 | }, 1500); 11 | }, 12 | render() { 13 | const {welcomeString} = this.state; 14 | return
    {welcomeString}
    15 | } 16 | }); 17 | module.exports = Welcome; 18 | 19 | -------------------------------------------------------------------------------- /examples/composable/src/welcome2.jsx: -------------------------------------------------------------------------------- 1 | const Svenjs = require('dist/index.js').default; 2 | 3 | const Welcome = Svenjs.create({ 4 | initialState: {welcomeString: "Hello World!"}, 5 | _didMount() { 6 | setTimeout(() => { 7 | this.setState({ 8 | welcomeString: "I am Santa Claus" 9 | }) 10 | }, 1500); 11 | }, 12 | render() { 13 | const {welcomeString} = this.state; 14 | const {greeting} = this.props; 15 | return
    {welcomeString} {greeting}
    16 | } 17 | }); 18 | module.exports = Welcome; 19 | 20 | -------------------------------------------------------------------------------- /examples/composable/webpack.config.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | var path = require('path'); 3 | const HtmlWebpackPlugin = require("html-webpack-plugin"); 4 | const history = require('connect-history-api-fallback'); 5 | const convert = require('koa-connect'); 6 | 7 | module.exports = { 8 | mode: "development", 9 | module: { 10 | rules: [ 11 | {test: /\.js$/, loaders: ['babel-loader'], exclude: /node_modules/}, 12 | {test: /\.jsx$/, enforce:"pre", loaders: ['svenjsx-loader'], exclude: /node_modules/} 13 | ] 14 | }, 15 | plugins: [ 16 | new webpack.DefinePlugin({ 17 | 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV) 18 | }), 19 | new HtmlWebpackPlugin( 20 | { 21 | template: path.resolve(__dirname, "src/index.html") 22 | } 23 | ) 24 | ], 25 | entry: [path.resolve(__dirname, 'src/index.jsx')], 26 | output: { 27 | path: path.resolve(__dirname, "build"), 28 | filename: "bundle.js" 29 | }, 30 | resolve: { 31 | extensions: [".js",".jsx"], 32 | alias: { 33 | root: path.resolve(__dirname, "../../src"), 34 | dist: path.resolve(__dirname, "../../dist"), 35 | assets: path.resolve(__dirname, "../../assets") 36 | } 37 | } 38 | }; 39 | module.exports.serve = { 40 | content: [__dirname], 41 | add: (app, middleware, options) => { 42 | const historyOptions = { 43 | rewrites: [ 44 | {from: /\/*bundle.js$/, to: '/bundle.js'} 45 | ], 46 | index: '/index.html', 47 | logger: console.log.bind(console) 48 | }; 49 | app.use(convert(history(historyOptions))); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /examples/todomvc/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "env":{ 3 | "development": { 4 | "presets": ["@babel/preset-env"] 5 | }, 6 | "production": { 7 | "presets": ["@babel/preset-env"] 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /examples/todomvc/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /examples/todomvc/docs/app.css: -------------------------------------------------------------------------------- 1 | h3 {margin: 0; font-size: 1em; text-decoration: underline} 2 | 3 | #row {width: 19em; margin: 0 auto} 4 | 5 | #app {width: 45%; float: left; border-right: 1px solid grey; } 6 | #app small {color: #afafaf;display: block; padding: 4px;} 7 | #myapp {width: 100%; float: left; border-right: 1px solid grey; } 8 | #myapp small {color: #afafaf;display: block; padding: 4px;} 9 | #ui, button {margin-top: 1em;display: block; padding: 4px} 10 | #ui ul {margin-top: 0.3em;display: block; } 11 | #ui li {cursor: pointer} 12 | #ui li:hover {text-decoration: line-through} 13 | 14 | #time-travel {width: 40%; float: left; padding-left: 1em;display: block;} 15 | -------------------------------------------------------------------------------- /examples/todomvc/docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SvenJS • TodoMVC 6 | 7 | 8 | 9 | 10 | 11 |
    12 |
    13 |

    Double-click to edit a todo

    14 |

    Created by Sven

    15 | 16 |
    17 | 18 | 19 | -------------------------------------------------------------------------------- /examples/todomvc/docs/todomvc-app-css/index.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | margin: 0; 4 | padding: 0; 5 | } 6 | 7 | button { 8 | margin: 0; 9 | padding: 0; 10 | border: 0; 11 | background: none; 12 | font-size: 100%; 13 | vertical-align: baseline; 14 | font-family: inherit; 15 | font-weight: inherit; 16 | color: inherit; 17 | -webkit-appearance: none; 18 | appearance: none; 19 | -webkit-font-smoothing: antialiased; 20 | -moz-font-smoothing: antialiased; 21 | font-smoothing: antialiased; 22 | } 23 | 24 | body { 25 | font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif; 26 | line-height: 1.4em; 27 | background: #f5f5f5; 28 | color: #4d4d4d; 29 | min-width: 230px; 30 | max-width: 550px; 31 | margin: 0 auto; 32 | -webkit-font-smoothing: antialiased; 33 | -moz-font-smoothing: antialiased; 34 | font-smoothing: antialiased; 35 | font-weight: 300; 36 | } 37 | 38 | button, 39 | input[type="checkbox"] { 40 | outline: none; 41 | } 42 | 43 | .hidden { 44 | display: none; 45 | } 46 | 47 | .todoapp { 48 | background: #fff; 49 | margin: 130px 0 40px 0; 50 | position: relative; 51 | box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 52 | 0 25px 50px 0 rgba(0, 0, 0, 0.1); 53 | } 54 | 55 | .todoapp input::-webkit-input-placeholder { 56 | font-style: italic; 57 | font-weight: 300; 58 | color: #e6e6e6; 59 | } 60 | 61 | .todoapp input::-moz-placeholder { 62 | font-style: italic; 63 | font-weight: 300; 64 | color: #e6e6e6; 65 | } 66 | 67 | .todoapp input::input-placeholder { 68 | font-style: italic; 69 | font-weight: 300; 70 | color: #e6e6e6; 71 | } 72 | 73 | .todoapp h1 { 74 | position: absolute; 75 | top: -155px; 76 | width: 100%; 77 | font-size: 100px; 78 | font-weight: 100; 79 | text-align: center; 80 | color: rgba(175, 47, 47, 0.15); 81 | -webkit-text-rendering: optimizeLegibility; 82 | -moz-text-rendering: optimizeLegibility; 83 | text-rendering: optimizeLegibility; 84 | } 85 | 86 | .new-todo, 87 | .edit { 88 | position: relative; 89 | margin: 0; 90 | width: 100%; 91 | font-size: 24px; 92 | font-family: inherit; 93 | font-weight: inherit; 94 | line-height: 1.4em; 95 | border: 0; 96 | outline: none; 97 | color: inherit; 98 | padding: 6px; 99 | border: 1px solid #999; 100 | box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2); 101 | box-sizing: border-box; 102 | -webkit-font-smoothing: antialiased; 103 | -moz-font-smoothing: antialiased; 104 | font-smoothing: antialiased; 105 | } 106 | 107 | .new-todo { 108 | padding: 16px 16px 16px 60px; 109 | border: none; 110 | background: rgba(0, 0, 0, 0.003); 111 | box-shadow: inset 0 -2px 1px rgba(0,0,0,0.03); 112 | } 113 | 114 | .main { 115 | position: relative; 116 | z-index: 2; 117 | border-top: 1px solid #e6e6e6; 118 | } 119 | 120 | label[for='toggle-all'] { 121 | display: none; 122 | } 123 | 124 | .toggle-all { 125 | position: absolute; 126 | top: -55px; 127 | left: -12px; 128 | width: 60px; 129 | height: 34px; 130 | text-align: center; 131 | border: none; /* Mobile Safari */ 132 | } 133 | 134 | .toggle-all:before { 135 | content: '❯'; 136 | font-size: 22px; 137 | color: #e6e6e6; 138 | padding: 10px 27px 10px 27px; 139 | } 140 | 141 | .toggle-all:checked:before { 142 | color: #737373; 143 | } 144 | 145 | .todo-list { 146 | margin: 0; 147 | padding: 0; 148 | list-style: none; 149 | } 150 | 151 | .todo-list li { 152 | position: relative; 153 | font-size: 24px; 154 | border-bottom: 1px solid #ededed; 155 | } 156 | 157 | .todo-list li:last-child { 158 | border-bottom: none; 159 | } 160 | 161 | .todo-list li.editing { 162 | border-bottom: none; 163 | padding: 0; 164 | } 165 | 166 | .todo-list li.editing .edit { 167 | display: block; 168 | width: 506px; 169 | padding: 13px 17px 12px 17px; 170 | margin: 0 0 0 43px; 171 | } 172 | 173 | .todo-list li.editing .view { 174 | display: none; 175 | } 176 | 177 | .todo-list li .toggle { 178 | text-align: center; 179 | width: 40px; 180 | /* auto, since non-WebKit browsers doesn't support input styling */ 181 | height: auto; 182 | position: absolute; 183 | top: 0; 184 | bottom: 0; 185 | margin: auto 0; 186 | border: none; /* Mobile Safari */ 187 | -webkit-appearance: none; 188 | appearance: none; 189 | } 190 | 191 | .todo-list li .toggle:after { 192 | content: url('data:image/svg+xml;utf8,'); 193 | } 194 | 195 | .todo-list li .toggle:checked:after { 196 | content: url('data:image/svg+xml;utf8,'); 197 | } 198 | 199 | .todo-list li label { 200 | white-space: pre; 201 | word-break: break-word; 202 | padding: 15px 60px 15px 15px; 203 | margin-left: 45px; 204 | display: block; 205 | line-height: 1.2; 206 | transition: color 0.4s; 207 | } 208 | 209 | .todo-list li.completed label { 210 | color: #d9d9d9; 211 | text-decoration: line-through; 212 | } 213 | 214 | .todo-list li .destroy { 215 | display: none; 216 | position: absolute; 217 | top: 0; 218 | right: 10px; 219 | bottom: 0; 220 | width: 40px; 221 | height: 40px; 222 | margin: auto 0; 223 | font-size: 30px; 224 | color: #cc9a9a; 225 | margin-bottom: 11px; 226 | transition: color 0.2s ease-out; 227 | } 228 | 229 | .todo-list li .destroy:hover { 230 | color: #af5b5e; 231 | } 232 | 233 | .todo-list li .destroy:after { 234 | content: '×'; 235 | } 236 | 237 | .todo-list li:hover .destroy { 238 | display: block; 239 | } 240 | 241 | .todo-list li .edit { 242 | display: none; 243 | } 244 | 245 | .todo-list li.editing:last-child { 246 | margin-bottom: -1px; 247 | } 248 | 249 | .footer { 250 | color: #777; 251 | padding: 10px 15px; 252 | height: 20px; 253 | text-align: center; 254 | border-top: 1px solid #e6e6e6; 255 | } 256 | 257 | .footer:before { 258 | content: ''; 259 | position: absolute; 260 | right: 0; 261 | bottom: 0; 262 | left: 0; 263 | height: 50px; 264 | overflow: hidden; 265 | box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2), 266 | 0 8px 0 -3px #f6f6f6, 267 | 0 9px 1px -3px rgba(0, 0, 0, 0.2), 268 | 0 16px 0 -6px #f6f6f6, 269 | 0 17px 2px -6px rgba(0, 0, 0, 0.2); 270 | } 271 | 272 | .todo-count { 273 | float: left; 274 | text-align: left; 275 | } 276 | 277 | .todo-count strong { 278 | font-weight: 300; 279 | } 280 | 281 | .filters { 282 | margin: 0; 283 | padding: 0; 284 | list-style: none; 285 | position: absolute; 286 | right: 0; 287 | left: 0; 288 | } 289 | 290 | .filters li { 291 | display: inline; 292 | } 293 | 294 | .filters li a { 295 | color: inherit; 296 | margin: 3px; 297 | padding: 3px 7px; 298 | text-decoration: none; 299 | border: 1px solid transparent; 300 | border-radius: 3px; 301 | } 302 | 303 | .filters li a.selected, 304 | .filters li a:hover { 305 | border-color: rgba(175, 47, 47, 0.1); 306 | } 307 | 308 | .filters li a.selected { 309 | border-color: rgba(175, 47, 47, 0.2); 310 | } 311 | 312 | .clear-completed, 313 | html .clear-completed:active { 314 | float: right; 315 | position: relative; 316 | line-height: 20px; 317 | text-decoration: none; 318 | cursor: pointer; 319 | position: relative; 320 | } 321 | 322 | .clear-completed:hover { 323 | text-decoration: underline; 324 | } 325 | 326 | .info { 327 | margin: 65px auto 0; 328 | color: #bfbfbf; 329 | font-size: 10px; 330 | text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); 331 | text-align: center; 332 | } 333 | 334 | .info p { 335 | line-height: 1; 336 | } 337 | 338 | .info a { 339 | color: inherit; 340 | text-decoration: none; 341 | font-weight: 400; 342 | } 343 | 344 | .info a:hover { 345 | text-decoration: underline; 346 | } 347 | 348 | /* 349 | Hack to remove background from Mobile Safari. 350 | Can't use it globally since it destroys checkboxes in Firefox 351 | */ 352 | @media screen and (-webkit-min-device-pixel-ratio:0) { 353 | .toggle-all, 354 | .todo-list li .toggle { 355 | background: none; 356 | } 357 | 358 | .todo-list li .toggle { 359 | height: 40px; 360 | } 361 | 362 | .toggle-all { 363 | -webkit-transform: rotate(90deg); 364 | transform: rotate(90deg); 365 | -webkit-appearance: none; 366 | appearance: none; 367 | } 368 | } 369 | 370 | @media (max-width: 430px) { 371 | .footer { 372 | height: 50px; 373 | } 374 | 375 | .filters { 376 | bottom: 10px; 377 | } 378 | } 379 | -------------------------------------------------------------------------------- /examples/todomvc/docs/todomvc-app-css/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "todomvc-app-css", 3 | "version": "2.0.1", 4 | "description": "CSS for TodoMVC apps", 5 | "license": "CC-BY-4.0", 6 | "repository": { 7 | "type": "git", 8 | "url": "git+https://github.com/tastejs/todomvc-app-css.git" 9 | }, 10 | "author": { 11 | "name": "Sindre Sorhus", 12 | "email": "sindresorhus@gmail.com", 13 | "url": "sindresorhus.com" 14 | }, 15 | "files": [ 16 | "index.css" 17 | ], 18 | "keywords": [ 19 | "todomvc", 20 | "tastejs", 21 | "app", 22 | "todo", 23 | "template", 24 | "css", 25 | "style", 26 | "stylesheet" 27 | ], 28 | "gitHead": "f1bb1aa9b19888f339055418374a9b3a2d4c6fc5", 29 | "bugs": { 30 | "url": "https://github.com/tastejs/todomvc-app-css/issues" 31 | }, 32 | "homepage": "https://github.com/tastejs/todomvc-app-css", 33 | "_id": "todomvc-app-css@2.0.1", 34 | "scripts": {}, 35 | "_shasum": "f64d50b744a8a83c1151a08055b88f3aa5ccb052", 36 | "_from": "todomvc-app-css@>=2.0.1 <3.0.0", 37 | "_npmVersion": "2.5.1", 38 | "_nodeVersion": "0.12.0", 39 | "_npmUser": { 40 | "name": "sindresorhus", 41 | "email": "sindresorhus@gmail.com" 42 | }, 43 | "maintainers": [ 44 | { 45 | "name": "sindresorhus", 46 | "email": "sindresorhus@gmail.com" 47 | }, 48 | { 49 | "name": "addyosmani", 50 | "email": "addyosmani@gmail.com" 51 | }, 52 | { 53 | "name": "passy", 54 | "email": "phartig@rdrei.net" 55 | }, 56 | { 57 | "name": "stephenplusplus", 58 | "email": "sawchuk@gmail.com" 59 | } 60 | ], 61 | "dist": { 62 | "shasum": "f64d50b744a8a83c1151a08055b88f3aa5ccb052", 63 | "tarball": "http://registry.npmjs.org/todomvc-app-css/-/todomvc-app-css-2.0.1.tgz" 64 | }, 65 | "directories": {}, 66 | "_resolved": "https://registry.npmjs.org/todomvc-app-css/-/todomvc-app-css-2.0.1.tgz", 67 | "readme": "ERROR: No README data found!" 68 | } 69 | -------------------------------------------------------------------------------- /examples/todomvc/docs/todomvc-app-css/readme.md: -------------------------------------------------------------------------------- 1 | # todomvc-app-css 2 | 3 | > CSS for TodoMVC apps 4 | 5 | ![](screenshot.png) 6 | 7 | 8 | ## Install 9 | 10 | 11 | ``` 12 | $ npm install --save todomvc-app-css 13 | ``` 14 | 15 | 16 | ## Getting started 17 | 18 | ```html 19 | 20 | ``` 21 | 22 | See the [TodoMVC app template](https://github.com/tastejs/todomvc-app-template). 23 | 24 | 25 | 26 | ## License 27 | 28 | Creative Commons License
    This work by Sindre Sorhus is licensed under a Creative Commons Attribution 4.0 International License. 29 | -------------------------------------------------------------------------------- /examples/todomvc/docs/todomvc-common/base.css: -------------------------------------------------------------------------------- 1 | hr { 2 | margin: 20px 0; 3 | border: 0; 4 | border-top: 1px dashed #c5c5c5; 5 | border-bottom: 1px dashed #f7f7f7; 6 | } 7 | 8 | .learn a { 9 | font-weight: normal; 10 | text-decoration: none; 11 | color: #b83f45; 12 | } 13 | 14 | .learn a:hover { 15 | text-decoration: underline; 16 | color: #787e7e; 17 | } 18 | 19 | .learn h3, 20 | .learn h4, 21 | .learn h5 { 22 | margin: 10px 0; 23 | font-weight: 500; 24 | line-height: 1.2; 25 | color: #000; 26 | } 27 | 28 | .learn h3 { 29 | font-size: 24px; 30 | } 31 | 32 | .learn h4 { 33 | font-size: 18px; 34 | } 35 | 36 | .learn h5 { 37 | margin-bottom: 0; 38 | font-size: 14px; 39 | } 40 | 41 | .learn ul { 42 | padding: 0; 43 | margin: 0 0 30px 25px; 44 | } 45 | 46 | .learn li { 47 | line-height: 20px; 48 | } 49 | 50 | .learn p { 51 | font-size: 15px; 52 | font-weight: 300; 53 | line-height: 1.3; 54 | margin-top: 0; 55 | margin-bottom: 0; 56 | } 57 | 58 | #issue-count { 59 | display: none; 60 | } 61 | 62 | .quote { 63 | border: none; 64 | margin: 20px 0 60px 0; 65 | } 66 | 67 | .quote p { 68 | font-style: italic; 69 | } 70 | 71 | .quote p:before { 72 | content: '“'; 73 | font-size: 50px; 74 | opacity: .15; 75 | position: absolute; 76 | top: -20px; 77 | left: 3px; 78 | } 79 | 80 | .quote p:after { 81 | content: '”'; 82 | font-size: 50px; 83 | opacity: .15; 84 | position: absolute; 85 | bottom: -42px; 86 | right: 3px; 87 | } 88 | 89 | .quote footer { 90 | position: absolute; 91 | bottom: -40px; 92 | right: 0; 93 | } 94 | 95 | .quote footer img { 96 | border-radius: 3px; 97 | } 98 | 99 | .quote footer a { 100 | margin-left: 5px; 101 | vertical-align: middle; 102 | } 103 | 104 | .speech-bubble { 105 | position: relative; 106 | padding: 10px; 107 | background: rgba(0, 0, 0, .04); 108 | border-radius: 5px; 109 | } 110 | 111 | .speech-bubble:after { 112 | content: ''; 113 | position: absolute; 114 | top: 100%; 115 | right: 30px; 116 | border: 13px solid transparent; 117 | border-top-color: rgba(0, 0, 0, .04); 118 | } 119 | 120 | .learn-bar > .learn { 121 | position: absolute; 122 | width: 272px; 123 | top: 8px; 124 | left: -300px; 125 | padding: 10px; 126 | border-radius: 5px; 127 | background-color: rgba(255, 255, 255, .6); 128 | transition-property: left; 129 | transition-duration: 500ms; 130 | } 131 | 132 | @media (min-width: 899px) { 133 | .learn-bar { 134 | width: auto; 135 | padding-left: 300px; 136 | } 137 | 138 | .learn-bar > .learn { 139 | left: 8px; 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /examples/todomvc/docs/todomvc-common/base.js: -------------------------------------------------------------------------------- 1 | /* global _ */ 2 | (function () { 3 | 'use strict'; 4 | 5 | /* jshint ignore:start */ 6 | // Underscore's Template Module 7 | // Courtesy of underscorejs.org 8 | var _ = (function (_) { 9 | _.defaults = function (object) { 10 | if (!object) { 11 | return object; 12 | } 13 | for (var argsIndex = 1, argsLength = arguments.length; argsIndex < argsLength; argsIndex++) { 14 | var iterable = arguments[argsIndex]; 15 | if (iterable) { 16 | for (var key in iterable) { 17 | if (object[key] == null) { 18 | object[key] = iterable[key]; 19 | } 20 | } 21 | } 22 | } 23 | return object; 24 | } 25 | 26 | // By default, Underscore uses ERB-style template delimiters, change the 27 | // following template settings to use alternative delimiters. 28 | _.templateSettings = { 29 | evaluate : /<%([\s\S]+?)%>/g, 30 | interpolate : /<%=([\s\S]+?)%>/g, 31 | escape : /<%-([\s\S]+?)%>/g 32 | }; 33 | 34 | // When customizing `templateSettings`, if you don't want to define an 35 | // interpolation, evaluation or escaping regex, we need one that is 36 | // guaranteed not to match. 37 | var noMatch = /(.)^/; 38 | 39 | // Certain characters need to be escaped so that they can be put into a 40 | // string literal. 41 | var escapes = { 42 | "'": "'", 43 | '\\': '\\', 44 | '\r': 'r', 45 | '\n': 'n', 46 | '\t': 't', 47 | '\u2028': 'u2028', 48 | '\u2029': 'u2029' 49 | }; 50 | 51 | var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g; 52 | 53 | // JavaScript micro-templating, similar to John Resig's implementation. 54 | // Underscore templating handles arbitrary delimiters, preserves whitespace, 55 | // and correctly escapes quotes within interpolated code. 56 | _.template = function(text, data, settings) { 57 | var render; 58 | settings = _.defaults({}, settings, _.templateSettings); 59 | 60 | // Combine delimiters into one regular expression via alternation. 61 | var matcher = new RegExp([ 62 | (settings.escape || noMatch).source, 63 | (settings.interpolate || noMatch).source, 64 | (settings.evaluate || noMatch).source 65 | ].join('|') + '|$', 'g'); 66 | 67 | // Compile the template source, escaping string literals appropriately. 68 | var index = 0; 69 | var source = "__p+='"; 70 | text.replace(matcher, function(match, escape, interpolate, evaluate, offset) { 71 | source += text.slice(index, offset) 72 | .replace(escaper, function(match) { return '\\' + escapes[match]; }); 73 | 74 | if (escape) { 75 | source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'"; 76 | } 77 | if (interpolate) { 78 | source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'"; 79 | } 80 | if (evaluate) { 81 | source += "';\n" + evaluate + "\n__p+='"; 82 | } 83 | index = offset + match.length; 84 | return match; 85 | }); 86 | source += "';\n"; 87 | 88 | // If a variable is not specified, place data values in local scope. 89 | if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n'; 90 | 91 | source = "var __t,__p='',__j=Array.prototype.join," + 92 | "print=function(){__p+=__j.call(arguments,'');};\n" + 93 | source + "return __p;\n"; 94 | 95 | try { 96 | render = new Function(settings.variable || 'obj', '_', source); 97 | } catch (e) { 98 | e.source = source; 99 | throw e; 100 | } 101 | 102 | if (data) return render(data, _); 103 | var template = function(data) { 104 | return render.call(this, data, _); 105 | }; 106 | 107 | // Provide the compiled function source as a convenience for precompilation. 108 | template.source = 'function(' + (settings.variable || 'obj') + '){\n' + source + '}'; 109 | 110 | return template; 111 | }; 112 | 113 | return _; 114 | })({}); 115 | 116 | if (location.hostname === 'todomvc.com') { 117 | (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ 118 | (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), 119 | m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) 120 | })(window,document,'script','https://www.google-analytics.com/analytics.js','ga'); 121 | ga('create', 'UA-31081062-1', 'auto'); 122 | ga('send', 'pageview'); 123 | } 124 | /* jshint ignore:end */ 125 | 126 | function redirect() { 127 | if (location.hostname === 'tastejs.github.io') { 128 | location.href = location.href.replace('tastejs.github.io/todomvc', 'todomvc.com'); 129 | } 130 | } 131 | 132 | function findRoot() { 133 | var base = location.href.indexOf('examples/'); 134 | return location.href.substr(0, base); 135 | } 136 | 137 | function getFile(file, callback) { 138 | if (!location.host) { 139 | return console.info('Miss the info bar? Run TodoMVC from a server to avoid a cross-origin error.'); 140 | } 141 | 142 | var xhr = new XMLHttpRequest(); 143 | 144 | xhr.open('GET', findRoot() + file, true); 145 | xhr.send(); 146 | 147 | xhr.onload = function () { 148 | if (xhr.status === 200 && callback) { 149 | callback(xhr.responseText); 150 | } 151 | }; 152 | } 153 | 154 | function Learn(learnJSON, config) { 155 | if (!(this instanceof Learn)) { 156 | return new Learn(learnJSON, config); 157 | } 158 | 159 | var template, framework; 160 | 161 | if (typeof learnJSON !== 'object') { 162 | try { 163 | learnJSON = JSON.parse(learnJSON); 164 | } catch (e) { 165 | return; 166 | } 167 | } 168 | 169 | if (config) { 170 | template = config.template; 171 | framework = config.framework; 172 | } 173 | 174 | if (!template && learnJSON.templates) { 175 | template = learnJSON.templates.todomvc; 176 | } 177 | 178 | if (!framework && document.querySelector('[data-framework]')) { 179 | framework = document.querySelector('[data-framework]').dataset.framework; 180 | } 181 | 182 | this.template = template; 183 | 184 | if (learnJSON.backend) { 185 | this.frameworkJSON = learnJSON.backend; 186 | this.frameworkJSON.issueLabel = framework; 187 | this.append({ 188 | backend: true 189 | }); 190 | } else if (learnJSON[framework]) { 191 | this.frameworkJSON = learnJSON[framework]; 192 | this.frameworkJSON.issueLabel = framework; 193 | this.append(); 194 | } 195 | 196 | this.fetchIssueCount(); 197 | } 198 | 199 | Learn.prototype.append = function (opts) { 200 | var aside = document.createElement('aside'); 201 | aside.innerHTML = _.template(this.template, this.frameworkJSON); 202 | aside.className = 'learn'; 203 | 204 | if (opts && opts.backend) { 205 | // Remove demo link 206 | var sourceLinks = aside.querySelector('.source-links'); 207 | var heading = sourceLinks.firstElementChild; 208 | var sourceLink = sourceLinks.lastElementChild; 209 | // Correct link path 210 | var href = sourceLink.getAttribute('href'); 211 | sourceLink.setAttribute('href', href.substr(href.lastIndexOf('http'))); 212 | sourceLinks.innerHTML = heading.outerHTML + sourceLink.outerHTML; 213 | } else { 214 | // Localize demo links 215 | var demoLinks = aside.querySelectorAll('.demo-link'); 216 | Array.prototype.forEach.call(demoLinks, function (demoLink) { 217 | if (demoLink.getAttribute('href').substr(0, 4) !== 'http') { 218 | demoLink.setAttribute('href', findRoot() + demoLink.getAttribute('href')); 219 | } 220 | }); 221 | } 222 | 223 | document.body.className = (document.body.className + ' learn-bar').trim(); 224 | document.body.insertAdjacentHTML('afterBegin', aside.outerHTML); 225 | }; 226 | 227 | Learn.prototype.fetchIssueCount = function () { 228 | var issueLink = document.getElementById('issue-count-link'); 229 | if (issueLink) { 230 | var url = issueLink.href.replace('https://github.com', 'https://api.github.com/repos'); 231 | var xhr = new XMLHttpRequest(); 232 | xhr.open('GET', url, true); 233 | xhr.onload = function (e) { 234 | var parsedResponse = JSON.parse(e.target.responseText); 235 | if (parsedResponse instanceof Array) { 236 | var count = parsedResponse.length; 237 | if (count !== 0) { 238 | issueLink.innerHTML = 'This app has ' + count + ' open issues'; 239 | document.getElementById('issue-count').style.display = 'inline'; 240 | } 241 | } 242 | }; 243 | xhr.send(); 244 | } 245 | }; 246 | 247 | redirect(); 248 | //getFile('learn.json', Learn); 249 | })(); 250 | -------------------------------------------------------------------------------- /examples/todomvc/docs/todomvc-common/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "todomvc-common", 3 | "version": "1.0.2", 4 | "description": "Common TodoMVC utilities used by our apps", 5 | "license": "MIT", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/tastejs/todomvc-common" 9 | }, 10 | "author": { 11 | "name": "TasteJS" 12 | }, 13 | "main": "base.js", 14 | "files": [ 15 | "base.js", 16 | "base.css" 17 | ], 18 | "keywords": [ 19 | "todomvc", 20 | "tastejs", 21 | "util", 22 | "utilities" 23 | ], 24 | "gitHead": "e82d0c79e01687ce7407df786cc784ad82166cb3", 25 | "bugs": { 26 | "url": "https://github.com/tastejs/todomvc-common/issues" 27 | }, 28 | "homepage": "https://github.com/tastejs/todomvc-common", 29 | "_id": "todomvc-common@1.0.2", 30 | "scripts": {}, 31 | "_shasum": "eb3ab61281ac74809f5869c917c7b08bc84234e0", 32 | "_from": "todomvc-common@>=1.0.1 <2.0.0", 33 | "_npmVersion": "2.7.4", 34 | "_nodeVersion": "0.12.2", 35 | "_npmUser": { 36 | "name": "sindresorhus", 37 | "email": "sindresorhus@gmail.com" 38 | }, 39 | "dist": { 40 | "shasum": "eb3ab61281ac74809f5869c917c7b08bc84234e0", 41 | "tarball": "http://registry.npmjs.org/todomvc-common/-/todomvc-common-1.0.2.tgz" 42 | }, 43 | "maintainers": [ 44 | { 45 | "name": "sindresorhus", 46 | "email": "sindresorhus@gmail.com" 47 | }, 48 | { 49 | "name": "addyosmani", 50 | "email": "addyosmani@gmail.com" 51 | }, 52 | { 53 | "name": "passy", 54 | "email": "phartig@rdrei.net" 55 | }, 56 | { 57 | "name": "stephenplusplus", 58 | "email": "sawchuk@gmail.com" 59 | } 60 | ], 61 | "directories": {}, 62 | "_resolved": "https://registry.npmjs.org/todomvc-common/-/todomvc-common-1.0.2.tgz" 63 | } 64 | -------------------------------------------------------------------------------- /examples/todomvc/docs/todomvc-common/readme.md: -------------------------------------------------------------------------------- 1 | # todomvc-common 2 | 3 | > Common TodoMVC utilities used by our apps 4 | 5 | 6 | ## Install 7 | 8 | ``` 9 | $ npm install --save todomvc-common 10 | ``` 11 | 12 | 13 | ## License 14 | 15 | MIT © [TasteJS](http://tastejs.com) 16 | -------------------------------------------------------------------------------- /examples/todomvc/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "svenjs-todomvc", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "webpack.config.js", 6 | "scripts": { 7 | "start": "NODE_ENV=development webpack-serve", 8 | "build": "webpack", 9 | "test": "echo \"Error: no test specified\" && exit 1" 10 | }, 11 | "author": "", 12 | "license": "ISC", 13 | "dependencies": { 14 | "@babel/core": "^7.0.0-rc.1", 15 | "@babel/preset-env": "^7.0.0-rc.1", 16 | "@babel/preset-react": "^7.0.0-rc.1", 17 | "babel-loader": "^8.0.0-beta.4", 18 | "connect-history-api-fallback": "^1.5.0", 19 | "html-webpack-plugin": "^3.2.0", 20 | "jstransform": "^11.0.3", 21 | "koa-connect": "^2.0.1", 22 | "webpack": "^4.17.0", 23 | "webpack-cli": "^3.1.0", 24 | "webpack-serve": "^2.0.2" 25 | }, 26 | "devDependencies": { 27 | "svenjs": "^2.0.0", 28 | "svenjsx": "^2.0.1", 29 | "svenjsx-loader": "^2.0.2" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /examples/todomvc/src/app.css: -------------------------------------------------------------------------------- 1 | h3 {margin: 0; font-size: 1em; text-decoration: underline} 2 | 3 | #row {width: 19em; margin: 0 auto} 4 | 5 | #app {width: 45%; float: left; border-right: 1px solid grey; } 6 | #app small {color: #afafaf;display: block; padding: 4px;} 7 | #myapp {width: 100%; float: left; border-right: 1px solid grey; } 8 | #myapp small {color: #afafaf;display: block; padding: 4px;} 9 | #ui, button {margin-top: 1em;display: block; padding: 4px} 10 | #ui ul {margin-top: 0.3em;display: block; } 11 | #ui li {cursor: pointer} 12 | #ui li:hover {text-decoration: line-through} 13 | 14 | #time-travel {width: 40%; float: left; padding-left: 1em;display: block;} 15 | -------------------------------------------------------------------------------- /examples/todomvc/src/app.jsx: -------------------------------------------------------------------------------- 1 | import SvenJs from "svenjs" 2 | let ENTER_KEY = 13; 3 | let ESCAPE_KEY = 27; 4 | let _toggled=false; 5 | let _prevEditing=false; 6 | let _currentEdit=0; 7 | 8 | function deepCopy (o) { 9 | return JSON.parse(JSON.stringify(o)); 10 | }; 11 | 12 | 13 | let todoMVCApp = SvenJs.create({ 14 | initialState: { 15 | messages:[ 16 | {id:1,message:"Answer all the mail",complete:false,editing:false}, 17 | {id:2,message:"Get a cup of coffee",complete:false,editing:false} 18 | ] 19 | }, 20 | _didUpdate(){ 21 | let node= document.getElementById('new-todo'); 22 | if(node!==null && _prevEditing){ 23 | _prevEditing=false; 24 | node.focus(); 25 | node.setSelectionRange(node.value.length, node.value.length); 26 | } 27 | }, 28 | _didMount(){ 29 | var url = self.history === true ? self.getPath() : window.location.hash.replace(/.*#\//, ''); 30 | this.setState({messages:this.state.messages,url:url}); 31 | window.addEventListener("hashchange", this.onHashChange.bind(this), false); 32 | }, 33 | handleEditTodoKeyDown(e){ 34 | 35 | if (e.keyCode === ESCAPE_KEY) { 36 | this.simpleResetEditing(); 37 | return; 38 | } 39 | if (e.keyCode !== ENTER_KEY) { 40 | return; 41 | } 42 | this.saveTodo(e); 43 | this.resetEditing(); 44 | e.preventDefault(); 45 | }, 46 | handleNewTodoKeyDown(id,e) { 47 | if (e.keyCode !== ENTER_KEY) { 48 | return; 49 | } 50 | this.addTodo(e); 51 | e.innerHTML=""; 52 | document.getElementById(id).focus(); 53 | e.preventDefault(); 54 | }, 55 | onHashChange(){ 56 | var url = self.history === true ? self.getPath() : window.location.hash.replace(/.*#\//, ''); 57 | this.resetEditing(); 58 | this.setState({messages:this.state.messages,url:url}); 59 | }, 60 | saveTodo(e){ 61 | let val = "undefined" === typeof e.srcElement ? e.target.value : e.srcElement.value; 62 | let messages=this.state.messages.filter((msg)=>{ 63 | if(msg.id === _currentEdit) msg.message=val; 64 | return msg 65 | }) 66 | this.setState({messages:messages, url:this.state.url}); 67 | }, 68 | addTodo(e){ 69 | let messages=deepCopy(this.state.messages); 70 | let lastId; 71 | let val = "undefined" === typeof e.srcElement ? e.target.value : e.srcElement.value; 72 | if(messages.length===0) lastId=1; 73 | else lastId=messages[messages.length-1].id; 74 | messages.push({id:lastId+1, message:val , complete: false, editing:false}); 75 | this.setState({messages:messages, url:this.state.url}); 76 | }, 77 | destroyMessage(item){ 78 | let messages=this.state.messages.filter((msg)=>{ 79 | return msg.id!==item.id 80 | }) 81 | this.setState({messages:messages}); 82 | }, 83 | destroyCompleted(){ 84 | let _messages=deepCopy(this.state.messages); 85 | let messages=_messages.filter((msg)=>{ 86 | return msg.complete===false 87 | }) 88 | this.resetEditing(); 89 | this.setState({messages:messages, url:this.state.url}); 90 | }, 91 | toggleOne(item,e){ 92 | let _messages=deepCopy(this.state.messages); 93 | let messages=_messages.filter((msg)=>{ 94 | if(msg.id === item.id) msg.complete=!msg.complete; 95 | return msg 96 | }) 97 | this.resetEditing(); 98 | this.setState({messages:messages, url:this.state.url}) 99 | }, 100 | simpleResetEditing(){ 101 | let _messages=deepCopy(this.state.messages); 102 | let messages=_messages.map((msg)=>{ 103 | msg.editing = false; 104 | return msg 105 | }); 106 | _prevEditing=false; 107 | this.setState({messages:messages, url:this.state.url}); 108 | }, 109 | resetEditing(e){ 110 | let update=false; 111 | let _messages=deepCopy(this.state.messages); 112 | 113 | let messages=_messages.map((msg)=>{ 114 | if(msg.editing) update=true; 115 | msg.editing = false; 116 | return msg 117 | }) 118 | if(update) { 119 | _prevEditing=true; 120 | this.setState({messages:messages, url:this.state.url}); 121 | } else { 122 | _prevEditing=false; 123 | } 124 | }, 125 | onDoubleClick(todo,e){ 126 | _currentEdit=todo.id; 127 | let _messages=deepCopy(this.state.messages); 128 | 129 | if(!todo.complete){ 130 | let messages=_messages.map((msg)=>{ 131 | msg.editing = msg.id===todo.id ? !msg.editing : false; 132 | return msg 133 | }) 134 | this.setState({messages:messages, url:this.state.url}); 135 | let node= document.getElementsByClassName('edit active')[0]; 136 | node.focus(); 137 | node.setSelectionRange(node.value.length, node.value.length); 138 | } 139 | }, 140 | toggleAll(){ 141 | _toggled=!_toggled; 142 | let _messages=deepCopy(this.state.messages); 143 | 144 | let messages=_messages.map((msg)=>{ 145 | msg.complete=_toggled; 146 | return msg; 147 | }) 148 | this.resetEditing(); 149 | this.setState({messages:messages}); 150 | }, 151 | listTodos(){ 152 | let _messages=deepCopy(this.state.messages); 153 | 154 | let shownTodos =_messages.filter( (todo) => { 155 | switch (this.state.url) { 156 | case "active": 157 | return !todo.complete; 158 | case "completed": 159 | return todo.complete; 160 | default: 161 | return true; 162 | } 163 | }, this); 164 | 165 | return shownTodos.map((todo)=>{ 166 | let label= todo.message; 167 | let checked=false; 168 | let className="todo"; 169 | let editClassName="edit"; 170 | if(todo.editing){ 171 | className="todo editing"; 172 | editClassName="edit active"; 173 | } 174 | if(todo.complete) { 175 | label= {todo.message}; 176 | checked=true; 177 | } 178 | return
  • 179 |
    180 | 181 | 182 | 183 |
    184 | 188 |
  • 189 | }) 190 | }, 191 | render(){ 192 | var state=this.state; 193 | let selected_all="", selected_active="", selected_completed=""; 194 | if(this.state.url==="" || this.state.url==="all") selected_all='selected'; 195 | if(this.state.url==="active") selected_active='selected'; 196 | if(this.state.url==="completed") selected_completed='selected'; 197 | 198 | return (
    199 |
    200 |

    todos

    201 | 206 |
    207 |
    208 | 209 | 210 |
      211 | {this.listTodos()} 212 |
    213 |
    214 | 215 |
    216 | {this.state.messages.length} 217 | {this.state.messages.length === 1 ? " item" : " items"} remaining 218 | 219 | 230 | 231 |
    232 |
    ); 233 | } 234 | 235 | }); 236 | export default todoMVCApp; 237 | -------------------------------------------------------------------------------- /examples/todomvc/src/get-json.js: -------------------------------------------------------------------------------- 1 | exports.getJSON=(url) =>{ 2 | 'use strict'; 3 | return new Promise((resolve, reject) =>{ 4 | const xhr = new XMLHttpRequest(); 5 | xhr.onreadystatechange = () => { 6 | if (xhr.readyState === 4) { 7 | if (xhr.status === 200) { 8 | resolve(JSON.parse(xhr.responseText)); 9 | } else { 10 | reject(xhr.responseText); 11 | } 12 | } 13 | }; 14 | xhr.open('GET', url); 15 | xhr.send(); 16 | }); 17 | } 18 | -------------------------------------------------------------------------------- /examples/todomvc/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SvenJS • TodoMVC 6 | 7 | 8 | 9 | 10 | 11 |
    12 |
    13 |

    Double-click to edit a todo

    14 |

    Created by Sven

    15 | 16 |
    17 | 18 | 19 | -------------------------------------------------------------------------------- /examples/todomvc/src/index.jsx: -------------------------------------------------------------------------------- 1 | import SvenJs from "svenjs" 2 | import App from "./app.jsx"; 3 | const rootNode = document.getElementById('myapp'); 4 | SvenJs.render( 5 | App, 6 | rootNode 7 | ); 8 | -------------------------------------------------------------------------------- /examples/todomvc/src/store.js: -------------------------------------------------------------------------------- 1 | const Svenjs = require('root/src/index.js'); 2 | // const Svenjs = require('assets/sven.js'); 3 | let _data = ['Do this','Then do this']; 4 | module.exports = Svenjs.createStore({ 5 | init(){ 6 | this.emit(_data); 7 | } 8 | }); 9 | -------------------------------------------------------------------------------- /examples/todomvc/src/todomvc-app-css/index.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | margin: 0; 4 | padding: 0; 5 | } 6 | 7 | button { 8 | margin: 0; 9 | padding: 0; 10 | border: 0; 11 | background: none; 12 | font-size: 100%; 13 | vertical-align: baseline; 14 | font-family: inherit; 15 | font-weight: inherit; 16 | color: inherit; 17 | -webkit-appearance: none; 18 | appearance: none; 19 | -webkit-font-smoothing: antialiased; 20 | -moz-font-smoothing: antialiased; 21 | font-smoothing: antialiased; 22 | } 23 | 24 | body { 25 | font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif; 26 | line-height: 1.4em; 27 | background: #f5f5f5; 28 | color: #4d4d4d; 29 | min-width: 230px; 30 | max-width: 550px; 31 | margin: 0 auto; 32 | -webkit-font-smoothing: antialiased; 33 | -moz-font-smoothing: antialiased; 34 | font-smoothing: antialiased; 35 | font-weight: 300; 36 | } 37 | 38 | button, 39 | input[type="checkbox"] { 40 | outline: none; 41 | } 42 | 43 | .hidden { 44 | display: none; 45 | } 46 | 47 | .todoapp { 48 | background: #fff; 49 | margin: 130px 0 40px 0; 50 | position: relative; 51 | box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 52 | 0 25px 50px 0 rgba(0, 0, 0, 0.1); 53 | } 54 | 55 | .todoapp input::-webkit-input-placeholder { 56 | font-style: italic; 57 | font-weight: 300; 58 | color: #e6e6e6; 59 | } 60 | 61 | .todoapp input::-moz-placeholder { 62 | font-style: italic; 63 | font-weight: 300; 64 | color: #e6e6e6; 65 | } 66 | 67 | .todoapp input::input-placeholder { 68 | font-style: italic; 69 | font-weight: 300; 70 | color: #e6e6e6; 71 | } 72 | 73 | .todoapp h1 { 74 | position: absolute; 75 | top: -155px; 76 | width: 100%; 77 | font-size: 100px; 78 | font-weight: 100; 79 | text-align: center; 80 | color: rgba(175, 47, 47, 0.15); 81 | -webkit-text-rendering: optimizeLegibility; 82 | -moz-text-rendering: optimizeLegibility; 83 | text-rendering: optimizeLegibility; 84 | } 85 | 86 | .new-todo, 87 | .edit { 88 | position: relative; 89 | margin: 0; 90 | width: 100%; 91 | font-size: 24px; 92 | font-family: inherit; 93 | font-weight: inherit; 94 | line-height: 1.4em; 95 | border: 0; 96 | outline: none; 97 | color: inherit; 98 | padding: 6px; 99 | border: 1px solid #999; 100 | box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2); 101 | box-sizing: border-box; 102 | -webkit-font-smoothing: antialiased; 103 | -moz-font-smoothing: antialiased; 104 | font-smoothing: antialiased; 105 | } 106 | 107 | .new-todo { 108 | padding: 16px 16px 16px 60px; 109 | border: none; 110 | background: rgba(0, 0, 0, 0.003); 111 | box-shadow: inset 0 -2px 1px rgba(0,0,0,0.03); 112 | } 113 | 114 | .main { 115 | position: relative; 116 | z-index: 2; 117 | border-top: 1px solid #e6e6e6; 118 | } 119 | 120 | label[for='toggle-all'] { 121 | display: none; 122 | } 123 | 124 | .toggle-all { 125 | position: absolute; 126 | top: -55px; 127 | left: -12px; 128 | width: 60px; 129 | height: 34px; 130 | text-align: center; 131 | border: none; /* Mobile Safari */ 132 | } 133 | 134 | .toggle-all:before { 135 | content: '❯'; 136 | font-size: 22px; 137 | color: #e6e6e6; 138 | padding: 10px 27px 10px 27px; 139 | } 140 | 141 | .toggle-all:checked:before { 142 | color: #737373; 143 | } 144 | 145 | .todo-list { 146 | margin: 0; 147 | padding: 0; 148 | list-style: none; 149 | } 150 | 151 | .todo-list li { 152 | position: relative; 153 | font-size: 24px; 154 | border-bottom: 1px solid #ededed; 155 | } 156 | 157 | .todo-list li:last-child { 158 | border-bottom: none; 159 | } 160 | 161 | .todo-list li.editing { 162 | border-bottom: none; 163 | padding: 0; 164 | } 165 | 166 | .todo-list li.editing .edit { 167 | display: block; 168 | width: 506px; 169 | padding: 13px 17px 12px 17px; 170 | margin: 0 0 0 43px; 171 | } 172 | 173 | .todo-list li.editing .view { 174 | display: none; 175 | } 176 | 177 | .todo-list li .toggle { 178 | text-align: center; 179 | width: 40px; 180 | /* auto, since non-WebKit browsers doesn't support input styling */ 181 | height: auto; 182 | position: absolute; 183 | top: 0; 184 | bottom: 0; 185 | margin: auto 0; 186 | border: none; /* Mobile Safari */ 187 | -webkit-appearance: none; 188 | appearance: none; 189 | } 190 | 191 | .todo-list li .toggle:after { 192 | content: url('data:image/svg+xml;utf8,'); 193 | } 194 | 195 | .todo-list li .toggle:checked:after { 196 | content: url('data:image/svg+xml;utf8,'); 197 | } 198 | 199 | .todo-list li label { 200 | white-space: pre; 201 | word-break: break-word; 202 | padding: 15px 60px 15px 15px; 203 | margin-left: 45px; 204 | display: block; 205 | line-height: 1.2; 206 | transition: color 0.4s; 207 | } 208 | 209 | .todo-list li.completed label { 210 | color: #d9d9d9; 211 | text-decoration: line-through; 212 | } 213 | 214 | .todo-list li .destroy { 215 | display: none; 216 | position: absolute; 217 | top: 0; 218 | right: 10px; 219 | bottom: 0; 220 | width: 40px; 221 | height: 40px; 222 | margin: auto 0; 223 | font-size: 30px; 224 | color: #cc9a9a; 225 | margin-bottom: 11px; 226 | transition: color 0.2s ease-out; 227 | } 228 | 229 | .todo-list li .destroy:hover { 230 | color: #af5b5e; 231 | } 232 | 233 | .todo-list li .destroy:after { 234 | content: '×'; 235 | } 236 | 237 | .todo-list li:hover .destroy { 238 | display: block; 239 | } 240 | 241 | .todo-list li .edit { 242 | display: none; 243 | } 244 | 245 | .todo-list li.editing:last-child { 246 | margin-bottom: -1px; 247 | } 248 | 249 | .footer { 250 | color: #777; 251 | padding: 10px 15px; 252 | height: 20px; 253 | text-align: center; 254 | border-top: 1px solid #e6e6e6; 255 | } 256 | 257 | .footer:before { 258 | content: ''; 259 | position: absolute; 260 | right: 0; 261 | bottom: 0; 262 | left: 0; 263 | height: 50px; 264 | overflow: hidden; 265 | box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2), 266 | 0 8px 0 -3px #f6f6f6, 267 | 0 9px 1px -3px rgba(0, 0, 0, 0.2), 268 | 0 16px 0 -6px #f6f6f6, 269 | 0 17px 2px -6px rgba(0, 0, 0, 0.2); 270 | } 271 | 272 | .todo-count { 273 | float: left; 274 | text-align: left; 275 | } 276 | 277 | .todo-count strong { 278 | font-weight: 300; 279 | } 280 | 281 | .filters { 282 | margin: 0; 283 | padding: 0; 284 | list-style: none; 285 | position: absolute; 286 | right: 0; 287 | left: 0; 288 | } 289 | 290 | .filters li { 291 | display: inline; 292 | } 293 | 294 | .filters li a { 295 | color: inherit; 296 | margin: 3px; 297 | padding: 3px 7px; 298 | text-decoration: none; 299 | border: 1px solid transparent; 300 | border-radius: 3px; 301 | } 302 | 303 | .filters li a.selected, 304 | .filters li a:hover { 305 | border-color: rgba(175, 47, 47, 0.1); 306 | } 307 | 308 | .filters li a.selected { 309 | border-color: rgba(175, 47, 47, 0.2); 310 | } 311 | 312 | .clear-completed, 313 | html .clear-completed:active { 314 | float: right; 315 | position: relative; 316 | line-height: 20px; 317 | text-decoration: none; 318 | cursor: pointer; 319 | position: relative; 320 | } 321 | 322 | .clear-completed:hover { 323 | text-decoration: underline; 324 | } 325 | 326 | .info { 327 | margin: 65px auto 0; 328 | color: #bfbfbf; 329 | font-size: 10px; 330 | text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); 331 | text-align: center; 332 | } 333 | 334 | .info p { 335 | line-height: 1; 336 | } 337 | 338 | .info a { 339 | color: inherit; 340 | text-decoration: none; 341 | font-weight: 400; 342 | } 343 | 344 | .info a:hover { 345 | text-decoration: underline; 346 | } 347 | 348 | /* 349 | Hack to remove background from Mobile Safari. 350 | Can't use it globally since it destroys checkboxes in Firefox 351 | */ 352 | @media screen and (-webkit-min-device-pixel-ratio:0) { 353 | .toggle-all, 354 | .todo-list li .toggle { 355 | background: none; 356 | } 357 | 358 | .todo-list li .toggle { 359 | height: 40px; 360 | } 361 | 362 | .toggle-all { 363 | -webkit-transform: rotate(90deg); 364 | transform: rotate(90deg); 365 | -webkit-appearance: none; 366 | appearance: none; 367 | } 368 | } 369 | 370 | @media (max-width: 430px) { 371 | .footer { 372 | height: 50px; 373 | } 374 | 375 | .filters { 376 | bottom: 10px; 377 | } 378 | } 379 | -------------------------------------------------------------------------------- /examples/todomvc/src/todomvc-app-css/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "todomvc-app-css", 3 | "version": "2.0.1", 4 | "description": "CSS for TodoMVC apps", 5 | "license": "CC-BY-4.0", 6 | "repository": { 7 | "type": "git", 8 | "url": "git+https://github.com/tastejs/todomvc-app-css.git" 9 | }, 10 | "author": { 11 | "name": "Sindre Sorhus", 12 | "email": "sindresorhus@gmail.com", 13 | "url": "sindresorhus.com" 14 | }, 15 | "files": [ 16 | "index.css" 17 | ], 18 | "keywords": [ 19 | "todomvc", 20 | "tastejs", 21 | "app", 22 | "todo", 23 | "template", 24 | "css", 25 | "style", 26 | "stylesheet" 27 | ], 28 | "gitHead": "f1bb1aa9b19888f339055418374a9b3a2d4c6fc5", 29 | "bugs": { 30 | "url": "https://github.com/tastejs/todomvc-app-css/issues" 31 | }, 32 | "homepage": "https://github.com/tastejs/todomvc-app-css", 33 | "_id": "todomvc-app-css@2.0.1", 34 | "scripts": {}, 35 | "_shasum": "f64d50b744a8a83c1151a08055b88f3aa5ccb052", 36 | "_from": "todomvc-app-css@>=2.0.1 <3.0.0", 37 | "_npmVersion": "2.5.1", 38 | "_nodeVersion": "0.12.0", 39 | "_npmUser": { 40 | "name": "sindresorhus", 41 | "email": "sindresorhus@gmail.com" 42 | }, 43 | "maintainers": [ 44 | { 45 | "name": "sindresorhus", 46 | "email": "sindresorhus@gmail.com" 47 | }, 48 | { 49 | "name": "addyosmani", 50 | "email": "addyosmani@gmail.com" 51 | }, 52 | { 53 | "name": "passy", 54 | "email": "phartig@rdrei.net" 55 | }, 56 | { 57 | "name": "stephenplusplus", 58 | "email": "sawchuk@gmail.com" 59 | } 60 | ], 61 | "dist": { 62 | "shasum": "f64d50b744a8a83c1151a08055b88f3aa5ccb052", 63 | "tarball": "http://registry.npmjs.org/todomvc-app-css/-/todomvc-app-css-2.0.1.tgz" 64 | }, 65 | "directories": {}, 66 | "_resolved": "https://registry.npmjs.org/todomvc-app-css/-/todomvc-app-css-2.0.1.tgz", 67 | "readme": "ERROR: No README data found!" 68 | } 69 | -------------------------------------------------------------------------------- /examples/todomvc/src/todomvc-app-css/readme.md: -------------------------------------------------------------------------------- 1 | # todomvc-app-css 2 | 3 | > CSS for TodoMVC apps 4 | 5 | ![](screenshot.png) 6 | 7 | 8 | ## Install 9 | 10 | 11 | ``` 12 | $ npm install --save todomvc-app-css 13 | ``` 14 | 15 | 16 | ## Getting started 17 | 18 | ```html 19 | 20 | ``` 21 | 22 | See the [TodoMVC app template](https://github.com/tastejs/todomvc-app-template). 23 | 24 | 25 | 26 | ## License 27 | 28 | Creative Commons License
    This work by Sindre Sorhus is licensed under a Creative Commons Attribution 4.0 International License. 29 | -------------------------------------------------------------------------------- /examples/todomvc/src/todomvc-common/base.css: -------------------------------------------------------------------------------- 1 | hr { 2 | margin: 20px 0; 3 | border: 0; 4 | border-top: 1px dashed #c5c5c5; 5 | border-bottom: 1px dashed #f7f7f7; 6 | } 7 | 8 | .learn a { 9 | font-weight: normal; 10 | text-decoration: none; 11 | color: #b83f45; 12 | } 13 | 14 | .learn a:hover { 15 | text-decoration: underline; 16 | color: #787e7e; 17 | } 18 | 19 | .learn h3, 20 | .learn h4, 21 | .learn h5 { 22 | margin: 10px 0; 23 | font-weight: 500; 24 | line-height: 1.2; 25 | color: #000; 26 | } 27 | 28 | .learn h3 { 29 | font-size: 24px; 30 | } 31 | 32 | .learn h4 { 33 | font-size: 18px; 34 | } 35 | 36 | .learn h5 { 37 | margin-bottom: 0; 38 | font-size: 14px; 39 | } 40 | 41 | .learn ul { 42 | padding: 0; 43 | margin: 0 0 30px 25px; 44 | } 45 | 46 | .learn li { 47 | line-height: 20px; 48 | } 49 | 50 | .learn p { 51 | font-size: 15px; 52 | font-weight: 300; 53 | line-height: 1.3; 54 | margin-top: 0; 55 | margin-bottom: 0; 56 | } 57 | 58 | #issue-count { 59 | display: none; 60 | } 61 | 62 | .quote { 63 | border: none; 64 | margin: 20px 0 60px 0; 65 | } 66 | 67 | .quote p { 68 | font-style: italic; 69 | } 70 | 71 | .quote p:before { 72 | content: '“'; 73 | font-size: 50px; 74 | opacity: .15; 75 | position: absolute; 76 | top: -20px; 77 | left: 3px; 78 | } 79 | 80 | .quote p:after { 81 | content: '”'; 82 | font-size: 50px; 83 | opacity: .15; 84 | position: absolute; 85 | bottom: -42px; 86 | right: 3px; 87 | } 88 | 89 | .quote footer { 90 | position: absolute; 91 | bottom: -40px; 92 | right: 0; 93 | } 94 | 95 | .quote footer img { 96 | border-radius: 3px; 97 | } 98 | 99 | .quote footer a { 100 | margin-left: 5px; 101 | vertical-align: middle; 102 | } 103 | 104 | .speech-bubble { 105 | position: relative; 106 | padding: 10px; 107 | background: rgba(0, 0, 0, .04); 108 | border-radius: 5px; 109 | } 110 | 111 | .speech-bubble:after { 112 | content: ''; 113 | position: absolute; 114 | top: 100%; 115 | right: 30px; 116 | border: 13px solid transparent; 117 | border-top-color: rgba(0, 0, 0, .04); 118 | } 119 | 120 | .learn-bar > .learn { 121 | position: absolute; 122 | width: 272px; 123 | top: 8px; 124 | left: -300px; 125 | padding: 10px; 126 | border-radius: 5px; 127 | background-color: rgba(255, 255, 255, .6); 128 | transition-property: left; 129 | transition-duration: 500ms; 130 | } 131 | 132 | @media (min-width: 899px) { 133 | .learn-bar { 134 | width: auto; 135 | padding-left: 300px; 136 | } 137 | 138 | .learn-bar > .learn { 139 | left: 8px; 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /examples/todomvc/src/todomvc-common/base.js: -------------------------------------------------------------------------------- 1 | /* global _ */ 2 | (function () { 3 | 'use strict'; 4 | 5 | /* jshint ignore:start */ 6 | // Underscore's Template Module 7 | // Courtesy of underscorejs.org 8 | var _ = (function (_) { 9 | _.defaults = function (object) { 10 | if (!object) { 11 | return object; 12 | } 13 | for (var argsIndex = 1, argsLength = arguments.length; argsIndex < argsLength; argsIndex++) { 14 | var iterable = arguments[argsIndex]; 15 | if (iterable) { 16 | for (var key in iterable) { 17 | if (object[key] == null) { 18 | object[key] = iterable[key]; 19 | } 20 | } 21 | } 22 | } 23 | return object; 24 | } 25 | 26 | // By default, Underscore uses ERB-style template delimiters, change the 27 | // following template settings to use alternative delimiters. 28 | _.templateSettings = { 29 | evaluate : /<%([\s\S]+?)%>/g, 30 | interpolate : /<%=([\s\S]+?)%>/g, 31 | escape : /<%-([\s\S]+?)%>/g 32 | }; 33 | 34 | // When customizing `templateSettings`, if you don't want to define an 35 | // interpolation, evaluation or escaping regex, we need one that is 36 | // guaranteed not to match. 37 | var noMatch = /(.)^/; 38 | 39 | // Certain characters need to be escaped so that they can be put into a 40 | // string literal. 41 | var escapes = { 42 | "'": "'", 43 | '\\': '\\', 44 | '\r': 'r', 45 | '\n': 'n', 46 | '\t': 't', 47 | '\u2028': 'u2028', 48 | '\u2029': 'u2029' 49 | }; 50 | 51 | var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g; 52 | 53 | // JavaScript micro-templating, similar to John Resig's implementation. 54 | // Underscore templating handles arbitrary delimiters, preserves whitespace, 55 | // and correctly escapes quotes within interpolated code. 56 | _.template = function(text, data, settings) { 57 | var render; 58 | settings = _.defaults({}, settings, _.templateSettings); 59 | 60 | // Combine delimiters into one regular expression via alternation. 61 | var matcher = new RegExp([ 62 | (settings.escape || noMatch).source, 63 | (settings.interpolate || noMatch).source, 64 | (settings.evaluate || noMatch).source 65 | ].join('|') + '|$', 'g'); 66 | 67 | // Compile the template source, escaping string literals appropriately. 68 | var index = 0; 69 | var source = "__p+='"; 70 | text.replace(matcher, function(match, escape, interpolate, evaluate, offset) { 71 | source += text.slice(index, offset) 72 | .replace(escaper, function(match) { return '\\' + escapes[match]; }); 73 | 74 | if (escape) { 75 | source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'"; 76 | } 77 | if (interpolate) { 78 | source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'"; 79 | } 80 | if (evaluate) { 81 | source += "';\n" + evaluate + "\n__p+='"; 82 | } 83 | index = offset + match.length; 84 | return match; 85 | }); 86 | source += "';\n"; 87 | 88 | // If a variable is not specified, place data values in local scope. 89 | if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n'; 90 | 91 | source = "var __t,__p='',__j=Array.prototype.join," + 92 | "print=function(){__p+=__j.call(arguments,'');};\n" + 93 | source + "return __p;\n"; 94 | 95 | try { 96 | render = new Function(settings.variable || 'obj', '_', source); 97 | } catch (e) { 98 | e.source = source; 99 | throw e; 100 | } 101 | 102 | if (data) return render(data, _); 103 | var template = function(data) { 104 | return render.call(this, data, _); 105 | }; 106 | 107 | // Provide the compiled function source as a convenience for precompilation. 108 | template.source = 'function(' + (settings.variable || 'obj') + '){\n' + source + '}'; 109 | 110 | return template; 111 | }; 112 | 113 | return _; 114 | })({}); 115 | 116 | if (location.hostname === 'todomvc.com') { 117 | (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ 118 | (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), 119 | m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) 120 | })(window,document,'script','https://www.google-analytics.com/analytics.js','ga'); 121 | ga('create', 'UA-31081062-1', 'auto'); 122 | ga('send', 'pageview'); 123 | } 124 | /* jshint ignore:end */ 125 | 126 | function redirect() { 127 | if (location.hostname === 'tastejs.github.io') { 128 | location.href = location.href.replace('tastejs.github.io/todomvc', 'todomvc.com'); 129 | } 130 | } 131 | 132 | function findRoot() { 133 | var base = location.href.indexOf('examples/'); 134 | return location.href.substr(0, base); 135 | } 136 | 137 | function getFile(file, callback) { 138 | if (!location.host) { 139 | return console.info('Miss the info bar? Run TodoMVC from a server to avoid a cross-origin error.'); 140 | } 141 | 142 | var xhr = new XMLHttpRequest(); 143 | 144 | xhr.open('GET', findRoot() + file, true); 145 | xhr.send(); 146 | 147 | xhr.onload = function () { 148 | if (xhr.status === 200 && callback) { 149 | callback(xhr.responseText); 150 | } 151 | }; 152 | } 153 | 154 | function Learn(learnJSON, config) { 155 | if (!(this instanceof Learn)) { 156 | return new Learn(learnJSON, config); 157 | } 158 | 159 | var template, framework; 160 | 161 | if (typeof learnJSON !== 'object') { 162 | try { 163 | learnJSON = JSON.parse(learnJSON); 164 | } catch (e) { 165 | return; 166 | } 167 | } 168 | 169 | if (config) { 170 | template = config.template; 171 | framework = config.framework; 172 | } 173 | 174 | if (!template && learnJSON.templates) { 175 | template = learnJSON.templates.todomvc; 176 | } 177 | 178 | if (!framework && document.querySelector('[data-framework]')) { 179 | framework = document.querySelector('[data-framework]').dataset.framework; 180 | } 181 | 182 | this.template = template; 183 | 184 | if (learnJSON.backend) { 185 | this.frameworkJSON = learnJSON.backend; 186 | this.frameworkJSON.issueLabel = framework; 187 | this.append({ 188 | backend: true 189 | }); 190 | } else if (learnJSON[framework]) { 191 | this.frameworkJSON = learnJSON[framework]; 192 | this.frameworkJSON.issueLabel = framework; 193 | this.append(); 194 | } 195 | 196 | this.fetchIssueCount(); 197 | } 198 | 199 | Learn.prototype.append = function (opts) { 200 | var aside = document.createElement('aside'); 201 | aside.innerHTML = _.template(this.template, this.frameworkJSON); 202 | aside.className = 'learn'; 203 | 204 | if (opts && opts.backend) { 205 | // Remove demo link 206 | var sourceLinks = aside.querySelector('.source-links'); 207 | var heading = sourceLinks.firstElementChild; 208 | var sourceLink = sourceLinks.lastElementChild; 209 | // Correct link path 210 | var href = sourceLink.getAttribute('href'); 211 | sourceLink.setAttribute('href', href.substr(href.lastIndexOf('http'))); 212 | sourceLinks.innerHTML = heading.outerHTML + sourceLink.outerHTML; 213 | } else { 214 | // Localize demo links 215 | var demoLinks = aside.querySelectorAll('.demo-link'); 216 | Array.prototype.forEach.call(demoLinks, function (demoLink) { 217 | if (demoLink.getAttribute('href').substr(0, 4) !== 'http') { 218 | demoLink.setAttribute('href', findRoot() + demoLink.getAttribute('href')); 219 | } 220 | }); 221 | } 222 | 223 | document.body.className = (document.body.className + ' learn-bar').trim(); 224 | document.body.insertAdjacentHTML('afterBegin', aside.outerHTML); 225 | }; 226 | 227 | Learn.prototype.fetchIssueCount = function () { 228 | var issueLink = document.getElementById('issue-count-link'); 229 | if (issueLink) { 230 | var url = issueLink.href.replace('https://github.com', 'https://api.github.com/repos'); 231 | var xhr = new XMLHttpRequest(); 232 | xhr.open('GET', url, true); 233 | xhr.onload = function (e) { 234 | var parsedResponse = JSON.parse(e.target.responseText); 235 | if (parsedResponse instanceof Array) { 236 | var count = parsedResponse.length; 237 | if (count !== 0) { 238 | issueLink.innerHTML = 'This app has ' + count + ' open issues'; 239 | document.getElementById('issue-count').style.display = 'inline'; 240 | } 241 | } 242 | }; 243 | xhr.send(); 244 | } 245 | }; 246 | 247 | redirect(); 248 | //getFile('learn.json', Learn); 249 | })(); 250 | -------------------------------------------------------------------------------- /examples/todomvc/src/todomvc-common/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "todomvc-common", 3 | "version": "1.0.2", 4 | "description": "Common TodoMVC utilities used by our apps", 5 | "license": "MIT", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/tastejs/todomvc-common" 9 | }, 10 | "author": { 11 | "name": "TasteJS" 12 | }, 13 | "main": "base.js", 14 | "files": [ 15 | "base.js", 16 | "base.css" 17 | ], 18 | "keywords": [ 19 | "todomvc", 20 | "tastejs", 21 | "util", 22 | "utilities" 23 | ], 24 | "gitHead": "e82d0c79e01687ce7407df786cc784ad82166cb3", 25 | "bugs": { 26 | "url": "https://github.com/tastejs/todomvc-common/issues" 27 | }, 28 | "homepage": "https://github.com/tastejs/todomvc-common", 29 | "_id": "todomvc-common@1.0.2", 30 | "scripts": {}, 31 | "_shasum": "eb3ab61281ac74809f5869c917c7b08bc84234e0", 32 | "_from": "todomvc-common@>=1.0.1 <2.0.0", 33 | "_npmVersion": "2.7.4", 34 | "_nodeVersion": "0.12.2", 35 | "_npmUser": { 36 | "name": "sindresorhus", 37 | "email": "sindresorhus@gmail.com" 38 | }, 39 | "dist": { 40 | "shasum": "eb3ab61281ac74809f5869c917c7b08bc84234e0", 41 | "tarball": "http://registry.npmjs.org/todomvc-common/-/todomvc-common-1.0.2.tgz" 42 | }, 43 | "maintainers": [ 44 | { 45 | "name": "sindresorhus", 46 | "email": "sindresorhus@gmail.com" 47 | }, 48 | { 49 | "name": "addyosmani", 50 | "email": "addyosmani@gmail.com" 51 | }, 52 | { 53 | "name": "passy", 54 | "email": "phartig@rdrei.net" 55 | }, 56 | { 57 | "name": "stephenplusplus", 58 | "email": "sawchuk@gmail.com" 59 | } 60 | ], 61 | "directories": {}, 62 | "_resolved": "https://registry.npmjs.org/todomvc-common/-/todomvc-common-1.0.2.tgz" 63 | } 64 | -------------------------------------------------------------------------------- /examples/todomvc/src/todomvc-common/readme.md: -------------------------------------------------------------------------------- 1 | # todomvc-common 2 | 3 | > Common TodoMVC utilities used by our apps 4 | 5 | 6 | ## Install 7 | 8 | ``` 9 | $ npm install --save todomvc-common 10 | ``` 11 | 12 | 13 | ## License 14 | 15 | MIT © [TasteJS](http://tastejs.com) 16 | -------------------------------------------------------------------------------- /examples/todomvc/todomvc-app-css/index.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | margin: 0; 4 | padding: 0; 5 | } 6 | 7 | button { 8 | margin: 0; 9 | padding: 0; 10 | border: 0; 11 | background: none; 12 | font-size: 100%; 13 | vertical-align: baseline; 14 | font-family: inherit; 15 | font-weight: inherit; 16 | color: inherit; 17 | -webkit-appearance: none; 18 | appearance: none; 19 | -webkit-font-smoothing: antialiased; 20 | -moz-font-smoothing: antialiased; 21 | font-smoothing: antialiased; 22 | } 23 | 24 | body { 25 | font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif; 26 | line-height: 1.4em; 27 | background: #f5f5f5; 28 | color: #4d4d4d; 29 | min-width: 230px; 30 | max-width: 550px; 31 | margin: 0 auto; 32 | -webkit-font-smoothing: antialiased; 33 | -moz-font-smoothing: antialiased; 34 | font-smoothing: antialiased; 35 | font-weight: 300; 36 | } 37 | 38 | button, 39 | input[type="checkbox"] { 40 | outline: none; 41 | } 42 | 43 | .hidden { 44 | display: none; 45 | } 46 | 47 | .todoapp { 48 | background: #fff; 49 | margin: 130px 0 40px 0; 50 | position: relative; 51 | box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 52 | 0 25px 50px 0 rgba(0, 0, 0, 0.1); 53 | } 54 | 55 | .todoapp input::-webkit-input-placeholder { 56 | font-style: italic; 57 | font-weight: 300; 58 | color: #e6e6e6; 59 | } 60 | 61 | .todoapp input::-moz-placeholder { 62 | font-style: italic; 63 | font-weight: 300; 64 | color: #e6e6e6; 65 | } 66 | 67 | .todoapp input::input-placeholder { 68 | font-style: italic; 69 | font-weight: 300; 70 | color: #e6e6e6; 71 | } 72 | 73 | .todoapp h1 { 74 | position: absolute; 75 | top: -155px; 76 | width: 100%; 77 | font-size: 100px; 78 | font-weight: 100; 79 | text-align: center; 80 | color: rgba(175, 47, 47, 0.15); 81 | -webkit-text-rendering: optimizeLegibility; 82 | -moz-text-rendering: optimizeLegibility; 83 | text-rendering: optimizeLegibility; 84 | } 85 | 86 | .new-todo, 87 | .edit { 88 | position: relative; 89 | margin: 0; 90 | width: 100%; 91 | font-size: 24px; 92 | font-family: inherit; 93 | font-weight: inherit; 94 | line-height: 1.4em; 95 | border: 0; 96 | outline: none; 97 | color: inherit; 98 | padding: 6px; 99 | border: 1px solid #999; 100 | box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2); 101 | box-sizing: border-box; 102 | -webkit-font-smoothing: antialiased; 103 | -moz-font-smoothing: antialiased; 104 | font-smoothing: antialiased; 105 | } 106 | 107 | .new-todo { 108 | padding: 16px 16px 16px 60px; 109 | border: none; 110 | background: rgba(0, 0, 0, 0.003); 111 | box-shadow: inset 0 -2px 1px rgba(0,0,0,0.03); 112 | } 113 | 114 | .main { 115 | position: relative; 116 | z-index: 2; 117 | border-top: 1px solid #e6e6e6; 118 | } 119 | 120 | label[for='toggle-all'] { 121 | display: none; 122 | } 123 | 124 | .toggle-all { 125 | position: absolute; 126 | top: -55px; 127 | left: -12px; 128 | width: 60px; 129 | height: 34px; 130 | text-align: center; 131 | border: none; /* Mobile Safari */ 132 | } 133 | 134 | .toggle-all:before { 135 | content: '❯'; 136 | font-size: 22px; 137 | color: #e6e6e6; 138 | padding: 10px 27px 10px 27px; 139 | } 140 | 141 | .toggle-all:checked:before { 142 | color: #737373; 143 | } 144 | 145 | .todo-list { 146 | margin: 0; 147 | padding: 0; 148 | list-style: none; 149 | } 150 | 151 | .todo-list li { 152 | position: relative; 153 | font-size: 24px; 154 | border-bottom: 1px solid #ededed; 155 | } 156 | 157 | .todo-list li:last-child { 158 | border-bottom: none; 159 | } 160 | 161 | .todo-list li.editing { 162 | border-bottom: none; 163 | padding: 0; 164 | } 165 | 166 | .todo-list li.editing .edit { 167 | display: block; 168 | width: 506px; 169 | padding: 13px 17px 12px 17px; 170 | margin: 0 0 0 43px; 171 | } 172 | 173 | .todo-list li.editing .view { 174 | display: none; 175 | } 176 | 177 | .todo-list li .toggle { 178 | text-align: center; 179 | width: 40px; 180 | /* auto, since non-WebKit browsers doesn't support input styling */ 181 | height: auto; 182 | position: absolute; 183 | top: 0; 184 | bottom: 0; 185 | margin: auto 0; 186 | border: none; /* Mobile Safari */ 187 | -webkit-appearance: none; 188 | appearance: none; 189 | } 190 | 191 | .todo-list li .toggle:after { 192 | content: url('data:image/svg+xml;utf8,'); 193 | } 194 | 195 | .todo-list li .toggle:checked:after { 196 | content: url('data:image/svg+xml;utf8,'); 197 | } 198 | 199 | .todo-list li label { 200 | white-space: pre; 201 | word-break: break-word; 202 | padding: 15px 60px 15px 15px; 203 | margin-left: 45px; 204 | display: block; 205 | line-height: 1.2; 206 | transition: color 0.4s; 207 | } 208 | 209 | .todo-list li.completed label { 210 | color: #d9d9d9; 211 | text-decoration: line-through; 212 | } 213 | 214 | .todo-list li .destroy { 215 | display: none; 216 | position: absolute; 217 | top: 0; 218 | right: 10px; 219 | bottom: 0; 220 | width: 40px; 221 | height: 40px; 222 | margin: auto 0; 223 | font-size: 30px; 224 | color: #cc9a9a; 225 | margin-bottom: 11px; 226 | transition: color 0.2s ease-out; 227 | } 228 | 229 | .todo-list li .destroy:hover { 230 | color: #af5b5e; 231 | } 232 | 233 | .todo-list li .destroy:after { 234 | content: '×'; 235 | } 236 | 237 | .todo-list li:hover .destroy { 238 | display: block; 239 | } 240 | 241 | .todo-list li .edit { 242 | display: none; 243 | } 244 | 245 | .todo-list li.editing:last-child { 246 | margin-bottom: -1px; 247 | } 248 | 249 | .footer { 250 | color: #777; 251 | padding: 10px 15px; 252 | height: 20px; 253 | text-align: center; 254 | border-top: 1px solid #e6e6e6; 255 | } 256 | 257 | .footer:before { 258 | content: ''; 259 | position: absolute; 260 | right: 0; 261 | bottom: 0; 262 | left: 0; 263 | height: 50px; 264 | overflow: hidden; 265 | box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2), 266 | 0 8px 0 -3px #f6f6f6, 267 | 0 9px 1px -3px rgba(0, 0, 0, 0.2), 268 | 0 16px 0 -6px #f6f6f6, 269 | 0 17px 2px -6px rgba(0, 0, 0, 0.2); 270 | } 271 | 272 | .todo-count { 273 | float: left; 274 | text-align: left; 275 | } 276 | 277 | .todo-count strong { 278 | font-weight: 300; 279 | } 280 | 281 | .filters { 282 | margin: 0; 283 | padding: 0; 284 | list-style: none; 285 | position: absolute; 286 | right: 0; 287 | left: 0; 288 | } 289 | 290 | .filters li { 291 | display: inline; 292 | } 293 | 294 | .filters li a { 295 | color: inherit; 296 | margin: 3px; 297 | padding: 3px 7px; 298 | text-decoration: none; 299 | border: 1px solid transparent; 300 | border-radius: 3px; 301 | } 302 | 303 | .filters li a.selected, 304 | .filters li a:hover { 305 | border-color: rgba(175, 47, 47, 0.1); 306 | } 307 | 308 | .filters li a.selected { 309 | border-color: rgba(175, 47, 47, 0.2); 310 | } 311 | 312 | .clear-completed, 313 | html .clear-completed:active { 314 | float: right; 315 | position: relative; 316 | line-height: 20px; 317 | text-decoration: none; 318 | cursor: pointer; 319 | position: relative; 320 | } 321 | 322 | .clear-completed:hover { 323 | text-decoration: underline; 324 | } 325 | 326 | .info { 327 | margin: 65px auto 0; 328 | color: #bfbfbf; 329 | font-size: 10px; 330 | text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); 331 | text-align: center; 332 | } 333 | 334 | .info p { 335 | line-height: 1; 336 | } 337 | 338 | .info a { 339 | color: inherit; 340 | text-decoration: none; 341 | font-weight: 400; 342 | } 343 | 344 | .info a:hover { 345 | text-decoration: underline; 346 | } 347 | 348 | /* 349 | Hack to remove background from Mobile Safari. 350 | Can't use it globally since it destroys checkboxes in Firefox 351 | */ 352 | @media screen and (-webkit-min-device-pixel-ratio:0) { 353 | .toggle-all, 354 | .todo-list li .toggle { 355 | background: none; 356 | } 357 | 358 | .todo-list li .toggle { 359 | height: 40px; 360 | } 361 | 362 | .toggle-all { 363 | -webkit-transform: rotate(90deg); 364 | transform: rotate(90deg); 365 | -webkit-appearance: none; 366 | appearance: none; 367 | } 368 | } 369 | 370 | @media (max-width: 430px) { 371 | .footer { 372 | height: 50px; 373 | } 374 | 375 | .filters { 376 | bottom: 10px; 377 | } 378 | } 379 | -------------------------------------------------------------------------------- /examples/todomvc/todomvc-app-css/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "todomvc-app-css", 3 | "version": "2.0.1", 4 | "description": "CSS for TodoMVC apps", 5 | "license": "CC-BY-4.0", 6 | "repository": { 7 | "type": "git", 8 | "url": "git+https://github.com/tastejs/todomvc-app-css.git" 9 | }, 10 | "author": { 11 | "name": "Sindre Sorhus", 12 | "email": "sindresorhus@gmail.com", 13 | "url": "sindresorhus.com" 14 | }, 15 | "files": [ 16 | "index.css" 17 | ], 18 | "keywords": [ 19 | "todomvc", 20 | "tastejs", 21 | "app", 22 | "todo", 23 | "template", 24 | "css", 25 | "style", 26 | "stylesheet" 27 | ], 28 | "gitHead": "f1bb1aa9b19888f339055418374a9b3a2d4c6fc5", 29 | "bugs": { 30 | "url": "https://github.com/tastejs/todomvc-app-css/issues" 31 | }, 32 | "homepage": "https://github.com/tastejs/todomvc-app-css", 33 | "_id": "todomvc-app-css@2.0.1", 34 | "scripts": {}, 35 | "_shasum": "f64d50b744a8a83c1151a08055b88f3aa5ccb052", 36 | "_from": "todomvc-app-css@>=2.0.1 <3.0.0", 37 | "_npmVersion": "2.5.1", 38 | "_nodeVersion": "0.12.0", 39 | "_npmUser": { 40 | "name": "sindresorhus", 41 | "email": "sindresorhus@gmail.com" 42 | }, 43 | "maintainers": [ 44 | { 45 | "name": "sindresorhus", 46 | "email": "sindresorhus@gmail.com" 47 | }, 48 | { 49 | "name": "addyosmani", 50 | "email": "addyosmani@gmail.com" 51 | }, 52 | { 53 | "name": "passy", 54 | "email": "phartig@rdrei.net" 55 | }, 56 | { 57 | "name": "stephenplusplus", 58 | "email": "sawchuk@gmail.com" 59 | } 60 | ], 61 | "dist": { 62 | "shasum": "f64d50b744a8a83c1151a08055b88f3aa5ccb052", 63 | "tarball": "http://registry.npmjs.org/todomvc-app-css/-/todomvc-app-css-2.0.1.tgz" 64 | }, 65 | "directories": {}, 66 | "_resolved": "https://registry.npmjs.org/todomvc-app-css/-/todomvc-app-css-2.0.1.tgz", 67 | "readme": "ERROR: No README data found!" 68 | } 69 | -------------------------------------------------------------------------------- /examples/todomvc/todomvc-app-css/readme.md: -------------------------------------------------------------------------------- 1 | # todomvc-app-css 2 | 3 | > CSS for TodoMVC apps 4 | 5 | ![](screenshot.png) 6 | 7 | 8 | ## Install 9 | 10 | 11 | ``` 12 | $ npm install --save todomvc-app-css 13 | ``` 14 | 15 | 16 | ## Getting started 17 | 18 | ```html 19 | 20 | ``` 21 | 22 | See the [TodoMVC app template](https://github.com/tastejs/todomvc-app-template). 23 | 24 | 25 | 26 | ## License 27 | 28 | Creative Commons License
    This work by Sindre Sorhus is licensed under a Creative Commons Attribution 4.0 International License. 29 | -------------------------------------------------------------------------------- /examples/todomvc/todomvc-common/base.css: -------------------------------------------------------------------------------- 1 | hr { 2 | margin: 20px 0; 3 | border: 0; 4 | border-top: 1px dashed #c5c5c5; 5 | border-bottom: 1px dashed #f7f7f7; 6 | } 7 | 8 | .learn a { 9 | font-weight: normal; 10 | text-decoration: none; 11 | color: #b83f45; 12 | } 13 | 14 | .learn a:hover { 15 | text-decoration: underline; 16 | color: #787e7e; 17 | } 18 | 19 | .learn h3, 20 | .learn h4, 21 | .learn h5 { 22 | margin: 10px 0; 23 | font-weight: 500; 24 | line-height: 1.2; 25 | color: #000; 26 | } 27 | 28 | .learn h3 { 29 | font-size: 24px; 30 | } 31 | 32 | .learn h4 { 33 | font-size: 18px; 34 | } 35 | 36 | .learn h5 { 37 | margin-bottom: 0; 38 | font-size: 14px; 39 | } 40 | 41 | .learn ul { 42 | padding: 0; 43 | margin: 0 0 30px 25px; 44 | } 45 | 46 | .learn li { 47 | line-height: 20px; 48 | } 49 | 50 | .learn p { 51 | font-size: 15px; 52 | font-weight: 300; 53 | line-height: 1.3; 54 | margin-top: 0; 55 | margin-bottom: 0; 56 | } 57 | 58 | #issue-count { 59 | display: none; 60 | } 61 | 62 | .quote { 63 | border: none; 64 | margin: 20px 0 60px 0; 65 | } 66 | 67 | .quote p { 68 | font-style: italic; 69 | } 70 | 71 | .quote p:before { 72 | content: '“'; 73 | font-size: 50px; 74 | opacity: .15; 75 | position: absolute; 76 | top: -20px; 77 | left: 3px; 78 | } 79 | 80 | .quote p:after { 81 | content: '”'; 82 | font-size: 50px; 83 | opacity: .15; 84 | position: absolute; 85 | bottom: -42px; 86 | right: 3px; 87 | } 88 | 89 | .quote footer { 90 | position: absolute; 91 | bottom: -40px; 92 | right: 0; 93 | } 94 | 95 | .quote footer img { 96 | border-radius: 3px; 97 | } 98 | 99 | .quote footer a { 100 | margin-left: 5px; 101 | vertical-align: middle; 102 | } 103 | 104 | .speech-bubble { 105 | position: relative; 106 | padding: 10px; 107 | background: rgba(0, 0, 0, .04); 108 | border-radius: 5px; 109 | } 110 | 111 | .speech-bubble:after { 112 | content: ''; 113 | position: absolute; 114 | top: 100%; 115 | right: 30px; 116 | border: 13px solid transparent; 117 | border-top-color: rgba(0, 0, 0, .04); 118 | } 119 | 120 | .learn-bar > .learn { 121 | position: absolute; 122 | width: 272px; 123 | top: 8px; 124 | left: -300px; 125 | padding: 10px; 126 | border-radius: 5px; 127 | background-color: rgba(255, 255, 255, .6); 128 | transition-property: left; 129 | transition-duration: 500ms; 130 | } 131 | 132 | @media (min-width: 899px) { 133 | .learn-bar { 134 | width: auto; 135 | padding-left: 300px; 136 | } 137 | 138 | .learn-bar > .learn { 139 | left: 8px; 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /examples/todomvc/todomvc-common/base.js: -------------------------------------------------------------------------------- 1 | /* global _ */ 2 | (function () { 3 | 'use strict'; 4 | 5 | /* jshint ignore:start */ 6 | // Underscore's Template Module 7 | // Courtesy of underscorejs.org 8 | var _ = (function (_) { 9 | _.defaults = function (object) { 10 | if (!object) { 11 | return object; 12 | } 13 | for (var argsIndex = 1, argsLength = arguments.length; argsIndex < argsLength; argsIndex++) { 14 | var iterable = arguments[argsIndex]; 15 | if (iterable) { 16 | for (var key in iterable) { 17 | if (object[key] == null) { 18 | object[key] = iterable[key]; 19 | } 20 | } 21 | } 22 | } 23 | return object; 24 | } 25 | 26 | // By default, Underscore uses ERB-style template delimiters, change the 27 | // following template settings to use alternative delimiters. 28 | _.templateSettings = { 29 | evaluate : /<%([\s\S]+?)%>/g, 30 | interpolate : /<%=([\s\S]+?)%>/g, 31 | escape : /<%-([\s\S]+?)%>/g 32 | }; 33 | 34 | // When customizing `templateSettings`, if you don't want to define an 35 | // interpolation, evaluation or escaping regex, we need one that is 36 | // guaranteed not to match. 37 | var noMatch = /(.)^/; 38 | 39 | // Certain characters need to be escaped so that they can be put into a 40 | // string literal. 41 | var escapes = { 42 | "'": "'", 43 | '\\': '\\', 44 | '\r': 'r', 45 | '\n': 'n', 46 | '\t': 't', 47 | '\u2028': 'u2028', 48 | '\u2029': 'u2029' 49 | }; 50 | 51 | var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g; 52 | 53 | // JavaScript micro-templating, similar to John Resig's implementation. 54 | // Underscore templating handles arbitrary delimiters, preserves whitespace, 55 | // and correctly escapes quotes within interpolated code. 56 | _.template = function(text, data, settings) { 57 | var render; 58 | settings = _.defaults({}, settings, _.templateSettings); 59 | 60 | // Combine delimiters into one regular expression via alternation. 61 | var matcher = new RegExp([ 62 | (settings.escape || noMatch).source, 63 | (settings.interpolate || noMatch).source, 64 | (settings.evaluate || noMatch).source 65 | ].join('|') + '|$', 'g'); 66 | 67 | // Compile the template source, escaping string literals appropriately. 68 | var index = 0; 69 | var source = "__p+='"; 70 | text.replace(matcher, function(match, escape, interpolate, evaluate, offset) { 71 | source += text.slice(index, offset) 72 | .replace(escaper, function(match) { return '\\' + escapes[match]; }); 73 | 74 | if (escape) { 75 | source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'"; 76 | } 77 | if (interpolate) { 78 | source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'"; 79 | } 80 | if (evaluate) { 81 | source += "';\n" + evaluate + "\n__p+='"; 82 | } 83 | index = offset + match.length; 84 | return match; 85 | }); 86 | source += "';\n"; 87 | 88 | // If a variable is not specified, place data values in local scope. 89 | if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n'; 90 | 91 | source = "var __t,__p='',__j=Array.prototype.join," + 92 | "print=function(){__p+=__j.call(arguments,'');};\n" + 93 | source + "return __p;\n"; 94 | 95 | try { 96 | render = new Function(settings.variable || 'obj', '_', source); 97 | } catch (e) { 98 | e.source = source; 99 | throw e; 100 | } 101 | 102 | if (data) return render(data, _); 103 | var template = function(data) { 104 | return render.call(this, data, _); 105 | }; 106 | 107 | // Provide the compiled function source as a convenience for precompilation. 108 | template.source = 'function(' + (settings.variable || 'obj') + '){\n' + source + '}'; 109 | 110 | return template; 111 | }; 112 | 113 | return _; 114 | })({}); 115 | 116 | if (location.hostname === 'todomvc.com') { 117 | (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ 118 | (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), 119 | m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) 120 | })(window,document,'script','https://www.google-analytics.com/analytics.js','ga'); 121 | ga('create', 'UA-31081062-1', 'auto'); 122 | ga('send', 'pageview'); 123 | } 124 | /* jshint ignore:end */ 125 | 126 | function redirect() { 127 | if (location.hostname === 'tastejs.github.io') { 128 | location.href = location.href.replace('tastejs.github.io/todomvc', 'todomvc.com'); 129 | } 130 | } 131 | 132 | function findRoot() { 133 | var base = location.href.indexOf('examples/'); 134 | return location.href.substr(0, base); 135 | } 136 | 137 | function getFile(file, callback) { 138 | if (!location.host) { 139 | return console.info('Miss the info bar? Run TodoMVC from a server to avoid a cross-origin error.'); 140 | } 141 | 142 | var xhr = new XMLHttpRequest(); 143 | 144 | xhr.open('GET', findRoot() + file, true); 145 | xhr.send(); 146 | 147 | xhr.onload = function () { 148 | if (xhr.status === 200 && callback) { 149 | callback(xhr.responseText); 150 | } 151 | }; 152 | } 153 | 154 | function Learn(learnJSON, config) { 155 | if (!(this instanceof Learn)) { 156 | return new Learn(learnJSON, config); 157 | } 158 | 159 | var template, framework; 160 | 161 | if (typeof learnJSON !== 'object') { 162 | try { 163 | learnJSON = JSON.parse(learnJSON); 164 | } catch (e) { 165 | return; 166 | } 167 | } 168 | 169 | if (config) { 170 | template = config.template; 171 | framework = config.framework; 172 | } 173 | 174 | if (!template && learnJSON.templates) { 175 | template = learnJSON.templates.todomvc; 176 | } 177 | 178 | if (!framework && document.querySelector('[data-framework]')) { 179 | framework = document.querySelector('[data-framework]').dataset.framework; 180 | } 181 | 182 | this.template = template; 183 | 184 | if (learnJSON.backend) { 185 | this.frameworkJSON = learnJSON.backend; 186 | this.frameworkJSON.issueLabel = framework; 187 | this.append({ 188 | backend: true 189 | }); 190 | } else if (learnJSON[framework]) { 191 | this.frameworkJSON = learnJSON[framework]; 192 | this.frameworkJSON.issueLabel = framework; 193 | this.append(); 194 | } 195 | 196 | this.fetchIssueCount(); 197 | } 198 | 199 | Learn.prototype.append = function (opts) { 200 | var aside = document.createElement('aside'); 201 | aside.innerHTML = _.template(this.template, this.frameworkJSON); 202 | aside.className = 'learn'; 203 | 204 | if (opts && opts.backend) { 205 | // Remove demo link 206 | var sourceLinks = aside.querySelector('.source-links'); 207 | var heading = sourceLinks.firstElementChild; 208 | var sourceLink = sourceLinks.lastElementChild; 209 | // Correct link path 210 | var href = sourceLink.getAttribute('href'); 211 | sourceLink.setAttribute('href', href.substr(href.lastIndexOf('http'))); 212 | sourceLinks.innerHTML = heading.outerHTML + sourceLink.outerHTML; 213 | } else { 214 | // Localize demo links 215 | var demoLinks = aside.querySelectorAll('.demo-link'); 216 | Array.prototype.forEach.call(demoLinks, function (demoLink) { 217 | if (demoLink.getAttribute('href').substr(0, 4) !== 'http') { 218 | demoLink.setAttribute('href', findRoot() + demoLink.getAttribute('href')); 219 | } 220 | }); 221 | } 222 | 223 | document.body.className = (document.body.className + ' learn-bar').trim(); 224 | document.body.insertAdjacentHTML('afterBegin', aside.outerHTML); 225 | }; 226 | 227 | Learn.prototype.fetchIssueCount = function () { 228 | var issueLink = document.getElementById('issue-count-link'); 229 | if (issueLink) { 230 | var url = issueLink.href.replace('https://github.com', 'https://api.github.com/repos'); 231 | var xhr = new XMLHttpRequest(); 232 | xhr.open('GET', url, true); 233 | xhr.onload = function (e) { 234 | var parsedResponse = JSON.parse(e.target.responseText); 235 | if (parsedResponse instanceof Array) { 236 | var count = parsedResponse.length; 237 | if (count !== 0) { 238 | issueLink.innerHTML = 'This app has ' + count + ' open issues'; 239 | document.getElementById('issue-count').style.display = 'inline'; 240 | } 241 | } 242 | }; 243 | xhr.send(); 244 | } 245 | }; 246 | 247 | redirect(); 248 | //getFile('learn.json', Learn); 249 | })(); 250 | -------------------------------------------------------------------------------- /examples/todomvc/todomvc-common/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "todomvc-common", 3 | "version": "1.0.2", 4 | "description": "Common TodoMVC utilities used by our apps", 5 | "license": "MIT", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/tastejs/todomvc-common" 9 | }, 10 | "author": { 11 | "name": "TasteJS" 12 | }, 13 | "main": "base.js", 14 | "files": [ 15 | "base.js", 16 | "base.css" 17 | ], 18 | "keywords": [ 19 | "todomvc", 20 | "tastejs", 21 | "util", 22 | "utilities" 23 | ], 24 | "gitHead": "e82d0c79e01687ce7407df786cc784ad82166cb3", 25 | "bugs": { 26 | "url": "https://github.com/tastejs/todomvc-common/issues" 27 | }, 28 | "homepage": "https://github.com/tastejs/todomvc-common", 29 | "_id": "todomvc-common@1.0.2", 30 | "scripts": {}, 31 | "_shasum": "eb3ab61281ac74809f5869c917c7b08bc84234e0", 32 | "_from": "todomvc-common@>=1.0.1 <2.0.0", 33 | "_npmVersion": "2.7.4", 34 | "_nodeVersion": "0.12.2", 35 | "_npmUser": { 36 | "name": "sindresorhus", 37 | "email": "sindresorhus@gmail.com" 38 | }, 39 | "dist": { 40 | "shasum": "eb3ab61281ac74809f5869c917c7b08bc84234e0", 41 | "tarball": "http://registry.npmjs.org/todomvc-common/-/todomvc-common-1.0.2.tgz" 42 | }, 43 | "maintainers": [ 44 | { 45 | "name": "sindresorhus", 46 | "email": "sindresorhus@gmail.com" 47 | }, 48 | { 49 | "name": "addyosmani", 50 | "email": "addyosmani@gmail.com" 51 | }, 52 | { 53 | "name": "passy", 54 | "email": "phartig@rdrei.net" 55 | }, 56 | { 57 | "name": "stephenplusplus", 58 | "email": "sawchuk@gmail.com" 59 | } 60 | ], 61 | "directories": {}, 62 | "_resolved": "https://registry.npmjs.org/todomvc-common/-/todomvc-common-1.0.2.tgz" 63 | } 64 | -------------------------------------------------------------------------------- /examples/todomvc/todomvc-common/readme.md: -------------------------------------------------------------------------------- 1 | # todomvc-common 2 | 3 | > Common TodoMVC utilities used by our apps 4 | 5 | 6 | ## Install 7 | 8 | ``` 9 | $ npm install --save todomvc-common 10 | ``` 11 | 12 | 13 | ## License 14 | 15 | MIT © [TasteJS](http://tastejs.com) 16 | -------------------------------------------------------------------------------- /examples/todomvc/webpack.config.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | var path = require('path'); 3 | const HtmlWebpackPlugin = require("html-webpack-plugin"); 4 | const history = require('connect-history-api-fallback'); 5 | const convert = require('koa-connect'); 6 | 7 | module.exports = { 8 | mode: "development", 9 | devtool: "eval-source-map", 10 | module: { 11 | rules: [ 12 | {test: /\.js$/, loaders: ['babel-loader']}, 13 | {test: /\.jsx?$/, enforce: "pre", loaders: ['svenjsx-loader']} 14 | ] 15 | }, 16 | plugins: [ 17 | new webpack.DefinePlugin({ 18 | 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV) 19 | }), 20 | new HtmlWebpackPlugin( 21 | { 22 | template: path.resolve(__dirname, "src/index.html") 23 | } 24 | ) 25 | ], 26 | entry: [path.resolve(__dirname, 'src/index.jsx')], 27 | output: { 28 | path: path.resolve(__dirname, "docs"), 29 | libraryTarget: 'umd', 30 | filename: "bundle.js" 31 | }, 32 | resolve: { 33 | extensions: [".js", ".jsx", ".css"], 34 | alias: { 35 | root: path.resolve(__dirname, "../../src"), 36 | assets: path.resolve(__dirname, "../../assets") 37 | } 38 | } 39 | }; 40 | -------------------------------------------------------------------------------- /npmbuild/README.md: -------------------------------------------------------------------------------- 1 | # SvenJS 2 | 3 | A micro javascript framework for creating composable web apps 4 | 5 | # Demos 6 | 7 | - [TodoMVC](http://svenanders.github.io/svenjs-todomvc/). [Source](https://github.com/svenanders/svenjs-todomvc) 8 | 9 | # Releases 10 | 11 | - 2.0.1: ES modules, bug fixes and more! 12 | - 0.3.2: Added *_beforeMount* life cycle method. 13 | - 0.3.1: Added composition (importing components and referencing them in JSX by name). For instance: _const SecondComponent = require("SecondComponent")_. Referenced in _render_ like this: __ 14 | - 0.3.0: Renamed life cycle methods. New names: *_didMount* & *_didUpdate* 15 | 16 | # Goals 17 | 18 | - A web library that enables you to write code that can be accessed both serverside and clientside 19 | 20 | - Enforced state immutability 21 | 22 | - Built in store implementation (todo) 23 | 24 | - Synthetic event handler. Implemented in such a way that input events work across browsers. 25 | 26 | - Minimal file size 27 | 28 | # Install 29 | 30 | Use the npm version: 31 | 32 | ```bash 33 | npm install svenjs 34 | ``` 35 | 36 | Build youself. Clone this repo and run 37 | 38 | ```bash 39 | npm run build 40 | ``` 41 | 42 | # How to use 43 | 44 | ```html 45 | import SvenJs from "svenjs"; 46 | 47 | SvenJs.create({ 48 | initialState: { 49 | clicks: 0 50 | }, 51 | render() { 52 | const clickFunc = () =>{ 53 | let clicks=this.state.clicks; 54 | this.setState({clicks: ++clicks }); 55 | } 56 | return (
    57 |

    The Click App

    58 |
    59 | 60 |
    61 |
    62 |

    Click stats

    63 |

    You have clicked on the button {this.state.clicks} times

    64 |
    65 |
    ) 66 | } 67 | }); 68 | SvenJs.render(App, document.getElementById("app")) 69 | ``` 70 | 71 | ## Related Modules 72 | 73 | * [svenjsx](https://github.com/svenanders/svenjsx) - JSX as used by SvenJS. 74 | 75 | * [svenjsx-loader](https://github.com/svenanders/svenjsx-loader) - Webpack loader for SvenJS. 76 | 77 | -------------------------------------------------------------------------------- /npmbuild/index.js: -------------------------------------------------------------------------------- 1 | module.exports=function(t){var n={};function e(o){if(n[o])return n[o].exports;var r=n[o]={i:o,l:!1,exports:{}};return t[o].call(r.exports,r,r.exports,e),r.l=!0,r.exports}return e.m=t,e.c=n,e.d=function(t,n,o){e.o(t,n)||Object.defineProperty(t,n,{enumerable:!0,get:o})},e.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},e.t=function(t,n){if(1&n&&(t=e(t)),8&n)return t;if(4&n&&"object"==typeof t&&t&&t.__esModule)return t;var o=Object.create(null);if(e.r(o),Object.defineProperty(o,"default",{enumerable:!0,value:t}),2&n&&"string"!=typeof t)for(var r in t)e.d(o,r,function(n){return t[n]}.bind(null,r));return o},e.n=function(t){var n=t&&t.__esModule?function(){return t.default}:function(){return t};return e.d(n,"a",n),n},e.o=function(t,n){return Object.prototype.hasOwnProperty.call(t,n)},e.p="",e(e.s=0)}([function(t,n,e){t.exports=e(1)},function(t,n,e){"use strict";e.r(n);function o(t){return(o="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}var r=function t(n){if(!n||"object"!==o(n))return n;var e={};return Array.isArray(n)?e=n.map(function(n){return t(n)}):Object.keys(n).forEach(function(o){return e[o]=t(n[o])}),e};function i(t){return(i="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}var u=function t(n){return Object.freeze(n),Object.getOwnPropertyNames(n).forEach(function(e){!n.hasOwnProperty(e)||null===n[e]||"object"!==i(n[e])&&"function"!=typeof n[e]||Object.isFrozen(n[e])||t(n[e])}),n},c=function(t,n){var e=r(n);return u(e),e},f={}.toString,a=function(t){return"function"==typeof t},s=function(t){return"[object Object]"===f.call(t)},l=function(t){return"[object Array]"===f.call(t)},p=function(){var t=function(){return Math.floor(65536*(1+Math.random())).toString(16).substring(1)};return"".concat(t()+t(),"-").concat(t())};function d(t){return(d="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}var y=function(t,n){return n.appendChild(t)},b=Object.prototype.hasOwnProperty,m=function(t,n){if(b.call(t,"children")&&l(t.children)&&t.children.forEach(function(t){"string"!=typeof t&&"number"!=typeof t||n.appendChild(document.createTextNode(t))}),b.call(t,"attrs")){var e=t.attrs;for(var o in e)if("config"!==o&&"key"!==o&&("disabled"!==o||!1!==e[o]))if("class"==o||"className"==o)n.className=e[o].toString();else if(a(e[o])&&"on"==o.slice(0,2))n[o.toLowerCase()]=e[o];else{if("checked"===o&&(!1===e[o]||""===e[o]))continue;try{n.setAttribute(""+o,e[o].toString())}catch(t){console.error("e",t)}}}return n},v=function(t){void 0===t.tag&&(t.tag="span",t.attrs={sjxid:p()});var n=document.createElement(t.tag);return m(t,n),n},j=function(t,n){var e=document.createDocumentFragment(),o=document.createElement(t.tag);m(t,n.rootNode);var r=function t(n,e){var o;return b.call(n,"children")?l(n.children)&&n.children.forEach(function(n){null!==n&&"object"===d(n)&&(o=v(n),t(n,o),y(o,e)),l(n)&&n.forEach(function(n){b.call(n,"render")||(o=v(n),t(n,o),y(o,e))})}):"object"===d(n)&&b.call(n,"render")&&t(n.render(),e),e}(t,o);return e.appendChild(r),e},h=function(t,n){var e,o=arguments.length>2&&void 0!==arguments[2]&&arguments[2];if(!n)return"Error: No node to attach";s(t)&&(b.call(t,"_svenjs")||(t._svenjs={rootNode:!1}),t._svenjs.rootNode=n),n.innerHTML="",e=s(o)?o:t.render(),n.appendChild(j(e,t._svenjs))},S=function(t){var n;t._svenjs.rootNode&&(n=t._svenjs.rootNode),t.hasOwnProperty("attrs")&&t.attrs.hasOwnProperty("sjxid")&&(n||(n=document.querySelector("[sjxid='"+t.attrs.sjxid+"']"))),t.isMounted&&(h(t,n),t.hasOwnProperty("_didUpdate")&&t._didUpdate.apply(t))},g=function(t,n){n.state=c(n,t),S(n)},O=function(t,n){var e=r(t);return e._svenjs={rootNode:!1},e.isBound=!1,e.isMounted=!1,e.props={},n&&(e._jsxid=e.props.sjxid,e.props=n,setTimeout(function(){return S(e)},0),delete e.props.sjxid),e.hasOwnProperty("attrs")||e.hasOwnProperty("attrs")||(e.attrs={sjxid:p()}),e.isBound||(e.version="2.0.1",e.isBound=!0,e.setState=function(t){return g(t,this)},"function"==typeof e._beforeMount&&e._beforeMount.apply(e)),e.isMounted||(e.isMounted=!0,void 0!==e.initialState&&(e.state=e.initialState),"function"==typeof e._didMount&&(e._didMount.apply(e),"function"==typeof S&&setTimeout(function(){return S(e)},100))),e},_=[],M=function(t){return t.isMounted||(t.listenTo=function(t){_.push(t)},t.emit=function(t){_.forEach(function(n){n(t)})},"function"==typeof t.init&&t.init.apply(t)),t};console.info("Running svenjs version ".concat("2.0.1"));var x={version:"2.0.1",create:O,setState:g,createStore:M,render:h,renderToString:function(t,n){return j(t,n).innerHTML},lifeCycle:S};n.default=x}]); -------------------------------------------------------------------------------- /npmbuild/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "svenjs", 3 | "version": "2.0.2", 4 | "description": "A micro javascript framework for creating composable web apps", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/svenanders/svenjs.git" 12 | }, 13 | "keywords": [ 14 | "svenjs" 15 | ], 16 | "author": { 17 | "name": "Sven Anders Robbestad", 18 | "email": "robbestad@gmail.com", 19 | "url": "http://svenardo.com" 20 | }, 21 | "license": "ISC", 22 | "bugs": { 23 | "url": "https://github.com/svenanders/svenjs/issues" 24 | }, 25 | "homepage": "https://github.com/svenanders/svenjs#readme", 26 | "_id": "svenjs@0.2.1", 27 | "_shasum": "8417ce8fd74750adc3c24b97ecd96c40e14c42f5", 28 | "_from": "svenjs@*", 29 | "_npmVersion": "2.11.3", 30 | "_nodeVersion": "0.12.7", 31 | "_npmUser": { 32 | "name": "svenanders", 33 | "email": "anders@robbestad.com" 34 | }, 35 | "dist": { 36 | "shasum": "8417ce8fd74750adc3c24b97ecd96c40e14c42f5", 37 | "tarball": "http://registry.npmjs.org/svenjs/-/svenjs-0.2.1.tgz" 38 | }, 39 | "maintainers": [ 40 | { 41 | "name": "svenanders", 42 | "email": "anders@robbestad.com" 43 | } 44 | ], 45 | "directories": {}, 46 | "_resolved": "https://registry.npmjs.org/svenjs/-/svenjs-0.2.1.tgz", 47 | "readme": "ERROR: No README data found!" 48 | } 49 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "svenjs", 3 | "version": "2.0.2", 4 | "description": "A micro javascript framework for creating composable web apps", 5 | "scripts": { 6 | "test": "tape tests/*", 7 | "build": "NODE_ENV=production webpack -p && npx rollup --config rollup.config.js", 8 | "postbuild": "cp dist/index.js npmbuild && cp dist/*.js assets/", 9 | "build:watch": "sane 'NODE_ENV=development npm run build' --glob='src/**/*.js' --watchman", 10 | "watch": "sane 'npm run build' --glob='src/**/*.js' --watchman" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/svenanders/svenjs.git" 15 | }, 16 | "main": "dist/index.js", 17 | "directories": { 18 | "example": "example" 19 | }, 20 | "devDependencies": { 21 | "@babel/core": "^7.0.0-rc.1", 22 | "@babel/preset-env": "^7.0.0-rc.1", 23 | "@babel/preset-react": "^7.0.0-rc.1", 24 | "babel-eslint": "^8.2.6", 25 | "babel-loader": "^8.0.0-beta.4", 26 | "eslint": "^5.4.0", 27 | "jsdom": "^12.0.0", 28 | "jstransform": "^11.0.3", 29 | "rimraf": "^2.3.4", 30 | "rollup": "^0.64.1", 31 | "sane": "^3.0.0", 32 | "tape": "~4.9.1", 33 | "webpack": "^4.17.0", 34 | "webpack-cli": "^3.1.0", 35 | "webpack-serve": "^2.0.2" 36 | }, 37 | "author": "", 38 | "license": "ISC", 39 | "dependencies": { 40 | "svenjsx": "^0.3.1", 41 | "svenjsx-loader": "^0.3.1" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | input: "src/index.js", 3 | output: { 4 | file: 'examples/browser/src/assets/sven.browser.js', 5 | format: 'es', 6 | globals: { 7 | svenjs: 'Svenjs' 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /scripts/authors.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | # Generate an AUTHORS file based on the output of git shortlog. It uses ABC 4 | # order, strips out leading spaces and numbers, then filters out specific 5 | # authors. 6 | 7 | git shortlog -se \ 8 | | perl -spe 's/^\s+\d+\s+//' \ 9 | | sed -e '/^CommitSyncScript.*$/d' \ 10 | > AUTHORS -------------------------------------------------------------------------------- /scripts/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | WEBPACK_CMD=node_modules/.bin/webpack 3 | 4 | # Generate libs 5 | rm -rf build 6 | rm -rf dist 7 | mkdir -p dist 8 | `npm bin`/babel src --out-dir build 9 | 10 | # Copy libs 11 | $WEBPACK_CMD build/index.jsx dist/sven.js 12 | cp dist/sven.js examples/sven.js 13 | -------------------------------------------------------------------------------- /scripts/lint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | `npm bin`/eslint src -------------------------------------------------------------------------------- /scripts/prod.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | WEBPACK_CMD=node_modules/.bin/webpack 3 | DEV='sh ./scripts/build.sh' 4 | 5 | # GENERATE SVENJS LIB 6 | $DEV 7 | 8 | echo 9 | echo Generating dist build of SVENJS 10 | echo 11 | $WEBPACK_CMD -p build/index.jsx dist/sven.min.js 12 | 13 | #cp dist/sven.min.js ../svenjs-npm/index.min.js 14 | #cp dist/sven.js ../svenjs-npm/index.jsx 15 | cp dist/sven.js assets/index.jsx 16 | 17 | #cp dist/sven.js ~/Jottacloud/Work/opensource/vdom-benchmark-svenjs/node_modules/svenjs/index.jsx 18 | #cp dist/sven.min.js ../svenjs-blueprint/node_modules/svenjs/index.min.js 19 | #cp dist/sven.js ../svenjs-blueprint/node_modules/svenjs/index.jsx 20 | #cp dist/sven.min.js ../svenjs-todomvc/node_modules/svenjs/index.min.js 21 | #cp dist/sven.js ../svenjs-todomvc/node_modules/svenjs/index.jsx 22 | 23 | #cp dist/sven.min.js ../map/node_modules/svenjs/index.min.js 24 | #cp dist/sven.js ../map/node_modules/svenjs/index.jsx 25 | 26 | ## For prod 27 | #echo 28 | #echo Generating timetravel app 29 | #echo 30 | #$WEBPACK_CMD -p examples/timetravel/src/index.jsx examples/timetravel/dist/app.min.js 31 | ##$WEBPACK_CMD examples/todomvc/src/index.jsx examples/todomvc/dist/app.jsx 32 | # 33 | #echo 34 | #echo Generating TodoMVC app 35 | #echo 36 | #$WEBPACK_CMD -p examples/todomvc/src/index.jsx examples/todomvc/dist/app.min.js 37 | ##$WEBPACK_CMD examples/main/src/index.jsx examples/main/dist/app.jsx 38 | # 39 | #echo 40 | #echo Generating main app 41 | #echo 42 | #$WEBPACK_CMD -p examples/composable/src/index.jsx examples/composable/dist/app.min.js 43 | # 44 | -------------------------------------------------------------------------------- /scripts/push.sh: -------------------------------------------------------------------------------- 1 | #!/usr/local/bin/bash 2 | git commit -am"Autopush" 3 | git push origin --all 4 | branch_name=$(git symbolic-ref -q HEAD) 5 | branch_name=${branch_name##refs/heads/} 6 | git push github $branch_name 7 | #git push --mirror git@github.com:svenanders/svenjs.git 8 | -------------------------------------------------------------------------------- /src/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robbestad/svenjs/88a3c127a21ac8a013e9a4d91a8235fedfdef868/src/.DS_Store -------------------------------------------------------------------------------- /src/core/create.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @module core/create 3 | * @see module:svenjs 4 | * @author Sven A Robbestad 5 | */ 6 | import version from './version.js'; 7 | import setState from '../web/set-state.js'; 8 | import lifeCycle from "../web/life-cycle.js"; 9 | import deepClone from "../lib/deep-clone.js"; 10 | import uuid from "../lib/uuid.js"; 11 | 12 | const create = (_spec, props) => { 13 | const spec = deepClone(_spec); 14 | spec._svenjs = {rootNode: false}; 15 | spec.isBound = false; 16 | spec.isMounted = false; 17 | spec.props = {}; 18 | 19 | if (props) { 20 | spec._jsxid = spec.props.sjxid; 21 | spec.props = props; 22 | setTimeout(() => lifeCycle(spec), 0); 23 | delete spec.props.sjxid; 24 | } 25 | if (!spec.hasOwnProperty("attrs")) { 26 | if (!spec.hasOwnProperty("attrs")) { 27 | spec.attrs = {sjxid: uuid()}; 28 | } 29 | } 30 | if (!spec.isBound) { 31 | spec.version = version; 32 | spec.isBound = true; 33 | spec.setState = function (state) { 34 | return setState(state, this); 35 | }; 36 | if ("function" === typeof spec._beforeMount) { 37 | spec._beforeMount.apply(spec); 38 | } 39 | } 40 | if (!spec.isMounted) { 41 | spec.isMounted = true; 42 | 43 | if (undefined !== spec.initialState) { 44 | spec.state = spec.initialState; 45 | } 46 | if ("function" === typeof spec._didMount) { 47 | spec._didMount.apply(spec); 48 | if ("function" === typeof lifeCycle) 49 | setTimeout(() => lifeCycle(spec), 100); 50 | } 51 | } 52 | return spec; 53 | }; 54 | export default create; 55 | -------------------------------------------------------------------------------- /src/core/render.js: -------------------------------------------------------------------------------- 1 | /** 2 | * render module. 3 | * @module core/render 4 | * @see module:svenjs 5 | * @author Sven A Robbestad 6 | */ 7 | 'use strict'; 8 | 9 | // define common functions used in this mod ule 10 | // import {isFunction, isObject, isString, isArray} from '../lib/validations'; 11 | 12 | import {isArray, isFunction, isObject} from "../lib/validations.js"; 13 | import uuid from "../lib/uuid.js"; 14 | 15 | const appendChild = (child, parent) => { 16 | return parent.appendChild(child); 17 | }; 18 | 19 | // Speed up calls to hasOwnProperty 20 | var hasOwnProperty = Object.prototype.hasOwnProperty; 21 | 22 | //const voidElems = /^(AREA|BASE|BR|COL|COMMAND|EMBED|HR|IMG|INPUT|KEYGEN|LINK|META|PARAM|SOURCE|TRACK|WBR)$/; 23 | 24 | /** 25 | * setAttrs. This sets all attributes on the node tag, like class names, event handlers etc. 26 | * @param {tag} a tag structure (e.g {tag: "div", attrs: {class:"test"}, children: []}) 27 | * @param {node} a DOM Node the children should be added to 28 | * @returns {Object} a DOM Node 29 | */ 30 | const setAttrs = (tag, node) => { 31 | if ((hasOwnProperty.call(tag, 'children'))) { 32 | if (isArray(tag.children)) { 33 | tag.children.forEach((childTag) => { 34 | if (typeof childTag == "string" || typeof childTag == "number") { 35 | node.appendChild(document.createTextNode(childTag)); 36 | } 37 | }); 38 | } 39 | } 40 | 41 | if ((hasOwnProperty.call(tag, 'attrs'))) { 42 | const attr = tag.attrs; 43 | for (var attrName in attr) { 44 | if (attrName === "config" || attrName === "key") continue; 45 | if (attrName === "disabled" && attr[attrName] === false) continue; 46 | else if (attrName == "class" || attrName == "className") node.className = attr[attrName].toString(); 47 | else if (isFunction(attr[attrName]) && attrName.slice(0, 2) == "on") { 48 | node[attrName.toLowerCase()] = attr[attrName]; 49 | } 50 | else if (attrName === "checked" && (attr[attrName] === false || attr[attrName] === "")) continue; 51 | else { 52 | try { 53 | node.setAttribute('' + attrName, attr[attrName].toString()); 54 | } catch (e) { 55 | console.error('e', e); 56 | } 57 | } 58 | } 59 | 60 | } 61 | return node; 62 | }; 63 | 64 | /** 65 | * buildElement 66 | * @param {tag} a tag structure (e.g {tag: "div", attrs: {class:"test"}, children: []}) 67 | * @returns {Object} a DOM Node 68 | */ 69 | const buildElement = (tag) => { 70 | if ("undefined" === typeof tag.tag) { 71 | tag.tag = "span"; 72 | tag.attrs = {"sjxid": uuid()}; 73 | } 74 | let child = document.createElement(tag.tag); 75 | setAttrs(tag, child); 76 | return child; 77 | }; 78 | 79 | /** 80 | * buildChildren 81 | * @param {tags} a tag structure (e.g {tag: "div", attrs: {class:"test"}, children: []}) 82 | * @param {parent} a DOM Node the children should be added to 83 | * @returns {Object} a DOM Node 84 | */ 85 | const buildChildren = (tags, parent) => { 86 | let childNode; 87 | if ((hasOwnProperty.call(tags, 'children'))) { 88 | if (isArray(tags.children)) { 89 | tags.children.forEach(tag => { 90 | if (null !== tag && 'object' === typeof tag) { 91 | childNode = buildElement(tag); 92 | buildChildren(tag, childNode); 93 | appendChild(childNode, parent); 94 | } 95 | if (isArray(tag)) { 96 | tag.forEach(childtag => { 97 | if (!(hasOwnProperty.call(childtag, 'render'))) { 98 | childNode = buildElement(childtag); 99 | buildChildren(childtag, childNode); 100 | appendChild(childNode, parent); 101 | } 102 | }); 103 | } 104 | }); 105 | } 106 | } else { 107 | // Components inside render 108 | if ('object' === typeof tags) { 109 | if ((hasOwnProperty.call(tags, 'render'))) { 110 | buildChildren(tags.render(), parent); 111 | } 112 | } 113 | } 114 | return parent; 115 | }; 116 | 117 | 118 | const renderToString = (tags, data) => { 119 | return vDom(tags, data).innerHTML; 120 | }; 121 | 122 | /** 123 | * vDom 124 | * @param {tags} a tag structure (e.g {tag: "div", attrs: {class:"test"}, children: []}) 125 | * @returns {Object} a DOM Node 126 | */ 127 | const vDom = (tags, data) => { 128 | var docFragment = document.createDocumentFragment(); 129 | 130 | // Root node 131 | var root = document.createElement(tags.tag); 132 | setAttrs(tags, data.rootNode); 133 | 134 | // Build children 135 | let childrenTree = buildChildren(tags, root); 136 | docFragment.appendChild(childrenTree); 137 | 138 | return docFragment; 139 | }; 140 | 141 | /** 142 | * Render 143 | * @alias svenjs.render 144 | * @param {spec} a svenjs component with a render method. Optional, set to false if not used 145 | * @param {node} a document node (e.g from document.getElementById()). 146 | * @param {tags} optional pre-rendered tags 147 | * @returns {undefined} 148 | */ 149 | const render = (spec, node, preRendered = false) => { 150 | if (node) { 151 | 152 | if (isObject(spec)) { 153 | // Set internal ref 154 | if (!(hasOwnProperty.call(spec, '_svenjs'))) { 155 | spec._svenjs = {rootNode: false}; 156 | } 157 | spec._svenjs.rootNode = node; 158 | } 159 | 160 | // reset HTML 161 | node.innerHTML = ""; 162 | // Get the converted tags 163 | let tags; 164 | 165 | if (isObject(preRendered)) { 166 | tags = preRendered; 167 | } else { 168 | tags = spec.render(); 169 | } 170 | 171 | // Append to window 172 | node.appendChild(vDom(tags, spec._svenjs)); 173 | } else { 174 | return 'Error: No node to attach'; 175 | } 176 | }; 177 | export { 178 | render, 179 | renderToString 180 | }; 181 | -------------------------------------------------------------------------------- /src/core/version.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @module core/version 3 | * @see module:svenjs 4 | * @author Sven A Robbestad 5 | */ 6 | 7 | // import {version} from "../../package.json" 8 | // export default version; 9 | export default "2.0.1"; 10 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import version from './core/version'; 2 | import create from './core/create'; 3 | import {render, renderToString} from './core/render'; 4 | import setState from './web/set-state'; 5 | import lifeCycle from './web/life-cycle'; 6 | import createStore from './store/create-store'; 7 | 8 | console.info(`Running svenjs version ${version}`); 9 | 10 | const Svenjs = { 11 | version, 12 | create, 13 | setState, 14 | createStore, 15 | render, 16 | renderToString, 17 | lifeCycle 18 | }; 19 | 20 | export default Svenjs; 21 | -------------------------------------------------------------------------------- /src/lib/deep-clone.js: -------------------------------------------------------------------------------- 1 | const deepClone = obj => { 2 | if (!obj || typeof obj !== 'object') { 3 | return obj; 4 | } 5 | let newObj = {}; 6 | if (Array.isArray(obj)) { 7 | newObj = obj.map(item => deepClone(item)); 8 | } else { 9 | Object.keys(obj).forEach((key) => { 10 | return newObj[key] = deepClone(obj[key]); 11 | }); 12 | } 13 | return newObj; 14 | }; 15 | export default deepClone; 16 | -------------------------------------------------------------------------------- /src/lib/deep-freeze.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const deepFreeze = function (o) { 4 | Object.freeze(o); 5 | 6 | Object.getOwnPropertyNames(o).forEach(function (prop) { 7 | if (o.hasOwnProperty(prop) 8 | && o[prop] !== null 9 | && (typeof o[prop] === "object" || typeof o[prop] === "function") 10 | && !Object.isFrozen(o[prop])) { 11 | deepFreeze(o[prop]); 12 | } 13 | }); 14 | return o; 15 | }; 16 | export default deepFreeze; 17 | -------------------------------------------------------------------------------- /src/lib/shallow-copy.js: -------------------------------------------------------------------------------- 1 | const shallowCopy = function (o) { 2 | return JSON.parse(JSON.stringify(o)); 3 | }; 4 | export default shallowCopy; 5 | -------------------------------------------------------------------------------- /src/lib/uuid.js: -------------------------------------------------------------------------------- 1 | const uuid = () => { 2 | const s = () => Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1); 3 | return `${s() + s()}-${s()}`; 4 | }; 5 | export default uuid; 6 | -------------------------------------------------------------------------------- /src/lib/validations.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | // define common functions used in this module 3 | var type = ({}).toString; 4 | const isFunction = function (object) { 5 | return typeof object === "function"; 6 | }; 7 | const isObject = function (object) { 8 | return type.call(object) === "[object Object]"; 9 | }; 10 | const isString = function (object) { 11 | return type.call(object) === "[object String]"; 12 | }; 13 | const isArray = function (object) { 14 | return type.call(object) === "[object Array]"; 15 | }; 16 | const isDefined = function (object) { 17 | return type.call(object) !== "undefined"; 18 | }; 19 | export { 20 | isFunction, 21 | isObject, 22 | isString, 23 | isArray, 24 | isDefined 25 | }; 26 | -------------------------------------------------------------------------------- /src/store/create-store.js: -------------------------------------------------------------------------------- 1 | let _callbacks=[]; 2 | const createStore = (spec)=> { 3 | if(!spec.isMounted){ 4 | spec.listenTo=function(cb){ 5 | _callbacks.push(cb); 6 | }; 7 | spec.emit=(data)=>{ 8 | _callbacks.forEach((cb)=>{ 9 | cb(data); 10 | }); 11 | }; 12 | 13 | if("function" === typeof spec.init){ 14 | spec.init.apply(spec); 15 | } 16 | } 17 | return spec; 18 | }; 19 | export default createStore; 20 | -------------------------------------------------------------------------------- /src/web/life-cycle.js: -------------------------------------------------------------------------------- 1 | import {render} from "../core/render"; 2 | 3 | const lifeCycle = (spec) => { 4 | let rootNode; 5 | if (spec._svenjs.rootNode) { 6 | rootNode = spec._svenjs.rootNode; 7 | } 8 | if (spec.hasOwnProperty("attrs") && spec.attrs.hasOwnProperty("sjxid")) { 9 | if (!rootNode) rootNode = document.querySelector("[sjxid='" + spec.attrs.sjxid + "']"); 10 | } 11 | 12 | if (spec.isMounted) { 13 | render(spec, rootNode); 14 | if (spec.hasOwnProperty('_didUpdate')) spec._didUpdate.apply(spec); 15 | } 16 | }; 17 | 18 | export default lifeCycle; 19 | -------------------------------------------------------------------------------- /src/web/save-state.js: -------------------------------------------------------------------------------- 1 | import deepClone from '../lib/deep-clone.js'; 2 | import deepFreeze from '../lib/deep-freeze.js'; 3 | const saveState = (spec,diff_state)=> { 4 | const state = deepClone(diff_state); 5 | deepFreeze(state); 6 | return state; 7 | }; 8 | export default saveState; 9 | -------------------------------------------------------------------------------- /src/web/set-state.js: -------------------------------------------------------------------------------- 1 | import saveState from './save-state'; 2 | import lifeCycle from './life-cycle'; 3 | 4 | const setState = (state, spec)=> { 5 | spec.state = saveState(spec, state); 6 | lifeCycle(spec); 7 | }; 8 | export default setState; 9 | -------------------------------------------------------------------------------- /tests/render-to-html.js: -------------------------------------------------------------------------------- 1 | var Svenjs = require('../assets/index.js'); 2 | var test = require('tape'); 3 | var myFunc = ()=> { 4 | }; 5 | var backDisabled = false; 6 | var nextDisabled = true; 7 | var goBack = ()=> { 8 | return "go back"; 9 | } 10 | var goForward = ()=> { 11 | return "go forward"; 12 | } 13 | var jsdom = require("jsdom"); 14 | const { JSDOM } = jsdom; 15 | const { document } = (new JSDOM('')).window; 16 | global.document = document; 17 | 18 | var nodeCache = [], cellCache = {}; 19 | 20 | var tags = ({ 21 | tag: "div", attrs: {id: "row"}, children: [ 22 | { 23 | tag: "div", attrs: {id: "app"}, children: [ 24 | {tag: "h3", attrs: {}, children: ["Sample App"]}, 25 | {tag: "button", attrs: {id: "add", onClick: myFunc}, children: ["Add word"]}, 26 | {tag: "div", attrs: {id: "ui"}}, 27 | {tag: "small", attrs: {}, children: ["(click word to delete)"]} 28 | ] 29 | }, 30 | { 31 | tag: "div", attrs: {id: "time-travel"}, children: [ 32 | {tag: "h3", attrs: {}, children: ["Time travel"]}, 33 | {tag: "button", attrs: {id: "back", disabled: backDisabled, onClick: goBack}, children: ["Back"]}, 34 | {tag: "button", attrs: {id: "next", disabled: nextDisabled, onClick: goForward}, children: ["Next"]}, 35 | {tag: "p", attrs: {id: "time-pos"}} 36 | ] 37 | } 38 | ] 39 | }); 40 | 41 | //const tags = spec.render(); 42 | const appendChild = (child, parent)=> { 43 | return parent.appendChild(child); 44 | } 45 | 46 | const setAttrs = (tag, node)=> { 47 | //console.log(tag); 48 | if (null != tag.children[0] && typeof tag.children[0] == "string") { 49 | let innerText = document.createTextNode(tag.children[0]); 50 | node.appendChild(innerText); 51 | } 52 | if (tag.hasOwnProperty('attrs')) { 53 | if (tag.attrs.hasOwnProperty('id')) { 54 | node.id = "row"; 55 | } 56 | if (tag.attrs.hasOwnProperty('onClick')) { 57 | node.onclick = tag.attrs.onClick; 58 | } 59 | } 60 | return node; 61 | } 62 | 63 | const addChildren = (tags, root)=> { 64 | if (typeof tags.children != "object") { 65 | return false; 66 | } 67 | var parent = document.createElement(tags.tag); 68 | 69 | tags.children.forEach((tag)=> { 70 | var child = document.createElement(tag.tag); 71 | appendChild(setAttrs(tag, child), parent) 72 | if (tag.children != null && typeof tag.children == "object") { 73 | const childrenTags = tag.children; 74 | childrenTags.forEach((childTag)=> { 75 | console.log(childTag); 76 | var childnode = document.createElement(childTag.tag); 77 | appendChild(setAttrs(childnode, child), parent) 78 | }) 79 | } 80 | 81 | }); 82 | appendChild(setAttrs(tags, parent), root) 83 | 84 | return parent; 85 | }; 86 | 87 | 88 | test('render html', function (t) { 89 | 90 | var docFragment = document.createDocumentFragment(); 91 | 92 | // Root node 93 | var div = document.createElement(tags.tag); 94 | if (tags.attrs.hasOwnProperty('id')) { 95 | div.id = tags.attrs.id; 96 | } 97 | 98 | docFragment.appendChild(div); 99 | 100 | // Build children 101 | let childrenTree = addChildren(tags, div); 102 | console.log(childrenTree); 103 | 104 | // Append to root node 105 | docFragment.appendChild(childrenTree); 106 | 107 | t.end(); 108 | }); 109 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | var path = require('path'); 3 | 4 | module.exports = { 5 | mode: "production", 6 | module: { 7 | rules: [ 8 | {test: /\.js$/, loaders: ['babel-loader'], exclude: /node_modules/}, 9 | {test: /\.jsx$/, enforce: "pre", loaders: ['svenjsx-loader'], exclude: /node_modules/} 10 | ] 11 | }, 12 | plugins: [ 13 | new webpack.DefinePlugin({ 14 | 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV) 15 | }) 16 | ], 17 | entry: [path.resolve(__dirname, 'src/index.js')], 18 | output: { 19 | library: 'Svenjs', 20 | libraryTarget: 'commonjs-module', 21 | path: path.resolve(__dirname, "dist"), 22 | filename: "index.js" 23 | }, 24 | resolve: { 25 | extensions: [".js", ".jsx"], 26 | alias: { 27 | root: path.resolve(__dirname, "src") 28 | } 29 | } 30 | }; 31 | --------------------------------------------------------------------------------