mirror of
https://github.com/spacedeck/spacedeck-open.git
synced 2026-01-30 15:05:24 +01:00
Compare commits
139 Commits
docker
...
fix-video-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9a1e85fb94 | ||
|
|
89f48e615f | ||
|
|
8b5aaed92a | ||
|
|
0eac7749cd | ||
|
|
fb217f358a | ||
|
|
e081bcd6fe | ||
|
|
819f1fecb3 | ||
|
|
4387e336bb | ||
|
|
5fcc4866bd | ||
|
|
4c7ab4df02 | ||
|
|
f7453b3977 | ||
|
|
121997ef3e | ||
|
|
16f732a50c | ||
|
|
096482d7eb | ||
|
|
6257e3410e | ||
|
|
7c6d5c83db | ||
|
|
2a2c7011c0 | ||
|
|
32323cbc57 | ||
|
|
cbd0034be1 | ||
|
|
b3927e0646 | ||
|
|
d4407556ca | ||
|
|
52ff6e351b | ||
|
|
137d17269a | ||
|
|
81a45bb0bd | ||
|
|
7dd3ceffc4 | ||
|
|
b60cd0ffa7 | ||
|
|
bd8667feba | ||
|
|
a6616f3463 | ||
|
|
3eb99d2635 | ||
|
|
09b42f24c2 | ||
|
|
a8b8e36ad3 | ||
|
|
b4f0fa16ef | ||
|
|
dccf0465b3 | ||
|
|
7f72992d06 | ||
|
|
ff1da80695 | ||
|
|
83928dee3b | ||
|
|
28f4c5d58b | ||
|
|
6e4685e589 | ||
|
|
9c44aee57a | ||
|
|
c491af64d8 | ||
|
|
6bbeb4383d | ||
|
|
0c5fa597e8 | ||
|
|
8ddbec6b68 | ||
|
|
3ce40c51a6 | ||
|
|
077bf165c7 | ||
|
|
e5fc11d2cd | ||
|
|
a185c1e3a6 | ||
|
|
effeb6c809 | ||
|
|
e61bc1e23f | ||
|
|
051821e26d | ||
|
|
0a4399951a | ||
|
|
f6cfef899e | ||
|
|
b99ec300bb | ||
|
|
b93cc20371 | ||
|
|
46769691d8 | ||
|
|
43d21ddb6c | ||
|
|
b5c6a79c0c | ||
|
|
8e11b6c4fa | ||
|
|
a92b915bc3 | ||
|
|
f5a6adc43b | ||
|
|
40202ff416 | ||
|
|
b07050095d | ||
|
|
9cb04422d8 | ||
|
|
d544caf4a7 | ||
|
|
2dbfae59f9 | ||
|
|
e25a56e85c | ||
|
|
6f1744bc5d | ||
|
|
fab2a61f83 | ||
|
|
9750f08606 | ||
|
|
643b75ebe9 | ||
|
|
2f39dd26be | ||
|
|
3edde7c53c | ||
|
|
01a6bec80e | ||
|
|
bdb2e9fde5 | ||
|
|
7cf68c94a5 | ||
|
|
ecdacd6e11 | ||
|
|
92cf6c4397 | ||
|
|
d6f93051ef | ||
|
|
4073e36441 | ||
|
|
16ffecdb16 | ||
|
|
c05afaba8a | ||
|
|
9d3105763b | ||
|
|
f5e5a7f8fe | ||
|
|
9ed5a9931f | ||
|
|
ddd1ed2cb1 | ||
|
|
2ac0d49f2f | ||
|
|
80f9b0d93f | ||
|
|
58250a72ad | ||
|
|
d19d02220e | ||
|
|
69037685c1 | ||
|
|
3fdb1bf8bb | ||
|
|
b72a3af124 | ||
|
|
f7394d3195 | ||
|
|
3b735d28f6 | ||
|
|
8ba37a11d6 | ||
|
|
58aa3fc41f | ||
|
|
db849bcb20 | ||
|
|
0efeb8d1df | ||
|
|
2fc14e1efe | ||
|
|
79a3d39015 | ||
|
|
628ceb6c3a | ||
|
|
9889aaa34c | ||
|
|
2974caab1d | ||
|
|
ea4f40b628 | ||
|
|
f71081b15b | ||
|
|
c075c562d6 | ||
|
|
dc986dcc7e | ||
|
|
fc653e5ce5 | ||
|
|
4c5e6ea286 | ||
|
|
5f4d41f3a4 | ||
|
|
fb8d3ac654 | ||
|
|
c19f00b316 | ||
|
|
60ccedd840 | ||
|
|
d2f07a73cf | ||
|
|
c5783feca9 | ||
|
|
f396bc2e40 | ||
|
|
44c84028dc | ||
|
|
54755621a1 | ||
|
|
30d2995565 | ||
|
|
f5acdcf614 | ||
|
|
f752ec4219 | ||
|
|
4caef3b913 | ||
|
|
f321d67597 | ||
|
|
ebac854da8 | ||
|
|
8e0bc69a11 | ||
|
|
38c0d75c28 | ||
|
|
65a5534cc6 | ||
|
|
fa81fd3d8a | ||
|
|
648a59c894 | ||
|
|
84d0be50f0 | ||
|
|
256d2b8cbf | ||
|
|
6d2d2310b6 | ||
|
|
462e9edaab | ||
|
|
ffb7f30133 | ||
|
|
8ee3386470 | ||
|
|
0d92343d55 | ||
|
|
efb7970ecb | ||
|
|
7e8a27e140 | ||
|
|
6ad97ac5c2 |
@@ -11,4 +11,4 @@ coverage
|
|||||||
.grunt
|
.grunt
|
||||||
.lock-wscript
|
.lock-wscript
|
||||||
build/Release
|
build/Release
|
||||||
node_modules
|
node_modules
|
||||||
|
|||||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -1,5 +1,8 @@
|
|||||||
node_modules
|
node_modules
|
||||||
public/stylesheets/*
|
|
||||||
javascripts/maps
|
javascripts/maps
|
||||||
javascripts/spacedeck.js
|
javascripts/spacedeck.js
|
||||||
|
public/stylesheets/*.css
|
||||||
|
database.sqlite
|
||||||
|
*.swp
|
||||||
|
*~
|
||||||
|
|
||||||
|
|||||||
43
Dockerfile
43
Dockerfile
@@ -1,19 +1,38 @@
|
|||||||
FROM spacedeck/docker-baseimage:latest
|
FROM node:10-alpine3.11
|
||||||
ENV NODE_ENV production
|
|
||||||
|
|
||||||
RUN mkdir -p /usr/src/app
|
WORKDIR /app
|
||||||
WORKDIR /usr/src/app
|
|
||||||
|
|
||||||
COPY package.json /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 ./
|
||||||
RUN npm install
|
RUN npm install
|
||||||
RUN npm install gulp-rev-replace gulp-clean gulp-fingerprint gulp-rev gulp-rev-all gulp-rev-replace
|
COPY . .
|
||||||
RUN npm install -g --save-dev gulp
|
|
||||||
|
|
||||||
COPY . /usr/src/app
|
# start app
|
||||||
RUN gulp styles
|
|
||||||
RUN npm cache clean
|
|
||||||
|
|
||||||
CMD [ "npm", "start" ]
|
|
||||||
|
|
||||||
EXPOSE 9666
|
EXPOSE 9666
|
||||||
|
CMD ["node", "spacedeck.js"]
|
||||||
|
|
||||||
|
|||||||
62
Gulpfile.js
62
Gulpfile.js
@@ -1,61 +1,13 @@
|
|||||||
var gulp = require('gulp');
|
const gulp = require('gulp')
|
||||||
var sass = require('gulp-sass');
|
const sass = require('gulp-sass')
|
||||||
var concat = require('gulp-concat');
|
const concat = require('gulp-concat')
|
||||||
var server = require('gulp-express');
|
|
||||||
var nodemon = require('gulp-nodemon');
|
|
||||||
var revReplace = require("gulp-rev-replace");
|
|
||||||
var clean = require('gulp-clean');
|
|
||||||
|
|
||||||
var child_process = require('child_process');
|
gulp.task('styles', function(done) {
|
||||||
var path = require('path');
|
|
||||||
var uglify = require('gulp-uglify');
|
|
||||||
var fingerprint = require('gulp-fingerprint');
|
|
||||||
var rev = require('gulp-rev');
|
|
||||||
|
|
||||||
var revAll = require('gulp-rev-all');
|
|
||||||
|
|
||||||
gulp.task('rev', () => {
|
|
||||||
return gulp.src(['public/**'])
|
|
||||||
.pipe(gulp.dest('build/assets'))
|
|
||||||
.pipe(revAll.revision())
|
|
||||||
.pipe(gulp.dest('build/assets'))
|
|
||||||
.pipe(revAll.manifestFile())
|
|
||||||
.pipe(gulp.dest('build/assets'));
|
|
||||||
});
|
|
||||||
|
|
||||||
gulp.task("all", ["styles", "uglify", "rev", "copylocales"], function(){
|
|
||||||
var manifest = gulp.src("./build/assets/rev-manifest.json");
|
|
||||||
return gulp.src("./views/**/*")
|
|
||||||
.pipe(revReplace({manifest: manifest}))
|
|
||||||
.pipe(gulp.dest("./build/views"));
|
|
||||||
});
|
|
||||||
|
|
||||||
gulp.task('copylocales', function(){
|
|
||||||
return gulp.src('./locales/*.js').pipe(gulp.dest('./build/locales'));
|
|
||||||
});
|
|
||||||
|
|
||||||
gulp.task('clean', function () {
|
|
||||||
return gulp.src('./build').pipe(clean());
|
|
||||||
});
|
|
||||||
|
|
||||||
gulp.task('styles', function() {
|
|
||||||
gulp.src('styles/**/*.scss')
|
gulp.src('styles/**/*.scss')
|
||||||
.pipe(sass({
|
.pipe(sass({
|
||||||
errLogToConsole: true
|
errLogToConsole: true
|
||||||
}))
|
}))
|
||||||
.pipe(gulp.dest('./public/stylesheets/'))
|
.pipe(gulp.dest('./public/stylesheets/'))
|
||||||
.pipe(concat('style.css'));
|
.pipe(concat('style.css'))
|
||||||
});
|
done()
|
||||||
|
})
|
||||||
gulp.task('uglify', () => {
|
|
||||||
child_process.exec('sed -n \'s/.*script minify src="\\(.*\\)".*/.\\/public\\/\\1/p\' views/spacedeck.html',
|
|
||||||
function (error, stdout, stderr) {
|
|
||||||
var scripts = stdout.split('\n').map(function(p){return path.normalize(p)}).filter(function(p){return p!='.'});
|
|
||||||
console.log("scripts: ",scripts);
|
|
||||||
|
|
||||||
gulp.src(scripts)
|
|
||||||
.pipe(uglify({output:{beautify:true}}))
|
|
||||||
.pipe(concat('spacedeck.js'))
|
|
||||||
.pipe(gulp.dest('public/javascripts'));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|||||||
98
README.md
98
README.md
@@ -1,70 +1,94 @@
|
|||||||
# Spacedeck Open
|
# Spacedeck Open
|
||||||
|
|
||||||
This is the free and open source version of Spacedeck, a web based, real time, collaborative whiteboard application with rich media support. Spacedeck was developed in 6 major releases during Autumn 2011 until the end of 2016 and was originally a commercial SaaS. The developers were Lukas F. Hartmann (mntmn) and Martin Güther (magegu). All icons and large parts of the CSS were designed by Thomas Helbig (dergraph).
|

|
||||||
|
|
||||||
As we plan to retire the subscription based service at spacedeck.com in late 2017, we decided to open-source Spacedeck to allow educational and other organizations who currently rely on Spacedeck to migrate to a self-hosted version.
|
This is the free and open source version of Spacedeck, a web based, real time, collaborative whiteboard application with rich media support. Spacedeck was developed in 6 major releases during Autumn 2011 until the end of 2016 and was originally a commercial SaaS. The developers were Lukas F. Hartmann (mntmn) and Martin Güther (magegu).
|
||||||
|
|
||||||
Data migration features will be added soon.
|
The spacedeck.com online service was shut down on May 1st 2018. We decided to open-source Spacedeck to allow educational and other organizations who currently rely on Spacedeck to migrate to a self-hosted or local version.
|
||||||
|
|
||||||
|
[MNT Research GmbH](https://mntre.com) has restarted development of Spacedeck Open in 2020.
|
||||||
|
|
||||||
We appreciate filed issues, pull requests and general discussion.
|
We appreciate filed issues, pull requests and general discussion.
|
||||||
|
|
||||||
# Features
|
# Features
|
||||||
|
|
||||||
- Create virtual whiteboards called "Spaces" with virtually unlimited size
|
- Create virtual whiteboards called *Spaces* with virtually unlimited size
|
||||||
- Drag & drop images, videos and audio from your computer or the web
|
- Drag & drop images, videos and audio from your computer or the web
|
||||||
- Write and format text with full control over fonts, colors and style
|
- Write and format text with full control over fonts, colors and style
|
||||||
- Draw, annotate and highlight with included graphical shapes
|
- Draw, annotate and highlight with included graphical shapes
|
||||||
- Turn your Space into a zooming presentation
|
- Turn your Space into a zooming presentation
|
||||||
- Collaborate and chat in realtime with teammates, students or friends
|
- Collaborate in realtime with teammates, students or friends
|
||||||
- Share Spaces on the web or via email
|
- Share Spaces on the web or via email
|
||||||
- Export your work as printable PDF or ZIP
|
- Export your work as printable PDF or ZIP (currently being fixed, stay tuned)
|
||||||
|
|
||||||
|
# Use Cases
|
||||||
|
|
||||||
|
- Education: Virtual classwork with multimedia
|
||||||
|
- Creative: Mood boards, Brainstorming, Design Thinking
|
||||||
|
- Visual note taking and planning
|
||||||
|
|
||||||
# Requirements, Installation
|
# Requirements, Installation
|
||||||
|
|
||||||
Spacedeck uses the following major building blocks:
|
Spacedeck requires:
|
||||||
|
|
||||||
- Vue.js (Frontend)
|
- Node.js 10.x: Web Server / API. Download: https://nodejs.org
|
||||||
- Node.js 7.x (Backend / API)
|
- Graphicsmagick. On non-Linux, Download: http://www.graphicsmagick.org/ On Linux, install via package manager.
|
||||||
- MongoDB 3.x (Datastore)
|
- Optionally ffmpeg, audiowaveform and ghostscript. See "Optional Dependencies" below.
|
||||||
- Redis 3.x (Datastore for realtime channels)
|
|
||||||
|
|
||||||
It also has some binary dependencies for media conversion and PDF export:
|
To run Spacedeck, you only need Node.JS 10.x.
|
||||||
|
|
||||||
- imagemagick, graphicsmagick, libav(+codecs, ffmpeg replacement), audiowaveform (https://github.com/bbcrd/audiowaveform), phantomjs (http://phantomjs.org/)
|
To install all node dependencies, run (do this once):
|
||||||
|
|
||||||
Currently, media files are stored in Amazon S3, so you need an Amazon AWS account and have the ```AWS_ACCESS_KEY_ID``` and ```AWS_SECRET_ACCESS_KEY``` environment variables defined. For sending emails in production, Amazon SES is required.
|
|
||||||
|
|
||||||
To install Spacedeck, you need node.js 4.x and a running MongoDB instance. Then, to install all node dependencies, run
|
|
||||||
|
|
||||||
npm install
|
npm install
|
||||||
|
|
||||||
To rebuild the frontend CSS styles (you need to do this at least once):
|
# Configuration
|
||||||
|
|
||||||
|
See [config/default.json](config/default.json). 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`.
|
||||||
|
|
||||||
|
# Run (web server)
|
||||||
|
|
||||||
|
node spacedeck.js
|
||||||
|
|
||||||
|
Then open http://localhost:9666 in a web browser.
|
||||||
|
|
||||||
|
# Optional Dependencies
|
||||||
|
|
||||||
|
For advanced media conversion:
|
||||||
|
|
||||||
|
- ffmpeg and ffprobe for video/audio conversion. Download: https://www.ffmpeg.org/download.html
|
||||||
|
- audiowaveform for audio waveform rendering. Download: https://github.com/bbcrd/audiowaveform
|
||||||
|
- ghostscript for PDF import. Download: https://www.ghostscript.com/download/gsdnld.html
|
||||||
|
|
||||||
|
# Data Storage
|
||||||
|
|
||||||
|
By default, media files are uploaded to the ```storage``` folder.
|
||||||
|
The database is stored in ```database.sqlite``` by default.
|
||||||
|
|
||||||
|
# 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
|
gulp styles
|
||||||
|
|
||||||
# Configuration
|
|
||||||
|
|
||||||
see: config/config.json
|
|
||||||
|
|
||||||
# Run
|
|
||||||
|
|
||||||
export NODE_ENV=development
|
|
||||||
npm start
|
|
||||||
open http://localhost:9666
|
|
||||||
|
|
||||||
# experimental docker(compose) support
|
|
||||||
|
|
||||||
We have a docker base image at https://github.com/spacedeck/docker-baseimage that includes all required binaries. Based on this image we can use Docker-Compose to bootstrap a Spacedeck including data storages.
|
|
||||||
|
|
||||||
docker-compose build
|
|
||||||
docker-compose run -e ENV=development -p 9666:9666 -e NODE_ENV=development spacedeck-open
|
|
||||||
|
|
||||||
# License
|
# License
|
||||||
|
|
||||||
Spacedeck Open is released under the GNU Affero General Public License Version 3 (GNU AGPLv3).
|
The Spacedeck logo and brand assets are registered trademarks of Spacedeck GmbH. All rights reserved.
|
||||||
|
|
||||||
|
Spacedeck Open source code is released under the GNU Affero General Public License Version 3 (GNU AGPLv3).
|
||||||
|
|
||||||
Spacedeck Open - Web-based Collaborative Whiteboard For Rich Media
|
Spacedeck Open - Web-based Collaborative Whiteboard For Rich Media
|
||||||
Copyright (C) 2011-2017 Lukas F. Hartmann, Martin Güther, Thomas Helbig
|
Copyright (C) 2011-2018 Lukas F. Hartmann, Martin Güther
|
||||||
|
Icons and original CSS design copyright by Thomas Helbig
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as
|
it under the terms of the GNU Affero General Public License as
|
||||||
|
|||||||
5
bin/www
5
bin/www
@@ -1,5 +0,0 @@
|
|||||||
#!/usr/bin/env node
|
|
||||||
|
|
||||||
var app = require('../app');
|
|
||||||
var http = require('http');
|
|
||||||
var server = http.createServer(app);
|
|
||||||
@@ -1,10 +1,30 @@
|
|||||||
{
|
{
|
||||||
|
"team_name": "My Open Spacedeck",
|
||||||
|
"contact_email": "support@example.org",
|
||||||
|
|
||||||
|
"host": "::",
|
||||||
|
"port": 9666,
|
||||||
"endpoint": "http://localhost:9666",
|
"endpoint": "http://localhost:9666",
|
||||||
"storage_bucket": "sdeck-development",
|
"invite_code": "top-sekrit",
|
||||||
"storage_cdn": "http://localhost:9123/sdeck-development",
|
|
||||||
"storage_endpoint": "http://storage:9000",
|
"storage_local_path": "./storage",
|
||||||
"google_access" : "",
|
"storage_local_db": "./database.sqlite",
|
||||||
"google_secret" : "",
|
"storage_region": "eu-central-1",
|
||||||
"admin_pass": "very_secret_admin_password",
|
"storage_endpoint": "http://localhost:4572",
|
||||||
"phantom_api_secret": "very_secret_phantom_password"
|
"storage_bucket": "my_spacedeck_bucket",
|
||||||
|
"storage_cdn": "/storage",
|
||||||
|
|
||||||
|
"mongodb_host": "localhost",
|
||||||
|
"redis_mock": true,
|
||||||
|
"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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,34 +1,12 @@
|
|||||||
version: '2'
|
version: "2.0"
|
||||||
|
|
||||||
services:
|
services:
|
||||||
sync:
|
spacedeck:
|
||||||
image: redis
|
|
||||||
storage:
|
|
||||||
image: minio/minio
|
|
||||||
environment:
|
|
||||||
- MINIO_ACCESS_KEY=AKIAIOSFODNN7EXAMPLE
|
|
||||||
- MINIO_SECRET_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
|
|
||||||
ports:
|
|
||||||
- 9123:9000
|
|
||||||
command: server /export
|
|
||||||
db:
|
|
||||||
image: mongo
|
|
||||||
spacedeck-open:
|
|
||||||
environment:
|
|
||||||
- env=development
|
|
||||||
- MINIO_ACCESS_KEY=AKIAIOSFODNN7EXAMPLE
|
|
||||||
- MINIO_SECRET_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
|
|
||||||
build: .
|
build: .
|
||||||
volumes:
|
container_name: spacedeck
|
||||||
# - ./:/usr/src/app
|
|
||||||
- /usr/src/app/node_modules
|
|
||||||
command: npm start
|
|
||||||
ports:
|
ports:
|
||||||
- 9666:9666
|
- "9666:9666"
|
||||||
depends_on:
|
volumes:
|
||||||
- db
|
- /absolute/path/to/storage:/app/storage
|
||||||
- sync
|
- /absolute/path/to/database.sqlite:/app/database.sqlite
|
||||||
- storage
|
|
||||||
links:
|
|
||||||
- storage
|
|
||||||
- db
|
|
||||||
- sync
|
|
||||||
|
|||||||
6
docs/adding_fonts.md
Normal file
6
docs/adding_fonts.md
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
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.
|
||||||
26
docs/adding_languages.md
Normal file
26
docs/adding_languages.md
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
## 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>
|
||||||
|
```
|
||||||
51
docs/apache_setup.md
Normal file
51
docs/apache_setup.md
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
|
||||||
|
# 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>
|
||||||
|
```
|
||||||
@@ -4,51 +4,18 @@ const exec = require('child_process');
|
|||||||
const gm = require('gm');
|
const gm = require('gm');
|
||||||
const async = require('async');
|
const async = require('async');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const Models = require('../models/schema');
|
const Models = require('../models/db');
|
||||||
const uploader = require('../helpers/uploader');
|
const uploader = require('../helpers/uploader');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
const os = require('os');
|
||||||
|
|
||||||
const fileExtensionMap = {
|
const db = require('../models/db');
|
||||||
".amr" : "audio/AMR",
|
const Sequelize = require('sequelize');
|
||||||
".ogg" : "audio/ogg",
|
const Op = Sequelize.Op;
|
||||||
".aac" : "audio/aac",
|
|
||||||
".mp3" : "audio/mpeg",
|
const mime = require('mime-types');
|
||||||
".mpg" : "video/mpeg",
|
const fileType = require('file-type');
|
||||||
".3ga" : "audio/3ga",
|
const readChunk = require('read-chunk');
|
||||||
".mp4" : "video/mp4",
|
|
||||||
".wav" : "audio/wav",
|
|
||||||
".mov" : "video/quicktime",
|
|
||||||
".doc" : "application/msword",
|
|
||||||
".dot" : "application/msword",
|
|
||||||
".docx" : "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
||||||
".dotx" : "application/vnd.openxmlformats-officedocument.wordprocessingml.template",
|
|
||||||
".docm" : "application/vnd.ms-word.document.macroEnabled.12",
|
|
||||||
".dotm" : "application/vnd.ms-word.template.macroEnabled.12",
|
|
||||||
".xls" : "application/vnd.ms-excel",
|
|
||||||
".xlt" : "application/vnd.ms-excel",
|
|
||||||
".xla" : "application/vnd.ms-excel",
|
|
||||||
".xlsx" : "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
||||||
".xltx" : "application/vnd.openxmlformats-officedocument.spreadsheetml.template",
|
|
||||||
".xlsm" : "application/vnd.ms-excel.sheet.macroEnabled.12",
|
|
||||||
".xltm" : "application/vnd.ms-excel.template.macroEnabled.12",
|
|
||||||
".xlam" : "application/vnd.ms-excel.addin.macroEnabled.12",
|
|
||||||
".xlsb" : "application/vnd.ms-excel.sheet.binary.macroEnabled.12",
|
|
||||||
".ppt" : "application/vnd.ms-powerpoint",
|
|
||||||
".pot" : "application/vnd.ms-powerpoint",
|
|
||||||
".pps" : "application/vnd.ms-powerpoint",
|
|
||||||
".ppa" : "application/vnd.ms-powerpoint",
|
|
||||||
".pptx" : "application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
||||||
".potx" : "application/vnd.openxmlformats-officedocument.presentationml.template",
|
|
||||||
".ppsx" : "application/vnd.openxmlformats-officedocument.presentationml.slideshow",
|
|
||||||
".ppam" : "application/vnd.ms-powerpoint.addin.macroEnabled.12",
|
|
||||||
".pptm" : "application/vnd.ms-powerpoint.presentation.macroEnabled.12",
|
|
||||||
".potm" : "application/vnd.ms-powerpoint.template.macroEnabled.12",
|
|
||||||
".ppsm" : "application/vnd.ms-powerpoint.slideshow.macroEnabled.12",
|
|
||||||
".key" : "application/x-iwork-keynote-sffkey",
|
|
||||||
".pages" : "application/x-iwork-pages-sffpages",
|
|
||||||
".numbers" : "application/x-iwork-numbers-sffnumbers",
|
|
||||||
".ttf" : "application/x-font-ttf"
|
|
||||||
};
|
|
||||||
|
|
||||||
const convertableImageTypes = [
|
const convertableImageTypes = [
|
||||||
"image/png",
|
"image/png",
|
||||||
@@ -68,9 +35,9 @@ const convertableVideoTypes = [
|
|||||||
|
|
||||||
const convertableAudioTypes = [
|
const convertableAudioTypes = [
|
||||||
"application/ogg",
|
"application/ogg",
|
||||||
"audio/AMR",
|
"audio/amr",
|
||||||
"audio/3ga",
|
"audio/3ga",
|
||||||
"audio/wav",
|
"audio/wave",
|
||||||
"audio/3gpp",
|
"audio/3gpp",
|
||||||
"audio/x-wav",
|
"audio/x-wav",
|
||||||
"audio/aiff",
|
"audio/aiff",
|
||||||
@@ -115,7 +82,7 @@ function createWaveform(fileName, localFilePath, callback){
|
|||||||
"-i", localFilePath, "-o", filePathImage
|
"-i", localFilePath, "-o", filePathImage
|
||||||
],
|
],
|
||||||
{}, function(error, stdout, stderr) {
|
{}, function(error, stdout, stderr) {
|
||||||
if(!error) {
|
if (!error) {
|
||||||
callback(null, filePathImage);
|
callback(null, filePathImage);
|
||||||
} else {
|
} else {
|
||||||
console.log("error:", stdout, stderr);
|
console.log("error:", stdout, stderr);
|
||||||
@@ -125,14 +92,14 @@ function createWaveform(fileName, localFilePath, callback){
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function convertVideo(fileName, filePath, codec, callback, progress_callback) {
|
function convertVideo(fileName, filePath, codec, callback, progressCallback) {
|
||||||
var ext = path.extname(fileName);
|
var ext = path.extname(fileName);
|
||||||
var presetMime = fileExtensionMap[ext];
|
var presetMime = mime.lookup(fileName);
|
||||||
|
|
||||||
var newExt = codec == "mp4" ? "mp4" : "ogv";
|
var newExt = codec == "mp4" ? "mp4" : "ogv";
|
||||||
var convertedPath = filePath + "." + newExt;
|
var convertedPath = filePath + "." + newExt;
|
||||||
|
|
||||||
console.log("converting", filePath, "to", convertedPath, "progress_cb:",progress_callback);
|
console.log("convertVideo", filePath, "to", convertedPath);
|
||||||
|
|
||||||
var convertArgs = (codec == "mp4") ? [
|
var convertArgs = (codec == "mp4") ? [
|
||||||
"-i", filePath,
|
"-i", filePath,
|
||||||
@@ -167,14 +134,14 @@ function convertVideo(fileName, filePath, codec, callback, progress_callback) {
|
|||||||
|
|
||||||
ff.stderr.on('data', function (data) {
|
ff.stderr.on('data', function (data) {
|
||||||
console.log('[ffmpeg-video] stderr: ' + data);
|
console.log('[ffmpeg-video] stderr: ' + data);
|
||||||
if (progress_callback) {
|
if (progressCallback) {
|
||||||
progress_callback(data);
|
progressCallback(data);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
ff.on('close', function (code) {
|
ff.on('close', function (code) {
|
||||||
console.log('[ffmpeg-video] child process exited with code ' + code);
|
console.log('[ffmpeg-video] child process exited with code ' + code);
|
||||||
if (!code) {
|
if (!code) {
|
||||||
console.log("converted", filePath, "to", convertedPath);
|
console.log("converted", filePath, "to", convertedPath);
|
||||||
callback(null, convertedPath);
|
callback(null, convertedPath);
|
||||||
} else {
|
} else {
|
||||||
@@ -185,7 +152,7 @@ function convertVideo(fileName, filePath, codec, callback, progress_callback) {
|
|||||||
|
|
||||||
function convertAudio(fileName, filePath, codec, callback) {
|
function convertAudio(fileName, filePath, codec, callback) {
|
||||||
var ext = path.extname(fileName);
|
var ext = path.extname(fileName);
|
||||||
var presetMime = fileExtensionMap[ext];
|
var presetMime = mime.lookup(fileName);
|
||||||
|
|
||||||
var newExt = codec == "mp3" ? "mp3" : "ogg";
|
var newExt = codec == "mp3" ? "mp3" : "ogg";
|
||||||
var convertedPath = filePath + "." + newExt;
|
var convertedPath = filePath + "." + newExt;
|
||||||
@@ -222,22 +189,14 @@ function createThumbnailForVideo(fileName, filePath, callback) {
|
|||||||
|
|
||||||
function getMime(fileName, filePath, callback) {
|
function getMime(fileName, filePath, callback) {
|
||||||
var ext = path.extname(fileName);
|
var ext = path.extname(fileName);
|
||||||
var presetMime = fileExtensionMap[ext];
|
var presetMime = mime.lookup(fileName);
|
||||||
|
|
||||||
if (presetMime) {
|
if (presetMime) {
|
||||||
callback(null, presetMime);
|
callback(null, presetMime);
|
||||||
} else {
|
} else {
|
||||||
exec.execFile("file", ["-b","--mime-type", filePath], {}, function(error, stdout, stderr) {
|
const buffer = readChunk.sync(filePath, 0, 4100);
|
||||||
console.log("file stdout: ",stdout);
|
var mimeType = fileType(buffer);
|
||||||
if (stderr === '' && error == null) {
|
callback(null, mimeType);
|
||||||
//filter special chars from commandline
|
|
||||||
var mime = stdout.replace(/[^a-zA-Z0-9\/\-]/g,'');
|
|
||||||
callback(null, mime);
|
|
||||||
} else {
|
|
||||||
console.log("getMime file error: ", error);
|
|
||||||
callback(error, null);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -245,13 +204,12 @@ function resizeAndUpload(a, size, max, fileName, localFilePath, callback) {
|
|||||||
if (max>320 || size.width > max || size.height > max) {
|
if (max>320 || size.width > max || size.height > max) {
|
||||||
var resizedFileName = max + "_"+fileName;
|
var resizedFileName = max + "_"+fileName;
|
||||||
var s3Key = "s"+ a.space_id.toString() + "/a" + a._id.toString() + "/" + resizedFileName;
|
var s3Key = "s"+ a.space_id.toString() + "/a" + a._id.toString() + "/" + resizedFileName;
|
||||||
var localResizedFilePath = "/tmp/"+resizedFileName;
|
var localResizedFilePath = os.tmpdir()+"/"+resizedFileName;
|
||||||
gm(localFilePath).resize(max, max).autoOrient().write(localResizedFilePath, function (err) {
|
gm(localFilePath).resize(max, max).autoOrient().write(localResizedFilePath, function (err) {
|
||||||
if(!err) {
|
if(!err) {
|
||||||
uploader.uploadFile(s3Key, "image/jpeg", localResizedFilePath, function(err, url) {
|
uploader.uploadFile(s3Key, "image/jpeg", localResizedFilePath, function(err, url) {
|
||||||
if (err) callback(err);
|
if (err) callback(err);
|
||||||
else{
|
else{
|
||||||
console.log(localResizedFilePath);
|
|
||||||
fs.unlink(localResizedFilePath, function (err) {
|
fs.unlink(localResizedFilePath, function (err) {
|
||||||
if (err) {
|
if (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
@@ -271,8 +229,7 @@ function resizeAndUpload(a, size, max, fileName, localFilePath, callback) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var resizeAndUploadImage = function(a, mimeType, size, fileName, fileNameOrg, imageFilePath, originalFilePath, payloadCallback) {
|
||||||
var resizeAndUploadImage = function(a, mime, size, fileName, fileNameOrg, imageFilePath, originalFilePath, payloadCallback) {
|
|
||||||
async.parallel({
|
async.parallel({
|
||||||
small: function(callback){
|
small: function(callback){
|
||||||
resizeAndUpload(a, size, 320, fileName, imageFilePath, callback);
|
resizeAndUpload(a, size, 320, fileName, imageFilePath, callback);
|
||||||
@@ -285,13 +242,13 @@ var resizeAndUploadImage = function(a, mime, size, fileName, fileNameOrg, imageF
|
|||||||
},
|
},
|
||||||
original: function(callback){
|
original: function(callback){
|
||||||
var s3Key = "s"+ a.space_id.toString() + "/a" + a._id + "/" + fileNameOrg;
|
var s3Key = "s"+ a.space_id.toString() + "/a" + a._id + "/" + fileNameOrg;
|
||||||
uploader.uploadFile(s3Key, mime, originalFilePath, function(err, url){
|
uploader.uploadFile(s3Key, mimeType, originalFilePath, function(err, url){
|
||||||
callback(null, url);
|
callback(null, url);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, function(err, results) {
|
}, function(err, results) {
|
||||||
a.state = "idle";
|
a.state = "idle";
|
||||||
a.mime = mime;
|
a.mime = mimeType;
|
||||||
var stats = fs.statSync(originalFilePath);
|
var stats = fs.statSync(originalFilePath);
|
||||||
|
|
||||||
a.payload_size = stats["size"];
|
a.payload_size = stats["size"];
|
||||||
@@ -301,46 +258,43 @@ var resizeAndUploadImage = function(a, mime, size, fileName, fileNameOrg, imageF
|
|||||||
a.payload_uri = results.original;
|
a.payload_uri = results.original;
|
||||||
|
|
||||||
var factor = 320/size.width;
|
var factor = 320/size.width;
|
||||||
var newBoardSpecs = a.board;
|
a.w = Math.round(size.width*factor);
|
||||||
newBoardSpecs.w = Math.round(size.width*factor);
|
a.h = Math.round(size.height*factor);
|
||||||
newBoardSpecs.h = Math.round(size.height*factor);
|
|
||||||
a.board = newBoardSpecs;
|
|
||||||
|
|
||||||
a.updated_at = new Date();
|
a.updated_at = new Date();
|
||||||
a.save(function(err) {
|
db.packArtifact(a);
|
||||||
if(err) payloadCallback(err, null);
|
|
||||||
else {
|
a.save().then(function() {
|
||||||
fs.unlink(originalFilePath, function (err) {
|
fs.unlink(originalFilePath, function (err) {
|
||||||
if (err){
|
if (err){
|
||||||
console.error(err);
|
console.error(err);
|
||||||
payloadCallback(err, null);
|
payloadCallback(err, null);
|
||||||
} else {
|
} else {
|
||||||
console.log('successfully deleted ' + originalFilePath);
|
console.log('successfully deleted ' + originalFilePath);
|
||||||
payloadCallback(null, a);
|
payloadCallback(null, a);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
convert: function(a, fileName, localFilePath, payloadCallback, progress_callback) {
|
convert: function(a, fileName, localFilePath, payloadCallback, progressCallback) {
|
||||||
getMime(fileName, localFilePath, function(err, mime){
|
getMime(fileName, localFilePath, function(err, mimeType) {
|
||||||
console.log("[convert] fn: "+fileName+" local: "+localFilePath+" mime:", mime);
|
console.log("[convert] fn: "+fileName+" local: "+localFilePath+" mimeType:", mimeType);
|
||||||
|
|
||||||
if (!err) {
|
if (!err) {
|
||||||
if (convertableImageTypes.indexOf(mime) > -1) {
|
if (convertableImageTypes.indexOf(mimeType) > -1) {
|
||||||
|
|
||||||
gm(localFilePath).size(function (err, size) {
|
gm(localFilePath).size(function (err, size) {
|
||||||
console.log("[convert] gm:", err, size);
|
console.log("[convert] gm:", err, size);
|
||||||
|
|
||||||
if (!err) {
|
if (!err) {
|
||||||
if(mime == "application/pdf") {
|
if (mimeType == "application/pdf") {
|
||||||
var firstImagePath = localFilePath + ".jpeg";
|
var firstImagePath = localFilePath + ".jpeg";
|
||||||
exec.execFile("gs", ["-sDEVICE=jpeg","-dNOPAUSE", "-dJPEGQ=80", "-dBATCH", "-dFirstPage=1", "-dLastPage=1", "-sOutputFile=" + firstImagePath, "-r90", "-f", localFilePath], {}, function(error, stdout, stderr) {
|
exec.execFile("gs", ["-sDEVICE=jpeg","-dNOPAUSE", "-dJPEGQ=80", "-dBATCH", "-dFirstPage=1", "-dLastPage=1", "-sOutputFile=" + firstImagePath, "-r90", "-f", localFilePath], {}, function(error, stdout, stderr) {
|
||||||
if(error === null) {
|
if(error === null) {
|
||||||
resizeAndUploadImage(a, mime, size, fileName + ".jpeg", fileName, firstImagePath, localFilePath, function(err, a) {
|
resizeAndUploadImage(a, mimeType, size, fileName + ".jpeg", fileName, firstImagePath, localFilePath, function(err, a) {
|
||||||
fs.unlink(firstImagePath, function (err) {
|
fs.unlink(firstImagePath, function (err) {
|
||||||
payloadCallback(err, a);
|
payloadCallback(err, a);
|
||||||
});
|
});
|
||||||
@@ -350,19 +304,19 @@ module.exports = {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
} else if(mime == "image/gif") {
|
} else if (mimeType == "image/gif") {
|
||||||
//gifs are buggy after convertion, so we should not convert them
|
//gifs are buggy after convertion, so we should not convert them
|
||||||
|
|
||||||
var s3Key = "s"+ a.space_id.toString() + "/a" + a._id.toString() + "/" + fileName;
|
var s3Key = "s"+ a.space_id.toString() + "/a" + a._id.toString() + "/" + fileName;
|
||||||
|
|
||||||
uploader.uploadFile(s3Key, "image/gif", localFilePath, function(err, url) {
|
uploader.uploadFile(s3Key, "image/gif", localFilePath, function(err, url) {
|
||||||
if(err)callback(err);
|
if (err) payloadCallback(err);
|
||||||
else{
|
else {
|
||||||
console.log(localFilePath);
|
console.log(localFilePath);
|
||||||
var stats = fs.statSync(localFilePath);
|
var stats = fs.statSync(localFilePath);
|
||||||
|
|
||||||
a.state = "idle";
|
a.state = "idle";
|
||||||
a.mime = mime;
|
a.mime = mimeType;
|
||||||
|
|
||||||
a.payload_size = stats["size"];
|
a.payload_size = stats["size"];
|
||||||
a.payload_thumbnail_web_uri = url;
|
a.payload_thumbnail_web_uri = url;
|
||||||
@@ -371,42 +325,39 @@ module.exports = {
|
|||||||
a.payload_uri = url;
|
a.payload_uri = url;
|
||||||
|
|
||||||
var factor = 320/size.width;
|
var factor = 320/size.width;
|
||||||
var newBoardSpecs = a.board;
|
a.w = Math.round(size.width*factor);
|
||||||
newBoardSpecs.w = Math.round(size.width*factor);
|
a.h = Math.round(size.height*factor);
|
||||||
newBoardSpecs.h = Math.round(size.height*factor);
|
|
||||||
a.board = newBoardSpecs;
|
|
||||||
|
|
||||||
a.updated_at = new Date();
|
a.updated_at = new Date();
|
||||||
a.save(function(err){
|
db.packArtifact(a);
|
||||||
if(err) payloadCallback(err, null);
|
|
||||||
else {
|
a.save().then(function() {
|
||||||
fs.unlink(localFilePath, function (err) {
|
fs.unlink(localFilePath, function (err) {
|
||||||
if (err){
|
if (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
payloadCallback(err, null);
|
payloadCallback(err, null);
|
||||||
} else {
|
} else {
|
||||||
console.log('successfully deleted ' + localFilePath);
|
console.log('successfully deleted ' + localFilePath);
|
||||||
payloadCallback(null, a);
|
payloadCallback(null, a);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
resizeAndUploadImage(a, mime, size, fileName, fileName, localFilePath, localFilePath, payloadCallback);
|
resizeAndUploadImage(a, mimeType, size, fileName, fileName, localFilePath, localFilePath, payloadCallback);
|
||||||
}
|
}
|
||||||
} else payloadCallback(err);
|
} else payloadCallback(err);
|
||||||
});
|
});
|
||||||
|
|
||||||
} else if (convertableVideoTypes.indexOf(mime) > -1) {
|
} else if (convertableVideoTypes.indexOf(mimeType) > -1) {
|
||||||
async.parallel({
|
async.parallel({
|
||||||
thumbnail: function(callback) {
|
thumbnail: function(callback) {
|
||||||
createThumbnailForVideo(fileName, localFilePath, function(err, created){
|
createThumbnailForVideo(fileName, localFilePath, function(err, created){
|
||||||
console.log("thumbnail created: ", err, created);
|
console.log("thumbnail created: ", err, created);
|
||||||
if(err) callback(err);
|
if (err) callback(err);
|
||||||
else{
|
else {
|
||||||
var keyName = "s" + a.space_id.toString() + "/a" + a._id.toString() + "/" + fileName + ".jpg" ;
|
var keyName = "s" + a.space_id.toString() + "/a" + a._id.toString() + "/" + fileName + ".jpg" ;
|
||||||
uploader.uploadFile(keyName, "image/jpeg", created, function(err, url){
|
uploader.uploadFile(keyName, "image/jpeg", created, function(err, url){
|
||||||
if (err) callback(err);
|
if (err) callback(err);
|
||||||
@@ -416,7 +367,7 @@ module.exports = {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
ogg: function(callback) {
|
ogg: function(callback) {
|
||||||
if (mime == "video/ogg") {
|
if (mimeType == "video/ogg") {
|
||||||
callback(null, "org");
|
callback(null, "org");
|
||||||
} else {
|
} else {
|
||||||
convertVideo(fileName, localFilePath, "ogg", function(err, file) {
|
convertVideo(fileName, localFilePath, "ogg", function(err, file) {
|
||||||
@@ -428,11 +379,11 @@ module.exports = {
|
|||||||
else callback(null, url);
|
else callback(null, url);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, progress_callback);
|
}, progressCallback);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mp4: function(callback) {
|
mp4: function(callback) {
|
||||||
if (mime == "video/mp4") {
|
if (mimeType == "video/mp4") {
|
||||||
callback(null, "org");
|
callback(null, "org");
|
||||||
} else {
|
} else {
|
||||||
convertVideo(fileName, localFilePath, "mp4", function(err, file) {
|
convertVideo(fileName, localFilePath, "mp4", function(err, file) {
|
||||||
@@ -444,21 +395,21 @@ module.exports = {
|
|||||||
else callback(null, url);
|
else callback(null, url);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, progress_callback);
|
}, progressCallback);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
original: function(callback){
|
original: function(callback){
|
||||||
uploader.uploadFile(fileName, mime, localFilePath, function(err, url){
|
uploader.uploadFile(fileName, mimeType, localFilePath, function(err, url){
|
||||||
callback(null, url);
|
callback(null, url);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, function(err, results){
|
}, function(err, results) {
|
||||||
console.log(err, results);
|
console.log(err, results);
|
||||||
|
|
||||||
if (err) payloadCallback(err, a);
|
if (err) payloadCallback(err, a);
|
||||||
else {
|
else {
|
||||||
a.state = "idle";
|
a.state = "idle";
|
||||||
a.mime = mime;
|
a.mime = mimeType;
|
||||||
var stats = fs.statSync(localFilePath);
|
var stats = fs.statSync(localFilePath);
|
||||||
|
|
||||||
a.payload_size = stats["size"];
|
a.payload_size = stats["size"];
|
||||||
@@ -467,7 +418,7 @@ module.exports = {
|
|||||||
a.payload_thumbnail_big_uri = results.thumbnail;
|
a.payload_thumbnail_big_uri = results.thumbnail;
|
||||||
a.payload_uri = results.original;
|
a.payload_uri = results.original;
|
||||||
|
|
||||||
if (mime == "video/mp4") {
|
if (mimeType == "video/mp4") {
|
||||||
a.payload_alternatives = [
|
a.payload_alternatives = [
|
||||||
{
|
{
|
||||||
mime: "video/ogg",
|
mime: "video/ogg",
|
||||||
@@ -483,30 +434,28 @@ module.exports = {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
db.packArtifact(a);
|
||||||
|
|
||||||
a.updated_at = new Date();
|
a.updated_at = new Date();
|
||||||
a.save(function(err) {
|
a.save().then(function() {
|
||||||
if (err) payloadCallback(err, null);
|
fs.unlink(localFilePath, function (err) {
|
||||||
else {
|
if (err) {
|
||||||
fs.unlink(localFilePath, function (err) {
|
console.error(err);
|
||||||
if (err){
|
payloadCallback(err, null);
|
||||||
console.error(err);
|
} else {
|
||||||
payloadCallback(err, null);
|
console.log('successfully deleted ' + localFilePath);
|
||||||
} else {
|
payloadCallback(null, a);
|
||||||
console.log('successfully deleted ' + localFilePath);
|
}
|
||||||
payloadCallback(null, a);
|
});
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
} else if (convertableAudioTypes.indexOf(mime) > -1) {
|
} else if (convertableAudioTypes.indexOf(mimeType) > -1) {
|
||||||
|
|
||||||
async.parallel({
|
async.parallel({
|
||||||
ogg: function(callback) {
|
ogg: function(callback) {
|
||||||
convertAudio(fileName, localFilePath, "ogg", function(err, file) {
|
convertAudio(fileName, localFilePath, "ogg", function(err, file) {
|
||||||
if(err) callback(err);
|
if (err) callback(err);
|
||||||
else {
|
else {
|
||||||
var keyName = "s" + a.space_id.toString() + "/a" + a._id.toString() + "/" + fileName + ".ogg" ;
|
var keyName = "s" + a.space_id.toString() + "/a" + a._id.toString() + "/" + fileName + ".ogg" ;
|
||||||
uploader.uploadFile(keyName, "audio/ogg", file, function(err, url){
|
uploader.uploadFile(keyName, "audio/ogg", file, function(err, url){
|
||||||
@@ -518,20 +467,16 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
mp3_waveform: function(callback) {
|
mp3_waveform: function(callback) {
|
||||||
convertAudio(fileName, localFilePath, "mp3", function(err, file) {
|
convertAudio(fileName, localFilePath, "mp3", function(err, file) {
|
||||||
if(err) callback(err);
|
if (err) callback(err);
|
||||||
else {
|
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";
|
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" ;
|
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);
|
if (err) callback(err);
|
||||||
else callback(null, {waveform: pngUrl, mp3: mp3Url});
|
else callback(null, {waveform: pngUrl, mp3: mp3Url});
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -539,7 +484,7 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
original: function(callback) {
|
original: function(callback) {
|
||||||
var keyName = "s" + a.space_id.toString() + "/a" + a._id.toString() + "/" + fileName;
|
var keyName = "s" + a.space_id.toString() + "/a" + a._id.toString() + "/" + fileName;
|
||||||
uploader.uploadFile(keyName, mime, localFilePath, function(err, url){
|
uploader.uploadFile(keyName, mimeType, localFilePath, function(err, url) {
|
||||||
callback(null, url);
|
callback(null, url);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -548,9 +493,8 @@ module.exports = {
|
|||||||
|
|
||||||
if (err) payloadCallback(err, a);
|
if (err) payloadCallback(err, a);
|
||||||
else {
|
else {
|
||||||
|
|
||||||
a.state = "idle";
|
a.state = "idle";
|
||||||
a.mime = mime;
|
a.mime = mimeType;
|
||||||
var stats = fs.statSync(localFilePath);
|
var stats = fs.statSync(localFilePath);
|
||||||
|
|
||||||
a.payload_size = stats["size"];
|
a.payload_size = stats["size"];
|
||||||
@@ -564,43 +508,39 @@ module.exports = {
|
|||||||
];
|
];
|
||||||
|
|
||||||
a.updated_at = new Date();
|
a.updated_at = new Date();
|
||||||
a.save(function(err){
|
db.packArtifact(a);
|
||||||
if(err) payloadCallback(err, null);
|
|
||||||
else {
|
a.save().then(function() {
|
||||||
fs.unlink(localFilePath, function (err) {
|
fs.unlink(localFilePath, function (err) {
|
||||||
if (err){
|
if (err){
|
||||||
console.error(err);
|
console.error(err);
|
||||||
payloadCallback(err, null);
|
payloadCallback(err, null);
|
||||||
} else {
|
} else {
|
||||||
console.log('successfully deleted ' + localFilePath);
|
console.log('successfully deleted ' + localFilePath);
|
||||||
payloadCallback(null, a);
|
payloadCallback(null, a);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
console.log("mime not matched for conversion, storing file");
|
console.log("mimeType not matched for conversion, storing file");
|
||||||
var keyName = "s" + a.space_id.toString() + "/a" + a._id.toString() + "/" + fileName;
|
var keyName = "s" + a.space_id.toString() + "/a" + a._id.toString() + "/" + fileName;
|
||||||
uploader.uploadFile(keyName, mime, localFilePath, function(err, url) {
|
uploader.uploadFile(keyName, mimeType, localFilePath, function(err, url) {
|
||||||
|
|
||||||
a.state = "idle";
|
a.state = "idle";
|
||||||
a.mime = mime;
|
a.mime = mimeType;
|
||||||
var stats = fs.statSync(localFilePath);
|
var stats = fs.statSync(localFilePath);
|
||||||
a.payload_size = stats["size"];
|
a.payload_size = stats["size"];
|
||||||
a.payload_uri = url;
|
a.payload_uri = url;
|
||||||
|
|
||||||
a.updated_at = new Date();
|
a.updated_at = new Date();
|
||||||
a.save(function(err) {
|
a.save().then(function() {
|
||||||
if(err) payloadCallback(err, null);
|
fs.unlink(localFilePath, function (err) {
|
||||||
else {
|
payloadCallback(null, a);
|
||||||
fs.unlink(localFilePath, function (err) {
|
});
|
||||||
payloadCallback(null, a);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -611,5 +551,3 @@ module.exports = {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
128
helpers/importer.js
Normal file
128
helpers/importer.js
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const extract = require('extract-zip')
|
||||||
|
const config = require('config')
|
||||||
|
const fs = require('fs')
|
||||||
|
const path = require('path')
|
||||||
|
|
||||||
|
const db = require('../models/db')
|
||||||
|
const Sequelize = require('sequelize')
|
||||||
|
const Op = Sequelize.Op
|
||||||
|
const uuidv4 = require('uuid/v4')
|
||||||
|
|
||||||
|
require('../models/db')
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
importZIP: function(user, zipPath) {
|
||||||
|
|
||||||
|
// 1. extract zip to local storage folder
|
||||||
|
// 2. read spaces.json from this folder
|
||||||
|
// 3. iterate through spaces and read all their artifact jsons
|
||||||
|
// 4. fixup storage paths
|
||||||
|
// 5. replace creator id by user._id
|
||||||
|
|
||||||
|
let relativeImportDir = 'import_'+user._id
|
||||||
|
let importDir = path.resolve(config.get('storage_local_path')+'/'+config.get('storage_bucket')+'/'+relativeImportDir)
|
||||||
|
|
||||||
|
if (!fs.existsSync(importDir)) {
|
||||||
|
fs.mkdirSync(importDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
extract(zipPath, {dir: importDir}, function(err) {
|
||||||
|
if (err) {
|
||||||
|
console.log(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
console.log('[import] extracted to',importDir)
|
||||||
|
|
||||||
|
let spacesJson = fs.readFileSync(importDir+'/spaces.json')
|
||||||
|
let spaces = JSON.parse(spacesJson)
|
||||||
|
var homeFolderId = null
|
||||||
|
|
||||||
|
console.log('[import] spaces:',spaces.length)
|
||||||
|
|
||||||
|
// pass 1: find homefolder
|
||||||
|
for (var i=0; i<spaces.length; i++) {
|
||||||
|
let space = spaces[i]
|
||||||
|
if (!space.parent_space_id) {
|
||||||
|
homeFolderId = space._id
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("[import] homeFolderId:",homeFolderId)
|
||||||
|
|
||||||
|
for (var i=0; i<spaces.length; i++) {
|
||||||
|
let space = spaces[i]
|
||||||
|
|
||||||
|
if (space.parent_space_id) {
|
||||||
|
let artifacts = JSON.parse(fs.readFileSync(importDir+'/'+space._id+'_artifacts.json'))
|
||||||
|
console.log('[import] space',space._id,'artifacts:',artifacts.length)
|
||||||
|
|
||||||
|
//let q = {where: {_id: space._id}}
|
||||||
|
space.creator_id = user._id
|
||||||
|
delete space.__v
|
||||||
|
|
||||||
|
// transplant homefolder
|
||||||
|
console.log("parent:",space.parent_space_id)
|
||||||
|
if (space.parent_space_id+"" == homeFolderId+"") {
|
||||||
|
space.parent_space_id = user.home_folder_id
|
||||||
|
}
|
||||||
|
|
||||||
|
// move nested attrs
|
||||||
|
console.log(space)
|
||||||
|
for (k in space.advanced) {
|
||||||
|
space[k] = space.advanced[k]
|
||||||
|
}
|
||||||
|
|
||||||
|
db.Space.create(space)
|
||||||
|
.error((err) => {
|
||||||
|
console.error("[import] space upsert err:",err)
|
||||||
|
})
|
||||||
|
|
||||||
|
for (var j=0; j<artifacts.length; j++) {
|
||||||
|
let a = artifacts[j]
|
||||||
|
|
||||||
|
let q = {_id: a._id}
|
||||||
|
a.user_id = user._id
|
||||||
|
delete a.__v
|
||||||
|
delete a.payload_thumbnail_big_uri
|
||||||
|
|
||||||
|
// move nested attrs
|
||||||
|
for (k in a.style) {
|
||||||
|
a[k] = a.style[k]
|
||||||
|
}
|
||||||
|
for (k in a.meta) {
|
||||||
|
a[k] = a.meta[k]
|
||||||
|
}
|
||||||
|
for (k in a.board) {
|
||||||
|
a[k] = a.board[k]
|
||||||
|
}
|
||||||
|
|
||||||
|
let prefix = "/storage/"+relativeImportDir+"/"+space._id+"_files/"
|
||||||
|
if (a.thumbnail_uri && a.thumbnail_uri[0]!='/') a.thumbnail_uri = prefix + a.thumbnail_uri
|
||||||
|
if (a.payload_uri && a.payload_uri[0]!='/') a.payload_uri = prefix + a.payload_uri
|
||||||
|
if (a.payload_thumbnail_web_uri && a.payload_thumbnail_web_uri[0]!='/') a.payload_thumbnail_web_uri = prefix + a.payload_thumbnail_web_uri
|
||||||
|
if (a.payload_thumbnail_medium_uri && a.payload_thumbnail_medium_uri[0]!='/') a.payload_thumbnail_medium_uri = prefix + a.payload_thumbnail_medium_uri
|
||||||
|
|
||||||
|
if (a.payload_alternatives) {
|
||||||
|
for (var k=0; k<a.payload_alternatives.length; k++) {
|
||||||
|
let alt = a.payload_alternatives[k]
|
||||||
|
|
||||||
|
if (alt.payload_uri && alt.payload_uri[0]!='/') alt.payload_uri = prefix + alt.payload_uri
|
||||||
|
if (alt.payload_thumbnail_web_uri && alt.payload_thumbnail_web_uri[0]!='/') alt.payload_thumbnail_web_uri = prefix + alt.payload_thumbnail_web_uri
|
||||||
|
if (alt.payload_thumbnail_medium_uri && alt.payload_thumbnail_medium_uri[0]!='/') alt.payload_thumbnail_medium_uri = prefix + alt.payload_thumbnail_medium_uri
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
db.packArtifact(a)
|
||||||
|
|
||||||
|
db.Artifact.create(a).error(function(err) {
|
||||||
|
console.error("[import] artifact upsert err:",err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,18 +1,16 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var swig = require('swig');
|
const config = require('config');
|
||||||
var AWS = require('aws-sdk');
|
const nodemailer = require('nodemailer');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
sendMail: (to_email, subject, body, options) => {
|
sendMail: (to_email, subject, body, options) => {
|
||||||
|
|
||||||
if (!options) {
|
if (!options) {
|
||||||
options = {};
|
options = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME
|
const teamname = options.teamname || config.get('team_name');
|
||||||
const teamname = options.teamname || "My Open Spacedeck"
|
const from = teamname + ' <' + config.get('contact_email') + '>';
|
||||||
const from = teamname + ' <support@example.org>';
|
|
||||||
|
|
||||||
let reply_to = [from];
|
let reply_to = [from];
|
||||||
if (options.reply_to) {
|
if (options.reply_to) {
|
||||||
@@ -24,38 +22,46 @@ module.exports = {
|
|||||||
plaintext+="\n"+options.action.link+"\n\n";
|
plaintext+="\n"+options.action.link+"\n\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
const htmlText = swig.renderFile('./views/emails/action.html', {
|
if (config.get('mail_provider') === 'console') {
|
||||||
text: body.replace(/(?:\n)/g, '<br />'),
|
|
||||||
options: options
|
|
||||||
});
|
|
||||||
|
|
||||||
if (process.env.NODE_ENV === 'development') {
|
console.log("Email: to " + to_email + " in production.\nreply_to: " + reply_to + "\nsubject: " + subject + "\nbody: \n" + plaintext + "\n\n plaintext:\n" + plaintext);
|
||||||
console.log("Email: to " + to_email + " in production.\nreply_to: " + reply_to + "\nsubject: " + subject + "\nbody: \n" + htmlText + "\n\n plaintext:\n" + plaintext);
|
|
||||||
} else {
|
|
||||||
AWS.config.update({region: 'eu-west-1'});
|
|
||||||
var ses = new AWS.SES();
|
|
||||||
|
|
||||||
ses.sendEmail( {
|
} else if (config.get('mail_provider') === 'smtp') {
|
||||||
Source: from,
|
let transporter;
|
||||||
Destination: { ToAddresses: [to_email] },
|
if (config.has('mail_smtp_user')) {
|
||||||
ReplyToAddresses: reply_to,
|
transporter = nodemailer.createTransport({
|
||||||
Message: {
|
host: config.get('mail_smtp_host'),
|
||||||
Subject: {
|
port: config.get('mail_smtp_port'),
|
||||||
Data: subject
|
secure: config.get('mail_smtp_secure'),
|
||||||
},
|
requireTLS: config.get('mail_smtp_require_tls'),
|
||||||
Body: {
|
auth: {
|
||||||
Text: {
|
user: config.get('mail_smtp_user'),
|
||||||
Data: plaintext,
|
pass: config.get('mail_smtp_pass'),
|
||||||
},
|
|
||||||
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.log('Email not sent:', err);
|
|
||||||
else console.log("Email sent.");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
require('../models/schema');
|
const db = require('../models/db');
|
||||||
var config = require('config');
|
const config = require('config');
|
||||||
var phantom = require('node-phantom-simple');
|
const phantom = require('node-phantom-simple');
|
||||||
|
const os = require('os');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
// type = "pdf" or "png"
|
// type = "pdf" or "png"
|
||||||
@@ -10,7 +11,7 @@ module.exports = {
|
|||||||
var spaceId = space._id;
|
var spaceId = space._id;
|
||||||
var space_url = config.get("endpoint")+"/api/spaces/"+spaceId+"/html";
|
var space_url = config.get("endpoint")+"/api/spaces/"+spaceId+"/html";
|
||||||
|
|
||||||
var export_path = "/tmp/"+spaceId+"."+type;
|
var export_path = os.tmpdir()+"/"+spaceId+"."+type;
|
||||||
|
|
||||||
var timeout = 5000;
|
var timeout = 5000;
|
||||||
if (type=="pdf") timeout = 30000;
|
if (type=="pdf") timeout = 30000;
|
||||||
@@ -24,7 +25,7 @@ module.exports = {
|
|||||||
|
|
||||||
var on_exit = function(exit_code) {
|
var on_exit = function(exit_code) {
|
||||||
if (exit_code>0) {
|
if (exit_code>0) {
|
||||||
console.log("phantom abnormal exit for url "+space_url);
|
console.error("phantom abnormal exit for url "+space_url);
|
||||||
if (!on_success_called && on_error) {
|
if (!on_success_called && on_error) {
|
||||||
on_error();
|
on_error();
|
||||||
}
|
}
|
||||||
@@ -32,16 +33,16 @@ module.exports = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
phantom.create({ path: require('phantomjs-prebuilt').path }, function (err, browser) {
|
phantom.create({ path: require('phantomjs-prebuilt').path }, function (err, browser) {
|
||||||
if(err){
|
if (err) {
|
||||||
console.log(err);
|
console.error(err);
|
||||||
}else{
|
} else {
|
||||||
return browser.createPage(function (err, page) {
|
return browser.createPage(function (err, page) {
|
||||||
console.log("page created, opening ",space_url);
|
console.log("page created, opening ",space_url);
|
||||||
|
|
||||||
if (type=="pdf") {
|
if (type=="pdf") {
|
||||||
var psz = {
|
var psz = {
|
||||||
width: space.advanced.width+"px",
|
width: space.width+"px",
|
||||||
height: space.advanced.height+"px"
|
height: space.height+"px"
|
||||||
};
|
};
|
||||||
page.set('paperSize', psz);
|
page.set('paperSize', psz);
|
||||||
}
|
}
|
||||||
|
|||||||
118
helpers/redis.js
118
helpers/redis.js
@@ -1,14 +1,111 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const RedisConnection = require('ioredis');
|
const config = require('config');
|
||||||
const websockets = require('./websockets');
|
|
||||||
|
// this is a mock version of the Redis API,
|
||||||
|
// emulating Redis if it is not available locally
|
||||||
|
var notRedis = {
|
||||||
|
state: {},
|
||||||
|
topics: {},
|
||||||
|
|
||||||
|
publish: function(topic, msg, cb) {
|
||||||
|
if (!this.topics[topic]) {
|
||||||
|
this.topics[topic] = {
|
||||||
|
subscribers: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
var t=this.topics[topic];
|
||||||
|
for (var i=0; i<t.subscribers.length; i++) {
|
||||||
|
var s=t.subscribers[i];
|
||||||
|
if (s.handler) {
|
||||||
|
s.handler(topic, msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (cb) cb(null);
|
||||||
|
},
|
||||||
|
|
||||||
|
subscribe: function(topics, cb) {
|
||||||
|
var handle = {
|
||||||
|
handler: null,
|
||||||
|
on: function(evt, cb) {
|
||||||
|
if (evt == "message") {
|
||||||
|
this.handler = cb;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
for (var i=0; i<topics.length; i++) {
|
||||||
|
var topic = topics[i];
|
||||||
|
if (!this.topics[topic]) {
|
||||||
|
this.topics[topic] = {
|
||||||
|
subscribers: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
var t=this.topics[topic];
|
||||||
|
t.subscribers.push(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
cb(null, topics.length);
|
||||||
|
return handle;
|
||||||
|
},
|
||||||
|
|
||||||
|
get: function(key, cb) {
|
||||||
|
cb(null, this.state[key]);
|
||||||
|
return this.state[key];
|
||||||
|
},
|
||||||
|
|
||||||
|
set: function(key, val, cb) {
|
||||||
|
this.state[key] = val;
|
||||||
|
cb();
|
||||||
|
},
|
||||||
|
|
||||||
|
del: function(key, cb) {
|
||||||
|
delete this.state[key];
|
||||||
|
cb(null);
|
||||||
|
},
|
||||||
|
|
||||||
|
sadd: function(key, skey, cb) {
|
||||||
|
if (!this.state[key]) this.state[key] = {};
|
||||||
|
this.state[key][skey] = true;
|
||||||
|
cb(null);
|
||||||
|
},
|
||||||
|
|
||||||
|
srem: function(key, skey, cb) {
|
||||||
|
if (this.state[key]) {
|
||||||
|
delete this.state[key][skey];
|
||||||
|
}
|
||||||
|
cb(null);
|
||||||
|
},
|
||||||
|
|
||||||
|
smembers: function(key, cb) {
|
||||||
|
cb(null, Object.keys(this.state[key]));
|
||||||
|
},
|
||||||
|
|
||||||
|
incr: function(key, cb) {
|
||||||
|
if (!this.state[key]) this.state[key] = 0;
|
||||||
|
this.state[key]++;
|
||||||
|
cb(null, this.state[key]);
|
||||||
|
},
|
||||||
|
|
||||||
|
expire: function() {
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
connectRedis(){
|
connectRedis: function() {
|
||||||
const redisHost = process.env.REDIS_PORT_6379_TCP_ADDR || 'sync';
|
if (config.get("redis_mock")) {
|
||||||
this.connection = new RedisConnection(6379, redisHost);
|
this.connection = notRedis;
|
||||||
|
} else {
|
||||||
|
const redisHost = process.env.REDIS_PORT_6379_TCP_ADDR || 'sync';
|
||||||
|
this.connection = new RedisConnection(6379, redisHost);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
sendMessage(action, model, attributes, channelId) {
|
getConnection: function() {
|
||||||
|
this.connectRedis();
|
||||||
|
return this.connection;
|
||||||
|
},
|
||||||
|
sendMessage: function(action, model, attributes, channelId) {
|
||||||
const data = JSON.stringify({
|
const data = JSON.stringify({
|
||||||
channel_id: channelId,
|
channel_id: channelId,
|
||||||
action: action,
|
action: action,
|
||||||
@@ -17,12 +114,12 @@ module.exports = {
|
|||||||
});
|
});
|
||||||
this.connection.publish('updates', data);
|
this.connection.publish('updates', data);
|
||||||
},
|
},
|
||||||
logIp(ip, cb) {
|
logIp: function(ip, cb) {
|
||||||
this.connection.incr("ip_"+ ip, (err, socketCounter) => {
|
this.connection.incr("ip_"+ ip, (err, socketCounter) => {
|
||||||
cb();
|
cb();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
rateLimit(namespace, ip, cb) {
|
rateLimit: function(namespace, ip, cb) {
|
||||||
const key = "limit_"+ namespace + "_"+ ip;
|
const key = "limit_"+ namespace + "_"+ ip;
|
||||||
const redis = this.connection;
|
const redis = this.connection;
|
||||||
|
|
||||||
@@ -47,7 +144,7 @@ module.exports = {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
isOnlineInSpace(user, space, cb) {
|
isOnlineInSpace: function(user, space, cb) {
|
||||||
this.connection.smembers("space_" + space._id.toString(), function(err, list) {
|
this.connection.smembers("space_" + space._id.toString(), function(err, list) {
|
||||||
if (err) cb(err);
|
if (err) cb(err);
|
||||||
else {
|
else {
|
||||||
@@ -59,3 +156,6 @@ module.exports = {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
return module.exports;
|
||||||
|
|
||||||
|
|||||||
@@ -1,28 +1,30 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var AWS = require('aws-sdk');
|
|
||||||
AWS.config.region = 'eu-central-1';
|
|
||||||
|
|
||||||
var fs = require('fs');
|
var fs = require('fs');
|
||||||
var config = require('config');
|
var config = require('config');
|
||||||
|
var s3 = null;
|
||||||
|
|
||||||
var cdn = config.get("storage_cdn");
|
// use AWS S3 or local folder depending on config
|
||||||
var storage_endpoint = config.get("storage_endpoint");
|
if (config.get("storage_local_path")) {
|
||||||
|
var AWS = require('mock-aws-s3');
|
||||||
|
AWS.config.basePath = config.get("storage_local_path");
|
||||||
|
s3 = new AWS.S3();
|
||||||
|
} else {
|
||||||
|
var AWS = require('aws-sdk');
|
||||||
|
var storage_endpoint = config.get("storage_endpoint");
|
||||||
|
const ep = new AWS.Endpoint(storage_endpoint);
|
||||||
|
|
||||||
const bucketName = "sdeck-fresh-development";
|
AWS.config.update(new AWS.Config({
|
||||||
const ep = new AWS.Endpoint(storage_endpoint);
|
accessKeyId: process.env.MINIO_ACCESS_KEY,
|
||||||
|
secretAccessKey: process.env.MINIO_SECRET_KEY,
|
||||||
AWS.config.update(new AWS.Config({
|
region: config.get("storage_region"),
|
||||||
accessKeyId: process.env.MINIO_ACCESS_KEY,
|
s3ForcePathStyle: true,
|
||||||
secretAccessKey: process.env.MINIO_SECRET_KEY,
|
signatureVersion: 'v4'
|
||||||
region: 'us-east-1',
|
}));
|
||||||
s3ForcePathStyle: true,
|
s3 = new AWS.S3({
|
||||||
signatureVersion: 'v4'
|
endpoint: ep
|
||||||
}));
|
});
|
||||||
|
}
|
||||||
const s3 = new AWS.S3({
|
|
||||||
endpoint: ep
|
|
||||||
});
|
|
||||||
|
|
||||||
s3.createBucket({
|
s3.createBucket({
|
||||||
Bucket: config.get("storage_bucket"),
|
Bucket: config.get("storage_bucket"),
|
||||||
@@ -71,8 +73,8 @@ module.exports = {
|
|||||||
if (err){
|
if (err){
|
||||||
console.error(err);
|
console.error(err);
|
||||||
callback(err);
|
callback(err);
|
||||||
}else {
|
} else {
|
||||||
const url = cdn + "/" + fileName;
|
const url = config.get("storage_cdn") + "/" + fileName;
|
||||||
console.log("[s3]" + localFilePath + " to " + url);
|
console.log("[s3]" + localFilePath + " to " + url);
|
||||||
callback(null, url);
|
callback(null, url);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,30 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
require('../models/schema');
|
|
||||||
|
const db = require('../models/db');
|
||||||
|
const Sequelize = require('sequelize');
|
||||||
|
const Op = Sequelize.Op;
|
||||||
|
|
||||||
|
const config = require('config');
|
||||||
|
|
||||||
const WebSocketServer = require('ws').Server;
|
const WebSocketServer = require('ws').Server;
|
||||||
|
|
||||||
const RedisConnection = require('ioredis');
|
//const RedisConnection = require('ioredis');
|
||||||
const async = require('async');
|
const async = require('async');
|
||||||
const _ = require("underscore");
|
const _ = require("underscore");
|
||||||
const mongoose = require("mongoose");
|
|
||||||
const crypto = require('crypto');
|
const crypto = require('crypto');
|
||||||
|
|
||||||
module.exports = {
|
const redisMock = require("./redis.js");
|
||||||
startWebsockets: function(server){
|
|
||||||
this.setupSubscription();
|
|
||||||
this.state = new RedisConnection(6379, process.env.REDIS_PORT_6379_TCP_ADDR || 'sync');
|
|
||||||
|
|
||||||
if(!this.current_websockets){
|
module.exports = {
|
||||||
|
startWebsockets: function(server) {
|
||||||
|
this.setupSubscription();
|
||||||
|
|
||||||
|
if (!this.current_websockets) {
|
||||||
|
if (config.get("redis_mock")) {
|
||||||
|
this.state = redisMock.getConnection();
|
||||||
|
} else {
|
||||||
|
this.state = new RedisConnection(6379, process.env.REDIS_PORT_6379_TCP_ADDR || config.get("redis_host"));
|
||||||
|
}
|
||||||
this.current_websockets = [];
|
this.current_websockets = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,11 +47,11 @@ module.exports = {
|
|||||||
const editorAuth = msg.editor_auth;
|
const editorAuth = msg.editor_auth;
|
||||||
const spaceId = msg.space_id;
|
const spaceId = msg.space_id;
|
||||||
|
|
||||||
Space.findOne({"_id": spaceId}).populate('creator').exec((err, space) => {
|
db.Space.findOne({where: {"_id": spaceId}}).then(space => {
|
||||||
if (space) {
|
if (space) {
|
||||||
const upgradeSocket = function() {
|
const upgradeSocket = function() {
|
||||||
if (token) {
|
if (token) {
|
||||||
User.findBySessionToken(token, function(err, user) {
|
db.findUserBySessionToken(token, function(err, user) {
|
||||||
if (err) {
|
if (err) {
|
||||||
console.error(err, user);
|
console.error(err, user);
|
||||||
} else {
|
} else {
|
||||||
@@ -117,10 +127,17 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
setupSubscription: function() {
|
setupSubscription: function() {
|
||||||
this.cursorSubscriber = new RedisConnection(6379, process.env.REDIS_PORT_6379_TCP_ADDR || 'sync');
|
if (config.get("redis_mock")) {
|
||||||
this.cursorSubscriber.subscribe(['cursors', 'users', 'updates'], function (err, count) {
|
this.cursorSubscriber = redisMock.getConnection().subscribe(['cursors', 'users', 'updates'], function (err, count) {
|
||||||
console.log("[redis] websockets to " + count + " topics." );
|
console.log("[redis-mock] websockets subscribed to " + count + " topics." );
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
this.cursorSubscriber = new RedisConnection(6379, process.env.REDIS_PORT_6379_TCP_ADDR || config.get("redis_host"));
|
||||||
|
this.cursorSubscriber.subscribe(['cursors', 'users', 'updates'], function (err, count) {
|
||||||
|
console.log("[redis] websockets subscribed to " + count + " topics." );
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
this.cursorSubscriber.on('message', function (channel, rawMessage) {
|
this.cursorSubscriber.on('message', function (channel, rawMessage) {
|
||||||
const msg = JSON.parse(rawMessage);
|
const msg = JSON.parse(rawMessage);
|
||||||
const spaceId = msg.space_id;
|
const spaceId = msg.space_id;
|
||||||
@@ -206,7 +223,7 @@ module.exports = {
|
|||||||
console.log("websocket not found to remove");
|
console.log("websocket not found to remove");
|
||||||
}
|
}
|
||||||
|
|
||||||
this.state.del(ws.id, function(err, res) {
|
this.state.del(ws.id+"", function(err, res) {
|
||||||
if (err) console.error(err, res);
|
if (err) console.error(err, res);
|
||||||
else {
|
else {
|
||||||
this.removeUserInSpace(ws.space_id, ws, (err) => {
|
this.removeUserInSpace(ws.space_id, ws, (err) => {
|
||||||
@@ -221,7 +238,8 @@ module.exports = {
|
|||||||
|
|
||||||
addUserInSpace: function(username, space, ws, cb) {
|
addUserInSpace: function(username, space, ws, cb) {
|
||||||
console.log("[websockets] user "+username+" in "+space.access_mode +" space " + space._id + " with socket " + ws.id);
|
console.log("[websockets] user "+username+" in "+space.access_mode +" space " + space._id + " with socket " + ws.id);
|
||||||
this.state.set(ws.id, username, function(err, res) {
|
|
||||||
|
this.state.set(ws.id+"", username+"", function(err, res) {
|
||||||
if(err) console.error(err, res);
|
if(err) console.error(err, res);
|
||||||
else {
|
else {
|
||||||
this.state.sadd("space_" + space._id, ws.id, function(err, res) {
|
this.state.sadd("space_" + space._id, ws.id, function(err, res) {
|
||||||
@@ -238,7 +256,7 @@ module.exports = {
|
|||||||
}.bind(this));
|
}.bind(this));
|
||||||
},
|
},
|
||||||
removeUserInSpace: function(spaceId, ws, cb) {
|
removeUserInSpace: function(spaceId, ws, cb) {
|
||||||
this.state.srem("space_" + spaceId, ws.id, function(err, res) {
|
this.state.srem("space_" + spaceId, ws.id+"", function(err, res) {
|
||||||
if (err) cb(err);
|
if (err) cb(err);
|
||||||
else {
|
else {
|
||||||
console.log("[websockets] socket "+ ws.id + " went offline in space " + spaceId);
|
console.log("[websockets] socket "+ ws.id + " went offline in space " + spaceId);
|
||||||
@@ -252,10 +270,10 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
distributeUsers: function(spaceId) {
|
distributeUsers: function(spaceId) {
|
||||||
if(!spaceId)
|
if (!spaceId)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
this.state.smembers("space_" + spaceId, function(err, list) {
|
/*this.state.smembers("space_" + spaceId, function(err, list) {
|
||||||
async.map(list, function(item, callback) {
|
async.map(list, function(item, callback) {
|
||||||
this.state.get(item, function(err, userId) {
|
this.state.get(item, function(err, userId) {
|
||||||
console.log(item, "->", userId);
|
console.log(item, "->", userId);
|
||||||
@@ -276,16 +294,14 @@ module.exports = {
|
|||||||
return {nickname: realNickname, email: null, avatar_thumbnail_uri: null };
|
return {nickname: realNickname, email: null, avatar_thumbnail_uri: null };
|
||||||
});
|
});
|
||||||
|
|
||||||
User.find({"_id" : { "$in" : validUserIds }}, { "nickname" : 1 , "email" : 1, "avatar_thumbnail_uri": 1 }, function(err, users) {
|
db.User.findAll({where: {
|
||||||
if (err)
|
"_id" : { "$in" : validUserIds }}, attributes: ["nickname","email","avatar_thumbnail_uri"]})
|
||||||
console.error(err);
|
.then(users) {
|
||||||
else {
|
|
||||||
const allUsers = users.concat(anonymousUsers);
|
const allUsers = users.concat(anonymousUsers);
|
||||||
const strUsers = JSON.stringify({users: allUsers, space_id: spaceId});
|
const strUsers = JSON.stringify({users: allUsers, space_id: spaceId});
|
||||||
this.state.publish("users", strUsers);
|
this.state.publish("users", strUsers);
|
||||||
}
|
}.bind(this));
|
||||||
}.bind(this));
|
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
}.bind(this));
|
}.bind(this));*/
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
202
integrations/wordpress/plugins/spacedeck/spacedeck.php
Normal file
202
integrations/wordpress/plugins/spacedeck/spacedeck.php
Normal file
@@ -0,0 +1,202 @@
|
|||||||
|
<?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
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
?>
|
||||||
@@ -46,7 +46,7 @@
|
|||||||
"specify": "Bitte spezifiziere",
|
"specify": "Bitte spezifiziere",
|
||||||
"confirm": "Bitte bestätige",
|
"confirm": "Bitte bestätige",
|
||||||
"signup_google": "Mit Google anmelden",
|
"signup_google": "Mit Google anmelden",
|
||||||
"error_unknown_email": "Unbekannte Kombination von Email und Passwort. Oder versuche dich mit Google anzumelden.",
|
"error_unknown_email": "Unbekannte Kombination von Email und Passwort.",
|
||||||
"error_password_confirmation": "Die beiden Passwörter stimmen nicht überein.",
|
"error_password_confirmation": "Die beiden Passwörter stimmen nicht überein.",
|
||||||
"error_domain_blocked": "Diese Domain ist gesperrt.",
|
"error_domain_blocked": "Diese Domain ist gesperrt.",
|
||||||
"error_user_email_already_used": "Diese Email-Adresse ist bereits registriert.",
|
"error_user_email_already_used": "Diese Email-Adresse ist bereits registriert.",
|
||||||
@@ -173,6 +173,7 @@
|
|||||||
"tool_styles": "Stil",
|
"tool_styles": "Stil",
|
||||||
"tool_bullets": "Bullets",
|
"tool_bullets": "Bullets",
|
||||||
"tool_numbers": "Zahlen",
|
"tool_numbers": "Zahlen",
|
||||||
|
"tool_font": "Font",
|
||||||
"color_fill": "Füllung",
|
"color_fill": "Füllung",
|
||||||
"color_stroke": "Strich",
|
"color_stroke": "Strich",
|
||||||
"color_text": "Text",
|
"color_text": "Text",
|
||||||
@@ -303,6 +304,7 @@
|
|||||||
"sharing": "sharing",
|
"sharing": "sharing",
|
||||||
"list": "Liste",
|
"list": "Liste",
|
||||||
"download_space": "Space Herunterladen",
|
"download_space": "Space Herunterladen",
|
||||||
|
"download_as_pdf": "Space als PDF herunterladen",
|
||||||
"duplicate_destination_folder": "Zielordner für Duplikat",
|
"duplicate_destination_folder": "Zielordner für Duplikat",
|
||||||
"type": "Typ",
|
"type": "Typ",
|
||||||
"promote": "Befördern",
|
"promote": "Befördern",
|
||||||
@@ -317,5 +319,6 @@
|
|||||||
"more": "mehr",
|
"more": "mehr",
|
||||||
"follow_present": "Folgen",
|
"follow_present": "Folgen",
|
||||||
"mute_present": "Entfolgen",
|
"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."
|
"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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,8 +44,7 @@
|
|||||||
"sure": "Are you sure?",
|
"sure": "Are you sure?",
|
||||||
"specify": "Please Specify",
|
"specify": "Please Specify",
|
||||||
"confirm": "Please Confirm",
|
"confirm": "Please Confirm",
|
||||||
"signup_google": "Sign In with Google",
|
"error_unknown_email": "This email/password combination is unknown.",
|
||||||
"error_unknown_email": "This email/password combination is unknown. Try login with Google.",
|
|
||||||
"error_password_confirmation": "The entered passwords don't match.",
|
"error_password_confirmation": "The entered passwords don't match.",
|
||||||
"error_domain_blocked": "Your domain is blocked.",
|
"error_domain_blocked": "Your domain is blocked.",
|
||||||
"error_user_email_already_used": "This email address is already in use.",
|
"error_user_email_already_used": "This email address is already in use.",
|
||||||
@@ -173,6 +172,7 @@
|
|||||||
"tool_styles": "Styles",
|
"tool_styles": "Styles",
|
||||||
"tool_bullets": "Bullets",
|
"tool_bullets": "Bullets",
|
||||||
"tool_numbers": "Numbers",
|
"tool_numbers": "Numbers",
|
||||||
|
"tool_font": "Font",
|
||||||
"color_fill": "Fill",
|
"color_fill": "Fill",
|
||||||
"color_stroke": "Stroke",
|
"color_stroke": "Stroke",
|
||||||
"color_text": "Text",
|
"color_text": "Text",
|
||||||
@@ -309,6 +309,7 @@
|
|||||||
"list": "Export List",
|
"list": "Export List",
|
||||||
"link": "Link",
|
"link": "Link",
|
||||||
"download_space": "Download Space",
|
"download_space": "Download Space",
|
||||||
|
"download_as_pdf": "Download Space as PDF",
|
||||||
"type": "Type",
|
"type": "Type",
|
||||||
"download": "Download",
|
"download": "Download",
|
||||||
"Previous Zone": "Previous Zone",
|
"Previous Zone": "Previous Zone",
|
||||||
@@ -321,5 +322,6 @@
|
|||||||
"follow_present": "Follow",
|
"follow_present": "Follow",
|
||||||
"mute_present": "Unfollow",
|
"mute_present": "Unfollow",
|
||||||
"follow_present_help": "If someone else is presenting this Space, the other members automatically follow the presentation. Switch following on or off with this button.",
|
"follow_present_help": "If someone else is presenting this Space, the other members automatically follow the presentation. Switch following on or off with this button.",
|
||||||
"export": "export"
|
"export": "export",
|
||||||
|
"media": "Media"
|
||||||
}
|
}
|
||||||
327
locales/es.js
Normal file
327
locales/es.js
Normal file
@@ -0,0 +1,327 @@
|
|||||||
|
{
|
||||||
|
"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"
|
||||||
|
}
|
||||||
@@ -46,7 +46,7 @@
|
|||||||
"specify": "Veuillez préciser:",
|
"specify": "Veuillez préciser:",
|
||||||
"confirm": "Veuillez confirmer",
|
"confirm": "Veuillez confirmer",
|
||||||
"signup_google": "S'inscrire avec Google",
|
"signup_google": "S'inscrire avec Google",
|
||||||
"error_unknown_email": "Combinaison inconnue de l'email et mot de passe. Ou essayer de signer avec Google.",
|
"error_unknown_email": "Combinaison inconnue de l'email et mot de passe.",
|
||||||
"error_password_confirmation": "Les deux mots de passe ne correspondent pas.",
|
"error_password_confirmation": "Les deux mots de passe ne correspondent pas.",
|
||||||
"error_domain_blocked": "Ce domaine a été désactivé.",
|
"error_domain_blocked": "Ce domaine a été désactivé.",
|
||||||
"error_user_email_already_used": "Cette adresse email est déjà enregistré.",
|
"error_user_email_already_used": "Cette adresse email est déjà enregistré.",
|
||||||
@@ -172,6 +172,7 @@
|
|||||||
"format_h3": "Titre 3",
|
"format_h3": "Titre 3",
|
||||||
"font_size": "Taille de la police",
|
"font_size": "Taille de la police",
|
||||||
"line_height": "Hauteur de ligne",
|
"line_height": "Hauteur de ligne",
|
||||||
|
"tool_font": "Font",
|
||||||
"tool_align": "Align",
|
"tool_align": "Align",
|
||||||
"tool_styles": "Style",
|
"tool_styles": "Style",
|
||||||
"tool_bullets": "Puces",
|
"tool_bullets": "Puces",
|
||||||
@@ -301,6 +302,7 @@
|
|||||||
"goto_folder": "Aller au dossier %s",
|
"goto_folder": "Aller au dossier %s",
|
||||||
"stay_here": "Reste ici",
|
"stay_here": "Reste ici",
|
||||||
"download_space": "télécharger un espace",
|
"download_space": "télécharger un espace",
|
||||||
|
"download_as_pdf": "télécharger un espace comme PDF",
|
||||||
"type": "Type",
|
"type": "Type",
|
||||||
"Previous Zone": "Zone précédent",
|
"Previous Zone": "Zone précédent",
|
||||||
"Next Zone": "Zone suivante",
|
"Next Zone": "Zone suivante",
|
||||||
@@ -314,5 +316,6 @@
|
|||||||
"more": "plus",
|
"more": "plus",
|
||||||
"follow_present": "Suivre",
|
"follow_present": "Suivre",
|
||||||
"mute_present": "Pas suivre",
|
"mute_present": "Pas suivre",
|
||||||
"follow_present_help": "follow_present_help"
|
"follow_present_help": "follow_present_help",
|
||||||
}
|
"media": "Media"
|
||||||
|
}
|
||||||
|
|||||||
327
locales/oc.js
Normal file
327
locales/oc.js
Normal file
@@ -0,0 +1,327 @@
|
|||||||
|
{
|
||||||
|
"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": "S’inscriure",
|
||||||
|
"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 d’especificar",
|
||||||
|
"confirm": "Mercés de confirmar",
|
||||||
|
"error_unknown_email": "Aquesta combinason d’adreç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 d’opcions.",
|
||||||
|
"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 d’equipa per %s",
|
||||||
|
"team_invite_membership_body": "Qualqu’un vos a convidat a %s sus Spacedeck. Mercés de clicar sul ligam seguent per acceptar l’invitacion.",
|
||||||
|
"team_invite_user_body": "Qualqu’un vos a convidat a %s sus Spacedeck.\nVòstre senhal temporari es « %s ».\nMercés de clicar sul ligam seguent per acceptar l’invitacion.",
|
||||||
|
"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 l’equipa %s sus Spacedeck",
|
||||||
|
"space_invite_membership_subject": "Invitacion Espaci per %s : %s",
|
||||||
|
"space_invite_membership_body": "%s vos a convit a l’Espaci « %s »",
|
||||||
|
"space_invite_membership_action": "Acceptar l’invitacion",
|
||||||
|
"folder_invite_membership_subject": "Espaci",
|
||||||
|
"folder_invite_membership_body": "Qualqu’un vos a convidat a Team sus Spacedeck. Clicatz lo ligam seguent per acceptar l’invitacion.",
|
||||||
|
"folder_invite_membership_acction": "Acceptar",
|
||||||
|
"login_google": "S’identificar 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 d’espacis.",
|
||||||
|
"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 : 200x200 pixè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 qu’explica 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 l’accè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 l’Espaci",
|
||||||
|
"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 l’escòla e a l’universitat pels mai rics, experiéncia d’aprendissatge connectat.",
|
||||||
|
"spaces": "Mos espacis",
|
||||||
|
"access_editor_link": "Ligam de modificacion dirècta",
|
||||||
|
"access_editor_link_desc": "Donatz aqueste ligam a qualqu’un que deu poder modificar dirèctament aqueste Espaci, cap de compte pas requerit : ",
|
||||||
|
"access_editor_link_desc_slug": "Aqueste ligam conten lo nom de l’espaci, tanben. ",
|
||||||
|
"access_anonymous_edit_blocking": "Los convidats pòdon pas modificar los elements qu’an creats.",
|
||||||
|
"access_current_members": "Membres actuals",
|
||||||
|
"access_new_members": "Convidar de novèls membres",
|
||||||
|
"access_no_members": "Los membres d’aqueste Espacii apreissaràn aquí.",
|
||||||
|
"comments": "comentaris",
|
||||||
|
"landing_customers": "La fisança de milièr de personas.",
|
||||||
|
"landing_features_title": "Un jòc d'enfants d’utilizar.",
|
||||||
|
"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 qu’auretz 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. S’avè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 l’equipa",
|
||||||
|
"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 l’espaci",
|
||||||
|
"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"
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
require('../models/schema');
|
require('../models/db');
|
||||||
var config = require('config');
|
var config = require('config');
|
||||||
|
|
||||||
module.exports = (req, res, next) => {
|
module.exports = (req, res, next) => {
|
||||||
@@ -16,4 +16,4 @@ module.exports = (req, res, next) => {
|
|||||||
} else {
|
} else {
|
||||||
res.status(404).send("Not Found.");
|
res.status(404).send("Not Found.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,28 +1,9 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
require('../models/schema');
|
require('../models/db');
|
||||||
var config = require('config');
|
var config = require('config');
|
||||||
const redis = require('../helpers/redis');
|
const redis = require('../helpers/redis');
|
||||||
|
|
||||||
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.toJSON()
|
|
||||||
};
|
|
||||||
|
|
||||||
let action = new Action(attr);
|
|
||||||
action.save(function(err) {
|
|
||||||
if (err)
|
|
||||||
console.error("saved create action err:", err);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = (req, res, next) => {
|
module.exports = (req, res, next) => {
|
||||||
res.header("Cache-Control", "no-cache");
|
res.header("Cache-Control", "no-cache");
|
||||||
|
|
||||||
@@ -32,23 +13,26 @@ module.exports = (req, res, next) => {
|
|||||||
|
|
||||||
res['distributeCreate'] = function(model, object) {
|
res['distributeCreate'] = function(model, object) {
|
||||||
if (!object) return;
|
if (!object) return;
|
||||||
redis.sendMessage("create", model, object.toJSON(), req.channelId);
|
redis.sendMessage("create", model, object, req.channelId);
|
||||||
this.status(201).json(object.toJSON());
|
this.status(201).json(object);
|
||||||
saveAction("create", object);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
res['distributeUpdate'] = function(model, object) {
|
res['distributeUpdate'] = function(model, object, sendToSelf) {
|
||||||
if (!object) return;
|
if (!object) return;
|
||||||
redis.sendMessage("update", model, object.toJSON(), req.channelId);
|
if (sendToSelf) {
|
||||||
this.status(200).json(object.toJSON());
|
// send this update to the initiating user, for example when
|
||||||
saveAction("update", object);
|
// a conversion task has finished
|
||||||
|
redis.sendMessage("update-self", model, object, req.channelId);
|
||||||
|
} else {
|
||||||
|
redis.sendMessage("update", model, object, req.channelId);
|
||||||
|
}
|
||||||
|
this.status(200).json(object);
|
||||||
};
|
};
|
||||||
|
|
||||||
res['distributeDelete'] = function(model, object) {
|
res['distributeDelete'] = function(model, object) {
|
||||||
if (!object) return;
|
if (!object) return;
|
||||||
redis.sendMessage("delete", model, object.toJSON(), req.channelId);
|
redis.sendMessage("delete", model, object, req.channelId);
|
||||||
this.sendStatus(204);
|
this.sendStatus(204);
|
||||||
saveAction("delete", object);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
next();
|
next();
|
||||||
|
|||||||
@@ -1,22 +1,20 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
const db = require('../models/db');
|
||||||
|
const Sequelize = require('sequelize');
|
||||||
|
const Op = Sequelize.Op;
|
||||||
|
|
||||||
require('../models/schema');
|
|
||||||
var config = require('config');
|
var config = require('config');
|
||||||
|
|
||||||
module.exports = (req, res, next) => {
|
module.exports = (req, res, next) => {
|
||||||
var artifactId = req.params.artifact_id;
|
var artifactId = req.params.artifact_id;
|
||||||
Artifact.findOne({
|
db.Artifact.findOne({where: {
|
||||||
"_id": artifactId
|
"_id": artifactId
|
||||||
}, (err, artifact) => {
|
}}).then(artifact => {
|
||||||
if (err) {
|
if (artifact) {
|
||||||
res.status(400).json(err);
|
req['artifact'] = artifact;
|
||||||
|
next();
|
||||||
} else {
|
} else {
|
||||||
if (artifact) {
|
res.sendStatus(404);
|
||||||
req['artifact'] = artifact;
|
|
||||||
next();
|
|
||||||
} else {
|
|
||||||
res.sendStatus(404);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
require('../models/schema');
|
require('../models/db');
|
||||||
const config = require('config');
|
const config = require('config');
|
||||||
const url = require('url');
|
const url = require('url');
|
||||||
|
|
||||||
@@ -26,20 +26,20 @@ module.exports = (req, res, next) => {
|
|||||||
const parsedUrl = url.parse(origin, true, true);
|
const parsedUrl = url.parse(origin, true, true);
|
||||||
|
|
||||||
// FIXME
|
// FIXME
|
||||||
if (parsedUrl.hostname == "cdn.spacedeck.com") {
|
if (parsedUrl.hostname == "cdn.spacedeck.com") {
|
||||||
res.header('Cache-Control', "max-age");
|
res.header('Cache-Control', "max-age");
|
||||||
res.header('Expires', "30d");
|
res.header('Expires', "30d");
|
||||||
res.removeHeader("Pragma");
|
res.removeHeader("Pragma");
|
||||||
|
|
||||||
respond(origin, req, res, next);
|
respond(origin, req, res, next);
|
||||||
} else {
|
} else {
|
||||||
Team.getTeamForHost(parsedUrl.hostname, (err, team, subdomain) => {
|
//Team.getTeamForHost(parsedUrl.hostname, (err, team, subdomain) => {
|
||||||
if (team) {
|
//if (team) {
|
||||||
respond(origin, req, res, next);
|
respond(origin, req, res, next);
|
||||||
} else {
|
//} else {
|
||||||
next();
|
next();
|
||||||
}
|
//}
|
||||||
});
|
//});
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
require('../models/schema');
|
require('../models/db');
|
||||||
var config = require('config');
|
var config = require('config');
|
||||||
|
|
||||||
module.exports = (req, res, next) => {
|
module.exports = (req, res, next) => {
|
||||||
@@ -10,8 +10,8 @@ module.exports = (req, res, next) => {
|
|||||||
req.i18n.setLocaleFromCookie();
|
req.i18n.setLocaleFromCookie();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (req.user && req.user.preferences.language) {
|
if (req.user && req.user.prefs_language) {
|
||||||
req.i18n.setLocale(req.user.preferences.language);
|
req.i18n.setLocale(req.user.prefs_language);
|
||||||
}
|
}
|
||||||
next();
|
next();
|
||||||
}
|
}
|
||||||
|
|||||||
65
middlewares/session.js
Normal file
65
middlewares/session.js
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
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 => {
|
||||||
|
if (!session) {
|
||||||
|
// session not found
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
else db.User.findOne({where: {_id: session.user_id}})
|
||||||
|
.then(user => {
|
||||||
|
if (!user) {
|
||||||
|
var domain = (process.env.NODE_ENV == "production") ? new URL(config.get('endpoint')).hostname : req.headers.hostname;
|
||||||
|
res.clearCookie('sdsession', { domain: domain });
|
||||||
|
|
||||||
|
if (req.accepts("text/html")) {
|
||||||
|
res.send("Please clear your cookies and try again.");
|
||||||
|
} else if (req.accepts('application/json')) {
|
||||||
|
res.status(403).json({
|
||||||
|
"error": "token_not_found"
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
res.send("Please clear your cookies and try again.");
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
req["token"] = token;
|
||||||
|
req["user"] = user;
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.error(err => {
|
||||||
|
console.error("Session resolve error",err);
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
require('../models/schema');
|
|
||||||
var config = require('config');
|
|
||||||
|
|
||||||
module.exports = (req, res, next) => {
|
|
||||||
const token = req.cookies["sdsession"];
|
|
||||||
if (token && token != "null" && token !== null) {
|
|
||||||
User.findOne({
|
|
||||||
"sessions.token": token
|
|
||||||
}).populate('team').exec((err, user) => {
|
|
||||||
if (!user) {
|
|
||||||
// FIXME
|
|
||||||
var domain = "localhost";
|
|
||||||
res.clearCookie('sdsession', {
|
|
||||||
domain: domain
|
|
||||||
});
|
|
||||||
|
|
||||||
if (req.accepts("text/html")) {
|
|
||||||
res.redirect("/");
|
|
||||||
} else if (req.accepts('application/json')) {
|
|
||||||
res.status(403).json({
|
|
||||||
"error": "token_not_found"
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
res.redirect("/");
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
req["token"] = token;
|
|
||||||
req["user"] = user;
|
|
||||||
next();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
next();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
require('../models/schema');
|
const db = require('../models/db');
|
||||||
|
const { Op } = require("sequelize");
|
||||||
var config = require('config');
|
var config = require('config');
|
||||||
|
|
||||||
module.exports = (req, res, next) => {
|
module.exports = (req, res, next) => {
|
||||||
@@ -19,50 +20,6 @@ module.exports = (req, res, next) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var rolePerUser = (originalSpace, user, cb) => {
|
|
||||||
originalSpace.path = [];
|
|
||||||
|
|
||||||
if (originalSpace._id.equals(req.user.home_folder_id) || (originalSpace.creator && originalSpace.creator._id.equals(req.user._id))) {
|
|
||||||
cb("admin");
|
|
||||||
} else {
|
|
||||||
var findMembershipsForSpace = function(space, allMemberships, prevRole) {
|
|
||||||
Membership.find({
|
|
||||||
"space": space._id
|
|
||||||
}, function(err, parentMemberships) {
|
|
||||||
var currentMemberships = parentMemberships.concat(allMemberships);
|
|
||||||
|
|
||||||
if (space.parent_space_id) {
|
|
||||||
Space.findOne({
|
|
||||||
"_id": space.parent_space_id
|
|
||||||
}, function(err, parentSpace) {
|
|
||||||
findMembershipsForSpace(parentSpace, currentMemberships, prevRole);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// reached the top
|
|
||||||
|
|
||||||
var role = prevRole;
|
|
||||||
space.memberships = currentMemberships;
|
|
||||||
|
|
||||||
if(role == "none"){
|
|
||||||
if(originalSpace.access_mode == "public") {
|
|
||||||
role = "viewer";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
currentMemberships.forEach(function(m, i) {
|
|
||||||
if (m.user && m.user.equals(user._id)) {
|
|
||||||
role = m.role;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
cb(role);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
findMembershipsForSpace(originalSpace, [], "none");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var finalizeAnonymousLogin = function(space, spaceAuth) {
|
var finalizeAnonymousLogin = function(space, spaceAuth) {
|
||||||
var role = "none";
|
var role = "none";
|
||||||
|
|
||||||
@@ -77,7 +34,7 @@ module.exports = (req, res, next) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (req.user) {
|
if (req.user) {
|
||||||
rolePerUser(space, req.user, function(newRole) {
|
db.getUserRoleInSpace(space, req.user, function(newRole) {
|
||||||
if (newRole == "admin" && (role == "editor" || role == "viewer")) {
|
if (newRole == "admin" && (role == "editor" || role == "viewer")) {
|
||||||
finalizeReq(space, newRole);
|
finalizeReq(space, newRole);
|
||||||
} else if (newRole == "editor" && (role == "viewer")) {
|
} else if (newRole == "editor" && (role == "viewer")) {
|
||||||
@@ -97,64 +54,65 @@ module.exports = (req, res, next) => {
|
|||||||
'email': 1
|
'email': 1
|
||||||
};
|
};
|
||||||
|
|
||||||
Space.findOne({
|
// find space by id or slug
|
||||||
"_id": spaceId
|
db.Space.findOne({where: {
|
||||||
}).populate("creator", userMapping).exec(function(err, space) {
|
[Op.or]: [
|
||||||
if (err) {
|
{"_id": spaceId},
|
||||||
res.status(400).json(err);
|
{"edit_slug": spaceId}
|
||||||
} else {
|
]
|
||||||
if (space) {
|
}}).then(function(space) {
|
||||||
|
|
||||||
if (space.access_mode == "public") {
|
if (space) {
|
||||||
|
if (space.access_mode == "public") {
|
||||||
if (space.password) {
|
if (space.password) {
|
||||||
if (req.spacePassword) {
|
if (req.spacePassword) {
|
||||||
if (req.spacePassword === space.password) {
|
if (req.spacePassword === space.password) {
|
||||||
finalizeAnonymousLogin(space, req["spaceAuth"]);
|
|
||||||
} else {
|
|
||||||
res.status(403).json({
|
|
||||||
"error": "password_wrong"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
res.status(401).json({
|
|
||||||
"error": "password_required"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
finalizeAnonymousLogin(space, req["spaceAuth"]);
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
// special permission for screenshot/pdf export from backend
|
|
||||||
if (req.query['api_token'] && req.query['api_token'] == config.get('phantom_api_secret')) {
|
|
||||||
finalizeReq(space, "viewer");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (req.user) {
|
|
||||||
rolePerUser(space, req.user, function(role) {
|
|
||||||
if (role == "none") {
|
|
||||||
finalizeAnonymousLogin(space, req["spaceAuth"]);
|
|
||||||
} else {
|
|
||||||
finalizeReq(space, role);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
if (req.spaceAuth && space.edit_hash) {
|
|
||||||
finalizeAnonymousLogin(space, req["spaceAuth"]);
|
finalizeAnonymousLogin(space, req["spaceAuth"]);
|
||||||
} else {
|
} else {
|
||||||
res.status(403).json({
|
res.status(403).json({
|
||||||
"error": "auth_required"
|
"error": "password_wrong"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
res.status(401).json({
|
||||||
|
"error": "password_required"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
finalizeAnonymousLogin(space, req["spaceAuth"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// space is private
|
||||||
|
|
||||||
|
// special permission for screenshot/pdf export from backend
|
||||||
|
if (req.query['api_token'] && req.query['api_token'] == config.get('phantom_api_secret')) {
|
||||||
|
finalizeReq(space, "viewer");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (req.user) {
|
||||||
|
db.getUserRoleInSpace(space, req.user, function(role) {
|
||||||
|
if (role == "none") {
|
||||||
|
finalizeAnonymousLogin(space, req["spaceAuth"]);
|
||||||
|
} else {
|
||||||
|
finalizeReq(space, role);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
if (req.spaceAuth && space.edit_hash) {
|
||||||
|
finalizeAnonymousLogin(space, req["spaceAuth"]);
|
||||||
|
} else {
|
||||||
|
res.status(403).json({
|
||||||
|
"error": "auth_required"
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
res.status(404).json({
|
|
||||||
"error": "space_not_found"
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
res.status(404).json({
|
||||||
|
"error": "space_not_found"
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,33 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
require('../models/schema');
|
|
||||||
var config = require('config');
|
|
||||||
|
|
||||||
module.exports = (req, res, next) => {
|
|
||||||
let host = req.headers.host;
|
|
||||||
Team.getTeamForHost(host, (err, team, subdomain) => {
|
|
||||||
if (subdomain) {
|
|
||||||
if (!err && team) {
|
|
||||||
req.subdomainTeam = team;
|
|
||||||
req.subdomain = subdomain;
|
|
||||||
next()
|
|
||||||
} else {
|
|
||||||
if (req.accepts('text/html')) {
|
|
||||||
res.status(404).render('not_found', {
|
|
||||||
title: 'Page Not Found.'
|
|
||||||
});
|
|
||||||
} else if (req.accepts('application/json')) {
|
|
||||||
res.status(404).json({
|
|
||||||
"error": "not_found"
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
res.status(404).render('not_found', {
|
|
||||||
title: 'Page Not Found.'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
next();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
require('../models/schema');
|
|
||||||
var config = require('config');
|
|
||||||
|
|
||||||
module.exports = (req, res, next) => {
|
|
||||||
if (req.user) {
|
|
||||||
var isAdmin = req.user.team.admins.indexOf(req.user._id) >= 0;
|
|
||||||
var correctMethod = req.method == "GET" || (req.method == "DELETE" || req.method == "PUT" || req.method == "POST");
|
|
||||||
|
|
||||||
if (correctMethod && isAdmin) {
|
|
||||||
next();
|
|
||||||
} else {
|
|
||||||
res.status(403, {
|
|
||||||
"error": "not authorized"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
res.status(403, {
|
|
||||||
"error": "not logged in"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
require('../models/schema');
|
|
||||||
var config = require('config');
|
|
||||||
var _ = require('underscore');
|
|
||||||
|
|
||||||
module.exports = (req, res, next) => {
|
|
||||||
res.oldRender = res.render;
|
|
||||||
res.render = function(template, params) {
|
|
||||||
|
|
||||||
var team = req.subdomainTeam;
|
|
||||||
if (team) {
|
|
||||||
team = _.pick(team.toObject(), ['_id', 'name', 'subdomain', 'avatar_original_uri']);
|
|
||||||
} else {
|
|
||||||
team = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const addParams = {
|
|
||||||
locale: req.i18n.locale,
|
|
||||||
config: config,
|
|
||||||
subdomain_team: team,
|
|
||||||
user: req.user,
|
|
||||||
csrf_token: "",
|
|
||||||
socket_auth: req.token
|
|
||||||
};
|
|
||||||
|
|
||||||
const all = _.extend(params, addParams);
|
|
||||||
res.oldRender(template, all);
|
|
||||||
};
|
|
||||||
next();
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
// FIXME port this last model
|
||||||
|
|
||||||
var mongoose = require('mongoose');
|
var mongoose = require('mongoose');
|
||||||
var Schema = mongoose.Schema;
|
var Schema = mongoose.Schema;
|
||||||
|
|
||||||
|
|||||||
@@ -1,88 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
var mongoose = require('mongoose');
|
|
||||||
var Schema = mongoose.Schema;
|
|
||||||
|
|
||||||
module.exports.artifactSchema = Schema({
|
|
||||||
mime: String,
|
|
||||||
thumbnail_uri: String,
|
|
||||||
space_id: Schema.Types.ObjectId,
|
|
||||||
user_id: {type: Schema.Types.ObjectId, ref: 'User' },
|
|
||||||
last_update_user_id: {type: Schema.Types.ObjectId, ref: 'User' },
|
|
||||||
editor_name: String,
|
|
||||||
last_update_editor_name: String,
|
|
||||||
description: String,
|
|
||||||
state: {type: String, default: "idle"},
|
|
||||||
meta: {
|
|
||||||
linked_to: [String],
|
|
||||||
title: String,
|
|
||||||
tags: [String],
|
|
||||||
search_text: String,
|
|
||||||
link_uri: String,
|
|
||||||
play_from: Number,
|
|
||||||
play_to: Number,
|
|
||||||
},
|
|
||||||
board: {
|
|
||||||
x: {type: Number, default: 0.0},
|
|
||||||
y: {type: Number, default: 0.0},
|
|
||||||
z: {type: Number, default: 0.0},
|
|
||||||
r: {type: Number, default: 0.0},
|
|
||||||
w: {type: Number, default: 100},
|
|
||||||
h: {type: Number, default: 100},
|
|
||||||
},
|
|
||||||
control_points: [{
|
|
||||||
dx: Number, dy: Number
|
|
||||||
}],
|
|
||||||
group:{type: String, default: ""},
|
|
||||||
locked: {type: Boolean, default: false},
|
|
||||||
payload_uri: String,
|
|
||||||
payload_thumbnail_web_uri: String,
|
|
||||||
payload_thumbnail_medium_uri: String,
|
|
||||||
payload_thumbnail_big_uri: String,
|
|
||||||
payload_size: Number, // file size in bytes
|
|
||||||
style: {
|
|
||||||
fill_color: {type: String, default: "transparent"},
|
|
||||||
stroke_color:{type: String, default: "#000000"},
|
|
||||||
text_color: String,
|
|
||||||
stroke: {type: Number, default: 0.0},
|
|
||||||
stroke_style: {type: String, default: "solid"},
|
|
||||||
alpha: {type: Number, default: 1.0},
|
|
||||||
order: {type: Number, default: 0},
|
|
||||||
crop: {
|
|
||||||
x: Number,
|
|
||||||
y: Number,
|
|
||||||
w: Number,
|
|
||||||
h: Number
|
|
||||||
},
|
|
||||||
shape: String,
|
|
||||||
shape_svg: String,
|
|
||||||
padding_left: Number,
|
|
||||||
padding_right: Number,
|
|
||||||
padding_top: Number,
|
|
||||||
padding_bottom: Number,
|
|
||||||
margin_left: Number,
|
|
||||||
margin_right: Number,
|
|
||||||
margin_top: Number,
|
|
||||||
margin_bottom: Number,
|
|
||||||
border_radius: Number,
|
|
||||||
align: {type: String, default: "left"},
|
|
||||||
valign: {type: String, default: "top"},
|
|
||||||
brightness: Number,
|
|
||||||
contrast: Number,
|
|
||||||
saturation: Number,
|
|
||||||
blur: Number,
|
|
||||||
hue: Number,
|
|
||||||
opacity: Number
|
|
||||||
},
|
|
||||||
payload_alternatives: [{
|
|
||||||
mime: String,
|
|
||||||
payload_uri: String,
|
|
||||||
payload_thumbnail_web_uri: String,
|
|
||||||
payload_thumbnail_medium_uri: String,
|
|
||||||
payload_thumbnail_big_uri: String,
|
|
||||||
payload_size: Number
|
|
||||||
}],
|
|
||||||
created_at: {type: Date, default: Date.now},
|
|
||||||
created_from_ip: {type: String},
|
|
||||||
updated_at: {type: Date, default: Date.now}
|
|
||||||
});
|
|
||||||
377
models/db.js
Normal file
377
models/db.js
Normal file
@@ -0,0 +1,377 @@
|
|||||||
|
const Umzug = require('umzug');
|
||||||
|
const config = require('config')
|
||||||
|
|
||||||
|
function sequel_log(a,b,c) {
|
||||||
|
console.log(a);
|
||||||
|
}
|
||||||
|
|
||||||
|
const Sequelize = require('sequelize');
|
||||||
|
const sequelize = new Sequelize('database', 'username', 'password', {
|
||||||
|
host: 'localhost',
|
||||||
|
dialect: 'sqlite',
|
||||||
|
|
||||||
|
pool: {
|
||||||
|
max: 5,
|
||||||
|
min: 0,
|
||||||
|
acquire: 30000,
|
||||||
|
idle: 10000
|
||||||
|
},
|
||||||
|
|
||||||
|
// SQLite only
|
||||||
|
storage: config.get('storage_local_db'),
|
||||||
|
logging: sequel_log,
|
||||||
|
|
||||||
|
// http://docs.sequelizejs.com/manual/tutorial/querying.html#operators
|
||||||
|
operatorsAliases: false
|
||||||
|
});
|
||||||
|
|
||||||
|
var User;
|
||||||
|
var Session;
|
||||||
|
var Space;
|
||||||
|
var Membership;
|
||||||
|
var Artifact;
|
||||||
|
var Message;
|
||||||
|
var Action;
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
User: sequelize.define('user', {
|
||||||
|
_id: {type: Sequelize.STRING, primaryKey: true},
|
||||||
|
email: Sequelize.STRING,
|
||||||
|
password_hash: Sequelize.STRING,
|
||||||
|
nickname: Sequelize.STRING,
|
||||||
|
avatar_original_uri: Sequelize.STRING,
|
||||||
|
avatar_thumb_uri: Sequelize.STRING,
|
||||||
|
confirmation_token: Sequelize.STRING,
|
||||||
|
password_reset_token: Sequelize.STRING,
|
||||||
|
api_token: Sequelize.STRING,
|
||||||
|
home_folder_id: Sequelize.STRING,
|
||||||
|
prefs_language: Sequelize.STRING,
|
||||||
|
prefs_email_notifications: Sequelize.STRING,
|
||||||
|
prefs_email_digest: Sequelize.STRING,
|
||||||
|
created_at: {type: Sequelize.DATE, defaultValue: Sequelize.NOW},
|
||||||
|
updated_at: {type: Sequelize.DATE, defaultValue: Sequelize.NOW}
|
||||||
|
}),
|
||||||
|
|
||||||
|
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,
|
||||||
|
expires: Sequelize.DATE,
|
||||||
|
created_at: {type: Sequelize.DATE, defaultValue: Sequelize.NOW},
|
||||||
|
device: Sequelize.STRING,
|
||||||
|
ip: Sequelize.STRING
|
||||||
|
}),
|
||||||
|
|
||||||
|
Space: sequelize.define('space', {
|
||||||
|
_id: {type: Sequelize.STRING, primaryKey: true},
|
||||||
|
name: {type: Sequelize.STRING, default: "New Space"},
|
||||||
|
space_type: {type: Sequelize.STRING, defaultValue: "space"},
|
||||||
|
creator_id: Sequelize.STRING,
|
||||||
|
parent_space_id: Sequelize.STRING,
|
||||||
|
|
||||||
|
access_mode: {type: Sequelize.STRING, default: "private"}, // "public" || "private"
|
||||||
|
password: Sequelize.STRING,
|
||||||
|
edit_hash: Sequelize.STRING,
|
||||||
|
edit_slug: Sequelize.STRING,
|
||||||
|
editors_locking: Sequelize.BOOLEAN,
|
||||||
|
|
||||||
|
thumbnail_uri: Sequelize.STRING,
|
||||||
|
|
||||||
|
width: Sequelize.INTEGER,
|
||||||
|
height: Sequelize.INTEGER,
|
||||||
|
background_color: Sequelize.STRING,
|
||||||
|
background_uri: Sequelize.STRING,
|
||||||
|
|
||||||
|
created_at: {type: Sequelize.DATE, defaultValue: Sequelize.NOW},
|
||||||
|
updated_at: {type: Sequelize.DATE, defaultValue: Sequelize.NOW},
|
||||||
|
thumbnail_url: Sequelize.STRING,
|
||||||
|
thumbnail_updated_at: {type: Sequelize.DATE}
|
||||||
|
}),
|
||||||
|
|
||||||
|
Membership: sequelize.define('membership', {
|
||||||
|
_id: {type: Sequelize.STRING, primaryKey: true},
|
||||||
|
space_id: Sequelize.STRING,
|
||||||
|
user_id: Sequelize.STRING,
|
||||||
|
role: Sequelize.STRING,
|
||||||
|
code: Sequelize.STRING,
|
||||||
|
state: {type: Sequelize.STRING, defaultValue: "pending"}, // valid: "pending", "active"
|
||||||
|
email_invited: Sequelize.STRING,
|
||||||
|
created_at: {type: Sequelize.DATE, defaultValue: Sequelize.NOW},
|
||||||
|
updated_at: {type: Sequelize.DATE, defaultValue: Sequelize.NOW}
|
||||||
|
}),
|
||||||
|
|
||||||
|
Message: sequelize.define('message', {
|
||||||
|
_id: {type: Sequelize.STRING, primaryKey: true},
|
||||||
|
space_id: Sequelize.STRING,
|
||||||
|
user_id: Sequelize.STRING,
|
||||||
|
editor_name: Sequelize.STRING,
|
||||||
|
message: Sequelize.TEXT,
|
||||||
|
created_at: {type: Sequelize.DATE, defaultValue: Sequelize.NOW},
|
||||||
|
updated_at: {type: Sequelize.DATE, defaultValue: Sequelize.NOW}
|
||||||
|
}),
|
||||||
|
|
||||||
|
Artifact: sequelize.define('artifact', {
|
||||||
|
_id: {type: Sequelize.STRING, primaryKey: true},
|
||||||
|
space_id: Sequelize.STRING,
|
||||||
|
user_id: Sequelize.STRING,
|
||||||
|
|
||||||
|
mime: Sequelize.STRING,
|
||||||
|
thumbnail_uri: Sequelize.STRING,
|
||||||
|
last_update_user_id: Sequelize.STRING,
|
||||||
|
editor_name: Sequelize.STRING,
|
||||||
|
last_update_editor_name: Sequelize.STRING,
|
||||||
|
description: Sequelize.TEXT,
|
||||||
|
state: {type: Sequelize.STRING, default: "idle"},
|
||||||
|
|
||||||
|
//linked_to: Sequelize.STRING,
|
||||||
|
title: Sequelize.STRING,
|
||||||
|
tags: Sequelize.TEXT,
|
||||||
|
search_text: Sequelize.STRING,
|
||||||
|
link_uri: Sequelize.STRING,
|
||||||
|
play_from: Sequelize.DECIMAL,
|
||||||
|
play_to: Sequelize.DECIMAL,
|
||||||
|
|
||||||
|
x: {type: Sequelize.DECIMAL, default: 0.0},
|
||||||
|
y: {type: Sequelize.DECIMAL, default: 0.0},
|
||||||
|
z: {type: Sequelize.DECIMAL, default: 0.0},
|
||||||
|
r: {type: Sequelize.DECIMAL, default: 0.0},
|
||||||
|
w: {type: Sequelize.DECIMAL, default: 100},
|
||||||
|
h: {type: Sequelize.DECIMAL, default: 100},
|
||||||
|
|
||||||
|
//control_points: [{
|
||||||
|
// dx: Number, dy: Number
|
||||||
|
//}],
|
||||||
|
|
||||||
|
control_points: Sequelize.TEXT,
|
||||||
|
|
||||||
|
group: Sequelize.STRING,
|
||||||
|
locked: {type: Sequelize.BOOLEAN, default: false},
|
||||||
|
|
||||||
|
payload_uri: Sequelize.STRING,
|
||||||
|
payload_thumbnail_web_uri: Sequelize.STRING,
|
||||||
|
payload_thumbnail_medium_uri: Sequelize.STRING,
|
||||||
|
payload_thumbnail_big_uri: Sequelize.STRING,
|
||||||
|
payload_size: Sequelize.INTEGER, // file size in bytes
|
||||||
|
|
||||||
|
fill_color: {type: Sequelize.STRING, default: "transparent"},
|
||||||
|
stroke_color: {type: Sequelize.STRING, default: "#000000"},
|
||||||
|
text_color: Sequelize.STRING,
|
||||||
|
stroke: {type: Sequelize.DECIMAL, default: 0.0},
|
||||||
|
stroke_style: {type: Sequelize.STRING, default: "solid"},
|
||||||
|
alpha: {type: Sequelize.DECIMAL, default: 1.0},
|
||||||
|
order: {type: Sequelize.INTEGER, default: 0},
|
||||||
|
crop_x: Sequelize.INTEGER,
|
||||||
|
crop_y: Sequelize.INTEGER,
|
||||||
|
crop_w: Sequelize.INTEGER,
|
||||||
|
crop_h: Sequelize.INTEGER,
|
||||||
|
shape: Sequelize.STRING,
|
||||||
|
shape_svg: Sequelize.TEXT,
|
||||||
|
padding_left: Sequelize.INTEGER,
|
||||||
|
padding_right: Sequelize.INTEGER,
|
||||||
|
padding_top: Sequelize.INTEGER,
|
||||||
|
padding_bottom: Sequelize.INTEGER,
|
||||||
|
margin_left: Sequelize.INTEGER,
|
||||||
|
margin_right: Sequelize.INTEGER,
|
||||||
|
margin_top: Sequelize.INTEGER,
|
||||||
|
margin_bottom: Sequelize.INTEGER,
|
||||||
|
border_radius: Sequelize.INTEGER,
|
||||||
|
align: {type: Sequelize.STRING, default: "left"},
|
||||||
|
valign: {type: Sequelize.STRING, default: "top"},
|
||||||
|
|
||||||
|
brightness: Sequelize.DECIMAL,
|
||||||
|
contrast: Sequelize.DECIMAL,
|
||||||
|
saturation: Sequelize.DECIMAL,
|
||||||
|
blur: Sequelize.DECIMAL,
|
||||||
|
hue: Sequelize.DECIMAL,
|
||||||
|
opacity: Sequelize.DECIMAL,
|
||||||
|
|
||||||
|
payload_alternatives: Sequelize.TEXT,
|
||||||
|
|
||||||
|
/*payload_alternatives: [{
|
||||||
|
mime: String,
|
||||||
|
payload_uri: String,
|
||||||
|
payload_thumbnail_web_uri: String,
|
||||||
|
payload_thumbnail_medium_uri: String,
|
||||||
|
payload_thumbnail_big_uri: String,
|
||||||
|
payload_size: Number
|
||||||
|
}],*/
|
||||||
|
|
||||||
|
created_at: {type: Sequelize.DATE, defaultValue: Sequelize.NOW},
|
||||||
|
updated_at: {type: Sequelize.DATE, defaultValue: Sequelize.NOW}
|
||||||
|
}),
|
||||||
|
|
||||||
|
init: async function() {
|
||||||
|
User = this.User;
|
||||||
|
Session = this.Session;
|
||||||
|
Space = this.Space;
|
||||||
|
Artifact = this.Artifact;
|
||||||
|
Message = this.Message;
|
||||||
|
Membership = this.Membership;
|
||||||
|
|
||||||
|
Space.belongsTo(User, {
|
||||||
|
foreignKey: {
|
||||||
|
name: 'creator_id'
|
||||||
|
},
|
||||||
|
as: 'creator'
|
||||||
|
});
|
||||||
|
|
||||||
|
Membership.belongsTo(User, {
|
||||||
|
foreignKey: {
|
||||||
|
name: 'user_id'
|
||||||
|
},
|
||||||
|
as: 'user'
|
||||||
|
});
|
||||||
|
|
||||||
|
Membership.belongsTo(Space, {
|
||||||
|
foreignKey: {
|
||||||
|
name: 'space_id'
|
||||||
|
},
|
||||||
|
as: 'space'
|
||||||
|
});
|
||||||
|
|
||||||
|
Artifact.belongsTo(User, {
|
||||||
|
foreignKey: {
|
||||||
|
name: 'user_id'
|
||||||
|
},
|
||||||
|
as: 'user'
|
||||||
|
});
|
||||||
|
|
||||||
|
Artifact.belongsTo(Space, {
|
||||||
|
foreignKey: {
|
||||||
|
name: 'space_id'
|
||||||
|
},
|
||||||
|
as: 'space'
|
||||||
|
});
|
||||||
|
|
||||||
|
Message.belongsTo(User, {
|
||||||
|
foreignKey: {
|
||||||
|
name: 'user_id'
|
||||||
|
},
|
||||||
|
as: 'user'
|
||||||
|
});
|
||||||
|
|
||||||
|
Message.belongsTo(Space, {
|
||||||
|
foreignKey: {
|
||||||
|
name: 'space_id'
|
||||||
|
},
|
||||||
|
as: 'space'
|
||||||
|
});
|
||||||
|
|
||||||
|
await sequelize.sync();
|
||||||
|
|
||||||
|
var umzug = new Umzug({
|
||||||
|
storage: 'sequelize',
|
||||||
|
storageOptions: {
|
||||||
|
sequelize: sequelize
|
||||||
|
},
|
||||||
|
migrations: {
|
||||||
|
params: [
|
||||||
|
sequelize.getQueryInterface(),
|
||||||
|
Sequelize
|
||||||
|
],
|
||||||
|
path: './models/migrations',
|
||||||
|
pattern: /\.js$/
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
umzug.up().then(function(migrations) {
|
||||||
|
console.log('Migration complete up!');
|
||||||
|
});
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
getUserRoleInSpace: (originalSpace, user, cb) => {
|
||||||
|
originalSpace.path = [];
|
||||||
|
|
||||||
|
if (originalSpace._id == user.home_folder_id || (originalSpace.creator_id && originalSpace.creator_id == user._id)) {
|
||||||
|
cb("admin");
|
||||||
|
} else {
|
||||||
|
var findMembershipsForSpace = function(space, allMemberships, prevRole) {
|
||||||
|
Membership.findAll({ where: {
|
||||||
|
"space_id": space._id
|
||||||
|
}}).then(function(parentMemberships) {
|
||||||
|
var currentMemberships = parentMemberships.concat(allMemberships);
|
||||||
|
|
||||||
|
if (space.parent_space_id) {
|
||||||
|
Space.findOne({ where: {
|
||||||
|
"_id": space.parent_space_id
|
||||||
|
}}).then(function(parentSpace) {
|
||||||
|
findMembershipsForSpace(parentSpace, currentMemberships, prevRole);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// reached the top
|
||||||
|
var role = prevRole;
|
||||||
|
space.memberships = currentMemberships;
|
||||||
|
|
||||||
|
if (role == "none") {
|
||||||
|
if (originalSpace.access_mode == "public") {
|
||||||
|
role = "viewer";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
currentMemberships.forEach(function(m, i) {
|
||||||
|
if (m.user_id && m.user_id == user._id) {
|
||||||
|
role = m.role;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
cb(role);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
findMembershipsForSpace(originalSpace, [], "none");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
spaceToObject: (space) => {
|
||||||
|
// FIXME TODO
|
||||||
|
return space;
|
||||||
|
},
|
||||||
|
|
||||||
|
findUserBySessionToken: (token, cb) => {
|
||||||
|
Session.findOne({where: {token: token}})
|
||||||
|
.then(session => {
|
||||||
|
if (!session) cb(null, null)
|
||||||
|
else User.findOne({where: {_id: session.user_id}})
|
||||||
|
.then(user => {
|
||||||
|
cb(null, user)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
unpackArtifact: (a) => {
|
||||||
|
if (a.tags && (typeof a.tags)=="string") {
|
||||||
|
a.tags = JSON.parse(a.tags);
|
||||||
|
}
|
||||||
|
if (a.control_points && (typeof a.control_points)=="string") {
|
||||||
|
a.control_points = JSON.parse(a.control_points);
|
||||||
|
}
|
||||||
|
if (a.payload_alternatives && (typeof a.payload_alternatives)=="string") {
|
||||||
|
a.payload_alternatives = JSON.parse(a.payload_alternatives);
|
||||||
|
}
|
||||||
|
return a;
|
||||||
|
},
|
||||||
|
|
||||||
|
packArtifact: (a) => {
|
||||||
|
if (a.tags && (typeof a.tags)!="string") {
|
||||||
|
a.tags = JSON.stringify(a.tags);
|
||||||
|
}
|
||||||
|
if (a.control_points && (typeof a.control_points)!="string") {
|
||||||
|
a.control_points = JSON.stringify(a.control_points);
|
||||||
|
}
|
||||||
|
if (a.payload_alternatives && (typeof a.payload_alternatives)!="string") {
|
||||||
|
a.payload_alternatives = JSON.stringify(a.payload_alternatives);
|
||||||
|
}
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
var mongoose = require('mongoose');
|
|
||||||
var Schema = mongoose.Schema;
|
|
||||||
|
|
||||||
module.exports.domainSchema = mongoose.Schema({
|
|
||||||
domain: String,
|
|
||||||
edu: Boolean,
|
|
||||||
created_at: {
|
|
||||||
type: Date,
|
|
||||||
default: Date.now
|
|
||||||
},
|
|
||||||
updated_at: {
|
|
||||||
type: Date,
|
|
||||||
default: Date.now
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports.domainSchema.index({
|
|
||||||
domain: 1
|
|
||||||
});
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
var mongoose = require('mongoose');
|
|
||||||
var Schema = mongoose.Schema;
|
|
||||||
|
|
||||||
module.exports.membershipSchema = mongoose.Schema({
|
|
||||||
user: {
|
|
||||||
type: Schema.Types.ObjectId,
|
|
||||||
ref: 'User'
|
|
||||||
},
|
|
||||||
space: {
|
|
||||||
type: Schema.Types.ObjectId,
|
|
||||||
ref: 'Space'
|
|
||||||
},
|
|
||||||
team: {
|
|
||||||
type: Schema.Types.ObjectId,
|
|
||||||
ref: 'Team'
|
|
||||||
},
|
|
||||||
role: {
|
|
||||||
type: String,
|
|
||||||
default: "viewer"
|
|
||||||
},
|
|
||||||
state: {
|
|
||||||
type: String,
|
|
||||||
default: "active"
|
|
||||||
},
|
|
||||||
email_invited: String,
|
|
||||||
code: String,
|
|
||||||
created_at: {
|
|
||||||
type: Date,
|
|
||||||
default: Date.now
|
|
||||||
},
|
|
||||||
updated_at: {
|
|
||||||
type: Date,
|
|
||||||
default: Date.now
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports.membershipSchema.index({
|
|
||||||
user: 1,
|
|
||||||
space: 1,
|
|
||||||
team: 1,
|
|
||||||
code: 1
|
|
||||||
});
|
|
||||||
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
var mongoose = require('mongoose');
|
|
||||||
var Schema = mongoose.Schema;
|
|
||||||
|
|
||||||
module.exports.messageSchema = mongoose.Schema({
|
|
||||||
user: {
|
|
||||||
type: Schema.Types.ObjectId,
|
|
||||||
ref: 'User'
|
|
||||||
},
|
|
||||||
editor_name: String,
|
|
||||||
space: {
|
|
||||||
type: Schema.Types.ObjectId,
|
|
||||||
ref: 'Space'
|
|
||||||
},
|
|
||||||
message: String,
|
|
||||||
created_from_ip: {type: String},
|
|
||||||
created_at: {
|
|
||||||
type: Date,
|
|
||||||
default: Date.now
|
|
||||||
},
|
|
||||||
updated_at: {
|
|
||||||
type: Date,
|
|
||||||
default: Date.now
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports.messageSchema.index({
|
|
||||||
space: 1,
|
|
||||||
user: 1
|
|
||||||
});
|
|
||||||
79
models/migrations/01-spaces-delete-cascade.js
Normal file
79
models/migrations/01-spaces-delete-cascade.js
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
up: function(migration, DataTypes) {
|
||||||
|
return Promise.all([
|
||||||
|
migration.changeColumn('memberships', 'space_id',
|
||||||
|
{
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
references: {
|
||||||
|
model: 'spaces',
|
||||||
|
key: '_id'
|
||||||
|
},
|
||||||
|
onDelete: 'CASCADE',
|
||||||
|
onUpdate: 'CASCADE'
|
||||||
|
}
|
||||||
|
),
|
||||||
|
migration.changeColumn('artifacts', 'space_id',
|
||||||
|
{
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
references: {
|
||||||
|
model: 'spaces',
|
||||||
|
key: '_id'
|
||||||
|
},
|
||||||
|
onDelete: 'CASCADE',
|
||||||
|
onUpdate: 'CASCADE'
|
||||||
|
}
|
||||||
|
),
|
||||||
|
migration.changeColumn('messages', 'space_id',
|
||||||
|
{
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
references: {
|
||||||
|
model: 'spaces',
|
||||||
|
key: '_id'
|
||||||
|
},
|
||||||
|
onDelete: 'CASCADE',
|
||||||
|
onUpdate: 'CASCADE'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
])
|
||||||
|
},
|
||||||
|
|
||||||
|
down: function(migration, DataTypes) {
|
||||||
|
return Promise.all([
|
||||||
|
migration.changeColumn('memberships', 'space_id',
|
||||||
|
{
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
references: {
|
||||||
|
model: 'spaces',
|
||||||
|
key: '_id'
|
||||||
|
},
|
||||||
|
onDelete: 'CASCADE',
|
||||||
|
onUpdate: 'NO ACTION'
|
||||||
|
}
|
||||||
|
),
|
||||||
|
migration.changeColumn('artifacts', 'space_id',
|
||||||
|
{
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
references: {
|
||||||
|
model: 'spaces',
|
||||||
|
key: '_id'
|
||||||
|
},
|
||||||
|
onDelete: 'CASCADE',
|
||||||
|
onUpdate: 'NO ACTION'
|
||||||
|
}
|
||||||
|
),
|
||||||
|
migration.changeColumn('messages', 'space_id',
|
||||||
|
{
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
references: {
|
||||||
|
model: 'spaces',
|
||||||
|
key: '_id'
|
||||||
|
},
|
||||||
|
onDelete: 'CASCADE',
|
||||||
|
onUpdate: 'NO ACTION'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
23
models/migrations/02-users-add-api-token.js
Normal file
23
models/migrations/02-users-add-api-token.js
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
'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
|
||||||
|
}
|
||||||
|
)
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
var mongoose = require('mongoose');
|
|
||||||
var Schema = mongoose.Schema;
|
|
||||||
|
|
||||||
Plan = mongoose.model('Plan', {
|
|
||||||
key: String,
|
|
||||||
description: String,
|
|
||||||
limit_folders: {
|
|
||||||
type: Number,
|
|
||||||
default: 200
|
|
||||||
},
|
|
||||||
limit_spaces: {
|
|
||||||
type: Number,
|
|
||||||
default: 500
|
|
||||||
},
|
|
||||||
limit_storage_bytes: {
|
|
||||||
type: Number,
|
|
||||||
default: 10737418240
|
|
||||||
},
|
|
||||||
plan_type: {
|
|
||||||
type: String,
|
|
||||||
default: "org"
|
|
||||||
},
|
|
||||||
price: Number,
|
|
||||||
public: Boolean,
|
|
||||||
recurring: {
|
|
||||||
type: String,
|
|
||||||
default: "month"
|
|
||||||
},
|
|
||||||
title: String,
|
|
||||||
trial_days: Number,
|
|
||||||
voucher_code: String,
|
|
||||||
created_at: {
|
|
||||||
type: Date,
|
|
||||||
default: Date.now
|
|
||||||
},
|
|
||||||
updated_at: {
|
|
||||||
type: Date,
|
|
||||||
default: Date.now
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
exports.planModel = Plan;
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
//'use strict';
|
|
||||||
|
|
||||||
var mongoose = require('mongoose');
|
|
||||||
|
|
||||||
User = mongoose.model('User', require('./user').userSchema);
|
|
||||||
Action = mongoose.model('Action', require('./action').actionSchema);
|
|
||||||
Space = mongoose.model('Space', require('./space').spaceSchema);
|
|
||||||
Artifact = mongoose.model('Artifact', require('./artifact').artifactSchema);
|
|
||||||
Team = mongoose.model('Team', require('./team').teamSchema);
|
|
||||||
Message = mongoose.model('Message', require('./message').messageSchema);
|
|
||||||
Membership = mongoose.model('Membership', require('./membership').membershipSchema);
|
|
||||||
Domain = mongoose.model('Domain', require('./domain').domainSchema);
|
|
||||||
273
models/space.js
273
models/space.js
@@ -1,273 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
var mongoose = require('mongoose');
|
|
||||||
var Schema = mongoose.Schema;
|
|
||||||
var async = require('async');
|
|
||||||
var _ = require("underscore");
|
|
||||||
var crypto = require('crypto');
|
|
||||||
|
|
||||||
module.exports.spaceSchema = Schema({
|
|
||||||
name: {type: String, default: "New Space"},
|
|
||||||
space_type: {type: String, default: "space"},
|
|
||||||
|
|
||||||
creator : { type: Schema.Types.ObjectId, ref: 'User' },
|
|
||||||
parent_space_id: Schema.Types.ObjectId,
|
|
||||||
|
|
||||||
access_mode: {type: String, default: "private"}, // "public" || "private"
|
|
||||||
password: String,
|
|
||||||
edit_hash: String,
|
|
||||||
edit_slug: String,
|
|
||||||
editors_locking: Boolean,
|
|
||||||
|
|
||||||
thumbnail_uri: String,
|
|
||||||
stats: {
|
|
||||||
num_children: Number,
|
|
||||||
total_spaces: Number,
|
|
||||||
total_folders: Number,
|
|
||||||
storage_bytes: Number,
|
|
||||||
},
|
|
||||||
|
|
||||||
advanced: {
|
|
||||||
type: {
|
|
||||||
width: Number,
|
|
||||||
height: Number,
|
|
||||||
margin: Number,
|
|
||||||
background_color: String,
|
|
||||||
background_uri: String,
|
|
||||||
background_repeat: Boolean,
|
|
||||||
grid_size: Number,
|
|
||||||
grid_divisions: Number,
|
|
||||||
gutter: Number,
|
|
||||||
columns: Number,
|
|
||||||
column_max_width: Number,
|
|
||||||
columns_responsive: Number,
|
|
||||||
row_max_height: Number,
|
|
||||||
padding_horz: Number,
|
|
||||||
padding_vert: Number
|
|
||||||
},
|
|
||||||
default: {
|
|
||||||
width: 200,
|
|
||||||
height: 400,
|
|
||||||
margin: 0,
|
|
||||||
background_color: "rgba(255,255,255,1)"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
blocked_at: {type: Date, default: Date.now},
|
|
||||||
created_at: {type: Date, default: Date.now},
|
|
||||||
updated_at: {type: Date, default: Date.now},
|
|
||||||
thumbnail_updated_at: {type: Date},
|
|
||||||
thumbnail_url: String
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports.spaceSchema.index({ creator: 1, parent_space_id: 1, created_at: 1, updated_at: 1, edit_hash: 1});
|
|
||||||
module.exports.spaceSchema.statics.allForUser = function (user, callback) {
|
|
||||||
return this.find({user_id: user_id}, callback);
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports.spaceSchema.statics.getMemberships = function (err, callback) {
|
|
||||||
callback(null, {});
|
|
||||||
};
|
|
||||||
|
|
||||||
var getRecursiveSubspacesForSpace = (parentSpace, cb) => {
|
|
||||||
if (parentSpace.space_type == "folder") {
|
|
||||||
Space.find({
|
|
||||||
"parent_space_id": parentSpace._id
|
|
||||||
}).exec((err, subspaces) => {
|
|
||||||
async.map(subspaces, (space, innerCb) => {
|
|
||||||
getRecursiveSubspacesForSpace(space, (err, spaces) => {
|
|
||||||
innerCb(err, spaces);
|
|
||||||
});
|
|
||||||
}, (err, subspaces) => {
|
|
||||||
var flattenSubspaces = _.flatten(subspaces);
|
|
||||||
flattenSubspaces.push(parentSpace);
|
|
||||||
cb(null, flattenSubspaces);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
cb(null, [parentSpace]);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports.spaceSchema.statics.getRecursiveSubspacesForSpace = getRecursiveSubspacesForSpace;
|
|
||||||
|
|
||||||
var roleMapping = {
|
|
||||||
"none": 0,
|
|
||||||
"viewer": 1,
|
|
||||||
"editor": 2,
|
|
||||||
"admin": 3
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports.spaceSchema.statics.roleInSpace = (originalSpace, user, cb) => {
|
|
||||||
if (user.home_folder_id.toString() === originalSpace._id.toString()) {
|
|
||||||
cb(null, "admin");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (originalSpace.creator) {
|
|
||||||
if (originalSpace.creator._id.toString() === user._id.toString()) {
|
|
||||||
cb(null, "admin");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var findMembershipsForSpace = function(space, allMemberships, prevRole) {
|
|
||||||
Membership.find({
|
|
||||||
"space": space._id
|
|
||||||
}, (err, parentMemberships) => {
|
|
||||||
var currentMemberships = parentMemberships.concat(allMemberships);
|
|
||||||
|
|
||||||
if (space.parent_space_id) {
|
|
||||||
Space.findOne({
|
|
||||||
"_id": space.parent_space_id
|
|
||||||
}, function(err, parentSpace) {
|
|
||||||
|
|
||||||
var role = prevRole;
|
|
||||||
if(role == "none"){
|
|
||||||
if(originalSpace.access_mode == "public") {
|
|
||||||
role = "viewer";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
findMembershipsForSpace(parentSpace, currentMemberships, role);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// reached the top
|
|
||||||
var role = prevRole;
|
|
||||||
space.memberships = currentMemberships;
|
|
||||||
currentMemberships.forEach(function(m, i) {
|
|
||||||
if (m.user && m.user.equals(user._id)) {
|
|
||||||
if (m.role != null) {
|
|
||||||
if (roleMapping[m.role] > roleMapping[role]) {
|
|
||||||
role = m.role;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
cb(err, role);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
findMembershipsForSpace(originalSpace, [], "none");
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports.spaceSchema.statics.recursiveDelete = (space, cb) => {
|
|
||||||
space.remove(function(err) {
|
|
||||||
|
|
||||||
Action.remove({
|
|
||||||
space: space
|
|
||||||
}, function(err) {
|
|
||||||
if (err)
|
|
||||||
console.error("removed actions for space: ", err);
|
|
||||||
});
|
|
||||||
|
|
||||||
Membership.remove({
|
|
||||||
space: space
|
|
||||||
}, function(err) {
|
|
||||||
if (err)
|
|
||||||
console.error("removed memberships for space: ", err);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (space.space_type === "folder") {
|
|
||||||
Space
|
|
||||||
.find({
|
|
||||||
parent_space_id: space._id
|
|
||||||
})
|
|
||||||
.exec(function(err, spaces) {
|
|
||||||
async.eachLimit(spaces, 10, function(subSpace, innerCb) {
|
|
||||||
module.exports.spaceSchema.statics.recursiveDelete(subSpace, function(err) {
|
|
||||||
innerCb(err);
|
|
||||||
});
|
|
||||||
}, function(err) {
|
|
||||||
cb(err);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
} else {
|
|
||||||
|
|
||||||
Artifact.find({
|
|
||||||
space_id: space._id
|
|
||||||
}, function(err, artifacts) {
|
|
||||||
if (err) cb(err);
|
|
||||||
else {
|
|
||||||
async.eachLimit(artifacts, 20, function(a, innerCb) {
|
|
||||||
a.remove(function(err) {
|
|
||||||
innerCb(null, a);
|
|
||||||
});
|
|
||||||
}, function(err) {
|
|
||||||
cb(err);
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
var duplicateRecursiveSpace = (space, user, depth, cb, newParentSpace) => {
|
|
||||||
var newSpace = new Space(space);
|
|
||||||
newSpace._id = mongoose.Types.ObjectId();
|
|
||||||
|
|
||||||
if (newParentSpace) {
|
|
||||||
newSpace.parent_space_id = newParentSpace._id;
|
|
||||||
} else {
|
|
||||||
newSpace.name = newSpace.name + " (b)";
|
|
||||||
}
|
|
||||||
|
|
||||||
newSpace.creator = user;
|
|
||||||
newSpace.created_at = new Date();
|
|
||||||
newSpace.updated_at = new Date();
|
|
||||||
|
|
||||||
if (newSpace.space_type === "space") {
|
|
||||||
newSpace.edit_hash = crypto.randomBytes(64).toString('hex').substring(0, 7);
|
|
||||||
}
|
|
||||||
|
|
||||||
newSpace.save(function(err) {
|
|
||||||
|
|
||||||
if (newSpace.space_type === "folder" && depth < 10) {
|
|
||||||
|
|
||||||
Space
|
|
||||||
.find({
|
|
||||||
parent_space_id: space._id
|
|
||||||
})
|
|
||||||
.exec(function(err, spaces) {
|
|
||||||
async.eachLimit(spaces, 10, function(subSpace, innerCb) {
|
|
||||||
|
|
||||||
duplicateRecursiveSpace(subSpace, user, ++depth, function(err, newSubSpace) {
|
|
||||||
innerCb(err, newSubSpace);
|
|
||||||
}, newSpace);
|
|
||||||
|
|
||||||
}, function(err, allNewSubspaces) {
|
|
||||||
cb(err, newSpace);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
} else {
|
|
||||||
|
|
||||||
Artifact.find({
|
|
||||||
space_id: space._id
|
|
||||||
}, function(err, artifacts) {
|
|
||||||
if (err) innerCb(err);
|
|
||||||
else {
|
|
||||||
async.eachLimit(artifacts, 20, function(a, innerCb) {
|
|
||||||
var newArtifact = new Artifact(a);
|
|
||||||
newArtifact._id = mongoose.Types.ObjectId();
|
|
||||||
newArtifact.space_id = newSpace._id;
|
|
||||||
newArtifact.created_at = new Date();
|
|
||||||
newArtifact.updated_at = new Date();
|
|
||||||
|
|
||||||
newArtifact.save(function(err) {
|
|
||||||
innerCb(null, newArtifact);
|
|
||||||
});
|
|
||||||
|
|
||||||
}, function(err, allNewArtifacts) {
|
|
||||||
cb(err, newSpace);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports.spaceSchema.statics.duplicateSpace = duplicateRecursiveSpace;
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
var mongoose = require('mongoose');
|
|
||||||
var Schema = mongoose.Schema;
|
|
||||||
|
|
||||||
module.exports.teamSchema = mongoose.Schema({
|
|
||||||
name: String,
|
|
||||||
subdomain: String,
|
|
||||||
creator: {
|
|
||||||
type: Schema.Types.ObjectId,
|
|
||||||
ref: 'User'
|
|
||||||
},
|
|
||||||
admins: [{
|
|
||||||
type: Schema.Types.ObjectId,
|
|
||||||
ref: 'User'
|
|
||||||
}],
|
|
||||||
invitation_codes: [String],
|
|
||||||
avatar_thumb_uri: String,
|
|
||||||
avatar_uri: String,
|
|
||||||
payment_type: {
|
|
||||||
type: String,
|
|
||||||
default: "auto"
|
|
||||||
},
|
|
||||||
payment_plan_key: String,
|
|
||||||
payment_subscription_id: String,
|
|
||||||
blocked_at: {
|
|
||||||
type: Date
|
|
||||||
},
|
|
||||||
upgraded_at: {
|
|
||||||
type: Date
|
|
||||||
},
|
|
||||||
created_at: {
|
|
||||||
type: Date,
|
|
||||||
default: Date.now
|
|
||||||
},
|
|
||||||
updated_at: {
|
|
||||||
type: Date,
|
|
||||||
default: Date.now
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports.teamSchema.index({
|
|
||||||
creator: 1
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports.teamSchema.statics.getTeamForHost = (host, cb) => {
|
|
||||||
|
|
||||||
if (host != "127.0.0.1:9666") { //phantomjs check
|
|
||||||
let subDomainParts = host.split('.');
|
|
||||||
|
|
||||||
if (subDomainParts.length > 2) {
|
|
||||||
const subdomain = subDomainParts[0];
|
|
||||||
|
|
||||||
if (subdomain != "www") {
|
|
||||||
Team.findOne({
|
|
||||||
subdomain: subdomain
|
|
||||||
}).exec((err, team) => {
|
|
||||||
cb(err, team, subdomain)
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
cb(null, null)
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
cb(null, null);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
cb(null, null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
var mongoose = require('mongoose');
|
|
||||||
var Schema = mongoose.Schema;
|
|
||||||
|
|
||||||
module.exports.userSchema = mongoose.Schema({
|
|
||||||
email: String,
|
|
||||||
password_hash: String,
|
|
||||||
nickname: String,
|
|
||||||
account_type: {type: String, default: "email"},
|
|
||||||
created_at: {type: Date, default: Date.now},
|
|
||||||
updated_at: {type: Date, default: Date.now},
|
|
||||||
avatar_original_uri: String,
|
|
||||||
avatar_thumb_uri: String,
|
|
||||||
src: String,
|
|
||||||
confirmation_token: String,
|
|
||||||
confirmed_at: Date,
|
|
||||||
password_reset_token: String,
|
|
||||||
home_folder_id: Schema.Types.ObjectId,
|
|
||||||
team : { type: Schema.Types.ObjectId, ref: 'Team' },
|
|
||||||
preferences: {
|
|
||||||
language: String,
|
|
||||||
email_notifications: {type: Boolean, default: true},
|
|
||||||
daily_digest_last_send: Date,
|
|
||||||
daily_digest: {type: Boolean, default: true}
|
|
||||||
},
|
|
||||||
sessions: [
|
|
||||||
{
|
|
||||||
token: String,
|
|
||||||
expires: Date,
|
|
||||||
device: String,
|
|
||||||
ip: String,
|
|
||||||
created_at: Date
|
|
||||||
}
|
|
||||||
],
|
|
||||||
payment_info: String,
|
|
||||||
payment_plan_key: {type: String, default: "free"},
|
|
||||||
payment_customer_id: String,
|
|
||||||
payment_subscription_id: String,
|
|
||||||
payment_notification_state: Number
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports.userSchema.index({
|
|
||||||
email: 1,
|
|
||||||
"sessions.token": 1,
|
|
||||||
team: 1,
|
|
||||||
created_at: 1,
|
|
||||||
home_folder_id: 1
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports.userSchema.statics.findBySessionToken = function (token, cb) {
|
|
||||||
return this.findOne({ "sessions.token": token}, cb);
|
|
||||||
};
|
|
||||||
89
package.json
89
package.json
@@ -3,86 +3,55 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "nodemon -e .js,.html bin/www",
|
"start": "node spacedeck.js"
|
||||||
"test": "mocha"
|
|
||||||
},
|
},
|
||||||
"engines" : {
|
"engines": {
|
||||||
"node" : ">=7.8.0"
|
"node": ">=10.0.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"archiver": "1.3.0",
|
"archiver": "1.3.0",
|
||||||
"async": "2.3.0",
|
"async": "2.3.0",
|
||||||
"aws-sdk": "2.39.0",
|
|
||||||
"basic-auth": "1.1.0",
|
|
||||||
"bcryptjs": "2.4.3",
|
"bcryptjs": "2.4.3",
|
||||||
"body-parser": "~1.17.1",
|
"body-parser": "^1.19.0",
|
||||||
"cheerio": "0.22.0",
|
"cheerio": "0.22.0",
|
||||||
"config": "1.25.1",
|
"config": "1.25.1",
|
||||||
"cookie-parser": "~1.4.3",
|
"cookie-parser": "~1.4.3",
|
||||||
"csurf": "1.9.0",
|
"ejs": "3.1.5",
|
||||||
"debug": "~2.6.3",
|
|
||||||
"execSync": "latest",
|
"execSync": "latest",
|
||||||
"express": "~4.13.0",
|
"express": "^4.16.4",
|
||||||
|
"file-type": "^7.6.0",
|
||||||
"glob": "7.1.1",
|
"glob": "7.1.1",
|
||||||
"gm": "1.23.0",
|
"gm": "^1.23.1",
|
||||||
"googleapis": "18.0.0",
|
"gulp": "^4.0.2",
|
||||||
"gulp": "^3.9.1",
|
"gulp-concat": "^2.6.1",
|
||||||
"gulp-concat": "2.6.0",
|
"gulp-sass": "^4.0.2",
|
||||||
"gulp-eslint": "*",
|
|
||||||
"gulp-express": "0.3.0",
|
|
||||||
"gulp-nodemon": "*",
|
|
||||||
"gulp-sass": "^2.0.3",
|
|
||||||
"gulp-uglify": "^1.5.1",
|
|
||||||
"gulp-util": "^3.0.6",
|
|
||||||
"helmet": "^3.5.0",
|
"helmet": "^3.5.0",
|
||||||
"i18n-2": "0.6.3",
|
"i18n-2": "0.6.3",
|
||||||
"ioredis": "2.5.0",
|
|
||||||
"lodash": "^4.3.0",
|
|
||||||
"log-timestamp": "latest",
|
"log-timestamp": "latest",
|
||||||
"md5": "2.2.1",
|
"mock-aws-s3": "^2.6.0",
|
||||||
"moment": "2.18.1",
|
"moment": "^2.19.3",
|
||||||
"mongoose": "4.9.3",
|
"morgan": "^1.9.1",
|
||||||
"morgan": "1.8.1",
|
|
||||||
"node-sass-middleware": "0.11.0",
|
|
||||||
"pdfkit": "0.8.0",
|
|
||||||
"validator": "7.0.0",
|
|
||||||
"node-phantom-simple": "2.2.4",
|
"node-phantom-simple": "2.2.4",
|
||||||
"phantomjs-prebuilt": "2.1.14",
|
"node-server-screenshot": "^0.2.1",
|
||||||
"pm2": "latest",
|
"nodemailer": "^4.6.7",
|
||||||
"qr-image": "3.2.0",
|
"phantomjs-prebuilt": "^2.1.16",
|
||||||
"raven": "1.2.0",
|
"read-chunk": "^2.1.0",
|
||||||
"request": "2.81.0",
|
"request": "^2.88.0",
|
||||||
"sanitize-html": "^1.11.1",
|
"sanitize-html": "^1.11.1",
|
||||||
|
"sequelize": "^4.37.6",
|
||||||
"serve-favicon": "~2.4.2",
|
"serve-favicon": "~2.4.2",
|
||||||
"swig": "1.4.2",
|
"serve-static": "^1.13.1",
|
||||||
"slug": "0.9.1",
|
"slug": "^1.1.0",
|
||||||
|
"sqlite3": "^4.0.0",
|
||||||
|
"umzug": "^2.1.0",
|
||||||
"underscore": "1.8.3",
|
"underscore": "1.8.3",
|
||||||
"weak": "1.0.1",
|
"uuid": "^3.2.1",
|
||||||
"ws": "2.2.3"
|
"validator": "7.0.0",
|
||||||
},
|
"ws": "3.3.1"
|
||||||
"devDependencies": {
|
|
||||||
"express": "^4.13.3",
|
|
||||||
"gulp": "^3.9.1",
|
|
||||||
"gulp-clean": "^0.3.2",
|
|
||||||
"gulp-concat": "^2.6.0",
|
|
||||||
"gulp-eslint": "^3.0.1",
|
|
||||||
"gulp-express": "^0.3.0",
|
|
||||||
"gulp-fingerprint": "^0.3.2",
|
|
||||||
"gulp-nodemon": "^2.0.4",
|
|
||||||
"gulp-rev": "^7.1.2",
|
|
||||||
"gulp-rev-all": "^0.9.7",
|
|
||||||
"gulp-rev-replace": "^0.4.3",
|
|
||||||
"gulp-sass": "^3.1.0",
|
|
||||||
"gulp-uglify": "^2.1.2",
|
|
||||||
"nodemon": "1.11.0",
|
|
||||||
"should": "^11.2.1",
|
|
||||||
"supertest": "^3.0.0",
|
|
||||||
"winston": "^2.3.1"
|
|
||||||
},
|
},
|
||||||
|
"main": "app.js",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "Gulpfile.js",
|
"directories": {},
|
||||||
"directories": {
|
|
||||||
},
|
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/spacedeck/spacedeck-open.git"
|
"url": "https://github.com/spacedeck/spacedeck-open.git"
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.4 KiB |
69
public/images/sd6-icon-white.svg
Normal file
69
public/images/sd6-icon-white.svg
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
width="17.370329mm"
|
||||||
|
height="17.370247mm"
|
||||||
|
viewBox="0 0 17.370329 17.370247"
|
||||||
|
version="1.1"
|
||||||
|
id="svg3417"
|
||||||
|
inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
|
||||||
|
sodipodi:docname="sd6-icon-white.svg"
|
||||||
|
inkscape:export-filename="/home/mntmn/code/spacedeck-open/public/images/favicon.png"
|
||||||
|
inkscape:export-xdpi="93.585312"
|
||||||
|
inkscape:export-ydpi="93.585312">
|
||||||
|
<defs
|
||||||
|
id="defs3411" />
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="base"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:zoom="5.6"
|
||||||
|
inkscape:cx="68.901329"
|
||||||
|
inkscape:cy="26.613846"
|
||||||
|
inkscape:document-units="mm"
|
||||||
|
inkscape:current-layer="layer1"
|
||||||
|
showgrid="false"
|
||||||
|
fit-margin-top="0"
|
||||||
|
fit-margin-left="0"
|
||||||
|
fit-margin-right="0"
|
||||||
|
fit-margin-bottom="0"
|
||||||
|
inkscape:window-width="2560"
|
||||||
|
inkscape:window-height="1376"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="27"
|
||||||
|
inkscape:window-maximized="1" />
|
||||||
|
<metadata
|
||||||
|
id="metadata3414">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work
|
||||||
|
rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
<dc:title></dc:title>
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<g
|
||||||
|
inkscape:label="Layer 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1"
|
||||||
|
transform="translate(-61.618407,-79.672019)">
|
||||||
|
<path
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path1681-6-5-3-7-4-9-2-0-2-9-7"
|
||||||
|
d="m 69.103371,79.69206 c -0.792105,0.07526 -1.553632,0.368078 -2.179688,0.99414 -0.967242,0.967233 -1.023215,2.24006 -0.822265,3.46875 -1.228429,-0.200703 -2.499819,-0.144769 -3.466797,0.822266 -1.252082,1.252133 -1.178244,3.043412 -0.677734,4.544922 0.500509,1.50151 1.477937,2.995513 2.832031,4.349611 1.354102,1.3541 2.848091,2.33152 4.349609,2.83203 1.501518,0.50051 3.292795,0.57437 4.544922,-0.67773 0.9673,-0.96727 1.023249,-2.24001 0.822266,-3.468755 1.228416,0.200714 2.499803,0.146691 3.466796,-0.820313 1.252124,-1.252112 1.17824,-3.045353 0.677735,-4.546874 -0.500505,-1.501522 -1.477926,-2.995502 -2.832031,-4.34961 -1.354109,-1.354105 -2.848087,-2.329573 -4.34961,-2.830078 -0.750761,-0.250253 -1.57313,-0.393617 -2.365234,-0.318359 z m 0.251953,3.427734 c -0.06232,0.06232 0.187775,-0.12686 1.025391,0.152344 0.837615,0.279204 1.980359,0.976455 3.005859,2.001953 1.025498,1.0255 1.720796,2.16629 2,3.003906 0.279204,0.837616 0.09198,1.087707 0.154297,1.025391 0.06232,-0.06232 -0.187775,0.124907 -1.025391,-0.154297 -0.817005,-0.272334 -1.926016,-0.966798 -2.93164,-1.951172 -0.02107,-0.02133 -0.03343,-0.04515 -0.05469,-0.06641 -0.02194,-0.02194 -0.04635,-0.0349 -0.06836,-0.05664 -0.984356,-1.005615 -1.678841,-2.112692 -1.951172,-2.929687 -0.279204,-0.837616 -0.09198,-1.087708 -0.154297,-1.025391 z m -4.289063,4.289063 c -0.06231,0.06232 0.187774,-0.124903 1.025391,0.154296 0.81575,0.271911 1.923337,0.965368 2.927735,1.947266 0.02276,0.02306 0.03561,0.04929 0.05859,0.07227 0.023,0.023 0.04918,0.03581 0.07227,0.05859 0.981898,1.004395 1.67535,2.111982 1.947265,2.927735 0.279205,0.837619 0.09198,1.087705 0.154297,1.025385 0.06232,-0.0623 -0.187772,0.12492 -1.02539,-0.154291 -0.837619,-0.27921 -1.980364,-0.974504 -3.00586,-2 -1.025488,-1.025491 -1.720791,-2.168245 -2,-3.00586 -0.279208,-0.837615 -0.09198,-1.087708 -0.154297,-1.02539 z"
|
||||||
|
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3.4395833;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:37.79527664;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 5.0 KiB |
66
public/images/sd6-icon.svg
Normal file
66
public/images/sd6-icon.svg
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
width="17.370329mm"
|
||||||
|
height="17.370247mm"
|
||||||
|
viewBox="0 0 17.370329 17.370247"
|
||||||
|
version="1.1"
|
||||||
|
id="svg3417"
|
||||||
|
inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
|
||||||
|
sodipodi:docname="sd6-icon.svg">
|
||||||
|
<defs
|
||||||
|
id="defs3411" />
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="base"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:zoom="5.6"
|
||||||
|
inkscape:cx="68.901329"
|
||||||
|
inkscape:cy="26.613846"
|
||||||
|
inkscape:document-units="mm"
|
||||||
|
inkscape:current-layer="layer1"
|
||||||
|
showgrid="false"
|
||||||
|
fit-margin-top="0"
|
||||||
|
fit-margin-left="0"
|
||||||
|
fit-margin-right="0"
|
||||||
|
fit-margin-bottom="0"
|
||||||
|
inkscape:window-width="2560"
|
||||||
|
inkscape:window-height="1376"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="27"
|
||||||
|
inkscape:window-maximized="1" />
|
||||||
|
<metadata
|
||||||
|
id="metadata3414">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work
|
||||||
|
rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
<dc:title></dc:title>
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<g
|
||||||
|
inkscape:label="Layer 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1"
|
||||||
|
transform="translate(-61.618407,-79.672019)">
|
||||||
|
<path
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path1681-6-5-3-7-4-9-2-0-2-9-7"
|
||||||
|
d="m 69.103371,79.69206 c -0.792105,0.07526 -1.553632,0.368078 -2.179688,0.99414 -0.967242,0.967233 -1.023215,2.24006 -0.822265,3.46875 -1.228429,-0.200703 -2.499819,-0.144769 -3.466797,0.822266 -1.252082,1.252133 -1.178244,3.043412 -0.677734,4.544922 0.500509,1.50151 1.477937,2.995513 2.832031,4.349611 1.354102,1.3541 2.848091,2.33152 4.349609,2.83203 1.501518,0.50051 3.292795,0.57437 4.544922,-0.67773 0.9673,-0.96727 1.023249,-2.24001 0.822266,-3.468755 1.228416,0.200714 2.499803,0.146691 3.466796,-0.820313 1.252124,-1.252112 1.17824,-3.045353 0.677735,-4.546874 -0.500505,-1.501522 -1.477926,-2.995502 -2.832031,-4.34961 -1.354109,-1.354105 -2.848087,-2.329573 -4.34961,-2.830078 -0.750761,-0.250253 -1.57313,-0.393617 -2.365234,-0.318359 z m 0.251953,3.427734 c -0.06232,0.06232 0.187775,-0.12686 1.025391,0.152344 0.837615,0.279204 1.980359,0.976455 3.005859,2.001953 1.025498,1.0255 1.720796,2.16629 2,3.003906 0.279204,0.837616 0.09198,1.087707 0.154297,1.025391 0.06232,-0.06232 -0.187775,0.124907 -1.025391,-0.154297 -0.817005,-0.272334 -1.926016,-0.966798 -2.93164,-1.951172 -0.02107,-0.02133 -0.03343,-0.04515 -0.05469,-0.06641 -0.02194,-0.02194 -0.04635,-0.0349 -0.06836,-0.05664 -0.984356,-1.005615 -1.678841,-2.112692 -1.951172,-2.929687 -0.279204,-0.837616 -0.09198,-1.087708 -0.154297,-1.025391 z m -4.289063,4.289063 c -0.06231,0.06232 0.187774,-0.124903 1.025391,0.154296 0.81575,0.271911 1.923337,0.965368 2.927735,1.947266 0.02276,0.02306 0.03561,0.04929 0.05859,0.07227 0.023,0.023 0.04918,0.03581 0.07227,0.05859 0.981898,1.004395 1.67535,2.111982 1.947265,2.927735 0.279205,0.837619 0.09198,1.087705 0.154297,1.025385 0.06232,-0.0623 -0.187772,0.12492 -1.02539,-0.154291 -0.837619,-0.27921 -1.980364,-0.974504 -3.00586,-2 -1.025488,-1.025491 -1.720791,-2.168245 -2,-3.00586 -0.279208,-0.837615 -0.09198,-1.087708 -0.154297,-1.02539 z"
|
||||||
|
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3.4395833;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:37.79527664;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 4.9 KiB |
129
public/images/sd6-logo-black.svg
Normal file
129
public/images/sd6-logo-black.svg
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
width="81.731232mm"
|
||||||
|
height="17.370247mm"
|
||||||
|
viewBox="0 0 81.731232 17.370247"
|
||||||
|
version="1.1"
|
||||||
|
id="svg2651"
|
||||||
|
inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
|
||||||
|
sodipodi:docname="sd6-logo-black.svg">
|
||||||
|
<defs
|
||||||
|
id="defs2645" />
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="base"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:zoom="2.8"
|
||||||
|
inkscape:cx="80.852573"
|
||||||
|
inkscape:cy="-16.110417"
|
||||||
|
inkscape:document-units="mm"
|
||||||
|
inkscape:current-layer="layer1"
|
||||||
|
showgrid="false"
|
||||||
|
fit-margin-top="0"
|
||||||
|
fit-margin-left="0"
|
||||||
|
fit-margin-right="0"
|
||||||
|
fit-margin-bottom="0"
|
||||||
|
inkscape:window-width="2560"
|
||||||
|
inkscape:window-height="1376"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="27"
|
||||||
|
inkscape:window-maximized="1" />
|
||||||
|
<metadata
|
||||||
|
id="metadata2648">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work
|
||||||
|
rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
<dc:title></dc:title>
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<g
|
||||||
|
inkscape:label="Layer 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1"
|
||||||
|
transform="translate(-29.059958,-86.19285)">
|
||||||
|
<g
|
||||||
|
id="g3248">
|
||||||
|
<path
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path1681-6-5-3-7-4-9-2-0-2-9-7"
|
||||||
|
d="m 36.544922,86.212891 c -0.792105,0.07526 -1.553632,0.368078 -2.179688,0.99414 -0.967242,0.967233 -1.023215,2.24006 -0.822265,3.46875 -1.228429,-0.200703 -2.499819,-0.144769 -3.466797,0.822266 -1.252082,1.252133 -1.178244,3.043412 -0.677734,4.544922 0.500509,1.50151 1.477937,2.995513 2.832031,4.349611 1.354102,1.3541 2.848091,2.33152 4.349609,2.83203 1.501518,0.50051 3.292795,0.57437 4.544922,-0.67773 0.9673,-0.96727 1.023249,-2.24001 0.822266,-3.468755 1.228416,0.200714 2.499803,0.146691 3.466796,-0.820313 1.252124,-1.252112 1.17824,-3.045353 0.677735,-4.546874 -0.500505,-1.501522 -1.477926,-2.995502 -2.832031,-4.34961 -1.354109,-1.354105 -2.848087,-2.329573 -4.34961,-2.830078 -0.750761,-0.250253 -1.57313,-0.393617 -2.365234,-0.318359 z m 0.251953,3.427734 c -0.06232,0.06232 0.187775,-0.12686 1.025391,0.152344 0.837615,0.279204 1.980359,0.976455 3.005859,2.001953 1.025498,1.0255 1.720796,2.16629 2,3.003906 0.279204,0.837616 0.09198,1.087707 0.154297,1.025391 0.06232,-0.06232 -0.187775,0.124907 -1.025391,-0.154297 -0.817005,-0.272334 -1.926016,-0.966798 -2.93164,-1.951172 -0.02107,-0.02133 -0.03343,-0.04515 -0.05469,-0.06641 -0.02194,-0.02194 -0.04635,-0.0349 -0.06836,-0.05664 -0.984356,-1.005615 -1.678841,-2.112692 -1.951172,-2.929687 -0.279204,-0.837616 -0.09198,-1.087708 -0.154297,-1.025391 z m -4.289063,4.289063 c -0.06231,0.06232 0.187774,-0.124903 1.025391,0.154296 0.81575,0.271911 1.923337,0.965368 2.927735,1.947266 0.02276,0.02306 0.03561,0.04929 0.05859,0.07227 0.023,0.023 0.04918,0.03581 0.07227,0.05859 0.981898,1.004395 1.67535,2.111982 1.947265,2.927735 0.279205,0.837619 0.09198,1.087705 0.154297,1.025385 0.06232,-0.0623 -0.187772,0.12492 -1.02539,-0.154291 -0.837619,-0.27921 -1.980364,-0.974504 -3.00586,-2 -1.025488,-1.025491 -1.720791,-2.168245 -2,-3.00586 -0.279208,-0.837615 -0.09198,-1.087708 -0.154297,-1.02539 z"
|
||||||
|
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3.4395833;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:37.79527664;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
|
||||||
|
<g
|
||||||
|
id="g2614"
|
||||||
|
transform="matrix(0.26458333,0,0,0.26458333,-523.78744,61.714265)">
|
||||||
|
<g
|
||||||
|
id="flowRoot1610-0-6-8-1-1"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:800;font-stretch:normal;font-size:16.00038528px;line-height:1.25;font-family:Inter;-inkscape-font-specification:'Inter, Ultra-Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;opacity:1;vector-effect:none;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.37800002;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:37.79527664;stroke-opacity:1"
|
||||||
|
transform="matrix(2.6369365,0,0,2.6369365,2045.0224,86.079903)"
|
||||||
|
aria-label="Spacedeck">
|
||||||
|
<path
|
||||||
|
id="path3214"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:800;font-stretch:normal;font-size:16.00038528px;font-family:Inter;-inkscape-font-specification:'Inter, Ultra-Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none"
|
||||||
|
d="m 52.795627,11.002199 h 2.693247 C 55.466146,8.82601 53.73315,7.3543837 51.028539,7.3543837 c -2.659155,0 -4.573974,1.4488985 -4.556928,3.6137233 -0.0057,1.767088 1.232985,2.76143 3.244397,3.221669 l 1.215938,0.284097 c 1.27844,0.295462 1.852317,0.642061 1.863681,1.295486 -0.01136,0.710244 -0.676152,1.204575 -1.806861,1.204575 -1.244349,0 -2.06255,-0.57956 -2.125052,-1.698905 h -2.693246 c 0.03409,2.721656 1.926182,4.022824 4.852389,4.022824 2.897797,0 4.613748,-1.312532 4.625112,-3.522812 -0.01136,-1.857999 -1.267076,-2.99439 -3.562586,-3.500084 l -1.000024,-0.227278 c -1.056844,-0.227279 -1.727315,-0.57956 -1.704587,-1.272758 0.0057,-0.636379 0.55115,-1.0966177 1.642085,-1.0966177 1.096618,0 1.698905,0.4943297 1.77277,1.3238957 z"
|
||||||
|
inkscape:connector-curvature="0" />
|
||||||
|
<path
|
||||||
|
id="path3216"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:800;font-stretch:normal;font-size:16.00038528px;font-family:Inter;-inkscape-font-specification:'Inter, Ultra-Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none"
|
||||||
|
d="m 57.068458,22.422928 h 2.778476 v -4.687613 h 0.05682 c 0.352281,0.806838 1.136391,1.53981 2.454605,1.53981 1.931864,0 3.48872,-1.5114 3.48872,-4.483062 0,-3.07962 -1.647767,-4.483063 -3.471674,-4.483063 -1.380715,0 -2.136415,0.806838 -2.471651,1.607994 h -0.08523 v -1.494355 h -2.750066 z m 2.721656,-7.636547 c 0,-1.426171 0.590923,-2.306874 1.607993,-2.306874 1.028434,0 1.59663,0.903431 1.59663,2.306874 0,1.409125 -0.568196,2.323919 -1.59663,2.323919 -1.01707,0 -1.607993,-0.909112 -1.607993,-2.323919 z"
|
||||||
|
inkscape:connector-curvature="0" />
|
||||||
|
<path
|
||||||
|
id="path3218"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:800;font-stretch:normal;font-size:16.00038528px;font-family:Inter;-inkscape-font-specification:'Inter, Ultra-Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none"
|
||||||
|
d="m 69.778991,19.297853 c 1.215939,0 2.056868,-0.471602 2.534152,-1.363669 h 0.06818 v 1.215938 h 2.613699 v -5.931961 c 0,-1.846635 -1.642085,-2.909161 -3.86373,-2.909161 -2.346647,0 -3.676224,1.181847 -3.897821,2.772794 l 2.562562,0.09091 c 0.119321,-0.556832 0.579559,-0.897749 1.312532,-0.897749 0.681834,0 1.113663,0.329553 1.113663,0.914794 v 0.02841 c 0,0.534104 -0.57956,0.647743 -2.068232,0.778428 -1.767088,0.147731 -3.244396,0.801156 -3.244396,2.73302 0,1.727315 1.198892,2.568244 2.869387,2.568244 z m 0.857975,-1.818226 c -0.642061,0 -1.096617,-0.306825 -1.096617,-0.886384 0,-0.562514 0.443193,-0.903431 1.232984,-1.022752 0.517058,-0.07387 1.153437,-0.187505 1.465945,-0.352282 v 0.829566 c 0,0.852293 -0.715927,1.431852 -1.602312,1.431852 z"
|
||||||
|
inkscape:connector-curvature="0" />
|
||||||
|
<path
|
||||||
|
id="path3220"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:800;font-stretch:normal;font-size:16.00038528px;font-family:Inter;-inkscape-font-specification:'Inter, Ultra-Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none"
|
||||||
|
d="m 80.767893,19.314899 c 2.454605,0 3.977369,-1.426171 4.051234,-3.585314 h -2.596653 c -0.102275,0.926159 -0.659107,1.431853 -1.420489,1.431853 -0.977296,0 -1.613675,-0.823883 -1.613675,-2.375057 0,-1.53981 0.642061,-2.363693 1.613675,-2.363693 0.795474,0 1.312532,0.539785 1.420489,1.431852 h 2.596653 c -0.0625,-2.147779 -1.630721,-3.54554 -4.056916,-3.54554 -2.744384,0 -4.403515,1.82959 -4.403515,4.505791 0,2.664836 1.647767,4.500108 4.409197,4.500108 z"
|
||||||
|
inkscape:connector-curvature="0" />
|
||||||
|
<path
|
||||||
|
id="path3222"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:800;font-stretch:normal;font-size:16.00038528px;font-family:Inter;-inkscape-font-specification:'Inter, Ultra-Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none"
|
||||||
|
d="m 90.336304,19.314899 c 2.289828,0 3.795546,-1.107981 4.113736,-2.823932 l -2.551198,-0.07387 c -0.215914,0.579559 -0.78411,0.892067 -1.5114,0.892067 -1.068208,0 -1.727315,-0.710245 -1.727315,-1.778452 v -0.07386 h 5.818322 v -0.693199 c 0,-2.875069 -1.750042,-4.454653 -4.227374,-4.454653 -2.636427,0 -4.32965,1.806862 -4.32965,4.511473 0,2.795521 1.670495,4.494426 4.414879,4.494426 z m -1.676177,-5.471723 c 0.03977,-0.869339 0.727291,-1.528446 1.647767,-1.528446 0.914795,0 1.573902,0.636379 1.585266,1.528446 z"
|
||||||
|
inkscape:connector-curvature="0" />
|
||||||
|
<path
|
||||||
|
id="path3224"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:800;font-stretch:normal;font-size:16.00038528px;font-family:Inter;-inkscape-font-specification:'Inter, Ultra-Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none"
|
||||||
|
d="m 99.10072,19.275125 c 1.31821,0 2.10232,-0.732972 2.4546,-1.53981 h 0.0852 v 1.414807 h 2.75007 V 7.5134784 h -2.77848 v 4.4035156 h -0.0568 C 101.22577,11.115838 100.46439,10.309 99.089356,10.309 c -1.823907,0 -3.477356,1.403443 -3.477356,4.483063 0,2.971662 1.562537,4.483062 3.48872,4.483062 z m 0.96593,-2.164825 c -1.028432,0 -1.602309,-0.914794 -1.602309,-2.323919 0,-1.403443 0.568196,-2.306874 1.602309,-2.306874 1.01707,0 1.608,0.880703 1.608,2.306874 0,1.414807 -0.59661,2.323919 -1.608,2.323919 z"
|
||||||
|
inkscape:connector-curvature="0" />
|
||||||
|
<path
|
||||||
|
id="path3226"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:800;font-stretch:normal;font-size:16.00038528px;font-family:Inter;-inkscape-font-specification:'Inter, Ultra-Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none"
|
||||||
|
d="m 110.24303,19.314899 c 2.28983,0 3.79555,-1.107981 4.11374,-2.823932 l -2.5512,-0.07387 c -0.21591,0.579559 -0.78411,0.892067 -1.5114,0.892067 -1.06821,0 -1.72731,-0.710245 -1.72731,-1.778452 v -0.07386 h 5.81832 v -0.693199 c 0,-2.875069 -1.75004,-4.454653 -4.22737,-4.454653 -2.63643,0 -4.32965,1.806862 -4.32965,4.511473 0,2.795521 1.67049,4.494426 4.41487,4.494426 z m -1.67617,-5.471723 c 0.0398,-0.869339 0.72729,-1.528446 1.64777,-1.528446 0.91479,0 1.5739,0.636379 1.58526,1.528446 z"
|
||||||
|
inkscape:connector-curvature="0" />
|
||||||
|
<path
|
||||||
|
id="path3228"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:800;font-stretch:normal;font-size:16.00038528px;font-family:Inter;-inkscape-font-specification:'Inter, Ultra-Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none"
|
||||||
|
d="m 119.89384,19.314899 c 2.4546,0 3.97736,-1.426171 4.05123,-3.585314 h -2.59665 c -0.10228,0.926159 -0.65911,1.431853 -1.42049,1.431853 -0.9773,0 -1.61368,-0.823883 -1.61368,-2.375057 0,-1.53981 0.64206,-2.363693 1.61368,-2.363693 0.79547,0 1.31253,0.539785 1.42049,1.431852 h 2.59665 c -0.0625,-2.147779 -1.63072,-3.54554 -4.05692,-3.54554 -2.74438,0 -4.40351,1.82959 -4.40351,4.505791 0,2.664836 1.64777,4.500108 4.4092,4.500108 z"
|
||||||
|
inkscape:connector-curvature="0" />
|
||||||
|
<path
|
||||||
|
id="path3230"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:800;font-stretch:normal;font-size:16.00038528px;font-family:Inter;-inkscape-font-specification:'Inter, Ultra-Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none"
|
||||||
|
d="m 125.3826,19.150122 h 2.77848 v -2.619381 l 0.56251,-0.681835 2.0796,3.301216 h 3.2103 l -3.22167,-4.926255 3.09667,-3.801228 h -3.1478 l -2.45461,3.125076 h -0.125 V 7.5134784 h -2.77848 z"
|
||||||
|
inkscape:connector-curvature="0" />
|
||||||
|
</g>
|
||||||
|
<path
|
||||||
|
d="m 2146.72,133.51812 a 23.030019,11.514995 45 0 1 -24.427,-8.1423 23.030019,11.514995 45 0 1 -8.1423,-24.427 23.030019,11.514995 45 0 1 24.427,8.1423 23.030019,11.514995 45 0 1 8.1423,24.427 z m -16.2137,16.2138 a 23.030019,11.514995 45 0 1 -24.427,-8.1424 23.030019,11.514995 45 0 1 -8.1424,-24.4269 23.030019,11.514995 45 0 1 24.4271,8.1422 23.030019,11.514995 45 0 1 8.1423,24.4271 z"
|
||||||
|
style="opacity:1;vector-effect:none;fill:none;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:37.79527664;stroke-opacity:1"
|
||||||
|
id="path1681-6-5-3-7-4-9-2-0-2-7-6"
|
||||||
|
inkscape:connector-curvature="0" />
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 16 KiB |
BIN
public/images/sd6-screenshot.png
Normal file
BIN
public/images/sd6-screenshot.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 158 KiB |
@@ -6,6 +6,10 @@ var websocket = null;
|
|||||||
var channel_id = null;
|
var channel_id = null;
|
||||||
var space_auth = 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) {
|
function load_resource(method, path, data, on_success, on_error, on_progress) {
|
||||||
var req = new XMLHttpRequest();
|
var req = new XMLHttpRequest();
|
||||||
req.onload = function(evt,b,c) {
|
req.onload = function(evt,b,c) {
|
||||||
@@ -44,24 +48,17 @@ function load_resource(method, path, data, on_success, on_error, on_progress) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
req.withCredentials = true;
|
req.withCredentials = true;
|
||||||
|
|
||||||
req.open(method, api_endpoint+"/api"+path, true);
|
req.open(method, api_endpoint+"/api"+path, true);
|
||||||
|
|
||||||
if (api_token) {
|
if (api_token) {
|
||||||
req.setRequestHeader("X-Spacedeck-Auth", api_token);
|
req.setRequestHeader("X-Spacedeck-Auth", api_token);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (space_auth) {
|
if (space_auth) {
|
||||||
console.log("set space auth", space_auth);
|
|
||||||
req.setRequestHeader("X-Spacedeck-Space-Auth", space_auth);
|
req.setRequestHeader("X-Spacedeck-Space-Auth", space_auth);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (channel_id) {
|
if (channel_id) {
|
||||||
req.setRequestHeader("X-Spacedeck-Channel", channel_id);
|
req.setRequestHeader("X-Spacedeck-Channel", channel_id);
|
||||||
}
|
}
|
||||||
if (csrf_token) {
|
|
||||||
req.setRequestHeader("X-csrf-token", csrf_token);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (data) {
|
if (data) {
|
||||||
@@ -133,10 +130,6 @@ function load_spaces(id, is_home, on_success, on_error) {
|
|||||||
}, on_error);
|
}, on_error);
|
||||||
}
|
}
|
||||||
|
|
||||||
function load_writable_folders( on_success, on_error) {
|
|
||||||
load_resource("get", "/spaces?writablefolders=true", null, on_success, on_error);
|
|
||||||
}
|
|
||||||
|
|
||||||
function load_history(s, on_success, on_error) {
|
function load_history(s, on_success, on_error) {
|
||||||
load_resource("get", "/spaces/"+ s._id +"/digest", null, on_success, on_error);
|
load_resource("get", "/spaces/"+ s._id +"/digest", null, on_success, on_error);
|
||||||
}
|
}
|
||||||
@@ -182,12 +175,10 @@ function delete_space(s, on_success, on_error) {
|
|||||||
load_resource("delete", "/spaces/"+s._id, null, on_success, on_error);
|
load_resource("delete", "/spaces/"+s._id, null, on_success, on_error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function delete_artifact(a, on_success, on_error) {
|
function delete_artifact(a, on_success, on_error) {
|
||||||
load_resource("delete", "/spaces/"+a.space_id+"/artifacts/"+a._id);
|
load_resource("delete", "/spaces/"+a.space_id+"/artifacts/"+a._id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function duplicate_space(s, to_space_id, on_success, on_error) {
|
function duplicate_space(s, to_space_id, on_success, on_error) {
|
||||||
var path = "/spaces/"+s._id+"/duplicate";
|
var path = "/spaces/"+s._id+"/duplicate";
|
||||||
if(to_space_id) {
|
if(to_space_id) {
|
||||||
@@ -266,8 +257,8 @@ function delete_user(u, password, on_success, on_error) {
|
|||||||
load_resource("delete", "/users/"+u._id +"?password="+password,null,on_success,on_error);
|
load_resource("delete", "/users/"+u._id +"?password="+password,null,on_success,on_error);
|
||||||
}
|
}
|
||||||
|
|
||||||
function create_user(name, email, password, password_confirmation, on_success, on_error) {
|
function create_user(name, email, password, password_confirmation, invite_code, on_success, on_error) {
|
||||||
load_resource("post", "/users", {email:email, nickname:name, password:password, password_confirmation: password_confirmation}, on_success, on_error);
|
load_resource("post", "/users", {email:email, nickname:name, password:password, password_confirmation: password_confirmation, invite_code: invite_code}, on_success, on_error);
|
||||||
}
|
}
|
||||||
|
|
||||||
function create_session(email, password, on_success, on_error) {
|
function create_session(email, password, on_success, on_error) {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ window.locales = {};
|
|||||||
window.locales.en = {};
|
window.locales.en = {};
|
||||||
window.locales.de = {};
|
window.locales.de = {};
|
||||||
window.locales.fr = {};
|
window.locales.fr = {};
|
||||||
|
window.locales.oc = {};
|
||||||
window.locales.en.translation =
|
window.locales.en.translation =
|
||||||
{
|
{
|
||||||
"ok": "OK",
|
"ok": "OK",
|
||||||
@@ -943,4 +944,328 @@ window.locales.fr.translation =
|
|||||||
"promote": "promouvoir",
|
"promote": "promouvoir",
|
||||||
"demote": "rétrograder"
|
"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": "S’inscriure",
|
||||||
|
"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 d’especificar",
|
||||||
|
"confirm": "Mercés de confirmar",
|
||||||
|
"error_unknown_email": "Aquesta combinason d’adreç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 d’opcions.",
|
||||||
|
"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 d’equipa per %s",
|
||||||
|
"team_invite_membership_body": "Qualqu’un vos a convidat a %s sus Spacedeck. Mercés de clicar sul ligam seguent per acceptar l’invitacion.",
|
||||||
|
"team_invite_user_body": "Qualqu’un vos a convidat a %s sus Spacedeck.\nVòstre senhal temporari es « %s ».\nMercés de clicar sul ligam seguent per acceptar l’invitacion.",
|
||||||
|
"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 l’equipa %s sus Spacedeck",
|
||||||
|
"space_invite_membership_subject": "Invitacion Espaci per %s : %s",
|
||||||
|
"space_invite_membership_body": "%s vos a convit a l’Espaci « %s »",
|
||||||
|
"space_invite_membership_action": "Acceptar l’invitacion",
|
||||||
|
"folder_invite_membership_subject": "Espaci",
|
||||||
|
"folder_invite_membership_body": "Qualqu’un vos a convidat a Team sus Spacedeck. Clicatz lo ligam seguent per acceptar l’invitacion.",
|
||||||
|
"folder_invite_membership_acction": "Acceptar",
|
||||||
|
"login_google": "S’identificar 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 d’espacis.",
|
||||||
|
"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 : 200x200 pixè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 qu’explica 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 l’accè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 l’Espaci",
|
||||||
|
"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 l’escòla e a l’universitat pels mai rics, experiéncia d’aprendissatge connectat.",
|
||||||
|
"spaces": "Mos espacis",
|
||||||
|
"access_editor_link": "Ligam de modificacion dirècta",
|
||||||
|
"access_editor_link_desc": "Donatz aqueste ligam a qualqu’un que deu poder modificar dirèctament aqueste Espaci, cap de compte pas requerit : ",
|
||||||
|
"access_editor_link_desc_slug": "Aqueste ligam conten lo nom de l’espaci, tanben. ",
|
||||||
|
"access_anonymous_edit_blocking": "Los convidats pòdon pas modificar los elements qu’an creats.",
|
||||||
|
"access_current_members": "Membres actuals",
|
||||||
|
"access_new_members": "Convidar de novèls membres",
|
||||||
|
"access_no_members": "Los membres d’aqueste Espacii apreissaràn aquí.",
|
||||||
|
"comments": "comentaris",
|
||||||
|
"landing_customers": "La fisança de milièr de personas.",
|
||||||
|
"landing_features_title": "Un jòc d'enfants d’utilizar.",
|
||||||
|
"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 qu’auretz 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. S’avè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 l’equipa",
|
||||||
|
"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 l’espaci",
|
||||||
|
"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
@@ -8,24 +8,22 @@ SpacedeckAccount = {
|
|||||||
account_confirmed_sent: false,
|
account_confirmed_sent: false,
|
||||||
account_tab: 'invoices',
|
account_tab: 'invoices',
|
||||||
password_change_error: null,
|
password_change_error: null,
|
||||||
feedback_text: ""
|
feedback_text: "",
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
show_account: function(user) {
|
show_account: function() {
|
||||||
this.activate_dropdown('account');
|
this.activate_dropdown('account');
|
||||||
this.load_subscription();
|
|
||||||
this.load_billing();
|
|
||||||
},
|
},
|
||||||
|
|
||||||
account_save_user_digest: function(val) {
|
account_save_user_digest: function(val) {
|
||||||
this.user.preferences.daily_digest = val;
|
this.user.prefs_email_digest = val;
|
||||||
this.save_user(function(){
|
this.save_user(function() {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
account_save_user_notifications: function(val) {
|
account_save_user_notifications: function(val) {
|
||||||
this.user.preferences.email_notifications = val;
|
this.user.prefs_email_notifications = val;
|
||||||
this.save_user(function(){
|
this.save_user(function() {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -36,13 +34,11 @@ SpacedeckAccount = {
|
|||||||
|
|
||||||
save_user_language: function(lang) {
|
save_user_language: function(lang) {
|
||||||
localStorage.lang = lang;
|
localStorage.lang = lang;
|
||||||
if (this.user.preferences) {
|
this.user.prefs_language = lang;
|
||||||
this.user.preferences.language = lang;
|
this.save_user(function() {
|
||||||
this.save_user(function() {
|
window._spacedeck_location_change = true;
|
||||||
window._spacedeck_location_change = true;
|
location.href="/spaces";
|
||||||
location.href="/spaces";
|
}.bind(this));
|
||||||
}.bind(this));
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
save_user: function(on_success) {
|
save_user: function(on_success) {
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ var SpacedeckBoardArtifacts = {
|
|||||||
if ("medium_for_object" in this) {
|
if ("medium_for_object" in this) {
|
||||||
var medium = this.medium_for_object[a._id];
|
var medium = this.medium_for_object[a._id];
|
||||||
if (medium && a._id != this.editing_artifact_id) {
|
if (medium && a._id != this.editing_artifact_id) {
|
||||||
medium.value(a.description);
|
medium.value(a.description.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -61,16 +61,16 @@ var SpacedeckBoardArtifacts = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
artifact_link: function(a) {
|
artifact_link: function(a) {
|
||||||
if (a.meta && a.meta.link_uri) {
|
if (a.link_uri) {
|
||||||
return a.meta.link_uri;
|
return a.link_uri;
|
||||||
} else {
|
} else {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
artifact_link_caption: function(a) {
|
artifact_link_caption: function(a) {
|
||||||
if (a.meta && a.meta.link_uri) {
|
if (a.link_uri) {
|
||||||
var parts = a.meta.link_uri.split("/");
|
var parts = a.link_uri.split("/");
|
||||||
// scheme://domain.foo/...
|
// scheme://domain.foo/...
|
||||||
// 0 1 2
|
// 0 1 2
|
||||||
if (parts.length>2) {
|
if (parts.length>2) {
|
||||||
@@ -88,10 +88,11 @@ var SpacedeckBoardArtifacts = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
artifact_is_text_blank: function(a) {
|
artifact_is_text_blank: function(a) {
|
||||||
if(a.description){
|
if (a.description) {
|
||||||
var filtered = a.description.replace(/<[^>]+>/g,"").replace(/\s/g,"");
|
desc = a.description.toString();
|
||||||
|
var filtered = desc.replace(/<[^>]+>/g,"").replace(/\s/g,"");
|
||||||
return (filtered.length<1);
|
return (filtered.length<1);
|
||||||
}else{
|
} else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -102,11 +103,9 @@ var SpacedeckBoardArtifacts = {
|
|||||||
if (this.artifact_is_selected(a) && this.editing_artifact_id!=a._id) clzs.push("selected");
|
if (this.artifact_is_selected(a) && this.editing_artifact_id!=a._id) clzs.push("selected");
|
||||||
if (!a._id) clzs.push("creating");
|
if (!a._id) clzs.push("creating");
|
||||||
|
|
||||||
if (a.style) {
|
if (a.align) clzs.push("align-"+a.align);
|
||||||
clzs.push("align-"+a.style.align);
|
if (a.valign) clzs.push("align-"+a.valign);
|
||||||
clzs.push("align-"+a.style.valign);
|
|
||||||
}
|
|
||||||
|
|
||||||
clzs.push("state-"+a.state);
|
clzs.push("state-"+a.state);
|
||||||
|
|
||||||
if (this.artifact_is_text_blank(a)) {
|
if (this.artifact_is_text_blank(a)) {
|
||||||
@@ -123,56 +122,56 @@ var SpacedeckBoardArtifacts = {
|
|||||||
artifact_inner_style: function(a) {
|
artifact_inner_style: function(a) {
|
||||||
var styles = [];
|
var styles = [];
|
||||||
|
|
||||||
if (a.style) {
|
//if (a.style) {
|
||||||
|
|
||||||
var svg_style = ((a.mime.match("vector") || a.mime.match("shape")) && a.style.shape!="square");
|
var svg_style = ((a.mime.match("vector") || a.mime.match("shape")) && a.shape!="square");
|
||||||
|
|
||||||
if (!svg_style) {
|
if (!svg_style) {
|
||||||
if (a.style.stroke) {
|
if (a.stroke) {
|
||||||
styles.push("border-width:"+a.style.stroke+"px");
|
styles.push("border-width:"+a.stroke+"px");
|
||||||
styles.push("border-style:"+(a.style.stroke_style||"solid"));
|
styles.push("border-style:"+(a.stroke_style||"solid"));
|
||||||
}
|
}
|
||||||
if (a.style.stroke_color) {
|
if (a.stroke_color) {
|
||||||
styles.push("border-color:"+a.style.stroke_color);
|
styles.push("border-color:"+a.stroke_color);
|
||||||
}
|
}
|
||||||
if (a.style.border_radius) {
|
if (a.border_radius) {
|
||||||
styles.push("border-radius:"+a.style.border_radius+"px");
|
styles.push("border-radius:"+a.border_radius+"px");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (a.style.fill_color && !svg_style) {
|
if (a.fill_color && !svg_style) {
|
||||||
styles.push("background-color:"+a.style.fill_color);
|
styles.push("background-color:"+a.fill_color);
|
||||||
}
|
}
|
||||||
if (a.style.text_color) {
|
if (a.text_color) {
|
||||||
styles.push("color:"+a.style.text_color);
|
styles.push("color:"+a.text_color);
|
||||||
}
|
}
|
||||||
|
|
||||||
var filters = [];
|
var filters = [];
|
||||||
|
|
||||||
if (!isNaN(a.style.brightness) && a.style.brightness != 100) {
|
if (!isNaN(a.brightness) && a.brightness != 100) {
|
||||||
filters.push("brightness("+a.style.brightness+"%)");
|
filters.push("brightness("+a.brightness+"%)");
|
||||||
}
|
}
|
||||||
if (!isNaN(a.style.contrast) && a.style.contrast != 100) {
|
if (!isNaN(a.contrast) && a.contrast != 100) {
|
||||||
filters.push("contrast("+a.style.contrast+"%)");
|
filters.push("contrast("+a.contrast+"%)");
|
||||||
}
|
}
|
||||||
if (!isNaN(a.style.opacity) && a.style.opacity != 100) {
|
if (!isNaN(a.opacity) && a.opacity != 100) {
|
||||||
filters.push("opacity("+a.style.opacity+"%)");
|
filters.push("opacity("+a.opacity+"%)");
|
||||||
}
|
}
|
||||||
if (!isNaN(a.style.hue) && a.style.hue) {
|
if (!isNaN(a.hue) && a.hue) {
|
||||||
filters.push("hue-rotate("+a.style.hue+"deg)");
|
filters.push("hue-rotate("+a.hue+"deg)");
|
||||||
}
|
}
|
||||||
if (!isNaN(a.style.saturation) && a.style.saturation != 100) {
|
if (!isNaN(a.saturation) && a.saturation != 100) {
|
||||||
filters.push("saturate("+a.style.saturation+"%)");
|
filters.push("saturate("+a.saturation+"%)");
|
||||||
}
|
}
|
||||||
if (!isNaN(a.style.blur) && a.style.blur) {
|
if (!isNaN(a.blur) && a.blur) {
|
||||||
filters.push("blur("+a.style.blur+"px)");
|
filters.push("blur("+a.blur+"px)");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (filters.length) {
|
if (filters.length) {
|
||||||
styles.push("-webkit-filter:"+filters.join(" "));
|
styles.push("-webkit-filter:"+filters.join(" "));
|
||||||
styles.push("filter:"+filters.join(" "));
|
styles.push("filter:"+filters.join(" "));
|
||||||
}
|
}
|
||||||
}
|
//}
|
||||||
|
|
||||||
return styles.join(";");
|
return styles.join(";");
|
||||||
},
|
},
|
||||||
@@ -180,12 +179,10 @@ var SpacedeckBoardArtifacts = {
|
|||||||
artifact_text_cell_style: function(a, for_text_editor) {
|
artifact_text_cell_style: function(a, for_text_editor) {
|
||||||
var styles = [];
|
var styles = [];
|
||||||
|
|
||||||
if (a.style) {
|
if (a.padding_left) styles.push("padding-left:"+a.padding_left+"px");
|
||||||
if (a.style.padding_left) styles.push("padding-left:"+a.style.padding_left+"px");
|
if (a.padding_right) styles.push("padding-right:"+a.padding_right+"px");
|
||||||
if (a.style.padding_right) styles.push("padding-right:"+a.style.padding_right+"px");
|
if (a.padding_top) styles.push("padding-top:"+a.padding_top+"px");
|
||||||
if (a.style.padding_top) styles.push("padding-top:"+a.style.padding_top+"px");
|
if (a.padding_bottom) styles.push("padding-bottom:"+a.padding_bottom+"px");
|
||||||
if (a.style.padding_bottom) styles.push("padding-bottom:"+a.style.padding_bottom+"px");
|
|
||||||
}
|
|
||||||
|
|
||||||
return styles.join(";");
|
return styles.join(";");
|
||||||
},
|
},
|
||||||
@@ -194,26 +191,22 @@ var SpacedeckBoardArtifacts = {
|
|||||||
var styles = [];
|
var styles = [];
|
||||||
var z = 0;
|
var z = 0;
|
||||||
|
|
||||||
if (a.board) {
|
z = a.z;
|
||||||
z = a.board.z;
|
if (z<0) z=0; // fix negative z-index
|
||||||
if (z<0) z=0; // fix negative z-index
|
|
||||||
|
styles = [
|
||||||
styles = [
|
"left:" +a.x+"px",
|
||||||
"left:" +a.board.x+"px",
|
"top:" +a.y+"px",
|
||||||
"top:" +a.board.y+"px",
|
"width:" +a.w+"px",
|
||||||
"width:" +a.board.w+"px",
|
"height:"+a.h+"px",
|
||||||
"height:"+a.board.h+"px",
|
"z-index:"+z
|
||||||
"z-index:"+z
|
];
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (a.style) {
|
|
||||||
if (a.style.margin_left) styles.push("margin-left:"+a.style.margin_left+"px");
|
|
||||||
if (a.style.margin_right) styles.push("margin-right:"+a.style.margin_right+"px");
|
|
||||||
if (a.style.margin_top) styles.push("margin-top:"+a.style.margin_top+"px");
|
|
||||||
if (a.style.margin_bottom) styles.push("margin-bottom:"+a.style.margin_bottom+"px");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if (a.margin_left) styles.push("margin-left:"+a.margin_left+"px");
|
||||||
|
if (a.margin_right) styles.push("margin-right:"+a.margin_right+"px");
|
||||||
|
if (a.margin_top) styles.push("margin-top:"+a.margin_top+"px");
|
||||||
|
if (a.margin_bottom) styles.push("margin-bottom:"+a.margin_bottom+"px");
|
||||||
|
|
||||||
// FIXME: via class logic?
|
// FIXME: via class logic?
|
||||||
if (a.mime.match("vector")) {
|
if (a.mime.match("vector")) {
|
||||||
styles.push("overflow:visible");
|
styles.push("overflow:visible");
|
||||||
@@ -240,8 +233,8 @@ var SpacedeckBoardArtifacts = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
artifact_thumbnail_uri: function(a) {
|
artifact_thumbnail_uri: function(a) {
|
||||||
if (a.payload_thumbnail_big_uri && a.board) {
|
if (a.payload_thumbnail_big_uri) {
|
||||||
if (a.board.w>800) {
|
if (a.w>800) {
|
||||||
return a.payload_thumbnail_big_uri;
|
return a.payload_thumbnail_big_uri;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -255,35 +248,35 @@ var SpacedeckBoardArtifacts = {
|
|||||||
var type = parts[0];
|
var type = parts[0];
|
||||||
var provider = parts[1];
|
var provider = parts[1];
|
||||||
|
|
||||||
if (!a.meta || !a.meta.link_uri) {
|
if (!a.link_uri) {
|
||||||
console.log("missing meta / link_uri: ",a);
|
console.log("missing meta / link_uri: ",a);
|
||||||
console.log("type/provider: ",type,provider);
|
console.log("type/provider: ",type,provider);
|
||||||
return ("missing metadata: "+a._id);
|
return ("missing metadata: "+a._id);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (provider=="youtube") {
|
if (provider=="youtube") {
|
||||||
var vid = a.meta.link_uri.match(/(v=|\/)([a-zA-Z0-9\-_]{11})/);
|
var vid = a.link_uri.match(/(v=|\/)([a-zA-Z0-9\-_]{11})/);
|
||||||
if (vid && vid.length>2) {
|
if (vid && vid.length>2) {
|
||||||
var uri = "https://youtube.com/embed/"+vid[2];
|
var uri = "https://youtube.com/embed/"+vid[2];
|
||||||
return "<iframe frameborder=0 allowfullscreen src=\""+uri+"?showinfo=0&rel=0&controls=0\"></iframe>";
|
return "<iframe frameborder=0 allowfullscreen src=\""+uri+"?showinfo=0&rel=0&controls=0\"></iframe>";
|
||||||
} else return "Can't resolve: "+a.payload_uri;
|
} else return "Can't resolve: "+a.payload_uri;
|
||||||
|
|
||||||
} else if (provider=="dailymotion") {
|
} else if (provider=="dailymotion") {
|
||||||
var match = a.meta.link_uri.match(/dailymotion.com\/video\/([^<]*)/);
|
var match = a.link_uri.match(/dailymotion.com\/video\/([^<]*)/);
|
||||||
if (match && match.length>1) {
|
if (match && match.length>1) {
|
||||||
var uri = "https://www.dailymotion.com/embed/video/"+match[1];
|
var uri = "https://www.dailymotion.com/embed/video/"+match[1];
|
||||||
return "<iframe frameborder=0 allowfullscreen src=\""+uri+"\"></iframe>";
|
return "<iframe frameborder=0 allowfullscreen src=\""+uri+"\"></iframe>";
|
||||||
} else return "Can't resolve: "+a.payload_uri;
|
} else return "Can't resolve: "+a.payload_uri;
|
||||||
|
|
||||||
} else if (provider=="vimeo") {
|
} else if (provider=="vimeo") {
|
||||||
var match = a.meta.link_uri.match(/https?:\/\/(www\.)?vimeo.com\/(\d+)($|\/)/);
|
var match = a.link_uri.match(/https?:\/\/(www\.)?vimeo.com\/(\d+)($|\/)/);
|
||||||
if (match) {
|
if (match) {
|
||||||
var uri = "https://player.vimeo.com/video/"+match[2];
|
var uri = "https://player.vimeo.com/video/"+match[2];
|
||||||
return "<iframe frameborder=0 allowfullscreen src=\""+uri+"\"></iframe>";
|
return "<iframe frameborder=0 allowfullscreen src=\""+uri+"\"></iframe>";
|
||||||
} else return "Can't resolve: "+a.payload_uri;
|
} else return "Can't resolve: "+a.payload_uri;
|
||||||
|
|
||||||
} else if (provider=="soundcloud") {
|
} else if (provider=="soundcloud") {
|
||||||
return '<iframe width="100%" height="166" scrolling="no" frameborder="no" src="https://w.soundcloud.com/player/?url='+a.meta.link_uri.replace(":", "%3A")+'"></iframe>';
|
return '<iframe width="100%" height="166" scrolling="no" frameborder="no" src="https://w.soundcloud.com/player/?url='+a.link_uri.replace(":", "%3A")+'"></iframe>';
|
||||||
|
|
||||||
} else if (provider=="spacedeck") {
|
} else if (provider=="spacedeck") {
|
||||||
|
|
||||||
@@ -299,8 +292,8 @@ var SpacedeckBoardArtifacts = {
|
|||||||
|
|
||||||
if (mtype != "vector" && mtype != "shape") return "";
|
if (mtype != "vector" && mtype != "shape") return "";
|
||||||
|
|
||||||
var shape = a.style.shape || "";
|
var shape = a.shape || "";
|
||||||
var padding = 32 + a.style.stroke*2;
|
var padding = 32 + a.stroke*2;
|
||||||
var path_svg;
|
var path_svg;
|
||||||
var fill = "";
|
var fill = "";
|
||||||
|
|
||||||
@@ -310,13 +303,13 @@ var SpacedeckBoardArtifacts = {
|
|||||||
fill = "fill:none";
|
fill = "fill:none";
|
||||||
} else {
|
} else {
|
||||||
path_svg = render_vector_shape(a, padding);
|
path_svg = render_vector_shape(a, padding);
|
||||||
fill = "fill:"+a.style.fill_color+";";
|
fill = "fill:"+a.fill_color+";";
|
||||||
padding = 0;
|
padding = 0;
|
||||||
}
|
}
|
||||||
var margin = padding;
|
var margin = padding;
|
||||||
|
|
||||||
var svg = "<svg xmlns='http://www.w3.org/2000/svg' width='"+(a.board.w+2*padding)+"' height='"+(a.board.h+2*padding)+"' ";
|
var svg = "<svg xmlns='http://www.w3.org/2000/svg' width='"+(a.w+2*padding)+"' height='"+(a.h+2*padding)+"' ";
|
||||||
svg += "style='margin-left:"+(-margin)+"px;margin-top:"+(-margin)+"px;stroke-width:"+a.style.stroke+";stroke:"+a.style.stroke_color+";"+fill+"'>";
|
svg += "style='margin-left:"+(-margin)+"px;margin-top:"+(-margin)+"px;stroke-width:"+a.stroke+";stroke:"+a.stroke_color+";"+fill+"'>";
|
||||||
svg += path_svg;
|
svg += path_svg;
|
||||||
svg += "</svg>";
|
svg += "</svg>";
|
||||||
|
|
||||||
@@ -329,10 +322,10 @@ var SpacedeckBoardArtifacts = {
|
|||||||
if (arts.length==0) return null;
|
if (arts.length==0) return null;
|
||||||
|
|
||||||
r = {
|
r = {
|
||||||
x1: parseInt(_.min(arts.map(function(a){return a.board.x}))),
|
x1: parseInt(_.min(arts.map(function(a){return a.x}))),
|
||||||
y1: parseInt(_.min(arts.map(function(a){return a.board.y}))),
|
y1: parseInt(_.min(arts.map(function(a){return a.y}))),
|
||||||
x2: parseInt(_.max(arts.map(function(a){return a.board.x+a.board.w}))),
|
x2: parseInt(_.max(arts.map(function(a){return a.x+a.w}))),
|
||||||
y2: parseInt(_.max(arts.map(function(a){return a.board.y+a.board.h})))
|
y2: parseInt(_.max(arts.map(function(a){return a.y+a.h})))
|
||||||
};
|
};
|
||||||
r.x=r.x1;
|
r.x=r.x1;
|
||||||
r.y=r.y1;
|
r.y=r.y1;
|
||||||
@@ -356,7 +349,7 @@ var SpacedeckBoardArtifacts = {
|
|||||||
|
|
||||||
artifacts_in_rect: function(rect) {
|
artifacts_in_rect: function(rect) {
|
||||||
return _.filter(this.active_space_artifacts, function(a) {
|
return _.filter(this.active_space_artifacts, function(a) {
|
||||||
return this.rects_intersecting(a.board, rect);
|
return this.rects_intersecting(a, rect);
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -366,15 +359,16 @@ var SpacedeckBoardArtifacts = {
|
|||||||
var rect = this.artifact_selection_rect();
|
var rect = this.artifact_selection_rect();
|
||||||
var overlapping = _.filter(this.artifacts_in_rect(rect), function(a){return !this.is_selected(a)}.bind(this));
|
var overlapping = _.filter(this.artifacts_in_rect(rect), function(a){return !this.is_selected(a)}.bind(this));
|
||||||
|
|
||||||
var max_z = _.max(overlapping,function(a){ return a.board.z; });
|
var max_z = _.max(overlapping,function(a){ return a.z; });
|
||||||
if (max_z.board) {
|
|
||||||
max_z = max_z.board.z + 1;
|
if (max_z.z) {
|
||||||
|
max_z = max_z.z + 1;
|
||||||
} else {
|
} else {
|
||||||
max_z = 1;
|
max_z = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.update_selected_artifacts(function(a) {
|
this.update_selected_artifacts(function(a) {
|
||||||
return { board: _.extend(a.board, { z: max_z }) };
|
return { z: max_z };
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -384,15 +378,15 @@ var SpacedeckBoardArtifacts = {
|
|||||||
var rect = this.artifact_selection_rect();
|
var rect = this.artifact_selection_rect();
|
||||||
var overlapping = _.filter(this.artifacts_in_rect(rect), function(a){return !this.is_selected(a);}.bind(this));
|
var overlapping = _.filter(this.artifacts_in_rect(rect), function(a){return !this.is_selected(a);}.bind(this));
|
||||||
|
|
||||||
var min_z = _.min(overlapping,function(a){ return (a.board?a.board.z:0); });
|
var min_z = _.min(overlapping,function(a){ return a.z; });
|
||||||
if (min_z.board) {
|
if (min_z.z) {
|
||||||
min_z = min_z.board.z - 1;
|
min_z = min_z.z - 1;
|
||||||
} else {
|
} else {
|
||||||
min_z = 0;
|
min_z = 0;
|
||||||
}
|
}
|
||||||
var my_z = _.max(this.selected_artifacts(),function(a){ (a.board?a.board.z:0); });
|
var my_z = _.max(this.selected_artifacts(),function(a){ return a.z; });
|
||||||
if (my_z.board) {
|
if (my_z.z) {
|
||||||
my_z = my_z.board.z - 1;
|
my_z = my_z.z - 1;
|
||||||
} else {
|
} else {
|
||||||
my_z = 0;
|
my_z = 0;
|
||||||
}
|
}
|
||||||
@@ -400,14 +394,14 @@ var SpacedeckBoardArtifacts = {
|
|||||||
// TODO: move all other items up in this case?
|
// TODO: move all other items up in this case?
|
||||||
if (min_z < 0) {
|
if (min_z < 0) {
|
||||||
this.update_artifacts(overlapping, function(a) {
|
this.update_artifacts(overlapping, function(a) {
|
||||||
return { board: _.extend(a.board, { z: (my_z + (a.board?a.board.z:0) + 1) }) };
|
return { z: (my_z + a.z + 1) };
|
||||||
});
|
});
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.update_selected_artifacts(function(a) {
|
this.update_selected_artifacts(function(a) {
|
||||||
return { board: _.extend(a.board, { z: min_z }) };
|
return { z: min_z };
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -416,7 +410,7 @@ var SpacedeckBoardArtifacts = {
|
|||||||
|
|
||||||
var rect = this.artifact_selection_rect();
|
var rect = this.artifact_selection_rect();
|
||||||
this.update_selected_artifacts(function(a) {
|
this.update_selected_artifacts(function(a) {
|
||||||
return { board: _.extend(a.board, { x: rect.x1 }) };
|
return { x: rect.x1 };
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -425,7 +419,7 @@ var SpacedeckBoardArtifacts = {
|
|||||||
|
|
||||||
var rect = this.artifact_selection_rect();
|
var rect = this.artifact_selection_rect();
|
||||||
this.update_selected_artifacts(function(a) {
|
this.update_selected_artifacts(function(a) {
|
||||||
return { board: _.extend(a.board, { y: rect.y1 }) };
|
return { y: rect.y1 };
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -434,7 +428,7 @@ var SpacedeckBoardArtifacts = {
|
|||||||
|
|
||||||
var rect = this.artifact_selection_rect();
|
var rect = this.artifact_selection_rect();
|
||||||
this.update_selected_artifacts(function(a) {
|
this.update_selected_artifacts(function(a) {
|
||||||
return { board: _.extend(a.board, { x: rect.x2 - a.board.w }) };
|
return { x: rect.x2 - a.w };
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -443,7 +437,7 @@ var SpacedeckBoardArtifacts = {
|
|||||||
|
|
||||||
var rect = this.artifact_selection_rect();
|
var rect = this.artifact_selection_rect();
|
||||||
this.update_selected_artifacts(function(a) {
|
this.update_selected_artifacts(function(a) {
|
||||||
return { board: _.extend(a.board, { y: rect.y2 - a.board.h }) };
|
return { y: rect.y2 - a.h };
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -453,7 +447,7 @@ var SpacedeckBoardArtifacts = {
|
|||||||
var rect = this.artifact_selection_rect();
|
var rect = this.artifact_selection_rect();
|
||||||
var cx = rect.x1 + (rect.x2-rect.x1)/2;
|
var cx = rect.x1 + (rect.x2-rect.x1)/2;
|
||||||
this.update_selected_artifacts(function(a) {
|
this.update_selected_artifacts(function(a) {
|
||||||
return { board: _.extend(a.board, { x: cx - a.board.w/2 }) };
|
return { x: cx - a.w/2 };
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -463,7 +457,7 @@ var SpacedeckBoardArtifacts = {
|
|||||||
var rect = this.artifact_selection_rect();
|
var rect = this.artifact_selection_rect();
|
||||||
var cy = rect.y1 + (rect.y2-rect.y1)/2;
|
var cy = rect.y1 + (rect.y2-rect.y1)/2;
|
||||||
this.update_selected_artifacts(function(a) {
|
this.update_selected_artifacts(function(a) {
|
||||||
return { board: _.extend(a.board, { y: cy - a.board.h/2 }) };
|
return { y: cy - a.h/2 };
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -473,11 +467,11 @@ var SpacedeckBoardArtifacts = {
|
|||||||
var arts = this.selected_artifacts();
|
var arts = this.selected_artifacts();
|
||||||
if (arts.length<2) return;
|
if (arts.length<2) return;
|
||||||
|
|
||||||
var totalw = _.reduce(arts, function(sum, a) { return sum + a.board.w }, 0);
|
var totalw = _.reduce(arts, function(sum, a) { return sum + a.w }, 0);
|
||||||
var avgw = totalw / arts.length;
|
var avgw = totalw / arts.length;
|
||||||
|
|
||||||
this.update_selected_artifacts(function(a) {
|
this.update_selected_artifacts(function(a) {
|
||||||
return { board: _.extend(a.board, { w: avgw }) };
|
return { w: avgw };
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -487,11 +481,11 @@ var SpacedeckBoardArtifacts = {
|
|||||||
var arts = this.selected_artifacts();
|
var arts = this.selected_artifacts();
|
||||||
if (arts.length<2) return;
|
if (arts.length<2) return;
|
||||||
|
|
||||||
var totalh = _.reduce(arts, function(sum, a) { return sum + a.board.h }, 0);
|
var totalh = _.reduce(arts, function(sum, a) { return sum + a.h }, 0);
|
||||||
var avgh = totalh / arts.length;
|
var avgh = totalh / arts.length;
|
||||||
|
|
||||||
this.update_selected_artifacts(function(a) {
|
this.update_selected_artifacts(function(a) {
|
||||||
return { board: _.extend(a.board, { h: avgh }) };
|
return { h: avgh };
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -506,16 +500,16 @@ var SpacedeckBoardArtifacts = {
|
|||||||
var selected = this.selected_artifacts();
|
var selected = this.selected_artifacts();
|
||||||
if (selected.length<3) return;
|
if (selected.length<3) return;
|
||||||
|
|
||||||
var sorted = _.sortBy(selected, function(a) { return a.board.x });
|
var sorted = _.sortBy(selected, function(a) { return a.x });
|
||||||
var startx = sorted[0].board.x + sorted[0].board.w/2;
|
var startx = sorted[0].x + sorted[0].w/2;
|
||||||
var stopx = _.last(sorted).board.x + _.last(sorted).board.w/2;
|
var stopx = _.last(sorted).x + _.last(sorted).w/2;
|
||||||
var step = (stopx-startx)/(sorted.length-1);
|
var step = (stopx-startx)/(sorted.length-1);
|
||||||
|
|
||||||
for (var i=1; i<sorted.length-1; i++) {
|
for (var i=1; i<sorted.length-1; i++) {
|
||||||
var a = sorted[i];
|
var a = sorted[i];
|
||||||
var x = startx + step*i - a.board.w/2;
|
var x = startx + step*i - a.w/2;
|
||||||
this.update_artifacts([a],function(a) {
|
this.update_artifacts([a],function(a) {
|
||||||
return { board: _.extend(a.board, {x: x}) }
|
return { x: x }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -526,16 +520,16 @@ var SpacedeckBoardArtifacts = {
|
|||||||
var selected = this.selected_artifacts();
|
var selected = this.selected_artifacts();
|
||||||
if (selected.length<3) return;
|
if (selected.length<3) return;
|
||||||
|
|
||||||
var sorted = _.sortBy(selected, function(a) { return a.board.y });
|
var sorted = _.sortBy(selected, function(a) { return a.y });
|
||||||
var starty = sorted[0].board.y + sorted[0].board.h/2;
|
var starty = sorted[0].y + sorted[0].h/2;
|
||||||
var stopy = _.last(sorted).board.y + _.last(sorted).board.h/2;
|
var stopy = _.last(sorted).y + _.last(sorted).h/2;
|
||||||
var step = (stopy-starty)/(sorted.length-1);
|
var step = (stopy-starty)/(sorted.length-1);
|
||||||
|
|
||||||
for (var i=1; i<sorted.length-1; i++) {
|
for (var i=1; i<sorted.length-1; i++) {
|
||||||
var a = sorted[i];
|
var a = sorted[i];
|
||||||
var y = starty + step*i - a.board.h/2;
|
var y = starty + step*i - a.h/2;
|
||||||
this.update_artifacts([a],function(a) {
|
this.update_artifacts([a],function(a) {
|
||||||
return { board: _.extend(a.board, {y: y}) }
|
return { y: y }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -546,21 +540,21 @@ var SpacedeckBoardArtifacts = {
|
|||||||
var selected = this.selected_artifacts();
|
var selected = this.selected_artifacts();
|
||||||
if (selected.length<3) return;
|
if (selected.length<3) return;
|
||||||
|
|
||||||
var sorted = _.sortBy(selected, function(a) { return a.board.x });
|
var sorted = _.sortBy(selected, function(a) { return a.x });
|
||||||
var startx = sorted[0].board.x;
|
var startx = sorted[0].x;
|
||||||
var stopx = _.last(sorted).board.x + _.last(sorted).board.w;
|
var stopx = _.last(sorted).x + _.last(sorted).w;
|
||||||
var range = stopx - startx;
|
var range = stopx - startx;
|
||||||
var totalw = _.reduce(sorted, function(sum, a) { return sum + a.board.w }, 0);
|
var totalw = _.reduce(sorted, function(sum, a) { return sum + a.w }, 0);
|
||||||
var avgs = (range - totalw) / (sorted.length-1);
|
var avgs = (range - totalw) / (sorted.length-1);
|
||||||
var prevend = startx + sorted[0].board.w;
|
var prevend = startx + sorted[0].w;
|
||||||
|
|
||||||
for (var i=1; i<sorted.length-1; i++) {
|
for (var i=1; i<sorted.length-1; i++) {
|
||||||
var a = sorted[i];
|
var a = sorted[i];
|
||||||
var x = prevend + avgs;
|
var x = prevend + avgs;
|
||||||
this.update_artifacts([a],function(a) {
|
this.update_artifacts([a],function(a) {
|
||||||
return { board: _.extend(a.board, {x: x}) }
|
return { x: x }
|
||||||
});
|
});
|
||||||
prevend = x+a.board.w;
|
prevend = x+a.w;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -570,21 +564,21 @@ var SpacedeckBoardArtifacts = {
|
|||||||
var selected = this.selected_artifacts();
|
var selected = this.selected_artifacts();
|
||||||
if (selected.length<3) return;
|
if (selected.length<3) return;
|
||||||
|
|
||||||
var sorted = _.sortBy(selected, function(a) { return a.board.y });
|
var sorted = _.sortBy(selected, function(a) { return a.y });
|
||||||
var starty = sorted[0].board.y;
|
var starty = sorted[0].y;
|
||||||
var stopy = _.last(sorted).board.y + _.last(sorted).board.h;
|
var stopy = _.last(sorted).y + _.last(sorted).h;
|
||||||
var range = stopy - starty;
|
var range = stopy - starty;
|
||||||
var totalh = _.reduce(sorted, function(sum, a) { return sum + a.board.h }, 0);
|
var totalh = _.reduce(sorted, function(sum, a) { return sum + a.h }, 0);
|
||||||
var avgs = (range - totalh) / (sorted.length-1);
|
var avgs = (range - totalh) / (sorted.length-1);
|
||||||
var prevend = starty + sorted[0].board.h;
|
var prevend = starty + sorted[0].h;
|
||||||
|
|
||||||
for (var i=1; i<sorted.length-1; i++) {
|
for (var i=1; i<sorted.length-1; i++) {
|
||||||
var a = sorted[i];
|
var a = sorted[i];
|
||||||
var y = prevend + avgs;
|
var y = prevend + avgs;
|
||||||
this.update_artifacts([a],function(a) {
|
this.update_artifacts([a],function(a) {
|
||||||
return { board: _.extend(a.board, {y: y}) }
|
return { y: y }
|
||||||
});
|
});
|
||||||
prevend = y+a.board.h;
|
prevend = y+a.h;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -594,20 +588,22 @@ var SpacedeckBoardArtifacts = {
|
|||||||
var selected = this.selected_artifacts();
|
var selected = this.selected_artifacts();
|
||||||
if (selected.length<2) return;
|
if (selected.length<2) return;
|
||||||
|
|
||||||
var sorted = _.sortBy(selected, function(a) { return a.board.x+a.board.y*this.active_space.advanced.width }.bind(this));
|
var sorted = _.sortBy(selected, function(a) { return a.x+a.y*this.active_space.width }.bind(this));
|
||||||
|
|
||||||
var minx = sorted[0].board.x;
|
var minx = sorted[0].x;
|
||||||
var miny = sorted[0].board.y;
|
var miny = sorted[0].y;
|
||||||
|
|
||||||
var sorted = _.sortBy(selected, function(a) { return -Math.max(a.board.w,a.board.h) }.bind(this));
|
var sorted = _.sortBy(selected, function(a) { return -Math.max(a.w,a.h) }.bind(this));
|
||||||
|
|
||||||
var blocks = [];
|
var blocks = [];
|
||||||
|
|
||||||
|
var padding = 10;
|
||||||
|
|
||||||
for (var i=0; i<sorted.length; i++) {
|
for (var i=0; i<sorted.length; i++) {
|
||||||
var a = sorted[i];
|
var a = sorted[i];
|
||||||
blocks.push({
|
blocks.push({
|
||||||
w: a.board.w,
|
w: a.w+padding,
|
||||||
h: a.board.h,
|
h: a.h+padding,
|
||||||
a: a
|
a: a
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -620,10 +616,10 @@ var SpacedeckBoardArtifacts = {
|
|||||||
if (block.fit) {
|
if (block.fit) {
|
||||||
var a = block.a;
|
var a = block.a;
|
||||||
this.update_artifacts([a],function(a) {
|
this.update_artifacts([a],function(a) {
|
||||||
return { board: _.extend(a.board, {
|
return {
|
||||||
x: minx+block.fit.x,
|
x: minx+block.fit.x,
|
||||||
y: miny+block.fit.y
|
y: miny+block.fit.y
|
||||||
}) }
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,21 @@ var SpacedeckRoutes = {
|
|||||||
}.bind(this)
|
}.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([
|
this.router.add([
|
||||||
{
|
{
|
||||||
@@ -170,7 +185,6 @@ var SpacedeckRoutes = {
|
|||||||
location.href = "/";
|
location.href = "/";
|
||||||
} else {
|
} else {
|
||||||
this.active_view = "account";
|
this.active_view = "account";
|
||||||
this.load_subscription();
|
|
||||||
}
|
}
|
||||||
}.bind(this)
|
}.bind(this)
|
||||||
}
|
}
|
||||||
@@ -253,8 +267,6 @@ var SpacedeckRoutes = {
|
|||||||
// #hash
|
// #hash
|
||||||
if (event.currentTarget.hash && event.currentTarget.hash.length>1) return;
|
if (event.currentTarget.hash && event.currentTarget.hash.length>1) return;
|
||||||
|
|
||||||
console.log("clicked", event.currentTarget.pathname);
|
|
||||||
|
|
||||||
// external link?
|
// external link?
|
||||||
if (event.currentTarget.host != location.host) return;
|
if (event.currentTarget.host != location.host) return;
|
||||||
|
|
||||||
@@ -270,35 +282,6 @@ var SpacedeckRoutes = {
|
|||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
|
|
||||||
if (location.host!=ENV.webHost) {
|
|
||||||
if (!subdomainTeam) {
|
|
||||||
location.href = ENV.webEndpoint;
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
if(subdomainTeam.subdomain) {
|
|
||||||
var realHost = (subdomainTeam.subdomain + "." + ENV.webHost);
|
|
||||||
if (location.host != realHost) {
|
|
||||||
location.href = realHost;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
location.href = ENV.webEndpoint;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.logged_in) {
|
|
||||||
if (this.user.team) {
|
|
||||||
if (this.user.team.subdomain && this.user.team.subdomain.length > 0) {
|
|
||||||
var realHost = (this.user.team.subdomain + "." + ENV.webHost);
|
|
||||||
if (location.host != realHost) {
|
|
||||||
location.href = location.protocol + "//" + realHost + location.pathname;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.internal_route(location.pathname);
|
this.internal_route(location.pathname);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -18,8 +18,6 @@ var SpacedeckSpaces = {
|
|||||||
active_space_path: [],
|
active_space_path: [],
|
||||||
access_settings_space: null,
|
access_settings_space: null,
|
||||||
access_settings_memberships: [],
|
access_settings_memberships: [],
|
||||||
duplicate_folders: [],
|
|
||||||
duplicate_folder_id: "",
|
|
||||||
pending_pdf_files: [],
|
pending_pdf_files: [],
|
||||||
|
|
||||||
meta_visible: false,
|
meta_visible: false,
|
||||||
@@ -71,9 +69,7 @@ var SpacedeckSpaces = {
|
|||||||
methods: {
|
methods: {
|
||||||
search_spaces: function() {
|
search_spaces: function() {
|
||||||
var query = this.folder_spaces_search;
|
var query = this.folder_spaces_search;
|
||||||
console.log("search query: ",query);
|
|
||||||
load_spaces_search(query, function(spaces) {
|
load_spaces_search(query, function(spaces) {
|
||||||
console.log("results: ",spaces);
|
|
||||||
this.active_profile_spaces = spaces;
|
this.active_profile_spaces = spaces;
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
},
|
},
|
||||||
@@ -85,14 +81,7 @@ var SpacedeckSpaces = {
|
|||||||
location.reload();
|
location.reload();
|
||||||
},
|
},
|
||||||
ask_guestname: function(dft, cb) {
|
ask_guestname: function(dft, cb) {
|
||||||
console.log("ask_guestname");
|
smoke.prompt(__('what_is_your_name', "Spacedeck") , function(content) {
|
||||||
|
|
||||||
var team_name = "Spacedeck";
|
|
||||||
|
|
||||||
if(subdomainTeam) {
|
|
||||||
team_name = subdomainTeam.name;
|
|
||||||
}
|
|
||||||
smoke.prompt(__('what_is_your_name', team_name) , function(content) {
|
|
||||||
if (!content || (content.length === 0)) {
|
if (!content || (content.length === 0)) {
|
||||||
this.ask_guestname(dft, cb);
|
this.ask_guestname(dft, cb);
|
||||||
} else {
|
} else {
|
||||||
@@ -101,7 +90,7 @@ var SpacedeckSpaces = {
|
|||||||
if ("localStorage" in window && localStorage) {
|
if ("localStorage" in window && localStorage) {
|
||||||
try {
|
try {
|
||||||
localStorage['guest_nickname'] = this.guest_nickname;
|
localStorage['guest_nickname'] = this.guest_nickname;
|
||||||
}catch(e) {
|
} catch(e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -110,36 +99,19 @@ var SpacedeckSpaces = {
|
|||||||
}.bind(this), {value: dft || "Guest "+parseInt(10000*Math.random()), ok: __("ok"), cancel: __("cancel")});
|
}.bind(this), {value: dft || "Guest "+parseInt(10000*Math.random()), ok: __("ok"), cancel: __("cancel")});
|
||||||
},
|
},
|
||||||
|
|
||||||
load_space: function(space_id, on_success, on_error) {
|
load_space: function(space_id, on_success, on_error, space_auth) {
|
||||||
|
|
||||||
console.log("load space: ", space_id);
|
|
||||||
this.folder_spaces_filter="";
|
this.folder_spaces_filter="";
|
||||||
this.folder_spaces_search="";
|
this.folder_spaces_search="";
|
||||||
|
|
||||||
space_auth = get_query_param("spaceAuth");
|
if (space_auth) {
|
||||||
|
set_space_auth(space_auth);
|
||||||
|
} else {
|
||||||
|
set_space_auth(get_query_param("spaceAuth"));
|
||||||
|
}
|
||||||
|
|
||||||
|
this.embedded = !!(get_query_param("embedded"));
|
||||||
|
|
||||||
var userReady = function() {
|
var userReady = function() {
|
||||||
if (get_query_param("embedded")) {
|
|
||||||
this.embedded = true;
|
|
||||||
this.guest_signup_enabled = true;
|
|
||||||
if (get_query_param("publish_cta")) {
|
|
||||||
this.publish_cta = get_query_param("publish_cta");
|
|
||||||
}
|
|
||||||
if (get_query_param("nosocial")) {
|
|
||||||
this.social_bar = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (get_query_param("confirm") && this.logged_in) {
|
|
||||||
var token = get_query_param("confirm");
|
|
||||||
confirm_user(this.user, token, function() {
|
|
||||||
this.redirect_to("/spaces/"+space_id+"?show_access=1");
|
|
||||||
}.bind(this), function() {
|
|
||||||
alert("An error occured confirming your email with the given token.");
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.close_dropdown();
|
this.close_dropdown();
|
||||||
|
|
||||||
this.active_space_loaded = false;
|
this.active_space_loaded = false;
|
||||||
@@ -167,12 +139,9 @@ var SpacedeckSpaces = {
|
|||||||
load_space(space_id, function(space, role) {
|
load_space(space_id, function(space, role) {
|
||||||
document.title = space.name;
|
document.title = space.name;
|
||||||
|
|
||||||
this.active_space_role = role || "viewer"; //via req header from backend
|
this.active_space_role = role || "viewer"; // via req header from backend
|
||||||
|
|
||||||
this.space_embed_html = "<iframe width=\"1024\" height=\"768\" seamless src=\""+ENV.webEndpoint+"/spaces/"+space._id+"?embedded=1\"></iframe>";
|
|
||||||
|
|
||||||
if (!is_home) {
|
if (!is_home) {
|
||||||
//console.log(space);
|
|
||||||
load_members(space, function(members) {
|
load_members(space, function(members) {
|
||||||
this.active_space_memberships = members;
|
this.active_space_memberships = members;
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
@@ -311,15 +280,6 @@ var SpacedeckSpaces = {
|
|||||||
// FIXME
|
// FIXME
|
||||||
this.active_join_link = "";
|
this.active_join_link = "";
|
||||||
this.join_link_role = "viewer";
|
this.join_link_role = "viewer";
|
||||||
|
|
||||||
// FIXME
|
|
||||||
if (this.active_space_role == "admin") {
|
|
||||||
this.space_info_section="access";
|
|
||||||
} else if (this.active_space_role == "editor") {
|
|
||||||
//this.space_info_section="versions";
|
|
||||||
} else {
|
|
||||||
this.space_info_section="info";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}.bind(this), function(xhr) {
|
}.bind(this), function(xhr) {
|
||||||
|
|
||||||
@@ -348,7 +308,7 @@ var SpacedeckSpaces = {
|
|||||||
userReady();
|
userReady();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (space_auth) {
|
if (!this.user && space_auth) {
|
||||||
if (this.guest_nickname) {
|
if (this.guest_nickname) {
|
||||||
userReady();
|
userReady();
|
||||||
} else {
|
} else {
|
||||||
@@ -636,17 +596,14 @@ var SpacedeckSpaces = {
|
|||||||
|
|
||||||
download_space: function() {
|
download_space: function() {
|
||||||
smoke.quiz(__("download_space"), function(e, test) {
|
smoke.quiz(__("download_space"), function(e, test) {
|
||||||
if (e == "PDF"){
|
if (e == "PDF") {
|
||||||
this.download_space_as_pdf(this.active_space);
|
this.download_space_as_pdf(this.active_space);
|
||||||
}else if (e == "ZIP"){
|
} else if (e == "ZIP") {
|
||||||
this.download_space_as_zip(this.active_space);
|
this.download_space_as_zip(this.active_space);
|
||||||
}else if (e == "TXT"){
|
|
||||||
this.download_space_as_list(this.active_space);
|
|
||||||
}
|
}
|
||||||
}.bind(this), {
|
}.bind(this), {
|
||||||
button_1: "PDF",
|
button_1: "PDF",
|
||||||
button_2: "ZIP",
|
button_2: "ZIP",
|
||||||
button_3: "TXT",
|
|
||||||
button_cancel:__("cancel")
|
button_cancel:__("cancel")
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -657,10 +614,11 @@ var SpacedeckSpaces = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
download_space_as_pdf: function(space) {
|
download_space_as_pdf: function(space) {
|
||||||
|
this.close_dropdown();
|
||||||
this.global_spinner = true;
|
this.global_spinner = true;
|
||||||
get_resource("/spaces/" + space._id + "/pdf", function(o) {
|
get_resource("/spaces/" + space._id + "/pdf", function(o) {
|
||||||
this.global_spinner = false;
|
this.global_spinner = false;
|
||||||
location.href = o.url;
|
window.open(o.url, "_blank");
|
||||||
}.bind(this), function(xhr) {
|
}.bind(this), function(xhr) {
|
||||||
this.global_spinner = false;
|
this.global_spinner = false;
|
||||||
alert("PDF export problem (" + xhr.status + ").");
|
alert("PDF export problem (" + xhr.status + ").");
|
||||||
@@ -686,47 +644,6 @@ var SpacedeckSpaces = {
|
|||||||
location.href = "/api/spaces/" + space._id + "/list";
|
location.href = "/api/spaces/" + space._id + "/list";
|
||||||
},
|
},
|
||||||
|
|
||||||
duplicate_space_into_folder: function() {
|
|
||||||
load_writable_folders( function(folders){
|
|
||||||
this.duplicate_folders = _.sortBy(folders, function (folder) { return folder.name; });
|
|
||||||
}.bind(this), function(xhr) {
|
|
||||||
console.error(xhr);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
duplicate_folder_confirm: function() {
|
|
||||||
var folderId = this.duplicate_folder_id;
|
|
||||||
var idx = _.findIndex(this.duplicate_folders, function(s) { return s._id == folderId;});
|
|
||||||
if (idx<0) idx = 0;
|
|
||||||
var folder = this.duplicate_folders[idx];
|
|
||||||
console.log("df f",folder);
|
|
||||||
if (!folder) return;
|
|
||||||
|
|
||||||
duplicate_space(this.active_space, folder._id, function(new_space) {
|
|
||||||
|
|
||||||
this.duplicate_folders = [];
|
|
||||||
this.duplicate_folder = null;
|
|
||||||
|
|
||||||
smoke.quiz(__("duplicate_success", this.active_space.name, folder.name), function(e, test){
|
|
||||||
if (e == __("goto_space", new_space.name)){
|
|
||||||
this.redirect_to("/spaces/" + new_space._id);
|
|
||||||
}else if (e == __("goto_folder", folder.name)){
|
|
||||||
this.redirect_to("/folders/" + folder._id);
|
|
||||||
}
|
|
||||||
}.bind(this), {
|
|
||||||
button_1: __("goto_space", new_space.name),
|
|
||||||
button_2: __("goto_folder", folder.name),
|
|
||||||
button_cancel:__("stay_here")
|
|
||||||
});
|
|
||||||
|
|
||||||
}.bind(this), function(xhr){
|
|
||||||
|
|
||||||
console.error(xhr);
|
|
||||||
smoke.prompt("error: " + xhr.statusText);
|
|
||||||
|
|
||||||
}.bind(this));
|
|
||||||
},
|
|
||||||
|
|
||||||
toggle_follow_mode: function() {
|
toggle_follow_mode: function() {
|
||||||
this.deselect();
|
this.deselect();
|
||||||
this.follow_mode = !this.follow_mode;
|
this.follow_mode = !this.follow_mode;
|
||||||
@@ -737,6 +654,13 @@ var SpacedeckSpaces = {
|
|||||||
this.present_mode = !this.present_mode;
|
this.present_mode = !this.present_mode;
|
||||||
if (this.present_mode) {
|
if (this.present_mode) {
|
||||||
//this.go_to_first_zone();
|
//this.go_to_first_zone();
|
||||||
|
if (this.embedded) {
|
||||||
|
document.documentElement.requestFullscreen();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (this.embedded) {
|
||||||
|
document.exitFullscreen();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -832,9 +756,12 @@ var SpacedeckSpaces = {
|
|||||||
this.invite_message = "";
|
this.invite_message = "";
|
||||||
}
|
}
|
||||||
}.bind(this), function(xhr){
|
}.bind(this), function(xhr){
|
||||||
|
try {
|
||||||
text = JSON.stringify(xhr.responseText);
|
var res = JSON.parse(xhr.response);
|
||||||
smoke.alert("Error: "+text);
|
alert("Error: "+res.error);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e, xhr);
|
||||||
|
}
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
},
|
},
|
||||||
@@ -842,9 +769,13 @@ var SpacedeckSpaces = {
|
|||||||
update_member: function(space, m, role) {
|
update_member: function(space, m, role) {
|
||||||
m.role = role;
|
m.role = role;
|
||||||
save_membership(space, m, function() {
|
save_membership(space, m, function() {
|
||||||
console.log("saved")
|
|
||||||
}.bind(this), function(xhr) {
|
}.bind(this), function(xhr) {
|
||||||
console.error(xhr);
|
try {
|
||||||
|
var res = JSON.parse(xhr.response);
|
||||||
|
alert("Error: "+res.error);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e, xhr);
|
||||||
|
}
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -853,7 +784,12 @@ var SpacedeckSpaces = {
|
|||||||
delete_membership(space, m, function() {
|
delete_membership(space, m, function() {
|
||||||
this.access_settings_memberships.splice(this.access_settings_memberships.indexOf(m), 1);
|
this.access_settings_memberships.splice(this.access_settings_memberships.indexOf(m), 1);
|
||||||
}.bind(this), function(xhr) {
|
}.bind(this), function(xhr) {
|
||||||
console.error(xhr);
|
try {
|
||||||
|
var res = JSON.parse(xhr.response);
|
||||||
|
alert("Error: "+res.error);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e, xhr);
|
||||||
|
}
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -889,10 +825,6 @@ var SpacedeckSpaces = {
|
|||||||
}.bind(this));
|
}.bind(this));
|
||||||
},
|
},
|
||||||
|
|
||||||
emojified_comment: function(comment) {
|
|
||||||
return twemoji.parse(comment);
|
|
||||||
},
|
|
||||||
|
|
||||||
set_folder_sorting: function(key,reverse) {
|
set_folder_sorting: function(key,reverse) {
|
||||||
this.folder_sorting = key;
|
this.folder_sorting = key;
|
||||||
this.folder_reverse = reverse?-1:1;
|
this.folder_reverse = reverse?-1:1;
|
||||||
|
|||||||
@@ -11,11 +11,12 @@ SpacedeckUsers = {
|
|||||||
login_email: "",
|
login_email: "",
|
||||||
login_password: "",
|
login_password: "",
|
||||||
signup_password: "",
|
signup_password: "",
|
||||||
|
signup_invite_code: "",
|
||||||
signup_password_confirmation: "",
|
signup_password_confirmation: "",
|
||||||
account_remove_error: null,
|
account_remove_error: null,
|
||||||
loading_user: false,
|
loading_user: false,
|
||||||
password_reset_confirm_error: "",
|
password_reset_confirm_error: "",
|
||||||
password_reset_error: ""
|
password_reset_error: "",
|
||||||
},
|
},
|
||||||
methods:{
|
methods:{
|
||||||
load_user: function(on_success, on_error) {
|
load_user: function(on_success, on_error) {
|
||||||
@@ -40,23 +41,7 @@ SpacedeckUsers = {
|
|||||||
}.bind(this));
|
}.bind(this));
|
||||||
},
|
},
|
||||||
|
|
||||||
login_google: function(evt) {
|
|
||||||
this.loading_user = true;
|
|
||||||
|
|
||||||
create_oauthtoken(function(data){
|
|
||||||
this.loading_user = false;
|
|
||||||
location.href = data.url;
|
|
||||||
}, function(xhr){
|
|
||||||
this.loading_user = false;
|
|
||||||
alert("could not get oauth token");
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
finalize_login: function(session_token, on_success) {
|
finalize_login: function(session_token, on_success) {
|
||||||
if(!window.socket_auth || window.socket_auth == '' || window.socket_auth == 'null') {
|
|
||||||
window.socket_auth = session_token;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.load_user(function(user) {
|
this.load_user(function(user) {
|
||||||
if (this.invitation_token) {
|
if (this.invitation_token) {
|
||||||
accept_invitation(this.invitation_token, function(memberships){
|
accept_invitation(this.invitation_token, function(memberships){
|
||||||
@@ -131,7 +116,7 @@ SpacedeckUsers = {
|
|||||||
signup_guest: function(on_success) {
|
signup_guest: function(on_success) {
|
||||||
},
|
},
|
||||||
|
|
||||||
signup_submit: function($event, name, email, password, password_confirmation, on_success) {
|
signup_submit: function($event, name, email, password, password_confirmation, invite_code, on_success) {
|
||||||
this.creating_user = true;
|
this.creating_user = true;
|
||||||
this.signup_error = null;
|
this.signup_error = null;
|
||||||
|
|
||||||
@@ -145,7 +130,7 @@ SpacedeckUsers = {
|
|||||||
$event.stopPropagation();
|
$event.stopPropagation();
|
||||||
}
|
}
|
||||||
|
|
||||||
create_user(name, email, password, password_confirmation, function(session) {
|
create_user(name, email, password, password_confirmation, invite_code, function(session) {
|
||||||
this.creating_user = false;
|
this.creating_user = false;
|
||||||
this.login_submit(email, password, null, on_success);
|
this.login_submit(email, password, null, on_success);
|
||||||
}.bind(this), function(req) {
|
}.bind(this), function(req) {
|
||||||
@@ -161,15 +146,14 @@ SpacedeckUsers = {
|
|||||||
}.bind(this));
|
}.bind(this));
|
||||||
},
|
},
|
||||||
|
|
||||||
signup_submit_modal: function($event, name, email, password, password_confirmation) {
|
signup_submit_modal: function($event, name, email, password, password_confirmation, invite_code) {
|
||||||
this.signup_submit($event, name, email, password, password_confirmation, function() {
|
this.signup_submit($event, name, email, password, password_confirmation, invite_code, function() {
|
||||||
alert("Success.");
|
alert("Success.");
|
||||||
location.reload();
|
location.reload();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
password_reset_submit: function(evt, email) {
|
password_reset_submit: function(evt, email) {
|
||||||
|
|
||||||
if (evt) {
|
if (evt) {
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
evt.stopPropagation();
|
evt.stopPropagation();
|
||||||
@@ -203,7 +187,6 @@ SpacedeckUsers = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
password_reset_confirm: function(evt, password, password_confirmation) {
|
password_reset_confirm: function(evt, password, password_confirmation) {
|
||||||
|
|
||||||
if (evt) {
|
if (evt) {
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
evt.stopPropagation();
|
evt.stopPropagation();
|
||||||
@@ -212,27 +195,29 @@ SpacedeckUsers = {
|
|||||||
this.password_reset_confirm_error = null;
|
this.password_reset_confirm_error = null;
|
||||||
this.password_reset_send = false;
|
this.password_reset_send = false;
|
||||||
|
|
||||||
if(password != password_confirmation) {
|
if (password != password_confirmation) {
|
||||||
this.password_reset_confirm_error = "Passwords do not match.";
|
this.password_reset_confirm_error = "Passwords do not match.";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(password.length < 5) {
|
if (password.length < 5) {
|
||||||
this.password_reset_confirm_error = "Password too short (must have at least 5 characters).";
|
this.password_reset_confirm_error = "Password too short (must have at least 5 characters).";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
confirm_password_reset(password, this.reset_token, function(parsed,req) {
|
confirm_password_reset(password, this.reset_token, function(parsed,req) {
|
||||||
if(req.status==201){
|
if (req.status==201) {
|
||||||
|
alert("New password set successfully.");
|
||||||
this.active_view = "login";
|
this.active_view = "login";
|
||||||
|
} else {
|
||||||
|
alert("An unknown error occured.");
|
||||||
}
|
}
|
||||||
}.bind(this), function(req) {
|
}.bind(this), function(req) {
|
||||||
if (req.status==404) {
|
if (req.status==404) {
|
||||||
var msg = "user not found";
|
alert("Error: Unknown user.");
|
||||||
} else {
|
} else {
|
||||||
var msg = "error: " + req.statusText;
|
alert("Error: "+req.statusText);
|
||||||
}
|
}
|
||||||
this.password_reset_confirm_error = msg;
|
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ function boot_spacedeck() {
|
|||||||
account: "profile",
|
account: "profile",
|
||||||
logged_in: false,
|
logged_in: false,
|
||||||
guest_nickname: null,
|
guest_nickname: null,
|
||||||
|
embedded: false,
|
||||||
user: {},
|
user: {},
|
||||||
|
|
||||||
active_profile: null,
|
active_profile: null,
|
||||||
@@ -158,7 +159,7 @@ function boot_spacedeck() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
$(document).ready(function(){
|
document.addEventListener("DOMContentLoaded",function() {
|
||||||
window.smoke = smoke;
|
window.smoke = smoke;
|
||||||
window.alert = smoke.alert;
|
window.alert = smoke.alert;
|
||||||
|
|
||||||
|
|||||||
@@ -40,7 +40,11 @@ SpacedeckWebsockets = {
|
|||||||
}
|
}
|
||||||
} else console.log("artifact created in another space.");
|
} else console.log("artifact created in another space.");
|
||||||
}
|
}
|
||||||
else if (msg.action == "update" && msg.object) {
|
else if ((msg.action == "update" || msg.action == "update-self") && msg.object) {
|
||||||
|
if (msg.action == "update-self") {
|
||||||
|
console.log(msg.object);
|
||||||
|
}
|
||||||
|
|
||||||
if (this.active_space) {
|
if (this.active_space) {
|
||||||
var o = msg.object;
|
var o = msg.object;
|
||||||
if (o && o._id) {
|
if (o && o._id) {
|
||||||
@@ -59,13 +63,13 @@ SpacedeckWebsockets = {
|
|||||||
else if (msg.action == "delete" && msg.object) {
|
else if (msg.action == "delete" && msg.object) {
|
||||||
if (this.active_space) {
|
if (this.active_space) {
|
||||||
var o = msg.object;
|
var o = msg.object;
|
||||||
if(o._id){
|
if (o._id){
|
||||||
var existing_artifact = this.find_artifact_by_id(o._id);
|
var existing_artifact = this.find_artifact_by_id(o._id);
|
||||||
if (existing_artifact) {
|
if (existing_artifact) {
|
||||||
var idx = this.active_space_artifacts.indexOf(existing_artifact);
|
var idx = this.active_space_artifacts.indexOf(existing_artifact);
|
||||||
this.active_space_artifacts.splice(idx, 1);
|
this.active_space_artifacts.splice(idx, 1);
|
||||||
} else console.log("existing artifact to delete not found");
|
} else console.log("existing artifact to delete not found");
|
||||||
}else console.error("object without _id");
|
} else console.error("object without _id");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -101,11 +105,13 @@ SpacedeckWebsockets = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.websocket && this.websocket.readyState==1) {
|
if (this.websocket && this.websocket.readyState==1) {
|
||||||
|
var token = "";
|
||||||
|
if (this.user) token = this.user.token;
|
||||||
var auth_params = {
|
var auth_params = {
|
||||||
action: "auth",
|
action: "auth",
|
||||||
editor_auth: space_auth,
|
editor_auth: space_auth,
|
||||||
editor_name: this.guest_nickname,
|
editor_name: this.guest_nickname,
|
||||||
auth_token: window.socket_auth,
|
auth_token: token,
|
||||||
space_id: space._id
|
space_id: space._id
|
||||||
};
|
};
|
||||||
console.log("[websocket] auth space");
|
console.log("[websocket] auth space");
|
||||||
@@ -183,7 +189,7 @@ SpacedeckWebsockets = {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (msg.channel_id == channel_id) {
|
if (msg.channel_id == channel_id && !msg.action.match("-self")) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -197,7 +203,7 @@ SpacedeckWebsockets = {
|
|||||||
this.handle_presenter_media_update(msg);
|
this.handle_presenter_media_update(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (msg.action == "update" || msg.action == "create" || msg.action == "delete"){
|
if (msg.action == "update" || msg.action == "update-self" || msg.action == "create" || msg.action == "delete") {
|
||||||
this.handle_live_updates(msg);
|
this.handle_live_updates(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -230,13 +236,13 @@ SpacedeckWebsockets = {
|
|||||||
return (u && (u._id != this.user._id));
|
return (u && (u._id != this.user._id));
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
users = _.filter(users, function(u) {
|
users = _.filter(users, function(u) {
|
||||||
return (u && (u._id || u.nickname));
|
return (u && (u._id || u.nickname));
|
||||||
});
|
});
|
||||||
|
|
||||||
this.users_online[spaceId] = users;
|
this.users_online[spaceId] = users;
|
||||||
|
|
||||||
if (this.active_space) {
|
if (this.active_space) {
|
||||||
if (this.active_space._id == spaceId) {
|
if (this.active_space._id == spaceId) {
|
||||||
this.active_space_users = users;
|
this.active_space_users = users;
|
||||||
|
|||||||
@@ -5,7 +5,10 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
function setup_whiteboard_directives() {
|
function setup_whiteboard_directives() {
|
||||||
|
var mode_touch = false;
|
||||||
|
|
||||||
if ('ontouchstart' in window) {
|
if ('ontouchstart' in window) {
|
||||||
|
mode_touch = true;
|
||||||
var edown = "touchstart";
|
var edown = "touchstart";
|
||||||
var emove = "touchmove";
|
var emove = "touchmove";
|
||||||
var eup = "touchend";
|
var eup = "touchend";
|
||||||
@@ -15,6 +18,12 @@ function setup_whiteboard_directives() {
|
|||||||
var eup = "mouseup";
|
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', {
|
Vue.directive('sd-whiteboard', {
|
||||||
bind: function () {
|
bind: function () {
|
||||||
var el = this.el;
|
var el = this.el;
|
||||||
@@ -23,9 +32,12 @@ function setup_whiteboard_directives() {
|
|||||||
$(el).on("dblclick", ".artifact", this.handle_double_click_artifact.bind(this));
|
$(el).on("dblclick", ".artifact", this.handle_double_click_artifact.bind(this));
|
||||||
$(el).on("keyup", ".artifact", this.handle_key_up_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).on("keydown", ".artifact", this.handle_key_down_artifact.bind(this));
|
||||||
$(el).bind(edown, this.handle_mouse_down_space.bind(this));
|
$(el).bind("touchstart", this.handle_mouse_down_space.bind(this));
|
||||||
$(el).bind(emove, this.handle_mouse_move.bind(this));
|
$(el).bind("touchmove", this.handle_mouse_move.bind(this));
|
||||||
$(el).bind(eup, this.handle_mouse_up_space.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("wheel", this.handle_wheel_space.bind(this));
|
$(el).bind("wheel", this.handle_wheel_space.bind(this));
|
||||||
|
|
||||||
@@ -80,10 +92,21 @@ function setup_whiteboard_directives() {
|
|||||||
evt.stopPropagation();
|
evt.stopPropagation();
|
||||||
}
|
}
|
||||||
|
|
||||||
var a = $scope.find_artifact_by_id(evt.currentTarget.id.replace("artifact-",""));
|
|
||||||
|
|
||||||
if ($scope.active_tool == "zoom") return;
|
if ($scope.active_tool == "zoom") return;
|
||||||
|
|
||||||
|
if (evt.which == 2) {
|
||||||
|
// middle mouse button
|
||||||
|
this.handle_mouse_down_space(evt);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 == "eyedrop") {
|
if ($scope.active_tool == "eyedrop") {
|
||||||
var arts = $scope.selected_artifacts();
|
var arts = $scope.selected_artifacts();
|
||||||
if (!$scope.is_selected(a) && arts.length > 0) {
|
if (!$scope.is_selected(a) && arts.length > 0) {
|
||||||
@@ -195,9 +218,11 @@ function setup_whiteboard_directives() {
|
|||||||
$scope.zoom_to_cursor(evt,amount);
|
$scope.zoom_to_cursor(evt,amount);
|
||||||
},
|
},
|
||||||
|
|
||||||
handle_mouse_down_space: function(evt) {
|
handle_mouse_down_space: function(evt, force) {
|
||||||
if (evt.target != evt.currentTarget && !_.include(["wrapper"],evt.target.className)) return;
|
if (!force && evt.which != 2) {
|
||||||
|
if (evt.target != evt.currentTarget && !_.include(["wrapper"],evt.target.className)) return;
|
||||||
|
}
|
||||||
|
|
||||||
var $scope = this.vm.$root;
|
var $scope = this.vm.$root;
|
||||||
|
|
||||||
$scope.opened_dialog="none";
|
$scope.opened_dialog="none";
|
||||||
@@ -206,7 +231,7 @@ function setup_whiteboard_directives() {
|
|||||||
$scope.mouse_ox = cursor.x;
|
$scope.mouse_ox = cursor.x;
|
||||||
$scope.mouse_oy = cursor.y;
|
$scope.mouse_oy = cursor.y;
|
||||||
|
|
||||||
if (evt.which == 2 || evt.buttons == 4) {
|
if ((mode_touch && $scope.active_tool=="pointer") || evt.which == 2 || evt.buttons == 4) {
|
||||||
$scope.active_tool = "pan";
|
$scope.active_tool = "pan";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -214,7 +239,7 @@ function setup_whiteboard_directives() {
|
|||||||
this.deselect();
|
this.deselect();
|
||||||
this.mouse_state = "transform";
|
this.mouse_state = "transform";
|
||||||
$scope.mouse_state = this.mouse_state;
|
$scope.mouse_state = this.mouse_state;
|
||||||
this.start_adding_note(evt);
|
this.start_drawing_note(evt);
|
||||||
return;
|
return;
|
||||||
|
|
||||||
} else if ($scope.active_tool=="arrow") {
|
} else if ($scope.active_tool=="arrow") {
|
||||||
@@ -331,7 +356,7 @@ function setup_whiteboard_directives() {
|
|||||||
var $scope = this.vm.$root;
|
var $scope = this.vm.$root;
|
||||||
|
|
||||||
return _.filter($scope.active_space_artifacts, function(a) {
|
return _.filter($scope.active_space_artifacts, function(a) {
|
||||||
return this.rects_intersecting(a.board, rect);
|
return this.rects_intersecting(a, rect);
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -354,12 +379,10 @@ function setup_whiteboard_directives() {
|
|||||||
var lasso_scaled = {
|
var lasso_scaled = {
|
||||||
x:this.lasso.x,
|
x:this.lasso.x,
|
||||||
y:this.lasso.y,
|
y:this.lasso.y,
|
||||||
w:this.lasso.w*$scope.viewport_zoom,
|
w:this.lasso.w,
|
||||||
h:this.lasso.h*$scope.viewport_zoom
|
h:this.lasso.h
|
||||||
}
|
}
|
||||||
lasso_scaled = this.abs_rect(lasso_scaled);
|
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;";
|
var s = "left:" +lasso_scaled.x+"px;";
|
||||||
s += "top:" +lasso_scaled.y+"px;";
|
s += "top:" +lasso_scaled.y+"px;";
|
||||||
@@ -379,15 +402,15 @@ function setup_whiteboard_directives() {
|
|||||||
$("#lasso").show();
|
$("#lasso").show();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Translate the mouse cursor location from device window coordinates to virtual space coordinates
|
||||||
cursor_point_to_space: function(evt) {
|
cursor_point_to_space: function(evt) {
|
||||||
var $scope = this.vm.$root;
|
var $scope = this.vm.$root;
|
||||||
var offset = {left: 0, top: 0};
|
|
||||||
|
|
||||||
evt = fixup_touches(evt);
|
evt = fixup_touches(evt);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
x: (parseInt(evt.pageX) - parseInt(offset.left) - $scope.bounds_margin_horiz) / this.space_zoom,
|
x: $scope.scroll_left + (parseInt(evt.pageX) - $scope.bounds_margin_horiz) / $scope.viewport_zoom,
|
||||||
y: (parseInt(evt.pageY) - parseInt(offset.top) - $scope.bounds_margin_vert) / this.space_zoom
|
y: $scope.scroll_top + (parseInt(evt.pageY) - $scope.bounds_margin_vert) / $scope.viewport_zoom
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -439,15 +462,15 @@ function setup_whiteboard_directives() {
|
|||||||
|
|
||||||
dists = $scope.unselected_artifacts().map(function(a){
|
dists = $scope.unselected_artifacts().map(function(a){
|
||||||
|
|
||||||
var r = this.rect_to_points(a.board);
|
var r = this.rect_to_points(a);
|
||||||
|
|
||||||
var xd1 = Math.abs(r[0].x-x);
|
var xd1 = Math.abs(r[0].x-x);
|
||||||
var xd2 = Math.abs(r[1].x-x);
|
var xd2 = Math.abs(r[1].x-x);
|
||||||
var xd3 = Math.abs(r[0].x+a.board.w/2 - x);
|
var xd3 = Math.abs(r[0].x+a.w/2 - x);
|
||||||
|
|
||||||
var yd1 = Math.abs(r[0].y-y);
|
var yd1 = Math.abs(r[0].y-y);
|
||||||
var yd2 = Math.abs(r[2].y-y);
|
var yd2 = Math.abs(r[2].y-y);
|
||||||
var yd3 = Math.abs(r[0].y+a.board.h/2 - y);
|
var yd3 = Math.abs(r[0].y+a.h/2 - y);
|
||||||
|
|
||||||
if (!snap_middle) {
|
if (!snap_middle) {
|
||||||
if (xd2<xd1) {
|
if (xd2<xd1) {
|
||||||
@@ -469,10 +492,10 @@ function setup_whiteboard_directives() {
|
|||||||
|
|
||||||
if (snap_middle) {
|
if (snap_middle) {
|
||||||
var xd = xd3;
|
var xd = xd3;
|
||||||
var sx = r[0].x+a.board.w/2;
|
var sx = r[0].x+a.w/2;
|
||||||
|
|
||||||
var yd = yd3;
|
var yd = yd3;
|
||||||
var sy = r[0].y+a.board.h/2;
|
var sy = r[0].y+a.h/2;
|
||||||
}
|
}
|
||||||
|
|
||||||
return [[xd,sx],[yd,sy]];
|
return [[xd,sx],[yd,sy]];
|
||||||
@@ -492,6 +515,7 @@ function setup_whiteboard_directives() {
|
|||||||
if (!xdists[0] || xdists[0][0]>TOL) {
|
if (!xdists[0] || xdists[0][0]>TOL) {
|
||||||
results.snapx = [0,x]; // distance, coordinate
|
results.snapx = [0,x]; // distance, coordinate
|
||||||
} else {
|
} else {
|
||||||
|
// FIXME snap rulers are broken
|
||||||
//$scope.snap_ruler_x = xdists[0][1];
|
//$scope.snap_ruler_x = xdists[0][1];
|
||||||
}
|
}
|
||||||
if (!ydists[0] || ydists[0][0]>TOL) {
|
if (!ydists[0] || ydists[0][0]>TOL) {
|
||||||
@@ -503,17 +527,45 @@ function setup_whiteboard_directives() {
|
|||||||
return results;
|
return results;
|
||||||
},
|
},
|
||||||
|
|
||||||
offset_point_in_wrapper: function(point) {
|
start_drawing_note: function(evt) {
|
||||||
|
evt.preventDefault();
|
||||||
|
evt.stopPropagation();
|
||||||
|
|
||||||
var $scope = this.vm.$root;
|
var $scope = this.vm.$root;
|
||||||
var section_el = $(this.el)[0];
|
var point = this.cursor_point_to_space(evt);
|
||||||
var z = $scope.viewport_zoom;
|
var z = $scope.highest_z()+1;
|
||||||
|
|
||||||
var pt = parseInt($("#space").css("padding-top"));
|
var note_w = 250;
|
||||||
|
var note_h = 250;
|
||||||
|
|
||||||
point.y=(point.y+section_el.scrollTop-pt)/z;
|
var a = {
|
||||||
point.x=(point.x+section_el.scrollLeft)/z;
|
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
|
||||||
|
};
|
||||||
|
|
||||||
return point;
|
$scope.save_artifact(a, function(saved_a) {
|
||||||
|
$scope.update_board_artifact_viewmodel(saved_a);
|
||||||
|
$scope.active_space_artifacts.push(saved_a);
|
||||||
|
$scope.select(evt,a);
|
||||||
|
$scope.transform_ox = 0;
|
||||||
|
$scope.transform_oy = 0;
|
||||||
|
$scope.begin_transaction();
|
||||||
|
}.bind(this));
|
||||||
},
|
},
|
||||||
|
|
||||||
start_drawing_scribble: function(evt) {
|
start_drawing_scribble: function(evt) {
|
||||||
@@ -521,7 +573,7 @@ function setup_whiteboard_directives() {
|
|||||||
evt.stopPropagation();
|
evt.stopPropagation();
|
||||||
|
|
||||||
var $scope = this.vm.$root;
|
var $scope = this.vm.$root;
|
||||||
var point = this.offset_point_in_wrapper(this.cursor_point_to_space(evt));
|
var point = this.cursor_point_to_space(evt);
|
||||||
var z = $scope.highest_z()+1;
|
var z = $scope.highest_z()+1;
|
||||||
|
|
||||||
$scope.deselect();
|
$scope.deselect();
|
||||||
@@ -531,18 +583,14 @@ function setup_whiteboard_directives() {
|
|||||||
mime: "x-spacedeck/vector",
|
mime: "x-spacedeck/vector",
|
||||||
description: "",
|
description: "",
|
||||||
control_points: [{dx:0,dy:0}],
|
control_points: [{dx:0,dy:0}],
|
||||||
board: {
|
x: point.x,
|
||||||
x: point.x,
|
y: point.y,
|
||||||
y: point.y,
|
z: z,
|
||||||
z: z,
|
w: 64,
|
||||||
w: 64,
|
h: 64,
|
||||||
h: 64
|
stroke_color: $scope.active_style.stroke_color,
|
||||||
},
|
stroke: 2,
|
||||||
style: {
|
shape: "scribble"
|
||||||
stroke_color: "#000000",
|
|
||||||
stroke: 2,
|
|
||||||
shape: "scribble"
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.save_artifact(a, function(saved_a) {
|
$scope.save_artifact(a, function(saved_a) {
|
||||||
@@ -564,7 +612,6 @@ function setup_whiteboard_directives() {
|
|||||||
|
|
||||||
var $scope = this.vm.$root;
|
var $scope = this.vm.$root;
|
||||||
var point = this.cursor_point_to_space(evt);
|
var point = this.cursor_point_to_space(evt);
|
||||||
this.offset_point_in_wrapper(point);
|
|
||||||
var z = $scope.highest_z()+1;
|
var z = $scope.highest_z()+1;
|
||||||
|
|
||||||
var a = {
|
var a = {
|
||||||
@@ -572,18 +619,14 @@ function setup_whiteboard_directives() {
|
|||||||
mime: "x-spacedeck/vector",
|
mime: "x-spacedeck/vector",
|
||||||
description: "",
|
description: "",
|
||||||
control_points: [{dx:0,dy:0},{dx:0,dy:0},{dx:0,dy:0}],
|
control_points: [{dx:0,dy:0},{dx:0,dy:0},{dx:0,dy:0}],
|
||||||
board: {
|
x: point.x,
|
||||||
x: point.x,
|
y: point.y,
|
||||||
y: point.y,
|
z: z,
|
||||||
z: z,
|
w: 64,
|
||||||
w: 64,
|
h: 64,
|
||||||
h: 64
|
stroke_color: $scope.active_style.stroke_color,
|
||||||
},
|
stroke: 2,
|
||||||
style: {
|
shape: "arrow"
|
||||||
stroke_color: "#000000",
|
|
||||||
stroke: 2,
|
|
||||||
shape: "arrow"
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.save_artifact(a, function(saved_a) {
|
$scope.save_artifact(a, function(saved_a) {
|
||||||
@@ -604,7 +647,6 @@ function setup_whiteboard_directives() {
|
|||||||
|
|
||||||
var $scope = this.vm.$root;
|
var $scope = this.vm.$root;
|
||||||
var point = this.cursor_point_to_space(evt);
|
var point = this.cursor_point_to_space(evt);
|
||||||
this.offset_point_in_wrapper(point);
|
|
||||||
var z = $scope.highest_z()+1;
|
var z = $scope.highest_z()+1;
|
||||||
|
|
||||||
var a = {
|
var a = {
|
||||||
@@ -612,18 +654,14 @@ function setup_whiteboard_directives() {
|
|||||||
mime: "x-spacedeck/vector",
|
mime: "x-spacedeck/vector",
|
||||||
description: "",
|
description: "",
|
||||||
control_points: [{dx:0,dy:0},{dx:0,dy:0}],
|
control_points: [{dx:0,dy:0},{dx:0,dy:0}],
|
||||||
board: {
|
x: point.x,
|
||||||
x: point.x,
|
y: point.y,
|
||||||
y: point.y,
|
z: z,
|
||||||
z: z,
|
w: 64,
|
||||||
w: 64,
|
h: 64,
|
||||||
h: 64
|
stroke_color: "#000000",
|
||||||
},
|
stroke: 2,
|
||||||
style: {
|
shape: "line"
|
||||||
stroke_color: "#000000",
|
|
||||||
stroke: 2,
|
|
||||||
shape: "line"
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.save_artifact(a, function(saved_a) {
|
$scope.save_artifact(a, function(saved_a) {
|
||||||
@@ -651,8 +689,7 @@ function setup_whiteboard_directives() {
|
|||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
|
|
||||||
if (this.mouse_state == "lasso") {
|
if (this.mouse_state == "lasso") {
|
||||||
var lasso_rect = this.abs_rect(this.offset_point_in_wrapper(this.lasso));
|
var lasso_rect = this.abs_rect(this.lasso);
|
||||||
// convert to space coordinates
|
|
||||||
|
|
||||||
if (lasso_rect.w>0 && lasso_rect.h>0) {
|
if (lasso_rect.w>0 && lasso_rect.h>0) {
|
||||||
var arts = this.artifacts_in_rect(lasso_rect);
|
var arts = this.artifacts_in_rect(lasso_rect);
|
||||||
@@ -675,11 +712,11 @@ function setup_whiteboard_directives() {
|
|||||||
|
|
||||||
if (_.include(["text","placeholder"],$scope.artifact_major_type(ars[i]))) {
|
if (_.include(["text","placeholder"],$scope.artifact_major_type(ars[i]))) {
|
||||||
// some types of artifact need a minimum size
|
// some types of artifact need a minimum size
|
||||||
if (ars[i].board.w<10) {
|
if (ars[i].w<10) {
|
||||||
ars[i].board.w = 10;
|
ars[i].w = 10;
|
||||||
}
|
}
|
||||||
if (ars[i].board.h<10) {
|
if (ars[i].h<10) {
|
||||||
ars[i].board.h = 10;
|
ars[i].h = 10;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -691,7 +728,7 @@ function setup_whiteboard_directives() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_.include(["zoom"], $scope.active_tool)) {
|
if (_.include(["zoom", "scribble"], $scope.active_tool)) {
|
||||||
// tools that stay active after use
|
// tools that stay active after use
|
||||||
this.mouse_state = "idle";
|
this.mouse_state = "idle";
|
||||||
$scope.mouse_state = this.mouse_state;
|
$scope.mouse_state = this.mouse_state;
|
||||||
@@ -731,18 +768,12 @@ function setup_whiteboard_directives() {
|
|||||||
|
|
||||||
$scope.handle_scroll();
|
$scope.handle_scroll();
|
||||||
|
|
||||||
var cursor = this.cursor_point_to_space(evt);
|
var cursor = this.cursor_point_to_space(evt); // takes the raw event data and finds the mouse location in virtual space
|
||||||
var dx = cursor.x - $scope.mouse_ox;
|
var dx = cursor.x - $scope.mouse_ox;
|
||||||
var dy = cursor.y - $scope.mouse_oy;
|
var dy = cursor.y - $scope.mouse_oy;
|
||||||
var dt = (new Date()).getTime() - this.last_mouse_move_time;
|
var dt = (new Date()).getTime() - this.last_mouse_move_time;
|
||||||
this.last_mouse_move_time = (new Date()).getTime();
|
this.last_mouse_move_time = (new Date()).getTime();
|
||||||
|
|
||||||
var zoom = $scope.viewport_zoom||1;
|
|
||||||
if (zoom) {
|
|
||||||
dx/=zoom;
|
|
||||||
dy/=zoom;
|
|
||||||
}
|
|
||||||
|
|
||||||
// send cursor
|
// send cursor
|
||||||
if (dx>10 || dy>10 || dt>100) {
|
if (dx>10 || dy>10 || dt>100) {
|
||||||
var name = "anonymous";
|
var name = "anonymous";
|
||||||
@@ -754,8 +785,8 @@ function setup_whiteboard_directives() {
|
|||||||
|
|
||||||
var cursor_msg = {
|
var cursor_msg = {
|
||||||
action: "cursor",
|
action: "cursor",
|
||||||
x: cursor.x/zoom,
|
x: cursor.x,
|
||||||
y: cursor.y/zoom,
|
y: cursor.y,
|
||||||
name: name,
|
name: name,
|
||||||
id: $scope.user._id||name
|
id: $scope.user._id||name
|
||||||
};
|
};
|
||||||
@@ -827,10 +858,8 @@ function setup_whiteboard_directives() {
|
|||||||
|
|
||||||
if (old_a) {
|
if (old_a) {
|
||||||
return {
|
return {
|
||||||
board: _.extend(a.board, {
|
x: old_a.x + dx - snap_dx,
|
||||||
x: old_a.board.x + dx - snap_dx,
|
y: old_a.y + dy - snap_dy
|
||||||
y: old_a.board.y + dy - snap_dy
|
|
||||||
})
|
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
// deleted?
|
// deleted?
|
||||||
@@ -870,21 +899,19 @@ function setup_whiteboard_directives() {
|
|||||||
$scope.update_selected_artifacts(function(a) {
|
$scope.update_selected_artifacts(function(a) {
|
||||||
var old_a = $scope.find_artifact_before_transaction(a);
|
var old_a = $scope.find_artifact_before_transaction(a);
|
||||||
|
|
||||||
var x1 = origin_x + ((old_a.board.x - origin_x) * scale_x);
|
var x1 = origin_x + ((old_a.x - origin_x) * scale_x);
|
||||||
var y1 = origin_y + ((old_a.board.y - origin_y) * scale_y);
|
var y1 = origin_y + ((old_a.y - origin_y) * scale_y);
|
||||||
var x2 = origin_x + (((old_a.board.x + old_a.board.w) - origin_x) * scale_x);
|
var x2 = origin_x + (((old_a.x + old_a.w) - origin_x) * scale_x);
|
||||||
var y2 = origin_y + (((old_a.board.y + old_a.board.h) - origin_y) * scale_y);
|
var y2 = origin_y + (((old_a.y + old_a.h) - origin_y) * scale_y);
|
||||||
|
|
||||||
if (x1>x2) { var t = x1; x1 = x2; x2 = t; }
|
if (x1>x2) { var t = x1; x1 = x2; x2 = t; }
|
||||||
if (y1>y2) { var t = y1; y1 = y2; y2 = t; }
|
if (y1>y2) { var t = y1; y1 = y2; y2 = t; }
|
||||||
|
|
||||||
return {
|
return {
|
||||||
board: _.extend(a.board, {
|
x: x1,
|
||||||
x: x1,
|
y: y1,
|
||||||
y: y1,
|
w: x2 - x1,
|
||||||
w: x2 - x1,
|
h: y2 - y1
|
||||||
h: y2 - y1
|
|
||||||
})
|
|
||||||
};
|
};
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
|
|
||||||
@@ -902,18 +929,17 @@ function setup_whiteboard_directives() {
|
|||||||
var old_a = $scope.find_artifact_before_transaction(a);
|
var old_a = $scope.find_artifact_before_transaction(a);
|
||||||
|
|
||||||
var control_points = _.cloneDeep(old_a.control_points);
|
var control_points = _.cloneDeep(old_a.control_points);
|
||||||
var board = _.clone(old_a.board);
|
|
||||||
var cp = control_points[$scope.selected_control_point_idx];
|
var cp = control_points[$scope.selected_control_point_idx];
|
||||||
|
|
||||||
var snapped = _this.snap_point(board.x+cp.dx+dx, board.y+cp.dy+dy);
|
var snapped = _this.snap_point(old_a.x+cp.dx+dx, old_a.y+cp.dy+dy);
|
||||||
dx = snapped.snapx[1]-(board.x+cp.dx);
|
dx = snapped.snapx[1]-(old_a.x+cp.dx);
|
||||||
dy = snapped.snapy[1]-(board.y+cp.dy);
|
dy = snapped.snapy[1]-(old_a.y+cp.dy);
|
||||||
|
|
||||||
cp.dx += dx;
|
cp.dx += dx;
|
||||||
cp.dy += dy;
|
cp.dy += dy;
|
||||||
|
|
||||||
// special case for arrow's 3rd point
|
// special case for arrow's 3rd point
|
||||||
if (a.style.shape == "arrow" && $scope.selected_control_point_idx!=2) {
|
if (a.shape == "arrow" && $scope.selected_control_point_idx!=2) {
|
||||||
/*control_points[2].dx += dx/2;
|
/*control_points[2].dx += dx/2;
|
||||||
control_points[2].dy += dy/2; */
|
control_points[2].dy += dy/2; */
|
||||||
|
|
||||||
@@ -921,7 +947,7 @@ function setup_whiteboard_directives() {
|
|||||||
control_points[2].dy = (control_points[0].dy+control_points[1].dy)/2;
|
control_points[2].dy = (control_points[0].dy+control_points[1].dy)/2;
|
||||||
}
|
}
|
||||||
|
|
||||||
return _this.normalize_control_points(control_points, board);
|
return _this.normalize_control_points(control_points, old_a);
|
||||||
});
|
});
|
||||||
|
|
||||||
} else if (this.mouse_state == "scribble") {
|
} else if (this.mouse_state == "scribble") {
|
||||||
@@ -930,16 +956,14 @@ function setup_whiteboard_directives() {
|
|||||||
var old_a = a;
|
var old_a = a;
|
||||||
|
|
||||||
var control_points = _.cloneDeep(old_a.control_points);
|
var control_points = _.cloneDeep(old_a.control_points);
|
||||||
var board = _.clone(old_a.board);
|
var offset = {x:cursor.x,y:cursor.y};
|
||||||
|
|
||||||
var offset = this.offset_point_in_wrapper({x:cursor.x,y:cursor.y});
|
|
||||||
|
|
||||||
control_points.push({
|
control_points.push({
|
||||||
dx: offset.x-board.x,
|
dx: offset.x-old_a.x,
|
||||||
dy: offset.y-board.y
|
dy: offset.y-old_a.y
|
||||||
});
|
});
|
||||||
|
|
||||||
return this.normalize_control_points(simplify_scribble_points(control_points), board);
|
return this.normalize_control_points(simplify_scribble_points(control_points), old_a);
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
|
|
||||||
var arts = $scope.selected_artifacts();
|
var arts = $scope.selected_artifacts();
|
||||||
@@ -952,14 +976,14 @@ function setup_whiteboard_directives() {
|
|||||||
if (!$("#space").length) return;
|
if (!$("#space").length) return;
|
||||||
el = $("#space")[0];
|
el = $("#space")[0];
|
||||||
|
|
||||||
el.scrollLeft = this.old_panx - dx*$scope.viewport_zoom;
|
el.scrollLeft -= dx*$scope.viewport_zoom;
|
||||||
el.scrollTop = this.old_pany - dy*$scope.viewport_zoom;
|
el.scrollTop -= dy*$scope.viewport_zoom;
|
||||||
|
|
||||||
$scope.handle_scroll();
|
$scope.handle_scroll();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
normalize_control_points: function(control_points, board) {
|
normalize_control_points: function(control_points, artifact) {
|
||||||
var x1 = _.min(control_points,"dx").dx;
|
var x1 = _.min(control_points,"dx").dx;
|
||||||
var y1 = _.min(control_points,"dy").dy;
|
var y1 = _.min(control_points,"dy").dy;
|
||||||
var x2 = _.max(control_points,"dx").dx;
|
var x2 = _.max(control_points,"dx").dx;
|
||||||
@@ -981,19 +1005,15 @@ function setup_whiteboard_directives() {
|
|||||||
var bshiftx = 0;
|
var bshiftx = 0;
|
||||||
var bshifty = 0;
|
var bshifty = 0;
|
||||||
|
|
||||||
if (board.w < 0) bshiftx = -board.w;
|
if (artifact.w < 0) bshiftx = -artifact.w;
|
||||||
if (board.h < 0) bshifty = -board.h;
|
if (artifact.h < 0) bshifty = -artifact.h;
|
||||||
|
|
||||||
var shifted_board = {
|
|
||||||
x: board.x + bshiftx - shiftx,
|
|
||||||
y: board.y + bshifty - shifty,
|
|
||||||
w: w,
|
|
||||||
h: h,
|
|
||||||
z: board.z
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
board: shifted_board,
|
x: artifact.x + bshiftx - shiftx,
|
||||||
|
y: artifact.y + bshifty - shifty,
|
||||||
|
w: w,
|
||||||
|
h: h,
|
||||||
|
z: artifact.z,
|
||||||
control_points: shifted_cps
|
control_points: shifted_cps
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ function vec2_angle(v) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function render_vector_drawing(a, padding) {
|
function render_vector_drawing(a, padding) {
|
||||||
var shape = a.style.shape || "";
|
var shape = a.shape || "";
|
||||||
var path = [];
|
var path = [];
|
||||||
var p = a.control_points[0];
|
var p = a.control_points[0];
|
||||||
|
|
||||||
@@ -48,8 +48,8 @@ function render_vector_drawing(a, padding) {
|
|||||||
|
|
||||||
var d = "M" + (cps.dx + padding) + "," + (cps.dy + padding) + " Q" + (scaledMiddlePoint.dx + padding) + "," + (scaledMiddlePoint.dy + padding) + " " + (cpe.dx + padding) + "," + (cpe.dy + padding);
|
var d = "M" + (cps.dx + padding) + "," + (cps.dy + padding) + " Q" + (scaledMiddlePoint.dx + padding) + "," + (scaledMiddlePoint.dy + padding) + " " + (cpe.dx + padding) + "," + (cpe.dy + padding);
|
||||||
var tip = "<defs><marker id='ae" + markerId + "' refX=\"0.1\" refY=\"3\" markerWidth=\"3\" markerHeight=\"6\" orient=\"auto\">";
|
var tip = "<defs><marker id='ae" + markerId + "' refX=\"0.1\" refY=\"3\" markerWidth=\"3\" markerHeight=\"6\" orient=\"auto\">";
|
||||||
tip += "<path d=\"M-3,0 V6 L3,3 Z\" fill=\""+a.style.stroke_color+"\" stroke-width=\"0\"/></marker></defs>";
|
tip += "<path d=\"M-3,0 V6 L3,3 Z\" fill=\""+a.stroke_color+"\" stroke-width=\"0\"/></marker></defs>";
|
||||||
var svg = tip + "<path d='" + d + "' style='stroke-width:" + a.style.stroke + ";' marker-end='url(#ae" + markerId + ")'/>";
|
var svg = tip + "<path d='" + d + "' style='stroke-width:" + a.stroke + ";' marker-end='url(#ae" + markerId + ")'/>";
|
||||||
|
|
||||||
return svg;
|
return svg;
|
||||||
}
|
}
|
||||||
@@ -121,33 +121,100 @@ function render_vector_drawing(a, padding) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function render_vector_star(edges,xradius,yradius,offset) {
|
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.
|
||||||
|
|
||||||
edges *= 2;
|
//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));
|
||||||
|
|
||||||
var points = [];
|
var points = [];
|
||||||
var degrees = 360 / edges;
|
|
||||||
for (var i=0; i < edges; i++) {
|
|
||||||
var a = i * degrees - 90;
|
|
||||||
var xr = xradius;
|
|
||||||
var yr = yradius;
|
|
||||||
|
|
||||||
if (i%2) {
|
// var tmp_outside_points = []; // uncomment to see the calculated edge of the star (outside the stroke width)
|
||||||
if (edges==20) {
|
|
||||||
xr/=1.5;
|
|
||||||
yr/=1.5;
|
|
||||||
} else {
|
|
||||||
xr/=2.8;
|
|
||||||
yr/=2.8;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var x = offset + xradius + xr * Math.cos(a * Math.PI / 180);
|
var angle = 2*Math.PI / tips;
|
||||||
var y = offset + yradius + yr * Math.sin(a * Math.PI / 180);
|
// generate points without offset from stroke width first
|
||||||
points.push(x+","+y);
|
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
return "<polygon points='"+points.join(" ")+"'/>";
|
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 + ")'/>";
|
||||||
}
|
}
|
||||||
|
|
||||||
function transform_vector_template(cmds, xr, yr, offset) {
|
function transform_vector_template(cmds, xr, yr, offset) {
|
||||||
@@ -237,11 +304,11 @@ function render_vector_rect(xradius,yradius,offset) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function render_vector_shape(a) {
|
function render_vector_shape(a) {
|
||||||
var stroke = parseInt(a.style.stroke) + 4;
|
var stroke = parseInt(a.stroke) + 4;
|
||||||
var offset = stroke / 2;
|
var offset = stroke / 2;
|
||||||
|
|
||||||
var xr = (a.board.w-stroke) / 2;
|
var xr = (a.w-stroke) / 2;
|
||||||
var yr = (a.board.h-stroke) / 2;
|
var yr = (a.h-stroke) / 2;
|
||||||
|
|
||||||
var shape_renderers = {
|
var shape_renderers = {
|
||||||
ellipse: function() { return render_vector_ellipse(xr, yr, offset); },
|
ellipse: function() { return render_vector_ellipse(xr, yr, offset); },
|
||||||
@@ -251,14 +318,14 @@ function render_vector_shape(a) {
|
|||||||
diamond: function() { return render_vector_ngon(4, xr, yr, offset); },
|
diamond: function() { return render_vector_ngon(4, xr, yr, offset); },
|
||||||
square: function() { return "" },
|
square: function() { return "" },
|
||||||
triangle: function() { return render_vector_ngon(3, xr, yr, offset); },
|
triangle: function() { return render_vector_ngon(3, xr, yr, offset); },
|
||||||
star: function() { return render_vector_star(5, xr, yr, offset); },
|
star: function() { return render_vector_star(5, a.w, a.h, a.stroke); },
|
||||||
burst: function() { return render_vector_star(10, xr, yr, offset); },
|
burst: function() { return render_vector_star(10, a.w, a.h, a.stroke); },
|
||||||
speechbubble: function() { return render_vector_speechbubble(xr, yr, offset); },
|
speechbubble: function() { return render_vector_speechbubble(xr, yr, offset); },
|
||||||
heart: function() { return render_vector_heart(xr, yr, offset); },
|
heart: function() { return render_vector_heart(xr, yr, offset); },
|
||||||
cloud: function() { return render_vector_cloud(xr, yr, offset); },
|
cloud: function() { return render_vector_cloud(xr, yr, offset); },
|
||||||
}
|
}
|
||||||
|
|
||||||
var render_func = shape_renderers[a.style.shape];
|
var render_func = shape_renderers[a.shape];
|
||||||
|
|
||||||
if (!render_func) return "";
|
if (!render_func) return "";
|
||||||
|
|
||||||
|
|||||||
16073
public/stylesheets/style.css
Normal file
16073
public/stylesheets/style.css
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,56 +1,40 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var config = require('config');
|
var config = require('config');
|
||||||
require('../../models/schema');
|
|
||||||
|
|
||||||
var fs = require('fs');
|
|
||||||
var _ = require("underscore");
|
|
||||||
var mongoose = require("mongoose");
|
|
||||||
var async = require('async');
|
var async = require('async');
|
||||||
var archiver = require('archiver');
|
|
||||||
var request = require('request');
|
|
||||||
var url = require("url");
|
var url = require("url");
|
||||||
var path = require("path");
|
var path = require("path");
|
||||||
var crypto = require('crypto');
|
var crypto = require('crypto');
|
||||||
var qr = require('qr-image');
|
|
||||||
var glob = require('glob');
|
var glob = require('glob');
|
||||||
var gm = require('gm');
|
|
||||||
|
|
||||||
var express = require('express');
|
var express = require('express');
|
||||||
var router = express.Router();
|
var router = express.Router();
|
||||||
|
|
||||||
var userMapping = { '_id': 1, 'nickname': 1, 'email': 1};
|
const db = require('../../models/db');
|
||||||
var spaceMapping = { '_id': 1, name: 1};
|
const Sequelize = require('sequelize');
|
||||||
|
const Op = Sequelize.Op;
|
||||||
|
const uuidv4 = require('uuid/v4');
|
||||||
|
|
||||||
router.get('/:membership_id/accept', function(req, res, next) {
|
router.get('/:membership_id/accept', function(req, res, next) {
|
||||||
if (req.user) {
|
if (req.user) {
|
||||||
Membership.findOne({
|
db.Membership.findOne({where:{
|
||||||
_id: req.params.membership_id,
|
_id: req.params.membership_id,
|
||||||
state: "pending",
|
code: req.query.code
|
||||||
code: req.query.code,
|
}, include: ['space']}).then((mem) => {
|
||||||
user: { "$exists": false }
|
if (mem) {
|
||||||
}).populate('space').exec((err,mem) => {
|
if (!mem.user) {
|
||||||
if (err) res.sendStatus(400);
|
mem.state = "active";
|
||||||
else {
|
mem.user_id = req.user._id;
|
||||||
if (mem) {
|
|
||||||
if(!mem.user) {
|
mem.save().then(function() {
|
||||||
mem.code = null;
|
res.status(200).json(mem);
|
||||||
mem.state = "active";
|
});
|
||||||
mem.user = req.user;
|
|
||||||
|
|
||||||
mem.save(function(err){
|
|
||||||
if (err) res.status(400).json(err);
|
|
||||||
else {
|
|
||||||
console.log(mem);
|
|
||||||
res.status(200).json(mem);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
res.status(400).json({"error": "already_used"});
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
res.status(404).json({"error": "not found"});
|
res.status(200).json(mem);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
res.status(404).json({"error": "not found"});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,79 +1,72 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var config = require('config');
|
var config = require('config');
|
||||||
require('../../models/schema');
|
const db = require('../../models/db');
|
||||||
|
|
||||||
var bcrypt = require('bcryptjs');
|
var bcrypt = require('bcryptjs');
|
||||||
var crypo = require('crypto');
|
var crypto = require('crypto');
|
||||||
|
var URL = require('url').URL;
|
||||||
|
|
||||||
var express = require('express');
|
var express = require('express');
|
||||||
var router = express.Router();
|
var router = express.Router();
|
||||||
|
|
||||||
router.post('/', function(req, res) {
|
router.post('/', function(req, res) {
|
||||||
var data = req.body;
|
var data = req.body;
|
||||||
if (data.email && data.password) {
|
if (!data.email || !data.password) {
|
||||||
var email = req.body.email.toLowerCase();
|
|
||||||
var password = req.body["password"];
|
|
||||||
|
|
||||||
User.find({email: email, account_type: "email"}, (function (err, users) {
|
|
||||||
if (err) {
|
|
||||||
res.status(400).json({"error":"session.users"});
|
|
||||||
} else {
|
|
||||||
|
|
||||||
if (users.length == 1) {
|
|
||||||
var user = users[0];
|
|
||||||
|
|
||||||
if (bcrypt.compareSync(password, user.password_hash)) {
|
|
||||||
crypo.randomBytes(48, function(ex, buf) {
|
|
||||||
var token = buf.toString('hex');
|
|
||||||
|
|
||||||
var session = {
|
|
||||||
token: token,
|
|
||||||
ip: req.ip,
|
|
||||||
device: "web",
|
|
||||||
created_at: new Date()
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!user.sessions)
|
|
||||||
user.sessions = [];
|
|
||||||
|
|
||||||
user.sessions.push(session);
|
|
||||||
|
|
||||||
user.save(function(err, result) {
|
|
||||||
// FIXME
|
|
||||||
var secure = process.env.NODE_ENV == "production" || process.env.NODE_ENV == "staging";
|
|
||||||
var domain = (process.env.NODE_ENV == "production") ? ".example.org" : "localhost";
|
|
||||||
|
|
||||||
res.cookie('sdsession', token, { domain: domain, httpOnly: true, secure: secure});
|
|
||||||
res.status(201).json(session);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}else{
|
|
||||||
res.sendStatus(403);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
res.sendStatus(404);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
} else {
|
|
||||||
res.status(400).json({});
|
res.status(400).json({});
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var email = req.body.email.toLowerCase();
|
||||||
|
var password = req.body["password"];
|
||||||
|
|
||||||
|
db.User.findOne({where: {email: email}})
|
||||||
|
.error(err => {
|
||||||
|
res.sendStatus(404);
|
||||||
|
})
|
||||||
|
.then(user => {
|
||||||
|
if (!user) {
|
||||||
|
res.sendStatus(404);
|
||||||
|
}
|
||||||
|
else if (bcrypt.compareSync(password, user.password_hash)) {
|
||||||
|
crypto.randomBytes(48, function(ex, buf) {
|
||||||
|
var token = buf.toString('hex');
|
||||||
|
|
||||||
|
var session = {
|
||||||
|
user_id: user._id,
|
||||||
|
token: token,
|
||||||
|
ip: req.ip,
|
||||||
|
device: "web",
|
||||||
|
created_at: new Date()
|
||||||
|
};
|
||||||
|
|
||||||
|
db.Session.create(session)
|
||||||
|
.error(err => {
|
||||||
|
console.error("Error creating Session:",err);
|
||||||
|
res.sendStatus(500);
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
var domain = (process.env.NODE_ENV == "production") ? new URL(config.get('endpoint')).hostname : req.headers.hostname;
|
||||||
|
res.cookie('sdsession', token, { domain: domain, httpOnly: true });
|
||||||
|
res.status(201).json(session);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
res.sendStatus(403);
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
router.delete('/current', function(req, res, next) {
|
router.delete('/current', function(req, res, next) {
|
||||||
if (req.user) {
|
if (req.user) {
|
||||||
var user = req.user;
|
var token = req.cookies['sdsession'];
|
||||||
var newSessions = user.sessions.filter( function(session){
|
db.Session.findOne({where: {token: token}})
|
||||||
return session.token != req.token;
|
.then(session => {
|
||||||
});
|
session.destroy();
|
||||||
user.sessions = newSessions;
|
});
|
||||||
user.save(function(err, result) {
|
var domain = (process.env.NODE_ENV == "production") ? new URL(config.get('endpoint')).hostname : req.headers.hostname;
|
||||||
// FIXME
|
res.clearCookie('sdsession', { domain: domain });
|
||||||
var domain = (process.env.NODE_ENV == "production") ? ".example.org" : "localhost";
|
res.sendStatus(204);
|
||||||
res.clearCookie('sdsession', { domain: domain });
|
|
||||||
res.sendStatus(204);
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
res.sendStatus(404);
|
res.sendStatus(404);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,12 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var config = require('config');
|
var config = require('config');
|
||||||
require('../../models/schema');
|
|
||||||
|
const os = require('os');
|
||||||
|
const db = require('../../models/db');
|
||||||
|
const Sequelize = require('sequelize');
|
||||||
|
const Op = Sequelize.Op;
|
||||||
|
const uuidv4 = require('uuid/v4');
|
||||||
|
|
||||||
var payloadConverter = require('../../helpers/artifact_converter');
|
var payloadConverter = require('../../helpers/artifact_converter');
|
||||||
var redis = require('../../helpers/redis');
|
var redis = require('../../helpers/redis');
|
||||||
@@ -9,13 +14,11 @@ var redis = require('../../helpers/redis');
|
|||||||
var async = require('async');
|
var async = require('async');
|
||||||
var fs = require('fs');
|
var fs = require('fs');
|
||||||
var _ = require("underscore");
|
var _ = require("underscore");
|
||||||
var mongoose = require("mongoose");
|
|
||||||
var archiver = require('archiver');
|
var archiver = require('archiver');
|
||||||
var request = require('request');
|
var request = require('request');
|
||||||
var url = require("url");
|
var url = require("url");
|
||||||
var path = require("path");
|
var path = require("path");
|
||||||
var crypto = require('crypto');
|
var crypto = require('crypto');
|
||||||
var qr = require('qr-image');
|
|
||||||
var glob = require('glob');
|
var glob = require('glob');
|
||||||
var gm = require('gm');
|
var gm = require('gm');
|
||||||
|
|
||||||
@@ -46,22 +49,27 @@ var roleMapping = {
|
|||||||
// ARTIFACTS
|
// ARTIFACTS
|
||||||
|
|
||||||
router.get('/', (req, res) => {
|
router.get('/', (req, res) => {
|
||||||
Artifact.find({
|
db.Artifact.findAll({where: {
|
||||||
space_id: req.space._id
|
space_id: req.space._id
|
||||||
}).exec((err, artifacts) => {
|
}}).then(artifacts => {
|
||||||
async.map(artifacts, (a, cb) => {
|
async.map(artifacts, (a, cb) => {
|
||||||
a = a.toObject();
|
db.unpackArtifact(a);
|
||||||
|
|
||||||
if (a.user_id) {
|
if (a.user_id) {
|
||||||
User.findOne({
|
// FIXME JOIN
|
||||||
|
/*User.findOne({where: {
|
||||||
"_id": a.user_id
|
"_id": a.user_id
|
||||||
}).select({
|
}}).select({
|
||||||
"_id": 1,
|
"_id": 1,
|
||||||
"nickname": 1,
|
"nickname": 1,
|
||||||
"email": 1
|
"email": 1
|
||||||
}).exec((err, user) => {
|
}).exec((err, user) => {
|
||||||
a['user'] = user.toObject();
|
if (user) {
|
||||||
|
a['user'] = user.toObject();
|
||||||
|
}
|
||||||
cb(err, a);
|
cb(err, a);
|
||||||
});
|
});*/
|
||||||
|
cb(null, a);
|
||||||
} else {
|
} else {
|
||||||
cb(null, a);
|
cb(null, a);
|
||||||
}
|
}
|
||||||
@@ -79,10 +87,9 @@ router.post('/', function(req, res, next) {
|
|||||||
|
|
||||||
attrs['space_id'] = req.space._id;
|
attrs['space_id'] = req.space._id;
|
||||||
|
|
||||||
var artifact = new Artifact(attrs);
|
var artifact = attrs;
|
||||||
|
artifact._id = uuidv4();
|
||||||
|
|
||||||
artifact.created_from_ip = req['real_ip'];
|
|
||||||
|
|
||||||
if (req.user) {
|
if (req.user) {
|
||||||
artifact.user_id = req.user._id;
|
artifact.user_id = req.user._id;
|
||||||
artifact.last_update_user_id = req.user._id;
|
artifact.last_update_user_id = req.user._id;
|
||||||
@@ -90,56 +97,59 @@ router.post('/', function(req, res, next) {
|
|||||||
artifact.last_update_editor_name = req.editor_name;
|
artifact.last_update_editor_name = req.editor_name;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (req.spaceRole == "editor" || req.spaceRole == "admin") {
|
db.packArtifact(artifact);
|
||||||
artifact.save(function(err) {
|
|
||||||
if (err) res.status(400).json(err);
|
if (req.spaceRole == "editor" || req.spaceRole == "admin") {
|
||||||
else {
|
db.Artifact.create(artifact).then(() => {
|
||||||
Space.update({
|
//if (err) res.status(400).json(err);
|
||||||
_id: req.space._id
|
db.unpackArtifact(artifact);
|
||||||
}, {
|
db.Space.update({ updated_at: new Date() }, {where: {_id: req.space._id}});
|
||||||
"$set": {
|
res.distributeCreate("Artifact", artifact);
|
||||||
updated_at: new Date()
|
|
||||||
}
|
|
||||||
});
|
|
||||||
res.distributeCreate("Artifact", artifact);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
res.status(401).json({
|
res.status(401).json({
|
||||||
"error": "no access"
|
"error": "Access denied"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post('/:artifact_id/payload', 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 a = req.artifact;
|
||||||
|
|
||||||
var fileName = (req.query.filename || "upload.bin").replace(/[^a-zA-Z0-9_\-\.]/g, '');
|
var fileName = (req.query.filename || "upload.bin").replace(/[^a-zA-Z0-9_\-\.]/g, '');
|
||||||
var localFilePath = "/tmp/" + fileName;
|
|
||||||
|
var localFilePath = os.tmpdir() + "/" + fileName;
|
||||||
var writeStream = fs.createWriteStream(localFilePath);
|
var writeStream = fs.createWriteStream(localFilePath);
|
||||||
var stream = req.pipe(writeStream);
|
var stream = req.pipe(writeStream);
|
||||||
|
|
||||||
var progress_callback = function(progress_msg) {
|
var progressCallback = function(progressMsg) {
|
||||||
a.description = progress_msg;
|
// merge progress message with any other changes (size/location)
|
||||||
a.save();
|
db.Artifact.findOne({where: {
|
||||||
redis.sendMessage("update", a, a.toJSON(), req.channelId);
|
_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!
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
stream.on('finish', function() {
|
stream.on('finish', function() {
|
||||||
payloadConverter.convert(a, fileName, localFilePath, function(error, artifact) {
|
payloadConverter.convert(a, fileName, localFilePath, function(error, artifact) {
|
||||||
if (error) res.status(400).json(error);
|
if (error) res.status(400).json(error);
|
||||||
else {
|
else {
|
||||||
Space.update({
|
db.Space.update({ updated_at: new Date() }, {where: {_id: req.space._id}});
|
||||||
_id: req.space._id
|
db.unpackArtifact(artifact);
|
||||||
}, {
|
res.distributeUpdate("Artifact", artifact, true);
|
||||||
"$set": {
|
|
||||||
updated_at: new Date()
|
|
||||||
}
|
|
||||||
});
|
|
||||||
res.distributeUpdate("Artifact", artifact);
|
|
||||||
}
|
}
|
||||||
}, progress_callback);
|
}, progressCallback);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
res.status(401).json({
|
res.status(401).json({
|
||||||
@@ -160,41 +170,23 @@ router.put('/:artifact_id', function(req, res, next) {
|
|||||||
newAttr.last_update_editor_name = req.editor_name;
|
newAttr.last_update_editor_name = req.editor_name;
|
||||||
}
|
}
|
||||||
|
|
||||||
Artifact.findOneAndUpdate({
|
db.packArtifact(newAttr);
|
||||||
|
|
||||||
|
db.Artifact.update(newAttr, { where: {
|
||||||
"_id": a._id
|
"_id": a._id
|
||||||
}, {
|
}}).then(rows => {
|
||||||
"$set": newAttr
|
db.unpackArtifact(newAttr);
|
||||||
}, {
|
db.Space.update({ updated_at: new Date() }, {where: {_id: req.space._id} });
|
||||||
"new": true
|
newAttr._id = a._id;
|
||||||
}, function(err, artifact) {
|
res.distributeUpdate("Artifact", newAttr);
|
||||||
if (err) res.status(400).json(err);
|
|
||||||
else {
|
|
||||||
Space.update({
|
|
||||||
_id: req.space._id
|
|
||||||
}, {
|
|
||||||
"$set": {
|
|
||||||
updated_at: new Date()
|
|
||||||
}
|
|
||||||
});
|
|
||||||
res.distributeUpdate("Artifact", artifact);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
router.delete('/:artifact_id', function(req, res, next) {
|
router.delete('/:artifact_id', function(req, res, next) {
|
||||||
var artifact = req.artifact;
|
var artifact = req.artifact;
|
||||||
artifact.remove(function(err) {
|
db.Artifact.destroy({where: { "_id": artifact._id}}).then(() => {
|
||||||
if (err) res.status(400).json(err);
|
db.Space.update({ updated_at: new Date() }, {where: {_id: req.space._id} });
|
||||||
else {
|
res.distributeDelete("Artifact", artifact);
|
||||||
Space.update({
|
|
||||||
_id: req.space._id
|
|
||||||
}, {
|
|
||||||
"$set": {
|
|
||||||
updated_at: new Date()
|
|
||||||
}
|
|
||||||
});
|
|
||||||
res.distributeDelete("Artifact", artifact);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +1,15 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var config = require('config');
|
var config = require('config');
|
||||||
require('../../models/schema');
|
require('../../models/db');
|
||||||
|
|
||||||
var async = require('async');
|
var async = require('async');
|
||||||
var fs = require('fs');
|
var fs = require('fs');
|
||||||
var _ = require("underscore");
|
var _ = require("underscore");
|
||||||
var mongoose = require("mongoose");
|
|
||||||
var request = require('request');
|
var request = require('request');
|
||||||
var url = require("url");
|
var url = require("url");
|
||||||
var path = require("path");
|
var path = require("path");
|
||||||
var crypto = require('crypto');
|
var crypto = require('crypto');
|
||||||
var qr = require('qr-image');
|
|
||||||
var glob = require('glob');
|
var glob = require('glob');
|
||||||
var gm = require('gm');
|
var gm = require('gm');
|
||||||
|
|
||||||
@@ -40,6 +38,12 @@ var roleMapping = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
router.get('/', function(req, res, next) {
|
router.get('/', function(req, res, next) {
|
||||||
|
|
||||||
|
res.status(200).json([]);
|
||||||
|
return;
|
||||||
|
|
||||||
|
// FIXME TODO
|
||||||
|
|
||||||
var showActionForSpaces = function(err, spaceIds) {
|
var showActionForSpaces = function(err, spaceIds) {
|
||||||
var userMapping = {
|
var userMapping = {
|
||||||
'_id': 1,
|
'_id': 1,
|
||||||
@@ -134,7 +138,6 @@ router.get('/', function(req, res, next) {
|
|||||||
"$exists": 1
|
"$exists": 1
|
||||||
}
|
}
|
||||||
}).populate("space").exec(function(err, memberships) {
|
}).populate("space").exec(function(err, memberships) {
|
||||||
|
|
||||||
async.map(memberships, function(membership, memcb) {
|
async.map(memberships, function(membership, memcb) {
|
||||||
Space.getRecursiveSubspacesForSpace(membership.space, function(err, spaces) {
|
Space.getRecursiveSubspacesForSpace(membership.space, function(err, spaces) {
|
||||||
cb(null, spaces.map(function(s) {
|
cb(null, spaces.map(function(s) {
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
var config = require('config');
|
var config = require('config');
|
||||||
require('../../models/schema');
|
const db = require('../../models/db');
|
||||||
|
|
||||||
var redis = require('../../helpers/redis');
|
|
||||||
var mailer = require('../../helpers/mailer');
|
var mailer = require('../../helpers/mailer');
|
||||||
var uploader = require('../../helpers/uploader');
|
var uploader = require('../../helpers/uploader');
|
||||||
var space_render = require('../../helpers/space-render');
|
var space_render = require('../../helpers/space-render');
|
||||||
@@ -12,13 +11,11 @@ var async = require('async');
|
|||||||
var moment = require('moment');
|
var moment = require('moment');
|
||||||
var fs = require('fs');
|
var fs = require('fs');
|
||||||
var _ = require("underscore");
|
var _ = require("underscore");
|
||||||
var mongoose = require("mongoose");
|
|
||||||
var archiver = require('archiver');
|
var archiver = require('archiver');
|
||||||
var request = require('request');
|
var request = require('request');
|
||||||
var url = require("url");
|
var url = require("url");
|
||||||
var path = require("path");
|
var path = require("path");
|
||||||
var crypto = require('crypto');
|
var crypto = require('crypto');
|
||||||
var qr = require('qr-image');
|
|
||||||
var glob = require('glob');
|
var glob = require('glob');
|
||||||
var gm = require('gm');
|
var gm = require('gm');
|
||||||
var sanitizeHtml = require('sanitize-html');
|
var sanitizeHtml = require('sanitize-html');
|
||||||
@@ -49,26 +46,17 @@ var roleMapping = {
|
|||||||
|
|
||||||
router.get('/png', function(req, res, next) {
|
router.get('/png', function(req, res, next) {
|
||||||
var triggered = new Date();
|
var triggered = new Date();
|
||||||
|
|
||||||
var s3_filename = "s" + req.space._id + "/" + "thumb_" + triggered.getTime() + ".jpg";
|
var s3_filename = "s" + req.space._id + "/" + "thumb_" + triggered.getTime() + ".jpg";
|
||||||
|
|
||||||
if (!req.space.thumbnail_updated_at || req.space.thumbnail_updated_at < req.space.updated_at || !req.space.thumbnail_url) {
|
if (!req.space.thumbnail_updated_at || req.space.thumbnail_updated_at < req.space.updated_at || !req.space.thumbnail_url) {
|
||||||
|
db.Space.update({ thumbnail_updated_at: triggered }, {where : {"_id": req.space._id }});
|
||||||
Space.update({
|
|
||||||
"_id": req.space._id
|
phantom.takeScreenshot(req.space, "png", function(local_path) {
|
||||||
}, {
|
|
||||||
"$set": {
|
|
||||||
thumbnail_updated_at: triggered
|
|
||||||
}
|
|
||||||
}, function(a, b, c) {});
|
|
||||||
|
|
||||||
phantom.takeScreenshot(req.space, "png",
|
|
||||||
function(local_path) {
|
|
||||||
var localResizedFilePath = local_path + ".thumb.jpg";
|
var localResizedFilePath = local_path + ".thumb.jpg";
|
||||||
gm(local_path).resize(640, 480).quality(70.0).autoOrient().write(localResizedFilePath, function(err) {
|
gm(local_path).resize(640, 480).quality(70.0).autoOrient().write(localResizedFilePath, function(err) {
|
||||||
|
|
||||||
if (err) {
|
if (err) {
|
||||||
console.error("screenshot resize error: ", err);
|
console.error("[space screenshot] resize error: ", err);
|
||||||
res.status(500).send("Error taking screenshot.");
|
res.status(500).send("Error taking screenshot.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -76,35 +64,28 @@ router.get('/png', function(req, res, next) {
|
|||||||
uploader.uploadFile(s3_filename, "image/jpeg", localResizedFilePath, function(err, thumbnailUrl) {
|
uploader.uploadFile(s3_filename, "image/jpeg", localResizedFilePath, function(err, thumbnailUrl) {
|
||||||
|
|
||||||
if (err) {
|
if (err) {
|
||||||
console.error("screenshot s3 upload error. filename: " + s3_filename + " details: ", err);
|
console.error("[space screenshot] upload error. filename: " + s3_filename + " details: ", err);
|
||||||
res.status(500).send("Error uploading screenshot.");
|
res.status(500).send("Error uploading screenshot.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var oldUrl = req.space.thumbnail_url;
|
var oldUrl = req.space.thumbnail_url;
|
||||||
|
|
||||||
Space.update({
|
db.Space.update({ thumbnail_url: thumbnailUrl }, {where : {"_id": req.space._id }}).then(() => {
|
||||||
"_id": req.space._id
|
|
||||||
}, {
|
|
||||||
"$set": {
|
|
||||||
thumbnail_url: thumbnailUrl
|
|
||||||
}
|
|
||||||
}, function(a, b, c) {
|
|
||||||
res.redirect(thumbnailUrl);
|
res.redirect(thumbnailUrl);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (oldUrl) {
|
if (oldUrl) {
|
||||||
var oldPath = url.parse(oldUrl).pathname;
|
var oldPath = url.parse(oldUrl).pathname;
|
||||||
uploader.removeFile(oldPath, function(err, res) {});
|
uploader.removeFile(oldPath, function(err, res) {});
|
||||||
}
|
}
|
||||||
fs.unlink(local_path);
|
fs.unlinkSync(local_path);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
fs.unlink(localResizedFilePath);
|
fs.unlinkSync(localResizedFilePath);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
}
|
}
|
||||||
@@ -113,7 +94,7 @@ router.get('/png', function(req, res, next) {
|
|||||||
},
|
},
|
||||||
function() {
|
function() {
|
||||||
// on_error
|
// on_error
|
||||||
console.error("phantom could not create screenshot for space " + req.space_id);
|
console.error("[space screenshot] could not create screenshot for space " + req.space_id);
|
||||||
res.status(404).send("Not found");
|
res.status(404).send("Not found");
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@@ -125,77 +106,6 @@ function make_export_filename(space, extension) {
|
|||||||
return space.name.replace(/[^\w]/g, '') + "-" + space._id + "-" + moment().format("YYYYMMDD-HH-mm-ss") + "." + extension;
|
return space.name.replace(/[^\w]/g, '') + "-" + space._id + "-" + moment().format("YYYYMMDD-HH-mm-ss") + "." + extension;
|
||||||
}
|
}
|
||||||
|
|
||||||
router.get('/list', function(req, res, next) {
|
|
||||||
|
|
||||||
if (req.user) {
|
|
||||||
if (req.spaceRole == "admin" || req.spaceRole == "editor") {
|
|
||||||
|
|
||||||
if (req.space.space_type == "space") {
|
|
||||||
Artifact.find({
|
|
||||||
space_id: req.space._id
|
|
||||||
}).exec(function(err, artifacts) {
|
|
||||||
async.map(artifacts, function(a, cb) {
|
|
||||||
if (a.user_id) {
|
|
||||||
User.findOne({
|
|
||||||
"_id": a.user_id
|
|
||||||
}).exec(function(err, user) {
|
|
||||||
a.user = user;
|
|
||||||
|
|
||||||
if (a.last_update_user_id) {
|
|
||||||
User.findOne({
|
|
||||||
"_id": a.last_update_user_id
|
|
||||||
}).exec(function(err, updateUser) {
|
|
||||||
a.update_user = updateUser;
|
|
||||||
cb(null, a);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
cb(null, a);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
cb(null, a);
|
|
||||||
}
|
|
||||||
}, function(err, mappedArtifacts) {
|
|
||||||
|
|
||||||
req.space.artifacts = mappedArtifacts.map(function(a) {
|
|
||||||
a.description = sanitizeHtml(a.description, {
|
|
||||||
allowedTags: [],
|
|
||||||
allowedAttributes: []
|
|
||||||
});
|
|
||||||
|
|
||||||
if (a.payload_uri) {
|
|
||||||
var parsed = url.parse(a.payload_uri);
|
|
||||||
var fileName = path.basename(parsed.pathname) || "file.bin";
|
|
||||||
a.filename = fileName;
|
|
||||||
}
|
|
||||||
|
|
||||||
return a;
|
|
||||||
});
|
|
||||||
|
|
||||||
res.render('artifact_list', {
|
|
||||||
space: req.space
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
Space.getRecursiveSubspacesForSpace(req.space, (err, subspaces) => {
|
|
||||||
res.render('space_list', {
|
|
||||||
subspaces: subspaces.map((s) => {
|
|
||||||
s.ae_link = config.endpoint + '/s/' + s.edit_hash + (s.edit_slug ? ('-'+s.edit_slug) : '')
|
|
||||||
return s;
|
|
||||||
}),
|
|
||||||
space: req.space
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
res.sendStatus(403);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
res.sendStatus(403);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
router.get('/pdf', function(req, res, next) {
|
router.get('/pdf', function(req, res, next) {
|
||||||
var s3_filename = make_export_filename(req.space, "pdf");
|
var s3_filename = make_export_filename(req.space, "pdf");
|
||||||
|
|
||||||
@@ -204,7 +114,12 @@ router.get('/pdf', function(req, res, next) {
|
|||||||
res.status(201).json({
|
res.status(201).json({
|
||||||
url: url
|
url: url
|
||||||
});
|
});
|
||||||
fs.unlink(local_path);
|
fs.unlink(local_path, function(){
|
||||||
|
if (err) console.log('unlink', err);
|
||||||
|
else {
|
||||||
|
console.log('unlink', local_path);
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}, (err) => {
|
}, (err) => {
|
||||||
res.status(500).json({
|
res.status(500).json({
|
||||||
@@ -329,36 +244,13 @@ router.get('/zip', function(req, res, next) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
router.get('/html', function(req, res) {
|
router.get('/html', function(req, res) {
|
||||||
Artifact.find({
|
db.Artifact.findAll({where: {
|
||||||
space_id: req.space._id
|
space_id: req.space._id
|
||||||
}, function(err, artifacts) {
|
}}).then(function(artifacts) {
|
||||||
var space = req.space;
|
var space = req.space;
|
||||||
res.send(space_render.render_space_as_html(space, artifacts));
|
res.send(space_render.render_space_as_html(space, artifacts));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
router.get('/path', (req, res) => {
|
|
||||||
// build up a breadcrumb trail (path)
|
|
||||||
var path = [];
|
|
||||||
var buildPath = (space) => {
|
|
||||||
if (space.parent_space_id) {
|
|
||||||
Space.findOne({
|
|
||||||
"_id": space.parent_space_id
|
|
||||||
}, (err, parentSpace) => {
|
|
||||||
if (space._id == parentSpace._id) {
|
|
||||||
console.log("error: circular parent reference for space " + space._id);
|
|
||||||
res.send("error: circular reference");
|
|
||||||
} else {
|
|
||||||
path.push(parentSpace);
|
|
||||||
buildPath(parentSpace);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// reached the top
|
|
||||||
res.json(path.reverse());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
buildPath(req.space);
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
|
|
||||||
|
|||||||
@@ -1,57 +1,31 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
var config = require('config');
|
var config = require('config');
|
||||||
require('../../models/schema');
|
const db = require('../../models/db');
|
||||||
|
const Sequelize = require('sequelize');
|
||||||
|
const Op = Sequelize.Op;
|
||||||
|
const uuidv4 = require('uuid/v4');
|
||||||
|
|
||||||
var redis = require('../../helpers/redis');
|
var redis = require('../../helpers/redis');
|
||||||
var mailer = require('../../helpers/mailer');
|
var mailer = require('../../helpers/mailer');
|
||||||
var uploader = require('../../helpers/uploader');
|
|
||||||
var space_render = require('../../helpers/space-render');
|
|
||||||
var phantom = require('../../helpers/phantom');
|
|
||||||
|
|
||||||
var async = require('async');
|
var async = require('async');
|
||||||
var fs = require('fs');
|
var fs = require('fs');
|
||||||
var _ = require("underscore");
|
var _ = require("underscore");
|
||||||
var mongoose = require("mongoose");
|
|
||||||
var archiver = require('archiver');
|
|
||||||
var request = require('request');
|
var request = require('request');
|
||||||
var url = require("url");
|
var url = require("url");
|
||||||
var path = require("path");
|
var path = require("path");
|
||||||
var crypto = require('crypto');
|
|
||||||
var qr = require('qr-image');
|
|
||||||
var glob = require('glob');
|
var glob = require('glob');
|
||||||
var gm = require('gm');
|
var crypto = require('crypto');
|
||||||
|
|
||||||
var express = require('express');
|
var express = require('express');
|
||||||
var router = express.Router({mergeParams: true});
|
var router = express.Router({mergeParams: true});
|
||||||
|
|
||||||
// JSON MAPPINGS
|
|
||||||
var userMapping = {
|
|
||||||
_id: 1,
|
|
||||||
nickname: 1,
|
|
||||||
email: 1,
|
|
||||||
avatar_thumb_uri: 1
|
|
||||||
};
|
|
||||||
|
|
||||||
var spaceMapping = {
|
|
||||||
_id: 1,
|
|
||||||
name: 1,
|
|
||||||
thumbnail_url: 1
|
|
||||||
};
|
|
||||||
|
|
||||||
var roleMapping = {
|
|
||||||
"none": 0,
|
|
||||||
"viewer": 1,
|
|
||||||
"editor": 2,
|
|
||||||
"admin": 3
|
|
||||||
}
|
|
||||||
|
|
||||||
router.get('/', function(req, res, next) {
|
router.get('/', function(req, res, next) {
|
||||||
Membership
|
db.Membership
|
||||||
.find({
|
.findAll({where: {
|
||||||
space: req.space._id
|
space_id: req.space._id
|
||||||
})
|
}, include: ['user']})
|
||||||
.populate("user")
|
.then(memberships => {
|
||||||
.exec(function(err, memberships) {
|
|
||||||
res.status(200).json(memberships);
|
res.status(200).json(memberships);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -59,65 +33,66 @@ router.get('/', function(req, res, next) {
|
|||||||
router.post('/', function(req, res, next) {
|
router.post('/', function(req, res, next) {
|
||||||
if (req.spaceRole == "admin") {
|
if (req.spaceRole == "admin") {
|
||||||
var attrs = req.body;
|
var attrs = req.body;
|
||||||
attrs['space'] = req.space._id;
|
attrs.space_id = req.space._id;
|
||||||
attrs['state'] = "pending";
|
attrs.state = "pending";
|
||||||
var membership = new Membership(attrs);
|
attrs._id = uuidv4();
|
||||||
|
var membership = attrs;
|
||||||
|
|
||||||
var msg = attrs.personal_message;
|
var msg = attrs.personal_message;
|
||||||
|
|
||||||
if (membership.email_invited != req.user.email) {
|
if (membership.email_invited != req.user.email) {
|
||||||
User.findOne({
|
db.User.findOne({where:{
|
||||||
"email": membership.email_invited
|
"email": membership.email_invited
|
||||||
}, function(err, user) {
|
}}).then(function(user) {
|
||||||
|
|
||||||
|
// existing user? then immediately activate membership
|
||||||
if (user) {
|
if (user) {
|
||||||
membership.user = user;
|
membership.user_id = user._id;
|
||||||
membership.state = "active";
|
membership.state = "active";
|
||||||
} else {
|
} else {
|
||||||
|
// if not, invite via email and invite code
|
||||||
membership.code = crypto.randomBytes(64).toString('hex').substring(0, 12);
|
membership.code = crypto.randomBytes(64).toString('hex').substring(0, 12);
|
||||||
}
|
}
|
||||||
|
|
||||||
membership.save(function(err) {
|
db.Membership.create(membership).then(function() {
|
||||||
if (err) res.sendStatus(400);
|
var accept_link = config.endpoint + "/accept/" + membership._id + "?code=" + membership.code;
|
||||||
else {
|
|
||||||
var accept_link = config.endpoint + "/accept/" + membership._id + "?code=" + membership.code;
|
|
||||||
|
|
||||||
if (user) {
|
if (user) {
|
||||||
accept_link = config.endpoint + "/" + req.space.space_type + "s/" + req.space._id;
|
accept_link = config.endpoint + "/" + req.space.space_type + "s/" + req.space._id;
|
||||||
}
|
|
||||||
|
|
||||||
var openText = req.i18n.__("space_invite_membership_action");
|
|
||||||
if (user) {
|
|
||||||
req.i18n.__("open");
|
|
||||||
}
|
|
||||||
|
|
||||||
const name = req.user.nickname || req.user.email
|
|
||||||
const subject = (req.space.space_type == "space") ? req.i18n.__("space_invite_membership_subject", name, req.space.name) : req.i18n.__("folder_invite_membership_subject", req.user.nickname, req.space.name)
|
|
||||||
const body = (req.space.space_type == "space") ? req.i18n.__("space_invite_membership_body", name, req.space.name) : req.i18n.__("folder_invite_membership_body", req.user.nickname, req.space.name)
|
|
||||||
|
|
||||||
mailer.sendMail(
|
|
||||||
membership.email_invited, subject, body, {
|
|
||||||
messsage: msg,
|
|
||||||
action: {
|
|
||||||
link: accept_link,
|
|
||||||
name: openText
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
res.status(201).json(membership);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var openText = req.i18n.__("space_invite_membership_action");
|
||||||
|
if (user) {
|
||||||
|
req.i18n.__("open");
|
||||||
|
}
|
||||||
|
|
||||||
|
const name = req.user.nickname || req.user.email
|
||||||
|
const subject = (req.space.space_type == "space") ? req.i18n.__("space_invite_membership_subject", name, req.space.name) : req.i18n.__("folder_invite_membership_subject", req.user.nickname, req.space.name)
|
||||||
|
const body = (req.space.space_type == "space") ? req.i18n.__("space_invite_membership_body", name, req.space.name) : req.i18n.__("folder_invite_membership_body", req.user.nickname, req.space.name)
|
||||||
|
|
||||||
|
mailer.sendMail(
|
||||||
|
membership.email_invited, subject, body, {
|
||||||
|
messsage: msg,
|
||||||
|
action: {
|
||||||
|
link: accept_link,
|
||||||
|
name: openText
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
res.status(201).json(membership);
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
res.status(400).json({
|
res.status(400).json({
|
||||||
"error": "user already in space"
|
"error": "This email is already included in the Space memberships."
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
res.status(403).json({
|
res.status(403).json({
|
||||||
"error": "not_permitted"
|
"error": "Only administrators can do that."
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -125,19 +100,20 @@ router.post('/', function(req, res, next) {
|
|||||||
router.put('/:membership_id', function(req, res, next) {
|
router.put('/:membership_id', function(req, res, next) {
|
||||||
if (req.user) {
|
if (req.user) {
|
||||||
if (req.spaceRole == "admin") {
|
if (req.spaceRole == "admin") {
|
||||||
Membership.findOne({
|
db.Membership.findOne({ where: {
|
||||||
_id: req.params.membership_id
|
_id: req.params.membership_id
|
||||||
}, function(err, mem) {
|
}}).then(function(mem) {
|
||||||
if (err) res.sendStatus(400);
|
if (mem) {
|
||||||
else {
|
// is the user trying to change their own role?
|
||||||
if (mem) {
|
if (mem.user_id == req.user._id) {
|
||||||
|
res.status(400).json({
|
||||||
|
"error": "Cannot change your own role."
|
||||||
|
});
|
||||||
|
} else {
|
||||||
var attrs = req.body;
|
var attrs = req.body;
|
||||||
mem.role = attrs.role;
|
mem.role = attrs.role;
|
||||||
mem.save(function(err) {
|
mem.save(function() {
|
||||||
if (err) res.sendStatus(400);
|
res.status(201).json(mem);
|
||||||
else {
|
|
||||||
res.status(201).json(mem);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -151,21 +127,25 @@ router.put('/:membership_id', function(req, res, next) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
router.delete('/:membership_id', function(req, res, next) {
|
router.delete('/:membership_id', function(req, res, next) {
|
||||||
if (req.user) {
|
if (req.user && req.spaceRole == 'admin') {
|
||||||
Membership.findOne({
|
db.Membership.count({ where: {
|
||||||
_id: req.params.membership_id
|
space_id: req.space._id,
|
||||||
}, function(err, mem) {
|
role: "admin"
|
||||||
if (err) res.sendStatus(400);
|
}}).then(function(adminCount) {
|
||||||
else {
|
db.Membership.findOne({ where: {
|
||||||
mem.remove(function(err) {
|
_id: req.params.membership_id
|
||||||
if (err) {
|
}}).then(function(mem) {
|
||||||
res.status(400).json(err);
|
// deleting an admin? need at least 1
|
||||||
} else {
|
if (mem.role != "admin" || adminCount > 1) {
|
||||||
// FIXME might need to delete the user?
|
mem.destroy().then(function() {
|
||||||
res.sendStatus(204);
|
res.sendStatus(204);
|
||||||
}
|
});
|
||||||
});
|
} else {
|
||||||
}
|
res.status(400).json({
|
||||||
|
"error": "Space needs at least one administrator."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
res.sendStatus(403);
|
res.sendStatus(403);
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
var config = require('config');
|
var config = require('config');
|
||||||
require('../../models/schema');
|
const db = require('../../models/db');
|
||||||
|
const Sequelize = require('sequelize');
|
||||||
|
const Op = Sequelize.Op;
|
||||||
|
const uuidv4 = require('uuid/v4');
|
||||||
|
|
||||||
var redis = require('../../helpers/redis');
|
var redis = require('../../helpers/redis');
|
||||||
var mailer = require('../../helpers/mailer');
|
var mailer = require('../../helpers/mailer');
|
||||||
@@ -11,15 +14,12 @@ var phantom = require('../../helpers/phantom');
|
|||||||
var async = require('async');
|
var async = require('async');
|
||||||
var fs = require('fs');
|
var fs = require('fs');
|
||||||
var _ = require("underscore");
|
var _ = require("underscore");
|
||||||
var mongoose = require("mongoose");
|
|
||||||
var archiver = require('archiver');
|
var archiver = require('archiver');
|
||||||
var request = require('request');
|
var request = require('request');
|
||||||
var url = require("url");
|
var url = require("url");
|
||||||
var path = require("path");
|
var path = require("path");
|
||||||
var crypto = require('crypto');
|
var crypto = require('crypto');
|
||||||
var qr = require('qr-image');
|
|
||||||
var glob = require('glob');
|
var glob = require('glob');
|
||||||
var gm = require('gm');
|
|
||||||
|
|
||||||
var express = require('express');
|
var express = require('express');
|
||||||
var router = express.Router({mergeParams: true});
|
var router = express.Router({mergeParams: true});
|
||||||
@@ -49,90 +49,44 @@ var roleMapping = {
|
|||||||
// MESSAGES
|
// MESSAGES
|
||||||
|
|
||||||
router.get('/', function(req, res, next) {
|
router.get('/', function(req, res, next) {
|
||||||
Message.find({
|
db.Message.findAll({where:{
|
||||||
space: req.space._id
|
space_id: req.space._id
|
||||||
}).populate('user', userMapping).exec(function(err, messages) {
|
}, include: ['user']})
|
||||||
res.status(200).json(messages);
|
.then(function(messages) {
|
||||||
});
|
res.status(200).json(messages);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post('/', function(req, res, next) {
|
router.post('/', function(req, res, next) {
|
||||||
var attrs = req.body;
|
var attrs = req.body;
|
||||||
attrs.space = req.space;
|
attrs.space_id = req.space._id;
|
||||||
|
|
||||||
if (req.user) {
|
if (req.user) {
|
||||||
attrs.user = req.user;
|
attrs.user = req.user;
|
||||||
|
attrs.user_id = req.user._id;
|
||||||
} else {
|
} else {
|
||||||
attrs.user = null;
|
attrs.user = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var msg = new Message(attrs);
|
var msg = attrs;
|
||||||
msg.save(function(err) {
|
msg._id = uuidv4();
|
||||||
if (err) res.status(400).json(erra);
|
|
||||||
else {
|
|
||||||
if (msg.message.length <= 1) return;
|
|
||||||
|
|
||||||
Membership
|
db.Message.create(msg).then(function() {
|
||||||
.find({
|
if (msg.message.length <= 1) return;
|
||||||
space: req.space,
|
// TODO reimplement notifications
|
||||||
user: {
|
res.distributeCreate("Message", msg);
|
||||||
"$exists": true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.populate('user')
|
|
||||||
.exec(function(err, memberships) {
|
|
||||||
var users = memberships.map(function(m) {
|
|
||||||
return m.user;
|
|
||||||
});
|
|
||||||
users.forEach((user) => {
|
|
||||||
if (user.preferences.email_notifications) {
|
|
||||||
redis.isOnlineInSpace(user, req.space, function(err, online) {
|
|
||||||
if (!online) {
|
|
||||||
var nickname = msg.editor_name;
|
|
||||||
if (req.user) {
|
|
||||||
nickname = req.user.nickname;
|
|
||||||
}
|
|
||||||
mailer.sendMail(
|
|
||||||
user.email,
|
|
||||||
req.i18n.__("space_message_subject", req.space.name),
|
|
||||||
req.i18n.__("space_message_body", nickname, req.space.name), {
|
|
||||||
message: msg.message,
|
|
||||||
action: {
|
|
||||||
link: config.endpoint + "/spaces/" + req.space._id.toString(),
|
|
||||||
name: req.i18n.__("open")
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
console.log("not sending message to user: is online.");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
console.log("not sending message to user: is disabled notifications.");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
res.distributeCreate("Message", msg);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
router.delete('/:message_id', function(req, res, next) {
|
router.delete('/:message_id', function(req, res, next) {
|
||||||
Message.findOne({
|
db.Message.findOne({where:{
|
||||||
"_id": req.params.message_id
|
"_id": req.params.message_id
|
||||||
}, function(err, msg) {
|
}}).then(function(msg) {
|
||||||
if (!msg) {
|
if (!msg) {
|
||||||
res.sendStatus(404);
|
res.sendStatus(404);
|
||||||
} else {
|
} else {
|
||||||
msg.remove(function(err) {
|
msg.destroy().then(function() {
|
||||||
if (err) res.status(400).json(err);
|
res.distributeDelete("Message", msg);
|
||||||
else {
|
|
||||||
if (msg) {
|
|
||||||
res.distributeDelete("Message", msg);
|
|
||||||
} else {
|
|
||||||
res.sendStatus(404);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
var config = require('config');
|
var config = require('config');
|
||||||
require('../../models/schema');
|
const os = require('os');
|
||||||
|
const db = require('../../models/db');
|
||||||
|
const Sequelize = require('sequelize');
|
||||||
|
const Op = Sequelize.Op;
|
||||||
|
const uuidv4 = require('uuid/v4');
|
||||||
|
|
||||||
var redis = require('../../helpers/redis');
|
var redis = require('../../helpers/redis');
|
||||||
var mailer = require('../../helpers/mailer');
|
var mailer = require('../../helpers/mailer');
|
||||||
@@ -14,13 +18,10 @@ var slug = require('slug');
|
|||||||
var fs = require('fs');
|
var fs = require('fs');
|
||||||
var async = require('async');
|
var async = require('async');
|
||||||
var _ = require("underscore");
|
var _ = require("underscore");
|
||||||
var mongoose = require("mongoose");
|
|
||||||
var archiver = require('archiver');
|
|
||||||
var request = require('request');
|
var request = require('request');
|
||||||
var url = require("url");
|
var url = require("url");
|
||||||
var path = require("path");
|
var path = require("path");
|
||||||
var crypto = require('crypto');
|
var crypto = require('crypto');
|
||||||
var qr = require('qr-image');
|
|
||||||
var glob = require('glob');
|
var glob = require('glob');
|
||||||
var gm = require('gm');
|
var gm = require('gm');
|
||||||
const exec = require('child_process');
|
const exec = require('child_process');
|
||||||
@@ -41,185 +42,130 @@ var spaceMapping = {
|
|||||||
thumbnail_url: 1
|
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) {
|
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) {
|
if (!req.user) {
|
||||||
res.status(403).json({
|
res.status(403).json({
|
||||||
error: "auth required"
|
error: "auth required"
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
if (req.query.writablefolders) {
|
if (req.query.search) {
|
||||||
Membership.find({
|
db.Membership.findAll({where:{
|
||||||
user: req.user._id
|
user_id: req.user._id
|
||||||
}, (err, memberships) => {
|
}}).then(memberships => {
|
||||||
|
// search for spaces
|
||||||
var validMemberships = memberships.filter((m) => {
|
|
||||||
if (!m.space || (m.space == "undefined"))
|
|
||||||
return false;
|
|
||||||
else
|
|
||||||
return mongoose.Types.ObjectId.isValid(m.space.toString());
|
|
||||||
});
|
|
||||||
|
|
||||||
var editorMemberships = validMemberships.filter((m) => {
|
|
||||||
return (m.role == "editor") || (m.role == "admin")
|
|
||||||
});
|
|
||||||
|
|
||||||
var spaceIds = editorMemberships.map(function(m) {
|
|
||||||
return new mongoose.Types.ObjectId(m.space);
|
|
||||||
});
|
|
||||||
|
|
||||||
var q = {
|
|
||||||
"space_type": "folder",
|
|
||||||
"$or": [{
|
|
||||||
"creator": req.user._id
|
|
||||||
}, {
|
|
||||||
"_id": {
|
|
||||||
"$in": spaceIds
|
|
||||||
},
|
|
||||||
"creator": {
|
|
||||||
"$ne": req.user._id
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
};
|
|
||||||
|
|
||||||
Space
|
|
||||||
.find(q)
|
|
||||||
.populate('creator', userMapping)
|
|
||||||
.exec(function(err, spaces) {
|
|
||||||
if (err) console.error(err);
|
|
||||||
var updatedSpaces = spaces.map(function(s) {
|
|
||||||
var spaceObj = s.toObject();
|
|
||||||
return spaceObj;
|
|
||||||
});
|
|
||||||
|
|
||||||
async.map(spaces, (space, cb) => {
|
|
||||||
Space.getRecursiveSubspacesForSpace(space, (err, spaces) => {
|
|
||||||
var allSpaces = spaces;
|
|
||||||
cb(err, allSpaces);
|
|
||||||
})
|
|
||||||
}, (err, spaces) => {
|
|
||||||
|
|
||||||
var allSpaces = _.flatten(spaces);
|
|
||||||
|
|
||||||
var onlyFolders = _.filter(allSpaces, (s) => {
|
|
||||||
return s.space_type == "folder";
|
|
||||||
})
|
|
||||||
var uniqueFolders = _.unique(onlyFolders, (s) => {
|
|
||||||
return s._id.toString();
|
|
||||||
})
|
|
||||||
|
|
||||||
res.status(200).json(uniqueFolders);
|
|
||||||
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
} else if (req.query.search) {
|
|
||||||
|
|
||||||
Membership.find({
|
|
||||||
user: req.user._id
|
|
||||||
}, function(err, memberships) {
|
|
||||||
|
|
||||||
var validMemberships = memberships.filter(function(m) {
|
var validMemberships = memberships.filter(function(m) {
|
||||||
if (!m.space || (m.space == "undefined"))
|
if (!m.space_id || (m.space_id == "undefined"))
|
||||||
return false;
|
return false;
|
||||||
else
|
else
|
||||||
return mongoose.Types.ObjectId.isValid(m.space.toString());
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
var spaceIds = validMemberships.map(function(m) {
|
var spaceIds = validMemberships.map(function(m) {
|
||||||
return new mongoose.Types.ObjectId(m.space);
|
return m.space_id;
|
||||||
});
|
});
|
||||||
|
|
||||||
var q = {
|
// TODO FIXME port
|
||||||
"$or": [{"creator": req.user._id},
|
var q = { where: {
|
||||||
{"_id": {"$in": spaceIds}},
|
[Op.or]: [{"creator_id": req.user._id},
|
||||||
{"parent_space_id": {"$in": spaceIds}}],
|
{"_id": {[Op.in]: spaceIds}},
|
||||||
name: new RegExp(req.query.search, "i")
|
{"parent_space_id": {[Op.in]: spaceIds}}],
|
||||||
};
|
name: {[Op.like]: "%"+req.query.search+"%"}
|
||||||
|
}, include: [db.CreatorSafeInclude(db)]};
|
||||||
|
|
||||||
Space
|
db.Space
|
||||||
.find(q)
|
.findAll(q)
|
||||||
.populate('creator', userMapping)
|
.then(function(spaces) {
|
||||||
.exec(function(err, spaces) {
|
|
||||||
if (err) console.error(err);
|
|
||||||
var updatedSpaces = spaces.map(function(s) {
|
|
||||||
var spaceObj = s.toObject();
|
|
||||||
return spaceObj;
|
|
||||||
});
|
|
||||||
res.status(200).json(spaces);
|
res.status(200).json(spaces);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
} else if (req.query.parent_space_id && req.query.parent_space_id != req.user.home_folder_id) {
|
} else if (req.query.parent_space_id && req.query.parent_space_id != req.user.home_folder_id) {
|
||||||
|
// list spaces in a folder
|
||||||
Space
|
|
||||||
.findOne({
|
listSpacesInFolder(req, res, req.query.parent_space_id);
|
||||||
_id: req.query.parent_space_id
|
|
||||||
})
|
|
||||||
.populate('creator', userMapping)
|
|
||||||
.exec(function(err, space) {
|
|
||||||
if (space) {
|
|
||||||
Space.roleInSpace(space, req.user, function(err, role) {
|
|
||||||
|
|
||||||
if (role == "none") {
|
|
||||||
if(space.access_mode == "public") {
|
|
||||||
role = "viewer";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (role != "none") {
|
|
||||||
Space
|
|
||||||
.find({
|
|
||||||
parent_space_id: req.query.parent_space_id
|
|
||||||
})
|
|
||||||
.populate('creator', userMapping)
|
|
||||||
.exec(function(err, spaces) {
|
|
||||||
res.status(200).json(spaces);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
res.status(403).json({"error": "no authorized"});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
res.status(404).json({"error": "space not found"});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
Membership.find({
|
// list home folder and spaces/folders that the user is a member of
|
||||||
user: req.user._id
|
|
||||||
}, function(err, memberships) {
|
db.Membership.findAll({ where: {
|
||||||
|
user_id: req.user._id
|
||||||
|
}}).then(memberships => {
|
||||||
|
if (!memberships) memberships = [];
|
||||||
|
|
||||||
var validMemberships = memberships.filter(function(m) {
|
var validMemberships = memberships.filter(function(m) {
|
||||||
if (!m.space || (m.space == "undefined"))
|
if (!m.space_id || (m.space_id == "undefined"))
|
||||||
return false;
|
return false;
|
||||||
else
|
return true;
|
||||||
return mongoose.Types.ObjectId.isValid(m.space.toString());
|
|
||||||
});
|
});
|
||||||
|
|
||||||
var spaceIds = validMemberships.map(function(m) {
|
var spaceIds = validMemberships.map(function(m) {
|
||||||
return new mongoose.Types.ObjectId(m.space);
|
return m.space_id;
|
||||||
});
|
});
|
||||||
|
|
||||||
var q = {
|
var q = {
|
||||||
"$or": [{
|
[Op.or]: [{
|
||||||
"creator": req.user._id,
|
"creator_id": req.user._id,
|
||||||
"parent_space_id": req.user.home_folder_id
|
"parent_space_id": req.user.home_folder_id
|
||||||
}, {
|
}, {
|
||||||
"_id": {
|
"_id": {
|
||||||
"$in": spaceIds
|
[Op.in]: spaceIds
|
||||||
},
|
},
|
||||||
"creator": {
|
"creator_id": {
|
||||||
"$ne": req.user._id
|
[Op.ne]: req.user._id
|
||||||
}
|
}
|
||||||
}]
|
}]
|
||||||
};
|
};
|
||||||
|
|
||||||
Space
|
db.Space
|
||||||
.find(q)
|
.findAll({where: q, include: [db.CreatorSafeInclude(db)]})
|
||||||
.populate('creator', userMapping)
|
.then(function(spaces) {
|
||||||
.exec(function(err, spaces) {
|
|
||||||
if (err) console.error(err);
|
|
||||||
var updatedSpaces = spaces.map(function(s) {
|
var updatedSpaces = spaces.map(function(s) {
|
||||||
var spaceObj = s.toObject();
|
var spaceObj = db.spaceToObject(s);
|
||||||
return spaceObj;
|
return spaceObj;
|
||||||
});
|
});
|
||||||
res.status(200).json(spaces);
|
res.status(200).json(spaces);
|
||||||
@@ -229,47 +175,47 @@ router.get('/', function(req, res, next) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// create a space
|
||||||
router.post('/', function(req, res, next) {
|
router.post('/', function(req, res, next) {
|
||||||
if (req.user) {
|
if (req.user) {
|
||||||
var attrs = req.body;
|
var attrs = req.body;
|
||||||
|
|
||||||
var createSpace = () => {
|
var createSpace = () => {
|
||||||
|
attrs._id = uuidv4();
|
||||||
attrs.creator = req.user;
|
attrs.creator_id = req.user._id;
|
||||||
attrs.edit_hash = crypto.randomBytes(64).toString('hex').substring(0, 7);
|
attrs.edit_hash = crypto.randomBytes(64).toString('hex').substring(0, 7);
|
||||||
attrs.edit_slug = slug(attrs.name);
|
attrs.edit_slug = attrs.edit_slug || slug(attrs.name);
|
||||||
|
attrs.access_mode = "private";
|
||||||
|
|
||||||
var space = new Space(attrs);
|
db.Space.create(attrs).then(createdSpace => {
|
||||||
space.save(function(err, createdSpace) {
|
res.status(201).json(createdSpace);
|
||||||
if (err) res.sendStatus(400);
|
|
||||||
else {
|
// create initial admin membership
|
||||||
var membership = new Membership({
|
var membership = {
|
||||||
user: req.user,
|
_id: uuidv4(),
|
||||||
space: createdSpace,
|
user_id: req.user._id,
|
||||||
role: "admin"
|
space_id: attrs._id,
|
||||||
});
|
role: "admin",
|
||||||
membership.save(function(err, createdTeam) {
|
state: "active"
|
||||||
if (err) {
|
};
|
||||||
res.status(400).json(err);
|
|
||||||
} else {
|
db.Membership.create(membership).then(() => {
|
||||||
res.status(201).json(createdSpace);
|
res.status(201).json(createdSpace);
|
||||||
}
|
});
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (attrs.parent_space_id) {
|
if (attrs.parent_space_id) {
|
||||||
Space.findOne({
|
db.Space.findOne({ where: {
|
||||||
"_id": attrs.parent_space_id
|
"_id": attrs.parent_space_id
|
||||||
}).populate('creator', userMapping).exec((err, parentSpace) => {
|
}}).then(parentSpace => {
|
||||||
if (parentSpace) {
|
if (parentSpace) {
|
||||||
Space.roleInSpace(parentSpace, req.user, (err, role) => {
|
db.getUserRoleInSpace(parentSpace, req.user, (role) => {
|
||||||
if ((role == "editor") || (role == "admin")) {
|
if ((role == "editor") || (role == "admin")) {
|
||||||
createSpace();
|
createSpace();
|
||||||
} else {
|
} else {
|
||||||
res.status(403).json({
|
res.status(403).json({
|
||||||
"error": "not editor in parent Space"
|
"error": "not editor in parent Space. role: "+role
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -280,6 +226,7 @@ router.post('/', function(req, res, next) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
attrs.parent_space_id = req.user.home_folder_id;
|
||||||
createSpace();
|
createSpace();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -292,6 +239,30 @@ router.get('/:id', function(req, res, next) {
|
|||||||
res.status(200).json(req.space);
|
res.status(200).json(req.space);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
router.get('/:id/path', (req, res) => {
|
||||||
|
// build up a breadcrumb trail (path)
|
||||||
|
var path = [];
|
||||||
|
var buildPath = (space) => {
|
||||||
|
if (space.parent_space_id) {
|
||||||
|
db.Space.findOne({ where: {
|
||||||
|
"_id": space.parent_space_id
|
||||||
|
}}).then(parentSpace => {
|
||||||
|
if (space._id == parentSpace._id) {
|
||||||
|
console.error("error: circular parent reference for space " + space._id);
|
||||||
|
res.send("error: circular reference");
|
||||||
|
} else {
|
||||||
|
path.push(parentSpace);
|
||||||
|
buildPath(parentSpace);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// reached the top
|
||||||
|
res.json(path.reverse());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buildPath(req.space);
|
||||||
|
});
|
||||||
|
|
||||||
router.put('/:id', function(req, res) {
|
router.put('/:id', function(req, res) {
|
||||||
var space = req.space;
|
var space = req.space;
|
||||||
var newAttr = req.body;
|
var newAttr = req.body;
|
||||||
@@ -305,27 +276,33 @@ router.put('/:id', function(req, res) {
|
|||||||
newAttr.edit_slug = slug(newAttr['name']);
|
newAttr.edit_slug = slug(newAttr['name']);
|
||||||
|
|
||||||
delete newAttr['_id'];
|
delete newAttr['_id'];
|
||||||
delete newAttr['editor_name'];
|
|
||||||
delete newAttr['creator'];
|
delete newAttr['creator'];
|
||||||
|
delete newAttr['creator_id'];
|
||||||
|
delete newAttr['space_type'];
|
||||||
|
|
||||||
Space.findOneAndUpdate({
|
if (req['spaceRole'] != "admin") {
|
||||||
|
delete newAttr['access_mode']
|
||||||
|
delete newAttr['password']
|
||||||
|
delete newAttr['edit_hash']
|
||||||
|
delete newAttr['edit_slug']
|
||||||
|
delete newAttr['editors_locking']
|
||||||
|
}
|
||||||
|
|
||||||
|
db.Space.update(newAttr, {where: {
|
||||||
"_id": space._id
|
"_id": space._id
|
||||||
}, {
|
}}).then(rows => {
|
||||||
"$set": newAttr
|
db.Space.findOne({ where: {
|
||||||
}, {
|
"_id": space._id
|
||||||
"new": true
|
}}).then(space => {
|
||||||
}, function(err, space) {
|
|
||||||
if (err) res.status(400).json(err);
|
|
||||||
else {
|
|
||||||
res.distributeUpdate("Space", space);
|
res.distributeUpdate("Space", space);
|
||||||
}
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post('/:id/background', function(req, res, next) {
|
router.post('/:id/background', function(req, res, next) {
|
||||||
var space = req.space;
|
var space = req.space;
|
||||||
var newDate = new Date();
|
var newDate = new Date();
|
||||||
var fileName = (req.query.filename || "upload.bin").replace(/[^a-zA-Z0-9\.]/g, '');
|
var fileName = (req.query.filename || "upload.jpg").replace(/[^a-zA-Z0-9\.]/g, '');
|
||||||
var localFilePath = "/tmp/" + fileName;
|
var localFilePath = "/tmp/" + fileName;
|
||||||
var writeStream = fs.createWriteStream(localFilePath);
|
var writeStream = fs.createWriteStream(localFilePath);
|
||||||
var stream = req.pipe(writeStream);
|
var stream = req.pipe(writeStream);
|
||||||
@@ -334,96 +311,49 @@ router.post('/:id/background', function(req, res, next) {
|
|||||||
uploader.uploadFile("s" + req.space._id + "/bg_" + newDate.getTime() + "_" + fileName, "image/jpeg", localFilePath, function(err, backgroundUrl) {
|
uploader.uploadFile("s" + req.space._id + "/bg_" + newDate.getTime() + "_" + fileName, "image/jpeg", localFilePath, function(err, backgroundUrl) {
|
||||||
if (err) res.status(400).json(err);
|
if (err) res.status(400).json(err);
|
||||||
else {
|
else {
|
||||||
var adv = space.advanced;
|
if (space.background_uri) {
|
||||||
|
var oldPath = url.parse(req.space.background_uri).pathname;
|
||||||
if (adv.background_uri) {
|
|
||||||
var oldPath = url.parse(req.space.thumbnail_url).pathname;
|
|
||||||
uploader.removeFile(oldPath, function(err) {
|
uploader.removeFile(oldPath, function(err) {
|
||||||
console.log("removed old bg error:", err);
|
console.error("remove old background error:", err);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
adv.background_uri = backgroundUrl;
|
db.Space.update({
|
||||||
|
background_uri: backgroundUrl
|
||||||
Space.findOneAndUpdate({
|
|
||||||
"_id": space._id
|
|
||||||
}, {
|
}, {
|
||||||
"$set": {
|
where: { "_id": space._id }
|
||||||
advanced: adv
|
}).then(rows => {
|
||||||
}
|
fs.unlink(localFilePath, function(err) {
|
||||||
}, {
|
if (err) {
|
||||||
"new": true
|
console.error(err);
|
||||||
}, function(err, space) {
|
res.status(400).json(err);
|
||||||
if (err) {
|
} else {
|
||||||
res.sendStatus(400);
|
db.Space.findOne({ where: {
|
||||||
} else {
|
"_id": space._id
|
||||||
fs.unlink(localFilePath, function(err) {
|
}}).then(space => {
|
||||||
if (err) {
|
console.log("========== space update:", space);
|
||||||
console.error(err);
|
res.distributeUpdate("Space", space);
|
||||||
res.status(400).json(err);
|
});
|
||||||
} else {
|
}
|
||||||
res.status(200).json(space);
|
});
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
var handleDuplicateSpaceRequest = function(req, res, parentSpace) {
|
|
||||||
Space.duplicateSpace(req.space, req.user, 0, (err, newSpace) => {
|
|
||||||
if (err) {
|
|
||||||
console.error(err);
|
|
||||||
res.status(400).json(err);
|
|
||||||
} else {
|
|
||||||
res.status(201).json(newSpace);
|
|
||||||
}
|
|
||||||
}, parentSpace);
|
|
||||||
}
|
|
||||||
|
|
||||||
router.post('/:id/duplicate', (req, res, next) => {
|
|
||||||
if (req.query.parent_space_id) {
|
|
||||||
Space.findOne({
|
|
||||||
_id: req.query.parent_space_id
|
|
||||||
}).populate('creator', userMapping).exec((err, parentSpace) => {
|
|
||||||
if (!parentSpace) {
|
|
||||||
res.status(404).json({
|
|
||||||
"error": "parent space not found for dupicate"
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
Space.roleInSpace(parentSpace, req.user, (err, role) => {
|
|
||||||
if (role == "admin" || role == "editor") {
|
|
||||||
handleDuplicateSpaceRequest(req, res, parentSpace);
|
|
||||||
} else {
|
|
||||||
res.status(403).json({
|
|
||||||
"error": "not authed for parent_space_id"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
handleDuplicateSpaceRequest(req, res);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
router.delete('/:id', function(req, res, next) {
|
router.delete('/:id', function(req, res, next) {
|
||||||
if (req.user) {
|
if (req.user) {
|
||||||
const space = req.space;
|
const space = req.space;
|
||||||
|
|
||||||
if (req.spaceRole == "admin") {
|
if (req.spaceRole == "admin") {
|
||||||
const attrs = req.body;
|
const attrs = req.body;
|
||||||
Space.recursiveDelete(space, function(err) {
|
space.destroy().then(function() {
|
||||||
if (err) res.status(400).json(err);
|
res.distributeDelete("Space", space);
|
||||||
else {
|
|
||||||
res.distributeDelete("Space", space);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
res.status(403).json({
|
res.status(403).json({
|
||||||
"error": "requires admin status"
|
"error": "requires admin role"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -431,139 +361,4 @@ router.delete('/:id', function(req, res, next) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post('/:id/artifacts-pdf', function(req, res, next) {
|
|
||||||
if (req.spaceRole == "editor" || req.spaceRole == "admin") {
|
|
||||||
|
|
||||||
var withZones = (req.query.zones) ? req.query.zones == "true" : false;
|
|
||||||
var fileName = (req.query.filename || "upload.bin").replace(/[^a-zA-Z0-9\.]/g, '');
|
|
||||||
var localFilePath = "/tmp/" + fileName;
|
|
||||||
var writeStream = fs.createWriteStream(localFilePath);
|
|
||||||
var stream = req.pipe(writeStream);
|
|
||||||
|
|
||||||
req.on('end', function() {
|
|
||||||
|
|
||||||
var rawName = fileName.slice(0, fileName.length - 4);
|
|
||||||
var outputFolder = "/tmp/" + rawName;
|
|
||||||
var rights = 777;
|
|
||||||
|
|
||||||
fs.mkdir(outputFolder, function(db) {
|
|
||||||
var images = outputFolder + "/" + rawName + "-page-%03d.jpeg";
|
|
||||||
|
|
||||||
exec.execFile("gs", ["-sDEVICE=jpeg", "-dDownScaleFactor=4", "-dDOINTERPOLATE", "-dNOPAUSE", "-dJPEGQ=80", "-dBATCH", "-sOutputFile=" + images, "-r250", "-f", localFilePath], {}, function(error, stdout, stderr) {
|
|
||||||
if (error === null) {
|
|
||||||
|
|
||||||
glob(outputFolder + "/*.jpeg", function(er, files) {
|
|
||||||
var count = files.length;
|
|
||||||
var delta = 10;
|
|
||||||
|
|
||||||
var limitPerRow = Math.ceil(Math.sqrt(count));
|
|
||||||
|
|
||||||
var startX = parseInt(req.query.x, delta);
|
|
||||||
var startY = parseInt(req.query.y, delta);
|
|
||||||
|
|
||||||
async.mapLimit(files, 20, function(localfilePath, cb) {
|
|
||||||
|
|
||||||
var fileName = path.basename(localfilePath);
|
|
||||||
var baseName = path.basename(localfilePath, ".jpeg");
|
|
||||||
|
|
||||||
var number = parseInt(baseName.slice(baseName.length - 3, baseName.length), 10);
|
|
||||||
|
|
||||||
gm(localFilePath).size(function(err, size) {
|
|
||||||
var w = 350;
|
|
||||||
var h = w;
|
|
||||||
|
|
||||||
var x = startX + (((number - 1) % limitPerRow) * w);
|
|
||||||
var y = startY + ((parseInt(((number - 1) / limitPerRow), 10) + 1) * w);
|
|
||||||
|
|
||||||
var userId;
|
|
||||||
if (req.user)
|
|
||||||
userId = req.user._id;
|
|
||||||
|
|
||||||
var a = new Artifact({
|
|
||||||
mime: "image/jpg",
|
|
||||||
space_id: req.space._id,
|
|
||||||
user_id: userId,
|
|
||||||
editor_name: req.guest_name,
|
|
||||||
board: {
|
|
||||||
w: w,
|
|
||||||
h: h,
|
|
||||||
x: x,
|
|
||||||
y: y,
|
|
||||||
z: (number + (count + 100))
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
payloadConverter.convert(a, fileName, localfilePath, (error, artifact) => {
|
|
||||||
if (error) res.status(400).json(error);
|
|
||||||
else {
|
|
||||||
if (withZones) {
|
|
||||||
var zone = new Artifact({
|
|
||||||
mime: "x-spacedeck/zone",
|
|
||||||
description: "Zone " + (number),
|
|
||||||
space_id: req.space._id,
|
|
||||||
user_id: userId,
|
|
||||||
editor_name: req.guest_name,
|
|
||||||
board: {
|
|
||||||
w: artifact.board.w + 20,
|
|
||||||
h: artifact.board.h + 40,
|
|
||||||
x: x - 10,
|
|
||||||
y: y - 30,
|
|
||||||
z: number
|
|
||||||
},
|
|
||||||
style: {
|
|
||||||
order: number,
|
|
||||||
valign: "middle",
|
|
||||||
align: "center"
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
zone.save((err) => {
|
|
||||||
redis.sendMessage("create", "Artifact", zone.toJSON(), req.channelId);
|
|
||||||
cb(null, [artifact, zone]);
|
|
||||||
});
|
|
||||||
|
|
||||||
} else {
|
|
||||||
cb(null, [artifact]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
}, function(err, artifacts) {
|
|
||||||
|
|
||||||
exec.execFile("rm", ["-r", outputFolder], function(err) {
|
|
||||||
res.status(201).json(_.flatten(artifacts));
|
|
||||||
|
|
||||||
async.eachLimit(artifacts, 10, (artifact_or_artifacts, cb) => {
|
|
||||||
|
|
||||||
if (artifact_or_artifacts instanceof Array) {
|
|
||||||
_.each(artifact_or_artifacts, (a) => {
|
|
||||||
redis.sendMessage("create", "Artifact", a.toJSON(), req.channelId);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
redis.sendMessage("create", "Artifact", artifact_or_artifacts.toJSON(), req.channelId);
|
|
||||||
}
|
|
||||||
cb(null);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
console.error("error:", error);
|
|
||||||
exec.execFile("rm", ["-r", outputFolder], function(err) {
|
|
||||||
fs.unlink(localFilePath);
|
|
||||||
res.status(400).json({});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
res.status(401).json({
|
|
||||||
"error": "no access"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
|
|||||||
@@ -1,265 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
|
|
||||||
var config = require('config');
|
|
||||||
require('../../models/schema');
|
|
||||||
|
|
||||||
var redis = require('../../helpers/redis');
|
|
||||||
var mailer = require('../../helpers/mailer');
|
|
||||||
|
|
||||||
var fs = require('fs');
|
|
||||||
var _ = require('underscore');
|
|
||||||
var crypto = require('crypto');
|
|
||||||
var bcrypt = require('bcryptjs');
|
|
||||||
|
|
||||||
var express = require('express');
|
|
||||||
var router = express.Router();
|
|
||||||
var userMapping = { '_id': 1, 'nickname': 1, 'email': 1};
|
|
||||||
|
|
||||||
router.get('/:id', (req, res) => {
|
|
||||||
res.status(200).json(req.user.team);
|
|
||||||
});
|
|
||||||
|
|
||||||
router.put('/:id', (req, res) => {
|
|
||||||
var team = req.user.team;
|
|
||||||
if (!team) {
|
|
||||||
res.status(400).json({"error": "user in no team"});
|
|
||||||
} else {
|
|
||||||
var newAttr = req.body;
|
|
||||||
newAttr.updated_at = new Date();
|
|
||||||
delete newAttr['_id'];
|
|
||||||
|
|
||||||
if(newAttr['subdomain']) {
|
|
||||||
newAttr['subdomain'] = newAttr['subdomain'].toLowerCase();
|
|
||||||
}
|
|
||||||
const new_subdomain = newAttr['subdomain'];
|
|
||||||
var forbidden_subdomains = [];
|
|
||||||
|
|
||||||
function updateTeam() {
|
|
||||||
Team.findOneAndUpdate({"_id": team._id}, {"$set": newAttr}, {"new": true}, (err, team) => {
|
|
||||||
if (err) res.status(400).json(err);
|
|
||||||
else {
|
|
||||||
res.status(200).json(team);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
var isForbidden = forbidden_subdomains.indexOf(new_subdomain) > -1;
|
|
||||||
if (isForbidden) {
|
|
||||||
res.bad_request("subdomain not valid");
|
|
||||||
} else {
|
|
||||||
if (new_subdomain) {
|
|
||||||
Team.findOne({"domain": new_subdomain}).exec((err, team) => {
|
|
||||||
if(team) {
|
|
||||||
res.bad_request("subdomain already used");
|
|
||||||
} else {
|
|
||||||
updateTeam()
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
updateTeam()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
router.get('/:id/memberships', (req, res) => {
|
|
||||||
User
|
|
||||||
.find({team: req.user.team})
|
|
||||||
.populate("team")
|
|
||||||
.exec(function(err, users){
|
|
||||||
if (err) res.status(400).json(err);
|
|
||||||
else {
|
|
||||||
res.status(200).json(users);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
router.post('/:id/memberships', (req, res, next) => {
|
|
||||||
if (req.body.email) {
|
|
||||||
const email = req.body.email.toLowerCase();
|
|
||||||
const team = req.user.team;
|
|
||||||
|
|
||||||
User.findOne({"email": email}).populate('team').exec((err, user) => {
|
|
||||||
if (user) {
|
|
||||||
const code = crypto.randomBytes(64).toString('hex').substring(0,7);
|
|
||||||
team.invitation_codes.push(code);
|
|
||||||
team.save((err) => {
|
|
||||||
if (err){ res.status(400).json(err); }
|
|
||||||
else {
|
|
||||||
mailer.sendMail(email, req.i18n.__("team_invite_membership_subject", team.name), req.i18n.__("team_invite_membership_body", team.name), { action: {
|
|
||||||
link: config.endpoint + "/teams/" + req.user.team._id + "/join?code=" + code,
|
|
||||||
name: req.i18n.__("team_invite_membership_action"),
|
|
||||||
teamname: team.name
|
|
||||||
}});
|
|
||||||
|
|
||||||
res.status(201).json(user);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
} else {
|
|
||||||
// complete new user
|
|
||||||
const password = crypto.randomBytes(64).toString('hex').substring(0,7);
|
|
||||||
const confirmation_token = crypto.randomBytes(64).toString('hex').substring(0,7);
|
|
||||||
|
|
||||||
bcrypt.genSalt(10, (err, salt) => {
|
|
||||||
bcrypt.hash(password, salt, (err, hash) => {
|
|
||||||
crypto.randomBytes(16, (ex, buf) => {
|
|
||||||
const token = buf.toString('hex');
|
|
||||||
|
|
||||||
var u = new User({
|
|
||||||
email: email,
|
|
||||||
account_type: "email",
|
|
||||||
nickname: email,
|
|
||||||
team: team._id,
|
|
||||||
password_hash: hash,
|
|
||||||
payment_plan_key: team.payment_plan_key,
|
|
||||||
confirmation_token: confirmation_token,
|
|
||||||
preferences: {
|
|
||||||
language: req.i18n.locale
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
u.save((err) => {
|
|
||||||
if(err) res.sendStatus(400);
|
|
||||||
else {
|
|
||||||
var homeSpace = new Space({
|
|
||||||
name: req.i18n.__("home"),
|
|
||||||
space_type: "folder",
|
|
||||||
creator: u
|
|
||||||
});
|
|
||||||
|
|
||||||
homeSpace.save((err, homeSpace) => {
|
|
||||||
if (err) res.sendStatus(400);
|
|
||||||
else {
|
|
||||||
u.home_folder_id = homeSpace._id;
|
|
||||||
u.save((err) => {
|
|
||||||
|
|
||||||
User.find({"_id": {"$in": team.admins }}).exec((err, admins) => {
|
|
||||||
admins.forEach((admin) => {
|
|
||||||
var i18n = req.i18n;
|
|
||||||
if(admin.preferences && admin.preferences.language){
|
|
||||||
i18n.setLocale(admin.preferences.language || "en");
|
|
||||||
}
|
|
||||||
mailer.sendMail(admin.email, i18n.__("team_invite_membership_subject", team.name), i18n.__("team_invite_admin_body", email, team.name, password), { teamname: team.name });
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
mailer.sendMail(email, req.i18n.__("team_invite_membership_subject", team.name), req.i18n.__("team_invite_user_body", team.name, password), { action: {
|
|
||||||
link: config.endpoint + "/users/byteam/" + req.user.team._id + "/join?confirmation_token=" + confirmation_token,
|
|
||||||
name: req.i18n.__("team_invite_membership_action")
|
|
||||||
}, teamname: team.name });
|
|
||||||
|
|
||||||
if (err) res.status(400).json(err);
|
|
||||||
else{
|
|
||||||
res.status(201).json(u)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
res.status(400).json({"error": "email missing"});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
router.put('/:id/memberships/:user_id', (req, res) => {
|
|
||||||
User.findOne({_id: req.params.user_id}, (err,mem) => {
|
|
||||||
if (err) res.sendStatus(400);
|
|
||||||
else {
|
|
||||||
if(user.team._id == req.user.team._id){
|
|
||||||
user['team'] = req.user.team._id;
|
|
||||||
user.save((err) => {
|
|
||||||
res.sendStatus(204);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
res.sendStatus(403);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
router.get('/:id/memberships/:user_id/promote', (req, res) => {
|
|
||||||
User.findOne({_id: req.params.user_id}, (err,user) => {
|
|
||||||
if (err) res.sendStatus(400);
|
|
||||||
else {
|
|
||||||
if (user.team.toString() == req.user.team._id.toString()) {
|
|
||||||
var team = req.user.team;
|
|
||||||
var adminIndex = team.admins.indexOf(user._id);
|
|
||||||
|
|
||||||
if (adminIndex == -1) {
|
|
||||||
team.admins.push(user._id);
|
|
||||||
team.save((err, team) => {
|
|
||||||
res.status(204).json(team);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
res.status(400).json({"error": "already admin"});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
res.status(403).json({"error": "team id not correct"});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
router.get('/:id/memberships/:user_id/demote', (req, res, next) => {
|
|
||||||
User.findOne({_id: req.params.user_id}, (err,user) => {
|
|
||||||
if (err) res.sendStatus(400);
|
|
||||||
else {
|
|
||||||
if (user.team.toString() == req.user.team._id.toString()) {
|
|
||||||
const team = req.user.team;
|
|
||||||
const adminIndex = team.admins.indexOf(user._id);
|
|
||||||
|
|
||||||
if(adminIndex > -1) {
|
|
||||||
team.admins.splice(adminIndex,1);
|
|
||||||
team.save((err, team) => {
|
|
||||||
res.status(204).json(team);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
res.sendStatus(404);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
res.sendStatus(403);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
router.delete('/:id/memberships/:user_id', (req, res) => {
|
|
||||||
User.findOne({_id: req.params.user_id}).populate('team').exec((err,user) => {
|
|
||||||
if (err) res.sendStatus(400);
|
|
||||||
else {
|
|
||||||
const currentUserId = req.user._id.toString();
|
|
||||||
const team = req.user.team;
|
|
||||||
|
|
||||||
const isAdmin = (req.user.team.admins.filter( mem => {
|
|
||||||
return mem == currentUserId;
|
|
||||||
}).length == 1)
|
|
||||||
|
|
||||||
if (isAdmin) {
|
|
||||||
user.team = null;
|
|
||||||
user.payment_plan_key = "free";
|
|
||||||
user.save( err => {
|
|
||||||
const adminIndex = team.admins.indexOf(user._id);
|
|
||||||
if(adminIndex > -1) {
|
|
||||||
team.admins.splice(adminIndex,1);
|
|
||||||
team.save((err, team) => {
|
|
||||||
console.log("admin removed");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
res.sendStatus(204);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
res.status(403).json({"error": "not admin"});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = router;
|
|
||||||
@@ -1,259 +1,141 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var config = require('config');
|
var config = require('config');
|
||||||
require('../../models/schema');
|
const db = require('../../models/db');
|
||||||
|
const uuidv4 = require('uuid/v4');
|
||||||
|
const os = require('os');
|
||||||
|
|
||||||
var mailer = require('../../helpers/mailer');
|
var mailer = require('../../helpers/mailer');
|
||||||
var uploader = require('../../helpers/uploader');
|
var uploader = require('../../helpers/uploader');
|
||||||
|
var importer = require('../../helpers/importer');
|
||||||
|
|
||||||
var bcrypt = require('bcryptjs');
|
var bcrypt = require('bcryptjs');
|
||||||
var crypo = require('crypto');
|
var crypto = require('crypto');
|
||||||
var swig = require('swig');
|
|
||||||
var async = require('async');
|
var async = require('async');
|
||||||
var _ = require('underscore');
|
var _ = require('underscore');
|
||||||
var fs = require('fs');
|
var fs = require('fs');
|
||||||
var request = require('request');
|
var request = require('request');
|
||||||
var gm = require('gm');
|
var gm = require('gm');
|
||||||
var validator = require('validator');
|
var validator = require('validator');
|
||||||
|
var URL = require('url').URL;
|
||||||
|
|
||||||
var express = require('express');
|
var express = require('express');
|
||||||
var router = express.Router();
|
var router = express.Router();
|
||||||
|
var glob = require('glob');
|
||||||
|
|
||||||
router.get('/current', function(req, res, next) {
|
router.get('/current', function(req, res, next) {
|
||||||
if (req.user) {
|
if (req.user) {
|
||||||
console.log(req.user.team);
|
var u = _.clone(req.user.dataValues);
|
||||||
res.status(200).json(req.user);
|
delete u.password_hash;
|
||||||
|
delete u.password_reset_token;
|
||||||
|
delete u.confirmation_token;
|
||||||
|
u.token = req.cookies['sdsession'];
|
||||||
|
|
||||||
|
console.log(u);
|
||||||
|
|
||||||
|
res.status(200).json(u);
|
||||||
} else {
|
} else {
|
||||||
res.status(401).json({"error":"user_not_found"});
|
res.status(401).json({"error":"user_not_found"});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// create user
|
||||||
router.post('/', function(req, res) {
|
router.post('/', function(req, res) {
|
||||||
if (req.body["email"] && req.body["password"]) {
|
if (!req.body["email"] || !req.body["password"]) {
|
||||||
|
|
||||||
var email = req.body["email"].toLowerCase();
|
|
||||||
var nickname = req.body["nickname"];
|
|
||||||
var password = req.body["password"];
|
|
||||||
var password_confirmation = req.body["password_confirmation"];
|
|
||||||
|
|
||||||
if (password_confirmation == password) {
|
|
||||||
if (validator.isEmail(email)) {
|
|
||||||
|
|
||||||
var createUser = function() {
|
|
||||||
bcrypt.genSalt(10, function(err, salt) {
|
|
||||||
bcrypt.hash(password, salt, function(err, hash) {
|
|
||||||
|
|
||||||
crypo.randomBytes(16, function(ex, buf) {
|
|
||||||
var token = buf.toString('hex');
|
|
||||||
|
|
||||||
var u = new User({
|
|
||||||
email: email,
|
|
||||||
account_type: "email",
|
|
||||||
nickname: nickname,
|
|
||||||
password_hash: hash,
|
|
||||||
preferences: {
|
|
||||||
language: req.i18n.locale
|
|
||||||
},
|
|
||||||
confirmation_token: token
|
|
||||||
});
|
|
||||||
|
|
||||||
u.save(function (err) {
|
|
||||||
if (err) res.sendStatus(400);
|
|
||||||
else {
|
|
||||||
var homeSpace = new Space({
|
|
||||||
name: req.i18n.__("home"),
|
|
||||||
space_type: "folder",
|
|
||||||
creator: u
|
|
||||||
});
|
|
||||||
|
|
||||||
homeSpace.save((err, homeSpace) => {
|
|
||||||
if (err) res.sendStatus(400);
|
|
||||||
else {
|
|
||||||
u.home_folder_id = homeSpace._id;
|
|
||||||
u.save((err) => {
|
|
||||||
|
|
||||||
mailer.sendMail(u.email, req.i18n.__("confirm_subject"), req.i18n.__("confirm_body"), {
|
|
||||||
action: {
|
|
||||||
link: config.endpoint + "/confirm/" + u.confirmation_token,
|
|
||||||
name: req.i18n.__("confirm_action")
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (err) res.status(400).json(err);
|
|
||||||
else {
|
|
||||||
res.status(201).json({});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
User.find({email: email}, (function (err, users) {
|
|
||||||
if (err) {
|
|
||||||
res.status(400).json({"error":"password_confirmation"});
|
|
||||||
} else {
|
|
||||||
|
|
||||||
if (users.length === 0) {
|
|
||||||
var domain = email.slice(email.lastIndexOf('@')+1);
|
|
||||||
|
|
||||||
Domain.findOne({domain: domain}, function(err, domain) {
|
|
||||||
if(domain){
|
|
||||||
if(domain.edu) {
|
|
||||||
createUser();
|
|
||||||
} else {
|
|
||||||
res.status(400).json({"error":"domain_blocked"});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
createUser();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
res.status(400).json({"error":"user_email_already_used"});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
} else {
|
|
||||||
res.status(400).json({"error":"email_invalid"});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
res.status(400).json({"error":"password_confirmation"});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
res.status(400).json({"error":"email or password missing"});
|
res.status(400).json({"error":"email or password missing"});
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
var email = req.body["email"].toLowerCase();
|
||||||
|
var nickname = req.body["nickname"];
|
||||||
|
var password = req.body["password"];
|
||||||
|
var password_confirmation = req.body["password_confirmation"];
|
||||||
|
var invite_code = req.body["invite_code"];
|
||||||
|
|
||||||
router.get('/oauth2callback/url', function(req, res) {
|
if (password_confirmation != password) {
|
||||||
var google = require('googleapis');
|
res.status(400).json({"error":"password_confirmation"});
|
||||||
var OAuth2 = google.auth.OAuth2;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.invite_code && invite_code != config.invite_code) {
|
||||||
|
res.status(400).json({"error":"Invalid Invite Code."});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!validator.isEmail(email)) {
|
||||||
|
res.status(400).json({"error":"email_invalid"});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var createUser = function() {
|
||||||
|
bcrypt.genSalt(10, function(err, salt) {
|
||||||
|
bcrypt.hash(password, salt, function(err, hash) {
|
||||||
|
crypto.randomBytes(16, function(ex, buf) {
|
||||||
|
var token = buf.toString('hex');
|
||||||
|
|
||||||
var oauth2Client = new OAuth2(
|
var u = {
|
||||||
config.google_access,
|
_id: uuidv4(),
|
||||||
config.google_secret,
|
email: email,
|
||||||
config.endpoint + "/login"
|
account_type: "email",
|
||||||
);
|
nickname: nickname,
|
||||||
|
password_hash: hash,
|
||||||
|
prefs_language: req.i18n.locale,
|
||||||
|
confirmation_token: token
|
||||||
|
};
|
||||||
|
|
||||||
var url = oauth2Client.generateAuthUrl({
|
db.User.create(u)
|
||||||
access_type: 'online',
|
.error(err => {
|
||||||
scope: "email"
|
res.sendStatus(400);
|
||||||
});
|
})
|
||||||
|
.then(u => {
|
||||||
res.status(200).json({"url":url});
|
var homeFolder = {
|
||||||
});
|
_id: uuidv4(),
|
||||||
|
name: req.i18n.__("home"),
|
||||||
router.get('/loginorsignupviagoogle', function(req, res) {
|
space_type: "folder",
|
||||||
var google = require('googleapis');
|
creator_id: u._id
|
||||||
var OAuth2 = google.auth.OAuth2;
|
};
|
||||||
var plus = google.plus('v1');
|
db.Space.create(homeFolder)
|
||||||
|
.error(err => {
|
||||||
var oauth2Client = new OAuth2(
|
res.sendStatus(400);
|
||||||
config.google_access,
|
})
|
||||||
config.google_secret,
|
.then(homeFolder => {
|
||||||
config.endpoint + "/login"
|
u.home_folder_id = homeFolder._id;
|
||||||
);
|
u.save()
|
||||||
|
.then(() => {
|
||||||
var loginUser = function(user, cb) {
|
// home folder created,
|
||||||
crypo.randomBytes(48, function(ex, buf) {
|
// auto accept pending invites
|
||||||
var token = buf.toString('hex');
|
db.Membership.update({
|
||||||
var session = {
|
"state": "active"
|
||||||
token: token,
|
}, {
|
||||||
created_at: new Date()
|
where: {
|
||||||
};
|
"email_invited": u.email,
|
||||||
if(!user.sessions)
|
"state": "pending"
|
||||||
user.sessions = [];
|
}
|
||||||
user.sessions.push(session);
|
});
|
||||||
user.save(function(err, user) {
|
res.status(201).json({});
|
||||||
cb(session);
|
})
|
||||||
|
.error(err => {
|
||||||
|
res.status(400).json(err);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
var code = req.query.code;
|
db.User.findAll({where: {email: email}})
|
||||||
oauth2Client.getToken(code, function(err, tokens) {
|
.then(users => {
|
||||||
|
if (users.length == 0) {
|
||||||
if (err) res.status(400).json(err);
|
createUser();
|
||||||
else {
|
} else {
|
||||||
var apiUrl = "https://www.googleapis.com/oauth2/v1/userinfo?alt=json&access_token=" + tokens.access_token;
|
res.status(400).json({"error":"user_email_already_used"});
|
||||||
|
}
|
||||||
var finalizeLogin = function(session){
|
})
|
||||||
var secure = process.env.NODE_ENV == "production" || process.env.NODE_ENV == "staging";
|
|
||||||
res.cookie('sdsession', session.token, { httpOnly: true, secure: secure});
|
|
||||||
res.status(201).json(session);
|
|
||||||
};
|
|
||||||
|
|
||||||
request.get(apiUrl, function(error, response, body) {
|
|
||||||
if (error) res.status(400).json(error);
|
|
||||||
else {
|
|
||||||
const data = JSON.parse(body);
|
|
||||||
const email = data.email;
|
|
||||||
const name = data.name;
|
|
||||||
|
|
||||||
User.findOne({email: email}, function (err, user) {
|
|
||||||
if (user) {
|
|
||||||
// login new google user
|
|
||||||
if (user.account_type == "google") {
|
|
||||||
// just login
|
|
||||||
loginUser(user, (session) => {
|
|
||||||
finalizeLogin(session);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
res.status(400).json({"error":"user_email_already_used"});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const u = new User({
|
|
||||||
email: email,
|
|
||||||
account_type: "google",
|
|
||||||
nickname: name,
|
|
||||||
avatar_thumb_uri: body.picture,
|
|
||||||
preferences: {
|
|
||||||
language: req.i18n.locale
|
|
||||||
},
|
|
||||||
confirmed_at: new Date()
|
|
||||||
});
|
|
||||||
|
|
||||||
u.save(function (err) {
|
|
||||||
if (err) res.status(400).json(err);
|
|
||||||
else {
|
|
||||||
var homeSpace = new Space({
|
|
||||||
name: req.i18n.__("home"),
|
|
||||||
space_type: "folder",
|
|
||||||
creator: u
|
|
||||||
});
|
|
||||||
|
|
||||||
homeSpace.save(function(err, homeSpace) {
|
|
||||||
if (err) res.status(400).json(err);
|
|
||||||
else {
|
|
||||||
u.home_folder_id = homeSpace._id;
|
|
||||||
u.save(function(err){
|
|
||||||
if (err) res.sendStatus(400);
|
|
||||||
else {
|
|
||||||
|
|
||||||
mailer.sendMail(u.email, req.i18n.__("welcome_subject"), req.i18n.__("welcome_body"), {});
|
|
||||||
loginUser(u, function(session) {
|
|
||||||
finalizeLogin(session);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
router.get('/ ', function(req, res, next) {
|
router.get('/current', function(req, res, next) {
|
||||||
if (req.user) {
|
if (req.user) {
|
||||||
console.log(req.user.team);
|
|
||||||
res.status(200).json(req.user);
|
res.status(200).json(req.user);
|
||||||
} else {
|
} else {
|
||||||
res.status(401).json({"error":"user_not_found"});
|
res.status(401).json({"error":"user_not_found"});
|
||||||
@@ -261,19 +143,15 @@ router.get('/ ', function(req, res, next) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
router.put('/:id', function(req, res, next) {
|
router.put('/:id', function(req, res, next) {
|
||||||
|
// TODO explicit whitelisting
|
||||||
var user = req.user;
|
var user = req.user;
|
||||||
console.log(req.params.id, user._id);
|
|
||||||
if (user._id == req.params.id) {
|
if (user._id == req.params.id) {
|
||||||
var newAttr = req.body;
|
var newAttr = req.body;
|
||||||
newAttr.updated_at = new Date();
|
newAttr.updated_at = new Date();
|
||||||
delete newAttr['_id'];
|
delete newAttr['_id'];
|
||||||
|
|
||||||
User.findOneAndUpdate({"_id": user._id}, {"$set": newAttr}, function(err, updatedUser) {
|
db.User.update(newAttr, {where: {"_id": user._id}}).then(function(updatedUser) {
|
||||||
if (err) {
|
res.status(200).json(newAttr);
|
||||||
res.sendStatus(400);
|
|
||||||
} else {
|
|
||||||
res.status(200).json(updatedUser);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
res.sendStatus(403);
|
res.sendStatus(403);
|
||||||
@@ -291,46 +169,41 @@ router.post('/:id/password', function(req, res, next) {
|
|||||||
bcrypt.genSalt(10, function(err, salt) {
|
bcrypt.genSalt(10, function(err, salt) {
|
||||||
bcrypt.hash(pass, salt, function(err, hash) {
|
bcrypt.hash(pass, salt, function(err, hash) {
|
||||||
user.password_hash = hash;
|
user.password_hash = hash;
|
||||||
user.save(function(err){
|
user.save().then(function() {
|
||||||
if(err){
|
res.sendStatus(204);
|
||||||
res.status(400).json(err);
|
|
||||||
}else{
|
|
||||||
res.sendStatus(204);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
res.status(403).json({"error": "old password wrong"});
|
res.status(403).json({"error": "Please enter the correct current password."});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
res.status(403).json({"error": "wrong user"});
|
res.status(403).json({"error": "Access denied."});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
res.status(400).json({"error": "password_to_short"});
|
res.status(400).json({"error": "Please choose a new password with at least 6 characters."});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
router.delete('/:id', (req, res, next) => {
|
router.delete('/:id', (req, res, next) => {
|
||||||
const user = req.user;
|
const user = req.user;
|
||||||
if(user._id == req.params.id) {
|
if (user._id == req.params.id) {
|
||||||
if (user.account_type == 'email') {
|
if (bcrypt.compareSync(req.query.password, user.password_hash)) {
|
||||||
if (bcrypt.compareSync(req.query.password, user.password_hash)) {
|
|
||||||
user.remove((err) => {
|
// TODO: this doesn't currently work.
|
||||||
if(err)res.status(400).json(err);
|
// all objects (indirectly) belonging to the user have
|
||||||
else res.sendStatus(204);
|
// to be walked and deleted first.
|
||||||
});
|
|
||||||
} else {
|
user.destroy().then(err => {
|
||||||
res.bad_request("password_incorrect");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
user.remove((err) => {
|
|
||||||
if(err)res.status(400).json(err);
|
if(err)res.status(400).json(err);
|
||||||
else res.sendStatus(204);
|
else res.sendStatus(204);
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
res.bad_request("Please enter the correct current password.");
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
res.status(403).json({error: "Access denied."});
|
||||||
}
|
}
|
||||||
else res.status(403).json({error: ""});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
router.put('/:user_id/confirm', (req, res) => {
|
router.put('/:user_id/confirm', (req, res) => {
|
||||||
@@ -356,8 +229,8 @@ router.post('/:user_id/avatar', (req, res, next) => {
|
|||||||
const user = req.user;
|
const user = req.user;
|
||||||
const filename = "u"+req.user._id+"_"+(new Date().getTime())+".jpeg"
|
const filename = "u"+req.user._id+"_"+(new Date().getTime())+".jpeg"
|
||||||
|
|
||||||
const localFilePath = "/tmp/"+filename;
|
const localFilePath = os.tmpdir()+"/"+filename;
|
||||||
const localResizedFilePath = "/tmp/resized_"+filename;
|
const localResizedFilePath = os.tmpdir()+"/resized_"+filename;
|
||||||
const writeStream = fs.createWriteStream(localFilePath);
|
const writeStream = fs.createWriteStream(localFilePath);
|
||||||
const stream = req.pipe(writeStream);
|
const stream = req.pipe(writeStream);
|
||||||
|
|
||||||
@@ -369,19 +242,15 @@ router.post('/:user_id/avatar', (req, res, next) => {
|
|||||||
if (err) res.status(400).json(err);
|
if (err) res.status(400).json(err);
|
||||||
else {
|
else {
|
||||||
user.avatar_thumb_uri = url;
|
user.avatar_thumb_uri = url;
|
||||||
user.save((err, updatedUser) => {
|
user.save().then(() => {
|
||||||
if (err) {
|
fs.unlink(localResizedFilePath, (err) => {
|
||||||
res.sendStatus(400);
|
if (err) {
|
||||||
} else {
|
console.error(err);
|
||||||
fs.unlink(localResizedFilePath, (err) => {
|
res.status(400).json(err);
|
||||||
if (err) {
|
} else {
|
||||||
console.error(err);
|
res.status(200).json(user);
|
||||||
res.status(400).json(err);
|
}
|
||||||
} else {
|
});
|
||||||
res.status(200).json(updatedUser);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -390,40 +259,22 @@ router.post('/:user_id/avatar', (req, res, next) => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post('/feedback', function(req, res, next) {
|
|
||||||
var text = req.body.text;
|
|
||||||
// FIXME
|
|
||||||
mailer.sendMail("support@example.org", "Support Request by " + req.user.email, text, {reply_to: req.user.email});
|
|
||||||
res.sendStatus(201);
|
|
||||||
});
|
|
||||||
|
|
||||||
router.post('/password_reset_requests', (req, res, next) => {
|
router.post('/password_reset_requests', (req, res, next) => {
|
||||||
const email = req.query.email;
|
const email = req.query.email;
|
||||||
User.findOne({"email": email}).exec((err, user) => {
|
db.User.findOne({where: {"email": email}}).then((user) => {
|
||||||
if (err) {
|
if (user) {
|
||||||
res.status(400).json(err);
|
crypto.randomBytes(16, (ex, buf) => {
|
||||||
|
user.password_reset_token = buf.toString('hex');
|
||||||
|
user.save().then(updatedUser => {
|
||||||
|
mailer.sendMail(email, req.i18n.__("password_reset_subject"), req.i18n.__("password_reset_body"), {action: {
|
||||||
|
link: config.endpoint + "/password-confirm/" + user.password_reset_token,
|
||||||
|
name: req.i18n.__("password_reset_action")
|
||||||
|
}});
|
||||||
|
res.status(201).json({});
|
||||||
|
});
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
if (user) {
|
res.status(404).json({"error": "error_unknown_email"});
|
||||||
if(user.account_type == "email") {
|
|
||||||
crypo.randomBytes(16, (ex, buf) => {
|
|
||||||
user.password_reset_token = buf.toString('hex');
|
|
||||||
user.save((err, updatedUser) => {
|
|
||||||
if (err) res.status(400).json(err);
|
|
||||||
else {
|
|
||||||
mailer.sendMail(email, req.i18n.__("password_reset_subject"), req.i18n.__("password_reset_body"), {action: {
|
|
||||||
link: config.endpoint + "/password-confirm/" + user.password_reset_token,
|
|
||||||
name: req.i18n.__("password_reset_action")
|
|
||||||
}});
|
|
||||||
res.status(201).json({});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
res.status(404).json({"error": "error_unknown_email"});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
res.status(404).json({"error": "error_unknown_email"});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -431,30 +282,21 @@ router.post('/password_reset_requests', (req, res, next) => {
|
|||||||
router.post('/password_reset_requests/:confirm_token/confirm', function(req, res, next) {
|
router.post('/password_reset_requests/:confirm_token/confirm', function(req, res, next) {
|
||||||
var password = req.body.password;
|
var password = req.body.password;
|
||||||
|
|
||||||
User
|
db.User
|
||||||
.findOne({"password_reset_token": req.params.confirm_token})
|
.findOne({where: {"password_reset_token": req.params.confirm_token}})
|
||||||
.exec((err, user) => {
|
.then((user) => {
|
||||||
if (err) {
|
if (user) {
|
||||||
res.sendStatus(400);
|
bcrypt.genSalt(10, (err, salt) => {
|
||||||
} else {
|
bcrypt.hash(password, salt, function(err, hash) {
|
||||||
if(user) {
|
user.password_hash = hash;
|
||||||
bcrypt.genSalt(10, (err, salt) => {
|
user.password_token = null;
|
||||||
bcrypt.hash(password, salt, function(err, hash) {
|
user.save().then(function(updatedUser) {
|
||||||
|
res.sendStatus(201);
|
||||||
user.password_hash = hash;
|
|
||||||
user.password_token = null;
|
|
||||||
user.save(function(err, updatedUser){
|
|
||||||
if (err) {
|
|
||||||
res.sendStatus(400);
|
|
||||||
} else {
|
|
||||||
res.sendStatus(201);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
} else {
|
});
|
||||||
res.sendStatus(404);
|
} else {
|
||||||
}
|
res.sendStatus(404);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var config = require('config');
|
var config = require('config');
|
||||||
require('../../models/schema');
|
require('../../models/db');
|
||||||
|
|
||||||
var fs = require('fs');
|
var fs = require('fs');
|
||||||
var phantom = require('node-phantom-simple');
|
var phantom = require('node-phantom-simple');
|
||||||
|
|||||||
251
routes/root.js
251
routes/root.js
@@ -1,7 +1,6 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const config = require('config');
|
const config = require('config');
|
||||||
require('../models/schema');
|
|
||||||
|
|
||||||
const redis = require('../helpers/redis');
|
const redis = require('../helpers/redis');
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
@@ -9,10 +8,14 @@ const crypto = require('crypto');
|
|||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
const mailer = require('../helpers/mailer');
|
const mailer = require('../helpers/mailer');
|
||||||
const _ = require('underscore');
|
const _ = require('underscore');
|
||||||
const qr = require('qr-image');
|
|
||||||
|
const db = require('../models/db');
|
||||||
|
const Sequelize = require('sequelize');
|
||||||
|
const Op = Sequelize.Op;
|
||||||
|
const uuidv4 = require('uuid/v4');
|
||||||
|
|
||||||
router.get('/', (req, res) => {
|
router.get('/', (req, res) => {
|
||||||
res.render('index', { title: 'Spaces' });
|
res.render('index', { config:config, user:req.user });
|
||||||
});
|
});
|
||||||
|
|
||||||
router.get('/ping', (req, res) => {
|
router.get('/ping', (req, res) => {
|
||||||
@@ -20,39 +23,35 @@ router.get('/ping', (req, res) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
router.get('/spaces', (req, res) => {
|
router.get('/spaces', (req, res) => {
|
||||||
res.render('spacedeck', { title: 'Spaces' });
|
res.render('spacedeck', { config:config, user:req.user });
|
||||||
});
|
});
|
||||||
|
|
||||||
router.get('/not_found', (req, res) => {
|
router.get('/not_found', (req, res) => {
|
||||||
res.render('not_found', { title: 'Spaces' });
|
res.render('not_found', {});
|
||||||
});
|
});
|
||||||
|
|
||||||
router.get('/confirm/:token', (req, res) => {
|
router.get('/confirm/:token', (req, res) => {
|
||||||
res.render('spacedeck', { title: 'Space' });
|
res.render('spacedeck', { config:config, user:req.user });
|
||||||
});
|
});
|
||||||
|
|
||||||
router.get('/folders/:id', (req, res) => {
|
router.get('/folders/:id', (req, res) => {
|
||||||
res.render('spacedeck', {});
|
res.render('spacedeck', { config:config, user:req.user });
|
||||||
});
|
});
|
||||||
|
|
||||||
router.get('/signup', (req, res) => {
|
router.get('/signup', (req, res) => {
|
||||||
res.render('spacedeck', {});
|
res.render('spacedeck', { config:config, user:req.user });
|
||||||
});
|
});
|
||||||
|
|
||||||
router.get('/accept/:id', (req, res) => {
|
router.get('/accept/:id', (req, res) => {
|
||||||
res.render('spacedeck', {});
|
res.render('spacedeck', { config:config, user:req.user });
|
||||||
});
|
});
|
||||||
|
|
||||||
router.get('/password-reset', (req, res) => {
|
router.get('/password-reset', (req, res) => {
|
||||||
res.render('spacedeck', { title: 'Signup' });
|
res.render('spacedeck', { config:config, user:req.user });
|
||||||
});
|
});
|
||||||
|
|
||||||
router.get('/password-confirm/:token', (req, res) => {
|
router.get('/password-confirm/:token', (req, res) => {
|
||||||
res.render('spacedeck', { title: 'Signup' });
|
res.render('spacedeck', { config:config, user:req.user });
|
||||||
});
|
|
||||||
|
|
||||||
router.get('/team', (req, res) => {
|
|
||||||
res.render('spacedeck');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
router.get('/de/*', (req, res) => {
|
router.get('/de/*', (req, res) => {
|
||||||
@@ -71,6 +70,14 @@ router.get('/fr', (req, res) => {
|
|||||||
res.redirect("/t/fr");
|
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) => {
|
router.get('/en/*', (req, res) => {
|
||||||
res.redirect("/t/en");
|
res.redirect("/t/en");
|
||||||
});
|
});
|
||||||
@@ -79,40 +86,16 @@ router.get('/en', (req, res) => {
|
|||||||
res.redirect("/t/end");
|
res.redirect("/t/end");
|
||||||
});
|
});
|
||||||
|
|
||||||
router.get('/it', (req, res) => {
|
|
||||||
res.redirect("/t/en");
|
|
||||||
});
|
|
||||||
|
|
||||||
router.get('/account', (req, res) => {
|
router.get('/account', (req, res) => {
|
||||||
res.render('spacedeck');
|
res.render('spacedeck');
|
||||||
});
|
});
|
||||||
|
|
||||||
router.get('/login', (req, res) => {
|
router.get('/login', (req, res) => {
|
||||||
res.render('spacedeck');
|
res.render('spacedeck', { config:config, user:req.user });
|
||||||
});
|
});
|
||||||
|
|
||||||
router.get('/logout', (req, res) => {
|
router.get('/logout', (req, res) => {
|
||||||
res.render('spacedeck');
|
res.render('spacedeck', { config:config, user:req.user });
|
||||||
});
|
|
||||||
|
|
||||||
router.get('/users/oauth2callback', (req, res) => {
|
|
||||||
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) => {
|
router.get('/t/:id', (req, res) => {
|
||||||
@@ -124,181 +107,31 @@ router.get('/t/:id', (req, res) => {
|
|||||||
res.redirect(path);
|
res.redirect(path);
|
||||||
});
|
});
|
||||||
|
|
||||||
router.get('/s/:token', (req, res) => {
|
router.get('/s/:hash', (req, res) => {
|
||||||
redis.rateLimit(req.real_ip, "token", function(ok) {
|
var hash = req.params.hash;
|
||||||
if (ok) {
|
if (hash.split("-").length > 0) {
|
||||||
var token = req.params.token;
|
hash = hash.split("-")[0];
|
||||||
if (token.split("-").length > 0) {
|
}
|
||||||
token = token.split("-")[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
Space.findOne({"edit_hash": token}).exec(function (err, space) {
|
db.Space.findOne({where: {"edit_hash": hash}}).then(function (space) {
|
||||||
if (err) {
|
if (space) {
|
||||||
res.status(404).render('not_found', { title: 'Page Not Found.' });
|
if (req.accepts('text/html')){
|
||||||
} else {
|
res.redirect("/spaces/"+space._id + "?spaceAuth=" + hash);
|
||||||
if (space) {
|
} else {
|
||||||
if(req.accepts('text/html')){
|
res.status(200).json(space);
|
||||||
res.redirect("/spaces/"+space._id + "?spaceAuth=" + token);
|
}
|
||||||
}else{
|
|
||||||
res.status(200).json(space);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if(req.accepts('text/html')){
|
|
||||||
res.status(404).render('not_found', { title: 'Page Not Found.' });
|
|
||||||
} else {
|
|
||||||
res.status(404).json({});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
res.status(429).json({"error": "Too Many Requests"});
|
if (req.accepts('text/html')) {
|
||||||
|
res.status(404).render('not_found', {});
|
||||||
|
} else {
|
||||||
|
res.status(404).json({});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
router.get('/spaces/:id', (req, res) => {
|
router.get('/spaces/:id', (req, res) => {
|
||||||
if (req.headers['user-agent']) {
|
res.render('spacedeck', { config:config, user:req.user });
|
||||||
if (req.headers['user-agent'].match(/facebook/)) {
|
|
||||||
Space.findOne({"_id": req.params.id }).exec(function (err, space) {
|
|
||||||
if (err) {
|
|
||||||
res.status(400).json(err);
|
|
||||||
} else {
|
|
||||||
if (space) {
|
|
||||||
if (space.access_mode == "public") {
|
|
||||||
Artifact.find({"space_id": req.params.id }).populate("creator").exec(function(err, artifacts) {
|
|
||||||
space.artifacts = artifacts;
|
|
||||||
res.render('facebook', { space: space });
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
res.redirect("/?error=space_not_accessible");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
res.render('not_found', { title: 'Spaces' });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// not facebook, render javascript
|
|
||||||
res.render('spacedeck', { title: 'Space' });
|
|
||||||
}
|
|
||||||
} else res.render('spacedeck', { title: 'Space' });
|
|
||||||
});
|
|
||||||
|
|
||||||
router.get('/users/byteam/:team_id/join', (req, res) => {
|
|
||||||
if (!req.user) {
|
|
||||||
const q = {confirmation_token: req.query.confirmation_token, account_type: "email", team: req.params.team_id};
|
|
||||||
User.findOne(q, (err, user) => {
|
|
||||||
if (err) {
|
|
||||||
res.status(400).json({"error":"session.users"});
|
|
||||||
} else {
|
|
||||||
if (user) {
|
|
||||||
crypto.randomBytes(48, function(ex, buf) {
|
|
||||||
const token = buf.toString('hex');
|
|
||||||
|
|
||||||
var session = {
|
|
||||||
token: token,
|
|
||||||
ip: req.ip,
|
|
||||||
device: "web",
|
|
||||||
created_at: new Date()
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!user.sessions)
|
|
||||||
user.sessions = [];
|
|
||||||
|
|
||||||
user.sessions.push(session);
|
|
||||||
user.confirmed_at = new Date();
|
|
||||||
user.confirmation_token = null;
|
|
||||||
|
|
||||||
user.save(function(err, result) {
|
|
||||||
// FIXME
|
|
||||||
const secure = process.env.NODE_ENV == "production" || process.env.NODE_ENV == "staging";
|
|
||||||
const domain = (process.env.NODE_ENV == "production") ? ".spacedeck.com" : ".spacedecklocal.de";
|
|
||||||
|
|
||||||
res.cookie('sdsession', token, { domain: domain, httpOnly: true, secure: secure});
|
|
||||||
res.redirect("/spaces");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
res.status(404).json({"error": "not found"});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
} else {
|
|
||||||
res.redirect("/spaces");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
router.get('/teams/:id/join', function(req, res, next) {
|
|
||||||
if (req.user) {
|
|
||||||
if (!req.user.team) {
|
|
||||||
Team.findOne({"_id": req.params.id}, function(err, team) {
|
|
||||||
if (team) {
|
|
||||||
const idx = team.invitation_codes.indexOf(req.query.code);
|
|
||||||
if (idx >= 0) {
|
|
||||||
const u = req.user;
|
|
||||||
u.team = team;
|
|
||||||
|
|
||||||
if(!u.confirmed_at) {
|
|
||||||
u.confirmed_at = new Date();
|
|
||||||
}
|
|
||||||
|
|
||||||
u.payment_plan_key = team.payment_plan_key;
|
|
||||||
u.save(function(err) {
|
|
||||||
if (err) res.status(400).json(err);
|
|
||||||
else {
|
|
||||||
team.invitation_condes = team.invitation_codes.slice(idx);
|
|
||||||
team.save(function(err) {
|
|
||||||
team.invitation_codes = null;
|
|
||||||
|
|
||||||
var finish = function(team, users) {
|
|
||||||
User.find({"_id": {"$in": team.admins}}).exec((err, admins) => {
|
|
||||||
if(admins) {
|
|
||||||
admins.forEach((admin) => {
|
|
||||||
mailer.sendMail(
|
|
||||||
admin.email,
|
|
||||||
req.i18n.__("team_new_member_subject", team.name),
|
|
||||||
req.i18n.__("team_new_member_body", u.email, team.name)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
User.find({team: team}, function(err, users) {
|
|
||||||
finish(team, users);
|
|
||||||
res.redirect("/spaces");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
res.redirect("/spaces?error=team_code_notfound");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
res.redirect("/spaces?error=team_notfound");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
res.redirect("/spaces?error=team_already");
|
|
||||||
}
|
|
||||||
} else res.redirect("/login");
|
|
||||||
});
|
|
||||||
|
|
||||||
router.get('/qrcode/:id', function(req, res) {
|
|
||||||
Space.findOne({"_id": req.params.id}).exec(function(err, space) {
|
|
||||||
if (space) {
|
|
||||||
const url = config.get("endpoint") + "/s/"+space.edit_hash;
|
|
||||||
const code = qr.image(url, { type: 'svg' });
|
|
||||||
res.type('svg');
|
|
||||||
code.pipe(res);
|
|
||||||
} else {
|
|
||||||
res.status(404).json({
|
|
||||||
"error": "not_found"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
require('./models/schema');
|
const db = require('./models/db.js');
|
||||||
require("log-timestamp");
|
require("log-timestamp");
|
||||||
|
|
||||||
const config = require('config');
|
const config = require('config');
|
||||||
@@ -15,37 +15,31 @@ const favicon = require('serve-favicon');
|
|||||||
const logger = require('morgan');
|
const logger = require('morgan');
|
||||||
const cookieParser = require('cookie-parser');
|
const cookieParser = require('cookie-parser');
|
||||||
const bodyParser = require('body-parser');
|
const bodyParser = require('body-parser');
|
||||||
const mongoose = require('mongoose');
|
|
||||||
const swig = require('swig');
|
|
||||||
const i18n = require('i18n-2');
|
const i18n = require('i18n-2');
|
||||||
const helmet = require('helmet');
|
const helmet = require('helmet');
|
||||||
|
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
const app = express();
|
const app = express();
|
||||||
|
const serveStatic = require('serve-static');
|
||||||
|
|
||||||
const isProduction = app.get('env') === 'production';
|
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') + ")");
|
console.log("Booting Spacedeck Open… (environment: " + app.get('env') + ")");
|
||||||
|
|
||||||
app.use(logger(isProduction ? 'combined' : 'dev'));
|
app.use(logger(isProduction ? 'combined' : 'dev'));
|
||||||
|
|
||||||
i18n.expressBind(app, {
|
i18n.expressBind(app, {
|
||||||
locales: ["en", "de", "fr"],
|
locales: ["en", "de", "fr", "oc", "es"],
|
||||||
defaultLocale: "en",
|
defaultLocale: "en",
|
||||||
cookieName: "spacedeck_locale",
|
cookieName: "spacedeck_locale",
|
||||||
devMode: (app.get('env') == 'development')
|
devMode: (app.get('env') == 'development')
|
||||||
});
|
});
|
||||||
|
|
||||||
swig.setDefaults({
|
app.set('view engine', 'ejs');
|
||||||
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) {
|
if (isProduction) {
|
||||||
app.set('views', path.join(__dirname, 'build', 'views'));
|
app.set('views', path.join(__dirname, 'build', 'views'));
|
||||||
@@ -67,30 +61,22 @@ app.use(bodyParser.urlencoded({
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
app.use(cookieParser());
|
app.use(cookieParser());
|
||||||
app.use(helmet.noCache())
|
//app.use(helmet.frameguard())
|
||||||
app.use(helmet.frameguard())
|
//app.use(helmet.xssFilter())
|
||||||
app.use(helmet.xssFilter())
|
/*app.use(helmet.hsts({
|
||||||
app.use(helmet.hsts({
|
|
||||||
maxAge: 7776000000,
|
maxAge: 7776000000,
|
||||||
includeSubdomains: true
|
includeSubDomains: true
|
||||||
}))
|
}))*/
|
||||||
app.disable('x-powered-by');
|
app.disable('x-powered-by');
|
||||||
app.use(helmet.noSniff())
|
//app.use(helmet.noSniff())
|
||||||
|
|
||||||
// CUSTOM MIDDLEWARES
|
//app.use(require("./middlewares/error_helpers"));
|
||||||
|
//app.use(require("./middlewares/cors"));
|
||||||
app.use(require("./middlewares/templates"));
|
app.use(require("./middlewares/session"));
|
||||||
app.use(require("./middlewares/error_helpers"));
|
|
||||||
app.use(require("./middlewares/setuser"));
|
|
||||||
app.use(require("./middlewares/subdomain"));
|
|
||||||
app.use(require("./middlewares/cors"));
|
|
||||||
app.use(require("./middlewares/i18n"));
|
app.use(require("./middlewares/i18n"));
|
||||||
app.use("/api", require("./middlewares/api_helpers"));
|
app.use("/api", require("./middlewares/api_helpers"));
|
||||||
app.use('/api/spaces/:id', require("./middlewares/space_helpers"));
|
app.use('/api/spaces/:id', require("./middlewares/space_helpers"));
|
||||||
app.use('/api/spaces/:id/artifacts/:artifact_id', require("./middlewares/artifact_helpers"));
|
app.use('/api/spaces/:id/artifacts/:artifact_id', require("./middlewares/artifact_helpers"));
|
||||||
app.use('/api/teams', require("./middlewares/team_helpers"));
|
|
||||||
|
|
||||||
// REAL ROUTES
|
|
||||||
|
|
||||||
app.use('/api/users', require('./routes/api/users'));
|
app.use('/api/users', require('./routes/api/users'));
|
||||||
app.use('/api/memberships', require('./routes/api/memberships'));
|
app.use('/api/memberships', require('./routes/api/memberships'));
|
||||||
@@ -105,15 +91,19 @@ spaceRouter.use('/:id/digest', require('./routes/api/space_digest'));
|
|||||||
spaceRouter.use('/:id', require('./routes/api/space_exports'));
|
spaceRouter.use('/:id', require('./routes/api/space_exports'));
|
||||||
|
|
||||||
app.use('/api/sessions', require('./routes/api/sessions'));
|
app.use('/api/sessions', require('./routes/api/sessions'));
|
||||||
app.use('/api/teams', require('./routes/api/teams'));
|
//app.use('/api/webgrabber', require('./routes/api/webgrabber'));
|
||||||
app.use('/api/webgrabber', require('./routes/api/webgrabber'));
|
|
||||||
app.use('/', require('./routes/root'));
|
app.use('/', require('./routes/root'));
|
||||||
|
|
||||||
|
if (config.get('storage_local_path')) {
|
||||||
|
app.use('/storage', serveStatic(config.get('storage_local_path')+"/"+config.get('storage_bucket'), {
|
||||||
|
maxAge: 24*3600
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
// catch 404 and forward to error handler
|
// catch 404 and forward to error handler
|
||||||
app.use(require('./middlewares/404'));
|
//app.use(require('./middlewares/404'));
|
||||||
if (app.get('env') == 'development') {
|
if (app.get('env') == 'development') {
|
||||||
app.set('view cache', false);
|
app.set('view cache', false);
|
||||||
swig.setDefaults({cache: false});
|
|
||||||
} else {
|
} else {
|
||||||
app.use(require('./middlewares/500'));
|
app.use(require('./middlewares/500'));
|
||||||
}
|
}
|
||||||
@@ -121,14 +111,14 @@ if (app.get('env') == 'development') {
|
|||||||
module.exports = app;
|
module.exports = app;
|
||||||
|
|
||||||
// CONNECT TO DATABASE
|
// CONNECT TO DATABASE
|
||||||
const mongoHost = process.env.MONGO_PORT_27017_TCP_ADDR || 'db';
|
db.init();
|
||||||
mongoose.connect('mongodb://' + mongoHost + '/spacedeck');
|
|
||||||
|
|
||||||
// START WEBSERVER
|
// START WEBSERVER
|
||||||
const port = 9666;
|
const host = config.get('host');
|
||||||
|
const port = config.get('port');
|
||||||
const server = http.Server(app).listen(port, () => {
|
|
||||||
|
|
||||||
|
const server = http.Server(app).listen(port, host, () => {
|
||||||
|
|
||||||
if ("send" in process) {
|
if ("send" in process) {
|
||||||
process.send('online');
|
process.send('online');
|
||||||
}
|
}
|
||||||
@@ -160,14 +150,13 @@ const server = http.Server(app).listen(port, () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
//WEBSOCKETS & WORKER
|
|
||||||
websockets.startWebsockets(server);
|
websockets.startWebsockets(server);
|
||||||
redis.connectRedis();
|
redis.connectRedis();
|
||||||
|
|
||||||
process.on('message', (message) => {
|
/*process.on('message', (message) => {
|
||||||
console.log("Process message:", message);
|
console.log("Process message:", message);
|
||||||
if (message === 'shutdown') {
|
if (message === 'shutdown') {
|
||||||
console.log("Exiting spacedeck.");
|
console.log("Exiting Spacedeck.");
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
}
|
}
|
||||||
});
|
});*/
|
||||||
@@ -26,12 +26,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*&.artifact-text.text-blank [contentEditable=true]:not(.text-editing) p:first-child::after {
|
&.artifact-text.text-blank [contentEditable=true]:not(.text-editing) p:first-child::after {
|
||||||
content: "Double click to edit";
|
content: "Double click to edit";
|
||||||
opacity: 0.25;
|
opacity: 0.25;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.artifact-text.text-blank [contentEditable=true].text-editing p:first-child::after {
|
/*&.artifact-text.text-blank [contentEditable=true].text-editing p:first-child::after {
|
||||||
content: "Type here";
|
content: "Type here";
|
||||||
opacity: 0.25;
|
opacity: 0.25;
|
||||||
}*/
|
}*/
|
||||||
@@ -288,7 +288,7 @@
|
|||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.title {font-size: 20px; }
|
.title {font-size: 20px; }
|
||||||
.image {
|
.image {
|
||||||
height: auto;
|
height: auto;
|
||||||
@@ -335,7 +335,7 @@
|
|||||||
padding: 10px 15px;
|
padding: 10px 15px;
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
video {
|
video {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
@@ -469,11 +469,10 @@
|
|||||||
color: black;
|
color: black;
|
||||||
//@include user-select(none);
|
//@include user-select(none);
|
||||||
white-space: normal;
|
white-space: normal;
|
||||||
font-size: 18px;
|
font-size: 36px;
|
||||||
|
|
||||||
&.artifact-zone {
|
&.artifact-zone {
|
||||||
border: 1px solid rgba(46,204,113,1);
|
background-color: rgba(0,0,0,0.05);
|
||||||
background-color: rgba(46,204,113,0.025);
|
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
&:after {display: none; }
|
&:after {display: none; }
|
||||||
.shape {display: none; }
|
.shape {display: none; }
|
||||||
@@ -502,7 +501,7 @@ body:not(.present-mode) {
|
|||||||
.Medium {
|
.Medium {
|
||||||
cursor: text;
|
cursor: text;
|
||||||
}
|
}
|
||||||
|
|
||||||
.artifact {
|
.artifact {
|
||||||
|
|
||||||
&.selected.text-editing,
|
&.selected.text-editing,
|
||||||
@@ -553,6 +552,10 @@ body:not(.present-mode) {
|
|||||||
cursor: grab !important;
|
cursor: grab !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tool-note {
|
||||||
|
cursor: crosshair !important;
|
||||||
|
}
|
||||||
|
|
||||||
.artifact.state-idle {
|
.artifact.state-idle {
|
||||||
.progress, .progress-text {
|
.progress, .progress-text {
|
||||||
display: none;
|
display: none;
|
||||||
@@ -563,16 +566,17 @@ body:not(.present-mode) {
|
|||||||
.progress {
|
.progress {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
|
|
||||||
background-color: $blue;
|
background-color: $blue;
|
||||||
opacity: 0.9;
|
opacity: 0.9;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
.progress-text {
|
.progress-text {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 0px;
|
left: 0px;
|
||||||
top: 0px;
|
top: 0px;
|
||||||
@@ -583,8 +587,8 @@ body:not(.present-mode) {
|
|||||||
video, audio, .tl-controls, .timeline {
|
video, audio, .tl-controls, .timeline {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
background-color: white;
|
background-color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -625,7 +629,7 @@ body:not(.present-mode) {
|
|||||||
img {
|
img {
|
||||||
width: 200px;
|
width: 200px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.artifact {
|
.artifact {
|
||||||
display: block;
|
display: block;
|
||||||
width: 200px;
|
width: 200px;
|
||||||
|
|||||||
@@ -7,12 +7,6 @@
|
|||||||
|
|
||||||
.btn-group.colors {
|
.btn-group.colors {
|
||||||
.btn {
|
.btn {
|
||||||
// padding: 4px;
|
|
||||||
// background-clip: content-box;
|
|
||||||
// padding-right: 2px;
|
|
||||||
// &:last-child {
|
|
||||||
// padding-right: 4px;
|
|
||||||
// }
|
|
||||||
box-shadow: inset 0 0 30px 0px rgba(40,40,40,0.1);
|
box-shadow: inset 0 0 30px 0px rgba(40,40,40,0.1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -64,7 +58,7 @@
|
|||||||
backface-visibility: hidden;
|
backface-visibility: hidden;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
background-color: $light;
|
background-color: $light;
|
||||||
color: $medium;;
|
color: $black;
|
||||||
|
|
||||||
@include user-select(none);
|
@include user-select(none);
|
||||||
&:last-child {border: none;}
|
&:last-child {border: none;}
|
||||||
@@ -82,12 +76,9 @@
|
|||||||
|
|
||||||
&.btn-link {
|
&.btn-link {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
color: $medium;;
|
color: $medium;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.facebook {background-color: $facebook !important; color: white !important;}
|
|
||||||
&.twitter {background-color: $twitter !important; color: white !important; }
|
|
||||||
|
|
||||||
&.btn-round {
|
&.btn-round {
|
||||||
border-radius: 100px !important;
|
border-radius: 100px !important;
|
||||||
}
|
}
|
||||||
@@ -96,21 +87,10 @@
|
|||||||
border-radius: 6px !important;
|
border-radius: 6px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
// &.close {
|
|
||||||
// position: absolute;
|
|
||||||
// top: 15px;
|
|
||||||
// right: 15px;
|
|
||||||
// z-index: 4000;
|
|
||||||
// font-size: 40px;
|
|
||||||
// }
|
|
||||||
|
|
||||||
&.btn-nude {
|
&.btn-nude {
|
||||||
min-width: 0 !important;
|
min-width: 0 !important;
|
||||||
// font-size: inherit !important;
|
|
||||||
padding: 0 !important;
|
padding: 0 !important;
|
||||||
// height: auto !important;
|
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
color: $medium;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&.btn-nude + .btn-nude {
|
&.btn-nude + .btn-nude {
|
||||||
@@ -123,7 +103,7 @@
|
|||||||
|
|
||||||
&.btn-stroke {
|
&.btn-stroke {
|
||||||
box-shadow: inset 0 0 0 1px $dark;
|
box-shadow: inset 0 0 0 1px $dark;
|
||||||
color: $dark !important;
|
color: $black;
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
&:active {
|
&:active {
|
||||||
box-shadow: inset 0 0 0 1px white;
|
box-shadow: inset 0 0 0 1px white;
|
||||||
@@ -132,9 +112,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&.btn-stroke-darken {
|
&.btn-stroke-darken {
|
||||||
//box-shadow: inset 0 0 0 1px rgba(0,0,0,0.1);
|
border: 1px solid $black;
|
||||||
border: 1px solid rgba(0,0,0,0.1);
|
color: $black;
|
||||||
color: $medium;
|
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
&:active {
|
&:active {
|
||||||
//box-shadow: inset 0 0 0 1px $dark;
|
//box-shadow: inset 0 0 0 1px $dark;
|
||||||
@@ -263,9 +242,18 @@
|
|||||||
|
|
||||||
&.btn-transparent {
|
&.btn-transparent {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
color: $medium;
|
color: $dark;
|
||||||
&.active {color: $darker !important; }
|
&.active {
|
||||||
&.open {color: white !important; }
|
//color: $black !important;
|
||||||
|
color: $white;
|
||||||
|
background-color: $black;
|
||||||
|
}
|
||||||
|
&.open {
|
||||||
|
//color: $black !important;
|
||||||
|
color: $white;
|
||||||
|
background-color: $black;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.btn-transparent-medium {
|
&.btn-transparent-medium {
|
||||||
@@ -313,7 +301,7 @@
|
|||||||
|
|
||||||
&.btn-dark {
|
&.btn-dark {
|
||||||
background-color: $dark ;
|
background-color: $dark ;
|
||||||
color: $medium;
|
color: $white;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.btn-medium {
|
&.btn-medium {
|
||||||
@@ -481,7 +469,6 @@
|
|||||||
|
|
||||||
&.btn-icon {
|
&.btn-icon {
|
||||||
padding: 0px !important;
|
padding: 0px !important;
|
||||||
font-weight: bold;
|
|
||||||
max-width: 60px;
|
max-width: 60px;
|
||||||
|
|
||||||
&.btn-xl { max-width: 80px; }
|
&.btn-xl { max-width: 80px; }
|
||||||
@@ -508,30 +495,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.btn-social {
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
&:hover .icon,
|
|
||||||
.number {
|
|
||||||
@include scale(0,0);
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover .number {
|
|
||||||
@include transition( all 0.1s 0.1s ease-in-out);
|
|
||||||
@include scale(1,1);
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.number,
|
|
||||||
.icon {
|
|
||||||
@include transition( all 0.1s 0s ease-in-out);
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.btn-md.btn-icon-labeled {
|
&.btn-md.btn-icon-labeled {
|
||||||
.icon:before {
|
.icon:before {
|
||||||
line-height: 29px;
|
line-height: 29px;
|
||||||
@@ -567,7 +530,6 @@
|
|||||||
.icon:before {line-height: 42px; }
|
.icon:before {line-height: 42px; }
|
||||||
.icon-label {
|
.icon-label {
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
text-transform: capitalize;
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
margin: 8px 0;
|
margin: 8px 0;
|
||||||
display: block;
|
display: block;
|
||||||
@@ -580,7 +542,7 @@
|
|||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
padding: 0 0px;
|
padding: 0 0px;
|
||||||
font-weight: bold;
|
font-weight: 300;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.hover {
|
&.hover {
|
||||||
@@ -714,7 +676,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
> * {
|
> * {
|
||||||
border-radius: 0 !important;
|
|
||||||
background-clip: padding-box;
|
background-clip: padding-box;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
float: left;
|
float: left;
|
||||||
@@ -775,7 +736,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.btn-group {
|
.btn-group {
|
||||||
@include scale(0,0);
|
//@include scale(0,0);
|
||||||
//@include transition( all 0.1s 0s ease-in-out);
|
//@include transition( all 0.1s 0s ease-in-out);
|
||||||
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@@ -787,7 +748,7 @@
|
|||||||
margin-left: -12px;
|
margin-left: -12px;
|
||||||
|
|
||||||
.btn {
|
.btn {
|
||||||
@include scale(0,0);
|
//@include scale(0,0);
|
||||||
//@include transition( all 0.1s 0.05s ease-in-out);
|
//@include transition( all 0.1s 0.05s ease-in-out);
|
||||||
|
|
||||||
|
|
||||||
@@ -979,31 +940,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-group.bottom-left > .btn {
|
// !btn-group
|
||||||
border-radius: 0px;
|
|
||||||
|
|
||||||
&:first-child{
|
|
||||||
border-top-left-radius: 0px;
|
|
||||||
border-bottom-left-radius: 0px;
|
|
||||||
}
|
|
||||||
&.last,
|
|
||||||
&:last-child{
|
|
||||||
border-top-right-radius: 3px;
|
|
||||||
border-bottom-right-radius: 0px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-xyz {
|
|
||||||
position: relative;
|
|
||||||
display: inline-block;
|
|
||||||
line-height: 0px;
|
|
||||||
padding: 0px;
|
|
||||||
font-size: 0px;
|
|
||||||
vertical-align: middle;
|
|
||||||
white-space: nowrap;
|
|
||||||
@include clearfix;
|
|
||||||
min-height: 44px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-group {
|
.btn-group {
|
||||||
position: relative;
|
position: relative;
|
||||||
@@ -1014,13 +951,16 @@
|
|||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
|
||||||
|
//border: 1px solid $dark;
|
||||||
|
border-radius: 5px;
|
||||||
|
|
||||||
&.dark {
|
&.dark {
|
||||||
border-radius: $radius;
|
border-radius: $radius;
|
||||||
background-color: $dark;
|
background-color: $dark;
|
||||||
color: $lighter;
|
color: $white;
|
||||||
|
|
||||||
.btn {
|
.btn {
|
||||||
color: $lighter;
|
color: $white;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1159,4 +1099,4 @@
|
|||||||
margin: 4px;
|
margin: 4px;
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
border-radius: 50px;
|
border-radius: 50px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -96,41 +96,37 @@
|
|||||||
border-bottom-right-radius: $radius*3;
|
border-bottom-right-radius: $radius*3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dialog-account {
|
||||||
|
width: 600px;
|
||||||
|
margin: auto;
|
||||||
|
margin-top: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
.dialog {
|
.dialog {
|
||||||
|
|
||||||
font-size: 13px;
|
|
||||||
|
|
||||||
ol, ul, p {
|
|
||||||
font-size: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
> .btn-block:last-child {
|
> .btn-block:last-child {
|
||||||
border-top-left-radius: 0px;
|
border-top-left-radius: 0px;
|
||||||
border-top-right-radius: 0px;
|
border-top-right-radius: 0px;
|
||||||
border-bottom-left-radius: $radius*3;
|
border-bottom-left-radius: $radius*3;
|
||||||
border-bottom-right-radius: $radius*3;
|
border-bottom-right-radius: $radius*3;
|
||||||
}
|
}
|
||||||
|
|
||||||
min-width: 200px;
|
|
||||||
|
|
||||||
@include backface-visibility(hidden);
|
|
||||||
white-space: normal;
|
|
||||||
z-index: 1000;
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
// white-space: normal;
|
font-size: 15px;
|
||||||
|
border: 1px solid black;
|
||||||
|
box-shadow: 0 0 30px 1px rgba(0, 0, 0, 0.15);
|
||||||
|
border-radius: 5px;
|
||||||
|
white-space: normal;
|
||||||
|
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
@include user-select(none);
|
@include transition(all 0.125s ease-in-out);
|
||||||
@include transition( all 0.125s ease-in-out);
|
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
background-color: $light;
|
background-color: $light;
|
||||||
color: $medium;
|
color: $dark;
|
||||||
&.dark {background-color: $dark; }
|
&.dark {
|
||||||
|
background-color: $dark;
|
||||||
|
}
|
||||||
|
|
||||||
border-radius: $radius*3;
|
|
||||||
box-shadow: 0 0 1px 1px rgba(0, 0, 0, 0.05), 0 2px 7px rgba(0, 0, 0, 0.1);
|
|
||||||
|
|
||||||
.dialog-tabs-wrapper {
|
.dialog-tabs-wrapper {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
border-top-left-radius: $radius*3;
|
border-top-left-radius: $radius*3;
|
||||||
@@ -150,15 +146,13 @@
|
|||||||
&:hover span {color: $dark; }
|
&:hover span {color: $dark; }
|
||||||
|
|
||||||
&.open span {
|
&.open span {
|
||||||
background-color: $light;
|
background-color: white;
|
||||||
color: $dark;
|
color: $dark;
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
box-shadow: 0 0 1px 1px rgba(0, 0, 0, 0.05), 0 2px 7px rgba(0, 0, 0, 0.1) !important;
|
|
||||||
border-bottom-right-radius: 0px !important;
|
border-bottom-right-radius: 0px !important;
|
||||||
border-bottom-left-radius: 0px !important;
|
border-bottom-left-radius: 0px !important;
|
||||||
border-top-left-radius: $radius*3;
|
border-top-left-radius: $radius*3;
|
||||||
border-top-right-radius: $radius*3;
|
border-top-right-radius: $radius*3;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&:first-child span {
|
&:first-child span {
|
||||||
@@ -200,7 +194,6 @@
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.dialog-section {
|
.dialog-section {
|
||||||
&:first-child {border: none !important; }
|
&:first-child {border: none !important; }
|
||||||
border-top: 2px solid rgba(0,0,0,0.1);
|
border-top: 2px solid rgba(0,0,0,0.1);
|
||||||
@@ -228,4 +221,13 @@
|
|||||||
h4 .icon {
|
h4 .icon {
|
||||||
height: 38px;
|
height: 38px;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
// account dialog
|
||||||
|
&.dialog-freestanding {
|
||||||
|
margin: auto;
|
||||||
|
position: relative;
|
||||||
|
top: 150px;
|
||||||
|
border: none;
|
||||||
|
width: 800px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -43,9 +43,6 @@ $predelay: 0;
|
|||||||
|
|
||||||
&.hover:hover,
|
&.hover:hover,
|
||||||
&.open {
|
&.open {
|
||||||
// &:before {opacity: 0.125; }
|
|
||||||
// pointer-events: auto;
|
|
||||||
background-color: $dark;
|
|
||||||
background-color: $light;
|
background-color: $light;
|
||||||
|
|
||||||
> * {
|
> * {
|
||||||
@@ -111,8 +108,8 @@ $predelay: 0;
|
|||||||
}
|
}
|
||||||
|
|
||||||
&:last-child > .btn{
|
&:last-child > .btn{
|
||||||
border-top-right-radius: $radius ;
|
border-top-right-radius: $radius;
|
||||||
border-bottom-right-radius: $radius ;
|
border-bottom-right-radius: $radius;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -121,6 +118,10 @@ $predelay: 0;
|
|||||||
display: inline-block;
|
display: inline-block;
|
||||||
position: relative;
|
position: relative;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
|
|
||||||
|
a {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
&.dropdown-block {
|
&.dropdown-block {
|
||||||
display: block;
|
display: block;
|
||||||
@@ -143,8 +144,7 @@ $predelay: 0;
|
|||||||
|
|
||||||
&.light > .dropdown-menu,
|
&.light > .dropdown-menu,
|
||||||
&.light > .dialog {
|
&.light > .dialog {
|
||||||
background: $light;
|
background: white;
|
||||||
color: $medium;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
> .dropdown-menu {
|
> .dropdown-menu {
|
||||||
@@ -189,8 +189,6 @@ $predelay: 0;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
&.hover:hover > .dialog,
|
&.hover:hover > .dialog,
|
||||||
&.hover:hover > .dropdown-menu,
|
&.hover:hover > .dropdown-menu,
|
||||||
|
|
||||||
@@ -206,9 +204,7 @@ $predelay: 0;
|
|||||||
&.open {
|
&.open {
|
||||||
> .dialog,
|
> .dialog,
|
||||||
> .dropdown-menu {
|
> .dropdown-menu {
|
||||||
-webkit-transform: translate3d(-50%, -50%, 100px) scale(1);
|
//transform: translate3d(-50%, -50%, 100px) scale(1);
|
||||||
-ms-transform: translate3d(-50%, -50%, 100px) scale(1);
|
|
||||||
transform: translate3d(-50%, -50%, 100px) scale(1);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -217,10 +213,8 @@ $predelay: 0;
|
|||||||
left: 50%;
|
left: 50%;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
margin-top: 0px;
|
margin-top: 0px;
|
||||||
@include transform-origin(center center);
|
//@include transform-origin(center center);
|
||||||
-webkit-transform: translate3d(-50%, -50%, 100px) scale(0.93,0.8);
|
//transform: translate3d(-50%, -50%, 100px) scale(0.93,0.8);
|
||||||
-ms-transform: translate3d(-50%, -50%, 100px) scale(0.93,0.8);
|
|
||||||
transform: translate3d(-50%, -50%, 100px) scale(0.93,0.8);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -230,10 +224,8 @@ $predelay: 0;
|
|||||||
top: auto;
|
top: auto;
|
||||||
bottom: 100%;
|
bottom: 100%;
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
@include transform-origin(bottom left);
|
//@include transform-origin(bottom left);
|
||||||
-webkit-transform: translate3d(-33%, 0%, 100px) scale(0.93,0.8);
|
//transform: translate3d(-33%, 0%, 100px) scale(0.93,0.8);
|
||||||
-ms-transform: translate3d(-33%, 0%, 100px) scale(0.93,0.8);
|
|
||||||
transform: translate3d(-33%, 0%, 100px) scale(0.93,0.8);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -243,10 +235,8 @@ $predelay: 0;
|
|||||||
top: auto;
|
top: auto;
|
||||||
bottom: 100%;
|
bottom: 100%;
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
@include transform-origin(bottom center);
|
//@include transform-origin(bottom center);
|
||||||
-webkit-transform: translate3d(-50%, 0%, 100px) scale(0.93,0.8);
|
//transform: translate3d(-50%, 0%, 100px) scale(0.93,0.8);
|
||||||
-ms-transform: translate3d(-50%, 0%, 100px) scale(0.93,0.8);
|
|
||||||
transform: translate3d(-50%, 0%, 100px) scale(0.93,0.8);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -257,33 +247,37 @@ $predelay: 0;
|
|||||||
top: 100%;
|
top: 100%;
|
||||||
bottom: auto;
|
bottom: auto;
|
||||||
margin-top: -16px;
|
margin-top: -16px;
|
||||||
@include transform-origin(top center);
|
//@include transform-origin(top center);
|
||||||
-webkit-transform: translate3d(-50%, 0%, 100px) scale(0.93,0.8);
|
//transform: translate3d(-50%, 0%, 100px) scale(0.93,0.8);
|
||||||
-ms-transform: translate3d(-50%, 0%, 100px) scale(0.93,0.8);
|
|
||||||
transform: translate3d(-50%, 0%, 100px) scale(0.93,0.8);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.top.left {
|
||||||
|
> .dialog,
|
||||||
|
> .dropdown-menu {
|
||||||
|
left: 70px;
|
||||||
|
margin-top: -60px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&.top.right {
|
&.top.right {
|
||||||
> .dialog,
|
> .dialog,
|
||||||
> .dropdown-menu {
|
> .dropdown-menu {
|
||||||
top: 100%;
|
top: 100%;
|
||||||
bottom: auto;
|
bottom: auto;
|
||||||
left: auto;
|
left: auto;
|
||||||
right: 0;
|
|
||||||
margin-top: 16px;
|
right: 70px;
|
||||||
@include transform-origin(top right);
|
margin-top: -60px;
|
||||||
-webkit-transform: translate3d(0%, 0%, 100px) scale(0.93,0.8);
|
|
||||||
-ms-transform: translate3d(0%, 0%, 100px) scale(0.93,0.8);
|
//@include transform-origin(top right);
|
||||||
transform: translate3d(0%, 0%, 100px) scale(0.93,0.8);
|
//transform: translate3d(0%, 0%, 100px) scale(0.93,0.8);
|
||||||
}
|
}
|
||||||
&.hover:hover,
|
&.hover:hover,
|
||||||
&.open {
|
&.open {
|
||||||
> .dialog,
|
> .dialog,
|
||||||
> .dropdown-menu {
|
> .dropdown-menu {
|
||||||
-webkit-transform: translate3d(0%, 0%, 100px) scale(1);
|
//transform: translate3d(0%, 0%, 100px) scale(1);
|
||||||
-ms-transform: translate3d(0%, 0%, 100px) scale(1);
|
|
||||||
transform: translate3d(0%, 0%, 100px) scale(1);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -312,9 +306,7 @@ $predelay: 0;
|
|||||||
|
|
||||||
> .dialog,
|
> .dialog,
|
||||||
> .dropdown-menu {
|
> .dropdown-menu {
|
||||||
-webkit-transform: translate3d(-50%, 0%, 100px) scale(1);
|
//transform: translate3d(-50%, 0%, 100px) scale(1);
|
||||||
-ms-transform: translate3d(-50%, 0%, 100px) scale(1);
|
|
||||||
transform: translate3d(-50%, 0%, 100px) scale(1);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -324,9 +316,7 @@ $predelay: 0;
|
|||||||
&.open {
|
&.open {
|
||||||
> .dialog,
|
> .dialog,
|
||||||
> .dropdown-menu {
|
> .dropdown-menu {
|
||||||
-webkit-transform: translate3d(-33%, 0%, 100px) scale(1) !important;
|
//transform: translate3d(-33%, 0%, 100px) scale(1) !important;
|
||||||
-ms-transform: translate3d(-33%, 0%, 100px) scale(1) !important;
|
|
||||||
transform: translate3d(-33%, 0%, 100px) scale(1) !important;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -334,7 +324,7 @@ $predelay: 0;
|
|||||||
|
|
||||||
|
|
||||||
.dropdown {
|
.dropdown {
|
||||||
&.options-3 {
|
/*&.options-3 {
|
||||||
&.option-1:after { margin-left: -68px;}
|
&.option-1:after { margin-left: -68px;}
|
||||||
&.option-2:after { margin-left: -8px;}
|
&.option-2:after { margin-left: -8px;}
|
||||||
&.option-3:after { margin-left: 52px;}
|
&.option-3:after { margin-left: 52px;}
|
||||||
@@ -348,8 +338,9 @@ $predelay: 0;
|
|||||||
-webkit-transform: scale(1);
|
-webkit-transform: scale(1);
|
||||||
-ms-transform: scale(1);
|
-ms-transform: scale(1);
|
||||||
transform: scale(1);
|
transform: scale(1);
|
||||||
}
|
}*/
|
||||||
|
|
||||||
|
/*
|
||||||
&:after {
|
&:after {
|
||||||
@include transition( all 0.1s ease-in-out 0s);
|
@include transition( all 0.1s ease-in-out 0s);
|
||||||
content: "";
|
content: "";
|
||||||
@@ -362,26 +353,24 @@ $predelay: 0;
|
|||||||
margin-left: -8px;
|
margin-left: -8px;
|
||||||
pointer-events: none !important;
|
pointer-events: none !important;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
-webkit-transform: scale(0,0);
|
//transform: scale(0,0);
|
||||||
-ms-transform: scale(0,0);
|
|
||||||
transform: scale(0,0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&.bottom:after, &.bottomleft:after {
|
&.bottom:after, &.bottomleft:after {
|
||||||
@include transform-origin(bottom center);
|
//@include transform-origin(bottom center);
|
||||||
bottom: 100%;
|
bottom: 100%;
|
||||||
border-bottom: 8px solid transparent;
|
border-bottom: 8px solid transparent;
|
||||||
border-right: 8px solid transparent;
|
border-right: 8px solid transparent;
|
||||||
border-top: 8px solid #303030;
|
border-top: 8px solid #303030;
|
||||||
border-left: 8px solid transparent;
|
border-left: 8px solid transparent;
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
&.top:after {
|
/*&.top:after {
|
||||||
@include transform-origin(top center);
|
@include transform-origin(top center);
|
||||||
top: 100%;
|
top: 100%;
|
||||||
border-bottom: 8px solid #303030;
|
border-bottom: 8px solid #303030;
|
||||||
border-right: 8px solid transparent;
|
border-right: 8px solid transparent;
|
||||||
border-top: 8px solid transparent;
|
border-top: 8px solid transparent;
|
||||||
border-left: 8px solid transparent;
|
border-left: 8px solid transparent;
|
||||||
}
|
}*/
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -254,7 +254,6 @@
|
|||||||
// word-wrap: break-word;
|
// word-wrap: break-word;
|
||||||
|
|
||||||
.item {
|
.item {
|
||||||
box-shadow: 0 0 1pxrgba(0,0,0,0.1);
|
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
padding-right: $folder-gutter*2;
|
padding-right: $folder-gutter*2;
|
||||||
@@ -397,7 +396,10 @@
|
|||||||
|
|
||||||
&:active { opacity: 0.95 !important; }
|
&:active { opacity: 0.95 !important; }
|
||||||
|
|
||||||
box-shadow: 0 0 1px 1px rgba(0, 0, 0, 0.025), 0 2px 7px rgba(0, 0, 0, 0.025);
|
box-shadow: 0 0 30px 1px rgba(0, 0, 0, 0.15);
|
||||||
|
border: 1px solid black;
|
||||||
|
|
||||||
|
// ???
|
||||||
@include opacity(1);
|
@include opacity(1);
|
||||||
color: $medium;
|
color: $medium;
|
||||||
// color: white;
|
// color: white;
|
||||||
@@ -417,14 +419,13 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
background-size: cover;
|
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
border-top-left-radius: $radius*2;
|
border-top-left-radius: $radius*2;
|
||||||
border-top-right-radius: $radius*2;
|
border-top-right-radius: $radius*2;
|
||||||
|
|
||||||
border-bottom-left-radius: 10px;
|
border-bottom-left-radius: 10px;
|
||||||
border-bottom-right-radius: 10px;
|
border-bottom-right-radius: 10px;
|
||||||
background-position: center 100%;
|
background-position: left top;
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -476,7 +477,6 @@
|
|||||||
left: 0px;
|
left: 0px;
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
width: auto;
|
width: auto;
|
||||||
background-color: rgba(255,255,255,1);
|
|
||||||
|
|
||||||
.dropdown {
|
.dropdown {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@@ -501,30 +501,6 @@
|
|||||||
color: $dark;
|
color: $dark;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
.item-social {
|
|
||||||
padding: 8px;
|
|
||||||
border-right: 2px solid rgba(0,0,0,0.025);
|
|
||||||
@include clearfix;
|
|
||||||
color: $medium;
|
|
||||||
|
|
||||||
.item-likes,
|
|
||||||
.item-comments,
|
|
||||||
.item-shares {
|
|
||||||
position: relative;
|
|
||||||
&:hover {
|
|
||||||
.icon {opacity: 0; }
|
|
||||||
.number {opacity: 1; }
|
|
||||||
}
|
|
||||||
.number {
|
|
||||||
position: absolute;
|
|
||||||
opacity: 0;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
}
|
|
||||||
.icon {opacity: 0.5; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.item-appendix {
|
.item-appendix {
|
||||||
|
|||||||
@@ -28,7 +28,6 @@
|
|||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
color: $medium;
|
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
border-radius: $radius;
|
border-radius: $radius;
|
||||||
@@ -139,4 +138,4 @@
|
|||||||
display: inline-block !important;
|
display: inline-block !important;
|
||||||
width: auto !important;
|
width: auto !important;
|
||||||
padding-right: 15px !important;
|
padding-right: 15px !important;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,24 +2,14 @@
|
|||||||
@import "mixins";
|
@import "mixins";
|
||||||
|
|
||||||
.input-select {
|
.input-select {
|
||||||
// background-color: rgba(255,255,255,0.04);
|
background-color: rgba(255,255,255,0.04);
|
||||||
// background-image: url('images/select_arrow.gif');
|
background-image: url('images/select_arrow.gif');
|
||||||
border-radius: $radius;
|
border-radius: $radius;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
@-moz-document url-prefix() {
|
|
||||||
select.input{
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-position: right center;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
select {
|
||||||
-webkit-appearance:none;
|
|
||||||
// -moz-appearance:window;
|
|
||||||
appearance:none;
|
appearance:none;
|
||||||
padding-left: 0px;
|
padding-left: 0px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ input:invalid {
|
|||||||
top: 0;
|
top: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
font-size: 10px;
|
|
||||||
margin: 12px;
|
margin: 12px;
|
||||||
color: red;
|
color: red;
|
||||||
margin-right: 25px;
|
margin-right: 25px;
|
||||||
@@ -113,43 +112,26 @@ select {
|
|||||||
|
|
||||||
&.input-white {
|
&.input-white {
|
||||||
background-color: white;
|
background-color: white;
|
||||||
color: $medium;
|
|
||||||
box-shadow: inset 0 0 1px 1px rgba(0, 0, 0, 0.05), inset 0 0px 4px rgba(0, 0, 0, 0.1);
|
box-shadow: inset 0 0 1px 1px rgba(0, 0, 0, 0.05), inset 0 0px 4px rgba(0, 0, 0, 0.1);
|
||||||
}
|
}
|
||||||
&.input-light {
|
&.input-light {
|
||||||
background-color: $light;
|
background-color: $light;
|
||||||
color: $medium;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&.input-dark {
|
&.input-dark {
|
||||||
background-color: $darker;
|
background-color: $darker;
|
||||||
color: $medium;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&.input-lighten {
|
&.input-lighten {
|
||||||
background-color: rgba(255,255,255,0.05);
|
background-color: rgba(255,255,255,0.05);
|
||||||
color: $medium !important;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&.input-darken {
|
&.input-darken {
|
||||||
background-color: rgba(0,0,0,0.05);
|
background-color: rgba(0,0,0,0.05);
|
||||||
color: $medium;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&.input-transparent {
|
&.input-transparent {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
color: $medium;
|
|
||||||
}
|
|
||||||
|
|
||||||
// &:focus {color: white; }
|
|
||||||
|
|
||||||
&:invalid {
|
|
||||||
// background-color: rgba(198,101,84,0.05);
|
|
||||||
// color: rgba(198,101,84,0.75)
|
|
||||||
|
|
||||||
&:after {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@include input-focus();
|
@include input-focus();
|
||||||
|
|||||||
@@ -69,26 +69,27 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.handles {
|
.handles {
|
||||||
// background-color: rgba(40,140,215,0.45);
|
//border: 1px solid rgba(255,255,255,0.5);
|
||||||
border: 1px solid rgba(255,255,255,0.5);
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 0;
|
left: 0;
|
||||||
top: 0;
|
top: 0;
|
||||||
bottom: 0;
|
bottom: -1;
|
||||||
right: 0;
|
right: 0;
|
||||||
z-index: 800;
|
z-index: 800;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
|
|
||||||
|
background: rgba(255,255,255,0.1);
|
||||||
|
|
||||||
&:after{
|
&:after{
|
||||||
border: 1px dotted rgba(40,140,215,1);
|
border: 4px dotted #000000;
|
||||||
content: "";
|
content: "";
|
||||||
display: block;
|
display: block;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
height: auto;
|
height: auto;
|
||||||
width: auto;
|
width: auto;
|
||||||
top: -1px;
|
top: 0px;
|
||||||
left: -1px;
|
left: 0px;
|
||||||
right: -1px;
|
right: 0px;
|
||||||
bottom: -1px;
|
bottom: -1px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -97,7 +98,7 @@
|
|||||||
border: 8px solid rgba(255,255,255,0.5);
|
border: 8px solid rgba(255,255,255,0.5);
|
||||||
|
|
||||||
&:after{
|
&:after{
|
||||||
border: 8px dotted rgba(40,140,215,1);
|
border: 8px dotted #000000;
|
||||||
top: -4px;
|
top: -4px;
|
||||||
left: -4px;
|
left: -4px;
|
||||||
right: -4px;
|
right: -4px;
|
||||||
@@ -332,16 +333,15 @@
|
|||||||
pointer-events:auto;
|
pointer-events:auto;
|
||||||
z-index: 2000;
|
z-index: 2000;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 30px !important;
|
|
||||||
height: 30px !important;
|
|
||||||
border-radius: 100%;
|
border-radius: 100%;
|
||||||
margin: -15px;
|
|
||||||
|
|
||||||
border: 1px solid rgba(0,0,0,0.25);
|
|
||||||
|
|
||||||
|
border: 1px solid black;
|
||||||
|
margin: -5px;
|
||||||
|
padding: 4px;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: rgba(255,255,255,0.5);
|
background-color: black;
|
||||||
cursor: move;
|
cursor: move;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -428,15 +428,8 @@
|
|||||||
border-style: solid;
|
border-style: solid;
|
||||||
border-width: 10px;
|
border-width: 10px;
|
||||||
border-color: transparent;
|
border-color: transparent;
|
||||||
-webkit-background-clip: padding-box;
|
background-clip: padding-box;
|
||||||
-moz-background-clip: padding-box;
|
transition: all .05s ease-in-out;
|
||||||
background-clip: padding-box;
|
|
||||||
|
|
||||||
-webkit-transition: all .05s ease-in-out;
|
|
||||||
-moz-transition: all .05s ease-in-out;
|
|
||||||
-ms-transition: all .05s ease-in-out;
|
|
||||||
-o-transition: all .05s ease-in-out;
|
|
||||||
transition: all .05s ease-in-out;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
div {
|
div {
|
||||||
|
|||||||
@@ -5,7 +5,6 @@
|
|||||||
.header-left,
|
.header-left,
|
||||||
.header-right {
|
.header-right {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
//@include transition( all 0.25s ease-in-out);
|
|
||||||
@include backface-visibility(hidden);
|
@include backface-visibility(hidden);
|
||||||
z-index: 3000;
|
z-index: 3000;
|
||||||
top: 10px;
|
top: 10px;
|
||||||
@@ -27,21 +26,21 @@
|
|||||||
.home {
|
.home {
|
||||||
margin-top: -20px;
|
margin-top: -20px;
|
||||||
margin-left: -20px;
|
margin-left: -20px;
|
||||||
// .icon {color: $dark; }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.header-left {
|
.header-left {
|
||||||
@include transform-origin(center left);
|
left: 0;
|
||||||
left: 0;
|
padding-left: 10px;
|
||||||
padding-left: 10px;
|
padding-left: 20px;
|
||||||
|
padding-top: 20px;
|
||||||
}
|
}
|
||||||
.header-right {
|
.header-right {
|
||||||
@include transform-origin(center right);
|
right: 0;
|
||||||
right: 0;
|
padding-right: 20px;
|
||||||
padding-right: 10px;
|
padding-top: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header-center {
|
.header-center {
|
||||||
@include transform-origin(center center);
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
@@ -56,7 +55,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.header-left > * { margin-right: 10px; }
|
.header-left > * { margin-right: 10px; }
|
||||||
.header-right > * { margin-left: 5px; }
|
.header-right > * { margin-left: 10px; }
|
||||||
.header-right { font-size: 0;}
|
.header-right { font-size: 0;}
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
@@ -90,21 +89,3 @@
|
|||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.present-mode #space-header {
|
|
||||||
background-color: transparent !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
#space-siblings {
|
|
||||||
background-color: rgba(245, 245, 245, 0.95);
|
|
||||||
padding: 35px;
|
|
||||||
max-height: 450px;
|
|
||||||
overflow-y: scroll;
|
|
||||||
margin-top: 54px;
|
|
||||||
border-bottom: 1px solid #eee;
|
|
||||||
|
|
||||||
.btn {
|
|
||||||
margin-bottom: 50px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|||||||
@@ -85,3 +85,12 @@
|
|||||||
transform: rotateZ(45deg) translateX(-8px);
|
transform: rotateZ(45deg) translateX(-8px);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.icon-svg {
|
||||||
|
background-size: 26px;
|
||||||
|
background-position: center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-sd6 {
|
||||||
|
background-image: url(/images/sd6-icon-white.svg);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,257 +1,52 @@
|
|||||||
@import "vars";
|
@import "vars";
|
||||||
|
|
||||||
#landing-header {
|
#landing-header {
|
||||||
background-color: rgba(255,255,255,0.3);
|
background-color: white;
|
||||||
height: 64px;
|
height: 64px;
|
||||||
position: absolute;
|
position: relative;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.landing-keyvisual-wrapper {
|
#landing {
|
||||||
background-image: url("../images/sd5-keyvisual-compressed.jpg");
|
margin-top: 100px;
|
||||||
background-size: cover;
|
|
||||||
background-position: center;
|
section {
|
||||||
padding-top: 40px;
|
margin-left: 300px;
|
||||||
padding-bottom: 40px;
|
|
||||||
}
|
> * {
|
||||||
|
max-width: 600px;
|
||||||
.landing-plans-wrapper {
|
}
|
||||||
background-image: url("../images/sd5-hero2-compressed.jpg");
|
|
||||||
background-size: cover;
|
|
||||||
background-position: center;
|
|
||||||
padding-top: 80px;
|
|
||||||
padding-bottom: 100px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.landing-box {
|
|
||||||
width: 800px;
|
|
||||||
margin: auto;
|
|
||||||
max-width: 90%;
|
|
||||||
background-color: white;
|
|
||||||
padding: 40px;
|
|
||||||
margin-bottom: 80px;
|
|
||||||
margin-top: 80px;
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
box-shadow: 0px 0px 50px rgba(0,0,0,0.2);
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.black {
|
|
||||||
background-color: #222;
|
|
||||||
color: white;
|
|
||||||
padding: 20px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.overlap {
|
|
||||||
position: absolute;
|
|
||||||
z-index: 2;
|
|
||||||
margin-top: -65px;
|
|
||||||
left: 50%;
|
|
||||||
top: 0px;
|
|
||||||
margin-left: -250px;
|
|
||||||
width: 500px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.screenshot {
|
|
||||||
width: 90%;
|
|
||||||
max-width: 90%;
|
|
||||||
padding: 20px;
|
|
||||||
box-shadow: none;
|
|
||||||
background-color: transparent;
|
|
||||||
|
|
||||||
img {
|
|
||||||
width: 100%;
|
|
||||||
position: absolute;
|
|
||||||
top: 0px;
|
|
||||||
left: 0px;
|
|
||||||
opacity: 0.3;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
&.landing-box-left {
|
|
||||||
margin-left: 30px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.lead-xxl {
|
|
||||||
}
|
|
||||||
|
|
||||||
.lead {
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lead-xl {
|
|
||||||
}
|
|
||||||
|
|
||||||
.plans-box {
|
|
||||||
background: linear-gradient(to bottom, #FEFFFF 25%,#D0D8E2 100%);
|
|
||||||
padding: 40px;
|
|
||||||
border-radius: 9px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.landing-box.plans-box {
|
|
||||||
margin-top: 200px;
|
|
||||||
width: 900px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.plans-table {
|
|
||||||
tr {
|
|
||||||
vertical-align: top;
|
|
||||||
}
|
|
||||||
|
|
||||||
th {
|
|
||||||
font-size: 42px;
|
|
||||||
padding-top: 40px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
th.best-plan {
|
|
||||||
padding-top: 20px;
|
|
||||||
font-size: 48px;
|
|
||||||
padding-bottom: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
td {
|
|
||||||
padding: 20px;
|
|
||||||
width: 30%;
|
|
||||||
p, li {
|
|
||||||
font-size: 18px;
|
|
||||||
}
|
|
||||||
li {
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
td.best-plan {
|
|
||||||
width: 40%;
|
|
||||||
p {
|
|
||||||
font-size: 22px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
td li {
|
|
||||||
list-style-type: none;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul {
|
|
||||||
margin: 0 !important;
|
|
||||||
padding: 0 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.upgrade-buttons {
|
|
||||||
text-align:center;
|
|
||||||
margin-top:20px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.logo-row {
|
|
||||||
position: relative;
|
|
||||||
padding: 80px;
|
|
||||||
background-color: white;
|
|
||||||
text-align: center;
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
&.blue {
|
|
||||||
background-color: $blue;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.logo-row div {
|
|
||||||
display: inline-block;
|
|
||||||
width: 200px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.landing-row {
|
|
||||||
background-color: white;
|
|
||||||
padding-bottom: 80px;
|
|
||||||
padding-top: 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#keyvisual {
|
|
||||||
border-radius: 20px;
|
|
||||||
box-shadow: 0px 0px 20px #eee;
|
|
||||||
|
|
||||||
width: 640px;
|
|
||||||
height: 420px;
|
|
||||||
background-size: contain;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-position: center;
|
|
||||||
background-image: url('/images/landing/spacedeck-screenshot1.jpg');
|
|
||||||
background-color: white;
|
|
||||||
margin: auto;
|
|
||||||
margin-top: 40px;
|
|
||||||
margin-bottom: 40px;
|
|
||||||
border: 1px solid #eee;
|
|
||||||
}
|
|
||||||
|
|
||||||
#legal {
|
|
||||||
.landing-box {
|
|
||||||
width: 800px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.footer {
|
.footer {
|
||||||
padding: 40px;
|
margin-left: 300px;
|
||||||
padding-bottom: 80px;
|
margin-top: 100px;
|
||||||
text-align: center;
|
margin-bottom: 100px;
|
||||||
color: $medium;
|
|
||||||
|
|
||||||
a {
|
|
||||||
margin-right: 20px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 1000px) {
|
||||||
|
#landing {
|
||||||
|
section {
|
||||||
|
margin-left: 20px;
|
||||||
|
margin-right: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.footer {
|
||||||
|
margin-left: 20px;
|
||||||
|
margin-right: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
@media screen and (min-width: 801px) {
|
.header-right {
|
||||||
.plans-table-mobile {
|
right: auto;
|
||||||
display: none;
|
padding-left: 10px;
|
||||||
}
|
padding-right: 20px;
|
||||||
}
|
padding-top: 80px;
|
||||||
|
}
|
||||||
@media screen and (max-width: 800px) {
|
|
||||||
|
#folder-wrapper {
|
||||||
ul.lead.lead-xl, p.lead.lead-xl, ol.lead.lead-xl {
|
padding-top: 128px;
|
||||||
font-size: 20px !important;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.header-right {
|
|
||||||
> span:first-child {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.plans-table {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.plans-table-mobile {
|
|
||||||
display: block;
|
|
||||||
|
|
||||||
tbody {
|
|
||||||
display: block;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
tr {
|
|
||||||
display: block;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
td, th {
|
|
||||||
display: block;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul, li {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
@import "mixins";
|
@import "mixins";
|
||||||
|
|
||||||
.wrapper {
|
.wrapper {
|
||||||
//@include transition( all 0.25s ease-in-out);
|
|
||||||
position: relative;
|
position: relative;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
max-width: 1160px;
|
max-width: 1160px;
|
||||||
|
|||||||
@@ -59,9 +59,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.close {
|
.close {
|
||||||
position: fixed;
|
margin-left: 44px;
|
||||||
margin: 44px 44px;
|
margin-bottom: 44px;
|
||||||
.icon {display: block; }
|
.icon {display: block; }
|
||||||
}
|
}
|
||||||
|
|
||||||
figure {
|
figure {
|
||||||
@@ -135,7 +135,6 @@
|
|||||||
outline: none;
|
outline: none;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
@include user-select(none);
|
|
||||||
|
|
||||||
border-radius: $radius*3;
|
border-radius: $radius*3;
|
||||||
background-color: $light !important;
|
background-color: $light !important;
|
||||||
@@ -146,7 +145,6 @@
|
|||||||
.modal-header {
|
.modal-header {
|
||||||
padding: 30px 40px;
|
padding: 30px 40px;
|
||||||
position: relative;
|
position: relative;
|
||||||
color: $medium;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.close-search {
|
.close-search {
|
||||||
@@ -279,25 +277,5 @@
|
|||||||
|
|
||||||
// Footer (for actions)
|
// Footer (for actions)
|
||||||
.modal-footer {
|
.modal-footer {
|
||||||
// border-bottom-left-radius: $radius;
|
margin-top: 20px;
|
||||||
// border-bottom-right-radius: $radius;
|
}
|
||||||
// background-color: $dark !important;
|
|
||||||
// padding: 40px;
|
|
||||||
// padding-top: 0px;
|
|
||||||
// text-align: right; // right align buttons
|
|
||||||
@include clearfix(); // clear it in case folks use .pull-* classes on buttons
|
|
||||||
|
|
||||||
// Properly space out buttons
|
|
||||||
// .btn + .btn {
|
|
||||||
// margin-left: 5px;
|
|
||||||
// margin-bottom: 0; // account for input[type="submit"] which gets the bottom margin like all other inputs
|
|
||||||
// }
|
|
||||||
// // but override that for button groups
|
|
||||||
// .btn-group .btn + .btn {
|
|
||||||
// margin-left: -1px;
|
|
||||||
// }
|
|
||||||
// // and override it for block buttons as well
|
|
||||||
// .btn-block + .btn-block {
|
|
||||||
// margin-left: 0;
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|||||||
14
styles/normalize.scss
vendored
14
styles/normalize.scss
vendored
@@ -1,17 +1,5 @@
|
|||||||
/*! normalize.css v3.0.0 | MIT License | git.io/normalize */
|
/*! normalize.css v3.0.0 | MIT License | git.io/normalize */
|
||||||
|
|
||||||
//
|
|
||||||
// 1. Set default font family to sans-serif.
|
|
||||||
// 2. Prevent iOS text size adjust after orientation change, without disabling
|
|
||||||
// user zoom.
|
|
||||||
//
|
|
||||||
|
|
||||||
html {
|
|
||||||
font-family: sans-serif; // 1
|
|
||||||
-ms-text-size-adjust: 100%; // 2
|
|
||||||
-webkit-text-size-adjust: 100%; // 2
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// Remove default margin.
|
// Remove default margin.
|
||||||
//
|
//
|
||||||
@@ -420,4 +408,4 @@ table {
|
|||||||
td,
|
td,
|
||||||
th {
|
th {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,5 @@
|
|||||||
right: 0;
|
right: 0;
|
||||||
z-index: 800;
|
z-index: 800;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
opacity: 0.25;
|
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,22 +6,18 @@
|
|||||||
li {
|
li {
|
||||||
&.checked {
|
&.checked {
|
||||||
|
|
||||||
&:before {background-color: $medium !important; }
|
|
||||||
> a,
|
> a,
|
||||||
> span {
|
> span {
|
||||||
color: $medium;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
&:before {background-color: $medium; }
|
|
||||||
> a,
|
> a,
|
||||||
> span {
|
> span {
|
||||||
background-color: rgba(0,0,0,0.025) !important;
|
background-color: rgba(0,0,0,0.025) !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&:before {background-color: $medium; }
|
|
||||||
> a,
|
> a,
|
||||||
> span {
|
> span {
|
||||||
color: $medium;
|
color: $medium;
|
||||||
@@ -30,7 +26,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.select-list {
|
.select-list {
|
||||||
&:empty:before{
|
&:empty:before {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
@@ -45,17 +41,14 @@
|
|||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
-webkit-mask-image: -webkit-gradient(linear, left top, left 15px, from(rgba(0,0,0,0)), to(rgba(0,0,0,0.5)));
|
|
||||||
|
|
||||||
background-clip: padding-box;
|
background-clip: padding-box;
|
||||||
font-size: 15px;
|
//font-size: 15px;
|
||||||
line-height: 14px;
|
//line-height: 14px;
|
||||||
list-style: none;
|
list-style: none;
|
||||||
margin: 0px;
|
margin: 0px;
|
||||||
padding: 15px 0;
|
padding: 15px 0;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
// background-color: $dark;
|
// background-color: $dark;
|
||||||
color: $medium;
|
|
||||||
border-radius: $radius;
|
border-radius: $radius;
|
||||||
|
|
||||||
.divider + li span {border: none !important; }
|
.divider + li span {border: none !important; }
|
||||||
@@ -90,15 +83,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
// background-color: rgba(0,0,0,0.025);
|
background-color: black;
|
||||||
&:before {
|
|
||||||
background-color: $medium;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
> a,
|
> a,
|
||||||
> span {
|
> span {
|
||||||
color: $medium;
|
color: white;
|
||||||
color: $dark;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,9 +115,8 @@
|
|||||||
display: block;
|
display: block;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
color: $medium;
|
|
||||||
margin: 0 25px;
|
margin: 0 25px;
|
||||||
padding: 16px 3px;
|
padding: 10px 0px;
|
||||||
// line-height: 50px;
|
// line-height: 50px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
@@ -154,4 +142,4 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -118,12 +118,9 @@
|
|||||||
padding: 0 !important;
|
padding: 0 !important;
|
||||||
|
|
||||||
.wrapper {
|
.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-repeat: no-repeat;
|
||||||
background-size: cover;
|
background-size: initial;
|
||||||
|
background-position: 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -132,32 +129,27 @@
|
|||||||
max-height: 100%;
|
max-height: 100%;
|
||||||
|
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
overflow: scroll;
|
overflow: scroll;
|
||||||
|
|
||||||
/** {
|
|
||||||
-moz-user-select: none !important; // firefox has selection problems
|
|
||||||
}*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.snap-ruler-h {
|
.snap-ruler-h {
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
z-index: 0;
|
z-index: 2000;
|
||||||
right: 0px;
|
right: 0px;
|
||||||
height: 1px;
|
height: 1px;
|
||||||
background-color: rgba(0,0,0,0.5);
|
background-color: black;
|
||||||
left: 0px;
|
left: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.snap-ruler-v {
|
.snap-ruler-v {
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
z-index: 0;
|
z-index: 2000;
|
||||||
top: 0px;
|
top: 0px;
|
||||||
bottom: 0px;
|
bottom: 0px;
|
||||||
width: 1px;
|
width: 1px;
|
||||||
background-color: rgba(0,0,0,0.5);
|
background-color: black;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cursor {
|
.cursor {
|
||||||
@@ -227,30 +219,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
#space {
|
#space {
|
||||||
/*-webkit-user-select: all;
|
// user-select: all;
|
||||||
-ms-user-select: all;
|
|
||||||
-moz-user-select: all;
|
|
||||||
user-select: all;*/
|
|
||||||
position: relative;
|
position: relative;
|
||||||
height: 100% !important;
|
height: 100% !important;
|
||||||
|
|
||||||
//padding-top: 64px !important;
|
|
||||||
background-color: #eee;
|
background-color: #eee;
|
||||||
}
|
}
|
||||||
|
|
||||||
#made-with {
|
|
||||||
position: fixed;
|
|
||||||
width: 100%;
|
|
||||||
bottom: 0;
|
|
||||||
padding: 12px;
|
|
||||||
opacity: 0.25;
|
|
||||||
a {color: $dark; }
|
|
||||||
p {
|
|
||||||
text-align: center;
|
|
||||||
font-size: 11px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#baseline {
|
#baseline {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -298,8 +272,8 @@
|
|||||||
|
|
||||||
.space-bounds {
|
.space-bounds {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 0px;
|
left: 0;
|
||||||
top: 0px;
|
top: 0;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
|
|||||||
@@ -65,10 +65,15 @@
|
|||||||
html,
|
html,
|
||||||
body {
|
body {
|
||||||
height:100%;
|
height:100%;
|
||||||
-webkit-tap-highlight-color: transparent;
|
|
||||||
background-color: white;
|
background-color: white;
|
||||||
background-color: $light;
|
color: $black;
|
||||||
color: $darker;
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
max-width: 100%;
|
||||||
|
padding: 0px;
|
||||||
|
text-rendering: optimizeLegibility;
|
||||||
|
cursor: default;
|
||||||
}
|
}
|
||||||
|
|
||||||
*[contenteditable="true"] {
|
*[contenteditable="true"] {
|
||||||
@@ -81,70 +86,12 @@ body {
|
|||||||
@include box-sizing(border-box);
|
@include box-sizing(border-box);
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
|
||||||
max-width: 100%;
|
|
||||||
padding: 0px;
|
|
||||||
text-rendering: optimizeLegibility;
|
|
||||||
//@include user-select(none);
|
|
||||||
cursor: default;
|
|
||||||
}
|
|
||||||
|
|
||||||
.img img {
|
.img img {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
height: auto;
|
height: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.plan {
|
/*.layer {
|
||||||
color: $medium;
|
|
||||||
border-radius: $radius;
|
|
||||||
display: inline-block;
|
|
||||||
padding: 30px;
|
|
||||||
|
|
||||||
background-color: transparent;
|
|
||||||
border: 2px solid rgba(0,0,0,0.05);
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
&.active {
|
|
||||||
background-color: white;
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
h4 {
|
|
||||||
color: black;
|
|
||||||
margin-bottom: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
font-size: 13px;
|
|
||||||
line-height: 1.4;
|
|
||||||
margin-top: 5px;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul {
|
|
||||||
list-style: none;
|
|
||||||
font-size: 10px;
|
|
||||||
margin: 0px;
|
|
||||||
padding: 0px;
|
|
||||||
border-top: 2px solid rgba(0,0,0,0.05);
|
|
||||||
padding-top: 20px;
|
|
||||||
margin-top: 20px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
li {padding-top: 2px; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#startup {
|
|
||||||
background-position: center;
|
|
||||||
background-image:url(/images/diamond.svg);
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
}
|
|
||||||
|
|
||||||
#home {
|
|
||||||
background-color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.layer {
|
|
||||||
@include transition( all 0.2s ease-in-out);
|
@include transition( all 0.2s ease-in-out);
|
||||||
@include backface-visibility(hidden);
|
@include backface-visibility(hidden);
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@@ -172,7 +119,7 @@ body {
|
|||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
}
|
}*/
|
||||||
|
|
||||||
[draggable] {
|
[draggable] {
|
||||||
-moz-user-select: none;
|
-moz-user-select: none;
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user