diff --git a/.babelrc b/.babelrc new file mode 100644 index 0000000..ea9b268 --- /dev/null +++ b/.babelrc @@ -0,0 +1,25 @@ +{ + "presets": ["es2015", "stage-0", "react"], + "plugins": [ + "transform-runtime", + "glamor/babel" + ], + "env": { + "start": { + "plugins": [ + "react-transform" + ] + }, + "extra": { + "react-transform": { + "transforms": [ + { + "transform": "react-transform-hmr", + "imports": ["react"], + "locals": ["module"] + } + ] + } + } + } +} diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..b947077 --- /dev/null +++ b/.eslintignore @@ -0,0 +1,2 @@ +node_modules/ +dist/ diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..0b5d4cf --- /dev/null +++ b/.eslintrc @@ -0,0 +1,24 @@ +{ + "parser": "babel-eslint", + "extends": ["standard", "standard-react"], + "plugins": [ + "react" + ], + "parserOptions": { + "ecmaFeatures": { + "jsx": true, + "experimentalObjectRestSpread": true + } + }, + "env": { + "node": true, + "es6": true + }, + "rules": { + "react/jsx-curly-spacing": [2, "never"], + "space-before-function-paren": [2, "always"], + "object-curly-spacing": [2, "always"], + "array-bracket-spacing": [2, "never"], + "no-console": 2 + } +} diff --git a/.gitignore b/.gitignore index f952ee6..c246f10 100755 --- a/.gitignore +++ b/.gitignore @@ -20,6 +20,5 @@ node_modules /mm-config.*.json # Extra ignores -components -public +dist .DS_Store diff --git a/Makefile b/Makefile deleted file mode 100755 index cb31761..0000000 --- a/Makefile +++ /dev/null @@ -1,26 +0,0 @@ -ifndef DEBUG - DEBUG="civicstack*" -endif - -ifndef NODE_ENV - NODE_ENV="development" -endif - -run: install - @echo "Booting application..." - @NODE_PATH=. DEBUG=$(DEBUG) node index.js - -debug: install - @echo "Booting application..." - @NODE_PATH=. DEBUG=$(DEBUG) node debug index.js - -install: - @echo "Installing dependencies..." - @npm install - -clean: - @echo "Removing dependencies, components and built assets." - @rm -rf components node_modules public - @echo "Done.\n" - -.PHONY: run clean diff --git a/Procfile b/Procfile deleted file mode 100644 index 1da0cd6..0000000 --- a/Procfile +++ /dev/null @@ -1 +0,0 @@ -web: node index.js diff --git a/bin/civicstack b/bin/civicstack index 2ecc037..e5d30bb 100755 --- a/bin/civicstack +++ b/bin/civicstack @@ -1,14 +1,9 @@ #!/usr/bin/env node -/** - * Module dependencies. - */ - -var program = require('commander'); +const program = require('commander') +const version = require('../package.json').version program - .version('0.0.2') - .command('install', "Install client components.") - .command('build', "Builds client application and compiles assets.") + .version(version) .command('dump-emails', "Exports all user's emails on database to a CSV file") .parse(process.argv) diff --git a/bin/civicstack-build b/bin/civicstack-build deleted file mode 100755 index ead7df9..0000000 --- a/bin/civicstack-build +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/env node - -/** - * Module dependencies. - */ - -var fs = require('fs'); -var resolve = require('path').resolve; -var write = fs.writeFileSync; - -// Little hack to include `NODE_PATH=.` -require('node-path')(module, [resolve('.')]); - -// Compile client application `./public/app.js` and `./public/app.css` -var Builder = require('lib/build'); - -Builder.build(function(err, res) { - if (err) return console.log(err), process.exit(1); - - write('public/app.js', res.js); - write('public/app.css', res.css); -}); diff --git a/bin/civicstack-config b/bin/civicstack-config deleted file mode 100755 index 01d5386..0000000 --- a/bin/civicstack-config +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/env node - -/** - * Module dependencies. - */ - -var fs = require('fs'); -var write = fs.writeFileSync; -var path = require('path'); -var resolve = path.resolve; - -// Little hack to include `NODE_PATH=.` -require('node-path')(module, [resolve('.')]); - -// Compile client's config file -var config = require('lib/config'); -var client = {}; - -config.client.forEach(function(k) { - client[k] = config[k]; -}); - -write(resolve('lib/config/client.js'), 'module.exports = ' + JSON.stringify(client)); diff --git a/bin/civicstack-dump-emails b/bin/civicstack-dump-emails index ddf7da0..4145d85 100644 --- a/bin/civicstack-dump-emails +++ b/bin/civicstack-dump-emails @@ -1,37 +1,35 @@ #!/usr/bin/env node -/** - * Module dependencies. - */ - -var fs = require('fs'); -var write = fs.writeFileSync; -var program = require('commander'); -var MongoClient = require('mongodb').MongoClient; +const fs = require('fs') +const write = fs.writeFileSync +const program = require('commander') +const MongoClient = require('mongodb').MongoClient program .option('-o, --out ', 'file name to write') .option('-d, --db ', 'database URL') - .parse(process.argv); + .parse(process.argv) MongoClient.connect(program.db, function (err, db) { if (err) { - console.error(err); - process.exit(1); + console.error(err) + process.exit(1) } - var users = db.collection('users'); - console.log('Fetching users...'); + const users = db.collection('users') + + console.log('Fetching users...') + users.find().toArray(function (err, docs) { - var userList = docs.filter(function (doc) { - return doc.email; + const userList = docs.filter(function (doc) { + return doc.email }).map(function (doc) { - return (doc.firstName || '') + ',' + (doc.lastName || '') + ',' + (doc.email || ''); - }); + return (doc.firstName || '') + ',' + (doc.lastName || '') + ',' + (doc.email || '') + }) console.log('Writing file...') - write(program.out, userList.join('\n')); + write(program.out, userList.join('\n')) console.log('Wrote ' + userList.length + ' entries.') - process.exit(0); - }); -}); + process.exit(0) + }) +}) diff --git a/bin/civicstack-install b/bin/civicstack-install deleted file mode 100755 index 834420b..0000000 --- a/bin/civicstack-install +++ /dev/null @@ -1,54 +0,0 @@ -#!/usr/bin/env node - -/** - * Module dependencies. - */ -var fs = require('fs'); -var exists = fs.existsSync; -var read = fs.readFileSync; -var write = fs.writeFileSync; -var parse = JSON.parse; -var stringify = JSON.stringify; -var path = require('path'); -var resolve = path.resolve; -var merge = require('merge-util'); -var exec = require('child_process').exec; -var program = require('commander'); -var installc = resolve('./node_modules/.bin/component-install'); -var env = process.env.NODE_ENV || 'development'; - -program - .option('-c, --config', 'copy and/or merge configuration file') - .option('-C, --no-components', 'ignore components install') - .parse(process.argv); - - -/** - * Make sure there is a config file - */ - -if (program.config) { - var dest = resolve('./lib/config/' + env + '.json'); - var orig = resolve('./lib/config/sample.json'); - if (!exists(dest)) { - // non-simple copying - write(dest, read(orig)); - } else { - var conf = parse(read(dest)); - var sample = parse(read(orig)); - - // Merge current config into original sample - merge(sample, conf); - - // save config file - write(dest, stringify(sample, null, 2)); - } -}; - -if (program.components && exists(installc)) { - exec(installc, function(err, stdout, stderr) { - if (stdout.length) console.log(stdout); - if (stderr.length) return console.log(stderr), process.exit(1); - if (err != null) return console.log(err), process.exit(1); - }); -}; diff --git a/component.json b/component.json deleted file mode 100755 index 6c7c739..0000000 --- a/component.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "civic-stack", - "version": "1.1.0", - "description": "Stack of online civic webapps", - "paths": [ - "lib" - ], - "local": [ - "boot" - ], - "keywords": [ - "nodejs", - "express", - "mongoose", - "passport", - "component", - "jade" - ], - "license": "MIT" -} diff --git a/config/.gitignore b/config/.gitignore new file mode 100644 index 0000000..78e3a98 --- /dev/null +++ b/config/.gitignore @@ -0,0 +1,2 @@ +*.json +!defaults.json diff --git a/config/defaults.json b/config/defaults.json new file mode 100644 index 0000000..fe416ff --- /dev/null +++ b/config/defaults.json @@ -0,0 +1,4 @@ +{ + "port": 3000, + "client": [] +} diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index cad10d9..0000000 --- a/docker-compose.yml +++ /dev/null @@ -1,25 +0,0 @@ -version: '2' - -services: - app: - build: - context: . - dockerfile: ./docker/development.Dockerfile - env_file: docker/development.env - links: - - mongo - ports: - - '3000:3000' - # mount source volume, but - # don't overwrite `node_modules` or mirror dir from container - volumes: - - ${PWD}:/usr/src - - /usr/src/node_modules - tty: true - - mongo: - image: mongo - tty: false - # Optionally mount external data directory - # volumes: - # - path-to-your-data-dir:/data/db diff --git a/docker/development.Dockerfile b/docker/development.Dockerfile deleted file mode 100644 index 4652ff2..0000000 --- a/docker/development.Dockerfile +++ /dev/null @@ -1,16 +0,0 @@ -FROM node:argon -MAINTAINER Matías Lescano - -RUN apt-get update && \ - apt-get install -y libkrb5-dev && \ - npm config set python python2.7 - -COPY package.json /usr/src/ - -WORKDIR /usr/src - -RUN npm install --quiet --unsafe-perm - -EXPOSE 3000 - -CMD ["make"] diff --git a/docker/development.env b/docker/development.env deleted file mode 100644 index a191224..0000000 --- a/docker/development.env +++ /dev/null @@ -1,2 +0,0 @@ -# Required for running under docker compose -MONGO_URL=mongodb://mongo/civicstack diff --git a/index.js b/index.js deleted file mode 100755 index 6ef4a18..0000000 --- a/index.js +++ /dev/null @@ -1,16 +0,0 @@ -/** - * Module dependencies. - */ - -var http = require('http'); -var express = require('express'); -var app = module.exports = require('lib/boot'); -var debug = require('debug')('civicstack'); - -/** - * Launch server - */ - -http.createServer(app).listen(app.get('port'), function() { - debug('Application started on port %d', app.get('port')); -}); diff --git a/lib/about/about.js b/lib/about/about.js deleted file mode 100644 index bbf6907..0000000 --- a/lib/about/about.js +++ /dev/null @@ -1,16 +0,0 @@ -/** - * Module dependencies. - */ - -var classes = require('classes'); -var empty = require('empty'); -var page = require('page'); -var render = require('render'); -var template = require('./template'); - -page('/about', function(ctx, next) { - var container = document.querySelector('section.site-content'); - - classes(document.body).add('about') - empty(container).appendChild(render.dom(template)); -}); diff --git a/lib/about/about.styl b/lib/about/about.styl deleted file mode 100644 index d9a6c59..0000000 --- a/lib/about/about.styl +++ /dev/null @@ -1,12 +0,0 @@ -.about - h1, - h2 - margin-top: 1.6em - margin-bottom: .5em - font-weight: 900 - - h1 - font-size: 2.3em - - h2 - font-size: 2em diff --git a/lib/about/component.json b/lib/about/component.json deleted file mode 100644 index 4aa43e9..0000000 --- a/lib/about/component.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "name": "about", - "dependencies": { - "component/classes": "1.2.3", - "component/domify": "1.2.2", - "visionmedia/page.js": "1.6.1", - "yields/empty": "0.0.1" - }, - "locals": [ "render" ], - "scripts": [ "about.js" ], - "styles": [ "about.styl" ], - "templates": [ "template.jade" ], - "main": "about.js" -} diff --git a/lib/about/template.jade b/lib/about/template.jade deleted file mode 100644 index 253132c..0000000 --- a/lib/about/template.jade +++ /dev/null @@ -1,10 +0,0 @@ -.container.about - h2= t('about.what-is-it.title') - p= t('about.what-is-it.text') - h2= t('about.goal.title') - ul - li= t('about.goal.1') - li= t('about.goal.2') - li= t('about.goal.3') - h2= t('about.what-information-is-available.title') - p!= t('about.what-information-is-available.text') diff --git a/lib/admin-apps-form/admin-apps-form.js b/lib/admin-apps-form/admin-apps-form.js deleted file mode 100644 index ab5940b..0000000 --- a/lib/admin-apps-form/admin-apps-form.js +++ /dev/null @@ -1,59 +0,0 @@ -/** - * Module dependencies. - */ - -var AppForm = require('./view'); -var countries = require('countries'); -var licenses = require('licenses'); -var log = require('debug')('civicstack:admin-apps-form'); -var page = require('page'); -var request = require('request'); -var tags = require('tags'); -var technologies = require('technologies'); - -page('/admin/apps/create', - tags.middleware, - countries.middleware, - licenses.middleware, - technologies.middleware, - function (ctx, next) { - var form = new AppForm({ - el: '.admin-content', - replace: true, - data: { - app: { } - } - }); -}); - -page('/admin/apps/:id', - tags.middleware, - countries.middleware, - licenses.middleware, - technologies.middleware, - load, function (ctx, next) { - var form = new AppForm({ - el: '.admin-content', - replace: true, - data: { - app: ctx.app - } - }); -}); - -function load(ctx, next) { - request - .get('/api/applications/' + ctx.params.id) - .end(function(err, res) { - if (err || !res.ok) { - var message = 'Unable to load app for ' + ctx.params.id; - return log(message); - }; - - var app = res.body; - app.country = app.country.id; - app.license = app.license ? app.license.id : null; - ctx.app = app; - return next(); - }); -} diff --git a/lib/admin-apps-form/component.json b/lib/admin-apps-form/component.json deleted file mode 100644 index fc5fa61..0000000 --- a/lib/admin-apps-form/component.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "name": "admin-apps-form", - "description": "Admin apps form view", - "dependencies": { - "component/t": "1.0.0", - "ractivejs/ractive": "v0.6.1", - "visionmedia/debug": "2.1.3", - "visionmedia/page.js": "1.6.1" - }, - "locals": [ - "applications", - "countries", - "language", - "licenses", - "panel", - "related-links", - "request", - "render", - "scroll", - "tags", - "technologies" - ], - "templates": [ - "template.jade" - ], - "scripts": [ - "admin-apps-form.js", - "view.js" - ], - "styles": [ "styles.styl" ], - "main": "admin-apps-form.js" -} diff --git a/lib/admin-apps-form/styles.styl b/lib/admin-apps-form/styles.styl deleted file mode 100644 index 1509866..0000000 --- a/lib/admin-apps-form/styles.styl +++ /dev/null @@ -1,16 +0,0 @@ -.admin-content - .error - background-color #f2dede - border 1px solid #d9534f - color #d9534f - outline none - - .related-links - .related-link - margin-bottom 20px - - input - width 100% - - .related-links-commands - margin-top 20px \ No newline at end of file diff --git a/lib/admin-apps-form/template.jade b/lib/admin-apps-form/template.jade deleted file mode 100644 index 647b390..0000000 --- a/lib/admin-apps-form/template.jade +++ /dev/null @@ -1,119 +0,0 @@ -.panels -form.panel.panel-default.form-horizontal(on-submit='save') - .panel-heading: h4 - | {{#if app.name }} - ='[[ app.name ]]' - | {{ else }} - =t('app-form.new-app') - | {{/if}} - .panel-body - | {{#if app.logo }} - .form-group - .row - .col-md-4.col-md-offset-4 - .app-logo(style='background-image: url({{ app.logo }}); background-color: {{ app.backgroundColor }}') - | {{/if}} - .form-group - label.col-sm-2.control-label(for='logo')=t('app.logo') - .col-sm-10 - input.form-control(type='text' id='logo' placeholder=t('app.logo') value='{{ app.logo }}') - .form-group - label.col-sm-2.control-label(for='backgroundColor')=t('app.backgroundColor') - .col-sm-10 - input.form-control(type='text' id='backgroundColor' placeholder=t('app.backgroundColor') value='{{ app.backgroundColor }}') - .form-group - label.col-sm-2.control-label(for='name')=t('app.name') - .col-sm-10 - input.form-control(type='text' id='name' placeholder=t('app.name') value='{{ app.name }}' class='{{ errors.name }}') - .form-group - label.col-sm-2.control-label=t('app.description') - .col-sm-offset-2.col-sm-10 - label(for='description[en]')=t('english') - textarea.form-control(type='text' id='description[en]' placeholder=t('app.description.en.placeholder') value='{{ app.description.en }}' class='{{ errors.description.en }}') - .col-sm-offset-2.col-sm-10 - label(for='description[es]')=t('spanish') - textarea.form-control(type='text' id='description[es]' placeholder=t('app.description.es.placeholder') value='{{ app.description.es }}' class='{{ errors.description.es }}') - .col-sm-offset-2.col-sm-10 - label(for='description[fr]')=t('french') - textarea.form-control(type='text' id='description[fr]' placeholder=t('app.description.fr.placeholder') value='{{ app.description.fr }}' class='{{ errors.description.fr }}') - .form-group - label.col-sm-2.control-label(for='video')=t('app.video') - .col-sm-10 - input.form-control(type='url' id='video' placeholder=t('app.video.placeholder') value='{{ app.video }}' class='{{ errors.video }}') - .form-group - label.col-sm-2.control-label(for='organization')=t('app.organization-name') - .col-sm-10 - input.form-control(type='text' id='organization' placeholder=t('app.organization-name') value='{{ app.organization }}' class='{{ errors.organization }}') - .form-group - label.col-sm-2.control-label(for='website')=t('app.website') - .col-sm-10 - input.form-control(type='text' id='website' placeholder=t('app.website') value='{{ app.website }}' class='{{ errors.website }}') - .form-group - label.col-sm-2.control-label(for='country')=t('app.country') - .col-sm-10 - select.form-control(value='{{ app.country }}' id='country' class='{{ errors.country }}') - option(value='')=t('app.country') - | {{#each countries}} - option(value='{{ this.id }}') {{ this.name[lang] }} - | {{/each}} - .form-group - label.col-sm-2.control-label(for='twitter')=t('app.twitter') - .col-sm-10 - input.form-control(type='text' id='twitter' placeholder=t('app.twitter') value='{{ app.twitter }}') - .form-group - label.col-sm-2.control-label(for='github')=t('app.github-repository') - .col-sm-10 - input.form-control(type='text' id='github' placeholder=t('app.github-repository') value='{{ app.github }}') - .form-group - label.col-sm-2.control-label(for='license')=t('app.license') - .col-sm-10 - select.form-control(value='{{ app.license }}' id='license') - option(value='')=t('app.license') - | {{#each licenses}} - option(value='{{ this.id }}') {{ this.name }} - | {{/each}} - .form-group - label.col-sm-2.control-label(for='contact')=t('app.contact') - .col-sm-10 - input.form-control(type='text' id='contact' placeholder=t('app.contact') value='{{ app.contact }}', class='{{ errors.contact }}') - .form-group - label.col-sm-2.control-label(for='comments')=t('app.comments') - .col-sm-10 - textarea.form-control(type='text' id='comments' placeholder=t('app.comments') value='{{ app.comments }}') - .form-group - label.col-sm-2.control-label=t('app.tags') - | {{#each tags }} - .col-sm-offset-2.col-sm-10 - .checkbox - label - input(type='checkbox' name='{{ app.tagids }}' value='{{ this.id }}') - | {{ this.name[lang] }} - | {{/each }} - .form-group - label.col-sm-2.control-label=t('app.technology') - | {{#each technologies }} - .col-sm-offset-2.col-sm-10 - .checkbox - label - input(type='checkbox' name='{{ app.technologyids }}' value='{{ this.id }}') - | {{ this.name }} - | {{/each }} - .form-group.related-links - label.col-sm-2.control-label=t('app.links') - relatedlinks(links='{{links}}') - .form-group - label.col-sm-2.control-label(for='approved')=t('app.approved') - .col-sm-10 - .checkbox - input(type='checkbox' id='approved' checked='{{ app.approved }}') - .form-group - label.col-sm-2.control-label(for='uploadedBy')=t('app.uploaded-by') - .col-sm-10 - | {{#with app.uploadedBy }} - img.avatar(src='{{ avatar }}') - | {{ firstName }} {{ lastName }} - | {{/with}} - .panel-footer - .col-sm-offset-2 - button.btn.btn-success(type='submit')=t('Save') - a.btn.btn-danger(type='submit' href='/admin/apps')=t('Cancel') diff --git a/lib/admin-apps-form/view.js b/lib/admin-apps-form/view.js deleted file mode 100644 index fd75b03..0000000 --- a/lib/admin-apps-form/view.js +++ /dev/null @@ -1,191 +0,0 @@ -/** - * Module dependencies. - */ - -var countries = require('countries'); -var licenses = require('licenses'); -var language = require('language'); -var Panel = require('panel'); -var Ractive = require('ractive'); -var RelatedLinks = require('related-links'); -var request = require('request'); -var render = require('render'); -var t = require('t'); -var apps = require('applications').all; -var approved = require('applications').approved; -var template = require('./template'); -var scroll = require('scroll'); -var tags = require('tags'); -var technologies = require('technologies'); - -var AdminAppForm = Ractive.extend({ - isolated: true, - template: render(template), - components: { - relatedlinks: RelatedLinks - }, - hastag: function (id) { - console.log(id); - return true; - }, - validate: function validate() { - var app = this.get('app'); - var errors = this.get('errors'); - var ok = true; - - function validateAttribute(attr) { - if (!app[attr]) this.set('errors.' + attr, 'error'), ok = false; - } - - validateAttribute = validateAttribute.bind(this); - validateAttribute('name'); - validateAttribute('organization'); - validateAttribute('country'); - validateAttribute('website'); - validateAttribute('contact'); - if (!app.description['en']) { - this.set('errors.description.en', 'error') - ok = false; - } else if (app.description['en'].length > 300) { - this.set('errors.description.en', 'error') - ok = false; - } - - if (!app.description['es']) { - this.set('errors.description.es', 'error') - ok = false; - } else if (app.description['es'].length > 300) { - this.set('errors.description.es', 'error') - ok = false; - } - - if (!app.description['fr']) { - this.set('errors.description.fr', 'error') - ok = false; - } else if (app.description['fr'].length > 300) { - this.set('errors.description.fr', 'error') - ok = false; - } - - return ok; - }, - - observeAppError: function (attr) { - this.observe('app.' + attr, function (newValue) { - if (newValue) this.set('errors.' + attr, ''); - }) - }, - - onrender: function () { - var app = this.get('app'); - var self = this; - this.set('lang', language()); - this.set('tags', tags.items); - this.set('countries', countries.items); - this.set('licenses', licenses.items); - this.set('technologies', technologies.items); - this.set('links', app.links.map(function (link, i) { - return { url: link.url, description: link.description, index: i, editing: false }; - })); - this.set('adding', false); - - this.observeAppError('name'); - this.observeAppError('organization'); - this.observeAppError('country'); - this.observeAppError('website'); - this.observeAppError('description.en'); - this.observeAppError('description.es'); - this.observeAppError('description.fr'); - this.observeAppError('contact'); - - this.on('saveEditedLink', function (ev) { - ev.context.editing = false; - this.set('adding', false); - this.update(); - }); - - this.on('editLink', function (ev) { - this.set('links.*.editing', false); - ev.context.editing = true; - this.set('adding', true); - this.update(); - }); - - this.on('removeLink', function (ev) { - function indexOf (arr, fn) { - for (var i = 0; i < arr.length; i++) { - if (fn(arr[i])) { - return i; - } - } - return -1; - } - - var currentIndex = ev.context.index; - var idx = indexOf(this.get('links'), function (v) { - return v.index === currentIndex; - }); - - this.splice('links', idx, 1).then(function () { - self.update(); - }); - }); - - function nextIndex () { - function max (arr) { - var m = -1; - for (var i = 0; i < arr.length; i++) { - if (arr[i].index > m) { - m = arr[i].index; - } - } - - return m; - } - - return max(self.get('links')) + 1; - } - - this.on('addLink', function (ev) { - this.push('links', { url: 'http://', description: '', editing: true, index: nextIndex() }); - this.set('adding', true); - }); - - this.on('save', function (ev) { - app.links = this.data.links.map(function(link) { - return { url: link.url, description: link.description }; - }); - - if (!this.validate()) return false; - var url = '/api/applications/' + (app.id || 'create'); - request - .post(url) - .send(app) - .end(function (err, res) { - scroll(document.body, 0, 500); - if (err) { - return new Panel({ - classes: 'panel-danger', - heading: t('panel.heading.error'), - body: t('panel.heading.body'), - el: self.find('.panels') - }); - } - - var panel = new Panel({ - classes: 'panel-success', - heading: t('panel.heading.success'), - body: t('admin.apps.save.success'), - el: self.find('.panels') - }); - - apps.fetch(); - approved.fetch(); - }); - - return false; - }); - } -}); - -module.exports = AdminAppForm; diff --git a/lib/admin-apps/admin-apps.js b/lib/admin-apps/admin-apps.js deleted file mode 100644 index 6149ee4..0000000 --- a/lib/admin-apps/admin-apps.js +++ /dev/null @@ -1,17 +0,0 @@ -/** - * Module dependencies. - */ - -var page = require('page'); -var apps = require('applications').all; -var AppsList = require('./view'); - -page('/admin/apps', apps.middleware, function (ctx, next) { - var appslist = new AppsList({ - el: '.admin-content', - replace: true, - data: { - apps: apps.items - } - }); -}); diff --git a/lib/admin-apps/component.json b/lib/admin-apps/component.json deleted file mode 100644 index 2949fca..0000000 --- a/lib/admin-apps/component.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "name": "admin-apps", - "description": "Admin apps view", - "dependencies": { - "component/t": "1.0.0", - "ractivejs/ractive": "v0.6.1", - "visionmedia/debug": "2.1.3", - "visionmedia/page.js": "1.6.1" - }, - "locals": [ - "applications", - "confirmation", - "render", - "request" - ], - "templates": [ - "template.jade" - ], - "scripts": [ - "admin-apps.js", - "view.js" - ], - "main": "admin-apps.js" -} diff --git a/lib/admin-apps/template.jade b/lib/admin-apps/template.jade deleted file mode 100644 index 1f0e94f..0000000 --- a/lib/admin-apps/template.jade +++ /dev/null @@ -1,22 +0,0 @@ -#apps-wrapper - .apps-admin - .row.well.well-lg - .col-xs-12.col-md-2 - a.btn.btn-success.btn-block.new(href='/admin/apps/create') - span.fa.fa-plus - = ' ' + t('admin.list.new') - .col-xs-12.col-md-7.col-md-offset-3 - .input-group.input-group-md - span.input-group-addon - i.fa.fa-search - input.form-control.search(type='text' placeholder=t('admin.list.search.placeholder') value='{{search}}') - .row - ul.list.list-group - | {{#each apps }} - a.list-group-item - span.name='{{ name }}' - a.btn-action.pull-right(href='#' on-click='delete:{{id}},{{name}}') - img(src='/lib/admin/images/cancel.svg') - a.btn-action.pull-right(href='/admin/apps/{{id}}') - img(src='/lib/admin/images/edit.svg') - | {{/each}} diff --git a/lib/admin-apps/view.js b/lib/admin-apps/view.js deleted file mode 100644 index 67946ee..0000000 --- a/lib/admin-apps/view.js +++ /dev/null @@ -1,61 +0,0 @@ -/** - * Module dependencies. - */ - -var confirm = require('confirmation'); -var log = require('debug')('civicstack:admin-apps'); -var render = require('render'); -var Ractive = require('ractive'); -var apps = require('applications').all; -var approved = require('applications').approved; -var template = require('./template'); -var page = require('page'); -var t = require('t'); -var request = require('request'); - -var TagsList = Ractive.extend({ - isolated: true, - template: render(template), - onrender: function () { - - this.observe('search', function (current, old) { - function filter(app) { - var name = app.name.toLowerCase(); - current = current ? current.toLowerCase() : '' - return current ? !!~name.indexOf(current) : true; - } - - var filtered = apps.items.filter(filter); - this.set('apps', filtered); - }) - - this.on('delete', function (ev, id, name) { - ev.original.preventDefault(); - - confirm(t('confirmation.title'), t('admin-apps-form.confirmation.body', {name: name})) - .cancel(t('confirmation.cancel')) - .ok(t('confirmation.ok')) - .modal() - .closable() - .effect('slide') - .focus() - .show(onconfirmdelete.bind(this)); - - function onconfirmdelete(ok) { - if (!ok) return; - - request - .del('/api/applications/' + id) - .end(function (err, res) { - if (err || !res.ok) return log('Found error %o', err || res.error); - - apps.fetch(); - approved.fetch(); - page('/admin/apps'); - }); - } - }); - } -}); - -module.exports = TagsList; diff --git a/lib/admin-countries-form/admin-countries-form.js b/lib/admin-countries-form/admin-countries-form.js deleted file mode 100644 index 8c7ecde..0000000 --- a/lib/admin-countries-form/admin-countries-form.js +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Module dependencies. - */ - -var log = require('debug')('civicstack:admin-countries-form'); -var page = require('page'); -var request = require('request'); -var countries = require('countries'); -var CountryForm = require('./view'); - -page('/admin/countries/create', function (ctx, next) { - var form = new CountryForm({ - el: '.admin-content', - replace: true, - data: { - country: {} - } - }); -}); - -page('/admin/countries/:id', load, function (ctx, next) { - var form = new CountryForm({ - el: '.admin-content', - replace: true, - data: { - country: ctx.country - } - }); -}); - -function load(ctx, next) { - request - .get('/api/countries/' + ctx.params.id) - .end(function(err, res) { - if (err || !res.ok) { - var message = 'Unable to load country for ' + ctx.params.id; - return log(message); - }; - - ctx.country = res.body; - return next(); - }); -} diff --git a/lib/admin-countries-form/component.json b/lib/admin-countries-form/component.json deleted file mode 100644 index 66ee54c..0000000 --- a/lib/admin-countries-form/component.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "name": "admin-countries-form", - "description": "Admin countries form view", - "dependencies": { - "component/t": "1.0.0", - "ractivejs/ractive": "v0.6.1", - "visionmedia/debug": "2.1.3", - "visionmedia/page.js": "1.6.1" - }, - "locals": [ - "language", - "panel", - "request", - "render", - "countries" - ], - "templates": [ - "template.jade" - ], - "scripts": [ - "admin-countries-form.js", - "view.js" - ], - "main": "admin-countries-form.js" -} diff --git a/lib/admin-countries-form/template.jade b/lib/admin-countries-form/template.jade deleted file mode 100644 index de57d44..0000000 --- a/lib/admin-countries-form/template.jade +++ /dev/null @@ -1,22 +0,0 @@ -.panels -form.panel.panel-default.form-horizontal(on-submit='save') - .panel-heading: h4='[[ country.name[lang] ]]' - .panel-body - .form-group - label.col-sm-12.text-left=t('Name') - .form-group - label.col-sm-2.control-label(for='name[en]')=t('english') - .col-sm-10 - input.form-control(type='text' id='name[en]' placeholder='Name' value='{{ country.name.en }}') - .form-group - label.col-sm-2.control-label(for='name[es]')=t('spanish') - .col-sm-10 - input.form-control(type='text' id='name[es]' placeholder='Nombre' value='{{ country.name.es }}') - .form-group - label.col-sm-2.control-label(for='name[fr]')=t('french') - .col-sm-10 - input.form-control(type='text' id='name[fr]' placeholder='Nom' value='{{ country.name.fr }}') - .panel-footer - .col-sm-offset-2 - button.btn.btn-success(type='submit')=t('Save') - a.btn.btn-danger(type='submit' href='/admin')=t('Cancel') diff --git a/lib/admin-countries-form/view.js b/lib/admin-countries-form/view.js deleted file mode 100644 index 2186591..0000000 --- a/lib/admin-countries-form/view.js +++ /dev/null @@ -1,52 +0,0 @@ -/** - * Module dependencies. - */ - -var language = require('language'); -var Panel = require('panel'); -var Ractive = require('ractive'); -var request = require('request'); -var render = require('render'); -var t = require('t'); -var countries = require('countries'); -var template = require('./template'); - -var CountryForm = Ractive.extend({ - isolated: true, - template: render(template), - onrender: function () { - var self = this; - this.set('lang', language()); - - this.on('save', function (ev) { - var country = this.get('country'); - var url = '/api/countries/' + (country.id || 'create'); - request - .post(url) - .send(country) - .end(function (err, res) { - if (err) { - return new Panel({ - classes: 'panel-danger', - heading: t('panel.heading.error'), - body: t('panel.heading.body'), - el: self.find('.panels') - }); - } - - var panel = new Panel({ - classes: 'panel-success', - heading: t('panel.heading.success'), - body: t('admin.countries.save.success'), - el: self.find('.panels') - }); - - countries.fetch(); - }); - - return false; - }); - } -}); - -module.exports = CountryForm; diff --git a/lib/admin-countries/admin-countries.js b/lib/admin-countries/admin-countries.js deleted file mode 100644 index c572a64..0000000 --- a/lib/admin-countries/admin-countries.js +++ /dev/null @@ -1,17 +0,0 @@ -/** - * Module dependencies. - */ - -var page = require('page'); -var countries = require('countries'); -var CategoriesList = require('./view'); - -page('/admin/countries', countries.middleware, function (ctx, next) { - var countrieslist = new CategoriesList({ - el: '.admin-content', - replace: true, - data: { - countries: countries.items - } - }); -}); diff --git a/lib/admin-countries/component.json b/lib/admin-countries/component.json deleted file mode 100644 index d50fec9..0000000 --- a/lib/admin-countries/component.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "name": "admin-countries", - "description": "Admin countries view", - "dependencies": { - "component/t": "1.0.0", - "ractivejs/ractive": "v0.6.1", - "visionmedia/debug": "2.1.3", - "visionmedia/page.js": "1.6.1" - }, - "locals": [ - "confirmation", - "countries", - "language", - "render", - "request" - ], - "templates": [ - "template.jade" - ], - "scripts": [ - "admin-countries.js", - "view.js" - ], - "main": "admin-countries.js" -} diff --git a/lib/admin-countries/template.jade b/lib/admin-countries/template.jade deleted file mode 100644 index 6dc19b6..0000000 --- a/lib/admin-countries/template.jade +++ /dev/null @@ -1,22 +0,0 @@ -#countries-wrapper - .countries-admin - .row.well.well-lg - .col-xs-12.col-md-2 - a.btn.btn-success.btn-block.new(href='/admin/countries/create') - span.fa.fa-plus - = ' ' + t('admin.list.new') - .col-xs-12.col-md-7.col-md-offset-3 - .input-group.input-group-md - span.input-group-addon - i.fa.fa-search - input.form-control.search(type='text', placeholder=t("admin.list.search.placeholder"), value='{{search}}') - .row - .list.list-group - | {{#each countries }} - a.list-group-item - span.name='{{ name[lang] }}' - a.btn-action.pull-right(href='#' on-click='delete:{{id}},{{name[lang]}}') - img(src='/lib/admin/images/cancel.svg') - a.btn-action.pull-right(href='/admin/countries/{{id}}') - img(src='/lib/admin/images/edit.svg') - | {{/each}} diff --git a/lib/admin-countries/view.js b/lib/admin-countries/view.js deleted file mode 100644 index ba4142c..0000000 --- a/lib/admin-countries/view.js +++ /dev/null @@ -1,61 +0,0 @@ -/** - * Module dependencies. - */ - -var confirm = require('confirmation'); -var language = require('language'); -var render = require('render'); -var Ractive = require('ractive'); -var countries = require('countries'); -var log = require('debug')('civicstack:admin-countries'); -var page = require('page'); -var t = require('t'); -var template = require('./template'); -var request = require('request'); - -var CountriesList = Ractive.extend({ - isolated: true, - template: render(template), - onrender: function () { - this.set('lang', language()); - - this.observe('search', function (current, old) { - function filter(country) { - var name = country.name[language()].toLowerCase(); - current = current ? current.toLowerCase() : '' - return current ? !!~name.indexOf(current) : true; - } - - var filtered = countries.items.filter(filter); - this.set('countries', filtered); - }) - - this.on('delete', function (ev, id, name) { - ev.original.preventDefault(); - - confirm(t('confirmation.title'), t('admin-countries-form.confirmation.body', {name: name})) - .cancel(t('confirmation.cancel')) - .ok(t('confirmation.ok')) - .modal() - .closable() - .effect('slide') - .focus() - .show(onconfirmdelete.bind(this)); - - function onconfirmdelete(ok) { - if (!ok) return; - - request - .del('/api/countries/' + id) - .end(function (err, res) { - if (err || !res.ok) return log('Found error %o', err || res.error); - - countries.fetch(); - page('/admin/countries'); - }); - } - }) - } -}); - -module.exports = CountriesList; diff --git a/lib/admin-licenses-form/admin-licenses-form.js b/lib/admin-licenses-form/admin-licenses-form.js deleted file mode 100644 index 9fd1382..0000000 --- a/lib/admin-licenses-form/admin-licenses-form.js +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Module dependencies. - */ - -var log = require('debug')('civicstack:admin-licenses-form'); -var page = require('page'); -var request = require('request'); -var licenses = require('licenses'); -var LicenseForm = require('./view'); - -page('/admin/licenses/create', function (ctx, next) { - var form = new LicenseForm({ - el: '.admin-content', - replace: true, - data: { - license: {} - } - }); -}); - -page('/admin/licenses/:id', load, function (ctx, next) { - var form = new LicenseForm({ - el: '.admin-content', - replace: true, - data: { - license: ctx.license - } - }); -}); - -function load(ctx, next) { - request - .get('/api/licenses/' + ctx.params.id) - .end(function(err, res) { - if (err || !res.ok) { - var message = 'Unable to load license for ' + ctx.params.id; - return log(message); - }; - - ctx.license = res.body; - return next(); - }); -} diff --git a/lib/admin-licenses-form/component.json b/lib/admin-licenses-form/component.json deleted file mode 100644 index a550357..0000000 --- a/lib/admin-licenses-form/component.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "name": "admin-licenses-form", - "description": "Admin licenses form view", - "dependencies": { - "component/t": "1.0.0", - "ractivejs/ractive": "v0.6.1", - "visionmedia/debug": "2.1.3", - "visionmedia/page.js": "1.6.1" - }, - "locals": [ - "language", - "panel", - "request", - "render", - "licenses" - ], - "templates": [ - "template.jade" - ], - "scripts": [ - "admin-licenses-form.js", - "view.js" - ], - "main": "admin-licenses-form.js" -} diff --git a/lib/admin-licenses-form/template.jade b/lib/admin-licenses-form/template.jade deleted file mode 100644 index 547e749..0000000 --- a/lib/admin-licenses-form/template.jade +++ /dev/null @@ -1,12 +0,0 @@ -.panels -form.panel.panel-default.form-horizontal(on-submit='save') - .panel-heading: h4='[[ license.name ]]' - .panel-body - .form-group - label.col-sm-2.control-label(for='name')=t('Name') - .col-sm-10 - input.form-control(type='text' id='name' placeholder=t('Name') value='{{ license.name }}') - .panel-footer - .col-sm-offset-2 - button.btn.btn-success(type='submit')=t('Save') - a.btn.btn-danger(type='submit' href='/admin/licenses')=t('Cancel') diff --git a/lib/admin-licenses-form/view.js b/lib/admin-licenses-form/view.js deleted file mode 100644 index 5ddcf39..0000000 --- a/lib/admin-licenses-form/view.js +++ /dev/null @@ -1,50 +0,0 @@ -/** - * Module dependencies. - */ - -var Panel = require('panel'); -var Ractive = require('ractive'); -var request = require('request'); -var render = require('render'); -var t = require('t'); -var licenses = require('licenses'); -var template = require('./template'); - -var LicenseForm = Ractive.extend({ - isolated: true, - template: render(template), - onrender: function () { - var self = this; - - this.on('save', function (ev) { - var license = this.get('license'); - var url = '/api/licenses/' + (license.id || 'create'); - request - .post(url) - .send(license) - .end(function (err, res) { - if (err) { - return new Panel({ - classes: 'panel-danger', - heading: t('panel.heading.error'), - body: t('panel.heading.body'), - el: self.find('.panels') - }); - } - - var panel = new Panel({ - classes: 'panel-success', - heading: t('panel.heading.success'), - body: t('admin.licenses.save.success'), - el: self.find('.panels') - }); - - licenses.fetch(); - }); - - return false; - }); - } -}); - -module.exports = LicenseForm; diff --git a/lib/admin-licenses/admin-licenses.js b/lib/admin-licenses/admin-licenses.js deleted file mode 100644 index 95461c8..0000000 --- a/lib/admin-licenses/admin-licenses.js +++ /dev/null @@ -1,17 +0,0 @@ -/** - * Module dependencies. - */ - -var page = require('page'); -var licenses = require('licenses'); -var LicensesList = require('./view'); - -page('/admin/licenses', licenses.middleware, function (ctx, next) { - var licenseslist = new LicensesList({ - el: '.admin-content', - replace: true, - data: { - licenses: licenses.items - } - }); -}); diff --git a/lib/admin-licenses/component.json b/lib/admin-licenses/component.json deleted file mode 100644 index 0e213f1..0000000 --- a/lib/admin-licenses/component.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "name": "admin-licenses", - "description": "Admin licenses view", - "dependencies": { - "component/t": "1.0.0", - "ractivejs/ractive": "v0.6.1", - "visionmedia/debug": "2.1.3", - "visionmedia/page.js": "1.6.1" - }, - "locals": [ - "confirmation", - "render", - "request", - "licenses" - ], - "templates": [ - "template.jade" - ], - "scripts": [ - "admin-licenses.js", - "view.js" - ], - "main": "admin-licenses.js" -} diff --git a/lib/admin-licenses/template.jade b/lib/admin-licenses/template.jade deleted file mode 100644 index 780bf21..0000000 --- a/lib/admin-licenses/template.jade +++ /dev/null @@ -1,22 +0,0 @@ -#licenses-wrapper - .licenses-admin - .row.well.well-lg - .col-xs-12.col-md-2 - a.btn.btn-success.btn-block.new(href='/admin/licenses/create') - span.fa.fa-plus - = ' ' + t('admin.list.new') - .col-xs-12.col-md-7.col-md-offset-3 - .input-group.input-group-md - span.input-group-addon - i.fa.fa-search - input.form-control.search(type='text', placeholder=t("admin.list.search.placeholder"), value='{{search}}') - .row - .list.list-group - | {{#each licenses}} - a.list-group-item - span.name='{{ name }}' - a.btn-action.pull-right(href='#' on-click='delete:{{id}},{{name}}') - img(src='/lib/admin/images/cancel.svg') - a.btn-action.pull-right(href='/admin/licenses/{{id}}') - img(src='/lib/admin/images/edit.svg') - | {{/each}} diff --git a/lib/admin-licenses/view.js b/lib/admin-licenses/view.js deleted file mode 100644 index 87d9fcd..0000000 --- a/lib/admin-licenses/view.js +++ /dev/null @@ -1,58 +0,0 @@ -/** - * Module dependencies. - */ - -var confirm = require('confirmation'); -var render = require('render'); -var Ractive = require('ractive'); -var licenses = require('licenses'); -var log = require('debug')('civicstack:admin-licenses'); -var page = require('page'); -var t = require('t'); -var template = require('./template'); -var request = require('request'); - -var LicensesList = Ractive.extend({ - isolated: true, - template: render(template), - onrender: function () { - this.observe('search', function (current, old) { - function filter(license) { - var name = license.name.toLowerCase(); - current = current ? current.toLowerCase() : '' - return current ? !!~name.indexOf(current) : true; - } - - var filtered = licenses.items.filter(filter); - this.set('licenses', filtered); - }) - - this.on('delete', function (ev, id, name) { - ev.original.preventDefault(); - - confirm(t('confirmation.title'), t('admin-licenses-form.confirmation.body', {name: name})) - .cancel(t('confirmation.cancel')) - .ok(t('confirmation.ok')) - .modal() - .closable() - .effect('slide') - .focus() - .show(onconfirmdelete.bind(this)); - - function onconfirmdelete(ok) { - if (!ok) return; - - request - .del('/api/licenses/' + id) - .end(function (err, res) { - if (err || !res.ok) return log('Found error %o', err || res.error); - - licenses.fetch(); - page('/admin/licenses'); - }); - } - }) - } -}); - -module.exports = LicensesList; diff --git a/lib/admin-sidebar/admin-sidebar.styl b/lib/admin-sidebar/admin-sidebar.styl deleted file mode 100644 index d351968..0000000 --- a/lib/admin-sidebar/admin-sidebar.styl +++ /dev/null @@ -1,2 +0,0 @@ -ul.admin-menu a.selected - background-color #eee diff --git a/lib/admin-sidebar/component.json b/lib/admin-sidebar/component.json deleted file mode 100644 index 30ed14b..0000000 --- a/lib/admin-sidebar/component.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "admin-sidebar", - "description": "Admin sidebar", - "dependencies": { - "component/classes": "1.2.4", - "ractivejs/ractive": "v0.6.1", - "visionmedia/debug": "2.1.3" - }, - "locals": [ - "render" - ], - "scripts": [ - "view.js" - ], - "styles": [ - "admin-sidebar.styl" - ], - "templates": [ "template.jade"], - "main": "view.js" -} diff --git a/lib/admin-sidebar/template.jade b/lib/admin-sidebar/template.jade deleted file mode 100644 index bd6d197..0000000 --- a/lib/admin-sidebar/template.jade +++ /dev/null @@ -1,6 +0,0 @@ -ul.admin-menu.nav.nav-pills.nav-stacked - li: a(href="/admin/apps")=t('admin-sidebar.applications.title') - li: a(href="/admin/tags")=t('admin-sidebar.tags.title') - li: a(href="/admin/countries")=t('admin-sidebar.countries.title') - li: a(href="/admin/technologies")=t('admin-sidebar.technologies.title') - li: a(href="/admin/licenses")=t('admin-sidebar.licenses.title') diff --git a/lib/admin-sidebar/view.js b/lib/admin-sidebar/view.js deleted file mode 100644 index 990da1e..0000000 --- a/lib/admin-sidebar/view.js +++ /dev/null @@ -1,25 +0,0 @@ -/** - * Module dependencies. - */ - -var classes = require('classes'); -var render = require('render'); -var Ractive = require('ractive'); -var template = require('./template'); - -var AdminSidebar = Ractive.extend({ - isolated: true, - template: render(template), - onrender: function () { - this.observe('selected', function (current) { - var items = this.findAll('a'); - var item = this.find('a[href="/admin/' + current + '"]'); - items.forEach(function (el) { - classes(el).remove('selected'); - }); - if (item) classes(item).add('selected'); - }) - } -}); - -module.exports = AdminSidebar; diff --git a/lib/admin-tags-form/admin-tags-form.js b/lib/admin-tags-form/admin-tags-form.js deleted file mode 100644 index ed4ca34..0000000 --- a/lib/admin-tags-form/admin-tags-form.js +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Module dependencies. - */ - -var log = require('debug')('civicstack:admin-tags-form'); -var page = require('page'); -var request = require('request'); -var tags = require('tags'); -var TagForm = require('./view'); - -page('/admin/tags/create', function (ctx, next) { - var form = new TagForm({ - el: '.admin-content', - replace: true, - data: { - tag: {} - } - }); -}); - -page('/admin/tags/:id', load, function (ctx, next) { - var form = new TagForm({ - el: '.admin-content', - replace: true, - data: { - tag: ctx.tag - } - }); -}); - -function load(ctx, next) { - request - .get('/api/tags/' + ctx.params.id) - .end(function(err, res) { - if (err || !res.ok) { - var message = 'Unable to load tag for ' + ctx.params.id; - return log(message); - }; - - ctx.tag = res.body; - return next(); - }); -} diff --git a/lib/admin-tags-form/component.json b/lib/admin-tags-form/component.json deleted file mode 100644 index 69bd244..0000000 --- a/lib/admin-tags-form/component.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "name": "admin-tags-form", - "description": "Admin tags form view", - "dependencies": { - "component/t": "1.0.0", - "ractivejs/ractive": "v0.6.1", - "visionmedia/debug": "2.1.3", - "visionmedia/page.js": "1.6.1" - }, - "locals": [ - "language", - "panel", - "request", - "render", - "tags" - ], - "templates": [ - "template.jade" - ], - "scripts": [ - "admin-tags-form.js", - "view.js" - ], - "main": "admin-tags-form.js" -} diff --git a/lib/admin-tags-form/template.jade b/lib/admin-tags-form/template.jade deleted file mode 100644 index b7586b4..0000000 --- a/lib/admin-tags-form/template.jade +++ /dev/null @@ -1,22 +0,0 @@ -.panels -form.panel.panel-default.form-horizontal(on-submit='save') - .panel-heading: h4='[[ tag.name[lang] ]]' - .panel-body - .form-group - label.col-sm-12.text-left=t('Name') - .form-group - label.col-sm-2.control-label(for='name[en]')=t('english') - .col-sm-10 - input.form-control(type='text' id='name[en]' placeholder='Name' value='{{ tag.name.en }}') - .form-group - label.col-sm-2.control-label(for='name[es]')=t('spanish') - .col-sm-10 - input.form-control(type='text' id='name[es]' placeholder='Nombre' value='{{ tag.name.es }}') - .form-group - label.col-sm-2.control-label(for='name[fr]')=t('french') - .col-sm-10 - input.form-control(type='text' id='name[fr]' placeholder='Nom' value='{{ tag.name.fr }}') - .panel-footer - .col-sm-offset-2 - button.btn.btn-success(type='submit')=t('Save') - a.btn.btn-danger(type='submit' href='/admin/tags')=t('Cancel') diff --git a/lib/admin-tags-form/view.js b/lib/admin-tags-form/view.js deleted file mode 100644 index f3a6474..0000000 --- a/lib/admin-tags-form/view.js +++ /dev/null @@ -1,52 +0,0 @@ -/** - * Module dependencies. - */ - -var language = require('language'); -var Panel = require('panel'); -var Ractive = require('ractive'); -var request = require('request'); -var render = require('render'); -var t = require('t'); -var tags = require('tags'); -var template = require('./template'); - -var TagForm = Ractive.extend({ - isolated: true, - template: render(template), - onrender: function () { - var self = this; - this.set('lang', language()); - - this.on('save', function (ev) { - var tag = this.get('tag'); - var url = '/api/tags/' + (tag.id || 'create'); - request - .post(url) - .send(tag) - .end(function (err, res) { - if (err) { - return new Panel({ - classes: 'panel-danger', - heading: t('panel.heading.error'), - body: t('panel.heading.body'), - el: self.find('.panels') - }); - } - - var panel = new Panel({ - classes: 'panel-success', - heading: t('panel.heading.success'), - body: t('admin.tags.save.success'), - el: self.find('.panels') - }); - - tags.fetch(); - }); - - return false; - }); - } -}); - -module.exports = TagForm; diff --git a/lib/admin-tags/admin-tags.js b/lib/admin-tags/admin-tags.js deleted file mode 100644 index 3de30f4..0000000 --- a/lib/admin-tags/admin-tags.js +++ /dev/null @@ -1,17 +0,0 @@ -/** - * Module dependencies. - */ - -var page = require('page'); -var tags = require('tags'); -var TagsList = require('./view'); - -page('/admin/tags', tags.middleware, function (ctx, next) { - var tagslist = new TagsList({ - el: '.admin-content', - replace: true, - data: { - tags: tags.items - } - }); -}); diff --git a/lib/admin-tags/component.json b/lib/admin-tags/component.json deleted file mode 100644 index e7080ee..0000000 --- a/lib/admin-tags/component.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "name": "admin-tags", - "description": "Admin tags view", - "dependencies": { - "component/t": "1.0.0", - "ractivejs/ractive": "v0.6.1", - "visionmedia/debug": "2.1.3", - "visionmedia/page.js": "1.6.1" - }, - "locals": [ - "confirmation", - "language", - "render", - "request", - "tags" - ], - "templates": [ - "template.jade" - ], - "scripts": [ - "admin-tags.js", - "view.js" - ], - "main": "admin-tags.js" -} diff --git a/lib/admin-tags/template.jade b/lib/admin-tags/template.jade deleted file mode 100644 index 9ab7270..0000000 --- a/lib/admin-tags/template.jade +++ /dev/null @@ -1,22 +0,0 @@ -#tags-wrapper - .tags-admin - .row.well.well-lg - .col-xs-12.col-md-2 - a.btn.btn-success.btn-block.new(href='/admin/tags/create') - span.fa.fa-plus - = ' ' + t('admin.list.new') - .col-xs-12.col-md-7.col-md-offset-3 - .input-group.input-group-md - span.input-group-addon - i.fa.fa-search - input.form-control.search(type='text', placeholder=t("admin.list.search.placeholder"), value='{{search}}') - .row - .list.list-group - | {{#each tags}} - a.list-group-item - span.name='{{ name[lang] }}' - a.btn-action.pull-right(href='#' on-click='delete:{{id}},{{ name[lang] }}') - img(src='/lib/admin/images/cancel.svg') - a.btn-action.pull-right(href='/admin/tags/{{id}}') - img(src='/lib/admin/images/edit.svg') - | {{/each}} diff --git a/lib/admin-tags/view.js b/lib/admin-tags/view.js deleted file mode 100644 index 76233ff..0000000 --- a/lib/admin-tags/view.js +++ /dev/null @@ -1,61 +0,0 @@ -/** - * Module dependencies. - */ - -var confirm = require('confirmation'); -var language = require('language'); -var render = require('render'); -var Ractive = require('ractive'); -var tags = require('tags'); -var page = require('page'); -var log = require('debug')('civicstack:admin-tags'); -var t = require('t'); -var template = require('./template'); -var request = require('request'); - -var TagsList = Ractive.extend({ - isolated: true, - template: render(template), - onrender: function () { - this.set('lang', language()); - - this.observe('search', function (current, old) { - function filter(tag) { - var name = tag.name[language()].toLowerCase(); - current = current ? current.toLowerCase() : '' - return current ? !!~name.indexOf(current) : true; - } - - var filtered = tags.items.filter(filter); - this.set('tags', filtered); - }) - - this.on('delete', function (ev, id, name) { - ev.original.preventDefault(); - - confirm(t('confirmation.title'), t('admin-tags-form.confirmation.body', {name: name})) - .cancel(t('confirmation.cancel')) - .ok(t('confirmation.ok')) - .modal() - .closable() - .effect('slide') - .focus() - .show(onconfirmdelete.bind(this)); - - function onconfirmdelete(ok) { - if (!ok) return; - - request - .del('/api/tags/' + id) - .end(function (err, res) { - if (err || !res.ok) return log('Found error %o', err || res.error); - - tags.fetch(); - page('/admin/tags'); - }); - } - }) - } -}); - -module.exports = TagsList; diff --git a/lib/admin-technologies-form/admin-technologies-form.js b/lib/admin-technologies-form/admin-technologies-form.js deleted file mode 100644 index 81b65da..0000000 --- a/lib/admin-technologies-form/admin-technologies-form.js +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Module dependencies. - */ - -var log = require('debug')('civicstack:admin-technologies-form'); -var page = require('page'); -var request = require('request'); -var technologies = require('technologies'); -var LicenseForm = require('./view'); - -page('/admin/technologies/create', function (ctx, next) { - var form = new LicenseForm({ - el: '.admin-content', - replace: true, - data: { - technology: {} - } - }); -}); - -page('/admin/technologies/:id', load, function (ctx, next) { - var form = new LicenseForm({ - el: '.admin-content', - replace: true, - data: { - technology: ctx.technology - } - }); -}); - -function load(ctx, next) { - request - .get('/api/technologies/' + ctx.params.id) - .end(function(err, res) { - if (err || !res.ok) { - var message = 'Unable to load technology for ' + ctx.params.id; - return log(message); - }; - - ctx.technology = res.body; - return next(); - }); -} diff --git a/lib/admin-technologies-form/component.json b/lib/admin-technologies-form/component.json deleted file mode 100644 index d0e8c40..0000000 --- a/lib/admin-technologies-form/component.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "name": "admin-technologies-form", - "description": "Admin technologies form view", - "dependencies": { - "component/t": "1.0.0", - "ractivejs/ractive": "v0.6.1", - "visionmedia/debug": "2.1.3", - "visionmedia/page.js": "1.6.1" - }, - "locals": [ - "language", - "panel", - "request", - "render", - "technologies" - ], - "templates": [ - "template.jade" - ], - "scripts": [ - "admin-technologies-form.js", - "view.js" - ], - "main": "admin-technologies-form.js" -} diff --git a/lib/admin-technologies-form/template.jade b/lib/admin-technologies-form/template.jade deleted file mode 100644 index db51a2e..0000000 --- a/lib/admin-technologies-form/template.jade +++ /dev/null @@ -1,12 +0,0 @@ -.panels -form.panel.panel-default.form-horizontal(on-submit='save') - .panel-heading: h4='[[ technology.name ]]' - .panel-body - .form-group - label.col-sm-2.control-label(for='name')=t('Name') - .col-sm-10 - input.form-control(type='text' id='name' placeholder=t('Name') value='{{ technology.name }}') - .panel-footer - .col-sm-offset-2 - button.btn.btn-success(type='submit')=t('Save') - a.btn.btn-danger(type='submit' href='/admin/technologies')=t('Cancel') diff --git a/lib/admin-technologies-form/view.js b/lib/admin-technologies-form/view.js deleted file mode 100644 index 66a6259..0000000 --- a/lib/admin-technologies-form/view.js +++ /dev/null @@ -1,50 +0,0 @@ -/** - * Module dependencies. - */ - -var Panel = require('panel'); -var Ractive = require('ractive'); -var request = require('request'); -var render = require('render'); -var t = require('t'); -var technologies = require('technologies'); -var template = require('./template'); - -var TechnologyForm = Ractive.extend({ - isolated: true, - template: render(template), - onrender: function () { - var self = this; - - this.on('save', function (ev) { - var technology = this.get('technology'); - var url = '/api/technologies/' + (technology.id || 'create'); - request - .post(url) - .send(technology) - .end(function (err, res) { - if (err) { - return new Panel({ - classes: 'panel-danger', - heading: t('panel.heading.error'), - body: t('panel.heading.body'), - el: self.find('.panels') - }); - } - - var panel = new Panel({ - classes: 'panel-success', - heading: t('panel.heading.success'), - body: t('admin.technologies.save.success'), - el: self.find('.panels') - }); - - technologies.fetch(); - }); - - return false; - }); - } -}); - -module.exports = TechnologyForm; diff --git a/lib/admin-technologies/admin-technologies.js b/lib/admin-technologies/admin-technologies.js deleted file mode 100644 index f36ea87..0000000 --- a/lib/admin-technologies/admin-technologies.js +++ /dev/null @@ -1,17 +0,0 @@ -/** - * Module dependencies. - */ - -var page = require('page'); -var technologies = require('technologies'); -var TechnologiesList = require('./view'); - -page('/admin/technologies', technologies.middleware, function (ctx, next) { - var technologieslist = new TechnologiesList({ - el: '.admin-content', - replace: true, - data: { - technologies: technologies.items - } - }); -}); diff --git a/lib/admin-technologies/component.json b/lib/admin-technologies/component.json deleted file mode 100644 index 45f620b..0000000 --- a/lib/admin-technologies/component.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "name": "admin-technologies", - "description": "Admin technologies view", - "dependencies": { - "component/t": "1.0.0", - "ractivejs/ractive": "v0.6.1", - "visionmedia/debug": "2.1.3", - "visionmedia/page.js": "1.6.1" - }, - "locals": [ - "confirmation", - "render", - "request", - "technologies" - ], - "templates": [ - "template.jade" - ], - "scripts": [ - "admin-technologies.js", - "view.js" - ], - "main": "admin-technologies.js" -} diff --git a/lib/admin-technologies/template.jade b/lib/admin-technologies/template.jade deleted file mode 100644 index de955db..0000000 --- a/lib/admin-technologies/template.jade +++ /dev/null @@ -1,22 +0,0 @@ -#technologies-wrapper - .technologies-admin - .row.well.well-lg - .col-xs-12.col-md-2 - a.btn.btn-success.btn-block.new(href='/admin/technologies/create') - span.fa.fa-plus - = ' ' + t('admin.list.new') - .col-xs-12.col-md-7.col-md-offset-3 - .input-group.input-group-md - span.input-group-addon - i.fa.fa-search - input.form-control.search(type='text', placeholder=t("admin.list.search.placeholder"), value='{{search}}') - .row - .list.list-group - | {{#each technologies}} - a.list-group-item - span.name='{{ name }}' - a.btn-action.pull-right(href='#' on-click='delete:{{id}}, {{ name }}') - img(src='/lib/admin/images/cancel.svg') - a.btn-action.pull-right(href='/admin/technologies/{{id}}') - img(src='/lib/admin/images/edit.svg') - | {{/each}} diff --git a/lib/admin-technologies/view.js b/lib/admin-technologies/view.js deleted file mode 100644 index 0a94116..0000000 --- a/lib/admin-technologies/view.js +++ /dev/null @@ -1,58 +0,0 @@ -/** - * Module dependencies. - */ - -var confirm = require('confirmation'); -var render = require('render'); -var log = require('debug')('civicstack:admin-technologies'); -var page = require('page'); -var Ractive = require('ractive'); -var technologies = require('technologies'); -var t = require('t'); -var template = require('./template'); -var request = require('request'); - -var TechnologiesList = Ractive.extend({ - isolated: true, - template: render(template), - onrender: function () { - this.observe('search', function (current, old) { - function filter(technology) { - var name = technology.name.toLowerCase(); - current = current ? current.toLowerCase() : '' - return current ? !!~name.indexOf(current) : true; - } - - var filtered = technologies.items.filter(filter); - this.set('technologies', filtered); - }) - - this.on('delete', function (ev, id, name) { - ev.original.preventDefault(); - - confirm(t('confirmation.title'), t('admin-technologies-form.confirmation.body', {name: name})) - .cancel(t('confirmation.cancel')) - .ok(t('confirmation.ok')) - .modal() - .closable() - .effect('slide') - .focus() - .show(onconfirmdelete.bind(this)); - - function onconfirmdelete(ok) { - if (!ok) return; - - request - .del('/api/technologies/' + id) - .end(function (err, res) { - if (err || !res.ok) return log('Found error %o', err || res.error); - - technologies.fetch(); - page('/admin/technologies'); - }); - } - }) - } -}); - -module.exports = TechnologiesList; diff --git a/lib/admin/admin.js b/lib/admin/admin.js deleted file mode 100644 index 07c86aa..0000000 --- a/lib/admin/admin.js +++ /dev/null @@ -1,53 +0,0 @@ -/** - * Module dependencies. - */ - -var AdminView = require('./view'); -var AdminSidebar = require('admin-sidebar'); -var log = require('debug')('civicstack:admin'); -var page = require('page'); -var tags = require('tags'); -var user = require('user'); - -page('/admin', '/admin/apps'); - -page("/admin/:section?/:id?", valid, user.required, user.isAdmin, function(ctx, next) { - var section = ctx.params.section; - - var admin = new AdminView({ - el: '.site-content' - }); - - var sidebar = new AdminSidebar({ - el: '.sidebar-container', - data: { - selected: section - } - }); - - // if all good, then jump to section route handler - next(); -}); - -require('admin-apps-form'); -require('admin-apps'); -require('admin-countries-form'); -require('admin-countries'); -require('admin-licenses-form'); -require('admin-licenses'); -require('admin-tags-form'); -require('admin-tags'); -require('admin-technologies-form'); -require('admin-technologies'); - -/** - * Check if page is valid - */ - -function valid(ctx, next) { - var section = ctx.params.section = ctx.params.section || 'apps'; - if (/apps|tags|countries|technologies|licenses/.test(section)) return next(); - if (/(apps|tags|countries|technologies|licenses)\/create/.test(section)) return next(); - if (/(apps|tags|countries|technologies|licenses)\/[a-z0-9]{24}\/?$/.test(section)) return next(); - page.redirect('/'); -} diff --git a/lib/admin/admin.styl b/lib/admin/admin.styl deleted file mode 100644 index 2a2b6c8..0000000 --- a/lib/admin/admin.styl +++ /dev/null @@ -1,20 +0,0 @@ -#admin-container - - .list-group-item - overflow hidden - width 100% - &:hover - background-color #f5f5f5 - - .name - height 32px - line-height 32px - - .btn-action - margin-left 6px - img - width 32px - - .panel-heading - h4 - font-weight bold \ No newline at end of file diff --git a/lib/admin/component.json b/lib/admin/component.json deleted file mode 100644 index a6ab6f1..0000000 --- a/lib/admin/component.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "name": "admin", - "description": "Admin page", - "dependencies": { - "ractivejs/ractive": "v0.6.1", - "visionmedia/debug": "2.1.3", - "visionmedia/page.js": "1.6.1" - }, - "locals": [ - "admin-sidebar", - "admin-apps", - "admin-apps-form", - "admin-countries-form", - "admin-countries", - "admin-licenses-form", - "admin-licenses", - "admin-tags", - "admin-tags-form", - "admin-technologies-form", - "admin-technologies", - "render", - "tags", - "user" - ], - "scripts": [ - "admin.js", - "view.js" - ], - "images": [ - "images/cancel.svg", - "images/edit.svg" - ], - "styles": [ "admin.styl" ], - "templates": [ "template.jade"], - "main": "admin.js" -} diff --git a/lib/admin/template.jade b/lib/admin/template.jade deleted file mode 100644 index e79d713..0000000 --- a/lib/admin/template.jade +++ /dev/null @@ -1,4 +0,0 @@ -#admin-container.container - #admin - .sidebar-container.col-sm-3 - .admin-content.col-sm-9 diff --git a/lib/admin/view.js b/lib/admin/view.js deleted file mode 100644 index aecd413..0000000 --- a/lib/admin/view.js +++ /dev/null @@ -1,14 +0,0 @@ -/** - * Module dependencies. - */ - -var render = require('render'); -var Ractive = require('ractive'); -var template = require('./template'); - -var AdminView = Ractive.extend({ - isolated: true, - template: render(template), -}); - -module.exports = AdminView; diff --git a/lib/app-detail-view/component.json b/lib/app-detail-view/component.json deleted file mode 100644 index 9c5623b..0000000 --- a/lib/app-detail-view/component.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "app-detail-view", - "dependencies": { - "ractivejs/ractive": "v0.6.1", - "visionmedia/page.js": "1.6.1" - }, - "locals": [ - "applications", - "language", - "render", - "config", - "request" - ], - "scripts": [ "view.js" ], - "styles": [ "styles.styl" ], - "templates": [ "template.jade" ], - "main": "view.js" -} diff --git a/lib/app-detail-view/styles.styl b/lib/app-detail-view/styles.styl deleted file mode 100644 index 78a33bd..0000000 --- a/lib/app-detail-view/styles.styl +++ /dev/null @@ -1,161 +0,0 @@ -.app-detail - max-width: 970px - - .app-header - position: relative - display: flex - flex-direction: row - flex-wrap: nowrap - padding: 25px - background-color: #333 - - & > * - max-width: 100% - width: 100% - - .app-logo - display: none - max-width: 220px - - @media (min-width: 540px) - .app-logo - display: block - - .app-details - padding-left: 0 - - .app-details - display: flex - flex-direction: column - padding-left: 25px - color: #fafafa - overflow: hidden - - h1 - word-break: break-word - margin: 0 0 .1em - font-size: 2em - font-weight: 900 - - .link - white-space: nowrap - overflow: hidden - text-overflow: ellipsis - color: #999 - - a - color: inherit - - .app-content - position: relative - margin-bottom: 25px - padding: 25px - background-color: #fff - - .app-more-info - width: 100% - - @media (min-width: 800px) - .app-content - display: flex - flex-direction: row - flex-wrap: nowrap - - .app-info - flex-grow: 2 - - .app-more-info - max-width: 220px - margin-left: 50px - - - .actions-wrapper - display: flex - align-items: flex-end - flex-grow: 2 - - .actions - display: flex - flex-wrap: wrap - flex-direction: row - align-items: flex-end - width: 100% - - .social - flex-grow: 2 - text-align: right - color: inherit - - a - margin-left: .3em - color: #ccc - font-size: 1.7em - - &:hover - color: #999 - text-decoration: none - - .sup - margin: 0 - color: #666 - line-height: 1 - font-size: 0.9em - - .sub - margin: 0 - font-weight: 900 - font-size: 1.2em - - .sub + .sup - margin-top: 1em - - .description - font-size: 1.2em - margin-bottom: 1em - - .cover-video - padding: 45px 0 - - .embed-container - position: relative - padding-bottom: 56.25% - height: 0 - overflow: hidden - max-width: 100% - - iframe, - object, - embed - position: absolute - top: 0 - left: 0 - width: 100% - height: 100% - - .social-badges - padding-left 15px - margin-bottom 15px - - .fb_iframe_widget - top -5px - - .app-links - .list-group-item - a - &:hover - text-decoration none - .no-og - h4 - margin-bottom 0 - - .app-tags - margin-bottom: .8em - - .label - display: inline-block - margin-right: .8em - - -.app-comments - padding: 10px 25px - background-color: #fff diff --git a/lib/app-detail-view/template.jade b/lib/app-detail-view/template.jade deleted file mode 100644 index c13125e..0000000 --- a/lib/app-detail-view/template.jade +++ /dev/null @@ -1,119 +0,0 @@ -| {{#application}} -main.app-detail.container - .app-header - .app-logo(style='background-color: {{backgroundColor}}; background-image: url({{logo}})', target='_blank') - .app-details - h1 {{name}} - p.link: a(href='{{website}}', target='_blank') {{website}} - .actions-wrapper: .actions - | {{#application.id}} - | {{#upvoted}} - a.btn-upvote.btn.btn-success.active(on-click='undoUpvote') - i.fa.fa-caret-up - |  - span {{application.upvotesCount}} - | {{/upvoted}} - | {{^upvoted}} - a.btn-upvote.btn.btn-default(on-click='upvote') - i.fa.fa-caret-up - |  - span {{application.upvotesCount}} - | {{/upvoted}} - | {{/application.id}} - .social - | {{#github}} - a.fa.fa-github(href='{{github}}', target='_blank') - | {{/github}} - | {{#twitter}} - a.fa.fa-twitter(href='https://twitter.com/{{twitter}}', title='@{{twitter}}', target='_blank') - | {{/twitter}} - | {{#contact}} - a.fa.fa-envelope-o(href='mailto:{{contact}}', title='{{contact}}', target='_blank') - | {{/contact}} - - .app-content - .app-info - | {{#description[lang]}} - p.description {{description[lang]}} - | {{/description[lang]}} - - | {{#if tagnames}} - .app-tags - | {{#each tagnames}} - span.label.label-default {{this}} - | {{/each}} - | {{/if}} - - | {{#video}} - .cover-video - .embed-container - iframe(src='{{video}}', width='100%', height='100%' frameborder='0', allowfullscreen) - | {{/video}} - - | {{#links}} - .app-links - h4.sup Links: - ul.list-group - | {{#each links}} - li.list-group-item - a(href='{{this.url}}', target='_blank') - | {{#application.id}} - .media - .media-left - img.media-object(width='64px' height='64px' src='{{this.og.image}}') - .media-body - a(href='{{this.url}}', target='_blank') {{this.description || this.url}} - h4.media-heading {{this.og.title}} - | {{this.og.description}} - .row.related-link.description - | {{/application.id}} - | {{^application.id}} - .no-og - h4 {{this.description}} - small {{this.url}} - | {{/application.id}} - | {{/each}} - | {{/links}} - - //- - .social-badges - | {{#website}} - .fb-like(data-href='{{website}}', data-layout='button_count', - data-action='like', - data-size='small', - data-show-faces='true', - data-share='false') - | {{/website}} - - | {{#twitter}} - a.twitter-follow-button(href='https://twitter.com/{{twitter}}', data-show-count='true') - | Follow @{{twitter}} - | {{/twitter}} - - | {{#github}} - a.github-button(href='{{github}}', - data-count-href='/{{githubShort}}/stargazers', - data-count-api='/repos/{{githubShort}}#stargazers_count', - data-count-aria-label='# stargazers on GitHub', - aria-label='Star {{githubShort}} on GitHub') - | Star - | {{/github}} - - .app-more-info - p.sup= t('app.country') - p.sub {{country.name[lang]}} - p.sup= t('app.organization-name') - p.sub {{organization}} - | {{#license}} - p.sup= t('app.license') - p.sub {{license.name}} - | {{/license}} - | {{#technologynames}} - p.sup= t('app.technology') - p.sub {{technologynames}} - | {{/technologynames}} - - | {{#showDisqus}} - #disqus_thread.app-comments.hide - | {{/showDisqus}} -| {{/application}} diff --git a/lib/app-detail-view/view.js b/lib/app-detail-view/view.js deleted file mode 100644 index cfc31d1..0000000 --- a/lib/app-detail-view/view.js +++ /dev/null @@ -1,117 +0,0 @@ -var applications = require('applications').approved; -var language = require('language'); -var Ractive = require('ractive'); -var render = require('render'); -var config = require('config'); -var request = require('request'); -var page = require('page'); -var template = require('./template'); - -module.exports = Ractive.extend({ - isolated: true, - template: render(template), - onrender: ApplicationDetail, - oncomplete: onComplete -}); - -function short (ghURL) { - if (!ghURL) return ''; - var splitted = ghURL.split('/'); - return splitted[splitted.length - 2] + '/' + splitted[splitted.length - 1]; -} - -function ApplicationDetail () { - var application = this.data.application; - var showDisqus = this.data.disqus; - var tags = application.tags; - var lang = language(); - - function name (application) { - return application.name[lang]; - } - - function technologyname (technology) { - return technology.name; - } - - var tagnames = tags.map(name); - var technologynames = application.technology.map(technologyname).join(', '); - - this.set('tagnames', tagnames); - this.set('technologynames', technologynames); - this.set('lang', lang); - this.set('host', config.host); - this.set('showDisqus', showDisqus); - this.set('facebook', this.data.application.facebook); - this.set('twitter', this.data.application.twitter); - this.set('github', this.data.application.github); - this.set('githubShort', short(this.data.application.github)); - - registerUpvoteEvents.apply(this); -} - -function onComplete () { - if (!this.data.application.id) return; - - this.on('*.signinrequired', function () { - page('/login'); - }); - - var self = this; - this.set('linksLoading', true); - request - .get('/api/applications/' + this.data.application.id + '/links') - .end(function (err, res) { - self.set('linksLoading', false); - if (!err && res.ok) self.set('application.links', res.body); - }); - - var getDisqusState = function () { - var data = this.data; - return function () { - this.page.url = data.host + '/apps/' + data.application.id; - this.page.identifier = data.application.id; - }; - }; - - // Reload Disqus widget. This enables to have a different discussion forum per page. - if (window.DISQUS && window.DISQUS.reset) { - window.DISQUS.reset({ - reload: true, - config: getDisqusState.call(this) - }); - } -} - -function registerUpvoteEvents () { - function handleResponse (err, res) { - if (!err && res.ok) { - this.set('application.upvoted', res.body.upvoted); - this.set('application.upvotesCount', res.body.upvotesCount); - } - } - - var self = this; - var url = '/api/applications/' + this.data.application.id + '/upvote'; - - this.on('upvote', function () { - if (self.data.user.logged()) { - request.post(url).end(handleResponse.bind(this)); - } else { - page('/login'); - } - }); - - this.on('undoUpvote', function () { - if (self.data.user.logged()) { - request.del(url).end(handleResponse.bind(this)); - } else { - page('/login'); - } - }); - - this.on('back', function () { - applications.fetch(); - page('/'); - }); -} diff --git a/lib/app-form/app-form.js b/lib/app-form/app-form.js deleted file mode 100644 index f5ccef4..0000000 --- a/lib/app-form/app-form.js +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Module dependencies. - */ - -var AppForm = require('./view'); -var classes = require('classes'); -var countries = require('countries'); -var licenses = require('licenses'); -var page = require('page'); -var tags = require('tags'); -var technologies = require('technologies'); -var user = require('user'); - -page('/apps/new', - user.required, - tags.middleware, - countries.middleware, - licenses.middleware, - technologies.middleware, - function (ctx, next) { - classes(document.body).add('app-form'); - - var form = new AppForm({ - el: '.site-content', - replace: true - }); -}); diff --git a/lib/app-form/component.json b/lib/app-form/component.json deleted file mode 100644 index 85ba56f..0000000 --- a/lib/app-form/component.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "name": "app-form", - "description": "Upload new app form", - "dependencies": { - "component/classes": "1.2.4", - "component/t": "1.0.0", - "ractivejs/ractive": "v0.6.1", - "visionmedia/page.js": "1.6.1" - }, - "locals": [ - "app-detail-view", - "color-picker", - "countries", - "language", - "licenses", - "modal", - "tags", - "technologies", - "related-links", - "render", - "request", - "user" - ], - "scripts": [ "app-form.js", "view.js" ], - "styles": [ "styles.styl" ], - "templates": [ "template.jade" ], - "main": "app-form.js" -} diff --git a/lib/app-form/styles.styl b/lib/app-form/styles.styl deleted file mode 100644 index aeea274..0000000 --- a/lib/app-form/styles.styl +++ /dev/null @@ -1,77 +0,0 @@ -body.app-form - background-color #e76056 - transition all ease .1s - - .error - background-color #f2dede - border 1px solid #d9534f - color #d9534f - outline none - - .step-1, - .step-2, - .header-step - color #fff - - .header-step - transition all ease .5s - - .left-container - padding-top 10px - - .disabled - color #f08d85 - - .color-picker - text-align center - - input - color #6d6d6d - padding 12px - margin-bottom 10px - width 100% - &.rgb - text-align center - text-transform uppercase - - .background-color - background-color #262626 - max-width 275px - height 200px - margin 0 auto - - h1 - height 200px - line-height 200px - margin 0 - text-align center - width 100% - - .title-container - background-color #d7d7d7 - height 200px - margin 0 auto - max-width 275px - padding 22px 0 - text-align center - - textarea - color #6d6d6d - height 100px - margin 0 auto - padding 6px 12px - resize none - width 80% - - textarea.description - height 100px - - .checkbox .label:hover - cursor default - - .continue - margin-top 10px - - .app-detail - .btn.back - display none \ No newline at end of file diff --git a/lib/app-form/template.jade b/lib/app-form/template.jade deleted file mode 100644 index ad752fb..0000000 --- a/lib/app-form/template.jade +++ /dev/null @@ -1,115 +0,0 @@ -section.container - .row - .col-xs-12.col-md-2.col-md-offset-3.text-center.header-step.header-step-1: h2 1. #{t('app-form.header.cover')} - .col-xs-12.col-md-2.hidden-xs.hidden-sm.text-center.disabled.header-step.header-step-2: h2 2. #{t('app-form.header.content')} - .col-xs-12.col-md-2.hidden-xs.hidden-sm.text-center.disabled.header-step.header-step-3: h2 3. #{t('app-form.header.publish')} - | {{#if step == 1 }} - .row.step.step-1 - .col-xs-12.col-sm-6.col-sm-offset-3.col-md-3.col-md-offset-3.left-container - .background-color.app-logo(style='background-image: url({{ app.logo }})') - | {{#if !app.logo }} - h1 Logo - | {{/if}} - .title-container - textarea(placeholder=t('app-form.name.placeholder') value='{{ app.name }}' class='{{ errors.name }}') - .col-xs-12.col-sm-6.col-sm-offset-3.col-md-offset-0.col-md-3 - h4=t('app-form.cover.logo-title') - input.logo(type='text' value='{{ app.logo }}') - ol - li!=t('app-form.cover.logo-instructions') - li!=t('app-form.cover.background-instructions') - colorpicker(value='{{ app.backgroundColor }}') - input.rgb(type='text' value='{{ app.backgroundColor }}' disabled) - .row - .col-xs-12.col-sm-4.col-sm-offset-4 - button.btn.btn-success.btn-block.continue(on-click='goto: 2') - =t('app-form.button.continue') + ' ' - i.fa.fa-check-circle - | {{/if}} - | {{#if step == 2 }} - .row.step.step-2 - .col-xs-12.col-sm-4 - .container - .row - .col-xs-12.col-sm-6.col-sm-offset-3 - h2.text-center: strong='{{ app.name }}' - form.row - .col-xs-12.col-sm-3.col-sm-offset-3 - .form-group - input.form-control(placeholder=t('app.organization-name') value='{{ app.organization }}' class='{{ errors.organization }}') - .form-group - select.form-control(value='{{ app.country }}' class='{{ errors.country }}') - option(value='')=t('app.country') - | {{#each countries}} - option(value='{{ this.id }}') {{ this.name[lang] }} - | {{/each}} - .form-group - input.form-control(placeholder=t('app.website') value='{{ app.website }}' class='{{ errors.website }}') - .form-group - .input-group - .input-group-addon @ - input.form-control(placeholder=t('app.twitter') value='{{ app.twitter }}') - .form-group - select.form-control(value='{{ app.license }}') - option(value='')=t('app.license') - | {{#each licenses}} - option(value='{{ this.id }}') {{ this.name }} - | {{/each}} - .col-xs-12.col-sm-3 - .form-group - textarea.description.form-control(placeholder=t('app.description.placeholder') value='{{ app.description[lang] }}' class='{{ errors.description }}') - .form-group - input.form-control(placeholder=t('app.github-repository') value='{{ app.github }}' class='{{ errors.github }}') - .form-group - input.form-control(placeholder=t('app.contact.placeholder') value='{{ app.contact }}' class='{{ errors.contact }}') - .row - .col-xs-12.col-sm-6.col-sm-offset-3 - .form-group - textarea.form-control(placeholder=t('app.comments') value='{{ app.comments }}') - .row - .col-xs-12.col-sm-6.col-sm-offset-3 - .form-group - h4: strong=t('app.tags') - | {{#each tags }} - .checkbox - label - input(type='checkbox' name='{{ app.tags }}' value='{{ this }}') - ='{{ this.name[lang] }}' - | {{/each }} - .row - .col-xs-12.col-sm-6.col-sm-offset-3 - .form-group - h4: strong=t('app.technology') - | {{#each technologies }} - .checkbox - label - input(type='checkbox' name='{{ app.technology }}' value='{{ this }}') - ='{{ this.name }}' - | {{/each }} - .row - .col-xs-12.col-sm-6.col-sm-offset-3 - .form-group - h4: strong=t('app.links') - relatedlinks(links='{{app.links}}') - .row - .col-xs-12.col-sm-2.col-sm-offset-4 - button.btn.btn-danger.btn-block.continue(on-click='goto: 1') - i.fa.fa-times-circle-o - =' ' + t('app-form.button.cancel') - .col-xs-12.col-sm-2 - button.btn.btn-success.btn-block.continue(on-click='goto: 3') - =t('app-form.button.continue') + ' ' - i.fa.fa-check-circle - | {{/if}} - | {{#if step == 3 }} - appdetail(application='{{ app }}', disqus=false) - .row - .col-xs-12.col-sm-2.col-sm-offset-4 - button.btn.btn-danger.btn-block.continue(on-click='goto: 2') - i.fa.fa-pencil-square-o - =' ' + t('app-form.button.edit') - .col-xs-12.col-sm-2 - button.btn.btn-success.btn-block.continue(on-click='save') - =t('app-form.button.done') + ' ' - i.fa.fa-check - | {{/if}} diff --git a/lib/app-form/view.js b/lib/app-form/view.js deleted file mode 100644 index 6df9f4d..0000000 --- a/lib/app-form/view.js +++ /dev/null @@ -1,145 +0,0 @@ -/** - * Module dependencies. - */ - -var Detail = require('app-detail-view'); -var language = require('language'); -var classes = require('classes'); -var ColorPicker = require('color-picker'); -var countries = require('countries'); -var licenses = require('licenses'); -var Modal = require('modal'); -var RelatedLinks = require('related-links'); -var page = require('page'); -var Ractive = require('ractive'); -var render = require('render'); -var request = require('request'); -var t = require('t'); -var tags = require('tags'); -var technologies = require('technologies'); -var template = require('./template'); - -/** - * Expose Homepage - */ - -module.exports = Ractive.extend({ - template: render(template), - components: { - appdetail: Detail, - colorpicker: ColorPicker, - relatedlinks: RelatedLinks - }, - validate: function validate(step) { - var app = this.get('app'); - var errors = this.get('errors'); - var ok = true; - - function validateAttribute(attr) { - if (!app[attr]) this.set('errors.' + attr, 'error'), ok = false; - } - - validateAttribute = validateAttribute.bind(this); - - if ((step - 1) == 1) { - validateAttribute('name'); - } else if ((step - 1) == 2) { - validateAttribute('organization'); - validateAttribute('country'); - validateAttribute('website'); - validateAttribute('github'); - validateAttribute('contact'); - if (!app.description[language()]) { - this.set('errors.description', 'error') - ok = false; - } else if (app.description[language()].length > 300) { - this.set('errors.description', 'error') - ok = false; - } - } - return ok; - }, - - observeAppError: function (attr) { - this.observe('app.' + attr, function (newValue) { - if (newValue) this.set('errors.' + attr, ''); - }) - }, - onrender: function () { - this.set('lang', language()); - this.set('countries', countries.items); - this.set('licenses', licenses.items); - this.set('tags', tags.items); - this.set('technologies', technologies.items); - this.set('step', 1); - var app = {}; - app.backgroundColor = 'rgb(38, 38, 38)'; - app.description = {}; - app.links = []; - this.set('app', app); - this.set('errors', {}); - - this.observe('step', function (newValue, oldValue) { - var oldStep = this.find('.header-step-' + oldValue); - var newStep = this.find('.header-step-' + newValue); - - if (oldStep) classes(oldStep).add('disabled').add('hidden-sm').add('hidden-xs'); - if (newStep) classes(newStep).remove('disabled').remove('hidden-sm').remove('hidden-xs'); - }); - - this.on('colorpicker.colorchange', function (color) { - this.set('app.backgroundColor', color); - this.find('.background-color').style.backgroundColor = color; - }) - - this.on('goto', function ( event, step ) { - if (this.validate(step)) { - this.set('step', step); - } - }); - - this.observeAppError('name'); - this.observeAppError('organization'); - this.observeAppError('country'); - this.observeAppError('github'); - this.observeAppError('website'); - this.observeAppError('description'); - - this.on('save', function (ev) { - var app = this.get('app'); - app.tagids = []; - app.technologyids = []; - - function tagid(tag) { - app.tagids.push(tag.id); - } - - function technologyid(technology) { - app.technologyids.push(technology.id); - } - - app.tags.map(tagid); - app.technology.map(technologyid); - - request - .post('/api/applications/create') - .send(app) - .end(function (err, res) { - if (err) { - - } - - var modal = new Modal({ - data: { - title: t('app-form.modal.title'), - body: t('app-form.modal.body') - } - }) - modal.render('body'); - modal.on('close', function () { - page.redirect('/'); - }) - }) - }) - } -}); diff --git a/lib/app/actions/apps.js b/lib/app/actions/apps.js new file mode 100644 index 0000000..286757f --- /dev/null +++ b/lib/app/actions/apps.js @@ -0,0 +1,52 @@ +const { get, post } = require('../utils/http') + +module.exports.fetchApps = () => (dispatch, getState) => { + dispatch(requestApps()) + get('/api/applications/approved') + .then(response => response.status >= 400 ? dispatch(failReceivingApps(response.status)) : response.json()) + .then(data => dispatch(receiveApps(data))) + .catch(err => dispatch(failReceivingApps(err))) +} + +const requestApps = () => ({ + type: 'REQUEST_APPS' +}) + +const receiveApps = apps => ({ + type: 'RECEIVE_APPS', + apps +}) + +const failReceivingApps = error => ({ + type: 'FAIL_RECEIVING_APPS', + error +}) + +module.exports.filterApps = filters => ({ + type: 'FILTER_APPS', + filters +}) + +export const clearAppsFilters = () => ({ + type: 'CLEAR_APPS_FILTERS' +}) + +module.exports.searchApps = searchPredicate => ({ + type: 'SEARCH_APPS', + searchPredicate +}) + +export const addUpvote = applicationId => (dispatch, getState) => { + dispatch(setUpvoted(applicationId)) + post(`/api/applications/${applicationId}/upvote`).catch(() => dispatch(clearUpvote(applicationId))) +} + +const setUpvoted = applicationId => ({ + type: 'SET_UPVOTED', + applicationId +}) + +const clearUpvote = applicationId => ({ + type: 'CLEAR_UPVOTE', + applicationId +}) diff --git a/lib/app/actions/index.js b/lib/app/actions/index.js new file mode 100644 index 0000000..21c99a5 --- /dev/null +++ b/lib/app/actions/index.js @@ -0,0 +1,8 @@ +const { fetchApps, filterApps, searchApps, addUpvote } = require('./apps') + +module.exports = { + fetchApps, + filterApps, + searchApps, + addUpvote +} diff --git a/lib/app/components/AppList/AppCard.js b/lib/app/components/AppList/AppCard.js new file mode 100644 index 0000000..11687e6 --- /dev/null +++ b/lib/app/components/AppList/AppCard.js @@ -0,0 +1,59 @@ +const React = require('react') +const { PropTypes } = React + +const AppCard = ({ + id, + upvoted, + onUpvote, + href, + name, + backgroundColor, + backgroundImageURL, + countryName, + description, + twitterHandle, + upvoteCount +}) => ( +
+ +
+) + +AppCard.propTypes = { + href: PropTypes.string.isRequired, + name: PropTypes.string.isRequired, + backgroundColor: PropTypes.string, + backgroundImageURL: PropTypes.string, + countryName: PropTypes.string.isRequired, + description: PropTypes.string.isRequired, + twitterHandle: PropTypes.string, + upvoteCount: PropTypes.number.isRequired +} + +module.exports = AppCard diff --git a/lib/app/components/AppList/CountriesModal.js b/lib/app/components/AppList/CountriesModal.js new file mode 100644 index 0000000..377f4de --- /dev/null +++ b/lib/app/components/AppList/CountriesModal.js @@ -0,0 +1,49 @@ +const React = require('react') +const { Component, PropTypes } = React +const FilterModal = require('./FilterModal') + +module.exports = class CountriesModal extends Component { + static propTypes = { + items: PropTypes.arrayOf(PropTypes.shape({ + selected: PropTypes.bool.isRequired, + id: PropTypes.string.isRequired, + name: PropTypes.string.isRequired + })), + show: PropTypes.bool, + onCloseClick: PropTypes.func, + onApplyFilterClick: PropTypes.func, + onClearFiltersClick: PropTypes.func + } + + constructor (props) { + super(props) + this.state = { + items: props.items + } + } + + render () { + const { show, onCloseClick, onApplyFilterClick, onClearFiltersClick } = this.props + const { items } = this.state + return ( + onApplyFilterClick(this.state.items)} + onClearFiltersClick={onClearFiltersClick}> +
+ {items.map(({ selected, id, name }) => ( +
+
+ +
+
+ ))} +
+
+ ) + } +} diff --git a/lib/app/components/AppList/FilterModal.js b/lib/app/components/AppList/FilterModal.js new file mode 100644 index 0000000..29a68a6 --- /dev/null +++ b/lib/app/components/AppList/FilterModal.js @@ -0,0 +1,35 @@ +const React = require('react') +const { PropTypes } = React +const Modal = require('../Modal') + +const FilterModal = ({ title, show, children, onClearFiltersClick, onSearchClick, onCloseClick }) => ( + +
{title} + +
+ +
{children}
+ +
+ + +
+
+) + +FilterModal.propTypes = { + title: PropTypes.string, + show: PropTypes.bool, + children: PropTypes.element, + onClearFiltersClick: PropTypes.func.isRequired, + onCloseClick: PropTypes.func.isRequired, + onSearchClick: PropTypes.func.isRequired +} + +module.exports = FilterModal diff --git a/lib/app/components/AppList/PrefilterButton.js b/lib/app/components/AppList/PrefilterButton.js new file mode 100644 index 0000000..b4b24f8 --- /dev/null +++ b/lib/app/components/AppList/PrefilterButton.js @@ -0,0 +1,25 @@ +const React = require('react') +const { PropTypes, Component } = React + +module.exports = class PrefilterButton extends Component { + static propTypes = { + tags: PropTypes.arrayOf(PropTypes.string), + sort: PropTypes.string, + order: PropTypes.string, + onClick: PropTypes.func.isRequired, + text: PropTypes.string + } + + handleClick = ev => { + const { tags, sort, order } = this.props + this.props.onClick({ tags, sort, order }) + } + + render () { + return ( + + ) + } +} diff --git a/lib/app/components/AppList/Prefilters.js b/lib/app/components/AppList/Prefilters.js new file mode 100644 index 0000000..8c07b74 --- /dev/null +++ b/lib/app/components/AppList/Prefilters.js @@ -0,0 +1,14 @@ +const React = require('react') +const { PropTypes } = React + +const Prefilters = ({ children }) => ( +
+ {children} +
+) + +Prefilters.propTypes = { + children: PropTypes.element +} + +module.exports = Prefilters diff --git a/lib/app/components/AppList/TagsModal.js b/lib/app/components/AppList/TagsModal.js new file mode 100644 index 0000000..52d7f7f --- /dev/null +++ b/lib/app/components/AppList/TagsModal.js @@ -0,0 +1,49 @@ +const React = require('react') +const { Component, PropTypes } = React +const FilterModal = require('./FilterModal') + +module.exports = class TagsModal extends Component { + static propTypes = { + items: PropTypes.arrayOf(PropTypes.shape({ + selected: PropTypes.bool.isRequired, + id: PropTypes.string.isRequired, + name: PropTypes.string.isRequired + })), + show: PropTypes.bool, + onCloseClick: PropTypes.func, + onApplyFilterClick: PropTypes.func, + onClearFiltersClick: PropTypes.func + } + + constructor (props) { + super(props) + this.state = { + items: props.items + } + } + + render () { + const { show, onCloseClick, onApplyFilterClick, onClearFiltersClick } = this.props + const { items } = this.state + return ( + onApplyFilterClick(this.state.items)} + onClearFiltersClick={onClearFiltersClick}> +
+ {items.map(({ selected, id, name }) => ( +
+
+ +
+
+ ))} +
+
+ ) + } +} diff --git a/lib/app/components/AppList/TechnologiesModal.js b/lib/app/components/AppList/TechnologiesModal.js new file mode 100644 index 0000000..4a3e47b --- /dev/null +++ b/lib/app/components/AppList/TechnologiesModal.js @@ -0,0 +1,49 @@ +const React = require('react') +const { Component, PropTypes } = React +const FilterModal = require('./FilterModal') + +module.exports = class TechnologiesModal extends Component { + static propTypes = { + items: PropTypes.arrayOf(PropTypes.shape({ + selected: PropTypes.bool.isRequired, + id: PropTypes.string.isRequired, + name: PropTypes.string.isRequired + })), + show: PropTypes.bool, + onCloseClick: PropTypes.func, + onApplyFilterClick: PropTypes.func, + onClearFiltersClick: PropTypes.func + } + + constructor (props) { + super(props) + this.state = { + items: props.items + } + } + + render () { + const { show, onCloseClick, onApplyFilterClick, onClearFiltersClick } = this.props + const { items } = this.state + return ( + onApplyFilterClick(this.state.items)} + onClearFiltersClick={onClearFiltersClick}> +
+ {items.map(({ selected, id, name }) => ( +
+
+ +
+
+ ))} +
+
+ ) + } +} diff --git a/lib/app/components/Drawer/index.js b/lib/app/components/Drawer/index.js new file mode 100644 index 0000000..c8691f8 --- /dev/null +++ b/lib/app/components/Drawer/index.js @@ -0,0 +1,50 @@ +const React = require('react') +const { PropTypes } = React + +const Drawer = ({ onCloseClick }) => ( + +) + +Drawer.propTypes = { + onCloseClick: PropTypes.func +} + +module.exports = Drawer diff --git a/lib/app/components/Footer/index.js b/lib/app/components/Footer/index.js new file mode 100644 index 0000000..4cf2dce --- /dev/null +++ b/lib/app/components/Footer/index.js @@ -0,0 +1,16 @@ +const React = require('react') +const { footer, container } = require('./styles') + +class Footer extends React.Component { + render () { + return ( + + ) + } +} + +module.exports = Footer diff --git a/lib/app/components/Footer/styles.js b/lib/app/components/Footer/styles.js new file mode 100644 index 0000000..f74efb7 --- /dev/null +++ b/lib/app/components/Footer/styles.js @@ -0,0 +1,20 @@ +const { style, merge, $ } = require('glamor') + +module.exports.footer = merge({ + paddingTop: '60px', + paddingBottom: '30px', + textAlign: 'center' + }, + $(' a', { + fontWeight: 'bold' + })) + +module.exports.container = style({ + paddingRight: '15px', + paddingLeft: '15px', + marginRight: 'auto', + marginLeft: 'auto', + '@media (min-width: 768px)': { + width: '750px' + } +}) diff --git a/lib/app/components/Header/index.js b/lib/app/components/Header/index.js new file mode 100644 index 0000000..3500f8c --- /dev/null +++ b/lib/app/components/Header/index.js @@ -0,0 +1,47 @@ +const React = require('react') +const { PropTypes } = React +const { style } = require('glamor') + +const siteHeader = style({ + height: '80px', + borderBottom: '2px', + backgroundColor: '#fafafa', + ':after': { + content: '', + display: 'block', + position: 'absolute', + bottom: 0, + left: 0, + width: '100%', + height: '1px', + backgroundPosition: 'center center', + backgroundImage: 'repeating-linear-gradient(to right, #f7c64d 0px, #f7c64d 120px, #61baa8 120px, #61baa8 240px, #1394af 240px, #1394af 360px, #d93f5d 360px, #d93f5d 480px, #714c80 480px, #714c80 600px, #e16b55 600px, #e16b55 720px)' + } +}) + +const Header = ({ onHamburgerClick }) => ( + +) + +Header.propTypes = { + onHamburgerClick: PropTypes.func +} + +module.exports = Header diff --git a/lib/app/components/Modal/index.js b/lib/app/components/Modal/index.js new file mode 100644 index 0000000..06f1d23 --- /dev/null +++ b/lib/app/components/Modal/index.js @@ -0,0 +1,29 @@ +const React = require('react') +const { Component, PropTypes } = React +const { css } = require('glamor') + +class Modal extends Component { + static propTypes = { + children: PropTypes.arrayOf(PropTypes.element), + show: PropTypes.bool + } + + render () { + const { children, show } = this.props + return ( +
+
+
+ {children} +
+
+
+ ) + } +} + +const style = css` + overflow: scroll; +` + +module.exports = Modal diff --git a/lib/app/containers/AppList.js b/lib/app/containers/AppList.js new file mode 100644 index 0000000..a2bb7a1 --- /dev/null +++ b/lib/app/containers/AppList.js @@ -0,0 +1,245 @@ +const debounce = require('lodash.debounce') +const React = require('react') +const { connect } = require('react-redux') + +const { fetchApps, filterApps, searchApps, addUpvote } = require('../actions') + +const AppCard = require('../components/AppList/AppCard') + +const CountriesModal = require('../components/AppList/CountriesModal') +const TechnologiesModal = require('../components/AppList/TechnologiesModal') +const TagsModal = require('../components/AppList/TagsModal') + +const Prefilters = require('../components/AppList/Prefilters') +const PrefilterButton = require('../components/AppList/PrefilterButton') + +const { PropTypes, Component } = React + +class AppList extends Component { + static propTypes = { + apps: PropTypes.arrayOf(PropTypes.object), + countries: PropTypes.arrayOf(PropTypes.shape({ + value: PropTypes.string.isRequired, + label: PropTypes.string.isRequired + })).isRequired, + tags: PropTypes.arrayOf(PropTypes.shape({ + value: PropTypes.string.isRequired, + label: PropTypes.string.isRequired + })).isRequired, + technologies: PropTypes.arrayOf(PropTypes.shape({ + value: PropTypes.string.isRequired, + label: PropTypes.string.isRequired + })).isRequired, + searchPredicate: PropTypes.string, + dispatch: PropTypes.func.isRequired + } + + constructor (props) { + super(props) + + this.onKeyUp = debounce(this.onKeyUp, 500) + + this.state = { + modals: { + countries: false, + technologies: false, + tags: false + }, + filters: { + countries: [], + technologies: [], + tags: [] + }, + searchPredicate: props.searchPredicate + } + } + + componentDidMount () { + this.props.dispatch(fetchApps()) + } + + applyPrefilter = filter => { + this.props.dispatch(filterApps({ + countries: [], + technologies: [], + tags: filter.tags + })) + } + + onCountriesFilter = items => { + this.setState({ + modals: { countries: false }, + filters: Object.assign(this.state.filters, { + countries: items.filter(({ selected }) => selected).map(({ id }) => id) + }) + }, () => { this.props.dispatch(filterApps(this.state.filters)) }) + } + + onClearCountriesFilter = () => { + this.setState({ + modals: { countries: false }, + filters: { countries: [] } + }, () => { this.props.dispatch(filterApps(this.state.filters)) }) + } + + onTechnologiesFilter = items => { + this.setState({ + modals: { technologies: false }, + filters: Object.assign(this.state.filters, { + technologies: items.filter(({ selected }) => selected).map(({ id }) => id) + }) + }, () => { this.props.dispatch(filterApps(this.state.filters)) }) + } + + onClearTechnologiesFilter = () => { + this.setState({ + modals: { technologies: false }, + filters: { technologies: [] } + }, () => { this.props.dispatch(filterApps(this.state.filters)) }) + } + + onTagsFilter = items => { + this.setState({ + modals: { tags: false }, + filters: Object.assign(this.state.filters, { + tags: items.filter(({ selected }) => selected).map(({ id }) => id) + }) + }, () => { this.props.dispatch(filterApps(this.state.filters)) }) + } + + onClearTagsFilter = () => { + this.setState({ + modals: { tags: false }, + filters: { tags: [] } + }, () => { this.props.dispatch(filterApps(this.state.filters)) }) + } + + onChange = ev => { + this.setState({ searchPredicate: ev.target.value }) + } + + onKeyUp = ev => { + this.props.dispatch(searchApps(this.state.searchPredicate)) + } + + toggleUpvote = ev => { + const { appid } = ev.currentTarget.dataset + this.props.dispatch(addUpvote(appid)) + } + + render () { + const { apps, countries, technologies, tags } = this.props + const { modals } = this.state + + return ( +
+ this.setState({ modals: { countries: false } })} + onApplyFilterClick={this.onCountriesFilter} + onClearFiltersClick={this.onClearCountriesFilter} /> + this.setState({ modals: { technologies: false } })} + onApplyFilterClick={this.onTechnologiesFilter} + onClearFiltersClick={this.onClearTechnologiesFilter} /> + this.setState({ modals: { tags: false } })} + onApplyFilterClick={this.onTagsFilter} + onClearFiltersClick={this.onClearTagsFilter} /> + +
+ +
+

