Merge branch 'master' of https://github.com/spacedeck/spacedeck-open
This commit is contained in:
commit
fb8d3ac654
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,5 +1,6 @@
|
||||
node_modules
|
||||
public/stylesheets/*
|
||||
javascripts/maps
|
||||
javascripts/spacedeck.js
|
||||
*.swp
|
||||
*~
|
||||
|
||||
|
48
Gulpfile.js
48
Gulpfile.js
@ -1,42 +1,6 @@
|
||||
var gulp = require('gulp');
|
||||
var sass = require('gulp-sass');
|
||||
var 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');
|
||||
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', () => {
|
||||
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')
|
||||
@ -47,15 +11,3 @@ gulp.task('styles', function() {
|
||||
.pipe(concat('style.css'));
|
||||
});
|
||||
|
||||
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'));
|
||||
});
|
||||
});
|
||||
|
20
README.md
20
README.md
@ -1,13 +1,15 @@
|
||||
# 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).
|
||||
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).
|
||||
|
||||
As we plan to retire the subscription based service at spacedeck.com in May 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.
|
||||
|
||||
Easy to use desktop releases with binaries for Linux, Mac and Windows will be published here soon. In the meantime, you have to install Node.JS.
|
||||
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.
|
||||
|
||||
We appreciate filed issues, pull requests and general discussion.
|
||||
|
||||
**Windows users:** Try the one-click release at https://github.com/spacedeck/spacedeck-open/releases/tag/v0.9
|
||||
|
||||
Desktop releases for Linux and Mac will be published here soon. In the meantime, you have to install Node.JS to run Spacedeck.
|
||||
|
||||
# Features
|
||||
|
||||
- Create virtual whiteboards called *Spaces* with virtually unlimited size
|
||||
@ -41,10 +43,6 @@ To install all node dependencies, run (do this once):
|
||||
|
||||
npm install
|
||||
|
||||
To rebuild the frontend CSS styles (do this at least once, too):
|
||||
|
||||
gulp styles
|
||||
|
||||
# Configuration
|
||||
|
||||
See [config/default.json](config/default.json)
|
||||
@ -72,6 +70,12 @@ For advanced media conversion:
|
||||
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
|
||||
|
||||
# License
|
||||
|
||||
The Spacedeck logo and brand assets are registered trademarks of Spacedeck GmbH. All rights reserved.
|
||||
|
17
electron-windows.md
Normal file
17
electron-windows.md
Normal 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 ..\..
|
||||
````
|
||||
|
@ -37,7 +37,7 @@ const convertableAudioTypes = [
|
||||
"application/ogg",
|
||||
"audio/amr",
|
||||
"audio/3ga",
|
||||
"audio/wav",
|
||||
"audio/wave",
|
||||
"audio/3gpp",
|
||||
"audio/x-wav",
|
||||
"audio/aiff",
|
||||
@ -263,6 +263,8 @@ var resizeAndUploadImage = function(a, mimeType, size, fileName, fileNameOrg, im
|
||||
a.h = Math.round(size.height*factor);
|
||||
|
||||
a.updated_at = new Date();
|
||||
db.packArtifact(a);
|
||||
|
||||
a.save().then(function() {
|
||||
fs.unlink(originalFilePath, function (err) {
|
||||
if (err){
|
||||
@ -328,6 +330,8 @@ module.exports = {
|
||||
a.h = Math.round(size.height*factor);
|
||||
|
||||
a.updated_at = new Date();
|
||||
db.packArtifact(a);
|
||||
|
||||
a.save().then(function() {
|
||||
fs.unlink(localFilePath, function (err) {
|
||||
if (err){
|
||||
|
@ -46,7 +46,7 @@
|
||||
"specify": "Bitte spezifiziere",
|
||||
"confirm": "Bitte bestätige",
|
||||
"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_domain_blocked": "Diese Domain ist gesperrt.",
|
||||
"error_user_email_already_used": "Diese Email-Adresse ist bereits registriert.",
|
||||
|
@ -44,8 +44,7 @@
|
||||
"sure": "Are you sure?",
|
||||
"specify": "Please Specify",
|
||||
"confirm": "Please Confirm",
|
||||
"signup_google": "Sign In with Google",
|
||||
"error_unknown_email": "This email/password combination is unknown. Try login with Google.",
|
||||
"error_unknown_email": "This email/password combination is unknown.",
|
||||
"error_password_confirmation": "The entered passwords don't match.",
|
||||
"error_domain_blocked": "Your domain is blocked.",
|
||||
"error_user_email_already_used": "This email address is already in use.",
|
||||
|
@ -46,7 +46,7 @@
|
||||
"specify": "Veuillez préciser:",
|
||||
"confirm": "Veuillez confirmer",
|
||||
"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_domain_blocked": "Ce domaine a été désactivé.",
|
||||
"error_user_email_already_used": "Cette adresse email est déjà enregistré.",
|
||||
|
12
models/db.js
12
models/db.js
@ -320,26 +320,26 @@ module.exports = {
|
||||
},
|
||||
|
||||
unpackArtifact: (a) => {
|
||||
if (a.tags) {
|
||||
if (a.tags && (typeof a.tags)=="string") {
|
||||
a.tags = JSON.parse(a.tags);
|
||||
}
|
||||
if (a.control_points) {
|
||||
if (a.control_points && (typeof a.control_points)=="string") {
|
||||
a.control_points = JSON.parse(a.control_points);
|
||||
}
|
||||
if (a.payload_alternatives) {
|
||||
if (a.payload_alternatives && (typeof a.payload_alternatives)=="string") {
|
||||
a.payload_alternatives = JSON.parse(a.payload_alternatives);
|
||||
}
|
||||
return a;
|
||||
},
|
||||
|
||||
packArtifact: (a) => {
|
||||
if (a.tags) {
|
||||
if (a.tags && (typeof a.tags)!="string") {
|
||||
a.tags = JSON.stringify(a.tags);
|
||||
}
|
||||
if (a.control_points) {
|
||||
if (a.control_points && (typeof a.control_points)!="string") {
|
||||
a.control_points = JSON.stringify(a.control_points);
|
||||
}
|
||||
if (a.payload_alternatives) {
|
||||
if (a.payload_alternatives && (typeof a.payload_alternatives)!="string") {
|
||||
a.payload_alternatives = JSON.stringify(a.payload_alternatives);
|
||||
}
|
||||
return a;
|
||||
|
@ -26,9 +26,9 @@
|
||||
"helmet": "^3.5.0",
|
||||
"i18n-2": "0.6.3",
|
||||
"log-timestamp": "latest",
|
||||
"morgan": "1.8.1",
|
||||
"mock-aws-s3": "^2.6.0",
|
||||
"moment": "^2.19.3",
|
||||
"morgan": "1.8.1",
|
||||
"node-phantom-simple": "2.2.4",
|
||||
"phantomjs-prebuilt": "2.1.14",
|
||||
"read-chunk": "^2.1.0",
|
||||
|
@ -41,7 +41,7 @@ var SpacedeckBoardArtifacts = {
|
||||
if ("medium_for_object" in this) {
|
||||
var medium = this.medium_for_object[a._id];
|
||||
if (medium && a._id != this.editing_artifact_id) {
|
||||
medium.value(a.description);
|
||||
medium.value(a.description.toString());
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -88,10 +88,11 @@ var SpacedeckBoardArtifacts = {
|
||||
},
|
||||
|
||||
artifact_is_text_blank: function(a) {
|
||||
if(a.description){
|
||||
var filtered = a.description.replace(/<[^>]+>/g,"").replace(/\s/g,"");
|
||||
if (a.description) {
|
||||
desc = a.description.toString();
|
||||
var filtered = desc.replace(/<[^>]+>/g,"").replace(/\s/g,"");
|
||||
return (filtered.length<1);
|
||||
}else{
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
16525
public/stylesheets/style.css
Normal file
16525
public/stylesheets/style.css
Normal file
File diff suppressed because it is too large
Load Diff
@ -25,7 +25,10 @@ router.post('/', function(req, res) {
|
||||
res.sendStatus(404);
|
||||
})
|
||||
.then(user => {
|
||||
if (bcrypt.compareSync(password, user.password_hash)) {
|
||||
if (!user) {
|
||||
res.sendStatus(404);
|
||||
}
|
||||
else if (bcrypt.compareSync(password, user.password_hash)) {
|
||||
crypto.randomBytes(48, function(ex, buf) {
|
||||
var token = buf.toString('hex');
|
||||
|
||||
|
@ -53,14 +53,7 @@ router.get('/', (req, res) => {
|
||||
space_id: req.space._id
|
||||
}}).then(artifacts => {
|
||||
async.map(artifacts, (a, cb) => {
|
||||
//a = a.toObject(); TODO
|
||||
|
||||
if (a.control_points) {
|
||||
a.control_points = JSON.parse(a.control_points);
|
||||
}
|
||||
if (a.payload_alternatives) {
|
||||
a.payload_alternatives = JSON.parse(a.payload_alternatives);
|
||||
}
|
||||
db.unpackArtifact(a);
|
||||
|
||||
if (a.user_id) {
|
||||
// FIXME JOIN
|
||||
@ -131,7 +124,8 @@ router.post('/:artifact_id/payload', function(req, res, next) {
|
||||
var stream = req.pipe(writeStream);
|
||||
|
||||
var progress_callback = function(progress_msg) {
|
||||
a.description = progress_msg;
|
||||
a.description = progress_msg.toString();
|
||||
db.packArtifact(a);
|
||||
a.save();
|
||||
redis.sendMessage("update", a, JSON.stringify(a), req.channelId);
|
||||
};
|
||||
|
@ -3,6 +3,7 @@
|
||||
var config = require('config');
|
||||
const db = require('../../models/db');
|
||||
const uuidv4 = require('uuid/v4');
|
||||
const os = require('os');
|
||||
|
||||
var mailer = require('../../helpers/mailer');
|
||||
var uploader = require('../../helpers/uploader');
|
||||
@ -222,8 +223,8 @@ router.post('/:user_id/avatar', (req, res, next) => {
|
||||
const user = req.user;
|
||||
const filename = "u"+req.user._id+"_"+(new Date().getTime())+".jpeg"
|
||||
|
||||
const localFilePath = "/tmp/"+filename;
|
||||
const localResizedFilePath = "/tmp/resized_"+filename;
|
||||
const localFilePath = os.tmpdir()+"/"+filename;
|
||||
const localResizedFilePath = os.tmpdir()+"/resized_"+filename;
|
||||
const writeStream = fs.createWriteStream(localFilePath);
|
||||
const stream = req.pipe(writeStream);
|
||||
|
||||
|
@ -1,7 +1,6 @@
|
||||
"use strict";
|
||||
|
||||
const config = require('config');
|
||||
require('../models/db');
|
||||
|
||||
const redis = require('../helpers/redis');
|
||||
const express = require('express');
|
||||
@ -10,6 +9,11 @@ const router = express.Router();
|
||||
const mailer = require('../helpers/mailer');
|
||||
const _ = require('underscore');
|
||||
|
||||
const db = require('../models/db');
|
||||
const Sequelize = require('sequelize');
|
||||
const Op = Sequelize.Op;
|
||||
const uuidv4 = require('uuid/v4');
|
||||
|
||||
router.get('/', (req, res) => {
|
||||
res.render('index', { title: 'Spaces' });
|
||||
});
|
||||
@ -120,79 +124,30 @@ router.get('/t/:id', (req, res) => {
|
||||
});
|
||||
|
||||
router.get('/s/:token', (req, res) => {
|
||||
redis.rateLimit(req.real_ip, "token", function(ok) {
|
||||
if (ok) {
|
||||
var token = req.params.token;
|
||||
if (token.split("-").length > 0) {
|
||||
token = token.split("-")[0];
|
||||
}
|
||||
|
||||
Space.findOne({"edit_hash": token}).exec(function (err, space) {
|
||||
if (err) {
|
||||
res.status(404).render('not_found', { title: 'Page Not Found.' });
|
||||
} else {
|
||||
db.Space.findOne({where: {"edit_hash": token}}).then(function (space) {
|
||||
if (space) {
|
||||
if(req.accepts('text/html')){
|
||||
if (req.accepts('text/html')){
|
||||
res.redirect("/spaces/"+space._id + "?spaceAuth=" + token);
|
||||
}else{
|
||||
} else {
|
||||
res.status(200).json(space);
|
||||
}
|
||||
} else {
|
||||
if(req.accepts('text/html')){
|
||||
if (req.accepts('text/html')) {
|
||||
res.status(404).render('not_found', { title: 'Page Not Found.' });
|
||||
} else {
|
||||
res.status(404).json({});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
res.status(429).json({"error": "Too Many Requests"});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
router.get('/spaces/:id', (req, res) => {
|
||||
if (req.headers['user-agent']) {
|
||||
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('/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;
|
||||
|
Loading…
Reference in New Issue
Block a user