Compare commits

...

98 Commits

Author SHA1 Message Date
e21d30ed6b some files might have been missing (?) 2020-05-08 23:41:15 +02:00
ef4064af3d disabled beta/invite code on registration (can be re-enabled by config and uncommenting the login html file) 2020-04-15 15:41:52 +02:00
7c5fd6485e keep the current tool as long as the cursor is in the web view 2020-04-15 15:36:37 +02:00
mntmn
a92b915bc3 fix password recovery 2020-04-09 22:21:55 +02:00
mntmn
f5a6adc43b remove unused modals 2020-04-09 21:46:23 +02:00
mntmn
40202ff416 fix logging in text on login button 2020-04-09 19:11:26 +02:00
mntmn
b07050095d remove production/development distinction for javascripts 2020-04-09 19:11:06 +02:00
mntmn
9cb04422d8 remove pdf import options 2020-04-09 19:10:34 +02:00
mntmn
d544caf4a7 anonymous editor: don't ask for username if already logged in 2020-04-09 18:05:07 +02:00
mntmn
2dbfae59f9 remove dead functions from folder 2020-04-09 17:56:47 +02:00
mntmn
e25a56e85c filter attributes on space PUT 2020-04-09 17:56:37 +02:00
mntmn
6f1744bc5d show share button only to admins 2020-04-09 17:50:48 +02:00
mntmn
fab2a61f83 fix error message for messing with space memberships 2020-04-09 17:37:49 +02:00
mntmn
9750f08606 bring back copy and paste of artifacts 2020-04-09 17:33:23 +02:00
mntmn
643b75ebe9 remove tiny piece of unused code 2020-04-09 17:27:14 +02:00
mntmn
2f39dd26be fix listing of invited-to-spaces in user's home folder 2020-04-09 17:26:58 +02:00
mntmn
3edde7c53c remove more dead code 2020-04-09 16:22:17 +02:00
mntmn
01a6bec80e fix loading space that user is member of 2020-04-09 16:20:29 +02:00
mntmn
bdb2e9fde5 clean up space memberships table; clean up terminate account view 2020-04-09 15:37:51 +02:00
mntmn
7cf68c94a5 remove unused templates 2020-04-09 15:26:28 +02:00
mntmn
ecdacd6e11 explicitly make new spaces private 2020-04-09 15:23:14 +02:00
mntmn
92cf6c4397 some cleanups to mailer and user deletion 2020-04-09 15:22:06 +02:00
mntmn
d6f93051ef signup: reorder fields to play better with conventions and pw managers 2020-04-09 15:21:47 +02:00
mntmn
4073e36441 remove noisy console.log 2020-04-09 15:02:10 +02:00
mntmn
16ffecdb16 fix error handling and displaying on membership PUT and DELETE; don't allow to change your own role; require at least one admin 2020-04-09 14:55:18 +02:00
mntmn
c05afaba8a remove folder and space 'duplication' leftovers 2020-04-09 14:15:48 +02:00
mntmn
9d3105763b readme update 2020-04-08 22:11:56 +02:00
mntmn
f5e5a7f8fe readme update 2020-04-08 22:11:10 +02:00
mntmn
9ed5a9931f update ws module 2020-04-08 22:00:20 +02:00
mntmn
ddd1ed2cb1 landing: add screenshot 2020-04-08 21:56:41 +02:00
mntmn
2ac0d49f2f quick mobile responsiveness fix 2020-04-08 21:29:16 +02:00
mntmn
80f9b0d93f Spacedeck 6.0 CI WIP; more style, UX cleanups; fix account dialog; add more color swatches; new landing page 2020-04-08 20:45:30 +02:00
mntmn
58250a72ad WIP MNT design/UX cleanup 2020-04-07 20:37:41 +02:00
mntmn
d19d02220e fix phantomjs ssl error and space screenshots 2019-05-19 23:16:30 +02:00
mntmn
69037685c1 add missing email_invited field on memberships 2019-05-19 22:42:23 +02:00
mntmn
3fdb1bf8bb add gulp-concat, gulp-sass deps 2019-05-19 22:38:31 +02:00
mntmn
b72a3af124 remove google login button 2019-05-19 22:38:03 +02:00
mntmn
f7394d3195 remove bin folder 2019-05-19 22:37:46 +02:00
mntmn
3b735d28f6 fix migration 01 2019-05-19 22:37:35 +02:00
mntmn
8ba37a11d6 purge IE 2019-05-15 22:36:53 +02:00
mntmn
58aa3fc41f fix gulpfile to signal completion, clean up a bit 2019-05-15 21:35:29 +02:00
mntmn
db849bcb20 fix most vulns via npm audit fix 2019-05-15 21:29:34 +02:00
mntmn
0efeb8d1df remove electron, docker 2019-05-15 21:25:53 +02:00
mntmn
2fc14e1efe
Update README.md 2018-07-25 11:05:35 +02:00
mntmn
79a3d39015
Remove windows prerelease from README
Releases should be tested and built via CI/CD.
2018-07-17 12:21:32 +02:00
Lukas F. Hartmann
628ceb6c3a Merge branch 'master' of https://github.com/spacedeck/spacedeck-open 2018-07-17 12:18:14 +02:00
Lukas F. Hartmann
9889aaa34c Merge branch 'kaleidos-issues/23/deleting-space-user-is-not-possible' 2018-07-17 12:17:56 +02:00
Lukas F. Hartmann
2974caab1d minor cleanup 2018-07-17 12:16:24 +02:00
Hirunatan
ea4f40b628 Implement SMTP email service (#28) 2018-07-17 12:05:27 +02:00
Alejandro Alonso
f71081b15b Adding migration support and delete cascade migration for space references 2018-07-17 08:39:50 +02:00
Lukas F. Hartmann
c075c562d6 fix mp4 upload and video conversion progress display; fixes #21 2018-05-07 20:19:07 +02:00
Lukas F. Hartmann
dc986dcc7e remove traces of team from guest login, fixes #20 2018-05-07 20:00:02 +02:00
Lukas F. Hartmann
fc653e5ce5 fix pdf grid import with zones #19 2018-05-07 19:56:26 +02:00
Lukas F. Hartmann
4c5e6ea286 fix auto layout button 2018-05-03 15:37:59 +02:00
Lukas F. Hartmann
5f4d41f3a4 fix PDF import 2018-05-03 15:35:51 +02:00
Lukas F. Hartmann
fb8d3ac654 Merge branch 'master' of https://github.com/spacedeck/spacedeck-open 2018-05-01 17:06:29 +02:00
Lukas F. Hartmann
c19f00b316 fix session token/cookie handling for arbitrary server IPs; fix realtime update distribution via websockets 2018-05-01 17:04:08 +02:00
mntmn
60ccedd840
Update README.md 2018-05-01 01:25:07 +02:00
mntmn
d2f07a73cf
Update README.md 2018-04-14 18:54:16 +02:00
mntmn
c5783feca9 fix some artifact serialization trouble; fix login error 2018-04-15 00:23:52 +02:00
mntmn
f396bc2e40 update README 2018-04-14 22:32:28 +02:00
mntmn
44c84028dc electron windows sqlite3 build instrs 2018-04-14 22:29:25 +02:00
mntmn
54755621a1 fix avatar api; fix edit link api 2018-04-14 22:27:38 +02:00
mntmn
30d2995565 package.json sort 2018-04-14 22:25:58 +02:00
mntmn
f5acdcf614 remove junk from gulpfile and ship compiled CSS 2018-04-14 22:24:16 +02:00
Lukas F. Hartmann
f752ec4219 readme tweaks 2018-04-12 20:27:58 +02:00
Lukas F. Hartmann
4caef3b913 update README 2018-04-12 19:56:06 +02:00
Lukas F. Hartmann
f321d67597 tune README 2018-04-12 18:49:45 +02:00
mntmn
ebac854da8
Port Backend to SQLite/Sequelize (removes MongoDB), Support Electron (#14)
* The MongoDB/Mongoose data storage is removed in favor of Sequelize. This abstracts over SQLite or RDBMs like PostgreSQL and MSSQL. The default is SQLite, which significantly simplifies deployments in end-user environments.

* As Spacedeck now has no more mandatory server dependencies, we can wrap it in Electron and ship it as a desktop application.

* Removes docker-compose.yml

* First version of import UI
2018-04-12 16:40:58 +00:00
Lukas F. Hartmann
8e0bc69a11 updated readme re: file storage requirements 2018-03-30 23:29:27 +02:00
Lukas F. Hartmann
38c0d75c28 updated and fixed README issues 2018-03-30 23:21:54 +02:00
Lukas F. Hartmann
65a5534cc6 fix config link in readme 2018-03-30 22:48:10 +02:00
Lukas F. Hartmann
fa81fd3d8a remove unfinished docker paragraph from README 2018-03-30 22:45:27 +02:00
mntmn
648a59c894
Merge pull request #11 from spacedeck/importer
update vulnerable moment package
2018-03-30 20:43:00 +00:00
Lukas F. Hartmann
84d0be50f0 update vulnerable moment package 2018-03-30 22:42:16 +02:00
mntmn
256d2b8cbf
Merge pull request #10 from spacedeck/importer
WIP Importer for spacedeck.com exports
Alternative (to S3) local file storage
Alternative mocked redis (run without redis for single process instances)
Experimental Docker support
2018-03-30 20:40:11 +00:00
Lukas F. Hartmann
6d2d2310b6 basic import functionality; dockerfile fixes; session and cookie handling fixes 2018-03-30 22:34:27 +02:00
mntmn
462e9edaab first importer WIP 2018-01-08 15:57:59 +01:00
mntmn
ffb7f30133 switch between s3/local storage in config 2018-01-08 12:43:47 +01:00
mntmn
8ee3386470 fix tmp dir in artifact uploader 2018-01-08 12:41:38 +01:00
mntmn
0d92343d55 fix redis mock incr() 2018-01-08 12:41:12 +01:00
mntmn
efb7970ecb fix express caching 2018-01-08 12:40:11 +01:00
mntmn
7e8a27e140 replace redis by in-memory object 2018-01-08 00:08:42 +01:00
mntmn
6ad97ac5c2 fix port number in readme 2018-01-07 22:54:31 +01:00
Lukas F. Hartmann
820203625c don't default to app volume mount; create s3 bucket on boot; fix revAll gulp step 2017-04-07 18:01:23 +02:00
Martin Guether
03059b67f1 add gulp stuff to build process 2017-04-07 15:34:33 +02:00
Martin Guether
1426bc9c24 updated storage config 2017-04-07 13:07:09 +02:00
Martin Guether
bd0471dad6 added docker ignore 2017-04-07 12:03:58 +02:00
Martin Guether
bbdcb2b2fe Merge remote-tracking branch 'origin/master' into docker
# Conflicts:
#	README.md
2017-04-07 12:00:28 +02:00
Martin Guether
af5335025f fixed crash on console 2017-04-07 11:58:29 +02:00
Martin Guether
f9cf8ba7e8 update ports and variables for docker compose bootstrap 2017-04-07 11:55:07 +02:00
Martin Guether
9877977750 add configuration lines to readme 2017-04-07 11:05:12 +02:00
Martin Guether
bda60bf877 add engine to package.json 2017-04-07 11:04:58 +02:00
Martin Guether
7605b8f373 node update 2017-04-07 10:59:10 +02:00
Martin Guether
dfcfd7f0b8 added binary dependencies to readme 2017-04-07 10:57:33 +02:00
Martin Guether
a3e2129b79 added experiemental docker support 2017-04-07 10:50:48 +02:00
Martin Guether
2e34b317a4 update packages 2017-04-07 10:39:35 +02:00
Martin Guether
d887d56dd3 fixed schema import for artifact 2017-04-07 10:39:24 +02:00
116 changed files with 19415 additions and 21041 deletions

14
.dockerignore Normal file
View File

@ -0,0 +1,14 @@
.DS_Store
.git
logs
*.log
scripts
pids
*.pid
*.seed
lib-cov
coverage
.grunt
.lock-wscript
build/Release
node_modules

5
.gitignore vendored
View File

@ -1,5 +1,8 @@
node_modules node_modules
public/stylesheets/*
javascripts/maps javascripts/maps
javascripts/spacedeck.js javascripts/spacedeck.js
public/stylesheets/*.css
database.sqlite
*.swp
*~

29
Dockerfile Normal file
View File

@ -0,0 +1,29 @@
FROM spacedeck/docker-baseimage:latest
ENV NODE_ENV production
RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app
COPY package.json /usr/src/app/
RUN npm install
RUN npm install gulp-rev-replace gulp-clean gulp-fingerprint gulp-rev gulp-rev-all gulp-rev-replace
RUN npm install -g --save-dev gulp
COPY app.js Dockerfile Gulpfile.js LICENSE /usr/src/app/
COPY config /usr/src/app/config
COPY helpers /usr/src/app/helpers
COPY locales /usr/src/app/locales
COPY middlewares /usr/src/app/middlewares
COPY models /usr/src/app/models
COPY public /usr/src/app/public
COPY routes /usr/src/app/routes
COPY styles /usr/src/app/styles
COPY views /usr/src/app/views
RUN gulp all
RUN npm cache clean
CMD [ "node", "app.js" ]
EXPOSE 9666

View File

@ -1,62 +1,13 @@
var gulp = require('gulp'); const gulp = require('gulp')
var sass = require('gulp-sass'); const sass = require('gulp-sass')
var concat = require('gulp-concat'); const concat = require('gulp-concat')
var server = require('gulp-express');
var nodemon = require('gulp-nodemon');
var revReplace = require("gulp-rev-replace");
var clean = require('gulp-clean');
var child_process = require('child_process'); gulp.task('styles', function(done) {
var path = require('path');
var uglify = require('gulp-uglify');
var fingerprint = require('gulp-fingerprint');
var rev = require('gulp-rev');
var RevAll = require('gulp-rev-all');
gulp.task('rev', () => {
var revAll = new RevAll();
return gulp.src(['public/**'])
.pipe(gulp.dest('build/assets'))
.pipe(revAll.revision())
.pipe(gulp.dest('build/assets'))
.pipe(revAll.manifestFile())
.pipe(gulp.dest('build/assets'));
});
gulp.task("all", ["styles", "uglify", "rev", "copylocales"], function(){
var manifest = gulp.src("./build/assets/rev-manifest.json");
return gulp.src("./views/**/*")
.pipe(revReplace({manifest: manifest}))
.pipe(gulp.dest("./build/views"));
});
gulp.task('copylocales', function(){
return gulp.src('./locales/*.js').pipe(gulp.dest('./build/locales'));
});
gulp.task('clean', function () {
return gulp.src('./build').pipe(clean());
});
gulp.task('styles', function() {
gulp.src('styles/**/*.scss') gulp.src('styles/**/*.scss')
.pipe(sass({ .pipe(sass({
errLogToConsole: true errLogToConsole: true
})) }))
.pipe(gulp.dest('./public/stylesheets/')) .pipe(gulp.dest('./public/stylesheets/'))
.pipe(concat('style.css')); .pipe(concat('style.css'))
}); done()
})
gulp.task('uglify', () => {
child_process.exec('sed -n \'s/.*script minify src="\\(.*\\)".*/.\\/public\\/\\1/p\' views/spacedeck.html',
function (error, stdout, stderr) {
var scripts = stdout.split('\n').map(function(p){return path.normalize(p)}).filter(function(p){return p!='.'});
console.log("scripts: ",scripts);
gulp.src(scripts)
.pipe(uglify({output:{beautify:true}}))
.pipe(concat('spacedeck.js'))
.pipe(gulp.dest('public/javascripts'));
});
});

View File

@ -1,58 +1,82 @@
# Spacedeck Open # Spacedeck Open
This is the free and open source version of Spacedeck, a web based, real time, collaborative whiteboard application with rich media support. Spacedeck was developed in 6 major releases during Autumn 2011 until the end of 2016 and was originally a commercial SaaS. The developers were Lukas F. Hartmann (mntmn) and Martin Güther (magegu). All icons and large parts of the CSS were designed by Thomas Helbig (dergraph). ![Spacedeck 6.0 Screenshot](/public/images/sd6-screenshot.png)
As we plan to retire the subscription based service at spacedeck.com in late 2017, we decided to open-source Spacedeck to allow educational and other organizations who currently rely on Spacedeck to migrate to a self-hosted version. This is the free and open source version of Spacedeck, a web based, real time, collaborative whiteboard application with rich media support. Spacedeck was developed in 6 major releases during Autumn 2011 until the end of 2016 and was originally a commercial SaaS. The developers were Lukas F. Hartmann (mntmn) and Martin Güther (magegu).
Data migration features will be added soon. The spacedeck.com online service was shut down on May 1st 2018. We decided to open-source Spacedeck to allow educational and other organizations who currently rely on Spacedeck to migrate to a self-hosted or local version.
[MNT Research GmbH](https://mntre.com) has restarted development of Spacedeck Open in 2020.
We appreciate filed issues, pull requests and general discussion. We appreciate filed issues, pull requests and general discussion.
# Features # Features
- Create virtual whiteboards called "Spaces" with virtually unlimited size - Create virtual whiteboards called *Spaces* with virtually unlimited size
- Drag & drop images, videos and audio from your computer or the web - Drag & drop images, videos and audio from your computer or the web
- Write and format text with full control over fonts, colors and style - Write and format text with full control over fonts, colors and style
- Draw, annotate and highlight with included graphical shapes - Draw, annotate and highlight with included graphical shapes
- Turn your Space into a zooming presentation - Turn your Space into a zooming presentation
- Collaborate and chat in realtime with teammates, students or friends - Collaborate in realtime with teammates, students or friends
- Share Spaces on the web or via email - Share Spaces on the web or via email
- Export your work as printable PDF or ZIP - Export your work as printable PDF or ZIP (currently being fixed, stay tuned)
# Use Cases
- Education: Virtual classwork with multimedia
- Creative: Mood boards, Brainstorming, Design Thinking
- Visual note taking and planning
# Requirements, Installation # Requirements, Installation
Spacedeck uses the following major building blocks: Spacedeck requires:
- Node.js 4.x (Backend / API) - Node.js 10.x: Web Server / API. Download: https://nodejs.org
- MongoDB 3.x (Datastore)
- Redis 3.x (Datastore for realtime channels)
- Vue.js (Frontend)
It also has some binary dependencies for media conversion and PDF export: To run Spacedeck, you only need Node.JS 10.x.
- imagemagick To install all node dependencies, run (do this once):
Currently, media files are stored in Amazon S3, so you need an Amazon AWS account and have the ```AWS_ACCESS_KEY_ID``` and ```AWS_SECRET_ACCESS_KEY``` environment variables defined. For sending emails, Amazon SES is required.
To install Spacedeck, you need node.js 4.x and a running MongoDB instance. Then, to install all node dependencies, run
npm install npm install
To rebuild the frontend CSS styles (you need to do this at least once): # Configuration
See [config/default.json](config/default.json)
# Run (web server)
node spacedeck.js
Then open http://localhost:9666 in a web browser.
# Optional Dependencies
For advanced media conversion:
- ffmpeg and ffprobe for video/audio conversion. Download: https://www.ffmpeg.org/download.html
- audiowaveform for audio waveform rendering. Download: https://github.com/bbcrd/audiowaveform
- ghostscript for PDF import. Download: https://www.ghostscript.com/download/gsdnld.html
# Data Storage
By default, media files are uploaded to the ```storage``` folder.
The database is stored in ```database.sqlite``` by default.
# Hacking
To rebuild the frontend CSS styles:
gulp styles gulp styles
# Run
export NODE_ENV=development
npm start
# License # License
Spacedeck Open is released under the GNU Affero General Public License Version 3 (GNU AGPLv3). The Spacedeck logo and brand assets are registered trademarks of Spacedeck GmbH. All rights reserved.
Spacedeck Open source code is released under the GNU Affero General Public License Version 3 (GNU AGPLv3).
Spacedeck Open - Web-based Collaborative Whiteboard For Rich Media Spacedeck Open - Web-based Collaborative Whiteboard For Rich Media
Copyright (C) 2011-2017 Lukas F. Hartmann, Martin Güther, Thomas Helbig Copyright (C) 2011-2018 Lukas F. Hartmann, Martin Güther
Icons and original CSS design copyright by Thomas Helbig
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as it under the terms of the GNU Affero General Public License as

190
app.js
View File

@ -1,173 +1,33 @@
"use strict"; const spacedeck = require('./spacedeck')
require('./models/schema'); const electron = require('electron')
require("log-timestamp"); const electronApp = electron.app
const BrowserWindow = electron.BrowserWindow
let mainWindow
const config = require('config'); function createWindow () {
const redis = require('./helpers/redis'); mainWindow = new BrowserWindow({width: 1200, height: 700})
const websockets = require('./helpers/websockets'); mainWindow.loadURL("http://localhost:9666")
mainWindow.on('closed', function () {
const http = require('http'); mainWindow = null
const path = require('path'); })
const _ = require('underscore');
const favicon = require('serve-favicon');
const logger = require('morgan');
const cookieParser = require('cookie-parser');
const bodyParser = require('body-parser');
const mongoose = require('mongoose');
const swig = require('swig');
const i18n = require('i18n-2');
const helmet = require('helmet');
const express = require('express');
const app = express();
const isProduction = app.get('env') === 'production';
console.log("Booting Spacedeck Open… (environment: " + app.get('env') + ")");
app.use(logger(isProduction ? 'combined' : 'dev'));
i18n.expressBind(app, {
locales: ["en", "de", "fr"],
defaultLocale: "en",
cookieName: "spacedeck_locale",
devMode: (app.get('env') == 'development')
});
swig.setDefaults({
varControls: ["[[", "]]"] // otherwise it's not compatible with vue.js
});
swig.setFilter('cdn', function(input, idx) {
return input;
});
app.engine('html', swig.renderFile);
app.set('view engine', 'html');
if (app.get('env') != 'development') {
app.set('views', path.join(__dirname, 'build', 'views'));
app.use(favicon(path.join(__dirname, 'build', 'assets', 'images', 'favicon.png')));
app.use(express.static(path.join(__dirname, 'build', 'assets')));
} else {
app.set('views', path.join(__dirname, 'views'));
app.use(favicon(path.join(__dirname, 'public', 'images', 'favicon.png')));
app.use(express.static(path.join(__dirname, 'public')));
} }
app.use(bodyParser.json({ electronApp.on('ready', createWindow)
limit: '50mb'
}));
app.use(bodyParser.urlencoded({ // Quit when all windows are closed.
extended: false, electronApp.on('window-all-closed', function () {
limit: '50mb' // On OS X it is common for applications and their menu bar
})); // to stay active until the user quits explicitly with Cmd + Q
if (process.platform !== 'darwin') {
app.use(cookieParser()); electronApp.quit()
app.use(helmet.noCache())
app.use(helmet.frameguard())
app.use(helmet.xssFilter())
app.use(helmet.hsts({
maxAge: 7776000000,
includeSubdomains: true
}))
app.disable('x-powered-by');
app.use(helmet.noSniff())
// CUSTOM MIDDLEWARES
app.use(require("./middlewares/templates"));
app.use(require("./middlewares/error_helpers"));
app.use(require("./middlewares/setuser"));
app.use(require("./middlewares/subdomain"));
app.use(require("./middlewares/cors"));
app.use(require("./middlewares/i18n"));
app.use("/api", require("./middlewares/api_helpers"));
app.use('/api/spaces/:id', require("./middlewares/space_helpers"));
app.use('/api/spaces/:id/artifacts/:artifact_id', require("./middlewares/artifact_helpers"));
app.use('/api/teams', require("./middlewares/team_helpers"));
// REAL ROUTES
app.use('/api/users', require('./routes/api/users'));
app.use('/api/memberships', require('./routes/api/memberships'));
const spaceRouter = require('./routes/api/spaces');
app.use('/api/spaces', spaceRouter);
spaceRouter.use('/:id/artifacts', require('./routes/api/space_artifacts'));
spaceRouter.use('/:id/memberships', require('./routes/api/space_memberships'));
spaceRouter.use('/:id/messages', require('./routes/api/space_messages'));
spaceRouter.use('/:id/digest', require('./routes/api/space_digest'));
spaceRouter.use('/:id', require('./routes/api/space_exports'));
app.use('/api/sessions', require('./routes/api/sessions'));
app.use('/api/teams', require('./routes/api/teams'));
app.use('/api/webgrabber', require('./routes/api/webgrabber'));
app.use('/', require('./routes/root'));
// catch 404 and forward to error handler
app.use(require('./middlewares/404'));
if (app.get('env') == 'development') {
app.set('view cache', false);
swig.setDefaults({cache: false});
} else {
app.use(require('./middlewares/500'));
}
module.exports = app;
// CONNECT TO DATABASE
const mongoHost = process.env.MONGO_PORT_27017_TCP_ADDR || 'localhost';
mongoose.connect('mongodb://' + mongoHost + '/spacedeck');
// START WEBSERVER
const port = 9000;
const server = http.Server(app).listen(port, () => {
if ("send" in process) {
process.send('online');
} }
})
}).on('listening', () => { electronApp.on('activate', function () {
// On OS X it's common to re-create a window in the app when the
const host = server.address().address; // dock icon is clicked and there are no other windows open.
const port = server.address().port; if (mainWindow === null) {
console.log('Spacedeck Open listening at http://%s:%s', host, port); createWindow()
}).on('error', (error) => {
if (error.syscall !== 'listen') {
throw error;
} }
})
const bind = typeof port === 'string' ? 'Pipe ' + port : 'Port ' + port;
switch (error.code) {
case 'EACCES':
console.error(bind + ' requires elevated privileges');
process.exit(1);
break;
case 'EADDRINUSE':
console.error(bind + ' is already in use');
process.exit(1);
break;
default:
throw error;
}
});
//WEBSOCKETS & WORKER
websockets.startWebsockets(server);
redis.connectRedis();
process.on('message', (message) => {
console.log("Process message:", message);
if (message === 'shutdown') {
console.log("Exiting spacedeck.");
process.exit(0);
}
});

0
bin/www Executable file → Normal file
View File

View File

@ -1,9 +1,34 @@
{ {
"endpoint": "http://localhost:9000", "team_name": "My Open Spacedeck",
"storage_bucket": "my_spacedeck_s3_bucket", "contact_email": "support@example.org",
"storage_cdn": "xyz.cloudfront.net",
"endpoint": "http://localhost:9666",
"invite_code": "", //disabled invite code by default
"storage_region": "eu-central-1",
//"storage_bucket": "sdeck-development",
//"storage_cdn": "http://localhost:9123/sdeck-development",
//"storage_endpoint": "http://storage:9000",
"storage_bucket": "my_spacedeck_bucket",
"storage_cdn": "/storage",
"storage_local_path": "./storage",
"redis_mock": true,
"mongodb_host": "localhost",
"redis_host": "localhost",
"google_access" : "", "google_access" : "",
"google_secret" : "", "google_secret" : "",
"admin_pass": "very_secret_admin_password", "admin_pass": "very_secret_admin_password",
"phantom_api_secret": "very_secret_phantom_password" "phantom_api_secret": "very_secret_phantom_password",
// Choose "console" or "smtp"
"mail_provider": "smtp",
"mail_smtp_host": "your.smtp.host",
"mail_smtp_port": 465,
"mail_smtp_secure": true,
"mail_smtp_require_tls": true,
"mail_smtp_user": "your.smtp.user",
"mail_smtp_pass": "your.secret.smtp.password"
} }

17
electron-windows.md Normal file
View File

@ -0,0 +1,17 @@
# Windows Electron Build
sqlite3 needs to be manually built for the iojs version that electron ships. The following code assumes electron v1.8.4.
````
npm -g install windows-build-tools
cd node_modules\sqlite3
node-gyp configure --module_name=node_sqlite3 --module_path=../lib/binding/electron-v1.8-win32-x64
node-gyp rebuild --target=1.8.4 --target_platform=win32 --dist-url=https://atom.io/download/atom-shell --module_name=node_sqlite3 --module_path=../lib/binding/electron-v1.8-win32-x64 --msvs_version=2015
cd ..\..
````

View File

@ -4,51 +4,18 @@ const exec = require('child_process');
const gm = require('gm'); const gm = require('gm');
const async = require('async'); const async = require('async');
const fs = require('fs'); const fs = require('fs');
const Models = require('../models/schema'); const Models = require('../models/db');
const uploader = require('../helpers/uploader'); const uploader = require('../helpers/uploader');
const path = require('path'); const path = require('path');
const os = require('os');
const fileExtensionMap = { const db = require('../models/db');
".amr" : "audio/AMR", const Sequelize = require('sequelize');
".ogg" : "audio/ogg", const Op = Sequelize.Op;
".aac" : "audio/aac",
".mp3" : "audio/mpeg", const mime = require('mime-types');
".mpg" : "video/mpeg", const fileType = require('file-type');
".3ga" : "audio/3ga", const readChunk = require('read-chunk');
".mp4" : "video/mp4",
".wav" : "audio/wav",
".mov" : "video/quicktime",
".doc" : "application/msword",
".dot" : "application/msword",
".docx" : "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
".dotx" : "application/vnd.openxmlformats-officedocument.wordprocessingml.template",
".docm" : "application/vnd.ms-word.document.macroEnabled.12",
".dotm" : "application/vnd.ms-word.template.macroEnabled.12",
".xls" : "application/vnd.ms-excel",
".xlt" : "application/vnd.ms-excel",
".xla" : "application/vnd.ms-excel",
".xlsx" : "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
".xltx" : "application/vnd.openxmlformats-officedocument.spreadsheetml.template",
".xlsm" : "application/vnd.ms-excel.sheet.macroEnabled.12",
".xltm" : "application/vnd.ms-excel.template.macroEnabled.12",
".xlam" : "application/vnd.ms-excel.addin.macroEnabled.12",
".xlsb" : "application/vnd.ms-excel.sheet.binary.macroEnabled.12",
".ppt" : "application/vnd.ms-powerpoint",
".pot" : "application/vnd.ms-powerpoint",
".pps" : "application/vnd.ms-powerpoint",
".ppa" : "application/vnd.ms-powerpoint",
".pptx" : "application/vnd.openxmlformats-officedocument.presentationml.presentation",
".potx" : "application/vnd.openxmlformats-officedocument.presentationml.template",
".ppsx" : "application/vnd.openxmlformats-officedocument.presentationml.slideshow",
".ppam" : "application/vnd.ms-powerpoint.addin.macroEnabled.12",
".pptm" : "application/vnd.ms-powerpoint.presentation.macroEnabled.12",
".potm" : "application/vnd.ms-powerpoint.template.macroEnabled.12",
".ppsm" : "application/vnd.ms-powerpoint.slideshow.macroEnabled.12",
".key" : "application/x-iwork-keynote-sffkey",
".pages" : "application/x-iwork-pages-sffpages",
".numbers" : "application/x-iwork-numbers-sffnumbers",
".ttf" : "application/x-font-ttf"
};
const convertableImageTypes = [ const convertableImageTypes = [
"image/png", "image/png",
@ -68,9 +35,9 @@ const convertableVideoTypes = [
const convertableAudioTypes = [ const convertableAudioTypes = [
"application/ogg", "application/ogg",
"audio/AMR", "audio/amr",
"audio/3ga", "audio/3ga",
"audio/wav", "audio/wave",
"audio/3gpp", "audio/3gpp",
"audio/x-wav", "audio/x-wav",
"audio/aiff", "audio/aiff",
@ -125,14 +92,14 @@ function createWaveform(fileName, localFilePath, callback){
}); });
} }
function convertVideo(fileName, filePath, codec, callback, progress_callback) { function convertVideo(fileName, filePath, codec, callback, progressCallback) {
var ext = path.extname(fileName); var ext = path.extname(fileName);
var presetMime = fileExtensionMap[ext]; var presetMime = mime.lookup(fileName);
var newExt = codec == "mp4" ? "mp4" : "ogv"; var newExt = codec == "mp4" ? "mp4" : "ogv";
var convertedPath = filePath + "." + newExt; var convertedPath = filePath + "." + newExt;
console.log("converting", filePath, "to", convertedPath, "progress_cb:",progress_callback); console.log("converting", filePath, "to", convertedPath);
var convertArgs = (codec == "mp4") ? [ var convertArgs = (codec == "mp4") ? [
"-i", filePath, "-i", filePath,
@ -167,8 +134,8 @@ function convertVideo(fileName, filePath, codec, callback, progress_callback) {
ff.stderr.on('data', function (data) { ff.stderr.on('data', function (data) {
console.log('[ffmpeg-video] stderr: ' + data); console.log('[ffmpeg-video] stderr: ' + data);
if (progress_callback) { if (progressCallback) {
progress_callback(data); progressCallback(data);
} }
}); });
@ -185,7 +152,7 @@ function convertVideo(fileName, filePath, codec, callback, progress_callback) {
function convertAudio(fileName, filePath, codec, callback) { function convertAudio(fileName, filePath, codec, callback) {
var ext = path.extname(fileName); var ext = path.extname(fileName);
var presetMime = fileExtensionMap[ext]; var presetMime = mime.lookup(fileName);
var newExt = codec == "mp3" ? "mp3" : "ogg"; var newExt = codec == "mp3" ? "mp3" : "ogg";
var convertedPath = filePath + "." + newExt; var convertedPath = filePath + "." + newExt;
@ -222,22 +189,14 @@ function createThumbnailForVideo(fileName, filePath, callback) {
function getMime(fileName, filePath, callback) { function getMime(fileName, filePath, callback) {
var ext = path.extname(fileName); var ext = path.extname(fileName);
var presetMime = fileExtensionMap[ext]; var presetMime = mime.lookup(fileName);
if (presetMime) { if (presetMime) {
callback(null, presetMime); callback(null, presetMime);
} else { } else {
exec.execFile("file", ["-b","--mime-type", filePath], {}, function(error, stdout, stderr) { const buffer = readChunk.sync(filePath, 0, 4100);
console.log("file stdout: ",stdout); var mimeType = fileType(buffer);
if (stderr === '' && error == null) { callback(null, mimeType);
//filter special chars from commandline
var mime = stdout.replace(/[^a-zA-Z0-9\/\-]/g,'');
callback(null, mime);
} else {
console.log("getMime file error: ", error);
callback(error, null);
}
});
} }
} }
@ -245,13 +204,12 @@ function resizeAndUpload(a, size, max, fileName, localFilePath, callback) {
if (max>320 || size.width > max || size.height > max) { if (max>320 || size.width > max || size.height > max) {
var resizedFileName = max + "_"+fileName; var resizedFileName = max + "_"+fileName;
var s3Key = "s"+ a.space_id.toString() + "/a" + a._id.toString() + "/" + resizedFileName; var s3Key = "s"+ a.space_id.toString() + "/a" + a._id.toString() + "/" + resizedFileName;
var localResizedFilePath = "/tmp/"+resizedFileName; var localResizedFilePath = os.tmpdir()+"/"+resizedFileName;
gm(localFilePath).resize(max, max).autoOrient().write(localResizedFilePath, function (err) { gm(localFilePath).resize(max, max).autoOrient().write(localResizedFilePath, function (err) {
if(!err) { if(!err) {
uploader.uploadFile(s3Key, "image/jpeg", localResizedFilePath, function(err, url) { uploader.uploadFile(s3Key, "image/jpeg", localResizedFilePath, function(err, url) {
if (err) callback(err); if (err) callback(err);
else{ else{
console.log(localResizedFilePath);
fs.unlink(localResizedFilePath, function (err) { fs.unlink(localResizedFilePath, function (err) {
if (err) { if (err) {
console.error(err); console.error(err);
@ -272,7 +230,7 @@ function resizeAndUpload(a, size, max, fileName, localFilePath, callback) {
} }
var resizeAndUploadImage = function(a, mime, size, fileName, fileNameOrg, imageFilePath, originalFilePath, payloadCallback) { var resizeAndUploadImage = function(a, mimeType, size, fileName, fileNameOrg, imageFilePath, originalFilePath, payloadCallback) {
async.parallel({ async.parallel({
small: function(callback){ small: function(callback){
resizeAndUpload(a, size, 320, fileName, imageFilePath, callback); resizeAndUpload(a, size, 320, fileName, imageFilePath, callback);
@ -285,13 +243,13 @@ var resizeAndUploadImage = function(a, mime, size, fileName, fileNameOrg, imageF
}, },
original: function(callback){ original: function(callback){
var s3Key = "s"+ a.space_id.toString() + "/a" + a._id + "/" + fileNameOrg; var s3Key = "s"+ a.space_id.toString() + "/a" + a._id + "/" + fileNameOrg;
uploader.uploadFile(s3Key, mime, originalFilePath, function(err, url){ uploader.uploadFile(s3Key, mimeType, originalFilePath, function(err, url){
callback(null, url); callback(null, url);
}); });
} }
}, function(err, results) { }, function(err, results) {
a.state = "idle"; a.state = "idle";
a.mime = mime; a.mime = mimeType;
var stats = fs.statSync(originalFilePath); var stats = fs.statSync(originalFilePath);
a.payload_size = stats["size"]; a.payload_size = stats["size"];
@ -301,46 +259,43 @@ var resizeAndUploadImage = function(a, mime, size, fileName, fileNameOrg, imageF
a.payload_uri = results.original; a.payload_uri = results.original;
var factor = 320/size.width; var factor = 320/size.width;
var newBoardSpecs = a.board; a.w = Math.round(size.width*factor);
newBoardSpecs.w = Math.round(size.width*factor); a.h = Math.round(size.height*factor);
newBoardSpecs.h = Math.round(size.height*factor);
a.board = newBoardSpecs;
a.updated_at = new Date(); a.updated_at = new Date();
a.save(function(err) { db.packArtifact(a);
if(err) payloadCallback(err, null);
else { a.save().then(function() {
fs.unlink(originalFilePath, function (err) { fs.unlink(originalFilePath, function (err) {
if (err){ if (err){
console.error(err); console.error(err);
payloadCallback(err, null); payloadCallback(err, null);
} else { } else {
console.log('successfully deleted ' + originalFilePath); console.log('successfully deleted ' + originalFilePath);
payloadCallback(null, a); payloadCallback(null, a);
} }
}); });
}
}); });
}); });
}; };
module.exports = { module.exports = {
convert: function(a, fileName, localFilePath, payloadCallback, progress_callback) { convert: function(a, fileName, localFilePath, payloadCallback, progressCallback) {
getMime(fileName, localFilePath, function(err, mime){ getMime(fileName, localFilePath, function(err, mimeType){
console.log("[convert] fn: "+fileName+" local: "+localFilePath+" mime:", mime); console.log("[convert] fn: "+fileName+" local: "+localFilePath+" mimeType:", mimeType);
if (!err) { if (!err) {
if (convertableImageTypes.indexOf(mime) > -1) { if (convertableImageTypes.indexOf(mimeType) > -1) {
gm(localFilePath).size(function (err, size) { gm(localFilePath).size(function (err, size) {
console.log("[convert] gm:", err, size); console.log("[convert] gm:", err, size);
if (!err) { if (!err) {
if(mime == "application/pdf") { if(mimeType == "application/pdf") {
var firstImagePath = localFilePath + ".jpeg"; var firstImagePath = localFilePath + ".jpeg";
exec.execFile("gs", ["-sDEVICE=jpeg","-dNOPAUSE", "-dJPEGQ=80", "-dBATCH", "-dFirstPage=1", "-dLastPage=1", "-sOutputFile=" + firstImagePath, "-r90", "-f", localFilePath], {}, function(error, stdout, stderr) { exec.execFile("gs", ["-sDEVICE=jpeg","-dNOPAUSE", "-dJPEGQ=80", "-dBATCH", "-dFirstPage=1", "-dLastPage=1", "-sOutputFile=" + firstImagePath, "-r90", "-f", localFilePath], {}, function(error, stdout, stderr) {
if(error === null) { if(error === null) {
resizeAndUploadImage(a, mime, size, fileName + ".jpeg", fileName, firstImagePath, localFilePath, function(err, a) { resizeAndUploadImage(a, mimeType, size, fileName + ".jpeg", fileName, firstImagePath, localFilePath, function(err, a) {
fs.unlink(firstImagePath, function (err) { fs.unlink(firstImagePath, function (err) {
payloadCallback(err, a); payloadCallback(err, a);
}); });
@ -350,19 +305,19 @@ module.exports = {
} }
}); });
} else if(mime == "image/gif") { } else if(mimeType == "image/gif") {
//gifs are buggy after convertion, so we should not convert them //gifs are buggy after convertion, so we should not convert them
var s3Key = "s"+ a.space_id.toString() + "/a" + a._id.toString() + "/" + fileName; var s3Key = "s"+ a.space_id.toString() + "/a" + a._id.toString() + "/" + fileName;
uploader.uploadFile(s3Key, "image/gif", localFilePath, function(err, url) { uploader.uploadFile(s3Key, "image/gif", localFilePath, function(err, url) {
if(err)callback(err); if (err) payloadCallback(err);
else{ else {
console.log(localFilePath); console.log(localFilePath);
var stats = fs.statSync(localFilePath); var stats = fs.statSync(localFilePath);
a.state = "idle"; a.state = "idle";
a.mime = mime; a.mime = mimeType;
a.payload_size = stats["size"]; a.payload_size = stats["size"];
a.payload_thumbnail_web_uri = url; a.payload_thumbnail_web_uri = url;
@ -371,42 +326,39 @@ module.exports = {
a.payload_uri = url; a.payload_uri = url;
var factor = 320/size.width; var factor = 320/size.width;
var newBoardSpecs = a.board; a.w = Math.round(size.width*factor);
newBoardSpecs.w = Math.round(size.width*factor); a.h = Math.round(size.height*factor);
newBoardSpecs.h = Math.round(size.height*factor);
a.board = newBoardSpecs;
a.updated_at = new Date(); a.updated_at = new Date();
a.save(function(err){ db.packArtifact(a);
if(err) payloadCallback(err, null);
else { a.save().then(function() {
fs.unlink(localFilePath, function (err) { fs.unlink(localFilePath, function (err) {
if (err){ if (err){
console.error(err); console.error(err);
payloadCallback(err, null); payloadCallback(err, null);
} else { } else {
console.log('successfully deleted ' + localFilePath); console.log('successfully deleted ' + localFilePath);
payloadCallback(null, a); payloadCallback(null, a);
} }
}); });
}
}); });
} }
}); });
} else { } else {
resizeAndUploadImage(a, mime, size, fileName, fileName, localFilePath, localFilePath, payloadCallback); resizeAndUploadImage(a, mimeType, size, fileName, fileName, localFilePath, localFilePath, payloadCallback);
} }
} else payloadCallback(err); } else payloadCallback(err);
}); });
} else if (convertableVideoTypes.indexOf(mime) > -1) { } else if (convertableVideoTypes.indexOf(mimeType) > -1) {
async.parallel({ async.parallel({
thumbnail: function(callback) { thumbnail: function(callback) {
createThumbnailForVideo(fileName, localFilePath, function(err, created){ createThumbnailForVideo(fileName, localFilePath, function(err, created){
console.log("thumbnail created: ", err, created); console.log("thumbnail created: ", err, created);
if(err) callback(err); if (err) callback(err);
else{ else {
var keyName = "s" + a.space_id.toString() + "/a" + a._id.toString() + "/" + fileName + ".jpg" ; var keyName = "s" + a.space_id.toString() + "/a" + a._id.toString() + "/" + fileName + ".jpg" ;
uploader.uploadFile(keyName, "image/jpeg", created, function(err, url){ uploader.uploadFile(keyName, "image/jpeg", created, function(err, url){
if (err) callback(err); if (err) callback(err);
@ -416,7 +368,7 @@ module.exports = {
}); });
}, },
ogg: function(callback) { ogg: function(callback) {
if (mime == "video/ogg") { if (mimeType == "video/ogg") {
callback(null, "org"); callback(null, "org");
} else { } else {
convertVideo(fileName, localFilePath, "ogg", function(err, file) { convertVideo(fileName, localFilePath, "ogg", function(err, file) {
@ -428,11 +380,11 @@ module.exports = {
else callback(null, url); else callback(null, url);
}); });
} }
}, progress_callback); }, progressCallback);
} }
}, },
mp4: function(callback) { mp4: function(callback) {
if (mime == "video/mp4") { if (mimeType == "video/mp4") {
callback(null, "org"); callback(null, "org");
} else { } else {
convertVideo(fileName, localFilePath, "mp4", function(err, file) { convertVideo(fileName, localFilePath, "mp4", function(err, file) {
@ -444,21 +396,21 @@ module.exports = {
else callback(null, url); else callback(null, url);
}); });
} }
}, progress_callback); }, progressCallback);
} }
}, },
original: function(callback){ original: function(callback){
uploader.uploadFile(fileName, mime, localFilePath, function(err, url){ uploader.uploadFile(fileName, mimeType, localFilePath, function(err, url){
callback(null, url); callback(null, url);
}); });
} }
}, function(err, results){ }, function(err, results) {
console.log(err, results); console.log(err, results);
if (err) payloadCallback(err, a); if (err) payloadCallback(err, a);
else { else {
a.state = "idle"; a.state = "idle";
a.mime = mime; a.mime = mimeType;
var stats = fs.statSync(localFilePath); var stats = fs.statSync(localFilePath);
a.payload_size = stats["size"]; a.payload_size = stats["size"];
@ -467,7 +419,7 @@ module.exports = {
a.payload_thumbnail_big_uri = results.thumbnail; a.payload_thumbnail_big_uri = results.thumbnail;
a.payload_uri = results.original; a.payload_uri = results.original;
if (mime == "video/mp4") { if (mimeType == "video/mp4") {
a.payload_alternatives = [ a.payload_alternatives = [
{ {
mime: "video/ogg", mime: "video/ogg",
@ -483,25 +435,24 @@ module.exports = {
]; ];
} }
db.packArtifact(a);
a.updated_at = new Date(); a.updated_at = new Date();
a.save(function(err) { a.save().then(function() {
if (err) payloadCallback(err, null); fs.unlink(localFilePath, function (err) {
else { if (err) {
fs.unlink(localFilePath, function (err) { console.error(err);
if (err){ payloadCallback(err, null);
console.error(err); } else {
payloadCallback(err, null); console.log('successfully deleted ' + localFilePath);
} else { payloadCallback(null, a);
console.log('successfully deleted ' + localFilePath); }
payloadCallback(null, a); });
}
});
}
}); });
} }
}); });
} else if (convertableAudioTypes.indexOf(mime) > -1) { } else if (convertableAudioTypes.indexOf(mimeType) > -1) {
async.parallel({ async.parallel({
ogg: function(callback) { ogg: function(callback) {
@ -539,7 +490,7 @@ module.exports = {
}, },
original: function(callback) { original: function(callback) {
var keyName = "s" + a.space_id.toString() + "/a" + a._id.toString() + "/" + fileName; var keyName = "s" + a.space_id.toString() + "/a" + a._id.toString() + "/" + fileName;
uploader.uploadFile(keyName, mime, localFilePath, function(err, url){ uploader.uploadFile(keyName, mimeType, localFilePath, function(err, url){
callback(null, url); callback(null, url);
}); });
} }
@ -550,7 +501,7 @@ module.exports = {
else { else {
a.state = "idle"; a.state = "idle";
a.mime = mime; a.mime = mimeType;
var stats = fs.statSync(localFilePath); var stats = fs.statSync(localFilePath);
a.payload_size = stats["size"]; a.payload_size = stats["size"];
@ -564,43 +515,40 @@ module.exports = {
]; ];
a.updated_at = new Date(); a.updated_at = new Date();
a.save(function(err){
if(err) payloadCallback(err, null); db.packArtifact(a);
else {
fs.unlink(localFilePath, function (err) { a.save().then(function(){
if (err){ fs.unlink(localFilePath, function (err) {
console.error(err); if (err){
payloadCallback(err, null); console.error(err);
} else { payloadCallback(err, null);
console.log('successfully deleted ' + localFilePath); } else {
payloadCallback(null, a); console.log('successfully deleted ' + localFilePath);
} payloadCallback(null, a);
}); }
} });
}); });
} }
}); });
} else { } else {
console.log("mime not matched for conversion, storing file"); console.log("mimeType not matched for conversion, storing file");
var keyName = "s" + a.space_id.toString() + "/a" + a._id.toString() + "/" + fileName; var keyName = "s" + a.space_id.toString() + "/a" + a._id.toString() + "/" + fileName;
uploader.uploadFile(keyName, mime, localFilePath, function(err, url) { uploader.uploadFile(keyName, mimeType, localFilePath, function(err, url) {
a.state = "idle"; a.state = "idle";
a.mime = mime; a.mime = mimeType;
var stats = fs.statSync(localFilePath); var stats = fs.statSync(localFilePath);
a.payload_size = stats["size"]; a.payload_size = stats["size"];
a.payload_uri = url; a.payload_uri = url;
a.updated_at = new Date(); a.updated_at = new Date();
a.save(function(err) { a.save().then(function() {
if(err) payloadCallback(err, null); fs.unlink(localFilePath, function (err) {
else { payloadCallback(null, a);
fs.unlink(localFilePath, function (err) { });
payloadCallback(null, a);
});
}
}); });
}); });
} }

128
helpers/importer.js Normal file
View File

@ -0,0 +1,128 @@
'use strict';
const extract = require('extract-zip')
const config = require('config')
const fs = require('fs')
const path = require('path')
const db = require('../models/db')
const Sequelize = require('sequelize')
const Op = Sequelize.Op
const uuidv4 = require('uuid/v4')
require('../models/db')
module.exports = {
importZIP: function(user, zipPath) {
// 1. extract zip to local storage folder
// 2. read spaces.json from this folder
// 3. iterate through spaces and read all their artifact jsons
// 4. fixup storage paths
// 5. replace creator id by user._id
let relativeImportDir = 'import_'+user._id
let importDir = path.resolve(config.get('storage_local_path')+'/'+config.get('storage_bucket')+'/'+relativeImportDir)
if (!fs.existsSync(importDir)) {
fs.mkdirSync(importDir)
}
extract(zipPath, {dir: importDir}, function(err) {
if (err) {
console.log(err)
return
}
console.log('[import] extracted to',importDir)
let spacesJson = fs.readFileSync(importDir+'/spaces.json')
let spaces = JSON.parse(spacesJson)
var homeFolderId = null
console.log('[import] spaces:',spaces.length)
// pass 1: find homefolder
for (var i=0; i<spaces.length; i++) {
let space = spaces[i]
if (!space.parent_space_id) {
homeFolderId = space._id
break
}
}
console.log("[import] homeFolderId:",homeFolderId)
for (var i=0; i<spaces.length; i++) {
let space = spaces[i]
if (space.parent_space_id) {
let artifacts = JSON.parse(fs.readFileSync(importDir+'/'+space._id+'_artifacts.json'))
console.log('[import] space',space._id,'artifacts:',artifacts.length)
//let q = {where: {_id: space._id}}
space.creator_id = user._id
delete space.__v
// transplant homefolder
console.log("parent:",space.parent_space_id)
if (space.parent_space_id+"" == homeFolderId+"") {
space.parent_space_id = user.home_folder_id
}
// move nested attrs
console.log(space)
for (k in space.advanced) {
space[k] = space.advanced[k]
}
db.Space.create(space)
.error((err) => {
console.error("[import] space upsert err:",err)
})
for (var j=0; j<artifacts.length; j++) {
let a = artifacts[j]
let q = {_id: a._id}
a.user_id = user._id
delete a.__v
delete a.payload_thumbnail_big_uri
// move nested attrs
for (k in a.style) {
a[k] = a.style[k]
}
for (k in a.meta) {
a[k] = a.meta[k]
}
for (k in a.board) {
a[k] = a.board[k]
}
let prefix = "/storage/"+relativeImportDir+"/"+space._id+"_files/"
if (a.thumbnail_uri && a.thumbnail_uri[0]!='/') a.thumbnail_uri = prefix + a.thumbnail_uri
if (a.payload_uri && a.payload_uri[0]!='/') a.payload_uri = prefix + a.payload_uri
if (a.payload_thumbnail_web_uri && a.payload_thumbnail_web_uri[0]!='/') a.payload_thumbnail_web_uri = prefix + a.payload_thumbnail_web_uri
if (a.payload_thumbnail_medium_uri && a.payload_thumbnail_medium_uri[0]!='/') a.payload_thumbnail_medium_uri = prefix + a.payload_thumbnail_medium_uri
if (a.payload_alternatives) {
for (var k=0; k<a.payload_alternatives.length; k++) {
let alt = a.payload_alternatives[k]
if (alt.payload_uri && alt.payload_uri[0]!='/') alt.payload_uri = prefix + alt.payload_uri
if (alt.payload_thumbnail_web_uri && alt.payload_thumbnail_web_uri[0]!='/') alt.payload_thumbnail_web_uri = prefix + alt.payload_thumbnail_web_uri
if (alt.payload_thumbnail_medium_uri && alt.payload_thumbnail_medium_uri[0]!='/') alt.payload_thumbnail_medium_uri = prefix + alt.payload_thumbnail_medium_uri
}
}
db.packArtifact(a)
db.Artifact.create(a).error(function(err) {
console.error("[import] artifact upsert err:",err)
})
}
}
}
})
}
}

View File

@ -1,18 +1,18 @@
'use strict'; 'use strict';
var swig = require('swig'); const config = require('config');
var AWS = require('aws-sdk'); const nodemailer = require('nodemailer');
const swig = require('swig');
//var AWS = require('aws-sdk');
module.exports = { module.exports = {
sendMail: (to_email, subject, body, options) => { sendMail: (to_email, subject, body, options) => {
if (!options) { if (!options) {
options = {}; options = {};
} }
// FIXME const teamname = options.teamname || config.get('team_name');
const teamname = options.teamname || "My Open Spacedeck" const from = teamname + ' <' + config.get('contact_email') + '>';
const from = teamname + ' <support@example.org>';
let reply_to = [from]; let reply_to = [from];
if (options.reply_to) { if (options.reply_to) {
@ -29,33 +29,38 @@ module.exports = {
options: options options: options
}); });
if (process.env.NODE_ENV === 'development') { if (config.get('mail_provider') === 'console') {
console.log("Email: to " + to_email + " in production.\nreply_to: " + reply_to + "\nsubject: " + subject + "\nbody: \n" + htmlText + "\n\n plaintext:\n" + plaintext);
} else {
AWS.config.update({region: 'eu-west-1'});
var ses = new AWS.SES();
ses.sendEmail( { console.log("Email: to " + to_email + " in production.\nreply_to: " + reply_to + "\nsubject: " + subject + "\nbody: \n" + htmlText + "\n\n plaintext:\n" + plaintext);
Source: from,
Destination: { ToAddresses: [to_email] }, } else if (config.get('mail_provider') === 'smtp') {
ReplyToAddresses: reply_to,
Message: { const transporter = nodemailer.createTransport({
Subject: { host: config.get('mail_smtp_host'),
Data: subject port: config.get('mail_smtp_port'),
}, secure: config.get('mail_smtp_secure'),
Body: { requireTLS: config.get('mail_smtp_require_tls'),
Text: { auth: {
Data: plaintext, user: config.get('mail_smtp_user'),
}, pass: config.get('mail_smtp_pass'),
Html: {
Data: htmlText
}
}
} }
}, function(err, data) {
if(err) console.log('Email not sent:', err);
else console.log("Email sent.");
}); });
transporter.sendMail({
from: from,
replyTo: reply_to,
to: to_email,
subject: subject,
text: plaintext,
html: htmlText,
}, function(err, info) {
if (err) {
console.error("Error sending email:", err);
} else {
console.log("Email sent.");
}
});
} }
} }
}; };

View File

@ -1,8 +1,9 @@
'use strict'; 'use strict';
require('../models/schema'); const db = require('../models/db');
var config = require('config'); const config = require('config');
var phantom = require('node-phantom-simple'); const phantom = require('node-phantom-simple');
const os = require('os');
module.exports = { module.exports = {
// type = "pdf" or "png" // type = "pdf" or "png"
@ -10,7 +11,7 @@ module.exports = {
var spaceId = space._id; var spaceId = space._id;
var space_url = config.get("endpoint")+"/api/spaces/"+spaceId+"/html"; var space_url = config.get("endpoint")+"/api/spaces/"+spaceId+"/html";
var export_path = "/tmp/"+spaceId+"."+type; var export_path = os.tmpdir()+"/"+spaceId+"."+type;
var timeout = 5000; var timeout = 5000;
if (type=="pdf") timeout = 30000; if (type=="pdf") timeout = 30000;
@ -24,7 +25,7 @@ module.exports = {
var on_exit = function(exit_code) { var on_exit = function(exit_code) {
if (exit_code>0) { if (exit_code>0) {
console.log("phantom abnormal exit for url "+space_url); console.error("phantom abnormal exit for url "+space_url);
if (!on_success_called && on_error) { if (!on_success_called && on_error) {
on_error(); on_error();
} }
@ -32,31 +33,36 @@ module.exports = {
}; };
phantom.create({ path: require('phantomjs-prebuilt').path }, function (err, browser) { phantom.create({ path: require('phantomjs-prebuilt').path }, function (err, browser) {
return browser.createPage(function (err, page) { if (err) {
console.log("page created, opening ",space_url); console.error(err);
} else {
return browser.createPage(function (err, page) {
console.log("page created, opening ",space_url);
if (type=="pdf") { if (type=="pdf") {
var psz = { var psz = {
width: space.advanced.width+"px", width: space.width+"px",
height: space.advanced.height+"px" height: space.height+"px"
}; };
page.set('paperSize', psz); page.set('paperSize', psz);
} }
page.set('settings.resourceTimeout',timeout); page.set('settings.resourceTimeout',timeout);
page.set('settings.javascriptEnabled',false); page.set('settings.javascriptEnabled',false);
return page.open(space_url, function (err,status) { return page.open(space_url, function (err,status) {
page.render(export_path, function() { page.render(export_path, function() {
on_success_called = true; on_success_called = true;
if (on_success) { if (on_success) {
on_success(export_path); on_success(export_path);
} }
page.close(); page.close();
browser.exit(); browser.exit();
});
}); });
}); });
}); }
}, { }, {
onExit: on_exit onExit: on_exit
}); });

View File

@ -1,14 +1,111 @@
'use strict'; 'use strict';
const RedisConnection = require('ioredis'); const config = require('config');
const websockets = require('./websockets');
// this is a mock version of the Redis API,
// emulating Redis if it is not available locally
var notRedis = {
state: {},
topics: {},
publish: function(topic, msg, cb) {
if (!this.topics[topic]) {
this.topics[topic] = {
subscribers: []
};
}
var t=this.topics[topic];
for (var i=0; i<t.subscribers.length; i++) {
var s=t.subscribers[i];
if (s.handler) {
s.handler(topic, msg);
}
}
if (cb) cb(null);
},
subscribe: function(topics, cb) {
var handle = {
handler: null,
on: function(evt, cb) {
if (evt == "message") {
this.handler = cb;
}
}
};
for (var i=0; i<topics.length; i++) {
var topic = topics[i];
if (!this.topics[topic]) {
this.topics[topic] = {
subscribers: []
};
}
var t=this.topics[topic];
t.subscribers.push(handle);
}
cb(null, topics.length);
return handle;
},
get: function(key, cb) {
cb(null, this.state[key]);
return this.state[key];
},
set: function(key, val, cb) {
this.state[key] = val;
cb();
},
del: function(key, cb) {
delete this.state[key];
cb(null);
},
sadd: function(key, skey, cb) {
if (!this.state[key]) this.state[key] = {};
this.state[key][skey] = true;
cb(null);
},
srem: function(key, skey, cb) {
if (this.state[key]) {
delete this.state[key][skey];
}
cb(null);
},
smembers: function(key, cb) {
cb(null, Object.keys(this.state[key]));
},
incr: function(key, cb) {
if (!this.state[key]) this.state[key] = 0;
this.state[key]++;
cb(null, this.state[key]);
},
expire: function() {
},
}
module.exports = { module.exports = {
connectRedis(){ connectRedis: function() {
const redisHost = process.env.REDIS_PORT_6379_TCP_ADDR || 'localhost'; if (config.get("redis_mock")) {
this.connection = new RedisConnection(6379, redisHost); this.connection = notRedis;
} else {
const redisHost = process.env.REDIS_PORT_6379_TCP_ADDR || 'sync';
this.connection = new RedisConnection(6379, redisHost);
}
}, },
sendMessage(action, model, attributes, channelId) { getConnection: function() {
this.connectRedis();
return this.connection;
},
sendMessage: function(action, model, attributes, channelId) {
const data = JSON.stringify({ const data = JSON.stringify({
channel_id: channelId, channel_id: channelId,
action: action, action: action,
@ -17,12 +114,12 @@ module.exports = {
}); });
this.connection.publish('updates', data); this.connection.publish('updates', data);
}, },
logIp(ip, cb) { logIp: function(ip, cb) {
this.connection.incr("ip_"+ ip, (err, socketCounter) => { this.connection.incr("ip_"+ ip, (err, socketCounter) => {
cb(); cb();
}); });
}, },
rateLimit(namespace, ip, cb) { rateLimit: function(namespace, ip, cb) {
const key = "limit_"+ namespace + "_"+ ip; const key = "limit_"+ namespace + "_"+ ip;
const redis = this.connection; const redis = this.connection;
@ -47,7 +144,7 @@ module.exports = {
} }
}); });
}, },
isOnlineInSpace(user, space, cb) { isOnlineInSpace: function(user, space, cb) {
this.connection.smembers("space_" + space._id.toString(), function(err, list) { this.connection.smembers("space_" + space._id.toString(), function(err, list) {
if (err) cb(err); if (err) cb(err);
else { else {
@ -59,3 +156,6 @@ module.exports = {
}); });
} }
}; };
return module.exports;

View File

@ -1,16 +1,41 @@
'use strict'; 'use strict';
var AWS = require('aws-sdk');
AWS.config.region = 'eu-central-1';
var fs = require('fs'); var fs = require('fs');
var config = require('config'); var config = require('config');
var s3 = null;
// use AWS S3 or local folder depending on config
if (config.get("storage_local_path")) {
var AWS = require('mock-aws-s3');
AWS.config.basePath = config.get("storage_local_path");
s3 = new AWS.S3();
} else {
var AWS = require('aws-sdk');
var storage_endpoint = config.get("storage_endpoint");
const ep = new AWS.Endpoint(storage_endpoint);
AWS.config.update(new AWS.Config({
accessKeyId: process.env.MINIO_ACCESS_KEY,
secretAccessKey: process.env.MINIO_SECRET_KEY,
region: config.get("storage_region"),
s3ForcePathStyle: true,
signatureVersion: 'v4'
}));
s3 = new AWS.S3({
endpoint: ep
});
}
s3.createBucket({
Bucket: config.get("storage_bucket"),
ACL: "public-read",
GrantRead: "*"
}, (err,res) => {
console.log("createBucket",err,res);
});
module.exports = { module.exports = {
removeFile: (path, callback) => { removeFile: (path, callback) => {
const s3 = new AWS.S3({
region: 'eu-central-1'
});
const bucket = config.get("storage_bucket"); const bucket = config.get("storage_bucket");
s3.deleteObject({ s3.deleteObject({
Bucket: bucket, Key: path Bucket: bucket, Key: path
@ -28,7 +53,7 @@ module.exports = {
callback({error:"missing path"}, null); callback({error:"missing path"}, null);
return; return;
} }
console.log("[s3] uploading", localFilePath, " to ", fileName); console.log("[storage] uploading", localFilePath, " to ", fileName);
const bucket = config.get("storage_bucket"); const bucket = config.get("storage_bucket");
const fileStream = fs.createReadStream(localFilePath); const fileStream = fs.createReadStream(localFilePath);
@ -39,11 +64,6 @@ module.exports = {
} }
}); });
fileStream.on('open', function () { fileStream.on('open', function () {
// FIXME
var s3 = new AWS.S3({
region: 'eu-central-1'
});
s3.putObject({ s3.putObject({
Bucket: bucket, Bucket: bucket,
Key: fileName, Key: fileName,
@ -53,8 +73,8 @@ module.exports = {
if (err){ if (err){
console.error(err); console.error(err);
callback(err); callback(err);
}else { } else {
const url = "https://"+ config.get("storage_cdn") + "/" + fileName; const url = config.get("storage_cdn") + "/" + fileName;
console.log("[s3]" + localFilePath + " to " + url); console.log("[s3]" + localFilePath + " to " + url);
callback(null, url); callback(null, url);
} }

View File

@ -1,20 +1,30 @@
'use strict'; 'use strict';
require('../models/schema');
const db = require('../models/db');
const Sequelize = require('sequelize');
const Op = Sequelize.Op;
const config = require('config');
const WebSocketServer = require('ws').Server; const WebSocketServer = require('ws').Server;
const Redis = require('ioredis'); //const RedisConnection = require('ioredis');
const async = require('async'); const async = require('async');
const _ = require("underscore"); const _ = require("underscore");
const mongoose = require("mongoose");
const crypto = require('crypto'); const crypto = require('crypto');
module.exports = { const redisMock = require("./redis.js");
startWebsockets: function(server){
this.setupSubscription();
this.state = new Redis(6379, process.env.REDIS_PORT_6379_TCP_ADDR || 'localhost');
if(!this.current_websockets){ module.exports = {
startWebsockets: function(server) {
this.setupSubscription();
if (!this.current_websockets) {
if (config.get("redis_mock")) {
this.state = redisMock.getConnection();
} else {
this.state = new RedisConnection(6379, process.env.REDIS_PORT_6379_TCP_ADDR || config.get("redis_host"));
}
this.current_websockets = []; this.current_websockets = [];
} }
@ -37,11 +47,11 @@ module.exports = {
const editorAuth = msg.editor_auth; const editorAuth = msg.editor_auth;
const spaceId = msg.space_id; const spaceId = msg.space_id;
Space.findOne({"_id": spaceId}).populate('creator').exec((err, space) => { db.Space.findOne({where: {"_id": spaceId}}).then(space => {
if (space) { if (space) {
const upgradeSocket = function() { const upgradeSocket = function() {
if (token) { if (token) {
User.findBySessionToken(token, function(err, user) { db.findUserBySessionToken(token, function(err, user) {
if (err) { if (err) {
console.error(err, user); console.error(err, user);
} else { } else {
@ -117,10 +127,17 @@ module.exports = {
}, },
setupSubscription: function() { setupSubscription: function() {
this.cursorSubscriber = new Redis(6379, process.env.REDIS_PORT_6379_TCP_ADDR || 'localhost'); if (config.get("redis_mock")) {
this.cursorSubscriber.subscribe(['cursors', 'users', 'updates'], function (err, count) { this.cursorSubscriber = redisMock.getConnection().subscribe(['cursors', 'users', 'updates'], function (err, count) {
console.log("[redis] websockets to " + count + " topics." ); console.log("[redis-mock] websockets subscribed to " + count + " topics." );
}); });
} else {
this.cursorSubscriber = new RedisConnection(6379, process.env.REDIS_PORT_6379_TCP_ADDR || config.get("redis_host"));
this.cursorSubscriber.subscribe(['cursors', 'users', 'updates'], function (err, count) {
console.log("[redis] websockets subscribed to " + count + " topics." );
});
}
this.cursorSubscriber.on('message', function (channel, rawMessage) { this.cursorSubscriber.on('message', function (channel, rawMessage) {
const msg = JSON.parse(rawMessage); const msg = JSON.parse(rawMessage);
const spaceId = msg.space_id; const spaceId = msg.space_id;
@ -206,7 +223,7 @@ module.exports = {
console.log("websocket not found to remove"); console.log("websocket not found to remove");
} }
this.state.del(ws.id, function(err, res) { this.state.del(ws.id+"", function(err, res) {
if (err) console.error(err, res); if (err) console.error(err, res);
else { else {
this.removeUserInSpace(ws.space_id, ws, (err) => { this.removeUserInSpace(ws.space_id, ws, (err) => {
@ -221,7 +238,8 @@ module.exports = {
addUserInSpace: function(username, space, ws, cb) { addUserInSpace: function(username, space, ws, cb) {
console.log("[websockets] user "+username+" in "+space.access_mode +" space " + space._id + " with socket " + ws.id); console.log("[websockets] user "+username+" in "+space.access_mode +" space " + space._id + " with socket " + ws.id);
this.state.set(ws.id, username, function(err, res) {
this.state.set(ws.id+"", username+"", function(err, res) {
if(err) console.error(err, res); if(err) console.error(err, res);
else { else {
this.state.sadd("space_" + space._id, ws.id, function(err, res) { this.state.sadd("space_" + space._id, ws.id, function(err, res) {
@ -238,7 +256,7 @@ module.exports = {
}.bind(this)); }.bind(this));
}, },
removeUserInSpace: function(spaceId, ws, cb) { removeUserInSpace: function(spaceId, ws, cb) {
this.state.srem("space_" + spaceId, ws.id, function(err, res) { this.state.srem("space_" + spaceId, ws.id+"", function(err, res) {
if (err) cb(err); if (err) cb(err);
else { else {
console.log("[websockets] socket "+ ws.id + " went offline in space " + spaceId); console.log("[websockets] socket "+ ws.id + " went offline in space " + spaceId);
@ -252,10 +270,10 @@ module.exports = {
}, },
distributeUsers: function(spaceId) { distributeUsers: function(spaceId) {
if(!spaceId) if (!spaceId)
return; return;
this.state.smembers("space_" + spaceId, function(err, list) { /*this.state.smembers("space_" + spaceId, function(err, list) {
async.map(list, function(item, callback) { async.map(list, function(item, callback) {
this.state.get(item, function(err, userId) { this.state.get(item, function(err, userId) {
console.log(item, "->", userId); console.log(item, "->", userId);
@ -276,16 +294,14 @@ module.exports = {
return {nickname: realNickname, email: null, avatar_thumbnail_uri: null }; return {nickname: realNickname, email: null, avatar_thumbnail_uri: null };
}); });
User.find({"_id" : { "$in" : validUserIds }}, { "nickname" : 1 , "email" : 1, "avatar_thumbnail_uri": 1 }, function(err, users) { db.User.findAll({where: {
if (err) "_id" : { "$in" : validUserIds }}, attributes: ["nickname","email","avatar_thumbnail_uri"]})
console.error(err); .then(users) {
else {
const allUsers = users.concat(anonymousUsers); const allUsers = users.concat(anonymousUsers);
const strUsers = JSON.stringify({users: allUsers, space_id: spaceId}); const strUsers = JSON.stringify({users: allUsers, space_id: spaceId});
this.state.publish("users", strUsers); this.state.publish("users", strUsers);
} }.bind(this));
}.bind(this));
}.bind(this)); }.bind(this));
}.bind(this)); }.bind(this));*/
} }
}; };

View File

@ -46,7 +46,7 @@
"specify": "Bitte spezifiziere", "specify": "Bitte spezifiziere",
"confirm": "Bitte bestätige", "confirm": "Bitte bestätige",
"signup_google": "Mit Google anmelden", "signup_google": "Mit Google anmelden",
"error_unknown_email": "Unbekannte Kombination von Email und Passwort. Oder versuche dich mit Google anzumelden.", "error_unknown_email": "Unbekannte Kombination von Email und Passwort.",
"error_password_confirmation": "Die beiden Passwörter stimmen nicht überein.", "error_password_confirmation": "Die beiden Passwörter stimmen nicht überein.",
"error_domain_blocked": "Diese Domain ist gesperrt.", "error_domain_blocked": "Diese Domain ist gesperrt.",
"error_user_email_already_used": "Diese Email-Adresse ist bereits registriert.", "error_user_email_already_used": "Diese Email-Adresse ist bereits registriert.",

View File

@ -44,8 +44,7 @@
"sure": "Are you sure?", "sure": "Are you sure?",
"specify": "Please Specify", "specify": "Please Specify",
"confirm": "Please Confirm", "confirm": "Please Confirm",
"signup_google": "Sign In with Google", "error_unknown_email": "This email/password combination is unknown.",
"error_unknown_email": "This email/password combination is unknown. Try login with Google.",
"error_password_confirmation": "The entered passwords don't match.", "error_password_confirmation": "The entered passwords don't match.",
"error_domain_blocked": "Your domain is blocked.", "error_domain_blocked": "Your domain is blocked.",
"error_user_email_already_used": "This email address is already in use.", "error_user_email_already_used": "This email address is already in use.",
@ -322,4 +321,4 @@
"mute_present": "Unfollow", "mute_present": "Unfollow",
"follow_present_help": "If someone else is presenting this Space, the other members automatically follow the presentation. Switch following on or off with this button.", "follow_present_help": "If someone else is presenting this Space, the other members automatically follow the presentation. Switch following on or off with this button.",
"export": "export" "export": "export"
} }

View File

@ -46,7 +46,7 @@
"specify": "Veuillez préciser:", "specify": "Veuillez préciser:",
"confirm": "Veuillez confirmer", "confirm": "Veuillez confirmer",
"signup_google": "S'inscrire avec Google", "signup_google": "S'inscrire avec Google",
"error_unknown_email": "Combinaison inconnue de l'email et mot de passe. Ou essayer de signer avec Google.", "error_unknown_email": "Combinaison inconnue de l'email et mot de passe.",
"error_password_confirmation": "Les deux mots de passe ne correspondent pas.", "error_password_confirmation": "Les deux mots de passe ne correspondent pas.",
"error_domain_blocked": "Ce domaine a été désactivé.", "error_domain_blocked": "Ce domaine a été désactivé.",
"error_user_email_already_used": "Cette adresse email est déjà enregistré.", "error_user_email_already_used": "Cette adresse email est déjà enregistré.",
@ -315,4 +315,4 @@
"follow_present": "Suivre", "follow_present": "Suivre",
"mute_present": "Pas suivre", "mute_present": "Pas suivre",
"follow_present_help": "follow_present_help" "follow_present_help": "follow_present_help"
} }

View File

@ -1,6 +1,6 @@
'use strict'; 'use strict';
require('../models/schema'); require('../models/db');
var config = require('config'); var config = require('config');
module.exports = (req, res, next) => { module.exports = (req, res, next) => {
@ -16,4 +16,4 @@ module.exports = (req, res, next) => {
} else { } else {
res.status(404).send("Not Found."); res.status(404).send("Not Found.");
} }
} }

View File

@ -1,9 +1,11 @@
'use strict'; 'use strict';
require('../models/schema'); require('../models/db');
var config = require('config'); var config = require('config');
const redis = require('../helpers/redis'); const redis = require('../helpers/redis');
// FIXME TODO object.toJSON()
var saveAction = (actionKey, object) => { var saveAction = (actionKey, object) => {
if (object.constructor.modelName == "Space") if (object.constructor.modelName == "Space")
return; return;
@ -13,14 +15,14 @@ var saveAction = (actionKey, object) => {
space: object.space_id || object.space, space: object.space_id || object.space,
user: object.user_id || object.user, user: object.user_id || object.user,
editor_name: object.editor_name, editor_name: object.editor_name,
object: object.toJSON() object: object
}; };
let action = new Action(attr); /*let action = new Action(attr);
action.save(function(err) { action.save(function(err) {
if (err) if (err)
console.error("saved create action err:", err); console.error("saved create action err:", err);
}); });*/
}; };
module.exports = (req, res, next) => { module.exports = (req, res, next) => {
@ -32,21 +34,21 @@ module.exports = (req, res, next) => {
res['distributeCreate'] = function(model, object) { res['distributeCreate'] = function(model, object) {
if (!object) return; if (!object) return;
redis.sendMessage("create", model, object.toJSON(), req.channelId); redis.sendMessage("create", model, object, req.channelId);
this.status(201).json(object.toJSON()); this.status(201).json(object);
saveAction("create", object); saveAction("create", object);
}; };
res['distributeUpdate'] = function(model, object) { res['distributeUpdate'] = function(model, object) {
if (!object) return; if (!object) return;
redis.sendMessage("update", model, object.toJSON(), req.channelId); redis.sendMessage("update", model, object, req.channelId);
this.status(200).json(object.toJSON()); this.status(200).json(object);
saveAction("update", object); saveAction("update", object);
}; };
res['distributeDelete'] = function(model, object) { res['distributeDelete'] = function(model, object) {
if (!object) return; if (!object) return;
redis.sendMessage("delete", model, object.toJSON(), req.channelId); redis.sendMessage("delete", model, object, req.channelId);
this.sendStatus(204); this.sendStatus(204);
saveAction("delete", object); saveAction("delete", object);
}; };

View File

@ -1,22 +1,20 @@
'use strict'; 'use strict';
const db = require('../models/db');
const Sequelize = require('sequelize');
const Op = Sequelize.Op;
require('../models/schema');
var config = require('config'); var config = require('config');
module.exports = (req, res, next) => { module.exports = (req, res, next) => {
var artifactId = req.params.artifact_id; var artifactId = req.params.artifact_id;
Artifact.findOne({ db.Artifact.findOne({where: {
"_id": artifactId "_id": artifactId
}, (err, artifact) => { }}).then(artifact => {
if (err) { if (artifact) {
res.status(400).json(err); req['artifact'] = artifact;
next();
} else { } else {
if (artifact) { res.sendStatus(404);
req['artifact'] = artifact;
next();
} else {
res.sendStatus(404);
}
} }
}); });
}; };

View File

@ -1,6 +1,6 @@
'use strict'; 'use strict';
require('../models/schema'); require('../models/db');
const config = require('config'); const config = require('config');
const url = require('url'); const url = require('url');
@ -26,20 +26,20 @@ module.exports = (req, res, next) => {
const parsedUrl = url.parse(origin, true, true); const parsedUrl = url.parse(origin, true, true);
// FIXME // FIXME
if (parsedUrl.hostname == "cdn.spacedeck.com") { if (parsedUrl.hostname == "cdn.spacedeck.com") {
res.header('Cache-Control', "max-age"); res.header('Cache-Control', "max-age");
res.header('Expires', "30d"); res.header('Expires', "30d");
res.removeHeader("Pragma"); res.removeHeader("Pragma");
respond(origin, req, res, next); respond(origin, req, res, next);
} else { } else {
Team.getTeamForHost(parsedUrl.hostname, (err, team, subdomain) => { //Team.getTeamForHost(parsedUrl.hostname, (err, team, subdomain) => {
if (team) { //if (team) {
respond(origin, req, res, next); respond(origin, req, res, next);
} else { //} else {
next(); next();
} //}
}); //});
} }
} else { } else {

View File

@ -1,6 +1,6 @@
'use strict'; 'use strict';
require('../models/schema'); require('../models/db');
var config = require('config'); var config = require('config');
module.exports = (req, res, next) => { module.exports = (req, res, next) => {
@ -10,8 +10,8 @@ module.exports = (req, res, next) => {
req.i18n.setLocaleFromCookie(); req.i18n.setLocaleFromCookie();
} }
if (req.user && req.user.preferences.language) { if (req.user && req.user.prefs_language) {
req.i18n.setLocale(req.user.preferences.language); req.i18n.setLocale(req.user.prefs_language);
} }
next(); next();
} }

47
middlewares/session.js Normal file
View File

@ -0,0 +1,47 @@
'use strict';
const db = require('../models/db');
var config = require('config');
module.exports = (req, res, next) => {
const token = req.cookies["sdsession"];
if (token && token != "null" && token != null) {
db.Session.findOne({where: {token: token}})
.then(session => {
if (!session) {
// session not found
next();
}
else db.User.findOne({where: {_id: session.user_id}})
.then(user => {
if (!user) {
var domain = (process.env.NODE_ENV == "production") ? new URL(config.get('endpoint')).hostname : req.headers.hostname;
res.clearCookie('sdsession', { domain: domain });
if (req.accepts("text/html")) {
res.send("Please clear your cookies and try again.");
} else if (req.accepts('application/json')) {
res.status(403).json({
"error": "token_not_found"
});
} else {
res.send("Please clear your cookies and try again.");
}
} else {
req["token"] = token;
req["user"] = user;
next();
}
});
})
.error(err => {
console.error("Session resolve error",err);
next();
});
} else {
next();
}
}

View File

@ -1,38 +0,0 @@
'use strict';
require('../models/schema');
var config = require('config');
module.exports = (req, res, next) => {
const token = req.cookies["sdsession"];
if (token && token != "null" && token !== null) {
User.findOne({
"sessions.token": token
}).populate('team').exec((err, user) => {
if (!user) {
// FIXME
var domain = "localhost";
res.clearCookie('sdsession', {
domain: domain
});
if (req.accepts("text/html")) {
res.redirect("/");
} else if (req.accepts('application/json')) {
res.status(403).json({
"error": "token_not_found"
});
} else {
res.redirect("/");
}
} else {
req["token"] = token;
req["user"] = user;
next();
}
});
} else {
next();
}
}

View File

@ -1,6 +1,6 @@
'use strict'; 'use strict';
require('../models/schema'); const db = require('../models/db');
var config = require('config'); var config = require('config');
module.exports = (req, res, next) => { module.exports = (req, res, next) => {
@ -19,50 +19,6 @@ module.exports = (req, res, next) => {
} }
}; };
var rolePerUser = (originalSpace, user, cb) => {
originalSpace.path = [];
if (originalSpace._id.equals(req.user.home_folder_id) || (originalSpace.creator && originalSpace.creator._id.equals(req.user._id))) {
cb("admin");
} else {
var findMembershipsForSpace = function(space, allMemberships, prevRole) {
Membership.find({
"space": space._id
}, function(err, parentMemberships) {
var currentMemberships = parentMemberships.concat(allMemberships);
if (space.parent_space_id) {
Space.findOne({
"_id": space.parent_space_id
}, function(err, parentSpace) {
findMembershipsForSpace(parentSpace, currentMemberships, prevRole);
});
} else {
// reached the top
var role = prevRole;
space.memberships = currentMemberships;
if(role == "none"){
if(originalSpace.access_mode == "public") {
role = "viewer";
}
}
currentMemberships.forEach(function(m, i) {
if (m.user && m.user.equals(user._id)) {
role = m.role;
}
});
cb(role);
}
});
};
findMembershipsForSpace(originalSpace, [], "none");
}
};
var finalizeAnonymousLogin = function(space, spaceAuth) { var finalizeAnonymousLogin = function(space, spaceAuth) {
var role = "none"; var role = "none";
@ -77,7 +33,7 @@ module.exports = (req, res, next) => {
} }
if (req.user) { if (req.user) {
rolePerUser(space, req.user, function(newRole) { db.getUserRoleInSpace(space, req.user, function(newRole) {
if (newRole == "admin" && (role == "editor" || role == "viewer")) { if (newRole == "admin" && (role == "editor" || role == "viewer")) {
finalizeReq(space, newRole); finalizeReq(space, newRole);
} else if (newRole == "editor" && (role == "viewer")) { } else if (newRole == "editor" && (role == "viewer")) {
@ -97,64 +53,61 @@ module.exports = (req, res, next) => {
'email': 1 'email': 1
}; };
Space.findOne({ db.Space.findOne({where: {
"_id": spaceId "_id": spaceId
}).populate("creator", userMapping).exec(function(err, space) { }}).then(function(space) {
if (err) {
res.status(400).json(err);
} else {
if (space) {
if (space.access_mode == "public") { if (space) {
if (space.access_mode == "public") {
if (space.password) { if (space.password) {
if (req.spacePassword) { if (req.spacePassword) {
if (req.spacePassword === space.password) { if (req.spacePassword === space.password) {
finalizeAnonymousLogin(space, req["spaceAuth"]);
} else {
res.status(403).json({
"error": "password_wrong"
});
}
} else {
res.status(401).json({
"error": "password_required"
});
}
} else {
finalizeAnonymousLogin(space, req["spaceAuth"]);
}
} else {
// special permission for screenshot/pdf export from backend
if (req.query['api_token'] && req.query['api_token'] == config.get('phantom_api_secret')) {
finalizeReq(space, "viewer");
return;
}
if (req.user) {
rolePerUser(space, req.user, function(role) {
if (role == "none") {
finalizeAnonymousLogin(space, req["spaceAuth"]);
} else {
finalizeReq(space, role);
}
});
} else {
if (req.spaceAuth && space.edit_hash) {
finalizeAnonymousLogin(space, req["spaceAuth"]); finalizeAnonymousLogin(space, req["spaceAuth"]);
} else { } else {
res.status(403).json({ res.status(403).json({
"error": "auth_required" "error": "password_wrong"
}); });
} }
} else {
res.status(401).json({
"error": "password_required"
});
}
} else {
finalizeAnonymousLogin(space, req["spaceAuth"]);
}
} else {
// space is private
// special permission for screenshot/pdf export from backend
if (req.query['api_token'] && req.query['api_token'] == config.get('phantom_api_secret')) {
finalizeReq(space, "viewer");
return;
}
if (req.user) {
db.getUserRoleInSpace(space, req.user, function(role) {
if (role == "none") {
finalizeAnonymousLogin(space, req["spaceAuth"]);
} else {
finalizeReq(space, role);
}
});
} else {
if (req.spaceAuth && space.edit_hash) {
finalizeAnonymousLogin(space, req["spaceAuth"]);
} else {
res.status(403).json({
"error": "auth_required"
});
} }
} }
} else {
res.status(404).json({
"error": "space_not_found"
});
} }
} else {
res.status(404).json({
"error": "space_not_found"
});
} }
}); });
} }

View File

@ -1,33 +0,0 @@
'use strict';
require('../models/schema');
var config = require('config');
module.exports = (req, res, next) => {
let host = req.headers.host;
Team.getTeamForHost(host, (err, team, subdomain) => {
if (subdomain) {
if (!err && team) {
req.subdomainTeam = team;
req.subdomain = subdomain;
next()
} else {
if (req.accepts('text/html')) {
res.status(404).render('not_found', {
title: 'Page Not Found.'
});
} else if (req.accepts('application/json')) {
res.status(404).json({
"error": "not_found"
});
} else {
res.status(404).render('not_found', {
title: 'Page Not Found.'
});
}
}
} else {
next();
}
});
}

View File

@ -1,23 +0,0 @@
'use strict';
require('../models/schema');
var config = require('config');
module.exports = (req, res, next) => {
if (req.user) {
var isAdmin = req.user.team.admins.indexOf(req.user._id) >= 0;
var correctMethod = req.method == "GET" || (req.method == "DELETE" || req.method == "PUT" || req.method == "POST");
if (correctMethod && isAdmin) {
next();
} else {
res.status(403, {
"error": "not authorized"
});
}
} else {
res.status(403, {
"error": "not logged in"
});
}
}

View File

@ -1,31 +0,0 @@
'use strict';
require('../models/schema');
var config = require('config');
var _ = require('underscore');
module.exports = (req, res, next) => {
res.oldRender = res.render;
res.render = function(template, params) {
var team = req.subdomainTeam;
if (team) {
team = _.pick(team.toObject(), ['_id', 'name', 'subdomain', 'avatar_original_uri']);
} else {
team = null;
}
const addParams = {
locale: req.i18n.locale,
config: config,
subdomain_team: team,
user: req.user,
csrf_token: "",
socket_auth: req.token
};
const all = _.extend(params, addParams);
res.oldRender(template, all);
};
next();
}

View File

@ -1,5 +1,7 @@
'use strict'; 'use strict';
// FIXME port this last model
var mongoose = require('mongoose'); var mongoose = require('mongoose');
var Schema = mongoose.Schema; var Schema = mongoose.Schema;

View File

@ -1,88 +0,0 @@
'use strict';
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
module.exports.artifactSchema = mongoose.model('Artifact', {
mime: String,
thumbnail_uri: String,
space_id: Schema.Types.ObjectId,
user_id: {type: Schema.Types.ObjectId, ref: 'User' },
last_update_user_id: {type: Schema.Types.ObjectId, ref: 'User' },
editor_name: String,
last_update_editor_name: String,
description: String,
state: {type: String, default: "idle"},
meta: {
linked_to: [String],
title: String,
tags: [String],
search_text: String,
link_uri: String,
play_from: Number,
play_to: Number,
},
board: {
x: {type: Number, default: 0.0},
y: {type: Number, default: 0.0},
z: {type: Number, default: 0.0},
r: {type: Number, default: 0.0},
w: {type: Number, default: 100},
h: {type: Number, default: 100},
},
control_points: [{
dx: Number, dy: Number
}],
group:{type: String, default: ""},
locked: {type: Boolean, default: false},
payload_uri: String,
payload_thumbnail_web_uri: String,
payload_thumbnail_medium_uri: String,
payload_thumbnail_big_uri: String,
payload_size: Number, // file size in bytes
style: {
fill_color: {type: String, default: "transparent"},
stroke_color:{type: String, default: "#000000"},
text_color: String,
stroke: {type: Number, default: 0.0},
stroke_style: {type: String, default: "solid"},
alpha: {type: Number, default: 1.0},
order: {type: Number, default: 0},
crop: {
x: Number,
y: Number,
w: Number,
h: Number
},
shape: String,
shape_svg: String,
padding_left: Number,
padding_right: Number,
padding_top: Number,
padding_bottom: Number,
margin_left: Number,
margin_right: Number,
margin_top: Number,
margin_bottom: Number,
border_radius: Number,
align: {type: String, default: "left"},
valign: {type: String, default: "top"},
brightness: Number,
contrast: Number,
saturation: Number,
blur: Number,
hue: Number,
opacity: Number
},
payload_alternatives: [{
mime: String,
payload_uri: String,
payload_thumbnail_web_uri: String,
payload_thumbnail_medium_uri: String,
payload_thumbnail_big_uri: String,
payload_size: Number
}],
created_at: {type: Date, default: Date.now},
created_from_ip: {type: String},
updated_at: {type: Date, default: Date.now}
});

364
models/db.js Normal file
View File

@ -0,0 +1,364 @@
const Umzug = require('umzug');
function sequel_log(a,b,c) {
console.log(a);
}
const Sequelize = require('sequelize');
const sequelize = new Sequelize('database', 'username', 'password', {
host: 'localhost',
dialect: 'sqlite',
pool: {
max: 5,
min: 0,
acquire: 30000,
idle: 10000
},
// SQLite only
storage: 'database.sqlite',
logging: sequel_log,
// http://docs.sequelizejs.com/manual/tutorial/querying.html#operators
operatorsAliases: false
});
var User;
var Session;
var Space;
var Membership;
var Artifact;
var Message;
var Action;
module.exports = {
User: sequelize.define('user', {
_id: {type: Sequelize.STRING, primaryKey: true},
email: Sequelize.STRING,
password_hash: Sequelize.STRING,
nickname: Sequelize.STRING,
avatar_original_uri: Sequelize.STRING,
avatar_thumb_uri: Sequelize.STRING,
confirmation_token: Sequelize.STRING,
password_reset_token: Sequelize.STRING,
home_folder_id: Sequelize.STRING,
prefs_language: Sequelize.STRING,
prefs_email_notifications: Sequelize.STRING,
prefs_email_digest: Sequelize.STRING,
created_at: {type: Sequelize.DATE, defaultValue: Sequelize.NOW},
updated_at: {type: Sequelize.DATE, defaultValue: Sequelize.NOW}
}),
Session: sequelize.define('session', {
token: {type: Sequelize.STRING, primaryKey: true},
user_id: Sequelize.STRING,
expires: Sequelize.DATE,
created_at: {type: Sequelize.DATE, defaultValue: Sequelize.NOW},
device: Sequelize.STRING,
ip: Sequelize.STRING
}),
Space: sequelize.define('space', {
_id: {type: Sequelize.STRING, primaryKey: true},
name: {type: Sequelize.STRING, default: "New Space"},
space_type: {type: Sequelize.STRING, defaultValue: "space"},
creator_id: Sequelize.STRING,
parent_space_id: Sequelize.STRING,
access_mode: {type: Sequelize.STRING, default: "private"}, // "public" || "private"
password: Sequelize.STRING,
edit_hash: Sequelize.STRING,
edit_slug: Sequelize.STRING,
editors_locking: Sequelize.BOOLEAN,
thumbnail_uri: Sequelize.STRING,
width: Sequelize.INTEGER,
height: Sequelize.INTEGER,
background_color: Sequelize.STRING,
background_uri: Sequelize.STRING,
created_at: {type: Sequelize.DATE, defaultValue: Sequelize.NOW},
updated_at: {type: Sequelize.DATE, defaultValue: Sequelize.NOW},
thumbnail_url: Sequelize.STRING,
thumbnail_updated_at: {type: Sequelize.DATE}
}),
Membership: sequelize.define('membership', {
_id: {type: Sequelize.STRING, primaryKey: true},
space_id: Sequelize.STRING,
user_id: Sequelize.STRING,
role: Sequelize.STRING,
code: Sequelize.STRING,
state: {type: Sequelize.STRING, defaultValue: "pending"}, // valid: "pending", "active"
email_invited: Sequelize.STRING,
created_at: {type: Sequelize.DATE, defaultValue: Sequelize.NOW},
updated_at: {type: Sequelize.DATE, defaultValue: Sequelize.NOW}
}),
Message: sequelize.define('message', {
_id: {type: Sequelize.STRING, primaryKey: true},
space_id: Sequelize.STRING,
user_id: Sequelize.STRING,
editor_name: Sequelize.STRING,
message: Sequelize.TEXT,
created_at: {type: Sequelize.DATE, defaultValue: Sequelize.NOW},
updated_at: {type: Sequelize.DATE, defaultValue: Sequelize.NOW}
}),
Artifact: sequelize.define('artifact', {
_id: {type: Sequelize.STRING, primaryKey: true},
space_id: Sequelize.STRING,
user_id: Sequelize.STRING,
mime: Sequelize.STRING,
thumbnail_uri: Sequelize.STRING,
last_update_user_id: Sequelize.STRING,
editor_name: Sequelize.STRING,
last_update_editor_name: Sequelize.STRING,
description: Sequelize.TEXT,
state: {type: Sequelize.STRING, default: "idle"},
//linked_to: Sequelize.STRING,
title: Sequelize.STRING,
tags: Sequelize.TEXT,
search_text: Sequelize.STRING,
link_uri: Sequelize.STRING,
play_from: Sequelize.DECIMAL,
play_to: Sequelize.DECIMAL,
x: {type: Sequelize.DECIMAL, default: 0.0},
y: {type: Sequelize.DECIMAL, default: 0.0},
z: {type: Sequelize.DECIMAL, default: 0.0},
r: {type: Sequelize.DECIMAL, default: 0.0},
w: {type: Sequelize.DECIMAL, default: 100},
h: {type: Sequelize.DECIMAL, default: 100},
//control_points: [{
// dx: Number, dy: Number
//}],
control_points: Sequelize.TEXT,
group: Sequelize.STRING,
locked: {type: Sequelize.BOOLEAN, default: false},
payload_uri: Sequelize.STRING,
payload_thumbnail_web_uri: Sequelize.STRING,
payload_thumbnail_medium_uri: Sequelize.STRING,
payload_thumbnail_big_uri: Sequelize.STRING,
payload_size: Sequelize.INTEGER, // file size in bytes
fill_color: {type: Sequelize.STRING, default: "transparent"},
stroke_color: {type: Sequelize.STRING, default: "#000000"},
text_color: Sequelize.STRING,
stroke: {type: Sequelize.DECIMAL, default: 0.0},
stroke_style: {type: Sequelize.STRING, default: "solid"},
alpha: {type: Sequelize.DECIMAL, default: 1.0},
order: {type: Sequelize.INTEGER, default: 0},
crop_x: Sequelize.INTEGER,
crop_y: Sequelize.INTEGER,
crop_w: Sequelize.INTEGER,
crop_h: Sequelize.INTEGER,
shape: Sequelize.STRING,
shape_svg: Sequelize.STRING,
padding_left: Sequelize.INTEGER,
padding_right: Sequelize.INTEGER,
padding_top: Sequelize.INTEGER,
padding_bottom: Sequelize.INTEGER,
margin_left: Sequelize.INTEGER,
margin_right: Sequelize.INTEGER,
margin_top: Sequelize.INTEGER,
margin_bottom: Sequelize.INTEGER,
border_radius: Sequelize.INTEGER,
align: {type: Sequelize.STRING, default: "left"},
valign: {type: Sequelize.STRING, default: "top"},
brightness: Sequelize.DECIMAL,
contrast: Sequelize.DECIMAL,
saturation: Sequelize.DECIMAL,
blur: Sequelize.DECIMAL,
hue: Sequelize.DECIMAL,
opacity: Sequelize.DECIMAL,
payload_alternatives: Sequelize.TEXT,
/*payload_alternatives: [{
mime: String,
payload_uri: String,
payload_thumbnail_web_uri: String,
payload_thumbnail_medium_uri: String,
payload_thumbnail_big_uri: String,
payload_size: Number
}],*/
created_at: {type: Sequelize.DATE, defaultValue: Sequelize.NOW},
updated_at: {type: Sequelize.DATE, defaultValue: Sequelize.NOW}
}),
init: async function() {
User = this.User;
Session = this.Session;
Space = this.Space;
Artifact = this.Artifact;
Message = this.Message;
Membership = this.Membership;
Space.belongsTo(User, {
foreignKey: {
name: 'creator_id'
},
as: 'creator'
});
Membership.belongsTo(User, {
foreignKey: {
name: 'user_id'
},
as: 'user'
});
Membership.belongsTo(Space, {
foreignKey: {
name: 'space_id'
},
as: 'space'
});
Artifact.belongsTo(User, {
foreignKey: {
name: 'user_id'
},
as: 'user'
});
Artifact.belongsTo(Space, {
foreignKey: {
name: 'space_id'
},
as: 'space'
});
Message.belongsTo(User, {
foreignKey: {
name: 'user_id'
},
as: 'user'
});
Message.belongsTo(Space, {
foreignKey: {
name: 'space_id'
},
as: 'space'
});
await sequelize.sync();
var umzug = new Umzug({
storage: 'sequelize',
storageOptions: {
sequelize: sequelize
},
migrations: {
params: [
sequelize.getQueryInterface(),
Sequelize
],
path: './models/migrations',
pattern: /\.js$/
}
});
umzug.up().then(function(migrations) {
console.log('Migration complete up!');
});
},
getUserRoleInSpace: (originalSpace, user, cb) => {
originalSpace.path = [];
if (originalSpace._id == user.home_folder_id || (originalSpace.creator_id && originalSpace.creator_id == user._id)) {
cb("admin");
} else {
var findMembershipsForSpace = function(space, allMemberships, prevRole) {
Membership.findAll({ where: {
"space_id": space._id
}}).then(function(parentMemberships) {
var currentMemberships = parentMemberships.concat(allMemberships);
if (space.parent_space_id) {
Space.findOne({ where: {
"_id": space.parent_space_id
}}).then(function(parentSpace) {
findMembershipsForSpace(parentSpace, currentMemberships, prevRole);
});
} else {
// reached the top
var role = prevRole;
space.memberships = currentMemberships;
if (role == "none") {
if (originalSpace.access_mode == "public") {
role = "viewer";
}
}
currentMemberships.forEach(function(m, i) {
if (m.user_id && m.user_id == user._id) {
role = m.role;
}
});
cb(role);
}
});
};
findMembershipsForSpace(originalSpace, [], "none");
}
},
spaceToObject: (space) => {
// FIXME TODO
return space;
},
findUserBySessionToken: (token, cb) => {
Session.findOne({where: {token: token}})
.then(session => {
if (!session) cb(null, null)
else User.findOne({where: {_id: session.user_id}})
.then(user => {
cb(null, user)
})
})
},
unpackArtifact: (a) => {
if (a.tags && (typeof a.tags)=="string") {
a.tags = JSON.parse(a.tags);
}
if (a.control_points && (typeof a.control_points)=="string") {
a.control_points = JSON.parse(a.control_points);
}
if (a.payload_alternatives && (typeof a.payload_alternatives)=="string") {
a.payload_alternatives = JSON.parse(a.payload_alternatives);
}
return a;
},
packArtifact: (a) => {
if (a.tags && (typeof a.tags)!="string") {
a.tags = JSON.stringify(a.tags);
}
if (a.control_points && (typeof a.control_points)!="string") {
a.control_points = JSON.stringify(a.control_points);
}
if (a.payload_alternatives && (typeof a.payload_alternatives)!="string") {
a.payload_alternatives = JSON.stringify(a.payload_alternatives);
}
return a;
}
}

View File

@ -1,21 +0,0 @@
'use strict';
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
module.exports.domainSchema = mongoose.Schema({
domain: String,
edu: Boolean,
created_at: {
type: Date,
default: Date.now
},
updated_at: {
type: Date,
default: Date.now
}
});
module.exports.domainSchema.index({
domain: 1
});

View File

@ -1,45 +0,0 @@
'use strict';
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
module.exports.membershipSchema = mongoose.Schema({
user: {
type: Schema.Types.ObjectId,
ref: 'User'
},
space: {
type: Schema.Types.ObjectId,
ref: 'Space'
},
team: {
type: Schema.Types.ObjectId,
ref: 'Team'
},
role: {
type: String,
default: "viewer"
},
state: {
type: String,
default: "active"
},
email_invited: String,
code: String,
created_at: {
type: Date,
default: Date.now
},
updated_at: {
type: Date,
default: Date.now
}
});
module.exports.membershipSchema.index({
user: 1,
space: 1,
team: 1,
code: 1
});

View File

@ -1,31 +0,0 @@
'use strict';
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
module.exports.messageSchema = mongoose.Schema({
user: {
type: Schema.Types.ObjectId,
ref: 'User'
},
editor_name: String,
space: {
type: Schema.Types.ObjectId,
ref: 'Space'
},
message: String,
created_from_ip: {type: String},
created_at: {
type: Date,
default: Date.now
},
updated_at: {
type: Date,
default: Date.now
}
});
module.exports.messageSchema.index({
space: 1,
user: 1
});

View File

@ -0,0 +1,79 @@
'use strict';
module.exports = {
up: function(migration, DataTypes) {
return Promise.all([
migration.changeColumn('memberships', 'space_id',
{
type: DataTypes.STRING,
references: {
model: 'spaces',
key: '_id'
},
onDelete: 'CASCADE',
onUpdate: 'CASCADE'
}
),
migration.changeColumn('artifacts', 'space_id',
{
type: DataTypes.STRING,
references: {
model: 'spaces',
key: '_id'
},
onDelete: 'CASCADE',
onUpdate: 'CASCADE'
}
),
migration.changeColumn('messages', 'space_id',
{
type: DataTypes.STRING,
references: {
model: 'spaces',
key: '_id'
},
onDelete: 'CASCADE',
onUpdate: 'CASCADE'
}
)
])
},
down: function(migration, DataTypes) {
return Promise.all([
migration.changeColumn('memberships', 'space_id',
{
type: DataTypes.STRING,
references: {
model: 'spaces',
key: '_id'
},
onDelete: 'CASCADE',
onUpdate: 'NO ACTION'
}
),
migration.changeColumn('artifacts', 'space_id',
{
type: DataTypes.STRING,
references: {
model: 'spaces',
key: '_id'
},
onDelete: 'CASCADE',
onUpdate: 'NO ACTION'
}
),
migration.changeColumn('messages', 'space_id',
{
type: DataTypes.STRING,
references: {
model: 'spaces',
key: '_id'
},
onDelete: 'CASCADE',
onUpdate: 'NO ACTION'
}
)
])
}
}

View File

@ -1,44 +0,0 @@
'use strict';
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
Plan = mongoose.model('Plan', {
key: String,
description: String,
limit_folders: {
type: Number,
default: 200
},
limit_spaces: {
type: Number,
default: 500
},
limit_storage_bytes: {
type: Number,
default: 10737418240
},
plan_type: {
type: String,
default: "org"
},
price: Number,
public: Boolean,
recurring: {
type: String,
default: "month"
},
title: String,
trial_days: Number,
voucher_code: String,
created_at: {
type: Date,
default: Date.now
},
updated_at: {
type: Date,
default: Date.now
}
});
exports.planModel = Plan;

View File

@ -1,12 +0,0 @@
//'use strict';
var mongoose = require('mongoose');
User = mongoose.model('User', require('./user').userSchema);
Action = mongoose.model('Action', require('./action').actionSchema);
Space = mongoose.model('Space', require('./space').spaceSchema);
Artifact = mongoose.model('Artifact', require('./artifact').artifactSchema);
Team = mongoose.model('Team', require('./team').teamSchema);
Message = mongoose.model('Message', require('./message').messageSchema);
Membership = mongoose.model('Membership', require('./membership').membershipSchema);
Domain = mongoose.model('Domain', require('./domain').domainSchema);

View File

@ -1,273 +0,0 @@
'use strict';
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var async = require('async');
var _ = require("underscore");
var crypto = require('crypto');
module.exports.spaceSchema = Schema({
name: {type: String, default: "New Space"},
space_type: {type: String, default: "space"},
creator : { type: Schema.Types.ObjectId, ref: 'User' },
parent_space_id: Schema.Types.ObjectId,
access_mode: {type: String, default: "private"}, // "public" || "private"
password: String,
edit_hash: String,
edit_slug: String,
editors_locking: Boolean,
thumbnail_uri: String,
stats: {
num_children: Number,
total_spaces: Number,
total_folders: Number,
storage_bytes: Number,
},
advanced: {
type: {
width: Number,
height: Number,
margin: Number,
background_color: String,
background_uri: String,
background_repeat: Boolean,
grid_size: Number,
grid_divisions: Number,
gutter: Number,
columns: Number,
column_max_width: Number,
columns_responsive: Number,
row_max_height: Number,
padding_horz: Number,
padding_vert: Number
},
default: {
width: 200,
height: 400,
margin: 0,
background_color: "rgba(255,255,255,1)"
}
},
blocked_at: {type: Date, default: Date.now},
created_at: {type: Date, default: Date.now},
updated_at: {type: Date, default: Date.now},
thumbnail_updated_at: {type: Date},
thumbnail_url: String
});
module.exports.spaceSchema.index({ creator: 1, parent_space_id: 1, created_at: 1, updated_at: 1, edit_hash: 1});
module.exports.spaceSchema.statics.allForUser = function (user, callback) {
return this.find({user_id: user_id}, callback);
};
module.exports.spaceSchema.statics.getMemberships = function (err, callback) {
callback(null, {});
};
var getRecursiveSubspacesForSpace = (parentSpace, cb) => {
if (parentSpace.space_type == "folder") {
Space.find({
"parent_space_id": parentSpace._id
}).exec((err, subspaces) => {
async.map(subspaces, (space, innerCb) => {
getRecursiveSubspacesForSpace(space, (err, spaces) => {
innerCb(err, spaces);
});
}, (err, subspaces) => {
var flattenSubspaces = _.flatten(subspaces);
flattenSubspaces.push(parentSpace);
cb(null, flattenSubspaces);
});
});
} else {
cb(null, [parentSpace]);
}
};
module.exports.spaceSchema.statics.getRecursiveSubspacesForSpace = getRecursiveSubspacesForSpace;
var roleMapping = {
"none": 0,
"viewer": 1,
"editor": 2,
"admin": 3
}
module.exports.spaceSchema.statics.roleInSpace = (originalSpace, user, cb) => {
if (user.home_folder_id.toString() === originalSpace._id.toString()) {
cb(null, "admin");
return;
}
if (originalSpace.creator) {
if (originalSpace.creator._id.toString() === user._id.toString()) {
cb(null, "admin");
return;
}
}
var findMembershipsForSpace = function(space, allMemberships, prevRole) {
Membership.find({
"space": space._id
}, (err, parentMemberships) => {
var currentMemberships = parentMemberships.concat(allMemberships);
if (space.parent_space_id) {
Space.findOne({
"_id": space.parent_space_id
}, function(err, parentSpace) {
var role = prevRole;
if(role == "none"){
if(originalSpace.access_mode == "public") {
role = "viewer";
}
}
findMembershipsForSpace(parentSpace, currentMemberships, role);
});
} else {
// reached the top
var role = prevRole;
space.memberships = currentMemberships;
currentMemberships.forEach(function(m, i) {
if (m.user && m.user.equals(user._id)) {
if (m.role != null) {
if (roleMapping[m.role] > roleMapping[role]) {
role = m.role;
}
}
}
});
cb(err, role);
}
});
};
findMembershipsForSpace(originalSpace, [], "none");
}
module.exports.spaceSchema.statics.recursiveDelete = (space, cb) => {
space.remove(function(err) {
Action.remove({
space: space
}, function(err) {
if (err)
console.error("removed actions for space: ", err);
});
Membership.remove({
space: space
}, function(err) {
if (err)
console.error("removed memberships for space: ", err);
});
if (space.space_type === "folder") {
Space
.find({
parent_space_id: space._id
})
.exec(function(err, spaces) {
async.eachLimit(spaces, 10, function(subSpace, innerCb) {
module.exports.spaceSchema.statics.recursiveDelete(subSpace, function(err) {
innerCb(err);
});
}, function(err) {
cb(err);
});
});
} else {
Artifact.find({
space_id: space._id
}, function(err, artifacts) {
if (err) cb(err);
else {
async.eachLimit(artifacts, 20, function(a, innerCb) {
a.remove(function(err) {
innerCb(null, a);
});
}, function(err) {
cb(err);
});
}
});
}
});
};
var duplicateRecursiveSpace = (space, user, depth, cb, newParentSpace) => {
var newSpace = new Space(space);
newSpace._id = mongoose.Types.ObjectId();
if (newParentSpace) {
newSpace.parent_space_id = newParentSpace._id;
} else {
newSpace.name = newSpace.name + " (b)";
}
newSpace.creator = user;
newSpace.created_at = new Date();
newSpace.updated_at = new Date();
if (newSpace.space_type === "space") {
newSpace.edit_hash = crypto.randomBytes(64).toString('hex').substring(0, 7);
}
newSpace.save(function(err) {
if (newSpace.space_type === "folder" && depth < 10) {
Space
.find({
parent_space_id: space._id
})
.exec(function(err, spaces) {
async.eachLimit(spaces, 10, function(subSpace, innerCb) {
duplicateRecursiveSpace(subSpace, user, ++depth, function(err, newSubSpace) {
innerCb(err, newSubSpace);
}, newSpace);
}, function(err, allNewSubspaces) {
cb(err, newSpace);
});
});
} else {
Artifact.find({
space_id: space._id
}, function(err, artifacts) {
if (err) innerCb(err);
else {
async.eachLimit(artifacts, 20, function(a, innerCb) {
var newArtifact = new Artifact(a);
newArtifact._id = mongoose.Types.ObjectId();
newArtifact.space_id = newSpace._id;
newArtifact.created_at = new Date();
newArtifact.updated_at = new Date();
newArtifact.save(function(err) {
innerCb(null, newArtifact);
});
}, function(err, allNewArtifacts) {
cb(err, newSpace);
});
}
});
}
});
};
module.exports.spaceSchema.statics.duplicateSpace = duplicateRecursiveSpace;

View File

@ -1,70 +0,0 @@
'use strict';
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
module.exports.teamSchema = mongoose.Schema({
name: String,
subdomain: String,
creator: {
type: Schema.Types.ObjectId,
ref: 'User'
},
admins: [{
type: Schema.Types.ObjectId,
ref: 'User'
}],
invitation_codes: [String],
avatar_thumb_uri: String,
avatar_uri: String,
payment_type: {
type: String,
default: "auto"
},
payment_plan_key: String,
payment_subscription_id: String,
blocked_at: {
type: Date
},
upgraded_at: {
type: Date
},
created_at: {
type: Date,
default: Date.now
},
updated_at: {
type: Date,
default: Date.now
}
});
module.exports.teamSchema.index({
creator: 1
});
module.exports.teamSchema.statics.getTeamForHost = (host, cb) => {
if (host != "127.0.0.1:9000") { //phantomjs check
let subDomainParts = host.split('.');
if (subDomainParts.length > 2) {
const subdomain = subDomainParts[0];
if (subdomain != "www") {
Team.findOne({
subdomain: subdomain
}).exec((err, team) => {
cb(err, team, subdomain)
});
} else {
cb(null, null)
}
} else {
cb(null, null);
}
} else {
cb(null, null);
}
}

View File

@ -1,53 +0,0 @@
'use strict';
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
module.exports.userSchema = mongoose.Schema({
email: String,
password_hash: String,
nickname: String,
account_type: {type: String, default: "email"},
created_at: {type: Date, default: Date.now},
updated_at: {type: Date, default: Date.now},
avatar_original_uri: String,
avatar_thumb_uri: String,
src: String,
confirmation_token: String,
confirmed_at: Date,
password_reset_token: String,
home_folder_id: Schema.Types.ObjectId,
team : { type: Schema.Types.ObjectId, ref: 'Team' },
preferences: {
language: String,
email_notifications: {type: Boolean, default: true},
daily_digest_last_send: Date,
daily_digest: {type: Boolean, default: true}
},
sessions: [
{
token: String,
expires: Date,
device: String,
ip: String,
created_at: Date
}
],
payment_info: String,
payment_plan_key: {type: String, default: "free"},
payment_customer_id: String,
payment_subscription_id: String,
payment_notification_state: Number
});
module.exports.userSchema.index({
email: 1,
"sessions.token": 1,
team: 1,
created_at: 1,
home_folder_id: 1
});
module.exports.userSchema.statics.findBySessionToken = function (token, cb) {
return this.findOne({ "sessions.token": token}, cb);
};

View File

@ -3,84 +3,56 @@
"version": "1.0.0", "version": "1.0.0",
"private": true, "private": true,
"scripts": { "scripts": {
"start": "nodemon -e .js,.html bin/www", "start": "node spacedeck.js"
"test": "mocha" },
"engines": {
"node": ">=10.0.0"
}, },
"dependencies": { "dependencies": {
"archiver": "0.14.4", "archiver": "1.3.0",
"async": "1.3.0", "async": "2.3.0",
"aws-sdk": "2.1.39", "basic-auth": "1.1.0",
"basic-auth": "1.0.3", "bcryptjs": "2.4.3",
"bcrypt": "*", "body-parser": "^1.19.0",
"body-parser": "~1.13.1", "cheerio": "0.22.0",
"cheerio": "0.19.0", "config": "1.25.1",
"config": "1.14.0", "cookie-parser": "~1.4.3",
"cookie-parser": "~1.3.5",
"csurf": "1.8.3",
"debug": "~2.2.0",
"execSync": "latest", "execSync": "latest",
"express": "~4.13.0", "express": "^4.16.4",
"glob": "5.0.14", "file-type": "^7.6.0",
"gm": "1.18.1", "glob": "7.1.1",
"googleapis": "2.1.3", "gm": "^1.23.1",
"gulp": "^3.9.0", "gulp": "^4.0.2",
"gulp-concat": "2.6.0", "gulp-concat": "^2.6.1",
"gulp-eslint": "*", "gulp-sass": "^4.0.2",
"gulp-express": "0.3.0", "helmet": "^3.5.0",
"gulp-nodemon": "*", "i18n-2": "0.6.3",
"gulp-sass": "^2.0.3",
"gulp-uglify": "^1.5.1",
"gulp-util": "^3.0.6",
"helmet": "^1.1.0",
"i18n-2": "0.4.6",
"ioredis": "1.6.1",
"lodash": "^4.3.0",
"log-timestamp": "latest", "log-timestamp": "latest",
"md5": "2.0.0", "mock-aws-s3": "^2.6.0",
"moment": "2.10.6", "moment": "^2.19.3",
"mongoose": "4.4.3", "morgan": "^1.9.1",
"morgan": "1.6.1",
"node-sass-middleware": "0.8.0",
"pdfkit": "0.7.1",
"validator": "5.2.0",
"node-phantom-simple": "2.2.4", "node-phantom-simple": "2.2.4",
"phantomjs-prebuilt": "2.1.7", "node-server-screenshot": "^0.2.1",
"pm2": "latest", "nodemailer": "^4.6.7",
"qr-image": "3.1.0", "phantomjs-prebuilt": "^2.1.16",
"raven": "0.8.1", "read-chunk": "^2.1.0",
"request": "2.60.0", "request": "^2.88.0",
"sanitize-html": "^1.11.1", "sanitize-html": "^1.11.1",
"serve-favicon": "~2.3.0", "sequelize": "^4.37.6",
"serve-favicon": "~2.4.2",
"serve-static": "^1.13.1",
"slug": "^1.1.0",
"sqlite3": "^4.0.0",
"swig": "1.4.2", "swig": "1.4.2",
"slug": "0.9.1", "umzug": "^2.1.0",
"underscore": "1.8.3", "underscore": "1.8.3",
"weak": "1.0.0", "uuid": "^3.2.1",
"ws": "0.7.2" "validator": "7.0.0",
}, "ws": "3.3.1"
"devDependencies": {
"express": "^4.13.3",
"gulp": "^3.9.0",
"gulp-clean": "^0.3.2",
"gulp-concat": "^2.6.0",
"gulp-eslint": "^1.1.0",
"gulp-express": "^0.3.0",
"gulp-fingerprint": "^0.3.2",
"gulp-nodemon": "^2.0.4",
"gulp-rev": "^6.0.1",
"gulp-rev-all": "^0.8.22",
"gulp-rev-replace": "^0.4.3",
"gulp-sass": "^2.1.0",
"gulp-uglify": "^1.4.2",
"mocha": "*",
"nodemon": "*",
"should": "^7.1.0",
"supertest": "^1.1.0",
"winston": "^1.0.1"
}, },
"main": "app.js",
"description": "", "description": "",
"main": "Gulpfile.js", "directories": {},
"directories": {
},
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/spacedeck/spacedeck-open.git" "url": "https://github.com/spacedeck/spacedeck-open.git"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1,69 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="17.370329mm"
height="17.370247mm"
viewBox="0 0 17.370329 17.370247"
version="1.1"
id="svg3417"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
sodipodi:docname="sd6-icon-white.svg"
inkscape:export-filename="/home/mntmn/code/spacedeck-open/public/images/favicon.png"
inkscape:export-xdpi="93.585312"
inkscape:export-ydpi="93.585312">
<defs
id="defs3411" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="5.6"
inkscape:cx="68.901329"
inkscape:cy="26.613846"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
showgrid="false"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
inkscape:window-width="2560"
inkscape:window-height="1376"
inkscape:window-x="0"
inkscape:window-y="27"
inkscape:window-maximized="1" />
<metadata
id="metadata3414">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-61.618407,-79.672019)">
<path
inkscape:connector-curvature="0"
id="path1681-6-5-3-7-4-9-2-0-2-9-7"
d="m 69.103371,79.69206 c -0.792105,0.07526 -1.553632,0.368078 -2.179688,0.99414 -0.967242,0.967233 -1.023215,2.24006 -0.822265,3.46875 -1.228429,-0.200703 -2.499819,-0.144769 -3.466797,0.822266 -1.252082,1.252133 -1.178244,3.043412 -0.677734,4.544922 0.500509,1.50151 1.477937,2.995513 2.832031,4.349611 1.354102,1.3541 2.848091,2.33152 4.349609,2.83203 1.501518,0.50051 3.292795,0.57437 4.544922,-0.67773 0.9673,-0.96727 1.023249,-2.24001 0.822266,-3.468755 1.228416,0.200714 2.499803,0.146691 3.466796,-0.820313 1.252124,-1.252112 1.17824,-3.045353 0.677735,-4.546874 -0.500505,-1.501522 -1.477926,-2.995502 -2.832031,-4.34961 -1.354109,-1.354105 -2.848087,-2.329573 -4.34961,-2.830078 -0.750761,-0.250253 -1.57313,-0.393617 -2.365234,-0.318359 z m 0.251953,3.427734 c -0.06232,0.06232 0.187775,-0.12686 1.025391,0.152344 0.837615,0.279204 1.980359,0.976455 3.005859,2.001953 1.025498,1.0255 1.720796,2.16629 2,3.003906 0.279204,0.837616 0.09198,1.087707 0.154297,1.025391 0.06232,-0.06232 -0.187775,0.124907 -1.025391,-0.154297 -0.817005,-0.272334 -1.926016,-0.966798 -2.93164,-1.951172 -0.02107,-0.02133 -0.03343,-0.04515 -0.05469,-0.06641 -0.02194,-0.02194 -0.04635,-0.0349 -0.06836,-0.05664 -0.984356,-1.005615 -1.678841,-2.112692 -1.951172,-2.929687 -0.279204,-0.837616 -0.09198,-1.087708 -0.154297,-1.025391 z m -4.289063,4.289063 c -0.06231,0.06232 0.187774,-0.124903 1.025391,0.154296 0.81575,0.271911 1.923337,0.965368 2.927735,1.947266 0.02276,0.02306 0.03561,0.04929 0.05859,0.07227 0.023,0.023 0.04918,0.03581 0.07227,0.05859 0.981898,1.004395 1.67535,2.111982 1.947265,2.927735 0.279205,0.837619 0.09198,1.087705 0.154297,1.025385 0.06232,-0.0623 -0.187772,0.12492 -1.02539,-0.154291 -0.837619,-0.27921 -1.980364,-0.974504 -3.00586,-2 -1.025488,-1.025491 -1.720791,-2.168245 -2,-3.00586 -0.279208,-0.837615 -0.09198,-1.087708 -0.154297,-1.02539 z"
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3.4395833;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:37.79527664;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.0 KiB

View File

@ -0,0 +1,66 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="17.370329mm"
height="17.370247mm"
viewBox="0 0 17.370329 17.370247"
version="1.1"
id="svg3417"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
sodipodi:docname="sd6-icon.svg">
<defs
id="defs3411" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="5.6"
inkscape:cx="68.901329"
inkscape:cy="26.613846"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
showgrid="false"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
inkscape:window-width="2560"
inkscape:window-height="1376"
inkscape:window-x="0"
inkscape:window-y="27"
inkscape:window-maximized="1" />
<metadata
id="metadata3414">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-61.618407,-79.672019)">
<path
inkscape:connector-curvature="0"
id="path1681-6-5-3-7-4-9-2-0-2-9-7"
d="m 69.103371,79.69206 c -0.792105,0.07526 -1.553632,0.368078 -2.179688,0.99414 -0.967242,0.967233 -1.023215,2.24006 -0.822265,3.46875 -1.228429,-0.200703 -2.499819,-0.144769 -3.466797,0.822266 -1.252082,1.252133 -1.178244,3.043412 -0.677734,4.544922 0.500509,1.50151 1.477937,2.995513 2.832031,4.349611 1.354102,1.3541 2.848091,2.33152 4.349609,2.83203 1.501518,0.50051 3.292795,0.57437 4.544922,-0.67773 0.9673,-0.96727 1.023249,-2.24001 0.822266,-3.468755 1.228416,0.200714 2.499803,0.146691 3.466796,-0.820313 1.252124,-1.252112 1.17824,-3.045353 0.677735,-4.546874 -0.500505,-1.501522 -1.477926,-2.995502 -2.832031,-4.34961 -1.354109,-1.354105 -2.848087,-2.329573 -4.34961,-2.830078 -0.750761,-0.250253 -1.57313,-0.393617 -2.365234,-0.318359 z m 0.251953,3.427734 c -0.06232,0.06232 0.187775,-0.12686 1.025391,0.152344 0.837615,0.279204 1.980359,0.976455 3.005859,2.001953 1.025498,1.0255 1.720796,2.16629 2,3.003906 0.279204,0.837616 0.09198,1.087707 0.154297,1.025391 0.06232,-0.06232 -0.187775,0.124907 -1.025391,-0.154297 -0.817005,-0.272334 -1.926016,-0.966798 -2.93164,-1.951172 -0.02107,-0.02133 -0.03343,-0.04515 -0.05469,-0.06641 -0.02194,-0.02194 -0.04635,-0.0349 -0.06836,-0.05664 -0.984356,-1.005615 -1.678841,-2.112692 -1.951172,-2.929687 -0.279204,-0.837616 -0.09198,-1.087708 -0.154297,-1.025391 z m -4.289063,4.289063 c -0.06231,0.06232 0.187774,-0.124903 1.025391,0.154296 0.81575,0.271911 1.923337,0.965368 2.927735,1.947266 0.02276,0.02306 0.03561,0.04929 0.05859,0.07227 0.023,0.023 0.04918,0.03581 0.07227,0.05859 0.981898,1.004395 1.67535,2.111982 1.947265,2.927735 0.279205,0.837619 0.09198,1.087705 0.154297,1.025385 0.06232,-0.0623 -0.187772,0.12492 -1.02539,-0.154291 -0.837619,-0.27921 -1.980364,-0.974504 -3.00586,-2 -1.025488,-1.025491 -1.720791,-2.168245 -2,-3.00586 -0.279208,-0.837615 -0.09198,-1.087708 -0.154297,-1.02539 z"
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3.4395833;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:37.79527664;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.9 KiB

View File

@ -0,0 +1,129 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="81.731232mm"
height="17.370247mm"
viewBox="0 0 81.731232 17.370247"
version="1.1"
id="svg2651"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
sodipodi:docname="sd6-logo-black.svg">
<defs
id="defs2645" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="2.8"
inkscape:cx="80.852573"
inkscape:cy="-16.110417"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
showgrid="false"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
inkscape:window-width="2560"
inkscape:window-height="1376"
inkscape:window-x="0"
inkscape:window-y="27"
inkscape:window-maximized="1" />
<metadata
id="metadata2648">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-29.059958,-86.19285)">
<g
id="g3248">
<path
inkscape:connector-curvature="0"
id="path1681-6-5-3-7-4-9-2-0-2-9-7"
d="m 36.544922,86.212891 c -0.792105,0.07526 -1.553632,0.368078 -2.179688,0.99414 -0.967242,0.967233 -1.023215,2.24006 -0.822265,3.46875 -1.228429,-0.200703 -2.499819,-0.144769 -3.466797,0.822266 -1.252082,1.252133 -1.178244,3.043412 -0.677734,4.544922 0.500509,1.50151 1.477937,2.995513 2.832031,4.349611 1.354102,1.3541 2.848091,2.33152 4.349609,2.83203 1.501518,0.50051 3.292795,0.57437 4.544922,-0.67773 0.9673,-0.96727 1.023249,-2.24001 0.822266,-3.468755 1.228416,0.200714 2.499803,0.146691 3.466796,-0.820313 1.252124,-1.252112 1.17824,-3.045353 0.677735,-4.546874 -0.500505,-1.501522 -1.477926,-2.995502 -2.832031,-4.34961 -1.354109,-1.354105 -2.848087,-2.329573 -4.34961,-2.830078 -0.750761,-0.250253 -1.57313,-0.393617 -2.365234,-0.318359 z m 0.251953,3.427734 c -0.06232,0.06232 0.187775,-0.12686 1.025391,0.152344 0.837615,0.279204 1.980359,0.976455 3.005859,2.001953 1.025498,1.0255 1.720796,2.16629 2,3.003906 0.279204,0.837616 0.09198,1.087707 0.154297,1.025391 0.06232,-0.06232 -0.187775,0.124907 -1.025391,-0.154297 -0.817005,-0.272334 -1.926016,-0.966798 -2.93164,-1.951172 -0.02107,-0.02133 -0.03343,-0.04515 -0.05469,-0.06641 -0.02194,-0.02194 -0.04635,-0.0349 -0.06836,-0.05664 -0.984356,-1.005615 -1.678841,-2.112692 -1.951172,-2.929687 -0.279204,-0.837616 -0.09198,-1.087708 -0.154297,-1.025391 z m -4.289063,4.289063 c -0.06231,0.06232 0.187774,-0.124903 1.025391,0.154296 0.81575,0.271911 1.923337,0.965368 2.927735,1.947266 0.02276,0.02306 0.03561,0.04929 0.05859,0.07227 0.023,0.023 0.04918,0.03581 0.07227,0.05859 0.981898,1.004395 1.67535,2.111982 1.947265,2.927735 0.279205,0.837619 0.09198,1.087705 0.154297,1.025385 0.06232,-0.0623 -0.187772,0.12492 -1.02539,-0.154291 -0.837619,-0.27921 -1.980364,-0.974504 -3.00586,-2 -1.025488,-1.025491 -1.720791,-2.168245 -2,-3.00586 -0.279208,-0.837615 -0.09198,-1.087708 -0.154297,-1.02539 z"
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3.4395833;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:37.79527664;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
<g
id="g2614"
transform="matrix(0.26458333,0,0,0.26458333,-523.78744,61.714265)">
<g
id="flowRoot1610-0-6-8-1-1"
style="font-style:normal;font-variant:normal;font-weight:800;font-stretch:normal;font-size:16.00038528px;line-height:1.25;font-family:Inter;-inkscape-font-specification:'Inter, Ultra-Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;opacity:1;vector-effect:none;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.37800002;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:37.79527664;stroke-opacity:1"
transform="matrix(2.6369365,0,0,2.6369365,2045.0224,86.079903)"
aria-label="Spacedeck">
<path
id="path3214"
style="font-style:normal;font-variant:normal;font-weight:800;font-stretch:normal;font-size:16.00038528px;font-family:Inter;-inkscape-font-specification:'Inter, Ultra-Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none"
d="m 52.795627,11.002199 h 2.693247 C 55.466146,8.82601 53.73315,7.3543837 51.028539,7.3543837 c -2.659155,0 -4.573974,1.4488985 -4.556928,3.6137233 -0.0057,1.767088 1.232985,2.76143 3.244397,3.221669 l 1.215938,0.284097 c 1.27844,0.295462 1.852317,0.642061 1.863681,1.295486 -0.01136,0.710244 -0.676152,1.204575 -1.806861,1.204575 -1.244349,0 -2.06255,-0.57956 -2.125052,-1.698905 h -2.693246 c 0.03409,2.721656 1.926182,4.022824 4.852389,4.022824 2.897797,0 4.613748,-1.312532 4.625112,-3.522812 -0.01136,-1.857999 -1.267076,-2.99439 -3.562586,-3.500084 l -1.000024,-0.227278 c -1.056844,-0.227279 -1.727315,-0.57956 -1.704587,-1.272758 0.0057,-0.636379 0.55115,-1.0966177 1.642085,-1.0966177 1.096618,0 1.698905,0.4943297 1.77277,1.3238957 z"
inkscape:connector-curvature="0" />
<path
id="path3216"
style="font-style:normal;font-variant:normal;font-weight:800;font-stretch:normal;font-size:16.00038528px;font-family:Inter;-inkscape-font-specification:'Inter, Ultra-Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none"
d="m 57.068458,22.422928 h 2.778476 v -4.687613 h 0.05682 c 0.352281,0.806838 1.136391,1.53981 2.454605,1.53981 1.931864,0 3.48872,-1.5114 3.48872,-4.483062 0,-3.07962 -1.647767,-4.483063 -3.471674,-4.483063 -1.380715,0 -2.136415,0.806838 -2.471651,1.607994 h -0.08523 v -1.494355 h -2.750066 z m 2.721656,-7.636547 c 0,-1.426171 0.590923,-2.306874 1.607993,-2.306874 1.028434,0 1.59663,0.903431 1.59663,2.306874 0,1.409125 -0.568196,2.323919 -1.59663,2.323919 -1.01707,0 -1.607993,-0.909112 -1.607993,-2.323919 z"
inkscape:connector-curvature="0" />
<path
id="path3218"
style="font-style:normal;font-variant:normal;font-weight:800;font-stretch:normal;font-size:16.00038528px;font-family:Inter;-inkscape-font-specification:'Inter, Ultra-Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none"
d="m 69.778991,19.297853 c 1.215939,0 2.056868,-0.471602 2.534152,-1.363669 h 0.06818 v 1.215938 h 2.613699 v -5.931961 c 0,-1.846635 -1.642085,-2.909161 -3.86373,-2.909161 -2.346647,0 -3.676224,1.181847 -3.897821,2.772794 l 2.562562,0.09091 c 0.119321,-0.556832 0.579559,-0.897749 1.312532,-0.897749 0.681834,0 1.113663,0.329553 1.113663,0.914794 v 0.02841 c 0,0.534104 -0.57956,0.647743 -2.068232,0.778428 -1.767088,0.147731 -3.244396,0.801156 -3.244396,2.73302 0,1.727315 1.198892,2.568244 2.869387,2.568244 z m 0.857975,-1.818226 c -0.642061,0 -1.096617,-0.306825 -1.096617,-0.886384 0,-0.562514 0.443193,-0.903431 1.232984,-1.022752 0.517058,-0.07387 1.153437,-0.187505 1.465945,-0.352282 v 0.829566 c 0,0.852293 -0.715927,1.431852 -1.602312,1.431852 z"
inkscape:connector-curvature="0" />
<path
id="path3220"
style="font-style:normal;font-variant:normal;font-weight:800;font-stretch:normal;font-size:16.00038528px;font-family:Inter;-inkscape-font-specification:'Inter, Ultra-Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none"
d="m 80.767893,19.314899 c 2.454605,0 3.977369,-1.426171 4.051234,-3.585314 h -2.596653 c -0.102275,0.926159 -0.659107,1.431853 -1.420489,1.431853 -0.977296,0 -1.613675,-0.823883 -1.613675,-2.375057 0,-1.53981 0.642061,-2.363693 1.613675,-2.363693 0.795474,0 1.312532,0.539785 1.420489,1.431852 h 2.596653 c -0.0625,-2.147779 -1.630721,-3.54554 -4.056916,-3.54554 -2.744384,0 -4.403515,1.82959 -4.403515,4.505791 0,2.664836 1.647767,4.500108 4.409197,4.500108 z"
inkscape:connector-curvature="0" />
<path
id="path3222"
style="font-style:normal;font-variant:normal;font-weight:800;font-stretch:normal;font-size:16.00038528px;font-family:Inter;-inkscape-font-specification:'Inter, Ultra-Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none"
d="m 90.336304,19.314899 c 2.289828,0 3.795546,-1.107981 4.113736,-2.823932 l -2.551198,-0.07387 c -0.215914,0.579559 -0.78411,0.892067 -1.5114,0.892067 -1.068208,0 -1.727315,-0.710245 -1.727315,-1.778452 v -0.07386 h 5.818322 v -0.693199 c 0,-2.875069 -1.750042,-4.454653 -4.227374,-4.454653 -2.636427,0 -4.32965,1.806862 -4.32965,4.511473 0,2.795521 1.670495,4.494426 4.414879,4.494426 z m -1.676177,-5.471723 c 0.03977,-0.869339 0.727291,-1.528446 1.647767,-1.528446 0.914795,0 1.573902,0.636379 1.585266,1.528446 z"
inkscape:connector-curvature="0" />
<path
id="path3224"
style="font-style:normal;font-variant:normal;font-weight:800;font-stretch:normal;font-size:16.00038528px;font-family:Inter;-inkscape-font-specification:'Inter, Ultra-Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none"
d="m 99.10072,19.275125 c 1.31821,0 2.10232,-0.732972 2.4546,-1.53981 h 0.0852 v 1.414807 h 2.75007 V 7.5134784 h -2.77848 v 4.4035156 h -0.0568 C 101.22577,11.115838 100.46439,10.309 99.089356,10.309 c -1.823907,0 -3.477356,1.403443 -3.477356,4.483063 0,2.971662 1.562537,4.483062 3.48872,4.483062 z m 0.96593,-2.164825 c -1.028432,0 -1.602309,-0.914794 -1.602309,-2.323919 0,-1.403443 0.568196,-2.306874 1.602309,-2.306874 1.01707,0 1.608,0.880703 1.608,2.306874 0,1.414807 -0.59661,2.323919 -1.608,2.323919 z"
inkscape:connector-curvature="0" />
<path
id="path3226"
style="font-style:normal;font-variant:normal;font-weight:800;font-stretch:normal;font-size:16.00038528px;font-family:Inter;-inkscape-font-specification:'Inter, Ultra-Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none"
d="m 110.24303,19.314899 c 2.28983,0 3.79555,-1.107981 4.11374,-2.823932 l -2.5512,-0.07387 c -0.21591,0.579559 -0.78411,0.892067 -1.5114,0.892067 -1.06821,0 -1.72731,-0.710245 -1.72731,-1.778452 v -0.07386 h 5.81832 v -0.693199 c 0,-2.875069 -1.75004,-4.454653 -4.22737,-4.454653 -2.63643,0 -4.32965,1.806862 -4.32965,4.511473 0,2.795521 1.67049,4.494426 4.41487,4.494426 z m -1.67617,-5.471723 c 0.0398,-0.869339 0.72729,-1.528446 1.64777,-1.528446 0.91479,0 1.5739,0.636379 1.58526,1.528446 z"
inkscape:connector-curvature="0" />
<path
id="path3228"
style="font-style:normal;font-variant:normal;font-weight:800;font-stretch:normal;font-size:16.00038528px;font-family:Inter;-inkscape-font-specification:'Inter, Ultra-Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none"
d="m 119.89384,19.314899 c 2.4546,0 3.97736,-1.426171 4.05123,-3.585314 h -2.59665 c -0.10228,0.926159 -0.65911,1.431853 -1.42049,1.431853 -0.9773,0 -1.61368,-0.823883 -1.61368,-2.375057 0,-1.53981 0.64206,-2.363693 1.61368,-2.363693 0.79547,0 1.31253,0.539785 1.42049,1.431852 h 2.59665 c -0.0625,-2.147779 -1.63072,-3.54554 -4.05692,-3.54554 -2.74438,0 -4.40351,1.82959 -4.40351,4.505791 0,2.664836 1.64777,4.500108 4.4092,4.500108 z"
inkscape:connector-curvature="0" />
<path
id="path3230"
style="font-style:normal;font-variant:normal;font-weight:800;font-stretch:normal;font-size:16.00038528px;font-family:Inter;-inkscape-font-specification:'Inter, Ultra-Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none"
d="m 125.3826,19.150122 h 2.77848 v -2.619381 l 0.56251,-0.681835 2.0796,3.301216 h 3.2103 l -3.22167,-4.926255 3.09667,-3.801228 h -3.1478 l -2.45461,3.125076 h -0.125 V 7.5134784 h -2.77848 z"
inkscape:connector-curvature="0" />
</g>
<path
d="m 2146.72,133.51812 a 23.030019,11.514995 45 0 1 -24.427,-8.1423 23.030019,11.514995 45 0 1 -8.1423,-24.427 23.030019,11.514995 45 0 1 24.427,8.1423 23.030019,11.514995 45 0 1 8.1423,24.427 z m -16.2137,16.2138 a 23.030019,11.514995 45 0 1 -24.427,-8.1424 23.030019,11.514995 45 0 1 -8.1424,-24.4269 23.030019,11.514995 45 0 1 24.4271,8.1422 23.030019,11.514995 45 0 1 8.1423,24.4271 z"
style="opacity:1;vector-effect:none;fill:none;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:37.79527664;stroke-opacity:1"
id="path1681-6-5-3-7-4-9-2-0-2-7-6"
inkscape:connector-curvature="0" />
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 158 KiB

View File

@ -133,10 +133,6 @@ function load_spaces(id, is_home, on_success, on_error) {
}, on_error); }, on_error);
} }
function load_writable_folders( on_success, on_error) {
load_resource("get", "/spaces?writablefolders=true", null, on_success, on_error);
}
function load_history(s, on_success, on_error) { function load_history(s, on_success, on_error) {
load_resource("get", "/spaces/"+ s._id +"/digest", null, on_success, on_error); load_resource("get", "/spaces/"+ s._id +"/digest", null, on_success, on_error);
} }
@ -182,12 +178,10 @@ function delete_space(s, on_success, on_error) {
load_resource("delete", "/spaces/"+s._id, null, on_success, on_error); load_resource("delete", "/spaces/"+s._id, null, on_success, on_error);
} }
function delete_artifact(a, on_success, on_error) { function delete_artifact(a, on_success, on_error) {
load_resource("delete", "/spaces/"+a.space_id+"/artifacts/"+a._id); load_resource("delete", "/spaces/"+a.space_id+"/artifacts/"+a._id);
} }
function duplicate_space(s, to_space_id, on_success, on_error) { function duplicate_space(s, to_space_id, on_success, on_error) {
var path = "/spaces/"+s._id+"/duplicate"; var path = "/spaces/"+s._id+"/duplicate";
if(to_space_id) { if(to_space_id) {
@ -266,8 +260,8 @@ function delete_user(u, password, on_success, on_error) {
load_resource("delete", "/users/"+u._id +"?password="+password,null,on_success,on_error); load_resource("delete", "/users/"+u._id +"?password="+password,null,on_success,on_error);
} }
function create_user(name, email, password, password_confirmation, on_success, on_error) { function create_user(name, email, password, password_confirmation, invite_code, on_success, on_error) {
load_resource("post", "/users", {email:email, nickname:name, password:password, password_confirmation: password_confirmation}, on_success, on_error); load_resource("post", "/users", {email:email, nickname:name, password:password, password_confirmation: password_confirmation, invite_code: invite_code}, on_success, on_error);
} }
function create_session(email, password, on_success, on_error) { function create_session(email, password, on_success, on_error) {

File diff suppressed because it is too large Load Diff

View File

@ -8,24 +8,22 @@ SpacedeckAccount = {
account_confirmed_sent: false, account_confirmed_sent: false,
account_tab: 'invoices', account_tab: 'invoices',
password_change_error: null, password_change_error: null,
feedback_text: "" feedback_text: "",
}, },
methods: { methods: {
show_account: function(user) { show_account: function() {
this.activate_dropdown('account'); this.activate_dropdown('account');
this.load_subscription();
this.load_billing();
}, },
account_save_user_digest: function(val) { account_save_user_digest: function(val) {
this.user.preferences.daily_digest = val; this.user.prefs_email_digest = val;
this.save_user(function(){ this.save_user(function() {
}); });
}, },
account_save_user_notifications: function(val) { account_save_user_notifications: function(val) {
this.user.preferences.email_notifications = val; this.user.prefs_email_notifications = val;
this.save_user(function(){ this.save_user(function() {
}); });
}, },
@ -36,13 +34,11 @@ SpacedeckAccount = {
save_user_language: function(lang) { save_user_language: function(lang) {
localStorage.lang = lang; localStorage.lang = lang;
if (this.user.preferences) { this.user.prefs_language = lang;
this.user.preferences.language = lang; this.save_user(function() {
this.save_user(function() { window._spacedeck_location_change = true;
window._spacedeck_location_change = true; location.href="/spaces";
location.href="/spaces"; }.bind(this));
}.bind(this));
}
}, },
save_user: function(on_success) { save_user: function(on_success) {

View File

@ -41,7 +41,7 @@ var SpacedeckBoardArtifacts = {
if ("medium_for_object" in this) { if ("medium_for_object" in this) {
var medium = this.medium_for_object[a._id]; var medium = this.medium_for_object[a._id];
if (medium && a._id != this.editing_artifact_id) { if (medium && a._id != this.editing_artifact_id) {
medium.value(a.description); medium.value(a.description.toString());
} }
} }
}, },
@ -61,16 +61,16 @@ var SpacedeckBoardArtifacts = {
}, },
artifact_link: function(a) { artifact_link: function(a) {
if (a.meta && a.meta.link_uri) { if (a.link_uri) {
return a.meta.link_uri; return a.link_uri;
} else { } else {
return ""; return "";
} }
}, },
artifact_link_caption: function(a) { artifact_link_caption: function(a) {
if (a.meta && a.meta.link_uri) { if (a.link_uri) {
var parts = a.meta.link_uri.split("/"); var parts = a.link_uri.split("/");
// scheme://domain.foo/... // scheme://domain.foo/...
// 0 1 2 // 0 1 2
if (parts.length>2) { if (parts.length>2) {
@ -88,10 +88,11 @@ var SpacedeckBoardArtifacts = {
}, },
artifact_is_text_blank: function(a) { artifact_is_text_blank: function(a) {
if(a.description){ if (a.description) {
var filtered = a.description.replace(/<[^>]+>/g,"").replace(/\s/g,""); desc = a.description.toString();
var filtered = desc.replace(/<[^>]+>/g,"").replace(/\s/g,"");
return (filtered.length<1); return (filtered.length<1);
}else{ } else {
return false; return false;
} }
}, },
@ -102,11 +103,9 @@ var SpacedeckBoardArtifacts = {
if (this.artifact_is_selected(a) && this.editing_artifact_id!=a._id) clzs.push("selected"); if (this.artifact_is_selected(a) && this.editing_artifact_id!=a._id) clzs.push("selected");
if (!a._id) clzs.push("creating"); if (!a._id) clzs.push("creating");
if (a.style) { if (a.align) clzs.push("align-"+a.align);
clzs.push("align-"+a.style.align); if (a.valign) clzs.push("align-"+a.valign);
clzs.push("align-"+a.style.valign);
}
clzs.push("state-"+a.state); clzs.push("state-"+a.state);
if (this.artifact_is_text_blank(a)) { if (this.artifact_is_text_blank(a)) {
@ -123,56 +122,56 @@ var SpacedeckBoardArtifacts = {
artifact_inner_style: function(a) { artifact_inner_style: function(a) {
var styles = []; var styles = [];
if (a.style) { //if (a.style) {
var svg_style = ((a.mime.match("vector") || a.mime.match("shape")) && a.style.shape!="square"); var svg_style = ((a.mime.match("vector") || a.mime.match("shape")) && a.shape!="square");
if (!svg_style) { if (!svg_style) {
if (a.style.stroke) { if (a.stroke) {
styles.push("border-width:"+a.style.stroke+"px"); styles.push("border-width:"+a.stroke+"px");
styles.push("border-style:"+(a.style.stroke_style||"solid")); styles.push("border-style:"+(a.stroke_style||"solid"));
} }
if (a.style.stroke_color) { if (a.stroke_color) {
styles.push("border-color:"+a.style.stroke_color); styles.push("border-color:"+a.stroke_color);
} }
if (a.style.border_radius) { if (a.border_radius) {
styles.push("border-radius:"+a.style.border_radius+"px"); styles.push("border-radius:"+a.border_radius+"px");
} }
} }
if (a.style.fill_color && !svg_style) { if (a.fill_color && !svg_style) {
styles.push("background-color:"+a.style.fill_color); styles.push("background-color:"+a.fill_color);
} }
if (a.style.text_color) { if (a.text_color) {
styles.push("color:"+a.style.text_color); styles.push("color:"+a.text_color);
} }
var filters = []; var filters = [];
if (!isNaN(a.style.brightness) && a.style.brightness != 100) { if (!isNaN(a.brightness) && a.brightness != 100) {
filters.push("brightness("+a.style.brightness+"%)"); filters.push("brightness("+a.brightness+"%)");
} }
if (!isNaN(a.style.contrast) && a.style.contrast != 100) { if (!isNaN(a.contrast) && a.contrast != 100) {
filters.push("contrast("+a.style.contrast+"%)"); filters.push("contrast("+a.contrast+"%)");
} }
if (!isNaN(a.style.opacity) && a.style.opacity != 100) { if (!isNaN(a.opacity) && a.opacity != 100) {
filters.push("opacity("+a.style.opacity+"%)"); filters.push("opacity("+a.opacity+"%)");
} }
if (!isNaN(a.style.hue) && a.style.hue) { if (!isNaN(a.hue) && a.hue) {
filters.push("hue-rotate("+a.style.hue+"deg)"); filters.push("hue-rotate("+a.hue+"deg)");
} }
if (!isNaN(a.style.saturation) && a.style.saturation != 100) { if (!isNaN(a.saturation) && a.saturation != 100) {
filters.push("saturate("+a.style.saturation+"%)"); filters.push("saturate("+a.saturation+"%)");
} }
if (!isNaN(a.style.blur) && a.style.blur) { if (!isNaN(a.blur) && a.blur) {
filters.push("blur("+a.style.blur+"px)"); filters.push("blur("+a.blur+"px)");
} }
if (filters.length) { if (filters.length) {
styles.push("-webkit-filter:"+filters.join(" ")); styles.push("-webkit-filter:"+filters.join(" "));
styles.push("filter:"+filters.join(" ")); styles.push("filter:"+filters.join(" "));
} }
} //}
return styles.join(";"); return styles.join(";");
}, },
@ -180,12 +179,10 @@ var SpacedeckBoardArtifacts = {
artifact_text_cell_style: function(a, for_text_editor) { artifact_text_cell_style: function(a, for_text_editor) {
var styles = []; var styles = [];
if (a.style) { if (a.padding_left) styles.push("padding-left:"+a.padding_left+"px");
if (a.style.padding_left) styles.push("padding-left:"+a.style.padding_left+"px"); if (a.padding_right) styles.push("padding-right:"+a.padding_right+"px");
if (a.style.padding_right) styles.push("padding-right:"+a.style.padding_right+"px"); if (a.padding_top) styles.push("padding-top:"+a.padding_top+"px");
if (a.style.padding_top) styles.push("padding-top:"+a.style.padding_top+"px"); if (a.padding_bottom) styles.push("padding-bottom:"+a.padding_bottom+"px");
if (a.style.padding_bottom) styles.push("padding-bottom:"+a.style.padding_bottom+"px");
}
return styles.join(";"); return styles.join(";");
}, },
@ -194,26 +191,22 @@ var SpacedeckBoardArtifacts = {
var styles = []; var styles = [];
var z = 0; var z = 0;
if (a.board) { z = a.z;
z = a.board.z; if (z<0) z=0; // fix negative z-index
if (z<0) z=0; // fix negative z-index
styles = [
styles = [ "left:" +a.x+"px",
"left:" +a.board.x+"px", "top:" +a.y+"px",
"top:" +a.board.y+"px", "width:" +a.w+"px",
"width:" +a.board.w+"px", "height:"+a.h+"px",
"height:"+a.board.h+"px", "z-index:"+z
"z-index:"+z ];
];
}
if (a.style) {
if (a.style.margin_left) styles.push("margin-left:"+a.style.margin_left+"px");
if (a.style.margin_right) styles.push("margin-right:"+a.style.margin_right+"px");
if (a.style.margin_top) styles.push("margin-top:"+a.style.margin_top+"px");
if (a.style.margin_bottom) styles.push("margin-bottom:"+a.style.margin_bottom+"px");
}
if (a.margin_left) styles.push("margin-left:"+a.margin_left+"px");
if (a.margin_right) styles.push("margin-right:"+a.margin_right+"px");
if (a.margin_top) styles.push("margin-top:"+a.margin_top+"px");
if (a.margin_bottom) styles.push("margin-bottom:"+a.margin_bottom+"px");
// FIXME: via class logic? // FIXME: via class logic?
if (a.mime.match("vector")) { if (a.mime.match("vector")) {
styles.push("overflow:visible"); styles.push("overflow:visible");
@ -241,7 +234,7 @@ var SpacedeckBoardArtifacts = {
artifact_thumbnail_uri: function(a) { artifact_thumbnail_uri: function(a) {
if (a.payload_thumbnail_big_uri && a.board) { if (a.payload_thumbnail_big_uri && a.board) {
if (a.board.w>800) { if (a.w>800) {
return a.payload_thumbnail_big_uri; return a.payload_thumbnail_big_uri;
} }
} }
@ -255,35 +248,35 @@ var SpacedeckBoardArtifacts = {
var type = parts[0]; var type = parts[0];
var provider = parts[1]; var provider = parts[1];
if (!a.meta || !a.meta.link_uri) { if (!a.link_uri) {
console.log("missing meta / link_uri: ",a); console.log("missing meta / link_uri: ",a);
console.log("type/provider: ",type,provider); console.log("type/provider: ",type,provider);
return ("missing metadata: "+a._id); return ("missing metadata: "+a._id);
} }
if (provider=="youtube") { if (provider=="youtube") {
var vid = a.meta.link_uri.match(/(v=|\/)([a-zA-Z0-9\-_]{11})/); var vid = a.link_uri.match(/(v=|\/)([a-zA-Z0-9\-_]{11})/);
if (vid && vid.length>2) { if (vid && vid.length>2) {
var uri = "https://youtube.com/embed/"+vid[2]; var uri = "https://youtube.com/embed/"+vid[2];
return "<iframe frameborder=0 allowfullscreen src=\""+uri+"?showinfo=0&rel=0&controls=0\"></iframe>"; return "<iframe frameborder=0 allowfullscreen src=\""+uri+"?showinfo=0&rel=0&controls=0\"></iframe>";
} else return "Can't resolve: "+a.payload_uri; } else return "Can't resolve: "+a.payload_uri;
} else if (provider=="dailymotion") { } else if (provider=="dailymotion") {
var match = a.meta.link_uri.match(/dailymotion.com\/video\/([^<]*)/); var match = a.link_uri.match(/dailymotion.com\/video\/([^<]*)/);
if (match && match.length>1) { if (match && match.length>1) {
var uri = "https://www.dailymotion.com/embed/video/"+match[1]; var uri = "https://www.dailymotion.com/embed/video/"+match[1];
return "<iframe frameborder=0 allowfullscreen src=\""+uri+"\"></iframe>"; return "<iframe frameborder=0 allowfullscreen src=\""+uri+"\"></iframe>";
} else return "Can't resolve: "+a.payload_uri; } else return "Can't resolve: "+a.payload_uri;
} else if (provider=="vimeo") { } else if (provider=="vimeo") {
var match = a.meta.link_uri.match(/https?:\/\/(www\.)?vimeo.com\/(\d+)($|\/)/); var match = a.link_uri.match(/https?:\/\/(www\.)?vimeo.com\/(\d+)($|\/)/);
if (match) { if (match) {
var uri = "https://player.vimeo.com/video/"+match[2]; var uri = "https://player.vimeo.com/video/"+match[2];
return "<iframe frameborder=0 allowfullscreen src=\""+uri+"\"></iframe>"; return "<iframe frameborder=0 allowfullscreen src=\""+uri+"\"></iframe>";
} else return "Can't resolve: "+a.payload_uri; } else return "Can't resolve: "+a.payload_uri;
} else if (provider=="soundcloud") { } else if (provider=="soundcloud") {
return '<iframe width="100%" height="166" scrolling="no" frameborder="no" src="https://w.soundcloud.com/player/?url='+a.meta.link_uri.replace(":", "%3A")+'"></iframe>'; return '<iframe width="100%" height="166" scrolling="no" frameborder="no" src="https://w.soundcloud.com/player/?url='+a.link_uri.replace(":", "%3A")+'"></iframe>';
} else if (provider=="spacedeck") { } else if (provider=="spacedeck") {
@ -299,8 +292,8 @@ var SpacedeckBoardArtifacts = {
if (mtype != "vector" && mtype != "shape") return ""; if (mtype != "vector" && mtype != "shape") return "";
var shape = a.style.shape || ""; var shape = a.shape || "";
var padding = 32 + a.style.stroke*2; var padding = 32 + a.stroke*2;
var path_svg; var path_svg;
var fill = ""; var fill = "";
@ -310,13 +303,13 @@ var SpacedeckBoardArtifacts = {
fill = "fill:none"; fill = "fill:none";
} else { } else {
path_svg = render_vector_shape(a, padding); path_svg = render_vector_shape(a, padding);
fill = "fill:"+a.style.fill_color+";"; fill = "fill:"+a.fill_color+";";
padding = 0; padding = 0;
} }
var margin = padding; var margin = padding;
var svg = "<svg xmlns='http://www.w3.org/2000/svg' width='"+(a.board.w+2*padding)+"' height='"+(a.board.h+2*padding)+"' "; var svg = "<svg xmlns='http://www.w3.org/2000/svg' width='"+(a.w+2*padding)+"' height='"+(a.h+2*padding)+"' ";
svg += "style='margin-left:"+(-margin)+"px;margin-top:"+(-margin)+"px;stroke-width:"+a.style.stroke+";stroke:"+a.style.stroke_color+";"+fill+"'>"; svg += "style='margin-left:"+(-margin)+"px;margin-top:"+(-margin)+"px;stroke-width:"+a.stroke+";stroke:"+a.stroke_color+";"+fill+"'>";
svg += path_svg; svg += path_svg;
svg += "</svg>"; svg += "</svg>";
@ -329,10 +322,10 @@ var SpacedeckBoardArtifacts = {
if (arts.length==0) return null; if (arts.length==0) return null;
r = { r = {
x1: parseInt(_.min(arts.map(function(a){return a.board.x}))), x1: parseInt(_.min(arts.map(function(a){return a.x}))),
y1: parseInt(_.min(arts.map(function(a){return a.board.y}))), y1: parseInt(_.min(arts.map(function(a){return a.y}))),
x2: parseInt(_.max(arts.map(function(a){return a.board.x+a.board.w}))), x2: parseInt(_.max(arts.map(function(a){return a.x+a.w}))),
y2: parseInt(_.max(arts.map(function(a){return a.board.y+a.board.h}))) y2: parseInt(_.max(arts.map(function(a){return a.y+a.h})))
}; };
r.x=r.x1; r.x=r.x1;
r.y=r.y1; r.y=r.y1;
@ -356,7 +349,7 @@ var SpacedeckBoardArtifacts = {
artifacts_in_rect: function(rect) { artifacts_in_rect: function(rect) {
return _.filter(this.active_space_artifacts, function(a) { return _.filter(this.active_space_artifacts, function(a) {
return this.rects_intersecting(a.board, rect); return this.rects_intersecting(a, rect);
}.bind(this)); }.bind(this));
}, },
@ -366,15 +359,15 @@ var SpacedeckBoardArtifacts = {
var rect = this.artifact_selection_rect(); var rect = this.artifact_selection_rect();
var overlapping = _.filter(this.artifacts_in_rect(rect), function(a){return !this.is_selected(a)}.bind(this)); var overlapping = _.filter(this.artifacts_in_rect(rect), function(a){return !this.is_selected(a)}.bind(this));
var max_z = _.max(overlapping,function(a){ return a.board.z; }); var max_z = _.max(overlapping,function(a){ return a.z; });
if (max_z.board) { if (max_z.board) {
max_z = max_z.board.z + 1; max_z = max_z.z + 1;
} else { } else {
max_z = 1; max_z = 1;
} }
this.update_selected_artifacts(function(a) { this.update_selected_artifacts(function(a) {
return { board: _.extend(a.board, { z: max_z }) }; return { z: max_z };
}); });
}, },
@ -384,15 +377,15 @@ var SpacedeckBoardArtifacts = {
var rect = this.artifact_selection_rect(); var rect = this.artifact_selection_rect();
var overlapping = _.filter(this.artifacts_in_rect(rect), function(a){return !this.is_selected(a);}.bind(this)); var overlapping = _.filter(this.artifacts_in_rect(rect), function(a){return !this.is_selected(a);}.bind(this));
var min_z = _.min(overlapping,function(a){ return (a.board?a.board.z:0); }); var min_z = _.min(overlapping,function(a){ return a.z; });
if (min_z.board) { if (min_z.board) {
min_z = min_z.board.z - 1; min_z = min_z.z - 1;
} else { } else {
min_z = 0; min_z = 0;
} }
var my_z = _.max(this.selected_artifacts(),function(a){ (a.board?a.board.z:0); }); var my_z = _.max(this.selected_artifacts(),function(a){ return a.z; });
if (my_z.board) { if (my_z.board) {
my_z = my_z.board.z - 1; my_z = my_z.z - 1;
} else { } else {
my_z = 0; my_z = 0;
} }
@ -400,14 +393,14 @@ var SpacedeckBoardArtifacts = {
// TODO: move all other items up in this case? // TODO: move all other items up in this case?
if (min_z < 0) { if (min_z < 0) {
this.update_artifacts(overlapping, function(a) { this.update_artifacts(overlapping, function(a) {
return { board: _.extend(a.board, { z: (my_z + (a.board?a.board.z:0) + 1) }) }; return { z: (my_z + a.z + 1) };
}); });
return; return;
} }
this.update_selected_artifacts(function(a) { this.update_selected_artifacts(function(a) {
return { board: _.extend(a.board, { z: min_z }) }; return { z: min_z };
}); });
}, },
@ -416,7 +409,7 @@ var SpacedeckBoardArtifacts = {
var rect = this.artifact_selection_rect(); var rect = this.artifact_selection_rect();
this.update_selected_artifacts(function(a) { this.update_selected_artifacts(function(a) {
return { board: _.extend(a.board, { x: rect.x1 }) }; return { x: rect.x1 };
}); });
}, },
@ -425,7 +418,7 @@ var SpacedeckBoardArtifacts = {
var rect = this.artifact_selection_rect(); var rect = this.artifact_selection_rect();
this.update_selected_artifacts(function(a) { this.update_selected_artifacts(function(a) {
return { board: _.extend(a.board, { y: rect.y1 }) }; return { y: rect.y1 };
}); });
}, },
@ -434,7 +427,7 @@ var SpacedeckBoardArtifacts = {
var rect = this.artifact_selection_rect(); var rect = this.artifact_selection_rect();
this.update_selected_artifacts(function(a) { this.update_selected_artifacts(function(a) {
return { board: _.extend(a.board, { x: rect.x2 - a.board.w }) }; return { x: rect.x2 - a.w };
}); });
}, },
@ -443,7 +436,7 @@ var SpacedeckBoardArtifacts = {
var rect = this.artifact_selection_rect(); var rect = this.artifact_selection_rect();
this.update_selected_artifacts(function(a) { this.update_selected_artifacts(function(a) {
return { board: _.extend(a.board, { y: rect.y2 - a.board.h }) }; return { y: rect.y2 - a.h };
}); });
}, },
@ -453,7 +446,7 @@ var SpacedeckBoardArtifacts = {
var rect = this.artifact_selection_rect(); var rect = this.artifact_selection_rect();
var cx = rect.x1 + (rect.x2-rect.x1)/2; var cx = rect.x1 + (rect.x2-rect.x1)/2;
this.update_selected_artifacts(function(a) { this.update_selected_artifacts(function(a) {
return { board: _.extend(a.board, { x: cx - a.board.w/2 }) }; return { x: cx - a.w/2 };
}); });
}, },
@ -463,7 +456,7 @@ var SpacedeckBoardArtifacts = {
var rect = this.artifact_selection_rect(); var rect = this.artifact_selection_rect();
var cy = rect.y1 + (rect.y2-rect.y1)/2; var cy = rect.y1 + (rect.y2-rect.y1)/2;
this.update_selected_artifacts(function(a) { this.update_selected_artifacts(function(a) {
return { board: _.extend(a.board, { y: cy - a.board.h/2 }) }; return { y: cy - a.h/2 };
}); });
}, },
@ -473,11 +466,11 @@ var SpacedeckBoardArtifacts = {
var arts = this.selected_artifacts(); var arts = this.selected_artifacts();
if (arts.length<2) return; if (arts.length<2) return;
var totalw = _.reduce(arts, function(sum, a) { return sum + a.board.w }, 0); var totalw = _.reduce(arts, function(sum, a) { return sum + a.w }, 0);
var avgw = totalw / arts.length; var avgw = totalw / arts.length;
this.update_selected_artifacts(function(a) { this.update_selected_artifacts(function(a) {
return { board: _.extend(a.board, { w: avgw }) }; return { w: avgw };
}); });
}, },
@ -487,11 +480,11 @@ var SpacedeckBoardArtifacts = {
var arts = this.selected_artifacts(); var arts = this.selected_artifacts();
if (arts.length<2) return; if (arts.length<2) return;
var totalh = _.reduce(arts, function(sum, a) { return sum + a.board.h }, 0); var totalh = _.reduce(arts, function(sum, a) { return sum + a.h }, 0);
var avgh = totalh / arts.length; var avgh = totalh / arts.length;
this.update_selected_artifacts(function(a) { this.update_selected_artifacts(function(a) {
return { board: _.extend(a.board, { h: avgh }) }; return { h: avgh };
}); });
}, },
@ -506,16 +499,16 @@ var SpacedeckBoardArtifacts = {
var selected = this.selected_artifacts(); var selected = this.selected_artifacts();
if (selected.length<3) return; if (selected.length<3) return;
var sorted = _.sortBy(selected, function(a) { return a.board.x }); var sorted = _.sortBy(selected, function(a) { return a.x });
var startx = sorted[0].board.x + sorted[0].board.w/2; var startx = sorted[0].x + sorted[0].w/2;
var stopx = _.last(sorted).board.x + _.last(sorted).board.w/2; var stopx = _.last(sorted).x + _.last(sorted).w/2;
var step = (stopx-startx)/(sorted.length-1); var step = (stopx-startx)/(sorted.length-1);
for (var i=1; i<sorted.length-1; i++) { for (var i=1; i<sorted.length-1; i++) {
var a = sorted[i]; var a = sorted[i];
var x = startx + step*i - a.board.w/2; var x = startx + step*i - a.w/2;
this.update_artifacts([a],function(a) { this.update_artifacts([a],function(a) {
return { board: _.extend(a.board, {x: x}) } return { x: x }
}); });
} }
}, },
@ -526,16 +519,16 @@ var SpacedeckBoardArtifacts = {
var selected = this.selected_artifacts(); var selected = this.selected_artifacts();
if (selected.length<3) return; if (selected.length<3) return;
var sorted = _.sortBy(selected, function(a) { return a.board.y }); var sorted = _.sortBy(selected, function(a) { return a.y });
var starty = sorted[0].board.y + sorted[0].board.h/2; var starty = sorted[0].y + sorted[0].h/2;
var stopy = _.last(sorted).board.y + _.last(sorted).board.h/2; var stopy = _.last(sorted).y + _.last(sorted).h/2;
var step = (stopy-starty)/(sorted.length-1); var step = (stopy-starty)/(sorted.length-1);
for (var i=1; i<sorted.length-1; i++) { for (var i=1; i<sorted.length-1; i++) {
var a = sorted[i]; var a = sorted[i];
var y = starty + step*i - a.board.h/2; var y = starty + step*i - a.h/2;
this.update_artifacts([a],function(a) { this.update_artifacts([a],function(a) {
return { board: _.extend(a.board, {y: y}) } return { y: y }
}); });
} }
}, },
@ -546,21 +539,21 @@ var SpacedeckBoardArtifacts = {
var selected = this.selected_artifacts(); var selected = this.selected_artifacts();
if (selected.length<3) return; if (selected.length<3) return;
var sorted = _.sortBy(selected, function(a) { return a.board.x }); var sorted = _.sortBy(selected, function(a) { return a.x });
var startx = sorted[0].board.x; var startx = sorted[0].x;
var stopx = _.last(sorted).board.x + _.last(sorted).board.w; var stopx = _.last(sorted).x + _.last(sorted).w;
var range = stopx - startx; var range = stopx - startx;
var totalw = _.reduce(sorted, function(sum, a) { return sum + a.board.w }, 0); var totalw = _.reduce(sorted, function(sum, a) { return sum + a.w }, 0);
var avgs = (range - totalw) / (sorted.length-1); var avgs = (range - totalw) / (sorted.length-1);
var prevend = startx + sorted[0].board.w; var prevend = startx + sorted[0].w;
for (var i=1; i<sorted.length-1; i++) { for (var i=1; i<sorted.length-1; i++) {
var a = sorted[i]; var a = sorted[i];
var x = prevend + avgs; var x = prevend + avgs;
this.update_artifacts([a],function(a) { this.update_artifacts([a],function(a) {
return { board: _.extend(a.board, {x: x}) } return { x: x }
}); });
prevend = x+a.board.w; prevend = x+a.w;
} }
}, },
@ -570,21 +563,21 @@ var SpacedeckBoardArtifacts = {
var selected = this.selected_artifacts(); var selected = this.selected_artifacts();
if (selected.length<3) return; if (selected.length<3) return;
var sorted = _.sortBy(selected, function(a) { return a.board.y }); var sorted = _.sortBy(selected, function(a) { return a.y });
var starty = sorted[0].board.y; var starty = sorted[0].y;
var stopy = _.last(sorted).board.y + _.last(sorted).board.h; var stopy = _.last(sorted).y + _.last(sorted).h;
var range = stopy - starty; var range = stopy - starty;
var totalh = _.reduce(sorted, function(sum, a) { return sum + a.board.h }, 0); var totalh = _.reduce(sorted, function(sum, a) { return sum + a.h }, 0);
var avgs = (range - totalh) / (sorted.length-1); var avgs = (range - totalh) / (sorted.length-1);
var prevend = starty + sorted[0].board.h; var prevend = starty + sorted[0].h;
for (var i=1; i<sorted.length-1; i++) { for (var i=1; i<sorted.length-1; i++) {
var a = sorted[i]; var a = sorted[i];
var y = prevend + avgs; var y = prevend + avgs;
this.update_artifacts([a],function(a) { this.update_artifacts([a],function(a) {
return { board: _.extend(a.board, {y: y}) } return { y: y }
}); });
prevend = y+a.board.h; prevend = y+a.h;
} }
}, },
@ -594,20 +587,22 @@ var SpacedeckBoardArtifacts = {
var selected = this.selected_artifacts(); var selected = this.selected_artifacts();
if (selected.length<2) return; if (selected.length<2) return;
var sorted = _.sortBy(selected, function(a) { return a.board.x+a.board.y*this.active_space.advanced.width }.bind(this)); var sorted = _.sortBy(selected, function(a) { return a.x+a.y*this.active_space.width }.bind(this));
var minx = sorted[0].board.x; var minx = sorted[0].x;
var miny = sorted[0].board.y; var miny = sorted[0].y;
var sorted = _.sortBy(selected, function(a) { return -Math.max(a.board.w,a.board.h) }.bind(this)); var sorted = _.sortBy(selected, function(a) { return -Math.max(a.w,a.h) }.bind(this));
var blocks = []; var blocks = [];
var padding = 10;
for (var i=0; i<sorted.length; i++) { for (var i=0; i<sorted.length; i++) {
var a = sorted[i]; var a = sorted[i];
blocks.push({ blocks.push({
w: a.board.w, w: a.w+padding,
h: a.board.h, h: a.h+padding,
a: a a: a
}); });
} }
@ -620,10 +615,10 @@ var SpacedeckBoardArtifacts = {
if (block.fit) { if (block.fit) {
var a = block.a; var a = block.a;
this.update_artifacts([a],function(a) { this.update_artifacts([a],function(a) {
return { board: _.extend(a.board, { return {
x: minx+block.fit.x, x: minx+block.fit.x,
y: miny+block.fit.y y: miny+block.fit.y
}) } }
}); });
} }
} }

View File

@ -170,7 +170,6 @@ var SpacedeckRoutes = {
location.href = "/"; location.href = "/";
} else { } else {
this.active_view = "account"; this.active_view = "account";
this.load_subscription();
} }
}.bind(this) }.bind(this)
} }
@ -253,8 +252,6 @@ var SpacedeckRoutes = {
// #hash // #hash
if (event.currentTarget.hash && event.currentTarget.hash.length>1) return; if (event.currentTarget.hash && event.currentTarget.hash.length>1) return;
console.log("clicked", event.currentTarget.pathname);
// external link? // external link?
if (event.currentTarget.host != location.host) return; if (event.currentTarget.host != location.host) return;
@ -270,35 +267,6 @@ var SpacedeckRoutes = {
event.preventDefault(); event.preventDefault();
}.bind(this)); }.bind(this));
if (location.host!=ENV.webHost) {
if (!subdomainTeam) {
location.href = ENV.webEndpoint;
return;
} else {
if(subdomainTeam.subdomain) {
var realHost = (subdomainTeam.subdomain + "." + ENV.webHost);
if (location.host != realHost) {
location.href = realHost;
return;
}
} else {
location.href = ENV.webEndpoint;
return;
}
}
}
if (this.logged_in) {
if (this.user.team) {
if (this.user.team.subdomain && this.user.team.subdomain.length > 0) {
var realHost = (this.user.team.subdomain + "." + ENV.webHost);
if (location.host != realHost) {
location.href = location.protocol + "//" + realHost + location.pathname;
return;
}
}
}
}
this.internal_route(location.pathname); this.internal_route(location.pathname);
}, },

View File

@ -63,8 +63,8 @@ var SpacedeckSections = {
active_style: { active_style: {
border_radius: 0, border_radius: 0,
stroke: 0, stroke: 0,
font_family: "Avenir W01", font_family: "Inter",
font_size: 18, font_size: 36,
line_height: 1.5, line_height: 1.5,
letter_spacing: 0, letter_spacing: 0,
@ -110,18 +110,30 @@ var SpacedeckSections = {
color_picker_opacity: 255, color_picker_opacity: 255,
swatches: [ swatches: [
{id:0, hex:"#4a2f7e"}, {id:1, hex:"#ff00ff"},
{id:1, hex:"#9b59b6"}, {id:2, hex:"#ffff00"},
{id:2, hex:"#3498db"}, {id:3, hex:"#00ffff"},
{id:3, hex:"#2ecc71"}, {id:5, hex:"#ff0000"},
{id:4, hex:"#f1c40f"}, {id:6, hex:"#00ff00"},
{id:5, hex:"#e67e22"}, {id:7, hex:"#0000ff"},
{id:6, hex:"#d55c4b"}, {id:8, hex:"#000000"},
{id:7, hex:"#6f4021"}, {id:9, hex:"#222222"},
{id:8, hex:"#ffffff"}, {id:10, hex:"#444444"},
{id:9, hex:"#95a5a6"}, {id:11, hex:"#888888"},
{id:10, hex:"#252525"}, {id:12, hex:"#bbbbbb"},
{id:11, hex:"rgba(0,0,0,0)"}, {id:13, hex:"#dddddd"},
{id:14, hex:"#ffffff"},
{id:20, hex:"#4a2f7e"},
{id:21, hex:"#9b59b6"},
{id:22, hex:"#3498db"},
{id:23, hex:"#2ecc71"},
{id:24, hex:"#f1c40f"},
{id:25, hex:"#e67e22"},
{id:26, hex:"#d55c4b"},
{id:27, hex:"#6f4021"},
{id:29, hex:"#95a5a6"},
{id:30, hex:"rgba(0,0,0,0)"},
], ],
swatches_text: [ swatches_text: [
@ -136,18 +148,8 @@ var SpacedeckSections = {
], ],
fonts: [ fonts: [
"Arial", "Inter",
"Courier", "Courier"
"Georgia",
"Verdana",
"Comic Sans MS",
"Montserrat",
"Lato",
"Roboto",
"Crimson Text",
"EB Garamond",
"Vollkorn",
"Avenir W01"
], ],
detected_text_formats: {}, detected_text_formats: {},
@ -180,7 +182,7 @@ var SpacedeckSections = {
toolbar_props_in: false, toolbar_props_in: false,
toolbar_artifacts_x: "-1000px", toolbar_artifacts_x: "-1000px",
toolbar_artifacts_y: "-1000px", toolbar_artifacts_y: "-1000px",
toolbar_artifacts_in: false toolbar_artifacts_in: true
}, },
methods: { methods: {
@ -369,8 +371,8 @@ var SpacedeckSections = {
// canvas // canvas
this.$watch('active_style.background_color', function (value, mutation) { this.$watch('active_style.background_color', function (value, mutation) {
if (this.active_style.background_color != this.active_space.advanced.background_color) { if (this.active_style.background_color != this.active_space.background_color) {
this.$set("active_space.advanced.background_color",this.active_style.background_color); this.$set("active_space.background_color",this.active_style.background_color);
this.throttled_save_active_space(); this.throttled_save_active_space();
} }
@ -448,7 +450,7 @@ var SpacedeckSections = {
for (var i=0; i<props.length; i++) { for (var i=0; i<props.length; i++) {
var prop = props[i]; var prop = props[i];
this.active_style[prop]=a.style[prop]; this.active_style[prop]=a[prop];
} }
// defaults // defaults
@ -457,10 +459,10 @@ var SpacedeckSections = {
this.active_style.line_height = this.default_style.line_height; this.active_style.line_height = this.default_style.line_height;
this.active_style.letter_spacing = this.default_style.letter_spacing; this.active_style.letter_spacing = this.default_style.letter_spacing;
this.active_style.padding_top = a.style.padding_top || 0; this.active_style.padding_top = a.padding_top || 0;
this.active_style.padding_bottom = a.style.padding_bottom || 0; this.active_style.padding_bottom = a.padding_bottom || 0;
this.active_style.padding_left = a.style.padding_left || 0; this.active_style.padding_left = a.padding_left || 0;
this.active_style.padding_right = a.style.padding_right || 0; this.active_style.padding_right = a.padding_right || 0;
if (this.active_style.padding_top == this.active_style.padding_bottom) { if (this.active_style.padding_top == this.active_style.padding_bottom) {
this.active_style.padding_vert = this.active_style.padding_top; this.active_style.padding_vert = this.active_style.padding_top;
@ -476,10 +478,10 @@ var SpacedeckSections = {
this.active_style.padding = this.active_style.padding_top; this.active_style.padding = this.active_style.padding_top;
} }
this.active_style.margin_top = a.style.margin_top || 0; this.active_style.margin_top = a.margin_top || 0;
this.active_style.margin_bottom = a.style.margin_bottom || 0; this.active_style.margin_bottom = a.margin_bottom || 0;
this.active_style.margin_left = a.style.margin_left || 0; this.active_style.margin_left = a.margin_left || 0;
this.active_style.margin_right = a.style.margin_right || 0; this.active_style.margin_right = a.margin_right || 0;
if (this.active_style.margin_top == this.active_style.margin_bottom) { if (this.active_style.margin_top == this.active_style.margin_bottom) {
this.active_style.margin_vert = this.active_style.margin_top; this.active_style.margin_vert = this.active_style.margin_top;
@ -758,8 +760,8 @@ var SpacedeckSections = {
}, },
resize_minimap: function() { resize_minimap: function() {
if (!this.active_space || !this.active_space.advanced) return; if (!this.active_space) return;
this.minimap_scale = this.active_space.advanced.width/100.0; this.minimap_scale = this.active_space.width/100.0;
}, },
handle_minimap_mouseup: function(evt) { handle_minimap_mouseup: function(evt) {
@ -921,7 +923,7 @@ var SpacedeckSections = {
discover_zones: function() { discover_zones: function() {
this.zones = _.sortBy(_.filter(this.active_space_artifacts, function(a) { return (a.mime=="x-spacedeck/zone") }), this.zones = _.sortBy(_.filter(this.active_space_artifacts, function(a) { return (a.mime=="x-spacedeck/zone") }),
function(z){return z.style.order}); function(z){return z.order});
}, },
artifact_plaintext: function(a) { artifact_plaintext: function(a) {
@ -1015,10 +1017,10 @@ var SpacedeckSections = {
arts = _.filter(arts); // remove any nulls arts = _.filter(arts); // remove any nulls
return { return {
x1: parseInt(_.min(arts.map(function(a){return ((!a.board || !a.board.x)?0:a.board.x)}))), x1: parseInt(_.min(arts.map(function(a){return ((!a || !a.x)?0:a.x)}))),
y1: parseInt(_.min(arts.map(function(a){return ((!a.board || !a.board.y)?0:a.board.y)}))), y1: parseInt(_.min(arts.map(function(a){return ((!a || !a.y)?0:a.y)}))),
x2: parseInt(_.max(arts.map(function(a){return (!a.board?0:a.board.x+a.board.w)}))), x2: parseInt(_.max(arts.map(function(a){return (!a?0:a.x+a.w)}))),
y2: parseInt(_.max(arts.map(function(a){return (!a.board?0:a.board.y+a.board.h)}))) y2: parseInt(_.max(arts.map(function(a){return (!a?0:a.y+a.h)})))
}; };
}, },
@ -1057,7 +1059,7 @@ var SpacedeckSections = {
this.toolbar_props_x = pp.x+"px"; this.toolbar_props_x = pp.x+"px";
this.toolbar_props_y = pp.y+"px"; this.toolbar_props_y = pp.y+"px";
this.hide_toolbar_artifacts(); //this.hide_toolbar_artifacts();
} }
this.selection_metrics.x1 = sr.x1; this.selection_metrics.x1 = sr.x1;
@ -1076,7 +1078,7 @@ var SpacedeckSections = {
this.selection_metrics.count=arts.length; this.selection_metrics.count=arts.length;
this.selection_metrics.scribble_selection = false; this.selection_metrics.scribble_selection = false;
if (arts.length == 1 && arts[0].mime == "x-spacedeck/vector") { if (arts.length == 1 && arts[0].mime == "x-spacedeck/vector") {
if (arts[0].style.shape == "scribble") { if (arts[0].shape == "scribble") {
this.selection_metrics.scribble_selection = true; this.selection_metrics.scribble_selection = true;
} }
this.selection_metrics.vector_points = arts[0].control_points; this.selection_metrics.vector_points = arts[0].control_points;
@ -1112,8 +1114,8 @@ var SpacedeckSections = {
fixup_space_size: function() { fixup_space_size: function() {
if (!this.active_space) return; if (!this.active_space) return;
this.active_space.advanced.width =Math.max(this.active_space.advanced.width, window.innerWidth); this.active_space.width =Math.max(this.active_space.width, window.innerWidth);
this.active_space.advanced.height=Math.max(this.active_space.advanced.height, window.innerHeight); this.active_space.height=Math.max(this.active_space.height, window.innerHeight);
}, },
end_transaction: function() { end_transaction: function() {
@ -1125,13 +1127,16 @@ var SpacedeckSections = {
var er = this.enclosing_rect(this.active_space_artifacts); var er = this.enclosing_rect(this.active_space_artifacts);
if (!er) return; if (!er) return;
this.active_space.advanced.width =Math.max(er.x2+100, window.innerWidth); // resize space
this.active_space.advanced.height=Math.max(er.y2+100, window.innerHeight); this.active_space.width =Math.max((parseInt(er.x2/window.innerWidth)+2)*window.innerWidth, window.innerWidth);
this.active_space.height=Math.max((parseInt(er.y2/window.innerHeight)+2)*window.innerHeight, window.innerHeight);
if (this._last_bounds_width != this.active_space.advanced.width || console.log("bounds: ",this.active_space.width,this.active_space.height);
this._last_bounds_height != this.active_space.advanced.height) {
this._last_bounds_width = this.active_space.advanced.width; if (this._last_bounds_width != this.active_space.width ||
this._last_bounds_height = this.active_space.advanced.height; this._last_bounds_height != this.active_space.height) {
this._last_bounds_width = this.active_space.width;
this._last_bounds_height = this.active_space.height;
save_space(this.active_space); save_space(this.active_space);
} }
@ -1214,7 +1219,7 @@ var SpacedeckSections = {
// this is a bit hacky, but might be the smartest place to do it // this is a bit hacky, but might be the smartest place to do it
if (a.view && a.view.vector_svg) { if (a.view && a.view.vector_svg) {
a.style.shape_svg = a.view.vector_svg; a.shape_svg = a.view.vector_svg;
} }
window.artifact_save_queue[a._id] = a; window.artifact_save_queue[a._id] = a;
@ -1329,7 +1334,7 @@ var SpacedeckSections = {
this.update_selected_artifacts(function(a) { this.update_selected_artifacts(function(a) {
var c = {}; var c = {};
if (c[prop] != val) { if (a[prop] != val) {
//console.log("set_artifact_prop: ",c,val); //console.log("set_artifact_prop: ",c,val);
c[prop]=val; c[prop]=val;
return c; return c;
@ -1343,11 +1348,11 @@ var SpacedeckSections = {
this.begin_transaction(); this.begin_transaction();
this.update_selected_artifacts(function(a) { this.update_selected_artifacts(function(a) {
var c = {style: a.style||{}}; var c = {};
if (c.style[prop] != val) { if (a[prop] != val) {
//console.log("set_artifact_style_prop: ",c,val); //console.log("set_artifact_style_prop: ",c,val);
c.style[prop]=val; c[prop]=val;
return c; return c;
} }
@ -1419,7 +1424,7 @@ var SpacedeckSections = {
if (this.selected_artifacts().length!=1 && this.opened_dialog!="background") return; if (this.selected_artifacts().length!=1 && this.opened_dialog!="background") return;
if (this.opened_dialog=="background") { if (this.opened_dialog=="background") {
this.active_style[this.color_picker_target] = this.active_space.advanced.background_color; this.active_style[this.color_picker_target] = this.active_space.background_color;
} else { } else {
if (!this.active_style[this.color_picker_target]) { if (!this.active_style[this.color_picker_target]) {
this.active_style[this.color_picker_target] = this.default_style[this.color_picker_target]; this.active_style[this.color_picker_target] = this.default_style[this.color_picker_target];
@ -1478,10 +1483,8 @@ var SpacedeckSections = {
this.update_selected_artifacts(function(a) { this.update_selected_artifacts(function(a) {
return { return {
board: _.extend(a.board, { x: a.x+dx,
x: a.board.x+dx, y: a.y+dy
y: a.board.y+dy
})
}; };
}); });
}, },
@ -1489,7 +1492,7 @@ var SpacedeckSections = {
/* -------------------------------------------------------------------- */ /* -------------------------------------------------------------------- */
highest_z: function() { highest_z: function() {
var z = _.max(this.active_space_artifacts.map(function(a){return a.board.z||0})); var z = _.max(this.active_space_artifacts.map(function(a){return a.z||0}));
if (z<0) z=0; if (z<0) z=0;
if (z>999) z=999; if (z>999) z=999;
return z; return z;
@ -1546,7 +1549,7 @@ var SpacedeckSections = {
add_artifact: function (space, item_type, url, evt) { add_artifact: function (space, item_type, url, evt) {
this.active_tool = "pointer"; this.active_tool = "pointer";
this.mouse_state = "idle"; this.mouse_state = "idle";
this.hide_toolbar_artifacts(); //this.hide_toolbar_artifacts();
if (!url && (item_type == 'image' || item_type == 'video' || item_type == 'embed')) { if (!url && (item_type == 'image' || item_type == 'video' || item_type == 'embed')) {
url = prompt("URL?"); url = prompt("URL?");
@ -1574,20 +1577,18 @@ var SpacedeckSections = {
payload_thumbnail_web_uri: url || null, payload_thumbnail_web_uri: url || null,
space_id: space._id, space_id: space._id,
style: { order: this.active_space_artifacts.length+1,
order: this.active_space_artifacts.length+1, valign: "middle",
valign: "middle", align: "center"
align: "center" //fill_color: "#f8f8f8"
//fill_color: "#f8f8f8"
}
}; };
if (mimes[item_type] == "text/html") { if (mimes[item_type] == "text/html") {
new_item.style.padding_left = 10; new_item.padding_left = 10;
new_item.style.padding_top = 10; new_item.padding_top = 10;
new_item.style.padding_right = 10; new_item.padding_right = 10;
new_item.style.padding_bottom = 10; new_item.padding_bottom = 10;
new_item.style.fill_color = "rgba(255,255,255,1)"; new_item.fill_color = "rgba(255,255,255,1)";
new_item.description = "<p>Text</p>"; new_item.description = "<p>Text</p>";
} }
@ -1600,13 +1601,11 @@ var SpacedeckSections = {
z = point.z; z = point.z;
} }
new_item.board = { new_item.x = parseInt(point.x);
x: parseInt(point.x), new_item.y = parseInt(point.y);
y: parseInt(point.y), new_item.z = z;
w: w, new_item.w = w;
h: h, new_item.h = h;
z: z
};
if (this.guest_nickname) { if (this.guest_nickname) {
new_item.editor_name = this.guest_nickname; new_item.editor_name = this.guest_nickname;
@ -1665,7 +1664,7 @@ var SpacedeckSections = {
for (var i=0; i<new_zones.length; i++) { for (var i=0; i<new_zones.length; i++) {
if (new_zones[i]) { if (new_zones[i]) {
if (!new_zones[i].style) new_zones[i].style = {}; if (!new_zones[i].style) new_zones[i].style = {};
new_zones[i].style.order = i; new_zones[i].order = i;
save_artifact(new_zones[i]); save_artifact(new_zones[i]);
} }
} }
@ -1679,7 +1678,7 @@ var SpacedeckSections = {
for (var i=0; i<new_zones.length; i++) { for (var i=0; i<new_zones.length; i++) {
if (new_zones[i]) { if (new_zones[i]) {
if (!new_zones[i].style) new_zones[i].style = {}; if (!new_zones[i].style) new_zones[i].style = {};
new_zones[i].style.order = i; new_zones[i].order = i;
save_artifact(new_zones[i]); save_artifact(new_zones[i]);
} }
} }
@ -1695,17 +1694,13 @@ var SpacedeckSections = {
space_id: this.active_space._id, space_id: this.active_space._id,
mime: "x-spacedeck/zone", mime: "x-spacedeck/zone",
description: "Zone "+(this.zones.length+1), description: "Zone "+(this.zones.length+1),
board: { x: point.x,
x: point.x, y: point.y,
y: point.y, w: w,
w: w, h: h,
h: h, z: 0,
z: 0 valign: "middle",
}, align: "center"
style: {
valign: "middle",
align: "center"
}
}; };
if (this.guest_nickname) { if (this.guest_nickname) {
@ -1734,23 +1729,19 @@ var SpacedeckSections = {
var a = { var a = {
space_id: this.active_space._id, space_id: this.active_space._id,
mime: "x-spacedeck/shape", mime: "x-spacedeck/shape",
description: "Text", description: "",
board: { x: point.x,
x: point.x, y: point.y,
y: point.y, z: point.z,
z: point.z, w: w,
w: w, h: h,
h: h stroke_color: "#ffffff",
}, text_color: "#ffffff",
style: { stroke: 0,
stroke_color: "#ffffff", fill_color: "#000000",
text_color: "#ffffff", shape: shape_type,
stroke: 0, valign: "middle",
fill_color: "#000000", align: "center",
shape: shape_type,
valign: "middle",
align: "center"
}
}; };
if (this.guest_nickname) { if (this.guest_nickname) {
@ -1802,9 +1793,7 @@ var SpacedeckSections = {
if (this.active_space_role=="viewer") { if (this.active_space_role=="viewer") {
return false; return false;
} }
this.hide_toolbar_artifacts();
// 1. create placeholder artifact // 1. create placeholder artifact
var w=300,h=150; var w=300,h=150;
var fill="transparent"; var fill="transparent";
@ -1829,17 +1818,13 @@ var SpacedeckSections = {
state: "uploading", state: "uploading",
payload_thumbnail_medium_uri: null, payload_thumbnail_medium_uri: null,
payload_thumbnail_web_uri: null, payload_thumbnail_web_uri: null,
board: { x: point.x,
x: point.x, y: point.y,
y: point.y, w: w,
w: w, h: h,
h: h, z: point.z,
z: point.z order: this.active_space_artifacts.length+1,
}, fill_color: fill
style: {
order: this.active_space_artifacts.length+1,
fill_color: fill
}
} }
this.update_board_artifact_viewmodel(a); this.update_board_artifact_viewmodel(a);
@ -1864,7 +1849,11 @@ var SpacedeckSections = {
a.payload_thumbnail_big_uri = updated_a.payload_thumbnail_big_uri; a.payload_thumbnail_big_uri = updated_a.payload_thumbnail_big_uri;
a.payload_alternatives = updated_a.payload_alternatives; a.payload_alternatives = updated_a.payload_alternatives;
a.mime = updated_a.mime; a.mime = updated_a.mime;
a.board = updated_a.board; a.x = updated_a.x;
a.y = updated_a.y;
a.w = updated_a.w;
a.h = updated_a.h;
a.z = updated_a.z;
a.state = updated_a.state; a.state = updated_a.state;
this.update_board_artifact_viewmodel(a); this.update_board_artifact_viewmodel(a);
@ -2002,26 +1991,26 @@ var SpacedeckSections = {
clear_formatting_walk: function(el,cmd,arg1,arg2) { clear_formatting_walk: function(el,cmd,arg1,arg2) {
if (el && el.style) { if (el && el.style) {
if (cmd == "preciseFontSize") { if (cmd == "preciseFontSize") {
el.style.fontSize = null; el.fontSize = null;
} else if (cmd == "letterSpacing") { } else if (cmd == "letterSpacing") {
el.style.letterSpacing = null; el.letterSpacing = null;
} else if (cmd == "lineHeight") { } else if (cmd == "lineHeight") {
el.style.lineHeight = null; el.lineHeight = null;
} else if (cmd == "fontName") { } else if (cmd == "fontName") {
el.style.fontFamily = null; el.fontFamily = null;
} else if (cmd == "fontWeight") { } else if (cmd == "fontWeight") {
el.style.fontWeight = null; el.fontWeight = null;
el.style.fontStyle = null; el.fontStyle = null;
} else if (cmd == "bold") { } else if (cmd == "bold") {
el.style.fontWeight = null; el.fontWeight = null;
} else if (cmd == "italic") { } else if (cmd == "italic") {
el.style.fontStyle = null; el.fontStyle = null;
} else if (cmd == "underline") { } else if (cmd == "underline") {
el.style.textDecoration = null; el.textDecoration = null;
} else if (cmd == "strikeThrough") { } else if (cmd == "strikeThrough") {
el.style.textDecoration = null; el.textDecoration = null;
} else if (cmd == "forecolor") { } else if (cmd == "forecolor") {
el.style.color = null; el.color = null;
} }
} }
@ -2108,6 +2097,9 @@ var SpacedeckSections = {
if (a.description!=dom.innerHTML) { if (a.description!=dom.innerHTML) {
a.description = dom.innerHTML; a.description = dom.innerHTML;
console.log("new DOM:",dom.innerHTML);
this.update_board_artifact_viewmodel(a); this.update_board_artifact_viewmodel(a);
this.queue_artifact_for_save(a); this.queue_artifact_for_save(a);
@ -2141,10 +2133,7 @@ var SpacedeckSections = {
remove_link_from_selected_artifacts: function() { remove_link_from_selected_artifacts: function() {
this.update_selected_artifacts(function(a) { this.update_selected_artifacts(function(a) {
var meta = a.meta || {}; return {link_uri: ""};
delete meta.link_uri;
return {meta: meta};
}); });
}, },
@ -2160,9 +2149,7 @@ var SpacedeckSections = {
var insert_link_url = prompt("URL:",def); var insert_link_url = prompt("URL:",def);
this.update_selected_artifacts(function(a) { this.update_selected_artifacts(function(a) {
var meta = a.meta || {}; var update = {link_uri: insert_link_url};
meta.link_uri = insert_link_url;
var update = {meta: meta};
if (a.payload_uri && a.payload_uri.match("webgrabber")) { if (a.payload_uri && a.payload_uri.match("webgrabber")) {
var enc_uri = encodeURIComponent(btoa(insert_link_url)); var enc_uri = encodeURIComponent(btoa(insert_link_url));
@ -2185,11 +2172,10 @@ var SpacedeckSections = {
delete copy["$index"]; delete copy["$index"];
delete copy["_id"]; delete copy["_id"];
if (dx) copy.board.x += dx; if (dx) copy.x += dx;
if (dy) copy.board.y += dy; if (dy) copy.y += dy;
if (!copy.style) copy.style = {}; copy.order = this.active_space_artifacts.length+1;
copy.style.order = this.active_space_artifacts.length+1;
if (this.guest_nickname) { if (this.guest_nickname) {
copy.editor_name = this.guest_nickname; copy.editor_name = this.guest_nickname;
@ -2310,11 +2296,6 @@ var SpacedeckSections = {
if (!pastedText) return; if (!pastedText) return;
if (!pastedText.match(/<[a-zA-Z]+>/g)) {
// crappy heuristic if this is actually HTML
pastedText = pastedText.replace(/\n/g,"<br>");
}
this.insert_embedded_artifact(pastedText); this.insert_embedded_artifact(pastedText);
}, },
@ -2334,16 +2315,16 @@ var SpacedeckSections = {
if (parsed[i].mime) { if (parsed[i].mime) {
var z = this.highest_z()+1; var z = this.highest_z()+1;
if (parsed.length==1) { if (parsed.length==1) {
var w = parsed[i].board.w; var w = parsed[i].w;
var h = parsed[i].board.h; var h = parsed[i].h;
var point = this.find_place_for_item(w,h); var point = this.find_place_for_item(w,h);
parsed[i].board.x = point.x; parsed[i].x = point.x;
parsed[i].board.y = point.y; parsed[i].y = point.y;
parsed[i].board.z = point.z; parsed[i].z = point.z;
} else { } else {
parsed[i].board.x = parsed[i].board.x+50; parsed[i].x = parsed[i].x+50;
parsed[i].board.y = parsed[i].board.y+50; parsed[i].y = parsed[i].y+50;
parsed[i].board.y = parsed[i].board.z+z; parsed[i].y = parsed[i].z+z;
} }
this.clone_artifact(parsed[i], 0,0, function(a) { this.clone_artifact(parsed[i], 0,0, function(a) {
this.multi_select([a]); this.multi_select([a]);
@ -2361,34 +2342,6 @@ var SpacedeckSections = {
this.create_artifact_via_embed_url(text); this.create_artifact_via_embed_url(text);
return; return;
} }
var new_item = {
mime: "text/html",
description: text.replace("\n", "<br />"),
title: "",
space_id: space._id
};
var w = 400;
var h = 300;
var point = this.find_place_for_item(w,h);
new_item.board = {
x: point.x,
y: point.y,
w: w,
h: h,
z: point.z
};
if (this.guest_nickname) {
new_item.editor_name = this.guest_nickname;
}
save_artifact(new_item, function(saved_item) {
this.update_board_artifact_viewmodel(saved_item);
this.active_space_artifacts.push(saved_item);
}.bind(this));
}, },
create_artifact_via_embed_url: function(url) { create_artifact_via_embed_url: function(url) {
@ -2402,16 +2355,12 @@ var SpacedeckSections = {
mime: "image/png", mime: "image/png",
description: url, description: url,
state: "uploading", state: "uploading",
board: { x: point.x,
x: point.x, y: point.y,
y: point.y, w: 200,
w: 200, h: 200,
h: 200, z: z,
z: z order: this.active_space_artifacts.length
},
style: {
order: this.active_space_artifacts.length
}
} }
var metadata = parse_link(url) var metadata = parse_link(url)
@ -2473,16 +2422,12 @@ var SpacedeckSections = {
payload_thumbnail_medium_uri: metadata.thumbnail_url, payload_thumbnail_medium_uri: metadata.thumbnail_url,
payload_thumbnail_web_uri: metadata.thumbnail_url, payload_thumbnail_web_uri: metadata.thumbnail_url,
state: "idle", state: "idle",
meta: { title: metadata.title,
title: metadata.title, link_uri: metadata.url || url,
link_uri: metadata.url || url x: point.x - w/2,
}, y: point.y - h/2,
board: { w: w,
x: point.x - w/2, h: h
y: point.y - h/2,
w: w,
h: h
}
}); });
if (this.guest_nickname) { if (this.guest_nickname) {
@ -2555,18 +2500,9 @@ var SpacedeckSections = {
this.opened_dialog = "none"; this.opened_dialog = "none";
if (files && files.length) { if (files && files.length) {
console.log("file: ",files[0]);
for (var i=0; i<files.length; i++) { for (var i=0; i<files.length; i++) {
var file = files[i]; var file = files[i];
if (file.type === "application/pdf") { this.create_artifact_via_upload(null, file, true);
var point = {x: 100, y: 100}; //fixme, center upload?
this.dropped_point = point;
this.pending_pdf_file = file;
this.activate_modal('pdfoptions');
} else {
this.create_artifact_via_upload(null, file, true);
}
} }
} }
}, },
@ -2591,7 +2527,7 @@ var SpacedeckSections = {
}, },
remove_section_background: function() { remove_section_background: function() {
this.active_space.advanced.background_uri = null; this.active_space.background_uri = null;
save_space(this.active_space); save_space(this.active_space);
}, },
@ -2605,12 +2541,11 @@ var SpacedeckSections = {
}, },
hide_toolbar_props: function() { hide_toolbar_props: function() {
this.toolbar_props_in = false; // FIXME test
//this.toolbar_props_in = false;
}, },
show_toolbar_artifacts: function(x,y) { show_toolbar_artifacts: function(x,y) {
this.toolbar_artifacts_x = (x-175)+"px";
this.toolbar_artifacts_y = y+"px";
this.toolbar_artifacts_in = true; this.toolbar_artifacts_in = true;
}, },
@ -2620,29 +2555,19 @@ var SpacedeckSections = {
start_adding_artifact: function(evt) { start_adding_artifact: function(evt) {
evt = fixup_touches(evt); evt = fixup_touches(evt);
// toggle
if (this.toolbar_artifacts_in) {
this.hide_toolbar_artifacts();
return;
}
this.show_toolbar_artifacts(evt.pageX,evt.pageY);
}, },
start_drawing_scribble: function(evt) { start_drawing_scribble: function(evt) {
this.hide_toolbar_artifacts();
this.active_tool = "scribble"; this.active_tool = "scribble";
this.opened_dialog = "none"; this.opened_dialog = "none";
}, },
start_drawing_arrow: function(evt) { start_drawing_arrow: function(evt) {
this.hide_toolbar_artifacts();
this.active_tool = "arrow"; this.active_tool = "arrow";
this.opened_dialog = "none"; this.opened_dialog = "none";
}, },
start_drawing_line: function(evt) { start_drawing_line: function(evt) {
this.hide_toolbar_artifacts();
this.active_tool = "line"; this.active_tool = "line";
this.opened_dialog = "none"; this.opened_dialog = "none";
}, },
@ -2652,8 +2577,8 @@ var SpacedeckSections = {
this.bounds_zoom = this.viewport_zoom; this.bounds_zoom = this.viewport_zoom;
var eff_w = this.active_space.advanced.width*this.viewport_zoom; var eff_w = this.active_space.width*this.viewport_zoom;
var eff_h = this.active_space.advanced.height*this.viewport_zoom; var eff_h = this.active_space.height*this.viewport_zoom;
if (window.innerWidth>eff_w) { if (window.innerWidth>eff_w) {
// horizontal centering // horizontal centering
@ -2846,8 +2771,8 @@ var SpacedeckSections = {
var el = $("#space")[0]; var el = $("#space")[0];
var eff_w = this.active_space.advanced.width*this.viewport_zoom; var eff_w = this.active_space.width*this.viewport_zoom;
var eff_h = this.active_space.advanced.height*this.viewport_zoom; var eff_h = this.active_space.height*this.viewport_zoom;
var sx = el.scrollLeft; var sx = el.scrollLeft;
var sy = el.scrollTop; var sy = el.scrollTop;
@ -2921,32 +2846,6 @@ var SpacedeckSections = {
}.bind(this),500); }.bind(this),500);
}, },
approve_pdf_upload: function(evt,approve_pdf_upload, mode){
this.close_modal();
if(mode == "classic"){
this.create_artifact_via_upload(evt, this.pending_pdf_file, false);
}
if(mode == "grid") {
this.global_spinner = true;
save_pdf_file(this.active_space, this.dropped_point, this.pending_pdf_file, approve_pdf_upload, function(createdArtifacts){
this.global_spinner = false;
_.each(createdArtifacts, function(new_artifact){
this.update_board_artifact_viewmodel(new_artifact);
this.active_space_artifacts.push(new_artifact)
}.bind(this));
}.bind(this), function(xhr) {
this.global_spinner = false;
alert("Error PDF ("+xhr.status+")");
}.bind(this));
}
},
handle_data_drop: function(evt) { handle_data_drop: function(evt) {
if (this.active_space_role=="viewer") { if (this.active_space_role=="viewer") {
return false; return false;
@ -2959,17 +2858,8 @@ var SpacedeckSections = {
if (files && files.length) { if (files && files.length) {
for (var i=0; i<files.length; i++) { for (var i=0; i<files.length; i++) {
var file = files[i]; var file = files[i];
if (file.type === "application/pdf") { this.create_artifact_via_upload(evt, file, (files.length>1));
var point = this.cursor_point_to_space(evt);
this.dropped_point = point;
this.pending_pdf_file = file;
this.activate_modal('pdfoptions');
} else {
this.create_artifact_via_upload(evt, file, (files.length>1));
}
} }
} else { } else {
var json = evt.dataTransfer.getData('application/json'); var json = evt.dataTransfer.getData('application/json');
@ -2980,9 +2870,9 @@ var SpacedeckSections = {
var w = 300; var w = 300;
var h = 200; var h = 200;
if (parsed.board && parsed.board.w && parsed.board.h) { if (parsed.board && parsed.w && parsed.h) {
w = parsed.board.w; w = parsed.w;
h = parsed.board.h; h = parsed.h;
} }
var point = this.cursor_point_to_space(evt); var point = this.cursor_point_to_space(evt);

View File

@ -18,8 +18,6 @@ var SpacedeckSpaces = {
active_space_path: [], active_space_path: [],
access_settings_space: null, access_settings_space: null,
access_settings_memberships: [], access_settings_memberships: [],
duplicate_folders: [],
duplicate_folder_id: "",
pending_pdf_files: [], pending_pdf_files: [],
meta_visible: false, meta_visible: false,
@ -71,9 +69,7 @@ var SpacedeckSpaces = {
methods: { methods: {
search_spaces: function() { search_spaces: function() {
var query = this.folder_spaces_search; var query = this.folder_spaces_search;
console.log("search query: ",query);
load_spaces_search(query, function(spaces) { load_spaces_search(query, function(spaces) {
console.log("results: ",spaces);
this.active_profile_spaces = spaces; this.active_profile_spaces = spaces;
}.bind(this)); }.bind(this));
}, },
@ -85,14 +81,7 @@ var SpacedeckSpaces = {
location.reload(); location.reload();
}, },
ask_guestname: function(dft, cb) { ask_guestname: function(dft, cb) {
console.log("ask_guestname"); smoke.prompt(__('what_is_your_name', "Spacedeck") , function(content) {
var team_name = "Spacedeck";
if(subdomainTeam) {
team_name = subdomainTeam.name;
}
smoke.prompt(__('what_is_your_name', team_name) , function(content) {
if (!content || (content.length === 0)) { if (!content || (content.length === 0)) {
this.ask_guestname(dft, cb); this.ask_guestname(dft, cb);
} else { } else {
@ -101,7 +90,7 @@ var SpacedeckSpaces = {
if ("localStorage" in window && localStorage) { if ("localStorage" in window && localStorage) {
try { try {
localStorage['guest_nickname'] = this.guest_nickname; localStorage['guest_nickname'] = this.guest_nickname;
}catch(e) { } catch(e) {
console.error(e); console.error(e);
} }
} }
@ -119,27 +108,6 @@ var SpacedeckSpaces = {
space_auth = get_query_param("spaceAuth"); space_auth = get_query_param("spaceAuth");
var userReady = function() { var userReady = function() {
if (get_query_param("embedded")) {
this.embedded = true;
this.guest_signup_enabled = true;
if (get_query_param("publish_cta")) {
this.publish_cta = get_query_param("publish_cta");
}
if (get_query_param("nosocial")) {
this.social_bar = false;
}
}
if (get_query_param("confirm") && this.logged_in) {
var token = get_query_param("confirm");
confirm_user(this.user, token, function() {
this.redirect_to("/spaces/"+space_id+"?show_access=1");
}.bind(this), function() {
alert("An error occured confirming your email with the given token.");
});
return;
}
this.close_dropdown(); this.close_dropdown();
this.active_space_loaded = false; this.active_space_loaded = false;
@ -167,12 +135,9 @@ var SpacedeckSpaces = {
load_space(space_id, function(space, role) { load_space(space_id, function(space, role) {
document.title = space.name; document.title = space.name;
this.active_space_role = role || "viewer"; //via req header from backend this.active_space_role = role || "viewer"; // via req header from backend
this.space_embed_html = "<iframe width=\"1024\" height=\"768\" seamless src=\""+ENV.webEndpoint+"/spaces/"+space._id+"?embedded=1\"></iframe>";
if (!is_home) { if (!is_home) {
//console.log(space);
load_members(space, function(members) { load_members(space, function(members) {
this.active_space_memberships = members; this.active_space_memberships = members;
}.bind(this)); }.bind(this));
@ -311,15 +276,6 @@ var SpacedeckSpaces = {
// FIXME // FIXME
this.active_join_link = ""; this.active_join_link = "";
this.join_link_role = "viewer"; this.join_link_role = "viewer";
// FIXME
if (this.active_space_role == "admin") {
this.space_info_section="access";
} else if (this.active_space_role == "editor") {
//this.space_info_section="versions";
} else {
this.space_info_section="info";
}
} }
}.bind(this), function(xhr) { }.bind(this), function(xhr) {
@ -348,7 +304,7 @@ var SpacedeckSpaces = {
userReady(); userReady();
} }
if (space_auth) { if (!this.user && space_auth) {
if (this.guest_nickname) { if (this.guest_nickname) {
userReady(); userReady();
} else { } else {
@ -636,17 +592,14 @@ var SpacedeckSpaces = {
download_space: function() { download_space: function() {
smoke.quiz(__("download_space"), function(e, test) { smoke.quiz(__("download_space"), function(e, test) {
if (e == "PDF"){ if (e == "PDF") {
this.download_space_as_pdf(this.active_space); this.download_space_as_pdf(this.active_space);
}else if (e == "ZIP"){ } else if (e == "ZIP") {
this.download_space_as_zip(this.active_space); this.download_space_as_zip(this.active_space);
}else if (e == "TXT"){
this.download_space_as_list(this.active_space);
} }
}.bind(this), { }.bind(this), {
button_1: "PDF", button_1: "PDF",
button_2: "ZIP", button_2: "ZIP",
button_3: "TXT",
button_cancel:__("cancel") button_cancel:__("cancel")
}); });
@ -686,47 +639,6 @@ var SpacedeckSpaces = {
location.href = "/api/spaces/" + space._id + "/list"; location.href = "/api/spaces/" + space._id + "/list";
}, },
duplicate_space_into_folder: function() {
load_writable_folders( function(folders){
this.duplicate_folders = _.sortBy(folders, function (folder) { return folder.name; });
}.bind(this), function(xhr) {
console.error(xhr);
});
},
duplicate_folder_confirm: function() {
var folderId = this.duplicate_folder_id;
var idx = _.findIndex(this.duplicate_folders, function(s) { return s._id == folderId;});
if (idx<0) idx = 0;
var folder = this.duplicate_folders[idx];
console.log("df f",folder);
if (!folder) return;
duplicate_space(this.active_space, folder._id, function(new_space) {
this.duplicate_folders = [];
this.duplicate_folder = null;
smoke.quiz(__("duplicate_success", this.active_space.name, folder.name), function(e, test){
if (e == __("goto_space", new_space.name)){
this.redirect_to("/spaces/" + new_space._id);
}else if (e == __("goto_folder", folder.name)){
this.redirect_to("/folders/" + folder._id);
}
}.bind(this), {
button_1: __("goto_space", new_space.name),
button_2: __("goto_folder", folder.name),
button_cancel:__("stay_here")
});
}.bind(this), function(xhr){
console.error(xhr);
smoke.prompt("error: " + xhr.statusText);
}.bind(this));
},
toggle_follow_mode: function() { toggle_follow_mode: function() {
this.deselect(); this.deselect();
this.follow_mode = !this.follow_mode; this.follow_mode = !this.follow_mode;
@ -832,9 +744,12 @@ var SpacedeckSpaces = {
this.invite_message = ""; this.invite_message = "";
} }
}.bind(this), function(xhr){ }.bind(this), function(xhr){
try {
text = JSON.stringify(xhr.responseText); var res = JSON.parse(xhr.response);
smoke.alert("Error: "+text); alert("Error: "+res.error);
} catch (e) {
console.error(e, xhr);
}
}.bind(this)); }.bind(this));
}.bind(this)); }.bind(this));
}, },
@ -842,9 +757,13 @@ var SpacedeckSpaces = {
update_member: function(space, m, role) { update_member: function(space, m, role) {
m.role = role; m.role = role;
save_membership(space, m, function() { save_membership(space, m, function() {
console.log("saved")
}.bind(this), function(xhr) { }.bind(this), function(xhr) {
console.error(xhr); try {
var res = JSON.parse(xhr.response);
alert("Error: "+res.error);
} catch (e) {
console.error(e, xhr);
}
}.bind(this)); }.bind(this));
}, },
@ -853,7 +772,12 @@ var SpacedeckSpaces = {
delete_membership(space, m, function() { delete_membership(space, m, function() {
this.access_settings_memberships.splice(this.access_settings_memberships.indexOf(m), 1); this.access_settings_memberships.splice(this.access_settings_memberships.indexOf(m), 1);
}.bind(this), function(xhr) { }.bind(this), function(xhr) {
console.error(xhr); try {
var res = JSON.parse(xhr.response);
alert("Error: "+res.error);
} catch (e) {
console.error(e, xhr);
}
}.bind(this)); }.bind(this));
}, },
@ -889,10 +813,6 @@ var SpacedeckSpaces = {
}.bind(this)); }.bind(this));
}, },
emojified_comment: function(comment) {
return twemoji.parse(comment);
},
set_folder_sorting: function(key,reverse) { set_folder_sorting: function(key,reverse) {
this.folder_sorting = key; this.folder_sorting = key;
this.folder_reverse = reverse?-1:1; this.folder_reverse = reverse?-1:1;

View File

@ -11,11 +11,13 @@ SpacedeckUsers = {
login_email: "", login_email: "",
login_password: "", login_password: "",
signup_password: "", signup_password: "",
signup_invite_code: "",
signup_password_confirmation: "", signup_password_confirmation: "",
account_remove_error: null, account_remove_error: null,
loading_user: false, loading_user: false,
password_reset_confirm_error: "", password_reset_confirm_error: "",
password_reset_error: "" password_reset_error: "",
}, },
methods:{ methods:{
load_user: function(on_success, on_error) { load_user: function(on_success, on_error) {
@ -40,23 +42,7 @@ SpacedeckUsers = {
}.bind(this)); }.bind(this));
}, },
login_google: function(evt) {
this.loading_user = true;
create_oauthtoken(function(data){
this.loading_user = false;
location.href = data.url;
}, function(xhr){
this.loading_user = false;
alert("could not get oauth token");
});
},
finalize_login: function(session_token, on_success) { finalize_login: function(session_token, on_success) {
if(!window.socket_auth || window.socket_auth == '' || window.socket_auth == 'null') {
window.socket_auth = session_token;
}
this.load_user(function(user) { this.load_user(function(user) {
if (this.invitation_token) { if (this.invitation_token) {
accept_invitation(this.invitation_token, function(memberships){ accept_invitation(this.invitation_token, function(memberships){
@ -131,7 +117,7 @@ SpacedeckUsers = {
signup_guest: function(on_success) { signup_guest: function(on_success) {
}, },
signup_submit: function($event, name, email, password, password_confirmation, on_success) { signup_submit: function($event, name, email, password, password_confirmation, invite_code, on_success) {
this.creating_user = true; this.creating_user = true;
this.signup_error = null; this.signup_error = null;
@ -145,7 +131,7 @@ SpacedeckUsers = {
$event.stopPropagation(); $event.stopPropagation();
} }
create_user(name, email, password, password_confirmation, function(session) { create_user(name, email, password, password_confirmation, invite_code, function(session) {
this.creating_user = false; this.creating_user = false;
this.login_submit(email, password, null, on_success); this.login_submit(email, password, null, on_success);
}.bind(this), function(req) { }.bind(this), function(req) {
@ -161,15 +147,14 @@ SpacedeckUsers = {
}.bind(this)); }.bind(this));
}, },
signup_submit_modal: function($event, name, email, password, password_confirmation) { signup_submit_modal: function($event, name, email, password, password_confirmation, invite_code) {
this.signup_submit($event, name, email, password, password_confirmation, function() { this.signup_submit($event, name, email, password, password_confirmation, invite_code, function() {
alert("Success."); alert("Success.");
location.reload(); location.reload();
}); });
}, },
password_reset_submit: function(evt, email) { password_reset_submit: function(evt, email) {
if (evt) { if (evt) {
evt.preventDefault(); evt.preventDefault();
evt.stopPropagation(); evt.stopPropagation();
@ -203,7 +188,6 @@ SpacedeckUsers = {
}, },
password_reset_confirm: function(evt, password, password_confirmation) { password_reset_confirm: function(evt, password, password_confirmation) {
if (evt) { if (evt) {
evt.preventDefault(); evt.preventDefault();
evt.stopPropagation(); evt.stopPropagation();
@ -212,27 +196,29 @@ SpacedeckUsers = {
this.password_reset_confirm_error = null; this.password_reset_confirm_error = null;
this.password_reset_send = false; this.password_reset_send = false;
if(password != password_confirmation) { if (password != password_confirmation) {
this.password_reset_confirm_error = "Passwords do not match."; this.password_reset_confirm_error = "Passwords do not match.";
return; return;
} }
if(password.length < 5) { if (password.length < 5) {
this.password_reset_confirm_error = "Password too short (must have at least 5 characters)."; this.password_reset_confirm_error = "Password too short (must have at least 5 characters).";
return; return;
} }
confirm_password_reset(password, this.reset_token, function(parsed,req) { confirm_password_reset(password, this.reset_token, function(parsed,req) {
if(req.status==201){ if (req.status==201) {
alert("New password set successfully.");
this.active_view = "login"; this.active_view = "login";
} else {
alert("An unknown error occured.");
} }
}.bind(this), function(req) { }.bind(this), function(req) {
if (req.status==404) { if (req.status==404) {
var msg = "user not found"; alert("Error: Unknown user.");
} else { } else {
var msg = "error: " + req.statusText; alert("Error: "+req.statusText);
} }
this.password_reset_confirm_error = msg;
}.bind(this)); }.bind(this));
}, },

View File

@ -158,7 +158,7 @@ function boot_spacedeck() {
}); });
} }
$(document).ready(function(){ document.addEventListener("DOMContentLoaded",function() {
window.smoke = smoke; window.smoke = smoke;
window.alert = smoke.alert; window.alert = smoke.alert;

View File

@ -59,13 +59,13 @@ SpacedeckWebsockets = {
else if (msg.action == "delete" && msg.object) { else if (msg.action == "delete" && msg.object) {
if (this.active_space) { if (this.active_space) {
var o = msg.object; var o = msg.object;
if(o._id){ if (o._id){
var existing_artifact = this.find_artifact_by_id(o._id); var existing_artifact = this.find_artifact_by_id(o._id);
if (existing_artifact) { if (existing_artifact) {
var idx = this.active_space_artifacts.indexOf(existing_artifact); var idx = this.active_space_artifacts.indexOf(existing_artifact);
this.active_space_artifacts.splice(idx, 1); this.active_space_artifacts.splice(idx, 1);
} else console.log("existing artifact to delete not found"); } else console.log("existing artifact to delete not found");
}else console.error("object without _id"); } else console.error("object without _id");
} }
} }
} }
@ -101,11 +101,13 @@ SpacedeckWebsockets = {
} }
if (this.websocket && this.websocket.readyState==1) { if (this.websocket && this.websocket.readyState==1) {
var token = "";
if (this.user) token = this.user.token;
var auth_params = { var auth_params = {
action: "auth", action: "auth",
editor_auth: space_auth, editor_auth: space_auth,
editor_name: this.guest_nickname, editor_name: this.guest_nickname,
auth_token: window.socket_auth, auth_token: token,
space_id: space._id space_id: space._id
}; };
console.log("[websocket] auth space"); console.log("[websocket] auth space");

View File

@ -80,10 +80,16 @@ function setup_whiteboard_directives() {
evt.stopPropagation(); evt.stopPropagation();
} }
var a = $scope.find_artifact_by_id(evt.currentTarget.id.replace("artifact-",""));
if ($scope.active_tool == "zoom") return; if ($scope.active_tool == "zoom") return;
if (evt.which == 2) {
// middle mouse button
this.handle_mouse_down_space(evt);
return;
}
var a = $scope.find_artifact_by_id(evt.currentTarget.id.replace("artifact-",""));
if ($scope.active_tool == "eyedrop") { if ($scope.active_tool == "eyedrop") {
var arts = $scope.selected_artifacts(); var arts = $scope.selected_artifacts();
if (!$scope.is_selected(a) && arts.length > 0) { if (!$scope.is_selected(a) && arts.length > 0) {
@ -196,8 +202,10 @@ function setup_whiteboard_directives() {
}, },
handle_mouse_down_space: function(evt) { handle_mouse_down_space: function(evt) {
if (evt.target != evt.currentTarget && !_.include(["wrapper"],evt.target.className)) return; if (evt.which != 2) {
if (evt.target != evt.currentTarget && !_.include(["wrapper"],evt.target.className)) return;
}
var $scope = this.vm.$root; var $scope = this.vm.$root;
$scope.opened_dialog="none"; $scope.opened_dialog="none";
@ -214,7 +222,7 @@ function setup_whiteboard_directives() {
this.deselect(); this.deselect();
this.mouse_state = "transform"; this.mouse_state = "transform";
$scope.mouse_state = this.mouse_state; $scope.mouse_state = this.mouse_state;
this.start_adding_note(evt); this.start_drawing_note(evt);
return; return;
} else if ($scope.active_tool=="arrow") { } else if ($scope.active_tool=="arrow") {
@ -331,7 +339,7 @@ function setup_whiteboard_directives() {
var $scope = this.vm.$root; var $scope = this.vm.$root;
return _.filter($scope.active_space_artifacts, function(a) { return _.filter($scope.active_space_artifacts, function(a) {
return this.rects_intersecting(a.board, rect); return this.rects_intersecting(a, rect);
}.bind(this)); }.bind(this));
}, },
@ -439,15 +447,15 @@ function setup_whiteboard_directives() {
dists = $scope.unselected_artifacts().map(function(a){ dists = $scope.unselected_artifacts().map(function(a){
var r = this.rect_to_points(a.board); var r = this.rect_to_points(a);
var xd1 = Math.abs(r[0].x-x); var xd1 = Math.abs(r[0].x-x);
var xd2 = Math.abs(r[1].x-x); var xd2 = Math.abs(r[1].x-x);
var xd3 = Math.abs(r[0].x+a.board.w/2 - x); var xd3 = Math.abs(r[0].x+a.w/2 - x);
var yd1 = Math.abs(r[0].y-y); var yd1 = Math.abs(r[0].y-y);
var yd2 = Math.abs(r[2].y-y); var yd2 = Math.abs(r[2].y-y);
var yd3 = Math.abs(r[0].y+a.board.h/2 - y); var yd3 = Math.abs(r[0].y+a.h/2 - y);
if (!snap_middle) { if (!snap_middle) {
if (xd2<xd1) { if (xd2<xd1) {
@ -469,10 +477,10 @@ function setup_whiteboard_directives() {
if (snap_middle) { if (snap_middle) {
var xd = xd3; var xd = xd3;
var sx = r[0].x+a.board.w/2; var sx = r[0].x+a.w/2;
var yd = yd3; var yd = yd3;
var sy = r[0].y+a.board.h/2; var sy = r[0].y+a.h/2;
} }
return [[xd,sx],[yd,sy]]; return [[xd,sx],[yd,sy]];
@ -492,6 +500,7 @@ function setup_whiteboard_directives() {
if (!xdists[0] || xdists[0][0]>TOL) { if (!xdists[0] || xdists[0][0]>TOL) {
results.snapx = [0,x]; // distance, coordinate results.snapx = [0,x]; // distance, coordinate
} else { } else {
// FIXME snap rulers are broken
//$scope.snap_ruler_x = xdists[0][1]; //$scope.snap_ruler_x = xdists[0][1];
} }
if (!ydists[0] || ydists[0][0]>TOL) { if (!ydists[0] || ydists[0][0]>TOL) {
@ -516,6 +525,41 @@ function setup_whiteboard_directives() {
return point; return point;
}, },
start_drawing_note: function(evt) {
evt.preventDefault();
evt.stopPropagation();
var $scope = this.vm.$root;
var point = this.cursor_point_to_space(evt);
this.offset_point_in_wrapper(point);
var z = $scope.highest_z()+1;
var a = {
space_id: $scope.active_space._id,
mime: "text/html",
description: "<p>Text</p>",
x: point.x,
y: point.y,
z: z,
w: 64,
h: 64,
align: "center",
valign: "middle",
stroke_color: "#000000",
fill_color: "rgb(241, 196, 15)",
stroke: 0
};
$scope.save_artifact(a, function(saved_a) {
$scope.update_board_artifact_viewmodel(saved_a);
$scope.active_space_artifacts.push(saved_a);
$scope.select(evt,a);
$scope.transform_ox = 0;
$scope.transform_oy = 0;
$scope.begin_transaction();
}.bind(this));
},
start_drawing_scribble: function(evt) { start_drawing_scribble: function(evt) {
evt.preventDefault(); evt.preventDefault();
evt.stopPropagation(); evt.stopPropagation();
@ -531,18 +575,14 @@ function setup_whiteboard_directives() {
mime: "x-spacedeck/vector", mime: "x-spacedeck/vector",
description: "", description: "",
control_points: [{dx:0,dy:0}], control_points: [{dx:0,dy:0}],
board: { x: point.x,
x: point.x, y: point.y,
y: point.y, z: z,
z: z, w: 64,
w: 64, h: 64,
h: 64 stroke_color: "#000000",
}, stroke: 2,
style: { shape: "scribble"
stroke_color: "#000000",
stroke: 2,
shape: "scribble"
}
}; };
$scope.save_artifact(a, function(saved_a) { $scope.save_artifact(a, function(saved_a) {
@ -572,18 +612,14 @@ function setup_whiteboard_directives() {
mime: "x-spacedeck/vector", mime: "x-spacedeck/vector",
description: "", description: "",
control_points: [{dx:0,dy:0},{dx:0,dy:0},{dx:0,dy:0}], control_points: [{dx:0,dy:0},{dx:0,dy:0},{dx:0,dy:0}],
board: { x: point.x,
x: point.x, y: point.y,
y: point.y, z: z,
z: z, w: 64,
w: 64, h: 64,
h: 64 stroke_color: "#000000",
}, stroke: 2,
style: { shape: "arrow"
stroke_color: "#000000",
stroke: 2,
shape: "arrow"
}
}; };
$scope.save_artifact(a, function(saved_a) { $scope.save_artifact(a, function(saved_a) {
@ -612,18 +648,14 @@ function setup_whiteboard_directives() {
mime: "x-spacedeck/vector", mime: "x-spacedeck/vector",
description: "", description: "",
control_points: [{dx:0,dy:0},{dx:0,dy:0}], control_points: [{dx:0,dy:0},{dx:0,dy:0}],
board: { x: point.x,
x: point.x, y: point.y,
y: point.y, z: z,
z: z, w: 64,
w: 64, h: 64,
h: 64 stroke_color: "#000000",
}, stroke: 2,
style: { shape: "line"
stroke_color: "#000000",
stroke: 2,
shape: "line"
}
}; };
$scope.save_artifact(a, function(saved_a) { $scope.save_artifact(a, function(saved_a) {
@ -675,11 +707,11 @@ function setup_whiteboard_directives() {
if (_.include(["text","placeholder"],$scope.artifact_major_type(ars[i]))) { if (_.include(["text","placeholder"],$scope.artifact_major_type(ars[i]))) {
// some types of artifact need a minimum size // some types of artifact need a minimum size
if (ars[i].board.w<10) { if (ars[i].w<10) {
ars[i].board.w = 10; ars[i].w = 10;
} }
if (ars[i].board.h<10) { if (ars[i].h<10) {
ars[i].board.h = 10; ars[i].h = 10;
} }
} }
@ -703,7 +735,7 @@ function setup_whiteboard_directives() {
this.mouse_state = "idle"; this.mouse_state = "idle";
$scope.mouse_state = this.mouse_state; $scope.mouse_state = this.mouse_state;
this.lasso = null; this.lasso = null;
$scope.active_tool = "pointer"; //$scope.active_tool = "pointer"; disabled to keep the same tool (eg. "Scribble") after drawing a single line.
$scope.end_transaction(); $scope.end_transaction();
$scope.show_toolbar_props(); $scope.show_toolbar_props();
@ -827,10 +859,8 @@ function setup_whiteboard_directives() {
if (old_a) { if (old_a) {
return { return {
board: _.extend(a.board, { x: old_a.x + dx - snap_dx,
x: old_a.board.x + dx - snap_dx, y: old_a.y + dy - snap_dy
y: old_a.board.y + dy - snap_dy
})
}; };
} else { } else {
// deleted? // deleted?
@ -870,21 +900,19 @@ function setup_whiteboard_directives() {
$scope.update_selected_artifacts(function(a) { $scope.update_selected_artifacts(function(a) {
var old_a = $scope.find_artifact_before_transaction(a); var old_a = $scope.find_artifact_before_transaction(a);
var x1 = origin_x + ((old_a.board.x - origin_x) * scale_x); var x1 = origin_x + ((old_a.x - origin_x) * scale_x);
var y1 = origin_y + ((old_a.board.y - origin_y) * scale_y); var y1 = origin_y + ((old_a.y - origin_y) * scale_y);
var x2 = origin_x + (((old_a.board.x + old_a.board.w) - origin_x) * scale_x); var x2 = origin_x + (((old_a.x + old_a.w) - origin_x) * scale_x);
var y2 = origin_y + (((old_a.board.y + old_a.board.h) - origin_y) * scale_y); var y2 = origin_y + (((old_a.y + old_a.h) - origin_y) * scale_y);
if (x1>x2) { var t = x1; x1 = x2; x2 = t; } if (x1>x2) { var t = x1; x1 = x2; x2 = t; }
if (y1>y2) { var t = y1; y1 = y2; y2 = t; } if (y1>y2) { var t = y1; y1 = y2; y2 = t; }
return { return {
board: _.extend(a.board, { x: x1,
x: x1, y: y1,
y: y1, w: x2 - x1,
w: x2 - x1, h: y2 - y1
h: y2 - y1
})
}; };
}.bind(this)); }.bind(this));
@ -902,18 +930,17 @@ function setup_whiteboard_directives() {
var old_a = $scope.find_artifact_before_transaction(a); var old_a = $scope.find_artifact_before_transaction(a);
var control_points = _.cloneDeep(old_a.control_points); var control_points = _.cloneDeep(old_a.control_points);
var board = _.clone(old_a.board);
var cp = control_points[$scope.selected_control_point_idx]; var cp = control_points[$scope.selected_control_point_idx];
var snapped = _this.snap_point(board.x+cp.dx+dx, board.y+cp.dy+dy); var snapped = _this.snap_point(old_a.x+cp.dx+dx, old_a.y+cp.dy+dy);
dx = snapped.snapx[1]-(board.x+cp.dx); dx = snapped.snapx[1]-(old_a.x+cp.dx);
dy = snapped.snapy[1]-(board.y+cp.dy); dy = snapped.snapy[1]-(old_a.y+cp.dy);
cp.dx += dx; cp.dx += dx;
cp.dy += dy; cp.dy += dy;
// special case for arrow's 3rd point // special case for arrow's 3rd point
if (a.style.shape == "arrow" && $scope.selected_control_point_idx!=2) { if (a.shape == "arrow" && $scope.selected_control_point_idx!=2) {
/*control_points[2].dx += dx/2; /*control_points[2].dx += dx/2;
control_points[2].dy += dy/2; */ control_points[2].dy += dy/2; */
@ -921,7 +948,7 @@ function setup_whiteboard_directives() {
control_points[2].dy = (control_points[0].dy+control_points[1].dy)/2; control_points[2].dy = (control_points[0].dy+control_points[1].dy)/2;
} }
return _this.normalize_control_points(control_points, board); return _this.normalize_control_points(control_points, old_a);
}); });
} else if (this.mouse_state == "scribble") { } else if (this.mouse_state == "scribble") {
@ -930,16 +957,14 @@ function setup_whiteboard_directives() {
var old_a = a; var old_a = a;
var control_points = _.cloneDeep(old_a.control_points); var control_points = _.cloneDeep(old_a.control_points);
var board = _.clone(old_a.board);
var offset = this.offset_point_in_wrapper({x:cursor.x,y:cursor.y}); var offset = this.offset_point_in_wrapper({x:cursor.x,y:cursor.y});
control_points.push({ control_points.push({
dx: offset.x-board.x, dx: offset.x-old_a.x,
dy: offset.y-board.y dy: offset.y-old_a.y
}); });
return this.normalize_control_points(simplify_scribble_points(control_points), board); return this.normalize_control_points(simplify_scribble_points(control_points), old_a);
}.bind(this)); }.bind(this));
var arts = $scope.selected_artifacts(); var arts = $scope.selected_artifacts();
@ -959,7 +984,7 @@ function setup_whiteboard_directives() {
} }
}, },
normalize_control_points: function(control_points, board) { normalize_control_points: function(control_points, artifact) {
var x1 = _.min(control_points,"dx").dx; var x1 = _.min(control_points,"dx").dx;
var y1 = _.min(control_points,"dy").dy; var y1 = _.min(control_points,"dy").dy;
var x2 = _.max(control_points,"dx").dx; var x2 = _.max(control_points,"dx").dx;
@ -981,19 +1006,15 @@ function setup_whiteboard_directives() {
var bshiftx = 0; var bshiftx = 0;
var bshifty = 0; var bshifty = 0;
if (board.w < 0) bshiftx = -board.w; if (artifact.w < 0) bshiftx = -artifact.w;
if (board.h < 0) bshifty = -board.h; if (artifact.h < 0) bshifty = -artifact.h;
var shifted_board = {
x: board.x + bshiftx - shiftx,
y: board.y + bshifty - shifty,
w: w,
h: h,
z: board.z
};
return { return {
board: shifted_board, x: artifact.x + bshiftx - shiftx,
y: artifact.y + bshifty - shifty,
w: w,
h: h,
z: artifact.z,
control_points: shifted_cps control_points: shifted_cps
}; };
} }

View File

@ -21,7 +21,7 @@ function vec2_angle(v) {
} }
function render_vector_drawing(a, padding) { function render_vector_drawing(a, padding) {
var shape = a.style.shape || ""; var shape = a.shape || "";
var path = []; var path = [];
var p = a.control_points[0]; var p = a.control_points[0];
@ -48,8 +48,8 @@ function render_vector_drawing(a, padding) {
var d = "M" + (cps.dx + padding) + "," + (cps.dy + padding) + " Q" + (scaledMiddlePoint.dx + padding) + "," + (scaledMiddlePoint.dy + padding) + " " + (cpe.dx + padding) + "," + (cpe.dy + padding); var d = "M" + (cps.dx + padding) + "," + (cps.dy + padding) + " Q" + (scaledMiddlePoint.dx + padding) + "," + (scaledMiddlePoint.dy + padding) + " " + (cpe.dx + padding) + "," + (cpe.dy + padding);
var tip = "<defs><marker id='ae" + markerId + "' refX=\"0.1\" refY=\"3\" markerWidth=\"3\" markerHeight=\"6\" orient=\"auto\">"; var tip = "<defs><marker id='ae" + markerId + "' refX=\"0.1\" refY=\"3\" markerWidth=\"3\" markerHeight=\"6\" orient=\"auto\">";
tip += "<path d=\"M-3,0 V6 L3,3 Z\" fill=\""+a.style.stroke_color+"\" stroke-width=\"0\"/></marker></defs>"; tip += "<path d=\"M-3,0 V6 L3,3 Z\" fill=\""+a.stroke_color+"\" stroke-width=\"0\"/></marker></defs>";
var svg = tip + "<path d='" + d + "' style='stroke-width:" + a.style.stroke + ";' marker-end='url(#ae" + markerId + ")'/>"; var svg = tip + "<path d='" + d + "' style='stroke-width:" + a.stroke + ";' marker-end='url(#ae" + markerId + ")'/>";
return svg; return svg;
} }
@ -237,11 +237,11 @@ function render_vector_rect(xradius,yradius,offset) {
} }
function render_vector_shape(a) { function render_vector_shape(a) {
var stroke = parseInt(a.style.stroke) + 4; var stroke = parseInt(a.stroke) + 4;
var offset = stroke / 2; var offset = stroke / 2;
var xr = (a.board.w-stroke) / 2; var xr = (a.w-stroke) / 2;
var yr = (a.board.h-stroke) / 2; var yr = (a.h-stroke) / 2;
var shape_renderers = { var shape_renderers = {
ellipse: function() { return render_vector_ellipse(xr, yr, offset); }, ellipse: function() { return render_vector_ellipse(xr, yr, offset); },
@ -258,7 +258,7 @@ function render_vector_shape(a) {
cloud: function() { return render_vector_cloud(xr, yr, offset); }, cloud: function() { return render_vector_cloud(xr, yr, offset); },
} }
var render_func = shape_renderers[a.style.shape]; var render_func = shape_renderers[a.shape];
if (!render_func) return ""; if (!render_func) return "";

16075
public/stylesheets/style.css Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,56 +1,40 @@
"use strict"; "use strict";
var config = require('config'); var config = require('config');
require('../../models/schema');
var fs = require('fs');
var _ = require("underscore");
var mongoose = require("mongoose");
var async = require('async'); var async = require('async');
var archiver = require('archiver');
var request = require('request');
var url = require("url"); var url = require("url");
var path = require("path"); var path = require("path");
var crypto = require('crypto'); var crypto = require('crypto');
var qr = require('qr-image');
var glob = require('glob'); var glob = require('glob');
var gm = require('gm');
var express = require('express'); var express = require('express');
var router = express.Router(); var router = express.Router();
var userMapping = { '_id': 1, 'nickname': 1, 'email': 1}; const db = require('../../models/db');
var spaceMapping = { '_id': 1, name: 1}; const Sequelize = require('sequelize');
const Op = Sequelize.Op;
const uuidv4 = require('uuid/v4');
router.get('/:membership_id/accept', function(req, res, next) { router.get('/:membership_id/accept', function(req, res, next) {
if (req.user) { if (req.user) {
Membership.findOne({ db.Membership.findOne({where:{
_id: req.params.membership_id, _id: req.params.membership_id,
state: "pending", code: req.query.code
code: req.query.code, }, include: ['space']}).then((mem) => {
user: { "$exists": false } if (mem) {
}).populate('space').exec((err,mem) => { if (!mem.user) {
if (err) res.sendStatus(400); mem.state = "active";
else { mem.user_id = req.user._id;
if (mem) {
if(!mem.user) { mem.save().then(function() {
mem.code = null; res.status(200).json(mem);
mem.state = "active"; });
mem.user = req.user;
mem.save(function(err){
if (err) res.status(400).json(err);
else {
console.log(mem);
res.status(200).json(mem);
}
});
} else {
res.status(400).json({"error": "already_used"});
}
} else { } else {
res.status(404).json({"error": "not found"}); res.status(200).json(mem);
} }
} else {
res.status(404).json({"error": "not found"});
} }
}); });
} else { } else {

View File

@ -1,79 +1,72 @@
"use strict"; "use strict";
var config = require('config'); var config = require('config');
require('../../models/schema'); const db = require('../../models/db');
var bcrypt = require('bcrypt'); var bcrypt = require('bcryptjs');
var crypo = require('crypto'); var crypto = require('crypto');
var URL = require('url').URL;
var express = require('express'); var express = require('express');
var router = express.Router(); var router = express.Router();
router.post('/', function(req, res) { router.post('/', function(req, res) {
var data = req.body; var data = req.body;
if (data.email && data.password) { if (!data.email || !data.password) {
var email = req.body.email.toLowerCase();
var password = req.body["password"];
User.find({email: email, account_type: "email"}, (function (err, users) {
if (err) {
res.status(400).json({"error":"session.users"});
} else {
if (users.length == 1) {
var user = users[0];
if (bcrypt.compareSync(password, user.password_hash)) {
crypo.randomBytes(48, function(ex, buf) {
var token = buf.toString('hex');
var session = {
token: token,
ip: req.ip,
device: "web",
created_at: new Date()
};
if (!user.sessions)
user.sessions = [];
user.sessions.push(session);
user.save(function(err, result) {
// FIXME
var secure = process.env.NODE_ENV == "production" || process.env.NODE_ENV == "staging";
var domain = (process.env.NODE_ENV == "production") ? ".example.org" : "localhost";
res.cookie('sdsession', token, { domain: domain, httpOnly: true, secure: secure});
res.status(201).json(session);
});
});
}else{
res.sendStatus(403);
}
} else {
res.sendStatus(404);
}
}
}));
} else {
res.status(400).json({}); res.status(400).json({});
return;
} }
var email = req.body.email.toLowerCase();
var password = req.body["password"];
db.User.findOne({where: {email: email}})
.error(err => {
res.sendStatus(404);
})
.then(user => {
if (!user) {
res.sendStatus(404);
}
else if (bcrypt.compareSync(password, user.password_hash)) {
crypto.randomBytes(48, function(ex, buf) {
var token = buf.toString('hex');
var session = {
user_id: user._id,
token: token,
ip: req.ip,
device: "web",
created_at: new Date()
};
db.Session.create(session)
.error(err => {
console.error("Error creating Session:",err);
res.sendStatus(500);
})
.then(() => {
var domain = (process.env.NODE_ENV == "production") ? new URL(config.get('endpoint')).hostname : req.headers.hostname;
res.cookie('sdsession', token, { domain: domain, httpOnly: true });
res.status(201).json(session);
});
});
} else {
res.sendStatus(403);
}
});
}); });
router.delete('/current', function(req, res, next) { router.delete('/current', function(req, res, next) {
if (req.user) { if (req.user) {
var user = req.user; var token = req.cookies['sdsession'];
var newSessions = user.sessions.filter( function(session){ db.Session.findOne({where: {token: token}})
return session.token != req.token; .then(session => {
}); session.destroy();
user.sessions = newSessions; });
user.save(function(err, result) { var domain = (process.env.NODE_ENV == "production") ? new URL(config.get('endpoint')).hostname : req.headers.hostname;
// FIXME res.clearCookie('sdsession', { domain: domain });
var domain = (process.env.NODE_ENV == "production") ? ".example.org" : "localhost"; res.sendStatus(204);
res.clearCookie('sdsession', { domain: domain });
res.sendStatus(204);
});
} else { } else {
res.sendStatus(404); res.sendStatus(404);
} }

View File

@ -1,7 +1,12 @@
"use strict"; "use strict";
var config = require('config'); var config = require('config');
require('../../models/schema');
const os = require('os');
const db = require('../../models/db');
const Sequelize = require('sequelize');
const Op = Sequelize.Op;
const uuidv4 = require('uuid/v4');
var payloadConverter = require('../../helpers/artifact_converter'); var payloadConverter = require('../../helpers/artifact_converter');
var redis = require('../../helpers/redis'); var redis = require('../../helpers/redis');
@ -9,13 +14,11 @@ var redis = require('../../helpers/redis');
var async = require('async'); var async = require('async');
var fs = require('fs'); var fs = require('fs');
var _ = require("underscore"); var _ = require("underscore");
var mongoose = require("mongoose");
var archiver = require('archiver'); var archiver = require('archiver');
var request = require('request'); var request = require('request');
var url = require("url"); var url = require("url");
var path = require("path"); var path = require("path");
var crypto = require('crypto'); var crypto = require('crypto');
var qr = require('qr-image');
var glob = require('glob'); var glob = require('glob');
var gm = require('gm'); var gm = require('gm');
@ -46,22 +49,27 @@ var roleMapping = {
// ARTIFACTS // ARTIFACTS
router.get('/', (req, res) => { router.get('/', (req, res) => {
Artifact.find({ db.Artifact.findAll({where: {
space_id: req.space._id space_id: req.space._id
}).exec((err, artifacts) => { }}).then(artifacts => {
async.map(artifacts, (a, cb) => { async.map(artifacts, (a, cb) => {
a = a.toObject(); db.unpackArtifact(a);
if (a.user_id) { if (a.user_id) {
User.findOne({ // FIXME JOIN
/*User.findOne({where: {
"_id": a.user_id "_id": a.user_id
}).select({ }}).select({
"_id": 1, "_id": 1,
"nickname": 1, "nickname": 1,
"email": 1 "email": 1
}).exec((err, user) => { }).exec((err, user) => {
a['user'] = user.toObject(); if (user) {
a['user'] = user.toObject();
}
cb(err, a); cb(err, a);
}); });*/
cb(null, a);
} else { } else {
cb(null, a); cb(null, a);
} }
@ -79,9 +87,8 @@ router.post('/', function(req, res, next) {
attrs['space_id'] = req.space._id; attrs['space_id'] = req.space._id;
var artifact = new Artifact(attrs); var artifact = attrs;
artifact._id = uuidv4();
artifact.created_from_ip = req['real_ip'];
if (req.user) { if (req.user) {
artifact.user_id = req.user._id; artifact.user_id = req.user._id;
@ -90,23 +97,18 @@ router.post('/', function(req, res, next) {
artifact.last_update_editor_name = req.editor_name; artifact.last_update_editor_name = req.editor_name;
} }
if (req.spaceRole == "editor"  ||  req.spaceRole == "admin") { db.packArtifact(artifact);
artifact.save(function(err) {
if (err) res.status(400).json(err); if (req.spaceRole == "editor" || req.spaceRole == "admin") {
else { db.Artifact.create(artifact).then(() => {
Space.update({ //if (err) res.status(400).json(err);
_id: req.space._id db.unpackArtifact(artifact);
}, { db.Space.update({ updated_at: new Date() }, {where: {_id: req.space._id}});
"$set": { res.distributeCreate("Artifact", artifact);
updated_at: new Date()
}
});
res.distributeCreate("Artifact", artifact);
}
}); });
} else { } else {
res.status(401).json({ res.status(401).json({
"error": "no access" "error": "Access denied"
}); });
} }
}); });
@ -116,30 +118,26 @@ router.post('/:artifact_id/payload', function(req, res, next) {
var a = req.artifact; var a = req.artifact;
var fileName = (req.query.filename || "upload.bin").replace(/[^a-zA-Z0-9_\-\.]/g, ''); var fileName = (req.query.filename || "upload.bin").replace(/[^a-zA-Z0-9_\-\.]/g, '');
var localFilePath = "/tmp/" + fileName;
var localFilePath = os.tmpdir() + "/" + fileName;
var writeStream = fs.createWriteStream(localFilePath); var writeStream = fs.createWriteStream(localFilePath);
var stream = req.pipe(writeStream); var stream = req.pipe(writeStream);
var progress_callback = function(progress_msg) { var progressCallback = function(progressMsg) {
a.description = progress_msg; a.description = progressMsg.toString();
db.packArtifact(a);
a.save(); a.save();
redis.sendMessage("update", a, a.toJSON(), req.channelId); redis.sendMessage("update", "Artifact", a, req.channelId);
}; };
stream.on('finish', function() { stream.on('finish', function() {
payloadConverter.convert(a, fileName, localFilePath, function(error, artifact) { payloadConverter.convert(a, fileName, localFilePath, function(error, artifact) {
if (error) res.status(400).json(error); if (error) res.status(400).json(error);
else { else {
Space.update({ db.Space.update({ updated_at: new Date() }, {where: {_id: req.space._id}});
_id: req.space._id
}, {
"$set": {
updated_at: new Date()
}
});
res.distributeUpdate("Artifact", artifact); res.distributeUpdate("Artifact", artifact);
} }
}, progress_callback); }, progressCallback);
}); });
} else { } else {
res.status(401).json({ res.status(401).json({
@ -159,42 +157,24 @@ router.put('/:artifact_id', function(req, res, next) {
} else { } else {
newAttr.last_update_editor_name = req.editor_name; newAttr.last_update_editor_name = req.editor_name;
} }
db.packArtifact(newAttr);
Artifact.findOneAndUpdate({ db.Artifact.update(newAttr, { where: {
"_id": a._id "_id": a._id
}, { }}).then(rows => {
"$set": newAttr db.unpackArtifact(newAttr);
}, { db.Space.update({ updated_at: new Date() }, {where: {_id: req.space._id} });
"new": true newAttr._id = a._id;
}, function(err, artifact) { res.distributeUpdate("Artifact", newAttr);
if (err) res.status(400).json(err);
else {
Space.update({
_id: req.space._id
}, {
"$set": {
updated_at: new Date()
}
});
res.distributeUpdate("Artifact", artifact);
}
}); });
}); });
router.delete('/:artifact_id', function(req, res, next) { router.delete('/:artifact_id', function(req, res, next) {
var artifact = req.artifact; var artifact = req.artifact;
artifact.remove(function(err) { db.Artifact.destroy({where: { "_id": artifact._id}}).then(() => {
if (err) res.status(400).json(err); db.Space.update({ updated_at: new Date() }, {where: {_id: req.space._id} });
else { res.distributeDelete("Artifact", artifact);
Space.update({
_id: req.space._id
}, {
"$set": {
updated_at: new Date()
}
});
res.distributeDelete("Artifact", artifact);
}
}); });
}); });

View File

@ -1,17 +1,15 @@
"use strict"; "use strict";
var config = require('config'); var config = require('config');
require('../../models/schema'); require('../../models/db');
var async = require('async'); var async = require('async');
var fs = require('fs'); var fs = require('fs');
var _ = require("underscore"); var _ = require("underscore");
var mongoose = require("mongoose");
var request = require('request'); var request = require('request');
var url = require("url"); var url = require("url");
var path = require("path"); var path = require("path");
var crypto = require('crypto'); var crypto = require('crypto');
var qr = require('qr-image');
var glob = require('glob'); var glob = require('glob');
var gm = require('gm'); var gm = require('gm');
@ -40,6 +38,12 @@ var roleMapping = {
}; };
router.get('/', function(req, res, next) { router.get('/', function(req, res, next) {
res.status(200).json([]);
return;
// FIXME TODO
var showActionForSpaces = function(err, spaceIds) { var showActionForSpaces = function(err, spaceIds) {
var userMapping = { var userMapping = {
'_id': 1, '_id': 1,
@ -134,7 +138,6 @@ router.get('/', function(req, res, next) {
"$exists": 1 "$exists": 1
} }
}).populate("space").exec(function(err, memberships) { }).populate("space").exec(function(err, memberships) {
async.map(memberships, function(membership, memcb) { async.map(memberships, function(membership, memcb) {
Space.getRecursiveSubspacesForSpace(membership.space, function(err, spaces) { Space.getRecursiveSubspacesForSpace(membership.space, function(err, spaces) {
cb(null, spaces.map(function(s) { cb(null, spaces.map(function(s) {

View File

@ -1,8 +1,7 @@
"use strict"; "use strict";
var config = require('config'); var config = require('config');
require('../../models/schema'); const db = require('../../models/db');
var redis = require('../../helpers/redis');
var mailer = require('../../helpers/mailer'); var mailer = require('../../helpers/mailer');
var uploader = require('../../helpers/uploader'); var uploader = require('../../helpers/uploader');
var space_render = require('../../helpers/space-render'); var space_render = require('../../helpers/space-render');
@ -12,13 +11,11 @@ var async = require('async');
var moment = require('moment'); var moment = require('moment');
var fs = require('fs'); var fs = require('fs');
var _ = require("underscore"); var _ = require("underscore");
var mongoose = require("mongoose");
var archiver = require('archiver'); var archiver = require('archiver');
var request = require('request'); var request = require('request');
var url = require("url"); var url = require("url");
var path = require("path"); var path = require("path");
var crypto = require('crypto'); var crypto = require('crypto');
var qr = require('qr-image');
var glob = require('glob'); var glob = require('glob');
var gm = require('gm'); var gm = require('gm');
var sanitizeHtml = require('sanitize-html'); var sanitizeHtml = require('sanitize-html');
@ -49,26 +46,17 @@ var roleMapping = {
router.get('/png', function(req, res, next) { router.get('/png', function(req, res, next) {
var triggered = new Date(); var triggered = new Date();
var s3_filename = "s" + req.space._id + "/" + "thumb_" + triggered.getTime() + ".jpg"; var s3_filename = "s" + req.space._id + "/" + "thumb_" + triggered.getTime() + ".jpg";
if (!req.space.thumbnail_updated_at || req.space.thumbnail_updated_at < req.space.updated_at || !req.space.thumbnail_url) { if (!req.space.thumbnail_updated_at || req.space.thumbnail_updated_at < req.space.updated_at || !req.space.thumbnail_url) {
db.Space.update({ thumbnail_updated_at: triggered }, {where : {"_id": req.space._id }});
Space.update({
"_id": req.space._id phantom.takeScreenshot(req.space, "png", function(local_path) {
}, {
"$set": {
thumbnail_updated_at: triggered
}
}, function(a, b, c) {});
phantom.takeScreenshot(req.space, "png",
function(local_path) {
var localResizedFilePath = local_path + ".thumb.jpg"; var localResizedFilePath = local_path + ".thumb.jpg";
gm(local_path).resize(640, 480).quality(70.0).autoOrient().write(localResizedFilePath, function(err) { gm(local_path).resize(640, 480).quality(70.0).autoOrient().write(localResizedFilePath, function(err) {
if (err) { if (err) {
console.error("screenshot resize error: ", err); console.error("[space screenshot] resize error: ", err);
res.status(500).send("Error taking screenshot."); res.status(500).send("Error taking screenshot.");
return; return;
} }
@ -76,35 +64,28 @@ router.get('/png', function(req, res, next) {
uploader.uploadFile(s3_filename, "image/jpeg", localResizedFilePath, function(err, thumbnailUrl) { uploader.uploadFile(s3_filename, "image/jpeg", localResizedFilePath, function(err, thumbnailUrl) {
if (err) { if (err) {
console.error("screenshot s3 upload error. filename: " + s3_filename + " details: ", err); console.error("[space screenshot] upload error. filename: " + s3_filename + " details: ", err);
res.status(500).send("Error uploading screenshot."); res.status(500).send("Error uploading screenshot.");
return; return;
} }
var oldUrl = req.space.thumbnail_url; var oldUrl = req.space.thumbnail_url;
Space.update({ db.Space.update({ thumbnail_url: thumbnailUrl }, {where : {"_id": req.space._id }}).then(() => {
"_id": req.space._id
}, {
"$set": {
thumbnail_url: thumbnailUrl
}
}, function(a, b, c) {
res.redirect(thumbnailUrl); res.redirect(thumbnailUrl);
try { try {
if (oldUrl) { if (oldUrl) {
var oldPath = url.parse(oldUrl).pathname; var oldPath = url.parse(oldUrl).pathname;
uploader.removeFile(oldPath, function(err, res) {}); uploader.removeFile(oldPath, function(err, res) {});
} }
fs.unlink(local_path); fs.unlinkSync(local_path);
} catch (e) { } catch (e) {
console.error(e); console.error(e);
} }
}); });
try { try {
fs.unlink(localResizedFilePath); fs.unlinkSync(localResizedFilePath);
} catch (e) { } catch (e) {
console.error(e); console.error(e);
} }
@ -113,7 +94,7 @@ router.get('/png', function(req, res, next) {
}, },
function() { function() {
// on_error // on_error
console.error("phantom could not create screenshot for space " + req.space_id); console.error("[space screenshot] could not create screenshot for space " + req.space_id);
res.status(404).send("Not found"); res.status(404).send("Not found");
}); });
} else { } else {
@ -125,77 +106,6 @@ function make_export_filename(space, extension) {
return space.name.replace(/[^\w]/g, '') + "-" + space._id + "-" + moment().format("YYYYMMDD-HH-mm-ss") + "." + extension; return space.name.replace(/[^\w]/g, '') + "-" + space._id + "-" + moment().format("YYYYMMDD-HH-mm-ss") + "." + extension;
} }
router.get('/list', function(req, res, next) {
if (req.user) {
if (req.spaceRole == "admin" ||  req.spaceRole == "editor") {
if (req.space.space_type == "space") {
Artifact.find({
space_id: req.space._id
}).exec(function(err, artifacts) {
async.map(artifacts, function(a, cb) {
if (a.user_id) {
User.findOne({
"_id": a.user_id
}).exec(function(err, user) {
a.user = user;
if (a.last_update_user_id) {
User.findOne({
"_id": a.last_update_user_id
}).exec(function(err, updateUser) {
a.update_user = updateUser;
cb(null, a);
});
} else {
cb(null, a);
}
});
} else {
cb(null, a);
}
}, function(err, mappedArtifacts) {
req.space.artifacts = mappedArtifacts.map(function(a) {
a.description = sanitizeHtml(a.description, {
allowedTags: [],
allowedAttributes: []
});
if (a.payload_uri) {
var parsed = url.parse(a.payload_uri);
var fileName = path.basename(parsed.pathname) || "file.bin";
a.filename = fileName;
}
return a;
});
res.render('artifact_list', {
space: req.space
});
});
});
} else {
Space.getRecursiveSubspacesForSpace(req.space, (err, subspaces) => {
res.render('space_list', {
subspaces: subspaces.map((s) => {
s.ae_link = config.endpoint + '/s/' + s.edit_hash + (s.edit_slug ? ('-'+s.edit_slug) : '')
return s;
}),
space: req.space
});
});
}
} else {
res.sendStatus(403);
}
} else {
res.sendStatus(403);
}
});
router.get('/pdf', function(req, res, next) { router.get('/pdf', function(req, res, next) {
var s3_filename = make_export_filename(req.space, "pdf"); var s3_filename = make_export_filename(req.space, "pdf");
@ -329,36 +239,13 @@ router.get('/zip', function(req, res, next) {
}); });
router.get('/html', function(req, res) { router.get('/html', function(req, res) {
Artifact.find({ db.Artifact.findAll({where: {
space_id: req.space._id space_id: req.space._id
}, function(err, artifacts) { }}).then(function(artifacts) {
var space = req.space; var space = req.space;
res.send(space_render.render_space_as_html(space, artifacts)); res.send(space_render.render_space_as_html(space, artifacts));
}); });
}); });
router.get('/path', (req, res) => {
// build up a breadcrumb trail (path)
var path = [];
var buildPath = (space) => {
if (space.parent_space_id) {
Space.findOne({
"_id": space.parent_space_id
}, (err, parentSpace) => {
if (space._id == parentSpace._id) {
console.log("error: circular parent reference for space " + space._id);
res.send("error: circular reference");
} else {
path.push(parentSpace);
buildPath(parentSpace);
}
});
} else {
// reached the top
res.json(path.reverse());
}
}
buildPath(req.space);
});
module.exports = router; module.exports = router;

View File

@ -1,57 +1,31 @@
"use strict"; "use strict";
var config = require('config'); var config = require('config');
require('../../models/schema'); const db = require('../../models/db');
const Sequelize = require('sequelize');
const Op = Sequelize.Op;
const uuidv4 = require('uuid/v4');
var redis = require('../../helpers/redis'); var redis = require('../../helpers/redis');
var mailer = require('../../helpers/mailer'); var mailer = require('../../helpers/mailer');
var uploader = require('../../helpers/uploader');
var space_render = require('../../helpers/space-render');
var phantom = require('../../helpers/phantom');
var async = require('async'); var async = require('async');
var fs = require('fs'); var fs = require('fs');
var _ = require("underscore"); var _ = require("underscore");
var mongoose = require("mongoose");
var archiver = require('archiver');
var request = require('request'); var request = require('request');
var url = require("url"); var url = require("url");
var path = require("path"); var path = require("path");
var crypto = require('crypto');
var qr = require('qr-image');
var glob = require('glob'); var glob = require('glob');
var gm = require('gm'); var crypto = require('crypto');
var express = require('express'); var express = require('express');
var router = express.Router({mergeParams: true}); var router = express.Router({mergeParams: true});
// JSON MAPPINGS
var userMapping = {
_id: 1,
nickname: 1,
email: 1,
avatar_thumb_uri: 1
};
var spaceMapping = {
_id: 1,
name: 1,
thumbnail_url: 1
};
var roleMapping = {
"none": 0,
"viewer": 1,
"editor": 2,
"admin": 3
}
router.get('/', function(req, res, next) { router.get('/', function(req, res, next) {
Membership db.Membership
.find({ .findAll({where: {
space: req.space._id space_id: req.space._id
}) }, include: ['user']})
.populate("user") .then(memberships => {
.exec(function(err, memberships) {
res.status(200).json(memberships); res.status(200).json(memberships);
}); });
}); });
@ -59,65 +33,66 @@ router.get('/', function(req, res, next) {
router.post('/', function(req, res, next) { router.post('/', function(req, res, next) {
if (req.spaceRole == "admin") { if (req.spaceRole == "admin") {
var attrs = req.body; var attrs = req.body;
attrs['space'] = req.space._id; attrs.space_id = req.space._id;
attrs['state'] = "pending"; attrs.state = "pending";
var membership = new Membership(attrs); attrs._id = uuidv4();
var membership = attrs;
var msg = attrs.personal_message; var msg = attrs.personal_message;
if (membership.email_invited != req.user.email) { if (membership.email_invited != req.user.email) {
User.findOne({ db.User.findOne({where:{
"email": membership.email_invited "email": membership.email_invited
}, function(err, user) { }}).then(function(user) {
// existing user? then immediately activate membership
if (user) { if (user) {
membership.user = user; membership.user_id = user._id;
membership.state = "active"; membership.state = "active";
} else { } else {
// if not, invite via email and invite code
membership.code = crypto.randomBytes(64).toString('hex').substring(0, 12); membership.code = crypto.randomBytes(64).toString('hex').substring(0, 12);
} }
membership.save(function(err) { db.Membership.create(membership).then(function() {
if (err) res.sendStatus(400); var accept_link = config.endpoint + "/accept/" + membership._id + "?code=" + membership.code;
else {
var accept_link = config.endpoint + "/accept/" + membership._id + "?code=" + membership.code;
if (user) { if (user) {
accept_link = config.endpoint + "/" + req.space.space_type + "s/" + req.space._id; accept_link = config.endpoint + "/" + req.space.space_type + "s/" + req.space._id;
}
var openText = req.i18n.__("space_invite_membership_action");
if (user) {
req.i18n.__("open");
}
const name = req.user.nickname || req.user.email
const subject = (req.space.space_type == "space") ? req.i18n.__("space_invite_membership_subject", name, req.space.name) : req.i18n.__("folder_invite_membership_subject", req.user.nickname, req.space.name)
const body = (req.space.space_type == "space") ? req.i18n.__("space_invite_membership_body", name, req.space.name) : req.i18n.__("folder_invite_membership_body", req.user.nickname, req.space.name)
mailer.sendMail(
membership.email_invited, subject, body, {
messsage: msg,
action: {
link: accept_link,
name: openText
}
});
res.status(201).json(membership);
} }
var openText = req.i18n.__("space_invite_membership_action");
if (user) {
req.i18n.__("open");
}
const name = req.user.nickname || req.user.email
const subject = (req.space.space_type == "space") ? req.i18n.__("space_invite_membership_subject", name, req.space.name) : req.i18n.__("folder_invite_membership_subject", req.user.nickname, req.space.name)
const body = (req.space.space_type == "space") ? req.i18n.__("space_invite_membership_body", name, req.space.name) : req.i18n.__("folder_invite_membership_body", req.user.nickname, req.space.name)
mailer.sendMail(
membership.email_invited, subject, body, {
messsage: msg,
action: {
link: accept_link,
name: openText
}
});
res.status(201).json(membership);
}); });
}); });
} else { } else {
res.status(400).json({ res.status(400).json({
"error": "user already in space" "error": "This email is already included in the Space memberships."
}); });
} }
} else { } else {
res.status(403).json({ res.status(403).json({
"error": "not_permitted" "error": "Only administrators can do that."
}); });
} }
}); });
@ -125,19 +100,20 @@ router.post('/', function(req, res, next) {
router.put('/:membership_id', function(req, res, next) { router.put('/:membership_id', function(req, res, next) {
if (req.user) { if (req.user) {
if (req.spaceRole == "admin") { if (req.spaceRole == "admin") {
Membership.findOne({ db.Membership.findOne({ where: {
_id: req.params.membership_id _id: req.params.membership_id
}, function(err, mem) { }}).then(function(mem) {
if (err) res.sendStatus(400); if (mem) {
else { // is the user trying to change their own role?
if (mem) { if (mem.user_id == req.user._id) {
res.status(400).json({
"error": "Cannot change your own role."
});
} else {
var attrs = req.body; var attrs = req.body;
mem.role = attrs.role; mem.role = attrs.role;
mem.save(function(err) { mem.save(function() {
if (err) res.sendStatus(400); res.status(201).json(mem);
else {
res.status(201).json(mem);
}
}); });
} }
} }
@ -151,21 +127,25 @@ router.put('/:membership_id', function(req, res, next) {
}); });
router.delete('/:membership_id', function(req, res, next) { router.delete('/:membership_id', function(req, res, next) {
if (req.user) { if (req.user && req.spaceRole == 'admin') {
Membership.findOne({ db.Membership.count({ where: {
_id: req.params.membership_id space_id: req.space._id,
}, function(err, mem) { role: "admin"
if (err) res.sendStatus(400); }}).then(function(adminCount) {
else { db.Membership.findOne({ where: {
mem.remove(function(err) { _id: req.params.membership_id
if (err) { }}).then(function(mem) {
res.status(400).json(err); // deleting an admin? need at least 1
} else { if (mem.role != "admin" || adminCount > 1) {
// FIXME might need to delete the user? mem.destroy().then(function() {
res.sendStatus(204); res.sendStatus(204);
} });
}); } else {
} res.status(400).json({
"error": "Space needs at least one administrator."
});
}
})
}); });
} else { } else {
res.sendStatus(403); res.sendStatus(403);

View File

@ -1,6 +1,9 @@
"use strict"; "use strict";
var config = require('config'); var config = require('config');
require('../../models/schema'); const db = require('../../models/db');
const Sequelize = require('sequelize');
const Op = Sequelize.Op;
const uuidv4 = require('uuid/v4');
var redis = require('../../helpers/redis'); var redis = require('../../helpers/redis');
var mailer = require('../../helpers/mailer'); var mailer = require('../../helpers/mailer');
@ -11,15 +14,12 @@ var phantom = require('../../helpers/phantom');
var async = require('async'); var async = require('async');
var fs = require('fs'); var fs = require('fs');
var _ = require("underscore"); var _ = require("underscore");
var mongoose = require("mongoose");
var archiver = require('archiver'); var archiver = require('archiver');
var request = require('request'); var request = require('request');
var url = require("url"); var url = require("url");
var path = require("path"); var path = require("path");
var crypto = require('crypto'); var crypto = require('crypto');
var qr = require('qr-image');
var glob = require('glob'); var glob = require('glob');
var gm = require('gm');
var express = require('express'); var express = require('express');
var router = express.Router({mergeParams: true}); var router = express.Router({mergeParams: true});
@ -49,90 +49,44 @@ var roleMapping = {
// MESSAGES // MESSAGES
router.get('/', function(req, res, next) { router.get('/', function(req, res, next) {
Message.find({ db.Message.findAll({where:{
space: req.space._id space_id: req.space._id
}).populate('user', userMapping).exec(function(err, messages) { }, include: ['user']})
res.status(200).json(messages); .then(function(messages) {
}); res.status(200).json(messages);
});
}); });
router.post('/', function(req, res, next) { router.post('/', function(req, res, next) {
var attrs = req.body; var attrs = req.body;
attrs.space = req.space; attrs.space_id = req.space._id;
if (req.user) { if (req.user) {
attrs.user = req.user; attrs.user = req.user;
attrs.user_id = req.user._id;
} else { } else {
attrs.user = null; attrs.user = null;
} }
var msg = new Message(attrs); var msg = attrs;
msg.save(function(err) { msg._id = uuidv4();
if (err) res.status(400).json(erra);
else {
if (msg.message.length <= 1) return;
Membership db.Message.create(msg).then(function() {
.find({ if (msg.message.length <= 1) return;
space: req.space, // TODO reimplement notifications
user: { res.distributeCreate("Message", msg);
"$exists": true
}
})
.populate('user')
.exec(function(err, memberships) {
var users = memberships.map(function(m) {
return m.user;
});
users.forEach((user) => {
if (user.preferences.email_notifications) {
redis.isOnlineInSpace(user, req.space, function(err, online) {
if (!online) {
var nickname = msg.editor_name;
if (req.user) {
nickname = req.user.nickname;
}
mailer.sendMail(
user.email,
req.i18n.__("space_message_subject", req.space.name),
req.i18n.__("space_message_body", nickname, req.space.name), {
message: msg.message,
action: {
link: config.endpoint + "/spaces/" + req.space._id.toString(),
name: req.i18n.__("open")
}
});
} else {
console.log("not sending message to user: is online.");
}
});
} else {
console.log("not sending message to user: is disabled notifications.");
}
});
});
res.distributeCreate("Message", msg);
}
}); });
}); });
router.delete('/:message_id', function(req, res, next) { router.delete('/:message_id', function(req, res, next) {
Message.findOne({ db.Message.findOne({where:{
"_id": req.params.message_id "_id": req.params.message_id
}, function(err, msg) { }}).then(function(msg) {
if (!msg) { if (!msg) {
res.sendStatus(404); res.sendStatus(404);
} else { } else {
msg.remove(function(err) { msg.destroy().then(function() {
if (err) res.status(400).json(err); res.distributeDelete("Message", msg);
else {
if (msg) {
res.distributeDelete("Message", msg);
} else {
res.sendStatus(404);
}
}
}); });
} }
}); });

View File

@ -1,6 +1,10 @@
"use strict"; "use strict";
var config = require('config'); var config = require('config');
require('../../models/schema'); const os = require('os');
const db = require('../../models/db');
const Sequelize = require('sequelize');
const Op = Sequelize.Op;
const uuidv4 = require('uuid/v4');
var redis = require('../../helpers/redis'); var redis = require('../../helpers/redis');
var mailer = require('../../helpers/mailer'); var mailer = require('../../helpers/mailer');
@ -14,13 +18,10 @@ var slug = require('slug');
var fs = require('fs'); var fs = require('fs');
var async = require('async'); var async = require('async');
var _ = require("underscore"); var _ = require("underscore");
var mongoose = require("mongoose");
var archiver = require('archiver');
var request = require('request'); var request = require('request');
var url = require("url"); var url = require("url");
var path = require("path"); var path = require("path");
var crypto = require('crypto'); var crypto = require('crypto');
var qr = require('qr-image');
var glob = require('glob'); var glob = require('glob');
var gm = require('gm'); var gm = require('gm');
const exec = require('child_process'); const exec = require('child_process');
@ -47,132 +48,61 @@ router.get('/', function(req, res, next) {
error: "auth required" error: "auth required"
}); });
} else { } else {
if (req.query.writablefolders) { if (req.query.search) {
Membership.find({ db.Membership.findAll({where:{
user: req.user._id user_id: req.user._id
}, (err, memberships) => { }}).then(memberships => {
// search for spaces
var validMemberships = memberships.filter((m) => {
if (!m.space || (m.space == "undefined"))
return false;
else
return mongoose.Types.ObjectId.isValid(m.space.toString());
});
var editorMemberships = validMemberships.filter((m) => {
return (m.role == "editor") || (m.role == "admin")
});
var spaceIds = editorMemberships.map(function(m) {
return new mongoose.Types.ObjectId(m.space);
});
var q = {
"space_type": "folder",
"$or": [{
"creator": req.user._id
}, {
"_id": {
"$in": spaceIds
},
"creator": {
"$ne": req.user._id
}
}]
};
Space
.find(q)
.populate('creator', userMapping)
.exec(function(err, spaces) {
if (err) console.error(err);
var updatedSpaces = spaces.map(function(s) {
var spaceObj = s.toObject();
return spaceObj;
});
async.map(spaces, (space, cb) => {
Space.getRecursiveSubspacesForSpace(space, (err, spaces) => {
var allSpaces = spaces;
cb(err, allSpaces);
})
}, (err, spaces) => {
var allSpaces = _.flatten(spaces);
var onlyFolders = _.filter(allSpaces, (s) => {
return s.space_type == "folder";
})
var uniqueFolders = _.unique(onlyFolders, (s) => {
return s._id.toString();
})
res.status(200).json(uniqueFolders);
});
});
});
} else if (req.query.search) {
Membership.find({
user: req.user._id
}, function(err, memberships) {
var validMemberships = memberships.filter(function(m) { var validMemberships = memberships.filter(function(m) {
if (!m.space || (m.space == "undefined")) if (!m.space_id || (m.space_id == "undefined"))
return false; return false;
else else
return mongoose.Types.ObjectId.isValid(m.space.toString()); return true;
}); });
var spaceIds = validMemberships.map(function(m) { var spaceIds = validMemberships.map(function(m) {
return new mongoose.Types.ObjectId(m.space); return m.space_id;
}); });
var q = { // TODO FIXME port
"$or": [{"creator": req.user._id}, var q = { where: {
{"_id": {"$in": spaceIds}}, [Op.or]: [{"creator_id": req.user._id},
{"parent_space_id": {"$in": spaceIds}}], {"_id": {[Op.in]: spaceIds}},
name: new RegExp(req.query.search, "i") {"parent_space_id": {[Op.in]: spaceIds}}],
}; name: {[Op.like]: "%"+req.query.search+"%"}
}, include: ['creator']};
Space db.Space
.find(q) .findAll(q)
.populate('creator', userMapping) .then(function(spaces) {
.exec(function(err, spaces) {
if (err) console.error(err);
var updatedSpaces = spaces.map(function(s) {
var spaceObj = s.toObject();
return spaceObj;
});
res.status(200).json(spaces); res.status(200).json(spaces);
}); });
}); });
} else if (req.query.parent_space_id && req.query.parent_space_id != req.user.home_folder_id) { } else if (req.query.parent_space_id && req.query.parent_space_id != req.user.home_folder_id) {
// list spaces in a folder
Space
.findOne({ db.Space
.findOne({where: {
_id: req.query.parent_space_id _id: req.query.parent_space_id
}) }})
.populate('creator', userMapping) //.populate('creator', userMapping)
.exec(function(err, space) { .then(function(space) {
if (space) { if (space) {
Space.roleInSpace(space, req.user, function(err, role) { db.getUserRoleInSpace(space, req.user, function(role) {
if (role == "none") { if (role == "none") {
if(space.access_mode == "public") { if (space.access_mode == "public") {
role = "viewer"; role = "viewer";
} }
} }
if (role != "none") { if (role != "none") {
Space db.Space
.find({ .findAll({where:{
parent_space_id: req.query.parent_space_id parent_space_id: req.query.parent_space_id
}) }, include:['creator']})
.populate('creator', userMapping) .then(function(spaces) {
.exec(function(err, spaces) {
res.status(200).json(spaces); res.status(200).json(spaces);
}); });
} else { } else {
@ -185,41 +115,42 @@ router.get('/', function(req, res, next) {
}); });
} else { } else {
Membership.find({ // list home folder and spaces/folders that the user is a member of
user: req.user._id
}, function(err, memberships) { db.Membership.findAll({ where: {
user_id: req.user._id
}}).then(memberships => {
if (!memberships) memberships = [];
var validMemberships = memberships.filter(function(m) { var validMemberships = memberships.filter(function(m) {
if (!m.space || (m.space == "undefined")) if (!m.space_id || (m.space_id == "undefined"))
return false; return false;
else return true;
return mongoose.Types.ObjectId.isValid(m.space.toString());
}); });
var spaceIds = validMemberships.map(function(m) { var spaceIds = validMemberships.map(function(m) {
return new mongoose.Types.ObjectId(m.space); return m.space_id;
}); });
var q = { var q = {
"$or": [{ [Op.or]: [{
"creator": req.user._id, "creator_id": req.user._id,
"parent_space_id": req.user.home_folder_id "parent_space_id": req.user.home_folder_id
}, { }, {
"_id": { "_id": {
"$in": spaceIds [Op.in]: spaceIds
}, },
"creator": { "creator_id": {
"$ne": req.user._id [Op.ne]: req.user._id
} }
}] }]
}; };
Space db.Space
.find(q) .findAll({where: q, include: ['creator']})
.populate('creator', userMapping) .then(function(spaces) {
.exec(function(err, spaces) {
if (err) console.error(err);
var updatedSpaces = spaces.map(function(s) { var updatedSpaces = spaces.map(function(s) {
var spaceObj = s.toObject(); var spaceObj = db.spaceToObject(s);
return spaceObj; return spaceObj;
}); });
res.status(200).json(spaces); res.status(200).json(spaces);
@ -229,47 +160,47 @@ router.get('/', function(req, res, next) {
} }
}); });
// create a space
router.post('/', function(req, res, next) { router.post('/', function(req, res, next) {
if (req.user) { if (req.user) {
var attrs = req.body; var attrs = req.body;
var createSpace = () => { var createSpace = () => {
attrs._id = uuidv4();
attrs.creator = req.user; attrs.creator_id = req.user._id;
attrs.edit_hash = crypto.randomBytes(64).toString('hex').substring(0, 7); attrs.edit_hash = crypto.randomBytes(64).toString('hex').substring(0, 7);
attrs.edit_slug = slug(attrs.name); attrs.edit_slug = slug(attrs.name);
attrs.access_mode = "private";
var space = new Space(attrs); db.Space.create(attrs).then(createdSpace => {
space.save(function(err, createdSpace) { res.status(201).json(createdSpace);
if (err) res.sendStatus(400);
else { // create initial admin membership
var membership = new Membership({ var membership = {
user: req.user, _id: uuidv4(),
space: createdSpace, user_id: req.user._id,
role: "admin" space_id: attrs._id,
}); role: "admin",
membership.save(function(err, createdTeam) { state: "active"
if (err) { };
res.status(400).json(err);
} else { db.Membership.create(membership).then(() => {
res.status(201).json(createdSpace); res.status(201).json(createdSpace);
} });
});
}
}); });
} }
if (attrs.parent_space_id) { if (attrs.parent_space_id) {
Space.findOne({ db.Space.findOne({ where: {
"_id": attrs.parent_space_id "_id": attrs.parent_space_id
}).populate('creator', userMapping).exec((err, parentSpace) => { }}).then(parentSpace => {
if (parentSpace) { if (parentSpace) {
Space.roleInSpace(parentSpace, req.user, (err, role) => { db.getUserRoleInSpace(parentSpace, req.user, (role) => {
if ((role == "editor") || (role == "admin")) { if ((role == "editor") || (role == "admin")) {
createSpace(); createSpace();
} else { } else {
res.status(403).json({ res.status(403).json({
"error": "not editor in parent Space" "error": "not editor in parent Space. role: "+role
}); });
} }
}); });
@ -292,6 +223,30 @@ router.get('/:id', function(req, res, next) {
res.status(200).json(req.space); res.status(200).json(req.space);
}); });
router.get('/:id/path', (req, res) => {
// build up a breadcrumb trail (path)
var path = [];
var buildPath = (space) => {
if (space.parent_space_id) {
db.Space.findOne({ where: {
"_id": space.parent_space_id
}}).then(parentSpace => {
if (space._id == parentSpace._id) {
console.error("error: circular parent reference for space " + space._id);
res.send("error: circular reference");
} else {
path.push(parentSpace);
buildPath(parentSpace);
}
});
} else {
// reached the top
res.json(path.reverse());
}
}
buildPath(req.space);
});
router.put('/:id', function(req, res) { router.put('/:id', function(req, res) {
var space = req.space; var space = req.space;
var newAttr = req.body; var newAttr = req.body;
@ -305,27 +260,29 @@ router.put('/:id', function(req, res) {
newAttr.edit_slug = slug(newAttr['name']); newAttr.edit_slug = slug(newAttr['name']);
delete newAttr['_id']; delete newAttr['_id'];
delete newAttr['editor_name'];
delete newAttr['creator']; delete newAttr['creator'];
delete newAttr['creator_id'];
delete newAttr['space_type'];
Space.findOneAndUpdate({ if (req['spaceRole'] != "admin") {
delete newAttr['access_mode']
delete newAttr['password']
delete newAttr['edit_hash']
delete newAttr['edit_slug']
delete newAttr['editors_locking']
}
db.Space.update(newAttr, {where: {
"_id": space._id "_id": space._id
}, { }}).then(space => {
"$set": newAttr res.distributeUpdate("Space", space);
}, {
"new": true
}, function(err, space) {
if (err) res.status(400).json(err);
else {
res.distributeUpdate("Space", space);
}
}); });
}); });
router.post('/:id/background', function(req, res, next) { router.post('/:id/background', function(req, res, next) {
var space = req.space; var space = req.space;
var newDate = new Date(); var newDate = new Date();
var fileName = (req.query.filename || "upload.bin").replace(/[^a-zA-Z0-9\.]/g, ''); var fileName = (req.query.filename || "upload.jpg").replace(/[^a-zA-Z0-9\.]/g, '');
var localFilePath = "/tmp/" + fileName; var localFilePath = "/tmp/" + fileName;
var writeStream = fs.createWriteStream(localFilePath); var writeStream = fs.createWriteStream(localFilePath);
var stream = req.pipe(writeStream); var stream = req.pipe(writeStream);
@ -334,96 +291,44 @@ router.post('/:id/background', function(req, res, next) {
uploader.uploadFile("s" + req.space._id + "/bg_" + newDate.getTime() + "_" + fileName, "image/jpeg", localFilePath, function(err, backgroundUrl) { uploader.uploadFile("s" + req.space._id + "/bg_" + newDate.getTime() + "_" + fileName, "image/jpeg", localFilePath, function(err, backgroundUrl) {
if (err) res.status(400).json(err); if (err) res.status(400).json(err);
else { else {
var adv = space.advanced; if (space.background_uri) {
var oldPath = url.parse(req.space.background_uri).pathname;
if (adv.background_uri) {
var oldPath = url.parse(req.space.thumbnail_url).pathname;
uploader.removeFile(oldPath, function(err) { uploader.removeFile(oldPath, function(err) {
console.log("removed old bg error:", err); console.error("removed old bg error:", err);
}); });
} }
adv.background_uri = backgroundUrl; db.Space.update({
background_uri: backgroundUrl
Space.findOneAndUpdate({
"_id": space._id
}, { }, {
"$set": { where: { "_id": space._id }
advanced: adv }, function(rows) {
} fs.unlink(localFilePath, function(err) {
}, { if (err) {
"new": true console.error(err);
}, function(err, space) { res.status(400).json(err);
if (err) { } else {
res.sendStatus(400); res.status(200).json(space);
} else { }
fs.unlink(localFilePath, function(err) { });
if (err) {
console.error(err);
res.status(400).json(err);
} else {
res.status(200).json(space);
}
});
}
}); });
} }
}); });
}); });
}); });
var handleDuplicateSpaceRequest = function(req, res, parentSpace) {
Space.duplicateSpace(req.space, req.user, 0, (err, newSpace) => {
if (err) {
console.error(err);
res.status(400).json(err);
} else {
res.status(201).json(newSpace);
}
}, parentSpace);
}
router.post('/:id/duplicate', (req, res, next) => {
if (req.query.parent_space_id) {
Space.findOne({
_id: req.query.parent_space_id
}).populate('creator', userMapping).exec((err, parentSpace) => {
if (!parentSpace) {
res.status(404).json({
"error": "parent space not found for dupicate"
});
} else {
Space.roleInSpace(parentSpace, req.user, (err, role) => {
if (role == "admin" ||  role == "editor") {
handleDuplicateSpaceRequest(req, res, parentSpace);
} else {
res.status(403).json({
"error": "not authed for parent_space_id"
});
}
});
}
});
} else {
handleDuplicateSpaceRequest(req, res);
}
});
router.delete('/:id', function(req, res, next) { router.delete('/:id', function(req, res, next) {
if (req.user) { if (req.user) {
const space = req.space; const space = req.space;
if (req.spaceRole == "admin") { if (req.spaceRole == "admin") {
const attrs = req.body; const attrs = req.body;
Space.recursiveDelete(space, function(err) { space.destroy().then(function() {
if (err) res.status(400).json(err); res.distributeDelete("Space", space);
else {
res.distributeDelete("Space", space);
}
}); });
} else { } else {
res.status(403).json({ res.status(403).json({
"error": "requires admin status" "error": "requires admin role"
}); });
} }
} else { } else {
@ -431,139 +336,4 @@ router.delete('/:id', function(req, res, next) {
} }
}); });
router.post('/:id/artifacts-pdf', function(req, res, next) {
if (req.spaceRole == "editor" || req.spaceRole == "admin") {
var withZones = (req.query.zones) ? req.query.zones == "true" : false;
var fileName = (req.query.filename || "upload.bin").replace(/[^a-zA-Z0-9\.]/g, '');
var localFilePath = "/tmp/" + fileName;
var writeStream = fs.createWriteStream(localFilePath);
var stream = req.pipe(writeStream);
req.on('end', function() {
var rawName = fileName.slice(0, fileName.length - 4);
var outputFolder = "/tmp/" + rawName;
var rights = 777;
fs.mkdir(outputFolder, function(db) {
var images = outputFolder + "/" + rawName + "-page-%03d.jpeg";
exec.execFile("gs", ["-sDEVICE=jpeg", "-dDownScaleFactor=4", "-dDOINTERPOLATE", "-dNOPAUSE", "-dJPEGQ=80", "-dBATCH", "-sOutputFile=" + images, "-r250", "-f", localFilePath], {}, function(error, stdout, stderr) {
if (error === null) {
glob(outputFolder + "/*.jpeg", function(er, files) {
var count = files.length;
var delta = 10;
var limitPerRow = Math.ceil(Math.sqrt(count));
var startX = parseInt(req.query.x, delta);
var startY = parseInt(req.query.y, delta);
async.mapLimit(files, 20, function(localfilePath, cb) {
var fileName = path.basename(localfilePath);
var baseName = path.basename(localfilePath, ".jpeg");
var number = parseInt(baseName.slice(baseName.length - 3, baseName.length), 10);
gm(localFilePath).size(function(err, size) {
var w = 350;
var h = w;
var x = startX + (((number - 1) % limitPerRow) * w);
var y = startY + ((parseInt(((number - 1) / limitPerRow), 10) + 1) * w);
var userId;
if (req.user)
userId = req.user._id;
var a = new Artifact({
mime: "image/jpg",
space_id: req.space._id,
user_id: userId,
editor_name: req.guest_name,
board: {
w: w,
h: h,
x: x,
y: y,
z: (number + (count + 100))
}
});
payloadConverter.convert(a, fileName, localfilePath, (error, artifact) => {
if (error) res.status(400).json(error);
else {
if (withZones) {
var zone = new Artifact({
mime: "x-spacedeck/zone",
description: "Zone " + (number),
space_id: req.space._id,
user_id: userId,
editor_name: req.guest_name,
board: {
w: artifact.board.w + 20,
h: artifact.board.h + 40,
x: x - 10,
y: y - 30,
z: number
},
style: {
order: number,
valign: "middle",
align: "center"
}
});
zone.save((err) => {
redis.sendMessage("create", "Artifact", zone.toJSON(), req.channelId);
cb(null, [artifact, zone]);
});
} else {
cb(null, [artifact]);
}
}
});
});
}, function(err, artifacts) {
exec.execFile("rm", ["-r", outputFolder], function(err) {
res.status(201).json(_.flatten(artifacts));
async.eachLimit(artifacts, 10, (artifact_or_artifacts, cb) => {
if (artifact_or_artifacts instanceof Array) {
_.each(artifact_or_artifacts, (a) => {
redis.sendMessage("create", "Artifact", a.toJSON(), req.channelId);
});
} else  {
redis.sendMessage("create", "Artifact", artifact_or_artifacts.toJSON(), req.channelId);
}
cb(null);
});
});
});
});
} else {
console.error("error:", error);
exec.execFile("rm", ["-r", outputFolder], function(err) {
fs.unlink(localFilePath);
res.status(400).json({});
});
}
});
});
});
} else {
res.status(401).json({
"error": "no access"
});
}
});
module.exports = router; module.exports = router;

View File

@ -1,265 +0,0 @@
"use strict";
var config = require('config');
require('../../models/schema');
var redis = require('../../helpers/redis');
var mailer = require('../../helpers/mailer');
var fs = require('fs');
var _ = require('underscore');
var crypto = require('crypto');
var bcrypt = require('bcrypt');
var express = require('express');
var router = express.Router();
var userMapping = { '_id': 1, 'nickname': 1, 'email': 1};
router.get('/:id', (req, res) => {
res.status(200).json(req.user.team);
});
router.put('/:id', (req, res) => {
var team = req.user.team;
if (!team) {
res.status(400).json({"error": "user in no team"});
} else {
var newAttr = req.body;
newAttr.updated_at = new Date();
delete newAttr['_id'];
if(newAttr['subdomain']) {
newAttr['subdomain'] = newAttr['subdomain'].toLowerCase();
}
const new_subdomain = newAttr['subdomain'];
var forbidden_subdomains = [];
function updateTeam() {
Team.findOneAndUpdate({"_id": team._id}, {"$set": newAttr}, {"new": true}, (err, team) => {
if (err) res.status(400).json(err);
else {
res.status(200).json(team);
}
});
}
var isForbidden = forbidden_subdomains.indexOf(new_subdomain) > -1;
if (isForbidden) {
res.bad_request("subdomain not valid");
} else {
if (new_subdomain) {
Team.findOne({"domain": new_subdomain}).exec((err, team) => {
if(team) {
res.bad_request("subdomain already used");
} else {
updateTeam()
}
});
} else {
updateTeam()
}
}
}
});
router.get('/:id/memberships', (req, res) => {
User
.find({team: req.user.team})
.populate("team")
.exec(function(err, users){
if (err) res.status(400).json(err);
else {
res.status(200).json(users);
}
});
});
router.post('/:id/memberships', (req, res, next) => {
if (req.body.email) {
const email = req.body.email.toLowerCase();
const team = req.user.team;
User.findOne({"email": email}).populate('team').exec((err, user) => {
if (user) {
const code = crypto.randomBytes(64).toString('hex').substring(0,7);
team.invitation_codes.push(code);
team.save((err) => {
if (err){ res.status(400).json(err); }
else {
mailer.sendMail(email, req.i18n.__("team_invite_membership_subject", team.name), req.i18n.__("team_invite_membership_body", team.name), { action: {
link: config.endpoint + "/teams/" + req.user.team._id + "/join?code=" + code,
name: req.i18n.__("team_invite_membership_action"),
teamname: team.name
}});
res.status(201).json(user);
}
});
} else {
// complete new user
const password = crypto.randomBytes(64).toString('hex').substring(0,7);
const confirmation_token = crypto.randomBytes(64).toString('hex').substring(0,7);
bcrypt.genSalt(10, (err, salt) => {
bcrypt.hash(password, salt, (err, hash) => {
crypto.randomBytes(16, (ex, buf) => {
const token = buf.toString('hex');
var u = new User({
email: email,
account_type: "email",
nickname: email,
team: team._id,
password_hash: hash,
payment_plan_key: team.payment_plan_key,
confirmation_token: confirmation_token,
preferences: {
language: req.i18n.locale
}
});
u.save((err) => {
if(err) res.sendStatus(400);
else {
var homeSpace = new Space({
name: req.i18n.__("home"),
space_type: "folder",
creator: u
});
homeSpace.save((err, homeSpace) => {
if (err) res.sendStatus(400);
else {
u.home_folder_id = homeSpace._id;
u.save((err) => {
User.find({"_id": {"$in": team.admins }}).exec((err, admins) => {
admins.forEach((admin) => {
var i18n = req.i18n;
if(admin.preferences && admin.preferences.language){
i18n.setLocale(admin.preferences.language || "en");
}
mailer.sendMail(admin.email, i18n.__("team_invite_membership_subject", team.name), i18n.__("team_invite_admin_body", email, team.name, password), { teamname: team.name });
});
});
mailer.sendMail(email, req.i18n.__("team_invite_membership_subject", team.name), req.i18n.__("team_invite_user_body", team.name, password), { action: {
link: config.endpoint + "/users/byteam/" + req.user.team._id + "/join?confirmation_token=" + confirmation_token,
name: req.i18n.__("team_invite_membership_action")
}, teamname: team.name });
if (err) res.status(400).json(err);
else{
res.status(201).json(u)
}
});
}
});
}
});
});
});
});
}
});
} else {
res.status(400).json({"error": "email missing"});
}
});
router.put('/:id/memberships/:user_id', (req, res) => {
User.findOne({_id: req.params.user_id}, (err,mem) => {
if (err) res.sendStatus(400);
else {
if(user.team._id == req.user.team._id){
user['team'] = req.user.team._id;
user.save((err) => {
res.sendStatus(204);
});
} else {
res.sendStatus(403);
}
}
});
});
router.get('/:id/memberships/:user_id/promote', (req, res) => {
User.findOne({_id: req.params.user_id}, (err,user) => {
if (err) res.sendStatus(400);
else {
if (user.team.toString() == req.user.team._id.toString()) {
var team = req.user.team;
var adminIndex = team.admins.indexOf(user._id);
if (adminIndex == -1) {
team.admins.push(user._id);
team.save((err, team) => {
res.status(204).json(team);
});
} else {
res.status(400).json({"error": "already admin"});
}
} else {
res.status(403).json({"error": "team id not correct"});
}
}
});
});
router.get('/:id/memberships/:user_id/demote', (req, res, next) => {
User.findOne({_id: req.params.user_id}, (err,user) => {
if (err) res.sendStatus(400);
else {
if (user.team.toString() == req.user.team._id.toString()) {
const team = req.user.team;
const adminIndex = team.admins.indexOf(user._id);
if(adminIndex > -1) {
team.admins.splice(adminIndex,1);
team.save((err, team) => {
res.status(204).json(team);
});
} else {
res.sendStatus(404);
}
} else {
res.sendStatus(403);
}
}
});
});
router.delete('/:id/memberships/:user_id', (req, res) => {
User.findOne({_id: req.params.user_id}).populate('team').exec((err,user) => {
if (err) res.sendStatus(400);
else {
const currentUserId = req.user._id.toString();
const team = req.user.team;
const isAdmin = (req.user.team.admins.filter( mem => {
return mem == currentUserId;
}).length == 1)
if (isAdmin) {
user.team = null;
user.payment_plan_key = "free";
user.save( err => {
const adminIndex = team.admins.indexOf(user._id);
if(adminIndex > -1) {
team.admins.splice(adminIndex,1);
team.save((err, team) => {
console.log("admin removed");
});
}
res.sendStatus(204);
});
} else {
res.status(403).json({"error": "not admin"});
}
}
});
});
module.exports = router;

View File

@ -1,13 +1,16 @@
"use strict"; "use strict";
var config = require('config'); var config = require('config');
require('../../models/schema'); const db = require('../../models/db');
const uuidv4 = require('uuid/v4');
const os = require('os');
var mailer = require('../../helpers/mailer'); var mailer = require('../../helpers/mailer');
var uploader = require('../../helpers/uploader'); var uploader = require('../../helpers/uploader');
var importer = require('../../helpers/importer');
var bcrypt = require('bcrypt'); var bcrypt = require('bcryptjs');
var crypo = require('crypto'); var crypto = require('crypto');
var swig = require('swig'); var swig = require('swig');
var async = require('async'); var async = require('async');
var _ = require('underscore'); var _ = require('underscore');
@ -15,245 +18,125 @@ var fs = require('fs');
var request = require('request'); var request = require('request');
var gm = require('gm'); var gm = require('gm');
var validator = require('validator'); var validator = require('validator');
var URL = require('url').URL;
var express = require('express'); var express = require('express');
var router = express.Router(); var router = express.Router();
var glob = require('glob');
router.get('/current', function(req, res, next) { router.get('/current', function(req, res, next) {
if (req.user) { if (req.user) {
console.log(req.user.team); var u = _.clone(req.user.dataValues);
res.status(200).json(req.user); delete u.password_hash;
delete u.password_reset_token;
delete u.confirmation_token;
u.token = req.cookies['sdsession'];
console.log(u);
res.status(200).json(u);
} else { } else {
res.status(401).json({"error":"user_not_found"}); res.status(401).json({"error":"user_not_found"});
} }
}); });
// create user
router.post('/', function(req, res) { router.post('/', function(req, res) {
if (req.body["email"] && req.body["password"]) { if (!req.body["email"] || !req.body["password"]) {
var email = req.body["email"].toLowerCase();
var nickname = req.body["nickname"];
var password = req.body["password"];
var password_confirmation = req.body["password_confirmation"];
if (password_confirmation == password) {
if (validator.isEmail(email)) {
var createUser = function() {
bcrypt.genSalt(10, function(err, salt) {
bcrypt.hash(password, salt, function(err, hash) {
crypo.randomBytes(16, function(ex, buf) {
var token = buf.toString('hex');
var u = new User({
email: email,
account_type: "email",
nickname: nickname,
password_hash: hash,
preferences: {
language: req.i18n.locale
},
confirmation_token: token
});
u.save(function (err) {
if (err) res.sendStatus(400);
else {
var homeSpace = new Space({
name: req.i18n.__("home"),
space_type: "folder",
creator: u
});
homeSpace.save((err, homeSpace) => {
if (err) res.sendStatus(400);
else {
u.home_folder_id = homeSpace._id;
u.save((err) => {
mailer.sendMail(u.email, req.i18n.__("confirm_subject"), req.i18n.__("confirm_body"), {
action: {
link: config.endpoint + "/confirm/" + u.confirmation_token,
name: req.i18n.__("confirm_action")
}
});
if (err) res.status(400).json(err);
else {
res.status(201).json({});
}
});
}
});
}
});
});
});
});
};
User.find({email: email}, (function (err, users) {
if (err) {
res.status(400).json({"error":"password_confirmation"});
} else {
if (users.length === 0) {
var domain = email.slice(email.lastIndexOf('@')+1);
Domain.findOne({domain: domain}, function(err, domain) {
if(domain){
if(domain.edu) {
createUser();
} else {
res.status(400).json({"error":"domain_blocked"});
}
} else {
createUser();
}
});
} else {
res.status(400).json({"error":"user_email_already_used"});
}
}
}));
} else {
res.status(400).json({"error":"email_invalid"});
}
} else {
res.status(400).json({"error":"password_confirmation"});
}
} else {
res.status(400).json({"error":"email or password missing"}); res.status(400).json({"error":"email or password missing"});
return;
} }
});
var email = req.body["email"].toLowerCase();
var nickname = req.body["nickname"];
var password = req.body["password"];
var password_confirmation = req.body["password_confirmation"];
var invite_code = req.body["invite_code"];
router.get('/oauth2callback/url', function(req, res) { if (password_confirmation != password) {
var google = require('googleapis'); res.status(400).json({"error":"password_confirmation"});
var OAuth2 = google.auth.OAuth2; return;
}
if (config.invite_code && invite_code != config.invite_code) {
res.status(400).json({"error":"Invalid Invite Code."});
return;
}
if (!validator.isEmail(email)) {
res.status(400).json({"error":"email_invalid"});
return;
}
var createUser = function() {
bcrypt.genSalt(10, function(err, salt) {
bcrypt.hash(password, salt, function(err, hash) {
crypto.randomBytes(16, function(ex, buf) {
var token = buf.toString('hex');
var oauth2Client = new OAuth2( var u = {
config.google_access, _id: uuidv4(),
config.google_secret, email: email,
config.endpoint + "/login" account_type: "email",
); nickname: nickname,
password_hash: hash,
prefs_language: req.i18n.locale,
confirmation_token: token
};
var url = oauth2Client.generateAuthUrl({ db.User.create(u)
access_type: 'online', .error(err => {
scope: "email" res.sendStatus(400);
}); })
.then(u => {
res.status(200).json({"url":url}); var homeFolder = {
}); _id: uuidv4(),
name: req.i18n.__("home"),
router.get('/loginorsignupviagoogle', function(req, res) { space_type: "folder",
var google = require('googleapis'); creator_id: u._id
var OAuth2 = google.auth.OAuth2; };
var plus = google.plus('v1'); db.Space.create(homeFolder)
.error(err => {
var oauth2Client = new OAuth2( res.sendStatus(400);
config.google_access, })
config.google_secret, .then(homeFolder => {
config.endpoint + "/login" u.home_folder_id = homeFolder._id;
); u.save()
.then(() => {
var loginUser = function(user, cb) { // home folder created,
crypo.randomBytes(48, function(ex, buf) { // auto accept pending invites
var token = buf.toString('hex'); db.Membership.update({
var session = { "state": "active"
token: token, }, {
created_at: new Date() where: {
}; "email_invited": u.email,
if(!user.sessions) "state": "pending"
user.sessions = []; }
user.sessions.push(session); });
user.save(function(err, user) { res.status(201).json({});
cb(session); })
.error(err => {
res.status(400).json(err);
});
})
});
});
}); });
}); });
}; };
var code = req.query.code; db.User.findAll({where: {email: email}})
oauth2Client.getToken(code, function(err, tokens) { .then(users => {
if (users.length == 0) {
if (err) res.status(400).json(err); createUser();
else { } else {
var apiUrl = "https://www.googleapis.com/oauth2/v1/userinfo?alt=json&access_token=" + tokens.access_token; res.status(400).json({"error":"user_email_already_used"});
}
var finalizeLogin = function(session){ })
var secure = process.env.NODE_ENV == "production" || process.env.NODE_ENV == "staging";
res.cookie('sdsession', session.token, { httpOnly: true, secure: secure});
res.status(201).json(session);
};
request.get(apiUrl, function(error, response, body) {
if (error) res.status(400).json(error);
else {
const data = JSON.parse(body);
const email = data.email;
const name = data.name;
User.findOne({email: email}, function (err, user) {
if (user) {
// login new google user
if (user.account_type == "google") {
// just login
loginUser(user, (session) => {
finalizeLogin(session);
});
} else {
res.status(400).json({"error":"user_email_already_used"});
}
} else {
const u = new User({
email: email,
account_type: "google",
nickname: name,
avatar_thumb_uri: body.picture,
preferences: {
language: req.i18n.locale
},
confirmed_at: new Date()
});
u.save(function (err) {
if (err) res.status(400).json(err);
else {
var homeSpace = new Space({
name: req.i18n.__("home"),
space_type: "folder",
creator: u
});
homeSpace.save(function(err, homeSpace) {
if (err) res.status(400).json(err);
else {
u.home_folder_id = homeSpace._id;
u.save(function(err){
if (err) res.sendStatus(400);
else {
mailer.sendMail(u.email, req.i18n.__("welcome_subject"), req.i18n.__("welcome_body"), {});
loginUser(u, function(session) {
finalizeLogin(session);
});
}
});
}
});
}
});
}
});
}
});
}
});
}); });
router.get('/ ', function(req, res, next) { router.get('/current', function(req, res, next) {
if (req.user) { if (req.user) {
console.log(req.user.team);
res.status(200).json(req.user); res.status(200).json(req.user);
} else { } else {
res.status(401).json({"error":"user_not_found"}); res.status(401).json({"error":"user_not_found"});
@ -261,19 +144,15 @@ router.get('/ ', function(req, res, next) {
}); });
router.put('/:id', function(req, res, next) { router.put('/:id', function(req, res, next) {
// TODO explicit whitelisting
var user = req.user; var user = req.user;
console.log(req.params.id, user._id);
if (user._id == req.params.id) { if (user._id == req.params.id) {
var newAttr = req.body; var newAttr = req.body;
newAttr.updated_at = new Date(); newAttr.updated_at = new Date();
delete newAttr['_id']; delete newAttr['_id'];
User.findOneAndUpdate({"_id": user._id}, {"$set": newAttr}, function(err, updatedUser) { db.User.update(newAttr, {where: {"_id": user._id}}).then(function(updatedUser) {
if (err) { res.status(200).json(newAttr);
res.sendStatus(400);
} else {
res.status(200).json(updatedUser);
}
}); });
} else { } else {
res.sendStatus(403); res.sendStatus(403);
@ -291,46 +170,41 @@ router.post('/:id/password', function(req, res, next) {
bcrypt.genSalt(10, function(err, salt) { bcrypt.genSalt(10, function(err, salt) {
bcrypt.hash(pass, salt, function(err, hash) { bcrypt.hash(pass, salt, function(err, hash) {
user.password_hash = hash; user.password_hash = hash;
user.save(function(err){ user.save().then(function() {
if(err){ res.sendStatus(204);
res.status(400).json(err);
}else{
res.sendStatus(204);
}
}); });
}); });
}); });
} else { } else {
res.status(403).json({"error": "old password wrong"}); res.status(403).json({"error": "Please enter the correct current password."});
} }
} else { } else {
res.status(403).json({"error": "wrong user"}); res.status(403).json({"error": "Access denied."});
} }
} else { } else {
res.status(400).json({"error": "password_to_short"}); res.status(400).json({"error": "Please choose a new password with at least 6 characters."});
} }
}); });
router.delete('/:id', (req, res, next) => { router.delete('/:id', (req, res, next) => {
const user = req.user; const user = req.user;
if(user._id == req.params.id) { if (user._id == req.params.id) {
if (user.account_type == 'email') { if (bcrypt.compareSync(req.query.password, user.password_hash)) {
if (bcrypt.compareSync(req.query.password, user.password_hash)) {
user.remove((err) => { // TODO: this doesn't currently work.
if(err)res.status(400).json(err); // all objects (indirectly) belonging to the user have
else res.sendStatus(204); // to be walked and deleted first.
});
} else { user.destroy().then(err => {
res.bad_request("password_incorrect");
}
} else {
user.remove((err) => {
if(err)res.status(400).json(err); if(err)res.status(400).json(err);
else res.sendStatus(204); else res.sendStatus(204);
}); });
} else {
res.bad_request("Please enter the correct current password.");
} }
} else {
res.status(403).json({error: "Access denied."});
} }
else res.status(403).json({error: ""});
}); });
router.put('/:user_id/confirm', (req, res) => { router.put('/:user_id/confirm', (req, res) => {
@ -356,8 +230,8 @@ router.post('/:user_id/avatar', (req, res, next) => {
const user = req.user; const user = req.user;
const filename = "u"+req.user._id+"_"+(new Date().getTime())+".jpeg" const filename = "u"+req.user._id+"_"+(new Date().getTime())+".jpeg"
const localFilePath = "/tmp/"+filename; const localFilePath = os.tmpdir()+"/"+filename;
const localResizedFilePath = "/tmp/resized_"+filename; const localResizedFilePath = os.tmpdir()+"/resized_"+filename;
const writeStream = fs.createWriteStream(localFilePath); const writeStream = fs.createWriteStream(localFilePath);
const stream = req.pipe(writeStream); const stream = req.pipe(writeStream);
@ -369,19 +243,15 @@ router.post('/:user_id/avatar', (req, res, next) => {
if (err) res.status(400).json(err); if (err) res.status(400).json(err);
else { else {
user.avatar_thumb_uri = url; user.avatar_thumb_uri = url;
user.save((err, updatedUser) => { user.save().then(() => {
if (err) { fs.unlink(localResizedFilePath, (err) => {
res.sendStatus(400); if (err) {
} else { console.error(err);
fs.unlink(localResizedFilePath, (err) => { res.status(400).json(err);
if (err) { } else {
console.error(err); res.status(200).json(user);
res.status(400).json(err); }
} else { });
res.status(200).json(updatedUser);
}
});
}
}); });
} }
}); });
@ -390,40 +260,22 @@ router.post('/:user_id/avatar', (req, res, next) => {
}); });
}); });
router.post('/feedback', function(req, res, next) {
var text = req.body.text;
// FIXME
mailer.sendMail("support@example.org", "Support Request by " + req.user.email, text, {reply_to: req.user.email});
res.sendStatus(201);
});
router.post('/password_reset_requests', (req, res, next) => { router.post('/password_reset_requests', (req, res, next) => {
const email = req.query.email; const email = req.query.email;
User.findOne({"email": email}).exec((err, user) => { db.User.findOne({where: {"email": email}}).then((user) => {
if (err) { if (user) {
res.status(400).json(err); crypto.randomBytes(16, (ex, buf) => {
user.password_reset_token = buf.toString('hex');
user.save().then(updatedUser => {
mailer.sendMail(email, req.i18n.__("password_reset_subject"), req.i18n.__("password_reset_body"), {action: {
link: config.endpoint + "/password-confirm/" + user.password_reset_token,
name: req.i18n.__("password_reset_action")
}});
res.status(201).json({});
});
});
} else { } else {
if (user) { res.status(404).json({"error": "error_unknown_email"});
if(user.account_type == "email") {
crypo.randomBytes(16, (ex, buf) => {
user.password_reset_token = buf.toString('hex');
user.save((err, updatedUser) => {
if (err) res.status(400).json(err);
else {
mailer.sendMail(email, req.i18n.__("password_reset_subject"), req.i18n.__("password_reset_body"), {action: {
link: config.endpoint + "/password-confirm/" + user.password_reset_token,
name: req.i18n.__("password_reset_action")
}});
res.status(201).json({});
}
});
});
} else {
res.status(404).json({"error": "error_unknown_email"});
}
} else {
res.status(404).json({"error": "error_unknown_email"});
}
} }
}); });
}); });
@ -431,30 +283,21 @@ router.post('/password_reset_requests', (req, res, next) => {
router.post('/password_reset_requests/:confirm_token/confirm', function(req, res, next) { router.post('/password_reset_requests/:confirm_token/confirm', function(req, res, next) {
var password = req.body.password; var password = req.body.password;
User db.User
.findOne({"password_reset_token": req.params.confirm_token}) .findOne({where: {"password_reset_token": req.params.confirm_token}})
.exec((err, user) => { .then((user) => {
if (err) { if (user) {
res.sendStatus(400); bcrypt.genSalt(10, (err, salt) => {
} else { bcrypt.hash(password, salt, function(err, hash) {
if(user) { user.password_hash = hash;
bcrypt.genSalt(10, (err, salt) => { user.password_token = null;
bcrypt.hash(password, salt, function(err, hash) { user.save().then(function(updatedUser) {
res.sendStatus(201);
user.password_hash = hash;
user.password_token = null;
user.save(function(err, updatedUser){
if (err) {
res.sendStatus(400);
} else {
res.sendStatus(201);
}
});
}); });
}); });
} else { });
res.sendStatus(404); } else {
} res.sendStatus(404);
} }
}); });
}); });

View File

@ -1,7 +1,7 @@
"use strict"; "use strict";
var config = require('config'); var config = require('config');
require('../../models/schema'); require('../../models/db');
var fs = require('fs'); var fs = require('fs');
var phantom = require('node-phantom-simple'); var phantom = require('node-phantom-simple');

View File

@ -1,7 +1,6 @@
"use strict"; "use strict";
const config = require('config'); const config = require('config');
require('../models/schema');
const redis = require('../helpers/redis'); const redis = require('../helpers/redis');
const express = require('express'); const express = require('express');
@ -9,7 +8,11 @@ const crypto = require('crypto');
const router = express.Router(); const router = express.Router();
const mailer = require('../helpers/mailer'); const mailer = require('../helpers/mailer');
const _ = require('underscore'); const _ = require('underscore');
const qr = require('qr-image');
const db = require('../models/db');
const Sequelize = require('sequelize');
const Op = Sequelize.Op;
const uuidv4 = require('uuid/v4');
router.get('/', (req, res) => { router.get('/', (req, res) => {
res.render('index', { title: 'Spaces' }); res.render('index', { title: 'Spaces' });
@ -51,10 +54,6 @@ router.get('/password-confirm/:token', (req, res) => {
res.render('spacedeck', { title: 'Signup' }); res.render('spacedeck', { title: 'Signup' });
}); });
router.get('/team', (req, res) => {
res.render('spacedeck');
});
router.get('/de/*', (req, res) => { router.get('/de/*', (req, res) => {
res.redirect("/t/de"); res.redirect("/t/de");
}); });
@ -79,10 +78,6 @@ router.get('/en', (req, res) => {
res.redirect("/t/end"); res.redirect("/t/end");
}); });
router.get('/it', (req, res) => {
res.redirect("/t/en");
});
router.get('/account', (req, res) => { router.get('/account', (req, res) => {
res.render('spacedeck'); res.render('spacedeck');
}); });
@ -95,10 +90,6 @@ router.get('/logout', (req, res) => {
res.render('spacedeck'); res.render('spacedeck');
}); });
router.get('/users/oauth2callback', (req, res) => {
res.render('spacedeck');
});
router.get('/contact', (req, res) => { router.get('/contact', (req, res) => {
res.render('public/contact'); res.render('public/contact');
}); });
@ -125,180 +116,30 @@ router.get('/t/:id', (req, res) => {
}); });
router.get('/s/:token', (req, res) => { router.get('/s/:token', (req, res) => {
redis.rateLimit(req.real_ip, "token", function(ok) { var token = req.params.token;
if (ok) { if (token.split("-").length > 0) {
var token = req.params.token; token = token.split("-")[0];
if (token.split("-").length > 0) { }
token = token.split("-")[0];
}
Space.findOne({"edit_hash": token}).exec(function (err, space) { db.Space.findOne({where: {"edit_hash": token}}).then(function (space) {
if (err) { if (space) {
res.status(404).render('not_found', { title: 'Page Not Found.' }); if (req.accepts('text/html')){
} else { res.redirect("/spaces/"+space._id + "?spaceAuth=" + token);
if (space) { } else {
if(req.accepts('text/html')){ res.status(200).json(space);
res.redirect("/spaces/"+space._id + "?spaceAuth=" + token); }
}else{
res.status(200).json(space);
}
} else {
if(req.accepts('text/html')){
res.status(404).render('not_found', { title: 'Page Not Found.' });
} else {
res.status(404).json({});
}
}
}
});
} else { } else {
res.status(429).json({"error": "Too Many Requests"}); if (req.accepts('text/html')) {
res.status(404).render('not_found', { title: 'Page Not Found.' });
} else {
res.status(404).json({});
}
} }
}); });
}); });
router.get('/spaces/:id', (req, res) => { router.get('/spaces/:id', (req, res) => {
if (req.headers['user-agent']) { res.render('spacedeck', { title: 'Space' });
if (req.headers['user-agent'].match(/facebook/)) {
Space.findOne({"_id": req.params.id }).exec(function (err, space) {
if (err) {
res.status(400).json(err);
} else {
if (space) {
if (space.access_mode == "public") {
Artifact.find({"space_id": req.params.id }).populate("creator").exec(function(err, artifacts) {
space.artifacts = artifacts;
res.render('facebook', { space: space });
});
} else {
res.redirect("/?error=space_not_accessible");
}
} else {
res.render('not_found', { title: 'Spaces' });
}
}
});
} else {
// not facebook, render javascript
res.render('spacedeck', { title: 'Space' });
}
} else res.render('spacedeck', { title: 'Space' });
});
router.get('/users/byteam/:team_id/join', (req, res) => {
if (!req.user) {
const q = {confirmation_token: req.query.confirmation_token, account_type: "email", team: req.params.team_id};
User.findOne(q, (err, user) => {
if (err) {
res.status(400).json({"error":"session.users"});
} else {
if (user) {
crypto.randomBytes(48, function(ex, buf) {
const token = buf.toString('hex');
var session = {
token: token,
ip: req.ip,
device: "web",
created_at: new Date()
};
if (!user.sessions)
user.sessions = [];
user.sessions.push(session);
user.confirmed_at = new Date();
user.confirmation_token = null;
user.save(function(err, result) {
// FIXME
const secure = process.env.NODE_ENV == "production" || process.env.NODE_ENV == "staging";
const domain = (process.env.NODE_ENV == "production") ? ".spacedeck.com" : ".spacedecklocal.de";
res.cookie('sdsession', token, { domain: domain, httpOnly: true, secure: secure});
res.redirect("/spaces");
});
});
} else {
res.status(404).json({"error": "not found"});
}
}
});
} else {
res.redirect("/spaces");
}
});
router.get('/teams/:id/join', function(req, res, next) {
if (req.user) {
if (!req.user.team) {
Team.findOne({"_id": req.params.id}, function(err, team) {
if (team) {
const idx = team.invitation_codes.indexOf(req.query.code);
if (idx >= 0) {
const u = req.user;
u.team = team;
if(!u.confirmed_at) {
u.confirmed_at = new Date();
}
u.payment_plan_key = team.payment_plan_key;
u.save(function(err) {
if (err) res.status(400).json(err);
else {
team.invitation_condes = team.invitation_codes.slice(idx);
team.save(function(err) {
team.invitation_codes = null;
var finish = function(team, users) {
User.find({"_id": {"$in": team.admins}}).exec((err, admins) => {
if(admins) {
admins.forEach((admin) => {
mailer.sendMail(
admin.email,
req.i18n.__("team_new_member_subject", team.name),
req.i18n.__("team_new_member_body", u.email, team.name)
);
});
}
});
}
User.find({team: team}, function(err, users) {
finish(team, users);
res.redirect("/spaces");
});
});
}
});
} else {
res.redirect("/spaces?error=team_code_notfound");
}
} else {
res.redirect("/spaces?error=team_notfound");
}
});
} else {
res.redirect("/spaces?error=team_already");
}
} else res.redirect("/login");
});
router.get('/qrcode/:id', function(req, res) {
Space.findOne({"_id": req.params.id}).exec(function(err, space) {
if (space) {
const url = config.get("endpoint") + "/s/"+space.edit_hash;
const code = qr.image(url, { type: 'svg' });
res.type('svg');
code.pipe(res);
} else {
res.status(404).json({
"error": "not_found"
});
}
});
}); });
module.exports = router; module.exports = router;

172
spacedeck.js Normal file
View File

@ -0,0 +1,172 @@
"use strict";
const db = require('./models/db.js');
require("log-timestamp");
const config = require('config');
const redis = require('./helpers/redis');
const websockets = require('./helpers/websockets');
const http = require('http');
const path = require('path');
const _ = require('underscore');
const favicon = require('serve-favicon');
const logger = require('morgan');
const cookieParser = require('cookie-parser');
const bodyParser = require('body-parser');
const swig = require('swig');
const i18n = require('i18n-2');
const helmet = require('helmet');
const express = require('express');
const app = express();
const serveStatic = require('serve-static');
const isProduction = app.get('env') === 'production';
// workaround for libssl_conf.so error triggered by phantomjs
process.env['OPENSSL_CONF'] = '/dev/null';
console.log("Booting Spacedeck Open… (environment: " + app.get('env') + ")");
app.use(logger(isProduction ? 'combined' : 'dev'));
i18n.expressBind(app, {
locales: ["en", "de", "fr"],
defaultLocale: "en",
cookieName: "spacedeck_locale",
devMode: (app.get('env') == 'development')
});
swig.setDefaults({
varControls: ["[[", "]]"] // otherwise it's not compatible with vue.js
});
swig.setFilter('cdn', function(input, idx) {
return input;
});
app.engine('html', swig.renderFile);
app.set('view engine', 'html');
if (isProduction) {
app.set('views', path.join(__dirname, 'build', 'views'));
app.use(favicon(path.join(__dirname, 'build', 'assets', 'images', 'favicon.png')));
app.use(express.static(path.join(__dirname, 'build', 'assets')));
} else {
app.set('views', path.join(__dirname, 'views'));
app.use(favicon(path.join(__dirname, 'public', 'images', 'favicon.png')));
app.use(express.static(path.join(__dirname, 'public')));
}
app.use(bodyParser.json({
limit: '50mb'
}));
app.use(bodyParser.urlencoded({
extended: false,
limit: '50mb'
}));
app.use(cookieParser());
app.use(helmet.frameguard())
app.use(helmet.xssFilter())
app.use(helmet.hsts({
maxAge: 7776000000,
includeSubDomains: true
}))
app.disable('x-powered-by');
app.use(helmet.noSniff())
//app.use(require("./middlewares/error_helpers"));
app.use(require("./middlewares/session"));
//app.use(require("./middlewares/cors"));
app.use(require("./middlewares/i18n"));
app.use("/api", require("./middlewares/api_helpers"));
app.use('/api/spaces/:id', require("./middlewares/space_helpers"));
app.use('/api/spaces/:id/artifacts/:artifact_id', require("./middlewares/artifact_helpers"));
app.use('/api/users', require('./routes/api/users'));
app.use('/api/memberships', require('./routes/api/memberships'));
const spaceRouter = require('./routes/api/spaces');
app.use('/api/spaces', spaceRouter);
spaceRouter.use('/:id/artifacts', require('./routes/api/space_artifacts'));
spaceRouter.use('/:id/memberships', require('./routes/api/space_memberships'));
spaceRouter.use('/:id/messages', require('./routes/api/space_messages'));
spaceRouter.use('/:id/digest', require('./routes/api/space_digest'));
spaceRouter.use('/:id', require('./routes/api/space_exports'));
app.use('/api/sessions', require('./routes/api/sessions'));
//app.use('/api/webgrabber', require('./routes/api/webgrabber'));
app.use('/', require('./routes/root'));
if (config.get('storage_local_path')) {
app.use('/storage', serveStatic(config.get('storage_local_path')+"/"+config.get('storage_bucket'), {
maxAge: 24*3600
}));
}
// catch 404 and forward to error handler
//app.use(require('./middlewares/404'));
if (app.get('env') == 'development') {
app.set('view cache', false);
swig.setDefaults({cache: false});
} else {
app.use(require('./middlewares/500'));
}
module.exports = app;
// CONNECT TO DATABASE
db.init();
// START WEBSERVER
const port = 9666;
const server = http.Server(app).listen(port, () => {
if ("send" in process) {
process.send('online');
}
}).on('listening', () => {
const host = server.address().address;
const port = server.address().port;
console.log('Spacedeck Open listening at http://%s:%s', host, port);
}).on('error', (error) => {
if (error.syscall !== 'listen') {
throw error;
}
const bind = typeof port === 'string' ? 'Pipe ' + port : 'Port ' + port;
switch (error.code) {
case 'EACCES':
console.error(bind + ' requires elevated privileges');
process.exit(1);
break;
case 'EADDRINUSE':
console.error(bind + ' is already in use');
process.exit(1);
break;
default:
throw error;
}
});
websockets.startWebsockets(server);
redis.connectRedis();
/*process.on('message', (message) => {
console.log("Process message:", message);
if (message === 'shutdown') {
console.log("Exiting Spacedeck.");
process.exit(0);
}
});*/

View File

@ -26,12 +26,12 @@
} }
} }
/*&.artifact-text.text-blank [contentEditable=true]:not(.text-editing) p:first-child::after { &.artifact-text.text-blank [contentEditable=true]:not(.text-editing) p:first-child::after {
content: "Double click to edit"; content: "Double click to edit";
opacity: 0.25; opacity: 0.25;
} }
&.artifact-text.text-blank [contentEditable=true].text-editing p:first-child::after { /*&.artifact-text.text-blank [contentEditable=true].text-editing p:first-child::after {
content: "Type here"; content: "Type here";
opacity: 0.25; opacity: 0.25;
}*/ }*/
@ -469,11 +469,10 @@
color: black; color: black;
//@include user-select(none); //@include user-select(none);
white-space: normal; white-space: normal;
font-size: 18px; font-size: 36px;
&.artifact-zone { &.artifact-zone {
border: 1px solid rgba(46,204,113,1); background-color: rgba(0,0,0,0.05);
background-color: rgba(46,204,113,0.025);
border-radius: 10px; border-radius: 10px;
&:after {display: none; } &:after {display: none; }
.shape {display: none; } .shape {display: none; }
@ -553,6 +552,10 @@ body:not(.present-mode) {
cursor: grab !important; cursor: grab !important;
} }
.tool-note {
cursor: crosshair !important;
}
.artifact.state-idle { .artifact.state-idle {
.progress, .progress-text { .progress, .progress-text {
display: none; display: none;

View File

@ -7,12 +7,6 @@
.btn-group.colors { .btn-group.colors {
.btn { .btn {
// padding: 4px;
// background-clip: content-box;
// padding-right: 2px;
// &:last-child {
// padding-right: 4px;
// }
box-shadow: inset 0 0 30px 0px rgba(40,40,40,0.1); box-shadow: inset 0 0 30px 0px rgba(40,40,40,0.1);
} }
} }
@ -64,7 +58,7 @@
backface-visibility: hidden; backface-visibility: hidden;
cursor: pointer; cursor: pointer;
background-color: $light; background-color: $light;
color: $medium;; color: $black;
@include user-select(none); @include user-select(none);
&:last-child {border: none;} &:last-child {border: none;}
@ -82,12 +76,9 @@
&.btn-link { &.btn-link {
background-color: transparent; background-color: transparent;
color: $medium;; color: $medium;
} }
&.facebook {background-color: $facebook !important; color: white !important;}
&.twitter {background-color: $twitter !important; color: white !important; }
&.btn-round { &.btn-round {
border-radius: 100px !important; border-radius: 100px !important;
} }
@ -96,21 +87,10 @@
border-radius: 6px !important; border-radius: 6px !important;
} }
// &.close {
// position: absolute;
// top: 15px;
// right: 15px;
// z-index: 4000;
// font-size: 40px;
// }
&.btn-nude { &.btn-nude {
min-width: 0 !important; min-width: 0 !important;
// font-size: inherit !important;
padding: 0 !important; padding: 0 !important;
// height: auto !important;
background-color: transparent; background-color: transparent;
color: $medium;
} }
&.btn-nude + .btn-nude { &.btn-nude + .btn-nude {
@ -123,7 +103,7 @@
&.btn-stroke { &.btn-stroke {
box-shadow: inset 0 0 0 1px $dark; box-shadow: inset 0 0 0 1px $dark;
color: $dark !important; color: $black;
background-color: transparent; background-color: transparent;
&:active { &:active {
box-shadow: inset 0 0 0 1px white; box-shadow: inset 0 0 0 1px white;
@ -132,9 +112,8 @@
} }
&.btn-stroke-darken { &.btn-stroke-darken {
//box-shadow: inset 0 0 0 1px rgba(0,0,0,0.1); border: 1px solid $black;
border: 1px solid rgba(0,0,0,0.1); color: $black;
color: $medium;
background-color: transparent; background-color: transparent;
&:active { &:active {
//box-shadow: inset 0 0 0 1px $dark; //box-shadow: inset 0 0 0 1px $dark;
@ -263,9 +242,18 @@
&.btn-transparent { &.btn-transparent {
background-color: transparent; background-color: transparent;
color: $medium; color: $dark;
&.active {color: $darker !important; } &.active {
&.open {color: white !important; } //color: $black !important;
color: $white;
background-color: $black;
}
&.open {
//color: $black !important;
color: $white;
background-color: $black;
border-radius: 0;
}
} }
&.btn-transparent-medium { &.btn-transparent-medium {
@ -313,7 +301,7 @@
&.btn-dark { &.btn-dark {
background-color: $dark ; background-color: $dark ;
color: $medium; color: $white;
} }
&.btn-medium { &.btn-medium {
@ -481,7 +469,6 @@
&.btn-icon { &.btn-icon {
padding: 0px !important; padding: 0px !important;
font-weight: bold;
max-width: 60px; max-width: 60px;
&.btn-xl { max-width: 80px; } &.btn-xl { max-width: 80px; }
@ -508,30 +495,6 @@
} }
} }
&.btn-social {
position: relative;
&:hover .icon,
.number {
@include scale(0,0);
opacity: 0;
}
&:hover .number {
@include transition( all 0.1s 0.1s ease-in-out);
@include scale(1,1);
opacity: 1;
}
.number,
.icon {
@include transition( all 0.1s 0s ease-in-out);
position: absolute;
top: 0;
left: 0;
}
}
&.btn-md.btn-icon-labeled { &.btn-md.btn-icon-labeled {
.icon:before { .icon:before {
line-height: 29px; line-height: 29px;
@ -567,7 +530,6 @@
.icon:before {line-height: 42px; } .icon:before {line-height: 42px; }
.icon-label { .icon-label {
font-size: 11px; font-size: 11px;
text-transform: capitalize;
text-align: center; text-align: center;
margin: 8px 0; margin: 8px 0;
display: block; display: block;
@ -580,7 +542,7 @@
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
padding: 0 0px; padding: 0 0px;
font-weight: bold; font-weight: 300;
} }
&.hover { &.hover {
@ -714,7 +676,6 @@
} }
> * { > * {
border-radius: 0 !important;
background-clip: padding-box; background-clip: padding-box;
width: 100%; width: 100%;
float: left; float: left;
@ -775,7 +736,7 @@
} }
} }
.btn-group { .btn-group {
@include scale(0,0); //@include scale(0,0);
//@include transition( all 0.1s 0s ease-in-out); //@include transition( all 0.1s 0s ease-in-out);
position: absolute; position: absolute;
@ -787,7 +748,7 @@
margin-left: -12px; margin-left: -12px;
.btn { .btn {
@include scale(0,0); //@include scale(0,0);
//@include transition( all 0.1s 0.05s ease-in-out); //@include transition( all 0.1s 0.05s ease-in-out);
@ -979,31 +940,7 @@
} }
} }
.btn-group.bottom-left > .btn { // !btn-group
border-radius: 0px;
&:first-child{
border-top-left-radius: 0px;
border-bottom-left-radius: 0px;
}
&.last,
&:last-child{
border-top-right-radius: 3px;
border-bottom-right-radius: 0px;
}
}
.btn-xyz {
position: relative;
display: inline-block;
line-height: 0px;
padding: 0px;
font-size: 0px;
vertical-align: middle;
white-space: nowrap;
@include clearfix;
min-height: 44px;
}
.btn-group { .btn-group {
position: relative; position: relative;
@ -1014,13 +951,16 @@
vertical-align: middle; vertical-align: middle;
white-space: nowrap; white-space: nowrap;
//border: 1px solid $dark;
border-radius: 5px;
&.dark { &.dark {
border-radius: $radius; border-radius: $radius;
background-color: $dark; background-color: $dark;
color: $lighter; color: $white;
.btn { .btn {
color: $lighter; color: $white;
} }
} }
@ -1159,4 +1099,4 @@
margin: 4px; margin: 4px;
z-index: 100; z-index: 100;
border-radius: 50px; border-radius: 50px;
} }

View File

@ -96,41 +96,37 @@
border-bottom-right-radius: $radius*3; border-bottom-right-radius: $radius*3;
} }
.dialog-account {
width: 600px;
margin: auto;
margin-top: 100px;
}
.dialog { .dialog {
font-size: 13px;
ol, ul, p {
font-size: inherit;
}
> .btn-block:last-child { > .btn-block:last-child {
border-top-left-radius: 0px; border-top-left-radius: 0px;
border-top-right-radius: 0px; border-top-right-radius: 0px;
border-bottom-left-radius: $radius*3; border-bottom-left-radius: $radius*3;
border-bottom-right-radius: $radius*3; border-bottom-right-radius: $radius*3;
} }
min-width: 200px;
@include backface-visibility(hidden);
white-space: normal;
z-index: 1000;
position: absolute; position: absolute;
// white-space: normal; font-size: 15px;
border: 1px solid black;
box-shadow: 0 0 30px 1px rgba(0, 0, 0, 0.15);
border-radius: 5px;
white-space: normal;
opacity: 0; opacity: 0;
@include user-select(none); @include transition(all 0.125s ease-in-out);
@include transition( all 0.125s ease-in-out);
pointer-events: none; pointer-events: none;
background-color: $light; background-color: $light;
color: $medium; color: $dark;
&.dark {background-color: $dark; } &.dark {
background-color: $dark;
}
border-radius: $radius*3;
box-shadow: 0 0 1px 1px rgba(0, 0, 0, 0.05), 0 2px 7px rgba(0, 0, 0, 0.1);
.dialog-tabs-wrapper { .dialog-tabs-wrapper {
overflow: hidden; overflow: hidden;
border-top-left-radius: $radius*3; border-top-left-radius: $radius*3;
@ -150,15 +146,13 @@
&:hover span {color: $dark; } &:hover span {color: $dark; }
&.open span { &.open span {
background-color: $light; background-color: white;
color: $dark; color: $dark;
opacity: 1; opacity: 1;
box-shadow: 0 0 1px 1px rgba(0, 0, 0, 0.05), 0 2px 7px rgba(0, 0, 0, 0.1) !important;
border-bottom-right-radius: 0px !important; border-bottom-right-radius: 0px !important;
border-bottom-left-radius: 0px !important; border-bottom-left-radius: 0px !important;
border-top-left-radius: $radius*3; border-top-left-radius: $radius*3;
border-top-right-radius: $radius*3; border-top-right-radius: $radius*3;
} }
&:first-child span { &:first-child span {
@ -200,7 +194,6 @@
text-align: center; text-align: center;
} }
.dialog-section { .dialog-section {
&:first-child {border: none !important; } &:first-child {border: none !important; }
border-top: 2px solid rgba(0,0,0,0.1); border-top: 2px solid rgba(0,0,0,0.1);
@ -228,4 +221,13 @@
h4 .icon { h4 .icon {
height: 38px; height: 38px;
} }
}
// account dialog
&.dialog-freestanding {
margin: auto;
position: relative;
top: 150px;
border: none;
width: 800px;
}
}

View File

@ -43,9 +43,6 @@ $predelay: 0;
&.hover:hover, &.hover:hover,
&.open { &.open {
// &:before {opacity: 0.125; }
// pointer-events: auto;
background-color: $dark;
background-color: $light; background-color: $light;
> * { > * {
@ -111,8 +108,8 @@ $predelay: 0;
} }
&:last-child > .btn{ &:last-child > .btn{
border-top-right-radius: $radius ; border-top-right-radius: $radius;
border-bottom-right-radius: $radius ; border-bottom-right-radius: $radius;
} }
} }
} }
@ -121,6 +118,10 @@ $predelay: 0;
display: inline-block; display: inline-block;
position: relative; position: relative;
vertical-align: middle; vertical-align: middle;
a {
text-decoration: none;
}
&.dropdown-block { &.dropdown-block {
display: block; display: block;
@ -143,8 +144,7 @@ $predelay: 0;
&.light > .dropdown-menu, &.light > .dropdown-menu,
&.light > .dialog { &.light > .dialog {
background: $light; background: white;
color: $medium;
} }
> .dropdown-menu { > .dropdown-menu {
@ -189,8 +189,6 @@ $predelay: 0;
} }
} }
&.hover:hover > .dialog, &.hover:hover > .dialog,
&.hover:hover > .dropdown-menu, &.hover:hover > .dropdown-menu,
@ -206,9 +204,7 @@ $predelay: 0;
&.open { &.open {
> .dialog, > .dialog,
> .dropdown-menu { > .dropdown-menu {
-webkit-transform: translate3d(-50%, -50%, 100px) scale(1); //transform: translate3d(-50%, -50%, 100px) scale(1);
-ms-transform: translate3d(-50%, -50%, 100px) scale(1);
transform: translate3d(-50%, -50%, 100px) scale(1);
} }
} }
@ -217,10 +213,8 @@ $predelay: 0;
left: 50%; left: 50%;
top: 50%; top: 50%;
margin-top: 0px; margin-top: 0px;
@include transform-origin(center center); //@include transform-origin(center center);
-webkit-transform: translate3d(-50%, -50%, 100px) scale(0.93,0.8); //transform: translate3d(-50%, -50%, 100px) scale(0.93,0.8);
-ms-transform: translate3d(-50%, -50%, 100px) scale(0.93,0.8);
transform: translate3d(-50%, -50%, 100px) scale(0.93,0.8);
} }
} }
@ -230,10 +224,8 @@ $predelay: 0;
top: auto; top: auto;
bottom: 100%; bottom: 100%;
margin-bottom: 16px; margin-bottom: 16px;
@include transform-origin(bottom left); //@include transform-origin(bottom left);
-webkit-transform: translate3d(-33%, 0%, 100px) scale(0.93,0.8); //transform: translate3d(-33%, 0%, 100px) scale(0.93,0.8);
-ms-transform: translate3d(-33%, 0%, 100px) scale(0.93,0.8);
transform: translate3d(-33%, 0%, 100px) scale(0.93,0.8);
} }
} }
@ -243,10 +235,8 @@ $predelay: 0;
top: auto; top: auto;
bottom: 100%; bottom: 100%;
margin-bottom: 16px; margin-bottom: 16px;
@include transform-origin(bottom center); //@include transform-origin(bottom center);
-webkit-transform: translate3d(-50%, 0%, 100px) scale(0.93,0.8); //transform: translate3d(-50%, 0%, 100px) scale(0.93,0.8);
-ms-transform: translate3d(-50%, 0%, 100px) scale(0.93,0.8);
transform: translate3d(-50%, 0%, 100px) scale(0.93,0.8);
} }
} }
@ -257,33 +247,37 @@ $predelay: 0;
top: 100%; top: 100%;
bottom: auto; bottom: auto;
margin-top: -16px; margin-top: -16px;
@include transform-origin(top center); //@include transform-origin(top center);
-webkit-transform: translate3d(-50%, 0%, 100px) scale(0.93,0.8); //transform: translate3d(-50%, 0%, 100px) scale(0.93,0.8);
-ms-transform: translate3d(-50%, 0%, 100px) scale(0.93,0.8);
transform: translate3d(-50%, 0%, 100px) scale(0.93,0.8);
} }
} }
&.top.left {
> .dialog,
> .dropdown-menu {
left: 70px;
margin-top: -60px;
}
}
&.top.right { &.top.right {
> .dialog, > .dialog,
> .dropdown-menu { > .dropdown-menu {
top: 100%; top: 100%;
bottom: auto; bottom: auto;
left: auto; left: auto;
right: 0;
margin-top: 16px; right: 70px;
@include transform-origin(top right); margin-top: -60px;
-webkit-transform: translate3d(0%, 0%, 100px) scale(0.93,0.8);
-ms-transform: translate3d(0%, 0%, 100px) scale(0.93,0.8); //@include transform-origin(top right);
transform: translate3d(0%, 0%, 100px) scale(0.93,0.8); //transform: translate3d(0%, 0%, 100px) scale(0.93,0.8);
} }
&.hover:hover, &.hover:hover,
&.open { &.open {
> .dialog, > .dialog,
> .dropdown-menu { > .dropdown-menu {
-webkit-transform: translate3d(0%, 0%, 100px) scale(1); //transform: translate3d(0%, 0%, 100px) scale(1);
-ms-transform: translate3d(0%, 0%, 100px) scale(1);
transform: translate3d(0%, 0%, 100px) scale(1);
} }
} }
@ -312,9 +306,7 @@ $predelay: 0;
> .dialog, > .dialog,
> .dropdown-menu { > .dropdown-menu {
-webkit-transform: translate3d(-50%, 0%, 100px) scale(1); //transform: translate3d(-50%, 0%, 100px) scale(1);
-ms-transform: translate3d(-50%, 0%, 100px) scale(1);
transform: translate3d(-50%, 0%, 100px) scale(1);
} }
} }
} }
@ -324,9 +316,7 @@ $predelay: 0;
&.open { &.open {
> .dialog, > .dialog,
> .dropdown-menu { > .dropdown-menu {
-webkit-transform: translate3d(-33%, 0%, 100px) scale(1) !important; //transform: translate3d(-33%, 0%, 100px) scale(1) !important;
-ms-transform: translate3d(-33%, 0%, 100px) scale(1) !important;
transform: translate3d(-33%, 0%, 100px) scale(1) !important;
} }
} }
} }
@ -334,7 +324,7 @@ $predelay: 0;
.dropdown { .dropdown {
&.options-3 { /*&.options-3 {
&.option-1:after { margin-left: -68px;} &.option-1:after { margin-left: -68px;}
&.option-2:after { margin-left: -8px;} &.option-2:after { margin-left: -8px;}
&.option-3:after { margin-left: 52px;} &.option-3:after { margin-left: 52px;}
@ -348,8 +338,9 @@ $predelay: 0;
-webkit-transform: scale(1); -webkit-transform: scale(1);
-ms-transform: scale(1); -ms-transform: scale(1);
transform: scale(1); transform: scale(1);
} }*/
/*
&:after { &:after {
@include transition( all 0.1s ease-in-out 0s); @include transition( all 0.1s ease-in-out 0s);
content: ""; content: "";
@ -362,26 +353,24 @@ $predelay: 0;
margin-left: -8px; margin-left: -8px;
pointer-events: none !important; pointer-events: none !important;
left: 50%; left: 50%;
-webkit-transform: scale(0,0); //transform: scale(0,0);
-ms-transform: scale(0,0);
transform: scale(0,0);
} }
&.bottom:after, &.bottomleft:after { &.bottom:after, &.bottomleft:after {
@include transform-origin(bottom center); //@include transform-origin(bottom center);
bottom: 100%; bottom: 100%;
border-bottom: 8px solid transparent; border-bottom: 8px solid transparent;
border-right: 8px solid transparent; border-right: 8px solid transparent;
border-top: 8px solid #303030; border-top: 8px solid #303030;
border-left: 8px solid transparent; border-left: 8px solid transparent;
} }
*/
&.top:after { /*&.top:after {
@include transform-origin(top center); @include transform-origin(top center);
top: 100%; top: 100%;
border-bottom: 8px solid #303030; border-bottom: 8px solid #303030;
border-right: 8px solid transparent; border-right: 8px solid transparent;
border-top: 8px solid transparent; border-top: 8px solid transparent;
border-left: 8px solid transparent; border-left: 8px solid transparent;
} }*/
} }

View File

@ -254,7 +254,6 @@
// word-wrap: break-word; // word-wrap: break-word;
.item { .item {
box-shadow: 0 0 1pxrgba(0,0,0,0.1);
display: inline-block; display: inline-block;
text-align: left; text-align: left;
padding-right: $folder-gutter*2; padding-right: $folder-gutter*2;
@ -397,7 +396,10 @@
&:active { opacity: 0.95 !important; } &:active { opacity: 0.95 !important; }
box-shadow: 0 0 1px 1px rgba(0, 0, 0, 0.025), 0 2px 7px rgba(0, 0, 0, 0.025); box-shadow: 0 0 30px 1px rgba(0, 0, 0, 0.15);
border: 1px solid black;
// ???
@include opacity(1); @include opacity(1);
color: $medium; color: $medium;
// color: white; // color: white;
@ -476,7 +478,6 @@
left: 0px; left: 0px;
z-index: 100; z-index: 100;
width: auto; width: auto;
background-color: rgba(255,255,255,1);
.dropdown { .dropdown {
position: absolute; position: absolute;
@ -501,30 +502,6 @@
color: $dark; color: $dark;
text-align: left; text-align: left;
} }
.item-social {
padding: 8px;
border-right: 2px solid rgba(0,0,0,0.025);
@include clearfix;
color: $medium;
.item-likes,
.item-comments,
.item-shares {
position: relative;
&:hover {
.icon {opacity: 0; }
.number {opacity: 1; }
}
.number {
position: absolute;
opacity: 0;
top: 0;
left: 0;
}
.icon {opacity: 0.5; }
}
}
} }
.item-appendix { .item-appendix {

View File

@ -28,7 +28,6 @@
line-height: 1.5; line-height: 1.5;
width: 100%; width: 100%;
text-align: left; text-align: left;
color: $medium;
font-weight: normal; font-weight: normal;
cursor: pointer; cursor: pointer;
border-radius: $radius; border-radius: $radius;
@ -139,4 +138,4 @@
display: inline-block !important; display: inline-block !important;
width: auto !important; width: auto !important;
padding-right: 15px !important; padding-right: 15px !important;
} }

View File

@ -2,24 +2,14 @@
@import "mixins"; @import "mixins";
.input-select { .input-select {
// background-color: rgba(255,255,255,0.04); background-color: rgba(255,255,255,0.04);
// background-image: url('images/select_arrow.gif'); background-image: url('images/select_arrow.gif');
border-radius: $radius; border-radius: $radius;
display: inline-block; display: inline-block;
width: 100%; width: 100%;
} }
@-moz-document url-prefix() {
select.input{
background-repeat: no-repeat;
background-position: right center;
cursor: pointer;
}
}
select { select {
-webkit-appearance:none;
// -moz-appearance:window;
appearance:none; appearance:none;
padding-left: 0px; padding-left: 0px;
width: 100%; width: 100%;

View File

@ -23,7 +23,6 @@ input:invalid {
top: 0; top: 0;
right: 0; right: 0;
line-height: 1; line-height: 1;
font-size: 10px;
margin: 12px; margin: 12px;
color: red; color: red;
margin-right: 25px; margin-right: 25px;
@ -113,43 +112,26 @@ select {
&.input-white { &.input-white {
background-color: white; background-color: white;
color: $medium;
box-shadow: inset 0 0 1px 1px rgba(0, 0, 0, 0.05), inset 0 0px 4px rgba(0, 0, 0, 0.1); box-shadow: inset 0 0 1px 1px rgba(0, 0, 0, 0.05), inset 0 0px 4px rgba(0, 0, 0, 0.1);
} }
&.input-light { &.input-light {
background-color: $light; background-color: $light;
color: $medium;
} }
&.input-dark { &.input-dark {
background-color: $darker; background-color: $darker;
color: $medium;
} }
&.input-lighten { &.input-lighten {
background-color: rgba(255,255,255,0.05); background-color: rgba(255,255,255,0.05);
color: $medium !important;
} }
&.input-darken { &.input-darken {
background-color: rgba(0,0,0,0.05); background-color: rgba(0,0,0,0.05);
color: $medium;
} }
&.input-transparent { &.input-transparent {
background-color: transparent; background-color: transparent;
color: $medium;
}
// &:focus {color: white; }
&:invalid {
// background-color: rgba(198,101,84,0.05);
// color: rgba(198,101,84,0.75)
&:after {
}
} }
@include input-focus(); @include input-focus();

View File

@ -69,26 +69,27 @@
} }
.handles { .handles {
// background-color: rgba(40,140,215,0.45); //border: 1px solid rgba(255,255,255,0.5);
border: 1px solid rgba(255,255,255,0.5);
position: absolute; position: absolute;
left: 0; left: 0;
top: 0; top: 0;
bottom: 0; bottom: -1;
right: 0; right: 0;
z-index: 800; z-index: 800;
pointer-events: none; pointer-events: none;
background: rgba(255,255,255,0.1);
&:after{ &:after{
border: 1px dotted rgba(40,140,215,1); border: 4px dotted #000000;
content: ""; content: "";
display: block; display: block;
position: absolute; position: absolute;
height: auto; height: auto;
width: auto; width: auto;
top: -1px; top: 0px;
left: -1px; left: 0px;
right: -1px; right: 0px;
bottom: -1px; bottom: -1px;
} }
} }
@ -97,7 +98,7 @@
border: 8px solid rgba(255,255,255,0.5); border: 8px solid rgba(255,255,255,0.5);
&:after{ &:after{
border: 8px dotted rgba(40,140,215,1); border: 8px dotted #000000;
top: -4px; top: -4px;
left: -4px; left: -4px;
right: -4px; right: -4px;
@ -332,16 +333,15 @@
pointer-events:auto; pointer-events:auto;
z-index: 2000; z-index: 2000;
position: absolute; position: absolute;
width: 30px !important;
height: 30px !important;
border-radius: 100%; border-radius: 100%;
margin: -15px;
border: 1px solid rgba(0,0,0,0.25);
border: 1px solid black;
margin: -5px;
padding: 4px;
&:hover { &:hover {
background-color: rgba(255,255,255,0.5); background-color: black;
cursor: move; cursor: move;
} }
} }
@ -428,15 +428,8 @@
border-style: solid; border-style: solid;
border-width: 10px; border-width: 10px;
border-color: transparent; border-color: transparent;
-webkit-background-clip: padding-box; background-clip: padding-box;
-moz-background-clip: padding-box; transition: all .05s ease-in-out;
background-clip: padding-box;
-webkit-transition: all .05s ease-in-out;
-moz-transition: all .05s ease-in-out;
-ms-transition: all .05s ease-in-out;
-o-transition: all .05s ease-in-out;
transition: all .05s ease-in-out;
} }
div { div {

View File

@ -5,7 +5,6 @@
.header-left, .header-left,
.header-right { .header-right {
position: absolute; position: absolute;
//@include transition( all 0.25s ease-in-out);
@include backface-visibility(hidden); @include backface-visibility(hidden);
z-index: 3000; z-index: 3000;
top: 10px; top: 10px;
@ -27,21 +26,21 @@
.home { .home {
margin-top: -20px; margin-top: -20px;
margin-left: -20px; margin-left: -20px;
// .icon {color: $dark; }
} }
.header-left { .header-left {
@include transform-origin(center left); left: 0;
left: 0; padding-left: 10px;
padding-left: 10px; padding-left: 20px;
padding-top: 20px;
} }
.header-right { .header-right {
@include transform-origin(center right); right: 0;
right: 0; padding-right: 20px;
padding-right: 10px; padding-top: 20px;
} }
.header-center { .header-center {
@include transform-origin(center center);
width: 100%; width: 100%;
left: 0; left: 0;
right: 0; right: 0;
@ -56,7 +55,7 @@
} }
} }
.header-left > * { margin-right: 10px; } .header-left > * { margin-right: 10px; }
.header-right > * { margin-left: 5px; } .header-right > * { margin-left: 10px; }
.header-right { font-size: 0;} .header-right { font-size: 0;}
.title { .title {
@ -90,21 +89,3 @@
opacity: 0.5; opacity: 0.5;
} }
} }
.present-mode #space-header {
background-color: transparent !important;
}
#space-siblings {
background-color: rgba(245, 245, 245, 0.95);
padding: 35px;
max-height: 450px;
overflow-y: scroll;
margin-top: 54px;
border-bottom: 1px solid #eee;
.btn {
margin-bottom: 50px;
}
}

View File

@ -85,3 +85,12 @@
transform: rotateZ(45deg) translateX(-8px); transform: rotateZ(45deg) translateX(-8px);
} }
.icon-svg {
background-size: 26px;
background-position: center;
background-repeat: no-repeat;
}
.icon-sd6 {
background-image: url(/images/sd6-icon-white.svg);
}

View File

@ -1,257 +1,52 @@
@import "vars"; @import "vars";
#landing-header { #landing-header {
background-color: rgba(255,255,255,0.3); background-color: white;
height: 64px; height: 64px;
position: absolute; position: relative;
top: 0; top: 0;
left: 0; left: 0;
right: 0; right: 0;
} }
.landing-keyvisual-wrapper { #landing {
background-image: url("../images/sd5-keyvisual-compressed.jpg"); margin-top: 100px;
background-size: cover;
background-position: center; section {
padding-top: 40px; margin-left: 300px;
padding-bottom: 40px;
} > * {
max-width: 600px;
.landing-plans-wrapper { }
background-image: url("../images/sd5-hero2-compressed.jpg");
background-size: cover;
background-position: center;
padding-top: 80px;
padding-bottom: 100px;
}
.landing-box {
width: 800px;
margin: auto;
max-width: 90%;
background-color: white;
padding: 40px;
margin-bottom: 80px;
margin-top: 80px;
position: relative;
box-shadow: 0px 0px 50px rgba(0,0,0,0.2);
h1 {
margin-bottom: 20px;
}
&.black {
background-color: #222;
color: white;
padding: 20px;
text-align: center;
}
&.overlap {
position: absolute;
z-index: 2;
margin-top: -65px;
left: 50%;
top: 0px;
margin-left: -250px;
width: 500px;
}
&.screenshot {
width: 90%;
max-width: 90%;
padding: 20px;
box-shadow: none;
background-color: transparent;
img {
width: 100%;
position: absolute;
top: 0px;
left: 0px;
opacity: 0.3;
} }
}
&.landing-box-left {
margin-left: 30px;
}
}
.lead-xxl {
}
.lead {
margin-bottom: 20px;
}
.lead-xl {
}
.plans-box {
background: linear-gradient(to bottom, #FEFFFF 25%,#D0D8E2 100%);
padding: 40px;
border-radius: 9px;
}
.landing-box.plans-box {
margin-top: 200px;
width: 900px;
}
.plans-table {
tr {
vertical-align: top;
}
th {
font-size: 42px;
padding-top: 40px;
text-align: center;
}
th.best-plan {
padding-top: 20px;
font-size: 48px;
padding-bottom: 0px;
}
td {
padding: 20px;
width: 30%;
p, li {
font-size: 18px;
}
li {
margin-bottom: 10px;
}
}
td.best-plan {
width: 40%;
p {
font-size: 22px;
}
}
td li {
list-style-type: none;
text-align: center;
}
ul {
margin: 0 !important;
padding: 0 !important;
}
.upgrade-buttons {
text-align:center;
margin-top:20px;
}
}
.logo-row {
position: relative;
padding: 80px;
background-color: white;
text-align: center;
width: 100%;
&.blue {
background-color: $blue;
color: white;
}
}
.logo-row div {
display: inline-block;
width: 200px;
}
.landing-row {
background-color: white;
padding-bottom: 80px;
padding-top: 40px;
}
#keyvisual {
border-radius: 20px;
box-shadow: 0px 0px 20px #eee;
width: 640px;
height: 420px;
background-size: contain;
background-repeat: no-repeat;
background-position: center;
background-image: url('/images/landing/spacedeck-screenshot1.jpg');
background-color: white;
margin: auto;
margin-top: 40px;
margin-bottom: 40px;
border: 1px solid #eee;
}
#legal {
.landing-box {
width: 800px;
}
} }
.footer { .footer {
padding: 40px; margin-left: 300px;
padding-bottom: 80px; margin-top: 100px;
text-align: center; margin-bottom: 100px;
color: $medium;
a {
margin-right: 20px;
}
} }
@media screen and (max-width: 1000px) {
#landing {
section {
margin-left: 20px;
margin-right: 20px;
}
}
.footer {
margin-left: 20px;
margin-right: 20px;
}
@media screen and (min-width: 801px) { .header-right {
.plans-table-mobile { right: auto;
display: none; padding-left: 10px;
} padding-right: 20px;
} padding-top: 80px;
}
@media screen and (max-width: 800px) {
#folder-wrapper {
ul.lead.lead-xl, p.lead.lead-xl, ol.lead.lead-xl { padding-top: 128px;
font-size: 20px !important; }
}
.header-right {
> span:first-child {
display: none;
}
}
.plans-table {
display: none;
}
.plans-table-mobile {
display: block;
tbody {
display: block;
width: 100%;
}
tr {
display: block;
width: 100%;
}
td, th {
display: block;
width: 100%;
}
ul, li {
width: 100%;
}
}
} }

View File

@ -2,7 +2,6 @@
@import "mixins"; @import "mixins";
.wrapper { .wrapper {
//@include transition( all 0.25s ease-in-out);
position: relative; position: relative;
margin: auto; margin: auto;
max-width: 1160px; max-width: 1160px;

View File

@ -59,9 +59,9 @@
} }
.close { .close {
position: fixed; margin-left: 44px;
margin: 44px 44px; margin-bottom: 44px;
.icon {display: block; } .icon {display: block; }
} }
figure { figure {
@ -135,7 +135,6 @@
outline: none; outline: none;
display: inline-block; display: inline-block;
text-align: left; text-align: left;
@include user-select(none);
border-radius: $radius*3; border-radius: $radius*3;
background-color: $light !important; background-color: $light !important;
@ -146,7 +145,6 @@
.modal-header { .modal-header {
padding: 30px 40px; padding: 30px 40px;
position: relative; position: relative;
color: $medium;
} }
.close-search { .close-search {
@ -279,25 +277,5 @@
// Footer (for actions) // Footer (for actions)
.modal-footer { .modal-footer {
// border-bottom-left-radius: $radius; margin-top: 20px;
// border-bottom-right-radius: $radius; }
// background-color: $dark !important;
// padding: 40px;
// padding-top: 0px;
// text-align: right; // right align buttons
@include clearfix(); // clear it in case folks use .pull-* classes on buttons
// Properly space out buttons
// .btn + .btn {
// margin-left: 5px;
// margin-bottom: 0; // account for input[type="submit"] which gets the bottom margin like all other inputs
// }
// // but override that for button groups
// .btn-group .btn + .btn {
// margin-left: -1px;
// }
// // and override it for block buttons as well
// .btn-block + .btn-block {
// margin-left: 0;
// }
}

14
styles/normalize.scss vendored
View File

@ -1,17 +1,5 @@
/*! normalize.css v3.0.0 | MIT License | git.io/normalize */ /*! normalize.css v3.0.0 | MIT License | git.io/normalize */
//
// 1. Set default font family to sans-serif.
// 2. Prevent iOS text size adjust after orientation change, without disabling
// user zoom.
//
html {
font-family: sans-serif; // 1
-ms-text-size-adjust: 100%; // 2
-webkit-text-size-adjust: 100%; // 2
}
// //
// Remove default margin. // Remove default margin.
// //
@ -420,4 +408,4 @@ table {
td, td,
th { th {
padding: 0; padding: 0;
} }

View File

@ -27,6 +27,5 @@
right: 0; right: 0;
z-index: 800; z-index: 800;
pointer-events: none; pointer-events: none;
opacity: 0.25;
display: block; display: block;
} }

View File

@ -6,22 +6,18 @@
li { li {
&.checked { &.checked {
&:before {background-color: $medium !important; }
> a, > a,
> span { > span {
color: $medium;
} }
} }
&:hover { &:hover {
&:before {background-color: $medium; }
> a, > a,
> span { > span {
background-color: rgba(0,0,0,0.025) !important; background-color: rgba(0,0,0,0.025) !important;
} }
} }
&:before {background-color: $medium; }
> a, > a,
> span { > span {
color: $medium; color: $medium;
@ -30,7 +26,7 @@
} }
.select-list { .select-list {
&:empty:before{ &:empty:before {
position: absolute; position: absolute;
top: 50%; top: 50%;
left: 50%; left: 50%;
@ -45,17 +41,14 @@
opacity: 0.5; opacity: 0.5;
} }
-webkit-mask-image: -webkit-gradient(linear, left top, left 15px, from(rgba(0,0,0,0)), to(rgba(0,0,0,0.5)));
background-clip: padding-box; background-clip: padding-box;
font-size: 15px; //font-size: 15px;
line-height: 14px; //line-height: 14px;
list-style: none; list-style: none;
margin: 0px; margin: 0px;
padding: 15px 0; padding: 15px 0;
text-align: left; text-align: left;
// background-color: $dark; // background-color: $dark;
color: $medium;
border-radius: $radius; border-radius: $radius;
.divider + li span {border: none !important; } .divider + li span {border: none !important; }
@ -90,15 +83,11 @@
} }
&:hover { &:hover {
// background-color: rgba(0,0,0,0.025); background-color: black;
&:before {
background-color: $medium;
display: block;
}
> a, > a,
> span { > span {
color: $medium; color: white;
color: $dark;
} }
} }
@ -126,9 +115,8 @@
display: block; display: block;
cursor: pointer; cursor: pointer;
white-space: nowrap; white-space: nowrap;
color: $medium;
margin: 0 25px; margin: 0 25px;
padding: 16px 3px; padding: 10px 0px;
// line-height: 50px; // line-height: 50px;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
@ -154,4 +142,4 @@
} }
} }
} }
} }

View File

@ -118,7 +118,7 @@
padding: 0 !important; padding: 0 !important;
.wrapper { .wrapper {
border: 1px dotted rgba(128,128,128,0.5); border: 4px solid black;
transition-duration: 0.25s; transition-duration: 0.25s;
transition-property: width, height, background-color; transition-property: width, height, background-color;
@ -132,32 +132,27 @@
max-height: 100%; max-height: 100%;
position: relative; position: relative;
overflow: scroll; overflow: scroll;
/** {
-moz-user-select: none !important; // firefox has selection problems
}*/
} }
.snap-ruler-h { .snap-ruler-h {
pointer-events: none; pointer-events: none;
position: fixed; position: fixed;
z-index: 0; z-index: 2000;
right: 0px; right: 0px;
height: 1px; height: 1px;
background-color: rgba(0,0,0,0.5); background-color: black;
left: 0px; left: 0px;
} }
.snap-ruler-v { .snap-ruler-v {
pointer-events: none; pointer-events: none;
position: fixed; position: fixed;
z-index: 0; z-index: 2000;
top: 0px; top: 0px;
bottom: 0px; bottom: 0px;
width: 1px; width: 1px;
background-color: rgba(0,0,0,0.5); background-color: black;
} }
.cursor { .cursor {
@ -227,30 +222,12 @@
} }
#space { #space {
/*-webkit-user-select: all; // user-select: all;
-ms-user-select: all;
-moz-user-select: all;
user-select: all;*/
position: relative; position: relative;
height: 100% !important; height: 100% !important;
//padding-top: 64px !important;
background-color: #eee; background-color: #eee;
} }
#made-with {
position: fixed;
width: 100%;
bottom: 0;
padding: 12px;
opacity: 0.25;
a {color: $dark; }
p {
text-align: center;
font-size: 11px;
}
}
#baseline { #baseline {
position: absolute; position: absolute;
width: 100%; width: 100%;
@ -298,8 +275,8 @@
.space-bounds { .space-bounds {
position: absolute; position: absolute;
left: 0px; left: 0;
top: 0px; top: 0;
pointer-events: none; pointer-events: none;
background-size: cover; background-size: cover;
background-repeat: no-repeat; background-repeat: no-repeat;

View File

@ -65,10 +65,15 @@
html, html,
body { body {
height:100%; height:100%;
-webkit-tap-highlight-color: transparent;
background-color: white; background-color: white;
background-color: $light; color: $black;
color: $darker; }
body {
max-width: 100%;
padding: 0px;
text-rendering: optimizeLegibility;
cursor: default;
} }
*[contenteditable="true"] { *[contenteditable="true"] {
@ -81,70 +86,12 @@ body {
@include box-sizing(border-box); @include box-sizing(border-box);
} }
body {
max-width: 100%;
padding: 0px;
text-rendering: optimizeLegibility;
//@include user-select(none);
cursor: default;
}
.img img { .img img {
max-width: 100%; max-width: 100%;
height: auto; height: auto;
} }
.plan { /*.layer {
color: $medium;
border-radius: $radius;
display: inline-block;
padding: 30px;
background-color: transparent;
border: 2px solid rgba(0,0,0,0.05);
width: 100%;
&.active {
background-color: white;
border: none;
}
h4 {
color: black;
margin-bottom: 0px;
}
p {
font-size: 13px;
line-height: 1.4;
margin-top: 5px;
margin-bottom: 5px;
}
ul {
list-style: none;
font-size: 10px;
margin: 0px;
padding: 0px;
border-top: 2px solid rgba(0,0,0,0.05);
padding-top: 20px;
margin-top: 20px;
margin-bottom: 20px;
li {padding-top: 2px; }
}
}
#startup {
background-position: center;
background-image:url(/images/diamond.svg);
background-repeat: no-repeat;
}
#home {
background-color: white;
}
.layer {
@include transition( all 0.2s ease-in-out); @include transition( all 0.2s ease-in-out);
@include backface-visibility(hidden); @include backface-visibility(hidden);
position: absolute; position: absolute;
@ -172,7 +119,7 @@ body {
pointer-events: auto; pointer-events: auto;
opacity: 1; opacity: 1;
} }
} }*/
[draggable] { [draggable] {
-moz-user-select: none; -moz-user-select: none;

View File

@ -8,10 +8,9 @@
} }
.table { .table {
width: 100%; width: 100%;
color: $medium;;
font-family: $main-font; font-family: $main-font;
// border-radius: $radius; border-radius: $radius;
// border: 2px solid rgba(0,0,0,0.0125) !important; border: 2px solid rgba(0,0,0,0.0125);
} }
.table thead > tr > th:first-child, .table thead > tr > th:first-child,

View File

@ -19,50 +19,23 @@
} }
margin: auto; margin: auto;
//text-align: center;
position: fixed; position: fixed;
bottom: 0px; top: 20px;
//width: 100%;
z-index: 3000; z-index: 3000;
padding: $gutter-b; padding: 0;
font-size: 0; font-size: 0;
line-height: 0; line-height: 0;
transition-duration: 0.15s; box-shadow: 0 0 30px 1px rgba(0, 0, 0, 0.15);
transition-timing-function: ease-in-out; border: 1px solid black;
transition-delay: initial; border-radius: 5px;
transition-property: opacity, transform;
@include backface-visibility(hidden); // FIXME questionable?
@include translate3d(0, 10px, 0);
pointer-events: none !important; pointer-events: none !important;
opacity: 0;
&.out {
@include translate3d(0, 10px, 0);
* {pointer-events: none !important; }
button, input, .dialog {
display: none;
}
}
&.in {
@include translate3d(0, 0, 0);
&.out {
@include translate3d(0, 10px, 0);
* {pointer-events: none !important; }
}
}
> * { > * {
margin: 0 2px;
margin-top: 4px;
pointer-events: auto !important; pointer-events: auto !important;
&.out {
margin: 0;
opacity: 0;
}
} }
&.toolbar-vertical { &.toolbar-vertical {
@ -187,7 +160,6 @@
} }
.toolbar-properties { .toolbar-properties {
bottom: 64px;
z-index: 0; z-index: 0;
&.in { &.in {
@ -196,12 +168,12 @@
.icon-sm { .icon-sm {
z-index: 110; z-index: 110;
background-color: #222; //background-color: #222;
border-radius: 50px; border-radius: 50px;
} }
.jewel { .jewel {
border: 2px solid rgba(255,255,255,0.5); border: 2px solid #888;
background-color: transparent; background-color: transparent;
color: #989898; color: #989898;
width: 36px; width: 36px;
@ -228,5 +200,22 @@
.toolbar-elements > .btn-group, .toolbar-elements > .btn-group,
.toolbar-properties > .btn-group { .toolbar-properties > .btn-group {
box-shadow: 0 0 30px rgba(0,0,0,0.5); //box-shadow: 0 0 30px rgba(0,0,0,0.5);
background-color: $white;
}
.toolbar-elements {
left: 20px;
}
.toolbar-properties {
right: 30px;
}
.zoom-bar {
position: absolute;
bottom: 30px;
right: 30px;
box-shadow: 0 0 30px 1px rgba(0, 0, 0, 0.15);
border: 1px solid black;
} }

View File

@ -33,10 +33,7 @@
@include translate3d(0, 0, 0); @include translate3d(0, 0, 0);
// @include backface-visibility(hidden); // @include backface-visibility(hidden);
-webkit-perspective: 1000; perspective: 1000;
-moz-perspective: 1000;
-ms-perspective: 1000;
perspective: 1000;
.panel-toggles { .panel-toggles {
@include transition( all 0.15s ease-in-out); @include transition( all 0.15s ease-in-out);
@ -99,10 +96,7 @@
display: table-cell; display: table-cell;
vertical-align: middle; vertical-align: middle;
// @include backface-visibility(hidden); // @include backface-visibility(hidden);
-webkit-perspective: 1000; perspective: 1000;
-moz-perspective: 1000;
-ms-perspective: 1000;
perspective: 1000;
z-index: 1000; z-index: 1000;

View File

@ -1,6 +1,8 @@
@import "vars"; @import "vars";
@import "mixins"; @import "mixins";
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;900&display=swap');
body { body {
background-color: $light; background-color: $light;
color: $medium; color: $medium;
@ -25,7 +27,7 @@ hr {
h1, h2, h3, h4, h5, h6, .h1, .h2, .h3, .h4, .h5, .h6 { h1, h2, h3, h4, h5, h6, .h1, .h2, .h3, .h4, .h5, .h6 {
color: inherit; color: inherit;
font-family: inherit; font-family: inherit;
font-weight: 500; font-weight: 900;
line-height: 1.3; line-height: 1.3;
margin-top: 0px; margin-top: 0px;
margin-bottom: 1em; margin-bottom: 1em;
@ -46,8 +48,7 @@ strong {font-weight: 500; }
small {font-size: 75%; } small {font-size: 75%; }
a { a {
text-decoration: none; color: black;
color: $medium;
} }
dl { dl {

View File

@ -9,13 +9,6 @@ $green: #2ecc71;
$red: #ff5955; $red: #ff5955;
$yellow: #f1c40f; $yellow: #f1c40f;
$light: #f5f5f5;
$lightish: #eee;
$facebook: #3e5b97;
$twitter: #2aa7de;
$color-1 : #4a2f7e; // purple $color-1 : #4a2f7e; // purple
$color-2 : #9b59b6; // lilac $color-2 : #9b59b6; // lilac
$color-3 : #3498db; // blue $color-3 : #3498db; // blue
@ -32,15 +25,18 @@ $black: #111; // black
$darker: #292929; $darker: #292929;
$dark: #222; // dark $dark: #222; // dark
$medium: #888; // medium $medium: #888; // medium
$light: #f5f5f5;
$lightish: #eee; // fixme
$lighter: #989898; $lighter: #989898;
$white: #ffffff;
$sidebar-width: 280px; $sidebar-width: 280px;
$main-font: Avenir W01; $main-font: Inter;
$sec-font: Avenir W01; $sec-font: Inter;
$font-size: 18px; $font-size: 20px;
$line-height: 24px; $line-height: 1.5em;
$gutter-a: 10px; $gutter-a: 10px;
$gutter-b: 20px; $gutter-b: 20px;
@ -48,4 +44,4 @@ $gutter-c: 40px;
$gutter-d: 60px; $gutter-d: 60px;
$gutter-e: 80px; $gutter-e: 80px;
$folder-gutter: 20px; $folder-gutter: 20px;

View File

@ -1,62 +1,34 @@
{% extends 'layouts/outer.html' %} {% extends 'layouts/outer.html' %}
{% block title %}[[ __("welcome") ]]{% endblock %} {% block title %}Spacedeck{% endblock %}
{% block content %} {% block content %}
<div id="landing"> <div id="landing">
<div class="landing-keyvisual-wrapper"> <section>
<div class="landing-box"> <h1>Work Together, Visually.</h1>
<h2>[[__("landing_title")]]</h2> <p>
Whenever you need to lay out pictures, text notes, video and audio clips on a blank canvas,
<p class="lead"> Spacedeck can help you.
<a href="/signup" class="btn btn-primary btn-block btn-xl">[[__("signup")]]</a> </p>
</p> <p>
Spacedeck is a browser based application. It is the right tool for you if you want to quickly put together a collage of your idea or concept, either for yourself or to share it with teammembers, clients or students. Changes are updated in realtime.
<p class="lead"> </p>
<a href="/login" class="btn btn-primary btn-block btn-xl">[[__("login")]]</a> <p>
</p> Spacedeck is not meant for creating polished designs, but it is a good fit for:
</p>
<p class="lead"> <ul>
[[__("landing_claim")]] <li>Moodboards</li>
</p> <li>Collages</li>
<li>Teaching (Virtual Blackboards)</li>
<p class="lead"> <li>Shared Whiteboards</li>
[[__("landing_example")]] <li>Design Thinking</li>
</p> </ul>
<img src="/images/sd6-screenshot.png" alt="Screenshot of Spacedeck 6.0">
<ul> <p>
<li class="lead"> The hosted version of Spacedeck 6.0 is currently in beta and invite only. You can also self-host and <a href="https://github.com/spacedeck/spacedeck-open">participate in the open source development</a>.
[[__("landing_features_1") | safe ]] </p>
</li> </section>
<li class="lead">
[[__("landing_features_2") | safe ]]
</li>
<li class="lead">
[[__("landing_features_3") | safe ]]
</li>
<li class="lead">
[[__("landing_features_4") | safe ]]
</li>
<li class="lead">
[[__("landing_features_5") | safe ]]
</li>
<li class="lead">
[[__("landing_features_6") | safe ]]
</li>
<li class="lead">
[[__("landing_features_7") | safe ]]
</li>
</ul>
</div>
</div>
</div> </div>
{% endblock %} {% endblock %}

View File

@ -9,39 +9,25 @@
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" /> <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<link href="[[ '/images/favicon.png' | cdn ]]" rel="icon" type="image/x-icon" /> <link href="[[ '/images/favicon.png' | cdn ]]" rel="icon" type="image/x-icon" />
<link href='https://fonts.googleapis.com/css?family=Open+Sans:300italic,400italic,600italic,700italic,800italic,400,700,600,800,300|Montserrat:400,700|EB+Garamond|Vollkorn|Lato|Roboto|Source+Code+Pro|Ubuntu|Raleway|Playfair+Display|Crimson+Text' rel='stylesheet' type='text/css'>
<link type="text/css" rel="stylesheet" href="https://fast.fonts.net/cssapi/ee1a3484-4d98-4f9f-9f55-020a7b37f3c5.css"/>
<link rel="stylesheet" href="[[ '/stylesheets/style.css' | cdn ]]"> <link rel="stylesheet" href="[[ '/stylesheets/style.css' | cdn ]]">
<script> var csrf_token = '[[ csrf_token ]]'; </script> <script> var csrf_token = '[[ csrf_token ]]'; </script>
<script src="[[ '/javascripts/jquery-2.1.4.min.js' | cdn ]]"></script> <!--script src="[[ '/javascripts/jquery-2.1.4.min.js' | cdn ]]"></script-->
</head> </head>
<body> <body>
<!--[if lt IE 10]>
<p class="browsehappy">You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com/">upgrade your browser</a> to improve your experience.</p>
<![endif]-->
<header id="landing-header" class="header"> <header id="landing-header" class="header">
<div class="header-left"> <div class="header-left">
<a class="btn btn-transparent btn-nude" href="[[config.endpoint]]/"><img src="[[ '/images/sd5-logo.svg' | cdn ]]" width="190"></a> <a class="btn btn-transparent btn-nude" href="[[config.endpoint]]/"><img src="[[ '/images/sd6-logo-black.svg' | cdn ]]" width="190"></a>
</div> </div>
<div class="header-right pull-right"> <div class="header-right pull-right">
{% if !user %} {% if !user %}
<span class="btn-group dark round">
{% if (locale != "de") %}<a href="/t/de" rel="alternate" hreflang="de" class="btn btn-transparent btn-md">Deutsch</a>{% endif %}
{% if (locale != "en") %}<a href="/t/en" rel="alternate" hreflang="en" class="btn btn-transparent btn-md">English</a>{% endif %}
{% if (locale != "fr") %}<a href="/t/fr" rel="alternate" hreflang="fr" class="btn btn-transparent btn-md">Français</a>{% endif %}
</span>
<a class="btn btn-md btn-dark btn-round" href="/login">[[__("login")]]</a> <a class="btn btn-md btn-dark btn-round" href="/login">[[__("login")]]</a>
<a class="btn btn-md btn-blue btn-round" href="/signup">[[__("signup")]]</a> <a class="btn btn-md btn-dark btn-round" href="/signup">[[__("signup")]]</a>
{% else %} {% else %}
<a class="btn btn-md btn-blue btn-round" href="/spaces">[[__("spaces")]]</a> <a class="btn btn-md btn-dark btn-round" href="/spaces">[[__("spaces")]]</a>
<a class="btn btn-md btn-dark btn-round" href="/logout">[[__("logout")]]</a> <a class="btn btn-md btn-dark btn-round" href="/logout">[[__("logout")]]</a>
{% endif %} {% endif %}
</div> </div>
@ -52,8 +38,11 @@
<div class="footer"> <div class="footer">
<p> <p>
<div class="col-xs-6"> <div class="col-xs-6">
<a href="/contact">[[ __("contact") ]]</a> &copy; 2020 <a href="https://mntre.com">MNT Research GmbH</a>, Fehlerstr. 8, 12161 Berlin, Germany<br>
<span style="color:#888">&copy; 20112017 The Spacedeck Open Developers</span> &copy; 20112019 Spacedeck GmbH (in liquidation)<br>
Source Code: <a href="https://github.com/mntmn/spacedeck-open">https://github.com/mntmn/spacedeck-open</a>
<br>
Font: <a href="https://rsms.me/inter/">Inter by rsms</a>
</div> </div>
</p> </p>
</div> </div>

Some files were not shown because too many files have changed in this diff Show More