9 Commits

Author SHA1 Message Date
Lukas F. Hartmann
4012ee0c1c remove old models; update README 2018-04-12 18:32:57 +02:00
Lukas F. Hartmann
012a76ee1f fix memberships, zones 2018-04-12 18:19:05 +02:00
Lukas F. Hartmann
86bd276d21 fix importer es6 syntax incompat. with electron 2018-04-12 17:46:40 +02:00
Lukas F. Hartmann
76f85aa538 first version of import GUI 2018-04-12 17:41:22 +02:00
Lukas F. Hartmann
08b81d5ff4 port most backend functionality, further cleanups, basic electron support 2018-04-12 16:38:48 +02:00
Lukas F. Hartmann
8dc48a84ba further porting, cleanups, artifact upload/conversion and space screenshots 2018-04-11 21:14:00 +02:00
Lukas F. Hartmann
c549fcf9ec remove surplus docker-compose.yml 2018-04-11 20:06:11 +02:00
Lukas F. Hartmann
9ff1c39e89 adjust readme 2018-04-11 20:04:36 +02:00
Lukas F. Hartmann
960a4d6866 WIP first partially working version without mongodb, using sqlite/sequelize 2018-04-11 19:59:18 +02:00
125 changed files with 19246 additions and 19597 deletions

View File

@@ -11,4 +11,4 @@ coverage
.grunt
.lock-wscript
build/Release
node_modules
node_modules

5
.gitignore vendored
View File

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

View File

@@ -1,38 +1,29 @@
FROM node:10-alpine3.11
FROM spacedeck/docker-baseimage:latest
ENV NODE_ENV production
WORKDIR /app
RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app
# build audiowaveform from source
RUN apk add git make cmake gcc g++ libmad-dev libid3tag-dev libsndfile-dev gd-dev boost-dev libgd libpng-dev zlib-dev
RUN apk add zlib-static libpng-static boost-static
RUN apk add autoconf automake libtool gettext
RUN wget https://github.com/xiph/flac/archive/1.3.3.tar.gz
RUN tar xzf 1.3.3.tar.gz
RUN cd flac-1.3.3/ && ./autogen.sh
RUN cd flac-1.3.3/ && ./configure --enable-shared=no
RUN cd flac-1.3.3/ && make
RUN cd flac-1.3.3/ && make install
RUN git clone https://github.com/bbc/audiowaveform.git
RUN mkdir audiowaveform/build/
RUN cd audiowaveform/build/ && cmake -D ENABLE_TESTS=0 -D BUILD_STATIC=1 ..
RUN cd audiowaveform/build/ && make
RUN cd audiowaveform/build/ && make install
# install other requirements
RUN apk add graphicsmagick ffmpeg ffmpeg-dev ghostscript
# install node package
COPY package*.json ./
COPY package.json /usr/src/app/
RUN npm install
COPY . .
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
# start app
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
CMD ["node", "spacedeck.js"]

View File

@@ -1,13 +1,61 @@
const gulp = require('gulp')
const sass = require('gulp-sass')
const concat = require('gulp-concat')
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');
gulp.task('styles', function(done) {
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')
.pipe(sass({
errLogToConsole: true
}))
.pipe(gulp.dest('./public/stylesheets/'))
.pipe(concat('style.css'))
done()
})
.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'));
});
});

View File

@@ -1,12 +1,10 @@
# Spacedeck Open
![Spacedeck 6.0 Screenshot](/public/images/sd6-screenshot.png)
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.
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.
Data migration features will be added soon.
We appreciate filed issues, pull requests and general discussion.
@@ -17,33 +15,39 @@ We appreciate filed issues, pull requests and general discussion.
- Write and format text with full control over fonts, colors and style
- Draw, annotate and highlight with included graphical shapes
- Turn your Space into a zooming presentation
- Collaborate in realtime with teammates, students or friends
- Collaborate and chat in realtime with teammates, students or friends
- Share Spaces on the web or via email
- 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
- Export your work as printable PDF or ZIP
# Requirements, Installation
Spacedeck requires:
Spacedeck uses the following major building blocks:
- Node.js 10.x: Web Server / API. Download: https://nodejs.org
- Graphicsmagick. On non-Linux, Download: http://www.graphicsmagick.org/ On Linux, install via package manager.
- Optionally ffmpeg, audiowaveform and ghostscript. See "Optional Dependencies" below.
- Node.js 9.x: Web Server / API
- Vue.js: Frontend UI Framework (included)
- SQLite (included)
To run Spacedeck, you only need Node.JS 10.x.
It also has some optional binary dependencies for advanced media conversion:
To install all node dependencies, run (do this once):
- ffmpeg and ffprobe (for video/audio conversion)
- audiowaveform (for audio waveform rendering) (https://github.com/bbcrd/audiowaveform)
- ghostscript (gs, for PDF import)
By default, media files are uploaded to the ```storage``` folder.
To use Spacedeck, you only need Node.JS 9.x.
Then, to install all node dependencies, run
npm install
To rebuild the frontend CSS styles (you need to do this at least once):
gulp styles
# Configuration
See [config/default.json](config/default.json). Set `storage_local_path` for a local sqlite database or `storage_region`, `storage_bucket`, `storage_cdn` and `storage_endpoint` for AWS S3. `mail_provider` may be one of `console` or `smtp`. Also, omit a trailing `/` for the `endpoint`.
See [config/default.json](config/default.json)
# Run (web server)
@@ -51,34 +55,9 @@ See [config/default.json](config/default.json). Set `storage_local_path` for a l
Then open http://localhost:9666 in a web browser.
# Optional Dependencies
# Run (desktop app with integrated web server)
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.
# Run with Docker
- configure `config/default.json`
- configure `volumes` section inside `docker-compose.yml`
- point to `database.sqlite` on the host system
- `touch database.sqlite` if it not exists
- point to `storage/` on the host system
- `mkdir storage/` if it not exists
- start the container with `sudo docker-compose up -f docker-compose.yml -d --build`
# Hacking
To rebuild the frontend CSS styles:
gulp styles
electron .
# License

33
app.js Normal file
View File

@@ -0,0 +1,33 @@
const spacedeck = require('./spacedeck')
const electron = require('electron')
const electronApp = electron.app
const BrowserWindow = electron.BrowserWindow
let mainWindow
function createWindow () {
mainWindow = new BrowserWindow({width: 1200, height: 700})
mainWindow.loadURL("http://localhost:9666")
mainWindow.on('closed', function () {
mainWindow = null
})
}
electronApp.on('ready', createWindow)
// Quit when all windows are closed.
electronApp.on('window-all-closed', function () {
// 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') {
electronApp.quit()
}
})
electronApp.on('activate', function () {
// On OS X it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (mainWindow === null) {
createWindow()
}
})

5
bin/www Executable file
View File

@@ -0,0 +1,5 @@
#!/usr/bin/env node
var app = require('../app');
var http = require('http');
var server = http.createServer(app);

View File

@@ -1,30 +1,22 @@
{
"team_name": "My Open Spacedeck",
"contact_email": "support@example.org",
"host": "::",
"port": 9666,
//"endpoint": "http://localhost:9000",
"endpoint": "http://localhost:9666",
"invite_code": "top-sekrit",
"storage_local_path": "./storage",
"storage_local_db": "./database.sqlite",
"storage_region": "eu-central-1",
"storage_endpoint": "http://localhost:4572",
//"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",
"mongodb_host": "localhost",
"redis_mock": true,
"mongodb_host": "localhost",
"redis_host": "localhost",
"phantom_api_secret": "very_secret_phantom_password",
"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"
"google_access" : "",
"google_secret" : "",
"admin_pass": "very_secret_admin_password",
"phantom_api_secret": "very_secret_phantom_password"
}

View File

@@ -1,12 +0,0 @@
version: "2.0"
services:
spacedeck:
build: .
container_name: spacedeck
ports:
- "9666:9666"
volumes:
- /absolute/path/to/storage:/app/storage
- /absolute/path/to/database.sqlite:/app/database.sqlite

View File

@@ -1,6 +0,0 @@
To add fonts to Spacedeck Open, follow these steps:
1. Find the googleapis link for the font and add it to [./styles/type.scss](https://github.com/spacedeck/spacedeck-open/blob/docs/styles/type.scss#L4) after the `Inter` font that is already there. Here is a good reference to using [Google Font API](https://www.webfx.com/blog/web-design/google-font-api-guide/).
2. Add the name of the font to the file [./public/javascripts/spacedeck_sections.js](https://github.com/spacedeck/spacedeck-open/blob/docs/public/javascripts/spacedeck_sections.js#L150) in the `fonts` section found around line 150. The order of the list here is the order in which fonts will be displayed in the user interface.
3. From the root of your install, do `gulp styles` to recompile the SCSS.
4. Restart your server.

View File

@@ -1,26 +0,0 @@
## Adding a new language to Spacedeck Open
To add a new language to Spacedeck Open, follow these steps:
*The steps are illustrated with Spanish (locale 'es') as the new language*
- Include the new locale ('es') in the locale list (./spacedeck.js):
```
locales: ["en",..., "es"],
```
- Create the new translation file (/locales/**es.js**, a copy of /locales/en.js) and translate the entries.
- Include the javascript for the new translation at the end of /views/spacedeck.ejs:
```
...
window.locales.es = {};
...
window.locales.es.translation = <%- include "./../locales/es.js" %>;
</script>
```
- Include a radio button for users to select the new language (/views/partials/account.html)
```
<label class="radio" v-bind:class="{checked: user.prefs_language=='es'}" v-on:click="save_user_language('es')">
<input type="radio" id="user-preferences_language" name="language" value="es"><span>Español</span>
</label>
```

View File

@@ -1,51 +0,0 @@
# Apache as reverse proxy
Once spacedeck is running you can use Apache as a reverse proxy. For a general overview of how this works or how to configure specifics the [Apache Reverse Proxy Guide](https://httpd.apache.org/docs/2.4/howto/reverse_proxy.html) provides a good reference.
If you have the required modules and ssl certificates installed skip to the site config.
# Required modules
Install `mod_rewrite`, `mod_proxy` and `mod_proxy_wstunnel`
```
sudo a2enmod proxy rewrite proxy_wstunnel
```
# SSL certificates
Set up `certbot` frmo [letsencrypt](https://letsencrypt.org/) (if required) and get some certs…
```
sudo certbot --apache certonly -n -d space.example.net
```
# Site config
This config should work with Apache 2.4 and assumes spacedeck is running on localhost using port 9666, that `mod_rewrite`, `mod_proxy` and `mod_proxy_wstunnel` are active, and that ssl certificates have been installed with `certbot`.
```
<VirtualHost *:443>
ServerName space.example.net
ServerAdmin webmaster@space.example.net
ErrorLog /var/log/apache2/spacedeck-error.log
CustomLog /var/log/apache2/spacedeck-access.log combined
# ssl options
Include /etc/letsencrypt/options-ssl-apache.conf
SSLCertificateFile /etc/letsencrypt/live/space.example.net/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/space.example.net/privkey.pem
# proxy spacedeck
RewriteEngine On
RewriteCond %{HTTP:Upgrade} =websocket [NC]
RewriteRule /(.*) ws://localhost:9666/$1 [P,L]
RewriteCond %{HTTP:Upgrade} !=websocket [NC]
RewriteRule /(.*) http://localhost:9666/$1 [P,L]
ProxyPassReverse / http://localhost:9666/
</VirtualHost>
```

View File

@@ -37,7 +37,7 @@ const convertableAudioTypes = [
"application/ogg",
"audio/amr",
"audio/3ga",
"audio/wave",
"audio/wav",
"audio/3gpp",
"audio/x-wav",
"audio/aiff",
@@ -82,7 +82,7 @@ function createWaveform(fileName, localFilePath, callback){
"-i", localFilePath, "-o", filePathImage
],
{}, function(error, stdout, stderr) {
if (!error) {
if(!error) {
callback(null, filePathImage);
} else {
console.log("error:", stdout, stderr);
@@ -92,14 +92,14 @@ function createWaveform(fileName, localFilePath, callback){
});
}
function convertVideo(fileName, filePath, codec, callback, progressCallback) {
function convertVideo(fileName, filePath, codec, callback, progress_callback) {
var ext = path.extname(fileName);
var presetMime = mime.lookup(fileName);
var newExt = codec == "mp4" ? "mp4" : "ogv";
var convertedPath = filePath + "." + newExt;
console.log("convertVideo", filePath, "to", convertedPath);
console.log("converting", filePath, "to", convertedPath, "progress_cb:",progress_callback);
var convertArgs = (codec == "mp4") ? [
"-i", filePath,
@@ -134,14 +134,14 @@ function convertVideo(fileName, filePath, codec, callback, progressCallback) {
ff.stderr.on('data', function (data) {
console.log('[ffmpeg-video] stderr: ' + data);
if (progressCallback) {
progressCallback(data);
if (progress_callback) {
progress_callback(data);
}
});
ff.on('close', function (code) {
console.log('[ffmpeg-video] child process exited with code ' + code);
if (!code) {
if (!code) {
console.log("converted", filePath, "to", convertedPath);
callback(null, convertedPath);
} else {
@@ -190,7 +190,7 @@ function createThumbnailForVideo(fileName, filePath, callback) {
function getMime(fileName, filePath, callback) {
var ext = path.extname(fileName);
var presetMime = mime.lookup(fileName);
if (presetMime) {
callback(null, presetMime);
} else {
@@ -229,6 +229,7 @@ function resizeAndUpload(a, size, max, fileName, localFilePath, callback) {
}
}
var resizeAndUploadImage = function(a, mimeType, size, fileName, fileNameOrg, imageFilePath, originalFilePath, payloadCallback) {
async.parallel({
small: function(callback){
@@ -262,8 +263,6 @@ 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){
@@ -279,18 +278,18 @@ var resizeAndUploadImage = function(a, mimeType, size, fileName, fileNameOrg, im
};
module.exports = {
convert: function(a, fileName, localFilePath, payloadCallback, progressCallback) {
getMime(fileName, localFilePath, function(err, mimeType) {
convert: function(a, fileName, localFilePath, payloadCallback, progress_callback) {
getMime(fileName, localFilePath, function(err, mimeType){
console.log("[convert] fn: "+fileName+" local: "+localFilePath+" mimeType:", mimeType);
if (!err) {
if (convertableImageTypes.indexOf(mimeType) > -1) {
gm(localFilePath).size(function (err, size) {
console.log("[convert] gm:", err, size);
if (!err) {
if (mimeType == "application/pdf") {
if(mimeType == "application/pdf") {
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) {
if(error === null) {
@@ -304,14 +303,14 @@ module.exports = {
}
});
} else if (mimeType == "image/gif") {
} else if(mimeType == "image/gif") {
//gifs are buggy after convertion, so we should not convert them
var s3Key = "s"+ a.space_id.toString() + "/a" + a._id.toString() + "/" + fileName;
uploader.uploadFile(s3Key, "image/gif", localFilePath, function(err, url) {
if (err) payloadCallback(err);
else {
if(err)callback(err);
else{
console.log(localFilePath);
var stats = fs.statSync(localFilePath);
@@ -329,11 +328,9 @@ 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) {
if (err){
console.error(err);
payloadCallback(err, null);
} else {
@@ -349,15 +346,15 @@ module.exports = {
resizeAndUploadImage(a, mimeType, size, fileName, fileName, localFilePath, localFilePath, payloadCallback);
}
} else payloadCallback(err);
});
});
} else if (convertableVideoTypes.indexOf(mimeType) > -1) {
async.parallel({
thumbnail: function(callback) {
createThumbnailForVideo(fileName, localFilePath, function(err, created){
console.log("thumbnail created: ", err, created);
if (err) callback(err);
else {
if(err) callback(err);
else{
var keyName = "s" + a.space_id.toString() + "/a" + a._id.toString() + "/" + fileName + ".jpg" ;
uploader.uploadFile(keyName, "image/jpeg", created, function(err, url){
if (err) callback(err);
@@ -379,7 +376,7 @@ module.exports = {
else callback(null, url);
});
}
}, progressCallback);
}, progress_callback);
}
},
mp4: function(callback) {
@@ -395,7 +392,7 @@ module.exports = {
else callback(null, url);
});
}
}, progressCallback);
}, progress_callback);
}
},
original: function(callback){
@@ -403,7 +400,7 @@ module.exports = {
callback(null, url);
});
}
}, function(err, results) {
}, function(err, results){
console.log(err, results);
if (err) payloadCallback(err, a);
@@ -437,25 +434,29 @@ module.exports = {
db.packArtifact(a);
a.updated_at = new Date();
a.save().then(function() {
fs.unlink(localFilePath, function (err) {
if (err) {
console.error(err);
payloadCallback(err, null);
} else {
console.log('successfully deleted ' + localFilePath);
payloadCallback(null, a);
}
});
a.save(function(err) {
if (err) payloadCallback(err, null);
else {
fs.unlink(localFilePath, function (err) {
if (err){
console.error(err);
payloadCallback(err, null);
} else {
console.log('successfully deleted ' + localFilePath);
payloadCallback(null, a);
}
});
}
});
}
});
} else if (convertableAudioTypes.indexOf(mimeType) > -1) {
async.parallel({
ogg: function(callback) {
convertAudio(fileName, localFilePath, "ogg", function(err, file) {
if (err) callback(err);
if(err) callback(err);
else {
var keyName = "s" + a.space_id.toString() + "/a" + a._id.toString() + "/" + fileName + ".ogg" ;
uploader.uploadFile(keyName, "audio/ogg", file, function(err, url){
@@ -467,16 +468,20 @@ module.exports = {
},
mp3_waveform: function(callback) {
convertAudio(fileName, localFilePath, "mp3", function(err, file) {
if (err) callback(err);
if(err) callback(err);
else {
createWaveform(fileName, file, function(err, filePath) {
createWaveform(fileName, file, function(err, filePath){
var keyName = "s" + a.space_id.toString() + "/a" + a._id.toString() + "/" + fileName + "-" + (new Date().getTime()) + ".png";
uploader.uploadFile(keyName, "image/png", filePath, function(err, pngUrl) {
uploader.uploadFile(keyName, "image/png", filePath, function(err, pngUrl){
var keyName = "s" + a.space_id.toString() + "/a" + a._id.toString() + "/" + fileName + ".mp3" ;
uploader.uploadFile(keyName, "audio/mp3", file, function(err, mp3Url) {
uploader.uploadFile(keyName, "audio/mp3", file, function(err, mp3Url){
if (err) callback(err);
else callback(null, {waveform: pngUrl, mp3: mp3Url});
});
});
});
}
@@ -484,7 +489,7 @@ module.exports = {
},
original: function(callback) {
var keyName = "s" + a.space_id.toString() + "/a" + a._id.toString() + "/" + fileName;
uploader.uploadFile(keyName, mimeType, localFilePath, function(err, url) {
uploader.uploadFile(keyName, mimeType, localFilePath, function(err, url){
callback(null, url);
});
}
@@ -493,6 +498,7 @@ module.exports = {
if (err) payloadCallback(err, a);
else {
a.state = "idle";
a.mime = mimeType;
var stats = fs.statSync(localFilePath);
@@ -508,9 +514,10 @@ module.exports = {
];
a.updated_at = new Date();
db.packArtifact(a);
a.save().then(function() {
db.packArtifact(a);
a.save().then(function(){
fs.unlink(localFilePath, function (err) {
if (err){
console.error(err);
@@ -529,13 +536,13 @@ module.exports = {
console.log("mimeType not matched for conversion, storing file");
var keyName = "s" + a.space_id.toString() + "/a" + a._id.toString() + "/" + fileName;
uploader.uploadFile(keyName, mimeType, localFilePath, function(err, url) {
a.state = "idle";
a.mime = mimeType;
var stats = fs.statSync(localFilePath);
a.payload_size = stats["size"];
a.payload_uri = url;
a.updated_at = new Date();
a.save().then(function() {
fs.unlink(localFilePath, function (err) {
@@ -551,3 +558,5 @@ module.exports = {
});
}
};

View File

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

View File

@@ -1,202 +0,0 @@
<?php
/*
Plugin Name: Spacedeck
Plugin URI: https://spacedeck.com
description: Embed Spacedeck Whiteboards in Wordpress Posts
Version: 1.0
Author: MNT Research GmbH
Author URI: https://mntre.com
License: GPLv3+
*/
add_option("spacedeck_settings");
function spacedeck_apicall($method, $path, $data) {
$spacedeck_api_base_uri = get_option("spacedeck_settings")[spacedeck_api_base_uri];
$spacedeck_api_key = get_option("spacedeck_settings")[spacedeck_api_key];
$data_string = json_encode($data);
$url = $spacedeck_api_base_uri . $path;
$headers = array(
'Content-Type' => 'application/json',
'X-Spacedeck-API-Token' => $spacedeck_api_key
);
$payload = array(
'method' => $method,
'timeout' => 10,
'blocking' => true,
'headers' => $headers,
'body' => $data_string
);
// echo("<p>payload:</p><pre>");
// print_r($payload);
// echo("</pre>");
$result = wp_remote_post($url, $payload);
if (is_wp_error($result)) {
return $result;
}
$result = json_decode($result[body], true);
// echo("<p>decoded:</p><pre>");
// print_r($result);
// echo("</pre>");
return $result;
}
function spacedeck_embed_space($slug, $width = '90%', $height = '800', $parent_space_id = null) {
$spacedeck_frontend_base_uri = get_option("spacedeck_settings")[spacedeck_frontend_base_uri];
// try to find the space identified by slug
$space = spacedeck_apicall("GET", "/spaces/" . $slug, array());
if (is_wp_error($space)) {
$error = $response->get_error_message();
return("<p><b>Spacedeck: WP Error looking up Space: $error</b></p>");
} else if ($space[error] && $space[error]!="space_not_found") {
return("<p><b>Spacedeck: Error looking up Space: $space[error]</b></p>");
}
// if it doesn't exist, create it:
if ($space[error]=="space_not_found") {
$data = array(
"name" => $slug,
"edit_slug" => $slug
);
if ($parent_space_id) {
$data[parent_space_id] = $parent_space_id;
}
$space = spacedeck_apicall("POST", "/spaces", $data);
if (is_wp_error($space)) {
$error = $response->get_error_message();
return("<p><b>Spacedeck: WP Error creating Space: $error</b></p>");
} else if ($space[error]) {
return("<p><b>Spacedeck: Error creating Space: $space[error]</b></p>");
}
}
if (is_wp_error($space)) {
$error = $response->get_error_message();
return("<p><b>Spacedeck: WP Error embedding Space: $error</b></p>");
} else if (!$space || $space[error]) {
return("<p><b>Spacedeck: Error embedding Space. Is your API key set up correctly?</b></p>");
}
$space_auth = $space[edit_hash];
// return a piece of html (iframe) embedding the space
$uri = $spacedeck_frontend_base_uri . '/spaces/' . $slug . '?embedded=1&spaceAuth=' . $space_auth;
$html = "<iframe src='$uri' class='spacedeck' width='$width' height='$height' style='max-width:100%' frameborder='0' allowFullScreen='true'></iframe>";
return $html;
}
function spacedeck_shortcode($attrs) {
extract(shortcode_atts(array(
'id' => 'none',
'parent_space_id' => null,
'width' => '100%',
'height' => '800'
), $attrs));
$w = $attrs[width];
$h = $attrs[height];
if (!$w) $w = '100%';
if (!$h) $h = 800;
return spacedeck_embed_space($attrs[id],$w,$h,$attrs[parent_space_id]);
}
add_shortcode('spacedeck_space', 'spacedeck_shortcode');
add_action('admin_menu', 'spacedeck_add_admin_menu');
add_action('admin_init', 'spacedeck_settings_init');
function spacedeck_add_admin_menu() {
add_options_page('spacedeck', 'Spacedeck', 'manage_options', 'spacedeck', 'spacedeck_options_page');
}
function spacedeck_settings_init() {
register_setting('pluginPage', 'spacedeck_settings');
add_settings_section(
'spacedeck_pluginPage_section',
'Spacedeck Settings',
'spacedeck_settings_section_callback',
'pluginPage'
);
add_settings_field(
'spacedeck_text_field_0',
'API key',
'spacedeck_text_field_0_render',
'pluginPage',
'spacedeck_pluginPage_section'
);
add_settings_field(
'spacedeck_text_field_1',
'API base URL',
'spacedeck_text_field_1_render',
'pluginPage',
'spacedeck_pluginPage_section'
);
add_settings_field(
'spacedeck_text_field_2',
'Frontend base URL',
'spacedeck_text_field_2_render',
'pluginPage',
'spacedeck_pluginPage_section'
);
}
function spacedeck_text_field_0_render() {
$opts = get_option('spacedeck_settings');
?>
<input type='text' name='spacedeck_settings[spacedeck_api_key]' value='<?php echo $opts[spacedeck_api_key]; ?>'>
<?php
}
function spacedeck_text_field_1_render() {
$opts = get_option('spacedeck_settings');
?>
<input type='text' name='spacedeck_settings[spacedeck_api_base_uri]' value='<?php echo $opts[spacedeck_api_base_uri]; ?>'>
<?php
}
function spacedeck_text_field_2_render() {
$opts = get_option('spacedeck_settings');
?>
<input type='text' name='spacedeck_settings[spacedeck_frontend_base_uri]' value='<?php echo $opts[spacedeck_frontend_base_uri]; ?>'>
<?php
}
function spacedeck_settings_section_callback() {
echo '';
}
function spacedeck_options_page() {
?>
<form action='options.php' method='post'>
<?php
settings_fields('pluginPage');
do_settings_sections('pluginPage');
submit_button();
?>
</form>
<?php
}
?>

View File

@@ -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.",
"error_unknown_email": "Unbekannte Kombination von Email und Passwort. Oder versuche dich mit Google anzumelden.",
"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.",
@@ -173,7 +173,6 @@
"tool_styles": "Stil",
"tool_bullets": "Bullets",
"tool_numbers": "Zahlen",
"tool_font": "Font",
"color_fill": "Füllung",
"color_stroke": "Strich",
"color_text": "Text",
@@ -304,7 +303,6 @@
"sharing": "sharing",
"list": "Liste",
"download_space": "Space Herunterladen",
"download_as_pdf": "Space als PDF herunterladen",
"duplicate_destination_folder": "Zielordner für Duplikat",
"type": "Typ",
"promote": "Befördern",
@@ -319,6 +317,5 @@
"more": "mehr",
"follow_present": "Folgen",
"mute_present": "Entfolgen",
"follow_present_help": "Wenn jemand den Space präsentiert, folgen die anderen Mitglieder automatisch der Präsentation. Mit diesem Knopf lässt sich das an- oder ausschalten.",
"media": "Media"
"follow_present_help": "Wenn jemand den Space präsentiert, folgen die anderen Mitglieder automatisch der Präsentation. Mit diesem Knopf lässt sich das an- oder ausschalten."
}

View File

@@ -44,7 +44,8 @@
"sure": "Are you sure?",
"specify": "Please Specify",
"confirm": "Please Confirm",
"error_unknown_email": "This email/password combination is unknown.",
"signup_google": "Sign In with Google",
"error_unknown_email": "This email/password combination is unknown. Try login with Google.",
"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.",
@@ -172,7 +173,6 @@
"tool_styles": "Styles",
"tool_bullets": "Bullets",
"tool_numbers": "Numbers",
"tool_font": "Font",
"color_fill": "Fill",
"color_stroke": "Stroke",
"color_text": "Text",
@@ -309,7 +309,6 @@
"list": "Export List",
"link": "Link",
"download_space": "Download Space",
"download_as_pdf": "Download Space as PDF",
"type": "Type",
"download": "Download",
"Previous Zone": "Previous Zone",
@@ -322,6 +321,5 @@
"follow_present": "Follow",
"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.",
"export": "export",
"media": "Media"
"export": "export"
}

View File

@@ -1,327 +0,0 @@
{
"ok": "OK",
"cancel": "Cancelar",
"close": "Cerrar",
"open": "Abrir",
"folder": "Directorio",
"save": "Salvar",
"saved": "Salvado",
"created": "creado",
"duplicate": "Duplicar",
"delete": "Borrar",
"remove": "Eliminar",
"set": "ajustar",
"reset": "reiniciar",
"thanks": "Gracias",
"share": "Compartir",
"signup": "Regístrate",
"login": "Iniciar sesión",
"logout": "Cerrar sesión",
"email": "Correo Electrónico",
"password": "Contraseña",
"width": "Anchura",
"height": "Altura",
"nick": "Nombre",
"role": "Rol",
"members": "Miembros",
"actions": "Acciones",
"or": "o",
"you": "tú",
"via": "via",
"by": "por",
"zero": "Cero",
"page": "Página",
"new": "Nuevo",
"copy": "Copiar",
"home": "Inicio",
"owner": "Propietario",
"space": "Espacio",
"second": "Segundo",
"not_found": "No encontrado.",
"untitled_space": "Espacio sin título",
"untitled_folder": "Directorio sin título",
"untitled": "sin título",
"sure": "Está seguro?",
"specify": "Por favor, Especifica",
"confirm": "Por favor, Confirma",
"error_unknown_email": "Esta combinación correo electrónico/contraseña no es conocida.",
"error_password_confirmation": "La contraseña introducida no coincide.",
"error_domain_blocked": "Tu dominio está bloqueado.",
"error_user_email_already_used": "Esta dirección de correo electrónico ya se está usando.",
"support": "Soporte para Spacedeck",
"offline": "Offline. Clica para más.",
"error": "Lo siento, pero algo salió mal. Por favor, contacta con support@spacedeck.com",
"welcome": "Bienvenido",
"claim": "Tu Pizarra digital.",
"trynow": "Inténtalo ahora.",
"about": "Sobre nosotros.",
"terms": "Términos",
"contact": "Contacto",
"privacy": "Privacidad",
"business_adress": "Dirección de Negocios",
"post_adress": "Dirección postal",
"phone": "Teléfono",
"ceo": "Director gerente",
"name": "Nombre",
"confirm_subject": "Correo electrónico de confirmación de Spacedeck",
"confirm_body": "Gracias por iniciar sesión en Spacedeck.\nPor favor, clica en el siguiente enlace para confirmar tu dirección de correo electrónico.\n",
"confirm_action": "Confirmar Ahora",
"team_invite_membership_subject": "Inivitación de equipo para %s",
"team_invite_membership_body": "Has sido invitado a %s en Spacedeck. Por favor, clica en el siguiente enlace para aceptar la invitación.",
"team_invite_user_body": "Has sido invitado a %s en Spacedeck.\nTu contraseña temporal es \"%s\".\nPor favor, clica en el siguiente enlace para aceptar la invitación.",
"team_invite_admin_body": "%s fue invitado tu equipo: %s. La contraseña temporal es \"%s\".",
"team_invite_membership_acction": "Aceptar",
"team_new_member_subject": "Un nuevo Miembro para el Equipo %s se ha registrado",
"team_new_member_body": "%s se acaba de unir al Equipo %s en Spacedeck.",
"space_invite_membership_subject": "%s te invitó al Espacio %s ",
"space_invite_membership_body": "Has sido invitado por %s para unirte al Espacio %s en Spacedeck. Por favor, clica en el siguiente enlace para aceptar la invitación.",
"space_invite_membership_action": "Aceptar",
"folder_invite_membership_subject": "Espacio",
"folder_invite_membership_body": "Has sido invitado a un Equipo en Spacedeck. Por favor, clica en el siguiente enlace para aceptar la invitación.",
"folder_invite_membership_acction": "Aceptar",
"login_google": "Iniciar sesión con Google",
"save_changes": "Salvar Cambios",
"upgrade": "Mejorar",
"upgrade_now": "Mejorar ahora",
"create_space": "Crear Espacio",
"create_folder": "Crear Directorio",
"email_unconfirmed": "Correo electrónico no confirmado",
"confirmation_sent": "Correo electrónico enviado",
"folder_filter": "Filtro",
"sort_by": "Ordenar por",
"last_modified": "Última Modificación",
"last_opened": "Última Apertura",
"title": "Título",
"edit_team": "Editar Equipo",
"edit_account": "Edit Cuenta",
"log_out": "Cerrar Sesión",
"no_spaces_yet": "¡Bienvenido! Puedes crear Espacios y Directorios aquí utilizando los botones que se encuentran en la esquina superior izquierda.",
"new_folder_title": "Nuevo título para el directorio",
"folder_settings": "Ajustes de Directorio",
"upload_cover_image": "Cargar imagen de cubierta",
"spacedeck_pro_ad_folders": "Con Spacedeck Pro, puedes organizar un ilimitado número de Espacios y Directorios, y gestionar el control de acceso para cada Directorio. ¿Te gustaría aprender más sobre las características Pro?",
"spacedeck_pro_ad_versions": "Con Spacedeck Pro, puedes organizar un ilimitado número de versiones para cada Espacio así como realizar un seguimiento de su progreso o mantener instantáneas ('snapshots') seguras. ¿Te gustaría aprender más sobre las características Pro?",
"spacedeck_pro_ad_pdf": "Con Spacedeck Pro, puedes exportar tus Espacios como PDFs para su archivo, envío por correo, o impresión. ¿Te gustaría aprender más sobre las características Pro?",
"spacedeck_pro_ad_zip": "Con Spacedeck Pro, puedes exportar los contenidos de un Espacio empaquetado como un fichero ZIP. ¿Te gustaría aprender más sobre las características Pro?",
"spacedeck_pro_ad_colors": "Con Spacedeck Pro, puedes puedes usar tus propios colores usando un selector de color profesional.",
"profile_caption": "Perfil",
"upload_avatar": "Cargar Avatar",
"uploading_avatar": "Cargando Avatar…",
"avatar_dimensions": "Dimensiones recomendadas: 200×200 pixels.",
"profile_name": "Nombre",
"profile_email": "Dirección de correo electrónico",
"send_again": "Enviar de nuevo",
"confirmation_sent_long": "Correo electrónico con enlace de confirmación enviado. Por favor, revisa tu bandeja de entrada de Correo.",
"confirmation_sent_another": "Otro enlace de confirmación enviado.",
"confirmation_sent_dialog_text": "Te hemos enviado un correo explicando como confirmar tu dirección de correo electrónico.",
"payment_caption": "Pago",
"language_caption": "Idioma",
"notifications_caption": "Notificaciones",
"notifications_option_chat": "Infórmame via correo electrónico sobre nuevos comentarios",
"notifications_option_spaces": "Envíame un resumen diario de lo que sucedió en mis Espacios y Directorios.",
"password_caption": "Contraseña",
"current_password": "Contraseña Actual",
"new_password": "Nueva Contraseña",
"verify_password": "Verificar Contraseña",
"change_password": "Cambiar Contraseña",
"reset_password": "Restablecer contraseña",
"terminate_caption": "Borrar Cuenta",
"terminate_warning": "Si borras tu cuenta, todos los Espacios, Directorios y Mensajes (incluyendo todo el contenido que tú y otras personas crearon en tus Espacios) serán destruidos.",
"terminate_warning2": "Esta acción no puede deshacerse.",
"terminate_reason": "Mensaje",
"terminate_reason_caption": "Ayúdanos a mejorar compartiendo las razones por las que cancelas la cuenta.",
"terminate_terminate": "Terminar",
"space_blank1": "¡Bienvenido a un nuevo Espacio en blanco!",
"space_blank2": "Suelta ficheros, pega enlaces",
"space_blank3": "o utilizar las herramientas que aparecen abajo",
"space_blank4": "para rellenar este Espacio con contenido.",
"draft": "Borrador",
"publish": "Publicar",
"published": "Publicado",
"save_version": "Salvar Versión",
"version_saved": "Versión Salvada",
"post": "Publicar mensaje",
"chat_invite_cta1": "¡La colaboración es divertida!",
"chat_invite_cta2": "¿Por qué no ",
"chat_invite_cta3": "invitar a algunas personas",
"chat_invite_cta4": "a trabajar contigo?",
"chat_message_placeholder": "Escribe tu mensaje…",
"view": "Ver",
"edit": "Editar",
"present": "Presentar",
"chat": "Chatear",
"meta": "Metadatos",
"tool_search": "Buscar",
"tool_upload": "Cargar",
"tool_text": "Texto",
"tool_shape": "Dar forma",
"tool_zones": "Zonas",
"tool_canvas": "Fondo pizarra",
"search_media": "Buscar multimedia…",
"type_here": "Escriba aquí",
"text_formats": "Formatos",
"format_p": "Párrafos",
"format_bullets": "Lista con 'Bullets'",
"format_numbers": "Lista Numérica",
"format_h1": "Titular 1",
"format_h2": "Titular 2",
"format_h3": "Titular 3",
"font_size": "Tamaño de Fuente",
"line_height": "Altura de la Línea",
"tool_align": "Alinear",
"tool_styles": "Estilos",
"tool_bullets": "'Bullets'",
"tool_numbers": "Números",
"tool_font": "Fuente",
"color_fill": "Rellenar",
"color_stroke": "Trazo",
"color_text": "Texto",
"tool_type": "Tipo",
"tool_box": "Caja",
"tool_link": "Enlace",
"tool_layout": "Disposición",
"tool_options": "Opciones",
"tool_stroke": "Trazar",
"tool_delete": "Borrar",
"tool_lock": "Bloquear",
"tool_copy": "Copiar",
"stack": "Apilar",
"tool_circle": "Círculo",
"tool_hexagon": "Hexágono",
"tool_square": "Cuadrado",
"tool_diamond": "Diamante",
"tool_bubble": "Burbuja",
"tool_cloud": "Nube",
"tool_burst": "Ráfaga",
"tool_star": "Estrella",
"tool_heart": "Corazón",
"tool_scribble": "Garabatear",
"tool_line": "Líneas",
"tool_arrow": "Flecha",
"search_media_placeholder": "Buscar multimedia en web…",
"add_zone": "Nueva Zona",
"palette": "Paleta",
"picker": "Selector",
"background_image_caption": "Imagen",
"background_color_caption": "Color",
"upload_background_caption": "Clica para cargar una imagen de fondo",
"upload_background": "Cargar Fundo",
"access_caption": "Acceso",
"versions_caption": "Versiones",
"info_caption": "Información",
"mode_private": "Privado: Solo miembros pueden visualizar o editar",
"mode_public": "Público: Cualquiera con el enlace puede visualizar",
"invite_collaborators": "Invitar Colaboradores",
"revoke_access": "Anular Acceso",
"invite": "Enviar Invitaciones",
"invitee_email_address": "Dirección de correo electrónico del nuevo miembro",
"optional_message": "Mensaje optional",
"role_viewer": "Visualizador",
"role_editor": "Editor",
"role_admin": "Administrador",
"new_space_title": "Nuevo título para el Espacio",
"team": "Equipo",
"search": "Buscar",
"search_no_results": "Búsqueda sin resultados",
"search_clear": "Limpiar búsqueda",
"rename": "Renombrar",
"mobile": "teléfono móvil",
"image": "imagen",
"tool_filter": "fíltro",
"canel": "canel",
"invite_membership_action": "Acción afiliación de miembros",
"viewer": "visualizador",
"editor": "editor",
"admin": "administrador",
"logging_in": "iniciando sesión",
"password_confirmation": "Confirmación de Contraseña",
"confirm_again": "Te hemos enviado un correo electrónico explicando cómo puedes confirmar tu dirección de correo electrónico.",
"confirmed": "Tú Cuenta ha sido confirmada satisfactoriamente. Gracias.",
"signing_up": "Registrándote",
"password_check_inbox": "Por favor, comprueba tu bandeja de entrada de correo electrónico",
"new_space": "Nuevo Espacio",
"tool_more": "Más",
"what_is_your_name": "¡Bienvenido a %s! Por favor, elige un nombre de usuario.",
"lang": "es",
"landing_title": "Tu Pizarra en la Web.",
"landing_claim": "Spacedeck te permite combinar fácilmente todo tipo de multimedia en pizarras virtuales: notas de texto, fotos, enlaces web, incluso videos y grabaciones de audio. ",
"landing_example": "Las personas usan Spacedeck para organizar en equipo sus ideas y así poder ver proyectos completos de un vistazo, o bien en escuelas y universidades para obtener experiencias de aprendizaje más enriquecedoras y conectadas.",
"spaces": "Mis Espacios",
"access_editor_link": "Enlace de Edición Instantánea",
"access_editor_link_desc": "Proporciona este enlace a cualquier persona que deba poder editar instantáneamente este Espacio, no se requiere una cuenta: ",
"access_editor_link_desc_slug": "Este enlace también contiene el nombre del Espacio. ",
"access_anonymous_edit_blocking": "Los editores anónimos únicamente pueden cambiar sus propios elementos",
"access_current_members": "Miembros Actuales",
"access_new_members": "Invita Nuevos Miembros",
"access_no_members": "Los Miembros de este Espacio se mostrarán aquí.",
"comments": "comentarios",
"landing_customers": "Confiado por multitudes.",
"landing_features_title": "Sencillo de usar.",
"landing_features_text": "El nuevo Spacedeck 6 tiene un hermoso y optimizado interfaz de usuario que hace que tu trabajo sea más fácil y divertido que nunca, al tiempo que te brinda funciones aún más poderosas:",
"landing_features_1": "<b>Arrastra & suelta</b> imágenes, vídeos y áudios desde tu computadora o desde la web",
"landing_features_2": "<b>Escribe texto y formatéalo</b> con pleno control sobre fuente, color y estilo",
"landing_features_3": "<b>Dibuja, anota y resalta</b> incluyendo contornos gráficos",
"landing_features_4": "Convierte tu tablero en un <b>área de presentación con zoom</b>",
"landing_features_5": "<b>Colabora y chatea</b> en tiempo real con compañeros de equipo, alumnos y amigos.",
"landing_features_6": "<b>Comparte Espacios</b> en la web o via correo electrónico",
"landing_features_7": "<b>Exporta tu trabajo</b> como fichero imprimible PDF o como ZIP",
"landing_pricing": "Increiblemente asequible.",
"landing_pricing_lite": "Uso Libre/Personal",
"landing_pricing_lite_text": "La versión sencilla y completa para recopilar imágenes y tomar notas.",
"landing_pricing_pro_features_list": "<ul><li>Espacios ilimitados</li><li>Estructura de Directorios</li><li>Exportación a ficheros PDF y ZIP</li><li>Sin Marcas de Agua</li><li>Personaliza tu fondo</li><li>Historial de Actividad</li><li>20 GB de Almacenamiento</li><ul>",
"landing_pricing_pro": "€4,90/Usuario/Mes <br><small>o 49,90/Usuario/Año</small>",
"landing_pricing_pro_text": "Con toda la potencia que esperas.",
"landing_pricing_pro_features": "Con toda la potencia que esperas.",
"welcome_subject": "Bienvenido a Spacedeck",
"welcome_body": "¡Hola!\nGracias por registrárte en Spacedeck.<br>Esperamos que disfrutes trabajando con Espacios.<br>Recuerda, tu cuenta incluye colaboradores ilimitados. Siénte libre de compartir tus Espacios con amigos y colegas de todo el mundo.",
"invite_emails": "Dirección/ones de correo electrónico separadas por coma (,)",
"history_recently_updated": "Recientemente Actualizado",
"history_recently_empty": "Aún no ha pasado nada.",
"parent_folder": "Directorio padre",
"created_by": "Creado por",
"last_updated": "Última actualización",
"feedback_sent": "¡Muchas gracias por tu comentarios!",
"role_member": "Miembro",
"team_invite_membership_action": "Aceptar invitación",
"space_message_subject": "Nuevo Mensaje en el Espacio %s",
"space_message_body": "%s escribió en %s: \n",
"pro_ad_history_headline": "Cuando actualices a Spacedeck Pro, verás el historial de actualizaciones recientes en todos tus Espacios (compartidos) aquí.",
"password_reset_subject": "Restablecer contraseña para Spacedeck",
"password_reset_body": "Has solicitado el restablecimiento de tu contraseña en Spacedeck.\nPor favor, clica en el siguiente enlace para establecer una nueva contraseña.",
"password_reset_action": "Restablecer Ahora",
"was_offline": "La conexión con Spacedeck se ha interrumpido. Si tienes trabajo sin salvar, mantén abierta, por favor, esta pestaña del navegador hasta que la conexión esté restablecida, entonces toca los objetos no salvados.",
"subscription_failed_user_subject": "Problemas con el pago en tu Spacedeck",
"subscription_failed_user_body": "Desafortunadamente, no pudimos procesar su método de pago. Puede crear fácilmente un nuevo método de pago que incluya PayPal en la configuración de su cuenta.",
"subscription_failed_team_subject": "Problemas con el pago en tu Spacedeck",
"subscription_failed_team_body": "Desafortunadamente, no pudimos procesar su método de pago para tu Cuenta de Equipo. Corrija su método de pago lo antes posible.",
"team_name": "Nombre del Equipo",
"subdomain": "Subdominio",
"team_adresses": "Direcciones de correo electrónico del Equipo",
"add": "Añadir",
"invited": "invitado",
"duplicate_destination": "¿En qué directorio quieres duplicar este Espacio??",
"duplicate_confirm": "Duplicar %s en el directorio %s?",
"duplicate_success": "%s fue duplicado en %s.",
"goto_space": "Ve al Espacio %s",
"goto_folder": "Ve al Directorio %s",
"stay_here": "Permanece aquí",
"sharing": "Compartiendo",
"list": "Lista para Exportar",
"link": "Enlace",
"download_space": "Espacio de Descarga",
"download_as_pdf": "Descargar espacio en PDF",
"type": "Tipo",
"download": "Descarga",
"Previous Zone": "Zona Previa",
"Next Zone": "Zona Siguiente",
"promote": "Promover",
"demote": "Degradar",
"more": "Más",
"lock": "Bloquear",
"unlock": "Desbloquear",
"follow_present": "Seguir",
"mute_present": "Dejar de Seguir",
"follow_present_help": "Si alguien más está presentando este espacio, los otros miembros siguen automáticamente la presentación. Active o desactive el seguimiento con este botón.",
"export": "exportar",
"media": "Media"
}

View File

@@ -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.",
"error_unknown_email": "Combinaison inconnue de l'email et mot de passe. Ou essayer de signer avec Google.",
"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é.",
@@ -172,7 +172,6 @@
"format_h3": "Titre 3",
"font_size": "Taille de la police",
"line_height": "Hauteur de ligne",
"tool_font": "Font",
"tool_align": "Align",
"tool_styles": "Style",
"tool_bullets": "Puces",
@@ -302,7 +301,6 @@
"goto_folder": "Aller au dossier %s",
"stay_here": "Reste ici",
"download_space": "télécharger un espace",
"download_as_pdf": "télécharger un espace comme PDF",
"type": "Type",
"Previous Zone": "Zone précédent",
"Next Zone": "Zone suivante",
@@ -316,6 +314,5 @@
"more": "plus",
"follow_present": "Suivre",
"mute_present": "Pas suivre",
"follow_present_help": "follow_present_help",
"media": "Media"
}
"follow_present_help": "follow_present_help"
}