Herramientas de código abierto para acciones políticas y sociales.

+
+ + + + + + + + + +
+
+ + +
+
+ + + +
+
+ +
+
+
+
+
+
+
+
+ {apps.map((app) => ( + + ))} +
+
+
+ ) + } +} + +const mapStateToProps = ({ apps, meta }) => ({ + loading: apps.fetching, + locale: meta.locale, + apps: apps.items.map(app => ({ + ...app, + description: app.description[meta.locale], + country: { + ...app.country, + name: app.country.name[meta.locale] + } + })), + countries: meta.countries.map(country => Object.assign({}, country, { + selected: apps.filters.countries.includes(country.id) + })), + tags: meta.tags.map(tag => Object.assign({}, tag, { + selected: apps.filters.tags.includes(tag.id) + })), + technologies: meta.technologies.map(technology => Object.assign({}, technology, { + selected: apps.filters.technologies.includes(technology.id) + })), + searchPredicate: apps.searchPredicate +}) + +module.exports = connect(mapStateToProps)(AppList) diff --git a/lib/app/containers/AppShow.js b/lib/app/containers/AppShow.js new file mode 100644 index 0000000..7cfd027 --- /dev/null +++ b/lib/app/containers/AppShow.js @@ -0,0 +1,10 @@ +const React = require('react') + +module.exports = ({ match }) => { + debugger + return ( +
+

Showing App { match.params.id }

+
+ ) +} diff --git a/lib/app/containers/Layout.js b/lib/app/containers/Layout.js new file mode 100644 index 0000000..1ec7dda --- /dev/null +++ b/lib/app/containers/Layout.js @@ -0,0 +1,45 @@ +const React = require('react') +const { PropTypes } = require('react') +const { Route, Switch } = require('react-router-dom') +const Header = require('../components/Header') +const Footer = require('../components/Footer') +const Drawer = require('../components/Drawer') +const AppList = require('./AppList') +const AppShow = require('./AppShow') + +module.exports = class Layout extends React.Component { + state = { + showDrawer: false + } + + toggleDrawer = () => { + this.setState({ + showDrawer: !this.state.showDrawer + }) + } + + render () { + const { showDrawer } = this.state + + return ( +
+
+ + ( + + )} /> + + + +
+ {showDrawer && } +
+ ) + } +} + +function NotFound () { + return
Not Found.
+} diff --git a/lib/app/home.js b/lib/app/home.js new file mode 100644 index 0000000..92dd9b5 --- /dev/null +++ b/lib/app/home.js @@ -0,0 +1,16 @@ +const React = require('react') +const ReactDOM = require('react-dom') +const { Provider } = require('react-redux') +const Router = require('react-router-dom/BrowserRouter').default + +const createStore = require('./store') + +const Layout = require('./containers/Layout') +const store = createStore(window.__INITIAL_STATE__) + +ReactDOM.render( + + + + + , document.getElementById('react-root')) diff --git a/lib/app/reducers/apps.js b/lib/app/reducers/apps.js new file mode 100644 index 0000000..6666214 --- /dev/null +++ b/lib/app/reducers/apps.js @@ -0,0 +1,76 @@ +const Fuse = require('fuse.js') + +const initialState = { + items: [], + allItems: [], + invalid: true, + fetching: false, + filters: { + countries: [], + technologies: [], + tags: [] + }, + fuse: null, + searchPredicate: '', + error: null +} + +module.exports = (state = initialState, action) => { + switch (action.type) { + case 'REQUEST_APPS': + return Object.assign({}, state, { + error: null, + invalid: true, + fetching: true + }) + case 'RECEIVE_APPS': + return Object.assign({}, state, { + error: null, + invalid: false, + fetching: false, + allItems: action.apps, + items: action.apps, + fuse: new Fuse(action.apps, { keys: ['name', 'organization', 'description'] }) + }) + case 'FAIL_RECEIVING_APPS': + return Object.assign({}, state, { + error: action.error, + invalid: true, + fetching: false + }) + case 'SEARCH_APPS': + return Object.assign({}, state, { + searchPredicate: action.searchPredicate, + items: (action.searchPredicate && state.fuse) ? state.fuse.search(action.searchPredicate) : state.allItems + }) + case 'FILTER_APPS': + const filterByCountries = (items = [], countries = []) => countries.length + ? items.filter(({ country }) => countries.includes(country.id)) + : items + const filterByTechnologies = (items = [], technologies = []) => technologies.length + ? items.filter(({ technology }) => technologies.some(t => technology.includes(t))) + : items + const filterByTags = (items = [], tags = []) => tags.length + ? items.filter(item => tags.some(t => item.tags.includes(t))) + : items + + return Object.assign({}, state, { + filters: action.filters, + items: filterByTags(filterByTechnologies(filterByCountries(state.allItems, action.filters.countries), action.filters.technologies), action.filters.tags) + }) + case 'SET_UPVOTED': + return Object.assign({}, state, { + items: state.items.map(app => Object.assign({}, app, { + upvoted: app.id === action.applicationId || app.upvoted + })) + }) + case 'CLEAR_UPVOTE': + return Object.assign({}, state, { + items: state.items.map(app => Object.assign({}, app, { + upvoted: app.id === action.applicationId ? undefined : app.upvoted + })) + }) + default: + return state + } +} diff --git a/lib/app/reducers/index.js b/lib/app/reducers/index.js new file mode 100644 index 0000000..15f4bcf --- /dev/null +++ b/lib/app/reducers/index.js @@ -0,0 +1,6 @@ +const { combineReducers } = require('redux') + +const meta = require('./meta') +const apps = require('./apps') + +module.exports = combineReducers({ meta, apps }) diff --git a/lib/app/reducers/meta.js b/lib/app/reducers/meta.js new file mode 100644 index 0000000..67cf933 --- /dev/null +++ b/lib/app/reducers/meta.js @@ -0,0 +1,7 @@ +const initialState = { + locale: 'es' +} + +module.exports = (state = initialState, action) => { + return state +} diff --git a/lib/app/store/index.js b/lib/app/store/index.js new file mode 100644 index 0000000..09e4e6a --- /dev/null +++ b/lib/app/store/index.js @@ -0,0 +1,6 @@ +const { createStore, applyMiddleware } = require('redux') +const thunk = require('redux-thunk').default +const logger = require('redux-logger') +const reducers = require('../reducers') + +module.exports = (initialState = {}) => createStore(reducers, initialState, applyMiddleware(thunk, logger())) diff --git a/lib/app/utils/http/index.js b/lib/app/utils/http/index.js new file mode 100644 index 0000000..ea6d538 --- /dev/null +++ b/lib/app/utils/http/index.js @@ -0,0 +1,17 @@ +const fetch = require('isomorphic-fetch') + +const params = { + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json', + 'cookie': document.cookie + }, + credentials: 'include' +} + +module.exports.get = path => fetch(path, params) + +module.exports.post = path => fetch(path, { + ...params, + method: 'POST' +}) diff --git a/lib/application-api/index.js b/lib/application-api/index.js deleted file mode 100644 index 4da9336..0000000 --- a/lib/application-api/index.js +++ /dev/null @@ -1,201 +0,0 @@ -/** - * Module dependencies. - */ - -var api = require('lib/db-api'); -var accepts = require('lib/accepts'); -var express = require('express'); -var log = require('debug')('civicstack:application'); -var Promise = require('bluebird'); -var og = require('lib/open-graph'); -var utils = require('lib/utils'); -var admin = utils.admin; -var restrict = utils.restrict; -var handleError = utils.handleError; - -var app = module.exports = express(); - -/** - * Limit request to json format only - */ - -app.use(accepts('application/json')); - -var all = ['id name logo backgroundColor video description technology technologyids links', - 'organization github website country twitter license contact partnership', - 'comments tags approved tagids uploadedBy upvotesCount upvoted'].join(' '); - -function expose (user) { - return function (app) { - var obj = utils.expose(all)(app); - obj.upvoted = user && app.upvotes !== undefined && - (app.upvotes.map(function (a) { return a.toString(); }).indexOf(user.id) >= 0); - - return obj; - } -} - -app.get('/applications', utils.admin, function (req, res) { - log('Request /applications'); - api.application.all(function (err, applications) { - if (err) return handleError(err, req, res); - log('Serving %s applications', applications.length); - - res.json(applications.map(expose(req.user))); - }); -}); - -app.get('/applications/approved', function (req, res) { - function parse (key) { - return req.query[key] ? req.query[key].split(',') : undefined - } - - function serveApplications(err, applications) { - if (err) return handleError(err, req, res); - log('Serving %s applications', applications.length); - res.json(applications.map(expose(req.user))); - } - - log('Request /applications/approved'); - - // Filters - var tags = parse('tags'); - var technologies = parse('technologies'); - var countries = parse('countries'); - var search = req.query.q; - // Sorting - var sortBy = req.query.sort === '_id' || req.query.sort === 'upvotesCount' - ? req.query.sort : undefined; - var sortOrder = req.query.order === 'asc' || req.query.order === 'desc' ? req.query.order : 'asc'; - - // Fetch data - if (tags || countries || technologies || search || sortBy) { - var filter = { - tags: tags, - technologies: technologies, - countries: countries, - search: search, - sortBy: sortBy, - sortOrder: sortOrder - }; - - api.application.filter(filter, serveApplications); - } else { - api.application.approved(serveApplications); - } -}); - -app.get('/applications/:id', function (req, res) { - log('Request GET /application/%s', req.params.id); - - api.application.get(req.params.id, function (err, application) { - if (err) return handleError(err, req, res); - if (!application) return res.send(404); - - log('Serving application %s', application.id); - - res.json(expose(req.user)(application.toJSON())); - }); -}); - -app.get('/applications/:id/links', function (req, res, next) { - function promisify (links) { - return links.map(function (link) { - return new Promise(function (resolve) { - og.get(link.url, function (err, result) { - var data = { url: link.url, description: link.description }; - data.og = err ? {} : ({ - title: result.data.ogTitle || result.data.title, - description: result.data.ogDescription, - image: result.data.ogImage && result.data.ogImage.url - }); - return resolve(data); - }); - }); - }); - } - - log('Request GET /application/%s/links', req.params.id); - - api.application.get(req.params.id, function (err, application) { - if (err) return handleError(err, req, res); - if (!application) return res.send(404); - - log('Serving application links for %s: %j', application.id, application.links); - Promise.all(promisify(application.links)) - .then(function (result) { - return res.json(result); - }) - .catch(function (err) { - return next(err); - }); - }); -}); - -app.post('/applications/create', restrict, protect, function (req, res) { - log('Request POST /applications/create %j', req.body); - - req.body.uploadedBy = req.user; - if (req.body.license == '') delete req.body.license; - - api.application.create(req.body, function (err, application) { - if (err) return handleError(err, req, res); - - log('Serving application %s', application.id); - - res.json(expose(req.user)(application.toJSON())); - }); -}); - -app.post('/applications/:id', admin, protect, function (req, res) { - log('Request POST /applications/%s %j', req.params.id, req.body); - - if (req.body.license == '') delete req.body.license; - req.body.video = utils.getEmbeddableYouTubeURL(req.body.video); - - api.application.update(req.body, function (err, app) { - if (err) return handleError(err, req, res); - - log('Serving application %s', app.id); - - res.json(expose(req.user)(app.toJSON())); - }); -}); - -app.post('/applications/:id/upvote', restrict, function (req, res) { - log('Request POST /applications/%s/upvote', req.params.id); - - api.application.upvote(req.params.id, req.user.id, function (err, app) { - if (err) return handleError(err, req, res); - res.json(expose(req.user)(app.toJSON())); - }); -}); - -app.delete('/applications/:id/upvote', restrict, function (req, res) { - log('Request DELETE /applications/%s/upvote', req.params.id); - - api.application.undoUpvote(req.params.id, req.user.id, function (err, app) { - if (err) return handleError(err, req, res); - res.json(expose(req.user)(app.toJSON())); - }); -}); - -app.delete('/applications/:id', admin, function (req, res) { - log('Request DEL /applications/%s %j', req.params.id, req.body); - - api.application.remove(req.params.id, function (err, id) { - if (err) return handleError(err, req, res); - - log('Deleted application %s', id.id); - - res.sendStatus(200); - }); -}); - - -function protect (req, res, next) { - if (!req.user.admin) { - delete req.body.approved; - } - next(); -} diff --git a/lib/application-card/component.json b/lib/application-card/component.json deleted file mode 100644 index f5e59dd..0000000 --- a/lib/application-card/component.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "name": "application-card", - "dependencies": { - "logo/logo": "0.0.1", - "ractivejs/ractive": "v0.6.1" - }, - "locals": [ - "language", - "render", - "request" - ], - "templates": [ "template.jade" ], - "scripts": [ "view.js" ], - "styles": [ "styles.styl" ], - "main": "view.js" -} diff --git a/lib/application-card/styles.styl b/lib/application-card/styles.styl deleted file mode 100644 index 60f3398..0000000 --- a/lib/application-card/styles.styl +++ /dev/null @@ -1,46 +0,0 @@ -.application-container - display inline-block - position relative - margin 15px - width 262px - -.application-card - display flex - flex-direction column - height 100% - box-shadow 0px 2px 5px rgba(#000, .1) - - .app-logo - height 175px - width 100% - - .app-detail - flex-grow 2 - font-size 12px - padding 12px - min-height 190px - background white - - header - margin-bottom 12px - - h1, - h2 - margin 0 - - h1 - font-size 16px - font-weight bold - h2 - font-size 14px - - .description - margin: 0 - font-size: 1em - - footer - background-color #efefef - font-size 12px - height 50px - line-height 50px - padding 0 12px diff --git a/lib/application-card/template.jade b/lib/application-card/template.jade deleted file mode 100644 index 5d13a27..0000000 --- a/lib/application-card/template.jade +++ /dev/null @@ -1,31 +0,0 @@ -| {{#with application }} -.application-container: article.application-card - a.app-logo(href='/apps/{{ id }}', style='background-color: {{ backgroundColor }}; background-image: url({{ logo }})') - h1 {{ name }} - section.app-detail - header - h1: a(href='/apps/{{ id }}')='{{ name }}' - h2='{{ country.name[lang] }}' - | {{#if description[lang]}} - p.description='{{ description[lang] }}' - | {{/if}} - footer - | {{#if twitter}} - .twitter.pull-left - a(href='http://twitter.com/{{ twitter }}' target='_blank')='@{{ twitter }}' - | {{/if}} - .upvotes.pull-right - | {{#upvoted}} - a.btn-upvote.btn.btn-sm.btn-primary(on-click='undoUpvote') - i.fa.fa-caret-up - |  - span {{application.upvotesCount}} - | {{/upvoted}} - | {{^upvoted}} - a.btn-upvote.btn.btn-sm.btn-default(on-click='upvote') - i.fa.fa-caret-up - |  - span {{application.upvotesCount}} - | {{/upvoted}} - -| {{/with application}} diff --git a/lib/application-card/view.js b/lib/application-card/view.js deleted file mode 100644 index fd2b770..0000000 --- a/lib/application-card/view.js +++ /dev/null @@ -1,46 +0,0 @@ -/** - * Module dependencies. - */ - -var language = require('language'); -var Ractive = require('ractive'); -var render = require('render'); -var request = require('request'); -var template = require('./template'); - -module.exports = Ractive.extend({ - isolated: true, - template: render(template), - onrender: function () { - this.set('lang', language()); - registerUpvoteEvents.apply(this); - } -}); - -function registerUpvoteEvents () { - function handleResponse (err, res) { - if (!err && res.ok) { - this.set('application.upvoted', res.body.upvoted); - this.set('application.upvotesCount', res.body.upvotesCount); - } - } - - var self = this; - var url = '/api/applications/' + this.data.application.id + '/upvote'; - - this.on('upvote', function (ev) { - if (self.data.user.logged()) { - request.post(url).end(handleResponse.bind(this)); - } else { - self.fire('signinrequired'); - } - }); - - this.on('undoUpvote', function (ev) { - if (self.data.user.logged()) { - request.del(url).end(handleResponse.bind(this)); - } else { - self.fire('signinrequired'); - } - }); -} diff --git a/lib/application-detail/application-detail.js b/lib/application-detail/application-detail.js deleted file mode 100644 index 1dfc325..0000000 --- a/lib/application-detail/application-detail.js +++ /dev/null @@ -1,32 +0,0 @@ -/** - * Module dependencies. - */ - -var Detail = require('app-detail-view'); -var empty = require('empty'); -var log = require('debug')('civicstack:application-detail'); -var page = require('page'); -var request = require('request'); -var user = require('user'); - -page('/apps/:id', user.optional, load, function(ctx, next) { - var container = document.querySelector('.site-content'); - - var detail = new Detail({ - data: { application: ctx.application, user: user, disqus: true }, - el: '.site-content', - replace: true - }); -}); - -function load(ctx, next) { - log('fetch for %s', ctx.params.id); - - request - .get('/api/applications/:id'.replace(':id', ctx.params.id)) - .end(function(err, res) { - if (err || !res.ok) return log('Found error: %s', err || res.error); - ctx.application = res.body; - next(); - }); -} diff --git a/lib/application-detail/component.json b/lib/application-detail/component.json deleted file mode 100644 index f9747c8..0000000 --- a/lib/application-detail/component.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "name": "application-detail", - "dependencies": { - "visionmedia/debug": "2.1.3", - "visionmedia/page.js": "1.6.1", - "yields/empty": "0.0.1" - }, - "locals": [ - "app-detail-view", - "request", - "user" - ], - "scripts": [ "application-detail.js"], - "main": "application-detail.js" -} diff --git a/lib/applications/applications.js b/lib/applications/applications.js deleted file mode 100644 index ef330a7..0000000 --- a/lib/applications/applications.js +++ /dev/null @@ -1,20 +0,0 @@ -/** - * Module dependencies. - */ - -var Collection = require('collection'); - -module.exports.all = new Collection('/api/applications'); - -var approved = new Collection('/api/applications/approved?sort=upvotesCount&order=desc'); - -approved.on('loaded', function () { - - function translate(app) { - app.description.es = app.description.es || app.description.en; - } - - approved.items.map(translate); -}); - -module.exports.approved = approved; diff --git a/lib/applications/component.json b/lib/applications/component.json deleted file mode 100644 index 76f8ca8..0000000 --- a/lib/applications/component.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "name": "applications", - "description": "Collection of applications", - "locals": [ - "collection", - "language" - ], - "scripts": [ - "applications.js" - ], - "main": "applications.js" -} diff --git a/lib/applications/proto.js b/lib/applications/proto.js deleted file mode 100644 index 20ba010..0000000 --- a/lib/applications/proto.js +++ /dev/null @@ -1,133 +0,0 @@ -/** - * Module dependencies. - */ - -var lang = require('language')(); -var Emitter = require('emitter'); -var request = require('request'); -var render = require('render'); -var Stateful = require('stateful'); -var log = require('debug')('civicstack:laws-proto'); - -/** - * Expose `Applications` proto constructor - */ - -module.exports = Applications; - -/** - * Applications collection constructor - */ - -function Applications() { - if (!(this instanceof Applications)) { - return new Applications(); - }; - - // instance bindings - this.middleware = this.middleware.bind(this); - this.fetch = this.fetch.bind(this); - - this.state('initializing'); - this.fetch(); -} - -/** - * Mixin Applications prototype with Emitter - */ - -Stateful(Applications); - -/** - * Fetch `laws` from source - * - * @param {String} src - * @api public - */ - -Applications.prototype.fetch = function(src) { - log('request in process'); - src = src || '/api/applications'; - - this.state('loading'); - - request - .get(src) - .end(onresponse.bind(this)); - - function onresponse(err, res) { - if (err || !res.ok) { - var message = 'Unable to load laws. Please try reloading the page. Thanks!'; - return this.error(message); - }; - - this.set(res.body); - } -} - -/** - * Set items to `v` - * - * @param {Array} v - * @return {Applications} Instance of `Applications` - * @api public - */ - -Applications.prototype.set = function(v) { - this.items = v; - - function name(application) { - return application.name[lang]; - } - - this.items.forEach(function (application) { - application.tagnames = application.tags.map(name).join(', '); - application.description = application.description[lang]; - }) - this.state('loaded'); - return this; -} - -/** - * Get current `items` - * - * @return {Array} Current `items` - * @api public - */ - -Applications.prototype.get = function() { - return this.items; -} - -/** - * Middleware for `page.js` like - * routers - * - * @param {Object} ctx - * @param {Function} next - * @api public - */ - -Applications.prototype.middleware = function(ctx, next) { - this.ready(next); -} - -/** - * Handle errors - * - * @param {String} error - * @return {Applications} Instance of `Applications` - * @api public - */ - -Applications.prototype.error = function(message) { - // TODO: We should use `Error`s instead of - // `Strings` to handle errors... - // Ref: http://www.devthought.com/2011/12/22/a-string-is-not-an-error/ - this.state('error', message); - log('error found: %s', message); - - // Unregister all `ready` listeners - this.off('ready'); - return this; -} diff --git a/lib/auth/auth.js b/lib/auth/auth.js deleted file mode 100755 index 441b729..0000000 --- a/lib/auth/auth.js +++ /dev/null @@ -1,25 +0,0 @@ -/** - * Module dependencies. - */ - -var page = require('page'); - -page('/auth/twitter', replace()); -page('/auth/github', replace()); -page('/logout', replace()); - -/** - * Page.js middleware for path - * replacing - * - * @param {String} path - * @return {Function} middleware - * @api private - */ - -function replace (path) { - return function middleware (ctx, next) { - path = path || ctx.path; - window.location.replace(path); - } -} diff --git a/lib/auth/component.json b/lib/auth/component.json deleted file mode 100755 index cc3e6d5..0000000 --- a/lib/auth/component.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "name": "auth", - "description": "Auth component", - "dependencies": { - "visionmedia/page.js": "1.6.1" - } -} \ No newline at end of file diff --git a/lib/auth/index.js b/lib/auth/index.js deleted file mode 100755 index ecc5a70..0000000 --- a/lib/auth/index.js +++ /dev/null @@ -1,31 +0,0 @@ -/** - * Module dependencies. - */ - -var routes = require('./routes'); -var passport = require('./passport'); - -/** - * Expose Auth Module - */ - -module.exports = Auth; - -/** - * Auth Module defining routes and - */ -function Auth (app) { - - /** - * Instantiates PassportJS - * login strategies - */ - - passport(app); - - /** - * Attach routes to parent application - */ - - app.use(routes); -} \ No newline at end of file diff --git a/lib/auth/passport.js b/lib/auth/passport.js deleted file mode 100644 index 1d27b39..0000000 --- a/lib/auth/passport.js +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Module dependencies - */ -var mongoose = require('mongoose'); -var passport = require('passport'); -var TwitterStrategy = require('passport-twitter').Strategy; -var GithubStrategy = require('passport-github').Strategy; -var registration = require('lib/registration'); - -/** - * Expose Passport login - * strategies - * - * @param {Express} app `Express` instance - * @api public - */ - -module.exports = function LoginStrategies (app) { - - /** - * User model - */ - - var User = mongoose.model('User'); - - /** - * Passport Serialization of logged - * User to Session from request - */ - - passport.serializeUser(function(user, done) { - done(null, user.id); - }); - - /** - * Passport Deserialization of logged - * User by Session into request - */ - - passport.deserializeUser(function(userId, done) { - User - .findById(userId) - .exec(function(err, user) { - done(null, user); - }); - }); - - /** - * Register Twitter Strategy - */ - - passport.use(new TwitterStrategy({ - consumerKey: app.get('config').auth.twitter.consumerKey, - consumerSecret: app.get('config').auth.twitter.consumerSecret, - callbackURL: app.get('config').auth.twitter.callback - }, function(accessToken, refreshToken, profile, done) { - User.findByProvider(profile, function(err, user) { - if (err) { - return done(err); - } - - if (user) { - return done(null, user); - } - - registration.twitter(profile, function(err, user) { - return done(err, user); - }); - }); - })); - - /** - * Register Twitter Strategy - */ - - passport.use(new GithubStrategy({ - clientID: app.get('config').auth.github.clientID, - clientSecret: app.get('config').auth.github.clientSecret, - callbackURL: app.get('config').auth.github.callback - }, function(accessToken, refreshToken, profile, done) { - User.findByProvider(profile, function(err, user) { - if (err) { - return done(err); - } - - if (user) { - return done(null, user); - } - - registration.github(profile, function(err, user) { - return done(err, user); - }); - }); - })); - -} diff --git a/lib/auth/routes.js b/lib/auth/routes.js deleted file mode 100644 index 442242f..0000000 --- a/lib/auth/routes.js +++ /dev/null @@ -1,82 +0,0 @@ -/** - * Module dependencies. - */ - -var express = require('express'); -var passport = require('passport'); -var disqus = require('lib/disqus'); -var debug = require('debug')('civicstack:auth:routes'); - -function redirect(req, res) { - // Successful authentication, redirect home. - debug('Log in user %s', req.user.id); - res.redirect('/'); -} - -/** - * Lazy create app - */ - -var app; - -/** - * Expose auth app - */ - -module.exports = app = express(); - -/** - * Logout - */ - -app.get('/logout' - , disqus.logout - , logout - , function (req, res, next) { - res.redirect('/'); - } -); - -/* - * Github Auth routes - */ - -app.get('/auth/github', passport.authenticate('github')); - -app.get('/auth/github/callback' - , logout - , passport.authenticate('github', { failureRedirect: '/login' }) - , disqus.login - , redirect - ); - -/* - * Twitter Auth routes - */ - -app.get('/auth/twitter', passport.authenticate('twitter')); - -app.get('/auth/twitter/callback' - , logout - , passport.authenticate('twitter', { failureRedirect: '/' }) - , disqus.login - , redirect -); - -/** - * Logs user out - * - * @param {Object} req - * @param {Object} res - * @param {Function} next - * @api private - */ - -function logout (req, res, next) { - if (req.user) { - debug('Log out user %s', req.user.id); - req.logout(); - } - - next(); -} diff --git a/lib/body-classes/body.js b/lib/body-classes/body.js deleted file mode 100644 index c9f1415..0000000 --- a/lib/body-classes/body.js +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Module dependencies. - */ - -var page = require('page'); -var bus = require('bus'); -var classes = require('classes'); -var config = require('config'); - -page('*', function(ctx, next) { - bus.emit('page:change', ctx.path); - - if (ctx.path == '/login') return next(); - - var body = classes(document.body); - body.remove(/[^browser\-page]/); - body.add(config.env); - next(); -}); diff --git a/lib/body-classes/component.json b/lib/body-classes/component.json deleted file mode 100644 index 0b9e758..0000000 --- a/lib/body-classes/component.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "name": "body-classes", - "description": "Attach and detach classes to body", - "dependencies": { - "component/bus": "0.0.2", - "component/classes": "1.2.3", - "visionmedia/page.js": "1.6.1" - }, - "locals": [ "config" ], - "scripts": [ "body.js" ], - "main": "body.js" -} diff --git a/lib/boot/boot.js b/lib/boot/boot.js deleted file mode 100755 index a8edfd1..0000000 --- a/lib/boot/boot.js +++ /dev/null @@ -1,54 +0,0 @@ -/** - * Module dependencies. - */ - -var config = require('config'); -var language = require('language'); -var page = require('page'); -var t = require('t'); -var translations = require('translations'); -var user = require('user'); - -/** - * Initialize lang - */ - -var lang = language(); - -/** - * Load localization dictionaries to translation application - */ - -translations.help(t); - -/** - * Init `t` component with config locale - */ - -t.lang(lang); - -/** - * Boot components - * and pages. - */ - -require('body-classes'); -require('header'); -require('sidebar'); -require('footer'); -require('admin'); -require('homepage'); -require('login'); -require('app-form'); -require('application-detail'); -require('about'); - -/** - * Boot page.js - */ - -page(); - -if(config['google analytics tracking id']) { - require('ga')(config['google analytics tracking id']); -} diff --git a/lib/boot/component.json b/lib/boot/component.json deleted file mode 100755 index cecf656..0000000 --- a/lib/boot/component.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "name": "boot", - "description": "Application Booting component.", - "dependencies": { - "component/t": "1.0.0", - "visionmedia/page.js": "1.6.1", - "code42day/ga": "1.1.0" - }, - "local": [ - "about", - "admin", - "app-form", - "application-detail", - "body-classes", - "config", - "header", - "sidebar", - "footer", - "homepage", - "language", - "login", - "styleguide", - "translations", - "user" - ], - "scripts": [ "boot.js" ], - "images": [ - "images/bg.jpg", - "images/favicon.ico" - ], - "main": "boot.js", - "license": "MIT" -} diff --git a/lib/boot/includes/disqus.jade b/lib/boot/includes/disqus.jade deleted file mode 100644 index 88d7ead..0000000 --- a/lib/boot/includes/disqus.jade +++ /dev/null @@ -1,16 +0,0 @@ -script. - var disqus_config = function () { - // The generated payload which authenticates users with Disqus - this.page.remote_auth_s3 = '#{ disqus.auth }'; - this.page.api_key = '#{ disqus.pubKey }'; - this.language = '#{ lang }'; - }; - - var disqus_developer = #{ Number(config.env === 'development') }; - (function() { - var d = document, s = d.createElement('script'); - s.src = 'https://civicstack.disqus.com/embed.js'; - s.setAttribute('data-timestamp', +new Date()); - (d.head || d.body).appendChild(s); - })(); - diff --git a/lib/boot/includes/segment.jade b/lib/boot/includes/segment.jade deleted file mode 100644 index 30f5f47..0000000 --- a/lib/boot/includes/segment.jade +++ /dev/null @@ -1,4 +0,0 @@ -script(type='text/javascript'). - !function(){var analytics=window.analytics=window.analytics||[];if(!analytics.initialize)if(analytics.invoked)window.console&&console.error&&console.error("Segment snippet included twice.");else{analytics.invoked=!0;analytics.methods=["trackSubmit","trackClick","trackLink","trackForm","pageview","identify","group","track","ready","alias","page","once","off","on"];analytics.factory=function(t){return function(){var e=Array.prototype.slice.call(arguments);e.unshift(t);analytics.push(e);return analytics}};for(var t=0;t b.name[lang]) return 1; - return 0; -} diff --git a/lib/country-api/index.js b/lib/country-api/index.js deleted file mode 100644 index 8168d1a..0000000 --- a/lib/country-api/index.js +++ /dev/null @@ -1,84 +0,0 @@ -/** - * Module dependencies. - */ - -var api = require('lib/db-api'); -var accepts = require('lib/accepts'); -var express = require('express'); -var log = require('debug')('civicstack:country'); -var utils = require('lib/utils'); -var admin = utils.admin; -var pluck = utils.pluck; -var expose = utils.expose; -var handleError = utils.handleError; - -var app = module.exports = express(); - -/** - * Limit request to json format only - */ - -app.use(accepts('application/json')); - -app.get('/countries', function (req, res) { - log('Request /countries'); - - api.country.all(function(err, countries) { - if (err) return handleError(err, req, res); - - log('Serving countries %j', pluck(countries, "id")); - - var keys = 'id name'; - - res.json(countries.map(expose(keys))); - }); -}); - -app.get('/countries/:id', function (req, res) { - log('Request GET /countries/%s', req.params.id); - - api.country.get(req.params.id, function (err, country) { - if (err) return handleError(err, req, res); - if (!country) return res.send(404); - - log('Serving country %s', country.id); - - res.json(country.toJSON()); - }); -}); - -app.post('/countries/create', admin, function (req, res) { - log('Request POST /countries/create %j', req.body); - - api.country.create(req.body, function (err, country) { - if (err) return handleError(err, req, res); - - log('Serving country %s', country.id); - - res.json(country.toJSON()); - }); -}); - -app.post('/countries/:id', admin, function (req, res) { - log('Request POST /countries/%s %j', req.params.id, req.body); - - api.country.update(req.body, function (err, country) { - if (err) return handleError(err, req, res); - - log('Serving country %s', country.id); - - res.json(country.toJSON()); - }); -}); - -app.delete('/countries/:id', admin, function (req, res) { - log('Request DEL /countries/%s %j', req.params.id, req.body); - - api.country.remove(req.params.id, function (err, id) { - if (err) return handleError(err, req, res); - - log('Deleted country %s', id.id); - - res.sendStatus(200); - }); -}); diff --git a/lib/db-api/application.js b/lib/db-api/application.js deleted file mode 100644 index 3a6feb8..0000000 --- a/lib/db-api/application.js +++ /dev/null @@ -1,382 +0,0 @@ -/** - * Extend module's NODE_PATH - * HACK: temporary solution - */ - -require('node-path')(module); - -/** - * Module dependencies. - */ - -var mongoose = require('mongoose'); -var Application = mongoose.model('Application'); -var utils = require('lib/utils'); -var pluck = utils.pluck; -var log = require('debug')('civicstack:db-api:application'); - -var fields = ['id name logo backgroundColor video description technology technologyids links tags', - 'organization github website country twitter license contact partnership comments approved tagids', - 'upvotes upvotesCount'] - .join(' ') - -/** - * Get all applications - * - * @param {Function} fn callback function - * - 'err' error found on query or `null` - * - 'applications' list items found or `undefined` - * @return {Module} `application` module - * @api public - */ - -exports.all = function all(fn) { - log('Looking for all applications.') - - Application - .find({ deletedAt: null }) - .select(fields) - .populate('tags') - .populate('country') - .populate('license') - .populate('technology') - .populate({ - path: 'uploadedBy', - select: 'avatar firstName lastName' - }) - .exec(function (err, applications) { - if (err) { - log('Found error %j', err); - return fn(err); - }; - - log('Delivering applications %j', pluck(applications, 'id')); - fn(null, applications); - }); - - return this; -}; - -/** - * Get all applications filtered and sorted - * - * @param {Function} fn callback function - * - 'err' error found on query or `null` - * - 'applications' list items found or `undefined` - * @return {Module} `application` module - * @api public - */ - -exports.filter = function filter(options, fn) { - function buildQuery(opts) { - var query = { - approved: true - }; - - if (opts.tags) { - query.tags = { $in: opts.tags } - } - if (opts.technologies) { - query.technology = { $in: opts.technologies } - } - if (opts.countries) { - query.country = { $in: opts.countries } - } - if (opts.search) { - query['$text'] = { $search: opts.search }; - } - return query; - } - - function buildProjection(opts) { - return opts.search ? { score: { $meta: "textScore" } } : undefined; - } - - function buildSort(opts) { - if (opts.search) { - return { score: { $meta: 'textScore' } }; - } else if (opts.sortBy) { - var order = opts.sortOrder === 'asc' ? '' : '-'; - var sort = order + opts.sortBy; - return sort; - } - } - - function handleResponse(err, apps) { - if (err) { - log('Found error: %j', err); - return fn(err); - } - log('Found approved apps %j', pluck(apps, 'id')); - fn(null, apps); - } - - var query = buildQuery(options); - var projection = buildProjection(options); - var sort = buildSort(options); - - log('Searching applications with query %j, projection %j and sort %j', query, projection, sort); - Application - .find(query, projection) - .select(fields) - .populate('tags') - .populate('country') - .populate('license') - .populate('technology') - .populate({ - path: 'uploadedBy', - select: 'avatar firstName lastName' - }) - .sort(sort) - .exec(handleResponse); - - return this; -}; - -/** - * Search apps from query - * - * @param {Object} query filter - * @param {Function} fn callback function - * - 'err' error found while process or `null` - * - 'apps' list of apps objects found or `undefined` - * @return {Module} `app` module - * @api public - */ - -exports.search = function search(query, fn) { - log('Searching for apps matching %j', query); - - Application - .find(query, function(err, apps) { - if (err) { - log('Found error: %j', err); - return fn(err); - } - - log('Found apps %j for %j', pluck(apps, 'id'), query); - fn(null, apps); - }); - - return this; -}; - -/** - * Get Application form `id` string or `ObjectId` - * - * @param {String|ObjectId} id Application's `id` - * @param {Function} fn callback function - * - 'err' error found while process or `null` - * - 'application' found item or `undefined` - * @api public - */ - -exports.get = function get(id, fn) { - var query = { _id: id, deletedAt: null }; - - log('Looking for application %s', id); - Application - .findOne(query) - .populate('tags') - .populate('country') - .populate('license') - .populate('technology') - .populate({ - path: 'uploadedBy', - select: 'avatar firstName lastName' - }) - .exec(function (err, application) { - if (err) { - log('Found error %s', err); - return fn(err); - }; - - if (!application) { - log('Application %s not found', id); - return fn(null); - } - log('Delivering application %s', application.id); - fn(null, application); - }); -}; - -/** - * Creates app - * - * @param {Object} data to create app - * @param {Function} fn callback function - * - 'err' error found on query or `null` - * - 'app' item created or `undefined` - * @return {Module} `app` module - * @api public - */ - -exports.create = function create(data, fn) { - log('Creating new app %j', data); - - data.tags = data.tagids || []; - data.technology = data.technologyids || []; - - var app = new Application(data); - app.save(onsave); - - function onsave(err) { - if (err) return log('Found error %s', err), fn(err); - - log('Saved app %s', app.id); - fn(null, app); - } - - return this; -}; - -/** - * Update given `app` - * - * @param {ObjectId|String} data to create app - * @param {Function} fn callback function - * - 'err' error found on query or `null` - * - 'app' item created or `undefined` - * @return {Module} `app` module - * @api public - */ - -exports.update = function update(data, fn) { - log('Updating app %s with %j', data.id, data); - - // get app - exports.get(data.id, onget); - - function onget(err, app) { - if (err) { - log('Found error %s', err.message); - return fn(err); - }; - - // update and save app document with data - data.tags = data.tagids || []; - data.technology = data.technologyids || []; - data.links = data.links || []; - app.set(data); - app.save(onupdate); - } - - function onupdate(err, app) { - if (err) return log('Found error %s', err), fn(err); - - log('Saved app %s', app.id) - fn(null, app); - } - - return this; -}; - -/** - * Adds an upvote to a given `app` - * - * @param {ObjectId} ID of the app - * @param {ObjectId} ID of the user that gives the upvote - * @param {Function} fn callback function - * - 'err' error found on query or `null` - * @return {Module} `app` module - * @api public - */ - -exports.upvote = function upvote(appId, userId, fn) { - log('Upvoting app %s', appId); - Application.findOne({ _id: appId }, function (err, doc) { - doc.upvotes.addToSet(userId); - doc.upvotesCount = doc.upvotes.length; - doc.save(onupdate); - }); - - function onupdate(err) { - if (err) return log('Found error %s', err), fn(err); - log('Saved app %s', appId) - exports.get(appId, fn); - } - - return this; -}; - -/** - * Removes an upvote from a given `app` - * - * @param {ObjectId} ID of the app - * @param {ObjectId} ID of the user - * @param {Function} fn callback function - * - 'err' error found on query or `null` - * @return {Module} `app` module - * @api public - */ - -exports.undoUpvote = function undoUpvote(appId, userId, fn) { - log('Undoing upvote for app %s', appId); - Application.findOne({ _id: appId }, function (err, doc) { - doc.upvotes.pull(userId); - doc.upvotesCount = doc.upvotes.length; - doc.save(onupdate); - }); - - function onupdate(err) { - if (err) return log('Found error %s', err), fn(err); - log('Saved app %s', appId) - exports.get(appId, fn); - } - - return this; -}; - -/** - * Search approved apps - * - * @param {Function} fn callback function - * - 'err' error found while process or `null` - * - 'apps' list of apps objects found or `undefined` - * @return {Module} `app` module - * @api public - */ - -exports.approved = function approved(fn) { - log('Searching for approved apps'); - - Application - .find({ approved: true }) - .populate('country') - .exec(function(err, apps) { - if (err) { - log('Found error: %j', err); - return fn(err); - } - log('Found approved apps %j', pluck(apps, 'id')); - fn(null, apps); - }); - - return this; -}; - -/** - * Deletes app - * - * @param {Object} data to remove app - * @param {Function} fn callback function - * - 'err' error found on query or `null` - * - 'id' id removed or `undefined` - * @return {Module} `app` module - * @api public - */ - -exports.remove = function remove(id, fn) { - log('Deleting app %s', id); - - Application - .remove({_id: id}) - .exec(function (err) { - if (err) return log('Found error %s', err), fn(err); - - log('Removed app %s', id); - fn(null, id); - }); - - return this; -}; diff --git a/lib/disqus/index.js b/lib/disqus/index.js deleted file mode 100644 index 616d952..0000000 --- a/lib/disqus/index.js +++ /dev/null @@ -1,10 +0,0 @@ -var config = require('lib/config'); -var disqusSSO = require('disqus-sso-express'); - -var disqusPublicKey = config.auth.disqus.clientID; -var disqusSecret = config.auth.disqus.clientSecret; - -module.exports = disqusSSO({ - publicKey: disqusPublicKey, - secret: disqusSecret -}); diff --git a/lib/filters-country/component.json b/lib/filters-country/component.json deleted file mode 100644 index 6db0465..0000000 --- a/lib/filters-country/component.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "name": "filters-country", - "dependencies": { - "ractivejs/ractive": "v0.6.1" - }, - "locals": [ - "config", - "countries", - "filters", - "language", - "render" - ], - "scripts": [ "filters-country.js" ], - "templates": [ "template.jade" ], - "main": "filters-country.js" -} diff --git a/lib/filters-country/filters-country.js b/lib/filters-country/filters-country.js deleted file mode 100644 index 67c32aa..0000000 --- a/lib/filters-country/filters-country.js +++ /dev/null @@ -1,38 +0,0 @@ -/** - * Module dependencies. - */ - -var config = require('config'); -var countries = require('countries'); -var filters = require('filters'); -var language = require('language'); -var Ractive = require('ractive'); -var render = require('render'); -var template = require('./template'); - -module.exports = Ractive.extend({ - isolated: true, - template: render(template), - onrender: function () { - this.set('lang', language()); - this.set('countries', countries.items); - this.set('selected', filters.countries); - - $('.modal').modal(); - - this.on('search', function (ev) { - filters.countries = this.get('selected'); - }); - - this.on('clear', function (ev) { - filters.countries = []; - }); - - var self = this; - - $('.modal').on('hidden.bs.modal', function (e) { - $('.modal').remove(); - self.fire('close'); - }) - } -}); diff --git a/lib/filters-country/template.jade b/lib/filters-country/template.jade deleted file mode 100644 index da7dffe..0000000 --- a/lib/filters-country/template.jade +++ /dev/null @@ -1,20 +0,0 @@ -.modal.fade(tabindex='-1', role='dialog', aria-labelledby='mySmallModalLabel', aria-hidden='true') - .modal-dialog - .modal-content - .modal-header=t('filters.country') - button.close(type='button', data-dismiss='modal', aria-label='Close') - span(aria-hidden='true') × - .modal-body - .row - | {{#each countries}} - .col-xs-12.col-sm-4 - .checkbox - label - input(type='checkbox' name='{{ selected }}' value='[[ id ]]') - ='[[ name[lang] ]]' - | {{/each}} - .modal-footer - button(type='button' class='btn btn-warning' data-dismiss='modal' on-click='clear')=t('filters.clear') - button.btn.btn-default(on-click='search' data-dismiss='modal') - .fa.fa-search - | #{t('filters.search')} diff --git a/lib/filters-tags/component.json b/lib/filters-tags/component.json deleted file mode 100644 index 8ec8a89..0000000 --- a/lib/filters-tags/component.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "name": "filters-tags", - "dependencies": { - "ractivejs/ractive": "v0.6.1" - }, - "locals": [ - "config", - "tags", - "filters", - "language", - "render" - ], - "scripts": [ "filters-tags.js" ], - "templates": [ "template.jade" ], - "main": "filters-tags.js" -} diff --git a/lib/filters-tags/filters-tags.js b/lib/filters-tags/filters-tags.js deleted file mode 100644 index 6f39b8d..0000000 --- a/lib/filters-tags/filters-tags.js +++ /dev/null @@ -1,38 +0,0 @@ -/** - * Module dependencies. - */ - -var config = require('config'); -var tags = require('tags'); -var filters = require('filters'); -var language = require('language'); -var Ractive = require('ractive'); -var render = require('render'); -var template = require('./template'); - -module.exports = Ractive.extend({ - isolated: true, - template: render(template), - onrender: function () { - this.set('lang', language()); - this.set('tags', tags.items); - this.set('selected', filters.tags); - - $('.modal').modal(); - - this.on('search', function (ev) { - filters.tags = this.get('selected'); - }); - - this.on('clear', function (ev) { - filters.tags = []; - }); - - var self = this; - - $('.modal').on('hidden.bs.modal', function (e) { - $('.modal').remove(); - self.fire('close'); - }) - } -}); diff --git a/lib/filters-tags/template.jade b/lib/filters-tags/template.jade deleted file mode 100644 index 7c052b1..0000000 --- a/lib/filters-tags/template.jade +++ /dev/null @@ -1,20 +0,0 @@ -.modal.fade(tabindex='-1', role='dialog', aria-labelledby='mySmallModalLabel', aria-hidden='true') - .modal-dialog - .modal-content - .modal-header=t('filters.tags') - button.close(type='button', data-dismiss='modal', aria-label='Close') - span(aria-hidden='true') × - .modal-body - .row - | {{#each tags}} - .col-xs-12.col-sm-4 - .checkbox - label - input(type='checkbox' name='{{ selected }}' value='[[ id ]]') - ='[[ name[lang] ]]' - | {{/each}} - .modal-footer - button(type='button' class='btn btn-warning' data-dismiss='modal' on-click='clear')=t('filters.clear') - button.btn.btn-default(on-click='search' data-dismiss='modal') - .fa.fa-search - | #{t('filters.search')} diff --git a/lib/filters-technology/component.json b/lib/filters-technology/component.json deleted file mode 100644 index 78b2dde..0000000 --- a/lib/filters-technology/component.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "name": "filters-technology", - "dependencies": { - "ractivejs/ractive": "v0.6.1" - }, - "locals": [ - "config", - "technologies", - "filters", - "render" - ], - "scripts": [ "filters-technology.js" ], - "templates": [ "template.jade" ], - "main": "filters-technology.js" -} diff --git a/lib/filters-technology/filters-technology.js b/lib/filters-technology/filters-technology.js deleted file mode 100644 index e344a9f..0000000 --- a/lib/filters-technology/filters-technology.js +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Module dependencies. - */ - -var config = require('config'); -var filters = require('filters'); -var Ractive = require('ractive'); -var render = require('render'); -var technologies = require('technologies'); -var template = require('./template'); - -module.exports = Ractive.extend({ - isolated: true, - template: render(template), - onrender: function () { - this.set('technologies', technologies.items); - this.set('selected', filters.technologies); - - $('.modal').modal(); - - this.on('search', function (ev) { - filters.technologies = this.get('selected'); - }); - - this.on('clear', function (ev) { - filters.technologies = []; - }); - - var self = this; - - $('.modal').on('hidden.bs.modal', function (e) { - $('.modal').remove(); - self.fire('close'); - }) - } -}); diff --git a/lib/filters-technology/template.jade b/lib/filters-technology/template.jade deleted file mode 100644 index 4f18e12..0000000 --- a/lib/filters-technology/template.jade +++ /dev/null @@ -1,20 +0,0 @@ -.modal.fade(tabindex='-1', role='dialog', aria-labelledby='mySmallModalLabel', aria-hidden='true') - .modal-dialog - .modal-content - .modal-header=t('filters.technology') - button.close(type='button', data-dismiss='modal', aria-label='Close') - span(aria-hidden='true') × - .modal-body - .row - | {{#each technologies}} - .col-xs-12.col-sm-4 - .checkbox - label - input(type='checkbox' name='{{ selected }}' value='[[ id ]]') - ='[[ name ]]' - | {{/each}} - .modal-footer - button(type='button' class='btn btn-warning' data-dismiss='modal' on-click='clear')=t('filters.clear') - button.btn.btn-default(on-click='search' data-dismiss='modal') - .fa.fa-search - | #{t('filters.search')} diff --git a/lib/filters/component.json b/lib/filters/component.json deleted file mode 100644 index 49904c9..0000000 --- a/lib/filters/component.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "filters", - "dependencies": { - "yields/some": "1.0.0" - }, - "scripts": [ "filters.js" ], - "main": "filters.js" -} \ No newline at end of file diff --git a/lib/filters/filters.js b/lib/filters/filters.js deleted file mode 100644 index ff7b6e2..0000000 --- a/lib/filters/filters.js +++ /dev/null @@ -1,49 +0,0 @@ -/** - * Module dependencies. - */ - -var some = require('some'); - -function Filters(search, countries, technologies, tags) { - this.search = search || ''; - this.countries = countries || []; - this.technologies = technologies || []; - this.tags = tags || []; -} - -Filters.prototype.filter = function(app) { - var search = this.search ? this.search.toLowerCase() : ''; - var countries = this.countries; - var technologies = this.technologies; - var tags = this.tags; - var name = app.name.toLowerCase(); - - var filterByCountry = filterByTechnology = filterByTags = true; - var filterByName; - - filterByCountry = !countries.length || !!~countries.indexOf(app.country.id); - filterByTechnology = !technologies.length || some(technologies, function (technology, i) { - return !!~app.technology.indexOf(technology); - }); - filterByTags = !tags.length || some(tags, function (tag, i) { - return !!~app.tags.indexOf(tag); - }); - - if (search == '') { - filterByName = true; - } else { - filterByName = !!~name.indexOf(search); - } - - return filterByName && filterByCountry && filterByTechnology && filterByTags; -}; - -Filters.prototype.reset = function() { - this.search = ''; - this.countries = []; - this.technologies = []; - this.tags = []; - return this; -}; - -module.exports = new Filters; \ No newline at end of file diff --git a/lib/footer/component.json b/lib/footer/component.json deleted file mode 100644 index 5882321..0000000 --- a/lib/footer/component.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "name": "footer", - "dependencies": { - "ractivejs/ractive": "v0.6.1" - }, - "locals": [ - "language", - "render" - ], - "templates": [ "template.jade" ], - "scripts": [ "footer.js" ], - "styles": [ "footer.styl" ], - "main": "footer.js" -} diff --git a/lib/footer/footer.js b/lib/footer/footer.js deleted file mode 100644 index 597f300..0000000 --- a/lib/footer/footer.js +++ /dev/null @@ -1,17 +0,0 @@ -/** - * Module dependencies. - */ - -var language = require('language'); -var render = require('render'); -var Ractive = require('ractive'); -var template = require('./template'); - -var header = new Ractive({ - isolated: true, - template: render(template), - el: '#site-footer', - data: { - lang: language() - } -}); diff --git a/lib/footer/footer.styl b/lib/footer/footer.styl deleted file mode 100644 index 6720a06..0000000 --- a/lib/footer/footer.styl +++ /dev/null @@ -1,7 +0,0 @@ -.footer - padding-top 60px - padding-bottom 30px - text-align center - - a - font-weight bold diff --git a/lib/footer/template.jade b/lib/footer/template.jade deleted file mode 100644 index 098943f..0000000 --- a/lib/footer/template.jade +++ /dev/null @@ -1,5 +0,0 @@ -.footer.container - = t('footer.powered-by') + ' ' - a(href='http://democraciaenred.org/' target='_blank') Democracia en Red - = ' ' + t('and') + ' ' - a(href='http://www.asuntosdelsur.org/' target='_blank') Asuntos del Sur diff --git a/lib/header/component.json b/lib/header/component.json deleted file mode 100644 index 3b7b9c3..0000000 --- a/lib/header/component.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "header", - "dependencies": { - "ractivejs/ractive": "v0.6.1" - }, - "locals": [ - "render", - "user", - "sidebar" - ], - "images": [ - "images/logo.svg" - ], - "templates": [ "template.jade" ], - "scripts": [ "header.js" ], - "styles": [ "header.styl" ], - "main": "header.js" -} diff --git a/lib/header/header.js b/lib/header/header.js deleted file mode 100644 index 4e15517..0000000 --- a/lib/header/header.js +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Module dependencies. - */ - -var render = require('render'); -var Ractive = require('ractive'); -var template = require('./template'); -var sidebar = require('sidebar'); -var user = require('user'); - -var header = new Ractive({ - isolated: true, - template: render(template), - el: '#site-header', - data: { - user: user - }, - openSidebar: sidebar.open.bind(sidebar) -}); - -user.on('ready', function () { - header.set('user', user); -}); diff --git a/lib/header/header.styl b/lib/header/header.styl deleted file mode 100644 index cc84630..0000000 --- a/lib/header/header.styl +++ /dev/null @@ -1,83 +0,0 @@ -#site-header - height 80px - border-bottom 2px - background-color #fafafa - - &:after - content '' - display block - position absolute - bottom 0 - left 0 - width 100% - height 1px - background-position center center - background-image repeating-linear-gradient( - to right, - #F7C64D (120px * 0), - #F7C64D (120px * 1), - #61BAA8 (120px * 1), - #61BAA8 (120px * 2), - #1394AF (120px * 2), - #1394AF (120px * 3), - #D93F5D (120px * 3), - #D93F5D (120px * 4), - #714C80 (120px * 4), - #714C80 (120px * 5), - #E16B55 (120px * 5), - #E16B55 (120px * 6) - ) - - .navbar-brand - font-size 20px - color #333 - height 80px - line-height 51px - - img - position relative - top -2px - display inline-block - margin-right 12px - width 30px - - @media (max-width: 480px) - span - display none - - .menu - text-align right - display flex - justify-content flex-end - flex-wrap wrap - align-items center - - .add-app - display inline-block - margin-right 15px - - .fa - margin-right: 0.3em - - .toggle-sidebar - position relative - display inline-block - margin 0 - border 0 - padding 33px 10px - border-radius 0 - transition all .3s - background-color transparent - - &:hover - background-color: #f5f5f5 - - .icon-bar - display block - width 17px - height 1px - border-radius 100% - background-color #333 - - & + .icon-bar - margin-top 5px diff --git a/lib/header/template.jade b/lib/header/template.jade deleted file mode 100644 index e416a3c..0000000 --- a/lib/header/template.jade +++ /dev/null @@ -1,13 +0,0 @@ -.container - a.navbar-brand(href='/') - img(src='/lib/header/images/logo.svg' title='Civic Stack') - span Civic Stack - .menu - a.add-app.btn.btn-primary(href='{{#if user.logged()}}/apps/new{{else}}/login{{/if}}') - .fa.fa-plus-circle - |   - = t('Upload app') - button.toggle-sidebar(type='button' on-click='openSidebar()') - span.icon-bar - span.icon-bar - span.icon-bar diff --git a/lib/homepage/component.json b/lib/homepage/component.json deleted file mode 100644 index a006852..0000000 --- a/lib/homepage/component.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "name": "homepage", - "description": "Homepage page.", - "dependencies": { - "component/t": "1.0.0", - "ractivejs/ractive": "v0.6.1", - "visionmedia/page.js": "1.6.1" - }, - "locals": [ - "applications", - "application-card", - "countries", - "config", - "filters", - "filters-country", - "filters-technology", - "filters-tags", - "language", - "render", - "request", - "tags", - "technologies", - "user" - ], - "scripts": [ "homepage.js", "view.js", "prefilters.js" ], - "styles": [ "styles.styl" ], - "templates": [ "template.jade" ], - "main": "homepage.js" -} diff --git a/lib/homepage/homepage.js b/lib/homepage/homepage.js deleted file mode 100644 index 48e2e8d..0000000 --- a/lib/homepage/homepage.js +++ /dev/null @@ -1,34 +0,0 @@ -/** - * Module dependencies. - */ - -var applications = require('applications').approved; -var countries = require('countries'); -var filters = require('filters'); -var Homepage = require('./view'); -var page = require('page'); -var tags = require('tags'); -var technologies = require('technologies'); -var user = require('user'); - -page('/', - applications.middleware, - countries.middleware, - technologies.middleware, - tags.middleware, - user.optional, - function (ctx, next) { - var homepage = new Homepage({ - data: { - applications: applications.items, - user: user - }, - el: '.site-content', - replace: true - }); - - homepage.set('filters', filters.reset()); - - if (ctx.path === '/login') next(); - } -); diff --git a/lib/homepage/prefilters.js b/lib/homepage/prefilters.js deleted file mode 100644 index e83e801..0000000 --- a/lib/homepage/prefilters.js +++ /dev/null @@ -1,90 +0,0 @@ -var config = require('config'); - -/** - * Prefilters Schema - -[ // Array - { - title: { - en: 'Communications for all' // String , - es: 'Comunicación más accesible' // String , - fr: 'Communications for all' // String - }, - tagss: [ // Array - { - type: 'tags' // String , - id: '552f40333ec2bb03001dece1' // String - }, - ... - ] - }, - ... -] - - */ - -var optionalLangs = config.languages.filter(function (lang) { - return lang !== 'en'; -}); - -module.exports = [ - { - title: { - en: 'Communications for all', - es: 'Comunicación más accesible' - }, - filters: [ - {type: 'tags', id: '552f40333ec2bb03001dece1'} - ] - }, - { - title: { - en: 'Open budgets', - es: 'Transparentar presupuestos' - }, - filters: [ - {type: 'tags', id: '552ef4c2f7692f03000022a2'} - ] - }, - { - title: { - en: 'Crowdsourcing legislation', - es: 'Difundir legislación' - }, - filters: [ - {type: 'tags', id: '552ef3d1f7692f0300002291'}, - {type: 'tags', id: '552f28bb3ec2bb03001deccd'} - ] - }, - { - title: { - en: 'Civic engagement', - es: 'Promover la participación ciudadana' - }, - filters: [ - {type: 'tags', id: '552f29e23ec2bb03001deccf'}, - {type: 'tags', id: '552ef383f7692f030000228d'}, - {type: 'tags', id: '552ef3b0f7692f030000228e'}, - {type: 'tags', id: '552f2a193ec2bb03001decd0'} - ] - }, - { - title: { - en: 'Data & visualizations', - es: 'Visualización de datos' - }, - filters: [ - {type: 'tags', id: '552ef4f0f7692f03000022a4'}, - {type: 'tags', id: '552f29b63ec2bb03001decce'}, - {type: 'tags', id: '552ef4d0f7692f03000022a3'}, - {type: 'tags', id: '55367179719aa70300d04489'}, - {type: 'tags', id: '552ef328f7692f030000228b'} - ] - } -].map(function (prefilter) { - optionalLangs.forEach(function (lang) { - prefilter.title[lang] = prefilter.title[lang] || prefilter.title.en; - }); - - return prefilter; -}); diff --git a/lib/homepage/styles.styl b/lib/homepage/styles.styl deleted file mode 100644 index 22abea0..0000000 --- a/lib/homepage/styles.styl +++ /dev/null @@ -1,105 +0,0 @@ -.homepage - .global-description - margin 20px 0 .8em - font-size 2em - line-height 1.4 - cursor default - - strong - color #27aeaa - - .prefilters - max-width 900px - margin 0 auto 60px - - button - margin 0 5px 10px - - .filters - .badge - margin 0 5px - - .filter-remove - margin-left 10px - - a - color white - - &:hover - text-decoration none - cursor pointer - - .loading-overlay - position fixed - height 100% - width 100% - opacity 0.8 - background-color #dfdfdf - z-index 10 - - .applications-wrapper - padding 0 - - .applications-container - display flex - flex-wrap wrap - justify-content center - -.homepage-navigation - margin-bottom 30px - - .btn - line-height 28px - - .btn-link - padding-left 0 - padding-right 0 - color #333 - - & + .btn-link - margin-left 30px - - &:hover - color inherit - - .search - outline 0 - padding 20px 20px 20px 30px - border 0 - border-radius 1px - box-shadow 0px 1px 2px rgba(0,0,0, .1) - background-color #fff - transition all .3s linear - - &:active, - &:focus - box-shadow 0px 1px 3px rgba(0,0,0, .25) - - .search-icon - color #b3b3b3 - position absolute - left 25px - top 13px - - .sort-btn - position relative - color #999 - text-decoration none - - &.active - color #333 - font-weight 900 - - & + .sort-btn - padding-left 10px - margin-left 10px - - &:before - content '' - display block - position absolute - left 0 - top 1em - height 1em - width 1px - background-color #ccc diff --git a/lib/homepage/template.jade b/lib/homepage/template.jade deleted file mode 100644 index ac020bb..0000000 --- a/lib/homepage/template.jade +++ /dev/null @@ -1,80 +0,0 @@ -section.homepage - .container - // Prefilters - .prefilters.text-center - h1.global-description - != t('global.description') - | {{#prefilters:i}} - button.btn.btn-sm.btn-default(type='button', on-click='prefilter:{{i}}') - | {{title[lang]}} - | {{/prefilters}} - - // Search and filters - .row.homepage-navigation - .col-xs-12.col-sm-4 - input.form-control.search(type='text' value='{{ filters.search }}' on-keyup='search') - span.search-icon.fa.fa-search - .col-xs-12.col-sm-4 - button.btn.btn-link(type='button' on-click='filter:country') - = t('app.country') - |   - span.caret - button.btn.btn-link(type='button' on-click='filter:technology') - = t('app.technology') - |   - span.caret - button.btn.btn-link(type='button' on-click='filter:tags') - = t('app.tags') - |   - span.caret - .col-xs-12.col-sm-4.sort.text-right - | {{#sorts}} - button( - type='button', - class='btn btn-link sort-btn {{isSortActive(key)}}', - on-click="sortBy:{{key}},{{order}}") - | {{title}} - | {{/sorts}} - - // Applied filters badges - .row.filters: .col-xs-12 - | {{#each selectedCountries}} - span.badge.filter-country {{ this.name[lang] }} - span.filter-remove - a(on-click="removeFilter:{{'country'}},{{this.id}}") × - | {{/each selectedCountries}} - | {{#each selectedTechnologies}} - span.badge.filter-technology {{ this.name }} - span.filter-remove - a(on-click="removeFilter:{{'technology'}},{{this.id}}") × - | {{/each selectedTechnologies}} - | {{#each selectedTags}} - span.badge.filter-tag {{ this.name[lang] }} - span.filter-remove - a(on-click="removeFilter:{{'tag'}},{{this.id}}") × - | {{/each selectedTags}} - - // Application list - .container.applications-wrapper - | {{#filterUpdating}} - .loading-overlay - | {{/filterUpdating}} - - | {{#if applications}} - .applications-container - | {{#each applications}} - application(application='{{ this }}' user='{{ user }}') - | {{/each applications}} - | {{ else }} - .jumbotron.text-center - p= t('No apps to show yet') - | {{#if user.id }} - a.btn.btn-default.btn-lg(href='/apps/new') - i.fa.fa-plus-circle - | #{t('Upload app')} - | {{ else }} - a.btn.btn-default.btn-lg(href='/login') - i.fa.fa-plus-circle - | #{t('Upload app')} - | {{/if}} - | {{/if}} diff --git a/lib/homepage/view.js b/lib/homepage/view.js deleted file mode 100644 index c950772..0000000 --- a/lib/homepage/view.js +++ /dev/null @@ -1,244 +0,0 @@ -var ApplicationCard = require('application-card'); -var applications = require('applications'); -var filters = require('filters'); -var countries = require('countries'); -var tags = require('tags'); -var t = require('t'); -var technologies = require('technologies'); -var FiltersCountry = require('filters-country'); -var FiltersTechnology = require('filters-technology'); -var FiltersTags = require('filters-tags'); -var Ractive = require('ractive'); -var render = require('render'); -var user = require('user'); -var language = require('language'); -var page = require('page'); -var request = require('request'); -var template = require('./template'); -var prefilters = require('./prefilters'); - -var lang = language(); -var filter = filters.filter.bind(filters); - -/** - * Expose Homepage - */ - -module.exports = Ractive.extend({ - template: render(template), - components: { - application: ApplicationCard - }, - data: { - sorts: [ - { - key: 'upvotesCount', - order: 'desc', - title: t('sort.by.popular'), - active: true - }, - { - key: '_id', - order: 'desc', - title: t('sort.by.newest'), - active: false - } - ], - isSortActive: function (sortKey) { - return this.get('sortBy') === sortKey ? 'active' : ''; - }, - prefilters: prefilters - }, - onrender: function () { - var self = this; - - // Initialisation - this.set('user', user); - this.set('filters', filters); - this.set('lang', lang); - this.set('filterUpdating', false); - this.set('sortBy', 'upvotesCount'); - this.set('sortOrder', 'desc'); - updateFilters.call(this); - - // Event handlers registration - this.observe('filters.search', function () { - this.set('applications', applications.approved.items.filter(filter)); - }); - - this.on('*.signinrequired', function () { - page('/login'); - }); - - // Sort links click handler - this.on('sortBy', function (ev, by, order) { - this.set('sortBy', by); - this.set('sortOrder', order || 'asc'); - updateFilters.call(this); - }); - - // Filters X button click handler - this.on('removeFilter', function (ev, type, id) { - function remove (keypath, id) { - var self = this; - var index = this.get(keypath).indexOf(id); - if (index >= 0) { - this.splice(keypath, index, 1).then(function () { - updateFilters.call(self); - self.update(); - }); - } - } - - switch (type) { - case 'country': - remove.call(this, 'filters.countries', id); - break; - case 'technology': - remove.call(this, 'filters.technologies', id); - break; - case 'tag': - remove.call(this, 'filters.tags', id); - break; - default: - throw new Error('error'); - } - }); - - // Search box keyup handler - (function () { - var timeoutId; - var TIME = 1000; - this.on('search', function (ev) { - if (timeoutId) { - clearTimeout(timeoutId); - } - - var self = this; - var value = ev.node.value; - timeoutId = setTimeout(function () { - self.set('filters.search', value); - updateFilters.apply(self); - }, TIME); - }); - }).apply(this); - - // Filter click handler - this.on('filter', function (ev, by) { - ev.original.preventDefault(); - - var modal; - if (by === 'country') { - modal = new FiltersCountry({ - data: { - selected: filters.countries - } - }); - } else if (by === 'technology') { - modal = new FiltersTechnology({ - data: { - selected: filters.technologies - } - }); - } else if (by === 'tags') { - modal = new FiltersTags({ - data: { - selected: filters.tags - } - }); - } - - modal.render('body'); - modal.on('close', updateFilters.bind(self)); - }); - - this.on('prefilter', function (ev, index) { - [ - 'tags', - 'technologies', - 'countries' - ].forEach(function (type) { - var prefilter = prefilters[index]; - this.set('filters.' + type, prefilter.filters.filter(function (item) { - return item.type === type; - }).map(function (item) { - return item.id; - })); - }, this); - updateFilters.call(this); - }); - } -}); - -/** - * Gathers applied filters and requests the backend for applications, then updates the model - */ - -function updateFilters () { - var self = this; - var filters = this.get('filters'); - var sortBy = this.get('sortBy'); - var sortOrder = this.get('sortOrder'); - - // Get current filters - var selectedCountries = countries.items.filter(function (e) { - return filters.countries.indexOf(e.id) >= 0; - }); - var selectedTags = tags.items.filter(function (e) { - return filters.tags.indexOf(e.id) >= 0; - }); - var selectedTechnologies = technologies.items.filter(function (e) { - return filters.technologies.indexOf(e.id) >= 0; - }); - - // Update filters UI - this.set('selectedCountries', selectedCountries); - this.set('selectedTags', selectedTags); - this.set('selectedTechnologies', selectedTechnologies); - - // Fetch applications from API - this.set('filterUpdating', true); - - request - .get(buildURL()) - .end(function (err, res) { - if (err) throw new Error(); - // Update applications - self.set('applications', res.body); - self.set('filterUpdating', false); - }); - - function buildURL () { - var url = '/api/applications/approved?'; - var countries = selectedCountries.length ? 'countries=' + selectedCountries.map(function (a) { - return a.id; - }).join(',') : undefined; - - var tags = selectedTags.length ? 'tags=' + selectedTags.map(function (a) { - return a.id; - }).join(',') : undefined; - - var technologies = selectedTechnologies.length ? 'technologies=' + selectedTechnologies.map(function (a) { - return a.id; - }).join(',') : undefined; - - var sort = sortBy ? 'sort=' + sortBy : undefined; - var order = sortOrder ? 'order=' + sortOrder : undefined; - var search = filters.search ? 'q=' + filters.search : undefined; - return url + compact([countries, tags, technologies, sort, order, search]).join('&'); - } -} - -/** - * Based on an array, returns other array filtering the `undefined` elements - * - * @param {Array} arr The list of elements to be filtered - * @return {Array} An array with non undefined elements - * @api public - */ - -function compact (arr) { - return arr.filter(function (el) { - return el !== undefined; - }); -} diff --git a/lib/language/component.json b/lib/language/component.json deleted file mode 100644 index 8e1cec0..0000000 --- a/lib/language/component.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "name": "language", - "dependencies": { - "component/cookie": "1.1.1" - }, - "locals": [ - "config" - ], - "scripts": [ "language.js" ], - "main": "language.js" -} \ No newline at end of file diff --git a/lib/language/index.js b/lib/language/index.js deleted file mode 100644 index 10b86f4..0000000 --- a/lib/language/index.js +++ /dev/null @@ -1,54 +0,0 @@ -/** - * Module dependencies. - */ - -var config = require('lib/config'); -var log = require('debug')('civicstack:language'); -var languages = [ 'en', 'es', 'fr' ]; - -module.exports = function language(req, res, next) { - var user = req.user; - var lang = config('locale'); - - if (req.query.lang) { - // set - lang = valid(req.query.lang) ? req.query.lang : lang; - - log('Setting language %s', lang); - if (user) { - log('User %s signed in, changing their language', user.id); - res.cookie('lang', lang); - user.lang = lang; - user.save(function (err) { - if (err) return res.send(500); - return res.redirect(req.path); - }); - } else { - log('No user signed in, setting cookie value to %s', lang); - return res.cookie('lang', lang).redirect(req.path); - } - } else { - // get - if (user) { - if (!user.lang) { - lang = req.cookies.lang - res.cookie('lang', lang); - user.lang = lang; - return user.save(function (err) { - if (err) return res.send(500); - return res.redirect(req.path); - }); - } - lang = user.lang; - } else { - lang = req.cookies.lang || lang; - } - log('Setting language to %s', lang); - res.cookie('lang', lang); - next(); - } -} - -function valid(lang) { - return !!~languages.indexOf(lang); -} \ No newline at end of file diff --git a/lib/language/language.js b/lib/language/language.js deleted file mode 100644 index ca36734..0000000 --- a/lib/language/language.js +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Module dependencies. - */ - -var config = require('config'); -var cookie = require('cookie'); - -function language(lang) { - lang = lang || cookie('lang') || config.locale; - - if (lang != cookie('lang')) { - cookie('lang', lang); - location.reload(); - } - - return lang; -} - -module.exports = language; \ No newline at end of file diff --git a/lib/license-api/index.js b/lib/license-api/index.js deleted file mode 100644 index ad0480e..0000000 --- a/lib/license-api/index.js +++ /dev/null @@ -1,84 +0,0 @@ -/** - * Module dependencies. - */ - -var api = require('lib/db-api'); -var accepts = require('lib/accepts'); -var express = require('express'); -var log = require('debug')('civicstack:license'); -var utils = require('lib/utils'); -var admin = utils.admin; -var pluck = utils.pluck; -var expose = utils.expose; -var handleError = utils.handleError; - -var app = module.exports = express(); - -/** - * Limit request to json format only - */ - -app.use(accepts('application/json')); - -app.get('/licenses', function (req, res) { - log('Request /licenses'); - - api.license.all(function(err, licenses) { - if (err) return handleError(err, req, res); - - log('Serving licenses %j', pluck(licenses, "id")); - - var keys = 'id name'; - - res.json(licenses.map(expose(keys))); - }); -}); - -app.get('/licenses/:id', function (req, res) { - log('Request GET /licenses/%s', req.params.id); - - api.license.get(req.params.id, function (err, license) { - if (err) return handleError(err, req, res); - if (!license) return res.send(404); - - log('Serving license %s', license.id); - - res.json(license.toJSON()); - }); -}); - -app.post('/licenses/create', admin, function (req, res) { - log('Request POST /licenses/create %j', req.body); - - api.license.create(req.body, function (err, license) { - if (err) return handleError(err, req, res); - - log('Serving license %s', license.id); - - res.json(license.toJSON()); - }); -}); - -app.post('/licenses/:id', admin, function (req, res) { - log('Request POST /licenses/%s %j', req.params.id, req.body); - - api.license.update(req.body, function (err, license) { - if (err) return handleError(err, req, res); - - log('Serving license %s', license.id); - - res.json(license.toJSON()); - }); -}); - -app.delete('/licenses/:id', admin, function (req, res) { - log('Request DEL /licenses/%s %j', req.params.id, req.body); - - api.license.remove(req.params.id, function (err, id) { - if (err) return handleError(err, req, res); - - log('Deleted license %s', id.id); - - res.sendStatus(200); - }); -}); diff --git a/lib/licenses/component.json b/lib/licenses/component.json deleted file mode 100644 index 59e718f..0000000 --- a/lib/licenses/component.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "name": "licenses", - "description": "Collection of licenses", - "locals": [ - "collection" - ], - "scripts": [ - "licenses.js" - ], - "main": "licenses.js" -} diff --git a/lib/licenses/licenses.js b/lib/licenses/licenses.js deleted file mode 100644 index 572aaa1..0000000 --- a/lib/licenses/licenses.js +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Module dependencies. - */ - -var Collection = require('collection'); - -var licenses = module.exports = new Collection('/api/licenses'); - -licenses.on('loaded', order.bind(licenses)); - -function order() { - this.items.sort(alphabetically); -} - -function alphabetically(a, b) { - if (a.name < b.name) return -1; - if (a.name > b.name) return 1; - return 0; -} diff --git a/lib/login/component.json b/lib/login/component.json deleted file mode 100644 index f98c57b..0000000 --- a/lib/login/component.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "name": "login", - "description": "Login page", - "dependencies": { - "ractivejs/ractive": "v0.6.1", - "visionmedia/page.js": "1.6.1" - }, - "locals": [ - "render", - "user" - ], - "scripts": [ "login.js", "view.js" ], - "styles": [ "styles.styl" ], - "templates": [ "template.jade" ], - "main": "login.js" -} diff --git a/lib/login/login.js b/lib/login/login.js deleted file mode 100644 index d3042ef..0000000 --- a/lib/login/login.js +++ /dev/null @@ -1,14 +0,0 @@ -/** - * Module dependencies. - */ - -var Login = require('./view') -var page = require('page'); -var user = require('user'); - -page('/login', function(ctx, next) { - if (user.logged()) return next(); - - var login = new Login(); - login.render('body'); -}); diff --git a/lib/login/styles.styl b/lib/login/styles.styl deleted file mode 100644 index f4af316..0000000 --- a/lib/login/styles.styl +++ /dev/null @@ -1,7 +0,0 @@ -#login-modal - a.login - font-size 48px - margin 0 16px - - &.twitter - color #55acee \ No newline at end of file diff --git a/lib/login/template.jade b/lib/login/template.jade deleted file mode 100644 index d7018bb..0000000 --- a/lib/login/template.jade +++ /dev/null @@ -1,9 +0,0 @@ -#login-modal.modal.fade(tabindex='-1', role='dialog', aria-labelledby='mySmallModalLabel', aria-hidden='true') - .modal-dialog.modal-sm - .modal-content - .modal-header=t('header.login') - button.close(type='button', data-dismiss='modal', aria-label='Close') - span(aria-hidden='true') × - .modal-body.text-center - a.login.twitter(href='/auth/twitter'): i.fa.fa-twitter - a.login.github(href='/auth/github'): i.fa.fa-github \ No newline at end of file diff --git a/lib/login/view.js b/lib/login/view.js deleted file mode 100644 index 7cf3ba5..0000000 --- a/lib/login/view.js +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Module dependencies. - */ - -var page = require('page'); -var Ractive = require('ractive'); -var render = require('render'); -var template = require('./template'); - -module.exports = Ractive.extend({ - isolated: true, - template: render(template), - onrender: function () { - $('.modal').modal(); - - var self = this; - - $('.modal').on('hidden.bs.modal', function (e) { - $('.modal').remove(); - self.fire('close'); - }) - } -}); diff --git a/lib/modal/component.json b/lib/modal/component.json deleted file mode 100644 index 885bed5..0000000 --- a/lib/modal/component.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "name": "modal", - "dependencies": { - "ractivejs/ractive": "v0.6.1" - }, - "locals": [ - "config", - "render" - ], - "scripts": [ "modal.js" ], - "styles": [ "styles.styl" ], - "templates": [ "template.jade" ], - "main": "modal.js" -} diff --git a/lib/modal/modal.js b/lib/modal/modal.js deleted file mode 100644 index 6151c04..0000000 --- a/lib/modal/modal.js +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Module dependencies. - */ - -var config = require('config'); -var Ractive = require('ractive'); -var render = require('render'); -var template = require('./template'); - -module.exports = Ractive.extend({ - isolated: true, - template: render(template), - onrender: function () { - $('.modal').modal(); - - var self = this; - - $('.modal').on('hidden.bs.modal', function (e) { - $('.modal').remove(); - self.fire('close'); - }) - } -}); diff --git a/lib/modal/styles.styl b/lib/modal/styles.styl deleted file mode 100644 index e69de29..0000000 diff --git a/lib/modal/template.jade b/lib/modal/template.jade deleted file mode 100644 index eda6150..0000000 --- a/lib/modal/template.jade +++ /dev/null @@ -1,7 +0,0 @@ -.modal.fade(tabindex='-1', role='dialog', aria-labelledby='mySmallModalLabel', aria-hidden='true') - .modal-dialog - .modal-content - .modal-header {{ title }} - button.close(type='button', data-dismiss='modal', aria-label='Close') - span(aria-hidden='true') × - .modal-body {{ body }} diff --git a/lib/models/country.js b/lib/models/country.js deleted file mode 100644 index a6306ba..0000000 --- a/lib/models/country.js +++ /dev/null @@ -1,31 +0,0 @@ -/** - * Module dependencies. - */ - -var i18nSchema = require('./i18n-schema') -var mongoose = require('mongoose'); -var Schema = mongoose.Schema; -var ObjectId = Schema.ObjectId; - -/** - * Country Schema - */ - -var CountrySchema = new Schema({ - name: i18nSchema -}); - -/** - * Make Schema `.toObject()` and - * `.toJSON()` parse getters for - * proper JSON API response - */ - -CountrySchema.set('toObject', { getters: true }); -CountrySchema.set('toJSON', { getters: true }); - -/** - * Expose Mongoose model loaded - */ - -module.exports = mongoose.model('Country', CountrySchema); diff --git a/lib/models/i18n-schema.js b/lib/models/i18n-schema.js deleted file mode 100644 index 32265ef..0000000 --- a/lib/models/i18n-schema.js +++ /dev/null @@ -1,14 +0,0 @@ -/** - * Module dependencies. - */ - -var config = require('lib/config'); -var languages = config('languages'); - -var i18nSchema = {}; - -languages.forEach(function (language) { - i18nSchema[language] = { type: String }; -}); - -module.exports = i18nSchema; diff --git a/lib/open-graph/index.js b/lib/open-graph/index.js deleted file mode 100644 index f254077..0000000 --- a/lib/open-graph/index.js +++ /dev/null @@ -1,37 +0,0 @@ -var og = require('open-graph-scraper'); -var Cache = require('node-cache'); - -var OpenGraph = function () { - var cache = new Cache({ stdTTL: 3600 }); - - return { - get: function(url, cb) { - cache.get(url, function(err, value) { - if (err) { - return cb(err); - } - - // If URL exists in cache, return it - if (value) { - return cb(null, value); - } - - // Get OG data from remote site - return og({ url: url }, function(err, result) { - // In case of error, pass control to callback - if (err) { - return cb(err); - } - - // Save in cache - cache.set(url, result, function() { - // Return control to callback - cb(null, result); - }); - }); - }); - } - }; -}; - -module.exports = new OpenGraph(); diff --git a/lib/panel/component.json b/lib/panel/component.json deleted file mode 100644 index a99cd34..0000000 --- a/lib/panel/component.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "name": "panel", - "locals": [ - "render" - ], - "scripts": [ "index.js" ], - "templates": [ "template.jade" ], - "main": "index.js" -} \ No newline at end of file diff --git a/lib/panel/index.js b/lib/panel/index.js deleted file mode 100644 index 296d67c..0000000 --- a/lib/panel/index.js +++ /dev/null @@ -1,20 +0,0 @@ -/** - * Module dependencies. - */ - -var render = require('render'); -var template = require('./template') - -function Panel(opts) { - if (!(this instanceof Panel)) { - return new Panel(opts); - } - - var panel = render.dom(template, opts); - - if (opts.el) { - opts.el.appendChild(panel); - } -} - -module.exports = Panel; diff --git a/lib/panel/template.jade b/lib/panel/template.jade deleted file mode 100644 index d3b7073..0000000 --- a/lib/panel/template.jade +++ /dev/null @@ -1,3 +0,0 @@ -.panel(class=classes) - .panel-heading: strong=heading - .panel-body=body \ No newline at end of file diff --git a/lib/registration/index.js b/lib/registration/index.js deleted file mode 100644 index 5c8e2de..0000000 --- a/lib/registration/index.js +++ /dev/null @@ -1,76 +0,0 @@ -/** - * Module dependencies. - */ - -var User = require('mongoose').model('User'); - -/** - * Twitter Registration - * - * @param {Object} profile PassportJS's profile - * @param {Function} fn Callback accepting `err` and `user` - * @api public - */ - -exports.twitter = function twitter (profile, fn) { - var user = new User(); - - user.fullName = profile.displayName; - user.profiles.twitter = profile; - user.avatar = profile.photos[0].value; - - user.save(function(err) { - return fn(err, user); - }); -} - -/** - * Github Registration - * - * @param {Object} profile PassportJS's profile - * @param {Function} fn Callback accepting `err` and `user` - * @api public - */ - -exports.github = function twitter (profile, fn) { - var user = new User(); - - user.fullName = profile.displayName; - user.profiles.github = profile; - user.avatar = profile._json.avatar_url; - user.email = profile._json.email; - - user.save(function(err) { - return fn(err, user); - }); -} - -/** - * Get image url for profile - * - * @param {Object} profile - * @param {String} email - * @return {String} Profile image url (or `avatar`) - * @api private - */ - -function getImageUrl (profile, email) { - return profile.imageUrl - || 'http://gravatar.com/avatar/'.concat(md5(email)).concat('?d=mm&size=200') - || ''; -} - -/** - * MD5 - * - * @param {String} source - * @return {String} target - * @api private - */ - -function md5 (source) { - return require('crypto') - .createHash('md5') - .update(source) - .digest("hex"); -} diff --git a/lib/related-links/component.json b/lib/related-links/component.json deleted file mode 100644 index 222c8f6..0000000 --- a/lib/related-links/component.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "name": "related-links", - "dependencies": { - "ractivejs/ractive": "v0.6.1" - }, - "locals": [ - "render" - ], - "scripts": [ "related-links.js" ], - "styles": [ "styles.styl" ], - "templates": [ "template.jade" ], - "main": "related-links.js" -} diff --git a/lib/related-links/related-links.js b/lib/related-links/related-links.js deleted file mode 100644 index a3bface..0000000 --- a/lib/related-links/related-links.js +++ /dev/null @@ -1,63 +0,0 @@ -var Ractive = require('ractive'); -var render = require('render'); -var template = require('./template'); - -module.exports = Ractive.extend({ - isolated: true, - template: render(template), - onrender: function () { - var self = this; - this.on('saveEditedLink', function (ev) { - ev.context.editing = false; - this.set('adding', false); - this.update(); - }); - - this.on('editLink', function (ev) { - this.set('links.*.editing', false); - ev.context.editing = true; - this.set('adding', true); - this.update(); - }); - - this.on('removeLink', function (ev) { - function indexOf (arr, fn) { - for (var i = 0; i < arr.length; i++) { - if (fn(arr[i])) { - return i; - } - } - return -1; - } - - var currentIndex = ev.context.index; - var idx = indexOf(this.get('links'), function (v) { - return v.index === currentIndex; - }); - - this.splice('links', idx, 1).then(function () { - self.update(); - }); - }); - - function nextIndex () { - function max (arr) { - var m = -1; - for (var i = 0; i < arr.length; i++) { - if (arr[i].index > m) { - m = arr[i].index; - } - } - - return m; - } - - return max(self.get('links')) + 1; - } - - this.on('addLink', function (ev) { - this.push('links', { url: 'http://', description: '', editing: true, index: nextIndex() }); - this.set('adding', true); - }); - } -}); diff --git a/lib/related-links/styles.styl b/lib/related-links/styles.styl deleted file mode 100644 index c2ed99b..0000000 --- a/lib/related-links/styles.styl +++ /dev/null @@ -1,9 +0,0 @@ - .related-links - .related-link - margin-bottom 20px - - input - width 100% - - .related-links-commands - margin-top 20px diff --git a/lib/related-links/template.jade b/lib/related-links/template.jade deleted file mode 100644 index 8d5ffa8..0000000 --- a/lib/related-links/template.jade +++ /dev/null @@ -1,25 +0,0 @@ -| {{#each links }} -.col-sm-offset-2.col-sm-10.related-link - .row - .col-sm-8 - .row.related-link-description - span(class="{{# this.editing}}hide{{/}}") - strong {{ this.description }} - input(type='text', value='{{ this.description }}', class="{{# !this.editing}}hide{{/}}", - placeholder=t('app.links.description.placeholder')) - .row.related-link-url - span(class="{{# this.editing}}hide{{/}}") {{ this.url }} - input(type='url', value='{{ this.url }}', class="{{# !this.editing}}hide{{/}}") - .col-sm-2(class="{{# !this.editing}}hide{{/}}") - .btn.btn-success.btn-links.save('on-click'='saveEditedLink') - span.fa.fa-check - .col-sm-1(class="{{# this.editing}}hide{{/}}") - .btn.btn-link.btn-links.edit('on-click'='editLink') - span.fa.fa-pencil - .col-sm-1(class="{{# this.editing}}hide{{/}}") - .btn.btn-link.btn-links.remove('on-click'='removeLink') - span.fa.fa-trash -| {{/each }} -.col-sm-offset-1.col-sm-11.related-links-commands - button.btn.btn-success.btn-links.add('on-click'='addLink', class="{{# this.adding}}hide{{/}}") - span.fa.fa-plus diff --git a/lib/render/component.json b/lib/render/component.json deleted file mode 100644 index c28e053..0000000 --- a/lib/render/component.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "name": "render", - "description": "Component render utility", - "dependencies": { - "component/domify": "1.3.1", - "cristiandouce/merge-util": "0.3.1", - "component/t": "1.0.0" - }, - "locals": [ "config" ], - "scripts": [ "render.js" ], - "main": "render.js" -} diff --git a/lib/render/render.js b/lib/render/render.js deleted file mode 100644 index 50af8b6..0000000 --- a/lib/render/render.js +++ /dev/null @@ -1,29 +0,0 @@ -/** - * Module dependencies. - */ - -var domify = require('domify'); -var merge = require('merge-util'); - -/** - * Render default modules - */ - -var t = require('t'); -var config = require('config'); - -exports = module.exports = render; -exports.dom = dom; - -function render(template, options) { - var defaults = { - t: t, - config: config - }; - - return template(merge(defaults, options, true)).replace(">", ">").replace("<", "<"); -} - -function dom(template, options) { - return domify(render(template, options)); -} diff --git a/lib/request/component.json b/lib/request/component.json deleted file mode 100644 index 1a47499..0000000 --- a/lib/request/component.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "name": "request", - "description": "Wrapper of visionmedia/superagent DemocracyOS", - "version": "0.0.1", - "dependencies": { - "component/bus": "0.0.2", - "visionmedia/debug": "2.1.3", - "visionmedia/superagent": "0.21.0" - }, - "scripts": [ "request.js" ], - "main": "request.js" -} diff --git a/lib/request/request.js b/lib/request/request.js deleted file mode 100644 index 5650924..0000000 --- a/lib/request/request.js +++ /dev/null @@ -1,47 +0,0 @@ -/** - * Module dependencies. - */ - -var request = require('superagent'); -var proto = request.Request.prototype; -var end = proto.end; -var bus = require('bus'); -var log = require('debug')('civicstack:request'); - -/** - * Expose request - */ - -module.exports = request; - -/** - * Wrapper of `request.end` - */ - -proto.end = function(fn) { - var req = this; - fn = fn || function() {}; - - // emit request about to exec - bus.emit('request', req); - - // Accept only json on requests - req.set('Accept', 'application/json'); - - // if `GET`, set random query parameter - // to avoid browser caching - - if ('GET' === req.method) req.query({ random: Math.random() }); - - // emit `request:abort` if req aborts - req.once('abort', function() { - bus.emit('request:abort', req); - }); - - return end.call(req, function(err, res) { - log('end with %s %o', err, res); - bus.emit('request:end', res, req, err); - - return fn(err, res); - }); -}; diff --git a/lib/scroll/component.json b/lib/scroll/component.json deleted file mode 100644 index 0faf9c1..0000000 --- a/lib/scroll/component.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "name": "scroll", - "scripts": [ "scroll.js" ], - "main": "scroll.js" -} \ No newline at end of file diff --git a/lib/scroll/scroll.js b/lib/scroll/scroll.js deleted file mode 100644 index 7bc3824..0000000 --- a/lib/scroll/scroll.js +++ /dev/null @@ -1,11 +0,0 @@ -module.exports = function scrollTo(element, to, duration) { - if (duration < 0) return; - var difference = to - element.scrollTop; - var perTick = difference / duration * 10; - - setTimeout(function() { - element.scrollTop = element.scrollTop + perTick; - if (element.scrollTop === to) return; - scrollTo(element, to, duration - 10); - }, 10); -} \ No newline at end of file diff --git a/lib/server/api/apps/index.js b/lib/server/api/apps/index.js new file mode 100644 index 0000000..ddb2ab8 --- /dev/null +++ b/lib/server/api/apps/index.js @@ -0,0 +1,176 @@ +const express = require('express') +const api = require('../../db-api') +const log = require('debug')('civicstack:api:application') +const utils = require('../../utils') +const { admin, restrict, handleError, openGraph, accepts } = utils + +const app = module.exports = express() + +/** + * Limit request to json format only + */ + +app.use(accepts('application/json')) + +const all = ['id name logo backgroundColor video description technology technologyids links', + 'organization github website country twitter license contact partnership', + 'comments tags approved tagids uploadedBy upvotesCount upvoted'].join(' ') + +function expose (user) { + return function (app) { + var obj = utils.expose(all)(app) + obj.upvoted = user && app.upvotes !== undefined && + (app.upvotes.map(a => a.toString()).indexOf(user.id) >= 0) + + return obj + } +} + +app.get('/applications', utils.admin, (req, res) => { + log('Request GET /applications') + api.application.all() + .then(applications => { + const body = applications.map(expose(req.user)) + log(`Response GET /applications: ${body.length} items`) + res.json(body) + }) + .catch(err => { + log(`Error GET /applications: ${err.toString()}`) + handleError(err, req, res) + }) +}) + +app.get('/applications/approved', function (req, res) { + function parse (key) { + return req.query[key] ? req.query[key].split(',') : undefined + } + + log('GET /applications/approved') + + // Filters + const tags = parse('tags') + const technologies = parse('technologies') + const countries = parse('countries') + const search = req.query.q + // Sorting + const sortBy = req.query.sort === '_id' || req.query.sort === 'upvotesCount' + ? req.query.sort : undefined + const sortOrder = req.query.order === 'asc' || req.query.order === 'desc' ? req.query.order : 'asc' + + // Fetch data + if (tags || countries || technologies || search || sortBy) { + const filter = { + tags: tags, + technologies: technologies, + countries: countries, + search: search, + sortBy: sortBy, + sortOrder: sortOrder + } + + api.application.filter(filter) + .then(applications => { + const body = applications.map(expose(req.user)) + log(`Response GET /applications/approved: ${body.length} items`) + res.json(body) + }) + .catch(err => handleError(err, req, res)) + } else { + api.application.approved() + .then(applications => { + const body = applications.map(expose(req.user)) + log(`Response GET /applications/approved: ${body.length} items`) + res.json(body) + }) + .catch(err => handleError(err, req, res)) + } +}) + +app.get('/applications/:id', (req, res) => api.application.get(req.params.id) + .then(application => application + ? res.json(expose(req.user)(application.toJSON())) + : res.send(404)) + .catch(err => handleError(err, req, res))) + +app.get('/applications/:id/links', function (req, res, next) { + function promisify (links) { + return links.map(function (link) { + return new Promise(function (resolve) { + openGraph.get(link.url, function (err, result) { + var data = { url: link.url, description: link.description } + data.og = err ? {} : ({ + title: result.data.ogTitle || result.data.title, + description: result.data.ogDescription, + image: result.data.ogImage && result.data.ogImage.url + }) + return resolve(data) + }) + }) + }) + } + + log('Request GET /application/%s/links', req.params.id) + + api.application.get(req.params.id) + .then(application => { + if (application) { + Promise.all(promisify(application.links)) + .then(app => { + log(`Response GET /application/${req.params.id}/links: ${JSON.stringify(app)}`) + res.json(app) + }) + .catch(err => { + log(`Error GET /application/${req.params.id}/links: ${err.toString()}`) + next(err) + }) + } else { + log(`Response GET /application/${req.params.id}/links: not found (404)`) + res.send(404) + } + }) + .catch(err => handleError(err, req, res)) +}) + +app.post('/applications/create', restrict, protect, function (req, res) { + log('Request POST /applications/create %j', req.body) + + req.body.uploadedBy = req.user + if (req.body.license === '') delete req.body.license + + api.application.create(req.body) + .then(application => res.json(expose(req.user)(application.toJSON()))) + .catch(err => handleError(err, req, res)) +}) + +app.post('/applications/:id', admin, protect, function (req, res) { + log('Request POST /applications/%s %j', req.params.id, req.body) + + if (req.body.license === '') delete req.body.license + req.body.video = utils.getEmbeddableYouTubeURL(req.body.video) + + api.application.update(req.body) + .then(app => res.json(expose(req.user)(app.toJSON()))) + .catch(err => handleError(err, req, res)) +}) + +app.post('/applications/:id/upvote', restrict, (req, res) => + api.application.upvote(req.params.id, req.user.id) + .then(app => res.json(expose(req.user)(app.toJSON()))) + .catch(err => handleError(err, req, res))) + +app.delete('/applications/:id/upvote', restrict, (req, res) => + api.application.undoUpvote(req.params.id, req.user.id) + .then(app => res.json(expose(req.user)(app.toJSON()))) + .catch(err => handleError(err, req, res))) + +app.delete('/applications/:id', admin, (req, res) => + api.application.remove(req.params.id) + .then(id => res.sendStatus(200)) + .catch(err => handleError(err, req, res))) + +function protect (req, res, next) { + if (!req.user.admin) { + delete req.body.approved + } + next() +} diff --git a/lib/server/db-api/application.js b/lib/server/db-api/application.js new file mode 100644 index 0000000..9b0b4d9 --- /dev/null +++ b/lib/server/db-api/application.js @@ -0,0 +1,137 @@ +const mongoose = require('mongoose') +const Application = mongoose.model('Application') + +const fields = ['id name logo backgroundColor video description technology technologyids links tags', + 'organization github website country twitter license contact partnership comments approved tagids', + 'upvotes upvotesCount'].join(' ') + +/** + * Get all applications + * + * @return {Promise} When fulfilled, returns all the apps + * @api public + */ + +exports.all = () => Application + .find({ deletedAt: null }) + .select(fields) + .populate('tags') + .populate('country') + .populate('license') + .populate('technology') + .populate({ + path: 'uploadedBy', + select: 'avatar firstName lastName' + }) + .exec() + +/** + * Get Application form `id` string or `ObjectId` + * + * @param {String|ObjectId} id Application's `id` + * @param {Function} fn callback function + * - 'err' error found while process or `null` + * - 'application' found item or `undefined` + * @api public + */ + +exports.get = id => Application + .findOne({ _id: id, deletedAt: null }) + .populate('tags') + .populate('country') + .populate('license') + .populate('technology') + .populate({ + path: 'uploadedBy', + select: 'avatar firstName lastName' + }) + .exec() + +/** + * Creates app + * + * @param {Object} data to create app + * @return {Promise} When fulfilled, the app has been created + * @api public + */ + +exports.create = data => { + data.tags = data.tagids || [] + data.technology = data.technologyids || [] + + var app = new Application(data) + return app.save() +} + +/** + * Update given `app` + * + * @param {ObjectId|String} data to create app + * @return {Promise} When fulfilled, the app has been updated + * @api public + */ + +exports.update = data => exports.get(data.id).then(app => { + // update and save app document with data + data.tags = data.tagids || [] + data.technology = data.technologyids || [] + data.links = data.links || [] + app.set(data) + return app.save() +}) + +/** + * Adds an upvote to a given `app` + * + * @param {ObjectId} ID of the app + * @param {ObjectId} ID of the user that gives the upvote + * @return {Promise} When fulfilled, returns the app + * @api public + */ + +exports.upvote = (appId, userId) => Application.findOne({ _id: appId }) + .then(app => { + app.upvotes.addToSet(userId) + app.upvotesCount = app.upvotes.length + return app.save() + }) + .then(() => exports.get(appId)) + +/** + * Removes an upvote from a given `app` + * + * @param {ObjectId} ID of the app + * @param {ObjectId} ID of the user + * @return {Promise} When fulfilled, returns the app + * @api public + */ + +exports.undoUpvote = (appId, userId) => Application.findOne({ _id: appId }) + .then(doc => { + doc.upvotes.pull(userId) + doc.upvotesCount = doc.upvotes.length + doc.save() + }) + .then(() => exports.get(appId)) + +/** + * Search approved apps + * + * @return {Promise} When fulfilled, returns a list of approved apps + * @api public + */ + +exports.approved = () => Application + .find({ approved: true }) + .populate('country') + .exec() + +/** + * Deletes app + * + * @param {Object} data to remove app + * @return {Promise} when fulfilled, the document has been deleted + * @api public + */ + +exports.remove = id => Application.remove({ _id: id }).exec() diff --git a/lib/db-api/country.js b/lib/server/db-api/country.js similarity index 53% rename from lib/db-api/country.js rename to lib/server/db-api/country.js index 2aa287b..2c46cc8 100644 --- a/lib/db-api/country.js +++ b/lib/server/db-api/country.js @@ -1,19 +1,6 @@ -/** - * Extend module's NODE_PATH - * HACK: temporary solution - */ - -require('node-path')(module); - -/** - * Module dependencies. - */ - -var mongoose = require('mongoose'); -var Country = mongoose.model('Country'); -var utils = require('lib/utils'); -var pluck = utils.pluck; -var log = require('debug')('civicstack:db-api:country'); +var mongoose = require('mongoose') +var Country = mongoose.model('Country') +var log = require('debug')('civicstack:db-api:country') /** * Get all countries @@ -25,24 +12,10 @@ var log = require('debug')('civicstack:db-api:country'); * @api public */ -exports.all = function all(fn) { +exports.all = () => { log('Looking for all countries.') - - Country - .find() - .select('id name') - .exec(function (err, countries) { - if (err) { - log('Found error %j', err); - return fn(err); - }; - - log('Delivering countries %j', pluck(countries, 'id')); - fn(null, countries); - }); - - return this; -}; + return Country.find().select('id name').exec() +} /** * Get Country form `id` string or `ObjectId` @@ -55,25 +28,25 @@ exports.all = function all(fn) { */ exports.get = function get(id, fn) { - var query = { _id: id }; + var query = { _id: id } - log('Looking for country %s', id); + log('Looking for country %s', id) Country .findOne(query) .exec(function (err, country) { if (err) { - log('Found error %s', err); - return fn(err); - }; + log('Found error %s', err) + return fn(err) + } if (!country) { - log('Country %s not found', id); - return fn(null); + log('Country %s not found', id) + return fn(null) } - log('Delivering country %s', country.id); - fn(null, country); - }); -}; + log('Delivering country %s', country.id) + fn(null, country) + }) +} /** * Creates country @@ -87,20 +60,20 @@ exports.get = function get(id, fn) { */ exports.create = function create(data, fn) { - log('Creating new country %j', data); - - var country = new Country(data); - country.save(onsave); + log('Creating new country %j', data) + + var country = new Country(data) + country.save(onsave) function onsave(err) { - if (err) return log('Found error %s', err), fn(err); + if (err) return log('Found error %s', err), fn(err) - log('Saved country %s', country.id); - fn(null, country); + log('Saved country %s', country.id) + fn(null, country) } - return this; -}; + return this +} /** * Update given `country` @@ -114,31 +87,31 @@ exports.create = function create(data, fn) { */ exports.update = function update(data, fn) { - log('Updating country %s with %j', data.id, data); + log('Updating country %s with %j', data.id, data) // get country - exports.get(data.id, onget); + exports.get(data.id, onget) function onget(err, country) { if (err) { - log('Found error %s', err.message); - return fn(err); - }; + log('Found error %s', err.message) + return fn(err) + } // update and save country document with data - country.set(data); - country.save(onupdate); + country.set(data) + country.save(onupdate) } function onupdate(err, country) { - if (err) return log('Found error %s', err), fn(err); - + if (err) return log('Found error %s', err), fn(err) + log('Saved country %s', country.id) - fn(null, country); + fn(null, country) } - return this; -}; + return this +} /** * Deletes country @@ -152,16 +125,16 @@ exports.update = function update(data, fn) { */ exports.remove = function remove(id, fn) { - log('Deleting country %s', id); + log('Deleting country %s', id) Country .remove({_id: id}) .exec(function (err) { - if (err) return log('Found error %s', err), fn(err); + if (err) return log('Found error %s', err), fn(err) - log('Removed country %s', id); - fn(null, id); - }); + log('Removed country %s', id) + fn(null, id) + }) - return this; -}; + return this +} diff --git a/lib/db-api/index.js b/lib/server/db-api/index.js similarity index 51% rename from lib/db-api/index.js rename to lib/server/db-api/index.js index d616caf..b284e3a 100644 --- a/lib/db-api/index.js +++ b/lib/server/db-api/index.js @@ -2,28 +2,28 @@ * Expose application's database api */ -exports.application = require('./application'); +exports.application = require('./application') /** * Expose tag's database api */ -exports.tag = require('./tag'); +exports.tag = require('./tag') /** * Expose country's database api */ -exports.country = require('./country'); +exports.country = require('./country') /** * Expose license's database api */ -exports.license = require('./license'); +exports.license = require('./license') /** * Expose technology's database api */ -exports.technology = require('./technology'); +exports.technology = require('./technology') diff --git a/lib/db-api/license.js b/lib/server/db-api/license.js similarity index 57% rename from lib/db-api/license.js rename to lib/server/db-api/license.js index 7dcaf22..cc9f1d5 100644 --- a/lib/db-api/license.js +++ b/lib/server/db-api/license.js @@ -1,19 +1,8 @@ -/** - * Extend module's NODE_PATH - * HACK: temporary solution - */ - -require('node-path')(module); - -/** - * Module dependencies. - */ - -var mongoose = require('mongoose'); -var License = mongoose.model('License'); -var utils = require('lib/utils'); -var pluck = utils.pluck; -var log = require('debug')('civicstack:db-api:license'); +var mongoose = require('mongoose') +var License = mongoose.model('License') +var utils = require('../utils') +var pluck = utils.pluck +var log = require('debug')('civicstack:db-api:license') /** * Get all licenses @@ -33,16 +22,16 @@ exports.all = function all(fn) { .select('id name') .exec(function (err, licenses) { if (err) { - log('Found error %j', err); - return fn(err); - }; + log('Found error %j', err) + return fn(err) + } - log('Delivering licenses %j', pluck(licenses, 'id')); - fn(null, licenses); - }); + log('Delivering licenses %j', pluck(licenses, 'id')) + fn(null, licenses) + }) - return this; -}; + return this +} /** * Get License form `id` string or `ObjectId` @@ -55,25 +44,25 @@ exports.all = function all(fn) { */ exports.get = function get(id, fn) { - var query = { _id: id }; + var query = { _id: id } - log('Looking for license %s', id); + log('Looking for license %s', id) License .findOne(query) .exec(function (err, license) { if (err) { - log('Found error %s', err); - return fn(err); - }; + log('Found error %s', err) + return fn(err) + } if (!license) { - log('License %s not found', id); - return fn(null); + log('License %s not found', id) + return fn(null) } - log('Delivering license %s', license.id); - fn(null, license); - }); -}; + log('Delivering license %s', license.id) + fn(null, license) + }) +} /** * Creates license @@ -87,20 +76,20 @@ exports.get = function get(id, fn) { */ exports.create = function create(data, fn) { - log('Creating new license %j', data); + log('Creating new license %j', data) - var license = new License(data); - license.save(onsave); + var license = new License(data) + license.save(onsave) function onsave(err) { - if (err) return log('Found error %s', err), fn(err); + if (err) return log('Found error %s', err), fn(err) - log('Saved license %s', license.id); - fn(null, license); + log('Saved license %s', license.id) + fn(null, license) } - return this; -}; + return this +} /** * Update given `license` @@ -114,31 +103,31 @@ exports.create = function create(data, fn) { */ exports.update = function update(data, fn) { - log('Updating license %s with %j', data.id, data); + log('Updating license %s with %j', data.id, data) // get license - exports.get(data.id, onget); + exports.get(data.id, onget) function onget(err, license) { if (err) { - log('Found error %s', err.message); - return fn(err); - }; + log('Found error %s', err.message) + return fn(err) + } // update and save license document with data - license.set(data); - license.save(onupdate); + license.set(data) + license.save(onupdate) } function onupdate(err, license) { - if (err) return log('Found error %s', err), fn(err); + if (err) return log('Found error %s', err), fn(err) log('Saved license %s', license.id) - fn(null, license); + fn(null, license) } - return this; -}; + return this +} /** * Deletes license @@ -152,16 +141,16 @@ exports.update = function update(data, fn) { */ exports.remove = function remove(id, fn) { - log('Deleting license %s', id); + log('Deleting license %s', id) License .remove({_id: id}) .exec(function (err) { - if (err) return log('Found error %s', err), fn(err); + if (err) return log('Found error %s', err), fn(err) - log('Removed license %s', id); - fn(null, id); - }); + log('Removed license %s', id) + fn(null, id) + }) - return this; -}; \ No newline at end of file + return this +} diff --git a/lib/db-api/tag.js b/lib/server/db-api/tag.js similarity index 51% rename from lib/db-api/tag.js rename to lib/server/db-api/tag.js index 71b5c24..226fdbd 100644 --- a/lib/db-api/tag.js +++ b/lib/server/db-api/tag.js @@ -1,19 +1,6 @@ -/** - * Extend module's NODE_PATH - * HACK: temporary solution - */ - -require('node-path')(module); - -/** - * Module dependencies. - */ - -var mongoose = require('mongoose'); -var Tag = mongoose.model('Tag'); -var utils = require('lib/utils'); -var pluck = utils.pluck; -var log = require('debug')('civicstack:db-api:tag'); +var mongoose = require('mongoose') +var Tag = mongoose.model('Tag') +var log = require('debug')('civicstack:db-api:tag') /** * Get all tags @@ -25,24 +12,7 @@ var log = require('debug')('civicstack:db-api:tag'); * @api public */ -exports.all = function all(fn) { - log('Looking for all tags.') - - Tag - .find({ deletedAt: null }) - .select('id name') - .exec(function (err, tags) { - if (err) { - log('Found error %j', err); - return fn(err); - }; - - log('Delivering tags %j', pluck(tags, 'id')); - fn(null, tags); - }); - - return this; -}; +exports.all = () => Tag.find({ deletedAt: null }).select('id name').exec() /** * Get Tag form `id` string or `ObjectId` @@ -55,23 +25,23 @@ exports.all = function all(fn) { */ exports.get = function get(id, fn) { - log('Looking for tag %s', id); + log('Looking for tag %s', id) Tag .findById(id) .exec(function (err, tag) { if (err) { - log('Found error %s', err); - return fn(err); - }; + log('Found error %s', err) + return fn(err) + } if (!tag) { - log('Tag %s not found', id); - return fn(null); + log('Tag %s not found', id) + return fn(null) } - log('Delivering tag %s', tag.id); - fn(null, tag); - }); -}; + log('Delivering tag %s', tag.id) + fn(null, tag) + }) +} /** * Creates tag @@ -85,20 +55,20 @@ exports.get = function get(id, fn) { */ exports.create = function create(data, fn) { - log('Creating new tag %j', data); - - var tag = new Tag(data); - tag.save(onsave); + log('Creating new tag %j', data) + + var tag = new Tag(data) + tag.save(onsave) function onsave(err) { - if (err) return log('Found error %s', err), fn(err); + if (err) return log('Found error %s', err), fn(err) - log('Saved tag %s', tag.id); - fn(null, tag); + log('Saved tag %s', tag.id) + fn(null, tag) } - return this; -}; + return this +} /** * Update tag @@ -112,28 +82,28 @@ exports.create = function create(data, fn) { */ exports.update = function update(data, fn) { - log('Updating tag %s with %j', data.id, data); + log('Updating tag %s with %j', data.id, data) - exports.get(data.id, onget); + exports.get(data.id, onget) function onget(err, tag) { if (err) { - log('Found error %s', err.message); - return fn(err); - }; + log('Found error %s', err.message) + return fn(err) + } // update and save tag document with data - tag.set(data); - tag.save(onupdate); + tag.set(data) + tag.save(onupdate) } function onupdate(err, tag) { - if (!err) return log('Saved tag %s', tag.id), fn(null, tag); - return log('Found error %s', err), fn(err); + if (!err) return log('Saved tag %s', tag.id), fn(null, tag) + return log('Found error %s', err), fn(err) } - return this; -}; + return this +} /** * Deletes tag @@ -147,16 +117,16 @@ exports.update = function update(data, fn) { */ exports.remove = function remove(id, fn) { - log('Deleting tag %s', id); + log('Deleting tag %s', id) Tag .remove({_id: id}) .exec(function (err) { - if (err) return log('Found error %s', err), fn(err); + if (err) return log('Found error %s', err), fn(err) - log('Removed tag %s', id); - fn(null, id); - }); + log('Removed tag %s', id) + fn(null, id) + }) - return this; -}; \ No newline at end of file + return this +} diff --git a/lib/db-api/technology.js b/lib/server/db-api/technology.js similarity index 52% rename from lib/db-api/technology.js rename to lib/server/db-api/technology.js index 88ef46c..22dad0a 100644 --- a/lib/db-api/technology.js +++ b/lib/server/db-api/technology.js @@ -1,19 +1,6 @@ -/** - * Extend module's NODE_PATH - * HACK: temporary solution - */ - -require('node-path')(module); - -/** - * Module dependencies. - */ - -var mongoose = require('mongoose'); -var Technology = mongoose.model('Technology'); -var utils = require('lib/utils'); -var pluck = utils.pluck; -var log = require('debug')('civicstack:db-api:technology'); +var mongoose = require('mongoose') +var Technology = mongoose.model('Technology') +var log = require('debug')('civicstack:db-api:technology') /** * Get all technologies @@ -25,24 +12,7 @@ var log = require('debug')('civicstack:db-api:technology'); * @api public */ -exports.all = function all(fn) { - log('Looking for all technologies.') - - Technology - .find({ deletedAt: null }) - .select('id name') - .exec(function (err, technologies) { - if (err) { - log('Found error %j', err); - return fn(err); - }; - - log('Delivering technologies %j', pluck(technologies, 'id')); - fn(null, technologies); - }); - - return this; -}; +exports.all = () => Technology.find({ deletedAt: null }).select('id name').exec() /** * Get Technology form `id` string or `ObjectId` @@ -55,25 +25,25 @@ exports.all = function all(fn) { */ exports.get = function get(id, fn) { - var query = { _id: id }; + var query = { _id: id } - log('Looking for technology %s', id); + log('Looking for technology %s', id) Technology .findOne(query) .exec(function (err, technology) { if (err) { - log('Found error %s', err); - return fn(err); - }; + log('Found error %s', err) + return fn(err) + } if (!technology) { - log('Technology %s not found', id); - return fn(null); + log('Technology %s not found', id) + return fn(null) } - log('Delivering technology %s', technology.id); - fn(null, technology); - }); -}; + log('Delivering technology %s', technology.id) + fn(null, technology) + }) +} /** * Creates technology @@ -87,20 +57,20 @@ exports.get = function get(id, fn) { */ exports.create = function create(data, fn) { - log('Creating new technology %j', data); + log('Creating new technology %j', data) - var technology = new Technology(data); - technology.save(onsave); + var technology = new Technology(data) + technology.save(onsave) function onsave(err) { - if (err) return log('Found error %s', err), fn(err); + if (err) return log('Found error %s', err), fn(err) - log('Saved technology %s', technology.id); - fn(null, technology); + log('Saved technology %s', technology.id) + fn(null, technology) } - return this; -}; + return this +} /** * Update given `technology` @@ -114,31 +84,31 @@ exports.create = function create(data, fn) { */ exports.update = function update(data, fn) { - log('Updating technology %s with %j', data.id, data); + log('Updating technology %s with %j', data.id, data) // get technology - exports.get(data.id, onget); + exports.get(data.id, onget) function onget(err, technology) { if (err) { - log('Found error %s', err.message); - return fn(err); - }; + log('Found error %s', err.message) + return fn(err) + } // update and save technology document with data - technology.set(data); - technology.save(onupdate); + technology.set(data) + technology.save(onupdate) } function onupdate(err, technology) { - if (err) return log('Found error %s', err), fn(err); + if (err) return log('Found error %s', err), fn(err) log('Saved technology %s', technology.id) - fn(null, technology); + fn(null, technology) } - return this; -}; + return this +} /** * Deletes technology @@ -152,16 +122,16 @@ exports.update = function update(data, fn) { */ exports.remove = function remove(id, fn) { - log('Deleting technology %s', id); + log('Deleting technology %s', id) Technology .remove({_id: id}) .exec(function (err) { - if (err) return log('Found error %s', err), fn(err); + if (err) return log('Found error %s', err), fn(err) - log('Removed technology %s', id); - fn(null, id); - }); + log('Removed technology %s', id) + fn(null, id) + }) - return this; -}; + return this +} diff --git a/lib/server/index.js b/lib/server/index.js new file mode 100644 index 0000000..b708db1 --- /dev/null +++ b/lib/server/index.js @@ -0,0 +1,48 @@ +const path = require('path') +const express = require('express') +const cookieParser = require('cookie-parser') +const config = require('../config') +const clientConfig = require('../config/client') + +const app = express() + +// Initialize database models +require('./models')(app) +const db = require('./db-api') + +// Middlewares +const language = require('./middlewares/language') +const initialState = require('./middlewares/initial-state') + +// Set template engine +app.set('view engine', 'pug') +app.set('views', path.resolve(__dirname, 'templates')) + +app.use(cookieParser()) + +// TODO: Refactor +const webpack = require('webpack') +const webpackConfig = require('../../webpack.config.babel') +const compiler = webpack(webpackConfig) + +app.use(express.static('./public')) + +app.use(require('webpack-dev-middleware')(compiler, { + noInfo: true, + publicPath: webpackConfig.output.publicPath +})) + +app.use(require('webpack-hot-middleware')(compiler)) + +app.use('/api', require('./api/apps')) + +const clientConfigJson = JSON.stringify(clientConfig) + +app.get(['/', '/apps/:id'], language, initialState(db), (req, res) => { + res.render('home', { + initialState: JSON.stringify(req.store.getState()), + clientConfig: clientConfigJson + }) +}) + +app.listen(config.port) diff --git a/lib/server/middlewares/initial-state.js b/lib/server/middlewares/initial-state.js new file mode 100644 index 0000000..f9224ce --- /dev/null +++ b/lib/server/middlewares/initial-state.js @@ -0,0 +1,21 @@ +const store = require('../../app/store') + +module.exports = db => (req, res, next) => { + // TODO: All those db requests are cacheable + Promise.all([ + db.country.all(), + db.technology.all(), + db.tag.all() + ]).then(([countries, technologies, tags]) => { + req.store = store({ + meta: { + locale: req.lang, + countries: countries.map(({ id, name }) => ({ id, name: name[req.lang] })), + technologies: technologies.map(({ id, name }) => ({ id, name })), + tags: tags.map(({ id, name }) => ({ id, name: name[req.lang] })) + } + }) + + next() + }) +} diff --git a/lib/server/middlewares/language.js b/lib/server/middlewares/language.js new file mode 100644 index 0000000..a8f22fd --- /dev/null +++ b/lib/server/middlewares/language.js @@ -0,0 +1,44 @@ +// const config = require('lib/config') +const log = require('debug')('civicstack:locale') +const languages = ['en', 'es', 'fr'] + +const valid = lang => !!~languages.indexOf(lang) + +module.exports = function language (req, res, next) { + const user = req.user + // let lang = config('locale') + let lang = 'en' + + if (req.query.lang) { + // set + lang = valid(req.query.lang) ? req.query.lang : lang + log('Setting language %s', lang) + + if (user) { + log('User %s signed in, changing their language', user.id) + res.cookie('lang', lang) + user.lang = lang + user.save(err => err ? res.send(500) : res.redirect(req.path)) + } else { + log('No user signed in, setting cookie value to %s', lang) + return res.cookie('lang', lang).redirect(req.path) + } + } else { + // get + if (user) { + if (!user.lang) { + lang = req.cookies.lang + res.cookie('lang', lang) + user.lang = lang + return user.save(err => err ? res.send(500) : res.redirect(req.path)) + } + lang = user.lang + } else { + lang = req.cookies.lang || lang + } + log('Setting language to %s', lang) + res.cookie('lang', lang) + req.lang = lang + next() + } +} diff --git a/lib/models/application.js b/lib/server/models/application.js similarity index 73% rename from lib/models/application.js rename to lib/server/models/application.js index 28aadd4..0c8623c 100644 --- a/lib/models/application.js +++ b/lib/server/models/application.js @@ -1,26 +1,21 @@ -/** - * Module dependencies. - */ - -var i18nSchema = require('./i18n-schema'); -var log = require('debug')('civicstack:models:law'); -var mongoose = require('mongoose'); -var Schema = mongoose.Schema; -var ObjectId = Schema.ObjectId; -var t = require('t-component'); +var i18nSchema = require('./i18n-schema') +var mongoose = require('mongoose') +var Schema = mongoose.Schema +var ObjectId = Schema.ObjectId +var t = require('t-component') /** * Application Vote Schema */ function max300Characters(val) { - return val.length <= 300; + return val.length <= 300 } -var descriptionSchema = i18nSchema; +var descriptionSchema = i18nSchema Object.keys(descriptionSchema).forEach(function (key) { - descriptionSchema[key].validate = { validator: max300Characters, msg: t('app.description.exceeded') }; -}); + descriptionSchema[key].validate = { validator: max300Characters, msg: t('app.description.exceeded') } +}) var ApplicationSchema = new Schema({ name: { type: String, required: true } @@ -47,7 +42,7 @@ var ApplicationSchema = new Schema({ , uploadedBy: { type: ObjectId, ref: 'User', required: true } , upvotes: [ { type: ObjectId, ref: 'User' } ] , upvotesCount: { type: Number, default: 0 } -}); +}) /** * Make Schema `.toObject()` and @@ -55,8 +50,8 @@ var ApplicationSchema = new Schema({ * proper JSON API response */ -ApplicationSchema.set('toObject', { getters: true }); -ApplicationSchema.set('toJSON', { getters: true }); +ApplicationSchema.set('toObject', { getters: true }) +ApplicationSchema.set('toJSON', { getters: true }) /** * Get `tags` ids @@ -67,11 +62,11 @@ ApplicationSchema.set('toJSON', { getters: true }); */ ApplicationSchema.virtual('tagids').get(function() { - if (!this.tags.length) return; + if (!this.tags.length) return return this.tags.map(function(tag) { - return tag._id; - }); -}); + return tag._id + }) +}) /** * Get `technology` ids @@ -82,20 +77,20 @@ ApplicationSchema.virtual('tagids').get(function() { */ ApplicationSchema.virtual('technologyids').get(function() { - if (!this.technology.length) return; + if (!this.technology.length) return return this.technology.map(function(technology) { - return technology._id; - }); -}); + return technology._id + }) +}) /** * Index */ -ApplicationSchema.index({ name: 'text', 'description.en': 'text', 'description.fr': 'text', 'description.en': 'text' }); +ApplicationSchema.index({ name: 'text', 'description.en': 'text', 'description.fr': 'text', 'description.en': 'text' }) /** * Expose Mongoose model loaded */ -module.exports = mongoose.model('Application', ApplicationSchema); +module.exports = mongoose.model('Application', ApplicationSchema) diff --git a/lib/server/models/country.js b/lib/server/models/country.js new file mode 100644 index 0000000..da18f14 --- /dev/null +++ b/lib/server/models/country.js @@ -0,0 +1,26 @@ +var i18nSchema = require('./i18n-schema') +var mongoose = require('mongoose') +var Schema = mongoose.Schema + +/** + * Country Schema + */ + +var CountrySchema = new Schema({ + name: i18nSchema +}) + +/** + * Make Schema `.toObject()` and + * `.toJSON()` parse getters for + * proper JSON API response + */ + +CountrySchema.set('toObject', { getters: true }) +CountrySchema.set('toJSON', { getters: true }) + +/** + * Expose Mongoose model loaded + */ + +module.exports = mongoose.model('Country', CountrySchema) diff --git a/lib/server/models/i18n-schema.js b/lib/server/models/i18n-schema.js new file mode 100644 index 0000000..05974ad --- /dev/null +++ b/lib/server/models/i18n-schema.js @@ -0,0 +1,14 @@ +/** + * Module dependencies. + */ + +// TODO: Make it configurable +var languages = ['en', 'es'] + +var i18nSchema = {} + +languages.forEach(function (language) { + i18nSchema[language] = { type: String } +}) + +module.exports = i18nSchema diff --git a/lib/models/index.js b/lib/server/models/index.js similarity index 72% rename from lib/models/index.js rename to lib/server/models/index.js index 7a6c190..2aebd48 100755 --- a/lib/models/index.js +++ b/lib/server/models/index.js @@ -1,8 +1,9 @@ -/* - * Module dependencies - */ +const mongoose = require('mongoose'); + +mongoose.Promise = global.Promise; -var mongoose = require('mongoose'); +// TODO: Make it configurable +const mongoUrl = 'mongodb://localhost/civicstack2' /** * Expose models linker helper @@ -12,11 +13,11 @@ var mongoose = require('mongoose'); module.exports = function models (app) { - /* + /** * Connect to mongo */ - mongoose.connect(app.get('config').mongoUrl, { db: { safe: true }}); + mongoose.connect(mongoUrl, { db: { safe: true }}); /** * Register `User` model diff --git a/lib/models/license.js b/lib/server/models/license.js similarity index 100% rename from lib/models/license.js rename to lib/server/models/license.js diff --git a/lib/models/tag.js b/lib/server/models/tag.js similarity index 100% rename from lib/models/tag.js rename to lib/server/models/tag.js diff --git a/lib/models/technology.js b/lib/server/models/technology.js similarity index 100% rename from lib/models/technology.js rename to lib/server/models/technology.js diff --git a/lib/models/user.js b/lib/server/models/user.js similarity index 84% rename from lib/models/user.js rename to lib/server/models/user.js index 3d1fe2c..14d3a61 100644 --- a/lib/models/user.js +++ b/lib/server/models/user.js @@ -2,7 +2,7 @@ * Module dependencies. */ -var config = require('lib/config'); +// var config = require('lib/config'); var mongoose = require('mongoose'); var Schema = mongoose.Schema; var ObjectId = Schema.ObjectId; @@ -121,18 +121,19 @@ UserSchema.statics.findByProvider = function(profile, cb) { */ UserSchema.virtual('admin').get(function() { - var twadmins = config.admins.twitter || []; - var githubadmins = config.admins.github || []; - - if (this.profiles.twitter) { - var id = this.profiles.twitter.id; - return !!~twadmins.indexOf(id); - } - - if (this.profiles.github) { - var id = this.profiles.github._json.id.toString(); - return !!~githubadmins.indexOf(id); - } + // TODO: Enable this + // var twadmins = config.admins.twitter || []; + // var githubadmins = config.admins.github || []; + // + // if (this.profiles.twitter) { + // var id = this.profiles.twitter.id; + // return !!~twadmins.indexOf(id); + // } + // + // if (this.profiles.github) { + // var id = this.profiles.github._json.id.toString(); + // return !!~githubadmins.indexOf(id); + // } return false; }); diff --git a/lib/server/templates/home.pug b/lib/server/templates/home.pug new file mode 100644 index 0000000..b4c125e --- /dev/null +++ b/lib/server/templates/home.pug @@ -0,0 +1,17 @@ +doctype html +html + head + title Civic Stack + meta(name="viewport", content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no") + link(href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css", rel="stylesheet", media="screen") + link(href="https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css", rel="stylesheet", media="screen") + link(rel="stylesheet", href="/app.css") + link(rel="stylesheet", href="https://unpkg.com/react-select/dist/react-select.css") + body + #react-root=reactOutput + script(type="text/javascript"). + window.__INITIAL_STATE__ = !{initialState} + window.config = !{clientConfig} + + script(type="text/javascript", src="/dist/vendors.js") + script(type="text/javascript", src="/dist/home.js") diff --git a/lib/accepts/index.js b/lib/server/utils/accepts.js similarity index 100% rename from lib/accepts/index.js rename to lib/server/utils/accepts.js diff --git a/lib/utils/index.js b/lib/server/utils/index.js similarity index 97% rename from lib/utils/index.js rename to lib/server/utils/index.js index 8168282..cd421b2 100644 --- a/lib/utils/index.js +++ b/lib/server/utils/index.js @@ -161,3 +161,6 @@ exports.getEmbeddableYouTubeURL = function getEmbeddableYoutubeURL(u) { var params = qs.parse(uri.query); return params.v ? uri.protocol + '//www.youtube.com/embed/' + params.v : u; } + +exports.OpenGraph = require('./open-graph') +exports.accepts = require('./accepts') diff --git a/lib/server/utils/open-graph.js b/lib/server/utils/open-graph.js new file mode 100644 index 0000000..411c6a4 --- /dev/null +++ b/lib/server/utils/open-graph.js @@ -0,0 +1,37 @@ +var og = require('open-graph-scraper') +var Cache = require('node-cache') + +var OpenGraph = function () { + var cache = new Cache({ stdTTL: 3600 }) + + return { + get: function (url, cb) { + cache.get(url, function (err, value) { + if (err) { + return cb(err) + } + + // If URL exists in cache, return it + if (value) { + return cb(null, value) + } + + // Get OG data from remote site + return og({ url: url }, function (err, result) { + // In case of error, pass control to callback + if (err) { + return cb(err) + } + + // Save in cache + cache.set(url, result, function () { + // Return control to callback + cb(null, result) + }) + }) + }) + } + } +} + +module.exports = new OpenGraph() diff --git a/lib/setup/index.js b/lib/setup/index.js deleted file mode 100644 index d9040be..0000000 --- a/lib/setup/index.js +++ /dev/null @@ -1,187 +0,0 @@ -/** - * Module dependencies. - */ - -var express = require('express'); -var passport = require('passport'); -var session = require('express-session'); -var bodyParser = require('body-parser'); -var cookieParser = require('cookie-parser'); -var MongoStore = require('connect-mongo')(session); -var errorhandler = require('errorhandler'); -var compression = require('compression'); -var resolve = require('path').resolve; -var nowww = require('nowww') -var config = require('lib/config'); -var path = require('path'); -var join = path.join; -var has = Object.prototype.hasOwnProperty; -var log = require('debug')('civicivicstack:setup'); - - -/** - * Expose configuration helper - * - * @param {Express} app `Express` instance. - * @api public - */ - -module.exports = function configuration (app) { - - /** - * Load configuration settings - * for development setup - */ - - if (config('env') == 'development') { - - /** - * Add build middleware - */ - - app.use(require('lib/build').middleware); - } - - /** - * Load configuration settings - * for testing setup - */ - - if (config('env') == 'testing') { - - // Log config settigs load - log( 'testing settings' ); - - } - - /** - * Load configuration settings - * for production setup - */ - - if (config('env') == 'production') { - - // Log config settigs load - log( 'production settings' ); - - /** - * Set `native` express compression middleware - */ - - app.use( compression() ); - } - - /** - * Load configuration settings - * for common setup - */ - - /** - * Save config in app - */ - - app.set('config', config); - - /** - * Set application port - */ - - app.set('port', app.get('config').port || 3000); - - /** - * Set `public-assets` default path - */ - - app.use(express.static(resolve('public'))); - - /** - * Configure native `express` body parser - */ - - // parse application/x-www-form-urlencoded - app.use(bodyParser.urlencoded({extended: true})) - - // parse application/json - app.use(bodyParser.json()) - - // parse application/vnd.api+json as json - app.use(bodyParser.json({ type: 'application/vnd.api+json' })) - - /** - * Configure native `express` cookie parser - */ - - app.use( cookieParser('civicivicstack') ); - - /** - * Configure native `express` session middleware - */ - - app.use(session({ - resave: false, - saveUninitialized: true, - cookie: { - maxAge: 1000 * 60 * 60 * 24 * 7 - }, - secret: config('secret'), - store: new MongoStore({ - url: config('mongoUrl') - }) - })); - - /** - * Use `passport` setup & helpers middleware - */ - - app.use(passport.initialize()); - - /** - * Use `passport` sessions middleware - */ - - app.use(passport.session()); - - /** - * Set custom error handler - */ - - app.use(function(err, req, res, next) { - // log - log('Some odd error: %j', err); - // now let it go - next(); - }); - - /** - * Set native `express` error handler - */ - - app.use(errorhandler()); -} - -/** - * Some helpers - */ - -/** - * Merge `b` into `a`. - * - * @param {Object} a - * @param {Object} b - * @return {Object} a - * @api public - */ - -function merge (a, b){ - for (var key in b) { - if (has.call(b, key) && b[key] != null) { - if (!a) a = {}; - if ('object' === typeof b[key]) { - a[key] = merge(a[key], b[key]); - } else { - a[key] = b[key]; - } - } - } - return a; -}; diff --git a/lib/sidebar/component.json b/lib/sidebar/component.json deleted file mode 100644 index 7efc1b1..0000000 --- a/lib/sidebar/component.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "sidebar", - "dependencies": { - "ractivejs/ractive": "v0.6.1" - }, - "locals": [ - "language", - "render", - "request", - "user" - ], - "images": [ - ], - "templates": [ "template.jade" ], - "scripts": [ "sidebar.js" ], - "styles": [ "styles.styl" ], - "main": "sidebar.js" -} diff --git a/lib/sidebar/sidebar.js b/lib/sidebar/sidebar.js deleted file mode 100644 index cba4524..0000000 --- a/lib/sidebar/sidebar.js +++ /dev/null @@ -1,44 +0,0 @@ -var language = require('language'); -var render = require('render'); -var Ractive = require('ractive'); -var template = require('./template'); -var user = require('user'); - -var doClose = true; -document.body.addEventListener('click', function onWindowClick () { - if (doClose) sidebar.close(); - doClose = true; -}); - -var sidebar = module.exports = new Ractive({ - isolated: true, - template: render(template), - el: 'body', - append: true, - data: { - user: user - }, - onrender: function () { - this.set('lang', language()); - this.on('lang', function (ev, lang) { - ev.original.preventDefault(); - window.location = window.location.pathname + '?lang=' + lang; - }); - }, - stopPropagation: function stopPropagation (evt) { - doClose = false; - }, - open: function open () { - document.body.classList.add('sidebar-is-open'); - doClose = false; - return false; - }, - close: function close () { - document.body.classList.remove('sidebar-is-open'); - return false; - } -}); - -user.on('ready', function () { - sidebar.set('user', user); -}); diff --git a/lib/sidebar/styles.styl b/lib/sidebar/styles.styl deleted file mode 100644 index a720793..0000000 --- a/lib/sidebar/styles.styl +++ /dev/null @@ -1,81 +0,0 @@ -#sidebar - position fixed - top 0 - right 0 - width 220px - height 100% - height 100vh - z-index 2000 - pointer-events none - - .sidebar-is-open & - pointer-events auto - - &:after - opacity .4 - visibility visible - transition transform .3s, opacity linear .3s, visibility 0s - - nav - transform: translateZ(0) - - & > * - pointer-events auto - - &:after - content '' - position fixed - top 0 - right 0 - bottom 0 - left 0 - transform translateZ(0) - opacity 0 - visibility hidden - background #000 - transition transform .3s, opacity linear .3s, visibility 0s .3s - - nav - position absolute - top 0 - right 0 - bottom 0 - left 0 - z-index 5 - padding 40px - text-align center - color #656564 - overflow auto - background-color #fafafa - transition transform .3s - transform translate3d(100%, 0, 0) - - p - margin-bottom 5px - - .close - position absolute - top 0 - right 0 - z-index 10 - padding 20px - opacity .4 - color #656564 - cursor pointer - transition opacity .15s linear - - &:hover - opacity 1 - - .avatar - margin-bottom 10px - width 48px - height 48px - border 1px solid #333 - border-radius 100% - - .divider - margin 25px auto - width 30px - height 1px - background-color #ccc diff --git a/lib/sidebar/template.jade b/lib/sidebar/template.jade deleted file mode 100644 index 8bec7d1..0000000 --- a/lib/sidebar/template.jade +++ /dev/null @@ -1,31 +0,0 @@ -#sidebar - nav(on-click='stopPropagation()') - .close.fa.fa-times(on-click='close()') - | {{#if user.logged() }} - .user - img.avatar(src='{{ user.avatar }}' title='{{ user.fullName }}') - p {{ user.fullName }} - .divider - | {{/if}} - | {{#if (user.admin) }} - p: a(href='/admin', on-click='close()')= t('header.admin') - | {{/if}} - | {{#if user.logged() }} - p: a(href='/logout', on-click='close()')= t('header.logout') - | {{ else }} - p: a(href='/login', on-click='close()') - .fa.fa-sign-in - |   - = t('header.login') - | {{/if}} - .divider - p: a(href='/', on-click='close()') Home - p: a(href='/about', on-click='close()')= t('header.about') - .divider - p: a.lang(href='#', title=t('english'), on-click='lang:en')= t('english') - p: a.lang(href='#', title=t('spanish'), on-click='lang:es')= t('spanish') - p: a.lang(href='#', title=t('french'), on-click='lang:fr')= t('french') - .divider - p: a(href='https://github.com/DemocraciaEnRed/civicstack', target='_blank', on-click='close()') - .fa.fa-github - |  GitHub diff --git a/lib/stateful/component.json b/lib/stateful/component.json deleted file mode 100644 index b849c52..0000000 --- a/lib/stateful/component.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "name": "stateful", - "description": "Give states to a protoype", - "dependencies": { - "component/emitter": "1.1.3", - "kewah/mixin": "0.1.0", - "visionmedia/debug": "2.1.3" - }, - "scripts": [ "stateful.js" ], - "main": "stateful.js" -} diff --git a/lib/stateful/stateful.js b/lib/stateful/stateful.js deleted file mode 100644 index 4e96a20..0000000 --- a/lib/stateful/stateful.js +++ /dev/null @@ -1,72 +0,0 @@ -/** - * Module dependencies. - */ - -var Emitter = require('emitter'); -var mixin = require('mixin'); -var log = require('debug')('civicstack:stateful'); - -function Stateful(A) { - return mixin(A.prototype, Stateful.prototype); -} - -/** - * Add Emitter capabilities - */ - -Emitter(Stateful.prototype); - -/** - * Save or retrieve current instance - * state and emit to observers - * - * @param {String} state - * @param {String} message - * @return {Stateful|String} Instance of `Stateful` or current `state` - * @api public - */ - -Stateful.prototype.state = function(state, message) { - if (0 === arguments.length) { - return this.$_state; - } - - log('state is now %s', state); - this.$_state = state; - this.emit(state, message); - return this; -}; - - -/** - * Emit `ready` if collection has - * completed a cycle of request - * - * @param {Function} fn - * @return {Stateful} Instance of `Stateful` - * @api public - */ - -Stateful.prototype.ready = function(fn) { - var self = this; - - function done() { - if ('loaded' === self.state()) { - return fn(); - } - } - - if ('loaded' === this.state()) { - setTimeout(done, 0); - } else { - this.once('loaded', done); - } - - return this; -} - -/** - * Expose stateful - */ - -module.exports = Stateful; diff --git a/lib/styleguide/component.json b/lib/styleguide/component.json deleted file mode 100644 index 264a482..0000000 --- a/lib/styleguide/component.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "styleguide", - "styles": [ "styleguide.styl" ] -} diff --git a/lib/styleguide/styleguide.styl b/lib/styleguide/styleguide.styl deleted file mode 100644 index 0db2275..0000000 --- a/lib/styleguide/styleguide.styl +++ /dev/null @@ -1,96 +0,0 @@ -html, -body - height 100% - margin 0 - padding 0 - -body - background-color #dfdfdf - font-family "Lucida Grande", "Lucida Sans Unicode", "Lucida Sans", Geneva, Verdana, sans-serif - padding-top 80px - transition all ease .3s - -webkit-font-smoothing antialiased - -.no-gutter [class*="col-"] - padding 0 !important - -h1, -h2, -h3 - font-weight 100 - -a - color #333 - - &:focus - outline none - - &:hover - color #333 - -.btn - border-radius 1px - box-shadow none - border 0 - -.btn-primary - color #fafafa - border-color transparent - background-color #27aeaa - - &:hover, - &:active, - &:focus, - &.active - color #fafafa - background-color darken(#27aeaa, 10%) - - &:hover:active, - &:active:focus - color #fafafa - box-shadow inset 0 3px 5px rgba(0,0,0,.125) - background-color darken(#27aeaa, 15%) - -.btn-upvote - span - font-family: monospace - font-weight: 900 - - i:before - position: relative - top: .09em - font-size: 1.4em - line-height: 0.8 - -.app-logo - position relative - display block - width 100% - background-repeat no-repeat - background-size 70% - background-position center center - - h1 - position absolute - visibility hidden - opacity 0 - pointer-events none - - &:after - content '' - display block - padding-top 64% - -textarea - resize none - -.modal - .modal-header, - .modal-footer - background-color #f4f4f4 - - .modal-header - font-weight bold - -.site-content - padding-top: 30px diff --git a/lib/tag-api/index.js b/lib/tag-api/index.js deleted file mode 100644 index 4fc44d2..0000000 --- a/lib/tag-api/index.js +++ /dev/null @@ -1,86 +0,0 @@ -/** - * Module dependencies. - */ - -var api = require('lib/db-api'); -var accepts = require('lib/accepts'); -var express = require('express'); -var log = require('debug')('civicstack:tag'); -var utils = require('lib/utils'); -var admin = utils.admin; -var pluck = utils.pluck; -var expose = utils.expose; -var handleError = utils.handleError; - -var app = module.exports = express(); - -/** - * Limit request to json format only - */ - -app.use(accepts('application/json')); - -app.get('/tags', function (req, res) { - log('Request /tags'); - - api.tag.all(function(err, tags) { - if (err) return handleError(err, req, res); - - log('Serving tags %j', pluck(tags, "id")); - - var keys = 'id name'; - - res.json(tags.map(expose(keys))); - }); -}); - -app.get('/tags/:id', function (req, res) { - log('Request GET /tag/%s', req.params.id); - - api.tag.get(req.params.id, function (err, tag) { - if (err) return handleError(err, req, res); - if (!tag) return res.send(404); - - log('Serving tag %s', tag.id); - - var keys = 'id name'; - - res.json(expose(keys)(tag.toJSON())); - }); -}); - -app.post('/tags/create', admin, function (req, res) { - log('Request POST /tags/create %j', req.body); - - api.tag.create(req.body, function (err, tag) { - if (err) return handleError(err, req, res); - - log('Serving tag %s', tag.id); - - res.json(tag.toJSON()); - }); -}); - -app.post('/tags/:id', admin, function (req, res) { - log('Request POST /tag/%s', req.params.id); - - api.tag.update(req.body, function (err, tag) { - if (err) return handleError(err, req, res); - - log('Serving tag %s', tag.id); - - res.json(tag); - }); -}); - -app.delete('/tags/:id', admin, function (req, res) { - log('Request DEL /tags/%s %j', req.params.id, req.body); - - api.tag.remove(req.params.id, function (err, id) { - if (err) return handleError(err, req, res); - - log('Deleted tag %s', id.id); - - res.sendStatus(200); - }); -}); diff --git a/lib/tags/component.json b/lib/tags/component.json deleted file mode 100644 index 537ff8e..0000000 --- a/lib/tags/component.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "name": "tags", - "description": "Collection of tags", - "locals": [ - "collection", - "language" - ], - "scripts": [ - "tags.js" - ], - "main": "tags.js" -} diff --git a/lib/tags/tags.js b/lib/tags/tags.js deleted file mode 100644 index 8da0550..0000000 --- a/lib/tags/tags.js +++ /dev/null @@ -1,21 +0,0 @@ -/** - * Module dependencies. - */ - -var language = require('language'); -var lang = language(); -var Collection = require('collection'); - -var tags = module.exports = new Collection('/api/tags'); - -tags.on('loaded', order.bind(tags)); - -function order() { - this.items.sort(alphabetically); -} - -function alphabetically(a, b) { - if (a.name[lang] < b.name[lang]) return -1; - if (a.name[lang] > b.name[lang]) return 1; - return 0; -} diff --git a/lib/technologies/component.json b/lib/technologies/component.json deleted file mode 100644 index 63edb67..0000000 --- a/lib/technologies/component.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "name": "technologies", - "description": "Collection of technologies", - "locals": [ - "collection" - ], - "scripts": [ - "technologies.js" - ], - "main": "technologies.js" -} diff --git a/lib/technologies/technologies.js b/lib/technologies/technologies.js deleted file mode 100644 index 1560f2b..0000000 --- a/lib/technologies/technologies.js +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Module dependencies. - */ - -var Collection = require('collection'); - -var technologies = module.exports = new Collection('/api/technologies'); - -technologies.on('loaded', order.bind(technologies)); - -function order() { - this.items.sort(alphabetically); -} - -function alphabetically(a, b) { - if (a.name < b.name) return -1; - if (a.name > b.name) return 1; - return 0; -} diff --git a/lib/technology-api/index.js b/lib/technology-api/index.js deleted file mode 100644 index 4ac41da..0000000 --- a/lib/technology-api/index.js +++ /dev/null @@ -1,85 +0,0 @@ -/** - * Module dependencies. - */ - -var api = require('lib/db-api'); -var accepts = require('lib/accepts'); -var express = require('express'); -var log = require('debug')('civicstack:technology'); -var utils = require('lib/utils'); -var admin = utils.admin; -var pluck = utils.pluck; -var expose = utils.expose; -var handleError = utils.handleError; - -var app = module.exports = express(); - -/** - * Limit request to json format only - */ - -app.use(accepts('application/json')); - -app.get('/technologies', function (req, res) { - log('Request /technologies'); - - api.technology.all(function(err, technologies) { - if (err) return handleError(err, req, res); - - log('Serving technologies %j', pluck(technologies, "id")); - - var keys = 'id name'; - - res.json(technologies.map(expose(keys))); - }); -}); - -app.get('/technologies/:id', function (req, res) { - log('Request GET /technologies/%s', req.params.id); - - api.technology.get(req.params.id, function (err, technology) { - if (err) return handleError(err, req, res); - if (!technology) return res.send(404); - - log('Serving technology %s', technology.id); - - res.json(technology.toJSON()); - }); -}); - -app.post('/technologies/create', admin, function (req, res) { - log('Request POST /technologies/create %j', req.body); - - api.technology.create(req.body, function (err, technology) { - if (err) return handleError(err, req, res); - - log('Serving technology %s', technology.id); - - res.json(technology.toJSON()); - }); -}); - -app.post('/technologies/:id', admin, function (req, res) { - log('Request POST /technologies/%s %j', req.params.id, req.body); - - api.technology.update(req.body, function (err, technology) { - if (err) return handleError(err, req, res); - - log('Serving technology %s', technology.id); - - res.json(technology.toJSON()); - }); -}); - -app.delete('/technologies/:id', admin, function (req, res) { - log('Request DEL /technologies/%s %j', req.params.id, req.body); - - api.technology.remove(req.params.id, function (err, id) { - if (err) return handleError(err, req, res); - - log('Deleted technology %s', id.id); - - res.sendStatus(200); - }); -}); - diff --git a/lib/translations/component.json b/lib/translations/component.json deleted file mode 100644 index 07c8187..0000000 --- a/lib/translations/component.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "name": "translations", - "description": "Translations dictionary", - "scripts": [ "index.js" ], - "json": [ - "lib/en.json", - "lib/es.json", - "lib/fr.json" - ], - "main": "index.js" -} diff --git a/lib/translations/index.js b/lib/translations/index.js deleted file mode 100644 index 4250d17..0000000 --- a/lib/translations/index.js +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Module dependencies. - */ - -var en = require('./lib/en'); -var es = require('./lib/es'); -var fr = require('./lib/fr'); - -module.exports.help = function(t) { - // English - t.en = en; - - // Spanish - t.es = es; - - // French - t.fr = fr; -} diff --git a/lib/translations/lib/en.json b/lib/translations/lib/en.json deleted file mode 100644 index 0e7558a..0000000 --- a/lib/translations/lib/en.json +++ /dev/null @@ -1,109 +0,0 @@ -{ - "global.app-name": "Civic Stack", - "global.description": "Open source tools for social change.", - - "english": "English", - "spanish": "Spanish", - "french": "French", - - "Name": "Name", - "Save": "Save", - "Cancel": "Cancel", - "Upload app": "Upload app", - "No apps to show yet": "No apps to show yet", - "and": "y", - - "header.about": "About us", - "header.admin": "Admin", - "header.login": "Login", - "header.or": "or", - "header.logout": "Logout", - - "admin-sidebar.applications.title": "Applications", - "admin-sidebar.tags.title": "Tags", - "admin-sidebar.countries.title": "Countries", - "admin-sidebar.technologies.title": "Technologies", - "admin-sidebar.licenses.title": "Licenses", - "admin.list.new": "New", - "admin.list.search.placeholder": "Search", - "admin.tags.save.success": "Tag was saved successfully", - "admin.app.save.success": "App was saved successfully", - "admin.countries.save.success": "Country was saved successfully", - "admin.technologies.save.success": "Technology was saved successfully", - "admin.licenses.save.success": "License was saved successfully", - - "confirmation.title": "Delete", - "confirmation.ok": "Ok", - "confirmation.cancel": "Cancel", - "admin-licenses-form.confirmation.body": "Are you sure to delete the \"{name}\" license?", - "admin-apps-form.confirmation.body": "Are you sure to delete the \"{name}\" app?", - "admin-tags-form.confirmation.body": "Are you sure to delete the \"{name}\" tag?", - "admin-countries-form.confirmation.body": "Are you sure to delete the \"{name}\" country?", - "admin-technologies-form.confirmation.body": "Are you sure to delete the \"{name}\" technology?", - - "about.what-is-it.title": "What is Civic Stack?", - "about.what-is-it.text": "Civic Stack is the place to discover and share civic open source tools so you can adapt them to different scenarios.", - "about.goal.title": "What is our goal?", - "about.goal.1": "Provide easy access to civic digital tools for organizations, activists, governments so they can innovate in their daily work.", - "about.goal.2": "Promote the work of organizations that develop open source technology with civic purposes.", - "about.goal.3": "Encourage collaboration and reuse, rather \"reinventing the wheel\" each time.", - "about.what-information-is-available.title": "What information appears in Civic Stack?", - "about.what-information-is-available.text": "In Civic Stack you will find useful information about each tool: aims, scope, its origin, its GitHub repository, the developers.

Organizations can login and upload their own tools.

In Civic Stack, all the tools you find —and all the tools you will find— are open source.", - - "panel.heading.error": "Error", - "panel.body.error": "An error was produced, try again later", - "panel.heading.success": "Success", - - "app-form.header.cover": "Cover", - "app-form.header.content": "Content", - "app-form.header.publish": "Publish", - "app-form.cover.logo-title": "Logo", - "app-form.cover.logo-instructions": "Only online url links, make sure that’s a public route.
Size: at least 000 x 000 px.", - "app-form.cover.background-instructions": "Please choose a background color:", - "app-form.name.placeholder": "Name", - "app-form.button.continue": "Continue", - "app-form.button.cancel": "Cancel", - "app-form.button.done": "Done", - "app-form.button.edit": "Edit", - "app-form.new-app": "New app", - "app-form.modal.title": "Great!", - "app-form.modal.body": "Your app uploaded successfully and it'll show up soon!", - - "app.logo": "Logo", - "app.backgroundColor": "Background color", - "app.name": "App name", - "app.organization-name": "Organization name", - "app.description": "Description", - "app.description.placeholder": "Description (300 characters max)", - "app.video": "Video", - "app.video.placeholder": "YouTube URL of the cover video", - "app.country": "Country", - "app.website": "Website", - "app.twitter": "Twitter", - "app.license": "License", - "app.links": "Related links", - "app.contact": "Contact", - "app.contact.placeholder": "Contact email", - "app.technology": "Technology", - "app.github-repository": "Github repository", - "app.tags": "Tags", - "app.approved": "Approved", - "app.description.en": "English description", - "app.description.en.placeholder": "English description (300 characters max)", - "app.description.es.placeholder": "Spanish description (300 characters max)", - "app.description.fr.placeholder": "French description (300 characters max)", - "app.comments": "Comments", - "app.uploaded-by": "Uploaded by", - "app.description.exceeded": "Description is limited up to 300 characters", - - "filters.search": "Go", - "filters.clear": "Clear all", - "filters.country": "Filter by country", - "filters.technology": "Filter by technology", - "filters.tags": "Filter by tags", - - "sort.by.popular": "Popular", - "sort.by.newest": "Newest", - - "footer.powered-by": "Powered by" -} diff --git a/lib/translations/lib/es.json b/lib/translations/lib/es.json deleted file mode 100644 index d22c1e1..0000000 --- a/lib/translations/lib/es.json +++ /dev/null @@ -1,108 +0,0 @@ -{ - "global.app-name": "Civic Stack", - "global.description": "Herramientas de código abierto para acciones políticas y sociales.", - - "english": "Inglés", - "spanish": "Español", - "french": "Francés", - - "Name": "Nombre", - "Save": "Guardar", - "Cancel": "Cancelar", - "Upload app": "Subir aplicación", - "No apps to show yet": "Aún no hay apps para buscar", - "and": "y", - - "header.about": "Nosotros", - "header.admin": "Administrador", - "header.login": "Entrar", - "header.or": "o", - "header.logout": "Salir", - - "admin-sidebar.applications.title": "Aplicaciones", - "admin-sidebar.tags.title": "Etiquetas", - "admin-sidebar.countries.title": "Países", - "admin-sidebar.technologies.title": "Tecnologías", - "admin-sidebar.licenses.title": "Licencias", - "admin.list.new": "Nuevo", - "admin.list.search.placeholder": "Buscar", - "admin.tags.save.success": "La etiqueta se ha guardado con éxito", - "admin.apps.save.success": "La aplicación se ha guardado con éxito", - "admin.countries.save.success": "El país se ha guardado con éxito", - "admin.technologies.save.success": "La tecnología se ha guardado con éxito", - "admin.licenses.save.success": "La licencia se ha guardado con éxito", - - "confirmation.title": "Eliminar", - "confirmation.ok": "Aceptar", - "confirmation.cancel": "Cancelar", - "admin-licenses-form.confirmation.body": "¿Estás seguro de eliminar la licencia \"{name}\"?", - "admin-apps-form.confirmation.body": "¿Estás seguro de eliminar la app \"{name}\"?", - "admin-tags-form.confirmation.body": "¿Estás seguro de eliminar la etiqueta \"{name}\"?", - "admin-countries-form.confirmation.body": "¿Estás seguro de eliminar el país \"{name}\"?", - "admin-technologies-form.confirmation.body": "¿Estás seguro de eliminar la tecnología \"{name}\"?", - - "about.what-is-it.title": "¿Qué es Civic Stack?", - "about.what-is-it.text": "Civic Stack es donde descubrimos y compartimos herramientas cívicas de código abierto para poder adaptarlas y usarlas frente a diferentes escenarios públicos.", - "about.goal.title": "¿Cuál es nuestro objetivo?", - "about.goal.1": "Dar fácil acceso a herramientas cívicas digitales para que organizaciones, activistas, gobiernos puedan innovar en sus tareas.", - "about.goal.2": "Difundir el trabajo de organizaciones que desarrollan tecnología de código abierto con fines cívicos.", - "about.goal.3": "Promover la colaboración y reutilización de aplicaciones, y así evitar la lógica de \"reinventar la rueda\" cada vez. :)", - "about.what-information-is-available.title": "¿Qué información aparece en Civic Stack?", - "about.what-information-is-available.text": "En Civic Stack vas a encontrar información útil sobre cada herramienta (app o web): su objetivo, su alcance, su origen, su repositorio de GitHub, el contacto con sus creadores/as.

Las organizaciones pueden registrarse y cargar sus propias herramientas.

En Civic Stack, todas las aplicaciones que encuentres, y que vas a encontrar siempre, son de código abierto!", - - "panel.heading.error": "Error", - "panel.body.error": "Se ha producido un error, intente de nuevo más tarde", - "panel.heading.success": "Éxito", - - "app-form.header.cover": "Portada", - "app-form.header.content": "Contenido", - "app-form.header.publish": "Publicación", - "app-form.cover.logo-title": "Logo", - "app-form.cover.logo-instructions": "Solamente los enlaces URL en línea, asegúrese de que es una URL pública
Tamaño: Al menos 000 x 000 px.", - "app-form.cover.background-instructions": "Por favor elige un color de fondo:", - "app-form.name.placeholder": "Título", - "app-form.button.continue": "Continuar", - "app-form.button.cancel": "Cancelar", - "app-form.button.done": "Listo", - "app-form.button.edit": "Editar", - "app-form.new-app": "Nueva aplicación", - "app-form.modal.title": "¡Genial!", - "app-form.modal.body": "Tu aplicación fue recibida exitosamente y ¡pronto la mostraremos!", - - "app.logo": "Logo", - "app.backgroundColor": "Color de fondo", - "app.name": "Nombre de la app", - "app.organization-name": "Nombre de la organización", - "app.description": "Descripción", - "app.description.placeholder": "Descripción (máx. 300 caracteres)", - "app.video": "Vídeo", - "app.video.placeholder": "URL de YouTube del video de cubierta", - "app.country": "País", - "app.website": "Sitio web", - "app.twitter": "Twitter", - "app.license": "Licencia", - "app.links": "Enlaces relacionados", - "app.contact": "Contacto", - "app.contact.placeholder": "Email de contacto", - "app.technology": "Tecnología", - "app.github-repository": "Repositorio de Github", - "app.tags": "Etiquetas", - "app.approved": "Aprobada", - "app.description.en.placeholder": "Descripción en inglés (máx. 300 caracteres)", - "app.description.es.placeholder": "Descripción en español (máx 300 caracteres)", - "app.description.fr.placeholder": "Descripción en francés (máx 300 caracteres)", - "app.comments": "Comentarios", - "app.uploaded-by": "Subido por", - "app.description.exceeded": "La descripción está limitada a 300 caracteres como máximo", - - "filters.search": "Buscar", - "filters.clear": "Limpiar filtros", - "filters.country": "Filtrar por país", - "filters.technology": "Filtrar por tecnología", - "filters.tags": "Filtrar por etiquetas", - - "sort.by.popular": "Populares", - "sort.by.newest": "Nuevas", - - "footer.powered-by": "Realizado por" -} diff --git a/lib/translations/lib/fr.json b/lib/translations/lib/fr.json deleted file mode 100644 index 2d30334..0000000 --- a/lib/translations/lib/fr.json +++ /dev/null @@ -1,108 +0,0 @@ -{ - "global.app-name": "Civic Stack", - "global.description": "Open source tools for social change.", - - "english": "Anglais", - "spanish": "Espagnol", - "french": "Français", - - "Name": "Nom", - "Save": "Sauvegarder", - "Cancel": "Annuler", - "Upload app": "Proposer une application", - "No apps to show yet": "Aucune application disponible pour le moment", - "and": "et", - - "header.about": "A propos de nous", - "header.admin": "Administrateur", - "header.login": "Connexion", - "header.or": "ou", - "header.logout": "Déconnexion", - - "admin-sidebar.applications.title": "Applications", - "admin-sidebar.tags.title": "Tags", - "admin-sidebar.countries.title": "Pays", - "admin-sidebar.technologies.title": "Technologies", - "admin-sidebar.licenses.title": "Licenses", - "admin.list.new": "Nouveau", - "admin.list.search.placeholder": "Rechercher", - "admin.tags.save.success": "Le tag a été sauvegardée avec succès", - "admin.app.save.success": "L'application a été sauvegardée avec succès", - "admin.countries.save.success": "Le pays été sauvegardé avec succès", - "admin.technologies.save.success": "La technologie été sauvegardée avec succès", - "admin.licenses.save.success": "La License été sauvegardée avec succès", - - "confirmation.title": "Supprimer", - "confirmation.ok": "Ok", - "confirmation.cancel": "Annuler", - "admin-licenses-form.confirmation.body": "Êtes vous sûr de vouloir supprimer la license \"{nom}\"?", - "admin-apps-form.confirmation.body": "Êtes vous sûr de vouloir supprimer l'application \"{nom}\"?", - "admin-tags-form.confirmation.body": "Êtes vous sûr de vouloir supprimer le tag \"{nom}\"?", - "admin-countries-form.confirmation.body": "Êtes vous sûr de vouloir supprimer le pays \"{nom}\"?", - "admin-technologies-form.confirmation.body": "Êtes vous sûr de vouloir supprimer la technologie \"{nom}\"?", - - "about.what-is-it.title": "Civic stack: qu'est ce que c'est?", - "about.what-is-it.text": "Civic Stack est l'endroit idéal pour se réunir et partager des outils civiques de différents pays et organisations afin de vous permettre de les adapter et utiliser là où vous vivez.", - "about.goal.title": "Quel est notre objectif?", - "about.goal.1": "Notre objectif est de fournir un accès simple aux outils numériques civiques pour les organisations militantes afin d'encourager la participation citoyenne et de renforcer leur processus organisationnel et décisionnel.", - "about.goal.2": "Dans le même temps, nous souhaitons que les organisatons qui se développent sur une base technologique inspirée d'autres travaux, collaborent avec ces derniers plutôt que de \"réinventer la roue\".", - "about.what-information-is-available.title": "Quelles informations apparaissent sur Civic Stack?", - "about.what-information-is-available.text": "Sur Civic Stack, vous trouverez des informations utiles sur chaque outil (application ou site internet): objectifs, portée, origines, code Github, créateurs.

Les organisations peuvent s'identifier et proposer leurs outils.

Sur Civic Stack, tous les outils que vous trouvez - et tout ceux que vous trouverez dans le futur - sont open source.", - - "panel.heading.error": "Erreur", - "panel.body.error": "Une erreur est survenue, veuillez réessayer plus tard", - "panel.heading.success": "Succès", - - "app-form.header.cover": "Couverture", - "app-form.header.content": "Contenu", - "app-form.header.publish": "Publier", - "app-form.cover.logo-title": "Logo", - "app-form.cover.logo-instructions": "Seulement les liens internet url, faire bien attention a ce qu'il s'agisse d'une url publique
Taille: minimum 000 x 000 px.", - "app-form.cover.background-instructions": "Veuillez choisir une couleur de fond", - "app-form.name.placeholder": "Nom", - "app-form.button.continue": "Continuer", - "app-form.button.cancel": "Annuler", - "app-form.button.done": "OK", - "app-form.button.edit": "Editer", - "app-form.new-app": "Nouvelle application", - "app-form.modal.title": "Super!", - "app-form.modal.body": "Votre proposition d'application a été réalisée avec succès, et elle apparaîtra très bientôt!", - - "app.logo": "Logo", - "app.backgroundColor": "Couleur de fond", - "app.name": "Nom de l'application", - "app.organization-name": "Nom de l'organisation", - "app.description": "Description", - "app.description.placeholder": "Description (300 caractères max)", - "app.video": "Vidéo", - "app.video.placeholder": "YouTube URL de la couverture vidéo", - "app.country": "Pays", - "app.website": "Site Internet", - "app.twitter": "Twitter", - "app.license": "License", - "app.links": "Liens connexes", - "app.contact": "Contact", - "app.contact.placeholder": "Email de contact", - "app.technology": "Technologie", - "app.github-repository": "Repository Github", - "app.tags": "Tags", - "app.approved": "Approuvé", - "app.description.fr": "Description en français", - "app.description.en.placeholder": "Description en anglais (max 300 caractères)", - "app.description.es.placeholder": "Description en espagnol (max 300 caractères)", - "app.description.fr.placeholder": "Description en français (max 300 caractères)", - "app.comments": "Commentaires", - "app.uploaded-by": "Proposé par", - "app.description.exceeded": "La description est limitée à 300 caractères", - - "filters.search": "Aller", - "filters.clear": "Supprimer tout", - "filters.country": "Filtrer par pays", - "filters.technology": "Filtrer par technologie", - "filters.tags": "Filtrer par tag", - - "sort.by.popular": "Popular", - "sort.by.newest": "Newest", - - "footer.powered-by": "Réalisé par" -} diff --git a/lib/user-model/component.json b/lib/user-model/component.json deleted file mode 100644 index 09246a1..0000000 --- a/lib/user-model/component.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "name": "user-model", - "description": "User model component.", - "dependencies": { - "component/emitter": "1.2.0", - "visionmedia/superagent": "0.18.0" - }, - "scripts": [ "model.js" ], - "main": "model.js", - "license": "MIT" -} diff --git a/lib/user-model/model.js b/lib/user-model/model.js deleted file mode 100644 index e6572de..0000000 --- a/lib/user-model/model.js +++ /dev/null @@ -1,136 +0,0 @@ -/** - * Module dependencies. - */ - -var request = require('superagent'); -var Emitter = require('emitter'); - -/** - * Expose user model - */ - -module.exports = User; - -/** - * User - * - * @param {String} path user's load path - * @return {User} `User` instance - * @api public - */ - -function User (path) { - if (!(this instanceof User)) { - return new User(path); - }; - - this.$_path = path; - this.$_ready = "unloaded"; -} - -/** - * Inherit from `Emitter` - */ - -Emitter(User.prototype); - -/** - * Loads user from path - * - * @param {String} path user's load path - * @return {User} `User` instance. - * @api public - */ - -User.prototype.load = function(path) { - var _this = this; - this.$_path = path || this.$_path; - this.$_ready = "loading"; - - request - .get('/api/users/'.concat(this.$_path)) - .set('Accept', 'application/json') - .on('error', _handleRequestError.bind(this)) - .end(function(res) { - var u = res.body; - - if (!res.ok) { - return _handleRequestError.bind(_this)(res.error); - }; - - if (!(u.id || u._id)) { - return _handleRequestError.bind(_this)('User not found'); - }; - - for (var prop in u) { - if (u.hasOwnProperty(prop)) { - _this[prop] = u[prop] - } - } - _this.$_ready = "loaded"; - _this.emit('ready'); - }); - - return this; -} - -/** - * Call `fn` once User is - * ready from loading - * - * @param {Function} fn callback fired on ready - * @return {User} `User` instance - * @api public - */ - -User.prototype.ready = function(fn) { - var _this = this; - - function done() { - if ("loaded" === _this.state()) { - return fn(); - } - } - - if ("loaded" === this.state()) { - setTimeout(done, 0); - } else { - this.once("ready", done); - } - - return this; -} - -/** - * Get $_ready state - * - * @return {String} - * @api public - */ - -User.prototype.state = function() { - return this.$_ready; -} - -/** - * Get user logged in state - * - * @return {Boolean} - * @api public - */ - -User.prototype.logged = function() { - return !!this.id; -} - -/** - * Handle error from requests - * - * @param {Object} err from request - * @api private - */ - -function _handleRequestError (err) { - this.$_ready = "unloaded"; - this.emit('error', err); -} diff --git a/lib/user/component.json b/lib/user/component.json deleted file mode 100644 index 91dedb2..0000000 --- a/lib/user/component.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "name": "user", - "description": "User component.", - "dependencies": { - "visionmedia/page.js": "1.6.1" - }, - "local": ["user-model"], - "scripts": [ "user.js" ], - "main": "user.js", - "license": "MIT" -} \ No newline at end of file diff --git a/lib/user/index.js b/lib/user/index.js deleted file mode 100644 index 33f844a..0000000 --- a/lib/user/index.js +++ /dev/null @@ -1,15 +0,0 @@ -/** - * Module dependencies. - */ - -var express = require('express'); -var mongoose = require('mongoose'); -var app = module.exports = express(); -var expose = require('lib/utils').expose; -var disqus = require('lib/disqus'); - -app.get("/users/me", disqus.login, function(req, res, next) { - return res.json(req.isAuthenticated() - ? expose('id firstName lastName fullName avatar admin disqus')(req.user) - : {}); -}); diff --git a/lib/user/user.js b/lib/user/user.js deleted file mode 100644 index 3f38d28..0000000 --- a/lib/user/user.js +++ /dev/null @@ -1,67 +0,0 @@ -/** - * Module dependencies. - */ - -var page = require('page'); -var User = require('user-model'); - -/** - * Instantiate and expose user - */ -var user = module.exports = new User(); - -user.load("me"); - -user.required = function(ctx, next) { - if ("unloaded" === user.state()) { - setTimeout(loggedout, 0); - } else if ("loading" === user.state()) { - user.once('error', loggedout); - } - user.ready(function() { - user.off('error', loggedout); - next(); - }); -}; - -user.optional = function(ctx, next) { - ctx.user = user; - - if (user.logged()) return next(); - user.load("me"); - user.once("error", onerror); - user.ready(onready); - - function onerror(err) { - logout(); - next(); - } - - function onready() { - user.off('error', onerror); - next(); - } -}; - -user.isAdmin = function(ctx, next) { - ctx.user = user; - - if (!user.logged()) user.load('me'); - user.once('error', loggedout); - user.ready(onready); - - function onready() { - user.off('error', loggedout); - if (user.admin) return next(); - loggedout(); - } -}; - -function loggedout () { - console.log('user logged out'); - page('/') -} - -function logout() { - if (user.logged()) user.unload(); -} diff --git a/package.json b/package.json index f8eeaf8..a3ea878 100644 --- a/package.json +++ b/package.json @@ -1,88 +1,99 @@ { - "name": "civic-stack", - "version": "1.1.0", + "name": "civicstack", + "version": "2.0.0", "description": "Stack of online civic webapps", - "main": "index.js", + "main": "lib/server", "repository": { "type": "git", - "url": "git://github.com/democracyos/civic-stack.git" + "url": "git://github.com/civicstack/civicstack.git" }, "keywords": [ "nodejs", - "express", - "mongoose", - "passport", - "component", - "stylus", - "jade" + "civic", + "open-source", + "government", + "applications", + "producthunt" ], "author": "slifszyc", + "contributors": [ + "José Fresco ", + "Matías Lescano " + ], "license": "MIT", "bugs": { - "url": "https://github.com/democracyos/civic-stack/issues" + "url": "https://github.com/civicstack/civicstack/issues" }, "engines": { - "node": "4.5.0", - "npm": "2.7.6" + "node": ">= 6.9.1", + "npm": ">= 4.0.2" }, "dependencies": { - "autoprefixer-stylus": "^0.9.4", - "batch": "0.5.2", - "bluebird": "^3.4.0", - "body-parser": "1.12.0", - "builder-jade": "1.0.1", - "commander": "2.5.0", - "component": "1.0.0", - "component-builder": "1.2.0", - "component-resolver": "1.2.7", - "compression": "1.2.1", - "connect-mongo": "0.8.2", - "cookie-parser": "1.3.4", - "crypto": "0.0.3", - "debug": "2.1.3", - "disqus-sso-express": "^0.1.0", - "errorhandler": "1.3.0", - "express": "4.12.0", - "express-session": "v1.9.2", - "jade": "1.7.0 ", - "merge-util": "0.1.0", - "mkdirp": "0.5.0", - "mongodb": "^2.2.9", - "mongoose": "3.8.24", - "mongoose-type-url": "1.0.0", - "mongoose-validator": "1.0.3", - "node-cache": "^3.2.1", - "node-path": "0.0.3", - "nowww": "1.1.3", - "open-graph-scraper": "^2.1.0", - "passport": "0.2.0", - "passport-github": "0.1.5", - "passport-twitter": "1.0.2", - "ready": "0.1.1", - "serve-favicon": "2.1.7", - "string-to-js": "0.0.1", - "stylus": "0.49.3", + "babel-cli": "6.22.2", + "babel-core": "~6.23.1", + "babel-loader": "6.2.10", + "babel-plugin-react-transform": "^2.0.2", + "babel-plugin-transform-runtime": "6.22.0", + "babel-polyfill": "6.22.0", + "babel-preset-es2015": "6.22.0", + "babel-preset-react": "6.22.0", + "babel-preset-stage-0": "6.22.0", + "commander": "2.9.0", + "cookie-parser": "1.4.3", + "debug": "2.6.0", + "democracyos-config": "^1.1.0", + "express": "4.14.0", + "extract-text-webpack-plugin": "^1.0.1", + "fuse.js": "^2.6.1", + "glamor": "2.20.22", + "isomorphic-fetch": "^2.2.1", + "lodash.debounce": "^4.0.8", + "lodash.orderby": "^4.6.0", + "lodash.some": "^4.6.0", + "merge-util": "0.3.1", + "mkdirp": "~0.5.1", + "mongodb": "2.2.22", + "mongoose": "4.7.8", + "mongoose-type-url": "^1.0.0", + "mongoose-validator": "^1.2.5", + "node-cache": "4.1.1", + "open-graph-scraper": "2.4.2", + "passport": "^0.3.2", + "passport-github": "1.1.0", + "passport-twitter": "1.0.4", + "pug": "^2.0.0-beta6", + "react": "15.4.2", + "react-dom": "15.4.2", + "react-redux": "5.0.2", + "react-router-dom": "~4.0.0-beta.6", + "react-select": "^1.0.0-rc.2", + "redux": "^3.6.0", + "redux-logger": "^2.7.4", + "redux-thunk": "2.2.0", "t-component": "1.0.0", - "type-component": "0.0.1" - }, - "scripts": { - "postinstall": "node ./bin/civicstack-install --config && node ./bin/civicstack-config && node ./bin/civicstack-build" + "type-component": "0.0.1", + "webpack": "1.14.0" }, "devDependencies": { - "mongodb-migrations": "^0.5.2", - "xo": "^0.16.0" + "babel-eslint": "^7.1.1", + "eslint": "3.14.0", + "eslint-config-standard": "^6.0.0", + "eslint-config-standard-react": "^4.0.0", + "eslint-plugin-promise": "^3.3.0", + "eslint-plugin-react": "6.9.0", + "eslint-plugin-standard": "^2.0.0", + "mongodb-migrations": "0.8.5", + "react-transform-hmr": "^1.0.4", + "webpack-dev-middleware": "1.9.0", + "webpack-hot-middleware": "2.15.0" }, - "xo": { - "space": 2, - "env": [ - "node", - "es6", - "browser" - ], - "rules": { - "import/no-extraneous-dependencies": 0, - "curly": ["error", "multi-line"], - "space-before-function-paren": ["error", "always"] - } + "browser": { + "lib/config": "lib/config/config" + }, + "scripts": { + "start": "DEBUG=civicstack* node .", + "debug": "DEBUG=civicstack* node debug ./lib/server", + "build": "webpack -p --colors --config webpack.config.babel.js", + "lint": "eslint ." } } diff --git a/public/app.css b/public/app.css new file mode 100644 index 0000000..31ceb61 --- /dev/null +++ b/public/app.css @@ -0,0 +1,1064 @@ + +.overlay { + position: absolute; + top: 0; + left: 0; + opacity: 1; + width: 100%; + height: 100%; + background: rgba(0,0,0,.75); + -webkit-transition: opacity 300ms; + -moz-transition: opacity 300ms; + transition: opacity 300ms; + z-index: 500; +} + +.overlay.hide { + pointer-events: none; + opacity: 0; +} + + +#dialog { + position: fixed; + left: 50%; + top: 150px; + max-width: 600px; + min-width: 250px; + border: 1px solid #eee; + background: white; + z-index: 1000; +} + +#dialog .content { + padding: 15px 20px; +} + +#dialog h1 { + margin: 0 0 5px 0; + font-size: 16px; + font-weight: normal; +} + +#dialog p { + margin: 0; + padding: 0; + font-size: .9em; +} + +/* close */ + +#dialog .close { + position: absolute; + top: 3px; + right: 10px; + text-decoration: none; + color: #888; + font-size: 16px; + font-weight: bold; + display: none; +} + +#dialog .close em { + display: none; +} + +#dialog.closable .close { + display: block; +} + +#dialog .close:hover { + color: black; +} + +#dialog .close:active { + margin-top: 1px; +} + +/* slide */ + +#dialog.slide { + -webkit-transition: opacity 300ms, top 300ms; + -moz-transition: opacity 300ms, top 300ms; +} + +#dialog.slide.hide { + opacity: 0; + top: -500px; +} + +/* fade */ + +#dialog.fade { + -webkit-transition: opacity 300ms; + -moz-transition: opacity 300ms; +} + +#dialog.fade.hide { + opacity: 0; +} + +/* scale */ + +#dialog.scale { + -webkit-transition: -webkit-transform 300ms; + -moz-transition: -moz-transform 300ms; + -webkit-transform: scale(1); + -moz-transform: scale(1); +} + +#dialog.scale.hide { + -webkit-transform: scale(0); + -moz-transform: scale(0); +} + +.confirmation-actions { + border-top: 1px solid #eee; + padding: 5px; + text-align: right; + background: #fafafa; + box-shadow: inset 0 1px 0 white; +} + + +.color-picker canvas { + border: 1px solid #888; +} + +.color-picker canvas.main:hover { + cursor: crosshair; +} + +.color-picker canvas.spectrum:hover { + cursor: row-resize; +} + +.color-picker canvas:active { + cursor: none; +} + + + +/** + * Logo. + * + * *.logo[data-logo][data-color] + * * + */ + +.logo { + margin: 0; /* reset */ + position: relative; + display: inline-block; + vertical-align: middle; /* not baseline, so we align up _and_ down */ + height: 1em; /* keep height constant regardless of innards */ +} + +.logo > * { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + text-indent: -9999px; /* image replacement */ + direction: ltr; /* make text-indent play nice */ + background-repeat: no-repeat; + background-position: center center; + background-size: contain; +} + +.about h1, +.about h2 { + margin-top: 1.6em; + margin-bottom: 0.5em; + font-weight: 900; +} +.about h1 { + font-size: 2.3em; +} +.about h2 { + font-size: 2em; +} + + +ul.admin-menu a.selected { + background-color: #eee; +} + + +.overlay { + background: -webkit-radial-gradient(center ellipse, rgba(0,0,0,0.25) 0%, rgba(0,0,0,0.55) 100%); + background: radial-gradient(ellipse at center, rgba(0,0,0,0.25) 0%, rgba(0,0,0,0.55) 100%); + z-index: 9999998; +} +#dialog { + position: fixed; + display: block; + max-width: 500px; + min-width: 250px; + left: 50%; + top: 150px; + right: initial; + bottom: initial; + background: #fff; + box-shadow: 2px 2px 5px rgba(0,0,0,0.4); + border: 0px; + overflow: initial; + z-index: 9999999; +} +#dialog p { + font-size: 1.1em; +} +#dialog .content { + padding: 0px 20px 10px 20px; + margin-top: 30px; +} +#dialog .content .title { + position: absolute; + left: 0px; + right: 0px; + top: 0px; + min-height: 20px; + padding: 5px 20px; + background: #d95e59; + color: #fff; + text-transform: uppercase; + font-weight: 600; +} +#dialog .content .body { + padding: 20px 0px; +} +#dialog .content .close { + line-height: 10px; + top: 10px; + right: 10px; + font-weight: 600; + color: #000; + opacity: 0.5; +} +#dialog .confirmation-actions { + padding: 5px 10px; +} +#dialog .confirmation-actions button { + border: none; + color: #fff; + background: #aaa; + padding: 5px 10px; + letter-spacing: 0.09em; + text-transform: uppercase; + font-size: 0.7em; + outline: none; + margin-left: 5px; +} +#dialog .confirmation-actions button.cancel { + outline: none; +} +#dialog .confirmation-actions button.cancel:focus, +#dialog .confirmation-actions button.cancel:hover { + background: #777; +} +#dialog .confirmation-actions button.main, +#dialog .confirmation-actions button.ok { + background: #d95e59; +} +#dialog .confirmation-actions button.main:focus, +#dialog .confirmation-actions button.ok:focus, +#dialog .confirmation-actions button.main:hover, +#dialog .confirmation-actions button.ok:hover { + background: #952722; +} + + +.related-links .related-link { + margin-bottom: 20px; +} +.related-links .related-link input { + width: 100%; +} +.related-links .related-links-commands { + margin-top: 20px; +} + + +.admin-content .error { + background-color: #f2dede; + border: 1px solid #d9534f; + color: #d9534f; + outline: none; +} +.admin-content .related-links .related-link { + margin-bottom: 20px; +} +.admin-content .related-links .related-link input { + width: 100%; +} +.admin-content .related-links .related-links-commands { + margin-top: 20px; +} + + +#admin-container .list-group-item { + overflow: hidden; + width: 100%; +} +#admin-container .list-group-item:hover { + background-color: #f5f5f5; +} +#admin-container .list-group-item .name { + height: 32px; + line-height: 32px; +} +#admin-container .list-group-item .btn-action { + margin-left: 6px; +} +#admin-container .list-group-item .btn-action img { + width: 32px; +} +#admin-container .panel-heading h4 { + font-weight: bold; +} + + +.app-detail { + max-width: 970px; +} +.app-detail .app-header { + position: relative; + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + -ms-flex-direction: row; + flex-direction: row; + -ms-flex-wrap: nowrap; + flex-wrap: nowrap; + padding: 25px; + background-color: #333; +} +.app-detail .app-header > * { + max-width: 100%; + width: 100%; +} +.app-detail .app-logo { + display: none; + max-width: 220px; +} +@media (min-width: 540px) { + .app-detail .app-logo { + display: block; + } + .app-detail .app-details { + padding-left: 0; + } +} +.app-detail .app-details { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -ms-flex-direction: column; + flex-direction: column; + padding-left: 25px; + color: #fafafa; + overflow: hidden; +} +.app-detail .app-details h1 { + word-break: break-word; + margin: 0 0 0.1em; + font-size: 2em; + font-weight: 900; +} +.app-detail .app-details .link { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + color: #999; +} +.app-detail .app-details .link a { + color: inherit; +} +.app-detail .app-content { + position: relative; + margin-bottom: 25px; + padding: 25px; + background-color: #fff; +} +.app-detail .app-more-info { + width: 100%; +} +@media (min-width: 800px) { + .app-detail .app-content { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + -ms-flex-direction: row; + flex-direction: row; + -ms-flex-wrap: nowrap; + flex-wrap: nowrap; + } + .app-detail .app-info { + -webkit-box-flex: 2; + -ms-flex-positive: 2; + flex-grow: 2; + } + .app-detail .app-more-info { + max-width: 220px; + margin-left: 50px; + } +} +.app-detail .actions-wrapper { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-align: end; + -ms-flex-align: end; + align-items: flex-end; + -webkit-box-flex: 2; + -ms-flex-positive: 2; + flex-grow: 2; +} +.app-detail .actions { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -ms-flex-wrap: wrap; + flex-wrap: wrap; + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + -ms-flex-direction: row; + flex-direction: row; + -webkit-box-align: end; + -ms-flex-align: end; + align-items: flex-end; + width: 100%; +} +.app-detail .social { + -webkit-box-flex: 2; + -ms-flex-positive: 2; + flex-grow: 2; + text-align: right; + color: inherit; +} +.app-detail .social a { + margin-left: 0.3em; + color: #ccc; + font-size: 1.7em; +} +.app-detail .social a:hover { + color: #999; + text-decoration: none; +} +.app-detail .sup { + margin: 0; + color: #666; + line-height: 1; + font-size: 0.9em; +} +.app-detail .sub { + margin: 0; + font-weight: 900; + font-size: 1.2em; +} +.app-detail .sub + .sup { + margin-top: 1em; +} +.app-detail .description { + font-size: 1.2em; + margin-bottom: 1em; +} +.app-detail .cover-video { + padding: 45px 0; +} +.app-detail .cover-video .embed-container { + position: relative; + padding-bottom: 56.25%; + height: 0; + overflow: hidden; + max-width: 100%; +} +.app-detail .cover-video .embed-container iframe, +.app-detail .cover-video .embed-container object, +.app-detail .cover-video .embed-container embed { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; +} +.app-detail .social-badges { + padding-left: 15px; + margin-bottom: 15px; +} +.app-detail .social-badges .fb_iframe_widget { + top: -5px; +} +.app-detail .social-badges .app-links .list-group-item a:hover { + text-decoration: none; +} +.app-detail .social-badges .app-links .list-group-item a .no-og h4 { + margin-bottom: 0; +} +.app-detail .app-tags { + margin-bottom: 0.8em; +} +.app-detail .app-tags .label { + display: inline-block; + margin-right: 0.8em; +} +.app-comments { + padding: 10px 25px; + background-color: #fff; +} + + +body.app-form { + background-color: #e76056; + -webkit-transition: all ease 0.1s; + transition: all ease 0.1s; +} +body.app-form .error { + background-color: #f2dede; + border: 1px solid #d9534f; + color: #d9534f; + outline: none; +} +body.app-form .step-1, +body.app-form .step-2, +body.app-form .header-step { + color: #fff; +} +body.app-form .header-step { + -webkit-transition: all ease 0.5s; + transition: all ease 0.5s; +} +body.app-form .left-container { + padding-top: 10px; +} +body.app-form .disabled { + color: #f08d85; +} +body.app-form .color-picker { + text-align: center; +} +body.app-form input { + color: #6d6d6d; + padding: 12px; + margin-bottom: 10px; + width: 100%; +} +body.app-form input.rgb { + text-align: center; + text-transform: uppercase; +} +body.app-form .background-color { + background-color: #262626; + max-width: 275px; + height: 200px; + margin: 0 auto; +} +body.app-form .background-color h1 { + height: 200px; + line-height: 200px; + margin: 0; + text-align: center; + width: 100%; +} +body.app-form .title-container { + background-color: #d7d7d7; + height: 200px; + margin: 0 auto; + max-width: 275px; + padding: 22px 0; + text-align: center; +} +body.app-form .title-container textarea { + color: #6d6d6d; + height: 100px; + margin: 0 auto; + padding: 6px 12px; + resize: none; + width: 80%; +} +body.app-form textarea.description { + height: 100px; +} +body.app-form .checkbox .label:hover { + cursor: default; +} +body.app-form .continue { + margin-top: 10px; +} +body.app-form .app-detail .btn.back { + display: none; +} + + +#sidebar { + position: fixed; + top: 0; + right: 0; + width: 220px; + height: 100%; + height: 100vh; + z-index: 2000; + pointer-events: none; +} +.sidebar-is-open #sidebar { + pointer-events: auto; +} +.sidebar-is-open #sidebar:after { + opacity: 0.4; + visibility: visible; + -webkit-transition: opacity linear 0.3s, visibility 0s, -webkit-transform 0.3s; + transition: opacity linear 0.3s, visibility 0s, -webkit-transform 0.3s; + transition: transform 0.3s, opacity linear 0.3s, visibility 0s; + transition: transform 0.3s, opacity linear 0.3s, visibility 0s, -webkit-transform 0.3s; +} +.sidebar-is-open #sidebar nav { + -webkit-transform: translateZ(0); + transform: translateZ(0); +} +#sidebar > * { + pointer-events: auto; +} +#sidebar:after { + content: ''; + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + -webkit-transform: translateZ(0); + transform: translateZ(0); + opacity: 0; + visibility: hidden; + background: #000; + -webkit-transition: opacity linear 0.3s, visibility 0s 0.3s, -webkit-transform 0.3s; + transition: opacity linear 0.3s, visibility 0s 0.3s, -webkit-transform 0.3s; + transition: transform 0.3s, opacity linear 0.3s, visibility 0s 0.3s; + transition: transform 0.3s, opacity linear 0.3s, visibility 0s 0.3s, -webkit-transform 0.3s; +} +#sidebar nav { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 5; + padding: 40px; + text-align: center; + color: #656564; + overflow: auto; + background-color: #fafafa; + -webkit-transition: -webkit-transform 0.3s; + transition: -webkit-transform 0.3s; + transition: transform 0.3s; + transition: transform 0.3s, -webkit-transform 0.3s; + -webkit-transform: translate3d(100%, 0, 0); + transform: translate3d(100%, 0, 0); +} +#sidebar nav p { + margin-bottom: 5px; +} +#sidebar .close { + position: absolute; + top: 0; + right: 0; + z-index: 10; + padding: 20px; + opacity: 0.4; + color: #656564; + cursor: pointer; + -webkit-transition: opacity 0.15s linear; + transition: opacity 0.15s linear; +} +#sidebar .close:hover { + opacity: 1; +} +#sidebar .avatar { + margin-bottom: 10px; + width: 48px; + height: 48px; + border: 1px solid #333; + border-radius: 100%; +} +#sidebar .divider { + margin: 25px auto; + width: 30px; + height: 1px; + background-color: #ccc; +} + + +#site-header { + height: 80px; + border-bottom: 2px; + background-color: #fafafa; +} +#site-header:after { + content: ''; + display: block; + position: absolute; + bottom: 0; + left: 0; + width: 100%; + height: 1px; + background-position: center center; + background-image: -webkit-repeating-linear-gradient(left, #f7c64d 0px, #f7c64d 120px, #61baa8 120px, #61baa8 240px, #1394af 240px, #1394af 360px, #d93f5d 360px, #d93f5d 480px, #714c80 480px, #714c80 600px, #e16b55 600px, #e16b55 720px); + background-image: repeating-linear-gradient(to right, #f7c64d 0px, #f7c64d 120px, #61baa8 120px, #61baa8 240px, #1394af 240px, #1394af 360px, #d93f5d 360px, #d93f5d 480px, #714c80 480px, #714c80 600px, #e16b55 600px, #e16b55 720px); +} +#site-header .navbar-brand { + font-size: 20px; + color: #333; + height: 80px; + line-height: 51px; +} +#site-header .navbar-brand img { + position: relative; + top: -2px; + display: inline-block; + margin-right: 12px; + width: 30px; +} +@media (max-width: 480px) { + #site-header .navbar-brand span { + display: none; + } +} +#site-header .menu { + text-align: right; + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-pack: end; + -ms-flex-pack: end; + justify-content: flex-end; + -ms-flex-wrap: wrap; + flex-wrap: wrap; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; +} +#site-header .add-app { + display: inline-block; + margin-right: 15px; +} +#site-header .add-app .fa { + margin-right: 0.3em; +} +#site-header .toggle-sidebar { + position: relative; + display: inline-block; + margin: 0; + border: 0; + padding: 33px 10px; + border-radius: 0; + -webkit-transition: all 0.3s; + transition: all 0.3s; + background-color: transparent; +} +#site-header .toggle-sidebar:hover { + background-color: #f5f5f5; +} +#site-header .toggle-sidebar .icon-bar { + display: block; + width: 17px; + height: 1px; + border-radius: 100%; + background-color: #333; +} +#site-header .toggle-sidebar .icon-bar + .icon-bar { + margin-top: 5px; +} + + +.footer { + padding-top: 60px; + padding-bottom: 30px; + text-align: center; +} +.footer a { + font-weight: bold; +} + + +.application-container { + display: inline-block; + position: relative; + margin: 15px; + width: 262px; +} +.application-card { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -ms-flex-direction: column; + flex-direction: column; + height: 100%; + box-shadow: 0px 2px 5px rgba(0,0,0,0.1); +} +.application-card .app-logo { + height: 175px; + width: 100%; +} +.application-card .app-detail { + -webkit-box-flex: 2; + -ms-flex-positive: 2; + flex-grow: 2; + font-size: 12px; + padding: 12px; + min-height: 190px; + background: #fff; +} +.application-card .app-detail header { + margin-bottom: 12px; +} +.application-card .app-detail header h1, +.application-card .app-detail header h2 { + margin: 0; +} +.application-card .app-detail header h1 { + font-size: 16px; + font-weight: bold; +} +.application-card .app-detail header h2 { + font-size: 14px; +} +.application-card .description { + margin: 0; + font-size: 1em; +} +.application-card footer { + background-color: #efefef; + font-size: 12px; + height: 50px; + line-height: 50px; + padding: 0 12px; +} + + +.homepage .global-description { + margin: 20px 0 0.8em; + font-size: 2em; + line-height: 1.4; + cursor: default; +} +.homepage .global-description strong { + color: #27aeaa; +} +.homepage .prefilters { + max-width: 900px; + margin: 0 auto 60px; +} +.homepage .prefilters button { + margin: 0 5px 10px; +} +.homepage .filters .badge { + margin: 0 5px; +} +.homepage .filters .filter-remove { + margin-left: 10px; +} +.homepage .filters .filter-remove a { + color: #fff; +} +.homepage .filters .filter-remove a:hover { + text-decoration: none; + cursor: pointer; +} +.homepage .loading-overlay { + position: fixed; + height: 100%; + width: 100%; + opacity: 0.8; + background-color: #dfdfdf; + z-index: 10; +} +.homepage .applications-wrapper { + padding: 0; +} +.homepage .applications-container { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -ms-flex-wrap: wrap; + flex-wrap: wrap; + -webkit-box-pack: center; + -ms-flex-pack: center; + justify-content: center; +} +.homepage-navigation { + margin-bottom: 30px; +} +.homepage-navigation .btn { + line-height: 28px; +} +.homepage-navigation .btn-link { + padding-left: 0; + padding-right: 0; + color: #333; +} +.homepage-navigation .btn-link + .btn-link { + margin-left: 30px; +} +.homepage-navigation .btn-link:hover { + color: inherit; +} +.homepage-navigation .search { + outline: 0; + padding: 20px 20px 20px 30px; + border: 0; + border-radius: 1px; + box-shadow: 0px 1px 2px rgba(0,0,0,0.1); + background-color: #fff; + -webkit-transition: all 0.3s linear; + transition: all 0.3s linear; +} +.homepage-navigation .search:active, +.homepage-navigation .search:focus { + box-shadow: 0px 1px 3px rgba(0,0,0,0.25); +} +.homepage-navigation .search-icon { + color: #b3b3b3; + position: absolute; + left: 25px; + top: 13px; +} +.homepage-navigation .sort-btn { + position: relative; + color: #999; + text-decoration: none; +} +.homepage-navigation .sort-btn.active { + color: #333; + font-weight: 900; +} +.homepage-navigation .sort-btn + .sort-btn { + padding-left: 10px; + margin-left: 10px; +} +.homepage-navigation .sort-btn + .sort-btn:before { + content: ''; + display: block; + position: absolute; + left: 0; + top: 1em; + height: 1em; + width: 1px; + background-color: #ccc; +} + + +#login-modal a.login { + font-size: 48px; + margin: 0 16px; +} +#login-modal a.login.twitter { + color: #55acee; +} + + +html, +body { + height: 100%; + margin: 0; + padding: 0; +} +body { + background-color: #dfdfdf; + font-family: "Lucida Grande", "Lucida Sans Unicode", "Lucida Sans", Geneva, Verdana, sans-serif; + padding-top: 80px; + -webkit-transition: all ease 0.3s; + transition: all ease 0.3s; + -webkit-font-smoothing: antialiased; +} +.no-gutter [class*="col-"] { + padding: 0 !important; +} +h1, +h2, +h3 { + font-weight: 100; +} +a { + color: #333; +} +a:focus { + outline: none; +} +a:hover { + color: #333; +} +.btn { + border-radius: 1px; + box-shadow: none; + border: 0; +} +.btn-primary { + color: #fafafa; + border-color: transparent; + background-color: #27aeaa; +} +.btn-primary:hover, +.btn-primary:active, +.btn-primary:focus, +.btn-primary.active { + color: #fafafa; + background-color: #239d99; +} +.btn-primary:hover:active, +.btn-primary:active:focus { + color: #fafafa; + box-shadow: inset 0 3px 5px rgba(0,0,0,0.125); + background-color: #219491; +} +.btn-upvote span { + font-family: monospace; + font-weight: 900; +} +.btn-upvote i:before { + position: relative; + top: 0.09em; + font-size: 1.4em; + line-height: 0.8; +} +.app-logo { + position: relative; + display: block; + width: 100%; + background-repeat: no-repeat; + background-size: 70%; + background-position: center center; +} +.app-logo h1 { + position: absolute; + visibility: hidden; + opacity: 0; + pointer-events: none; +} +.app-logo:after { + content: ''; + display: block; + padding-top: 64%; +} +textarea { + resize: none; +} +.modal .modal-header, +.modal .modal-footer { + background-color: #f4f4f4; +} +.modal .modal-header { + font-weight: bold; +} +.site-content { + padding-top: 30px; +} + + diff --git a/lib/boot/images/favicon.ico b/public/favicon.ico similarity index 100% rename from lib/boot/images/favicon.ico rename to public/favicon.ico diff --git a/lib/admin/images/cancel.svg b/public/lib/admin/images/cancel.svg similarity index 100% rename from lib/admin/images/cancel.svg rename to public/lib/admin/images/cancel.svg diff --git a/lib/admin/images/edit.svg b/public/lib/admin/images/edit.svg similarity index 100% rename from lib/admin/images/edit.svg rename to public/lib/admin/images/edit.svg diff --git a/lib/boot/images/bg.jpg b/public/lib/boot/images/bg.jpg similarity index 100% rename from lib/boot/images/bg.jpg rename to public/lib/boot/images/bg.jpg diff --git a/public/lib/boot/images/favicon.ico b/public/lib/boot/images/favicon.ico new file mode 100644 index 0000000..3fe2f9f Binary files /dev/null and b/public/lib/boot/images/favicon.ico differ diff --git a/public/lib/header/images/border.png b/public/lib/header/images/border.png new file mode 100644 index 0000000..044c8cb Binary files /dev/null and b/public/lib/header/images/border.png differ diff --git a/lib/header/images/logo.svg b/public/lib/header/images/logo.svg similarity index 100% rename from lib/header/images/logo.svg rename to public/lib/header/images/logo.svg diff --git a/webpack.config.babel.js b/webpack.config.babel.js new file mode 100644 index 0000000..e2907c1 --- /dev/null +++ b/webpack.config.babel.js @@ -0,0 +1,44 @@ +const path = require('path') +const webpack = require('webpack') + +const paths = { + app: path.join(__dirname, 'lib', 'app'), + home: path.join(__dirname, 'lib', 'app', 'home.js'), + build: path.join(__dirname, 'dist') +} + +const entry = commonEntry => ({ + home: [...commonEntry, paths.home], + vendors: ['react'] +}) + +const resolve = { + extensions: ['', '.js', '.jsx'] +} + +const output = { + path: paths.build, + filename: '[name].js', + publicPath: '/dist/', +} + +const loaders = [ + { + test: /\.jsx?$/, + loader: 'babel-loader', + include: [paths.app] + } +] + +module.exports = { + entry: entry(['babel-polyfill', 'webpack-hot-middleware/client']), + resolve, + output, + module: { loaders }, + plugins: [ + new webpack.optimize.CommonsChunkPlugin('vendors', 'vendors.js'), + new webpack.optimize.OccurenceOrderPlugin(), + new webpack.HotModuleReplacementPlugin(), + new webpack.NoErrorsPlugin() + ] +}