Initial commit.
This commit is contained in:
commit
256842e42d
16
.babelrc
Normal file
16
.babelrc
Normal file
@ -0,0 +1,16 @@
|
||||
{
|
||||
"env": {
|
||||
"server": {
|
||||
"presets": [ "es2015-node6" ],
|
||||
"plugins": [
|
||||
"add-module-exports"
|
||||
]
|
||||
},
|
||||
"client": {
|
||||
"presets": [ "es2015", "react" ],
|
||||
"plugins": [
|
||||
"add-module-exports"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
52
.eslintrc
Normal file
52
.eslintrc
Normal file
@ -0,0 +1,52 @@
|
||||
{
|
||||
"extends": "airbnb-base",
|
||||
|
||||
"parserOptions": {
|
||||
"sourceType": "strict"
|
||||
},
|
||||
|
||||
"env": {
|
||||
"mocha": true
|
||||
},
|
||||
|
||||
"rules": {
|
||||
"semi": [ 2, "never" ],
|
||||
"import/no-extraneous-dependencies": ["error", {"devDependencies": true, "optionalDependencies": false, "peerDependencies": false}],
|
||||
"new-cap": 0,
|
||||
"no-mixed-operators": 0,
|
||||
"no-cond-assign": 0,
|
||||
"guard-for-in": 0,
|
||||
"global-require": 0,
|
||||
"no-underscore-dangle": 0,
|
||||
"object-shorthand": 0,
|
||||
"default-case": 0,
|
||||
"one-var": 0,
|
||||
"prefer-rest-params": 0,
|
||||
"no-unused-vars": [ 2, { "args": "none" } ],
|
||||
"no-alert": 0,
|
||||
"quote-props": 0,
|
||||
"no-nested-ternary": 0,
|
||||
"no-use-before-define": [2, { "functions": false, "classes": true }],
|
||||
"consistent-return": 0,
|
||||
"no-eval": 0,
|
||||
"prefer-arrow-callback": 0,
|
||||
"array-bracket-spacing": 0,
|
||||
"no-console": 0,
|
||||
"indent": [ 2, 2, { "SwitchCase": 1 }],
|
||||
"max-len": 0,
|
||||
"comma-dangle": 0,
|
||||
"no-param-reassign": 0,
|
||||
"prefer-template": 0,
|
||||
"curly": 0,
|
||||
"func-names": 0,
|
||||
"no-shadow": 0,
|
||||
"spaced-comment": 0,
|
||||
"strict": [ 2, "global" ]
|
||||
},
|
||||
|
||||
"globals": {
|
||||
"PWD": true,
|
||||
"ENV": true,
|
||||
"location": true
|
||||
}
|
||||
}
|
||||
40
.gitignore
vendored
Normal file
40
.gitignore
vendored
Normal file
@ -0,0 +1,40 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (http://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# NPM Dependency directory
|
||||
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
|
||||
node_modules
|
||||
|
||||
npm-debug.log
|
||||
|
||||
# Bower dependency directory
|
||||
bower_components
|
||||
|
||||
*.marko.js
|
||||
|
||||
/dump
|
||||
|
||||
/public
|
||||
|
||||
/server/uploads
|
||||
158
README.md
Normal file
158
README.md
Normal file
@ -0,0 +1,158 @@
|
||||
# MyPaper
|
||||
|
||||
## Running
|
||||
|
||||
1. `$ git clone git@gitlab.thecodebureau.com:lohfu/pomodoro.git`
|
||||
2. `$ cd pomodoro`
|
||||
2. `$ npm install`
|
||||
3. `$ npm run gulp`
|
||||
|
||||
Navigate to localhost:1337 (Node app runs on the port given in
|
||||
`server/config/port`, but Gulp starts a
|
||||
[BrowserSync](https://github.com/BrowserSync/browser-sync) proxy that reloads
|
||||
your browser automatically when you change certain files).
|
||||
|
||||
## Building (Production environment)
|
||||
|
||||
Simply call `$ npm run gulp:production` to run all build tasks in
|
||||
production mode, without starting the server.
|
||||
|
||||
## Routes
|
||||
|
||||
## Files & Directories
|
||||
|
||||
### /img/raster
|
||||
|
||||
All raster images. Will be symlinked into `/public/img`.
|
||||
|
||||
### /img/svg
|
||||
|
||||
All SVG images. Will be symlinked into `/public/img` in development ENV,
|
||||
and minified to the same location in production ENV.
|
||||
|
||||
### /gulpfile.js
|
||||
|
||||
Runs the code in `/gulp`.
|
||||
|
||||
### /gulp
|
||||
|
||||
This contains ALL gulp logic.
|
||||
|
||||
#### /gulp/config.js
|
||||
|
||||
This file contains all the configuration for the gulp tasks. Editing
|
||||
this file should be enough for most needs.
|
||||
|
||||
### /less
|
||||
|
||||
This folder contains all SASS files and is compiled with the gulp sass task,
|
||||
which puts the output CSS into `/public/css`
|
||||
|
||||
### /public
|
||||
|
||||
This directory is maintained by gulp. All static assets get symlinked in here,
|
||||
and any built code
|
||||
|
||||
This folder should not be edited directly, or commited to git. It is
|
||||
removed everytime gulp initializes.
|
||||
|
||||
### /server
|
||||
|
||||
This directory contains all JavaScript that only has to do with running your
|
||||
server instance.
|
||||
|
||||
### /server/middleware
|
||||
|
||||
Generic middleware that do not belong to a specific service.
|
||||
|
||||
Should export a single middleware function, or a namespaced object of
|
||||
functions.
|
||||
|
||||
### /server/models
|
||||
|
||||
Shared models or models that do not belong to a specific service.
|
||||
|
||||
Should export the initialized model, ie:
|
||||
|
||||
```
|
||||
module.exports = new mongoose.Model('Model', ModelSchema);
|
||||
```
|
||||
|
||||
### /server/routes
|
||||
|
||||
Routes that do not belong to a server.
|
||||
|
||||
Should return an array of route arrays (`[ path, method, [ mw ] ]`)routes:
|
||||
|
||||
```
|
||||
module.exports = [
|
||||
[ '/', 'get', [ mw.one, mw.two ] ],
|
||||
[ '/page', 'get', [ mw.three, mw.four ] ],
|
||||
[ '/page2', 'get', [ mw.five, mw.eight ] ]
|
||||
];
|
||||
```
|
||||
|
||||
### /server/config
|
||||
|
||||
All configuration for the server. Most files export different options
|
||||
depending on the environment. A file might look like this:
|
||||
|
||||
```
|
||||
module.exports = {
|
||||
development: {},
|
||||
testing: {},
|
||||
staging: {},
|
||||
production: {}
|
||||
}[ENV];
|
||||
```
|
||||
|
||||
If you want defaults applied, you might do something like this:
|
||||
|
||||
```
|
||||
const defaults = {}
|
||||
|
||||
module.exports = Object.assign({}, defaults, {
|
||||
production: {}
|
||||
}[ENV]);
|
||||
```
|
||||
|
||||
In which case defaults will be used for all environments except
|
||||
`production`, in which the production settings will override
|
||||
any settings in defaults.
|
||||
|
||||
If you don't want to override, do this instead:
|
||||
|
||||
```
|
||||
const defaults = {}
|
||||
|
||||
module.exports = {
|
||||
production: {}
|
||||
}[ENV] || defaults);
|
||||
```
|
||||
|
||||
### /server/services
|
||||
|
||||
Instead of splitting models, middlewares and routes into three seperate
|
||||
folders, we create service directories that contain files relating
|
||||
to the same functionality.
|
||||
|
||||
### /server/templates
|
||||
|
||||
Marko templates.
|
||||
|
||||
### /src
|
||||
|
||||
This contains all isomorphic and pure browser JavaScript.
|
||||
|
||||
Browserify bundles these files (entry point is `app.js`) and outputs
|
||||
`public/app.js`.
|
||||
|
||||
### /static
|
||||
|
||||
This contains all static content. All files in here are symlinked into
|
||||
`/public`, while retaining its relative path. IE `/static/fonts/font.otf` gets
|
||||
symlinked to `/public/fonts/font.otf`.
|
||||
|
||||
The only special files are robots.txt. In all ENV besides production
|
||||
`robots.txt` will be symlinked, in production environment
|
||||
`robots-production.txt` will be used instead.
|
||||
47
bin/create-roles.js
Executable file
47
bin/create-roles.js
Executable file
@ -0,0 +1,47 @@
|
||||
#!/bin/env node
|
||||
|
||||
'use strict';
|
||||
|
||||
const p = require('path');
|
||||
const chalk = require('chalk');
|
||||
|
||||
const MongoClient = require('mongodb').MongoClient;
|
||||
|
||||
const successPrefix = '[' + chalk.green('SUCCESS') + '] ';
|
||||
const errorPrefix = '[' + chalk.red('ERROR') + '] ';
|
||||
global.PWD = p.dirname(__dirname);
|
||||
global.ENV = process.env.NODE_ENV || 'development';
|
||||
|
||||
console.log(PWD);
|
||||
function _mongo(collection, cb) {
|
||||
const mongoConfig = require(p.join(PWD, 'server/config/mongo'));
|
||||
|
||||
MongoClient.connect(mongoConfig.uri, function (err, db) {
|
||||
if (err) {
|
||||
console.error(errorPrefix);
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
}
|
||||
cb(db.collection(collection), db);
|
||||
});
|
||||
}
|
||||
|
||||
function createRoles(roles) {
|
||||
const roleNames = (roles ? roles.split(',') : [ 'user', 'admin' ]);
|
||||
|
||||
_mongo('roles', function (roles, db) {
|
||||
roles.insert(roleNames.map(function (role) { return { name: role }; }), function (err) {
|
||||
db.close();
|
||||
if (err) {
|
||||
console.error(errorPrefix);
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
} else {
|
||||
console.log(successPrefix + 'Created roles: ' + roleNames.join(', '));
|
||||
process.exit(0);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
createRoles();
|
||||
98
bin/create-user.js
Executable file
98
bin/create-user.js
Executable file
@ -0,0 +1,98 @@
|
||||
#!/bin/env node
|
||||
|
||||
'use strict';
|
||||
|
||||
const p = require('path');
|
||||
const crypto = require('crypto');
|
||||
|
||||
const chalk = require('chalk');
|
||||
|
||||
const MongoClient = require('mongodb').MongoClient;
|
||||
|
||||
const successPrefix = '[' + chalk.green('SUCCESS') + '] ';
|
||||
const errorPrefix = '[' + chalk.red('ERROR') + '] ';
|
||||
|
||||
global.PWD = p.dirname(__dirname);
|
||||
global.ENV = process.env.NODE_ENV || 'development';
|
||||
|
||||
function _mongo(collection, cb) {
|
||||
const mongoConfig = require(p.join(PWD, 'server/config/mongo'));
|
||||
|
||||
MongoClient.connect(mongoConfig.uri, function (err, db) {
|
||||
if (err) {
|
||||
console.error(errorPrefix);
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
}
|
||||
cb(db.collection(collection), db);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
const SALT_LENGTH = 16;
|
||||
|
||||
function _hash(password) {
|
||||
// generate salt
|
||||
password = password.trim();
|
||||
const chars = '0123456789abcdefghijklmnopqurstuvwxyz';
|
||||
let salt = '';
|
||||
for (let i = 0; i < SALT_LENGTH; i++) {
|
||||
const j = Math.floor(Math.random() * chars.length);
|
||||
salt += chars[j];
|
||||
}
|
||||
|
||||
// hash the password
|
||||
const passwordHash = crypto.createHash('sha512').update(salt + password).digest('hex');
|
||||
|
||||
// entangle the hashed password with the salt and save to the model
|
||||
return _entangle(passwordHash, salt, password.length);
|
||||
}
|
||||
|
||||
function _entangle(string, salt, t) {
|
||||
string = salt + string;
|
||||
const length = string.length;
|
||||
|
||||
const arr = string.split('');
|
||||
for (let i = 0; i < salt.length; i++) {
|
||||
const num = ((i + 1) * t) % length;
|
||||
const tmp = arr[i];
|
||||
arr[i] = arr[num];
|
||||
arr[num] = tmp;
|
||||
}
|
||||
|
||||
return arr.join('');
|
||||
}
|
||||
|
||||
function createUser(email, password, roles) {
|
||||
if (!email || !password) {
|
||||
console.log('Usage: bin/create-user.js [email] [password] [?roles]');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
|
||||
const user = {
|
||||
email: email,
|
||||
local: {
|
||||
password: _hash(password)
|
||||
},
|
||||
roles: roles ? roles.split(',') : [ 'admin' ],
|
||||
isVerified: true,
|
||||
dateCreated: new Date()
|
||||
};
|
||||
|
||||
_mongo('users', function (users, db) {
|
||||
users.insert(user, function (err, user) {
|
||||
db.close();
|
||||
if (err) {
|
||||
console.error(errorPrefix);
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
} else {
|
||||
console.log(successPrefix + 'Saved user: ' + user.ops[0].email);
|
||||
process.exit(0);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
createUser.apply(null, process.argv.slice(2));
|
||||
1
doc/ideas.md
Normal file
1
doc/ideas.md
Normal file
@ -0,0 +1 @@
|
||||
+ timer in document title (so you can see it in the browser tab)
|
||||
22
gulp/LICENSE
Normal file
22
gulp/LICENSE
Normal file
@ -0,0 +1,22 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016 Linus Miller
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
23
gulp/README.md
Normal file
23
gulp/README.md
Normal file
@ -0,0 +1,23 @@
|
||||
# gulp
|
||||
|
||||
## Tasks
|
||||
|
||||
### browserify.js
|
||||
|
||||
### browser-sync.js
|
||||
|
||||
### fonts.js
|
||||
|
||||
### nodemon.js
|
||||
|
||||
### raster.js
|
||||
|
||||
### sass.js
|
||||
|
||||
### static.js
|
||||
|
||||
### svg.js
|
||||
|
||||
### watch.js
|
||||
|
||||
### wipe.js
|
||||
121
gulp/config.js
Normal file
121
gulp/config.js
Normal file
@ -0,0 +1,121 @@
|
||||
'use strict'
|
||||
|
||||
const p = require('path')
|
||||
const fs = require('fs')
|
||||
|
||||
const port = fs.existsSync(p.join(PWD, 'server/config/port.js')) ?
|
||||
require(p.join(PWD, 'server/config/port')) : 10000
|
||||
|
||||
module.exports = {
|
||||
browserSync: {
|
||||
browser: null,
|
||||
ghostMode: false,
|
||||
proxy: 'localhost:' + port,
|
||||
port: 1337,
|
||||
ui: {
|
||||
port: 1338
|
||||
},
|
||||
files: [
|
||||
p.join(PWD, 'public/css/**/*.css'),
|
||||
p.join(PWD, 'public/js/**/*.js'),
|
||||
]
|
||||
},
|
||||
|
||||
browserify: {
|
||||
suffix: true,
|
||||
debug: true,
|
||||
dest: p.join(PWD, 'public/js'),
|
||||
extensions: [ '.js', '.jsx', '.json' ],
|
||||
// PWD/node_modules is added so symlinked ridge does not break. used to
|
||||
// work without this in browserify 9
|
||||
paths: [ p.join(PWD, 'node_modules'), p.join(PWD, 'modules') ],
|
||||
// outputs only need to be used if output names are different from entries.
|
||||
// Otherwise the entries array is copied into the outputs array.
|
||||
entries: [
|
||||
'src/app.js'
|
||||
],
|
||||
src: p.join(PWD, 'src')
|
||||
},
|
||||
|
||||
less: {
|
||||
suffix: true,
|
||||
src: [
|
||||
p.join(PWD, 'less/*.less'),
|
||||
],
|
||||
dest: p.join(PWD, 'public/css'),
|
||||
autoprefixer: {
|
||||
browsers: [
|
||||
'safari >= 5',
|
||||
'ie >= 8',
|
||||
'ios >= 6',
|
||||
'opera >= 12.1',
|
||||
'firefox >= 17',
|
||||
'chrome >= 30',
|
||||
'android >= 4'
|
||||
],
|
||||
cascade: true
|
||||
},
|
||||
functions: require('./less/functions'),
|
||||
options: {
|
||||
paths: [
|
||||
p.join(PWD, 'node_modules/spineless/less')
|
||||
],
|
||||
}
|
||||
},
|
||||
|
||||
nodemon: {
|
||||
ext: 'js, marko',
|
||||
watch: [
|
||||
'server',
|
||||
'src',
|
||||
],
|
||||
ignore: [
|
||||
'*.marko.js',
|
||||
// only watch marko files in src folders.
|
||||
'src/**/*.js'
|
||||
],
|
||||
script: fs.existsSync(p.join(PWD, 'package.json')) ? require(p.join(PWD, 'package.json')).main.replace(/^\./, PWD) : 'server/server.js',
|
||||
env: {
|
||||
BABEL_ENV: 'server',
|
||||
// what port you actually put into the browser... when using browser-sync
|
||||
// this will differ from the internal port used by express
|
||||
EXTERNAL_PORT: 1337,
|
||||
// needed to force debug to use colors despite tty.istty(0) being false,
|
||||
// which it is in a child process
|
||||
DEBUG_COLORS: true,
|
||||
// needed to force chalk to use when running gulp nodemon tasks.
|
||||
FORCE_COLOR: true,
|
||||
PWD,
|
||||
NODE_ENV: ENV,
|
||||
DEBUG: 'mypaper:*'
|
||||
}
|
||||
},
|
||||
|
||||
raster: {
|
||||
src: p.join(PWD, 'img/raster/**/*.{png,gif,jpg}'),
|
||||
dest: p.join(PWD, 'public/img')
|
||||
},
|
||||
|
||||
static: {
|
||||
src: p.join(PWD, 'static/**/*'),
|
||||
dest: p.join(PWD, 'public')
|
||||
},
|
||||
|
||||
svg: {
|
||||
src: p.join(PWD, 'img/svg/**/*.svg'),
|
||||
dest: p.join(PWD, 'public/img')
|
||||
},
|
||||
|
||||
tasks: {
|
||||
development: [ 'wipe', [ 'browserify', 'less', 'raster', 'static', 'svg' ], [ 'nodemon' ], [ 'watch', 'browser-sync' ] ],
|
||||
production: [ 'wipe', [ 'browserify', 'less', 'raster', 'static', 'svg' ]]
|
||||
}[ENV],
|
||||
|
||||
watch: {
|
||||
less: p.join(PWD, 'less/**/*.less')
|
||||
},
|
||||
|
||||
wipe: {
|
||||
src: [ p.join(PWD, 'public') ]
|
||||
}
|
||||
}
|
||||
30
gulp/index.js
Normal file
30
gulp/index.js
Normal file
@ -0,0 +1,30 @@
|
||||
'use strict'
|
||||
|
||||
// modules > 3rd party
|
||||
const _ = require('lodash')
|
||||
|
||||
// modules > gulp
|
||||
const gulp = require('gulp')
|
||||
|
||||
global.ENV = process.env.NODE_ENV || 'development'
|
||||
global.PWD = process.env.PWD
|
||||
|
||||
const args = process.argv.slice(2)
|
||||
|
||||
// use tasks from arguments list if present, otherwise use tasks from
|
||||
// configuration (environment specific)
|
||||
let tasks = args.length > 0 ? args : require('./config').tasks
|
||||
|
||||
// only require used tasks
|
||||
_.flatten(tasks, true).forEach((task) => require('./tasks/' + task))
|
||||
|
||||
tasks = tasks.map((task) => {
|
||||
if (Array.isArray(task)) {
|
||||
return gulp.parallel(...task)
|
||||
}
|
||||
|
||||
return task
|
||||
})
|
||||
|
||||
// set up the 'default' task to use runSequence to run all tasks
|
||||
gulp.task('default', gulp.series(...tasks))
|
||||
42
gulp/less/functions.js
Normal file
42
gulp/less/functions.js
Normal file
@ -0,0 +1,42 @@
|
||||
'use strict'
|
||||
|
||||
const p = require('path')
|
||||
|
||||
module.exports = {
|
||||
'img-url': function (value) {
|
||||
const tree = this.context.pluginManager.less.tree
|
||||
|
||||
return new tree.URL(new tree.Quoted('"', p.join('/img', value.value)), this.index, this.currentFileInfo)
|
||||
},
|
||||
|
||||
rem: function (value, context) {
|
||||
const tree = this.context.pluginManager.less.tree
|
||||
|
||||
if (value.type === 'Expression')
|
||||
return new tree.Expression(value.value.map((value) => {
|
||||
if (value.unit.backupUnit === 'px')
|
||||
return new tree.Dimension(value.value / 14, 'rem')
|
||||
|
||||
return new tree.Dimension(value.value, 'rem')
|
||||
}))
|
||||
|
||||
return new tree.Dimension(value.value / 14, 'rem')
|
||||
},
|
||||
|
||||
px: function (value) {
|
||||
const tree = this.context.pluginManager.less.tree
|
||||
|
||||
if (value.type === 'Expression')
|
||||
return new tree.Expression(value.value.map((value) => {
|
||||
if (value.unit.backupUnit === 'px')
|
||||
return value
|
||||
|
||||
return new tree.Dimension(value.value * 14, 'px')
|
||||
}))
|
||||
|
||||
if (value.unit.backupUnit === 'px')
|
||||
return value
|
||||
|
||||
return new tree.Dimension(value.value * 14, 'px')
|
||||
}
|
||||
}
|
||||
9
gulp/tasks/browser-sync.js
Normal file
9
gulp/tasks/browser-sync.js
Normal file
@ -0,0 +1,9 @@
|
||||
'use strict'
|
||||
|
||||
const gulp = require('gulp')
|
||||
const browserSync = require('browser-sync')
|
||||
|
||||
const TASK_NAME = 'browser-sync'
|
||||
const config = require('../config').browserSync
|
||||
|
||||
gulp.task(TASK_NAME, () => browserSync(config))
|
||||
126
gulp/tasks/browserify.js
Normal file
126
gulp/tasks/browserify.js
Normal file
@ -0,0 +1,126 @@
|
||||
'use strict'
|
||||
|
||||
// native modules
|
||||
const p = require('path')
|
||||
const fs = require('fs')
|
||||
|
||||
// 3rd party modules
|
||||
const _ = require('lodash')
|
||||
const browserify = require('browserify')
|
||||
const buffer = require('vinyl-buffer')
|
||||
const chalk = require('chalk')
|
||||
const es = require('event-stream')
|
||||
const gulp = require('gulp')
|
||||
const gutil = require('gulp-util')
|
||||
const rename = require('gulp-rename')
|
||||
const source = require('vinyl-source-stream')
|
||||
const sourcemaps = require('gulp-sourcemaps')
|
||||
const uglify = require('gulp-uglify')
|
||||
const watchify = require('watchify')
|
||||
const babelify = require('babelify')
|
||||
const markoify = require('markoify')
|
||||
|
||||
require('marko/compiler').defaultOptions.writeToDisk = false
|
||||
|
||||
process.env.BABEL_ENV = 'client'
|
||||
|
||||
const config = require('../config').browserify
|
||||
const suffix = config.suffix && ENV === 'production' ? '-' + Date.now().toString(16) : ''
|
||||
|
||||
const errorHandler = require('../util/error-handler')
|
||||
|
||||
function formatError(err) {
|
||||
err.task = 'browserify'
|
||||
|
||||
const matchFileName = err.message.match(/\/[^\s:]*/)
|
||||
|
||||
if (matchFileName) {
|
||||
let fileName = matchFileName[0]
|
||||
|
||||
if (fileName.indexOf(PWD) > -1)
|
||||
fileName = './' + p.relative(PWD, fileName)
|
||||
|
||||
fileName = chalk.yellow(fileName)
|
||||
|
||||
const matchNumbers = err.message.match(/ \((.*)\)/)
|
||||
|
||||
if (matchNumbers) {
|
||||
const arr = matchNumbers[1].split(':')
|
||||
|
||||
err.message = err.message.slice(0, matchNumbers.index) +
|
||||
' at line ' + arr[0] + ', col ' + arr[1]
|
||||
}
|
||||
|
||||
err.message = err.message.split(matchFileName[0]).join(fileName)
|
||||
}
|
||||
|
||||
err.message = err.message.split(/:\s*/).join('\n')
|
||||
|
||||
errorHandler.call(this, err)
|
||||
}
|
||||
|
||||
if (typeof config.entries === 'string') config.entries = [config.entries]
|
||||
|
||||
const TASK_NAME = 'browserify'
|
||||
gulp.task(TASK_NAME, (cb) => {
|
||||
cb = _.after(config.entries.length, cb)
|
||||
|
||||
if (config.suffix)
|
||||
fs.writeFileSync(config.dest + '.json', JSON.stringify({ suffix }))
|
||||
|
||||
const tasks = config.entries.map((entry, index) => {
|
||||
function bundler(bundle) {
|
||||
let outputPath = config.outputs && config.outputs[index] || entry
|
||||
|
||||
let pipe = bundle.bundle()
|
||||
.on('error', formatError)
|
||||
.pipe(source(outputPath))
|
||||
.pipe(rename((path) => {
|
||||
if (outputPath === entry) {
|
||||
// remove first level of directores, eg `src/` or `src-widget/`
|
||||
path.dirname = path.dirname.replace(/^src[^\/]*\/?/, '')
|
||||
|
||||
outputPath = path.dirname + path.basename + (path.suffix || '') + path.extname
|
||||
}
|
||||
|
||||
path.basename += suffix
|
||||
|
||||
return path
|
||||
}))
|
||||
.on('end', (...args) => {
|
||||
gutil.log(chalk.cyan(TASK_NAME) + ' wrote ' + chalk.magenta(outputPath) + '.')
|
||||
|
||||
cb()
|
||||
})
|
||||
|
||||
if (ENV !== 'development')
|
||||
pipe = pipe.pipe(buffer())
|
||||
.pipe(sourcemaps.init({ loadMaps: true }))
|
||||
.pipe(uglify())
|
||||
.pipe(sourcemaps.write('./'))
|
||||
|
||||
return pipe.pipe(gulp.dest(config.dest))
|
||||
}
|
||||
|
||||
const bundle = browserify(_.defaults({
|
||||
entries: [ entry ],
|
||||
cache: {},
|
||||
packageCache: {}
|
||||
}, config))
|
||||
|
||||
if (ENV !== 'production')
|
||||
bundle.plugin(watchify, {
|
||||
ignoreWatch: [ '/public/**', '**/node_modules/**', '**/bower_components/**']
|
||||
})
|
||||
|
||||
bundle.transform(babelify)
|
||||
bundle.transform(markoify)
|
||||
|
||||
bundle.on('update', () => bundler(bundle))
|
||||
|
||||
return bundler(bundle)
|
||||
})
|
||||
|
||||
// return a single stream from all bundle streams
|
||||
return es.merge.apply(null, tasks)
|
||||
})
|
||||
49
gulp/tasks/less.js
Normal file
49
gulp/tasks/less.js
Normal file
@ -0,0 +1,49 @@
|
||||
'use strict'
|
||||
|
||||
const fs = require('fs')
|
||||
const gulp = require('gulp')
|
||||
const _less = require('less')
|
||||
const less = require('gulp-less')
|
||||
const sourcemaps = require('gulp-sourcemaps')
|
||||
const rename = require('gulp-rename')
|
||||
const mkdirp = require('mkdirp')
|
||||
|
||||
const postcss = require('gulp-postcss')
|
||||
const autoprefixer = require('autoprefixer')
|
||||
|
||||
const config = require('../config').less
|
||||
|
||||
|
||||
const errorHandler = require('../util/error-handler')
|
||||
|
||||
_less.functions.functionRegistry.addMultiple(config.functions)
|
||||
|
||||
const processors = [
|
||||
autoprefixer(config.autoprefixer),
|
||||
]
|
||||
|
||||
if (ENV === 'production') {
|
||||
const csswring = require('csswring')
|
||||
|
||||
processors.push(csswring(config.csswring))
|
||||
}
|
||||
|
||||
const suffix = config.suffix && ENV === 'production' ? '-' + Date.now().toString(16) : undefined
|
||||
|
||||
gulp.task('less', function () {
|
||||
mkdirp(config.dest)
|
||||
|
||||
if (suffix)
|
||||
fs.writeFile(config.dest + '.json', JSON.stringify({ suffix }))
|
||||
|
||||
let pipe = gulp.src(config.src)
|
||||
.pipe(sourcemaps.init())
|
||||
.pipe(less(config.options).on('error', errorHandler))
|
||||
.pipe(postcss(processors))
|
||||
|
||||
if (suffix)
|
||||
pipe = pipe.pipe(rename({ suffix }))
|
||||
|
||||
return pipe.pipe(sourcemaps.write('./maps'))
|
||||
.pipe(gulp.dest(config.dest))
|
||||
})
|
||||
35
gulp/tasks/nodemon.js
Normal file
35
gulp/tasks/nodemon.js
Normal file
@ -0,0 +1,35 @@
|
||||
'use strict'
|
||||
|
||||
const _ = require('lodash')
|
||||
|
||||
const gulp = require('gulp')
|
||||
const browserSync = require('browser-sync')
|
||||
const nodemon = require('nodemon')
|
||||
|
||||
const config = require('../config').nodemon
|
||||
|
||||
// for some reason, this was needed somewhere before
|
||||
//process.stdout.isTTY = true
|
||||
|
||||
gulp.task('nodemon', (cb) => {
|
||||
nodemon(_.defaults({ stdout: false }, config))
|
||||
.on('log', function (log) {
|
||||
console.log(log.colour)
|
||||
})
|
||||
.on('readable', function () {
|
||||
this.stdout.pipe(process.stdout)
|
||||
this.stderr.pipe(process.stderr)
|
||||
|
||||
this.stdout.on('data', (chunk) => {
|
||||
if (/HTTP server (running|listening|started) on|at port/i.test(chunk.toString('utf-8').trim())) {
|
||||
if (cb)
|
||||
cb()
|
||||
|
||||
cb = null
|
||||
|
||||
if (browserSync.active)
|
||||
browserSync.reload()
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
27
gulp/tasks/raster.js
Normal file
27
gulp/tasks/raster.js
Normal file
@ -0,0 +1,27 @@
|
||||
'use strict'
|
||||
|
||||
// modules > native
|
||||
const p = require('path')
|
||||
|
||||
// modules > 3rd party
|
||||
const chalk = require('chalk')
|
||||
|
||||
// modules > gulp:utilities
|
||||
const gulp = require('gulp')
|
||||
const gutil = require('gulp-util')
|
||||
|
||||
const TASK_NAME = 'raster'
|
||||
const config = require('../config').raster
|
||||
|
||||
gulp.task(TASK_NAME, () => {
|
||||
let count = 0
|
||||
|
||||
return gulp.src(config.src)
|
||||
.pipe(gulp.symlink((file) => {
|
||||
count++
|
||||
return p.join(config.dest)
|
||||
}, { log: false }))
|
||||
.on('end', () => {
|
||||
gutil.log(chalk.cyan(TASK_NAME) + ' done symlinking ' + chalk.bold.blue(count) + ' files')
|
||||
})
|
||||
})
|
||||
39
gulp/tasks/static.js
Normal file
39
gulp/tasks/static.js
Normal file
@ -0,0 +1,39 @@
|
||||
'use strict'
|
||||
|
||||
// modules > native
|
||||
const p = require('path')
|
||||
const fs = require('fs')
|
||||
|
||||
// modules > 3rd party
|
||||
const chalk = require('chalk')
|
||||
|
||||
// modules > gulp:utilities
|
||||
const gulp = require('gulp')
|
||||
const through = require('through2')
|
||||
const gutil = require('gulp-util')
|
||||
|
||||
const TASK_NAME = 'static'
|
||||
const config = require('../config').static
|
||||
|
||||
gulp.task(TASK_NAME, () => {
|
||||
let count = 0
|
||||
|
||||
return gulp.src(config.src)
|
||||
// we use through so that we can skip directories
|
||||
// we skip directories because we want to merge file structure
|
||||
.pipe(through.obj((file, enc, callback) => {
|
||||
fs.stat(file.path, (err, stats) => {
|
||||
if (stats.isDirectory())
|
||||
file = null
|
||||
|
||||
callback(null, file)
|
||||
})
|
||||
}))
|
||||
.pipe(gulp.symlink((file) => {
|
||||
count++
|
||||
return p.join(config.dest)
|
||||
}))
|
||||
.on('end', () => {
|
||||
gutil.log(chalk.cyan(TASK_NAME) + ' done symlinking ' + chalk.bold.blue(count) + ' files')
|
||||
})
|
||||
})
|
||||
27
gulp/tasks/svg.js
Normal file
27
gulp/tasks/svg.js
Normal file
@ -0,0 +1,27 @@
|
||||
'use strict'
|
||||
|
||||
// modules > native
|
||||
const p = require('path')
|
||||
|
||||
// modules > 3rd party
|
||||
const chalk = require('chalk')
|
||||
|
||||
// modules > gulp:utilities
|
||||
const gulp = require('gulp')
|
||||
const gutil = require('gulp-util')
|
||||
|
||||
const TASK_NAME = 'svg'
|
||||
const config = require('../config').svg
|
||||
|
||||
gulp.task(TASK_NAME, () => {
|
||||
let count = 0
|
||||
|
||||
return gulp.src(config.src)
|
||||
.pipe(gulp.symlink((file) => {
|
||||
count++
|
||||
return p.join(config.dest)
|
||||
}))
|
||||
.on('end', () => {
|
||||
gutil.log(chalk.cyan(TASK_NAME) + ' done symlinking ' + chalk.bold.blue(count) + ' files')
|
||||
})
|
||||
})
|
||||
16
gulp/tasks/watch.js
Normal file
16
gulp/tasks/watch.js
Normal file
@ -0,0 +1,16 @@
|
||||
'use strict'
|
||||
|
||||
// modules > 3rd party
|
||||
const _ = require('lodash')
|
||||
|
||||
// modules > gulp:utilities
|
||||
const gulp = require('gulp')
|
||||
|
||||
const TASK_NAME = 'watch'
|
||||
const config = require('../config').watch
|
||||
|
||||
gulp.task(TASK_NAME, () => {
|
||||
_.forIn(config, (value, key) => {
|
||||
gulp.watch(value, gulp.series(key))
|
||||
})
|
||||
})
|
||||
44
gulp/tasks/wipe.js
Normal file
44
gulp/tasks/wipe.js
Normal file
@ -0,0 +1,44 @@
|
||||
'use strict'
|
||||
|
||||
// native modules
|
||||
const fs = require('fs')
|
||||
|
||||
// 3rd party modules
|
||||
const mkdirp = require('mkdirp')
|
||||
const chalk = require('chalk')
|
||||
const gulp = require('gulp')
|
||||
const gutil = require('gulp-util')
|
||||
const rimraf = require('rimraf')
|
||||
|
||||
const TASK_NAME = 'wipe'
|
||||
|
||||
const config = require('../config').wipe
|
||||
|
||||
gulp.task(TASK_NAME, (cb) => {
|
||||
let count = 0
|
||||
config.src.forEach((folder) => {
|
||||
fs.exists(folder, (exists) => {
|
||||
if (exists) {
|
||||
rimraf(folder, (err) => {
|
||||
if (err) throw err
|
||||
gutil.log('Folder ' + chalk.magenta(folder) + ' removed')
|
||||
|
||||
mkdirp.sync(folder)
|
||||
|
||||
count++
|
||||
if (count >= config.src.length) {
|
||||
cb()
|
||||
}
|
||||
})
|
||||
} else {
|
||||
count++
|
||||
|
||||
mkdirp.sync(folder)
|
||||
|
||||
if (count >= config.src.length) {
|
||||
cb()
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
16
gulp/util/error-handler.js
Normal file
16
gulp/util/error-handler.js
Normal file
@ -0,0 +1,16 @@
|
||||
'use strict'
|
||||
|
||||
const util = require('gulp-util')
|
||||
const chalk = require('chalk')
|
||||
|
||||
module.exports = function (err) {
|
||||
util.log(chalk.red('ERROR') + (err.task ? ' in task \'' + chalk.cyan(err.task) : ' in plugin \'' + chalk.cyan(err.plugin)) + '\'')
|
||||
|
||||
console.log('\n' + err.message.trim() + '\n')
|
||||
|
||||
// needed for error handling not thrown by gulp-watch
|
||||
if (this.emit) {
|
||||
// Keep gulp from hanging on this task
|
||||
this.emit('end')
|
||||
}
|
||||
}
|
||||
7
gulpfile.js
Normal file
7
gulpfile.js
Normal file
@ -0,0 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
require('./gulp');
|
||||
|
||||
process.on('SIGINT', () => {
|
||||
process.exit(0);
|
||||
});
|
||||
0
less/main.less
Normal file
0
less/main.less
Normal file
79
package.json
Normal file
79
package.json
Normal file
@ -0,0 +1,79 @@
|
||||
{
|
||||
"name": "budbilar",
|
||||
"version": "0.0.1",
|
||||
"description": "När du vill få något budat till rätt pris.",
|
||||
"main": "server/server.js",
|
||||
"private": true,
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git@gitlab.thecodebureau.com:axel/budbilar.git"
|
||||
},
|
||||
"scripts": {
|
||||
"gulp": "gulp",
|
||||
"gulp:production": "NODE_ENV=production gulp",
|
||||
"gulp:development": "NODE_ENV=development gulp",
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"start": "node server/server.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"babel-preset-es2015-node6": "^0.3.0",
|
||||
"babel-register": "^6.11.6",
|
||||
"body-parser": "^1.15.2",
|
||||
"chalk": "^1.1.3",
|
||||
"connect-redis": "^3.1.0",
|
||||
"cookie-parser": "^1.4.3",
|
||||
"dollr": "0.0.7",
|
||||
"express": "^4.14.0",
|
||||
"express-module-membership": "github:thecodebureau/express-module-membership",
|
||||
"express-service-errors": "github:thecodebureau/express-service-errors",
|
||||
"express-session": "^1.14.0",
|
||||
"lodash": "^4.15.0",
|
||||
"marko": "^3.7.2",
|
||||
"marko-widgets": "^6.3.3",
|
||||
"mongoose": "^4.5.8",
|
||||
"mongopot": "github:lohfu/mongopot",
|
||||
"morgan": "^1.7.0",
|
||||
"nodemailer": "^2.5.0",
|
||||
"passport": "^0.3.2",
|
||||
"passport-local": "^1.0.0",
|
||||
"require-dir": "^0.3.0",
|
||||
"warepot": "github:lohfu/warepot"
|
||||
},
|
||||
"devDependencies": {
|
||||
"autoprefixer": "^6.4.0",
|
||||
"babel-plugin-add-module-exports": "^0.2.1",
|
||||
"babel-preset-es2015": "^6.13.2",
|
||||
"babel-preset-react": "^6.11.1",
|
||||
"babelify": "^7.3.0",
|
||||
"browser-sync": "^2.14.0",
|
||||
"browserify": "^13.1.0",
|
||||
"chalk": "^1.1.3",
|
||||
"csswring": "^5.1.0",
|
||||
"eslint": "^3.3.0",
|
||||
"eslint-config-airbnb": "^10.0.1",
|
||||
"eslint-config-airbnb-base": "^5.0.2",
|
||||
"eslint-plugin-import": "^1.13.0",
|
||||
"eslint-plugin-jsx-a11y": "^2.1.0",
|
||||
"eslint-plugin-react": "^6.0.0",
|
||||
"event-stream": "^3.3.4",
|
||||
"gulp": "github:gulpjs/gulp#4.0",
|
||||
"gulp-less": "^3.1.0",
|
||||
"gulp-postcss": "^6.1.1",
|
||||
"gulp-rename": "^1.2.2",
|
||||
"gulp-sourcemaps": "^1.6.0",
|
||||
"gulp-svgmin": "^1.2.2",
|
||||
"gulp-uglify": "^2.0.0",
|
||||
"gulp-util": "^3.0.7",
|
||||
"marko": "^3.9.4",
|
||||
"markoify": "^2.1.1",
|
||||
"mkdirp": "^0.5.1",
|
||||
"nodemon": "^1.10.0",
|
||||
"postcss": "^5.1.2",
|
||||
"pretty-hrtime": "^1.0.2",
|
||||
"rimraf": "^2.5.4",
|
||||
"through2": "^2.0.1",
|
||||
"vinyl-buffer": "^1.0.0",
|
||||
"vinyl-source-stream": "^1.1.0",
|
||||
"watchify": "^3.7.0"
|
||||
}
|
||||
}
|
||||
7
server/config/dir.js
Normal file
7
server/config/dir.js
Normal file
@ -0,0 +1,7 @@
|
||||
'use strict'
|
||||
|
||||
const p = require('path')
|
||||
|
||||
module.exports = {
|
||||
static: p.join(PWD, 'public'),
|
||||
}
|
||||
58
server/config/error-handler.js
Normal file
58
server/config/error-handler.js
Normal file
@ -0,0 +1,58 @@
|
||||
'use strict'
|
||||
|
||||
const _ = require('lodash')
|
||||
|
||||
const errorTemplate = require('../templates/error.marko')
|
||||
|
||||
const defaults = {
|
||||
post: (req, res, next) => {
|
||||
res.template = errorTemplate
|
||||
|
||||
next()
|
||||
},
|
||||
|
||||
mystify: {
|
||||
properties: ['errors', 'message', 'name', 'status', 'statusText']
|
||||
},
|
||||
|
||||
log: {
|
||||
// if database = true there has to be a mongoose model name ErrorModel
|
||||
ignore: [],
|
||||
}
|
||||
}
|
||||
|
||||
const ErrorModel = require('mongopot/models/error')
|
||||
|
||||
function store(error) {
|
||||
ErrorModel.create(error, (err) => {
|
||||
// TODO handle errors in error handler better
|
||||
if (err) {
|
||||
console.error('ERROR WRITING TO DATABASE')
|
||||
console.error(err)
|
||||
console.log(err.errors)
|
||||
console.error('ORIGINAL ERROR')
|
||||
console.error(error)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = _.merge(defaults, {
|
||||
development: {
|
||||
log: {
|
||||
store: store,
|
||||
console: true,
|
||||
}
|
||||
},
|
||||
testing: {
|
||||
log: {
|
||||
store: false,
|
||||
console: false,
|
||||
},
|
||||
},
|
||||
production: {
|
||||
log: {
|
||||
store: store,
|
||||
console: false,
|
||||
}
|
||||
},
|
||||
}[ENV])
|
||||
4
server/config/globals.js
Normal file
4
server/config/globals.js
Normal file
@ -0,0 +1,4 @@
|
||||
'use strict'
|
||||
|
||||
global.LOGIN_USER = 'lohfu@lohfu.io'
|
||||
//global.LOGIN_USER = 'zarac@zarac.se'
|
||||
69
server/config/membership.js
Normal file
69
server/config/membership.js
Normal file
@ -0,0 +1,69 @@
|
||||
'use strict'
|
||||
|
||||
const config = {
|
||||
smtp: require('./smtp'),
|
||||
site: require('./site')
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
invite: {
|
||||
from: config.site.title + ' Robot <' + config.site.emails.robot + '>',
|
||||
subject: 'You have been invited to ' + config.site.title
|
||||
},
|
||||
|
||||
paths: {
|
||||
register: '/register',
|
||||
login: '/login',
|
||||
forgotPassword: '/forgot-password',
|
||||
updatePassword: '/update-password'
|
||||
},
|
||||
|
||||
remember: {
|
||||
// if expires is defined, it will be used. otherwise maxage
|
||||
expires: new Date('2038-01-19T03:14:07.000Z'),
|
||||
//expires: Date.now() - 1,
|
||||
maxAge: 30 * 24 * 60 * 60 * 1000,
|
||||
},
|
||||
|
||||
messages: {
|
||||
login: {
|
||||
notLocal: 'Account requires external login.',
|
||||
wrongPassword: 'Wrong password.',
|
||||
noLocalUser: 'No user registered with that email.',
|
||||
noExternalUser: 'The account is not connected to this website.',
|
||||
externalLoginFailed: 'External login failed.',
|
||||
unverified: 'This account has not been verified.',
|
||||
banned: 'User is banned.',
|
||||
blocked: 'User is blocked due to too many login attempts.'
|
||||
},
|
||||
|
||||
register: {
|
||||
notAuthorized: 'The email is not authorized to create an account.',
|
||||
duplicateEmail: 'The email has already been registered.'
|
||||
}
|
||||
},
|
||||
|
||||
passport: {
|
||||
local: {
|
||||
usernameField: 'email'
|
||||
},
|
||||
|
||||
scope: [ 'email' ],
|
||||
|
||||
//providers: {
|
||||
// facebook: {
|
||||
// clientID: 'change-this-fool',
|
||||
// clientSecret: 'change-this-fool',
|
||||
// callbackURL: p.join(config.site.domain, '/auth/facebook/callback'),
|
||||
// passReqToCallback: true
|
||||
// },
|
||||
|
||||
// google: {
|
||||
// clientID: 'change-this-fool',
|
||||
// clientSecret: 'change-this-fool',
|
||||
// callbackURL: p.join(config.site.domain, '/auth/google/callback'),
|
||||
// passReqToCallback: true
|
||||
// }
|
||||
//}
|
||||
}
|
||||
}
|
||||
11
server/config/mongo.js
Normal file
11
server/config/mongo.js
Normal file
@ -0,0 +1,11 @@
|
||||
'use strict'
|
||||
|
||||
const defaults = {
|
||||
uri: 'mongodb://pomodoro-supreme:lets-work-our-asses-off-poop-poop@mongo.thecodebureau.com/pomodoro'
|
||||
}
|
||||
|
||||
module.exports = Object.assign(defaults, {
|
||||
production: {
|
||||
//uri: 'mongodb://pomodoro-supreme:lets-work-our-asses-off-poop-poop@localhost/pomodoro'
|
||||
}
|
||||
}[ENV])
|
||||
10
server/config/port.js
Normal file
10
server/config/port.js
Normal file
@ -0,0 +1,10 @@
|
||||
'use strict'
|
||||
|
||||
const basePort = 3050
|
||||
|
||||
module.exports = {
|
||||
development: basePort,
|
||||
testing: basePort + 1,
|
||||
staging: basePort + 2,
|
||||
production: basePort + 3
|
||||
}[ENV]
|
||||
37
server/config/session.js
Normal file
37
server/config/session.js
Normal file
@ -0,0 +1,37 @@
|
||||
'use strict'
|
||||
|
||||
const session = require('express-session')
|
||||
|
||||
let redisStore
|
||||
|
||||
const config = {
|
||||
secret: 'asdf1h918798&(*&ijh21kj4hk123j45h2k34jh52k3g45)thisisacompletelyrandomgeneratedstring...whatastrangecoincidence...',
|
||||
resave: false,
|
||||
saveUninitialized: true
|
||||
}
|
||||
|
||||
const redisConfig = {
|
||||
host: 'localhost',
|
||||
port: 6379
|
||||
}
|
||||
|
||||
if (ENV === 'production') {
|
||||
const RedisStore = require('connect-redis')(require('express-session'))
|
||||
|
||||
redisStore = new RedisStore(redisConfig)
|
||||
|
||||
redisStore.on('connect', function () {
|
||||
console.info('Redis connected succcessfully')
|
||||
})
|
||||
|
||||
redisStore.on('disconnect', function () {
|
||||
throw new Error('Unable to connect to redis. Has it been started?')
|
||||
})
|
||||
|
||||
config.store = redisStore
|
||||
} else {
|
||||
config.store = new session.MemoryStore()
|
||||
}
|
||||
|
||||
module.exports = config
|
||||
|
||||
51
server/config/site.js
Normal file
51
server/config/site.js
Normal file
@ -0,0 +1,51 @@
|
||||
'use strict'
|
||||
|
||||
const _ = require('lodash')
|
||||
|
||||
const domain = 'pomodoro.bitmill.co'
|
||||
|
||||
const defaults = {
|
||||
domain: domain,
|
||||
title: 'Pomodoro',
|
||||
name: 'pomodoro',
|
||||
protocol: 'http',
|
||||
get host() {
|
||||
return this.port ? this.hostname + ':' + this.port : this.hostname
|
||||
},
|
||||
get url() {
|
||||
return this.protocol + '://' + this.host + '/'
|
||||
},
|
||||
emails: {
|
||||
robot: 'no-reply@thecodebureau.com',
|
||||
info: 'info@thecodebureau.com',
|
||||
webmaster: 'webmaster@thecodebureau.com',
|
||||
order: 'info@thecodebureau.com'
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = _.merge(defaults, {
|
||||
development: {
|
||||
hostname: 'localhost',
|
||||
port: process.env.EXTERNAL_PORT || process.env.PORT || require('./port')
|
||||
},
|
||||
|
||||
testing: {
|
||||
hostname: 'localhost',
|
||||
port: process.env.PORT || require('./port')
|
||||
},
|
||||
|
||||
staging: {
|
||||
hostname: 'staging.' + domain
|
||||
},
|
||||
|
||||
production: {
|
||||
hostname: domain,
|
||||
protocol: 'https',
|
||||
emails: {
|
||||
robot: 'no-reply@bitmill.co',
|
||||
info: 'info@bitmill.co',
|
||||
webmaster: 'webmaster@bitmill.co',
|
||||
order: 'order@bitmill.co'
|
||||
}
|
||||
}
|
||||
}[ENV])
|
||||
15
server/config/smtp.js
Normal file
15
server/config/smtp.js
Normal file
@ -0,0 +1,15 @@
|
||||
'use strict'
|
||||
|
||||
const _ = require('lodash')
|
||||
|
||||
const defaults = {
|
||||
auth: {
|
||||
user: 'SMTP_Injection',
|
||||
// dev key
|
||||
pass: '2eec390c5b3f5d593c9f152179bf51e90b073784'
|
||||
},
|
||||
host: 'smtp.sparkpostmail.com',
|
||||
port: 587
|
||||
}
|
||||
|
||||
module.exports = _.merge(defaults, {}[ENV])
|
||||
11
server/pages.js
Normal file
11
server/pages.js
Normal file
@ -0,0 +1,11 @@
|
||||
'use strict'
|
||||
|
||||
const masterTemplate = require('../src/master.marko')
|
||||
|
||||
module.exports = [
|
||||
[ '/', 'get', (req, res, next) => {
|
||||
res.template = masterTemplate
|
||||
|
||||
next()
|
||||
} ],
|
||||
]
|
||||
130
server/server.js
Normal file
130
server/server.js
Normal file
@ -0,0 +1,130 @@
|
||||
'use strict'
|
||||
|
||||
require('babel-register')
|
||||
/*
|
||||
* The main file that sets up the Express instance and node
|
||||
*
|
||||
* @module server/server
|
||||
* @type {Express instance}
|
||||
*/
|
||||
|
||||
// set up some globals (these are also set in Epiphany if not already set)
|
||||
global.ENV = process.env.NODE_ENV || 'development'
|
||||
global.PWD = process.env.NODE_PWD || process.cwd()
|
||||
|
||||
// make node understand `*.marko` files
|
||||
require('marko/node-require').install()
|
||||
|
||||
// modules > native
|
||||
const p = require('path')
|
||||
|
||||
// modules > 3rd party
|
||||
const requireDir = require('require-dir')
|
||||
const _ = require('lodash')
|
||||
const express = require('express')
|
||||
const mongoose = require('mongoose')
|
||||
const passport = require('passport')
|
||||
|
||||
// modules > express middlewares
|
||||
const bodyParser = require('body-parser')
|
||||
const session = require('express-session')
|
||||
const cookieParser = require('cookie-parser')
|
||||
|
||||
// modules > 3rd party
|
||||
const chalk = require('chalk')
|
||||
|
||||
// modules > warepot
|
||||
const colorizeStack = require('warepot/util/colorize-stack')
|
||||
|
||||
// modules > locals
|
||||
const initRoutes = require('warepot/util/init-routes')
|
||||
|
||||
const config = requireDir('./config')
|
||||
|
||||
// make error output stack pretty
|
||||
process.on('uncaughtException', (err) => {
|
||||
console.error(chalk.red('UNCAUGHT EXCEPTION'))
|
||||
if (err.stack) {
|
||||
console.error(colorizeStack(err.stack))
|
||||
} else {
|
||||
console.error(err)
|
||||
}
|
||||
process.exit(1)
|
||||
})
|
||||
|
||||
const prewares = [
|
||||
express.static(config.dir.static, ENV === 'production' ? { maxAge: '1 year' } : null),
|
||||
bodyParser.json(),
|
||||
bodyParser.urlencoded({ extended: true }),
|
||||
cookieParser(),
|
||||
session(config.session),
|
||||
passport.initialize(),
|
||||
passport.session(),
|
||||
]
|
||||
|
||||
if (ENV === 'development') {
|
||||
// only log requests to console in development mode
|
||||
prewares.unshift(require('morgan')('dev'))
|
||||
prewares.push(require('express-module-membership/passport/automatic-login'))
|
||||
}
|
||||
|
||||
const postwares = [
|
||||
require('warepot/ensure-found'),
|
||||
// transform and log error
|
||||
require('warepot/error-handler'),
|
||||
// respond
|
||||
require('warepot/responder'),
|
||||
// handle error rendering error
|
||||
require('warepot/responder-error'),
|
||||
]
|
||||
|
||||
const routes = [
|
||||
...prewares,
|
||||
...require('./pages'),
|
||||
...require('./services/pomodoros/routes'),
|
||||
...require('express-module-membership/routes'),
|
||||
...postwares
|
||||
]
|
||||
|
||||
const server = express()
|
||||
|
||||
// get IP & whatnot from nginx proxy
|
||||
server.set('trust proxy', true)
|
||||
|
||||
server.locals.site = require('./config/site')
|
||||
|
||||
if (ENV === 'production') {
|
||||
Object.assign(server.locals, {
|
||||
js: require(p.join(PWD, 'public/js.json')),
|
||||
css: require(p.join(PWD, 'public/css.json'))
|
||||
})
|
||||
}
|
||||
|
||||
// override default response render method for
|
||||
// more convenient use with marko
|
||||
server.response.render = function (template) {
|
||||
const locals = _.extend({}, server.locals, this.locals)
|
||||
|
||||
template.render(locals, this)
|
||||
}
|
||||
|
||||
initRoutes(server, routes)
|
||||
|
||||
// mongoose mpromise library is being deprecated
|
||||
mongoose.Promise = Promise
|
||||
|
||||
// connect to mongodb
|
||||
mongoose.connect(config.mongo.uri, _.omit(config.mongo, 'uri'), (err) => {
|
||||
if (err) {
|
||||
console.error(err)
|
||||
process.exit()
|
||||
}
|
||||
|
||||
console.info('[' + chalk.cyan('INIT') + '] Mongoose is connected.')
|
||||
})
|
||||
|
||||
server.listen(config.port, () => {
|
||||
console.info('[' + chalk.cyan('INIT') + '] HTTP Server listening on port ' + chalk.magenta('%s') + ' (' + chalk.yellow('%s') + ')', config.port, ENV)
|
||||
})
|
||||
|
||||
module.exports = server
|
||||
72
server/services/pomodoros/middleware.js
Normal file
72
server/services/pomodoros/middleware.js
Normal file
@ -0,0 +1,72 @@
|
||||
'use strict'
|
||||
|
||||
const _ = require('lodash')
|
||||
|
||||
const formatQuery = require('warepot/format-query')
|
||||
|
||||
const Pomodoro = require('./model')
|
||||
|
||||
function create(req, res, next) {
|
||||
Pomodoro.create(Object.assign(req.body, { user: req.user && req.user.id || '57b04f50a1eaaf354f3b96a6' }), (err, pomodoro) => {
|
||||
res.locals.pomodoro = pomodoro
|
||||
|
||||
next(err)
|
||||
})
|
||||
}
|
||||
|
||||
function find(req, res, next) {
|
||||
const limit = Math.max(0, req.query.limit) || res.locals.limit
|
||||
|
||||
const query = Pomodoro.find(_.omit(req.query, 'limit', 'sort', 'page'),
|
||||
null,
|
||||
{ sort: req.query.sort || '-startDate', lean: true })
|
||||
|
||||
if (limit)
|
||||
query.limit(limit)
|
||||
|
||||
query.exec((err, pomodoros) => {
|
||||
res.locals.pomodoros = pomodoros
|
||||
|
||||
next(err)
|
||||
})
|
||||
}
|
||||
|
||||
function end(req, res, next) {
|
||||
Pomodoro.findByIdAndUpdate(req.params.id, { endDate: new Date() }, (err, pomodoro) => {
|
||||
if (err) return next(err)
|
||||
|
||||
res.locals.pomodoro = pomodoro
|
||||
|
||||
next()
|
||||
})
|
||||
}
|
||||
|
||||
function getActive(req, res, next) {
|
||||
Pomodoro.find({ user: req.user.id, endDate: { $eq: null } }, (err, pomodoros) => {
|
||||
res.locals.pomodoros = pomodoros
|
||||
|
||||
return next(err)
|
||||
})
|
||||
}
|
||||
|
||||
function patch(req, res, next) {
|
||||
Pomodoro.findByIdAndUpdate(req.params.id, req.body, (err, pomodoro) => {
|
||||
if (err) return next(err)
|
||||
|
||||
res.locals.pomodoro = pomodoro
|
||||
|
||||
next()
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
create,
|
||||
end,
|
||||
find,
|
||||
formatQuery: formatQuery([ 'limit', 'sort' ], {
|
||||
endDate: 'exists'
|
||||
}),
|
||||
getActive,
|
||||
patch
|
||||
}
|
||||
|
||||
23
server/services/pomodoros/model.js
Normal file
23
server/services/pomodoros/model.js
Normal file
@ -0,0 +1,23 @@
|
||||
'use strict'
|
||||
|
||||
const mongoose = require('mongoose')
|
||||
|
||||
const PomodoroSchema = new mongoose.Schema({
|
||||
startTime: {
|
||||
type: Date,
|
||||
required: true
|
||||
},
|
||||
endTime: {
|
||||
type: Date,
|
||||
required: true
|
||||
},
|
||||
name: String,
|
||||
location: String,
|
||||
user: {
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
ref: 'User',
|
||||
required: true
|
||||
}
|
||||
})
|
||||
|
||||
module.exports = mongoose.model('Pomodoro', PomodoroSchema)
|
||||
13
server/services/pomodoros/routes.js
Normal file
13
server/services/pomodoros/routes.js
Normal file
@ -0,0 +1,13 @@
|
||||
'use strict'
|
||||
|
||||
//const isAuthenticated = require('express-module-membership/passport/authorization-middleware').isAuthenticated;
|
||||
const isAuthenticated = (req, res, next) => next()
|
||||
|
||||
const mw = require('./middleware')
|
||||
|
||||
module.exports = [
|
||||
[ '/api/pomodoros', 'post', [ isAuthenticated, mw.create ]],
|
||||
[ '/api/pomodoros/:id', 'patch', [ isAuthenticated, mw.patch ]],
|
||||
//[ '/api/pomodoros', 'get', [ mw.authorization.isAuthenticated, mw.pomodoros.getActive ]]
|
||||
[ '/api/pomodoros', 'get', [ isAuthenticated, mw.formatQuery, mw.find ]]
|
||||
]
|
||||
15
server/templates/error.marko
Normal file
15
server/templates/error.marko
Normal file
@ -0,0 +1,15 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Newseri - ERROR</title>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="content">
|
||||
<h1>Error ${data.error.status}</h1>
|
||||
<pre>${JSON.stringify(data.error, null, ' ')}</pre>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
32
server/templates/index.marko
Normal file
32
server/templates/index.marko
Normal file
@ -0,0 +1,32 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Budbilar</title>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>Välkommen till Budbilar</h1>
|
||||
<nav>
|
||||
<li><a href="/">Start</a></li>
|
||||
</nav>
|
||||
<p>Lorem ipsum dolizzle sit sizzle, things adipiscing elit. Nullizzle
|
||||
sapizzle velizzle, for sure volutpizzle, suscipit quis, boofron vizzle, pot.
|
||||
Pellentesque its fo rizzle tortizzle. Sed erizzle. Fusce izzle dolor brizzle we
|
||||
gonna chung pot ass. Maurizzle yo crunk et turpizzle. Gangsta izzle tortizzle.
|
||||
Pellentesque eleifend rhoncizzle its fo rizzle. In yippiyo mammasay mammasa
|
||||
mamma oo sa pizzle dictumst. Boofron dapibus. Crunk tellus urna, pretizzle
|
||||
daahng dawg, mattizzle izzle, eleifend vitae, nunc. Boofron suscipizzle.
|
||||
Integizzle semper fo shizzle sizzle sizzle.</p>
|
||||
|
||||
<p>Phasellizzle mammasay mammasa mamma oo sa crackalackin tellizzle. Ut
|
||||
phat ma nizzle that's the shizzle. Donizzle i saw beyonces tizzles and my
|
||||
pizzle went crizzle cool. Nulla sapizzle phat, ultricizzle nizzle, accumsan
|
||||
the bizzle, fermentizzle hizzle, pede. Sizzle nizzle ass. Etizzle owned
|
||||
hizzle rizzle. Mauris uhuh ... yih!. Ma nizzle ut fo shizzle mah nizzle fo
|
||||
rizzle, mah home g-dizzle varius nibh commodo commodo. Sure fo shizzle my
|
||||
nizzle dolor brizzle fo, consectetizzle yo mamma elit. Sizzle ac mi. That's
|
||||
the shizzle mi sizzle, sizzle izzle, away a, eleifend fo, for sure.</p>
|
||||
</body>
|
||||
</html>
|
||||
45
src/.eslintrc
Normal file
45
src/.eslintrc
Normal file
@ -0,0 +1,45 @@
|
||||
{
|
||||
"extends": "airbnb",
|
||||
|
||||
"rules": {
|
||||
"semi": [ 2, "never" ],
|
||||
"no-confusing-arrow": 0,
|
||||
"react/prop-types": 0,
|
||||
"guard-for-in": 0,
|
||||
"global-require": 0,
|
||||
"no-underscore-dangle": 0,
|
||||
"object-shorthand": 0,
|
||||
"default-case": 0,
|
||||
"one-var": 0,
|
||||
"prefer-rest-params": 0,
|
||||
"no-unused-vars": [ 2, { "args": "none" } ],
|
||||
"no-alert": 0,
|
||||
"quote-props": 0,
|
||||
"no-nested-ternary": 0,
|
||||
"no-use-before-define": [2, { "functions": false, "classes": true }],
|
||||
"consistent-return": 0,
|
||||
"no-eval": 0,
|
||||
"prefer-arrow-callback": 0,
|
||||
"array-bracket-spacing": 0,
|
||||
"no-console": 0,
|
||||
"indent": [ 2, 2, { "SwitchCase": 1 }],
|
||||
"max-len": 0,
|
||||
"comma-dangle": 0,
|
||||
"no-param-reassign": 0,
|
||||
"prefer-template": 0,
|
||||
"curly": 0,
|
||||
"func-names": 0,
|
||||
"no-shadow": 0,
|
||||
"spaced-comment": 0,
|
||||
"strict": [ 2, "global" ]
|
||||
},
|
||||
|
||||
"globals": {
|
||||
"ENV": true,
|
||||
"google": true,
|
||||
"INITIAL_STATE": true,
|
||||
"INITIAL_CONTEXT": true,
|
||||
"PWD": true,
|
||||
"$": true
|
||||
}
|
||||
}
|
||||
20
src/app.js
Normal file
20
src/app.js
Normal file
@ -0,0 +1,20 @@
|
||||
import { $ } from 'dollr/dollr'
|
||||
|
||||
import Timer from './components/Timer'
|
||||
|
||||
// request permission on page load
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
if (!Notification) {
|
||||
alert('Desktop notifications not available in your browser. Try Chromium.')
|
||||
return
|
||||
}
|
||||
|
||||
if (Notification.permission !== 'granted')
|
||||
Notification.requestPermission()
|
||||
})
|
||||
|
||||
$(() => {
|
||||
const timer = $('.timer')
|
||||
|
||||
Timer.render().replace(timer).getWidget()
|
||||
})
|
||||
87
src/components/Timer/index.js
Normal file
87
src/components/Timer/index.js
Normal file
@ -0,0 +1,87 @@
|
||||
import markoWidgets from 'marko-widgets'
|
||||
import template from './template.marko'
|
||||
|
||||
const length = 25 * 60 * 1000
|
||||
|
||||
export default markoWidgets.defineComponent({
|
||||
template,
|
||||
|
||||
componentDidMount() {
|
||||
this.reset()
|
||||
},
|
||||
|
||||
start(e) {
|
||||
this.setState({
|
||||
startTime: Date.now()
|
||||
})
|
||||
|
||||
this.interval = setInterval(this.updateTimer.bind(this), 10)
|
||||
},
|
||||
|
||||
pause(e) {
|
||||
clearInterval(this.interval)
|
||||
},
|
||||
|
||||
reset(e) {
|
||||
clearInterval(this.interval)
|
||||
|
||||
this.setState({
|
||||
time: length,
|
||||
totalTime: length,
|
||||
startTime: undefined
|
||||
})
|
||||
},
|
||||
|
||||
updateTimer() {
|
||||
let time = this.state.totalTime - (Date.now() - this.state.startTime)
|
||||
|
||||
if (time <= 0) {
|
||||
this.pause()
|
||||
|
||||
this.end()
|
||||
|
||||
time = 0
|
||||
}
|
||||
|
||||
this.setState({
|
||||
time
|
||||
})
|
||||
},
|
||||
|
||||
end(time) {
|
||||
const data = {
|
||||
startTime: this.state.startTime,
|
||||
endTime: new Date()
|
||||
}
|
||||
|
||||
if (Notification.permission !== 'granted')
|
||||
Notification.requestPermission()
|
||||
else {
|
||||
const notification = new Notification('Notification title', {
|
||||
icon: 'http://cdn.sstatic.net/stackexchange/img/logos/so/so-icon.png',
|
||||
body: 'Pomdoro is done!',
|
||||
})
|
||||
}
|
||||
|
||||
fetch('/api/pomodoros', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(data),
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then((json) => {
|
||||
console.log('succes')
|
||||
})
|
||||
},
|
||||
|
||||
getInitialState(input) {
|
||||
return {
|
||||
time: length,
|
||||
totalTime: length,
|
||||
startTime: undefined
|
||||
}
|
||||
}
|
||||
})
|
||||
11
src/components/Timer/template.marko
Normal file
11
src/components/Timer/template.marko
Normal file
@ -0,0 +1,11 @@
|
||||
<script marko-init>
|
||||
const time = require('../../util/time-filter');
|
||||
</script>
|
||||
<div class="timer" w-bind>
|
||||
<div class="time">${time(data.time || 0)}</div>
|
||||
<div class="buttons">
|
||||
<button w-onClick="start">Start</button>
|
||||
<button>Pause</button>
|
||||
<button>Reset</button>
|
||||
</div>
|
||||
</div>
|
||||
3
src/error.marko
Normal file
3
src/error.marko
Normal file
@ -0,0 +1,3 @@
|
||||
<section>
|
||||
<h1>Error!</h1>
|
||||
</section>
|
||||
3
src/marko.json
Normal file
3
src/marko.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"tags-dir": [ "./components" ]
|
||||
}
|
||||
31
src/master.marko
Normal file
31
src/master.marko
Normal file
@ -0,0 +1,31 @@
|
||||
<!doctype html>
|
||||
<html xml:lang="sv" lang="sv">
|
||||
<head>
|
||||
<title>Pomodoro</title>
|
||||
|
||||
<!-- Meta Tags -->
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
<meta name="robots" content="index, follow" />
|
||||
<meta name="description" content="Beta Pomodoro timer that stores your pomodors." />
|
||||
<meta name="keywords" content="pomodoro,gtd,productivity" />
|
||||
<meta name="author" content="Linus Miller" />
|
||||
|
||||
<!-- CSS -->
|
||||
<link rel="stylesheet" href="/css/main${data.css && data.css.suffix}.css">
|
||||
|
||||
<!--[if IE]>
|
||||
<link rel="stylesheet" type="text/css" href="css/default-IE.css" />
|
||||
<![endif]-->
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<main>
|
||||
<h1>Pomodoro Time!</h1>
|
||||
<Timer/>
|
||||
</main>
|
||||
|
||||
<script src="/js/app${data.js && data.js.suffix}.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
45
src/routes.js
Normal file
45
src/routes.js
Normal file
@ -0,0 +1,45 @@
|
||||
export default {
|
||||
'': {
|
||||
name: '',
|
||||
//view: PageView,
|
||||
template: require('./pages/index.marko'),
|
||||
//subviews: {
|
||||
// newsletterForm: [ '.newsletter', require('./views/newsletter-form') ]
|
||||
//}
|
||||
widget: require('./pages/index')
|
||||
},
|
||||
|
||||
'samarbetspartners': {
|
||||
//view: 'Page'
|
||||
},
|
||||
|
||||
'om-oss': {
|
||||
name: 'om-oss',
|
||||
template: require('./pages/om-oss.marko'),
|
||||
//view: PageView,
|
||||
widget: require('./pages/om-oss')
|
||||
},
|
||||
|
||||
'nyhetsbrev': {
|
||||
name: 'nyhetsbrev',
|
||||
template: require('./pages/nyhetsbrev.marko'),
|
||||
//view: PageView,
|
||||
widget: require('./pages/nyhetsbrev'),
|
||||
//subviews: {
|
||||
// newsletterForm: [ '.newsletter-form', require('./components/newsletter-form/view') ]
|
||||
//},
|
||||
},
|
||||
|
||||
|
||||
'kontakt': {
|
||||
name: 'kontakt',
|
||||
//view: PageView,
|
||||
template: require('./pages/kontakt.marko'),
|
||||
widget: require('./pages/kontakt'),
|
||||
//subviews: {
|
||||
// googleMap: [ '#map-canvas', require('./views/google-map') ],
|
||||
// contactForm: [ '.contact-form', require('./components/contact-form/view') ],
|
||||
// newsletterForm: [ '.nyhetsbrev', require('./views/newsletter-form') ]
|
||||
//}
|
||||
}
|
||||
};
|
||||
19
src/util/split-time.js
Normal file
19
src/util/split-time.js
Normal file
@ -0,0 +1,19 @@
|
||||
'use strict';
|
||||
|
||||
const divs = [ 60, 100, 10 ];
|
||||
|
||||
module.exports = function (time) {
|
||||
const arr = [];
|
||||
for (let i = 0; i < divs.length; i++) {
|
||||
const nbr = divs.slice(i).reduce((a, b) => a * b);
|
||||
|
||||
const result = Math.floor(time / nbr);
|
||||
|
||||
arr.push(result);
|
||||
|
||||
time = time - result * nbr;
|
||||
//this.timerElements[i].textContent = result;
|
||||
}
|
||||
|
||||
return arr;
|
||||
};
|
||||
10
src/util/time-filter.js
Normal file
10
src/util/time-filter.js
Normal file
@ -0,0 +1,10 @@
|
||||
'use strict';
|
||||
|
||||
const splitTime = require('./split-time');
|
||||
const twoDigits = require('./two-digits');
|
||||
|
||||
module.exports = function (time) {
|
||||
const arr = splitTime(time).map(twoDigits);
|
||||
|
||||
return arr.join(':');
|
||||
};
|
||||
5
src/util/two-digits.js
Normal file
5
src/util/two-digits.js
Normal file
@ -0,0 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = function twoDigits(val) {
|
||||
return val >= 10 ? val : '0' + val;
|
||||
};
|
||||
2
static/robots.txt
Normal file
2
static/robots.txt
Normal file
@ -0,0 +1,2 @@
|
||||
User-agent: *
|
||||
Disallow: /
|
||||
Loading…
Reference in New Issue
Block a user