View File

@@ -1,327 +0,0 @@
{
"ok": "D'acòrdi",
"cancel": "Anullar",
"close": "Tampar",
"open": "Dobrir",
"folder": "Repertòri",
"save": "Enregistrar",
"saved": "Enregistrat",
"created": "creat",
"duplicate": "Duplicar",
"delete": "Suprimir",
"remove": "Suprimir",
"set": "definir",
"reset": "reïnicializar",
"thanks": "Mercés",
"share": "Partejar",
"signup": "Sinscriure",
"login": "Connexion",
"logout": "Se desconnectar",
"email": "Adreça electronica",
"password": "Senhal",
"width": "Largor",
"height": "Nautor",
"nick": "Escais",
"role": "Ròtle",
"members": "Membres",
"actions": "Accions",
"or": "o",
"you": "vos",
"via": "via",
"by": "per",
"zero": "Zéro",
"page": "Pagina",
"new": "Nòu",
"copy": "Copiar",
"home": "Acuèlh",
"owner": "Proprietari",
"space": "Espaci",
"second": "Segond",
"not_found": "Pas trobat.",
"untitled_space": "Espaci sens nom",
"untitled_folder": "Repertòri sens nom",
"untitled": "sens títol",
"sure": "O volètz vertadièrament?",
"specify": "Mercés despecificar",
"confirm": "Mercés de confirmar",
"error_unknown_email": "Aquesta combinason dadreça electronica/senhal es desconeguda.",
"error_password_confirmation": "Los senhals picats correspondon pas.",
"error_domain_blocked": "Lo domeni es blocat.",
"error_user_email_already_used": "Aquesta adreça es ja utilizada.",
"support": "Assisténcia Spacedeck",
"offline": "Fòra linha. Clicatz per mai dopcions.",
"error": "O planhèm, quicòm a trucat. Mercés de contactar support@spacedeck.com",
"welcome": "La benvenguda",
"claim": "Vòstre tablèu numeric.",
"trynow": "Ensajatz ara.",
"about": "A prepaus de nosautre",
"terms": "Tèrmes",
"contact": "Contacte",
"privacy": "Confidencialitat",
"business_adress": "Adreça professionala",
"post_adress": "Adreça postala",
"phone": "Telefòn",
"ceo": "Gestionari",
"name": "Nom",
"confirm_subject": "Corrièl de confirmacion de Spacedeck",
"confirm_body": "Mercés de vòstra inscripcion a Spacedeck.\nMercés de clicar lo ligam seguent per confirmar vòstra adreça electronica.\n",
"confirm_action": "Confirmar",
"team_invite_membership_subject": "Invitacion dequipa per %s",
"team_invite_membership_body": "Qualquun vos a convidat a %s sus Spacedeck. Mercés de clicar sul ligam seguent per acceptar linvitacion.",
"team_invite_user_body": "Qualquun vos a convidat a %s sus Spacedeck.\nVòstre senhal temporari es « %s».\nMercés de clicar sul ligam seguent per acceptar linvitacion.",
"team_invite_admin_body": "%s es estat convidat a vòstra equipa: %s. Lo senhal temporari es « %s».",
"team_invite_membership_acction": "Acceptar",
"team_new_member_subject": "Membre novèl",
"team_new_member_body": "%s a rejonch lequipa %s sus Spacedeck",
"space_invite_membership_subject": "Invitacion Espaci per %s: %s",
"space_invite_membership_body": "%s vos a convit a lEspaci « %s»",
"space_invite_membership_action": "Acceptar linvitacion",
"folder_invite_membership_subject": "Espaci",
"folder_invite_membership_body": "Qualquun vos a convidat a Team sus Spacedeck. Clicatz lo ligam seguent per acceptar linvitacion.",
"folder_invite_membership_acction": "Acceptar",
"login_google": "Sidentificar amb Google",
"save_changes": "Enregistrar las modificacions",
"upgrade": "Metre a jorn",
"upgrade_now": "Metre a nivèl ara",
"create_space": "Crear un espaci",
"create_folder": "Crear un repertòri",
"email_unconfirmed": "Adreça pas confirmada",
"confirmation_sent": "Messatge enviat",
"folder_filter": "Filtre",
"sort_by": "Triar per",
"last_modified": "Darrièra modificacion",
"last_opened": "Darrièra dobertura",
"title": "Títol",
"edit_team": "Modificar equipa",
"edit_account": "Modificar compte",
"log_out": "Se desconnectar",
"no_spaces_yet": "Avètz pas encara creat cap despacis.",
"new_folder_title": "Novèl títol pel repertòri",
"folder_settings": "Paramètres repertòri",
"upload_cover_image": "Enviar imatge cobèrta",
"spacedeck_pro_ad_folders": "Avec Spacedeck Pro, vous pouvez organiser un nombre illimité de espaces dans les dossiers et gérer les contrôles d'accès pour chaque dossier. Voulez-vous en savoir plus sur les fonctionnalités Pro?",
"spacedeck_pro_ad_versions": "Avec Spacedeck Pro, vous pouvez enregistrer des versions illimitées de chaque espace pour suivre vos progrès ou de conserver des instantanés sécurité. Voulez-vous en savoir plus sur les fonctionnalités Pro?",
"spacedeck_pro_ad_pdf": "Avec Spacedeck Pro, vous pouvez exporter vos espaces et même des dossiers entiers belles PDF pour l'archivage, de diffusion, ou autour de l'impression. Voulez-vous en savoir plus sur les fonctionnalités Pro?",
"spacedeck_pro_ad_zip": "Avec Spacedeck Pro, vous pouvez exporter le contenu d'un espace comme un paquet ZIP. Voulez-vous en savoir plus sur les fonctionnalités Pro?",
"spacedeck_pro_ad_colors": "Avec Spacedeck Pro, vous pouvez mélanger vos propres couleurs en utilisant un sélecteur de couleur professionnelle.",
"profile_caption": "Perfil",
"upload_avatar": "Enviar avatar",
"uploading_avatar": "Mandadís avatar…",
"avatar_dimensions": "Dimensions recomandadas: 200x200pixèls.",
"profile_name": "Nom",
"profile_email": "Adreça electronica",
"send_again": "Tornar enviar",
"confirmation_sent_long": "Ligam de confirmacion enviat. Mercés de verificar vòstres corrièrs.",
"confirmation_sent_another": "Un autre ligam de confirmacion enviat.",
"confirmation_sent_dialog_text": "Avèm enviat un corrièl quexplica cossí confirmar vòstra adreça electronica.",
"payment_caption": "Pagament",
"language_caption": "Lenga",
"notifications_caption": "Notificacions",
"notifications_option_chat": "Enviatz-me de comentaris novèls per corrièl",
"notifications_option_spaces": "Enviatz-me un resumit jornadièr de las modificacions dels espacis",
"password_caption": "Senhal",
"current_password": "Senhal actual",
"new_password": "Senhal novèl",
"verify_password": "Verificar lo senhal novèl",
"change_password": "Modificar senhal",
"reset_password": "Reïnicializar senhal",
"terminate_caption": "Suprimir lo compte",
"terminate_warning": "En escafant vòstre compte, vòstres messatges, espacis, repertòris e lor contengut seràn suprimits. Aquesta accion pòt pas èsser anullada.",
"terminate_warning2": "Aquò pòt pas èsser anullat.",
"terminate_reason": "Messatge",
"terminate_reason_caption": "Ajudatz-nos a melhorar lo logicial en nos diguent las rasons de la supression de vòstre compte",
"terminate_terminate": "Suprimir vòstre compte per totjorn?",
"space_blank1": "Aquò es vòstre novèl espaci",
"space_blank2": "Lisatz de fichièrs, pegatz de ligams",
"space_blank3": "o utilizatz las aisinas",
"space_blank4": "Siatz creatius!",
"draft": "Borrolhon",
"publish": "Publicar",
"published": "Publicat",
"save_version": "Enregistrar version",
"version_saved": "Version enregistrada",
"post": "Publicar messatge",
"chat_invite_cta1": "Collaboratz amb amusament!",
"chat_invite_cta2": "Perqué pas",
"chat_invite_cta3": "convidar de monde",
"chat_invite_cta4": "per trabalhar amb vos?",
"chat_message_placeholder": "Escrivètz vòstre messatge…",
"view": "Afichatge",
"edit": "Edicion",
"present": "Present",
"chat": "Messatjariá",
"meta": "Mèta",
"tool_search": "Recercar",
"tool_upload": "Enviar",
"tool_text": "Tèxte",
"tool_shape": "Forma",
"tool_zones": "Zònas",
"tool_canvas": "Canvas",
"search_media": "Cercar de mèdias…",
"type_here": "Picatz aquí",
"text_formats": "Formats",
"format_p": "Paragraph",
"format_bullets": "Lista a piuses",
"format_numbers": "Lista numeratada",
"format_h1": "Títol 1",
"format_h2": "Títol 2",
"format_h3": "Títol 3",
"font_size": "Font Size",
"line_height": "Nnautor de linha",
"tool_align": "Alinhar",
"tool_styles": "Estils",
"tool_bullets": "Bullets",
"tool_numbers": "Nombres",
"color_fill": "Fill",
"tool_font": "Font",
"color_stroke": "Traçat",
"color_text": "Tèxte",
"tool_type": "Tipe",
"tool_box": "Bóstia",
"tool_link": "Ligam",
"tool_layout": "Agençament",
"tool_options": "Opcions",
"tool_stroke": "Traçat",
"tool_delete": "Suprimir",
"tool_lock": "Verrolhar",
"tool_copy": "Copiar",
"stack": "Pila",
"tool_circle": "Cercle",
"tool_hexagon": "Exagòn",
"tool_square": "Carrat",
"tool_diamond": "Diamond",
"tool_bubble": "Bulla",
"tool_cloud": "Nívol",
"tool_burst": "Burst",
"tool_star": "Star",
"tool_heart": "Còr",
"tool_scribble": "Barbolhatge",
"tool_line": "Linha",
"tool_arrow": "Sageta",
"search_media_placeholder": "Cercar de mèdias web…",
"add_zone": "Zòna novèla",
"palette": "Paleta",
"picker": "Pipeta",
"background_image_caption": "Imatge",
"background_color_caption": "Color",
"upload_background_caption": "Clicar per enviar un imatge de rèireplan",
"upload_background": "Enviar rèireplan",
"access_caption": "Accès",
"versions_caption": "Versions",
"info_caption": "Info",
"mode_private": "Privat: sonque los membres pòdon veire o modificar",
"mode_public": "Public: qual que siá amb lo ligam pòt veire",
"invite_collaborators": "Convidar collaborators",
"revoke_access": "Revocar laccès",
"invite": "Enviar invitacions",
"invitee_email_address": "Adreça electronica del novèl membre",
"optional_message": "Messatge opcional",
"role_viewer": "Visualizaira",
"role_editor": "Editor",
"role_admin": "Admin",
"new_space_title": "Títol novèl per lEspaci",
"team": "Equipa",
"search": "Recercar",
"search_no_results": "search_no_results",
"search_clear": "search_clear",
"rename": "Renomenar",
"mobile": "mobil",
"image": "imatge",
"tool_filter": "filtre",
"canel": "canel",
"invite_membership_action": "invite_membership_action",
"viewer": "visualizaira",
"editor": "editor",
"admin": "admin",
"logging_in": "connexion",
"password_confirmation": "Confirmacion del senhla",
"confirm_again": "Mercés de consultar vòstra bóstia de recepcion per confirmar vòstra adreça.",
"confirmed": "Vòstre compte es estat corrèctament confirmat. Mercés.",
"signing_up": "Inscripcion",
"password_check_inbox": "Verificatz vòstra bóstia de recepcion",
"new_space": "Espaci novèl",
"tool_more": "Mai",
"what_is_your_name": "La benvenguda a %s! Mercés de causir un escais-nom.",
"lang": "en",
"landing_title": "Vòstre tablèu blanc sul Web.",
"landing_claim": "Spacedeck vos permet de facilament combinar quin que siá tipe de mèdias sus un tablèu virtual: tèxte, nòtas, ligams web, amai vidèos e enregistraments àudio. ",
"landing_example": "Lo monde utiliza Spacedeck per organizar lors idèas, en equipa per veire totes los projèctes en una ulhada, a lescòla e a luniversitat pels mai rics, experiéncia daprendissatge connectat.",
"spaces": "Mos espacis",
"access_editor_link": "Ligam de modificacion dirècta",
"access_editor_link_desc": "Donatz aqueste ligam a qualquun que deu poder modificar dirèctament aqueste Espaci, cap de compte pas requerit: ",
"access_editor_link_desc_slug": "Aqueste ligam conten lo nom de lespaci, tanben. ",
"access_anonymous_edit_blocking": "Los convidats pòdon pas modificar los elements quan creats.",
"access_current_members": "Membres actuals",
"access_new_members": "Convidar de novèls membres",
"access_no_members": "Los membres daqueste Espacii apreissaràn aquí.",
"comments": "comentaris",
"landing_customers": "La fisança de milièr de personas.",
"landing_features_title": "Un jòc d'enfants dutilizar.",
"landing_features_text": "Le tout nouveau Spacedeck 5 vous permet de travailler bien plus facilement grâce à sa magnifique interface simplifiée.",
"landing_features_1": "Glissez & déposez images, vidéos et audios de votre ordinateur ou du web",
"landing_features_2": "Ecrivez directement sur l'espace et choisissez les polices de caractère, couleurs et styles",
"landing_features_3": "Dessinez, annotez et surlignez grâce aux formes graphiques intégrées",
"landing_features_4": "Transformez votre espace en une présentation dynamique",
"landing_features_5": "Collaborez et discutez en temps réel avec vos collègues, élèves et amis",
"landing_features_6": "Partagez vos espaces sur le web ou par email",
"landing_features_7": "Exportez votre espace en PDF pour l'imprimer",
"landing_pricing": "Incroyablement abordable.",
"landing_pricing_lite": "Usage personnel",
"landing_pricing_lite_text": "La version de base, bien arrondi pour recueillir des images et de garder des notes.",
"landing_pricing_pro_features_list": "<ul><li>Unlimited Spaces</li><li>Exporter PDF, ZIP</li><li>No Watermarks</li><li>Image de fonds</li><li>Activity History</li><li>20 Go de stockage</li><ul>",
"landing_pricing_pro": "€4,90/User/Mo. <br><small> €49,90/User/Year</small>",
"landing_pricing_pro_text": "Avec toute la puissance que vous attendez.",
"landing_pricing_pro_features": "€4,90/User/Mo. <br><small> €49,90/User/Year</small>",
"welcome_subject": "La benvenguda a Spacedeck",
"welcome_body": "Mercés per vòstra inscripcion a Spacedeck.\nEsperam quauretz plaser a trabalhar dins los Espacis. <br> Oblidetz pas que vòstre compte conten un nombre illimitat de collaborators. <br> Esitetz pas a partejar vòstres espacis amb los amics e collègas del monde entièr.",
"invite_emails": "Picatz las adreças mails (separadas per de vergulas)",
"history_recently_updated": "Novèlas",
"history_recently_empty": "Pas res",
"parent_folder": "Repertòri parent",
"created_by": "Creat per",
"last_updated": "Darrièra mesa a jorn",
"feedback_sent": "Comentari enviat",
"role_member": "Membre",
"team_invite_membership_action": "Acceptar",
"space_message_subject": "A publicat sus %s",
"space_message_body": "%s a comentat dins %s:\n",
"pro_ad_history_headline": "Aprèp una mesa a nivèl podètz obténer un apercebut de totas las activitats actualas dels espacis aquí.",
"password_reset_subject": "Reïnicializar lo senhal per Spacedeck",
"password_reset_body": "Òu!<br><br>Avètz demandat la reïnicializacion del senhal.<br>Mercés de clicar sul ligam seguent per ne causir un novèl.<br>",
"password_reset_action": "Reïnicializar ara",
"was_offline": "La connexion a Spacedeck es estada copada. Savètz de trabalh pas enregistratz, gardatz aqueste onglet de navigador dobèrt fins que la connexion siá restablida puèi tocatz de nòu los elements pas enregistrats.",
"subscription_failed_user_subject": "Problèma amb lo pagament Spacedeck",
"subscription_failed_user_body": "Unfortunately, we could not process your Payment-method. You can easly create a new payment method including PayPal in your account settings.",
"subscription_failed_team_subject": "Problem with your Spacedeck Payment",
"subscription_failed_team_body": "Unfortunately, we could not process your Payment-method for your Team-Account. Please fix your payment method asap.",
"team_name": "Nom de lequipa",
"subdomain": "jos-domeni",
"team_adresses": "Adreças equipa",
"add": "Ajustar",
"invited": "convidat",
"duplicate_destination": "Seleccionatz lo repertòri de destinacion",
"duplicate_confirm": "Duplicar %s dins %s?",
"duplicate_success": "%s es estat duplicat dins %s.",
"goto_space": "anar a lespaci",
"goto_folder": "anar al repertòri",
"stay_here": "Demorar aquí",
"sharing": "partatge",
"list": "lista",
"link": "Ligam",
"download_space": "Telecargar espaci",
"download_space_as_pdf": "Telecargar espaci PDF",
"type": "Tipe",
"download": "Telecargar",
"Previous Zone": "Zòna precedenta",
"Next Zone": "Zòna seguenta",
"promote": "Promòure",
"demote": "Retrogradar",
"more": "Mai",
"lock": "Verrolhar",
"unlock": "Desverrolhar",
"follow_present": "Seguir",
"mute_present": "Quitar de seguir",
"follow_present_help": "follow_present_help",
"export": "exportar",
"media": "Media"
}

View File

@@ -4,6 +4,27 @@ require('../models/db');
var config = require('config');
const redis = require('../helpers/redis');
// FIXME TODO object.toJSON()
var saveAction = (actionKey, object) => {
if (object.constructor.modelName == "Space")
return;
let attr = {
action: actionKey,
space: object.space_id || object.space,
user: object.user_id || object.user,
editor_name: object.editor_name,
object: object
};
/*let action = new Action(attr);
action.save(function(err) {
if (err)
console.error("saved create action err:", err);
});*/
};
module.exports = (req, res, next) => {
res.header("Cache-Control", "no-cache");
@@ -15,24 +36,21 @@ module.exports = (req, res, next) => {
if (!object) return;
redis.sendMessage("create", model, object, req.channelId);
this.status(201).json(object);
saveAction("create", object);
};
res['distributeUpdate'] = function(model, object, sendToSelf) {
res['distributeUpdate'] = function(model, object) {
if (!object) return;
if (sendToSelf) {
// send this update to the initiating user, for example when
// a conversion task has finished
redis.sendMessage("update-self", model, object, req.channelId);
} else {
redis.sendMessage("update", model, object, req.channelId);
}
redis.sendMessage("update", model, object, req.channelId);
this.status(200).json(object);
saveAction("update", object);
};
res['distributeDelete'] = function(model, object) {
if (!object) return;
redis.sendMessage("delete", model, object, req.channelId);
this.sendStatus(204);
saveAction("delete", object);
};
next();

View File

@@ -4,27 +4,8 @@ const db = require('../models/db');
var config = require('config');
module.exports = (req, res, next) => {
// authentication via API token
const api_token = req.headers["x-spacedeck-api-token"];
if (api_token && api_token.length>7) {
db.User.findOne({where: {api_token: api_token}}).then(user => {
req.user = user;
next();
}).error(err => {
res.status(403).json({
"error": "invalid_api-token"
});
next();
});
return;
}
// authentication via session/cookie
const token = req.cookies["sdsession"];
if (token && token != "null" && token != null) {
db.Session.findOne({where: {token: token}})
.then(session => {
@@ -35,8 +16,7 @@ module.exports = (req, res, 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 });
res.clearCookie('sdsession');
if (req.accepts("text/html")) {
res.send("Please clear your cookies and try again.");
@@ -47,7 +27,7 @@ module.exports = (req, res, next) => {
} else {
res.send("Please clear your cookies and try again.");
}
} else {
req["token"] = token;
req["user"] = user;
@@ -63,3 +43,4 @@ module.exports = (req, res, next) => {
next();
}
}

View File

@@ -1,7 +1,6 @@
'use strict';
const db = require('../models/db');
const { Op } = require("sequelize");
var config = require('config');
module.exports = (req, res, next) => {
@@ -54,14 +53,15 @@ module.exports = (req, res, next) => {
'email': 1
};
// find space by id or slug
db.Space.findOne({where: {
[Op.or]: [
{"_id": spaceId},
{"edit_slug": spaceId}
]
"_id": spaceId
}}).then(function(space) {
//.populate("creator", userMapping)
//if (err) {
// res.status(400).json(err);
//} else {
if (space) {
if (space.access_mode == "public") {
if (space.password) {

View File

@@ -1,5 +1,7 @@
const Umzug = require('umzug');
const config = require('config')
//'use strict';
//var mongoose = require('mongoose');
//const sqlite3 = require('sqlite3').verbose();
function sequel_log(a,b,c) {
console.log(a);
@@ -18,7 +20,7 @@ const sequelize = new Sequelize('database', 'username', 'password', {
},
// SQLite only
storage: config.get('storage_local_db'),
storage: 'database.sqlite',
logging: sequel_log,
// http://docs.sequelizejs.com/manual/tutorial/querying.html#operators
@@ -43,7 +45,6 @@ module.exports = {
avatar_thumb_uri: Sequelize.STRING,
confirmation_token: Sequelize.STRING,
password_reset_token: Sequelize.STRING,
api_token: Sequelize.STRING,
home_folder_id: Sequelize.STRING,
prefs_language: Sequelize.STRING,
prefs_email_notifications: Sequelize.STRING,
@@ -52,17 +53,6 @@ module.exports = {
updated_at: {type: Sequelize.DATE, defaultValue: Sequelize.NOW}
}),
CreatorSafeInclude: function(db) {
return {
model: this.User,
as: 'creator',
attributes: ['_id','email','nickname',
'avatar_original_uri',
'avatar_thumb_uri',
'created_at','updated_at']
};
},
Session: sequelize.define('session', {
token: {type: Sequelize.STRING, primaryKey: true},
user_id: Sequelize.STRING,
@@ -91,25 +81,24 @@ module.exports = {
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,
state: {type: Sequelize.STRING, defaultValue: "pending"},
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,
@@ -119,7 +108,7 @@ module.exports = {
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,
@@ -132,7 +121,7 @@ module.exports = {
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,
@@ -153,16 +142,16 @@ module.exports = {
//}],
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,
@@ -175,7 +164,7 @@ module.exports = {
crop_w: Sequelize.INTEGER,
crop_h: Sequelize.INTEGER,
shape: Sequelize.STRING,
shape_svg: Sequelize.TEXT,
shape_svg: Sequelize.STRING,
padding_left: Sequelize.INTEGER,
padding_right: Sequelize.INTEGER,
padding_top: Sequelize.INTEGER,
@@ -187,7 +176,7 @@ module.exports = {
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,
@@ -196,7 +185,7 @@ module.exports = {
opacity: Sequelize.DECIMAL,
payload_alternatives: Sequelize.TEXT,
/*payload_alternatives: [{
mime: String,
payload_uri: String,
@@ -205,12 +194,12 @@ module.exports = {
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() {
init: function() {
User = this.User;
Session = this.Session;
Space = this.Space;
@@ -231,81 +220,62 @@ module.exports = {
},
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!');
});
sequelize.sync();
},
getUserRoleInSpace: (originalSpace, user, cb) => {
originalSpace.path = [];
console.log("getUserRoleInSpace",originalSpace._id,user._id,user.home_folder_id);
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
"space": 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) {
}}, function(err, parentSpace) {
findMembershipsForSpace(parentSpace, currentMemberships, prevRole);
});
} else {
@@ -350,26 +320,26 @@ module.exports = {
},
unpackArtifact: (a) => {
if (a.tags && (typeof a.tags)=="string") {
if (a.tags) {
a.tags = JSON.parse(a.tags);
}
if (a.control_points && (typeof a.control_points)=="string") {
if (a.control_points) {
a.control_points = JSON.parse(a.control_points);
}
if (a.payload_alternatives && (typeof a.payload_alternatives)=="string") {
if (a.payload_alternatives) {
a.payload_alternatives = JSON.parse(a.payload_alternatives);
}
return a;
},
packArtifact: (a) => {
if (a.tags && (typeof a.tags)!="string") {
if (a.tags) {
a.tags = JSON.stringify(a.tags);
}
if (a.control_points && (typeof a.control_points)!="string") {
if (a.control_points) {
a.control_points = JSON.stringify(a.control_points);
}
if (a.payload_alternatives && (typeof a.payload_alternatives)!="string") {
if (a.payload_alternatives) {
a.payload_alternatives = JSON.stringify(a.payload_alternatives);
}
return a;

View File

@@ -1,79 +0,0 @@
'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,23 +0,0 @@
'use strict';
module.exports = {
up: function(migration, DataTypes) {
return Promise.all([
migration.changeColumn('users', 'api_token',
{
type: DataTypes.STRING
}
)
])
},
down: function(migration, DataTypes) {
return Promise.all([
migration.changeColumn('users', 'api_token',
{
type: Sequelize.STRING
}
)
])
}
}

View File

@@ -3,51 +3,47 @@
"version": "1.0.0",
"private": true,
"scripts": {
"start": "node spacedeck.js"
"start": "electron ."
},
"engines": {
"node": ">=10.0.0"
"node": ">=7.8.0"
},
"dependencies": {
"archiver": "1.3.0",
"async": "2.3.0",
"basic-auth": "1.1.0",
"bcryptjs": "2.4.3",
"body-parser": "^1.19.0",
"body-parser": "~1.17.1",
"cheerio": "0.22.0",
"config": "1.25.1",
"cookie-parser": "~1.4.3",
"ejs": "3.1.5",
"electron": "^1.8.4",
"execSync": "latest",
"express": "^4.16.4",
"express": "~4.13.0",
"file-type": "^7.6.0",
"glob": "7.1.1",
"gm": "^1.23.1",
"gulp": "^4.0.2",
"gulp-concat": "^2.6.1",
"gulp-sass": "^4.0.2",
"gm": "1.23.0",
"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.9.1",
"node-phantom-simple": "2.2.4",
"node-server-screenshot": "^0.2.1",
"nodemailer": "^4.6.7",
"phantomjs-prebuilt": "^2.1.16",
"phantomjs-prebuilt": "2.1.14",
"read-chunk": "^2.1.0",
"request": "^2.88.0",
"request": "2.81.0",
"sanitize-html": "^1.11.1",
"sequelize": "^4.37.6",
"serve-favicon": "~2.4.2",
"serve-static": "^1.13.1",
"slug": "^1.1.0",
"slug": "0.9.1",
"sqlite3": "^4.0.0",
"umzug": "^2.1.0",
"swig": "1.4.2",
"underscore": "1.8.3",
"uuid": "^3.2.1",
"validator": "7.0.0",
"ws": "3.3.1"
"ws": "2.2.3"
},
"main": "app.js",
"description": "",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -1,69 +0,0 @@
<?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>

Before

Width:  |  Height:  |  Size: 5.0 KiB

View File

@@ -1,66 +0,0 @@
<?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>

Before

Width:  |  Height:  |  Size: 4.9 KiB

View File

@@ -1,129 +0,0 @@
<?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>

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 158 KiB

View File

@@ -6,10 +6,6 @@ var websocket = null;
var channel_id = null;
var space_auth = null;
function set_space_auth(hash) {
space_auth = hash;
}
function load_resource(method, path, data, on_success, on_error, on_progress) {
var req = new XMLHttpRequest();
req.onload = function(evt,b,c) {
@@ -48,17 +44,24 @@ function load_resource(method, path, data, on_success, on_error, on_progress) {
}
req.withCredentials = true;
req.open(method, api_endpoint+"/api"+path, true);
if (api_token) {
req.setRequestHeader("X-Spacedeck-Auth", api_token);
}
if (space_auth) {
console.log("set space auth", space_auth);
req.setRequestHeader("X-Spacedeck-Space-Auth", space_auth);
}
if (channel_id) {
req.setRequestHeader("X-Spacedeck-Channel", channel_id);
}
if (csrf_token) {
req.setRequestHeader("X-csrf-token", csrf_token);
}
try {
if (data) {
@@ -130,6 +133,18 @@ function load_spaces(id, is_home, on_success, on_error) {
}, on_error);
}
function load_importables(user, on_success, on_error) {
load_resource("get", "/users/"+user._id+"/importables", null, on_success, on_error);
}
function import_zip(user, filename, on_success, on_error) {
load_resource("get", "/users/"+user._id+"/import?zip="+filename, null, on_success, 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) {
load_resource("get", "/spaces/"+ s._id +"/digest", null, on_success, on_error);
}
@@ -175,10 +190,12 @@ function delete_space(s, on_success, on_error) {
load_resource("delete", "/spaces/"+s._id, null, on_success, on_error);
}
function delete_artifact(a, on_success, on_error) {
load_resource("delete", "/spaces/"+a.space_id+"/artifacts/"+a._id);
}
function duplicate_space(s, to_space_id, on_success, on_error) {
var path = "/spaces/"+s._id+"/duplicate";
if(to_space_id) {
@@ -257,8 +274,8 @@ function delete_user(u, password, 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, invite_code, 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_user(name, email, password, password_confirmation, on_success, on_error) {
load_resource("post", "/users", {email:email, nickname:name, password:password, password_confirmation: password_confirmation}, on_success, on_error);
}
function create_session(email, password, on_success, on_error) {

View File

@@ -2,7 +2,6 @@ window.locales = {};
window.locales.en = {};
window.locales.de = {};
window.locales.fr = {};
window.locales.oc = {};
window.locales.en.translation =
{
"ok": "OK",
@@ -944,328 +943,4 @@ window.locales.fr.translation =
"promote": "promouvoir",
"demote": "rétrograder"
}
window.locales.oc.translation =
{
"ok": "D'acòrdi",
"cancel": "Anullar",
"close": "Tampar",
"open": "Dobrir",
"folder": "Repertòri",
"save": "Enregistrar",
"saved": "Enregistrat",
"created": "creat",
"duplicate": "Duplicar",
"delete": "Suprimir",
"remove": "Suprimir",
"set": "definir",
"reset": "reïnicializar",
"thanks": "Mercés",
"share": "Partejar",
"signup": "Sinscriure",
"login": "Connexion",
"logout": "Se desconnectar",
"email": "Adreça electronica",
"password": "Senhal",
"width": "Largor",
"height": "Nautor",
"nick": "Escais",
"role": "Ròtle",
"members": "Membres",
"actions": "Accions",
"or": "o",
"you": "vos",
"via": "via",
"by": "per",
"zero": "Zéro",
"page": "Pagina",
"new": "Nòu",
"copy": "Copiar",
"home": "Acuèlh",
"owner": "Proprietari",
"space": "Espaci",
"second": "Segond",
"not_found": "Pas trobat.",
"untitled_space": "Espaci sens nom",
"untitled_folder": "Repertòri sens nom",
"untitled": "sens títol",
"sure": "O volètz vertadièrament?",
"specify": "Mercés despecificar",
"confirm": "Mercés de confirmar",
"error_unknown_email": "Aquesta combinason dadreça electronica/senhal es desconeguda.",
"error_password_confirmation": "Los senhals picats correspondon pas.",
"error_domain_blocked": "Lo domeni es blocat.",
"error_user_email_already_used": "Aquesta adreça es ja utilizada.",
"support": "Assisténcia Spacedeck",
"offline": "Fòra linha. Clicatz per mai dopcions.",
"error": "O planhèm, quicòm a trucat. Mercés de contactar support@spacedeck.com",
"welcome": "La benvenguda",
"claim": "Vòstre tablèu numeric.",
"trynow": "Ensajatz ara.",
"about": "A prepaus de nosautre",
"terms": "Tèrmes",
"contact": "Contacte",
"privacy": "Confidencialitat",
"business_adress": "Adreça professionala",
"post_adress": "Adreça postala",
"phone": "Telefòn",
"ceo": "Gestionari",
"name": "Nom",
"confirm_subject": "Corrièl de confirmacion de Spacedeck",
"confirm_body": "Mercés de vòstra inscripcion a Spacedeck.\nMercés de clicar lo ligam seguent per confirmar vòstra adreça electronica.\n",
"confirm_action": "Confirmar",
"team_invite_membership_subject": "Invitacion dequipa per %s",
"team_invite_membership_body": "Qualquun vos a convidat a %s sus Spacedeck. Mercés de clicar sul ligam seguent per acceptar linvitacion.",
"team_invite_user_body": "Qualquun vos a convidat a %s sus Spacedeck.\nVòstre senhal temporari es « %s».\nMercés de clicar sul ligam seguent per acceptar linvitacion.",
"team_invite_admin_body": "%s es estat convidat a vòstra equipa: %s. Lo senhal temporari es « %s».",
"team_invite_membership_acction": "Acceptar",
"team_new_member_subject": "Membre novèl",
"team_new_member_body": "%s a rejonch lequipa %s sus Spacedeck",
"space_invite_membership_subject": "Invitacion Espaci per %s: %s",
"space_invite_membership_body": "%s vos a convit a lEspaci « %s»",
"space_invite_membership_action": "Acceptar linvitacion",
"folder_invite_membership_subject": "Espaci",
"folder_invite_membership_body": "Qualquun vos a convidat a Team sus Spacedeck. Clicatz lo ligam seguent per acceptar linvitacion.",
"folder_invite_membership_acction": "Acceptar",
"login_google": "Sidentificar amb Google",
"save_changes": "Enregistrar las modificacions",
"upgrade": "Metre a jorn",
"upgrade_now": "Metre a nivèl ara",
"create_space": "Crear un espaci",
"create_folder": "Crear un repertòri",
"email_unconfirmed": "Adreça pas confirmada",
"confirmation_sent": "Messatge enviat",
"folder_filter": "Filtre",
"sort_by": "Triar per",
"last_modified": "Darrièra modificacion",
"last_opened": "Darrièra dobertura",
"title": "Títol",
"edit_team": "Modificar equipa",
"edit_account": "Modificar compte",
"log_out": "Se desconnectar",
"no_spaces_yet": "Avètz pas encara creat cap despacis.",
"new_folder_title": "Novèl títol pel repertòri",
"folder_settings": "Paramètres repertòri",
"upload_cover_image": "Enviar imatge cobèrta",
"spacedeck_pro_ad_folders": "Avec Spacedeck Pro, vous pouvez organiser un nombre illimité de espaces dans les dossiers et gérer les contrôles d'accès pour chaque dossier. Voulez-vous en savoir plus sur les fonctionnalités Pro?",
"spacedeck_pro_ad_versions": "Avec Spacedeck Pro, vous pouvez enregistrer des versions illimitées de chaque espace pour suivre vos progrès ou de conserver des instantanés sécurité. Voulez-vous en savoir plus sur les fonctionnalités Pro?",
"spacedeck_pro_ad_pdf": "Avec Spacedeck Pro, vous pouvez exporter vos espaces et même des dossiers entiers belles PDF pour l'archivage, de diffusion, ou autour de l'impression. Voulez-vous en savoir plus sur les fonctionnalités Pro?",
"spacedeck_pro_ad_zip": "Avec Spacedeck Pro, vous pouvez exporter le contenu d'un espace comme un paquet ZIP. Voulez-vous en savoir plus sur les fonctionnalités Pro?",
"spacedeck_pro_ad_colors": "Avec Spacedeck Pro, vous pouvez mélanger vos propres couleurs en utilisant un sélecteur de couleur professionnelle.",
"profile_caption": "Perfil",
"upload_avatar": "Enviar avatar",
"uploading_avatar": "Mandadís avatar…",
"avatar_dimensions": "Dimensions recomandadas: 200x200pixèls.",
"profile_name": "Nom",
"profile_email": "Adreça electronica",
"send_again": "Tornar enviar",
"confirmation_sent_long": "Ligam de confirmacion enviat. Mercés de verificar vòstres corrièrs.",
"confirmation_sent_another": "Un autre ligam de confirmacion enviat.",
"confirmation_sent_dialog_text": "Avèm enviat un corrièl quexplica cossí confirmar vòstra adreça electronica.",
"payment_caption": "Pagament",
"language_caption": "Lenga",
"notifications_caption": "Notificacions",
"notifications_option_chat": "Enviatz-me de comentaris novèls per corrièl",
"notifications_option_spaces": "Enviatz-me un resumit jornadièr de las modificacions dels espacis",
"password_caption": "Senhal",
"current_password": "Senhal actual",
"new_password": "Senhal novèl",
"verify_password": "Verificar lo senhal novèl",
"change_password": "Modificar senhal",
"reset_password": "Reïnicializar senhal",
"terminate_caption": "Suprimir lo compte",
"terminate_warning": "En escafant vòstre compte, vòstres messatges, espacis, repertòris e lor contengut seràn suprimits. Aquesta accion pòt pas èsser anullada.",
"terminate_warning2": "Aquò pòt pas èsser anullat.",
"terminate_reason": "Messatge",
"terminate_reason_caption": "Ajudatz-nos a melhorar lo logicial en nos diguent las rasons de la supression de vòstre compte",
"terminate_terminate": "Suprimir vòstre compte per totjorn?",
"space_blank1": "Aquò es vòstre novèl espaci",
"space_blank2": "Lisatz de fichièrs, pegatz de ligams",
"space_blank3": "o utilizatz las aisinas",
"space_blank4": "Siatz creatius!",
"draft": "Borrolhon",
"publish": "Publicar",
"published": "Publicat",
"save_version": "Enregistrar version",
"version_saved": "Version enregistrada",
"post": "Publicar messatge",
"chat_invite_cta1": "Collaboratz amb amusament!",
"chat_invite_cta2": "Perqué pas",
"chat_invite_cta3": "convidar de monde",
"chat_invite_cta4": "per trabalhar amb vos?",
"chat_message_placeholder": "Escrivètz vòstre messatge…",
"view": "Afichatge",
"edit": "Edicion",
"present": "Present",
"chat": "Messatjariá",
"meta": "Mèta",
"tool_search": "Recercar",
"tool_upload": "Enviar",
"tool_text": "Tèxte",
"tool_shape": "Forma",
"tool_zones": "Zònas",
"tool_canvas": "Canvas",
"search_media": "Cercar de mèdias…",
"type_here": "Picatz aquí",
"text_formats": "Formats",
"format_p": "Paragraph",
"format_bullets": "Lista a piuses",
"format_numbers": "Lista numeratada",
"format_h1": "Títol 1",
"format_h2": "Títol 2",
"format_h3": "Títol 3",
"font_size": "Font Size",
"line_height": "Nnautor de linha",
"tool_align": "Alinhar",
"tool_styles": "Estils",
"tool_bullets": "Bullets",
"tool_numbers": "Nombres",
"color_fill": "Fill",
"color_stroke": "Traçat",
"color_text": "Tèxte",
"tool_type": "Tipe",
"tool_box": "Bóstia",
"tool_link": "Ligam",
"tool_layout": "Agençament",
"tool_options": "Opcions",
"tool_stroke": "Traçat",
"tool_delete": "Suprimir",
"tool_lock": "Verrolhar",
"tool_copy": "Copiar",
"stack": "Pila",
"tool_circle": "Cercle",
"tool_hexagon": "Exagòn",
"tool_square": "Carrat",
"tool_diamond": "Diamond",
"tool_bubble": "Bulla",
"tool_cloud": "Nívol",
"tool_burst": "Burst",
"tool_star": "Star",
"tool_heart": "Còr",
"tool_scribble": "Barbolhatge",
"tool_line": "Linha",
"tool_arrow": "Sageta",
"search_media_placeholder": "Cercar de mèdias web…",
"add_zone": "Zòna novèla",
"palette": "Paleta",
"picker": "Pipeta",
"background_image_caption": "Imatge",
"background_color_caption": "Color",
"upload_background_caption": "Clicar per enviar un imatge de rèireplan",
"upload_background": "Enviar rèireplan",
"access_caption": "Accès",
"versions_caption": "Versions",
"info_caption": "Info",
"mode_private": "Privat: sonque los membres pòdon veire o modificar",
"mode_public": "Public: qual que siá amb lo ligam pòt veire",
"invite_collaborators": "Convidar collaborators",
"revoke_access": "Revocar laccès",
"invite": "Enviar invitacions",
"invitee_email_address": "Adreça electronica del novèl membre",
"optional_message": "Messatge opcional",
"role_viewer": "Visualizaira",
"role_editor": "Editor",
"role_admin": "Admin",
"new_space_title": "Títol novèl per lEspaci",
"team": "Equipa",
"search": "Recercar",
"search_no_results": "search_no_results",
"search_clear": "search_clear",
"rename": "Renomenar",
"mobile": "mobil",
"image": "imatge",
"tool_filter": "filtre",
"canel": "canel",
"invite_membership_action": "invite_membership_action",
"viewer": "visualizaira",
"editor": "editor",
"admin": "admin",
"logging_in": "connexion",
"password_confirmation": "Confirmacion del senhla",
"confirm_again": "Mercés de consultar vòstra bóstia de recepcion per confirmar vòstra adreça.",
"confirmed": "Vòstre compte es estat corrèctament confirmat. Mercés.",
"signing_up": "Inscripcion",
"password_check_inbox": "Verificatz vòstra bóstia de recepcion",
"new_space": "Espaci novèl",
"tool_more": "Mai",
"what_is_your_name": "La benvenguda a %s! Mercés de causir un escais-nom.",
"lang": "en",
"landing_title": "Vòstre tablèu blanc sul Web.",
"landing_claim": "Spacedeck vos permet de facilament combinar quin que siá tipe de mèdias sus un tablèu virtual: tèxte, nòtas, ligams web, amai vidèos e enregistraments àudio. ",
"landing_example": "Lo monde utiliza Spacedeck per organizar lors idèas, en equipa per veire totes los projèctes en una ulhada, a lescòla e a luniversitat pels mai rics, experiéncia daprendissatge connectat.",
"spaces": "Mos espacis",
"access_editor_link": "Ligam de modificacion dirècta",
"access_editor_link_desc": "Donatz aqueste ligam a qualquun que deu poder modificar dirèctament aqueste Espaci, cap de compte pas requerit: ",
"access_editor_link_desc_slug": "Aqueste ligam conten lo nom de lespaci, tanben. ",
"access_anonymous_edit_blocking": "Los convidats pòdon pas modificar los elements quan creats.",
"access_current_members": "Membres actuals",
"access_new_members": "Convidar de novèls membres",
"access_no_members": "Los membres daqueste Espacii apreissaràn aquí.",
"comments": "comentaris",
"landing_customers": "La fisança de milièr de personas.",
"landing_features_title": "Un jòc d'enfants dutilizar.",
"landing_features_text": "Le tout nouveau Spacedeck 5 vous permet de travailler bien plus facilement grâce à sa magnifique interface simplifiée.",
"landing_features_1": "Glissez & déposez images, vidéos et audios de votre ordinateur ou du web",
"landing_features_2": "Ecrivez directement sur l'espace et choisissez les polices de caractère, couleurs et styles",
"landing_features_3": "Dessinez, annotez et surlignez grâce aux formes graphiques intégrées",
"landing_features_4": "Transformez votre espace en une présentation dynamique",
"landing_features_5": "Collaborez et discutez en temps réel avec vos collègues, élèves et amis",
"landing_features_6": "Partagez vos espaces sur le web ou par email",
"landing_features_7": "Exportez votre espace en PDF pour l'imprimer",
"landing_pricing": "Incroyablement abordable.",
"landing_pricing_lite": "Usage personnel",
"landing_pricing_lite_text": "La version de base, bien arrondi pour recueillir des images et de garder des notes.",
"landing_pricing_pro_features_list": "<ul><li>Unlimited Spaces</li><li>Exporter PDF, ZIP</li><li>No Watermarks</li><li>Image de fonds</li><li>Activity History</li><li>20 Go de stockage</li><ul>",
"landing_pricing_pro": "€4,90/User/Mo. <br><small> €49,90/User/Year</small>",
"landing_pricing_pro_text": "Avec toute la puissance que vous attendez.",
"landing_pricing_pro_features": "€4,90/User/Mo. <br><small> €49,90/User/Year</small>",
"welcome_subject": "La benvenguda a Spacedeck",
"welcome_body": "Mercés per vòstra inscripcion a Spacedeck.\nEsperam quauretz plaser a trabalhar dins los Espacis. <br> Oblidetz pas que vòstre compte conten un nombre illimitat de collaborators. <br> Esitetz pas a partejar vòstres espacis amb los amics e collègas del monde entièr.",
"invite_emails": "Picatz las adreças mails (separadas per de vergulas)",
"history_recently_updated": "Novèlas",
"history_recently_empty": "Pas res",
"parent_folder": "Repertòri parent",
"created_by": "Creat per",
"last_updated": "Darrièra mesa a jorn",
"feedback_sent": "Comentari enviat",
"role_member": "Membre",
"team_invite_membership_action": "Acceptar",
"space_message_subject": "A publicat sus %s",
"space_message_body": "%s a comentat dins %s:\n",
"pro_ad_history_headline": "Aprèp una mesa a nivèl podètz obténer un apercebut de totas las activitats actualas dels espacis aquí.",
"password_reset_subject": "Reïnicializar lo senhal per Spacedeck",
"password_reset_body": "Òu!<br><br>Avètz demandat la reïnicializacion del senhal.<br>Mercés de clicar sul ligam seguent per ne causir un novèl.<br>",
"password_reset_action": "Reïnicializar ara",
"was_offline": "La connexion a Spacedeck es estada copada. Savètz de trabalh pas enregistratz, gardatz aqueste onglet de navigador dobèrt fins que la connexion siá restablida puèi tocatz de nòu los elements pas enregistrats.",
"subscription_failed_user_subject": "Problèma amb lo pagament Spacedeck",
"subscription_failed_user_body": "Unfortunately, we could not process your Payment-method. You can easly create a new payment method including PayPal in your account settings.",
"subscription_failed_team_subject": "Problem with your Spacedeck Payment",
"subscription_failed_team_body": "Unfortunately, we could not process your Payment-method for your Team-Account. Please fix your payment method asap.",
"team_name": "Nom de lequipa",
"subdomain": "jos-domeni",
"team_adresses": "Adreças equipa",
"add": "Ajustar",
"invited": "convidat",
"duplicate_destination": "Seleccionatz lo repertòri de destinacion",
"duplicate_confirm": "Duplicar %s dins %s?",
"duplicate_success": "%s es estat duplicat dins %s.",
"goto_space": "anar a lespaci",
"goto_folder": "anar al repertòri",
"stay_here": "Demorar aquí",
"sharing": "partatge",
"list": "lista",
"link": "Ligam",
"download_space": "Telecargar espaci",
"type": "Tipe",
"download": "Telecargar",
"Previous Zone": "Zòna precedenta",
"Next Zone": "Zòna seguenta",
"promote": "Promòure",
"demote": "Retrogradar",
"more": "Mai",
"lock": "Verrolhar",
"unlock": "Desverrolhar",
"follow_present": "Seguir",
"mute_present": "Quitar de seguir",
"follow_present_help": "follow_present_help",
"export": "exportar"
}

File diff suppressed because it is too large Load Diff

View File

@@ -9,12 +9,19 @@ SpacedeckAccount = {
account_tab: 'invoices',
password_change_error: null,
feedback_text: "",
importables: [], // spacedeck.com zip import files
},
methods: {
show_account: function() {
this.activate_dropdown('account');
},
start_zip_import: function(f) {
if (confirm("Your archive will be imported in the background. This can take a few minutes. You can continue using Spacedeck in the meantime.")) {
import_zip(this.user, f);
}
},
account_save_user_digest: function(val) {
this.user.prefs_email_digest = val;
this.save_user(function() {

View File

@@ -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.toString());
medium.value(a.description);
}
}
},
@@ -88,11 +88,10 @@ var SpacedeckBoardArtifacts = {
},
artifact_is_text_blank: function(a) {
if (a.description) {
desc = a.description.toString();
var filtered = desc.replace(/<[^>]+>/g,"").replace(/\s/g,"");
if(a.description){
var filtered = a.description.replace(/<[^>]+>/g,"").replace(/\s/g,"");
return (filtered.length<1);
} else {
}else{
return false;
}
},
@@ -233,7 +232,7 @@ var SpacedeckBoardArtifacts = {
},
artifact_thumbnail_uri: function(a) {
if (a.payload_thumbnail_big_uri) {
if (a.payload_thumbnail_big_uri && a.board) {
if (a.w>800) {
return a.payload_thumbnail_big_uri;
}
@@ -360,8 +359,7 @@ var SpacedeckBoardArtifacts = {
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.z; });
if (max_z.z) {
if (max_z.board) {
max_z = max_z.z + 1;
} else {
max_z = 1;
@@ -379,13 +377,13 @@ var SpacedeckBoardArtifacts = {
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.z; });
if (min_z.z) {
if (min_z.board) {
min_z = min_z.z - 1;
} else {
min_z = 0;
}
var my_z = _.max(this.selected_artifacts(),function(a){ return a.z; });
if (my_z.z) {
if (my_z.board) {
my_z = my_z.z - 1;
} else {
my_z = 0;
@@ -588,7 +586,7 @@ var SpacedeckBoardArtifacts = {
var selected = this.selected_artifacts();
if (selected.length<2) return;
var sorted = _.sortBy(selected, function(a) { return a.x+a.y*this.active_space.width }.bind(this));
var sorted = _.sortBy(selected, function(a) { return a.x+a.y*this.active_space.advanced.width }.bind(this));
var minx = sorted[0].x;
var miny = sorted[0].y;
@@ -597,13 +595,11 @@ var SpacedeckBoardArtifacts = {
var blocks = [];
var padding = 10;
for (var i=0; i<sorted.length; i++) {
var a = sorted[i];
blocks.push({
w: a.w+padding,
h: a.h+padding,
w: a.w,
h: a.h,
a: a
});
}

View File

@@ -17,21 +17,6 @@ var SpacedeckRoutes = {
}.bind(this)
}
]);
this.router.add([
{
path: "/s/:hash",
handler: function(params, on_success) {
var parts = params.hash.split("-");
if (path.length > 0) {
this.load_space(parts.slice(1).join("-"), on_success, null, parts[0]);
} else {
// FIXME error handling
on_success();
}
}.bind(this)
}
]);
this.router.add([
{
@@ -267,6 +252,8 @@ var SpacedeckRoutes = {
// #hash
if (event.currentTarget.hash && event.currentTarget.hash.length>1) return;
console.log("clicked", event.currentTarget.pathname);
// external link?
if (event.currentTarget.host != location.host) return;
@@ -282,6 +269,35 @@ var SpacedeckRoutes = {
event.preventDefault();
}.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);
},

View File

@@ -63,8 +63,8 @@ var SpacedeckSections = {
active_style: {
border_radius: 0,
stroke: 0,
font_family: "Inter",
font_size: 36,
font_family: "Avenir W01",
font_size: 18,
line_height: 1.5,
letter_spacing: 0,
@@ -110,32 +110,20 @@ var SpacedeckSections = {
color_picker_opacity: 255,
swatches: [
{id:1, hex:"#ff00ff"},
{id:2, hex:"#ffff00"},
{id:3, hex:"#00ffff"},
{id:5, hex:"#ff0000"},
{id:6, hex:"#00ff00"},
{id:7, hex:"#0000ff"},
{id:8, hex:"#000000"},
{id:9, hex:"#222222"},
{id:10, hex:"#444444"},
{id:11, hex:"#888888"},
{id:12, hex:"#bbbbbb"},
{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)"},
{id:0, hex:"#4a2f7e"},
{id:1, hex:"#9b59b6"},
{id:2, hex:"#3498db"},
{id:3, hex:"#2ecc71"},
{id:4, hex:"#f1c40f"},
{id:5, hex:"#e67e22"},
{id:6, hex:"#d55c4b"},
{id:7, hex:"#6f4021"},
{id:8, hex:"#ffffff"},
{id:9, hex:"#95a5a6"},
{id:10, hex:"#252525"},
{id:11, hex:"rgba(0,0,0,0)"},
],
swatches_text: [
{id:1, hex:"#9b59b6"},
{id:2, hex:"#3498db"},
@@ -148,8 +136,18 @@ var SpacedeckSections = {
],
fonts: [
"Inter",
"Courier"
"Arial",
"Courier",
"Georgia",
"Verdana",
"Comic Sans MS",
"Montserrat",
"Lato",
"Roboto",
"Crimson Text",
"EB Garamond",
"Vollkorn",
"Avenir W01"
],
detected_text_formats: {},
@@ -182,7 +180,7 @@ var SpacedeckSections = {
toolbar_props_in: false,
toolbar_artifacts_x: "-1000px",
toolbar_artifacts_y: "-1000px",
toolbar_artifacts_in: true
toolbar_artifacts_in: false
},
methods: {
@@ -197,7 +195,6 @@ var SpacedeckSections = {
Mousetrap.bind('del', function(evt) { this.if_editable(function() {this.delete_selected_artifacts(evt);}) }.bind(this));
Mousetrap.bind('backspace', function(evt) { this.if_editable(function() {this.delete_selected_artifacts(evt);}) }.bind(this));
Mousetrap.bind('esc', function(evt) { this.deselect(); this.deactivate_tool(); }.bind(this));
Mousetrap.bind(['command+d', 'ctrl+d' ], function(evt) { evt.preventDefault(); evt.stopPropagation(); this.if_editable(function() {this.duplicate_selected_artifacts();}) }.bind(this));
Mousetrap.bind(['command+z', 'ctrl+z' ], function(evt) { this.if_editable(function() {this.undo();}) }.bind(this));
Mousetrap.bind(['command+shift+z','ctrl+shift+z'], function(evt) { this.if_editable(function() {this.redo();}) }.bind(this));
@@ -216,7 +213,7 @@ var SpacedeckSections = {
Mousetrap.bind('shift+left', function(evt) { this.if_editable(function() {this.nudge_selected_artifacts(-10,0,evt);}) }.bind(this));
Mousetrap.bind('shift+right', function(evt) { this.if_editable(function() {this.nudge_selected_artifacts(10,0,evt);}) }.bind(this));
Mousetrap.bind('space', function(evt) { this.activate_pan_tool(evt); }.bind(this));
$(document).bind("beforecopy", this.handle_onbeforecopy.bind(this));
$(window).bind("beforeunload", this.handle_onunload.bind(this));
$(window).bind("resize", this.handle_window_resize.bind(this));
@@ -406,12 +403,7 @@ var SpacedeckSections = {
}
if (space.space_type == "folder") return "";
var query_string = "";
if (space_auth) {
query_string+="?spaceAuth="+space.edit_hash;
}
return "background-image:url('/api/spaces/"+space._id+"/png"+query_string+"')";
return "background-image:url('/api/spaces/"+space._id+"/png')";
},
reset_artifact_filters: function() {
@@ -434,7 +426,7 @@ var SpacedeckSections = {
extract_properties_from_selection: function() {
// stop extract->apply feedback loop
this.skip_formatting = true;
var arts = this.selected_artifacts();
window.setTimeout(function() {
this.skip_formatting = false;
@@ -593,6 +585,8 @@ var SpacedeckSections = {
evt.preventDefault();
}
this.active_tool = "pointer";
if (this.opened_dialog == id) {
this.opened_dialog = "none";
return;
@@ -601,7 +595,7 @@ var SpacedeckSections = {
if (_.contains(["mobile","shapes","zones"],id)) {
this.deselect();
}
this.opened_dialog=id;
if (id.match("color") || id.match("background")) {
@@ -665,6 +659,14 @@ var SpacedeckSections = {
},100);
},
handle_section_keydown: function(evt) {
if (evt.keyCode == 67 && (evt.ctrlKey || evt.metaKey)) { // c key
this.prepare_clipboard();
this.prepare_clipboard_step2();
}
return true;
},
handle_onbeforecopy: function(evt) {
if (this.editing_artifact_id) return;
@@ -719,7 +721,7 @@ var SpacedeckSections = {
presenter_send_viewport: function() {
name = this.user.nickname || this.user.email;
var msg = {
action: "viewport",
x: this.scroll_left,
@@ -734,14 +736,14 @@ var SpacedeckSections = {
var packed = JSON.stringify(msg);
if (packed==this._old_viewport_msg) return;
this._old_viewport_msg = packed;
if (this.present_mode && this.active_space_role!="viewer")
this.websocket_send(msg);
},
presenter_send_media_action: function(artifact_id,type,cmd,time) {
name = this.user.nickname || this.user.email;
var msg = {
action: "media",
artifact_id: artifact_id,
@@ -788,6 +790,7 @@ var SpacedeckSections = {
},
handle_user_cursor_update: function(msg) {
// console.log("handle cursor", msg);
var now = new Date().getTime();
msg.t = now;
var existing = false;
@@ -799,6 +802,7 @@ var SpacedeckSections = {
u.y = msg.y;
u.t = now;
u.name = msg.name;
// console.log("updated cursor "+i);
existing = true;
} else {
// hide if no updates since 2sec
@@ -820,7 +824,7 @@ var SpacedeckSections = {
y2: msg.y+msg.h
});
},
handle_presenter_media_update: function(msg) {
if(this.follow_mode) {
if (msg.type=="audio") {
@@ -847,7 +851,7 @@ var SpacedeckSections = {
may_select: function(a) {
if (!a) return false;
if (!this.active_space) return false;
if (this.active_space_role=="viewer" || (a.locked && this.active_space_role!="admin")) {
return false;
}
@@ -864,7 +868,7 @@ var SpacedeckSections = {
if (evt && !evt.shiftKey && this.is_selected(a)) return; // already selected
if (!evt || !evt.shiftKey) {
this.selected_artifacts_dict = {};
this.deselect();
}
if (evt && evt.shiftKey) {
@@ -1024,7 +1028,7 @@ var SpacedeckSections = {
this.selection_metrics.count = 1;
return;
}
var sr = this.selection_rect() || {x:0,y:0,w:0,h:0,style:"display:none"};
if (sr.x1 || sr.x2) {
@@ -1052,8 +1056,8 @@ var SpacedeckSections = {
// FIXME make sure that menus fit in window
this.toolbar_props_x = pp.x+"px";
this.toolbar_props_y = pp.y+"px";
//this.hide_toolbar_artifacts();
this.hide_toolbar_artifacts();
}
this.selection_metrics.x1 = sr.x1;
@@ -1121,9 +1125,8 @@ var SpacedeckSections = {
var er = this.enclosing_rect(this.active_space_artifacts);
if (!er) return;
// resize space
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);
this.active_space.width =Math.max(er.x2+100, window.innerWidth);
this.active_space.height=Math.max(er.y2+100, window.innerHeight);
if (this._last_bounds_width != this.active_space.width ||
this._last_bounds_height != this.active_space.height) {
@@ -1341,7 +1344,7 @@ var SpacedeckSections = {
this.update_selected_artifacts(function(a) {
var c = {};
if (a[prop] != val) {
//console.log("set_artifact_style_prop: ",c,val);
c[prop]=val;
@@ -1496,7 +1499,7 @@ var SpacedeckSections = {
var tw = window.innerWidth;
var th = window.innerHeight;
var el = $("#space")[0];
if (!el) return {x:0,y:0,z:1}; // FIXME
var wrap = $(".wrapper");
@@ -1541,8 +1544,8 @@ var SpacedeckSections = {
add_artifact: function (space, item_type, url, evt) {
this.active_tool = "pointer";
this.mouse_state = "idle";
//this.hide_toolbar_artifacts();
this.hide_toolbar_artifacts();
if (!url && (item_type == 'image' || item_type == 'video' || item_type == 'embed')) {
url = prompt("URL?");
if (!url || !url.length) return;
@@ -1602,8 +1605,12 @@ var SpacedeckSections = {
if (this.guest_nickname) {
new_item.editor_name = this.guest_nickname;
}
// console.log("new artifact", new_item);
save_artifact(new_item, function(saved_item) {
// console.log("saved artifact", saved_item);
this.update_board_artifact_viewmodel(saved_item);
this.active_space_artifacts.push(saved_item);
@@ -1711,15 +1718,15 @@ var SpacedeckSections = {
w = 400;
}
//var point = this.find_place_for_item(w,h);
var point = this.cursor_point_to_space(evt);
point.z = this.highest_z()+1;
var a = {
space_id: this.active_space._id,
mime: "x-spacedeck/shape",
description: "",
x: point.x+w/2,
y: point.y+h/2,
description: "Text",
x: point.x,
y: point.y,
z: point.z,
w: w,
h: h,
@@ -1729,7 +1736,7 @@ var SpacedeckSections = {
fill_color: "#000000",
shape: shape_type,
valign: "middle",
align: "center",
align: "center"
};
if (this.guest_nickname) {
@@ -1769,7 +1776,7 @@ var SpacedeckSections = {
space_point_to_window: function(x,y) {
var rx = 0;
var ry = 0;
var el = $("#space")[0];
rx = x*this.viewport_zoom-el.scrollLeft+this.bounds_margin_horiz;
ry = y*this.viewport_zoom-el.scrollTop+this.bounds_margin_vert;
@@ -1781,7 +1788,9 @@ var SpacedeckSections = {
if (this.active_space_role=="viewer") {
return false;
}
this.hide_toolbar_artifacts();
// 1. create placeholder artifact
var w=300,h=150;
var fill="transparent";
@@ -1849,7 +1858,7 @@ var SpacedeckSections = {
// upload progress
var progress = e.loaded/e.total;
if (progress===1) {
if (progress=1) {
a.description = "Converting Media…";
} else {
a.description = "Upload "+parseInt(progress*100)+"%";
@@ -2087,7 +2096,7 @@ var SpacedeckSections = {
a.description = dom.innerHTML;
console.log("new DOM:",dom.innerHTML);
this.update_board_artifact_viewmodel(a);
this.queue_artifact_for_save(a);
@@ -2105,12 +2114,12 @@ var SpacedeckSections = {
// text level selection
var a = this.find_artifact_by_id(this.editing_artifact_id);
var medium = this.medium_for_object[a._id];
if (medium && a) {
medium.focus();
medium.element.focus();
medium.invokeElement(cmd);
a.description = medium.value();
this.queue_artifact_for_save(a);
}
@@ -2148,7 +2157,7 @@ var SpacedeckSections = {
update.payload_thumbnail_medium_uri = thumb_uri;
update.payload_thumbnail_big_uri = thumb_uri;
}
return update;
});
@@ -2283,7 +2292,12 @@ var SpacedeckSections = {
}
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);
},
@@ -2296,7 +2310,7 @@ var SpacedeckSections = {
try {
parsed = JSON.parse(text);
if (text[0]=='{') parsed = [parsed];
this.deselect();
for (var i=0; i<parsed.length; i++) {
@@ -2330,11 +2344,37 @@ var SpacedeckSections = {
this.create_artifact_via_embed_url(text);
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.x = point.x;
new_item.y = point.y;
new_item.w = w;
new_item.h = h;
new_item.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) {
this.close_modal();
var point = this.find_place_for_item(200,200);
var z = this.highest_z()+1;
@@ -2352,7 +2392,7 @@ var SpacedeckSections = {
}
var metadata = parse_link(url)
if (!metadata) {
return;
}
@@ -2488,9 +2528,18 @@ var SpacedeckSections = {
this.opened_dialog = "none";
if (files && files.length) {
console.log("file: ",files[0]);
for (var i=0; i<files.length; i++) {
var file = files[i];
this.create_artifact_via_upload(null, file, true);
if (file.type === "application/pdf") {
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);
}
}
}
},
@@ -2527,56 +2576,46 @@ var SpacedeckSections = {
}
this.toolbar_props_in = true;
},
hide_toolbar_props: function() {
// FIXME test
//this.toolbar_props_in = false;
this.toolbar_props_in = false;
},
show_toolbar_artifacts: function(x,y) {
this.toolbar_artifacts_x = (x-175)+"px";
this.toolbar_artifacts_y = y+"px";
this.toolbar_artifacts_in = true;
},
hide_toolbar_artifacts: function() {
this.toolbar_artifacts_in = false;
},
deactivate_tool: function(evt) {
this.active_tool = "pointer";
},
start_adding_artifact: function(evt) {
evt = fixup_touches(evt);
},
start_adding_note: function(evt) {
this.deselect();
if (this.active_tool == "note") {
this.active_tool = "pointer";
} else {
this.active_tool = "note";
// toggle
if (this.toolbar_artifacts_in) {
this.hide_toolbar_artifacts();
return;
}
this.opened_dialog = "none";
this.show_toolbar_artifacts(evt.pageX,evt.pageY);
},
start_drawing_scribble: function(evt) {
this.deselect();
if (this.active_tool == "scribble") {
this.active_tool = "pointer";
} else {
this.active_tool = "scribble";
}
this.hide_toolbar_artifacts();
this.active_tool = "scribble";
this.opened_dialog = "none";
},
start_drawing_arrow: function(evt) {
this.deselect();
this.hide_toolbar_artifacts();
this.active_tool = "arrow";
this.opened_dialog = "none";
},
start_drawing_line: function(evt) {
this.deselect();
this.hide_toolbar_artifacts();
this.active_tool = "line";
this.opened_dialog = "none";
},
@@ -2638,7 +2677,7 @@ var SpacedeckSections = {
x2: (el.scrollLeft+window.innerWidth)/this.viewport_zoom,
y2: (el.scrollTop+window.innerHeight)/this.viewport_zoom
};
var pad = 10;
er.x1-=pad;
er.y1-=pad;
@@ -2704,7 +2743,7 @@ var SpacedeckSections = {
var el = $("#space")[0];
var anim_res = 20;
if (!elapsed) elapsed = 0;
if (duration>elapsed) {
window.setTimeout(function() {
this.animate_zoom_to_rect(target_r, duration, cur_r, elapsed+anim_res);
@@ -2743,7 +2782,7 @@ var SpacedeckSections = {
zoom_to_point: function(p,amount) {
var el = $("#space")[0];
var sx = el.scrollLeft/this.viewport_zoom;
var sy = el.scrollTop/this.viewport_zoom;
var ww = window.innerWidth/(this.viewport_zoom);
@@ -2753,7 +2792,7 @@ var SpacedeckSections = {
var oyy = (p.y-(sy+wh/2))*amount;
var ox = -oxx;
var oy = -oyy;
var r = {
x1: p.x-(ww/2)*amount + ox,
y1: p.y-(wh/2)*amount + oy,
@@ -2777,12 +2816,12 @@ var SpacedeckSections = {
var adjust_scroll = function() {
if (!$("#space").length) return;
if (!this.active_space || !this.active_space_loaded) return;
var el = $("#space")[0];
var eff_w = this.active_space.width*this.viewport_zoom;
var eff_h = this.active_space.height*this.viewport_zoom;
var sx = el.scrollLeft;
var sy = el.scrollTop;
@@ -2855,6 +2894,32 @@ var SpacedeckSections = {
}.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) {
if (this.active_space_role=="viewer") {
return false;
@@ -2867,8 +2932,17 @@ var SpacedeckSections = {
if (files && files.length) {
for (var i=0; i<files.length; i++) {
var file = files[i];
this.create_artifact_via_upload(evt, file, (files.length>1));
if (file.type === "application/pdf") {
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 {
var json = evt.dataTransfer.getData('application/json');
@@ -2908,7 +2982,7 @@ var SpacedeckSections = {
}
var html = evt.dataTransfer.getData('text/html');
if (html) {
var rx = /src="([^"]+)"/g;
var m = rx.exec(html);

View File

@@ -18,6 +18,8 @@ var SpacedeckSpaces = {
active_space_path: [],
access_settings_space: null,
access_settings_memberships: [],
duplicate_folders: [],
duplicate_folder_id: "",
pending_pdf_files: [],
meta_visible: false,
@@ -69,7 +71,9 @@ var SpacedeckSpaces = {
methods: {
search_spaces: function() {
var query = this.folder_spaces_search;
console.log("search query: ",query);
load_spaces_search(query, function(spaces) {
console.log("results: ",spaces);
this.active_profile_spaces = spaces;
}.bind(this));
},
@@ -81,7 +85,14 @@ var SpacedeckSpaces = {
location.reload();
},
ask_guestname: function(dft, cb) {
smoke.prompt(__('what_is_your_name', "Spacedeck") , function(content) {
console.log("ask_guestname");
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)) {
this.ask_guestname(dft, cb);
} else {
@@ -90,7 +101,7 @@ var SpacedeckSpaces = {
if ("localStorage" in window && localStorage) {
try {
localStorage['guest_nickname'] = this.guest_nickname;
} catch(e) {
}catch(e) {
console.error(e);
}
}
@@ -99,19 +110,36 @@ var SpacedeckSpaces = {
}.bind(this), {value: dft || "Guest "+parseInt(10000*Math.random()), ok: __("ok"), cancel: __("cancel")});
},
load_space: function(space_id, on_success, on_error, space_auth) {
load_space: function(space_id, on_success, on_error) {
console.log("load space: ", space_id);
this.folder_spaces_filter="";
this.folder_spaces_search="";
if (space_auth) {
set_space_auth(space_auth);
} else {
set_space_auth(get_query_param("spaceAuth"));
}
this.embedded = !!(get_query_param("embedded"));
space_auth = get_query_param("spaceAuth");
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.active_space_loaded = false;
@@ -139,9 +167,12 @@ var SpacedeckSpaces = {
load_space(space_id, function(space, role) {
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) {
//console.log(space);
load_members(space, function(members) {
this.active_space_memberships = members;
}.bind(this));
@@ -252,9 +283,9 @@ var SpacedeckSpaces = {
this.discover_zones();
window.setTimeout(function() {
this.zoom_to_fit();
}.bind(this),10);
//window.setTimeout(function() {
// this.zoom_to_fit();
//}.bind(this),10);
if (on_success) {
on_success();
@@ -280,6 +311,15 @@ var SpacedeckSpaces = {
// FIXME
this.active_join_link = "";
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) {
@@ -308,7 +348,7 @@ var SpacedeckSpaces = {
userReady();
}
if (!this.user && space_auth) {
if (space_auth) {
if (this.guest_nickname) {
userReady();
} else {
@@ -614,11 +654,10 @@ var SpacedeckSpaces = {
},
download_space_as_pdf: function(space) {
this.close_dropdown();
this.global_spinner = true;
get_resource("/spaces/" + space._id + "/pdf", function(o) {
this.global_spinner = false;
window.open(o.url, "_blank");
location.href = o.url;
}.bind(this), function(xhr) {
this.global_spinner = false;
alert("PDF export problem (" + xhr.status + ").");
@@ -644,6 +683,47 @@ var SpacedeckSpaces = {
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() {
this.deselect();
this.follow_mode = !this.follow_mode;
@@ -654,13 +734,6 @@ var SpacedeckSpaces = {
this.present_mode = !this.present_mode;
if (this.present_mode) {
//this.go_to_first_zone();
if (this.embedded) {
document.documentElement.requestFullscreen();
}
} else {
if (this.embedded) {
document.exitFullscreen();
}
}
},
@@ -756,12 +829,9 @@ var SpacedeckSpaces = {
this.invite_message = "";
}
}.bind(this), function(xhr){
try {
var res = JSON.parse(xhr.response);
alert("Error: "+res.error);
} catch (e) {
console.error(e, xhr);
}
text = JSON.stringify(xhr.responseText);
smoke.alert("Error: "+text);
}.bind(this));
}.bind(this));
},
@@ -769,13 +839,9 @@ var SpacedeckSpaces = {
update_member: function(space, m, role) {
m.role = role;
save_membership(space, m, function() {
console.log("saved")
}.bind(this), function(xhr) {
try {
var res = JSON.parse(xhr.response);
alert("Error: "+res.error);
} catch (e) {
console.error(e, xhr);
}
console.error(xhr);
}.bind(this));
},
@@ -784,12 +850,7 @@ var SpacedeckSpaces = {
delete_membership(space, m, function() {
this.access_settings_memberships.splice(this.access_settings_memberships.indexOf(m), 1);
}.bind(this), function(xhr) {
try {
var res = JSON.parse(xhr.response);
alert("Error: "+res.error);
} catch (e) {
console.error(e, xhr);
}
console.error(xhr);
}.bind(this));
},
@@ -825,6 +886,10 @@ var SpacedeckSpaces = {
}.bind(this));
},
emojified_comment: function(comment) {
return twemoji.parse(comment);
},
set_folder_sorting: function(key,reverse) {
this.folder_sorting = key;
this.folder_reverse = reverse?-1:1;

View File

@@ -11,12 +11,12 @@ SpacedeckUsers = {
login_email: "",
login_password: "",
signup_password: "",
signup_invite_code: "",
signup_password_confirmation: "",
account_remove_error: null,
loading_user: false,
password_reset_confirm_error: "",
password_reset_error: "",
},
methods:{
load_user: function(on_success, on_error) {
@@ -30,6 +30,12 @@ SpacedeckUsers = {
if (on_success) {
on_success(user);
}
// see spacedeck_account.js
load_importables(this.user, function(files) {
this.importables = files;
}.bind(this));
}.bind(this), function() {
// error
this.loading_user = false;
@@ -42,6 +48,10 @@ SpacedeckUsers = {
},
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) {
if (this.invitation_token) {
accept_invitation(this.invitation_token, function(memberships){
@@ -116,7 +126,7 @@ SpacedeckUsers = {
signup_guest: function(on_success) {
},
signup_submit: function($event, name, email, password, password_confirmation, invite_code, on_success) {
signup_submit: function($event, name, email, password, password_confirmation, on_success) {
this.creating_user = true;
this.signup_error = null;
@@ -130,7 +140,7 @@ SpacedeckUsers = {
$event.stopPropagation();
}
create_user(name, email, password, password_confirmation, invite_code, function(session) {
create_user(name, email, password, password_confirmation, function(session) {
this.creating_user = false;
this.login_submit(email, password, null, on_success);
}.bind(this), function(req) {
@@ -146,8 +156,8 @@ SpacedeckUsers = {
}.bind(this));
},
signup_submit_modal: function($event, name, email, password, password_confirmation, invite_code) {
this.signup_submit($event, name, email, password, password_confirmation, invite_code, function() {
signup_submit_modal: function($event, name, email, password, password_confirmation) {
this.signup_submit($event, name, email, password, password_confirmation, function() {
alert("Success.");
location.reload();
});
@@ -195,29 +205,27 @@ SpacedeckUsers = {
this.password_reset_confirm_error = null;
this.password_reset_send = false;
if (password != password_confirmation) {
if(password != password_confirmation) {
this.password_reset_confirm_error = "Passwords do not match.";
return;
}
if (password.length < 5) {
if(password.length < 5) {
this.password_reset_confirm_error = "Password too short (must have at least 5 characters).";
return;
}
confirm_password_reset(password, this.reset_token, function(parsed,req) {
if (req.status==201) {
alert("New password set successfully.");
if(req.status==201){
this.active_view = "login";
} else {
alert("An unknown error occured.");
}
}.bind(this), function(req) {
if (req.status==404) {
alert("Error: Unknown user.");
var msg = "user not found";
} else {
alert("Error: "+req.statusText);
var msg = "error: " + req.statusText;
}
this.password_reset_confirm_error = msg;
}.bind(this));
},

View File

@@ -14,7 +14,6 @@ function boot_spacedeck() {
account: "profile",
logged_in: false,
guest_nickname: null,
embedded: false,
user: {},
active_profile: null,

View File

@@ -40,11 +40,7 @@ SpacedeckWebsockets = {
}
} else console.log("artifact created in another space.");
}
else if ((msg.action == "update" || msg.action == "update-self") && msg.object) {
if (msg.action == "update-self") {
console.log(msg.object);
}
else if (msg.action == "update" && msg.object) {
if (this.active_space) {
var o = msg.object;
if (o && o._id) {
@@ -63,13 +59,13 @@ SpacedeckWebsockets = {
else if (msg.action == "delete" && msg.object) {
if (this.active_space) {
var o = msg.object;
if (o._id){
if(o._id){
var existing_artifact = this.find_artifact_by_id(o._id);
if (existing_artifact) {
var idx = this.active_space_artifacts.indexOf(existing_artifact);
this.active_space_artifacts.splice(idx, 1);
} else console.log("existing artifact to delete not found");
} else console.error("object without _id");
}else console.error("object without _id");
}
}
}
@@ -105,13 +101,11 @@ SpacedeckWebsockets = {
}
if (this.websocket && this.websocket.readyState==1) {
var token = "";
if (this.user) token = this.user.token;
var auth_params = {
action: "auth",
editor_auth: space_auth,
editor_name: this.guest_nickname,
auth_token: token,
auth_token: window.socket_auth,
space_id: space._id
};
console.log("[websocket] auth space");
@@ -189,7 +183,7 @@ SpacedeckWebsockets = {
return;
}
if (msg.channel_id == channel_id && !msg.action.match("-self")) {
if (msg.channel_id == channel_id) {
return;
}
@@ -203,7 +197,7 @@ SpacedeckWebsockets = {
this.handle_presenter_media_update(msg);
}
if (msg.action == "update" || msg.action == "update-self" || msg.action == "create" || msg.action == "delete") {
if (msg.action == "update" || msg.action == "create" || msg.action == "delete"){
this.handle_live_updates(msg);
}
@@ -236,13 +230,13 @@ SpacedeckWebsockets = {
return (u && (u._id != this.user._id));
}.bind(this));
}
users = _.filter(users, function(u) {
return (u && (u._id || u.nickname));
});
this.users_online[spaceId] = users;
if (this.active_space) {
if (this.active_space._id == spaceId) {
this.active_space_users = users;

View File

@@ -5,10 +5,7 @@
*/
function setup_whiteboard_directives() {
var mode_touch = false;
if ('ontouchstart' in window) {
mode_touch = true;
var edown = "touchstart";
var emove = "touchmove";
var eup = "touchend";
@@ -18,12 +15,6 @@ function setup_whiteboard_directives() {
var eup = "mouseup";
}
// detect first touch event
window.addEventListener('touchstart', function on_first_touch() {
mode_touch = true;
window.removeEventListener('touchstart', on_first_touch, false);
}, false);
Vue.directive('sd-whiteboard', {
bind: function () {
var el = this.el;
@@ -32,12 +23,9 @@ function setup_whiteboard_directives() {
$(el).on("dblclick", ".artifact", this.handle_double_click_artifact.bind(this));
$(el).on("keyup", ".artifact", this.handle_key_up_artifact.bind(this));
$(el).on("keydown", ".artifact", this.handle_key_down_artifact.bind(this));
$(el).bind("touchstart", this.handle_mouse_down_space.bind(this));
$(el).bind("touchmove", this.handle_mouse_move.bind(this));
$(el).bind("touchend", this.handle_mouse_up_space.bind(this));
$(el).bind("mousedown", this.handle_mouse_down_space.bind(this));
$(el).bind("mousemove", this.handle_mouse_move.bind(this));
$(el).bind("mouseup", this.handle_mouse_up_space.bind(this));
$(el).bind(edown, this.handle_mouse_down_space.bind(this));
$(el).bind(emove, this.handle_mouse_move.bind(this));
$(el).bind(eup, this.handle_mouse_up_space.bind(this));
$(el).bind("wheel", this.handle_wheel_space.bind(this));
@@ -92,21 +80,10 @@ function setup_whiteboard_directives() {
evt.stopPropagation();
}
if ($scope.active_tool == "zoom") return;
if (evt.which == 2) {
// middle mouse button
this.handle_mouse_down_space(evt);
return;
}
if ($scope.active_tool == "note") {
this.handle_mouse_down_space(evt, true);
return;
}
var a = $scope.find_artifact_by_id(evt.currentTarget.id.replace("artifact-",""));
if ($scope.active_tool == "zoom") return;
if ($scope.active_tool == "eyedrop") {
var arts = $scope.selected_artifacts();
if (!$scope.is_selected(a) && arts.length > 0) {
@@ -218,11 +195,9 @@ function setup_whiteboard_directives() {
$scope.zoom_to_cursor(evt,amount);
},
handle_mouse_down_space: function(evt, force) {
if (!force && evt.which != 2) {
if (evt.target != evt.currentTarget && !_.include(["wrapper"],evt.target.className)) return;
}
handle_mouse_down_space: function(evt) {
if (evt.target != evt.currentTarget && !_.include(["wrapper"],evt.target.className)) return;
var $scope = this.vm.$root;
$scope.opened_dialog="none";
@@ -231,7 +206,7 @@ function setup_whiteboard_directives() {
$scope.mouse_ox = cursor.x;
$scope.mouse_oy = cursor.y;
if ((mode_touch && $scope.active_tool=="pointer") || evt.which == 2 || evt.buttons == 4) {
if (evt.which == 2 || evt.buttons == 4) {
$scope.active_tool = "pan";
}
@@ -239,7 +214,7 @@ function setup_whiteboard_directives() {
this.deselect();
this.mouse_state = "transform";
$scope.mouse_state = this.mouse_state;
this.start_drawing_note(evt);
this.start_adding_note(evt);
return;
} else if ($scope.active_tool=="arrow") {
@@ -379,10 +354,12 @@ function setup_whiteboard_directives() {
var lasso_scaled = {
x:this.lasso.x,
y:this.lasso.y,
w:this.lasso.w,
h:this.lasso.h
w:this.lasso.w*$scope.viewport_zoom,
h:this.lasso.h*$scope.viewport_zoom
}
lasso_scaled = this.abs_rect(lasso_scaled);
lasso_scaled.x += $scope.bounds_margin_horiz;
lasso_scaled.y += $scope.bounds_margin_vert;
var s = "left:" +lasso_scaled.x+"px;";
s += "top:" +lasso_scaled.y+"px;";
@@ -402,15 +379,15 @@ function setup_whiteboard_directives() {
$("#lasso").show();
},
// Translate the mouse cursor location from device window coordinates to virtual space coordinates
cursor_point_to_space: function(evt) {
var $scope = this.vm.$root;
var offset = {left: 0, top: 0};
evt = fixup_touches(evt);
return {
x: $scope.scroll_left + (parseInt(evt.pageX) - $scope.bounds_margin_horiz) / $scope.viewport_zoom,
y: $scope.scroll_top + (parseInt(evt.pageY) - $scope.bounds_margin_vert) / $scope.viewport_zoom
x: (parseInt(evt.pageX) - parseInt(offset.left) - $scope.bounds_margin_horiz) / this.space_zoom,
y: (parseInt(evt.pageY) - parseInt(offset.top) - $scope.bounds_margin_vert) / this.space_zoom
};
},
@@ -515,7 +492,6 @@ function setup_whiteboard_directives() {
if (!xdists[0] || xdists[0][0]>TOL) {
results.snapx = [0,x]; // distance, coordinate
} else {
// FIXME snap rulers are broken
//$scope.snap_ruler_x = xdists[0][1];
}
if (!ydists[0] || ydists[0][0]>TOL) {
@@ -527,45 +503,17 @@ function setup_whiteboard_directives() {
return results;
},
start_drawing_note: function(evt) {
evt.preventDefault();
evt.stopPropagation();
offset_point_in_wrapper: function(point) {
var $scope = this.vm.$root;
var point = this.cursor_point_to_space(evt);
var z = $scope.highest_z()+1;
var section_el = $(this.el)[0];
var z = $scope.viewport_zoom;
var note_w = 250;
var note_h = 250;
var pt = parseInt($("#space").css("padding-top"));
var a = {
space_id: $scope.active_space._id,
mime: "text/html",
description: "<p>Text</p>",
x: point.x-note_w/2,
y: point.y-note_h/2,
z: z,
w: note_w,
h: note_h,
align: "center",
valign: "middle",
stroke_color: "#000000",
fill_color: "rgb(241, 196, 15)",
stroke: 0,
padding_left: 10,
padding_right: 10,
padding_top: 10,
padding_bottom: 10
};
point.y=(point.y+section_el.scrollTop-pt)/z;
point.x=(point.x+section_el.scrollLeft)/z;
$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));
return point;
},
start_drawing_scribble: function(evt) {
@@ -573,7 +521,7 @@ function setup_whiteboard_directives() {
evt.stopPropagation();
var $scope = this.vm.$root;
var point = this.cursor_point_to_space(evt);
var point = this.offset_point_in_wrapper(this.cursor_point_to_space(evt));
var z = $scope.highest_z()+1;
$scope.deselect();
@@ -588,7 +536,7 @@ function setup_whiteboard_directives() {
z: z,
w: 64,
h: 64,
stroke_color: $scope.active_style.stroke_color,
stroke_color: "#000000",
stroke: 2,
shape: "scribble"
};
@@ -612,6 +560,7 @@ function setup_whiteboard_directives() {
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 = {
@@ -624,7 +573,7 @@ function setup_whiteboard_directives() {
z: z,
w: 64,
h: 64,
stroke_color: $scope.active_style.stroke_color,
stroke_color: "#000000",
stroke: 2,
shape: "arrow"
};
@@ -647,6 +596,7 @@ function setup_whiteboard_directives() {
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 = {
@@ -689,7 +639,8 @@ function setup_whiteboard_directives() {
evt.preventDefault();
if (this.mouse_state == "lasso") {
var lasso_rect = this.abs_rect(this.lasso);
var lasso_rect = this.abs_rect(this.offset_point_in_wrapper(this.lasso));
// convert to space coordinates
if (lasso_rect.w>0 && lasso_rect.h>0) {
var arts = this.artifacts_in_rect(lasso_rect);
@@ -728,7 +679,7 @@ function setup_whiteboard_directives() {
return;
}
if (_.include(["zoom", "scribble"], $scope.active_tool)) {
if (_.include(["zoom"], $scope.active_tool)) {
// tools that stay active after use
this.mouse_state = "idle";
$scope.mouse_state = this.mouse_state;
@@ -768,12 +719,18 @@ function setup_whiteboard_directives() {
$scope.handle_scroll();
var cursor = this.cursor_point_to_space(evt); // takes the raw event data and finds the mouse location in virtual space
var cursor = this.cursor_point_to_space(evt);
var dx = cursor.x - $scope.mouse_ox;
var dy = cursor.y - $scope.mouse_oy;
var dt = (new Date()).getTime() - this.last_mouse_move_time;
this.last_mouse_move_time = (new Date()).getTime();
var zoom = $scope.viewport_zoom||1;
if (zoom) {
dx/=zoom;
dy/=zoom;
}
// send cursor
if (dx>10 || dy>10 || dt>100) {
var name = "anonymous";
@@ -785,8 +742,8 @@ function setup_whiteboard_directives() {
var cursor_msg = {
action: "cursor",
x: cursor.x,
y: cursor.y,
x: cursor.x/zoom,
y: cursor.y/zoom,
name: name,
id: $scope.user._id||name
};
@@ -894,7 +851,7 @@ function setup_whiteboard_directives() {
var scale_x = lead_x ? (moved_x)/lead_x : 1;
var scale_y = lead_y ? (moved_y)/lead_y : 1;
if ($scope.transform_lock) scale_y = scale_x;
if (!$scope.transform_lock) scale_y = scale_x;
$scope.update_selected_artifacts(function(a) {
var old_a = $scope.find_artifact_before_transaction(a);
@@ -956,7 +913,7 @@ function setup_whiteboard_directives() {
var old_a = a;
var control_points = _.cloneDeep(old_a.control_points);
var offset = {x:cursor.x,y:cursor.y};
var offset = this.offset_point_in_wrapper({x:cursor.x,y:cursor.y});
control_points.push({
dx: offset.x-old_a.x,
@@ -976,8 +933,8 @@ function setup_whiteboard_directives() {
if (!$("#space").length) return;
el = $("#space")[0];
el.scrollLeft -= dx*$scope.viewport_zoom;
el.scrollTop -= dy*$scope.viewport_zoom;
el.scrollLeft = this.old_panx - dx*$scope.viewport_zoom;
el.scrollTop = this.old_pany - dy*$scope.viewport_zoom;
$scope.handle_scroll();
}

View File

@@ -121,100 +121,33 @@ function render_vector_drawing(a, padding) {
}
function render_vector_star(tips,width,height,stroke) {
//A 5-pointed (5 tips) regular star of radius from center to tip of 1 has a box around it of width = 2 cos(pi/10) and height = 1 + cos(pi/5)
// assuming the star is oriented with one point directly above the center.
// So the center of the star is at width * 1/2 and height * 0.552786 which is 1 / (1 + cos(pi/5)) (also assuming the y-axis is inverted).
// The inner points are at radius 0.381966 = sin(pi/10)/cos(pi/5).
// Fortunately with simple transformations with matrices, we can do rotations and scales easily.
// See https://en.wikipedia.org/wiki/Rotation_matrix for details.
// But because the stroke is done after scaling (it's not scaled), we have to adjust the points after the rotation and scaling happens.
//A 10-pointed regular star is simpler because it is vertically symmetrical.
function render_vector_star(edges,xradius,yradius,offset) {
//NOTE: for very thick stroke widths, and small stars, the star might render very strangely!
var xcenter = width/2;
var ycenter = 0;
var inner_radius = 0;
if (tips == 5) {
ycenter = height * 0.552786;
inner_radius = 0.381966; //scale compared to outer_radius of 1.0
} else {
//tips == 10
ycenter = height/2;
inner_radius = 0.7; //scale compared to outer_radius of 1.0
}
// Coordinates of the first tip, and the first inner corner
var xtip = 1; // radius 1
var ytip = 0;
var xinner = inner_radius * Math.cos(Math.PI/(tips==5?5:10));
var yinner = inner_radius * Math.sin(Math.PI/(tips==5?5:10));
edges *= 2;
var points = [];
var degrees = 360 / edges;
for (var i=0; i < edges; i++) {
var a = i * degrees - 90;
var xr = xradius;
var yr = yradius;
// var tmp_outside_points = []; // uncomment to see the calculated edge of the star (outside the stroke width)
if (i%2) {
if (edges==20) {
xr/=1.5;
yr/=1.5;
} else {
xr/=2.8;
yr/=2.8;
}
}
var angle = 2*Math.PI / tips;
// generate points without offset from stroke width first
for (var i=0; i < tips; i++) {
var a = i * angle - Math.PI/2;
// Tip first...
// Rotate the outer tip around the origin:
var x = xtip * Math.cos(a); // because ytip = 0 we don't include: - ytip * Math.sin(a);
var y = xtip * Math.sin(a); // because ytip = 0 we don't include: + ytip * Math.cos(a);
// Scale for the bounding box:
x = x * width / (2 * Math.cos(Math.PI/10));
y = y * height / (tips==5?(1 + Math.cos(Math.PI/5)):2);
points.push([x,y]);
// tmp_outside_points.push(x+" "+y); // uncomment to see the calculated edge of the star (outside the stroke width)
// Now the inner corner...
// Rotate the inner corner around the origin:
x = xinner * Math.cos(a) - yinner * Math.sin(a);
y = xinner * Math.sin(a) + yinner * Math.cos(a);
// Scale for the bounding box:
x = x * width / (2 * Math.cos(Math.PI/10));
y = y * height / (tips==5?(1 + Math.cos(Math.PI/5)):2);
points.push([x,y]);
// tmp_outside_points.push(x+" "+y); // uncomment to see the calculated edge of the star (outside the stroke width)
var x = offset + xradius + xr * Math.cos(a * Math.PI / 180);
var y = offset + yradius + yr * Math.sin(a * Math.PI / 180);
points.push(x+","+y);
}
var inset_points = [];
for (var i=0; i < points.length; i++) {
var pA = points[(((i-1)%points.length)+points.length)%points.length]; // Javascript modulus "bug"
var p0 = points[i];
var pB = points[(i+1)%points.length];
var dAx = p0[0] - pA[0];
var dAy = p0[1] - pA[1];
var dBx = p0[0] - pB[0];
var dBy = p0[1] - pB[1];
var dBLength = Math.sqrt(dBx**2 + dBy**2);
// The trig here is a bit hairy. Basically, finding the inset points is done by finding the angle (theta)
// between the tips and the neighboring inner corners (or vice versa). Then, that angle is used to
// calculate vector scaling factors for half the thickness of the stroked path. Which then is used to find
// the actual inset points for the tips and inner corners.
var theta = Math.atan2(dAx*dBy-dAy*dBx, dAx*dBx + dAy*dBy); // angle between the vectors
var theta = (i%2? Math.PI * 2 - theta : theta);
var stroke_prime = dBLength * Math.tan(theta/2); // this is really a scaling factor
var xprime = p0[0] + (i%2?-1:1)*((stroke/2)/stroke_prime)*dBx + dBy*(stroke/2)/dBLength;
var yprime = p0[1] + (i%2?-1:1)*((stroke/2)/stroke_prime)*dBy + -1 * dBx*(stroke/2)/dBLength;;
inset_points.push(xprime+","+yprime);
}
// NOTE: use svg transformations to center the thing
return "<polygon stroke-miterlimit='64' points='"+inset_points.join(" ")+"' transform='translate(" + xcenter + " " + ycenter + ")'/>";
// Append these if you want to see what is being calculated.
// The cyan dashed line is the outside of the star including the stroke width.
// The red dashed line is just the star polygon points themselves.
// "<polygon stroke-width='4' stroke='red' stroke-dasharray='16 12' fill-opacity='0' points='"+inset_points.join(" ")+"' transform='translate(" + xcenter + " " + ycenter + ")'/>" +
// "<polygon stroke-width='4' stroke='cyan' stroke-dasharray='16 12' fill-opacity='0' points='"+tmp_outside_points.join(" ")+"' transform='translate(" + xcenter + " " + ycenter + ")'/>";
return "<polygon points='"+points.join(" ")+"'/>";
}
function transform_vector_template(cmds, xr, yr, offset) {
@@ -318,8 +251,8 @@ function render_vector_shape(a) {
diamond: function() { return render_vector_ngon(4, xr, yr, offset); },
square: function() { return "" },
triangle: function() { return render_vector_ngon(3, xr, yr, offset); },
star: function() { return render_vector_star(5, a.w, a.h, a.stroke); },
burst: function() { return render_vector_star(10, a.w, a.h, a.stroke); },
star: function() { return render_vector_star(5, xr, yr, offset); },
burst: function() { return render_vector_star(10, xr, yr, offset); },
speechbubble: function() { return render_vector_speechbubble(xr, yr, offset); },
heart: function() { return render_vector_heart(xr, yr, offset); },
cloud: function() { return render_vector_cloud(xr, yr, offset); },

File diff suppressed because it is too large Load Diff

View File

@@ -23,14 +23,15 @@ router.post('/', function(req, res) {
db.User.findOne({where: {email: email}})
.error(err => {
res.sendStatus(404);
//res.status(400).json({"error":"session.users"});
})
.then(user => {
if (!user) {
res.sendStatus(404);
}
else if (bcrypt.compareSync(password, user.password_hash)) {
console.log("!!! user: ",user.password_hash);
if (bcrypt.compareSync(password, user.password_hash)) {
crypto.randomBytes(48, function(ex, buf) {
var token = buf.toString('hex');
console.log("!!! token: ",token);
var session = {
user_id: user._id,
@@ -46,7 +47,7 @@ router.post('/', function(req, res) {
res.sendStatus(500);
})
.then(() => {
var domain = (process.env.NODE_ENV == "production") ? new URL(config.get('endpoint')).hostname : req.headers.hostname;
var domain = (process.env.NODE_ENV == "production") ? new URL(config.get('endpoint')).hostname : "localhost";
res.cookie('sdsession', token, { domain: domain, httpOnly: true });
res.status(201).json(session);
});
@@ -59,14 +60,16 @@ router.post('/', function(req, res) {
router.delete('/current', function(req, res, next) {
if (req.user) {
var token = req.cookies['sdsession'];
db.Session.findOne({where: {token: token}})
.then(session => {
session.destroy();
});
var domain = (process.env.NODE_ENV == "production") ? new URL(config.get('endpoint')).hostname : req.headers.hostname;
res.clearCookie('sdsession', { domain: domain });
res.sendStatus(204);
/*var user = req.user;
var newSessions = user.sessions.filter( function(session){
return session.token != req.token;
});*/
//user.sessions = newSessions;
//user.save(function(err, result) {
var domain = new URL(config.get('endpoint')).hostname;
res.clearCookie('sdsession', { domain: domain });
res.sendStatus(204);
//});
} else {
res.sendStatus(404);
}

View File

@@ -53,8 +53,15 @@ router.get('/', (req, res) => {
space_id: req.space._id
}}).then(artifacts => {
async.map(artifacts, (a, cb) => {
db.unpackArtifact(a);
//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);
}
if (a.user_id) {
// FIXME JOIN
/*User.findOne({where: {
@@ -89,7 +96,7 @@ router.post('/', function(req, res, next) {
var artifact = attrs;
artifact._id = uuidv4();
if (req.user) {
artifact.user_id = req.user._id;
artifact.last_update_user_id = req.user._id;
@@ -114,7 +121,7 @@ router.post('/', function(req, res, next) {
});
router.post('/:artifact_id/payload', function(req, res, next) {
if (req.spaceRole == "editor" || req.spaceRole == "admin") {
if (req.spaceRole == "editor"  ||  req.spaceRole == "admin") {
var a = req.artifact;
var fileName = (req.query.filename || "upload.bin").replace(/[^a-zA-Z0-9_\-\.]/g, '');
@@ -123,22 +130,10 @@ router.post('/:artifact_id/payload', function(req, res, next) {
var writeStream = fs.createWriteStream(localFilePath);
var stream = req.pipe(writeStream);
var progressCallback = function(progressMsg) {
// merge progress message with any other changes (size/location)
db.Artifact.findOne({where: {
_id: a._id
}}).then(a => {
if (a) {
a.description = progressMsg.toString();
db.packArtifact(a);
a.save();
db.unpackArtifact(a);
redis.sendMessage("update-self", "Artifact", a, req.channelId);
} else {
// artifact has been deleted
// TODO: stop conversion process!
}
});
var progress_callback = function(progress_msg) {
a.description = progress_msg;
a.save();
redis.sendMessage("update", a, a.toJSON(), req.channelId);
};
stream.on('finish', function() {
@@ -146,10 +141,9 @@ router.post('/:artifact_id/payload', function(req, res, next) {
if (error) res.status(400).json(error);
else {
db.Space.update({ updated_at: new Date() }, {where: {_id: req.space._id}});
db.unpackArtifact(artifact);
res.distributeUpdate("Artifact", artifact, true);
res.distributeUpdate("Artifact", artifact);
}
}, progressCallback);
}, progress_callback);
});
} else {
res.status(401).json({
@@ -169,7 +163,7 @@ router.put('/:artifact_id', function(req, res, next) {
} else {
newAttr.last_update_editor_name = req.editor_name;
}
db.packArtifact(newAttr);
db.Artifact.update(newAttr, { where: {
@@ -177,7 +171,6 @@ router.put('/:artifact_id', function(req, res, next) {
}}).then(rows => {
db.unpackArtifact(newAttr);
db.Space.update({ updated_at: new Date() }, {where: {_id: req.space._id} });
newAttr._id = a._id;
res.distributeUpdate("Artifact", newAttr);
});
});

View File

@@ -138,6 +138,7 @@ router.get('/', function(req, res, next) {
"$exists": 1
}
}).populate("space").exec(function(err, memberships) {
async.map(memberships, function(membership, memcb) {
Space.getRecursiveSubspacesForSpace(membership.space, function(err, spaces) {
cb(null, spaces.map(function(s) {

View File

@@ -51,7 +51,8 @@ router.get('/png', function(req, res, next) {
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 }});
phantom.takeScreenshot(req.space, "png", function(local_path) {
phantom.takeScreenshot(req.space, "png",
function(local_path) {
var localResizedFilePath = local_path + ".thumb.jpg";
gm(local_path).resize(640, 480).quality(70.0).autoOrient().write(localResizedFilePath, function(err) {
@@ -78,14 +79,14 @@ router.get('/png', function(req, res, next) {
var oldPath = url.parse(oldUrl).pathname;
uploader.removeFile(oldPath, function(err, res) {});
}
fs.unlinkSync(local_path);
fs.unlink(local_path);
} catch (e) {
console.error(e);
}
});
try {
fs.unlinkSync(localResizedFilePath);
fs.unlink(localResizedFilePath);
} catch (e) {
console.error(e);
}
@@ -94,7 +95,7 @@ router.get('/png', function(req, res, next) {
},
function() {
// on_error
console.error("[space screenshot] could not create screenshot for space " + req.space_id);
console.error("phantom could not create screenshot for space " + req.space_id);
res.status(404).send("Not found");
});
} else {
@@ -114,12 +115,7 @@ router.get('/pdf', function(req, res, next) {
res.status(201).json({
url: url
});
fs.unlink(local_path, function(){
if (err) console.log('unlink', err);
else {
console.log('unlink', local_path);
}
});
fs.unlink(local_path);
});
}, (err) => {
res.status(500).json({
@@ -244,6 +240,7 @@ router.get('/zip', function(req, res, next) {
});
router.get('/html', function(req, res) {
console.log("!!!!! hello ");
db.Artifact.findAll({where: {
space_id: req.space._id
}}).then(function(artifacts) {

View File

@@ -45,12 +45,10 @@ router.post('/', function(req, res, next) {
"email": membership.email_invited
}}).then(function(user) {
// existing user? then immediately activate membership
if (user) {
membership.user_id = user._id;
membership.state = "active";
} else {
// if not, invite via email and invite code
membership.code = crypto.randomBytes(64).toString('hex').substring(0, 12);
}
@@ -86,13 +84,13 @@ router.post('/', function(req, res, next) {
} else {
res.status(400).json({
"error": "This email is already included in the Space memberships."
"error": "user already in space"
});
}
} else {
res.status(403).json({
"error": "Only administrators can do that."
"error": "not_permitted"
});
}
});
@@ -104,18 +102,11 @@ router.put('/:membership_id', function(req, res, next) {
_id: req.params.membership_id
}}).then(function(mem) {
if (mem) {
// is the user trying to change their own role?
if (mem.user_id == req.user._id) {
res.status(400).json({
"error": "Cannot change your own role."
});
} else {
var attrs = req.body;
mem.role = attrs.role;
mem.save(function() {
res.status(201).json(mem);
});
}
var attrs = req.body;
mem.role = attrs.role;
mem.save(function() {
res.status(201).json(mem);
});
}
});
} else {
@@ -127,25 +118,13 @@ router.put('/:membership_id', function(req, res, next) {
});
router.delete('/:membership_id', function(req, res, next) {
if (req.user && req.spaceRole == 'admin') {
db.Membership.count({ where: {
space_id: req.space._id,
role: "admin"
}}).then(function(adminCount) {
db.Membership.findOne({ where: {
_id: req.params.membership_id
}}).then(function(mem) {
// deleting an admin? need at least 1
if (mem.role != "admin" || adminCount > 1) {
mem.destroy().then(function() {
res.sendStatus(204);
});
} else {
res.status(400).json({
"error": "Space needs at least one administrator."
});
}
})
if (req.user) {
db.Membership.findOne({ where: {
_id: req.params.membership_id
}}).then(function(mem) {
mem.destroy().then(function() {
res.sendStatus(204);
});
});
} else {
res.sendStatus(403);

View File

@@ -1,6 +1,5 @@
"use strict";
var config = require('config');
const os = require('os');
const db = require('../../models/db');
const Sequelize = require('sequelize');
const Op = Sequelize.Op;
@@ -42,63 +41,80 @@ var spaceMapping = {
thumbnail_url: 1
};
function listSpacesInFolder(req, res, parent_space_id) {
db.Space
.findOne({where: {
_id: parent_space_id
}})
.then(function(space) {
if (space) {
function spacesForRole(role) {
if (role == "none") {
if (space.access_mode == "public") {
role = "viewer";
}
}
if (role != "none") {
db.Space
.findAll({where:{
parent_space_id: parent_space_id
}, include:[db.CreatorSafeInclude(db)]})
.then(function(spaces) {
res.status(200).json(spaces);
});
} else {
res.status(403).json({"error": "not authorized"});
}
}
if (req["spaceAuth"] && space.edit_hash) {
// TODO could be editor, too
spacesForRole("none");
} else {
db.getUserRoleInSpace(space, req.user, spacesForRole);
}
} else {
res.status(404).json({"error": "space not found"});
}
});
}
router.get('/', function(req, res, next) {
if (req.query.parent_space_id && req["spaceAuth"]) {
// list subspaces of a space authorized anonymously
listSpacesInFolder(req, res, req.query.parent_space_id);
return;
}
if (!req.user) {
res.status(403).json({
error: "auth required"
});
} else {
if (req.query.search) {
if (req.query.writablefolders) {
db.Membership.find({where: {
user_id: req.user._id
}}, (memberships) => {
var validMemberships = memberships.filter((m) => {
if (!m.space_id || (m.space_id == "undefined"))
return false;
return true;
});
var editorMemberships = validMemberships.filter((m) => {
return (m.role == "editor") || (m.role == "admin")
});
var spaceIds = editorMemberships.map(function(m) {
return m.space_id;
});
// TODO port
var q = {
"space_type": "folder",
"$or": [{
"creator": req.user._id
}, {
"_id": {
"$in": spaceIds
},
"creator": {
"$ne": req.user._id
}
}]
};
db.Space
.findAll({where: q})
.then(function(spaces) {
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;
})
res.status(200).json(uniqueFolders);
});
});
});
} else if (req.query.search) {
db.Membership.findAll({where:{
user_id: req.user._id
}}).then(memberships => {
// search for spaces
var validMemberships = memberships.filter(function(m) {
if (!m.space_id || (m.space_id == "undefined"))
return false;
@@ -116,7 +132,7 @@ router.get('/', function(req, res, next) {
{"_id": {[Op.in]: spaceIds}},
{"parent_space_id": {[Op.in]: spaceIds}}],
name: {[Op.like]: "%"+req.query.search+"%"}
}, include: [db.CreatorSafeInclude(db)]};
}, include: ['creator']};
db.Space
.findAll(q)
@@ -126,21 +142,47 @@ router.get('/', function(req, res, next) {
});
} else if (req.query.parent_space_id && req.query.parent_space_id != req.user.home_folder_id) {
// list spaces in a folder
listSpacesInFolder(req, res, req.query.parent_space_id);
db.Space
.findOne({where: {
_id: req.query.parent_space_id
}})
//.populate('creator', userMapping)
.then(function(space) {
if (space) {
db.getUserRoleInSpace(space, req.user, function(role) {
if (role == "none") {
if (space.access_mode == "public") {
role = "viewer";
}
}
if (role != "none") {
db.Space
.findAll({where:{
parent_space_id: req.query.parent_space_id
}, include:['creator']})
.then(function(spaces) {
res.status(200).json(spaces);
});
} else {
res.status(403).json({"error": "no authorized"});
}
});
} else {
res.status(404).json({"error": "space not found"});
}
});
} else {
// list home folder and spaces/folders that the user is a member of
db.Membership.findAll({ where: {
user_id: req.user._id
}}).then(memberships => {
if (!memberships) memberships = [];
var validMemberships = memberships.filter(function(m) {
if (!m.space_id || (m.space_id == "undefined"))
return false;
return true;
});
var spaceIds = validMemberships.map(function(m) {
@@ -162,7 +204,7 @@ router.get('/', function(req, res, next) {
};
db.Space
.findAll({where: q, include: [db.CreatorSafeInclude(db)]})
.findAll({where: q, include: ['creator']})
.then(function(spaces) {
var updatedSpaces = spaces.map(function(s) {
var spaceObj = db.spaceToObject(s);
@@ -184,19 +226,15 @@ router.post('/', function(req, res, next) {
attrs._id = uuidv4();
attrs.creator_id = req.user._id;
attrs.edit_hash = crypto.randomBytes(64).toString('hex').substring(0, 7);
attrs.edit_slug = attrs.edit_slug || slug(attrs.name);
attrs.access_mode = "private";
attrs.edit_slug = slug(attrs.name);
db.Space.create(attrs).then(createdSpace => {
res.status(201).json(createdSpace);
// create initial admin membership
//if (err) res.sendStatus(400);
var membership = {
_id: uuidv4(),
user_id: req.user._id,
space_id: attrs._id,
role: "admin",
state: "active"
role: "admin"
};
db.Membership.create(membership).then(() => {
@@ -226,7 +264,6 @@ router.post('/', function(req, res, next) {
}
});
} else {
attrs.parent_space_id = req.user.home_folder_id;
createSpace();
}
@@ -276,26 +313,13 @@ router.put('/:id', function(req, res) {
newAttr.edit_slug = slug(newAttr['name']);
delete newAttr['_id'];
delete newAttr['editor_name'];
delete newAttr['creator'];
delete newAttr['creator_id'];
delete newAttr['space_type'];
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
}}).then(rows => {
db.Space.findOne({ where: {
"_id": space._id
}}).then(space => {
res.distributeUpdate("Space", space);
});
}}).then(space => {
res.distributeUpdate("Space", space);
});
});
@@ -314,7 +338,7 @@ router.post('/:id/background', function(req, res, next) {
if (space.background_uri) {
var oldPath = url.parse(req.space.background_uri).pathname;
uploader.removeFile(oldPath, function(err) {
console.error("remove old background error:", err);
console.error("removed old bg error:", err);
});
}
@@ -322,18 +346,13 @@ router.post('/:id/background', function(req, res, next) {
background_uri: backgroundUrl
}, {
where: { "_id": space._id }
}).then(rows => {
}, function(rows) {
fs.unlink(localFilePath, function(err) {
if (err) {
console.error(err);
res.status(400).json(err);
} else {
db.Space.findOne({ where: {
"_id": space._id
}}).then(space => {
console.log("========== space update:", space);
res.distributeUpdate("Space", space);
});
res.status(200).json(space);
}
});
});
@@ -342,6 +361,43 @@ router.post('/:id/background', function(req, res, next) {
});
});
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 duplicate"
});
} else {
db.getUserRoleInSpace(parentSpace, req.user, (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) {
if (req.user) {
const space = req.space;
@@ -361,4 +417,142 @@ 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";
// FIXME not portable
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) {
// FIXME not portable
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);
// FIXME not portable
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;

View File

@@ -3,7 +3,6 @@
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');
@@ -11,6 +10,7 @@ var importer = require('../../helpers/importer');
var bcrypt = require('bcryptjs');
var crypto = require('crypto');
var swig = require('swig');
var async = require('async');
var _ = require('underscore');
var fs = require('fs');
@@ -25,15 +25,8 @@ var glob = require('glob');
router.get('/current', function(req, res, next) {
if (req.user) {
var u = _.clone(req.user.dataValues);
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);
console.log(req.user.team);
res.status(200).json(req.user);
} else {
res.status(401).json({"error":"user_not_found"});
}
@@ -50,18 +43,12 @@ router.post('/', function(req, res) {
var nickname = req.body["nickname"];
var password = req.body["password"];
var password_confirmation = req.body["password_confirmation"];
var invite_code = req.body["invite_code"];
if (password_confirmation != password) {
res.status(400).json({"error":"password_confirmation"});
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;
@@ -88,31 +75,28 @@ router.post('/', function(req, res) {
res.sendStatus(400);
})
.then(u => {
var homeFolder = {
var homeSpace = {
_id: uuidv4(),
name: req.i18n.__("home"),
space_type: "folder",
creator_id: u._id
};
db.Space.create(homeFolder)
db.Space.create(homeSpace)
.error(err => {
res.sendStatus(400);
})
.then(homeFolder => {
u.home_folder_id = homeFolder._id;
.then(homeSpace => {
u.home_folder_id = homeSpace._id;
u.save()
.then(() => {
// home folder created,
// auto accept pending invites
db.Membership.update({
"state": "active"
}, {
where: {
"email_invited": u.email,
"state": "pending"
res.status(201).json({});
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")
}
});
res.status(201).json({});
})
.error(err => {
res.status(400).json(err);
@@ -127,6 +111,7 @@ router.post('/', function(req, res) {
db.User.findAll({where: {email: email}})
.then(users => {
if (users.length == 0) {
//var domain = email.slice(email.lastIndexOf('@')+1);
createUser();
} else {
res.status(400).json({"error":"user_email_already_used"});
@@ -175,35 +160,36 @@ router.post('/:id/password', function(req, res, next) {
});
});
} else {
res.status(403).json({"error": "Please enter the correct current password."});
res.status(403).json({"error": "old password wrong"});
}
} else {
res.status(403).json({"error": "Access denied."});
res.status(403).json({"error": "wrong user"});
}
} else {
res.status(400).json({"error": "Please choose a new password with at least 6 characters."});
res.status(400).json({"error": "password_to_short"});
}
});
router.delete('/:id', (req, res, next) => {
const user = req.user;
if (user._id == req.params.id) {
if (bcrypt.compareSync(req.query.password, user.password_hash)) {
// TODO: this doesn't currently work.
// all objects (indirectly) belonging to the user have
// to be walked and deleted first.
user.destroy().then(err => {
if(err)res.status(400).json(err);
if(user._id == req.params.id) {
if (user.account_type == 'email') {
if (bcrypt.compareSync(req.query.password, user.password_hash)) {
user.remove((err) => {
if(err)res.status(400).json(err);
else res.sendStatus(204);
});
} else {
res.bad_request("password_incorrect");
}
} else {
user.remove((err) => {
if (err) res.status(400).json(err);
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) => {
@@ -229,8 +215,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 = os.tmpdir()+"/"+filename;
const localResizedFilePath = os.tmpdir()+"/resized_"+filename;
const localFilePath = "/tmp/"+filename;
const localResizedFilePath = "/tmp/resized_"+filename;
const writeStream = fs.createWriteStream(localFilePath);
const stream = req.pipe(writeStream);
@@ -259,6 +245,13 @@ 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) => {
const email = req.query.email;
db.User.findOne({where: {"email": email}}).then((user) => {
@@ -282,16 +275,21 @@ router.post('/password_reset_requests', (req, res, next) => {
router.post('/password_reset_requests/:confirm_token/confirm', function(req, res, next) {
var password = req.body.password;
db.User
User
.findOne({where: {"password_reset_token": req.params.confirm_token}})
.then((user) => {
if (user) {
bcrypt.genSalt(10, (err, salt) => {
bcrypt.hash(password, salt, function(err, hash) {
user.password_hash = hash;
user.password_token = null;
user.save().then(function(updatedUser) {
res.sendStatus(201);
user.save(function(err, updatedUser){
if (err) {
res.sendStatus(400);
} else {
res.sendStatus(201);
}
});
});
});
@@ -309,4 +307,19 @@ router.post('/:user_id/confirm', function(req, res, next) {
res.sendStatus(201);
});
router.get('/:user_id/importables', function(req, res, next) {
glob('*.zip', function(err, files) {
res.status(200).json(files);
});
});
router.get('/:user_id/import', function(req, res, next) {
if (req.query.zip) {
res.send("importing");
importer.importZIP(req.user, req.query.zip);
} else {
res.sendStatus(400);
}
});
module.exports = router;

View File

@@ -1,6 +1,7 @@
"use strict";
const config = require('config');
require('../models/db');
const redis = require('../helpers/redis');
const express = require('express');
@@ -9,13 +10,8 @@ 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', { config:config, user:req.user });
res.render('index', { title: 'Spaces' });
});
router.get('/ping', (req, res) => {
@@ -23,35 +19,39 @@ router.get('/ping', (req, res) => {
});
router.get('/spaces', (req, res) => {
res.render('spacedeck', { config:config, user:req.user });
res.render('spacedeck', { title: 'Spaces' });
});
router.get('/not_found', (req, res) => {
res.render('not_found', {});
res.render('not_found', { title: 'Spaces' });
});
router.get('/confirm/:token', (req, res) => {
res.render('spacedeck', { config:config, user:req.user });
res.render('spacedeck', { title: 'Space' });
});
router.get('/folders/:id', (req, res) => {
res.render('spacedeck', { config:config, user:req.user });
res.render('spacedeck', {});
});
router.get('/signup', (req, res) => {
res.render('spacedeck', { config:config, user:req.user });
res.render('spacedeck', {});
});
router.get('/accept/:id', (req, res) => {
res.render('spacedeck', { config:config, user:req.user });
res.render('spacedeck', {});
});
router.get('/password-reset', (req, res) => {
res.render('spacedeck', { config:config, user:req.user });
res.render('spacedeck', { title: 'Signup' });
});
router.get('/password-confirm/:token', (req, res) => {
res.render('spacedeck', { config:config, user:req.user });
res.render('spacedeck', { title: 'Signup' });
});
router.get('/team', (req, res) => {
res.render('spacedeck');
});
router.get('/de/*', (req, res) => {
@@ -70,14 +70,6 @@ router.get('/fr', (req, res) => {
res.redirect("/t/fr");
});
router.get('/oc/*', (req, res) => {
res.redirect("/t/oc");
});
router.get('/oc', (req, res) => {
res.redirect("/t/oc");
});
router.get('/en/*', (req, res) => {
res.redirect("/t/en");
});
@@ -86,16 +78,36 @@ router.get('/en', (req, res) => {
res.redirect("/t/end");
});
router.get('/it', (req, res) => {
res.redirect("/t/en");
});
router.get('/account', (req, res) => {
res.render('spacedeck');
});
router.get('/login', (req, res) => {
res.render('spacedeck', { config:config, user:req.user });
res.render('spacedeck');
});
router.get('/logout', (req, res) => {
res.render('spacedeck', { config:config, user:req.user });
res.render('spacedeck');
});
router.get('/contact', (req, res) => {
res.render('public/contact');
});
router.get('/about', (req, res) => {
res.render('public/about');
});
router.get('/terms', (req, res) => {
res.render('public/terms');
});
router.get('/privacy', (req, res) => {
res.render('public/privacy');
});
router.get('/t/:id', (req, res) => {
@@ -107,31 +119,80 @@ router.get('/t/:id', (req, res) => {
res.redirect(path);
});
router.get('/s/:hash', (req, res) => {
var hash = req.params.hash;
if (hash.split("-").length > 0) {
hash = hash.split("-")[0];
}
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];
}
db.Space.findOne({where: {"edit_hash": hash}}).then(function (space) {
if (space) {
if (req.accepts('text/html')){
res.redirect("/spaces/"+space._id + "?spaceAuth=" + hash);
} else {
res.status(200).json(space);
}
Space.findOne({"edit_hash": token}).exec(function (err, space) {
if (err) {
res.status(404).render('not_found', { title: 'Page Not Found.' });
} else {
if (space) {
if(req.accepts('text/html')){
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 {
if (req.accepts('text/html')) {
res.status(404).render('not_found', {});
} else {
res.status(404).json({});
}
res.status(429).json({"error": "Too Many Requests"});
}
});
});
router.get('/spaces/:id', (req, res) => {
res.render('spacedeck', { config:config, user:req.user });
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;

View File

@@ -16,6 +16,7 @@ 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');
@@ -25,21 +26,27 @@ 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", "oc", "es"],
locales: ["en", "de", "fr"],
defaultLocale: "en",
cookieName: "spacedeck_locale",
devMode: (app.get('env') == 'development')
});
app.set('view engine', 'ejs');
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'));
@@ -61,18 +68,18 @@ app.use(bodyParser.urlencoded({
}));
app.use(cookieParser());
//app.use(helmet.frameguard())
//app.use(helmet.xssFilter())
/*app.use(helmet.hsts({
app.use(helmet.frameguard())
app.use(helmet.xssFilter())
app.use(helmet.hsts({
maxAge: 7776000000,
includeSubDomains: true
}))*/
includeSubdomains: true
}))
app.disable('x-powered-by');
//app.use(helmet.noSniff())
app.use(helmet.noSniff())
//app.use(require("./middlewares/error_helpers"));
//app.use(require("./middlewares/cors"));
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"));
@@ -104,6 +111,7 @@ if (config.get('storage_local_path')) {
//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'));
}
@@ -114,10 +122,9 @@ module.exports = app;
db.init();
// START WEBSERVER
const host = config.get('host');
const port = config.get('port');
const port = 9666;
const server = http.Server(app).listen(port, host, () => {
const server = http.Server(app).listen(port, () => {
if ("send" in process) {
process.send('online');

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";
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";
opacity: 0.25;
}*/
@@ -288,7 +288,7 @@
pointer-events: none;
}
}
.title {font-size: 20px; }
.image {
height: auto;
@@ -335,7 +335,7 @@
padding: 10px 15px;
font-size: 10px;
}
video {
width: 100%;
height: 100%;
@@ -469,10 +469,11 @@
color: black;
//@include user-select(none);
white-space: normal;
font-size: 36px;
font-size: 18px;
&.artifact-zone {
background-color: rgba(0,0,0,0.05);
border: 1px solid rgba(46,204,113,1);
background-color: rgba(46,204,113,0.025);
border-radius: 10px;
&:after {display: none; }
.shape {display: none; }
@@ -501,7 +502,7 @@ body:not(.present-mode) {
.Medium {
cursor: text;
}
.artifact {
&.selected.text-editing,
@@ -552,10 +553,6 @@ body:not(.present-mode) {
cursor: grab !important;
}
.tool-note {
cursor: crosshair !important;
}
.artifact.state-idle {
.progress, .progress-text {
display: none;
@@ -566,17 +563,16 @@ body:not(.present-mode) {
.progress {
height: 100%;
padding: 10px;
background-color: $blue;
opacity: 0.9;
text-align: center;
font-size: 14px;
}
.progress-text {
text-align: center;
padding: 8px;
width: 100%;
position: absolute;
left: 0px;
top: 0px;
@@ -587,8 +583,8 @@ body:not(.present-mode) {
video, audio, .tl-controls, .timeline {
display: none;
}
background-color: white;
}
@@ -629,7 +625,7 @@ body:not(.present-mode) {
img {
width: 200px;
}
.artifact {
display: block;
width: 200px;

View File

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

View File

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

View File

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

View File

@@ -254,6 +254,7 @@
// word-wrap: break-word;
.item {
box-shadow: 0 0 1pxrgba(0,0,0,0.1);
display: inline-block;
text-align: left;
padding-right: $folder-gutter*2;
@@ -396,10 +397,7 @@
&:active { opacity: 0.95 !important; }
box-shadow: 0 0 30px 1px rgba(0, 0, 0, 0.15);
border: 1px solid black;
// ???
box-shadow: 0 0 1px 1px rgba(0, 0, 0, 0.025), 0 2px 7px rgba(0, 0, 0, 0.025);
@include opacity(1);
color: $medium;
// color: white;
@@ -419,13 +417,14 @@
width: 100%;
overflow: hidden;
background-size: cover;
background-color: transparent;
border-top-left-radius: $radius*2;
border-top-right-radius: $radius*2;
border-bottom-left-radius: 10px;
border-bottom-right-radius: 10px;
background-position: left top;
background-position: center 100%;
background-repeat: no-repeat;
}
@@ -477,6 +476,7 @@
left: 0px;
z-index: 100;
width: auto;
background-color: rgba(255,255,255,1);
.dropdown {
position: absolute;
@@ -501,6 +501,30 @@
color: $dark;
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 {

View File

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

View File

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

View File

@@ -23,6 +23,7 @@ input:invalid {
top: 0;
right: 0;
line-height: 1;
font-size: 10px;
margin: 12px;
color: red;
margin-right: 25px;
@@ -112,26 +113,43 @@ select {
&.input-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);
}
&.input-light {
background-color: $light;
color: $medium;
}
&.input-dark {
background-color: $darker;
color: $medium;
}
&.input-lighten {
background-color: rgba(255,255,255,0.05);
color: $medium !important;
}
&.input-darken {
background-color: rgba(0,0,0,0.05);
color: $medium;
}
&.input-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();

View File

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

View File

@@ -5,6 +5,7 @@
.header-left,
.header-right {
position: absolute;
//@include transition( all 0.25s ease-in-out);
@include backface-visibility(hidden);
z-index: 3000;
top: 10px;
@@ -26,21 +27,21 @@
.home {
margin-top: -20px;
margin-left: -20px;
// .icon {color: $dark; }
}
.header-left {
left: 0;
padding-left: 10px;
padding-left: 20px;
padding-top: 20px;
@include transform-origin(center left);
left: 0;
padding-left: 10px;
}
.header-right {
right: 0;
padding-right: 20px;
padding-top: 20px;
.header-right {
@include transform-origin(center right);
right: 0;
padding-right: 10px;
}
.header-center {
@include transform-origin(center center);
width: 100%;
left: 0;
right: 0;
@@ -55,7 +56,7 @@
}
}
.header-left > * { margin-right: 10px; }
.header-right > * { margin-left: 10px; }
.header-right > * { margin-left: 5px; }
.header-right { font-size: 0;}
.title {
@@ -89,3 +90,21 @@
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,12 +85,3 @@
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,52 +1,257 @@
@import "vars";
#landing-header {
background-color: white;
height: 64px;
position: relative;
top: 0;
left: 0;
right: 0;
background-color: rgba(255,255,255,0.3);
height: 64px;
position: absolute;
top: 0;
left: 0;
right: 0;
}
#landing {
margin-top: 100px;
section {
margin-left: 300px;
> * {
max-width: 600px;
}
.landing-keyvisual-wrapper {
background-image: url("../images/sd5-keyvisual-compressed.jpg");
background-size: cover;
background-position: center;
padding-top: 40px;
padding-bottom: 40px;
}
.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 {
margin-left: 300px;
margin-top: 100px;
margin-bottom: 100px;
padding: 40px;
padding-bottom: 80px;
text-align: center;
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;
}
.header-right {
right: auto;
padding-left: 10px;
padding-right: 20px;
padding-top: 80px;
}
#folder-wrapper {
padding-top: 128px;
}
@media screen and (min-width: 801px) {
.plans-table-mobile {
display: none;
}
}
@media screen and (max-width: 800px) {
ul.lead.lead-xl, p.lead.lead-xl, ol.lead.lead-xl {
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,6 +2,7 @@
@import "mixins";
.wrapper {
//@include transition( all 0.25s ease-in-out);
position: relative;
margin: auto;
max-width: 1160px;

View File

@@ -59,9 +59,9 @@
}
.close {
margin-left: 44px;
margin-bottom: 44px;
.icon {display: block; }
position: fixed;
margin: 44px 44px;
.icon {display: block; }
}
figure {
@@ -135,6 +135,7 @@
outline: none;
display: inline-block;
text-align: left;
@include user-select(none);
border-radius: $radius*3;
background-color: $light !important;
@@ -145,6 +146,7 @@
.modal-header {
padding: 30px 40px;
position: relative;
color: $medium;
}
.close-search {
@@ -277,5 +279,25 @@
// Footer (for actions)
.modal-footer {
margin-top: 20px;
}
// border-bottom-left-radius: $radius;
// 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,5 +1,17 @@
/*! 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.
//
@@ -408,4 +420,4 @@ table {
td,
th {
padding: 0;
}
}

View File

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

View File

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

View File

@@ -118,9 +118,12 @@
padding: 0 !important;
.wrapper {
border: 1px dotted rgba(128,128,128,0.5);
transition-duration: 0.25s;
transition-property: width, height, background-color;
background-repeat: no-repeat;
background-size: initial;
background-position: 0 0;
background-size: cover;
}
width: 100%;
@@ -129,27 +132,32 @@
max-height: 100%;
position: relative;
overflow: scroll;
/** {
-moz-user-select: none !important; // firefox has selection problems
}*/
}
.snap-ruler-h {
pointer-events: none;
position: fixed;
z-index: 2000;
z-index: 0;
right: 0px;
height: 1px;
background-color: black;
background-color: rgba(0,0,0,0.5);
left: 0px;
}
.snap-ruler-v {
pointer-events: none;
position: fixed;
z-index: 2000;
z-index: 0;
top: 0px;
bottom: 0px;
width: 1px;
background-color: black;
background-color: rgba(0,0,0,0.5);
}
.cursor {
@@ -219,12 +227,30 @@
}
#space {
// user-select: all;
/*-webkit-user-select: all;
-ms-user-select: all;
-moz-user-select: all;
user-select: all;*/
position: relative;
height: 100% !important;
//padding-top: 64px !important;
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 {
position: absolute;
width: 100%;
@@ -272,8 +298,8 @@
.space-bounds {
position: absolute;
left: 0;
top: 0;
left: 0px;
top: 0px;
pointer-events: none;
background-size: cover;
background-repeat: no-repeat;

View File

@@ -65,15 +65,10 @@
html,
body {
height:100%;
-webkit-tap-highlight-color: transparent;
background-color: white;
color: $black;
}
body {
max-width: 100%;
padding: 0px;
text-rendering: optimizeLegibility;
cursor: default;
background-color: $light;
color: $darker;
}
*[contenteditable="true"] {
@@ -86,12 +81,70 @@ body {
@include box-sizing(border-box);
}
body {
max-width: 100%;
padding: 0px;
text-rendering: optimizeLegibility;
//@include user-select(none);
cursor: default;
}
.img img {
max-width: 100%;
height: auto;
}
/*.layer {
.plan {
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 backface-visibility(hidden);
position: absolute;
@@ -119,7 +172,7 @@ body {
pointer-events: auto;
opacity: 1;
}
}*/
}
[draggable] {
-moz-user-select: none;

View File

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

View File

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

View File

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

View File

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

View File

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

24
views/artifact_list.html Normal file
View File

@@ -0,0 +1,24 @@
<html>
<body>
<h2>[[space.name]]</h2>
<table class="table table-striped" border=1>
<tr>
<th>created</th>
<th>updated</th>
<th>filetype</th>
<th>filename</th>
<th>preview</th>
</tr>
{% for a in space.artifacts %}
<tr>
<td>[[ a.created_at | date('d.m.Y H:i') ]] by [[ a.user.email ]][[ a.editor_name ]]</td>
<td>[[ a.updated_at | date('d.m.Y H:i') ]] by [[ a.update_user.email ]][[ a.last_update_editor_name ]]</td>
<td>[[ a.mime ]]</td>
<td>{% if a.payload_uri %}<a href="[[a.payload_uri]]">[[ a.filename ]]</a>{% endif %}</td>
<td>[[ a.description ]]</td>
</tr>
{% endfor %}
</table>
</body>
</html>

11
views/emails/action.html Normal file
View File

@@ -0,0 +1,11 @@
[[ text | safe ]]
{% if options.message %}
<p>
<i>[[options.message]]</i>
</p>
{% endif %}
{% if options.action %}<br><br>
<a href="[[options.action.link]]" target="_blank">[[options.action.name]]</a><br>
{% endif %}

View File

@@ -1,5 +0,0 @@
<%- include('layouts/outer-header') -%>
<h1><%= message %></h1>
<h2><%= error.status %></h2>
<pre><%= error.stack %></pre>
<%- include('layouts/outer-footer') -%>

3
views/error.html Normal file
View File

@@ -0,0 +1,3 @@
<h1>[[ message ]]</h1>
<h2>[[ error.status ]]</h2>
<pre>[[ error.stack ]]</pre>

26
views/facebook.html Normal file
View File

@@ -0,0 +1,26 @@
<!DOCTYPE html>
<html>
<head>
<title>[[space.name]]</title>
<meta charset="utf-8" />
<meta property="og:title" content="[[space.name]]" />
<meta property="og:description" content="" />
<meta property="og:updated_time" content="[[space.updated_at.getTime()]]" />
<meta property="og:type" content="website" />
<meta property="og:image" content="[[space.thumbnail_url]]" />
</head>
<body>
<h1>[[space.name]]</h1>
{% for a in space.artifacts %}
<tr>
<td>[[ a.mime ]]</td>
<td>[[ a.description | striptags ]]</td>
<td>{% if a.payload_uri %}<a href="[[ a.payload_uri ]]">download</a>{% endif %}</td>
</tr>
{% endfor %}
</body>
</html>

View File

@@ -1,28 +0,0 @@
<%- include('layouts/outer-header') %>
<div id="landing">
<section>
<h1>Work Together, Visually.</h1>
<p>
Whenever you need to lay out pictures, text notes, video and audio clips on a blank canvas,
Spacedeck can help you.
</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>
<p>
Spacedeck is not meant for creating polished designs, but it is a good fit for:
</p>
<ul>
<li>Moodboards</li>
<li>Collages</li>
<li>Teaching (Virtual Blackboards)</li>
<li>Shared Whiteboards</li>
<li>Design Thinking</li>
</ul>
<img src="/images/sd6-screenshot.png" alt="Screenshot of Spacedeck 6.0">
<p>
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>.
</p>
</section>
</div>
<%- include('layouts/outer-footer') %>

62
views/index.html Normal file
View File

@@ -0,0 +1,62 @@
{% extends 'layouts/outer.html' %}
{% block title %}[[ __("welcome") ]]{% endblock %}
{% block content %}
<div id="landing">
<div class="landing-keyvisual-wrapper">
<div class="landing-box">
<h2>[[__("landing_title")]]</h2>
<p class="lead">
<a href="/signup" class="btn btn-primary btn-block btn-xl">[[__("signup")]]</a>
</p>
<p class="lead">
<a href="/login" class="btn btn-primary btn-block btn-xl">[[__("login")]]</a>
</p>
<p class="lead">
[[__("landing_claim")]]
</p>
<p class="lead">
[[__("landing_example")]]
</p>
<ul>
<li class="lead">
[[__("landing_features_1") | safe ]]
</li>
<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>
{% endblock %}

View File

@@ -1,14 +0,0 @@
<div class="footer">
<p>
<div class="col-xs-6">
&copy; 2020 <a href="https://mntre.com">MNT Research GmbH</a>, Fehlerstr. 8, 12161 Berlin, Germany<br>
&copy; 20112020 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>
</p>
</div>
</body>
</html>

View File

@@ -1,29 +0,0 @@
<!doctype html>
<html class="no-js">
<head>
<meta charset="utf-8">
<title>Spacedeck Open</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<link href="/images/favicon.png" rel="icon" type="image/x-icon" />
<link rel="stylesheet" href="/stylesheets/style.css">
</head>
<body>
<header id="landing-header" class="header">
<div class="header-left">
<a class="btn btn-transparent btn-nude" href="<%= config.endpoint %>/"><img src="/images/sd6-logo-black.svg" width="190"></a>
</div>
<div class="header-right pull-right">
<% if (!user) { %>
<a class="btn btn-md btn-dark btn-round" href="/login"><%=__("login")%></a>
<a class="btn btn-md btn-dark btn-round" href="/signup"><%=__("signup")%></a>
<% } else { %>
<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>
<% } %>
</div>
</header>

61
views/layouts/outer.html Normal file
View File

@@ -0,0 +1,61 @@
<!doctype html>
<html class="no-js">
<head>
<meta charset="utf-8">
<title>Spacedeck Open {% block title %}{% endblock %}</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<meta name="apple-mobile-web-app-capable" content="yes">
<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='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 ]]">
<script> var csrf_token = '[[ csrf_token ]]'; </script>
<script src="[[ '/javascripts/jquery-2.1.4.min.js' | cdn ]]"></script>
</head>
<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">
<div class="header-left">
<a class="btn btn-transparent btn-nude" href="[[config.endpoint]]/"><img src="[[ '/images/sd5-logo.svg' | cdn ]]" width="190"></a>
</div>
<div class="header-right pull-right">
{% 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-blue btn-round" href="/signup">[[__("signup")]]</a>
{% else %}
<a class="btn btn-md btn-blue btn-round" href="/spaces">[[__("spaces")]]</a>
<a class="btn btn-md btn-dark btn-round" href="/logout">[[__("logout")]]</a>
{% endif %}
</div>
</header>
{% block content %}{% endblock %}
<div class="footer">
<p>
<div class="col-xs-6">
<a href="/contact">[[ __("contact") ]]</a>
<span style="color:#888">&copy; 20112018 The Spacedeck Open Developers <a href="https://github.com/spacedeck/spacedeck-open">https://github.com/spacedeck/spacedeck-open</a></span>
</div>
</p>
</div>
</body>
</html>

View File

@@ -1,4 +0,0 @@
<div id="landing" style="padding-top:200px;margin:auto;width:300px;">
<h1><%=__("not_found")%></h1>
</div>

11
views/not_found.html Normal file
View File

@@ -0,0 +1,11 @@
{% extends 'layouts/outer.html' %}
{% block title %}[[ __("not_found") ]]{% endblock %}
{% block content %}
<div id="landing" style="padding-top:200px;margin:auto;width:300px;">
<h1>[[__("not_found")]]</h1>
</div>
{% endblock %}

View File

@@ -1,30 +1,16 @@
<header id="dialog-header" class="header" v-if="(active_view == 'account' && user)" v-cloak>
<div v-cloak class="header-left pull-left">
<a class="btn btn-dark btn-md btn-round btn-icon" href="/spaces">
<span class="icon icon-svg icon-sd6"></span>
</a>
<h5>Edit Account</h5>
</div>
<div id="team" class="dialog in" style="padding:100px;z-index:20000;position:absolute;width:100%;min-height:100%;background-color:#eee" v-if="active_view == 'account' && user" v-cloak>
<div class="header-right pull-right">
<a class="btn btn-dark btn-md btn-round btn-icon" href="/spaces">
<span class="icon icon-cross-0"></span>
</a>
</div>
</header>
<div class="dialog-freestanding dialog in" v-if="active_view == 'account' && user" v-cloak>
<a href="/spaces" class="btn btn-round btn-icon btn-dark btn-md pull-right" style="position:absolute;top:30px;right:30px"><span class="icon icon-cross-0"></span></a>
<div class="dialog-tabs" style="margin:auto">
<div class="dialog-tab" v-bind:class="{open:account=='profile'}" v-on:click="account='profile'"><span><%=__("profile_caption")%></span></div>
<div class="dialog-tab" v-bind:class="{open:account=='language'}" v-on:click="account='language'"><span><%=__("language_caption")%></span></div>
<div class="dialog-tab" v-bind:class="{open:account=='notifications'}" v-on:click="account='notifications'"><span><%=__("notifications_caption")%></span></div>
<div class="dialog-tab" v-bind:class="{open:account=='password'}" v-on:click="account='password'"><span><%=__("password_caption")%></span></div>
<div class="dialog-tab" v-bind:class="{open:account=='terminate'}" v-on:click="account='terminate'"><span><%=__("terminate_caption")%></span></div>
<div class="dialog-tab" v-bind:class="{open:account=='profile'}" v-on:click="account='profile'"><span>[[__("profile_caption")]]</span></div>
<div class="dialog-tab" v-bind:class="{open:account=='language'}" v-on:click="account='language'"><span>[[__("language_caption")]]</span></div>
<div class="dialog-tab" v-bind:class="{open:account=='notifications'}" v-on:click="account='notifications'"><span>[[__("notifications_caption")]]</span></div>
<div class="dialog-tab" v-if="user.account_type=='email'" v-bind:class="{open:account=='password'}" v-on:click="account='password'"><span>[[__("password_caption")]]</span></div>
<div class="dialog-tab" v-bind:class="{open:account=='terminate'}" v-on:click="account='terminate'"><span>[[__("terminate_caption")]]</span></div>
</div>
<div class="dialog-section text-left">
<div class="dialog-section text-left" style="background-color:#f5f5f5;padding-top:40px;padding-bottom:40px">
<div class="collapse" v-bind:class="{in:account=='profile'}">
<div class="labels-inline relative" style="margin-bottom:40px">
<div class="form-group">
@@ -44,29 +30,20 @@
<div class="form-group">
<label class="file btn btn-md btn-darken" style="margin-right: 5px;">
<input type="file" v-on:change="save_user_avatar_image(this)">
<span v-if="!uploading_avatar"><%=__("upload_avatar")%></span>
<span v-if="uploading_avatar"><%=__("uploading_avatar")%></span>
<span v-if="!uploading_avatar">[[__("upload_avatar")]]</span>
<span v-if="uploading_avatar">[[__("uploading_avatar")]]</span>
</label>
<p class="message"><%=__("avatar_dimensions")%></p>
<p class="message">[[__("avatar_dimensions")]]</p>
</div>
</div>
</div>
</div>
<div>
<div class="form-group">
<label class="label">API Token</label>
<input
type="text"
id="api-token"
class="input input-white no-b"
v-model="user.api_token"
placeholder="secret key">
</div>
<div class="form-group">
<label class="label" ><%=__("profile_name")%></label>
<label class="label" >[[__("profile_name")]]</label>
<input type="text" id="user-nickname"
pattern=".{3,}"
required title="3 characters minimum"
@@ -75,18 +52,41 @@
</div>
<div class="form-group">
<label class="label"><%=__("profile_email")%></label>
<label class="label">[[__("profile_email")]]</label>
<input
type="email"
id="new-email"
v-bind:class="{disabled: user.account_type=='google'}"
v-bind:disabled="user.account_type=='google'"
class="input input-white no-b"
type="email"
v-model="user.email"
v-on:change="user.email_changed=true"
placeholder="mail@example.com">
<button class="btn btn-md btn-darken" v-if="user.account_type=='email'" v-on:click=" save_user()" style="margin-top:20px">[[__("ok")]]</button>
</div>
<div class="form-group" v-if="!user.confirmed_at">
<p v-if="!user.confirmed_at && !account_confirmed_sent">[[__("confirmation_sent_long")]]</p>
<span
class="btn btn-xs btn-stroke-darken btn-round"
v-on:click=" confirm_again()"
v-if="!user.confirmed_at && !account_confirmed_sent"
>[[__("send_again")]]</span>
<p v-if="account_confirmed_sent">
<span class="icon icon-check"></span> <span>[[__("confirmation_sent_another")]]</span>
</p>
</div>
<div class="form-group">
<button class="btn btn-md btn-dark" v-on:click="save_user()">Save</button>
<label class="label">Spacedeck.com Data Import</label>
<p v-if="!importables">No .ZIP files found in Spacedeck application folder.</p>
<ul>
<li v-for="f in importables">{{f}} <button v-on:click="start_zip_import(f)">Start Import</button></li>
</ul>
</div>
</div>
</div>
@@ -105,12 +105,6 @@
<label class="radio" v-bind:class="{checked: user.prefs_language=='fr'}" v-on:click="save_user_language('fr')">
<input type="radio" id="user-preferences_language" name="language" value="fr"><span>Français</span>
</label>
<label class="radio" v-bind:class="{checked: user.prefs_language=='oc'}" v-on:click="save_user_language('oc')">
<input type="radio" id="user-preferences_language" name="language" value="oc"><span>Occitan</span>
</label>
<label class="radio" v-bind:class="{checked: user.prefs_language=='es'}" v-on:click="save_user_language('es')">
<input type="radio" id="user-preferences_language" name="language" value="es"><span>Español</span>
</label>
</div>
</div>
@@ -120,25 +114,25 @@
<label class="checkbox"
v-bind:class="{checked: user.prefs_email_notifications}"
v-on:click="account_save_user_notifications(!user.prefs_email_notifications);">
<span><%=__('notifications_option_chat')%></span>
<span>[[__('notifications_option_chat')]]</span>
</label>
</div>
</div>
</div>
<div class="collapse" v-bind:class="{in:account=='password'}">
<h4>Change Password</h4>
<h4 class="modal-title">Change Password</h4>
<div class="modal-section labels-inline">
<div class="form-group">
<label class="label"><%=__("current_password")%></label>
<label class="label">[[__("current_password")]]</label>
<input id="current-password" class="input input-white no-b" v-model="password_change_current" type="password">
</div>
<div class="form-group">
<label class="label"><%=__("new_password")%></label>
<label class="label">[[__("new_password")]]</label>
<input id="new-password" class="input input-white no-b" v-model="password_change_new" type="password">
</div>
<div class="form-group">
<label class="label"><%=__("verify_password")%></label>
<label class="label">[[__("verify_password")]]</label>
<input id="new-password-confirmation" class="input input-white no-b" v-model="password_change_new_confirmation" type="password">
</div>
@@ -146,36 +140,38 @@
</div>
<div class="modal-footer">
<div class="btn-cluster">
<button
class="btn btn-dark btn-md"
v-on:click="save_user_password(password_change_current, password_change_new, password_change_new_confirmation);" >
<%=__("change_password")%>
class="btn btn-transparent btn-block"
v-on:click="save_user_password(password_change_current, password_change_new, password_change_new_confirmation);" >
[[__("change_password")]]
</button>
</div>
</div>
</div>
<div class="collapse" v-bind:class="{in:account=='terminate'}">
<h4>Terminate Account</h4>
<div class="modal-section labels-inline">
<div class="">
<p>[[__("terminate_warning")]]</p>
<p>[[__("terminate_warning2")]]</p>
</div>
<div class="labels-inline" v-if="user.account_type == 'email'">
<div class="form-group">
<label class="label"><%=__("current_password")%></label>
<label class="label">[[__("current_password")]]</label>
<input v-model="account_remove_password" class="input input-white no-b" type="password">
</div>
<div class="form-group">
<label class="label"><%=__("terminate_reason")%></label>
<label class="label">[[__("terminate_reason")]]</label>
<textarea class="input input-white no-b" v-model="account_remove_feedback"></textarea>
<p class="message"><%=__("terminate_reason_caption")%></p>
<p class="message">[[__("terminate_reason_caption")]]</p>
</div>
</div>
<div class="modal-section labels-inline">
<div class="center alert alert-danger" v-if="account_remove_error">{{account_remove_error}}</div>
</div>
<div class="modal-footer">
<button class="btn btn-stroke-darken btn-md" v-on:click="remove_account(account_remove_password, account_remove_feedback)">Terminate Account</button>
</div>
<button class="btn btn-transparent btn-block" v-on:click="remove_account(account_remove_password, account_remove_feedback)">[[__("terminate_terminate")]]</button>
</div>
</div>
</div>

View File

@@ -1,56 +1,61 @@
<header id="folder-header" class="header" v-if="(active_view == 'folders' && active_folder)" v-cloak>
<div v-cloak class="header-left pull-left">
<a class="btn btn-dark btn-md btn-round btn-icon" href="/spaces">
<span class="icon icon-svg icon-sd6"></span>
<a class="btn btn-stroke-darken btn-md btn-round btn-icon" href="/spaces">
<span class="icon icon-home"></span>
</a>
<button v-if="logged_in && (active_space_role == 'editor' || active_space_role == 'admin')" class="btn btn-dark btn-md btn-round" v-on:click="create_space('space')"><%= __('create_space') %></button>
<button v-if="logged_in && (active_space_role == 'editor' || active_space_role == 'admin')" class="btn btn-primary btn-md btn-round" v-on:click="create_space('space')">[[ __('create_space') ]]</button>
<button v-if="logged_in && (active_space_role == 'editor' || active_space_role == 'admin')" class="btn btn-stroke-darken btn-md btn-round" v-on:click="create_space('folder')">
<span><%= __('create_folder') %></span>
<span v-bind:class="{'disabled-pro':!is_pro(user)}">[[ __('create_folder') ]]</span>
</button>
</div>
<label class="relative compact-hidden" v-if="logged_in">
<span class="icon icon-sm icon-zoom no-events absolute-top-left" style="margin: 5px;"></span>
<input id="folder-search"
type="search" name="search"
style="padding-left: 40px !important; margin-right: 10px;"
placeholder="<%= __('search') %>"
class="input input-md input-white input-round no-b w-2"
v-model="folder_spaces_search" v-on:change="search_spaces">
</label>
<div class="dropdown top light m-r-20 compact-hidden" v-bind:class="{open : active_dropdown=='folder_sorting'}" v-if="logged_in">
<div class="header-right pull-right">
<div class="dropdown top light m-r-20 compact-hidden" v-bind:class="{open : active_dropdown=='folder_sorting'}">
<button class="btn btn-sm btn-nude" v-on:click="activate_dropdown('folder_sorting')">
<span><%= __('sort_by') %></span>:
<b v-if="folder_sorting=='updated_at'"><%= __('last_modified') %></b>
<b v-if="folder_sorting=='name'"><%= __('title') %></b>
<b v-if="folder_sorting=='space_type'"><%= __('type') %></b>
<span>[[ __('sort_by') ]]</span>:
<b v-if="folder_sorting=='updated_at'">[[ __('last_modified') ]]</b>
<b v-if="folder_sorting=='name'">[[ __('title') ]]</b>
<b v-if="folder_sorting=='space_type'">[[ __('type') ]]</b>
</button>
<div class="dropdown-menu" role="menu">
<ul class="select-list">
<li v-bind:class="{checked:folder_sorting=='updated_at'}"
v-on:click="set_folder_sorting('updated_at',true)">
<span><%= __('last_modified') %></span>
<span>[[ __('last_modified') ]]</span>
</li>
<li v-bind:class="{checked:folder_sorting=='name'}"
v-on:click="set_folder_sorting('name',false)">
<span><%= __('title') %></span>
<span>[[ __('title') ]]</span>
</li>
<li v-bind:class="{checked:folder_sorting=='space_type'}"
v-on:click="set_folder_sorting('space_type',false)">
<span><%= __('type') %></span>
<span>[[ __('type') ]]</span>
</li>
</ul>
</div>
</div>
</div>
<div class="header-right pull-right">
<label class="relative compact-hidden">
<span class="icon icon-sm icon-zoom no-events absolute-top-left" style="margin: 5px;"></span>
<input id="folder-search"
type="search" name="search"
style="padding-left: 40px !important; margin-right: 10px;"
placeholder="[[ __('search') ]]"
class="input input-md input-white input-round no-b w-2"
v-model="folder_spaces_search" v-on:change="search_spaces">
</label>
<button class="btn btn-stroke-darken btn-md btn-round" v-if="!user.confirmed_at" v-on:click="confirm_again()">
<span v-if="!account_confirmed_sent">[[ __('email_unconfirmed') ]]</span>
<span v-if="account_confirmed_sent">[[ __('confirmation_sent') ]]</span>
</button>
<div class="dropdown top right light" v-bind:class="{open: active_dropdown=='account'}">
<button
class="profile-avatar btn btn-md btn-icon btn-dark btn-round"
v-if="logged_in"
class="profile-avatar btn btn-md btn-icon btn-darken btn-round"
v-bind:style="background_image_style([user.avatar_thumb_uri])"
v-bind:class="{'has-avatar-image':!!user.avatar_thumb_uri}" v-on:click="show_account();">
<span class="icon icon-user" v-if="logged_in && !user.avatar_thumb_uri"></span></button>
@@ -60,27 +65,42 @@
<li v-if="user.team && is_admin(user)">
<a href="/team">
<span class="icon icon-sm icon-user-group"></span>
<span><%= __('edit_team') %></span>
<span>[[ __('edit_team') ]]</span>
</a>
</li>
<li>
<a href="/account">
<span class="icon icon-sm icon-user"></span>
<span><%= __('edit_account') %></span>
<span>[[ __('edit_account') ]]</span>
</a>
</li>
<li v-on:click="activate_modal('support')">
<span>
<span class="icon icon-sm icon-info"></span>
<span>[[ __('support') ]]</span>
</span>
</li>
<li v-on:click="logout()">
<span>
<span class="icon icon-sm icon-logout"></span>
<span><%= __('log_out') %></span>
<span>[[ __('log_out') ]]</span>
</span>
</li>
</ul>
</div>
</div>
<div class="btn-group dark round" id="meta-toggle" style="margin-right:10px">
<button class="btn btn-md btn-transparent btn-icon btn-icon" v-on:click="toggle_meta()">
<span class="jewel" style="color: white; background-color: red" v-if="meta_unseen>0">{{meta_unseen}}</span>
<span class="icon icon-menu"></span>
</button>
</div>
</div>
</header>
@@ -91,41 +111,46 @@
<div id="folder-breadcrumb">
<span v-if="logged_in" v-for="item in active_space_path" class="btn btn-sm btn-transparent" v-sd-droppable="handle_folder_drop;item">
<a href="/{{item.space_type}}s/{{item._id}}">{{item.name}}</a>&nbsp;</span>
<a v-for="item in active_space_path" type="button" class="btn btn-sm btn-transparent" href="/{{item.space_type}}s/{{item._id}}" v-sd-droppable="handle_folder_drop;item">
<span>{{item.name}}</span>
<span class="seperator">/</span>
</a>
<a v-if="(active_space_role != 'admin')" type="button" class="btn btn-sm btn-transparent">
<span>{{active_folder.name}}</span>
</a>
<div class="dropdown top light" v-bind:class="{open:active_dropdown=='breadcrumb'}" v-if="(active_folder._id != user.home_folder_id) && ((active_space_role == 'admin'))">
<div class="dropdown top light" v-bind:class="{open:active_dropdown=='breadcrumb'}" v-if="(active_folder._id != user.home_folder_id) && ((active_space_role == 'admin') || (active_space_role == 'editor'))">
<button type="button" class="btn btn-sm btn-transparent btn-dropdown" data-toggle="dropdown" v-on:click=" activate_dropdown('breadcrumb')">
<span>{{active_folder.name}}</span>
</button>
<div class="dropdown-menu" v-if="active_folder && active_folder._id != user.home_folder_id">
<ul class="select-list">
<li><span class="tile-rename" v-on:click="rename_folder(active_folder)"><%=__("rename")%></span></li>
<li v-if="active_space_role == 'admin'"><span class="tile-share" v-on:click="activate_access()"><%=__("share")%></span></li>
<li><span class="tile-rename" v-on:click="rename_folder(active_folder)">[[__("rename")]]</span></li>
<li v-if="active_space_role == 'admin'"><span class="tile-share" v-on:click="activate_access()">[[__("share")]]</span></li>
<li><a v-on:click=" open_url('[[config.endpoint]]/api/spaces/'+active_folder._id+'/list')" target="_blank">[[__("list")]]</a></li>
</ul>
</div>
</div>
<div v-if="active_folder._id == user.home_folder_id">
<span><%= __('home') %></span>
<span>[[ __('home') ]]</span>
</div>
</div>
<div id="folder-empty" v-if="(active_profile_spaces.length == 0)">
<div>
<p><%= __('no_spaces_yet') %></p>
<p>[[ __('no_spaces_yet') ]]</p>
</div>
</div>
<div id="folder-empty" v-if="folder_spaces_filter">
<div v-if="active_profile_spaces | empty?">
<p><b>"{{folder_spaces_filter}}"</b> <br/><%= __('search_no_results') %></p>
<button type="button" class="btn btn-md btn-round btn-stroke-darken events" v-on:click=" folder_spaces_filter = ''"><%= __('search_clear') %></button>
<p><b>"{{folder_spaces_filter}}"</b> <br/>[[ __('search_no_results') ]]</p>
<button type="button" class="btn btn-md btn-round btn-stroke-darken events" v-on:click=" folder_spaces_filter = ''">[[ __('search_clear') ]]</button>
</div>
</div>
<div id="folder-grid">
@@ -135,15 +160,8 @@
v-sd-droppable="handle_folder_drop;item"
draggable="true"
class="item" v-bind:class="item.space_type"
v-bind:style="{'z-index': (active_profile_spaces.length - $index)}">
<!-- anonymous editors can go edit spaces in a folder -->
<a href="/s/{{item.edit_hash}}-{{item.edit_slug}}" v-if="active_space_role=='editor' && !logged_in">
<span class="item-thumbnail thumbnail-loading" v-if="item.space_type=='space'"></span>
<span class="item-thumbnail" v-bind:style="space_thumbnail_style(item)"></span>
</a>
<a v-if="active_space_role=='viewer' || logged_in" href="/{{item.space_type}}s/{{item._id}}">
v-bind:style="{'z-index': (active_profile_spaces.length - $index)}">
<a href="/{{item.space_type}}s/{{item._id}}">
<span class="item-thumbnail thumbnail-loading" v-if="item.space_type=='space'"></span>
<span class="item-thumbnail" v-bind:style="space_thumbnail_style(item)"></span>
</a>
@@ -156,9 +174,9 @@
<div class="dropdown-menu" role="menu">
<ul class="select-list">
<li v-on:click="download_space_as_pdf(item)"><span><span class="icon icon-sm icon-clipboard"></span><%= __('download_as_pdf') %></span></li>
<li v-on:click="rename_space(item)"><span><span class="icon icon-sm icon-tag"></span><%= __('rename') %></span></li>
<li v-on:click="delete_space(item)"><span><span class="icon icon-sm icon-trash"></span><%= __('delete') %></span></li>
<li v-on:click="duplicate_space(item)"><span><span class="icon icon-sm icon-duplicate"></span>[[ __('duplicate') ]]</span></li>
<li v-on:click="rename_space(item)"><span><span class="icon icon-sm icon-tag"></span>[[ __('rename') ]]</span></li>
<li v-on:click="delete_space(item)"><span><span class="icon icon-sm icon-trash"></span>[[ __('delete') ]]</span></li>
</ul>
</div>
</div>

View File

@@ -1,11 +1,17 @@
<header id="landing-header" class="header" v-cloak v-if="(active_view == 'login' || active_view == 'signup' || active_view == 'password-reset' || active_view == 'password-confirm')">
<div class="header-left">
<a class="btn btn-transparent btn-nude" href="/"><img src="/images/sd6-logo-black.svg" width="190"></a>
<a class="btn btn-transparent btn-nude" href="/"><img src="/images/sd5-logo.svg" width="190"></a>
</div>
<div class="header-right pull-right">
<a v-if="active_view != 'login'" class="btn btn-md btn-dark btn-round" href="/login"><%= __("login") %></a>
<a v-if="active_view != 'signup'" class="btn btn-md btn-dark btn-round" href="/signup"><%= __("signup") %></a>
<span class="btn-group dark round">
{% if (locale != "de") %}<a href="/t/de?r={{active_view}}" class="btn btn-transparent btn-md">Deutsch</a>{% endif %}
{% if (locale != "en") %}<a href="/t/en?r={{active_view}}" class="btn btn-transparent btn-md">English</a>{% endif %}
{% if (locale != "fr") %}<a href="/t/fr?r={{active_view}}" 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-blue btn-round" href="/signup">[[__("signup")]]</a>
</div>
</header>
@@ -17,26 +23,30 @@
<div id="login" v-bind:class="{active : active_view == 'login'}">
<div class="content">
<form v-on:submit="login_submit(user_forms_email, login_password, $event)">
<h3>Login</h3>
<span class="btn btn-xs btn-darken pull-right" v-on:click="login_google();">[[__("login_google")]]</span>
<h4>[[__("login")]]</h4>
<div class="tight">
<div class="form-group">
<input class="input" name="email" type="email" required v-model="user_forms_email" placeholder="<%=__("email")%>">
<input class="input" name="email" type="email" required v-model="user_forms_email" placeholder="[[__("email")]]">
</div>
<div class="form-group">
<input class="input" name="password" type="password" required v-model="login_password" placeholder="<%=__("password")%>">
<input class="input" name="password" type="password" required v-model="login_password" placeholder="[[__("password")]]">
</div>
</div>
<button type="submit" class="btn btn-dark btn-block">
<span v-show="!loading_user">Login</span>
<span v-show="loading_user">Logging in</span>
<button type="submit" class="btn btn-primary btn-block">
<span v-show="!loading_user">[[__("login")]]</span>
<span v-show="loading_user">[[__("logging_in")]]</span>
</button>
<div class="center alert alert-danger" v-if="login_error">{{login_error}}</div>
<div style="margin-top:2em">
<a href="/password-reset">Forgot Password</a>
<div class="pull-right">
<a class="btn btn-xs btn-darken" href="/signup">[[__("signup")]]</a>&nbsp;
<a class="btn btn-xs btn-darken" href="/password-reset">[[__("reset_password")]]</a>
</div>
</form>
</div>
@@ -44,42 +54,45 @@
<div id="signup" v-bind:class="{active : active_view == 'signup'}">
<div class="content">
<form v-on:submit="signup_submit($event, user_forms_name, user_forms_email, signup_password, signup_password_confirmation, signup_invite_code)">
<h4><%=__("signup")%></h4>
<form v-on:submit="signup_submit($event, user_forms_name, user_forms_email, signup_password, signup_password_confirmation)">
<span class="btn btn-xs btn-darken pull-right" v-on:click="login_google();">[[__("login_google")]]</span>
<h4>[[__("signup")]]</h4>
<div class="tight">
<div class="form-group">
<input class="input" type="email" required id="user-email" v-model="user_forms_email" placeholder="<%=__("email")%>" autofocus v-focus>
</div>
<div class="form-group">
<input class="input" id="user-password" required type="password" v-model="signup_password" placeholder="<%=__("password")%>">
<input class="input" type="text" id="user-name" v-model="user_forms_name" placeholder="[[__("name")]]" v-focus autofocus>
</div>
<div class="form-group">
<input class="input" id="user-password-confirmation" required type="password" v-model="signup_password_confirmation" placeholder="Repeat Password">
</div>
</div>
<div class="tight">
<div class="form-group">
<input class="input" type="text" id="user-name" v-model="user_forms_name" placeholder="Pick a username">
<input class="input" type="email" required id="user-email" v-model="user_forms_email" placeholder="[[__("email")]]">
</div>
<div class="form-group">
<input class="input" id="invite-code" required type="text" v-model="signup_invite_code" placeholder="Beta Invite Code">
<input class="input" id="user-password" required type="password" v-model="signup_password" placeholder="[[__("password")]]">
</div>
<div class="form-group">
<input class="input" id="user-password-confirmation" required type="password" v-model="signup_password_confirmation" placeholder="[[__("password_confirmation")]]">
</div>
</div>
<!--div style="margin-top: -7px; margin-bottom: 7px;"><small>By signing up you agree to our <a href="/terms" target="_blank">TOS</a> and <a href="/privacy" target="_blank">Privacy Policy.</a></small><br/>
</div-->
<div style="margin-top: -7px; margin-bottom: 7px;"><small>By signing up you agree to our <a href="/terms" target="_blank">TOS</a> and <a href="/privacy" target="_blank">Privacy Policy.</a></small><br/>
</div>
<button class="btn btn-dark btn-block">
<span v-if="!creating_user"><%=__("signup")%></span>
<span v-if="creating_user"><%=__("signing_up")%></span>
<button class="btn btn-primary btn-block">
<span v-if="!creating_user">[[__("signup")]]</span>
<span v-if="creating_user">[[__("signing_up")]]</span>
</button>
<div class="center alert alert-danger" style="width:100%;" v-if="signup_error">{{signup_error}}</div>
<a class="btn btn-link btn-block" href="/login" style="margin-top: 20px">[[__("login")]]</a>
</form>
</div>
</div>
@@ -90,16 +103,16 @@
<h4>Password Recovery</h4>
<div class="tight">
<div class="form-group">
<input class="input" type="email" id="user-email" v-model="reset_email" placeholder="<%=__("email")%>">
<input class="input" type="email" id="user-email" v-model="reset_email" placeholder="[[__("email")]]">
</div>
</div>
<div class="text-center alert alert-danger" v-if="password_reset_error">{{password_reset_error}}</div>
<button class="btn btn-dark btn-block" v-on:click="password_reset_submit($event, reset_email)"><%=__("reset_password")%></button>
<button class="btn btn-primary btn-block" v-on:click="password_reset_submit($event, reset_email)">[[__("reset_password")]]</button>
</form>
</div>
<div class="content" v-if="password_reset_send==true">
<h4><%=__("reset_password")%></h4>
<%=__("password_check_inbox")%>
<h4>[[__("password_confirmation")]]</h4>
[[__("password_check_inbox")]]
</div>
</div>
@@ -110,16 +123,16 @@
<div class="tight">
<div class="form-group">
<input class="input" id="user-password" type="password" v-model="signup_password" placeholder="New Password">
<input class="input" id="user-password" type="password" v-model="signup_password" placeholder="[[__("password")]]">
</div>
<div class="form-group">
<input class="input" id="user-password" type="password" v-model="signup_password_confirmation" placeholder="Repeat Password">
<input class="input" id="user-password" type="password" v-model="signup_password_confirmation" placeholder="[[__("password_confirmation")]]">
</div>
</div>
<div class="text-center alert alert-danger" v-if="password_reset_confirm_error">{{password_reset_confirm_error}}</div>
<button class="btn btn-dark btn-block" v-on:click="password_reset_confirm($event, signup_password, signup_password_confirmation)"><%=__("save")%></button>
<button class="btn btn-primary btn-block" v-on:click="password_reset_confirm($event, signup_password, signup_password_confirmation)">[[__("save")]]</button>
</form>
</div>
</div>

View File

@@ -9,23 +9,23 @@
<div style="margin-bottom: 15px; margin-top: 8px" >
<small>
<%=__("created_by")%> <b>{{active_folder.creator.nickname||active_folder.creator.slug}}.</b>
<br/><%=__("last_updated")%> <b>{{active_folder.updated_at | date 'MMMM Do YYYY, HH:mm'}}.</b>
[[__("created_by")]] <b>{{active_folder.creator.nickname||active_folder.creator.slug}}.</b>
<br/>[[__("last_updated")]] <b>{{active_folder.updated_at | date 'MMMM Do YYYY, HH:mm'}}.</b>
</small>
</div>
<div v-if="logged_in && user.home_folder_id!=active_folder._id">
<button class="btn btn-sm btn-round btn-primary m-r-5" v-on:click="activate_access()">
<span class="icon-label"><%=__("share")%></span>
<span class="icon-label">[[__("share")]]</span>
</button>
</div>
</div>
<div class="sidebar-section" v-if="is_pro(user)">
<h5><%=__("history_recently_updated")%></h5>
<h5>[[__("history_recently_updated")]]</h5>
<div v-if="active_folder_history_items.length == 0">
<%=__("history_recently_empty")%>
[[__("history_recently_empty")]]
</div>
<ul id="updates">
@@ -33,7 +33,7 @@
<a v-bind:href="'/spaces/' + item.space._id">{{item.space.name}}</a>
<small>
<%=__("by")%>
[[__("by")]]
<span v-for="u in item.users">{{u}}<span v-if="$index < (item.users.length-1)">, </span></span>
</small>
</li>
@@ -41,10 +41,10 @@
</div>
<div class="sidebar-section" v-if="!is_pro(user)">
<h5><%=__("history_recently_updated")%></h5>
<h5>[[__("history_recently_updated")]]</h5>
<p>
<%=__("pro_ad_history_headline")%>
[[__("pro_ad_history_headline")]]
</p>
<p>

View File

@@ -9,8 +9,8 @@
<div style="margin-bottom: 15px; margin-top: 8px" >
<small>
<%=__("created_by")%> <b>{{active_space.creator.nickname}}.</b><br/>
<%=__("last_updated")%> <b>{{active_space.updated_at | date 'MMMM Do YYYY, HH:mm'}}.</b>
[[__("created_by")]] <b>{{active_space.creator.nickname}}.</b><br/>
[[__("last_updated")]] <b>{{active_space.updated_at | date 'MMMM Do YYYY, HH:mm'}}.</b>
</small>
</div-->
@@ -21,18 +21,18 @@
</button>
<button class="btn btn-sm btn-round btn-primary m-r-5"
v-on:click="guest_logout()">
<%=__("logout")%>
[[__("logout")]]
</button>
</div>
</div>
<div class="sidebar-section">
<h5><%=__("chat")%> &nbsp; <a v-if="active_space_role!='viewer'" v-on:click="activate_access()" class="btn btn-xs btn-darken">Add People</a></h5>
<h5>[[__("chat")]] &nbsp; <a v-if="active_space_role!='viewer'" v-on:click="activate_access()" class="btn btn-xs btn-darken">Add People</a></h5>
<textarea id="new-comment" style="min-height:80px;padding: 5px 9px;margin-bottom:10px" class="input input-darken no-b" v-if="can_add_comment" v-model="space_comment" placeholder="<%=__("chat_message_placeholder")%>" spellcheck="false"></textarea>
<textarea id="new-comment" style="min-height:80px;padding: 5px 9px;margin-bottom:10px" class="input input-darken no-b" v-if="can_add_comment" v-model="space_comment" placeholder="[[__("chat_message_placeholder")]]" spellcheck="false"></textarea>
<div v-if="can_add_comment">
<button class="btn btn-sm btn-primary" v-on:click="create_space_comment(space_comment)"><%=__("post")%></button>
<button class="btn btn-sm btn-primary" v-on:click="create_space_comment(space_comment)">[[__("post")]]</button>
</div>
<ul class="comments">
@@ -50,7 +50,7 @@
class="delete pull-right"
v-if="active_space_role!='viewer'"
v-on:click="remove_space_comment(item)">
<a title="<%=__("delete")%>" style="cursor:pointer"></a>
<a title="[[__("delete")]]" style="cursor:pointer"></a>
</li>
</ul>

View File

@@ -3,7 +3,7 @@
<div class="modal-dialog">
<div class="modal-content" style="width:760px">
<div class="modal-header" style="padding-bottom:0">
<h3 class="text-left"><%=__("share")%>: {{access_settings_space.name}}</h3>
<h3 class="text-left"><span class="icon icon-share icon-sm"></span> [[__("share")]]: {{access_settings_space.name}}</h3>
<button type="button" class="btn btn-icon btn-light btn-round close" v-on:click=" close_modal()">
<span class="icon icon-cross-1"></span>
</button>
@@ -12,17 +12,17 @@
<div class="modal-body">
<div class="modal-section" style="padding-top:0;padding-bottom:20px">
<label class="radio" v-bind:class="{checked:access_settings_space.access_mode=='private'}">
<input type="radio" name="access_mode" value="private" v-model="access_settings_space.access_mode" v-on:click="save_space_access_mode($event)"> <%=__("mode_private")%>
<input type="radio" name="access_mode" value="private" v-model="access_settings_space.access_mode" v-on:click="save_space_access_mode($event)"> [[__("mode_private")]]
</label>
<label class="radio" v-bind:class="{checked:access_settings_space.access_mode=='public'}">
<input type="radio" name="access_mode" value="public" v-model="access_settings_space.access_mode" v-on:click="save_space_access_mode($event)"><%=__("mode_public")%>
<input type="radio" name="access_mode" value="public" v-model="access_settings_space.access_mode" v-on:click="save_space_access_mode($event)">[[__("mode_public")]]
</label>
</div>
<div class="modal-section" v-if="active_space" style="padding-bottom:10px">
<h4 class="text-left"><span class="icon icon-link icon-sm"></span> <%=__("access_editor_link")%> </h4>
<h4 class="text-left"><span class="icon icon-link icon-sm"></span> [[__("access_editor_link")]] </h4>
<p class="text-left">
<%=__("access_editor_link_desc")%>
[[__("access_editor_link_desc")]]
</p>
<div class="input-group org-add-form">
@@ -30,7 +30,7 @@
<span class="input-group-btn">
<span id="org-add-member" class="btn btn-lg btn-primary clipboard-btn" data-clipboard-target="#editorurl2" v-clipboard>
<%=__("copy")%>
[[__("copy")]]
</span>
</span>
</div>
@@ -41,13 +41,13 @@
type="checkbox"
v-bind:checked="access_settings_space.editors_locking"
v-model="access_settings_space.editors_locking" v-on:change="save_space_editors_locking($event)" />
<%=__("access_anonymous_edit_blocking")%>
[[__("access_anonymous_edit_blocking")]]
</label>
</div>
<!-- subsection "invite" -->
<div id="new-editor" class="modal-section" style="padding-bottom:20px">
<h4 class="text-left"><span class="icon icon-user-add icon-sm"></span> <%=__('access_new_members')%></h4>
<h4 class="text-left"><span class="icon icon-user-add icon-sm"></span> [[__('access_new_members')]]</h4>
<div class="form-group">
<span class="error-note" v-if="invite_email_error">{{invite_email_error}}</span>
@@ -55,27 +55,27 @@
v-bind:class="{error: !!invite_email_error}"
id="invitee_email"
v-model="invite_email"
type="email" placeholder="<%=__('invite_emails')%>">
type="email" placeholder="[[__('invite_emails')]]">
<textarea id="invite-message" class="input input-block input-white overflow-y-scroll"
v-model="invite_message" placeholder="<%=__('optional_message')%>" name="Message"></textarea>
v-model="invite_message" placeholder="[[__('optional_message')]]" name="Message"></textarea>
<select class="input input-white" v-model="invite_member_role">
<option value="viewer"><%=__("role_viewer")%></option>
<option value="editor"><%=__("role_editor")%></option>
<option value="admin"><%=__("role_admin")%></option>
<option value="viewer">[[__("role_viewer")]]</option>
<option value="editor">[[__("role_editor")]]</option>
<option value="admin">[[__("role_admin")]]</option>
</select>
</div>
<div class="form-group">
<button class="btn btn-primary btn-md" v-on:click="invite_member(access_settings_space, invite_email, invite_message, invite_member_role)"> <%=__("invite")%> </button>
<button class="btn btn-primary btn-md btn-round" v-on:click="invite_member(access_settings_space, invite_email, invite_message, invite_member_role)"> [[__("invite")]] </button>
</div>
</div>
<!-- subsection "editors list" -->
<div class="modal-section" style="padding-bottom:60px">
<h4 class="text-left"><span class="icon icon-user-group icon-sm"></span> <%=__("access_current_members")%></h4>
<h4 class="text-left"><span class="icon icon-user-group icon-sm"></span> [[__("access_current_members")]]</h4>
<table class="table">
<tr>
<th></th>
@@ -85,40 +85,35 @@
</tr>
<tr v-for="member in access_settings_memberships" v-bind:class="member.state">
<td>
<span class="editor-avatar btn btn-md btn-dark btn-icon btn-round"
v-if="member.user"
v-bind:style="background_image_style([member.user.avatar_thumb_uri])"
v-bind:class="{'has-avatar-image':!!member.user.avatar_thumb_uri}">
<span class="icon icon-user" v-if="!member.user.avatar_thumb_uri"></span>
</span>
<span class="editor-avatar btn btn-md btn-round btn-icon icon-hourglass" v-if="!member.user"></span>
<span class="editor-avatar btn btn-xs btn-round btn-icon" v-if="member.user">{{member.user.initials}}</span>
<span class="editor-avatar btn btn-xs btn-round btn-icon icon-hourglass" v-if="!member.user"></span>
</td>
<td>
<span class="editor-name" v-if="member.user && member.state == 'active'">{{member.user.nickname}}</span>
<span class="editor-email" v-if="!member.user || member.state == 'pending'">(pending)</span>
<span class="editor-email" v-if="member.user && member.state == 'active'">({{member.user.email}})</span>
<span class="editor-email" v-if="!member.user || member.state == 'pending'">({{member.email_invited}})</span>
<span class="editor-email" v-if="member.state == 'active'">{{member.user.email}}</span>
<span class="editor-email" v-if="member.state == 'pending'">{{member.email_invited}}</span>
<span class="editor-name" v-if="member.state == 'active'">{{member.user.nickname}}</span>
<span class="editor-email" v-if="member.state == 'pending'">(pending)</span>
</td>
<td>
<div class="form-group">
<select class="input input-sm" v-model="member.role" v-on:change="update_member(access_settings_space, member, $event.currentTarget.value)" style="width:auto">
<option value="viewer"><%=__("role_viewer")%></option>
<option value="editor"><%=__("role_editor")%></option>
<option value="admin"><%=__("role_admin")%></option>
<option value="viewer">[[__("role_viewer")]]</option>
<option value="editor">[[__("role_editor")]]</option>
<option value="admin">[[__("role_admin")]]</option>
</select>
</div>
</td>
<td>
<button class="btn btn-sm btn-stroke-darken" v-on:click="remove_member(access_settings_space, member)"><%=__("delete")%></button>
<button class="btn btn-sm btn-stroke-darken" v-on:click="remove_member(access_settings_space, member)">[[__("delete")]]</button>
</td>
</tr>
</table>
<p class="text-left" v-if="!access_settings_memberships.length">
<%=__("access_no_members")%>
[[__("access_no_members")]]
</p>
<div class="form-group" style="padding-top: 40px">
<button class="btn btn-primary btn-md" v-on:click="close_modal();"> <%=__("ok")%> </button>
<button class="btn btn-primary btn-md btn-round" v-on:click="close_modal();"> [[__("ok")]] </button>
</div>
</div>

View File

@@ -0,0 +1,35 @@
<div class="modal" v-if="active_modal == 'create-space'" v-cloak>
<div class="modal-wrapper">
<div class="modal-dialog">
<button type="button" class="btn btn-icon btn-light btn-round close" v-on="click : close_modal()">
<span class="icon icon-cross-1"></span>
</button>
<div class="modal-content">
<div class="modal-body labels-inline">
<div class="modal-section p-5">
What would you like to create?
<div>
<input type="text" v-model="create_space_title" placeholder="Title">
</div>
<div>
<button class="btn" v-on="click: create_space('space','whiteboard')">Whiteboard</button>
</div>
<div>
<button class="btn" v-on="click: create_space('space','collection')">Note Collection</button>
</div>
<div>
<button class="btn" v-on="click: create_space('space','article')">Article</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>

View File

@@ -6,13 +6,13 @@
<button type="button" class="btn btn-icon btn-light btn-round close" v-on:click="close_modal()">
<span class="icon icon-cross-1"></span>
</button>
<h4 class="modal-title"><%=__("folder_settings")%></h4>
<h4 class="modal-title">[[__("folder_settings")]]</h4>
</div>
<div class="modal-body">
<div class="modal-section">
<div class="form-group">
<label class="label"><%=__("title")%></label>
<label class="label">[[__("title")]]</label>
<input type="text" class="input" v-model="active_folder.name">
</div>
@@ -26,13 +26,13 @@
<div class="form-group text-center">
<label class="file btn btn-sm btn-round btn-stroke-darken">
<input type="file" id="file" v-on:change="save_folder_avatar_image(this);">
<span><%=__("upload_cover_image")%></span>
<span>[[__("upload_cover_image")]]</span>
</label>
</div>
</div>
<div class="modal-section">
<h4 class="modal-title" style="padding-top:0px"><%=__("access_caption")%></h4>
<h4 class="modal-title" style="padding-top:0px">[[__("access_caption")]]</h4>
</div>
</div>

View File

@@ -0,0 +1,84 @@
<div class="modal" v-if="active_modal == 'login'" v-cloak>
<div class="modal-wrapper">
<div class="modal-dialog">
<button type="button" class="btn btn-icon btn-light btn-round close" v-on:click="close_modal()">
<span class="icon icon-cross-1"></span>
</button>
<div class="modal-content">
<div class="modal-body">
<div class="modal-section no-b">
<div id="account-forms-modal">
<div v-show="active_modal_view != 'signup'">
<div class="content">
<form>
<h4>Login</h4>
<div class="tight">
<div class="form-group">
<input class="input" type="text" focus-me="true" v-model="login_email" placeholder="Email">
</div>
<div class="form-group">
<input class="input" type="password" v-model="login_password" placeholder="Password">
</div>
</div>
<span type="submit" class="btn btn-primary btn-xl btn-block btn-round" v-on:click="login_submit_modal(login_email, login_password, $event)">
<span v-show="!loading_user">Log In</span>
<span v-show="loading_user">Logging in…</span>
</span>
<div class="center alert alert-danger" v-if="login_error" v-model="login_error"></div>
<span class="btn btn-link btn-block" v-on:click="active_modal_view='signup'">Create a new account</span>
</form>
</div>
</div>
<div v-show="active_modal_view == 'signup'">
<div class="content">
<form>
<h4>Sign Up</h4>
<div class="tight">
<div class="form-group">
<input class="input" type="text" id="user-name" v-model="name" placeholder="Name">
</div>
<div class="form-group">
<input class="input" type="email" id="user-email" v-model="email" placeholder="Email">
</div>
<div class="form-group">
<input class="input" id="user-password" type="password" v-model="password" placeholder="Password">
</div>
<div class="form-group">
<input class="input" id="user-password-confirmation" type="password" v-model="password_confirmation" placeholder="Repeat Password">
</div>
</div>
<div class="text-center" style="margin-top: -7px; margin-bottom: 30px;"><small>By signing up you agree to our <a href="/terms.html" target="_blank">TOS</a> and <a href="/privacy.html" target="_blank">Privacy Policy.</a></small><br/>
</div>
<span class="btn btn-primary btn-xl btn-round btn-block" v-on:click="signup_submit_modal($event, name, email, password, password_confirmation)">
<span v-if="!creating_user">Sign Up</span>
<span v-if="creating_user">Signing Up…</span>
</span>
<div class="center alert alert-danger" v-if="signup_error">{{signup_error}}</div>
<span class="btn btn-link btn-block" v-on:click="active_modal_view='login'">I already have an account</span>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>

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